OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library uriTest; | |
6 | |
7 import "package:expect/expect.dart"; | |
8 import 'dart:convert'; | |
9 | |
10 testUri(String uriText, bool isAbsolute) { | |
11 var uri = Uri.parse(uriText); | |
12 | |
13 // Test that parsing a substring works the same as parsing the string. | |
14 String wrapper = "://@[]:/%?#"; | |
15 var embeddedUri = Uri.parse( | |
16 "$wrapper$uri$wrapper", wrapper.length, uriText.length + wrapper.length); | |
17 | |
18 Expect.equals(uri, embeddedUri); | |
19 Expect.equals(isAbsolute, uri.isAbsolute); | |
20 Expect.stringEquals(uriText, uri.toString()); | |
21 | |
22 // Test equals and hashCode members. | |
23 var uri2 = Uri.parse(uriText); | |
24 Expect.equals(uri, uri2); | |
25 Expect.equals(uri.hashCode, uri2.hashCode); | |
26 | |
27 // Test that removeFragment doesn't change anything else. | |
28 if (uri.hasFragment) { | |
29 Expect.equals(Uri.parse(uriText.substring(0, uriText.indexOf('#'))), | |
30 uri.removeFragment()); | |
31 } else { | |
32 Expect.equals(uri, Uri.parse(uriText + "#fragment").removeFragment()); | |
33 } | |
34 | |
35 Expect.isTrue(uri.isScheme(uri.scheme)); | |
36 Expect.isTrue(uri.isScheme(uri.scheme.toLowerCase())); | |
37 Expect.isTrue(uri.isScheme(uri.scheme.toUpperCase())); | |
38 if (uri.hasScheme) { | |
39 // Capitalize | |
40 Expect.isTrue( | |
41 uri.isScheme(uri.scheme[0].toUpperCase() + uri.scheme.substring(1))); | |
42 Expect | |
43 .isFalse(uri.isScheme(uri.scheme.substring(0, uri.scheme.length - 1))); | |
44 Expect.isFalse(uri.isScheme(uri.scheme + ":")); | |
45 Expect.isFalse(uri.isScheme(uri.scheme + "\x00")); | |
46 } else { | |
47 Expect.isTrue(uri.isScheme(null)); | |
48 Expect.isFalse(uri.isScheme(":")); | |
49 } | |
50 } | |
51 | |
52 testEncodeDecode(String orig, String encoded) { | |
53 var e = Uri.encodeFull(orig); | |
54 Expect.stringEquals(encoded, e); | |
55 var d = Uri.decodeFull(encoded); | |
56 Expect.stringEquals(orig, d); | |
57 } | |
58 | |
59 testEncodeDecodeComponent(String orig, String encoded) { | |
60 var e = Uri.encodeComponent(orig); | |
61 Expect.stringEquals(encoded, e); | |
62 var d = Uri.decodeComponent(encoded); | |
63 Expect.stringEquals(orig, d); | |
64 } | |
65 | |
66 testEncodeDecodeQueryComponent(String orig, String encodedUTF8, | |
67 String encodedLatin1, String encodedAscii) { | |
68 var e, d; | |
69 e = Uri.encodeQueryComponent(orig); | |
70 Expect.stringEquals(encodedUTF8, e); | |
71 d = Uri.decodeQueryComponent(encodedUTF8); | |
72 Expect.stringEquals(orig, d); | |
73 | |
74 e = Uri.encodeQueryComponent(orig, encoding: UTF8); | |
75 Expect.stringEquals(encodedUTF8, e); | |
76 d = Uri.decodeQueryComponent(encodedUTF8, encoding: UTF8); | |
77 Expect.stringEquals(orig, d); | |
78 | |
79 e = Uri.encodeQueryComponent(orig, encoding: LATIN1); | |
80 Expect.stringEquals(encodedLatin1, e); | |
81 d = Uri.decodeQueryComponent(encodedLatin1, encoding: LATIN1); | |
82 Expect.stringEquals(orig, d); | |
83 | |
84 if (encodedAscii != null) { | |
85 e = Uri.encodeQueryComponent(orig, encoding: ASCII); | |
86 Expect.stringEquals(encodedAscii, e); | |
87 d = Uri.decodeQueryComponent(encodedAscii, encoding: ASCII); | |
88 Expect.stringEquals(orig, d); | |
89 } else { | |
90 Expect.throws(() => Uri.encodeQueryComponent(orig, encoding: ASCII), | |
91 (e) => e is ArgumentError); | |
92 } | |
93 } | |
94 | |
95 testUriPerRFCs() { | |
96 // Convert a Uri to a guaranteed "non simple" URI with the same content. | |
97 toComplex(Uri uri) { | |
98 Uri complex = new Uri( | |
99 scheme: uri.scheme, | |
100 userInfo: uri.hasAuthority ? uri.userInfo : null, | |
101 host: uri.hasAuthority ? uri.host : null, | |
102 port: uri.hasAuthority ? uri.port : null, | |
103 path: uri.path, | |
104 query: uri.hasQuery ? uri.query : null, | |
105 fragment: uri.hasFragment ? uri.fragment : null, | |
106 ); | |
107 assert(complex.toString() == uri.toString()); | |
108 return complex; | |
109 } | |
110 | |
111 Uri base; | |
112 Uri complexBase; | |
113 // Sets the [base] and [complexBase] to the parse of the URI and a | |
114 // guaranteed non-simple version of the same URI. | |
115 setBase(String uri) { | |
116 base = Uri.parse(uri); | |
117 complexBase = toComplex(base); | |
118 } | |
119 | |
120 testResolve(expect, relative) { | |
121 String name = "$base << $relative"; | |
122 Expect.stringEquals(expect, base.resolve(relative).toString(), name); | |
123 | |
124 Expect.stringEquals(expect, complexBase.resolve(relative).toString(), | |
125 name + " (complex base)"); | |
126 } | |
127 | |
128 // From RFC 3986. | |
129 final urisSample = "http://a/b/c/d;p?q"; | |
130 setBase(urisSample); | |
131 | |
132 testResolve("g:h", "g:h"); | |
133 testResolve("http://a/b/c/g", "g"); | |
134 testResolve("http://a/b/c/g", "./g"); | |
135 testResolve("http://a/b/c/g/", "g/"); | |
136 testResolve("http://a/g", "/g"); | |
137 testResolve("http://g", "//g"); | |
138 testResolve("http://a/b/c/d;p?y", "?y"); | |
139 testResolve("http://a/b/c/g?y", "g?y"); | |
140 testResolve("http://a/b/c/d;p?q#s", "#s"); | |
141 testResolve("http://a/b/c/g#s", "g#s"); | |
142 testResolve("http://a/b/c/g?y#s", "g?y#s"); | |
143 testResolve("http://a/b/c/;x", ";x"); | |
144 testResolve("http://a/b/c/g;x", "g;x"); | |
145 testResolve("http://a/b/c/g;x?y#s", "g;x?y#s"); | |
146 testResolve("http://a/b/c/d;p?q", ""); | |
147 testResolve("http://a/b/c/", "."); | |
148 testResolve("http://a/b/c/", "./"); | |
149 testResolve("http://a/b/", ".."); | |
150 testResolve("http://a/b/", "../"); | |
151 testResolve("http://a/b/g", "../g"); | |
152 testResolve("http://a/", "../.."); | |
153 testResolve("http://a/", "../../"); | |
154 testResolve("http://a/g", "../../g"); | |
155 testResolve("http://a/g", "../../../g"); | |
156 testResolve("http://a/g", "../../../../g"); | |
157 testResolve("http://a/g", "/./g"); | |
158 testResolve("http://a/g", "/../g"); | |
159 testResolve("http://a/b/c/g.", "g."); | |
160 testResolve("http://a/b/c/.g", ".g"); | |
161 testResolve("http://a/b/c/g..", "g.."); | |
162 testResolve("http://a/b/c/..g", "..g"); | |
163 testResolve("http://a/b/g", "./../g"); | |
164 testResolve("http://a/b/c/g/", "./g/."); | |
165 testResolve("http://a/b/c/g/h", "g/./h"); | |
166 testResolve("http://a/b/c/h", "g/../h"); | |
167 testResolve("http://a/b/c/g;x=1/y", "g;x=1/./y"); | |
168 testResolve("http://a/b/c/y", "g;x=1/../y"); | |
169 testResolve("http://a/b/c/g?y/./x", "g?y/./x"); | |
170 testResolve("http://a/b/c/g?y/../x", "g?y/../x"); | |
171 testResolve("http://a/b/c/g#s/./x", "g#s/./x"); | |
172 testResolve("http://a/b/c/g#s/../x", "g#s/../x"); | |
173 testResolve("http:g", "http:g"); | |
174 | |
175 // Additional tests (not from RFC 3986). | |
176 testResolve("http://a/b/g;p/h;s", "../g;p/h;s"); | |
177 | |
178 setBase("s:a/b"); | |
179 testResolve("s:a/c", "c"); | |
180 testResolve("s:/c", "../c"); | |
181 | |
182 setBase("S:a/b"); | |
183 testResolve("s:a/c", "c"); | |
184 testResolve("s:/c", "../c"); | |
185 | |
186 setBase("s:foo"); | |
187 testResolve("s:bar", "bar"); | |
188 testResolve("s:bar", "../bar"); | |
189 | |
190 setBase("S:foo"); | |
191 testResolve("s:bar", "bar"); | |
192 testResolve("s:bar", "../bar"); | |
193 | |
194 // Special-case (deliberate non-RFC behavior). | |
195 setBase("foo/bar"); | |
196 testResolve("foo/baz", "baz"); | |
197 testResolve("baz", "../baz"); | |
198 | |
199 setBase("s:/foo"); | |
200 testResolve("s:/bar", "bar"); | |
201 testResolve("s:/bar", "../bar"); | |
202 | |
203 setBase("S:/foo"); | |
204 testResolve("s:/bar", "bar"); | |
205 testResolve("s:/bar", "../bar"); | |
206 | |
207 // Test non-URI base (no scheme, no authority, relative path). | |
208 setBase("a/b/c?_#_"); | |
209 testResolve("a/b/g?q#f", "g?q#f"); | |
210 testResolve("./", "../.."); | |
211 testResolve("../", "../../.."); | |
212 testResolve("a/b/", "."); | |
213 testResolve("c", "../../c"); // Deliberate non-RFC behavior. | |
214 setBase("../../a/b/c?_#_"); // Initial ".." in base url. | |
215 testResolve("../../a/d", "../d"); | |
216 testResolve("../../d", "../../d"); | |
217 testResolve("../../../d", "../../../d"); | |
218 setBase("../../a/b"); | |
219 testResolve("../../a/d", "d"); | |
220 testResolve("../../d", "../d"); | |
221 testResolve("../../../d", "../../d"); | |
222 setBase("../../a"); | |
223 testResolve("../../d", "d"); | |
224 testResolve("../../../d", "../d"); | |
225 testResolve("../../../../d", "../../d"); | |
226 | |
227 // Absolute path, not scheme or authority. | |
228 setBase("/a"); | |
229 testResolve("/b", "b"); | |
230 testResolve("/b", "../b"); | |
231 testResolve("/b", "../../b"); | |
232 setBase("/a/b"); | |
233 testResolve("/a/c", "c"); | |
234 testResolve("/c", "../c"); | |
235 testResolve("/c", "../../c"); | |
236 | |
237 setBase("s://h/p?q#f"); // A simple base. | |
238 // Simple references: | |
239 testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F"); | |
240 testResolve("s://h2/P?Q#F", "//h2/P?Q#F"); | |
241 testResolve("s://h/P?Q#F", "/P?Q#F"); | |
242 testResolve("s://h/p?Q#F", "?Q#F"); | |
243 testResolve("s://h/p?q#F", "#F"); | |
244 testResolve("s://h/p?q", ""); | |
245 // Non-simple references: | |
246 testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20"); | |
247 testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20"); | |
248 testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20"); | |
249 testResolve("s://h/P?Q#F%20", "/P?Q#F%20"); | |
250 testResolve("s://h/p?Q#F%20", "?Q#F%20"); | |
251 testResolve("s://h/p?q#F%20", "#F%20"); | |
252 | |
253 setBase("s://h/p1/p2/p3"); // A simple base with a path. | |
254 testResolve("s://h/p1/p2/", "."); | |
255 testResolve("s://h/p1/p2/", "./"); | |
256 testResolve("s://h/p1/", ".."); | |
257 testResolve("s://h/p1/", "../"); | |
258 testResolve("s://h/", "../.."); | |
259 testResolve("s://h/", "../../"); | |
260 testResolve("s://h/p1/%20", "../%20"); | |
261 testResolve("s://h/", "../../../.."); | |
262 testResolve("s://h/", "../../../../"); | |
263 | |
264 setBase("s://h/p?q#f%20"); // A non-simpe base. | |
265 // Simple references: | |
266 testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F"); | |
267 testResolve("s://h2/P?Q#F", "//h2/P?Q#F"); | |
268 testResolve("s://h/P?Q#F", "/P?Q#F"); | |
269 testResolve("s://h/p?Q#F", "?Q#F"); | |
270 testResolve("s://h/p?q#F", "#F"); | |
271 testResolve("s://h/p?q", ""); | |
272 // Non-simple references: | |
273 testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20"); | |
274 testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20"); | |
275 testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20"); | |
276 testResolve("s://h/P?Q#F%20", "/P?Q#F%20"); | |
277 testResolve("s://h/p?Q#F%20", "?Q#F%20"); | |
278 testResolve("s://h/p?q#F%20", "#F%20"); | |
279 | |
280 setBase("S://h/p1/p2/p3"); // A non-simple base with a path. | |
281 testResolve("s://h/p1/p2/", "."); | |
282 testResolve("s://h/p1/p2/", "./"); | |
283 testResolve("s://h/p1/", ".."); | |
284 testResolve("s://h/p1/", "../"); | |
285 testResolve("s://h/", "../.."); | |
286 testResolve("s://h/", "../../"); | |
287 testResolve("s://h/p1/%20", "../%20"); | |
288 testResolve("s://h/", "../../../.."); | |
289 testResolve("s://h/", "../../../../"); | |
290 | |
291 setBase("../../../"); // A simple relative path. | |
292 testResolve("../../../a", "a"); | |
293 testResolve("../../../../a", "../a"); | |
294 testResolve("../../../a%20", "a%20"); | |
295 testResolve("../../../../a%20", "../a%20"); | |
296 | |
297 // Tests covering the branches of the merge algorithm in RFC 3986 | |
298 // with both simple and complex base URIs. | |
299 for (var b in ["s://a/pa/pb?q#f", "s://a/pa/pb?q#f%20"]) { | |
300 setBase(b); | |
301 | |
302 // if defined(R.scheme) then ... | |
303 testResolve("s2://a2/p2?q2#f2", "s2://a2/p2?q2#f2"); | |
304 // else, if defined(R.authority) then ... | |
305 testResolve("s://a2/p2?q2#f2", "//a2/p2?q2#f2"); | |
306 testResolve("s://a2/?q2#f2", "//a2/../?q2#f2"); | |
307 testResolve("s://a2?q2#f2", "//a2?q2#f2"); | |
308 testResolve("s://a2#f2", "//a2#f2"); | |
309 testResolve("s://a2", "//a2"); | |
310 // else, if (R.path == "") then ... | |
311 // if defined(R.query) then | |
312 testResolve("s://a/pa/pb?q2#f2", "?q2#f2"); | |
313 testResolve("s://a/pa/pb?q2", "?q2"); | |
314 // else | |
315 testResolve("s://a/pa/pb?q#f2", "#f2"); | |
316 testResolve("s://a/pa/pb?q", ""); | |
317 // else, if (R.path starts-with "/") then ... | |
318 testResolve("s://a/p2?q2#f2", "/p2?q2#f2"); | |
319 testResolve("s://a/?q2#f2", "/?q2#f2"); | |
320 testResolve("s://a/#f2", "/#f2"); | |
321 testResolve("s://a/", "/"); | |
322 testResolve("s://a/", "/../"); | |
323 // else ... T.path = merge(Base.path, R.path) | |
324 // ... remove-dot-fragments(T.path) ... | |
325 // (Cover the merge function and the remove-dot-fragments functions too). | |
326 | |
327 // If base has authority and empty path ... | |
328 var emptyPathBase = b.replaceFirst("/pa/pb", ""); | |
329 setBase(emptyPathBase); | |
330 testResolve("s://a/p2?q2#f2", "p2?q2#f2"); | |
331 testResolve("s://a/p2#f2", "p2#f2"); | |
332 testResolve("s://a/p2", "p2"); | |
333 | |
334 setBase(b); | |
335 // otherwise | |
336 // (Cover both no authority and non-empty path and both). | |
337 var noAuthEmptyPathBase = b.replaceFirst("//a/pa/pb", ""); | |
338 var noAuthAbsPathBase = b.replaceFirst("//a", ""); | |
339 var noAuthRelPathBase = b.replaceFirst("//a/", ""); | |
340 var noAuthRelSinglePathBase = b.replaceFirst("//a/pa/", ""); | |
341 | |
342 testResolve("s://a/pa/p2?q2#f2", "p2?q2#f2"); | |
343 testResolve("s://a/pa/p2#f2", "p2#f2"); | |
344 testResolve("s://a/pa/p2", "p2"); | |
345 | |
346 setBase(noAuthEmptyPathBase); | |
347 testResolve("s:p2?q2#f2", "p2?q2#f2"); | |
348 testResolve("s:p2#f2", "p2#f2"); | |
349 testResolve("s:p2", "p2"); | |
350 | |
351 setBase(noAuthAbsPathBase); | |
352 testResolve("s:/pa/p2?q2#f2", "p2?q2#f2"); | |
353 testResolve("s:/pa/p2#f2", "p2#f2"); | |
354 testResolve("s:/pa/p2", "p2"); | |
355 | |
356 setBase(noAuthRelPathBase); | |
357 testResolve("s:pa/p2?q2#f2", "p2?q2#f2"); | |
358 testResolve("s:pa/p2#f2", "p2#f2"); | |
359 testResolve("s:pa/p2", "p2"); | |
360 | |
361 setBase(noAuthRelSinglePathBase); | |
362 testResolve("s:p2?q2#f2", "p2?q2#f2"); | |
363 testResolve("s:p2#f2", "p2#f2"); | |
364 testResolve("s:p2", "p2"); | |
365 | |
366 // Then remove dot segments. | |
367 | |
368 // A. if input buffer starts with "../" or "./". | |
369 // This only happens if base has only a single (may be empty) segment and | |
370 // no slash. | |
371 setBase(emptyPathBase); | |
372 testResolve("s://a/p2", "../p2"); | |
373 testResolve("s://a/", "../"); | |
374 testResolve("s://a/", ".."); | |
375 testResolve("s://a/p2", "./p2"); | |
376 testResolve("s://a/", "./"); | |
377 testResolve("s://a/", "."); | |
378 testResolve("s://a/p2", "../../p2"); | |
379 testResolve("s://a/p2", "../../././p2"); | |
380 | |
381 setBase(noAuthRelSinglePathBase); | |
382 testResolve("s:p2", "../p2"); | |
383 testResolve("s:", "../"); | |
384 testResolve("s:", ".."); | |
385 testResolve("s:p2", "./p2"); | |
386 testResolve("s:", "./"); | |
387 testResolve("s:", "."); | |
388 testResolve("s:p2", "../../p2"); | |
389 testResolve("s:p2", "../../././p2"); | |
390 | |
391 // B. if input buffer starts with "/./" or is "/.". replace with "/". | |
392 // (The URI implementation removes the "." path segments when parsing, | |
393 // so this case isn't handled by merge). | |
394 setBase(b); | |
395 testResolve("s://a/pa/p2", "./p2"); | |
396 | |
397 // C. if input buffer starts with "/../" or is "/..", replace with "/" | |
398 // and remove preceeding segment. | |
399 testResolve("s://a/p2", "../p2"); | |
400 var longPathBase = b.replaceFirst("/pb", "/pb/pc/pd"); | |
401 setBase(longPathBase); | |
402 testResolve("s://a/pa/pb/p2", "../p2"); | |
403 testResolve("s://a/pa/p2", "../../p2"); | |
404 testResolve("s://a/p2", "../../../p2"); | |
405 testResolve("s://a/p2", "../../../../p2"); | |
406 var noAuthRelLongPathBase = b.replaceFirst("//a/pa/pb", "pa/pb/pc/pd"); | |
407 setBase(noAuthRelLongPathBase); | |
408 testResolve("s:pa/pb/p2", "../p2"); | |
409 testResolve("s:pa/p2", "../../p2"); | |
410 testResolve("s:/p2", "../../../p2"); | |
411 testResolve("s:/p2", "../../../../p2"); | |
412 | |
413 // D. if the input buffer contains only ".." or ".", remove it. | |
414 setBase(noAuthEmptyPathBase); | |
415 testResolve("s:", ".."); | |
416 testResolve("s:", "."); | |
417 setBase(noAuthRelSinglePathBase); | |
418 testResolve("s:", ".."); | |
419 testResolve("s:", "."); | |
420 } | |
421 } | |
422 | |
423 void testResolvePath(String expected, String path) { | |
424 Expect.equals( | |
425 expected, new Uri(path: '/').resolveUri(new Uri(path: path)).path); | |
426 Expect.equals("http://localhost$expected", | |
427 Uri.parse("http://localhost").resolveUri(new Uri(path: path)).toString()); | |
428 } | |
429 | |
430 const ALPHA = r"abcdefghijklmnopqrstuvwxuzABCDEFGHIJKLMNOPQRSTUVWXUZ"; | |
431 const DIGIT = r"0123456789"; | |
432 const PERCENT_ENCODED = "%00%ff"; | |
433 const SUBDELIM = r"!$&'()*+,;="; | |
434 | |
435 const SCHEMECHAR = "$ALPHA$DIGIT+-."; | |
436 const UNRESERVED = "$ALPHA$DIGIT-._~"; | |
437 const REGNAMECHAR = "$UNRESERVED$SUBDELIM$PERCENT_ENCODED"; | |
438 const USERINFOCHAR = "$REGNAMECHAR:"; | |
439 | |
440 const PCHAR_NC = "$UNRESERVED$SUBDELIM$PERCENT_ENCODED@"; | |
441 const PCHAR = "$PCHAR_NC:"; | |
442 const QUERYCHAR = "$PCHAR/?"; | |
443 | |
444 void testValidCharacters() { | |
445 // test that all valid characters are accepted. | |
446 | |
447 for (var scheme in ["", "$SCHEMECHAR$SCHEMECHAR:"]) { | |
448 for (var userinfo in [ | |
449 "", | |
450 "@", | |
451 "$USERINFOCHAR$USERINFOCHAR@", | |
452 "$USERINFOCHAR:$DIGIT@" | |
453 ]) { | |
454 for (var host in [ | |
455 "", "$REGNAMECHAR$REGNAMECHAR", | |
456 "255.255.255.256", // valid reg-name. | |
457 "[ffff::ffff:ffff]", "[ffff::255.255.255.255]" | |
458 ]) { | |
459 for (var port in ["", ":", ":$DIGIT$DIGIT"]) { | |
460 var auth = "$userinfo$host$port"; | |
461 if (auth.isNotEmpty) auth = "//$auth"; | |
462 var paths = ["", "/", "/$PCHAR", "/$PCHAR/"]; // Absolute or empty. | |
463 if (auth.isNotEmpty) { | |
464 // Initial segment may be empty. | |
465 paths..add("//$PCHAR"); | |
466 } else { | |
467 // Path may begin with non-slash. | |
468 if (scheme.isEmpty) { | |
469 // Initial segment must not contain colon. | |
470 paths | |
471 ..add(PCHAR_NC) | |
472 ..add("$PCHAR_NC/$PCHAR") | |
473 ..add("$PCHAR_NC/$PCHAR/"); | |
474 } else { | |
475 paths..add(PCHAR)..add("$PCHAR/$PCHAR")..add("$PCHAR/$PCHAR/"); | |
476 } | |
477 } | |
478 for (var path in paths) { | |
479 for (var query in ["", "?", "?$QUERYCHAR"]) { | |
480 for (var fragment in ["", "#", "#$QUERYCHAR"]) { | |
481 var uri = "$scheme$auth$path$query$fragment"; | |
482 // Should not throw. | |
483 var result = Uri.parse(uri); | |
484 } | |
485 } | |
486 } | |
487 } | |
488 } | |
489 } | |
490 } | |
491 } | |
492 | |
493 void testInvalidUrls() { | |
494 void checkInvalid(uri) { | |
495 try { | |
496 var result = Uri.parse(uri); | |
497 Expect.fail("Invalid URI `$uri` parsed to $result\n" + dump(result)); | |
498 } on FormatException { | |
499 // Success. | |
500 } | |
501 } | |
502 | |
503 checkInvalid("s%41://x.x/"); // No escapes in scheme, | |
504 // and no colon before slash in path. | |
505 checkInvalid("1a://x.x/"); // Scheme must start with letter, | |
506 // and no colon before slash in path. | |
507 checkInvalid(".a://x.x/"); // Scheme must start with letter, | |
508 // and no colon before slash in path. | |
509 checkInvalid("_:"); // Character not valid in scheme, | |
510 // and no colon before slash in path. | |
511 checkInvalid(":"); // Scheme must start with letter, | |
512 // and no colon before slash in path. | |
513 | |
514 void checkInvalidReplaced(uri, invalid, replacement) { | |
515 var source = uri.replaceAll('{}', invalid); | |
516 var expected = uri.replaceAll('{}', replacement); | |
517 var result = Uri.parse(source); | |
518 Expect.equals(expected, "$result", "Source: $source\n${dump(result)}"); | |
519 } | |
520 | |
521 // Regression test for http://dartbug.com/16081 | |
522 checkInvalidReplaced( | |
523 "http://www.example.org/red%09ros{}#red)", "\u00e9", "%C3%A9"); | |
524 checkInvalidReplaced("http://r{}sum\{}.example.org", "\u00E9", "%C3%A9"); | |
525 | |
526 // Invalid characters. The characters must be rejected, even if normalizing | |
527 // the input would cause them to be valid (normalization happens after | |
528 // validation). | |
529 var invalidCharsAndReplacements = [ | |
530 "\xe7", "%C3%A7", // Arbitrary non-ASCII letter | |
531 " ", "%20", // Space, not allowed anywhere. | |
532 '"', "%22", // Quote, not allowed anywhere | |
533 "<>", "%3C%3E", // Less/greater-than, not allowed anywhere. | |
534 "\x7f", "%7F", // DEL, not allowed anywhere | |
535 "\xdf", "%C3%9F", // German lower-case scharf-S. | |
536 // Becomes ASCII when upper-cased. | |
537 "\u0130", "%C4%B0", // Latin capital dotted I, | |
538 // becomes ASCII lower-case in Turkish. | |
539 "%\uFB03", "%25%EF%AC%83", // % + Ligature ffi, | |
540 // becomes ASCII when upper-cased, | |
541 // should not be read as "%FFI". | |
542 "\u212a", "%E2%84%AA", // Kelvin sign. Becomes ASCII when lower-cased. | |
543 "%1g", "%251g", // Invalid escape. | |
544 "\u{10000}", "%F0%90%80%80", // Non-BMP character as surrogate pair. | |
545 ]; | |
546 for (int i = 0; i < invalidCharsAndReplacements.length; i += 2) { | |
547 var invalid = invalidCharsAndReplacements[i]; | |
548 var valid = invalidCharsAndReplacements[i + 1]; | |
549 checkInvalid("A{}b:///".replaceAll('{}', invalid)); | |
550 checkInvalid("{}b:///".replaceAll('{}', invalid)); | |
551 checkInvalidReplaced("s://user{}info@x.x/", invalid, valid); | |
552 checkInvalidReplaced("s://reg{}name/", invalid, valid); | |
553 checkInvalid("s://regname:12{}45/".replaceAll("{}", invalid)); | |
554 checkInvalidReplaced("s://regname/p{}ath/", invalid, valid); | |
555 checkInvalidReplaced("/p{}ath/", invalid, valid); | |
556 checkInvalidReplaced("p{}ath/", invalid, valid); | |
557 checkInvalidReplaced("s://regname/path/?x{}x", invalid, valid); | |
558 checkInvalidReplaced("s://regname/path/#x{}x", invalid, valid); | |
559 checkInvalidReplaced("s://regname/path/??#x{}x", invalid, valid); | |
560 } | |
561 | |
562 // At most one @ in userinfo. | |
563 checkInvalid("s://x@x@x.x/"); | |
564 // No colon in host except before a port. | |
565 checkInvalid("s://x@x:x/"); | |
566 // At most one port. | |
567 checkInvalid("s://x@x:9:9/"); | |
568 // At most one #. | |
569 checkInvalid("s://x/x#foo#bar"); | |
570 // @ not allowed in scheme. | |
571 checkInvalid("s@://x:9/x?x#x"); | |
572 // ] not allowed alone in host. | |
573 checkInvalid("s://xx]/"); | |
574 // ] not allowed anywhere except in host. | |
575 checkInvalid("s://xx/]"); | |
576 checkInvalid("s://xx/?]"); | |
577 checkInvalid("s://xx/#]"); | |
578 checkInvalid("s:/]"); | |
579 checkInvalid("s:/?]"); | |
580 checkInvalid("s:/#]"); | |
581 // IPv6 must be enclosed in [ and ] for Uri.parse. | |
582 // It is allowed un-enclosed as argument to `Uri(host:...)` because we don't | |
583 // need to delimit. | |
584 checkInvalid("s://ffff::ffff:1234/"); | |
585 } | |
586 | |
587 void testNormalization() { | |
588 // The Uri constructor and the Uri.parse function performs RFC-3986 | |
589 // syntax based normalization. | |
590 | |
591 var uri; | |
592 | |
593 // Scheme: Only case normalization. Schemes cannot contain escapes. | |
594 uri = Uri.parse("A:"); | |
595 Expect.equals("a", uri.scheme); | |
596 uri = Uri.parse("Z:"); | |
597 Expect.equals("z", uri.scheme); | |
598 uri = Uri.parse("$SCHEMECHAR:"); | |
599 Expect.equals(SCHEMECHAR.toLowerCase(), uri.scheme); | |
600 | |
601 // Percent escape normalization. | |
602 // Escapes of unreserved characters are converted to the character, | |
603 // subject to case normalization in reg-name. | |
604 for (var i = 0; i < UNRESERVED.length; i++) { | |
605 var char = UNRESERVED[i]; | |
606 var escape = "%" + char.codeUnitAt(0).toRadixString(16); // all > 0xf. | |
607 | |
608 uri = Uri.parse("s://xX${escape}xX@yY${escape}yY/zZ${escape}zZ" | |
609 "?vV${escape}vV#wW${escape}wW"); | |
610 Expect.equals("xX${char}xX", uri.userInfo); | |
611 Expect.equals("yY${char}yY".toLowerCase(), uri.host); | |
612 Expect.equals("/zZ${char}zZ", uri.path); | |
613 Expect.equals("vV${char}vV", uri.query); | |
614 Expect.equals("wW${char}wW", uri.fragment); | |
615 | |
616 uri = Uri.parse("s://yY${escape}yY/zZ${escape}zZ" | |
617 "?vV${escape}vV#wW${escape}wW"); | |
618 Expect.equals("yY${char}yY".toLowerCase(), uri.host); | |
619 Expect.equals("/zZ${char}zZ", uri.path); | |
620 Expect.equals("vV${char}vV", uri.query); | |
621 Expect.equals("wW${char}wW", uri.fragment); | |
622 } | |
623 | |
624 // Escapes of reserved characters are kept, but upper-cased. | |
625 for (var escape in ["%00", "%1f", "%7F", "%fF"]) { | |
626 uri = Uri.parse("s://xX${escape}xX@yY${escape}yY/zZ${escape}zZ" | |
627 "?vV${escape}vV#wW${escape}wW"); | |
628 var normalizedEscape = escape.toUpperCase(); | |
629 Expect.equals("xX${normalizedEscape}xX", uri.userInfo); | |
630 Expect.equals("yy${normalizedEscape}yy", uri.host); | |
631 Expect.equals("/zZ${normalizedEscape}zZ", uri.path); | |
632 Expect.equals("vV${normalizedEscape}vV", uri.query); | |
633 Expect.equals("wW${normalizedEscape}wW", uri.fragment); | |
634 } | |
635 | |
636 // Some host normalization edge cases. | |
637 uri = Uri.parse("x://x%61X%41x%41X%61x/"); | |
638 Expect.equals("xaxaxaxax", uri.host); | |
639 | |
640 uri = Uri.parse("x://Xxxxxxxx/"); | |
641 Expect.equals("xxxxxxxx", uri.host); | |
642 | |
643 uri = Uri.parse("x://xxxxxxxX/"); | |
644 Expect.equals("xxxxxxxx", uri.host); | |
645 | |
646 uri = Uri.parse("x://xxxxxxxx%61/"); | |
647 Expect.equals("xxxxxxxxa", uri.host); | |
648 | |
649 uri = Uri.parse("x://%61xxxxxxxx/"); | |
650 Expect.equals("axxxxxxxx", uri.host); | |
651 | |
652 uri = Uri.parse("x://X/"); | |
653 Expect.equals("x", uri.host); | |
654 | |
655 uri = Uri.parse("x://%61/"); | |
656 Expect.equals("a", uri.host); | |
657 | |
658 uri = new Uri(scheme: "x", path: "//y"); | |
659 Expect.equals("//y", uri.path); | |
660 Expect.equals("x:////y", uri.toString()); | |
661 | |
662 uri = new Uri(scheme: "file", path: "//y"); | |
663 Expect.equals("//y", uri.path); | |
664 Expect.equals("file:////y", uri.toString()); | |
665 | |
666 // File scheme noralizes to always showing authority, even if empty. | |
667 uri = new Uri(scheme: "file", path: "/y"); | |
668 Expect.equals("file:///y", uri.toString()); | |
669 uri = new Uri(scheme: "file", path: "y"); | |
670 Expect.equals("file:///y", uri.toString()); | |
671 | |
672 // Empty host/query/fragment ensures the delimiter is there. | |
673 // Different from not being there. | |
674 Expect.equals("scheme:/", Uri.parse("scheme:/").toString()); | |
675 Expect.equals("scheme:/", new Uri(scheme: "scheme", path: "/").toString()); | |
676 | |
677 Expect.equals("scheme:///?#", Uri.parse("scheme:///?#").toString()); | |
678 Expect.equals( | |
679 "scheme:///#", | |
680 new Uri(scheme: "scheme", host: "", path: "/", query: "", fragment: "") | |
681 .toString()); | |
682 } | |
683 | |
684 void testReplace() { | |
685 var uris = [ | |
686 Uri.parse(""), | |
687 Uri.parse("a://@:/?#"), | |
688 Uri.parse("a://:/?#"), // Parsed as simple URI. | |
689 Uri.parse("a://b@c:4/e/f?g#h"), | |
690 Uri.parse("a://c:4/e/f?g#h"), // Parsed as simple URI. | |
691 Uri.parse("$SCHEMECHAR://$REGNAMECHAR:$DIGIT/$PCHAR/$PCHAR" | |
692 "?$QUERYCHAR#$QUERYCHAR"), // Parsed as simple URI. | |
693 Uri.parse("$SCHEMECHAR://$USERINFOCHAR@$REGNAMECHAR:$DIGIT/$PCHAR/$PCHAR" | |
694 "?$QUERYCHAR#$QUERYCHAR"), | |
695 ]; | |
696 for (var uri1 in uris) { | |
697 for (var uri2 in uris) { | |
698 if (identical(uri1, uri2)) continue; | |
699 var scheme = uri1.scheme; | |
700 var userInfo = uri1.hasAuthority ? uri1.userInfo : ""; | |
701 var host = uri1.hasAuthority ? uri1.host : null; | |
702 var port = uri1.hasAuthority ? uri1.port : 0; | |
703 var path = uri1.path; | |
704 var query = uri1.hasQuery ? uri1.query : null; | |
705 var fragment = uri1.hasFragment ? uri1.fragment : null; | |
706 | |
707 var tmp1 = uri1; | |
708 | |
709 void test() { | |
710 var tmp2 = new Uri( | |
711 scheme: scheme, | |
712 userInfo: userInfo, | |
713 host: host, | |
714 port: port, | |
715 path: path, | |
716 query: query == "" ? null : query, | |
717 queryParameters: query == "" ? {} : null, | |
718 fragment: fragment); | |
719 Expect.equals(tmp1, tmp2); | |
720 } | |
721 | |
722 test(); | |
723 | |
724 scheme = uri2.scheme; | |
725 tmp1 = tmp1.replace(scheme: scheme); | |
726 test(); | |
727 | |
728 if (uri2.hasAuthority) { | |
729 userInfo = uri2.userInfo; | |
730 host = uri2.host; | |
731 port = uri2.port; | |
732 tmp1 = tmp1.replace(userInfo: userInfo, host: host, port: port); | |
733 test(); | |
734 } | |
735 | |
736 path = uri2.path; | |
737 tmp1 = tmp1.replace(path: path); | |
738 test(); | |
739 | |
740 if (uri2.hasQuery) { | |
741 query = uri2.query; | |
742 tmp1 = tmp1.replace(query: query); | |
743 test(); | |
744 } | |
745 | |
746 if (uri2.hasFragment) { | |
747 fragment = uri2.fragment; | |
748 tmp1 = tmp1.replace(fragment: fragment); | |
749 test(); | |
750 } | |
751 } | |
752 } | |
753 | |
754 // Regression test, http://dartbug.com/20814 | |
755 var uri = Uri.parse("/no-authorty/"); | |
756 uri = uri.replace(fragment: "fragment"); | |
757 Expect.isFalse(uri.hasAuthority); | |
758 | |
759 uri = new Uri(scheme: "foo", path: "bar"); | |
760 uri = uri.replace(queryParameters: { | |
761 "x": ["42", "37"], | |
762 "y": ["43", "38"] | |
763 }); | |
764 var params = uri.queryParametersAll; | |
765 Expect.equals(2, params.length); | |
766 Expect.listEquals(["42", "37"], params["x"]); | |
767 Expect.listEquals(["43", "38"], params["y"]); | |
768 | |
769 // Test replacing with empty strings. | |
770 uri = Uri.parse("s://a:1/b/c?d#e"); | |
771 Expect.equals("s://a:1/b/c?d#", uri.replace(fragment: "").toString()); | |
772 Expect.equals("s://a:1/b/c?#e", uri.replace(query: "").toString()); | |
773 Expect.equals("s://a:1?d#e", uri.replace(path: "").toString()); | |
774 Expect.equals("s://:1/b/c?d#e", uri.replace(host: "").toString()); | |
775 | |
776 // Test uri.replace on uri with fragment | |
777 uri = Uri.parse('http://hello.com/fake#fragment'); | |
778 uri = uri.replace(path: "D/E/E"); | |
779 Expect.stringEquals('http://hello.com/D/E/E#fragment', uri.toString()); | |
780 } | |
781 | |
782 void testRegression28359() { | |
783 var uri = new Uri(path: "//"); | |
784 // This is an invalid path for a URI reference with no authority | |
785 // since it looks like an authority. | |
786 // Normalized to have an authority. | |
787 Expect.equals("////", "$uri"); | |
788 Expect.equals("//", uri.path); | |
789 Expect.isTrue(uri.hasAuthority, "$uri has authority"); | |
790 | |
791 uri = new Uri(path: "file:///wat"); | |
792 // This is an invalid path for a URI reference with no authority or scheme | |
793 // since the path looks like it starts with a scheme. | |
794 // Normalized by escaping the ":". | |
795 Expect.equals("file%3A///wat", uri.path); | |
796 Expect.equals("file%3A///wat", "$uri"); | |
797 Expect.isFalse(uri.hasAuthority); | |
798 Expect.isFalse(uri.hasScheme); | |
799 } | |
800 | |
801 main() { | |
802 testUri("http:", true); | |
803 testUri("file:///", true); | |
804 testUri("file", false); | |
805 testUri("http://user@example.com:8080/fisk?query=89&hest=silas", true); | |
806 testUri( | |
807 "http://user@example.com:8080/fisk?query=89&hest=silas#fragment", false); | |
808 Expect.stringEquals( | |
809 "http://user@example.com/a/b/c?query#fragment", | |
810 new Uri( | |
811 scheme: "http", | |
812 userInfo: "user", | |
813 host: "example.com", | |
814 port: 80, | |
815 path: "/a/b/c", | |
816 query: "query", | |
817 fragment: "fragment") | |
818 .toString()); | |
819 Expect.stringEquals( | |
820 "/a/b/c/", | |
821 new Uri( | |
822 scheme: null, | |
823 userInfo: null, | |
824 host: null, | |
825 port: 0, | |
826 path: "/a/b/c/", | |
827 query: null, | |
828 fragment: null) | |
829 .toString()); | |
830 Expect.stringEquals("file:///", Uri.parse("file:").toString()); | |
831 Expect.stringEquals("file:///", Uri.parse("file:/").toString()); | |
832 Expect.stringEquals("file:///", Uri.parse("file:").toString()); | |
833 Expect.stringEquals("file:///foo", Uri.parse("file:foo").toString()); | |
834 Expect.stringEquals("file:///foo", Uri.parse("file:/foo").toString()); | |
835 Expect.stringEquals("file://foo/", Uri.parse("file://foo").toString()); | |
836 | |
837 testResolvePath("/a/g", "/a/b/c/./../../g"); | |
838 testResolvePath("/a/g", "/a/b/c/./../../g"); | |
839 testResolvePath("/mid/6", "mid/content=5/../6"); | |
840 testResolvePath("/a/b/e", "a/b/c/d/../../e"); | |
841 testResolvePath("/a/b/e", "../a/b/c/d/../../e"); | |
842 testResolvePath("/a/b/e", "./a/b/c/d/../../e"); | |
843 testResolvePath("/a/b/e", "../a/b/./c/d/../../e"); | |
844 testResolvePath("/a/b/e", "./a/b/./c/d/../../e"); | |
845 testResolvePath("/a/b/e/", "./a/b/./c/d/../../e/."); | |
846 testResolvePath("/a/b/e/", "./a/b/./c/d/../../e/./."); | |
847 testResolvePath("/a/b/e/", "./a/b/./c/d/../../e/././."); | |
848 | |
849 testUriPerRFCs(); | |
850 | |
851 Expect.stringEquals( | |
852 "http://example.com", Uri.parse("http://example.com/a/b/c").origin); | |
853 Expect.stringEquals( | |
854 "https://example.com", Uri.parse("https://example.com/a/b/c").origin); | |
855 Expect.stringEquals("http://example.com:1234", | |
856 Uri.parse("http://example.com:1234/a/b/c").origin); | |
857 Expect.stringEquals("https://example.com:1234", | |
858 Uri.parse("https://example.com:1234/a/b/c").origin); | |
859 Expect.throws(() => Uri.parse("http:").origin, (e) { | |
860 return e is StateError; | |
861 }, "origin for URI with empty host should fail"); | |
862 Expect.throws( | |
863 () => new Uri( | |
864 scheme: "http", | |
865 userInfo: null, | |
866 host: "", | |
867 port: 80, | |
868 path: "/a/b/c", | |
869 query: "query", | |
870 fragment: "fragment") | |
871 .origin, (e) { | |
872 return e is StateError; | |
873 }, "origin for URI with empty host should fail"); | |
874 Expect.throws( | |
875 () => new Uri( | |
876 scheme: null, | |
877 userInfo: null, | |
878 host: "", | |
879 port: 80, | |
880 path: "/a/b/c", | |
881 query: "query", | |
882 fragment: "fragment") | |
883 .origin, (e) { | |
884 return e is StateError; | |
885 }, "origin for URI with empty scheme should fail"); | |
886 Expect.throws( | |
887 () => new Uri( | |
888 scheme: "http", | |
889 userInfo: null, | |
890 host: null, | |
891 port: 80, | |
892 path: "/a/b/c", | |
893 query: "query", | |
894 fragment: "fragment") | |
895 .origin, (e) { | |
896 return e is StateError; | |
897 }, "origin for URI with empty host should fail"); | |
898 Expect.throws(() => Uri.parse("http://:80").origin, (e) { | |
899 return e is StateError; | |
900 }, "origin for URI with empty host should fail"); | |
901 Expect.throws(() => Uri.parse("file://localhost/test.txt").origin, (e) { | |
902 return e is StateError; | |
903 }, "origin for non-http/https uri should fail"); | |
904 | |
905 // URI encode tests | |
906 // Create a string with code point 0x10000 encoded as a surrogate pair. | |
907 var s = UTF8.decode([0xf0, 0x90, 0x80, 0x80]); | |
908 | |
909 Expect.stringEquals("\u{10000}", s); | |
910 | |
911 testEncodeDecode("A + B", "A%20+%20B"); | |
912 testEncodeDecode("\uFFFE", "%EF%BF%BE"); | |
913 testEncodeDecode("\uFFFF", "%EF%BF%BF"); | |
914 testEncodeDecode("\uFFFE", "%EF%BF%BE"); | |
915 testEncodeDecode("\uFFFF", "%EF%BF%BF"); | |
916 testEncodeDecode("\x7f", "%7F"); | |
917 testEncodeDecode("\x80", "%C2%80"); | |
918 testEncodeDecode("\u0800", "%E0%A0%80"); | |
919 // All characters not escaped by encodeFull. | |
920 var unescapedFull = r"abcdefghijklmnopqrstuvwxyz" | |
921 r"ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
922 r"0123456789!#$&'()*+,-./:;=?@_~"; | |
923 // ASCII characters escaped by encodeFull: | |
924 var escapedFull = | |
925 "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" | |
926 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" | |
927 r' "%<>[\]^`{|}' | |
928 "\x7f"; | |
929 var escapedTo = "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F" | |
930 "%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F" | |
931 "%20%22%25%3C%3E%5B%5C%5D%5E%60%7B%7C%7D%7F"; | |
932 testEncodeDecode(unescapedFull, unescapedFull); | |
933 testEncodeDecode(escapedFull, escapedTo); | |
934 var nonAscii = | |
935 "\x80-\xff-\u{100}-\u{7ff}-\u{800}-\u{ffff}-\u{10000}-\u{10ffff}"; | |
936 var nonAsciiEncoding = "%C2%80-%C3%BF-%C4%80-%DF%BF-%E0%A0%80-%EF%BF%BF-" | |
937 "%F0%90%80%80-%F4%8F%BF%BF"; | |
938 testEncodeDecode(nonAscii, nonAsciiEncoding); | |
939 testEncodeDecode(s, "%F0%90%80%80"); | |
940 testEncodeDecodeComponent("A + B", "A%20%2B%20B"); | |
941 testEncodeDecodeComponent("\uFFFE", "%EF%BF%BE"); | |
942 testEncodeDecodeComponent("\uFFFF", "%EF%BF%BF"); | |
943 testEncodeDecodeComponent("\uFFFE", "%EF%BF%BE"); | |
944 testEncodeDecodeComponent("\uFFFF", "%EF%BF%BF"); | |
945 testEncodeDecodeComponent("\x7f", "%7F"); | |
946 testEncodeDecodeComponent("\x80", "%C2%80"); | |
947 testEncodeDecodeComponent("\u0800", "%E0%A0%80"); | |
948 testEncodeDecodeComponent(":/@',;?&=+\$", "%3A%2F%40'%2C%3B%3F%26%3D%2B%24"); | |
949 testEncodeDecodeComponent(s, "%F0%90%80%80"); | |
950 testEncodeDecodeQueryComponent("A + B", "A+%2B+B", "A+%2B+B", "A+%2B+B"); | |
951 testEncodeDecodeQueryComponent( | |
952 "æ ø å", "%C3%A6+%C3%B8+%C3%A5", "%E6+%F8+%E5", null); | |
953 testEncodeDecodeComponent(nonAscii, nonAsciiEncoding); | |
954 | |
955 // Invalid URI - : and @ is swapped, port ("host") should be numeric. | |
956 Expect.throws(() => Uri.parse("file://user@password:host/path"), | |
957 (e) => e is FormatException); | |
958 | |
959 testValidCharacters(); | |
960 testInvalidUrls(); | |
961 testNormalization(); | |
962 testReplace(); | |
963 testRegression28359(); | |
964 } | |
965 | |
966 String dump(Uri uri) { | |
967 return "URI: $uri\n" | |
968 " Scheme: ${uri.scheme} #${uri.scheme.length}\n" | |
969 " User-info: ${uri.userInfo} #${uri.userInfo.length}\n" | |
970 " Host: ${uri.host} #${uri.host.length}\n" | |
971 " Port: ${uri.port}\n" | |
972 " Path: ${uri.path} #${uri.path.length}\n" | |
973 " Query: ${uri.query} #${uri.query.length}\n" | |
974 " Fragment: ${uri.fragment} #${uri.fragment.length}\n"; | |
975 } | |
OLD | NEW |