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