Reviewed by Eric Seidel.
[WebKit-https.git] / WebCore / platform / text / TextCodecICU.cpp
1 /*
2  * Copyright (C) 2004, 2006, 2007, 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 "TextCodecICU.h"
29
30 #include "CharacterNames.h"
31 #include "CString.h"
32 #include "PlatformString.h"
33 #include <unicode/ucnv.h>
34 #include <unicode/ucnv_cb.h>
35 #include <wtf/Assertions.h>
36
37 using std::auto_ptr;
38 using std::min;
39
40 namespace WebCore {
41
42 const size_t ConversionBufferSize = 16384;
43
44 static UConverter* cachedConverterICU;
45
46 static auto_ptr<TextCodec> newTextCodecICU(const TextEncoding& encoding, const void*)
47 {
48     return auto_ptr<TextCodec>(new TextCodecICU(encoding));
49 }
50
51 void TextCodecICU::registerBaseEncodingNames(EncodingNameRegistrar registrar)
52 {
53     registrar("UTF-8", "UTF-8");
54 }
55
56 void TextCodecICU::registerBaseCodecs(TextCodecRegistrar registrar)
57 {
58     registrar("UTF-8", newTextCodecICU, 0);
59 }
60
61 // FIXME: Registering all the encodings we get from ucnv_getAvailableName
62 // includes encodings we don't want or need. For example: UTF16_PlatformEndian,
63 // UTF16_OppositeEndian, UTF32_PlatformEndian, UTF32_OppositeEndian, and all
64 // the encodings with commas and version numbers.
65
66 void TextCodecICU::registerExtendedEncodingNames(EncodingNameRegistrar registrar)
67 {
68     // We register Hebrew with logical ordering using a separate name.
69     // Otherwise, this would share the same canonical name as the
70     // visual ordering case, and then TextEncoding could not tell them
71     // apart; ICU works with either name.
72     registrar("ISO-8859-8-I", "ISO-8859-8-I");
73
74     int32_t numEncodings = ucnv_countAvailable();
75     for (int32_t i = 0; i < numEncodings; ++i) {
76         const char* name = ucnv_getAvailableName(i);
77         UErrorCode error = U_ZERO_ERROR;
78         // FIXME: Should we use the "MIME" standard instead of "IANA"?
79         const char* standardName = ucnv_getStandardName(name, "IANA", &error);
80         if (!U_SUCCESS(error) || !standardName)
81             continue;
82
83         // 1. Treat GB2312 encoding as GBK (its more modern superset), to match other browsers.
84         // 2. On the Web, GB2312 is encoded as EUC-CN or HZ, while ICU provides a native encoding
85         //    for encoding GB_2312-80 and several others. So, we need to override this behavior, too.
86         if (strcmp(standardName, "GB2312") == 0 || strcmp(standardName, "GB_2312-80") == 0)
87             standardName = "GBK";
88         // Similarly, EUC-KR encodings all map to an extended version.
89         else if (strcmp(standardName, "KS_C_5601-1987") == 0 || strcmp(standardName, "EUC-KR") == 0)
90             standardName = "windows-949-2000";
91         // And so on.
92         else if (strcmp(standardName, "ISO_8859-9:1989") == 0)
93             standardName = "windows-1254";
94
95         registrar(standardName, standardName);
96
97         uint16_t numAliases = ucnv_countAliases(name, &error);
98         ASSERT(U_SUCCESS(error));
99         if (U_SUCCESS(error))
100             for (uint16_t j = 0; j < numAliases; ++j) {
101                 error = U_ZERO_ERROR;
102                 const char* alias = ucnv_getAlias(name, j, &error);
103                 ASSERT(U_SUCCESS(error));
104                 if (U_SUCCESS(error) && alias != standardName)
105                     registrar(alias, standardName);
106             }
107     }
108
109     // Additional aliases.
110     // Perhaps we can get these added to ICU.
111     registrar("macroman", "macintosh");
112     registrar("xmacroman", "macintosh");
113
114     // Additional aliases that historically were present in the encoding
115     // table in WebKit on Macintosh that don't seem to be present in ICU.
116     // Perhaps we can prove these are not used on the web and remove them.
117     // Or perhaps we can get them added to ICU.
118     registrar("cnbig5", "Big5");
119     registrar("cngb", "EUC-CN");
120     registrar("csISO88598I", "ISO_8859-8-I");
121     registrar("csgb231280", "EUC-CN");
122     registrar("dos720", "cp864");
123     registrar("dos874", "cp874");
124     registrar("jis7", "ISO-2022-JP");
125     registrar("koi", "KOI8-R");
126     registrar("logical", "ISO-8859-8-I");
127     registrar("unicode11utf8", "UTF-8");
128     registrar("unicode20utf8", "UTF-8");
129     registrar("visual", "ISO-8859-8");
130     registrar("winarabic", "windows-1256");
131     registrar("winbaltic", "windows-1257");
132     registrar("wincyrillic", "windows-1251");
133     registrar("windows874", "cp874");
134     registrar("wingreek", "windows-1253");
135     registrar("winhebrew", "windows-1255");
136     registrar("winlatin2", "windows-1250");
137     registrar("winturkish", "windows-1254");
138     registrar("winvietnamese", "windows-1258");
139     registrar("xcp1250", "windows-1250");
140     registrar("xcp1251", "windows-1251");
141     registrar("xeuc", "EUC-JP");
142     registrar("xeuccn", "EUC-CN");
143     registrar("xgbk", "EUC-CN");
144     registrar("xunicode20utf8", "UTF-8");
145     registrar("xxbig5", "Big5");
146 }
147
148 void TextCodecICU::registerExtendedCodecs(TextCodecRegistrar registrar)
149 {
150     // See comment above in registerEncodingNames.
151     registrar("ISO-8859-8-I", newTextCodecICU, 0);
152
153     int32_t numEncodings = ucnv_countAvailable();
154     for (int32_t i = 0; i < numEncodings; ++i) {
155         const char* name = ucnv_getAvailableName(i);
156         UErrorCode error = U_ZERO_ERROR;
157         // FIXME: Should we use the "MIME" standard instead of "IANA"?
158         const char* standardName = ucnv_getStandardName(name, "IANA", &error);
159         if (!U_SUCCESS(error) || !standardName)
160             continue;
161         registrar(standardName, newTextCodecICU, 0);
162     }
163 }
164
165 TextCodecICU::TextCodecICU(const TextEncoding& encoding)
166     : m_encoding(encoding)
167     , m_numBufferedBytes(0)
168     , m_converterICU(0)
169     , m_needsGBKFallbacks(false)
170 {
171 }
172
173 TextCodecICU::~TextCodecICU()
174 {
175     releaseICUConverter();
176 }
177
178 void TextCodecICU::releaseICUConverter() const
179 {
180     if (m_converterICU) {
181         if (cachedConverterICU)
182             ucnv_close(cachedConverterICU);
183         cachedConverterICU = m_converterICU;
184         m_converterICU = 0;
185     }
186 }
187
188 void TextCodecICU::createICUConverter() const
189 {
190     ASSERT(!m_converterICU);
191
192     const char* name = m_encoding.name();
193     m_needsGBKFallbacks = name[0] == 'G' && name[1] == 'B' && name[2] == 'K' && !name[3];
194
195     UErrorCode err;
196
197     if (cachedConverterICU) {
198         err = U_ZERO_ERROR;
199         const char* cachedName = ucnv_getName(cachedConverterICU, &err);
200         if (U_SUCCESS(err) && m_encoding == cachedName) {
201             m_converterICU = cachedConverterICU;
202             cachedConverterICU = 0;
203             return;
204         }
205     }
206
207     err = U_ZERO_ERROR;
208     m_converterICU = ucnv_open(m_encoding.name(), &err);
209 #if !LOG_DISABLED
210     if (err == U_AMBIGUOUS_ALIAS_WARNING)
211         LOG_ERROR("ICU ambiguous alias warning for encoding: %s", m_encoding.name());
212 #endif
213     if (m_converterICU)
214         ucnv_setFallback(m_converterICU, TRUE);
215 }
216
217 int TextCodecICU::decodeToBuffer(UChar* target, UChar* targetLimit, const char*& source, const char* sourceLimit, int32_t* offsets, bool flush, UErrorCode& err)
218 {
219     UChar* targetStart = target;
220     err = U_ZERO_ERROR;
221     ucnv_toUnicode(m_converterICU, &target, targetLimit, &source, sourceLimit, offsets, flush, &err);
222     return target - targetStart;
223 }
224
225 class ErrorCallbackSetter {
226 public:
227     ErrorCallbackSetter(UConverter* converter, bool stopOnError)
228         : m_converter(converter)
229         , m_shouldStopOnEncodingErrors(stopOnError)
230     {
231         if (m_shouldStopOnEncodingErrors) {
232             UErrorCode err = U_ZERO_ERROR;
233             ucnv_setToUCallBack(m_converter, UCNV_TO_U_CALLBACK_SUBSTITUTE,
234                            UCNV_SUB_STOP_ON_ILLEGAL, &m_savedAction,
235                            &m_savedContext, &err);
236             ASSERT(err == U_ZERO_ERROR);
237         }
238     }
239     ~ErrorCallbackSetter()
240     {
241         if (m_shouldStopOnEncodingErrors) {
242             UErrorCode err = U_ZERO_ERROR;
243             const void* oldContext;
244             UConverterToUCallback oldAction;
245             ucnv_setToUCallBack(m_converter, m_savedAction,
246                    m_savedContext, &oldAction,
247                    &oldContext, &err);
248             ASSERT(oldAction == UCNV_TO_U_CALLBACK_SUBSTITUTE);
249             ASSERT(!strcmp(static_cast<const char*>(oldContext), UCNV_SUB_STOP_ON_ILLEGAL));
250             ASSERT(err == U_ZERO_ERROR);
251         }
252     }
253 private:
254     UConverter* m_converter;
255     bool m_shouldStopOnEncodingErrors;
256     const void* m_savedContext;
257     UConverterToUCallback m_savedAction;
258 };
259
260 String TextCodecICU::decode(const char* bytes, size_t length, bool flush, bool stopOnError, bool& sawError)
261 {
262     // Get a converter for the passed-in encoding.
263     if (!m_converterICU) {
264         createICUConverter();
265         ASSERT(m_converterICU);
266         if (!m_converterICU) {
267             LOG_ERROR("error creating ICU encoder even though encoding was in table");
268             return String();
269         }
270     }
271     
272     ErrorCallbackSetter callbackSetter(m_converterICU, stopOnError);
273
274     Vector<UChar> result;
275
276     UChar buffer[ConversionBufferSize];
277     UChar* bufferLimit = buffer + ConversionBufferSize;
278     const char* source = reinterpret_cast<const char*>(bytes);
279     const char* sourceLimit = source + length;
280     int32_t* offsets = NULL;
281     UErrorCode err = U_ZERO_ERROR;
282
283     do {
284         int ucharsDecoded = decodeToBuffer(buffer, bufferLimit, source, sourceLimit, offsets, flush, err);
285         result.append(buffer, ucharsDecoded);
286     } while (err == U_BUFFER_OVERFLOW_ERROR);
287
288     if (U_FAILURE(err)) {
289         // flush the converter so it can be reused, and not be bothered by this error.
290         do {
291             decodeToBuffer(buffer, bufferLimit, source, sourceLimit, offsets, true, err);
292         } while (source < sourceLimit);
293         sawError = true;
294     }
295
296     String resultString = String::adopt(result);
297
298     // <http://bugs.webkit.org/show_bug.cgi?id=17014>
299     // Simplified Chinese pages use the code A3A0 to mean "full-width space", but ICU decodes it as U+E5E5.
300     if (m_encoding == "GBK" || m_encoding == "gb18030")
301         resultString.replace(0xE5E5, ideographicSpace);
302
303     return resultString;
304 }
305
306 // We need to apply these fallbacks ourselves as they are not currently supported by ICU and
307 // they were provided by the old TEC encoding path
308 // Needed to fix <rdar://problem/4708689>
309 static UChar getGbkEscape(UChar32 codePoint)
310 {
311     switch (codePoint) {
312         case 0x01F9:
313             return 0xE7C8;
314         case 0x1E3F:
315             return 0xE7C7;
316         case 0x22EF:
317             return 0x2026;
318         case 0x301C:
319             return 0xFF5E;
320         default:
321             return 0;
322     }
323 }
324
325 // Invalid character handler when writing escaped entities for unrepresentable
326 // characters. See the declaration of TextCodec::encode for more.
327 static void urlEscapedEntityCallback(const void* context, UConverterFromUnicodeArgs* fromUArgs, const UChar* codeUnits, int32_t length,
328                                      UChar32 codePoint, UConverterCallbackReason reason, UErrorCode* err)
329 {
330     if (reason == UCNV_UNASSIGNED) {
331         *err = U_ZERO_ERROR;
332
333         UnencodableReplacementArray entity;
334         int entityLen = TextCodec::getUnencodableReplacement(codePoint, URLEncodedEntitiesForUnencodables, entity);
335         ucnv_cbFromUWriteBytes(fromUArgs, entity, entityLen, 0, err);
336     } else
337         UCNV_FROM_U_CALLBACK_ESCAPE(context, fromUArgs, codeUnits, length, codePoint, reason, err);
338 }
339
340 // Substitutes special GBK characters, escaping all other unassigned entities.
341 static void gbkCallbackEscape(const void* context, UConverterFromUnicodeArgs* fromUArgs, const UChar* codeUnits, int32_t length,
342                               UChar32 codePoint, UConverterCallbackReason reason, UErrorCode* err) 
343 {
344     UChar outChar;
345     if (reason == UCNV_UNASSIGNED && (outChar = getGbkEscape(codePoint))) {
346         const UChar* source = &outChar;
347         *err = U_ZERO_ERROR;
348         ucnv_cbFromUWriteUChars(fromUArgs, &source, source + 1, 0, err);
349         return;
350     }
351     UCNV_FROM_U_CALLBACK_ESCAPE(context, fromUArgs, codeUnits, length, codePoint, reason, err);
352 }
353
354 // Combines both gbkUrlEscapedEntityCallback and GBK character substitution.
355 static void gbkUrlEscapedEntityCallack(const void* context, UConverterFromUnicodeArgs* fromUArgs, const UChar* codeUnits, int32_t length,
356                                        UChar32 codePoint, UConverterCallbackReason reason, UErrorCode* err) 
357 {
358     if (reason == UCNV_UNASSIGNED) {
359         if (UChar outChar = getGbkEscape(codePoint)) {
360             const UChar* source = &outChar;
361             *err = U_ZERO_ERROR;
362             ucnv_cbFromUWriteUChars(fromUArgs, &source, source + 1, 0, err);
363             return;
364         }
365         urlEscapedEntityCallback(context, fromUArgs, codeUnits, length, codePoint, reason, err);
366         return;
367     }
368     UCNV_FROM_U_CALLBACK_ESCAPE(context, fromUArgs, codeUnits, length, codePoint, reason, err);
369 }
370
371 static void gbkCallbackSubstitute(const void* context, UConverterFromUnicodeArgs* fromUArgs, const UChar* codeUnits, int32_t length,
372                                   UChar32 codePoint, UConverterCallbackReason reason, UErrorCode* err) 
373 {
374     UChar outChar;
375     if (reason == UCNV_UNASSIGNED && (outChar = getGbkEscape(codePoint))) {
376         const UChar* source = &outChar;
377         *err = U_ZERO_ERROR;
378         ucnv_cbFromUWriteUChars(fromUArgs, &source, source + 1, 0, err);
379         return;
380     }
381     UCNV_FROM_U_CALLBACK_SUBSTITUTE(context, fromUArgs, codeUnits, length, codePoint, reason, err);
382 }
383
384 CString TextCodecICU::encode(const UChar* characters, size_t length, UnencodableHandling handling)
385 {
386     if (!length)
387         return "";
388
389     if (!m_converterICU)
390         createICUConverter();
391     if (!m_converterICU)
392         return CString();
393
394     // FIXME: We should see if there is "force ASCII range" mode in ICU;
395     // until then, we change the backslash into a yen sign.
396     // Encoding will change the yen sign back into a backslash.
397     String copy(characters, length);
398     copy.replace('\\', m_encoding.backslashAsCurrencySymbol());
399
400     const UChar* source = copy.characters();
401     const UChar* sourceLimit = source + copy.length();
402
403     UErrorCode err = U_ZERO_ERROR;
404
405     switch (handling) {
406         case QuestionMarksForUnencodables:
407             ucnv_setSubstChars(m_converterICU, "?", 1, &err);
408             ucnv_setFromUCallBack(m_converterICU, m_needsGBKFallbacks ? gbkCallbackSubstitute : UCNV_FROM_U_CALLBACK_SUBSTITUTE, 0, 0, 0, &err);
409             break;
410         case EntitiesForUnencodables:
411             ucnv_setFromUCallBack(m_converterICU, m_needsGBKFallbacks ? gbkCallbackEscape : UCNV_FROM_U_CALLBACK_ESCAPE, UCNV_ESCAPE_XML_DEC, 0, 0, &err);
412             break;
413         case URLEncodedEntitiesForUnencodables:
414             ucnv_setFromUCallBack(m_converterICU, m_needsGBKFallbacks ? gbkUrlEscapedEntityCallack : urlEscapedEntityCallback, 0, 0, 0, &err);
415             break;
416     }
417
418     ASSERT(U_SUCCESS(err));
419     if (U_FAILURE(err))
420         return CString();
421
422     Vector<char> result;
423     size_t size = 0;
424     do {
425         char buffer[ConversionBufferSize];
426         char* target = buffer;
427         char* targetLimit = target + ConversionBufferSize;
428         err = U_ZERO_ERROR;
429         ucnv_fromUnicode(m_converterICU, &target, targetLimit, &source, sourceLimit, 0, true, &err);
430         size_t count = target - buffer;
431         result.grow(size + count);
432         memcpy(result.data() + size, buffer, count);
433         size += count;
434     } while (err == U_BUFFER_OVERFLOW_ERROR);
435
436     return CString(result.data(), size);
437 }
438
439
440 } // namespace WebCore