Get rid of ICU_UNICODE and WCHAR_UNICODE remnants
[WebKit-https.git] / Source / WebCore / platform / URL.cpp
1 /*
2  * Copyright (C) 2004, 2007, 2008, 2011, 2012, 2013 Apple Inc. All rights reserved.
3  * Copyright (C) 2012 Research In Motion Limited. 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
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "URL.h"
29
30 #include "DecodeEscapeSequences.h"
31 #include "MIMETypeRegistry.h"
32 #include "TextEncoding.h"
33 #include <stdio.h>
34 #include <unicode/uidna.h>
35 #include <wtf/HashMap.h>
36 #include <wtf/HexNumber.h>
37 #include <wtf/StdLibExtras.h>
38 #include <wtf/text/CString.h>
39 #include <wtf/text/StringBuilder.h>
40 #include <wtf/text/StringHash.h>
41
42 // FIXME: This file makes too much use of the + operator on String.
43 // We either have to optimize that operator so it doesn't involve
44 // so many allocations, or change this to use StringBuffer instead.
45
46 using namespace WTF;
47
48 namespace WebCore {
49
50 typedef Vector<char, 512> CharBuffer;
51 typedef Vector<UChar, 512> UCharBuffer;
52
53 static const unsigned maximumValidPortNumber = 0xFFFE;
54 static const unsigned invalidPortNumber = 0xFFFF;
55
56 static inline bool isLetterMatchIgnoringCase(UChar character, char lowercaseLetter)
57 {
58     ASSERT(isASCIILower(lowercaseLetter));
59     return (character | 0x20) == lowercaseLetter;
60 }
61
62 static const char wsScheme[] = {'w', 's'};
63 static const char ftpScheme[] = {'f', 't', 'p'};
64 static const char ftpPort[] = {'2', '1'};
65 static const char wssScheme[] = {'w', 's', 's'};
66 static const char fileScheme[] = {'f', 'i', 'l', 'e'};
67 static const char httpScheme[] = {'h', 't', 't', 'p'};
68 static const char httpPort[] = {'8', '0'};
69 static const char httpsScheme[] = {'h', 't', 't', 'p', 's'};
70 static const char httpsPort[] = {'4', '4', '3'};
71 static const char gopherScheme[] = {'g', 'o', 'p', 'h', 'e', 'r'};
72 static const char gopherPort[] = {'7', '0'};
73
74 static inline bool isLetterMatchIgnoringCase(char character, char lowercaseLetter)
75 {
76     ASSERT(isASCIILower(lowercaseLetter));
77     return (character | 0x20) == lowercaseLetter;
78 }
79
80 enum URLCharacterClasses {
81     // alpha 
82     SchemeFirstChar = 1 << 0,
83
84     // ( alpha | digit | "+" | "-" | "." )
85     SchemeChar = 1 << 1,
86
87     // mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
88     // unreserved  = alphanum | mark
89     // ( unreserved | escaped | ";" | ":" | "&" | "=" | "+" | "$" | "," )
90     UserInfoChar = 1 << 2,
91
92     // alnum | "." | "-" | "%"
93     // The above is what the specification says, but we are lenient to
94     // match existing practice and also allow:
95     // "_"
96     HostnameChar = 1 << 3,
97
98     // hexdigit | ":" | "%"
99     IPv6Char = 1 << 4,
100
101     // "#" | "?" | "/" | nul
102     PathSegmentEndChar = 1 << 5,
103
104     // not allowed in path
105     BadChar = 1 << 6
106 };
107
108 static const unsigned char characterClassTable[256] = {
109     /* 0 nul */ PathSegmentEndChar,    /* 1 soh */ BadChar,
110     /* 2 stx */ BadChar,    /* 3 etx */ BadChar,
111     /* 4 eot */ BadChar,    /* 5 enq */ BadChar,    /* 6 ack */ BadChar,    /* 7 bel */ BadChar,
112     /* 8 bs */ BadChar,     /* 9 ht */ BadChar,     /* 10 nl */ BadChar,    /* 11 vt */ BadChar,
113     /* 12 np */ BadChar,    /* 13 cr */ BadChar,    /* 14 so */ BadChar,    /* 15 si */ BadChar,
114     /* 16 dle */ BadChar,   /* 17 dc1 */ BadChar,   /* 18 dc2 */ BadChar,   /* 19 dc3 */ BadChar,
115     /* 20 dc4 */ BadChar,   /* 21 nak */ BadChar,   /* 22 syn */ BadChar,   /* 23 etb */ BadChar,
116     /* 24 can */ BadChar,   /* 25 em */ BadChar,    /* 26 sub */ BadChar,   /* 27 esc */ BadChar,
117     /* 28 fs */ BadChar,    /* 29 gs */ BadChar,    /* 30 rs */ BadChar,    /* 31 us */ BadChar,
118     /* 32 sp */ BadChar,    /* 33  ! */ UserInfoChar,
119     /* 34  " */ BadChar,    /* 35  # */ PathSegmentEndChar | BadChar,
120     /* 36  $ */ UserInfoChar,    /* 37  % */ UserInfoChar | HostnameChar | IPv6Char | BadChar,
121     /* 38  & */ UserInfoChar,    /* 39  ' */ UserInfoChar,
122     /* 40  ( */ UserInfoChar,    /* 41  ) */ UserInfoChar,
123     /* 42  * */ UserInfoChar,    /* 43  + */ SchemeChar | UserInfoChar,
124     /* 44  , */ UserInfoChar,
125     /* 45  - */ SchemeChar | UserInfoChar | HostnameChar,
126     /* 46  . */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
127     /* 47  / */ PathSegmentEndChar,
128     /* 48  0 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
129     /* 49  1 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,    
130     /* 50  2 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
131     /* 51  3 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
132     /* 52  4 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
133     /* 53  5 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
134     /* 54  6 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
135     /* 55  7 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
136     /* 56  8 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
137     /* 57  9 */ SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
138     /* 58  : */ UserInfoChar | IPv6Char,    /* 59  ; */ UserInfoChar,
139     /* 60  < */ BadChar,    /* 61  = */ UserInfoChar,
140     /* 62  > */ BadChar,    /* 63  ? */ PathSegmentEndChar | BadChar,
141     /* 64  @ */ 0,
142     /* 65  A */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,    
143     /* 66  B */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
144     /* 67  C */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
145     /* 68  D */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
146     /* 69  E */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
147     /* 70  F */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
148     /* 71  G */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
149     /* 72  H */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
150     /* 73  I */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
151     /* 74  J */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
152     /* 75  K */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
153     /* 76  L */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
154     /* 77  M */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
155     /* 78  N */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
156     /* 79  O */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
157     /* 80  P */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
158     /* 81  Q */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
159     /* 82  R */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
160     /* 83  S */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
161     /* 84  T */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
162     /* 85  U */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
163     /* 86  V */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
164     /* 87  W */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
165     /* 88  X */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
166     /* 89  Y */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
167     /* 90  Z */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
168     /* 91  [ */ 0,
169     /* 92  \ */ 0,    /* 93  ] */ 0,
170     /* 94  ^ */ 0,
171     /* 95  _ */ UserInfoChar | HostnameChar,
172     /* 96  ` */ 0,
173     /* 97  a */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
174     /* 98  b */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
175     /* 99  c */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
176     /* 100  d */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
177     /* 101  e */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char,
178     /* 102  f */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar | IPv6Char, 
179     /* 103  g */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
180     /* 104  h */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
181     /* 105  i */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
182     /* 106  j */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
183     /* 107  k */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
184     /* 108  l */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
185     /* 109  m */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
186     /* 110  n */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
187     /* 111  o */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
188     /* 112  p */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
189     /* 113  q */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
190     /* 114  r */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
191     /* 115  s */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
192     /* 116  t */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
193     /* 117  u */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
194     /* 118  v */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
195     /* 119  w */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
196     /* 120  x */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
197     /* 121  y */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar,
198     /* 122  z */ SchemeFirstChar | SchemeChar | UserInfoChar | HostnameChar, 
199     /* 123  { */ 0,
200     /* 124  | */ 0,   /* 125  } */ 0,   /* 126  ~ */ UserInfoChar,   /* 127 del */ BadChar,
201     /* 128 */ BadChar, /* 129 */ BadChar, /* 130 */ BadChar, /* 131 */ BadChar,
202     /* 132 */ BadChar, /* 133 */ BadChar, /* 134 */ BadChar, /* 135 */ BadChar,
203     /* 136 */ BadChar, /* 137 */ BadChar, /* 138 */ BadChar, /* 139 */ BadChar,
204     /* 140 */ BadChar, /* 141 */ BadChar, /* 142 */ BadChar, /* 143 */ BadChar,
205     /* 144 */ BadChar, /* 145 */ BadChar, /* 146 */ BadChar, /* 147 */ BadChar,
206     /* 148 */ BadChar, /* 149 */ BadChar, /* 150 */ BadChar, /* 151 */ BadChar,
207     /* 152 */ BadChar, /* 153 */ BadChar, /* 154 */ BadChar, /* 155 */ BadChar,
208     /* 156 */ BadChar, /* 157 */ BadChar, /* 158 */ BadChar, /* 159 */ BadChar,
209     /* 160 */ BadChar, /* 161 */ BadChar, /* 162 */ BadChar, /* 163 */ BadChar,
210     /* 164 */ BadChar, /* 165 */ BadChar, /* 166 */ BadChar, /* 167 */ BadChar,
211     /* 168 */ BadChar, /* 169 */ BadChar, /* 170 */ BadChar, /* 171 */ BadChar,
212     /* 172 */ BadChar, /* 173 */ BadChar, /* 174 */ BadChar, /* 175 */ BadChar,
213     /* 176 */ BadChar, /* 177 */ BadChar, /* 178 */ BadChar, /* 179 */ BadChar,
214     /* 180 */ BadChar, /* 181 */ BadChar, /* 182 */ BadChar, /* 183 */ BadChar,
215     /* 184 */ BadChar, /* 185 */ BadChar, /* 186 */ BadChar, /* 187 */ BadChar,
216     /* 188 */ BadChar, /* 189 */ BadChar, /* 190 */ BadChar, /* 191 */ BadChar,
217     /* 192 */ BadChar, /* 193 */ BadChar, /* 194 */ BadChar, /* 195 */ BadChar,
218     /* 196 */ BadChar, /* 197 */ BadChar, /* 198 */ BadChar, /* 199 */ BadChar,
219     /* 200 */ BadChar, /* 201 */ BadChar, /* 202 */ BadChar, /* 203 */ BadChar,
220     /* 204 */ BadChar, /* 205 */ BadChar, /* 206 */ BadChar, /* 207 */ BadChar,
221     /* 208 */ BadChar, /* 209 */ BadChar, /* 210 */ BadChar, /* 211 */ BadChar,
222     /* 212 */ BadChar, /* 213 */ BadChar, /* 214 */ BadChar, /* 215 */ BadChar,
223     /* 216 */ BadChar, /* 217 */ BadChar, /* 218 */ BadChar, /* 219 */ BadChar,
224     /* 220 */ BadChar, /* 221 */ BadChar, /* 222 */ BadChar, /* 223 */ BadChar,
225     /* 224 */ BadChar, /* 225 */ BadChar, /* 226 */ BadChar, /* 227 */ BadChar,
226     /* 228 */ BadChar, /* 229 */ BadChar, /* 230 */ BadChar, /* 231 */ BadChar,
227     /* 232 */ BadChar, /* 233 */ BadChar, /* 234 */ BadChar, /* 235 */ BadChar,
228     /* 236 */ BadChar, /* 237 */ BadChar, /* 238 */ BadChar, /* 239 */ BadChar,
229     /* 240 */ BadChar, /* 241 */ BadChar, /* 242 */ BadChar, /* 243 */ BadChar,
230     /* 244 */ BadChar, /* 245 */ BadChar, /* 246 */ BadChar, /* 247 */ BadChar,
231     /* 248 */ BadChar, /* 249 */ BadChar, /* 250 */ BadChar, /* 251 */ BadChar,
232     /* 252 */ BadChar, /* 253 */ BadChar, /* 254 */ BadChar, /* 255 */ BadChar
233 };
234
235 static int copyPathRemovingDots(char* dst, const char* src, int srcStart, int srcEnd);
236 static void encodeRelativeString(const String& rel, const TextEncoding&, CharBuffer& ouput);
237 static String substituteBackslashes(const String&);
238
239 static inline bool isSchemeFirstChar(char c) { return characterClassTable[static_cast<unsigned char>(c)] & SchemeFirstChar; }
240 static inline bool isSchemeFirstChar(UChar c) { return c <= 0xff && (characterClassTable[c] & SchemeFirstChar); }
241 static inline bool isSchemeChar(char c) { return characterClassTable[static_cast<unsigned char>(c)] & SchemeChar; }
242 static inline bool isSchemeChar(UChar c) { return c <= 0xff && (characterClassTable[c] & SchemeChar); }
243 static inline bool isUserInfoChar(unsigned char c) { return characterClassTable[c] & UserInfoChar; }
244 static inline bool isHostnameChar(unsigned char c) { return characterClassTable[c] & HostnameChar; }
245 static inline bool isIPv6Char(unsigned char c) { return characterClassTable[c] & IPv6Char; }
246 static inline bool isPathSegmentEndChar(char c) { return characterClassTable[static_cast<unsigned char>(c)] & PathSegmentEndChar; }
247 static inline bool isPathSegmentEndChar(UChar c) { return c <= 0xff && (characterClassTable[c] & PathSegmentEndChar); }
248 static inline bool isBadChar(unsigned char c) { return characterClassTable[c] & BadChar; }
249     
250 static inline bool isSchemeCharacterMatchIgnoringCase(char character, char schemeCharacter)
251 {
252     ASSERT(isSchemeChar(character));
253     ASSERT(schemeCharacter & 0x20);
254     ASSERT(isASCIILower(schemeCharacter) || (!isASCIIUpper(schemeCharacter) && isSchemeChar(schemeCharacter)));
255     return (character | 0x20) == schemeCharacter;
256 }
257
258 // Copies the source to the destination, assuming all the source characters are
259 // ASCII. The destination buffer must be large enough. Null characters are allowed
260 // in the source string, and no attempt is made to null-terminate the result.
261 static void copyASCII(const String& string, char* dest)
262 {
263     if (string.isEmpty())
264         return;
265
266     if (string.is8Bit())
267         memcpy(dest, string.characters8(), string.length());
268     else {
269         const UChar* src = string.characters16();
270         size_t length = string.length();
271         for (size_t i = 0; i < length; i++)
272             dest[i] = static_cast<char>(src[i]);
273     }
274 }
275
276 static void appendASCII(const String& base, const char* rel, size_t len, CharBuffer& buffer)
277 {
278     buffer.resize(base.length() + len + 1);
279     copyASCII(base, buffer.data());
280     memcpy(buffer.data() + base.length(), rel, len);
281     buffer[buffer.size() - 1] = '\0';
282 }
283
284 // FIXME: Move to WTFString.h eventually.
285 // Returns the index of the first index in string |s| of any of the characters
286 // in |toFind|. |toFind| should be a null-terminated string, all characters up
287 // to the null will be searched. Returns int if not found.
288 static int findFirstOf(const UChar* s, int sLen, int startPos, const char* toFind)
289 {
290     for (int i = startPos; i < sLen; i++) {
291         const char* cur = toFind;
292         while (*cur) {
293             if (s[i] == *(cur++))
294                 return i;
295         }
296     }
297     return -1;
298 }
299
300 static inline void checkEncodedString(const String& url)
301 {
302     ASSERT_UNUSED(url, url.containsOnlyASCII());
303     ASSERT_UNUSED(url, url.isEmpty() || isSchemeFirstChar(url[0]));
304 }
305
306 inline bool URL::protocolIs(const String& string, const char* protocol)
307 {
308     return WebCore::protocolIs(string, protocol);
309 }
310
311 void URL::invalidate()
312 {
313     m_isValid = false;
314     m_protocolIsInHTTPFamily = false;
315     m_schemeEnd = 0;
316     m_userStart = 0;
317     m_userEnd = 0;
318     m_passwordEnd = 0;
319     m_hostEnd = 0;
320     m_portEnd = 0;
321     m_pathEnd = 0;
322     m_pathAfterLastSlash = 0;
323     m_queryEnd = 0;
324     m_fragmentEnd = 0;
325 }
326
327 URL::URL(ParsedURLStringTag, const String& url)
328 {
329     parse(url);
330     ASSERT(url == m_string);
331 }
332
333 URL::URL(const URL& base, const String& relative)
334 {
335     init(base, relative, UTF8Encoding());
336 }
337
338 URL::URL(const URL& base, const String& relative, const TextEncoding& encoding)
339 {
340     // For UTF-{7,16,32}, we want to use UTF-8 for the query part as 
341     // we do when submitting a form. A form with GET method
342     // has its contents added to a URL as query params and it makes sense
343     // to be consistent.
344     init(base, relative, encoding.encodingForFormSubmission());
345 }
346
347 static bool shouldTrimFromURL(unsigned char c)
348 {
349     // Browsers ignore leading/trailing whitespace and control
350     // characters from URLs.  Note that c is an *unsigned* char here
351     // so this comparison should only catch control characters.
352     return c <= ' ';
353 }
354
355 void URL::init(const URL& base, const String& relative, const TextEncoding& encoding)
356 {
357     // Allow resolutions with a null or empty base URL, but not with any other invalid one.
358     // FIXME: Is this a good rule?
359     if (!base.m_isValid && !base.isEmpty()) {
360         m_string = relative;
361         invalidate();
362         return;
363     }
364
365     // For compatibility with Win IE, treat backslashes as if they were slashes,
366     // as long as we're not dealing with javascript: or data: URLs.
367     String rel = relative;
368     if (rel.contains('\\') && !(protocolIsJavaScript(rel) || protocolIs(rel, "data")))
369         rel = substituteBackslashes(rel);
370
371     bool allASCII = rel.containsOnlyASCII();
372     CharBuffer strBuffer;
373     char* str;
374     size_t len;
375     if (allASCII) {
376         len = rel.length();
377         strBuffer.resize(len + 1);
378         copyASCII(rel, strBuffer.data());
379         strBuffer[len] = 0;
380         str = strBuffer.data();
381     } else {
382         encodeRelativeString(rel, encoding, strBuffer);
383         str = strBuffer.data();
384         len = strlen(str);
385     }
386
387     // Get rid of leading whitespace and control characters.
388     while (len && shouldTrimFromURL(*str)) {
389         str++;
390         --len;
391     }
392
393     // Get rid of trailing whitespace and control characters.
394     while (len && shouldTrimFromURL(str[len - 1]))
395         str[--len] = '\0';
396
397     // According to the RFC, the reference should be interpreted as an
398     // absolute URI if possible, using the "leftmost, longest"
399     // algorithm. If the URI reference is absolute it will have a
400     // scheme, meaning that it will have a colon before the first
401     // non-scheme element.
402     bool absolute = false;
403     char* p = str;
404     if (isSchemeFirstChar(*p)) {
405         ++p;
406         while (isSchemeChar(*p)) {
407             ++p;
408         }
409         if (*p == ':') {
410             if (p[1] != '/' && equalIgnoringCase(base.protocol(), String(str, p - str)) && base.isHierarchical())
411                 str = p + 1;
412             else
413                 absolute = true;
414         }
415     }
416
417     CharBuffer parseBuffer;
418
419     if (absolute) {
420         parse(str, &relative);
421     } else {
422         // If the base is empty or opaque (e.g. data: or javascript:), then the URL is invalid
423         // unless the relative URL is a single fragment.
424         if (!base.isHierarchical()) {
425             if (str[0] == '#') {
426                 appendASCII(base.m_string.left(base.m_queryEnd), str, len, parseBuffer);
427                 parse(parseBuffer.data(), &relative);
428             } else {
429                 m_string = relative;
430                 invalidate();
431             }
432             return;
433         }
434
435         switch (str[0]) {
436         case '\0':
437             // The reference is empty, so this is a reference to the same document with any fragment identifier removed.
438             *this = base;
439             removeFragmentIdentifier();
440             break;
441         case '#': {
442             // must be fragment-only reference
443             appendASCII(base.m_string.left(base.m_queryEnd), str, len, parseBuffer);
444             parse(parseBuffer.data(), &relative);
445             break;
446         }
447         case '?': {
448             // query-only reference, special case needed for non-URL results
449             appendASCII(base.m_string.left(base.m_pathEnd), str, len, parseBuffer);
450             parse(parseBuffer.data(), &relative);
451             break;
452         }
453         case '/':
454             // must be net-path or absolute-path reference
455             if (str[1] == '/') {
456                 // net-path
457                 appendASCII(base.m_string.left(base.m_schemeEnd + 1), str, len, parseBuffer);
458                 parse(parseBuffer.data(), &relative);
459             } else {
460                 // abs-path
461                 appendASCII(base.m_string.left(base.m_portEnd), str, len, parseBuffer);
462                 parse(parseBuffer.data(), &relative);
463             }
464             break;
465         default:
466             {
467                 // must be relative-path reference
468
469                 // Base part plus relative part plus one possible slash added in between plus terminating \0 byte.
470                 const size_t bufferSize = base.m_pathEnd + 1 + len + 1;
471                 parseBuffer.resize(bufferSize);
472
473                 char* bufferPos = parseBuffer.data();
474                 char* bufferStart = bufferPos;
475
476                 // first copy everything before the path from the base
477                 CharBuffer baseStringBuffer(base.m_string.length());
478                 copyASCII(base.m_string, baseStringBuffer.data());
479                 const char* baseString = baseStringBuffer.data();
480                 const char* baseStringStart = baseString;
481                 const char* pathStart = baseStringStart + base.m_portEnd;
482                 while (baseStringStart < pathStart)
483                     *bufferPos++ = *baseStringStart++;
484                 char* bufferPathStart = bufferPos;
485
486                 // now copy the base path
487                 const char* baseStringEnd = baseString + base.m_pathEnd;
488
489                 // go back to the last slash
490                 while (baseStringEnd > baseStringStart && baseStringEnd[-1] != '/')
491                     baseStringEnd--;
492
493                 if (baseStringEnd == baseStringStart) {
494                     // no path in base, add a path separator if necessary
495                     if (base.m_schemeEnd + 1 != base.m_pathEnd && *str && *str != '?' && *str != '#')
496                         *bufferPos++ = '/';
497                 } else {
498                     bufferPos += copyPathRemovingDots(bufferPos, baseStringStart, 0, baseStringEnd - baseStringStart);
499                 }
500
501                 const char* relStringStart = str;
502                 const char* relStringPos = relStringStart;
503
504                 while (*relStringPos && *relStringPos != '?' && *relStringPos != '#') {
505                     if (relStringPos[0] == '.' && bufferPos[-1] == '/') {
506                         if (isPathSegmentEndChar(relStringPos[1])) {
507                             // skip over "." segment
508                             relStringPos += 1;
509                             if (relStringPos[0] == '/')
510                                 relStringPos++;
511                             continue;
512                         } else if (relStringPos[1] == '.' && isPathSegmentEndChar(relStringPos[2])) {
513                             // skip over ".." segment and rewind the last segment
514                             // the RFC leaves it up to the app to decide what to do with excess
515                             // ".." segments - we choose to drop them since some web content
516                             // relies on this.
517                             relStringPos += 2;
518                             if (relStringPos[0] == '/')
519                                 relStringPos++;
520                             if (bufferPos > bufferPathStart + 1)
521                                 bufferPos--;
522                             while (bufferPos > bufferPathStart + 1  && bufferPos[-1] != '/')
523                                 bufferPos--;
524                             continue;
525                         }
526                     }
527
528                     *bufferPos = *relStringPos;
529                     relStringPos++;
530                     bufferPos++;
531                 }
532
533                 // all done with the path work, now copy any remainder
534                 // of the relative reference; this will also add a null terminator
535                 strncpy(bufferPos, relStringPos, bufferSize - (bufferPos - bufferStart));
536
537                 parse(parseBuffer.data(), &relative);
538
539                 ASSERT(strlen(parseBuffer.data()) + 1 <= parseBuffer.size());
540                 break;
541             }
542         }
543     }
544 }
545
546 URL URL::copy() const
547 {
548     URL result = *this;
549     result.m_string = result.m_string.isolatedCopy();
550     return result;
551 }
552
553 String URL::lastPathComponent() const
554 {
555     if (!hasPath())
556         return String();
557
558     unsigned end = m_pathEnd - 1;
559     if (m_string[end] == '/')
560         --end;
561
562     size_t start = m_string.reverseFind('/', end);
563     if (start < static_cast<unsigned>(m_portEnd))
564         return String();
565     ++start;
566
567     return m_string.substring(start, end - start + 1);
568 }
569
570 String URL::protocol() const
571 {
572     return m_string.left(m_schemeEnd);
573 }
574
575 String URL::host() const
576 {
577     int start = hostStart();
578     return decodeURLEscapeSequences(m_string.substring(start, m_hostEnd - start));
579 }
580
581 unsigned short URL::port() const
582 {
583     // We return a port of 0 if there is no port specified. This can happen in two situations:
584     // 1) The URL contains no colon after the host name and before the path component of the URL.
585     // 2) The URL contains a colon but there's no port number before the path component of the URL begins.
586     if (m_hostEnd == m_portEnd || m_hostEnd == m_portEnd - 1)
587         return 0;
588
589     bool ok = false;
590     unsigned number = charactersToUIntStrict(m_string.deprecatedCharacters() + m_hostEnd + 1, m_portEnd - m_hostEnd - 1, &ok);
591     if (!ok || number > maximumValidPortNumber)
592         return invalidPortNumber;
593     return number;
594 }
595
596 String URL::pass() const
597 {
598     if (m_passwordEnd == m_userEnd)
599         return String();
600
601     return decodeURLEscapeSequences(m_string.substring(m_userEnd + 1, m_passwordEnd - m_userEnd - 1)); 
602 }
603
604 String URL::user() const
605 {
606     return decodeURLEscapeSequences(m_string.substring(m_userStart, m_userEnd - m_userStart));
607 }
608
609 String URL::fragmentIdentifier() const
610 {
611     if (m_fragmentEnd == m_queryEnd)
612         return String();
613
614     return m_string.substring(m_queryEnd + 1, m_fragmentEnd - (m_queryEnd + 1));
615 }
616
617 bool URL::hasFragmentIdentifier() const
618 {
619     return m_fragmentEnd != m_queryEnd;
620 }
621
622 String URL::baseAsString() const
623 {
624     return m_string.left(m_pathAfterLastSlash);
625 }
626
627 #if !USE(CF)
628 String URL::fileSystemPath() const
629 {
630     if (!isValid() || !isLocalFile())
631         return String();
632
633     return decodeURLEscapeSequences(path());
634 }
635 #endif
636
637 #ifdef NDEBUG
638
639 static inline void assertProtocolIsGood(const char*)
640 {
641 }
642
643 #else
644
645 static void assertProtocolIsGood(const char* protocol)
646 {
647     const char* p = protocol;
648     while (*p) {
649         ASSERT(*p > ' ' && *p < 0x7F && !(*p >= 'A' && *p <= 'Z'));
650         ++p;
651     }
652 }
653
654 #endif
655
656 bool URL::protocolIs(const char* protocol) const
657 {
658     assertProtocolIsGood(protocol);
659
660     // JavaScript URLs are "valid" and should be executed even if URL decides they are invalid.
661     // The free function protocolIsJavaScript() should be used instead. 
662     ASSERT(!equalIgnoringCase(protocol, String("javascript")));
663
664     if (!m_isValid)
665         return false;
666
667     // Do the comparison without making a new string object.
668     for (int i = 0; i < m_schemeEnd; ++i) {
669         if (!protocol[i] || !isSchemeCharacterMatchIgnoringCase(m_string[i], protocol[i]))
670             return false;
671     }
672     return !protocol[m_schemeEnd]; // We should have consumed all characters in the argument.
673 }
674
675 String URL::query() const
676 {
677     if (m_queryEnd == m_pathEnd)
678         return String();
679
680     return m_string.substring(m_pathEnd + 1, m_queryEnd - (m_pathEnd + 1)); 
681 }
682
683 String URL::path() const
684 {
685     return m_string.substring(m_portEnd, m_pathEnd - m_portEnd);
686 }
687
688 bool URL::setProtocol(const String& s)
689 {
690     // Firefox and IE remove everything after the first ':'.
691     size_t separatorPosition = s.find(':');
692     String newProtocol = s.substring(0, separatorPosition);
693
694     if (!isValidProtocol(newProtocol))
695         return false;
696
697     if (!m_isValid) {
698         parse(newProtocol + ':' + m_string);
699         return true;
700     }
701
702     parse(newProtocol + m_string.substring(m_schemeEnd));
703     return true;
704 }
705
706 void URL::setHost(const String& s)
707 {
708     if (!m_isValid)
709         return;
710
711     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
712     // and to avoid changing more than just the host.
713
714     bool slashSlashNeeded = m_userStart == m_schemeEnd + 1;
715
716     parse(m_string.left(hostStart()) + (slashSlashNeeded ? "//" : "") + s + m_string.substring(m_hostEnd));
717 }
718
719 void URL::removePort()
720 {
721     if (m_hostEnd == m_portEnd)
722         return;
723     parse(m_string.left(m_hostEnd) + m_string.substring(m_portEnd));
724 }
725
726 void URL::setPort(unsigned short i)
727 {
728     if (!m_isValid)
729         return;
730
731     bool colonNeeded = m_portEnd == m_hostEnd;
732     int portStart = (colonNeeded ? m_hostEnd : m_hostEnd + 1);
733
734     parse(m_string.left(portStart) + (colonNeeded ? ":" : "") + String::number(i) + m_string.substring(m_portEnd));
735 }
736
737 void URL::setHostAndPort(const String& hostAndPort)
738 {
739     if (!m_isValid)
740         return;
741
742     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
743     // and to avoid changing more than just host and port.
744
745     bool slashSlashNeeded = m_userStart == m_schemeEnd + 1;
746
747     parse(m_string.left(hostStart()) + (slashSlashNeeded ? "//" : "") + hostAndPort + m_string.substring(m_portEnd));
748 }
749
750 void URL::setUser(const String& user)
751 {
752     if (!m_isValid)
753         return;
754
755     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
756     // and to avoid changing more than just the user login.
757
758     int end = m_userEnd;
759     if (!user.isEmpty()) {
760         String u = user;
761         if (m_userStart == m_schemeEnd + 1)
762             u = "//" + u;
763         // Add '@' if we didn't have one before.
764         if (end == m_hostEnd || (end == m_passwordEnd && m_string[end] != '@'))
765             u.append('@');
766         parse(m_string.left(m_userStart) + u + m_string.substring(end));
767     } else {
768         // Remove '@' if we now have neither user nor password.
769         if (m_userEnd == m_passwordEnd && end != m_hostEnd && m_string[end] == '@')
770             end += 1;
771         // We don't want to parse in the extremely common case where we are not going to make a change.
772         if (m_userStart != end)
773             parse(m_string.left(m_userStart) + m_string.substring(end));
774     }
775 }
776
777 void URL::setPass(const String& password)
778 {
779     if (!m_isValid)
780         return;
781
782     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
783     // and to avoid changing more than just the user password.
784
785     int end = m_passwordEnd;
786     if (!password.isEmpty()) {
787         String p = ":" + password + "@";
788         if (m_userEnd == m_schemeEnd + 1)
789             p = "//" + p;
790         // Eat the existing '@' since we are going to add our own.
791         if (end != m_hostEnd && m_string[end] == '@')
792             end += 1;
793         parse(m_string.left(m_userEnd) + p + m_string.substring(end));
794     } else {
795         // Remove '@' if we now have neither user nor password.
796         if (m_userStart == m_userEnd && end != m_hostEnd && m_string[end] == '@')
797             end += 1;
798         // We don't want to parse in the extremely common case where we are not going to make a change.
799         if (m_userEnd != end)
800             parse(m_string.left(m_userEnd) + m_string.substring(end));
801     }
802 }
803
804 void URL::setFragmentIdentifier(const String& s)
805 {
806     if (!m_isValid)
807         return;
808
809     // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations.
810     parse(m_string.left(m_queryEnd) + "#" + s);
811 }
812
813 void URL::removeFragmentIdentifier()
814 {
815     if (!m_isValid)
816         return;
817     parse(m_string.left(m_queryEnd));
818 }
819     
820 void URL::setQuery(const String& query)
821 {
822     if (!m_isValid)
823         return;
824
825     // FIXME: '#' and non-ASCII characters must be encoded and escaped.
826     // Usually, the query is encoded using document encoding, not UTF-8, but we don't have
827     // access to the document in this function.
828     if ((query.isEmpty() || query[0] != '?') && !query.isNull())
829         parse(m_string.left(m_pathEnd) + "?" + query + m_string.substring(m_queryEnd));
830     else
831         parse(m_string.left(m_pathEnd) + query + m_string.substring(m_queryEnd));
832
833 }
834
835 void URL::setPath(const String& s)
836 {
837     if (!m_isValid)
838         return;
839
840     // FIXME: encodeWithURLEscapeSequences does not correctly escape '#' and '?', so fragment and query parts
841     // may be inadvertently affected.
842     String path = s;
843     if (path.isEmpty() || path[0] != '/')
844         path = "/" + path;
845
846     parse(m_string.left(m_portEnd) + encodeWithURLEscapeSequences(path) + m_string.substring(m_pathEnd));
847 }
848
849 String decodeURLEscapeSequences(const String& string)
850 {
851     return decodeEscapeSequences<URLEscapeSequence>(string, UTF8Encoding());
852 }
853
854 String decodeURLEscapeSequences(const String& string, const TextEncoding& encoding)
855 {
856     return decodeEscapeSequences<URLEscapeSequence>(string, encoding);
857 }
858
859 // Caution: This function does not bounds check.
860 static void appendEscapedChar(char*& buffer, unsigned char c)
861 {
862     *buffer++ = '%';
863     placeByteAsHex(c, buffer);
864 }
865
866 static void appendEscapingBadChars(char*& buffer, const char* strStart, size_t length)
867 {
868     char* p = buffer;
869
870     const char* str = strStart;
871     const char* strEnd = strStart + length;
872     while (str < strEnd) {
873         unsigned char c = *str++;
874         if (isBadChar(c)) {
875             if (c == '%' || c == '?')
876                 *p++ = c;
877             else if (c != 0x09 && c != 0x0a && c != 0x0d)
878                 appendEscapedChar(p, c);
879         } else
880             *p++ = c;
881     }
882
883     buffer = p;
884 }
885
886 static void escapeAndAppendNonHierarchicalPart(char*& buffer, const char* strStart, size_t length)
887 {
888     char* p = buffer;
889
890     const char* str = strStart;
891     const char* strEnd = strStart + length;
892     while (str < strEnd) {
893         unsigned char c = *str++;
894         // Strip CR, LF and Tab from fragments, per:
895         // https://bugs.webkit.org/show_bug.cgi?id=8770
896         if (c == 0x09 || c == 0x0a || c == 0x0d)
897             continue;
898
899         // Chrome and IE allow non-ascii characters in fragments, however doing
900         // so would hit an ASSERT in checkEncodedString, so for now we don't.
901         if (c < 0x20 || c >= 127) {
902             appendEscapedChar(p, c);
903             continue;
904         }
905         *p++ = c;
906     }
907
908     buffer = p;
909 }
910
911 // copy a path, accounting for "." and ".." segments
912 static int copyPathRemovingDots(char* dst, const char* src, int srcStart, int srcEnd)
913 {
914     char* bufferPathStart = dst;
915
916     // empty path is a special case, and need not have a leading slash
917     if (srcStart != srcEnd) {
918         const char* baseStringStart = src + srcStart;
919         const char* baseStringEnd = src + srcEnd;
920         const char* baseStringPos = baseStringStart;
921
922         // this code is unprepared for paths that do not begin with a
923         // slash and we should always have one in the source string
924         ASSERT(baseStringPos[0] == '/');
925
926         // copy the leading slash into the destination
927         *dst = *baseStringPos;
928         baseStringPos++;
929         dst++;
930
931         while (baseStringPos < baseStringEnd) {
932             if (baseStringPos[0] == '.' && dst[-1] == '/') {
933                 if (baseStringPos[1] == '/' || baseStringPos + 1 == baseStringEnd) {
934                     // skip over "." segment
935                     baseStringPos += 2;
936                     continue;
937                 } else if (baseStringPos[1] == '.' && (baseStringPos[2] == '/' ||
938                                        baseStringPos + 2 == baseStringEnd)) {
939                     // skip over ".." segment and rewind the last segment
940                     // the RFC leaves it up to the app to decide what to do with excess
941                     // ".." segments - we choose to drop them since some web content
942                     // relies on this.
943                     baseStringPos += 3;
944                     if (dst > bufferPathStart + 1)
945                         dst--;
946                     while (dst > bufferPathStart && dst[-1] != '/')
947                         dst--;
948                     continue;
949                 }
950             }
951
952             *dst = *baseStringPos;
953             baseStringPos++;
954             dst++;
955         }
956     }
957     *dst = '\0';
958     return dst - bufferPathStart;
959 }
960
961 static inline bool hasSlashDotOrDotDot(const char* str)
962 {
963     const unsigned char* p = reinterpret_cast<const unsigned char*>(str);
964     if (!*p)
965         return false;
966     unsigned char pc = *p;
967     while (unsigned char c = *++p) {
968         if (c == '.' && (pc == '/' || pc == '.'))
969             return true;
970         pc = c;
971     }
972     return false;
973 }
974
975 void URL::parse(const String& string)
976 {
977     checkEncodedString(string);
978
979     CharBuffer buffer(string.length() + 1);
980     copyASCII(string, buffer.data());
981     buffer[string.length()] = '\0';
982     parse(buffer.data(), &string);
983 }
984
985 #if PLATFORM(IOS)
986 static bool shouldCanonicalizeScheme = true;
987
988 void enableURLSchemeCanonicalization(bool enableSchemeCanonicalization)
989 {
990     shouldCanonicalizeScheme = enableSchemeCanonicalization;
991 }
992 #endif
993
994 template<size_t length>
995 static inline bool equal(const char* a, const char (&b)[length])
996 {
997 #if PLATFORM(IOS)
998     if (!shouldCanonicalizeScheme) {
999         for (size_t i = 0; i < length; ++i) {
1000             if (toASCIILower(a[i]) != b[i])
1001                 return false;
1002         }
1003         return true;
1004     }
1005 #endif
1006     for (size_t i = 0; i < length; ++i) {
1007         if (a[i] != b[i])
1008             return false;
1009     }
1010     return true;
1011 }
1012
1013 template<size_t lengthB>
1014 static inline bool equal(const char* stringA, size_t lengthA, const char (&stringB)[lengthB])
1015 {
1016     return lengthA == lengthB && equal(stringA, stringB);
1017 }
1018
1019 // List of default schemes is taken from google-url:
1020 // http://code.google.com/p/google-url/source/browse/trunk/src/url_canon_stdurl.cc#120
1021 static inline bool isDefaultPortForScheme(const char* port, size_t portLength, const char* scheme, size_t schemeLength)
1022 {
1023     // This switch is theoretically a performance optimization.  It came over when
1024     // the code was moved from google-url, but may be removed later.
1025     switch (schemeLength) {
1026     case 2:
1027         return equal(scheme, wsScheme) && equal(port, portLength, httpPort);
1028     case 3:
1029         if (equal(scheme, ftpScheme))
1030             return equal(port, portLength, ftpPort);
1031         if (equal(scheme, wssScheme))
1032             return equal(port, portLength, httpsPort);
1033         break;
1034     case 4:
1035         return equal(scheme, httpScheme) && equal(port, portLength, httpPort);
1036     case 5:
1037         return equal(scheme, httpsScheme) && equal(port, portLength, httpsPort);
1038     case 6:
1039         return equal(scheme, gopherScheme) && equal(port, portLength, gopherPort);
1040     }
1041     return false;
1042 }
1043
1044 static inline bool hostPortIsEmptyButCredentialsArePresent(int hostStart, int portEnd, char userinfoEndChar)
1045 {
1046     return userinfoEndChar == '@' && hostStart == portEnd;
1047 }
1048
1049 static bool isNonFileHierarchicalScheme(const char* scheme, size_t schemeLength)
1050 {
1051     switch (schemeLength) {
1052     case 2:
1053         return equal(scheme, wsScheme);
1054     case 3:
1055         return equal(scheme, ftpScheme) || equal(scheme, wssScheme);
1056     case 4:
1057         return equal(scheme, httpScheme);
1058     case 5:
1059         return equal(scheme, httpsScheme);
1060     case 6:
1061         return equal(scheme, gopherScheme);
1062     }
1063     return false;
1064 }
1065
1066 static bool isCanonicalHostnameLowercaseForScheme(const char* scheme, size_t schemeLength)
1067 {
1068     switch (schemeLength) {
1069     case 2:
1070         return equal(scheme, wsScheme);
1071     case 3:
1072         return equal(scheme, ftpScheme) || equal(scheme, wssScheme);
1073     case 4:
1074         return equal(scheme, httpScheme) || equal(scheme, fileScheme);
1075     case 5:
1076         return equal(scheme, httpsScheme);
1077     case 6:
1078         return equal(scheme, gopherScheme);
1079     }
1080     return false;
1081 }
1082
1083 void URL::parse(const char* url, const String* originalString)
1084 {
1085     if (!url || url[0] == '\0') {
1086         // valid URL must be non-empty
1087         m_string = originalString ? *originalString : url;
1088         invalidate();
1089         return;
1090     }
1091
1092     if (!isSchemeFirstChar(url[0])) {
1093         // scheme must start with an alphabetic character
1094         m_string = originalString ? *originalString : url;
1095         invalidate();
1096         return;
1097     }
1098
1099     int schemeEnd = 0;
1100     while (isSchemeChar(url[schemeEnd]))
1101         schemeEnd++;
1102
1103     if (url[schemeEnd] != ':') {
1104         m_string = originalString ? *originalString : url;
1105         invalidate();
1106         return;
1107     }
1108
1109     int userStart = schemeEnd + 1;
1110     int userEnd;
1111     int passwordStart;
1112     int passwordEnd;
1113     int hostStart;
1114     int hostEnd;
1115     int portStart;
1116     int portEnd;
1117
1118     bool hierarchical = url[schemeEnd + 1] == '/';
1119     bool hasSecondSlash = hierarchical && url[schemeEnd + 2] == '/';
1120
1121     bool isFile = schemeEnd == 4
1122         && isLetterMatchIgnoringCase(url[0], 'f')
1123         && isLetterMatchIgnoringCase(url[1], 'i')
1124         && isLetterMatchIgnoringCase(url[2], 'l')
1125         && isLetterMatchIgnoringCase(url[3], 'e');
1126
1127     m_protocolIsInHTTPFamily = isLetterMatchIgnoringCase(url[0], 'h')
1128         && isLetterMatchIgnoringCase(url[1], 't')
1129         && isLetterMatchIgnoringCase(url[2], 't')
1130         && isLetterMatchIgnoringCase(url[3], 'p')
1131         && (url[4] == ':' || (isLetterMatchIgnoringCase(url[4], 's') && url[5] == ':'));
1132
1133     if ((hierarchical && hasSecondSlash) || isNonFileHierarchicalScheme(url, schemeEnd)) {
1134         // The part after the scheme is either a net_path or an abs_path whose first path segment is empty.
1135         // Attempt to find an authority.
1136         // FIXME: Authority characters may be scanned twice, and it would be nice to be faster.
1137
1138         if (hierarchical)
1139             userStart++;
1140         if (hasSecondSlash)
1141             userStart++;
1142         userEnd = userStart;
1143
1144         int colonPos = 0;
1145         while (isUserInfoChar(url[userEnd])) {
1146             if (url[userEnd] == ':' && colonPos == 0)
1147                 colonPos = userEnd;
1148             userEnd++;
1149         }
1150
1151         if (url[userEnd] == '@') {
1152             // actual end of the userinfo, start on the host
1153             if (colonPos != 0) {
1154                 passwordEnd = userEnd;
1155                 userEnd = colonPos;
1156                 passwordStart = colonPos + 1;
1157             } else
1158                 passwordStart = passwordEnd = userEnd;
1159
1160             hostStart = passwordEnd + 1;
1161         } else if (url[userEnd] == '[' || isPathSegmentEndChar(url[userEnd])) {
1162             // hit the end of the authority, must have been no user
1163             // or looks like an IPv6 hostname
1164             // either way, try to parse it as a hostname
1165             userEnd = userStart;
1166             passwordStart = passwordEnd = userEnd;
1167             hostStart = userStart;
1168         } else {
1169             // invalid character
1170             m_string = originalString ? *originalString : url;
1171             invalidate();
1172             return;
1173         }
1174
1175         hostEnd = hostStart;
1176
1177         // IPV6 IP address
1178         if (url[hostEnd] == '[') {
1179             hostEnd++;
1180             while (isIPv6Char(url[hostEnd]))
1181                 hostEnd++;
1182             if (url[hostEnd] == ']')
1183                 hostEnd++;
1184             else {
1185                 // invalid character
1186                 m_string = originalString ? *originalString : url;
1187                 invalidate();
1188                 return;
1189             }
1190         } else {
1191             while (isHostnameChar(url[hostEnd]))
1192                 hostEnd++;
1193         }
1194         
1195         if (url[hostEnd] == ':') {
1196             portStart = portEnd = hostEnd + 1;
1197  
1198             // possible start of port
1199             portEnd = portStart;
1200             while (isASCIIDigit(url[portEnd]))
1201                 portEnd++;
1202         } else
1203             portStart = portEnd = hostEnd;
1204
1205         if (!isPathSegmentEndChar(url[portEnd])) {
1206             // invalid character
1207             m_string = originalString ? *originalString : url;
1208             invalidate();
1209             return;
1210         }
1211
1212         if (hostPortIsEmptyButCredentialsArePresent(hostStart, portEnd, url[passwordEnd])) {
1213             m_string = originalString ? *originalString : url;
1214             invalidate();
1215             return;
1216         }
1217
1218         if (userStart == portEnd && !m_protocolIsInHTTPFamily && !isFile) {
1219             // No authority found, which means that this is not a net_path, but rather an abs_path whose first two
1220             // path segments are empty. For file, http and https only, an empty authority is allowed.
1221             userStart -= 2;
1222             userEnd = userStart;
1223             passwordStart = userEnd;
1224             passwordEnd = passwordStart;
1225             hostStart = passwordEnd;
1226             hostEnd = hostStart;
1227             portStart = hostEnd;
1228             portEnd = hostEnd;
1229         }
1230     } else {
1231         // the part after the scheme must be an opaque_part or an abs_path
1232         userEnd = userStart;
1233         passwordStart = passwordEnd = userEnd;
1234         hostStart = hostEnd = passwordEnd;
1235         portStart = portEnd = hostEnd;
1236     }
1237
1238     int pathStart = portEnd;
1239     int pathEnd = pathStart;
1240     while (url[pathEnd] && url[pathEnd] != '?' && url[pathEnd] != '#')
1241         pathEnd++;
1242
1243     int queryStart = pathEnd;
1244     int queryEnd = queryStart;
1245     if (url[queryStart] == '?') {
1246         while (url[queryEnd] && url[queryEnd] != '#')
1247             queryEnd++;
1248     }
1249
1250     int fragmentStart = queryEnd;
1251     int fragmentEnd = fragmentStart;
1252     if (url[fragmentStart] == '#') {
1253         fragmentStart++;
1254         fragmentEnd = fragmentStart;
1255         while (url[fragmentEnd])
1256             fragmentEnd++;
1257     }
1258
1259     // assemble it all, remembering the real ranges
1260
1261     Vector<char, 4096> buffer(fragmentEnd * 3 + 1);
1262
1263     char *p = buffer.data();
1264     const char *strPtr = url;
1265
1266     // copy in the scheme
1267     const char *schemeEndPtr = url + schemeEnd;
1268 #if PLATFORM(IOS)
1269     if (shouldCanonicalizeScheme || m_protocolIsInHTTPFamily) {
1270         while (strPtr < schemeEndPtr)
1271             *p++ = toASCIILower(*strPtr++);
1272     } else {
1273         while (strPtr < schemeEndPtr)
1274             *p++ = *strPtr++;
1275     }
1276 #else
1277     while (strPtr < schemeEndPtr)
1278         *p++ = toASCIILower(*strPtr++);
1279 #endif
1280     m_schemeEnd = p - buffer.data();
1281
1282     bool hostIsLocalHost = portEnd - userStart == 9
1283         && isLetterMatchIgnoringCase(url[userStart], 'l')
1284         && isLetterMatchIgnoringCase(url[userStart+1], 'o')
1285         && isLetterMatchIgnoringCase(url[userStart+2], 'c')
1286         && isLetterMatchIgnoringCase(url[userStart+3], 'a')
1287         && isLetterMatchIgnoringCase(url[userStart+4], 'l')
1288         && isLetterMatchIgnoringCase(url[userStart+5], 'h')
1289         && isLetterMatchIgnoringCase(url[userStart+6], 'o')
1290         && isLetterMatchIgnoringCase(url[userStart+7], 's')
1291         && isLetterMatchIgnoringCase(url[userStart+8], 't');
1292
1293     // File URLs need a host part unless it is just file:// or file://localhost
1294     bool degenerateFilePath = pathStart == pathEnd && (hostStart == hostEnd || hostIsLocalHost);
1295
1296     // We drop empty credentials, but keep a colon in an empty host/port pair.
1297     // Removing hostname completely would change the structure of the URL on re-parsing.
1298     bool haveNonHostAuthorityPart = userStart != userEnd || passwordStart != passwordEnd || hostEnd != portEnd;
1299
1300     // add ":" after scheme
1301     *p++ = ':';
1302
1303     // if we have at least one authority part or a file URL - add "//" and authority
1304     if (isFile ? !degenerateFilePath : (haveNonHostAuthorityPart || hostStart != hostEnd)) {
1305         *p++ = '/';
1306         *p++ = '/';
1307
1308         m_userStart = p - buffer.data();
1309
1310         // copy in the user
1311         strPtr = url + userStart;
1312         const char* userEndPtr = url + userEnd;
1313         while (strPtr < userEndPtr) {
1314             char c = *strPtr++;
1315             ASSERT(isUserInfoChar(c));
1316             *p++ = c;
1317         }
1318         m_userEnd = p - buffer.data();
1319
1320         // copy in the password
1321         if (passwordEnd != passwordStart) {
1322             *p++ = ':';
1323             strPtr = url + passwordStart;
1324             const char* passwordEndPtr = url + passwordEnd;
1325             while (strPtr < passwordEndPtr) {
1326                 char c = *strPtr++;
1327                 ASSERT(isUserInfoChar(c));
1328                 *p++ = c;
1329             }
1330         }
1331         m_passwordEnd = p - buffer.data();
1332
1333         // If we had any user info, add "@"
1334         if (p - buffer.data() != m_userStart)
1335             *p++ = '@';
1336
1337         // copy in the host, except in the case of a file URL with authority="localhost"
1338         if (!(isFile && hostIsLocalHost && !haveNonHostAuthorityPart)) {
1339             strPtr = url + hostStart;
1340             const char* hostEndPtr = url + hostEnd;
1341             if (isCanonicalHostnameLowercaseForScheme(buffer.data(), m_schemeEnd)) {
1342                 while (strPtr < hostEndPtr) {
1343                     char c = toASCIILower(*strPtr++);
1344                     ASSERT(isHostnameChar(c) || c == '[' || c == ']' || c == ':');
1345                     *p++ = c;
1346                 }
1347             } else {
1348                 while (strPtr < hostEndPtr) {
1349                     char c = *strPtr++;
1350                     ASSERT(isHostnameChar(c) || c == '[' || c == ']' || c == ':');
1351                     *p++ = c;
1352                 }
1353             }
1354         }
1355         m_hostEnd = p - buffer.data();
1356
1357         // Copy in the port if the URL has one (and it's not default). Also, copy it if there was no hostname, so that there is still something in authority component.
1358         if (hostEnd != portStart) {
1359             const char* portStr = url + portStart;
1360             size_t portLength = portEnd - portStart;
1361             if ((portLength && !isDefaultPortForScheme(portStr, portLength, buffer.data(), m_schemeEnd))
1362                 || (hostStart == hostEnd && hostEnd != portStart)) {
1363                 *p++ = ':';
1364                 const char* portEndPtr = url + portEnd;
1365                 while (portStr < portEndPtr)
1366                     *p++ = *portStr++;
1367             }
1368         }
1369         m_portEnd = p - buffer.data();
1370     } else {
1371         if (isFile) {
1372             ASSERT(degenerateFilePath);
1373             *p++ = '/';
1374             *p++ = '/';
1375         }
1376         m_userStart = m_userEnd = m_passwordEnd = m_hostEnd = m_portEnd = p - buffer.data();
1377     }
1378
1379     // For canonicalization, ensure we have a '/' for no path.
1380     // Do this only for URL with protocol file, http or https.
1381     if ((m_protocolIsInHTTPFamily || isFile) && pathEnd == pathStart)
1382         *p++ = '/';
1383
1384     // add path, escaping bad characters
1385     if (!hierarchical)
1386         escapeAndAppendNonHierarchicalPart(p, url + pathStart, pathEnd - pathStart);
1387     else if (!hasSlashDotOrDotDot(url))
1388         appendEscapingBadChars(p, url + pathStart, pathEnd - pathStart);
1389     else {
1390         CharBuffer pathBuffer(pathEnd - pathStart + 1);
1391         size_t length = copyPathRemovingDots(pathBuffer.data(), url, pathStart, pathEnd);
1392         appendEscapingBadChars(p, pathBuffer.data(), length);
1393     }
1394
1395     m_pathEnd = p - buffer.data();
1396
1397     // Find the position after the last slash in the path, or
1398     // the position before the path if there are no slashes in it.
1399     int i;
1400     for (i = m_pathEnd; i > m_portEnd; --i) {
1401         if (buffer[i - 1] == '/')
1402             break;
1403     }
1404     m_pathAfterLastSlash = i;
1405
1406     // add query, escaping bad characters
1407     appendEscapingBadChars(p, url + queryStart, queryEnd - queryStart);
1408     m_queryEnd = p - buffer.data();
1409
1410     // add fragment, escaping bad characters
1411     if (fragmentEnd != queryEnd) {
1412         *p++ = '#';
1413         escapeAndAppendNonHierarchicalPart(p, url + fragmentStart, fragmentEnd - fragmentStart);
1414     }
1415     m_fragmentEnd = p - buffer.data();
1416
1417     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1418     ASSERT(buffer.size() > 0);
1419
1420     // If we didn't end up actually changing the original string and
1421     // it was already in a String, reuse it to avoid extra allocation.
1422     if (originalString && equal(originalString->impl(), buffer.data(), m_fragmentEnd))
1423         m_string = *originalString;
1424     else
1425         m_string = String(buffer.data(), m_fragmentEnd);
1426
1427     m_isValid = true;
1428 }
1429
1430 bool equalIgnoringFragmentIdentifier(const URL& a, const URL& b)
1431 {
1432     if (a.m_queryEnd != b.m_queryEnd)
1433         return false;
1434     unsigned queryLength = a.m_queryEnd;
1435     for (unsigned i = 0; i < queryLength; ++i)
1436         if (a.string()[i] != b.string()[i])
1437             return false;
1438     return true;
1439 }
1440
1441 bool protocolHostAndPortAreEqual(const URL& a, const URL& b)
1442 {
1443     if (a.m_schemeEnd != b.m_schemeEnd)
1444         return false;
1445
1446     int hostStartA = a.hostStart();
1447     int hostLengthA = a.hostEnd() - hostStartA;
1448     int hostStartB = b.hostStart();
1449     int hostLengthB = b.hostEnd() - b.hostStart();
1450     if (hostLengthA != hostLengthB)
1451         return false;
1452
1453     // Check the scheme
1454     for (int i = 0; i < a.m_schemeEnd; ++i)
1455         if (a.string()[i] != b.string()[i])
1456             return false;
1457
1458     // And the host
1459     for (int i = 0; i < hostLengthA; ++i)
1460         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
1461             return false;
1462
1463     if (a.port() != b.port())
1464         return false;
1465
1466     return true;
1467 }
1468
1469 String encodeWithURLEscapeSequences(const String& notEncodedString)
1470 {
1471     CString asUTF8 = notEncodedString.utf8();
1472
1473     CharBuffer buffer(asUTF8.length() * 3 + 1);
1474     char* p = buffer.data();
1475
1476     const char* str = asUTF8.data();
1477     const char* strEnd = str + asUTF8.length();
1478     while (str < strEnd) {
1479         unsigned char c = *str++;
1480         if (isBadChar(c))
1481             appendEscapedChar(p, c);
1482         else
1483             *p++ = c;
1484     }
1485
1486     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1487
1488     return String(buffer.data(), p - buffer.data());
1489 }
1490
1491 // Appends the punycoded hostname identified by the given string and length to
1492 // the output buffer. The result will not be null terminated.
1493 static void appendEncodedHostname(UCharBuffer& buffer, const UChar* str, unsigned strLen)
1494 {
1495     // Needs to be big enough to hold an IDN-encoded name.
1496     // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
1497     const unsigned hostnameBufferLength = 2048;
1498
1499     if (strLen > hostnameBufferLength || charactersAreAllASCII(str, strLen)) {
1500         buffer.append(str, strLen);
1501         return;
1502     }
1503
1504     UChar hostnameBuffer[hostnameBufferLength];
1505     UErrorCode error = U_ZERO_ERROR;
1506     int32_t numCharactersConverted = uidna_IDNToASCII(str, strLen, hostnameBuffer,
1507         hostnameBufferLength, UIDNA_ALLOW_UNASSIGNED, 0, &error);
1508     if (error == U_ZERO_ERROR)
1509         buffer.append(hostnameBuffer, numCharactersConverted);
1510 }
1511
1512 static void findHostnamesInMailToURL(const UChar* str, int strLen, Vector<std::pair<int, int>>& nameRanges)
1513 {
1514     // In a mailto: URL, host names come after a '@' character and end with a '>' or ',' or '?' or end of string character.
1515     // Skip quoted strings so that characters in them don't confuse us.
1516     // When we find a '?' character, we are past the part of the URL that contains host names.
1517
1518     nameRanges.clear();
1519
1520     int p = 0;
1521     while (1) {
1522         // Find start of host name or of quoted string.
1523         int hostnameOrStringStart = findFirstOf(str, strLen, p, "\"@?");
1524         if (hostnameOrStringStart == -1)
1525             return;
1526         UChar c = str[hostnameOrStringStart];
1527         p = hostnameOrStringStart + 1;
1528
1529         if (c == '?')
1530             return;
1531
1532         if (c == '@') {
1533             // Find end of host name.
1534             int hostnameStart = p;
1535             int hostnameEnd = findFirstOf(str, strLen, p, ">,?");
1536             bool done;
1537             if (hostnameEnd == -1) {
1538                 hostnameEnd = strLen;
1539                 done = true;
1540             } else {
1541                 p = hostnameEnd;
1542                 done = false;
1543             }
1544
1545             nameRanges.append(std::make_pair(hostnameStart, hostnameEnd));
1546
1547             if (done)
1548                 return;
1549         } else {
1550             // Skip quoted string.
1551             ASSERT(c == '"');
1552             while (1) {
1553                 int escapedCharacterOrStringEnd = findFirstOf(str, strLen, p, "\"\\");
1554                 if (escapedCharacterOrStringEnd == -1)
1555                     return;
1556
1557                 c = str[escapedCharacterOrStringEnd];
1558                 p = escapedCharacterOrStringEnd + 1;
1559
1560                 // If we are the end of the string, then break from the string loop back to the host name loop.
1561                 if (c == '"')
1562                     break;
1563
1564                 // Skip escaped character.
1565                 ASSERT(c == '\\');
1566                 if (p == strLen)
1567                     return;
1568
1569                 ++p;
1570             }
1571         }
1572     }
1573 }
1574
1575 static bool findHostnameInHierarchicalURL(const UChar* str, int strLen, int& startOffset, int& endOffset)
1576 {
1577     // Find the host name in a hierarchical URL.
1578     // It comes after a "://" sequence, with scheme characters preceding, and
1579     // this should be the first colon in the string.
1580     // It ends with the end of the string or a ":" or a path segment ending character.
1581     // If there is a "@" character, the host part is just the part after the "@".
1582     int separator = findFirstOf(str, strLen, 0, ":");
1583     if (separator == -1 || separator + 2 >= strLen ||
1584         str[separator + 1] != '/' || str[separator + 2] != '/')
1585         return false;
1586
1587     // Check that all characters before the :// are valid scheme characters.
1588     if (!isSchemeFirstChar(str[0]))
1589         return false;
1590     for (int i = 1; i < separator; ++i) {
1591         if (!isSchemeChar(str[i]))
1592             return false;
1593     }
1594
1595     // Start after the separator.
1596     int authorityStart = separator + 3;
1597
1598     // Find terminating character.
1599     int hostnameEnd = strLen;
1600     for (int i = authorityStart; i < strLen; ++i) {
1601         UChar c = str[i];
1602         if (c == ':' || (isPathSegmentEndChar(c) && c != 0)) {
1603             hostnameEnd = i;
1604             break;
1605         }
1606     }
1607
1608     // Find "@" for the start of the host name.
1609     int userInfoTerminator = findFirstOf(str, strLen, authorityStart, "@");
1610     int hostnameStart;
1611     if (userInfoTerminator == -1 || userInfoTerminator > hostnameEnd)
1612         hostnameStart = authorityStart;
1613     else
1614         hostnameStart = userInfoTerminator + 1;
1615
1616     startOffset = hostnameStart;
1617     endOffset = hostnameEnd;
1618     return true;
1619 }
1620
1621 // Converts all hostnames found in the given input to punycode, preserving the
1622 // rest of the URL unchanged. The output will NOT be null-terminated.
1623 static void encodeHostnames(const String& str, UCharBuffer& output)
1624 {
1625     output.clear();
1626
1627     if (protocolIs(str, "mailto")) {
1628         Vector<std::pair<int, int>> hostnameRanges;
1629         findHostnamesInMailToURL(str.deprecatedCharacters(), str.length(), hostnameRanges);
1630         int n = hostnameRanges.size();
1631         int p = 0;
1632         for (int i = 0; i < n; ++i) {
1633             const std::pair<int, int>& r = hostnameRanges[i];
1634             output.append(&str.deprecatedCharacters()[p], r.first - p);
1635             appendEncodedHostname(output, &str.deprecatedCharacters()[r.first], r.second - r.first);
1636             p = r.second;
1637         }
1638         // This will copy either everything after the last hostname, or the
1639         // whole thing if there is no hostname.
1640         output.append(&str.deprecatedCharacters()[p], str.length() - p);
1641     } else {
1642         int hostStart, hostEnd;
1643         if (findHostnameInHierarchicalURL(str.deprecatedCharacters(), str.length(), hostStart, hostEnd)) {
1644             output.append(str.deprecatedCharacters(), hostStart); // Before hostname.
1645             appendEncodedHostname(output, &str.deprecatedCharacters()[hostStart], hostEnd - hostStart);
1646             output.append(&str.deprecatedCharacters()[hostEnd], str.length() - hostEnd); // After hostname.
1647         } else {
1648             // No hostname to encode, return the input.
1649             output.append(str.deprecatedCharacters(), str.length());
1650         }
1651     }
1652 }
1653
1654 static void encodeRelativeString(const String& rel, const TextEncoding& encoding, CharBuffer& output)
1655 {
1656     UCharBuffer s;
1657     encodeHostnames(rel, s);
1658
1659     TextEncoding pathEncoding(UTF8Encoding()); // Path is always encoded as UTF-8; other parts may depend on the scheme.
1660
1661     int pathEnd = -1;
1662     if (encoding != pathEncoding && encoding.isValid() && !protocolIs(rel, "mailto") && !protocolIs(rel, "data") && !protocolIsJavaScript(rel)) {
1663         // Find the first instance of either # or ?, keep pathEnd at -1 otherwise.
1664         pathEnd = findFirstOf(s.data(), s.size(), 0, "#?");
1665     }
1666
1667     if (pathEnd == -1) {
1668         CString decoded = pathEncoding.encode(s.data(), s.size(), URLEncodedEntitiesForUnencodables);
1669         output.resize(decoded.length());
1670         memcpy(output.data(), decoded.data(), decoded.length());
1671     } else {
1672         CString pathDecoded = pathEncoding.encode(s.data(), pathEnd, URLEncodedEntitiesForUnencodables);
1673         // Unencodable characters in URLs are represented by converting
1674         // them to XML entities and escaping non-alphanumeric characters.
1675         CString otherDecoded = encoding.encode(s.data() + pathEnd, s.size() - pathEnd, URLEncodedEntitiesForUnencodables);
1676
1677         output.resize(pathDecoded.length() + otherDecoded.length());
1678         memcpy(output.data(), pathDecoded.data(), pathDecoded.length());
1679         memcpy(output.data() + pathDecoded.length(), otherDecoded.data(), otherDecoded.length());
1680     }
1681     output.append('\0'); // null-terminate the output.
1682 }
1683
1684 static String substituteBackslashes(const String& string)
1685 {
1686     size_t questionPos = string.find('?');
1687     size_t hashPos = string.find('#');
1688     unsigned pathEnd;
1689
1690     if (hashPos != notFound && (questionPos == notFound || questionPos > hashPos))
1691         pathEnd = hashPos;
1692     else if (questionPos != notFound)
1693         pathEnd = questionPos;
1694     else
1695         pathEnd = string.length();
1696
1697     return string.left(pathEnd).replace('\\','/') + string.substring(pathEnd);
1698 }
1699
1700 bool URL::isHierarchical() const
1701 {
1702     if (!m_isValid)
1703         return false;
1704     ASSERT(m_string[m_schemeEnd] == ':');
1705     return m_string[m_schemeEnd + 1] == '/';
1706 }
1707
1708 void URL::copyToBuffer(Vector<char, 512>& buffer) const
1709 {
1710     // FIXME: This throws away the high bytes of all the characters in the string!
1711     // That's fine for a valid URL, which is all ASCII, but not for invalid URLs.
1712     buffer.resize(m_string.length());
1713     copyASCII(m_string, buffer.data());
1714 }
1715
1716 bool protocolIs(const String& url, const char* protocol)
1717 {
1718     // Do the comparison without making a new string object.
1719     assertProtocolIsGood(protocol);
1720     for (int i = 0; ; ++i) {
1721         if (!protocol[i])
1722             return url[i] == ':';
1723         if (!isLetterMatchIgnoringCase(url[i], protocol[i]))
1724             return false;
1725     }
1726 }
1727
1728 bool isValidProtocol(const String& protocol)
1729 {
1730     // RFC3986: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1731     if (protocol.isEmpty())
1732         return false;
1733     if (!isSchemeFirstChar(protocol[0]))
1734         return false;
1735     unsigned protocolLength = protocol.length();
1736     for (unsigned i = 1; i < protocolLength; i++) {
1737         if (!isSchemeChar(protocol[i]))
1738             return false;
1739     }
1740     return true;
1741 }
1742
1743 #ifndef NDEBUG
1744 void URL::print() const
1745 {
1746     printf("%s\n", m_string.utf8().data());
1747 }
1748 #endif
1749
1750 String URL::strippedForUseAsReferrer() const
1751 {
1752     URL referrer(*this);
1753     referrer.setUser(String());
1754     referrer.setPass(String());
1755     referrer.removeFragmentIdentifier();
1756     return referrer.string();
1757 }
1758
1759 bool URL::isLocalFile() const
1760 {
1761     // Including feed here might be a bad idea since drag and drop uses this check
1762     // and including feed would allow feeds to potentially let someone's blog
1763     // read the contents of the clipboard on a drag, even without a drop.
1764     // Likewise with using the FrameLoader::shouldTreatURLAsLocal() function.
1765     return protocolIs("file");
1766 }
1767
1768 bool protocolIsJavaScript(const String& url)
1769 {
1770     return protocolIs(url, "javascript");
1771 }
1772
1773 bool protocolIsInHTTPFamily(const String& url)
1774 {
1775     // Do the comparison without making a new string object.
1776     return isLetterMatchIgnoringCase(url[0], 'h')
1777         && isLetterMatchIgnoringCase(url[1], 't')
1778         && isLetterMatchIgnoringCase(url[2], 't')
1779         && isLetterMatchIgnoringCase(url[3], 'p')
1780         && (url[4] == ':' || (isLetterMatchIgnoringCase(url[4], 's') && url[5] == ':'));
1781 }
1782
1783 const URL& blankURL()
1784 {
1785     DEFINE_STATIC_LOCAL(URL, staticBlankURL, (ParsedURLString, "about:blank"));
1786     return staticBlankURL;
1787 }
1788
1789 bool URL::isBlankURL() const
1790 {
1791     return protocolIs("about");
1792 }
1793
1794 bool isDefaultPortForProtocol(unsigned short port, const String& protocol)
1795 {
1796     if (protocol.isEmpty())
1797         return false;
1798
1799     typedef HashMap<String, unsigned, CaseFoldingHash> DefaultPortsMap;
1800     DEFINE_STATIC_LOCAL(DefaultPortsMap, defaultPorts, ());
1801     if (defaultPorts.isEmpty()) {
1802         defaultPorts.set("http", 80);
1803         defaultPorts.set("https", 443);
1804         defaultPorts.set("ftp", 21);
1805         defaultPorts.set("ftps", 990);
1806     }
1807     return defaultPorts.get(protocol) == port;
1808 }
1809
1810 bool portAllowed(const URL& url)
1811 {
1812     unsigned short port = url.port();
1813
1814     // Since most URLs don't have a port, return early for the "no port" case.
1815     if (!port)
1816         return true;
1817
1818     // This blocked port list matches the port blocking that Mozilla implements.
1819     // See http://www.mozilla.org/projects/netlib/PortBanning.html for more information.
1820     static const unsigned short blockedPortList[] = {
1821         1,    // tcpmux
1822         7,    // echo
1823         9,    // discard
1824         11,   // systat
1825         13,   // daytime
1826         15,   // netstat
1827         17,   // qotd
1828         19,   // chargen
1829         20,   // FTP-data
1830         21,   // FTP-control
1831         22,   // SSH
1832         23,   // telnet
1833         25,   // SMTP
1834         37,   // time
1835         42,   // name
1836         43,   // nicname
1837         53,   // domain
1838         77,   // priv-rjs
1839         79,   // finger
1840         87,   // ttylink
1841         95,   // supdup
1842         101,  // hostriame
1843         102,  // iso-tsap
1844         103,  // gppitnp
1845         104,  // acr-nema
1846         109,  // POP2
1847         110,  // POP3
1848         111,  // sunrpc
1849         113,  // auth
1850         115,  // SFTP
1851         117,  // uucp-path
1852         119,  // nntp
1853         123,  // NTP
1854         135,  // loc-srv / epmap
1855         139,  // netbios
1856         143,  // IMAP2
1857         179,  // BGP
1858         389,  // LDAP
1859         465,  // SMTP+SSL
1860         512,  // print / exec
1861         513,  // login
1862         514,  // shell
1863         515,  // printer
1864         526,  // tempo
1865         530,  // courier
1866         531,  // Chat
1867         532,  // netnews
1868         540,  // UUCP
1869         556,  // remotefs
1870         563,  // NNTP+SSL
1871         587,  // ESMTP
1872         601,  // syslog-conn
1873         636,  // LDAP+SSL
1874         993,  // IMAP+SSL
1875         995,  // POP3+SSL
1876         2049, // NFS
1877         3659, // apple-sasl / PasswordServer [Apple addition]
1878         4045, // lockd
1879         6000, // X11
1880         6665, // Alternate IRC [Apple addition]
1881         6666, // Alternate IRC [Apple addition]
1882         6667, // Standard IRC [Apple addition]
1883         6668, // Alternate IRC [Apple addition]
1884         6669, // Alternate IRC [Apple addition]
1885         invalidPortNumber, // Used to block all invalid port numbers
1886     };
1887     const unsigned short* const blockedPortListEnd = blockedPortList + WTF_ARRAY_LENGTH(blockedPortList);
1888
1889 #ifndef NDEBUG
1890     // The port list must be sorted for binary_search to work.
1891     static bool checkedPortList = false;
1892     if (!checkedPortList) {
1893         for (const unsigned short* p = blockedPortList; p != blockedPortListEnd - 1; ++p)
1894             ASSERT(*p < *(p + 1));
1895         checkedPortList = true;
1896     }
1897 #endif
1898
1899     // If the port is not in the blocked port list, allow it.
1900     if (!std::binary_search(blockedPortList, blockedPortListEnd, port))
1901         return true;
1902
1903     // Allow ports 21 and 22 for FTP URLs, as Mozilla does.
1904     if ((port == 21 || port == 22) && url.protocolIs("ftp"))
1905         return true;
1906
1907     // Allow any port number in a file URL, since the port number is ignored.
1908     if (url.protocolIs("file"))
1909         return true;
1910
1911     return false;
1912 }
1913
1914 String mimeTypeFromDataURL(const String& url)
1915 {
1916     ASSERT(protocolIs(url, "data"));
1917     size_t index = url.find(';');
1918     if (index == notFound)
1919         index = url.find(',');
1920     if (index != notFound) {
1921         if (index > 5)
1922             return url.substring(5, index - 5).lower();
1923         return "text/plain"; // Data URLs with no MIME type are considered text/plain.
1924     }
1925     return "";
1926 }
1927
1928 String mimeTypeFromURL(const URL& url)
1929 {
1930     String decodedPath = decodeURLEscapeSequences(url.path());
1931     String extension = decodedPath.substring(decodedPath.reverseFind('.') + 1);
1932
1933     // We don't use MIMETypeRegistry::getMIMETypeForPath() because it returns "application/octet-stream" upon failure
1934     return MIMETypeRegistry::getMIMETypeForExtension(extension);
1935 }
1936
1937 bool URL::isSafeToSendToAnotherThread() const
1938 {
1939     return m_string.isSafeToSendToAnotherThread();
1940 }
1941
1942 String URL::stringCenterEllipsizedToLength(unsigned length) const
1943 {
1944     if (string().length() <= length)
1945         return string();
1946
1947     return string().left(length / 2 - 1) + "..." + string().right(length / 2 - 2);
1948 }
1949
1950 }