aae12f08f36285c0e7a392e9c77ce39fd1b73d10
[WebKit-https.git] / WebCore / platform / text / mac / TextCodecMac.cpp
1 /*
2  * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "TextCodecMac.h"
29
30 #include "CString.h"
31 #include "CharacterNames.h"
32 #include "CharsetData.h"
33 #include "PlatformString.h"
34 #include <wtf/Assertions.h>
35
36 using std::auto_ptr;
37 using std::min;
38
39 namespace WebCore {
40
41 // We need to keep this because ICU doesn't support some of the encodings that we need:
42 // <http://bugs.webkit.org/show_bug.cgi?id=4195>.
43
44 const size_t ConversionBufferSize = 16384;
45
46 static TECObjectRef cachedConverterTEC;
47 static TECTextEncodingID cachedConverterEncoding = invalidEncoding;
48
49 void TextCodecMac::registerEncodingNames(EncodingNameRegistrar registrar)
50 {
51     TECTextEncodingID lastEncoding = invalidEncoding;
52     const char* lastName = 0;
53
54     for (size_t i = 0; CharsetTable[i].name; ++i) {
55         if (CharsetTable[i].encoding != lastEncoding) {
56             lastEncoding = CharsetTable[i].encoding;
57             lastName = CharsetTable[i].name;
58         }
59         registrar(CharsetTable[i].name, lastName);
60     }
61 }
62
63 static auto_ptr<TextCodec> newTextCodecMac(const TextEncoding&, const void* additionalData)
64 {
65     return auto_ptr<TextCodec>(new TextCodecMac(*static_cast<const TECTextEncodingID*>(additionalData)));
66 }
67
68 void TextCodecMac::registerCodecs(TextCodecRegistrar registrar)
69 {
70     TECTextEncodingID lastEncoding = invalidEncoding;
71
72     for (size_t i = 0; CharsetTable[i].name; ++i)
73         if (CharsetTable[i].encoding != lastEncoding) {
74             registrar(CharsetTable[i].name, newTextCodecMac, &CharsetTable[i].encoding);
75             lastEncoding = CharsetTable[i].encoding;
76         }
77 }
78
79 TextCodecMac::TextCodecMac(TECTextEncodingID encoding)
80     : m_encoding(encoding)
81     , m_numBufferedBytes(0)
82     , m_converterTEC(0)
83 {
84 }
85
86 TextCodecMac::~TextCodecMac()
87 {
88     releaseTECConverter();
89 }
90
91 void TextCodecMac::releaseTECConverter() const
92 {
93     if (m_converterTEC) {
94         if (cachedConverterTEC != 0)
95             TECDisposeConverter(cachedConverterTEC);
96         cachedConverterTEC = m_converterTEC;
97         cachedConverterEncoding = m_encoding;
98         m_converterTEC = 0;
99     }
100 }
101
102 OSStatus TextCodecMac::createTECConverter() const
103 {
104     bool cachedEncodingEqual = cachedConverterEncoding == m_encoding;
105     cachedConverterEncoding = invalidEncoding;
106
107     if (cachedEncodingEqual && cachedConverterTEC) {
108         m_converterTEC = cachedConverterTEC;
109         cachedConverterTEC = 0;
110         TECClearConverterContextInfo(m_converterTEC);
111     } else {
112         OSStatus status = TECCreateConverter(&m_converterTEC, m_encoding,
113             CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicode16BitFormat));
114         if (status)
115             return status;
116
117         TECSetBasicOptions(m_converterTEC, kUnicodeForceASCIIRangeMask);
118     }
119     
120     return noErr;
121 }
122
123 OSStatus TextCodecMac::decode(const unsigned char* inputBuffer, int inputBufferLength, int& inputLength,
124     void *outputBuffer, int outputBufferLength, int& outputLength)
125 {
126     OSStatus status;
127     unsigned long bytesRead = 0;
128     unsigned long bytesWritten = 0;
129
130     if (m_numBufferedBytes != 0) {
131         // Finish converting a partial character that's in our buffer.
132         
133         // First, fill the partial character buffer with as many bytes as are available.
134         ASSERT(m_numBufferedBytes < sizeof(m_bufferedBytes));
135         const int spaceInBuffer = sizeof(m_bufferedBytes) - m_numBufferedBytes;
136         const int bytesToPutInBuffer = MIN(spaceInBuffer, inputBufferLength);
137         ASSERT(bytesToPutInBuffer != 0);
138         memcpy(m_bufferedBytes + m_numBufferedBytes, inputBuffer, bytesToPutInBuffer);
139
140         // Now, do a conversion on the buffer.
141         status = TECConvertText(m_converterTEC, m_bufferedBytes, m_numBufferedBytes + bytesToPutInBuffer, &bytesRead,
142             reinterpret_cast<unsigned char*>(outputBuffer), outputBufferLength, &bytesWritten);
143         ASSERT(bytesRead <= m_numBufferedBytes + bytesToPutInBuffer);
144
145         if (status == kTECPartialCharErr && bytesRead == 0) {
146             // Handle the case where the partial character was not converted.
147             if (bytesToPutInBuffer >= spaceInBuffer) {
148                 LOG_ERROR("TECConvertText gave a kTECPartialCharErr but read none of the %zu bytes in the buffer", sizeof(m_bufferedBytes));
149                 m_numBufferedBytes = 0;
150                 status = kTECUnmappableElementErr; // should never happen, but use this error code
151             } else {
152                 // Tell the caller we read all the source bytes and keep them in the buffer.
153                 m_numBufferedBytes += bytesToPutInBuffer;
154                 bytesRead = bytesToPutInBuffer;
155                 status = noErr;
156             }
157         } else {
158             // We are done with the partial character buffer.
159             // Also, we have read some of the bytes from the main buffer.
160             if (bytesRead > m_numBufferedBytes) {
161                 bytesRead -= m_numBufferedBytes;
162             } else {
163                 LOG_ERROR("TECConvertText accepted some bytes it previously rejected with kTECPartialCharErr");
164                 bytesRead = 0;
165             }
166             m_numBufferedBytes = 0;
167             if (status == kTECPartialCharErr) {
168                 // While there may be a partial character problem in the small buffer,
169                 // we have to try again and not get confused and think there is a partial
170                 // character problem in the large buffer.
171                 status = noErr;
172             }
173         }
174     } else {
175         status = TECConvertText(m_converterTEC, inputBuffer, inputBufferLength, &bytesRead,
176             static_cast<unsigned char*>(outputBuffer), outputBufferLength, &bytesWritten);
177         ASSERT(static_cast<int>(bytesRead) <= inputBufferLength);
178     }
179
180     // Work around bug 3351093, where sometimes we get kTECBufferBelowMinimumSizeErr instead of kTECOutputBufferFullStatus.
181     if (status == kTECBufferBelowMinimumSizeErr && bytesWritten != 0)
182         status = kTECOutputBufferFullStatus;
183
184     inputLength = bytesRead;
185     outputLength = bytesWritten;
186     return status;
187 }
188
189 String TextCodecMac::decode(const char* bytes, size_t length, bool flush, bool stopOnError, bool& sawError)
190 {
191     // Get a converter for the passed-in encoding.
192     if (!m_converterTEC && createTECConverter() != noErr)
193         return String();
194     
195     Vector<UChar> result;
196
197     const unsigned char* sourcePointer = reinterpret_cast<const unsigned char*>(bytes);
198     int sourceLength = length;
199     bool bufferWasFull = false;
200     UniChar buffer[ConversionBufferSize];
201
202     while ((sourceLength || bufferWasFull) && !sawError) {
203         int bytesRead = 0;
204         int bytesWritten = 0;
205         OSStatus status = decode(sourcePointer, sourceLength, bytesRead, buffer, sizeof(buffer), bytesWritten);
206         ASSERT(bytesRead <= sourceLength);
207         sourcePointer += bytesRead;
208         sourceLength -= bytesRead;
209         
210         switch (status) {
211             case noErr:
212             case kTECOutputBufferFullStatus:
213                 break;
214             case kTextMalformedInputErr:
215             case kTextUndefinedElementErr:
216                 // FIXME: Put FFFD character into the output string in this case?
217                 TECClearConverterContextInfo(m_converterTEC);
218                 if (stopOnError) {
219                     sawError = true;
220                     break;
221                 }
222                 if (sourceLength) {
223                     sourcePointer += 1;
224                     sourceLength -= 1;
225                 }
226                 break;
227             case kTECPartialCharErr: {
228                 // Put the partial character into the buffer.
229                 ASSERT(m_numBufferedBytes == 0);
230                 const int bufferSize = sizeof(m_numBufferedBytes);
231                 if (sourceLength < bufferSize) {
232                     memcpy(m_bufferedBytes, sourcePointer, sourceLength);
233                     m_numBufferedBytes = sourceLength;
234                 } else {
235                     LOG_ERROR("TECConvertText gave a kTECPartialCharErr, but left %u bytes in the buffer", sourceLength);
236                 }
237                 sourceLength = 0;
238                 break;
239             }
240             default:
241                 sawError = true;
242                 return String();
243         }
244
245         ASSERT(!(bytesWritten % sizeof(UChar)));
246         appendOmittingBOM(result, buffer, bytesWritten / sizeof(UChar));
247
248         bufferWasFull = status == kTECOutputBufferFullStatus;
249     }
250     
251     if (flush) {
252         unsigned long bytesWritten = 0;
253         TECFlushText(m_converterTEC, reinterpret_cast<unsigned char*>(buffer), sizeof(buffer), &bytesWritten);
254         ASSERT(!(bytesWritten % sizeof(UChar)));
255         appendOmittingBOM(result, buffer, bytesWritten / sizeof(UChar));
256     }
257
258     String resultString = String::adopt(result);
259
260     // <rdar://problem/3225472>
261     // Simplified Chinese pages use the code A3A0 to mean "full-width space".
262     // But GB18030 decodes it to U+E5E5, which is correct in theory but not in practice.
263     // To work around, just change all occurences of U+E5E5 to U+3000 (ideographic space).
264     if (m_encoding == kCFStringEncodingGB_18030_2000)
265         resultString.replace(0xE5E5, ideographicSpace);
266     
267     return resultString;
268 }
269
270 CString TextCodecMac::encode(const UChar* characters, size_t length, UnencodableHandling handling)
271 {
272     // FIXME: We should really use TEC here instead of CFString for consistency with the other direction.
273
274     // FIXME: Since there's no "force ASCII range" mode in CFString, we change the backslash into a yen sign.
275     // Encoding will change the yen sign back into a backslash.
276     String copy(characters, length);
277     copy.replace('\\', m_backslashAsCurrencySymbol);
278     CFStringRef cfs = copy.createCFString();
279
280     CFIndex startPos = 0;
281     CFIndex charactersLeft = CFStringGetLength(cfs);
282     Vector<char> result;
283     size_t size = 0;
284     UInt8 lossByte = handling == QuestionMarksForUnencodables ? '?' : 0;
285     while (charactersLeft > 0) {
286         CFRange range = CFRangeMake(startPos, charactersLeft);
287         CFIndex bufferLength;
288         CFStringGetBytes(cfs, range, m_encoding, lossByte, false, NULL, 0x7FFFFFFF, &bufferLength);
289
290         result.grow(size + bufferLength);
291         unsigned char* buffer = reinterpret_cast<unsigned char*>(result.data() + size);
292         CFIndex charactersConverted = CFStringGetBytes(cfs, range, m_encoding, lossByte, false, buffer, bufferLength, &bufferLength);
293         size += bufferLength;
294
295         if (charactersConverted != charactersLeft) {
296             unsigned badChar = CFStringGetCharacterAtIndex(cfs, startPos + charactersConverted);
297             ++charactersConverted;
298             if ((badChar & 0xFC00) == 0xD800 && charactersConverted != charactersLeft) { // is high surrogate
299                 UniChar low = CFStringGetCharacterAtIndex(cfs, startPos + charactersConverted);
300                 if ((low & 0xFC00) == 0xDC00) { // is low surrogate
301                     badChar <<= 10;
302                     badChar += low;
303                     badChar += 0x10000 - (0xD800 << 10) - 0xDC00;
304                     ++charactersConverted;
305                 }
306             }
307             UnencodableReplacementArray entity;
308             int entityLength = getUnencodableReplacement(badChar, handling, entity);
309             result.grow(size + entityLength);
310             memcpy(result.data() + size, entity, entityLength);
311             size += entityLength;
312         }
313
314         startPos += charactersConverted;
315         charactersLeft -= charactersConverted;
316     }
317     CFRelease(cfs);
318     return CString(result.data(), size);
319 }
320
321 } // namespace WebCore