a747f39c8be46d1abc14a4cbc3f1665505e4772f
[WebKit-https.git] / Source / WebCore / platform / KURL.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 "KURL.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 KURL::protocolIs(const String& string, const char* protocol)
311 {
312     return WebCore::protocolIs(string, protocol);
313 }
314
315 void KURL::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 KURL::KURL(ParsedURLStringTag, const String& url)
332 {
333     parse(url);
334     ASSERT(url == m_string);
335 }
336
337 KURL::KURL(const KURL& base, const String& relative)
338 {
339     init(base, relative, UTF8Encoding());
340 }
341
342 KURL::KURL(const KURL& 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 KURL::init(const KURL& 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 KURL KURL::copy() const
551 {
552     KURL result = *this;
553     result.m_string = result.m_string.isolatedCopy();
554     return result;
555 }
556
557 String KURL::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 KURL::protocol() const
575 {
576     return m_string.left(m_schemeEnd);
577 }
578
579 String KURL::host() const
580 {
581     int start = hostStart();
582     return decodeURLEscapeSequences(m_string.substring(start, m_hostEnd - start));
583 }
584
585 unsigned short KURL::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 KURL::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 KURL::user() const
610 {
611     return decodeURLEscapeSequences(m_string.substring(m_userStart, m_userEnd - m_userStart));
612 }
613
614 String KURL::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 KURL::hasFragmentIdentifier() const
623 {
624     return m_fragmentEnd != m_queryEnd;
625 }
626
627 String KURL::baseAsString() const
628 {
629     return m_string.left(m_pathAfterLastSlash);
630 }
631
632 #if !PLATFORM(QT) && !USE(CF)
633 String KURL::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 KURL::protocolIs(const char* protocol) const
662 {
663     assertProtocolIsGood(protocol);
664
665     // JavaScript URLs are "valid" and should be executed even if KURL 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 KURL::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 KURL::path() const
689 {
690     return m_string.substring(m_portEnd, m_pathEnd - m_portEnd);
691 }
692
693 bool KURL::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 KURL::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 KURL::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 KURL::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 KURL::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 KURL::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 KURL::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 KURL::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 KURL::removeFragmentIdentifier()
819 {
820     if (!m_isValid)
821         return;
822     parse(m_string.left(m_queryEnd));
823 }
824     
825 void KURL::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 KURL::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 KURL::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 userEndChar)
1032 {
1033     return userEndChar == '@' && 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 KURL::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[userEnd])) {
1211             // in this circumstance, act as if there is an erroneous hostname containing an '@'
1212             userEnd = userStart;
1213             hostStart = userEnd;
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     bool haveNonHostAuthorityPart = userStart != userEnd || passwordStart != passwordEnd || portStart != portEnd;
1285
1286     // add ":" after scheme
1287     *p++ = ':';
1288
1289     // if we have at least one authority part or a file URL - add "//" and authority
1290     if (isFile ? !degenerateFilePath : (haveNonHostAuthorityPart || hostStart != hostEnd)) {
1291         *p++ = '/';
1292         *p++ = '/';
1293
1294         m_userStart = p - buffer.data();
1295
1296         // copy in the user
1297         strPtr = url + userStart;
1298         const char* userEndPtr = url + userEnd;
1299         while (strPtr < userEndPtr)
1300             *p++ = *strPtr++;
1301         m_userEnd = p - buffer.data();
1302
1303         // copy in the password
1304         if (passwordEnd != passwordStart) {
1305             *p++ = ':';
1306             strPtr = url + passwordStart;
1307             const char* passwordEndPtr = url + passwordEnd;
1308             while (strPtr < passwordEndPtr)
1309                 *p++ = *strPtr++;
1310         }
1311         m_passwordEnd = p - buffer.data();
1312
1313         // If we had any user info, add "@"
1314         if (p - buffer.data() != m_userStart)
1315             *p++ = '@';
1316
1317         // copy in the host, except in the case of a file URL with authority="localhost"
1318         if (!(isFile && hostIsLocalHost && !haveNonHostAuthorityPart)) {
1319             strPtr = url + hostStart;
1320             const char* hostEndPtr = url + hostEnd;
1321             if (isCanonicalHostnameLowercaseForScheme(buffer.data(), m_schemeEnd)) {
1322                 while (strPtr < hostEndPtr)
1323                     *p++ = toASCIILower(*strPtr++);
1324             } else {
1325                 while (strPtr < hostEndPtr)
1326                     *p++ = *strPtr++;
1327             }
1328         }
1329         m_hostEnd = p - buffer.data();
1330
1331         // Copy in the port if the URL has one (and it's not default).
1332         if (hostEnd != portStart) {
1333             const char* portStr = url + portStart;
1334             size_t portLength = portEnd - portStart;
1335             if (portLength && !isDefaultPortForScheme(portStr, portLength, buffer.data(), m_schemeEnd)) {
1336                 *p++ = ':';
1337                 const char* portEndPtr = url + portEnd;
1338                 while (portStr < portEndPtr)
1339                     *p++ = *portStr++;
1340             }
1341         }
1342         m_portEnd = p - buffer.data();
1343     } else {
1344         if (isFile) {
1345             ASSERT(degenerateFilePath);
1346             *p++ = '/';
1347             *p++ = '/';
1348         }
1349         m_userStart = m_userEnd = m_passwordEnd = m_hostEnd = m_portEnd = p - buffer.data();
1350     }
1351
1352     // For canonicalization, ensure we have a '/' for no path.
1353     // Do this only for URL with protocol file, http or https.
1354     if ((m_protocolIsInHTTPFamily || isFile) && pathEnd == pathStart)
1355         *p++ = '/';
1356
1357     // add path, escaping bad characters
1358     if (!hierarchical)
1359         escapeAndAppendNonHierarchicalPart(p, url + pathStart, pathEnd - pathStart);
1360     else if (!hasSlashDotOrDotDot(url))
1361         appendEscapingBadChars(p, url + pathStart, pathEnd - pathStart);
1362     else {
1363         CharBuffer pathBuffer(pathEnd - pathStart + 1);
1364         size_t length = copyPathRemovingDots(pathBuffer.data(), url, pathStart, pathEnd);
1365         appendEscapingBadChars(p, pathBuffer.data(), length);
1366     }
1367
1368     m_pathEnd = p - buffer.data();
1369
1370     // Find the position after the last slash in the path, or
1371     // the position before the path if there are no slashes in it.
1372     int i;
1373     for (i = m_pathEnd; i > m_portEnd; --i) {
1374         if (buffer[i - 1] == '/')
1375             break;
1376     }
1377     m_pathAfterLastSlash = i;
1378
1379     // add query, escaping bad characters
1380     appendEscapingBadChars(p, url + queryStart, queryEnd - queryStart);
1381     m_queryEnd = p - buffer.data();
1382
1383     // add fragment, escaping bad characters
1384     if (fragmentEnd != queryEnd) {
1385         *p++ = '#';
1386         escapeAndAppendNonHierarchicalPart(p, url + fragmentStart, fragmentEnd - fragmentStart);
1387     }
1388     m_fragmentEnd = p - buffer.data();
1389
1390     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1391     ASSERT(buffer.size() > 0);
1392
1393     // If we didn't end up actually changing the original string and
1394     // it was already in a String, reuse it to avoid extra allocation.
1395     if (originalString && *originalString == buffer)
1396         m_string = *originalString;
1397     else
1398         m_string = String(buffer.data(), m_fragmentEnd);
1399
1400     m_isValid = true;
1401 }
1402
1403 bool equalIgnoringFragmentIdentifier(const KURL& a, const KURL& b)
1404 {
1405     if (a.m_queryEnd != b.m_queryEnd)
1406         return false;
1407     unsigned queryLength = a.m_queryEnd;
1408     for (unsigned i = 0; i < queryLength; ++i)
1409         if (a.string()[i] != b.string()[i])
1410             return false;
1411     return true;
1412 }
1413
1414 bool protocolHostAndPortAreEqual(const KURL& a, const KURL& b)
1415 {
1416     if (a.m_schemeEnd != b.m_schemeEnd)
1417         return false;
1418
1419     int hostStartA = a.hostStart();
1420     int hostLengthA = a.hostEnd() - hostStartA;
1421     int hostStartB = b.hostStart();
1422     int hostLengthB = b.hostEnd() - b.hostStart();
1423     if (hostLengthA != hostLengthB)
1424         return false;
1425
1426     // Check the scheme
1427     for (int i = 0; i < a.m_schemeEnd; ++i)
1428         if (a.string()[i] != b.string()[i])
1429             return false;
1430
1431     // And the host
1432     for (int i = 0; i < hostLengthA; ++i)
1433         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
1434             return false;
1435
1436     if (a.port() != b.port())
1437         return false;
1438
1439     return true;
1440 }
1441
1442 String encodeWithURLEscapeSequences(const String& notEncodedString)
1443 {
1444     CString asUTF8 = notEncodedString.utf8();
1445
1446     CharBuffer buffer(asUTF8.length() * 3 + 1);
1447     char* p = buffer.data();
1448
1449     const char* str = asUTF8.data();
1450     const char* strEnd = str + asUTF8.length();
1451     while (str < strEnd) {
1452         unsigned char c = *str++;
1453         if (isBadChar(c))
1454             appendEscapedChar(p, c);
1455         else
1456             *p++ = c;
1457     }
1458
1459     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1460
1461     return String(buffer.data(), p - buffer.data());
1462 }
1463
1464 // Appends the punycoded hostname identified by the given string and length to
1465 // the output buffer. The result will not be null terminated.
1466 static void appendEncodedHostname(UCharBuffer& buffer, const UChar* str, unsigned strLen)
1467 {
1468     // Needs to be big enough to hold an IDN-encoded name.
1469     // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
1470     const unsigned hostnameBufferLength = 2048;
1471
1472     if (strLen > hostnameBufferLength || charactersAreAllASCII(str, strLen)) {
1473         buffer.append(str, strLen);
1474         return;
1475     }
1476
1477 #if USE(ICU_UNICODE)
1478     UChar hostnameBuffer[hostnameBufferLength];
1479     UErrorCode error = U_ZERO_ERROR;
1480     int32_t numCharactersConverted = uidna_IDNToASCII(str, strLen, hostnameBuffer,
1481         hostnameBufferLength, UIDNA_ALLOW_UNASSIGNED, 0, &error);
1482     if (error == U_ZERO_ERROR)
1483         buffer.append(hostnameBuffer, numCharactersConverted);
1484 #endif
1485 }
1486
1487 static void findHostnamesInMailToURL(const UChar* str, int strLen, Vector<pair<int, int> >& nameRanges)
1488 {
1489     // In a mailto: URL, host names come after a '@' character and end with a '>' or ',' or '?' or end of string character.
1490     // Skip quoted strings so that characters in them don't confuse us.
1491     // When we find a '?' character, we are past the part of the URL that contains host names.
1492
1493     nameRanges.clear();
1494
1495     int p = 0;
1496     while (1) {
1497         // Find start of host name or of quoted string.
1498         int hostnameOrStringStart = findFirstOf(str, strLen, p, "\"@?");
1499         if (hostnameOrStringStart == -1)
1500             return;
1501         UChar c = str[hostnameOrStringStart];
1502         p = hostnameOrStringStart + 1;
1503
1504         if (c == '?')
1505             return;
1506
1507         if (c == '@') {
1508             // Find end of host name.
1509             int hostnameStart = p;
1510             int hostnameEnd = findFirstOf(str, strLen, p, ">,?");
1511             bool done;
1512             if (hostnameEnd == -1) {
1513                 hostnameEnd = strLen;
1514                 done = true;
1515             } else {
1516                 p = hostnameEnd;
1517                 done = false;
1518             }
1519
1520             nameRanges.append(make_pair(hostnameStart, hostnameEnd));
1521
1522             if (done)
1523                 return;
1524         } else {
1525             // Skip quoted string.
1526             ASSERT(c == '"');
1527             while (1) {
1528                 int escapedCharacterOrStringEnd = findFirstOf(str, strLen, p, "\"\\");
1529                 if (escapedCharacterOrStringEnd == -1)
1530                     return;
1531
1532                 c = str[escapedCharacterOrStringEnd];
1533                 p = escapedCharacterOrStringEnd + 1;
1534
1535                 // If we are the end of the string, then break from the string loop back to the host name loop.
1536                 if (c == '"')
1537                     break;
1538
1539                 // Skip escaped character.
1540                 ASSERT(c == '\\');
1541                 if (p == strLen)
1542                     return;
1543
1544                 ++p;
1545             }
1546         }
1547     }
1548 }
1549
1550 static bool findHostnameInHierarchicalURL(const UChar* str, int strLen, int& startOffset, int& endOffset)
1551 {
1552     // Find the host name in a hierarchical URL.
1553     // It comes after a "://" sequence, with scheme characters preceding, and
1554     // this should be the first colon in the string.
1555     // It ends with the end of the string or a ":" or a path segment ending character.
1556     // If there is a "@" character, the host part is just the part after the "@".
1557     int separator = findFirstOf(str, strLen, 0, ":");
1558     if (separator == -1 || separator + 2 >= strLen ||
1559         str[separator + 1] != '/' || str[separator + 2] != '/')
1560         return false;
1561
1562     // Check that all characters before the :// are valid scheme characters.
1563     if (!isSchemeFirstChar(str[0]))
1564         return false;
1565     for (int i = 1; i < separator; ++i) {
1566         if (!isSchemeChar(str[i]))
1567             return false;
1568     }
1569
1570     // Start after the separator.
1571     int authorityStart = separator + 3;
1572
1573     // Find terminating character.
1574     int hostnameEnd = strLen;
1575     for (int i = authorityStart; i < strLen; ++i) {
1576         UChar c = str[i];
1577         if (c == ':' || (isPathSegmentEndChar(c) && c != 0)) {
1578             hostnameEnd = i;
1579             break;
1580         }
1581     }
1582
1583     // Find "@" for the start of the host name.
1584     int userInfoTerminator = findFirstOf(str, strLen, authorityStart, "@");
1585     int hostnameStart;
1586     if (userInfoTerminator == -1 || userInfoTerminator > hostnameEnd)
1587         hostnameStart = authorityStart;
1588     else
1589         hostnameStart = userInfoTerminator + 1;
1590
1591     startOffset = hostnameStart;
1592     endOffset = hostnameEnd;
1593     return true;
1594 }
1595
1596 // Converts all hostnames found in the given input to punycode, preserving the
1597 // rest of the URL unchanged. The output will NOT be null-terminated.
1598 static void encodeHostnames(const String& str, UCharBuffer& output)
1599 {
1600     output.clear();
1601
1602     if (protocolIs(str, "mailto")) {
1603         Vector<pair<int, int> > hostnameRanges;
1604         findHostnamesInMailToURL(str.characters(), str.length(), hostnameRanges);
1605         int n = hostnameRanges.size();
1606         int p = 0;
1607         for (int i = 0; i < n; ++i) {
1608             const pair<int, int>& r = hostnameRanges[i];
1609             output.append(&str.characters()[p], r.first - p);
1610             appendEncodedHostname(output, &str.characters()[r.first], r.second - r.first);
1611             p = r.second;
1612         }
1613         // This will copy either everything after the last hostname, or the
1614         // whole thing if there is no hostname.
1615         output.append(&str.characters()[p], str.length() - p);
1616     } else {
1617         int hostStart, hostEnd;
1618         if (findHostnameInHierarchicalURL(str.characters(), str.length(), hostStart, hostEnd)) {
1619             output.append(str.characters(), hostStart); // Before hostname.
1620             appendEncodedHostname(output, &str.characters()[hostStart], hostEnd - hostStart);
1621             output.append(&str.characters()[hostEnd], str.length() - hostEnd); // After hostname.
1622         } else {
1623             // No hostname to encode, return the input.
1624             output.append(str.characters(), str.length());
1625         }
1626     }
1627 }
1628
1629 static void encodeRelativeString(const String& rel, const TextEncoding& encoding, CharBuffer& output)
1630 {
1631     UCharBuffer s;
1632     encodeHostnames(rel, s);
1633
1634     TextEncoding pathEncoding(UTF8Encoding()); // Path is always encoded as UTF-8; other parts may depend on the scheme.
1635
1636     int pathEnd = -1;
1637     if (encoding != pathEncoding && encoding.isValid() && !protocolIs(rel, "mailto") && !protocolIs(rel, "data") && !protocolIsJavaScript(rel)) {
1638         // Find the first instance of either # or ?, keep pathEnd at -1 otherwise.
1639         pathEnd = findFirstOf(s.data(), s.size(), 0, "#?");
1640     }
1641
1642     if (pathEnd == -1) {
1643         CString decoded = pathEncoding.encode(s.data(), s.size(), URLEncodedEntitiesForUnencodables);
1644         output.resize(decoded.length());
1645         memcpy(output.data(), decoded.data(), decoded.length());
1646     } else {
1647         CString pathDecoded = pathEncoding.encode(s.data(), pathEnd, URLEncodedEntitiesForUnencodables);
1648         // Unencodable characters in URLs are represented by converting
1649         // them to XML entities and escaping non-alphanumeric characters.
1650         CString otherDecoded = encoding.encode(s.data() + pathEnd, s.size() - pathEnd, URLEncodedEntitiesForUnencodables);
1651
1652         output.resize(pathDecoded.length() + otherDecoded.length());
1653         memcpy(output.data(), pathDecoded.data(), pathDecoded.length());
1654         memcpy(output.data() + pathDecoded.length(), otherDecoded.data(), otherDecoded.length());
1655     }
1656     output.append('\0'); // null-terminate the output.
1657 }
1658
1659 static String substituteBackslashes(const String& string)
1660 {
1661     size_t questionPos = string.find('?');
1662     size_t hashPos = string.find('#');
1663     unsigned pathEnd;
1664
1665     if (hashPos != notFound && (questionPos == notFound || questionPos > hashPos))
1666         pathEnd = hashPos;
1667     else if (questionPos != notFound)
1668         pathEnd = questionPos;
1669     else
1670         pathEnd = string.length();
1671
1672     return string.left(pathEnd).replace('\\','/') + string.substring(pathEnd);
1673 }
1674
1675 bool KURL::isHierarchical() const
1676 {
1677     if (!m_isValid)
1678         return false;
1679     ASSERT(m_string[m_schemeEnd] == ':');
1680     return m_string[m_schemeEnd + 1] == '/';
1681 }
1682
1683 void KURL::copyToBuffer(CharBuffer& buffer) const
1684 {
1685     // FIXME: This throws away the high bytes of all the characters in the string!
1686     // That's fine for a valid URL, which is all ASCII, but not for invalid URLs.
1687     buffer.resize(m_string.length());
1688     copyASCII(m_string, buffer.data());
1689 }
1690
1691 bool protocolIs(const String& url, const char* protocol)
1692 {
1693     // Do the comparison without making a new string object.
1694     assertProtocolIsGood(protocol);
1695     for (int i = 0; ; ++i) {
1696         if (!protocol[i])
1697             return url[i] == ':';
1698         if (!isLetterMatchIgnoringCase(url[i], protocol[i]))
1699             return false;
1700     }
1701 }
1702
1703 bool isValidProtocol(const String& protocol)
1704 {
1705     // RFC3986: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1706     if (protocol.isEmpty())
1707         return false;
1708     if (!isSchemeFirstChar(protocol[0]))
1709         return false;
1710     unsigned protocolLength = protocol.length();
1711     for (unsigned i = 1; i < protocolLength; i++) {
1712         if (!isSchemeChar(protocol[i]))
1713             return false;
1714     }
1715     return true;
1716 }
1717
1718 #ifndef NDEBUG
1719 void KURL::print() const
1720 {
1721     printf("%s\n", m_string.utf8().data());
1722 }
1723 #endif
1724
1725 String KURL::strippedForUseAsReferrer() const
1726 {
1727     KURL referrer(*this);
1728     referrer.setUser(String());
1729     referrer.setPass(String());
1730     referrer.removeFragmentIdentifier();
1731     return referrer.string();
1732 }
1733
1734 bool KURL::isLocalFile() const
1735 {
1736     // Including feed here might be a bad idea since drag and drop uses this check
1737     // and including feed would allow feeds to potentially let someone's blog
1738     // read the contents of the clipboard on a drag, even without a drop.
1739     // Likewise with using the FrameLoader::shouldTreatURLAsLocal() function.
1740     return protocolIs("file");
1741 }
1742
1743 bool protocolIsJavaScript(const String& url)
1744 {
1745     return protocolIs(url, "javascript");
1746 }
1747
1748 const KURL& blankURL()
1749 {
1750     DEFINE_STATIC_LOCAL(KURL, staticBlankURL, (ParsedURLString, "about:blank"));
1751     return staticBlankURL;
1752 }
1753
1754 bool KURL::isBlankURL() const
1755 {
1756     return protocolIs("about");
1757 }
1758
1759 bool isDefaultPortForProtocol(unsigned short port, const String& protocol)
1760 {
1761     if (protocol.isEmpty())
1762         return false;
1763
1764     typedef HashMap<String, unsigned, CaseFoldingHash> DefaultPortsMap;
1765     DEFINE_STATIC_LOCAL(DefaultPortsMap, defaultPorts, ());
1766     if (defaultPorts.isEmpty()) {
1767         defaultPorts.set("http", 80);
1768         defaultPorts.set("https", 443);
1769         defaultPorts.set("ftp", 21);
1770         defaultPorts.set("ftps", 990);
1771     }
1772     return defaultPorts.get(protocol) == port;
1773 }
1774
1775 bool portAllowed(const KURL& url)
1776 {
1777     unsigned short port = url.port();
1778
1779     // Since most URLs don't have a port, return early for the "no port" case.
1780     if (!port)
1781         return true;
1782
1783     // This blocked port list matches the port blocking that Mozilla implements.
1784     // See http://www.mozilla.org/projects/netlib/PortBanning.html for more information.
1785     static const unsigned short blockedPortList[] = {
1786         1,    // tcpmux
1787         7,    // echo
1788         9,    // discard
1789         11,   // systat
1790         13,   // daytime
1791         15,   // netstat
1792         17,   // qotd
1793         19,   // chargen
1794         20,   // FTP-data
1795         21,   // FTP-control
1796         22,   // SSH
1797         23,   // telnet
1798         25,   // SMTP
1799         37,   // time
1800         42,   // name
1801         43,   // nicname
1802         53,   // domain
1803         77,   // priv-rjs
1804         79,   // finger
1805         87,   // ttylink
1806         95,   // supdup
1807         101,  // hostriame
1808         102,  // iso-tsap
1809         103,  // gppitnp
1810         104,  // acr-nema
1811         109,  // POP2
1812         110,  // POP3
1813         111,  // sunrpc
1814         113,  // auth
1815         115,  // SFTP
1816         117,  // uucp-path
1817         119,  // nntp
1818         123,  // NTP
1819         135,  // loc-srv / epmap
1820         139,  // netbios
1821         143,  // IMAP2
1822         179,  // BGP
1823         389,  // LDAP
1824         465,  // SMTP+SSL
1825         512,  // print / exec
1826         513,  // login
1827         514,  // shell
1828         515,  // printer
1829         526,  // tempo
1830         530,  // courier
1831         531,  // Chat
1832         532,  // netnews
1833         540,  // UUCP
1834         556,  // remotefs
1835         563,  // NNTP+SSL
1836         587,  // ESMTP
1837         601,  // syslog-conn
1838         636,  // LDAP+SSL
1839         993,  // IMAP+SSL
1840         995,  // POP3+SSL
1841         2049, // NFS
1842         3659, // apple-sasl / PasswordServer [Apple addition]
1843         4045, // lockd
1844         6000, // X11
1845         6665, // Alternate IRC [Apple addition]
1846         6666, // Alternate IRC [Apple addition]
1847         6667, // Standard IRC [Apple addition]
1848         6668, // Alternate IRC [Apple addition]
1849         6669, // Alternate IRC [Apple addition]
1850         invalidPortNumber, // Used to block all invalid port numbers
1851     };
1852     const unsigned short* const blockedPortListEnd = blockedPortList + WTF_ARRAY_LENGTH(blockedPortList);
1853
1854 #ifndef NDEBUG
1855     // The port list must be sorted for binary_search to work.
1856     static bool checkedPortList = false;
1857     if (!checkedPortList) {
1858         for (const unsigned short* p = blockedPortList; p != blockedPortListEnd - 1; ++p)
1859             ASSERT(*p < *(p + 1));
1860         checkedPortList = true;
1861     }
1862 #endif
1863
1864     // If the port is not in the blocked port list, allow it.
1865     if (!binary_search(blockedPortList, blockedPortListEnd, port))
1866         return true;
1867
1868     // Allow ports 21 and 22 for FTP URLs, as Mozilla does.
1869     if ((port == 21 || port == 22) && url.protocolIs("ftp"))
1870         return true;
1871
1872     // Allow any port number in a file URL, since the port number is ignored.
1873     if (url.protocolIs("file"))
1874         return true;
1875
1876 #if PLATFORM(BLACKBERRY)
1877     if (url.protocolIs("local"))
1878         return true;
1879 #endif
1880
1881     return false;
1882 }
1883
1884 String mimeTypeFromDataURL(const String& url)
1885 {
1886     ASSERT(protocolIs(url, "data"));
1887     size_t index = url.find(';');
1888     if (index == notFound)
1889         index = url.find(',');
1890     if (index != notFound) {
1891         if (index > 5)
1892             return url.substring(5, index - 5).lower();
1893         return "text/plain"; // Data URLs with no MIME type are considered text/plain.
1894     }
1895     return "";
1896 }
1897
1898 String mimeTypeFromURL(const KURL& url)
1899 {
1900     String decodedPath = decodeURLEscapeSequences(url.path());
1901     String extension = decodedPath.substring(decodedPath.reverseFind('.') + 1);
1902
1903     // We don't use MIMETypeRegistry::getMIMETypeForPath() because it returns "application/octet-stream" upon failure
1904     return MIMETypeRegistry::getMIMETypeForExtension(extension);
1905 }
1906
1907 bool KURL::isSafeToSendToAnotherThread() const
1908 {
1909     return m_string.isSafeToSendToAnotherThread();
1910 }
1911
1912 String KURL::elidedString() const
1913 {
1914     if (string().length() <= 1024)
1915         return string();
1916
1917     return string().left(511) + "..." + string().right(510);
1918 }
1919
1920 }