7dc6813b4b060987f4ce9290107a4c61720ee235
[WebKit-https.git] / Source / WebCore / platform / text / TextEncodingRegistry.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2007-2009 Torch Mobile, Inc.
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 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 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 "TextEncodingRegistry.h"
29
30 #include "TextCodecICU.h"
31 #include "TextCodecLatin1.h"
32 #include "TextCodecReplacement.h"
33 #include "TextCodecUserDefined.h"
34 #include "TextCodecUTF16.h"
35 #include "TextCodecUTF8.h"
36 #include "TextEncoding.h"
37 #include <mutex>
38 #include <wtf/ASCIICType.h>
39 #include <wtf/HashMap.h>
40 #include <wtf/HashSet.h>
41 #include <wtf/Lock.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/StdLibExtras.h>
44 #include <wtf/StringExtras.h>
45
46 #if PLATFORM(COCOA)
47 #include "WebCoreSystemInterface.h"
48 #endif
49
50 #if PLATFORM(MAC)
51 #include "TextCodecMac.h"
52 #endif
53
54 #include <wtf/CurrentTime.h>
55 #include <wtf/text/CString.h>
56
57 using namespace WTF;
58
59 namespace WebCore {
60
61 const size_t maxEncodingNameLength = 63;
62
63 // Hash for all-ASCII strings that does case folding.
64 struct TextEncodingNameHash {
65     static bool equal(const char* s1, const char* s2)
66     {
67         char c1;
68         char c2;
69         do {
70             c1 = *s1++;
71             c2 = *s2++;
72             if (toASCIILower(c1) != toASCIILower(c2))
73                 return false;
74         } while (c1 && c2);
75         return !c1 && !c2;
76     }
77
78     // This algorithm is the one-at-a-time hash from:
79     // http://burtleburtle.net/bob/hash/hashfaq.html
80     // http://burtleburtle.net/bob/hash/doobs.html
81     static unsigned hash(const char* s)
82     {
83         unsigned h = WTF::stringHashingStartValue;
84         for (;;) {
85             char c = *s++;
86             if (!c) {
87                 h += (h << 3);
88                 h ^= (h >> 11);
89                 h += (h << 15);
90                 return h;
91             }
92             h += toASCIILower(c);
93             h += (h << 10); 
94             h ^= (h >> 6); 
95         }
96     }
97
98     static const bool safeToCompareToEmptyOrDeleted = false;
99 };
100
101 struct TextCodecFactory {
102     NewTextCodecFunction function;
103     const void* additionalData;
104     TextCodecFactory(NewTextCodecFunction f = 0, const void* d = 0) : function(f), additionalData(d) { }
105 };
106
107 typedef HashMap<const char*, const char*, TextEncodingNameHash> TextEncodingNameMap;
108 typedef HashMap<const char*, TextCodecFactory> TextCodecMap;
109
110 static StaticLock encodingRegistryMutex;
111
112 static TextEncodingNameMap* textEncodingNameMap;
113 static TextCodecMap* textCodecMap;
114 static bool didExtendTextCodecMaps;
115 static HashSet<const char*>* japaneseEncodings;
116 static HashSet<const char*>* nonBackslashEncodings;
117
118 static const char* const textEncodingNameBlacklist[] = { "UTF-7", "BOCU-1", "SCSU" };
119
120 #if ERROR_DISABLED
121
122 static inline void checkExistingName(const char*, const char*) { }
123
124 #else
125
126 static void checkExistingName(const char* alias, const char* atomicName)
127 {
128     const char* oldAtomicName = textEncodingNameMap->get(alias);
129     if (!oldAtomicName)
130         return;
131     if (oldAtomicName == atomicName)
132         return;
133     // Keep the warning silent about one case where we know this will happen.
134     if (strcmp(alias, "ISO-8859-8-I") == 0
135             && strcmp(oldAtomicName, "ISO-8859-8-I") == 0
136             && strcasecmp(atomicName, "iso-8859-8") == 0)
137         return;
138     LOG_ERROR("alias %s maps to %s already, but someone is trying to make it map to %s", alias, oldAtomicName, atomicName);
139 }
140
141 #endif
142
143 static bool isUndesiredAlias(const char* alias)
144 {
145     // Reject aliases with version numbers that are supported by some back-ends (such as "ISO_2022,locale=ja,version=0" in ICU).
146     for (const char* p = alias; *p; ++p) {
147         if (*p == ',')
148             return true;
149     }
150     // 8859_1 is known to (at least) ICU, but other browsers don't support this name - and having it caused a compatibility
151     // problem, see bug 43554.
152     if (0 == strcmp(alias, "8859_1"))
153         return true;
154     return false;
155 }
156
157 static void addToTextEncodingNameMap(const char* alias, const char* name)
158 {
159     ASSERT(strlen(alias) <= maxEncodingNameLength);
160     if (isUndesiredAlias(alias))
161         return;
162     const char* atomicName = textEncodingNameMap->get(name);
163     ASSERT(strcmp(alias, name) == 0 || atomicName);
164     if (!atomicName)
165         atomicName = name;
166     checkExistingName(alias, atomicName);
167     textEncodingNameMap->add(alias, atomicName);
168 }
169
170 static void addToTextCodecMap(const char* name, NewTextCodecFunction function, const void* additionalData)
171 {
172     const char* atomicName = textEncodingNameMap->get(name);
173     ASSERT(atomicName);
174     textCodecMap->add(atomicName, TextCodecFactory(function, additionalData));
175 }
176
177 static void pruneBlacklistedCodecs()
178 {
179     for (size_t i = 0; i < WTF_ARRAY_LENGTH(textEncodingNameBlacklist); ++i) {
180         const char* atomicName = textEncodingNameMap->get(textEncodingNameBlacklist[i]);
181         if (!atomicName)
182             continue;
183
184         Vector<const char*> names;
185         TextEncodingNameMap::const_iterator it = textEncodingNameMap->begin();
186         TextEncodingNameMap::const_iterator end = textEncodingNameMap->end();
187         for (; it != end; ++it) {
188             if (it->value == atomicName)
189                 names.append(it->key);
190         }
191
192         size_t length = names.size();
193         for (size_t j = 0; j < length; ++j)
194             textEncodingNameMap->remove(names[j]);
195
196         textCodecMap->remove(atomicName);
197     }
198 }
199
200 static void buildBaseTextCodecMaps()
201 {
202     ASSERT(isMainThread());
203     ASSERT(!textCodecMap);
204     ASSERT(!textEncodingNameMap);
205
206     textCodecMap = new TextCodecMap;
207     textEncodingNameMap = new TextEncodingNameMap;
208
209     TextCodecLatin1::registerEncodingNames(addToTextEncodingNameMap);
210     TextCodecLatin1::registerCodecs(addToTextCodecMap);
211
212     TextCodecUTF8::registerEncodingNames(addToTextEncodingNameMap);
213     TextCodecUTF8::registerCodecs(addToTextCodecMap);
214
215     TextCodecUTF16::registerEncodingNames(addToTextEncodingNameMap);
216     TextCodecUTF16::registerCodecs(addToTextCodecMap);
217
218     TextCodecUserDefined::registerEncodingNames(addToTextEncodingNameMap);
219     TextCodecUserDefined::registerCodecs(addToTextCodecMap);
220 }
221
222 static void addEncodingName(HashSet<const char*>* set, const char* name)
223 {
224     // We must not use atomicCanonicalTextEncodingName() because this function is called in it.
225     const char* atomicName = textEncodingNameMap->get(name);
226     if (atomicName)
227         set->add(atomicName);
228 }
229
230 static void buildQuirksSets()
231 {
232     // FIXME: Having isJapaneseEncoding() and shouldShowBackslashAsCurrencySymbolIn()
233     // and initializing the sets for them in TextEncodingRegistry.cpp look strange.
234
235     ASSERT(!japaneseEncodings);
236     ASSERT(!nonBackslashEncodings);
237
238     japaneseEncodings = new HashSet<const char*>;
239     addEncodingName(japaneseEncodings, "EUC-JP");
240     addEncodingName(japaneseEncodings, "ISO-2022-JP");
241     addEncodingName(japaneseEncodings, "ISO-2022-JP-1");
242     addEncodingName(japaneseEncodings, "ISO-2022-JP-2");
243     addEncodingName(japaneseEncodings, "ISO-2022-JP-3");
244     addEncodingName(japaneseEncodings, "JIS_C6226-1978");
245     addEncodingName(japaneseEncodings, "JIS_X0201");
246     addEncodingName(japaneseEncodings, "JIS_X0208-1983");
247     addEncodingName(japaneseEncodings, "JIS_X0208-1990");
248     addEncodingName(japaneseEncodings, "JIS_X0212-1990");
249     addEncodingName(japaneseEncodings, "Shift_JIS");
250     addEncodingName(japaneseEncodings, "Shift_JIS_X0213-2000");
251     addEncodingName(japaneseEncodings, "cp932");
252     addEncodingName(japaneseEncodings, "x-mac-japanese");
253
254     nonBackslashEncodings = new HashSet<const char*>;
255     // The text encodings below treat backslash as a currency symbol for IE compatibility.
256     // See http://blogs.msdn.com/michkap/archive/2005/09/17/469941.aspx for more information.
257     addEncodingName(nonBackslashEncodings, "x-mac-japanese");
258     addEncodingName(nonBackslashEncodings, "ISO-2022-JP");
259     addEncodingName(nonBackslashEncodings, "EUC-JP");
260     // Shift_JIS_X0213-2000 is not the same encoding as Shift_JIS on Mac. We need to register both of them.
261     addEncodingName(nonBackslashEncodings, "Shift_JIS");
262     addEncodingName(nonBackslashEncodings, "Shift_JIS_X0213-2000");
263 }
264
265 bool isJapaneseEncoding(const char* canonicalEncodingName)
266 {
267     return canonicalEncodingName && japaneseEncodings && japaneseEncodings->contains(canonicalEncodingName);
268 }
269
270 bool isReplacementEncoding(const char* alias)
271 {
272     if (!alias)
273         return false;
274
275     if (strlen(alias) != 11)
276         return false;
277
278     return !strcasecmp(alias, "replacement");
279 }
280
281 bool isReplacementEncoding(const String& alias)
282 {
283     return equalLettersIgnoringASCIICase(alias, "replacement");
284 }
285
286 bool shouldShowBackslashAsCurrencySymbolIn(const char* canonicalEncodingName)
287 {
288     return canonicalEncodingName && nonBackslashEncodings && nonBackslashEncodings->contains(canonicalEncodingName);
289 }
290
291 static void extendTextCodecMaps()
292 {
293     TextCodecReplacement::registerEncodingNames(addToTextEncodingNameMap);
294     TextCodecReplacement::registerCodecs(addToTextCodecMap);
295
296     TextCodecICU::registerEncodingNames(addToTextEncodingNameMap);
297     TextCodecICU::registerCodecs(addToTextCodecMap);
298
299 #if PLATFORM(MAC)
300     TextCodecMac::registerEncodingNames(addToTextEncodingNameMap);
301     TextCodecMac::registerCodecs(addToTextCodecMap);
302 #endif
303
304     pruneBlacklistedCodecs();
305     buildQuirksSets();
306 }
307
308 std::unique_ptr<TextCodec> newTextCodec(const TextEncoding& encoding)
309 {
310     std::lock_guard<StaticLock> lock(encodingRegistryMutex);
311
312     ASSERT(textCodecMap);
313     TextCodecFactory factory = textCodecMap->get(encoding.name());
314     ASSERT(factory.function);
315     return factory.function(encoding, factory.additionalData);
316 }
317
318 const char* atomicCanonicalTextEncodingName(const char* name)
319 {
320     if (!name || !name[0])
321         return nullptr;
322
323     if (!textEncodingNameMap)
324         buildBaseTextCodecMaps();
325
326     std::lock_guard<StaticLock> lock(encodingRegistryMutex);
327
328     if (const char* atomicName = textEncodingNameMap->get(name))
329         return atomicName;
330     if (didExtendTextCodecMaps)
331         return nullptr;
332
333     extendTextCodecMaps();
334     didExtendTextCodecMaps = true;
335     return textEncodingNameMap->get(name);
336 }
337
338 template <typename CharacterType>
339 const char* atomicCanonicalTextEncodingName(const CharacterType* characters, size_t length)
340 {
341     char buffer[maxEncodingNameLength + 1];
342     size_t j = 0;
343     for (size_t i = 0; i < length; ++i) {
344         CharacterType c = characters[i];
345         if (j == maxEncodingNameLength)
346             return 0;
347         buffer[j++] = c;
348     }
349     buffer[j] = 0;
350     return atomicCanonicalTextEncodingName(buffer);
351 }
352
353 const char* atomicCanonicalTextEncodingName(const String& alias)
354 {
355     if (!alias.length())
356         return nullptr;
357
358     if (alias.is8Bit())
359         return atomicCanonicalTextEncodingName(alias.characters8(), alias.length());
360
361     return atomicCanonicalTextEncodingName(alias.characters16(), alias.length());
362 }
363
364 bool noExtendedTextEncodingNameUsed()
365 {
366     // If the calling thread did not use extended encoding names, it is fine for it to use a stale false value.
367     return !didExtendTextCodecMaps;
368 }
369
370 String defaultTextEncodingNameForSystemLanguage()
371 {
372 #if PLATFORM(COCOA)
373     String systemEncodingName = CFStringConvertEncodingToIANACharSetName(wkGetWebDefaultCFStringEncoding());
374
375     // CFStringConvertEncodingToIANACharSetName() returns cp949 for kTextEncodingDOSKorean AKA "extended EUC-KR" AKA windows-949.
376     // ICU uses this name for a different encoding, so we need to change the name to a value that actually gives us windows-949.
377     // In addition, this value must match what is used in Safari, see <rdar://problem/5579292>.
378     // On some OS versions, the result is CP949 (uppercase).
379     if (equalLettersIgnoringASCIICase(systemEncodingName, "cp949"))
380         systemEncodingName = ASCIILiteral("ks_c_5601-1987");
381     return systemEncodingName;
382 #else
383     return ASCIILiteral("ISO-8859-1");
384 #endif
385 }
386
387 #ifndef NDEBUG
388 void dumpTextEncodingNameMap()
389 {
390     unsigned size = textEncodingNameMap->size();
391     fprintf(stderr, "Dumping %u entries in WebCore::textEncodingNameMap...\n", size);
392
393     std::lock_guard<StaticLock> lock(encodingRegistryMutex);
394
395     TextEncodingNameMap::const_iterator it = textEncodingNameMap->begin();
396     TextEncodingNameMap::const_iterator end = textEncodingNameMap->end();
397     for (; it != end; ++it)
398         fprintf(stderr, "'%s' => '%s'\n", it->key, it->value);
399 }
400 #endif
401
402 } // namespace WebCore