Reviewed by Maciej.
[WebKit-https.git] / WebCore / kwq / KWQTextCodec.mm
1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "KWQTextCodec.h"
27
28 #import "KWQAssertions.h"
29 #import "KWQCharsets.h"
30
31 const UniChar BOM = 0xFEFF;
32
33 class KWQTextDecoder : public QTextDecoder {
34 public:
35     KWQTextDecoder(CFStringEncoding, KWQEncodingFlags);
36     ~KWQTextDecoder();
37     
38     QString toUnicode(const char *chs, int len, bool flush);
39
40 private:
41     QString convert(const char *chs, int len, bool flush)
42         { return convert(reinterpret_cast<const unsigned char *>(chs), len, flush); }
43     QString convert(const unsigned char *chs, int len, bool flush);
44     QString convertLatin1(const unsigned char *chs, int len);
45     QString convertUTF16(const unsigned char *chs, int len);
46     QString convertUsingTEC(const unsigned char *chs, int len, bool flush);
47     
48     OSStatus createTECConverter();
49     OSStatus convertOneChunkUsingTEC(const unsigned char *inputBuffer, int inputBufferLength, int &inputLength,
50         void *outputBuffer, int outputBufferLength, int &outputLength);
51     static void appendOmittingNullsAndBOMs(QString &s, const UniChar *characters, int byteCount);
52     
53     KWQTextDecoder(const KWQTextDecoder &);
54     KWQTextDecoder &operator=(const KWQTextDecoder &);
55
56     CFStringEncoding _encoding;
57     bool _littleEndian;
58     bool _atStart;
59     bool _error;
60
61     unsigned _numBufferedBytes;
62     unsigned char _bufferedBytes[16]; // bigger than any single multi-byte character
63
64     // State for TEC decoding.
65     TECObjectRef _converter;
66     static TECObjectRef _cachedConverter;
67     static CFStringEncoding _cachedConverterEncoding;
68 };
69
70 TECObjectRef KWQTextDecoder::_cachedConverter;
71 CFStringEncoding KWQTextDecoder::_cachedConverterEncoding = kCFStringEncodingInvalidId;
72
73 static Boolean QTextCodecsEqual(const void *value1, const void *value2);
74 static CFHashCode QTextCodecHash(const void *value);
75
76 static QTextCodec *codecForCFStringEncoding(CFStringEncoding encoding, KWQEncodingFlags flags)
77 {
78     if (encoding == kCFStringEncodingInvalidId) {
79         return 0;
80     }
81     
82     static const CFDictionaryKeyCallBacks QTextCodecKeyCallbacks = { 0, NULL, NULL, NULL, QTextCodecsEqual, QTextCodecHash };
83     static CFMutableDictionaryRef encodingToCodec = CFDictionaryCreateMutable(NULL, 0, &QTextCodecKeyCallbacks, NULL);
84     
85     QTextCodec key(encoding, flags);
86     const void *value;
87     if (CFDictionaryGetValueIfPresent(encodingToCodec, &key, &value)) {
88         return const_cast<QTextCodec *>(static_cast<const QTextCodec *>(value));
89     }
90     QTextCodec *codec = new QTextCodec(encoding, flags);
91     CFDictionarySetValue(encodingToCodec, codec, codec);
92     return codec;
93 }
94
95 QTextCodec *QTextCodec::codecForName(const char *name)
96 {
97     KWQEncodingFlags flags;
98     CFStringEncoding encoding = KWQCFStringEncodingFromIANACharsetName(name, &flags);
99     return codecForCFStringEncoding(encoding, flags);
100 }
101
102 QTextCodec *QTextCodec::codecForNameEightBitOnly(const char *name)
103 {
104     KWQEncodingFlags flags;
105     CFStringEncoding encoding = KWQCFStringEncodingFromIANACharsetName(name, &flags);
106     switch (encoding) {
107         case kCFStringEncodingUnicode:
108             encoding = kCFStringEncodingUTF8;
109             break;
110     }
111     return codecForCFStringEncoding(encoding, flags);
112 }
113
114 QTextCodec *QTextCodec::codecForLocale()
115 {
116     return codecForCFStringEncoding(CFStringGetSystemEncoding(), NoEncodingFlags);
117 }
118
119 const char *QTextCodec::name() const
120 {
121     return KWQCFStringEncodingToIANACharsetName(_encoding);
122 }
123
124 QTextDecoder *QTextCodec::makeDecoder() const
125 {
126     return new KWQTextDecoder(_encoding, _flags);
127 }
128
129 inline CFStringEncoding effectiveEncoding(CFStringEncoding e)
130 {
131     switch (e) {
132         case kCFStringEncodingISOLatin1:
133         case kCFStringEncodingASCII:
134             e = kCFStringEncodingWindowsLatin1;
135             break;
136     }
137     return e;
138 }
139
140 QCString QTextCodec::fromUnicode(const QString &qcs) const
141 {
142     // FIXME: We should really use the same API in both directions.
143     // Currently we use TEC to decode and CFString to encode; it would be better to encode with TEC too.
144     
145     CFStringEncoding encoding = effectiveEncoding(_encoding);
146
147     // FIXME: Since there's no "force ASCII range" mode in CFString, we change the backslash into a yen sign.
148     // Encoding will change the yen sign back into a backslash.
149     QString copy = qcs;
150     copy.replace('\\', backslashAsCurrencySymbol());
151     CFStringRef cfs = copy.getCFString();
152
153     CFRange range = CFRangeMake(0, CFStringGetLength(cfs));
154     CFIndex bufferLength;
155     CFStringGetBytes(cfs, range, encoding, '?', FALSE, NULL, 0x7FFFFFFF, &bufferLength);
156     QCString result(bufferLength + 1);
157     CFStringGetBytes(cfs, range, encoding, '?', FALSE, reinterpret_cast<unsigned char *>(result.data()), bufferLength, &bufferLength);
158     result[bufferLength] = 0;
159     return result;
160 }
161
162 QString QTextCodec::toUnicode(const char *chs, int len) const
163 {
164     return KWQTextDecoder(_encoding, _flags).toUnicode(chs, len, true);
165 }
166
167 QString QTextCodec::toUnicode(const QByteArray &qba, int len) const
168 {
169     return KWQTextDecoder(_encoding, _flags).toUnicode(qba, len, true);
170 }
171
172 QChar QTextCodec::backslashAsCurrencySymbol() const
173 {
174     // FIXME: We should put this information into KWQCharsetData instead of having a switch here.
175     switch (_encoding) {
176         case kCFStringEncodingShiftJIS_X0213_00:
177         case kCFStringEncodingEUC_JP:
178             return 0x00A5; // yen sign
179         default:
180             return '\\';
181     }
182 }
183
184 bool operator==(const QTextCodec &a, const QTextCodec &b)
185 {
186     return a._encoding == b._encoding && a._flags == b._flags;
187 }
188
189 unsigned QTextCodec::hash() const
190 {
191     unsigned h = _encoding;
192
193     h += (h << 10);
194     h ^= (h << 6);
195     
196     h ^= _flags;
197
198     h += (h << 3);
199     h ^= (h >> 11);
200     h += (h << 15);
201     
202     return h;
203 }
204
205 static Boolean QTextCodecsEqual(const void *a, const void *b)
206 {
207     return *static_cast<const QTextCodec *>(a) == *static_cast<const QTextCodec *>(b);
208 }
209
210 static CFHashCode QTextCodecHash(const void *value)
211 {
212     return static_cast<const QTextCodec *>(value)->hash();
213 }
214
215 // ================
216
217 QTextDecoder::~QTextDecoder()
218 {
219 }
220
221 // ================
222
223 KWQTextDecoder::KWQTextDecoder(CFStringEncoding e, KWQEncodingFlags f)
224     : _encoding(e), _littleEndian(f & ::LittleEndian), _atStart(true), _error(false)
225     , _numBufferedBytes(0), _converter(0)
226 {
227 }
228
229 KWQTextDecoder::~KWQTextDecoder()
230 {
231     if (_converter) {
232         if (_cachedConverter != 0) {
233             TECDisposeConverter(_cachedConverter);
234         }
235         _cachedConverter = _converter;
236         _cachedConverterEncoding = _encoding;
237     }
238 }
239
240 QString KWQTextDecoder::convertLatin1(const unsigned char *s, int length)
241 {
242     ASSERT(_numBufferedBytes == 0);
243
244     int i;
245     for (i = 0; i != length; ++i) {
246         if (s[i] == 0) {
247             break;
248         }
249     }
250     if (i == length) {
251         return QString(reinterpret_cast<const char *>(s), length);
252     }
253
254     QString result;
255     
256     result.reserve(length);
257     
258     result.append(reinterpret_cast<const char *>(s), i);
259     int start = ++i;
260     for (; i != length; ++i) {
261         if (s[i] == 0) {
262             if (start != i) {
263                 result.append(reinterpret_cast<const char *>(&s[start]), i - start);
264             }
265             start = i + 1;
266         }
267     }
268     if (start != length) {
269         result.append(reinterpret_cast<const char *>(&s[start]), length - start);
270     }
271
272     return result;
273 }
274
275 QString KWQTextDecoder::convertUTF16(const unsigned char *s, int length)
276 {
277     ASSERT(_numBufferedBytes == 0 || _numBufferedBytes == 1);
278
279     const unsigned char *p = s;
280     unsigned len = length;
281     
282     QString result;
283     
284     result.reserve(length / 2);
285
286     if (_numBufferedBytes != 0 && len != 0) {
287         ASSERT(_numBufferedBytes == 1);
288         UniChar c;
289         if (_littleEndian) {
290             c = _bufferedBytes[0] | (p[0] << 8);
291         } else {
292             c = (_bufferedBytes[0] << 8) | p[0];
293         }
294         if (c) {
295             result.append(reinterpret_cast<QChar *>(&c), 1);
296         }
297         _numBufferedBytes = 0;
298         p += 1;
299         len -= 1;
300     }
301     
302     while (len > 1) {
303         UniChar buffer[16384];
304         int runLength = MIN(len / 2, sizeof(buffer) / sizeof(buffer[0]));
305         int bufferLength = 0;
306         if (_littleEndian) {
307             for (int i = 0; i < runLength; ++i) {
308                 UniChar c = p[0] | (p[1] << 8);
309                 p += 2;
310                 if (c && c != BOM) {
311                     buffer[bufferLength++] = c;
312                 }
313             }
314         } else {
315             for (int i = 0; i < runLength; ++i) {
316                 UniChar c = (p[0] << 8) | p[1];
317                 p += 2;
318                 if (c && c != BOM) {
319                     buffer[bufferLength++] = c;
320                 }
321             }
322         }
323         result.append(reinterpret_cast<QChar *>(buffer), bufferLength);
324         len -= runLength * 2;
325     }
326     
327     if (len) {
328         ASSERT(_numBufferedBytes == 0);
329         _numBufferedBytes = 1;
330         _bufferedBytes[0] = p[0];
331     }
332     
333     return result;
334 }
335
336 OSStatus KWQTextDecoder::createTECConverter()
337 {
338     const CFStringEncoding encoding = effectiveEncoding(_encoding);
339
340     if (_cachedConverterEncoding == encoding) {
341         _converter = _cachedConverter;
342         _cachedConverter = 0;
343         _cachedConverterEncoding = kCFStringEncodingInvalidId;
344         TECClearConverterContextInfo(_converter);
345     } else {
346         OSStatus status = TECCreateConverter(&_converter, encoding,
347             CreateTextEncoding(kTextEncodingUnicodeDefault, kTextEncodingDefaultVariant, kUnicode16BitFormat));
348         if (status) {
349             ERROR("the Text Encoding Converter won't convert from text encoding 0x%X, error %d", encoding, status);
350             return status;
351         }
352
353         TECSetBasicOptions(_converter, kUnicodeForceASCIIRangeMask);
354     }
355     
356     return noErr;
357 }
358
359 void KWQTextDecoder::appendOmittingNullsAndBOMs(QString &s, const UniChar *characters, int byteCount)
360 {
361     ASSERT(byteCount % sizeof(UniChar) == 0);
362     int start = 0;
363     int characterCount = byteCount / sizeof(UniChar);
364     for (int i = 0; i != characterCount; ++i) {
365         UniChar c = characters[i];
366         if (c == 0 || c == BOM) {
367             if (start != i) {
368                 s.append(reinterpret_cast<const QChar *>(&characters[start]), i - start);
369             }
370             start = i + 1;
371         }
372     }
373     if (start != characterCount) {
374         s.append(reinterpret_cast<const QChar *>(&characters[start]), characterCount - start);
375     }
376 }
377
378 OSStatus KWQTextDecoder::convertOneChunkUsingTEC(const unsigned char *inputBuffer, int inputBufferLength, int &inputLength,
379     void *outputBuffer, int outputBufferLength, int &outputLength)
380 {
381     OSStatus status;
382     unsigned long bytesRead = 0;
383     unsigned long bytesWritten = 0;
384
385     if (_numBufferedBytes != 0) {
386         // Finish converting a partial character that's in our buffer.
387         
388         // First, fill the partial character buffer with as many bytes as are available.
389         ASSERT(_numBufferedBytes < sizeof(_bufferedBytes));
390         const int spaceInBuffer = sizeof(_bufferedBytes) - _numBufferedBytes;
391         const int bytesToPutInBuffer = MIN(spaceInBuffer, inputBufferLength);
392         ASSERT(bytesToPutInBuffer != 0);
393         memcpy(_bufferedBytes + _numBufferedBytes, inputBuffer, bytesToPutInBuffer);
394
395         // Now, do a conversion on the buffer.
396         status = TECConvertText(_converter, _bufferedBytes, _numBufferedBytes + bytesToPutInBuffer, &bytesRead,
397             reinterpret_cast<unsigned char *>(outputBuffer), outputBufferLength, &bytesWritten);
398         ASSERT(bytesRead <= _numBufferedBytes + bytesToPutInBuffer);
399
400         if (status == kTECPartialCharErr && bytesRead == 0) {
401             // Handle the case where the partial character was not converted.
402             if (bytesToPutInBuffer >= spaceInBuffer) {
403                 ERROR("TECConvertText gave a kTECPartialCharErr but read none of the %u bytes in the buffer", sizeof(_bufferedBytes));
404                 _numBufferedBytes = 0;
405                 status = kTECUnmappableElementErr; // should never happen, but use this error code
406             } else {
407                 // Tell the caller we read all the source bytes and keep them in the buffer.
408                 _numBufferedBytes += bytesToPutInBuffer;
409                 bytesRead = bytesToPutInBuffer;
410                 status = noErr;
411             }
412         } else {
413             // We are done with the partial character buffer.
414             // Also, we have read some of the bytes from the main buffer.
415             if (bytesRead > _numBufferedBytes) {
416                 bytesRead -= _numBufferedBytes;
417             } else {
418                 ERROR("TECConvertText accepted some bytes it previously rejected with kTECPartialCharErr");
419                 bytesRead = 0;
420             }
421             _numBufferedBytes = 0;
422             if (status == kTECPartialCharErr) {
423                 // While there may be a partial character problem in the small buffer,
424                 // we have to try again and not get confused and think there is a partial
425                 // character problem in the large buffer.
426                 status = noErr;
427             }
428         }
429     } else {
430         status = TECConvertText(_converter, inputBuffer, inputBufferLength, &bytesRead,
431             static_cast<unsigned char *>(outputBuffer), outputBufferLength, &bytesWritten);
432         ASSERT(static_cast<int>(bytesRead) <= inputBufferLength);
433     }
434
435     // Work around bug 3351093, where sometimes we get kTECBufferBelowMinimumSizeErr instead of kTECOutputBufferFullStatus.
436     if (status == kTECBufferBelowMinimumSizeErr && bytesWritten != 0) {
437         status = kTECOutputBufferFullStatus;
438     }
439
440     inputLength = bytesRead;
441     outputLength = bytesWritten;
442     return status;
443 }
444
445 QString KWQTextDecoder::convertUsingTEC(const unsigned char *chs, int len, bool flush)
446 {
447     // Get a converter for the passed-in encoding.
448     if (!_converter && createTECConverter() != noErr) {
449         return QString();
450     }
451     
452     QString result;
453
454     result.reserve(len);
455
456     const unsigned char *sourcePointer = chs;
457     int sourceLength = len;
458     bool bufferWasFull = false;
459     UniChar buffer[16384];
460
461     while (sourceLength || bufferWasFull) {
462         int bytesRead = 0;
463         int bytesWritten = 0;
464         OSStatus status = convertOneChunkUsingTEC(sourcePointer, sourceLength, bytesRead, buffer, sizeof(buffer), bytesWritten);
465         ASSERT(bytesRead <= sourceLength);
466         sourcePointer += bytesRead;
467         sourceLength -= bytesRead;
468         
469         switch (status) {
470             case noErr:
471             case kTECOutputBufferFullStatus:
472                 break;
473             case kTextMalformedInputErr:
474             case kTextUndefinedElementErr:
475                 // FIXME: Put FFFD character into the output string in this case?
476                 TECClearConverterContextInfo(_converter);
477                 if (sourceLength) {
478                     sourcePointer += 1;
479                     sourceLength -= 1;
480                 }
481                 break;
482             case kTECPartialCharErr: {
483                 // Put the partial character into the buffer.
484                 ASSERT(_numBufferedBytes == 0);
485                 const int bufferSize = sizeof(_numBufferedBytes);
486                 if (sourceLength < bufferSize) {
487                     memcpy(_bufferedBytes, sourcePointer, sourceLength);
488                     _numBufferedBytes = sourceLength;
489                 } else {
490                     ERROR("TECConvertText gave a kTECPartialCharErr, but left %u bytes in the buffer", sourceLength);
491                 }
492                 sourceLength = 0;
493                 break;
494             }
495             default:
496                 ERROR("text decoding failed with error %d", status);
497                 _error = true;
498                 return QString();
499         }
500
501         appendOmittingNullsAndBOMs(result, buffer, bytesWritten);
502
503         bufferWasFull = status == kTECOutputBufferFullStatus;
504     }
505     
506     if (flush) {
507         unsigned long bytesWritten = 0;
508         TECFlushText(_converter, reinterpret_cast<unsigned char *>(buffer), sizeof(buffer), &bytesWritten);
509         appendOmittingNullsAndBOMs(result, buffer, bytesWritten);
510     }
511
512     // Workaround for a bug in the Text Encoding Converter (see bug 3225472).
513     // Simplified Chinese pages use the code U+A3A0 to mean "full-width space".
514     // But GB18030 decodes it to U+E5E5, which is correct in theory but not in practice.
515     // To work around, just change all occurences of U+E5E5 to U+3000 (ideographic space).
516     if (_encoding == kCFStringEncodingGB_18030_2000) {
517         result.replace(0xE5E5, 0x3000);
518     }
519     
520     return result;
521 }
522
523 QString KWQTextDecoder::convert(const unsigned char *chs, int len, bool flush)
524 {
525     //#define PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE 1000
526
527     switch (_encoding) {
528     case kCFStringEncodingISOLatin1:
529     case kCFStringEncodingWindowsLatin1:
530         return convertLatin1(chs, len);
531
532     case kCFStringEncodingUnicode:
533         return convertUTF16(chs, len);
534
535     default:
536 #if PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE
537         QString result;
538         int chunkSize;
539         for (int i = 0; i != len; i += chunkSize) {
540             chunkSize = len - i;
541             if (chunkSize > PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE) {
542                 chunkSize = PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE;
543             }
544             result += convertUsingTEC(chs + i, chunkSize, flush && (i + chunkSize == len));
545         }
546         return result;
547 #else
548         return convertUsingTEC(chs, len, flush);
549 #endif
550     }
551 }
552
553 QString KWQTextDecoder::toUnicode(const char *chs, int len, bool flush)
554 {
555     ASSERT_ARG(len, len >= 0);
556     
557     if (_error || !chs || (len <= 0 && !flush)) {
558         return QString();
559     }
560
561     // Handle normal case.
562     if (!_atStart) {
563         return convert(chs, len, flush);
564     }
565
566     // Check to see if we found a BOM.
567     int numBufferedBytes = _numBufferedBytes;
568     int buf1Len = numBufferedBytes;
569     int buf2Len = len;
570     const unsigned char *buf1 = _bufferedBytes;
571     const unsigned char *buf2 = reinterpret_cast<const unsigned char *>(chs);
572     unsigned char c1 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
573     unsigned char c2 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
574     unsigned char c3 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
575     int BOMLength = 0;
576     if (c1 == 0xFF && c2 == 0xFE) {
577         _encoding = kCFStringEncodingUnicode;
578         _littleEndian = true;
579         BOMLength = 2;
580     } else if (c1 == 0xFE && c2 == 0xFF) {
581         _encoding = kCFStringEncodingUnicode;
582         _littleEndian = false;
583         BOMLength = 2;
584     } else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
585         _encoding = kCFStringEncodingUTF8;
586         BOMLength = 3;
587     }
588
589     // Handle case where we found a BOM.
590     if (BOMLength != 0) {
591         ASSERT(numBufferedBytes + len >= BOMLength);
592         int skip = BOMLength - numBufferedBytes;
593         _numBufferedBytes = 0;
594         _atStart = false;
595         return len == skip ? QString() : convert(chs + skip, len - skip, flush);
596     }
597
598     // Handle case where we know there is no BOM coming.
599     const int bufferSize = sizeof(_bufferedBytes);
600     if (numBufferedBytes + len > bufferSize || flush) {
601         _atStart = false;
602         if (numBufferedBytes == 0) {
603             return convert(chs, len, flush);
604         }
605         unsigned char bufferedBytes[sizeof(_bufferedBytes)];
606         memcpy(bufferedBytes, _bufferedBytes, numBufferedBytes);
607         _numBufferedBytes = 0;
608         return convert(bufferedBytes, numBufferedBytes, false) + convert(chs, len, flush);
609     }
610
611     // Continue to look for the BOM.
612     memcpy(&_bufferedBytes[numBufferedBytes], chs, len);
613     _numBufferedBytes += len;
614     return QString();
615 }