Rubber stamped by Maciej (kinda).
[WebKit-https.git] / WebCore / platform / TextEncoding.cpp
1 /*
2  * Copyright (C) 2004, 2006 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 #include "config.h"
27 #include "TextEncoding.h"
28
29 #include "CharsetNames.h"
30 #include <wtf/Assertions.h>
31 #include <wtf/HashSet.h>
32 #include "StreamingTextDecoder.h"
33 #include <unicode/unorm.h>
34
35 namespace WebCore {
36
37 TextEncoding::TextEncoding(const char* name, bool eightBitOnly)
38 {
39     m_encodingID = textEncodingIDFromCharsetName(name, &m_flags);
40     if (eightBitOnly && m_encodingID == UTF16Encoding)
41         m_encodingID = UTF8Encoding;
42 }
43
44 const char* TextEncoding::name() const
45 {
46     return charsetNameFromTextEncodingID(m_encodingID);
47 }
48 UChar TextEncoding::backslashAsCurrencySymbol() const
49 {
50     if (m_flags & BackslashIsYen)
51         return 0x00A5; // yen sign
52  
53     return '\\';
54 }
55
56 DeprecatedString TextEncoding::toUnicode(const char* chs, int len) const
57 {
58     return StreamingTextDecoder(*this).toUnicode(chs, len, true);
59 }
60
61 // We'd like to use ICU for this on OS X as well eventually, but we need to make sure
62 // it covers all the encodings that we need
63 #ifndef __APPLE__
64
65 static UConverter* cachedConverter;
66 static TextEncodingID cachedConverterEncoding = InvalidEncoding;
67
68 static const int ConversionBufferSize = 16384;
69
70 static inline UConverter* getConverter(TextEncodingID encoding, UErrorCode* status)
71 {
72     if (cachedConverter && encoding == cachedConverterEncoding) {
73         UConverter* conv = cachedConverter;
74         cachedConverter = 0;
75         return conv;
76     }
77
78     const char* encodingName = charsetNameFromTextEncodingID(encoding);
79     UErrorCode err = U_ZERO_ERROR;
80     UConverter* conv = ucnv_open(encodingName, &err);
81     if (err == U_AMBIGUOUS_ALIAS_WARNING)
82         LOG_ERROR("ICU ambiguous alias warning for encoding: %s", encodingName);
83
84     if (!conv) {
85         LOG_ERROR("the ICU Converter won't convert to text encoding 0x%X, error %d", encoding, err);
86         *status = err;
87         return 0;
88     }
89
90     return conv;
91 }
92
93 static inline void cacheConverter(TextEncodingID id, UConverter* conv)
94 {
95     if (conv) {
96         if (cachedConverter)
97             ucnv_close(cachedConverter);
98         cachedConverter = conv;
99         cachedConverterEncoding = id;
100     }
101 }
102
103 static inline TextEncodingID effectiveEncoding(TextEncodingID encoding)
104 {
105     if (encoding == Latin1Encoding || encoding == ASCIIEncoding)
106         return WinLatin1Encoding;
107     return encoding;
108 }
109
110 DeprecatedCString TextEncoding::fromUnicode(const DeprecatedString &qcs, bool allowEntities) const
111 {
112     TextEncodingID encoding = effectiveEncoding(m_encodingID);
113
114     if (encoding == WinLatin1Encoding && qcs.isAllLatin1())
115         return qcs.latin1();
116
117     if ((encoding == WinLatin1Encoding || encoding == UTF8Encoding || encoding == ASCIIEncoding) 
118         && qcs.isAllASCII())
119         return qcs.ascii();
120
121     // FIXME: We should see if there is "force ASCII range" mode in ICU;
122     // until then, we change the backslash into a yen sign.
123     // Encoding will change the yen sign back into a backslash.
124     DeprecatedString copy = qcs;
125     copy.replace('\\', backslashAsCurrencySymbol());
126
127     UErrorCode err = U_ZERO_ERROR;
128     UConverter* conv = getConverter(encoding, &err);
129     if (!conv && U_FAILURE(err))
130         return DeprecatedCString();
131
132     ASSERT(conv);
133
134     // FIXME: when DeprecatedString buffer is latin1, it would be nice to
135     // convert from that w/o having to allocate a unicode buffer
136
137     char buffer[ConversionBufferSize];
138     const UChar* source = reinterpret_cast<const UChar*>(copy.unicode());
139     const UChar* sourceLimit = source + copy.length();
140
141     DeprecatedString normalizedString;
142     if (UNORM_YES != unorm_quickCheck(source, copy.length(), UNORM_NFC, &err)) {
143         normalizedString.truncate(copy.length()); // normalization to NFC rarely increases the length, so this first attempt will usually succeed
144         
145         int32_t normalizedLength = unorm_normalize(source, copy.length(), UNORM_NFC, 0, reinterpret_cast<UChar*>(const_cast<DeprecatedChar*>(normalizedString.unicode())), copy.length(), &err);
146         if (err == U_BUFFER_OVERFLOW_ERROR) {
147             err = U_ZERO_ERROR;
148             normalizedString.truncate(normalizedLength);
149             normalizedLength = unorm_normalize(source, copy.length(), UNORM_NFC, 0, reinterpret_cast<UChar*>(const_cast<DeprecatedChar*>(normalizedString.unicode())), normalizedLength, &err);
150         }
151         
152         source = reinterpret_cast<const UChar*>(normalizedString.unicode());
153         sourceLimit = source + normalizedLength;
154     }
155
156     DeprecatedCString result(1); // for trailing zero
157
158     if (allowEntities)
159         ucnv_setFromUCallBack(conv, UCNV_FROM_U_CALLBACK_ESCAPE, UCNV_ESCAPE_XML_DEC, 0, 0, &err);
160     else {
161         ucnv_setSubstChars(conv, "?", 1, &err);
162         ucnv_setFromUCallBack(conv, UCNV_FROM_U_CALLBACK_SUBSTITUTE, 0, 0, 0, &err);
163     }
164
165     ASSERT(U_SUCCESS(err));
166     if (U_FAILURE(err))
167         return DeprecatedCString();
168
169     do {
170         char* target = buffer;
171         char* targetLimit = target + ConversionBufferSize;
172         err = U_ZERO_ERROR;
173         ucnv_fromUnicode(conv, &target, targetLimit, &source, sourceLimit, 0, true,  &err);
174         int count = target - buffer;
175         buffer[count] = 0;
176         result.append(buffer);
177     } while (err == U_BUFFER_OVERFLOW_ERROR);
178
179     cacheConverter(encoding, conv);
180
181     return result;
182 }
183 #endif
184
185
186 } // namespace WebCore