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