Integrate most of GoogleURL in WTFURL
[WebKit-https.git] / Source / WTF / wtf / url / src / URLCanonHost.cpp
1 /*
2  * Copyright 2007 Google Inc. All rights reserved.
3  * Copyright 2012 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "URLCanon.h"
34
35 #include "RawURLBuffer.h"
36 #include "URLCanonInternal.h"
37
38 #if USE(WTFURL)
39
40 namespace WTF {
41
42 namespace URLCanonicalizer {
43
44 namespace {
45
46 // For reference, here's what IE supports:
47 // Key: 0 (disallowed: failure if present in the input)
48 //      + (allowed either escaped or unescaped, and unmodified)
49 //      U (allowed escaped or unescaped but always unescaped if present in
50 //         escaped form)
51 //      E (allowed escaped or unescaped but always escaped if present in
52 //         unescaped form)
53 //      % (only allowed escaped in the input, will be unmodified).
54 //      I left blank alpha numeric characters.
55 //
56 //    00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
57 //    -----------------------------------------------
58 // 0   0  E  E  E  E  E  E  E  E  E  E  E  E  E  E  E
59 // 1   E  E  E  E  E  E  E  E  E  E  E  E  E  E  E  E
60 // 2   E  +  E  E  +  E  +  +  +  +  +  +  +  U  U  0
61 // 3                                 %  %  E  +  E  0  <-- Those are  : ; < = > ?
62 // 4   %
63 // 5                                    U  0  U  U  U  <-- Those are  [ \ ] ^ _
64 // 6   E                                               <-- That's  `
65 // 7                                    E  E  E  U  E  <-- Those are { | } ~ (UNPRINTABLE)
66 //
67 // NOTE: I didn't actually test all the control characters. Some may be
68 // disallowed in the input, but they are all accepted escaped except for 0.
69 // I also didn't test if characters affecting HTML parsing are allowed
70 // unescaped, eg. (") or (#), which would indicate the beginning of the path.
71 // Surprisingly, space is accepted in the input and always escaped.
72
73 // This table lists the canonical version of all characters we allow in the
74 // input, with 0 indicating it is disallowed. We use the magic kEscapedHostChar
75 // value to indicate that this character should be escaped. We are a little more
76 // restrictive than IE, but less restrictive than Firefox.
77 //
78 // Note that we disallow the % character. We will allow it when part of an
79 // escape sequence, of course, but this disallows "%25". Even though IE allows
80 // it, allowing it would put us in a funny state. If there was an invalid
81 // escape sequence like "%zz", we'll add "%25zz" to the output and fail.
82 // Allowing percents means we'll succeed a second time, so validity would change
83 // based on how many times you run the canonicalizer. We prefer to always report
84 // the same vailidity, so reject this.
85 const unsigned char kEsc = 0xff;
86 const unsigned char kHostCharLookup[0x80] = {
87     0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
88     0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
89 //  ' '   !     "     #     $     %     &     '     (     )     *     +     ,     -     .     /
90     kEsc, kEsc, kEsc, kEsc, kEsc,   0,  kEsc, kEsc, kEsc, kEsc, kEsc,  '+', kEsc,  '-',  '.',   0,
91 //   0    1    2    3    4    5    6    7    8    9    :    ;    <    =    >    ?
92     '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',  '8',  '9',  ':',   0 , kEsc, kEsc, kEsc,   0 ,
93 //   @      A     B     C     D     E     F     G     H     I     J     K     L     M     N     O
94     kEsc,  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
95 //   P     Q     R     S     T     U     V     W     X     Y     Z     [     \     ]     ^     _
96     'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  '[',   0 ,  ']',   0 ,  '_',
97 //   `      a     b     c     d     e     f     g     h     i     j     k     l     m     n     o
98     kEsc,  'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
99 //   p     q     r     s     t     u     v     w     x     y     z     {     |     }     ~
100     'p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z', kEsc, kEsc, kEsc,   0 ,   0 };
101
102 typedef RawURLBuffer<char> StackBuffer;
103 typedef RawURLBuffer<UChar> StackBufferW;
104
105 // Scans a host name and fills in the output flags according to what we find.
106 // |hasNonASCII| will be true if there are any non-7-bit characters, and
107 // |hasEscaped| will be true if there is a percent sign.
108 template<typename CharacterType, typename UCHAR>
109 void scanHostname(const CharacterType* spec, const URLComponent& host, bool& hasNonASCII, bool& hasEscaped)
110 {
111     int end = host.end();
112     hasNonASCII = false;
113     hasEscaped = false;
114     for (int i = host.begin(); i < end; ++i) {
115         if (static_cast<UCHAR>(spec[i]) >= 0x80)
116             hasNonASCII = true;
117         else if (spec[i] == '%')
118             hasEscaped = true;
119     }
120 }
121
122 // Canonicalizes a host name that is entirely 8-bit characters (even though
123 // the type holding them may be 16 bits. Escaped characters will be unescaped.
124 // Non-7-bit characters (for example, UTF-8) will be passed unchanged.
125 //
126 // The |*hasNonASCII| flag will be true if there are non-7-bit characters in
127 // the output.
128 //
129 // This function is used in two situations:
130 //
131 //  * When the caller knows there is no non-ASCII or percent escaped
132 //    characters. This is what DoHost does. The result will be a completely
133 //    canonicalized host since we know nothing weird can happen (escaped
134 //    characters could be unescaped to non-7-bit, so they have to be treated
135 //    with suspicion at this point). It does not use the |hasNonASCII| flag.
136 //
137 //  * When the caller has an 8-bit string that may need unescaping.
138 //    doComplexHost calls us this situation to do unescaping and validation.
139 //    After this, it may do other IDN operations depending on the value of the
140 //    |*hasNonASCII| flag.
141 //
142 // The return value indicates if the output is a potentially valid host name.
143 template<typename INCHAR, typename OUTCHAR>
144 bool doSimpleHost(const INCHAR* host, int hostLength, URLBuffer<OUTCHAR>& output, bool& hasNonASCII)
145 {
146     hasNonASCII = false;
147
148     bool success = true;
149     for (int i = 0; i < hostLength; ++i) {
150         unsigned source = host[i];
151         if (source == '%') {
152             // Unescape first, if possible.
153             // Source will be used only if decode operation was successful.
154             if (!DecodeEscaped(host, &i, hostLength,
155                                reinterpret_cast<unsigned char*>(&source))) {
156                 // Invalid escaped character. There is nothing that can make this
157                 // host valid. We append an escaped percent so the URL looks reasonable
158                 // and mark as failed.
159                 appendURLEscapedCharacter('%', output);
160                 success = false;
161                 continue;
162             }
163         }
164
165         if (source < 0x80) {
166             // We have ASCII input, we can use our lookup table.
167             unsigned char replacement = kHostCharLookup[source];
168             if (!replacement) {
169                 // Invalid character, add it as percent-escaped and mark as failed.
170                 appendURLEscapedCharacter(source, output);
171                 success = false;
172             } else if (replacement == kEsc) {
173                 // This character is valid but should be escaped.
174                 appendURLEscapedCharacter(source, output);
175             } else {
176                 // Common case, the given character is valid in a hostname, the lookup
177                 // table tells us the canonical representation of that character (lower
178                 // cased).
179                 output.append(replacement);
180             }
181         } else {
182             // It's a non-ascii char. Just push it to the output.
183             // In case where we have UChar input, and char output it's safe to
184             // cast UChar->char only if input string was converted to ASCII.
185             output.append(static_cast<OUTCHAR>(source));
186             hasNonASCII = true;
187         }
188     }
189
190     return success;
191 }
192
193 // Canonicalizes a host that requires IDN conversion. Returns true on success
194 bool doIDNHost(const UChar* src, int sourceLength, URLBuffer<char>& output)
195 {
196     // We need to escape URL before doing IDN conversion, since punicode strings
197     // cannot be escaped after they are created.
198     RawURLBuffer<UChar> urlEscapedHost;
199     bool hasNonASCII;
200     doSimpleHost(src, sourceLength, urlEscapedHost, hasNonASCII);
201
202     StackBufferW wideOutput;
203     if (!IDNToASCII(urlEscapedHost.data(),
204                     urlEscapedHost.length(),
205                     wideOutput)) {
206         // Some error, give up. This will write some reasonable looking
207         // representation of the string to the output.
208         AppendInvalidNarrowString(src, 0, sourceLength, output);
209         return false;
210     }
211
212     // Now we check the ASCII output like a normal host. It will also handle
213     // unescaping. Although we unescaped everything before this function call, if
214     // somebody does %00 as fullwidth, ICU will convert this to ASCII.
215     bool success = doSimpleHost(wideOutput.data(), wideOutput.length(), output, hasNonASCII);
216     ASSERT(!hasNonASCII);
217     return success;
218 }
219
220 // 8-bit convert host to its ASCII version: this converts the UTF-8 input to
221 // UTF-16. The hasEscaped flag should be set if the input string requires
222 // unescaping.
223 bool doComplexHost(const char* host, int hostLength, bool hasNonASCII, bool hasEscaped, URLBuffer<char>& output)
224 {
225     // Save the current position in the output. We may write stuff and rewind it
226     // below, so we need to know where to rewind to.
227     int beginLength = output.length();
228
229     // Points to the UTF-8 data we want to convert. This will either be the
230     // input or the unescaped version written to |output| if necessary.
231     const char* utf8Source;
232     int utf8SourceLength;
233     if (hasEscaped) {
234         // Unescape before converting to UTF-16 for IDN. We write this into the
235         // output because it most likely does not require IDNization, and we can
236         // save another huge stack buffer. It will be replaced below if it requires
237         // IDN. This will also update our non-ASCII flag so we know whether the
238         // unescaped input requires IDN.
239         if (!doSimpleHost(host, hostLength, output, hasNonASCII)) {
240             // Error with some escape sequence. We'll call the current output
241             // complete. doSimpleHost will have written some "reasonable" output.
242             return false;
243         }
244
245         // Unescaping may have left us with ASCII input, in which case the
246         // unescaped version we wrote to output is complete.
247         if (!hasNonASCII)
248             return true;
249
250         // Save the pointer into the data was just converted (it may be appended to
251         // other data in the output buffer).
252         utf8Source = &output.data()[beginLength];
253         utf8SourceLength = output.length() - beginLength;
254     } else {
255         // We don't need to unescape, use input for IDNization later. (We know the
256         // input has non-ASCII, or the simple version would have been called
257         // instead of us.)
258         utf8Source = host;
259         utf8SourceLength = hostLength;
260     }
261
262     // Non-ASCII input requires IDN, convert to UTF-16 and do the IDN conversion.
263     // Above, we may have used the output to write the unescaped values to, so
264     // we have to rewind it to where we started after we convert it to UTF-16.
265     StackBufferW utf16;
266     if (!ConvertUTF8ToUTF16(utf8Source, utf8SourceLength, utf16)) {
267         // In this error case, the input may or may not be the output.
268         StackBuffer utf8;
269         for (int i = 0; i < utf8SourceLength; i++)
270             utf8.append(utf8Source[i]);
271         output.setLength(beginLength);
272         AppendInvalidNarrowString(utf8.data(), 0, utf8.length(), output);
273         return false;
274     }
275     output.setLength(beginLength);
276
277     // This will call doSimpleHost which will do normal ASCII canonicalization
278     // and also check for IP addresses in the outpt.
279     return doIDNHost(utf16.data(), utf16.length(), output);
280 }
281
282 // UTF-16 convert host to its ASCII version. The set up is already ready for
283 // the backend, so we just pass through. The hasEscaped flag should be set if
284 // the input string requires unescaping.
285 bool doComplexHost(const UChar* host, int hostLength, bool hasNonASCII, bool hasEscaped, URLBuffer<char>& output)
286 {
287     if (hasEscaped) {
288         // Yikes, we have escaped characters with wide input. The escaped
289         // characters should be interpreted as UTF-8. To solve this problem,
290         // we convert to UTF-8, unescape, then convert back to UTF-16 for IDN.
291         //
292         // We don't bother to optimize the conversion in the ASCII case (which
293         // *could* just be a copy) and use the UTF-8 path, because it should be
294         // very rare that host names have escaped characters, and it is relatively
295         // fast to do the conversion anyway.
296         StackBuffer utf8;
297         if (!ConvertUTF16ToUTF8(host, hostLength, utf8)) {
298             AppendInvalidNarrowString(host, 0, hostLength, output);
299             return false;
300         }
301
302         // Once we convert to UTF-8, we can use the 8-bit version of the complex
303         // host handling code above.
304         return doComplexHost(utf8.data(), utf8.length(), hasNonASCII,
305                              hasEscaped, output);
306     }
307
308     // No unescaping necessary, we can safely pass the input to ICU. This
309     // function will only get called if we either have escaped or non-ascii
310     // input, so it's safe to just use ICU now. Even if the input is ASCII,
311     // this function will do the right thing (just slower than we could).
312     return doIDNHost(host, hostLength, output);
313 }
314
315 template<typename CharacterType, typename UCHAR>
316 void doHost(const CharacterType* spec, const URLComponent& host, URLBuffer<char>& output, CanonHostInfo& hostInfo)
317 {
318     if (host.length() <= 0) {
319         // Empty hosts don't need anything.
320         hostInfo.family = CanonHostInfo::NEUTRAL;
321         hostInfo.ouputHost = URLComponent();
322         return;
323     }
324
325     bool hasNonASCII;
326     bool hasEscaped;
327     scanHostname<CharacterType, UCHAR>(spec, host, hasNonASCII, hasEscaped);
328
329     // Keep track of output's initial length, so we can rewind later.
330     const int outputBegin = output.length();
331
332     bool success;
333     if (!hasNonASCII && !hasEscaped) {
334         success = doSimpleHost(&spec[host.begin()], host.length(), output, hasNonASCII);
335         ASSERT(!hasNonASCII);
336     } else
337         success = doComplexHost(&spec[host.begin()], host.length(), hasNonASCII, hasEscaped, output);
338
339     if (!success) {
340         // Canonicalization failed. Set BROKEN to notify the caller.
341         hostInfo.family = CanonHostInfo::BROKEN;
342     } else {
343         // After all the other canonicalization, check if we ended up with an IP
344         // address. IP addresses are small, so writing into this temporary buffer
345         // should not cause an allocation.
346         RawURLBuffer<char, 64> canon_ip;
347         canonicalizeIPAddress(output.data(), URLComponent::fromRange(outputBegin, output.length()), canon_ip, hostInfo);
348
349         // If we got an IPv4/IPv6 address, copy the canonical form back to the
350         // real buffer. Otherwise, it's a hostname or broken IP, in which case
351         // we just leave it in place.
352         if (hostInfo.IsIPAddress()) {
353             output.setLength(outputBegin);
354             output.append(canon_ip.data(), canon_ip.length());
355         }
356     }
357
358     hostInfo.ouputHost = URLComponent::fromRange(outputBegin, output.length());
359 }
360
361 } // namespace
362
363 bool canonicalizeHost(const char* spec, const URLComponent& host, URLBuffer<char>& output, URLComponent& ouputHost)
364 {
365     CanonHostInfo hostInfo;
366     doHost<char, unsigned char>(spec, host, output, hostInfo);
367     ouputHost = hostInfo.ouputHost;
368     return (hostInfo.family != CanonHostInfo::BROKEN);
369 }
370
371 bool canonicalizeHost(const UChar* spec, const URLComponent& host, URLBuffer<char>& output, URLComponent& ouputHost)
372 {
373     CanonHostInfo hostInfo;
374     doHost<UChar, UChar>(spec, host, output, hostInfo);
375     ouputHost = hostInfo.ouputHost;
376     return (hostInfo.family != CanonHostInfo::BROKEN);
377 }
378
379 } // namespace URLCanonicalizer
380
381 } // namespace WTF
382
383 #endif // USE(WTFURL)