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