Fix 64-bit debug build.
[WebKit-https.git] / WebCore / platform / text / mac / TextCodecMac.cpp
1 /*
2  * Copyright (C) 2004, 2006 Apple Computer, 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_error(false)
82     , m_numBufferedBytes(0)
83     , m_converterTEC(0)
84 {
85 }
86
87 TextCodecMac::~TextCodecMac()
88 {
89     releaseTECConverter();
90 }
91
92 void TextCodecMac::releaseTECConverter() const
93 {
94     if (m_converterTEC) {
95         if (cachedConverterTEC != 0)
96             TECDisposeConverter(cachedConverterTEC);
97         cachedConverterTEC = m_converterTEC;
98         cachedConverterEncoding = m_encoding;
99         m_converterTEC = 0;
100     }
101 }
102
103 OSStatus TextCodecMac::createTECConverter() const
104 {
105     bool cachedEncodingEqual = cachedConverterEncoding == m_encoding;
106     cachedConverterEncoding = invalidEncoding;
107
108     if (cachedEncodingEqual && cachedConverterTEC) {
109         m_converterTEC = cachedConverterTEC;
110         cachedConverterTEC = 0;
111         TECClearConverterContextInfo(m_converterTEC);
112     } else {
113         OSStatus status = TECCreateConverter(&m_converterTEC, m_encoding,
114             CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicode16BitFormat));
115         if (status)
116             return status;
117
118         TECSetBasicOptions(m_converterTEC, kUnicodeForceASCIIRangeMask);
119     }
120     
121     return noErr;
122 }
123
124 OSStatus TextCodecMac::decode(const unsigned char* inputBuffer, int inputBufferLength, int& inputLength,
125     void *outputBuffer, int outputBufferLength, int& outputLength)
126 {
127     OSStatus status;
128     unsigned long bytesRead = 0;
129     unsigned long bytesWritten = 0;
130
131     if (m_numBufferedBytes != 0) {
132         // Finish converting a partial character that's in our buffer.
133         
134         // First, fill the partial character buffer with as many bytes as are available.
135         ASSERT(m_numBufferedBytes < sizeof(m_bufferedBytes));
136         const int spaceInBuffer = sizeof(m_bufferedBytes) - m_numBufferedBytes;
137         const int bytesToPutInBuffer = MIN(spaceInBuffer, inputBufferLength);
138         ASSERT(bytesToPutInBuffer != 0);
139         memcpy(m_bufferedBytes + m_numBufferedBytes, inputBuffer, bytesToPutInBuffer);
140
141         // Now, do a conversion on the buffer.
142         status = TECConvertText(m_converterTEC, m_bufferedBytes, m_numBufferedBytes + bytesToPutInBuffer, &bytesRead,
143             reinterpret_cast<unsigned char*>(outputBuffer), outputBufferLength, &bytesWritten);
144         ASSERT(bytesRead <= m_numBufferedBytes + bytesToPutInBuffer);
145
146         if (status == kTECPartialCharErr && bytesRead == 0) {
147             // Handle the case where the partial character was not converted.
148             if (bytesToPutInBuffer >= spaceInBuffer) {
149                 LOG_ERROR("TECConvertText gave a kTECPartialCharErr but read none of the %zu bytes in the buffer", sizeof(m_bufferedBytes));
150                 m_numBufferedBytes = 0;
151                 status = kTECUnmappableElementErr; // should never happen, but use this error code
152             } else {
153                 // Tell the caller we read all the source bytes and keep them in the buffer.
154                 m_numBufferedBytes += bytesToPutInBuffer;
155                 bytesRead = bytesToPutInBuffer;
156                 status = noErr;
157             }
158         } else {
159             // We are done with the partial character buffer.
160             // Also, we have read some of the bytes from the main buffer.
161             if (bytesRead > m_numBufferedBytes) {
162                 bytesRead -= m_numBufferedBytes;
163             } else {
164                 LOG_ERROR("TECConvertText accepted some bytes it previously rejected with kTECPartialCharErr");
165                 bytesRead = 0;
166             }
167             m_numBufferedBytes = 0;
168             if (status == kTECPartialCharErr) {
169                 // While there may be a partial character problem in the small buffer,
170                 // we have to try again and not get confused and think there is a partial
171                 // character problem in the large buffer.
172                 status = noErr;
173             }
174         }
175     } else {
176         status = TECConvertText(m_converterTEC, inputBuffer, inputBufferLength, &bytesRead,
177             static_cast<unsigned char*>(outputBuffer), outputBufferLength, &bytesWritten);
178         ASSERT(static_cast<int>(bytesRead) <= inputBufferLength);
179     }
180
181     // Work around bug 3351093, where sometimes we get kTECBufferBelowMinimumSizeErr instead of kTECOutputBufferFullStatus.
182     if (status == kTECBufferBelowMinimumSizeErr && bytesWritten != 0) {
183         status = kTECOutputBufferFullStatus;
184     }
185
186     inputLength = bytesRead;
187     outputLength = bytesWritten;
188     return status;
189 }
190
191 String TextCodecMac::decode(const char* bytes, size_t length, bool flush)
192 {
193     // Get a converter for the passed-in encoding.
194     if (!m_converterTEC && createTECConverter() != noErr)
195         return String();
196     
197     Vector<UChar> result;
198
199     const unsigned char* sourcePointer = reinterpret_cast<const unsigned char*>(bytes);
200     int sourceLength = length;
201     bool bufferWasFull = false;
202     UniChar buffer[ConversionBufferSize];
203
204     while (sourceLength || bufferWasFull) {
205         int bytesRead = 0;
206         int bytesWritten = 0;
207         OSStatus status = decode(sourcePointer, sourceLength, bytesRead, buffer, sizeof(buffer), bytesWritten);
208         ASSERT(bytesRead <= sourceLength);
209         sourcePointer += bytesRead;
210         sourceLength -= bytesRead;
211         
212         switch (status) {
213             case noErr:
214             case kTECOutputBufferFullStatus:
215                 break;
216             case kTextMalformedInputErr:
217             case kTextUndefinedElementErr:
218                 // FIXME: Put FFFD character into the output string in this case?
219                 TECClearConverterContextInfo(m_converterTEC);
220                 if (sourceLength) {
221                     sourcePointer += 1;
222                     sourceLength -= 1;
223                 }
224                 break;
225             case kTECPartialCharErr: {
226                 // Put the partial character into the buffer.
227                 ASSERT(m_numBufferedBytes == 0);
228                 const int bufferSize = sizeof(m_numBufferedBytes);
229                 if (sourceLength < bufferSize) {
230                     memcpy(m_bufferedBytes, sourcePointer, sourceLength);
231                     m_numBufferedBytes = sourceLength;
232                 } else {
233                     LOG_ERROR("TECConvertText gave a kTECPartialCharErr, but left %u bytes in the buffer", sourceLength);
234                 }
235                 sourceLength = 0;
236                 break;
237             }
238             default:
239                 LOG_ERROR("text decoding failed with error %ld", static_cast<long>(status));
240                 m_error = true;
241                 return String();
242         }
243
244         ASSERT(!(bytesWritten % sizeof(UChar)));
245         appendOmittingBOM(result, buffer, bytesWritten / sizeof(UChar));
246
247         bufferWasFull = status == kTECOutputBufferFullStatus;
248     }
249     
250     if (flush) {
251         unsigned long bytesWritten = 0;
252         TECFlushText(m_converterTEC, reinterpret_cast<unsigned char*>(buffer), sizeof(buffer), &bytesWritten);
253         ASSERT(!(bytesWritten % sizeof(UChar)));
254         appendOmittingBOM(result, buffer, bytesWritten / sizeof(UChar));
255     }
256
257     String resultString = String::adopt(result);
258
259     // Workaround for a bug in the Text Encoding Converter (see bug 3225472).
260     // Simplified Chinese pages use the code U+A3A0 to mean "full-width space".
261     // But GB18030 decodes it to U+E5E5, which is correct in theory but not in practice.
262     // To work around, just change all occurences of U+E5E5 to U+3000 (ideographic space).
263     if (m_encoding == kCFStringEncodingGB_18030_2000)
264         resultString.replace(0xE5E5, ideographicSpace);
265     
266     return resultString;
267 }
268
269 CString TextCodecMac::encode(const UChar* characters, size_t length, bool allowEntities)
270 {
271     // FIXME: We should really use TEC here instead of CFString for consistency with the other direction.
272
273     // FIXME: Since there's no "force ASCII range" mode in CFString, we change the backslash into a yen sign.
274     // Encoding will change the yen sign back into a backslash.
275     String copy(characters, length);
276     copy.replace('\\', m_backslashAsCurrencySymbol);
277     CFStringRef cfs = copy.createCFString();
278
279     CFIndex startPos = 0;
280     CFIndex charactersLeft = CFStringGetLength(cfs);
281     Vector<char> result;
282     size_t size = 0;
283     UInt8 lossByte = allowEntities ? 0 : '?';
284     while (charactersLeft > 0) {
285         CFRange range = CFRangeMake(startPos, charactersLeft);
286         CFIndex bufferLength;
287         CFStringGetBytes(cfs, range, m_encoding, lossByte, false, NULL, 0x7FFFFFFF, &bufferLength);
288
289         result.resize(size + bufferLength);
290         unsigned char* buffer = reinterpret_cast<unsigned char*>(result.data() + size);
291         CFIndex charactersConverted = CFStringGetBytes(cfs, range, m_encoding, lossByte, false, buffer, bufferLength, &bufferLength);
292         size += bufferLength;
293
294         if (charactersConverted != charactersLeft) {
295             unsigned badChar = CFStringGetCharacterAtIndex(cfs, startPos + charactersConverted);
296             ++charactersConverted;
297             if ((badChar & 0xFC00) == 0xD800 && charactersConverted != charactersLeft) { // is high surrogate
298                 UniChar low = CFStringGetCharacterAtIndex(cfs, startPos + charactersConverted);
299                 if ((low & 0xFC00) == 0xDC00) { // is low surrogate
300                     badChar <<= 10;
301                     badChar += low;
302                     badChar += 0x10000 - (0xD800 << 10) - 0xDC00;
303                     ++charactersConverted;
304                 }
305             }
306             char entityBuffer[16];
307             sprintf(entityBuffer, "&#%u;", badChar);
308             size_t entityLength = strlen(entityBuffer);
309             result.resize(size + entityLength);
310             memcpy(result.data() + size, entityBuffer, 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