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