2007-07-06 Jungshik Shin <jungshik.shin@gmail.com>
[WebKit-https.git] / WebCore / loader / TextResourceDecoder.cpp
1 /*
2     This file is part of the KDE libraries
3
4     Copyright (C) 1999 Lars Knoll (knoll@mpi-hd.mpg.de)
5     Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc.
6     Copyright (C) 2005, 2006 Alexey Proskuryakov (ap@nypop.com)
7
8     This library is free software; you can redistribute it and/or
9     modify it under the terms of the GNU Library General Public
10     License as published by the Free Software Foundation; either
11     version 2 of the License, or (at your option) any later version.
12
13     This library is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16     Library General Public License for more details.
17
18     You should have received a copy of the GNU Library General Public License
19     along with this library; see the file COPYING.LIB.  If not, write to
20     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21     Boston, MA 02111-1307, USA.
22 */
23
24
25 #include "config.h"
26 #include "TextResourceDecoder.h"
27
28 #include "CString.h"
29 #include "DOMImplementation.h"
30 #include "DeprecatedCString.h"
31 #include "DeprecatedString.h"
32 #include "HTMLNames.h"
33 #include "TextCodec.h"
34
35 namespace WebCore {
36
37 using namespace HTMLNames;
38
39 class KanjiCode {
40 public:
41     enum Type { ASCII, JIS, EUC, SJIS, UTF16, UTF8 };
42     static enum Type judge(const char* str, int length);
43     static const int ESC = 0x1b;
44     static const unsigned char sjisMap[256];
45     static int ISkanji(int code)
46     {
47         if (code >= 0x100)
48             return 0;
49         return sjisMap[code & 0xff] & 1;
50     }
51     static int ISkana(int code)
52     {
53         if (code >= 0x100)
54             return 0;
55         return sjisMap[code & 0xff] & 2;
56     }
57 };
58
59 const unsigned char KanjiCode::sjisMap[256] = {
60     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
61     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
62     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
63     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
64     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
65     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
66     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
67     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
68     0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
69     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
70     0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
71     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
72     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
73     2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
74     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
75     1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0
76 };
77
78 /*
79  * EUC-JP is
80  *     [0xa1 - 0xfe][0xa1 - 0xfe]
81  *     0x8e[0xa1 - 0xfe](SS2)
82  *     0x8f[0xa1 - 0xfe][0xa1 - 0xfe](SS3)
83  *
84  * Shift_Jis is
85  *     [0x81 - 0x9f, 0xe0 - 0xef(0xfe?)][0x40 - 0x7e, 0x80 - 0xfc]
86  *
87  * Shift_Jis Hankaku Kana is
88  *     [0xa1 - 0xdf]
89  */
90
91 /*
92  * KanjiCode::judge() is based on judge_jcode() from jvim
93  *     http://hp.vector.co.jp/authors/VA003457/vim/
94  *
95  * Special Thanks to Kenichi Tsuchida
96  */
97
98 enum KanjiCode::Type KanjiCode::judge(const char* str, int size)
99 {
100     enum Type code;
101     int i;
102     int bfr = false;            /* Kana Moji */
103     int bfk = 0;                /* EUC Kana */
104     int sjis = 0;
105     int euc = 0;
106
107     const unsigned char* ptr = reinterpret_cast<const unsigned char*>(str);
108
109     code = ASCII;
110
111     i = 0;
112     while (i < size) {
113         if (ptr[i] == ESC && (size - i >= 3)) {
114             if ((ptr[i + 1] == '$' && ptr[i + 2] == 'B')
115             || (ptr[i + 1] == '(' && ptr[i + 2] == 'B')) {
116                 code = JIS;
117                 goto breakBreak;
118             } else if ((ptr[i + 1] == '$' && ptr[i + 2] == '@')
119                     || (ptr[i + 1] == '(' && ptr[i + 2] == 'J')) {
120                 code = JIS;
121                 goto breakBreak;
122             } else if (ptr[i + 1] == '(' && ptr[i + 2] == 'I') {
123                 code = JIS;
124                 i += 3;
125             } else if (ptr[i + 1] == ')' && ptr[i + 2] == 'I') {
126                 code = JIS;
127                 i += 3;
128             } else {
129                 i++;
130             }
131             bfr = false;
132             bfk = 0;
133         } else {
134             if (ptr[i] < 0x20) {
135                 bfr = false;
136                 bfk = 0;
137                 /* ?? check kudokuten ?? && ?? hiragana ?? */
138                 if ((i >= 2) && (ptr[i - 2] == 0x81)
139                         && (0x41 <= ptr[i - 1] && ptr[i - 1] <= 0x49)) {
140                     code = SJIS;
141                     sjis += 100;        /* kudokuten */
142                 } else if ((i >= 2) && (ptr[i - 2] == 0xa1)
143                         && (0xa2 <= ptr[i - 1] && ptr[i - 1] <= 0xaa)) {
144                     code = EUC;
145                     euc += 100;         /* kudokuten */
146                 } else if ((i >= 2) && (ptr[i - 2] == 0x82) && (0xa0 <= ptr[i - 1])) {
147                     sjis += 40;         /* hiragana */
148                 } else if ((i >= 2) && (ptr[i - 2] == 0xa4) && (0xa0 <= ptr[i - 1])) {
149                     euc += 40;          /* hiragana */
150                 }
151             } else {
152                 /* ?? check hiragana or katana ?? */
153                 if ((size - i > 1) && (ptr[i] == 0x82) && (0xa0 <= ptr[i + 1])) {
154                     sjis++;     /* hiragana */
155                 } else if ((size - i > 1) && (ptr[i] == 0x83)
156                          && (0x40 <= ptr[i + 1] && ptr[i + 1] <= 0x9f)) {
157                     sjis++;     /* katakana */
158                 } else if ((size - i > 1) && (ptr[i] == 0xa4) && (0xa0 <= ptr[i + 1])) {
159                     euc++;      /* hiragana */
160                 } else if ((size - i > 1) && (ptr[i] == 0xa5) && (0xa0 <= ptr[i + 1])) {
161                     euc++;      /* katakana */
162                 }
163                 if (bfr) {
164                     if ((i >= 1) && (0x40 <= ptr[i] && ptr[i] <= 0xa0) && ISkanji(ptr[i - 1])) {
165                         code = SJIS;
166                         goto breakBreak;
167                     } else if ((i >= 1) && (0x81 <= ptr[i - 1] && ptr[i - 1] <= 0x9f) && ((0x40 <= ptr[i] && ptr[i] < 0x7e) || (0x7e < ptr[i] && ptr[i] <= 0xfc))) {
168                         code = SJIS;
169                         goto breakBreak;
170                     } else if ((i >= 1) && (0xfd <= ptr[i] && ptr[i] <= 0xfe) && (0xa1 <= ptr[i - 1] && ptr[i - 1] <= 0xfe)) {
171                         code = EUC;
172                         goto breakBreak;
173                     } else if ((i >= 1) && (0xfd <= ptr[i - 1] && ptr[i - 1] <= 0xfe) && (0xa1 <= ptr[i] && ptr[i] <= 0xfe)) {
174                         code = EUC;
175                         goto breakBreak;
176                     } else if ((i >= 1) && (ptr[i] < 0xa0 || 0xdf < ptr[i]) && (0x8e == ptr[i - 1])) {
177                         code = SJIS;
178                         goto breakBreak;
179                     } else if (ptr[i] <= 0x7f) {
180                         code = SJIS;
181                         goto breakBreak;
182                     } else {
183                         if (0xa1 <= ptr[i] && ptr[i] <= 0xa6) {
184                             euc++;      /* sjis hankaku kana kigo */
185                         } else if (0xa1 <= ptr[i] && ptr[i] <= 0xdf) {
186                             ;           /* sjis hankaku kana */
187                         } else if (0xa1 <= ptr[i] && ptr[i] <= 0xfe) {
188                             euc++;
189                         } else if (0x8e == ptr[i]) {
190                             euc++;
191                         } else if (0x20 <= ptr[i] && ptr[i] <= 0x7f) {
192                             sjis++;
193                         }
194                         bfr = false;
195                         bfk = 0;
196                     }
197                 } else if (0x8e == ptr[i]) {
198                     if (size - i <= 1) {
199                         ;
200                     } else if (0xa1 <= ptr[i + 1] && ptr[i + 1] <= 0xdf) {
201                         /* EUC KANA or SJIS KANJI */
202                         if (bfk == 1) {
203                             euc += 100;
204                         }
205                         bfk++;
206                         i++;
207                     } else {
208                         /* SJIS only */
209                         code = SJIS;
210                         goto breakBreak;
211                     }
212                 } else if (0x81 <= ptr[i] && ptr[i] <= 0x9f) {
213                     /* SJIS only */
214                     code = SJIS;
215                     if ((size - i >= 1)
216                             && ((0x40 <= ptr[i + 1] && ptr[i + 1] <= 0x7e)
217                             || (0x80 <= ptr[i + 1] && ptr[i + 1] <= 0xfc))) {
218                         goto breakBreak;
219                     }
220                 } else if (0xfd <= ptr[i] && ptr[i] <= 0xfe) {
221                     /* EUC only */
222                     code = EUC;
223                     if ((size - i >= 1)
224                             && (0xa1 <= ptr[i + 1] && ptr[i + 1] <= 0xfe)) {
225                         goto breakBreak;
226                     }
227                 } else if (ptr[i] <= 0x7f) {
228                     ;
229                 } else {
230                     bfr = true;
231                     bfk = 0;
232                 }
233             }
234             i++;
235         }
236     }
237     if (code == ASCII) {
238         if (sjis > euc) {
239             code = SJIS;
240         } else if (sjis < euc) {
241             code = EUC;
242         }
243     }
244 breakBreak:
245     return (code);
246 }
247
248 TextResourceDecoder::ContentType TextResourceDecoder::determineContentType(const String& mimeType)
249 {
250     if (equalIgnoringCase(mimeType, "text/css"))
251         return CSS;
252     if (equalIgnoringCase(mimeType, "text/html"))
253         return HTML;
254     if (DOMImplementation::isXMLMIMEType(mimeType))
255         return XML;
256     return PlainText;
257 }
258
259 const TextEncoding& TextResourceDecoder::defaultEncoding(ContentType contentType, const TextEncoding& specifiedDefaultEncoding)
260 {
261     // Despite 8.5 "Text/xml with Omitted Charset" of RFC 3023, we assume UTF-8 instead of US-ASCII 
262     // for text/xml. This matches Firefox.
263     if (contentType == XML)
264         return UTF8Encoding();
265     if (!specifiedDefaultEncoding.isValid())
266         return Latin1Encoding();
267     return specifiedDefaultEncoding;
268 }
269
270 TextResourceDecoder::TextResourceDecoder(const String& mimeType, const TextEncoding& specifiedDefaultEncoding)
271     : m_contentType(determineContentType(mimeType))
272     , m_decoder(defaultEncoding(m_contentType, specifiedDefaultEncoding))
273     , m_source(DefaultEncoding)
274     , m_checkedForBOM(false)
275     , m_checkedForCSSCharset(false)
276     , m_checkedForHeadCharset(false)
277 {
278 }
279
280 TextResourceDecoder::~TextResourceDecoder()
281 {
282 }
283
284 void TextResourceDecoder::setEncoding(const TextEncoding& encoding, EncodingSource source)
285 {
286     // In case the encoding didn't exist, we keep the old one (helps some sites specifying invalid encodings).
287     if (!encoding.isValid())
288         return;
289
290     if (source == EncodingFromMetaTag || source == EncodingFromXMLHeader || source == EncodingFromCSSCharset)        
291         m_decoder.reset(encoding.closest8BitEquivalent());
292     else
293         m_decoder.reset(encoding);
294
295     m_source = source;
296 }
297
298 // Returns the position of the encoding string.
299 static int findXMLEncoding(const DeprecatedCString &str, int &encodingLength)
300 {
301     int len = str.length();
302
303     int pos = str.find("encoding");
304     if (pos == -1)
305         return -1;
306     pos += 8;
307     
308     // Skip spaces and stray control characters.
309     while (str[pos] <= ' ' && pos != len)
310         ++pos;
311
312     // Skip equals sign.
313     if (str[pos] != '=')
314         return -1;
315     ++pos;
316
317     // Skip spaces and stray control characters.
318     while (str[pos] <= ' ' && pos != len)
319         ++pos;
320
321     // Skip quotation mark.
322     char quoteMark = str[pos];
323     if (quoteMark != '"' && quoteMark != '\'')
324         return -1;
325     ++pos;
326
327     // Find the trailing quotation mark.
328     int end = pos;
329     while (str[end] != quoteMark)
330         ++end;
331
332     if (end == len)
333         return -1;
334     
335     encodingLength = end - pos;
336     return pos;
337 }
338
339 // true if there is more to parse
340 static inline bool skipWhitespace(const char*& pos, const char* dataEnd)
341 {
342     while (pos < dataEnd && (*pos == '\t' || *pos == ' '))
343         ++pos;
344     return pos != dataEnd;
345 }
346
347 void TextResourceDecoder::checkForBOM(const char* data, size_t len)
348 {
349     // Check for UTF-16/32 or UTF-8 BOM mark at the beginning, which is a sure sign of a Unicode encoding.
350
351     if (m_source == UserChosenEncoding) {
352         // FIXME: Maybe a BOM should override even a user-chosen encoding.
353         m_checkedForBOM = true;
354         return;
355     }
356
357     // Check if we have enough data.
358     size_t bufferLength = m_buffer.size();
359     if (bufferLength + len < 4)
360         return;
361
362     m_checkedForBOM = true;
363
364     // Extract the first four bytes.
365     // Handle the case where some of bytes are already in the buffer.
366     // The last byte is always guaranteed to not be in the buffer.
367     const unsigned char* udata = reinterpret_cast<const unsigned char*>(data);
368     unsigned char c1 = bufferLength >= 1 ? m_buffer[0] : *udata++;
369     unsigned char c2 = bufferLength >= 2 ? m_buffer[1] : *udata++;
370     unsigned char c3 = bufferLength >= 3 ? m_buffer[2] : *udata++;
371     ASSERT(bufferLength < 4);
372     unsigned char c4 = *udata;
373
374     // Check for the BOM.
375     if (c1 == 0xFF && c2 == 0xFE) {
376         if (c3 !=0 || c4 != 0)
377             setEncoding(UTF16LittleEndianEncoding(), AutoDetectedEncoding);
378         else 
379             setEncoding(UTF32LittleEndianEncoding(), AutoDetectedEncoding);
380     }
381     else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF)
382         setEncoding(UTF8Encoding(), AutoDetectedEncoding);
383     else if (c1 == 0xFE && c2 == 0xFF)
384         setEncoding(UTF16BigEndianEncoding(), AutoDetectedEncoding);
385     else if (c1 == 0 && c2 == 0 && c3 == 0xFE && c4 == 0xFF)
386         setEncoding(UTF32BigEndianEncoding(), AutoDetectedEncoding);
387 }
388
389 bool TextResourceDecoder::checkForCSSCharset(const char* data, size_t len, bool& movedDataToBuffer)
390 {
391     if (m_source != DefaultEncoding) {
392         m_checkedForCSSCharset = true;
393         return true;
394     }
395
396     size_t oldSize = m_buffer.size();
397     m_buffer.resize(oldSize + len);
398     memcpy(m_buffer.data() + oldSize, data, len);
399
400     movedDataToBuffer = true;
401
402     if (m_buffer.size() > 8) { // strlen("@charset") == 8
403         const char* dataStart = m_buffer.data();
404         const char* dataEnd = dataStart + m_buffer.size();
405
406         if (dataStart[0] == '@' && dataStart[1] == 'c' && dataStart[2] == 'h' && dataStart[3] == 'a' && dataStart[4] == 'r' && 
407             dataStart[5] == 's' && dataStart[6] == 'e' && dataStart[7] == 't') {
408     
409             dataStart += 8;
410             const char* pos = dataStart;
411             if (!skipWhitespace(pos, dataEnd))
412                 return false;
413
414             if (*pos == '"' || *pos == '\'') {
415                 char quotationMark = *pos;
416                 ++pos;
417                 dataStart = pos;
418             
419                 while (pos < dataEnd && *pos != quotationMark)
420                     ++pos;
421                 if (pos == dataEnd)
422                     return false;
423
424                 CString encodingName(dataStart, pos - dataStart + 1);
425                 
426                 ++pos;
427                 if (!skipWhitespace(pos, dataEnd))
428                     return false;
429
430                 if (*pos == ';')
431                     setEncoding(TextEncoding(encodingName.data()), EncodingFromCSSCharset);
432             }
433         }
434         m_checkedForCSSCharset = true;
435         return true;
436     }
437     return false;
438 }
439
440 // Other browsers allow comments in the head section, so we need to also.
441 // It's important not to look for tags inside the comments.
442 static inline void skipComment(const char*& ptr, const char* pEnd)
443 {
444     const char* p = ptr;
445     // Allow <!-->; other browsers do.
446     if (*p == '>') {
447         p++;
448     } else {
449         while (p != pEnd) {
450             if (*p == '-') {
451                 // This is the real end of comment, "-->".
452                 if (p[1] == '-' && p[2] == '>') {
453                     p += 3;
454                     break;
455                 }
456                 // This is the incorrect end of comment that other browsers allow, "--!>".
457                 if (p[1] == '-' && p[2] == '!' && p[3] == '>') {
458                     p += 4;
459                     break;
460                 }
461             }
462             p++;
463         }
464     }
465     ptr = p;
466 }
467
468 bool TextResourceDecoder::checkForHeadCharset(const char* data, size_t len, bool& movedDataToBuffer)
469 {
470     if (m_source != DefaultEncoding) {
471         m_checkedForHeadCharset = true;
472         return true;
473     }
474
475     // This is not completely efficient, since the function might go
476     // through the HTML head several times.
477
478     size_t oldSize = m_buffer.size();
479     m_buffer.resize(oldSize + len);
480     memcpy(m_buffer.data() + oldSize, data, len);
481
482     movedDataToBuffer = true;
483     
484     // we still don't have an encoding, and are in the head
485     // the following tags are allowed in <head>:
486     // SCRIPT|STYLE|META|LINK|OBJECT|TITLE|BASE
487     
488     // We stop scanning when a tag that is not permitted in <head>
489     // is seen, rather when </head> is seen, because that more closely
490     // matches behavior in other browsers; more details in
491     // <http://bugs.webkit.org/show_bug.cgi?id=3590>.
492     
493     // Additionally, we ignore things that looks like tags in <title>, <script> and <noscript>; see
494     // <http://bugs.webkit.org/show_bug.cgi?id=4560>, <http://bugs.webkit.org/show_bug.cgi?id=12165>
495     // and <http://bugs.webkit.org/show_bug.cgi?id=12389>.
496     
497     AtomicStringImpl* enclosingTagName = 0;
498
499     const char* ptr = m_buffer.data();
500     const char* pEnd = ptr + m_buffer.size();
501     while (ptr + 7 < pEnd) { // +7 guarantees that "<!--" and "<?xml" fit in the buffer - and certainly we aren't going to lose any "charset" that way.
502         if (*ptr == '<') {
503             bool end = false;
504             ptr++;
505
506             // Handle comments.
507             if (ptr[0] == '!' && ptr[1] == '-' && ptr[2] == '-') {
508                 ptr += 3;
509                 skipComment(ptr, pEnd);
510                 continue;
511             }
512
513             // Handle XML declaration, which can have encoding in it.
514             // This encoding is honored even for HTML documents.
515             if (ptr[0] == '?' && ptr[1] == 'x' && ptr[2] == 'm' && ptr[3] == 'l') {
516                 const char* xmlDeclarationEnd = ptr;
517                 while (xmlDeclarationEnd != pEnd && *xmlDeclarationEnd != '>')
518                     ++xmlDeclarationEnd;
519                 if (xmlDeclarationEnd == pEnd)
520                     return false;
521                 DeprecatedCString str(ptr, xmlDeclarationEnd - ptr); // No need for +1, because we have an extra "?" to lose at the end of XML declaration.
522                 int len = 0;
523                 int pos = findXMLEncoding(str, len);
524                 if (pos != -1)
525                     setEncoding(TextEncoding(str.mid(pos, len)), EncodingFromXMLHeader);
526                 // continue looking for a charset - it may be specified in an HTTP-Equiv meta
527             } else if (ptr[0] == 0 && ptr[1] == '?' && ptr[2] == 0 && ptr[3] == 'x' && ptr[4] == 0 && ptr[5] == 'm' && ptr[6] == 0 && ptr[7] == 'l') {
528                 // UTF-16 without BOM
529                 setEncoding(((ptr - m_buffer.data()) % 2) ? UTF16LittleEndianEncoding() : UTF16BigEndianEncoding(), AutoDetectedEncoding);
530                 return true;
531             } else if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == '?' && ptr[4] == 0 && ptr[5] == 0 && ptr[6] == 0 && ptr[7] == 'x') {
532                 // UTF-32 without BOM
533                 setEncoding(((ptr - m_buffer.data()) % 4) ? UTF32LittleEndianEncoding() : UTF32BigEndianEncoding(), AutoDetectedEncoding);
534                 return true;
535             }
536
537             // the HTTP-EQUIV meta has no effect on XHTML
538             if (m_contentType == XML)
539                 return true;
540
541             if (*ptr == '/') {
542                 ++ptr;
543                 end = true;
544             }
545
546             char tmp[20];
547             int len = 0;
548             while (
549                 ((*ptr >= 'a') && (*ptr <= 'z') ||
550                  (*ptr >= 'A') && (*ptr <= 'Z') ||
551                  (*ptr >= '0') && (*ptr <= '9'))
552                 && len < 19 )
553             {
554                 if (ptr == pEnd)
555                     return false;
556                 tmp[len] = tolower(*ptr);
557                 ptr++;
558                 len++;
559             }
560             tmp[len] = 0;
561             AtomicString tag(tmp);
562             
563             if (enclosingTagName) {
564                 if (end && tag.impl() == enclosingTagName)
565                     enclosingTagName = 0;
566             } else {
567                 if (tag == titleTag)
568                     enclosingTagName = titleTag.localName().impl();
569                 else if (tag == scriptTag)
570                     enclosingTagName = scriptTag.localName().impl();
571                 else if (tag == noscriptTag)
572                     enclosingTagName = noscriptTag.localName().impl();
573             }
574             
575             // Find where the opening tag ends.
576             const char* tagContentStart = ptr;
577             if (!end) {
578                 while (ptr != pEnd && *ptr != '>') {
579                     if (*ptr == '\'' || *ptr == '"') {
580                         char quoteMark = *ptr;
581                         ++ptr;
582                         while (ptr != pEnd && *ptr != quoteMark)
583                             ++ptr;
584                         if (ptr == pEnd)
585                             return false;
586                     }
587                     ++ptr;
588                 }
589                 if (ptr == pEnd)
590                     return false;
591                 ++ptr;
592             }
593             
594             if (!end && tag == metaTag) {
595                 DeprecatedCString str(tagContentStart, ptr - tagContentStart);
596                 str = str.lower();
597                 int pos = 0;
598                 while (pos < (int)str.length()) {
599                     if ((pos = str.find("charset", pos, false)) == -1)
600                         break;
601                     pos += 7;
602                     // skip whitespace..
603                     while (pos < (int)str.length() && str[pos] <= ' ')
604                         pos++;
605                     if (pos == (int)str.length())
606                         break;
607                     if (str[pos++] != '=')
608                         continue;
609                     while (pos < (int)str.length() &&
610                             (str[pos] <= ' ') || str[pos] == '=' || str[pos] == '"' || str[pos] == '\'')
611                         pos++;
612
613                     // end ?
614                     if (pos == (int)str.length())
615                         break;
616                     unsigned endpos = pos;
617                     while (endpos < str.length() &&
618                            str[endpos] != ' ' && str[endpos] != '"' && str[endpos] != '\'' &&
619                            str[endpos] != ';' && str[endpos] != '>')
620                         endpos++;
621                     setEncoding(TextEncoding(str.mid(pos, endpos - pos)), EncodingFromMetaTag);
622                     if (m_source == EncodingFromMetaTag)
623                         return true;
624
625                     if (endpos >= str.length() || str[endpos] == '/' || str[endpos] == '>')
626                         break;
627
628                     pos = endpos + 1;
629                 }
630             } else if (tag != scriptTag && tag != noscriptTag && tag != styleTag &&
631                        tag != linkTag && tag != metaTag && tag != objectTag &&
632                        tag != titleTag && tag != baseTag && 
633                        (end || tag != htmlTag) && !enclosingTagName &&
634                        (tag != headTag) && isalpha(tmp[0])) {
635                 m_checkedForHeadCharset = true;
636                 return true;
637             }
638         }
639         else
640             ptr++;
641     }
642     return false;
643 }
644
645 void TextResourceDecoder::detectJapaneseEncoding(const char* data, size_t len)
646 {
647     switch (KanjiCode::judge(data, len)) {
648         case KanjiCode::JIS:
649             setEncoding("ISO-2022-JP", AutoDetectedEncoding);
650             break;
651         case KanjiCode::EUC:
652             setEncoding("EUC-JP", AutoDetectedEncoding);
653             break;
654         case KanjiCode::SJIS:
655             setEncoding("Shift_JIS", AutoDetectedEncoding);
656             break;
657         case KanjiCode::ASCII:
658         case KanjiCode::UTF16:
659         case KanjiCode::UTF8:
660             break;
661     }
662 }
663
664 String TextResourceDecoder::decode(const char* data, size_t len)
665 {
666     if (!m_checkedForBOM)
667         checkForBOM(data, len);
668
669     bool movedDataToBuffer = false;
670
671     if (m_contentType == CSS && !m_checkedForCSSCharset)
672         if (!checkForCSSCharset(data, len, movedDataToBuffer))
673             return "";
674
675     if ((m_contentType == HTML || m_contentType == XML) && !m_checkedForHeadCharset) // HTML and XML
676         if (!checkForHeadCharset(data, len, movedDataToBuffer))
677             return "";
678
679     // Do the auto-detect if our default encoding is one of the Japanese ones.
680     // FIXME: It seems wrong to change our encoding downstream after we have already done some decoding.
681     if (m_source != UserChosenEncoding && m_source != AutoDetectedEncoding && encoding().isJapanese())
682         detectJapaneseEncoding(data, len);
683
684     ASSERT(encoding().isValid());
685
686     if (m_buffer.isEmpty())
687         return m_decoder.decode(data, len);
688
689     if (!movedDataToBuffer) {
690         size_t oldSize = m_buffer.size();
691         m_buffer.resize(oldSize + len);
692         memcpy(m_buffer.data() + oldSize, data, len);
693     }
694
695     String result = m_decoder.decode(m_buffer.data(), m_buffer.size());
696     m_buffer.resize(0);
697     return result;
698 }
699
700 String TextResourceDecoder::flush()
701 {
702     String result = m_decoder.decode(m_buffer.data(), m_buffer.size(), true);
703     m_buffer.resize(0);
704     return result;
705 }
706
707 }