WebCore:
[WebKit-https.git] / WebCore / platform / StreamingTextDecoder.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 "StreamingTextDecoder.h"
28
29 #include <kxmlcore/Assertions.h>
30
31 using std::min;
32
33 namespace WebCore {
34
35 StreamingTextDecoder::StreamingTextDecoder(const TextEncoding& encoding)
36     : m_encoding(encoding)
37     , m_littleEndian(encoding.flags() & LittleEndian)
38     , m_atStart(true)
39     , m_error(false)
40     , m_numBufferedBytes(0)
41     , m_converterICU(0)
42 {
43 }
44
45 static const UChar replacementCharacter = 0xFFFD;
46 static const UChar BOM = 0xFEFF;
47 static const int ConversionBufferSize = 16384;
48     
49 static UConverter* cachedConverterICU;
50 static TextEncodingID cachedConverterEncoding = InvalidEncoding;
51
52 StreamingTextDecoder::~StreamingTextDecoder()
53 {
54     if (m_converterICU) {
55         if (cachedConverterICU != 0)
56             ucnv_close(cachedConverterICU);
57         cachedConverterICU = m_converterICU;
58         cachedConverterEncoding = m_encoding.encodingID();
59     }
60 }
61
62 DeprecatedString StreamingTextDecoder::convertUTF16(const unsigned char *s, int length)
63 {
64     ASSERT(m_numBufferedBytes == 0 || m_numBufferedBytes == 1);
65
66     const unsigned char *p = s;
67     unsigned len = length;
68     
69     DeprecatedString result("");
70     
71     result.reserve(length / 2);
72
73     if (m_numBufferedBytes != 0 && len != 0) {
74         ASSERT(m_numBufferedBytes == 1);
75         UChar c;
76         if (m_littleEndian)
77             c = m_bufferedBytes[0] | (p[0] << 8);
78         else
79             c = (m_bufferedBytes[0] << 8) | p[0];
80
81         if (c)
82             result.append(reinterpret_cast<QChar *>(&c), 1);
83
84         m_numBufferedBytes = 0;
85         p += 1;
86         len -= 1;
87     }
88     
89     while (len > 1) {
90         UChar buffer[ConversionBufferSize];
91         int runLength = min(len / 2, (unsigned)(sizeof(buffer) / sizeof(buffer[0])));
92         int bufferLength = 0;
93         if (m_littleEndian) {
94             for (int i = 0; i < runLength; ++i) {
95                 UChar c = p[0] | (p[1] << 8);
96                 p += 2;
97                 if (c && c != BOM)
98                     buffer[bufferLength++] = c;
99             }
100         } else {
101             for (int i = 0; i < runLength; ++i) {
102                 UChar c = (p[0] << 8) | p[1];
103                 p += 2;
104                 if (c && c != BOM)
105                     buffer[bufferLength++] = c;
106             }
107         }
108         result.append(reinterpret_cast<QChar *>(buffer), bufferLength);
109         len -= runLength * 2;
110     }
111     
112     if (len) {
113         ASSERT(m_numBufferedBytes == 0);
114         m_numBufferedBytes = 1;
115         m_bufferedBytes[0] = p[0];
116     }
117     
118     return result;
119 }
120
121 static inline TextEncoding effectiveEncoding(const TextEncoding& encoding)
122 {
123     TextEncodingID id = encoding.encodingID();
124     if (id == Latin1Encoding || id == ASCIIEncoding)
125         id = WinLatin1Encoding;
126     return TextEncoding(id, encoding.flags());
127 }
128
129 UErrorCode StreamingTextDecoder::createICUConverter()
130 {
131     TextEncoding encoding = effectiveEncoding(m_encoding);
132     const char* encodingName = encoding.name();
133
134     bool cachedEncodingEqual = cachedConverterEncoding == encoding.encodingID();
135     cachedConverterEncoding = InvalidEncoding;
136
137     if (cachedEncodingEqual && cachedConverterICU) {
138         m_converterICU = cachedConverterICU;
139         cachedConverterICU = 0;
140     } else {    
141         UErrorCode err = U_ZERO_ERROR;
142         ASSERT(!m_converterICU);
143         m_converterICU = ucnv_open(encodingName, &err);
144         if (err == U_AMBIGUOUS_ALIAS_WARNING)
145             LOG_ERROR("ICU ambiguous alias warning for encoding: %s", encodingName);
146
147         if (!m_converterICU) {
148             LOG_ERROR("the ICU Converter won't convert from text encoding 0x%X, error %d", encoding.encodingID(), err);
149             return err;
150         }
151     }
152     
153     return U_ZERO_ERROR;
154 }
155
156 // We strip replacement characters because the ICU converter for UTF-8 converts
157 // invalid sequences into replacement characters, but other browsers discard them.
158 // We strip BOM characters because they can show up both at the start of content
159 // and inside content, and we never want them to end up in the decoded text.
160 static inline bool unwanted(UChar c)
161 {
162     return c == replacementCharacter || c == BOM;
163 }
164
165 void StreamingTextDecoder::appendOmittingUnwanted(DeprecatedString &s, const UChar *characters, int byteCount)
166 {
167     ASSERT(byteCount % sizeof(UChar) == 0);
168     int start = 0;
169     int characterCount = byteCount / sizeof(UChar);
170     for (int i = 0; i != characterCount; ++i) {
171         if (unwanted(characters[i])) {
172             if (start != i)
173                 s.append(reinterpret_cast<const QChar *>(&characters[start]), i - start);
174             start = i + 1;
175         }
176     }
177     if (start != characterCount)
178         s.append(reinterpret_cast<const QChar *>(&characters[start]), characterCount - start);
179 }
180
181 DeprecatedString StreamingTextDecoder::convertUsingICU(const unsigned char *chs, int len, bool flush)
182 {
183     // Get a converter for the passed-in encoding.
184     if (!m_converterICU && U_FAILURE(createICUConverter()))
185         return DeprecatedString();
186
187     ASSERT(m_converterICU);
188
189     DeprecatedString result("");
190     result.reserve(len);
191
192     UChar buffer[ConversionBufferSize];
193     const char *source = reinterpret_cast<const char *>(chs);
194     const char *sourceLimit = source + len;
195     int32_t *offsets = NULL;
196     UErrorCode err;
197     
198     do {
199         UChar *target = buffer;
200         const UChar *targetLimit = target + ConversionBufferSize;
201         err = U_ZERO_ERROR;
202         ucnv_toUnicode(m_converterICU, &target, targetLimit, &source, sourceLimit, offsets, flush, &err);
203         int count = target - buffer;
204         appendOmittingUnwanted(result, reinterpret_cast<const UChar *>(buffer), count * sizeof(UChar));
205     } while (err == U_BUFFER_OVERFLOW_ERROR);
206
207     if (U_FAILURE(err)) {
208         // flush the converter so it can be reused, and not be bothered by this error.
209         do {
210             UChar *target = buffer;
211             const UChar *targetLimit = target + ConversionBufferSize;
212             err = U_ZERO_ERROR;
213             ucnv_toUnicode(m_converterICU, &target, targetLimit, &source, sourceLimit, offsets, true, &err);
214         } while (source < sourceLimit);
215         LOG_ERROR("ICU conversion error");
216         return DeprecatedString();
217     }
218     
219     return result;
220 }
221
222 DeprecatedString StreamingTextDecoder::convert(const unsigned char *chs, int len, bool flush)
223 {
224     //#define PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE 1000
225
226     switch (m_encoding.encodingID()) {
227     case UTF16Encoding:
228         return convertUTF16(chs, len);
229
230     default:
231 #if PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE
232         DeprecatedString result;
233         int chunkSize;
234         for (int i = 0; i != len; i += chunkSize) {
235             chunkSize = len - i;
236             if (chunkSize > PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE) {
237                 chunkSize = PARTIAL_CHARACTER_HANDLING_TEST_CHUNK_SIZE;
238             }
239             result += convertUsingICU(chs + i, chunkSize, flush && (i + chunkSize == len));
240         }
241         return result;
242 #else
243         return convertUsingICU(chs, len, flush);
244 #endif
245     }
246     ASSERT_NOT_REACHED();
247     return DeprecatedString();
248 }
249
250 DeprecatedString StreamingTextDecoder::toUnicode(const char *chs, int len, bool flush)
251 {
252     ASSERT_ARG(len, len >= 0);
253     
254     if (m_error || !chs)
255         return DeprecatedString();
256
257     if (len <= 0 && !flush)
258         return "";
259
260     // Handle normal case.
261     if (!m_atStart)
262         return convert(chs, len, flush);
263
264     // Check to see if we found a BOM.
265     int numBufferedBytes = m_numBufferedBytes;
266     int buf1Len = numBufferedBytes;
267     int buf2Len = len;
268     const unsigned char *buf1 = m_bufferedBytes;
269     const unsigned char *buf2 = reinterpret_cast<const unsigned char *>(chs);
270     unsigned char c1 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
271     unsigned char c2 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
272     unsigned char c3 = buf1Len ? (--buf1Len, *buf1++) : buf2Len ? (--buf2Len, *buf2++) : 0;
273     int BOMLength = 0;
274     if (c1 == 0xFF && c2 == 0xFE) {
275         m_encoding = TextEncoding(UTF16Encoding, LittleEndian);
276         m_littleEndian = true;
277         BOMLength = 2;
278     } else if (c1 == 0xFE && c2 == 0xFF) {
279         m_encoding = TextEncoding(UTF16Encoding, BigEndian);
280         m_littleEndian = false;
281         BOMLength = 2;
282     } else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
283         m_encoding = TextEncoding(UTF8Encoding);
284         BOMLength = 3;
285     }
286
287     // Handle case where we found a BOM.
288     if (BOMLength != 0) {
289         ASSERT(numBufferedBytes + len >= BOMLength);
290         int skip = BOMLength - numBufferedBytes;
291         m_numBufferedBytes = 0;
292         m_atStart = false;
293         return len == skip ? DeprecatedString("") : convert(chs + skip, len - skip, flush);
294     }
295
296     // Handle case where we know there is no BOM coming.
297     const int bufferSize = sizeof(m_bufferedBytes);
298     if (numBufferedBytes + len > bufferSize || flush) {
299         m_atStart = false;
300         if (numBufferedBytes == 0) {
301             return convert(chs, len, flush);
302         }
303         unsigned char bufferedBytes[sizeof(m_bufferedBytes)];
304         memcpy(bufferedBytes, m_bufferedBytes, numBufferedBytes);
305         m_numBufferedBytes = 0;
306         return convert(bufferedBytes, numBufferedBytes, false) + convert(chs, len, flush);
307     }
308
309     // Continue to look for the BOM.
310     memcpy(&m_bufferedBytes[numBufferedBytes], chs, len);
311     m_numBufferedBytes += len;
312     return "";
313 }
314     
315 } // namespace WebCore