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