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