c24925b6c02e04daac73639d09e50751070e7016
[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     // https://webkit.org/b/161176
1041     if ((query.isEmpty() || query[0] != '?') && !query.isNull())
1042         parse(m_string.left(m_pathEnd) + "?" + query + m_string.substring(m_queryEnd));
1043     else
1044         parse(m_string.left(m_pathEnd) + query + m_string.substring(m_queryEnd));
1045
1046 }
1047
1048 void URL::setPath(const String& s)
1049 {
1050     if (!m_isValid)
1051         return;
1052
1053     // FIXME: encodeWithURLEscapeSequences does not correctly escape '#' and '?', so fragment and query parts
1054     // may be inadvertently affected.
1055     String path = s;
1056     if (path.isEmpty() || path[0] != '/')
1057         path = "/" + path;
1058
1059     parse(m_string.left(m_portEnd) + encodeWithURLEscapeSequences(path) + m_string.substring(m_pathEnd));
1060 }
1061
1062 String decodeURLEscapeSequences(const String& string)
1063 {
1064     return decodeEscapeSequences<URLEscapeSequence>(string, UTF8Encoding());
1065 }
1066
1067 String decodeURLEscapeSequences(const String& string, const TextEncoding& encoding)
1068 {
1069     return decodeEscapeSequences<URLEscapeSequence>(string, encoding);
1070 }
1071
1072 // Caution: This function does not bounds check.
1073 static void appendEscapedChar(char*& buffer, unsigned char c)
1074 {
1075     *buffer++ = '%';
1076     placeByteAsHex(c, buffer);
1077 }
1078
1079 static void appendEscapingBadChars(char*& buffer, const char* strStart, size_t length)
1080 {
1081     char* p = buffer;
1082
1083     const char* str = strStart;
1084     const char* strEnd = strStart + length;
1085     while (str < strEnd) {
1086         unsigned char c = *str++;
1087         if (isBadChar(c)) {
1088             if (c == '%' || c == '?')
1089                 *p++ = c;
1090             else if (c != 0x09 && c != 0x0a && c != 0x0d)
1091                 appendEscapedChar(p, c);
1092         } else
1093             *p++ = c;
1094     }
1095
1096     buffer = p;
1097 }
1098
1099 static void escapeAndAppendNonHierarchicalPart(char*& buffer, const char* strStart, size_t length)
1100 {
1101     char* p = buffer;
1102
1103     const char* str = strStart;
1104     const char* strEnd = strStart + length;
1105     while (str < strEnd) {
1106         unsigned char c = *str++;
1107         // Strip CR, LF and Tab from fragments, per:
1108         // https://bugs.webkit.org/show_bug.cgi?id=8770
1109         if (c == 0x09 || c == 0x0a || c == 0x0d)
1110             continue;
1111
1112         // Chrome and IE allow non-ascii characters in fragments, however doing
1113         // so would hit an ASSERT in checkEncodedString, so for now we don't.
1114         if (c < 0x20 || c >= 127) {
1115             appendEscapedChar(p, c);
1116             continue;
1117         }
1118         *p++ = c;
1119     }
1120
1121     buffer = p;
1122 }
1123
1124 // copy a path, accounting for "." and ".." segments
1125 static unsigned copyPathRemovingDots(char* dst, const char* src, unsigned srcStart, unsigned srcEnd)
1126 {
1127     char* bufferPathStart = dst;
1128
1129     // empty path is a special case, and need not have a leading slash
1130     if (srcStart != srcEnd) {
1131         const char* baseStringStart = src + srcStart;
1132         const char* baseStringEnd = src + srcEnd;
1133         const char* baseStringPos = baseStringStart;
1134
1135         // this code is unprepared for paths that do not begin with a
1136         // slash and we should always have one in the source string
1137         ASSERT(baseStringPos[0] == '/');
1138
1139         // copy the leading slash into the destination
1140         *dst = *baseStringPos;
1141         baseStringPos++;
1142         dst++;
1143
1144         while (baseStringPos < baseStringEnd) {
1145             if (baseStringPos[0] == '.' && dst[-1] == '/') {
1146                 if (baseStringPos[1] == '/' || baseStringPos + 1 == baseStringEnd) {
1147                     // skip over "." segment
1148                     baseStringPos += 2;
1149                     continue;
1150                 } else if (baseStringPos[1] == '.' && (baseStringPos[2] == '/' ||
1151                                        baseStringPos + 2 == baseStringEnd)) {
1152                     // skip over ".." segment and rewind the last segment
1153                     // the RFC leaves it up to the app to decide what to do with excess
1154                     // ".." segments - we choose to drop them since some web content
1155                     // relies on this.
1156                     baseStringPos += 3;
1157                     if (dst > bufferPathStart + 1)
1158                         dst--;
1159                     while (dst > bufferPathStart && dst[-1] != '/')
1160                         dst--;
1161                     continue;
1162                 }
1163             }
1164
1165             *dst = *baseStringPos;
1166             baseStringPos++;
1167             dst++;
1168         }
1169     }
1170     *dst = '\0';
1171     return dst - bufferPathStart;
1172 }
1173
1174 static inline bool hasSlashDotOrDotDot(const char* str)
1175 {
1176     const unsigned char* p = reinterpret_cast<const unsigned char*>(str);
1177     if (!*p)
1178         return false;
1179     unsigned char pc = *p;
1180     while (unsigned char c = *++p) {
1181         if (c == '.' && (pc == '/' || pc == '.'))
1182             return true;
1183         pc = c;
1184     }
1185     return false;
1186 }
1187
1188 void URL::parse(const String& string)
1189 {
1190     checkEncodedString(string);
1191
1192     CharBuffer buffer(string.length() + 1);
1193     copyASCII(string, buffer.data());
1194     buffer[string.length()] = '\0';
1195     parse(buffer.data(), &string);
1196 }
1197
1198 static inline bool cannotBeABaseURL(const URL& url)
1199 {
1200     // FIXME: Support https://url.spec.whatwg.org/#url-cannot-be-a-base-url-flag properly
1201     // According spec, this should be computed at parsing time.
1202     // For the moment, we just check whether the scheme is special or not.
1203     if (url.protocolIs("ftp") || url.protocolIs("file") || url.protocolIs("gopher") || url.protocolIs("http") || url.protocolIs("https") || url.protocolIs("ws") || url.protocolIs("wss"))
1204         return false;
1205     return true;
1206 }
1207
1208 // Implementation of https://url.spec.whatwg.org/#url-serializing
1209 String URL::serialize(bool omitFragment) const
1210 {
1211     if (isNull())
1212         return String();
1213
1214     StringBuilder urlBuilder;
1215     urlBuilder.append(m_string, 0, m_schemeEnd);
1216     urlBuilder.appendLiteral(":");
1217     unsigned start = hostStart();
1218     if (start < m_hostEnd) {
1219         urlBuilder.appendLiteral("//");
1220         if (hasUsername()) {
1221             urlBuilder.append(m_string, m_userStart, m_userEnd - m_userStart);
1222             unsigned passwordStart = m_userEnd + 1;
1223             if (hasPassword()) {
1224                 urlBuilder.appendLiteral(":");
1225                 urlBuilder.append(m_string, passwordStart, m_passwordEnd - passwordStart);
1226             }
1227             urlBuilder.appendLiteral("@");
1228         }
1229         // FIXME: Serialize host according https://url.spec.whatwg.org/#concept-host-serializer for IPv4 and IPv6 addresses.
1230         urlBuilder.append(m_string, start, m_hostEnd - start);
1231         if (hasPort()) {
1232             urlBuilder.appendLiteral(":");
1233             urlBuilder.appendNumber(port());
1234         }
1235     } else if (protocolIs("file"))
1236         urlBuilder.appendLiteral("//");
1237     if (cannotBeABaseURL(*this))
1238         urlBuilder.append(m_string, m_portEnd, m_pathEnd - m_portEnd);
1239     else {
1240         urlBuilder.appendLiteral("/");
1241         if (m_pathEnd > m_portEnd) {
1242             unsigned pathStart = m_portEnd + 1;
1243             urlBuilder.append(m_string, pathStart, m_pathEnd - pathStart);
1244         }
1245     }
1246     if (hasQuery()) {
1247         urlBuilder.appendLiteral("?");
1248         urlBuilder.append(m_string, m_pathEnd + 1, m_queryEnd - (m_pathEnd + 1));
1249     }
1250     if (!omitFragment && hasFragment()) {
1251         urlBuilder.appendLiteral("#");
1252         urlBuilder.append(m_string, m_queryEnd + 1, m_fragmentEnd - (m_queryEnd + 1));
1253     }
1254     return urlBuilder.toString();
1255 }
1256
1257 #if PLATFORM(IOS)
1258 static bool shouldCanonicalizeScheme = true;
1259
1260 void enableURLSchemeCanonicalization(bool enableSchemeCanonicalization)
1261 {
1262     shouldCanonicalizeScheme = enableSchemeCanonicalization;
1263 }
1264 #endif
1265
1266 template<size_t length>
1267 static inline bool equal(const char* a, const char (&b)[length])
1268 {
1269 #if PLATFORM(IOS)
1270     if (!shouldCanonicalizeScheme) {
1271         for (size_t i = 0; i < length; ++i) {
1272             if (toASCIILower(a[i]) != b[i])
1273                 return false;
1274         }
1275         return true;
1276     }
1277 #endif
1278     for (size_t i = 0; i < length; ++i) {
1279         if (a[i] != b[i])
1280             return false;
1281     }
1282     return true;
1283 }
1284
1285 template<size_t lengthB>
1286 static inline bool equal(const char* stringA, size_t lengthA, const char (&stringB)[lengthB])
1287 {
1288     return lengthA == lengthB && equal(stringA, stringB);
1289 }
1290
1291 // List of default schemes is taken from google-url:
1292 // http://code.google.com/p/google-url/source/browse/trunk/src/url_canon_stdurl.cc#120
1293 static inline bool isDefaultPortForScheme(const char* port, size_t portLength, const char* scheme, size_t schemeLength)
1294 {
1295     // This switch is theoretically a performance optimization.  It came over when
1296     // the code was moved from google-url, but may be removed later.
1297     switch (schemeLength) {
1298     case 2:
1299         return equal(scheme, wsScheme) && equal(port, portLength, httpPort);
1300     case 3:
1301         if (equal(scheme, ftpScheme))
1302             return equal(port, portLength, ftpPort);
1303         if (equal(scheme, wssScheme))
1304             return equal(port, portLength, httpsPort);
1305         break;
1306     case 4:
1307         return equal(scheme, httpScheme) && equal(port, portLength, httpPort);
1308     case 5:
1309         return equal(scheme, httpsScheme) && equal(port, portLength, httpsPort);
1310     case 6:
1311         return equal(scheme, gopherScheme) && equal(port, portLength, gopherPort);
1312     }
1313     return false;
1314 }
1315
1316 static inline bool hostPortIsEmptyButCredentialsArePresent(unsigned hostStart, unsigned portEnd, char userinfoEndChar)
1317 {
1318     return userinfoEndChar == '@' && hostStart == portEnd;
1319 }
1320
1321 static bool isNonFileHierarchicalScheme(const char* scheme, size_t schemeLength)
1322 {
1323     switch (schemeLength) {
1324     case 2:
1325         return equal(scheme, wsScheme);
1326     case 3:
1327         return equal(scheme, ftpScheme) || equal(scheme, wssScheme);
1328     case 4:
1329         return equal(scheme, httpScheme);
1330     case 5:
1331         return equal(scheme, httpsScheme);
1332     case 6:
1333         return equal(scheme, gopherScheme);
1334     }
1335     return false;
1336 }
1337
1338 static bool isCanonicalHostnameLowercaseForScheme(const char* scheme, size_t schemeLength)
1339 {
1340     switch (schemeLength) {
1341     case 2:
1342         return equal(scheme, wsScheme);
1343     case 3:
1344         return equal(scheme, ftpScheme) || equal(scheme, wssScheme);
1345     case 4:
1346         return equal(scheme, httpScheme) || equal(scheme, fileScheme);
1347     case 5:
1348         return equal(scheme, httpsScheme);
1349     case 6:
1350         return equal(scheme, gopherScheme);
1351     }
1352     return false;
1353 }
1354
1355 void URL::parse(const char* url, const String* originalString)
1356 {
1357     if (!url || url[0] == '\0') {
1358         // valid URL must be non-empty
1359         m_string = originalString ? *originalString : url;
1360         invalidate();
1361         return;
1362     }
1363
1364     if (!isSchemeFirstChar(url[0])) {
1365         // scheme must start with an alphabetic character
1366         m_string = originalString ? *originalString : url;
1367         invalidate();
1368         return;
1369     }
1370
1371     unsigned schemeEnd = 0;
1372     while (isSchemeChar(url[schemeEnd]))
1373         schemeEnd++;
1374
1375     if (url[schemeEnd] != ':') {
1376         m_string = originalString ? *originalString : url;
1377         invalidate();
1378         return;
1379     }
1380
1381     unsigned userStart = schemeEnd + 1;
1382     unsigned userEnd;
1383     unsigned passwordStart;
1384     unsigned passwordEnd;
1385     unsigned hostStart;
1386     unsigned hostEnd;
1387     unsigned portStart;
1388     unsigned portEnd;
1389
1390     bool hierarchical = url[schemeEnd + 1] == '/';
1391     bool hasSecondSlash = hierarchical && url[schemeEnd + 2] == '/';
1392
1393     bool isFile = schemeEnd == 4
1394         && isLetterMatchIgnoringCase(url[0], 'f')
1395         && isLetterMatchIgnoringCase(url[1], 'i')
1396         && isLetterMatchIgnoringCase(url[2], 'l')
1397         && isLetterMatchIgnoringCase(url[3], 'e');
1398
1399     m_protocolIsInHTTPFamily = isLetterMatchIgnoringCase(url[0], 'h')
1400         && isLetterMatchIgnoringCase(url[1], 't')
1401         && isLetterMatchIgnoringCase(url[2], 't')
1402         && isLetterMatchIgnoringCase(url[3], 'p')
1403         && (url[4] == ':' || (isLetterMatchIgnoringCase(url[4], 's') && url[5] == ':'));
1404
1405     if ((hierarchical && hasSecondSlash) || isNonFileHierarchicalScheme(url, schemeEnd)) {
1406         // The part after the scheme is either a net_path or an abs_path whose first path segment is empty.
1407         // Attempt to find an authority.
1408         // FIXME: Authority characters may be scanned twice, and it would be nice to be faster.
1409
1410         if (hierarchical) {
1411             userStart++;
1412             if (hasSecondSlash) {
1413                 userStart++;
1414                 if (isNonFileHierarchicalScheme(url, schemeEnd)) {
1415                     while (url[userStart] == '/')
1416                         userStart++;
1417                 }
1418             }
1419         }
1420
1421         userEnd = userStart;
1422
1423         unsigned colonPos = 0;
1424         while (isUserInfoChar(url[userEnd])) {
1425             if (url[userEnd] == ':' && colonPos == 0)
1426                 colonPos = userEnd;
1427             userEnd++;
1428         }
1429
1430         if (url[userEnd] == '@') {
1431             // actual end of the userinfo, start on the host
1432             if (colonPos != 0) {
1433                 passwordEnd = userEnd;
1434                 userEnd = colonPos;
1435                 passwordStart = colonPos + 1;
1436             } else
1437                 passwordStart = passwordEnd = userEnd;
1438
1439             hostStart = passwordEnd + 1;
1440         } else if (url[userEnd] == '[' || isPathSegmentEndChar(url[userEnd])) {
1441             // hit the end of the authority, must have been no user
1442             // or looks like an IPv6 hostname
1443             // either way, try to parse it as a hostname
1444             userEnd = userStart;
1445             passwordStart = passwordEnd = userEnd;
1446             hostStart = userStart;
1447         } else {
1448             // invalid character
1449             m_string = originalString ? *originalString : url;
1450             invalidate();
1451             return;
1452         }
1453
1454         hostEnd = hostStart;
1455
1456         // IPV6 IP address
1457         if (url[hostEnd] == '[') {
1458             hostEnd++;
1459             while (isIPv6Char(url[hostEnd]))
1460                 hostEnd++;
1461             if (url[hostEnd] == ']')
1462                 hostEnd++;
1463             else {
1464                 // invalid character
1465                 m_string = originalString ? *originalString : url;
1466                 invalidate();
1467                 return;
1468             }
1469         } else {
1470             while (isHostnameChar(url[hostEnd]))
1471                 hostEnd++;
1472         }
1473         
1474         if (url[hostEnd] == ':') {
1475             portStart = portEnd = hostEnd + 1;
1476  
1477             // possible start of port
1478             portEnd = portStart;
1479             while (isASCIIDigit(url[portEnd]))
1480                 portEnd++;
1481         } else
1482             portStart = portEnd = hostEnd;
1483
1484         if (!isPathSegmentEndChar(url[portEnd])) {
1485             // invalid character
1486             m_string = originalString ? *originalString : url;
1487             invalidate();
1488             return;
1489         }
1490
1491         if (hostPortIsEmptyButCredentialsArePresent(hostStart, portEnd, url[passwordEnd])) {
1492             m_string = originalString ? *originalString : url;
1493             invalidate();
1494             return;
1495         }
1496
1497         if (userStart == portEnd && !m_protocolIsInHTTPFamily && !isFile) {
1498             // No authority found, which means that this is not a net_path, but rather an abs_path whose first two
1499             // path segments are empty. For file, http and https only, an empty authority is allowed.
1500             userStart -= 2;
1501             userEnd = userStart;
1502             passwordStart = userEnd;
1503             passwordEnd = passwordStart;
1504             hostStart = passwordEnd;
1505             hostEnd = hostStart;
1506             portStart = hostEnd;
1507             portEnd = hostEnd;
1508         }
1509     } else {
1510         // the part after the scheme must be an opaque_part or an abs_path
1511         userEnd = userStart;
1512         passwordStart = passwordEnd = userEnd;
1513         hostStart = hostEnd = passwordEnd;
1514         portStart = portEnd = hostEnd;
1515     }
1516
1517     unsigned pathStart = portEnd;
1518     unsigned pathEnd = pathStart;
1519     while (url[pathEnd] && url[pathEnd] != '?' && url[pathEnd] != '#')
1520         pathEnd++;
1521
1522     unsigned queryStart = pathEnd;
1523     unsigned queryEnd = queryStart;
1524     if (url[queryStart] == '?') {
1525         while (url[queryEnd] && url[queryEnd] != '#')
1526             queryEnd++;
1527     }
1528
1529     unsigned fragmentStart = queryEnd;
1530     unsigned fragmentEnd = fragmentStart;
1531     if (url[fragmentStart] == '#') {
1532         fragmentStart++;
1533         fragmentEnd = fragmentStart;
1534         while (url[fragmentEnd])
1535             fragmentEnd++;
1536     }
1537
1538     // assemble it all, remembering the real ranges
1539
1540     Vector<char, 4096> buffer(fragmentEnd * 3 + 1);
1541
1542     char* p = buffer.data();
1543     const char* strPtr = url;
1544
1545     // copy in the scheme
1546     const char* schemeEndPtr = url + schemeEnd;
1547 #if PLATFORM(IOS)
1548     if (shouldCanonicalizeScheme || m_protocolIsInHTTPFamily) {
1549         while (strPtr < schemeEndPtr)
1550             *p++ = toASCIILower(*strPtr++);
1551     } else {
1552         while (strPtr < schemeEndPtr)
1553             *p++ = *strPtr++;
1554     }
1555 #else
1556     while (strPtr < schemeEndPtr)
1557         *p++ = toASCIILower(*strPtr++);
1558 #endif
1559     m_schemeEnd = p - buffer.data();
1560
1561     bool hostIsLocalHost = portEnd - userStart == 9
1562         && isLetterMatchIgnoringCase(url[userStart], 'l')
1563         && isLetterMatchIgnoringCase(url[userStart+1], 'o')
1564         && isLetterMatchIgnoringCase(url[userStart+2], 'c')
1565         && isLetterMatchIgnoringCase(url[userStart+3], 'a')
1566         && isLetterMatchIgnoringCase(url[userStart+4], 'l')
1567         && isLetterMatchIgnoringCase(url[userStart+5], 'h')
1568         && isLetterMatchIgnoringCase(url[userStart+6], 'o')
1569         && isLetterMatchIgnoringCase(url[userStart+7], 's')
1570         && isLetterMatchIgnoringCase(url[userStart+8], 't');
1571
1572     // File URLs need a host part unless it is just file:// or file://localhost
1573     bool degenerateFilePath = pathStart == pathEnd && (hostStart == hostEnd || hostIsLocalHost);
1574
1575     // We drop empty credentials, but keep a colon in an empty host/port pair.
1576     // Removing hostname completely would change the structure of the URL on re-parsing.
1577     bool haveNonHostAuthorityPart = userStart != userEnd || passwordStart != passwordEnd || hostEnd != portEnd;
1578
1579     // add ":" after scheme
1580     *p++ = ':';
1581
1582     // if we have at least one authority part or a file URL - add "//" and authority
1583     if (isFile ? !degenerateFilePath : (haveNonHostAuthorityPart || hostStart != hostEnd)) {
1584         *p++ = '/';
1585         *p++ = '/';
1586
1587         m_userStart = p - buffer.data();
1588
1589         // copy in the user
1590         strPtr = url + userStart;
1591         const char* userEndPtr = url + userEnd;
1592         while (strPtr < userEndPtr) {
1593             char c = *strPtr++;
1594             ASSERT(isUserInfoChar(c));
1595             *p++ = c;
1596         }
1597         m_userEnd = p - buffer.data();
1598
1599         // copy in the password
1600         if (passwordEnd != passwordStart) {
1601             *p++ = ':';
1602             strPtr = url + passwordStart;
1603             const char* passwordEndPtr = url + passwordEnd;
1604             while (strPtr < passwordEndPtr) {
1605                 char c = *strPtr++;
1606                 ASSERT(isUserInfoChar(c));
1607                 *p++ = c;
1608             }
1609         }
1610         m_passwordEnd = p - buffer.data();
1611
1612         // If we had any user info, add "@"
1613         if (static_cast<unsigned>(p - buffer.data()) != m_userStart)
1614             *p++ = '@';
1615
1616         // copy in the host, except in the case of a file URL with authority="localhost"
1617         if (!(isFile && hostIsLocalHost && !haveNonHostAuthorityPart)) {
1618             strPtr = url + hostStart;
1619             const char* hostEndPtr = url + hostEnd;
1620             if (isCanonicalHostnameLowercaseForScheme(buffer.data(), m_schemeEnd)) {
1621                 while (strPtr < hostEndPtr) {
1622                     char c = toASCIILower(*strPtr++);
1623                     ASSERT(isHostnameChar(c) || c == '[' || c == ']' || c == ':');
1624                     *p++ = c;
1625                 }
1626             } else {
1627                 while (strPtr < hostEndPtr) {
1628                     char c = *strPtr++;
1629                     ASSERT(isHostnameChar(c) || c == '[' || c == ']' || c == ':');
1630                     *p++ = c;
1631                 }
1632             }
1633         }
1634         m_hostEnd = p - buffer.data();
1635
1636         // 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.
1637         if (hostEnd != portStart) {
1638             const char* portStr = url + portStart;
1639             size_t portLength = portEnd - portStart;
1640             if ((portLength && !isDefaultPortForScheme(portStr, portLength, buffer.data(), m_schemeEnd))
1641                 || (hostStart == hostEnd && hostEnd != portStart)) {
1642                 *p++ = ':';
1643                 const char* portEndPtr = url + portEnd;
1644                 while (portStr < portEndPtr)
1645                     *p++ = *portStr++;
1646             }
1647         }
1648         m_portEnd = p - buffer.data();
1649     } else {
1650         if (isFile) {
1651             ASSERT(degenerateFilePath);
1652             *p++ = '/';
1653             *p++ = '/';
1654         }
1655         m_userStart = m_userEnd = m_passwordEnd = m_hostEnd = m_portEnd = p - buffer.data();
1656     }
1657
1658     // For canonicalization, ensure we have a '/' for no path.
1659     // Do this only for URL with protocol file, http or https.
1660     if ((m_protocolIsInHTTPFamily || isFile) && pathEnd == pathStart)
1661         *p++ = '/';
1662
1663     // add path, escaping bad characters
1664     if (!hierarchical)
1665         escapeAndAppendNonHierarchicalPart(p, url + pathStart, pathEnd - pathStart);
1666     else if (!hasSlashDotOrDotDot(url))
1667         appendEscapingBadChars(p, url + pathStart, pathEnd - pathStart);
1668     else {
1669         CharBuffer pathBuffer(pathEnd - pathStart + 1);
1670         unsigned length = copyPathRemovingDots(pathBuffer.data(), url, pathStart, pathEnd);
1671         appendEscapingBadChars(p, pathBuffer.data(), length);
1672     }
1673
1674     m_pathEnd = p - buffer.data();
1675
1676     // Find the position after the last slash in the path, or
1677     // the position before the path if there are no slashes in it.
1678     unsigned i;
1679     for (i = m_pathEnd; i > m_portEnd; --i) {
1680         if (buffer[i - 1] == '/')
1681             break;
1682     }
1683     m_pathAfterLastSlash = i;
1684
1685     // add query, escaping bad characters
1686     appendEscapingBadChars(p, url + queryStart, queryEnd - queryStart);
1687     m_queryEnd = p - buffer.data();
1688
1689     // add fragment, escaping bad characters
1690     if (fragmentEnd != queryEnd) {
1691         *p++ = '#';
1692         escapeAndAppendNonHierarchicalPart(p, url + fragmentStart, fragmentEnd - fragmentStart);
1693     }
1694     m_fragmentEnd = p - buffer.data();
1695
1696     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1697     ASSERT(buffer.size() > 0);
1698
1699     // If we didn't end up actually changing the original string and
1700     // it was already in a String, reuse it to avoid extra allocation.
1701     if (originalString && equal(originalString->impl(), buffer.data(), m_fragmentEnd))
1702         m_string = *originalString;
1703     else
1704         m_string = String(buffer.data(), m_fragmentEnd);
1705
1706     m_isValid = true;
1707 }
1708
1709 bool equalIgnoringFragmentIdentifier(const URL& a, const URL& b)
1710 {
1711     if (a.m_queryEnd != b.m_queryEnd)
1712         return false;
1713     unsigned queryLength = a.m_queryEnd;
1714     for (unsigned i = 0; i < queryLength; ++i)
1715         if (a.string()[i] != b.string()[i])
1716             return false;
1717     return true;
1718 }
1719
1720 bool protocolHostAndPortAreEqual(const URL& a, const URL& b)
1721 {
1722     if (a.m_schemeEnd != b.m_schemeEnd)
1723         return false;
1724
1725     unsigned hostStartA = a.hostStart();
1726     unsigned hostLengthA = a.hostEnd() - hostStartA;
1727     unsigned hostStartB = b.hostStart();
1728     unsigned hostLengthB = b.hostEnd() - b.hostStart();
1729     if (hostLengthA != hostLengthB)
1730         return false;
1731
1732     // Check the scheme
1733     for (unsigned i = 0; i < a.m_schemeEnd; ++i) {
1734         if (a.string()[i] != b.string()[i])
1735             return false;
1736     }
1737
1738     // And the host
1739     for (unsigned i = 0; i < hostLengthA; ++i) {
1740         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
1741             return false;
1742     }
1743
1744     if (a.port() != b.port())
1745         return false;
1746
1747     return true;
1748 }
1749
1750 bool hostsAreEqual(const URL& a, const URL& b)
1751 {
1752     unsigned hostStartA = a.hostStart();
1753     unsigned hostLengthA = a.hostEnd() - hostStartA;
1754     unsigned hostStartB = b.hostStart();
1755     unsigned hostLengthB = b.hostEnd() - hostStartB;
1756     if (hostLengthA != hostLengthB)
1757         return false;
1758
1759     for (unsigned i = 0; i < hostLengthA; ++i) {
1760         if (a.string()[hostStartA + i] != b.string()[hostStartB + i])
1761             return false;
1762     }
1763
1764     return true;
1765 }
1766
1767 String encodeWithURLEscapeSequences(const String& notEncodedString, PercentEncodeCharacterClass whatToEncode)
1768 {
1769     CString asUTF8 = notEncodedString.utf8();
1770
1771     CharBuffer buffer(asUTF8.length() * 3 + 1);
1772     char* p = buffer.data();
1773
1774     const char* str = asUTF8.data();
1775     const char* strEnd = str + asUTF8.length();
1776     while (str < strEnd) {
1777         unsigned char c = *str++;
1778         if (percentEncodeClassTable[c] >= whatToEncode)
1779             appendEscapedChar(p, c);
1780         else
1781             *p++ = c;
1782     }
1783
1784     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1785
1786     return String(buffer.data(), p - buffer.data());
1787 }
1788
1789 String encodeWithURLEscapeSequences(const String& notEncodedString)
1790 {
1791     CString asUTF8 = notEncodedString.utf8();
1792
1793     CharBuffer buffer(asUTF8.length() * 3 + 1);
1794     char* p = buffer.data();
1795
1796     const char* str = asUTF8.data();
1797     const char* strEnd = str + asUTF8.length();
1798     while (str < strEnd) {
1799         unsigned char c = *str++;
1800         if (isBadChar(c))
1801             appendEscapedChar(p, c);
1802         else
1803             *p++ = c;
1804     }
1805
1806     ASSERT(p - buffer.data() <= static_cast<int>(buffer.size()));
1807
1808     return String(buffer.data(), p - buffer.data());
1809 }
1810
1811 static bool protocolIs(StringView stringURL, const char* protocol)
1812 {
1813     assertProtocolIsGood(protocol);
1814     unsigned length = stringURL.length();
1815     for (unsigned i = 0; i < length; ++i) {
1816         if (!protocol[i])
1817             return stringURL[i] == ':';
1818         if (!isLetterMatchIgnoringCase(stringURL[i], protocol[i]))
1819             return false;
1820     }
1821     return false;
1822 }
1823
1824 static void findHostnamesInMailToURL(StringView string, Vector<std::pair<unsigned, unsigned>>& nameRanges)
1825 {
1826     // In a mailto: URL, host names come after a '@' character and end with a '>' or ',' or '?' or end of string character.
1827     // Skip quoted strings so that characters in them don't confuse us.
1828     // When we find a '?' character, we are past the part of the URL that contains host names.
1829
1830     nameRanges.clear();
1831
1832     unsigned p = 0;
1833     while (1) {
1834         // Find start of host name or of quoted string.
1835         unsigned hostnameOrStringStart = findFirstOf(string, p, "\"@?");
1836         if (hostnameOrStringStart == notFoundUnsigned)
1837             return;
1838         UChar c = string[hostnameOrStringStart];
1839         p = hostnameOrStringStart + 1;
1840
1841         if (c == '?')
1842             return;
1843
1844         if (c == '@') {
1845             // Find end of host name.
1846             unsigned hostnameStart = p;
1847             unsigned hostnameEnd = findFirstOf(string, p, ">,?");
1848             bool done;
1849             if (hostnameEnd == notFoundUnsigned) {
1850                 hostnameEnd = string.length();
1851                 done = true;
1852             } else {
1853                 p = hostnameEnd;
1854                 done = false;
1855             }
1856
1857             nameRanges.append(std::make_pair(hostnameStart, hostnameEnd));
1858
1859             if (done)
1860                 return;
1861         } else {
1862             // Skip quoted string.
1863             ASSERT(c == '"');
1864             while (1) {
1865                 unsigned escapedCharacterOrStringEnd = findFirstOf(string, p, "\"\\");
1866                 if (escapedCharacterOrStringEnd == notFoundUnsigned)
1867                     return;
1868
1869                 c = string[escapedCharacterOrStringEnd];
1870                 p = escapedCharacterOrStringEnd + 1;
1871
1872                 // If we are the end of the string, then break from the string loop back to the host name loop.
1873                 if (c == '"')
1874                     break;
1875
1876                 // Skip escaped character.
1877                 ASSERT(c == '\\');
1878                 if (p == string.length())
1879                     return;
1880
1881                 ++p;
1882             }
1883         }
1884     }
1885 }
1886
1887 static bool findHostnameInHierarchicalURL(StringView string, unsigned& startOffset, unsigned& endOffset)
1888 {
1889     // Find the host name in a hierarchical URL.
1890     // It comes after a "://" sequence, with scheme characters preceding, and
1891     // this should be the first colon in the string.
1892     // It ends with the end of the string or a ":" or a path segment ending character.
1893     // If there is a "@" character, the host part is just the part after the "@".
1894     unsigned separator = findFirstOf(string, 0, ":");
1895     if (separator == notFoundUnsigned || separator + 2 >= string.length() || string[separator + 1] != '/' || string[separator + 2] != '/')
1896         return false;
1897
1898     // Check that all characters before the :// are valid scheme characters.
1899     if (!isSchemeFirstChar(string[0]))
1900         return false;
1901     for (unsigned i = 1; i < separator; ++i) {
1902         if (!isSchemeChar(string[i]))
1903             return false;
1904     }
1905
1906     // Start after the separator.
1907     unsigned authorityStart = separator + 3;
1908
1909     // Find terminating character.
1910     unsigned hostnameEnd = string.length();
1911     for (unsigned i = authorityStart; i < hostnameEnd; ++i) {
1912         UChar c = string[i];
1913         if (c == ':' || (isPathSegmentEndChar(c) && c != 0)) {
1914             hostnameEnd = i;
1915             break;
1916         }
1917     }
1918
1919     // Find "@" for the start of the host name.
1920     unsigned userInfoTerminator = findFirstOf(string, authorityStart, "@");
1921     unsigned hostnameStart;
1922     if (userInfoTerminator == notFoundUnsigned || userInfoTerminator > hostnameEnd)
1923         hostnameStart = authorityStart;
1924     else
1925         hostnameStart = userInfoTerminator + 1;
1926
1927     startOffset = hostnameStart;
1928     endOffset = hostnameEnd;
1929     return true;
1930 }
1931
1932 // Converts all hostnames found in the given input to punycode, preserving the
1933 // rest of the URL unchanged. The output will NOT be null-terminated.
1934 // Return value of false means error in encoding.
1935 static bool encodeHostnames(StringView string, UCharBuffer& buffer)
1936 {
1937     buffer.clear();
1938
1939     if (protocolIs(string, "mailto")) {
1940         Vector<std::pair<unsigned, unsigned>> hostnameRanges;
1941         findHostnamesInMailToURL(string, hostnameRanges);
1942         unsigned n = hostnameRanges.size();
1943         unsigned p = 0;
1944         for (unsigned i = 0; i < n; ++i) {
1945             const std::pair<unsigned, unsigned>& r = hostnameRanges[i];
1946             append(buffer, string.substring(p, r.first - p));
1947             if (!appendEncodedHostname(buffer, string.substring(r.first, r.second - r.first)))
1948                 return false;
1949             p = r.second;
1950         }
1951         // This will copy either everything after the last hostname, or the
1952         // whole thing if there is no hostname.
1953         append(buffer, string.substring(p));
1954     } else {
1955         unsigned hostStart, hostEnd;
1956         if (findHostnameInHierarchicalURL(string, hostStart, hostEnd)) {
1957             append(buffer, string.substring(0, hostStart)); // Before hostname.
1958             if (!appendEncodedHostname(buffer, string.substring(hostStart, hostEnd - hostStart)))
1959                 return false;
1960             append(buffer, string.substring(hostEnd)); // After hostname.
1961         } else {
1962             // No hostname to encode, return the input.
1963             append(buffer, string);
1964         }
1965     }
1966
1967     return true;
1968 }
1969
1970 // Return value of false means error in encoding.
1971 static bool encodeRelativeString(const String& rel, const TextEncoding& encoding, CharBuffer& output)
1972 {
1973     UCharBuffer s;
1974     if (!encodeHostnames(rel, s))
1975         return false;
1976
1977     TextEncoding pathEncoding(UTF8Encoding()); // Path is always encoded as UTF-8; other parts may depend on the scheme.
1978
1979     unsigned pathEnd = notFoundUnsigned;
1980     if (encoding != pathEncoding && encoding.isValid() && !protocolIs(rel, "mailto") && !protocolIs(rel, "data") && !protocolIsJavaScript(rel)) {
1981         // Find the first instance of either # or ?, keep pathEnd at -1 otherwise.
1982         pathEnd = findFirstOf(StringView(s.data(), s.size()), 0, "#?");
1983     }
1984
1985     if (pathEnd == notFoundUnsigned) {
1986         CString decoded = pathEncoding.encode(StringView(s.data(), s.size()), URLEncodedEntitiesForUnencodables);
1987         output.resize(decoded.length());
1988         memcpy(output.data(), decoded.data(), decoded.length());
1989     } else {
1990         CString pathDecoded = pathEncoding.encode(StringView(s.data(), pathEnd), URLEncodedEntitiesForUnencodables);
1991         // Unencodable characters in URLs are represented by converting
1992         // them to XML entities and escaping non-alphanumeric characters.
1993         CString otherDecoded = encoding.encode(StringView(s.data() + pathEnd, s.size() - pathEnd), URLEncodedEntitiesForUnencodables);
1994
1995         output.resize(pathDecoded.length() + otherDecoded.length());
1996         memcpy(output.data(), pathDecoded.data(), pathDecoded.length());
1997         memcpy(output.data() + pathDecoded.length(), otherDecoded.data(), otherDecoded.length());
1998     }
1999     output.append('\0'); // null-terminate the output.
2000
2001     return true;
2002 }
2003
2004 static String substituteBackslashes(const String& string)
2005 {
2006     size_t questionPos = string.find('?');
2007     size_t hashPos = string.find('#');
2008     unsigned pathEnd;
2009
2010     if (hashPos != notFound && (questionPos == notFound || questionPos > hashPos))
2011         pathEnd = hashPos;
2012     else if (questionPos != notFound)
2013         pathEnd = questionPos;
2014     else
2015         pathEnd = string.length();
2016
2017     return string.left(pathEnd).replace('\\','/') + string.substring(pathEnd);
2018 }
2019
2020 bool URL::isHierarchical() const
2021 {
2022     if (!m_isValid)
2023         return false;
2024     ASSERT(m_string[m_schemeEnd] == ':');
2025     return m_string[m_schemeEnd + 1] == '/';
2026 }
2027
2028 void URL::copyToBuffer(Vector<char, 512>& buffer) const
2029 {
2030     // FIXME: This throws away the high bytes of all the characters in the string!
2031     // That's fine for a valid URL, which is all ASCII, but not for invalid URLs.
2032     buffer.resize(m_string.length());
2033     copyASCII(m_string, buffer.data());
2034 }
2035
2036 bool protocolIs(const String& url, const char* protocol)
2037 {
2038     // Do the comparison without making a new string object.
2039     assertProtocolIsGood(protocol);
2040     bool isLeading = true;
2041     for (unsigned i = 0, j = 0; url[i]; ++i) {
2042         // skip leading whitespace and control characters.
2043         if (isLeading && shouldTrimFromURL(url[i]))
2044             continue;
2045         isLeading = false;
2046
2047         // skip any tabs and newlines.
2048         if (isTabNewline(url[i]))
2049             continue;
2050
2051         if (!protocol[j])
2052             return url[i] == ':';
2053         if (!isLetterMatchIgnoringCase(url[i], protocol[j]))
2054             return false;
2055
2056         ++j;
2057     }
2058
2059     return false;
2060 }
2061
2062 bool isValidProtocol(const String& protocol)
2063 {
2064     // RFC3986: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
2065     if (protocol.isEmpty())
2066         return false;
2067     if (!isSchemeFirstChar(protocol[0]))
2068         return false;
2069     unsigned protocolLength = protocol.length();
2070     for (unsigned i = 1; i < protocolLength; i++) {
2071         if (!isSchemeChar(protocol[i]))
2072             return false;
2073     }
2074     return true;
2075 }
2076
2077 #ifndef NDEBUG
2078 void URL::print() const
2079 {
2080     printf("%s\n", m_string.utf8().data());
2081 }
2082 #endif
2083
2084 String URL::strippedForUseAsReferrer() const
2085 {
2086     URL referrer(*this);
2087     referrer.setUser(String());
2088     referrer.setPass(String());
2089     referrer.removeFragmentIdentifier();
2090     return referrer.string();
2091 }
2092
2093 bool URL::isLocalFile() const
2094 {
2095     // Including feed here might be a bad idea since drag and drop uses this check
2096     // and including feed would allow feeds to potentially let someone's blog
2097     // read the contents of the clipboard on a drag, even without a drop.
2098     // Likewise with using the FrameLoader::shouldTreatURLAsLocal() function.
2099     return protocolIs("file");
2100 }
2101
2102 bool protocolIsJavaScript(const String& url)
2103 {
2104     return protocolIs(url, "javascript");
2105 }
2106
2107 bool protocolIsInHTTPFamily(const String& url)
2108 {
2109     // Do the comparison without making a new string object.
2110     return isLetterMatchIgnoringCase(url[0], 'h')
2111         && isLetterMatchIgnoringCase(url[1], 't')
2112         && isLetterMatchIgnoringCase(url[2], 't')
2113         && isLetterMatchIgnoringCase(url[3], 'p')
2114         && (url[4] == ':' || (isLetterMatchIgnoringCase(url[4], 's') && url[5] == ':'));
2115 }
2116
2117 const URL& blankURL()
2118 {
2119     static NeverDestroyed<URL> staticBlankURL(ParsedURLString, "about:blank");
2120     return staticBlankURL;
2121 }
2122
2123 bool URL::isBlankURL() const
2124 {
2125     return protocolIs("about");
2126 }
2127
2128 typedef HashMap<String, unsigned short, ASCIICaseInsensitiveHash> DefaultPortsMap;
2129 static const DefaultPortsMap& defaultPortsMap()
2130 {
2131     static NeverDestroyed<const DefaultPortsMap> defaultPortsMap(DefaultPortsMap({
2132         { "http", 80 },
2133         { "https", 443 },
2134         { "ftp", 21 },
2135         { "ftps", 990 }
2136     }));
2137     return defaultPortsMap.get();
2138 }
2139 unsigned short defaultPortForProtocol(const String& protocol)
2140 {
2141     return defaultPortsMap().get(protocol);
2142 }
2143
2144 bool isDefaultPortForProtocol(unsigned short port, const String& protocol)
2145 {
2146     if (protocol.isEmpty())
2147         return false;
2148
2149     return defaultPortForProtocol(protocol) == port;
2150 }
2151
2152 bool portAllowed(const URL& url)
2153 {
2154     unsigned short port = url.port();
2155
2156     // Since most URLs don't have a port, return early for the "no port" case.
2157     if (!port)
2158         return true;
2159
2160     // This blocked port list matches the port blocking that Mozilla implements.
2161     // See http://www.mozilla.org/projects/netlib/PortBanning.html for more information.
2162     static const unsigned short blockedPortList[] = {
2163         1,    // tcpmux
2164         7,    // echo
2165         9,    // discard
2166         11,   // systat
2167         13,   // daytime
2168         15,   // netstat
2169         17,   // qotd
2170         19,   // chargen
2171         20,   // FTP-data
2172         21,   // FTP-control
2173         22,   // SSH
2174         23,   // telnet
2175         25,   // SMTP
2176         37,   // time
2177         42,   // name
2178         43,   // nicname
2179         53,   // domain
2180         77,   // priv-rjs
2181         79,   // finger
2182         87,   // ttylink
2183         95,   // supdup
2184         101,  // hostriame
2185         102,  // iso-tsap
2186         103,  // gppitnp
2187         104,  // acr-nema
2188         109,  // POP2
2189         110,  // POP3
2190         111,  // sunrpc
2191         113,  // auth
2192         115,  // SFTP
2193         117,  // uucp-path
2194         119,  // nntp
2195         123,  // NTP
2196         135,  // loc-srv / epmap
2197         139,  // netbios
2198         143,  // IMAP2
2199         179,  // BGP
2200         389,  // LDAP
2201         465,  // SMTP+SSL
2202         512,  // print / exec
2203         513,  // login
2204         514,  // shell
2205         515,  // printer
2206         526,  // tempo
2207         530,  // courier
2208         531,  // Chat
2209         532,  // netnews
2210         540,  // UUCP
2211         556,  // remotefs
2212         563,  // NNTP+SSL
2213         587,  // ESMTP
2214         601,  // syslog-conn
2215         636,  // LDAP+SSL
2216         993,  // IMAP+SSL
2217         995,  // POP3+SSL
2218         2049, // NFS
2219         3659, // apple-sasl / PasswordServer [Apple addition]
2220         4045, // lockd
2221         4190, // ManageSieve [Apple addition]
2222         6000, // X11
2223         6665, // Alternate IRC [Apple addition]
2224         6666, // Alternate IRC [Apple addition]
2225         6667, // Standard IRC [Apple addition]
2226         6668, // Alternate IRC [Apple addition]
2227         6669, // Alternate IRC [Apple addition]
2228         invalidPortNumber, // Used to block all invalid port numbers
2229     };
2230     const unsigned short* const blockedPortListEnd = blockedPortList + WTF_ARRAY_LENGTH(blockedPortList);
2231
2232 #ifndef NDEBUG
2233     // The port list must be sorted for binary_search to work.
2234     static bool checkedPortList = false;
2235     if (!checkedPortList) {
2236         for (const unsigned short* p = blockedPortList; p != blockedPortListEnd - 1; ++p)
2237             ASSERT(*p < *(p + 1));
2238         checkedPortList = true;
2239     }
2240 #endif
2241
2242     // If the port is not in the blocked port list, allow it.
2243     if (!std::binary_search(blockedPortList, blockedPortListEnd, port))
2244         return true;
2245
2246     // Allow ports 21 and 22 for FTP URLs, as Mozilla does.
2247     if ((port == 21 || port == 22) && url.protocolIs("ftp"))
2248         return true;
2249
2250     // Allow any port number in a file URL, since the port number is ignored.
2251     if (url.protocolIs("file"))
2252         return true;
2253
2254     return false;
2255 }
2256
2257 String mimeTypeFromDataURL(const String& url)
2258 {
2259     ASSERT(protocolIs(url, "data"));
2260
2261     // FIXME: What's the right behavior when the URL has a comma first, but a semicolon later?
2262     // Currently this code will break at the semicolon in that case. Not sure that's correct.
2263     auto index = url.find(';', 5);
2264     if (index == notFound)
2265         index = url.find(',', 5);
2266     if (index == notFound) {
2267         // FIXME: There was an old comment here that made it sound like this should be returning text/plain.
2268         // But we have been returning empty string here for some time, so not changing its behavior at this time.
2269         return emptyString();
2270     }
2271     if (index == 5)
2272         return ASCIILiteral("text/plain");
2273     ASSERT(index >= 5);
2274     return url.substring(5, index - 5).convertToASCIILowercase();
2275 }
2276
2277 String mimeTypeFromURL(const URL& url)
2278 {
2279     String decodedPath = decodeURLEscapeSequences(url.path());
2280     String extension = decodedPath.substring(decodedPath.reverseFind('.') + 1);
2281
2282     // We don't use MIMETypeRegistry::getMIMETypeForPath() because it returns "application/octet-stream" upon failure
2283     return MIMETypeRegistry::getMIMETypeForExtension(extension);
2284 }
2285
2286 String URL::stringCenterEllipsizedToLength(unsigned length) const
2287 {
2288     if (string().length() <= length)
2289         return string();
2290
2291     return string().left(length / 2 - 1) + "..." + string().right(length / 2 - 2);
2292 }
2293
2294 URL URL::fakeURLWithRelativePart(const String& relativePart)
2295 {
2296     return URL(URL(), "webkit-fake-url://" + createCanonicalUUIDString() + '/' + relativePart);
2297 }
2298
2299 URL URL::fileURLWithFileSystemPath(const String& filePath)
2300 {
2301     return URL(URL(), "file:///" + filePath);
2302 }
2303
2304 }