Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(502)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 2086613003: Add fast-mode Uri class. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Tweak operator== Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.core; 5 part of dart.core;
6 6
7 // Frequently used character codes.
8 const int _SPACE = 0x20;
9 const int _PERCENT = 0x25;
10 const int _PLUS = 0x2B;
11 const int _DOT = 0x2E;
12 const int _SLASH = 0x2F;
13 const int _COLON = 0x3A;
14 const int _UPPER_CASE_A = 0x41;
15 const int _UPPER_CASE_Z = 0x5A;
16 const int _LEFT_BRACKET = 0x5B;
17 const int _BACKSLASH = 0x5C;
18 const int _RIGHT_BRACKET = 0x5D;
19 const int _LOWER_CASE_A = 0x61;
20 const int _LOWER_CASE_F = 0x66;
21 const int _LOWER_CASE_Z = 0x7A;
22
23 const String _hexDigits = "0123456789ABCDEF";
24
7 /** 25 /**
8 * A parsed URI, such as a URL. 26 * A parsed URI, such as a URL.
9 * 27 *
10 * **See also:** 28 * **See also:**
11 * 29 *
12 * * [URIs][uris] in the [library tour][libtour] 30 * * [URIs][uris] in the [library tour][libtour]
13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986) 31 * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
14 * 32 *
15 * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris 33 * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
16 * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.ht ml 34 * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.ht ml
17 */ 35 */
18 class Uri { 36 abstract class Uri {
19 /** 37 /**
20 * The scheme component of the URI. 38 * Returns the natural base URI for the current platform.
21 * 39 *
22 * Returns the empty string if there is no scheme component. 40 * When running in a browser this is the current URL of the current page
41 * (from `window.location.href`).
23 * 42 *
24 * A URI scheme is case insensitive. 43 * When not running in a browser this is the file URI referencing
25 * The returned scheme is canonicalized to lowercase letters. 44 * the current working directory.
26 */ 45 */
27 // We represent the missing scheme as an empty string. 46 external static Uri get base;
28 // A valid scheme cannot be empty.
29 final String scheme;
30
31 /**
32 * The user-info part of the authority.
33 *
34 * Does not distinguish between an empty user-info and an absent one.
35 * The value is always non-null.
36 * Is considered absent if [_host] is `null`.
37 */
38 final String _userInfo;
39
40 /**
41 * The host name of the URI.
42 *
43 * Set to `null` if there is no authority in the URI.
44 * The host name is the only mandatory part of an authority, so we use
45 * it to mark whether an authority part was present or not.
46 */
47 final String _host;
48
49 /**
50 * The port number part of the authority.
51 *
52 * The port. Set to null if there is no port. Normalized to null if
53 * the port is the default port for the scheme.
54 */
55 int _port;
56
57 /**
58 * The path of the URI.
59 *
60 * Always non-null.
61 */
62 String _path;
63
64 // The query content, or null if there is no query.
65 final String _query;
66
67 // The fragment content, or null if there is no fragment.
68 final String _fragment;
69
70 /**
71 * Cache the computed return value of [pathSegements].
72 */
73 List<String> _pathSegments;
74
75 /**
76 * Cache the computed return value of [queryParameters].
77 */
78 Map<String, String> _queryParameters;
79 Map<String, List<String>> _queryParameterLists;
80
81 /// Internal non-verifying constructor. Only call with validated arguments.
82 Uri._internal(this.scheme,
83 this._userInfo,
84 this._host,
85 this._port,
86 this._path,
87 this._query,
88 this._fragment);
89 47
90 /** 48 /**
91 * Creates a new URI from its components. 49 * Creates a new URI from its components.
92 * 50 *
93 * Each component is set through a named argument. Any number of 51 * Each component is set through a named argument. Any number of
94 * components can be provided. The [path] and [query] components can be set 52 * components can be provided. The [path] and [query] components can be set
95 * using either of two different named arguments. 53 * using either of two different named arguments.
96 * 54 *
97 * The scheme component is set through [scheme]. The scheme is 55 * The scheme component is set through [scheme]. The scheme is
98 * normalized to all lowercase letters. If the scheme is omitted or empty, 56 * normalized to all lowercase letters. If the scheme is omitted or empty,
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
151 * use an empty map for `queryParameters`. 109 * use an empty map for `queryParameters`.
152 * 110 *
153 * If both `query` and `queryParameters` are omitted or `null`, 111 * If both `query` and `queryParameters` are omitted or `null`,
154 * the URI has no query part. 112 * the URI has no query part.
155 * 113 *
156 * The fragment component is set through [fragment]. 114 * The fragment component is set through [fragment].
157 * It should be a valid URI fragment, but invalid characters other than 115 * It should be a valid URI fragment, but invalid characters other than
158 * general delimiters, are escaped if necessary. 116 * general delimiters, are escaped if necessary.
159 * If `fragment` is omitted or `null`, the URI has no fragment part. 117 * If `fragment` is omitted or `null`, the URI has no fragment part.
160 */ 118 */
161 factory Uri({String scheme : "", 119 factory Uri({String scheme,
162 String userInfo : "", 120 String userInfo,
163 String host, 121 String host,
164 int port, 122 int port,
165 String path, 123 String path,
166 Iterable<String> pathSegments, 124 Iterable<String> pathSegments,
167 String query, 125 String query,
168 Map<String, dynamic/*String|Iterable<String>*/> queryParameters, 126 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
169 String fragment}) { 127 String fragment}) = _Uri;
170 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
171 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
172 host = _makeHost(host, 0, _stringOrNullLength(host), false);
173 // Special case this constructor for backwards compatibility.
174 if (query == "") query = null;
175 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
176 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
177 port = _makePort(port, scheme);
178 bool isFile = (scheme == "file");
179 if (host == null &&
180 (userInfo.isNotEmpty || port != null || isFile)) {
181 host = "";
182 }
183 bool hasAuthority = (host != null);
184 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
185 scheme, hasAuthority);
186 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
187 path = _normalizeRelativePath(path);
188 } else {
189 path = _removeDotSegments(path);
190 }
191 return new Uri._internal(scheme, userInfo, host, port,
192 path, query, fragment);
193 }
194 128
195 /** 129 /**
196 * Creates a new `http` URI from authority, path and query. 130 * Creates a new `http` URI from authority, path and query.
197 * 131 *
198 * Examples: 132 * Examples:
199 * 133 *
200 * ``` 134 * ```
201 * // http://example.org/path?q=dart. 135 * // http://example.org/path?q=dart.
202 * new Uri.http("google.com", "/search", { "q" : "dart" }); 136 * new Uri.http("google.com", "/search", { "q" : "dart" });
203 * 137 *
(...skipping 16 matching lines...) Expand all
220 * 154 *
221 * The `path` component is set from the [unencodedPath] 155 * The `path` component is set from the [unencodedPath]
222 * argument. The path passed must not be encoded as this constructor 156 * argument. The path passed must not be encoded as this constructor
223 * encodes the path. 157 * encodes the path.
224 * 158 *
225 * The `query` component is set from the optional [queryParameters] 159 * The `query` component is set from the optional [queryParameters]
226 * argument. 160 * argument.
227 */ 161 */
228 factory Uri.http(String authority, 162 factory Uri.http(String authority,
229 String unencodedPath, 163 String unencodedPath,
230 [Map<String, String> queryParameters]) { 164 [Map<String, String> queryParameters]) = _Uri.http;
231 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
232 }
233 165
234 /** 166 /**
235 * Creates a new `https` URI from authority, path and query. 167 * Creates a new `https` URI from authority, path and query.
236 * 168 *
237 * This constructor is the same as [Uri.http] except for the scheme 169 * This constructor is the same as [Uri.http] except for the scheme
238 * which is set to `https`. 170 * which is set to `https`.
239 */ 171 */
240 factory Uri.https(String authority, 172 factory Uri.https(String authority,
241 String unencodedPath, 173 String unencodedPath,
242 [Map<String, String> queryParameters]) { 174 [Map<String, String> queryParameters]) = _Uri.https;
243 return _makeHttpUri("https", authority, unencodedPath, queryParameters); 175
176 /**
177 * Creates a new file URI from an absolute or relative file path.
178 *
179 * The file path is passed in [path].
180 *
181 * This path is interpreted using either Windows or non-Windows
182 * semantics.
183 *
184 * With non-Windows semantics the slash ("/") is used to separate
185 * path segments.
186 *
187 * With Windows semantics, backslash ("\") and forward-slash ("/")
188 * are used to separate path segments, except if the path starts
189 * with "\\?\" in which case, only backslash ("\") separates path
190 * segments.
191 *
192 * If the path starts with a path separator an absolute URI is
193 * created. Otherwise a relative URI is created. One exception from
194 * this rule is that when Windows semantics is used and the path
195 * starts with a drive letter followed by a colon (":") and a
196 * path separator then an absolute URI is created.
197 *
198 * The default for whether to use Windows or non-Windows semantics
199 * determined from the platform Dart is running on. When running in
200 * the standalone VM this is detected by the VM based on the
201 * operating system. When running in a browser non-Windows semantics
202 * is always used.
203 *
204 * To override the automatic detection of which semantics to use pass
205 * a value for [windows]. Passing `true` will use Windows
206 * semantics and passing `false` will use non-Windows semantics.
207 *
208 * Examples using non-Windows semantics:
209 *
210 * ```
211 * // xxx/yyy
212 * new Uri.file("xxx/yyy", windows: false);
213 *
214 * // xxx/yyy/
215 * new Uri.file("xxx/yyy/", windows: false);
216 *
217 * // file:///xxx/yyy
218 * new Uri.file("/xxx/yyy", windows: false);
219 *
220 * // file:///xxx/yyy/
221 * new Uri.file("/xxx/yyy/", windows: false);
222 *
223 * // C:
224 * new Uri.file("C:", windows: false);
225 * ```
226 *
227 * Examples using Windows semantics:
228 *
229 * ```
230 * // xxx/yyy
231 * new Uri.file(r"xxx\yyy", windows: true);
232 *
233 * // xxx/yyy/
234 * new Uri.file(r"xxx\yyy\", windows: true);
235 *
236 * file:///xxx/yyy
237 * new Uri.file(r"\xxx\yyy", windows: true);
238 *
239 * file:///xxx/yyy/
240 * new Uri.file(r"\xxx\yyy/", windows: true);
241 *
242 * // file:///C:/xxx/yyy
243 * new Uri.file(r"C:\xxx\yyy", windows: true);
244 *
245 * // This throws an error. A path with a drive letter is not absolute.
246 * new Uri.file(r"C:", windows: true);
247 *
248 * // This throws an error. A path with a drive letter is not absolute.
249 * new Uri.file(r"C:xxx\yyy", windows: true);
250 *
251 * // file://server/share/file
252 * new Uri.file(r"\\server\share\file", windows: true);
253 * ```
254 *
255 * If the path passed is not a legal file path [ArgumentError] is thrown.
256 */
257 factory Uri.file(String path, {bool windows}) = _Uri.file;
258
259 /**
260 * Like [Uri.file] except that a non-empty URI path ends in a slash.
261 *
262 * If [path] is not empty, and it doesn't end in a directory separator,
263 * then a slash is added to the returned URI's path.
264 * In all other cases, the result is the same as returned by `Uri.file`.
265 */
266 factory Uri.directory(String path, {bool windows}) = _Uri.directory;
267
268 /**
269 * Creates a `data:` URI containing the [content] string.
270 *
271 * Converts the content to a bytes using [encoding] or the charset specified
272 * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
273 * then encodes the bytes into the resulting data URI.
274 *
275 * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
276 * bytes is replaced by a percent encoding). If [base64] is true, the bytes
277 * are instead encoded using [BASE64].
278 *
279 * If [encoding] is not provided and [parameters] has a `charset` entry,
280 * that name is looked up using [Encoding.getByName],
281 * and if the lookup returns an encoding, that encoding is used to convert
282 * [content] to bytes.
283 * If providing both an [encoding] and a charset [parameter], they should
284 * agree, otherwise decoding won't be able to use the charset parameter
285 * to determine the encoding.
286 *
287 * If [mimeType] and/or [parameters] are supplied, they are added to the
288 * created URI. If any of these contain characters that are not allowed
289 * in the data URI, the character is percent-escaped. If the character is
290 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
291 * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
292 * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
293 *
294 * To read the content back, use [UriData.contentAsString].
295 */
296 factory Uri.dataFromString(String content,
297 {String mimeType,
298 Encoding encoding,
299 Map<String, String> parameters,
300 bool base64: false}) {
301 UriData data = new UriData.fromString(content,
302 mimeType: mimeType,
303 encoding: encoding,
304 parameters: parameters,
305 base64: base64);
306 return data.uri;
244 } 307 }
245 308
246 /** 309 /**
310 * Creates a `data:` URI containing an encoding of [bytes].
311 *
312 * Defaults to Base64 encoding the bytes, but if [percentEncoded]
313 * is `true`, the bytes will instead be percent encoded (any non-ASCII
314 * or non-valid-ASCII-character byte is replaced by a percent encoding).
315 *
316 * To read the bytes back, use [UriData.contentAsBytes].
317 *
318 * It defaults to having the mime-type `application/octet-stream`.
319 * The [mimeType] and [parameters] are added to the created URI.
320 * If any of these contain characters that are not allowed
321 * in the data URI, the character is percent-escaped. If the character is
322 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
323 * encoded.
324 */
325 factory Uri.dataFromBytes(List<int> bytes,
326 {mimeType: "application/octet-stream",
327 Map<String, String> parameters,
328 percentEncoded: false}) {
329 UriData data = new UriData.fromBytes(bytes,
330 mimeType: mimeType,
331 parameters: parameters,
332 percentEncoded: percentEncoded);
333 return data.uri;
334 }
335
336 /**
337 * The scheme component of the URI.
338 *
339 * Returns the empty string if there is no scheme component.
340 *
341 * A URI scheme is case insensitive.
342 * The returned scheme is canonicalized to lowercase letters.
343 */
344 String get scheme;
345
346 /**
247 * Returns the authority component. 347 * Returns the authority component.
248 * 348 *
249 * The authority is formatted from the [userInfo], [host] and [port] 349 * The authority is formatted from the [userInfo], [host] and [port]
250 * parts. 350 * parts.
251 * 351 *
252 * Returns the empty string if there is no authority component. 352 * Returns the empty string if there is no authority component.
253 */ 353 */
254 String get authority { 354 String get authority;
255 if (!hasAuthority) return "";
256 var sb = new StringBuffer();
257 _writeAuthority(sb);
258 return sb.toString();
259 }
260 355
261 /** 356 /**
262 * Returns the user info part of the authority component. 357 * Returns the user info part of the authority component.
263 * 358 *
264 * Returns the empty string if there is no user info in the 359 * Returns the empty string if there is no user info in the
265 * authority component. 360 * authority component.
266 */ 361 */
267 String get userInfo => _userInfo; 362 String get userInfo;
268 363
269 /** 364 /**
270 * Returns the host part of the authority component. 365 * Returns the host part of the authority component.
271 * 366 *
272 * Returns the empty string if there is no authority component and 367 * Returns the empty string if there is no authority component and
273 * hence no host. 368 * hence no host.
274 * 369 *
275 * If the host is an IP version 6 address, the surrounding `[` and `]` is 370 * If the host is an IP version 6 address, the surrounding `[` and `]` is
276 * removed. 371 * removed.
277 * 372 *
278 * The host string is case-insensitive. 373 * The host string is case-insensitive.
279 * The returned host name is canonicalized to lower-case 374 * The returned host name is canonicalized to lower-case
280 * with upper-case percent-escapes. 375 * with upper-case percent-escapes.
281 */ 376 */
282 String get host { 377 String get host;
283 if (_host == null) return "";
284 if (_host.startsWith('[')) {
285 return _host.substring(1, _host.length - 1);
286 }
287 return _host;
288 }
289 378
290 /** 379 /**
291 * Returns the port part of the authority component. 380 * Returns the port part of the authority component.
292 * 381 *
293 * Returns the defualt port if there is no port number in the authority 382 * Returns the defualt port if there is no port number in the authority
294 * component. That's 80 for http, 443 for https, and 0 for everything else. 383 * component. That's 80 for http, 443 for https, and 0 for everything else.
295 */ 384 */
296 int get port { 385 int get port;
297 if (_port == null) return _defaultPort(scheme);
298 return _port;
299 }
300
301 // The default port for the scheme of this Uri..
302 static int _defaultPort(String scheme) {
303 if (scheme == "http") return 80;
304 if (scheme == "https") return 443;
305 return 0;
306 }
307 386
308 /** 387 /**
309 * Returns the path component. 388 * Returns the path component.
310 * 389 *
311 * The returned path is encoded. To get direct access to the decoded 390 * The returned path is encoded. To get direct access to the decoded
312 * path use [pathSegments]. 391 * path use [pathSegments].
313 * 392 *
314 * Returns the empty string if there is no path component. 393 * Returns the empty string if there is no path component.
315 */ 394 */
316 String get path => _path; 395 String get path;
317 396
318 /** 397 /**
319 * Returns the query component. The returned query is encoded. To get 398 * Returns the query component. The returned query is encoded. To get
320 * direct access to the decoded query use [queryParameters]. 399 * direct access to the decoded query use [queryParameters].
321 * 400 *
322 * Returns the empty string if there is no query component. 401 * Returns the empty string if there is no query component.
323 */ 402 */
324 String get query => (_query == null) ? "" : _query; 403 String get query;
325 404
326 /** 405 /**
327 * Returns the fragment identifier component. 406 * Returns the fragment identifier component.
328 * 407 *
329 * Returns the empty string if there is no fragment identifier 408 * Returns the empty string if there is no fragment identifier
330 * component. 409 * component.
331 */ 410 */
332 String get fragment => (_fragment == null) ? "" : _fragment; 411 String get fragment;
412
413 /**
414 * Returns the URI path split into its segments. Each of the segments in the
415 * returned list have been decoded. If the path is empty the empty list will
416 * be returned. A leading slash `/` does not affect the segments returned.
417 *
418 * The returned list is unmodifiable and will throw [UnsupportedError] on any
419 * calls that would mutate it.
420 */
421 List<String> get pathSegments;
422
423 /**
424 * Returns the URI query split into a map according to the rules
425 * specified for FORM post in the [HTML 4.01 specification section
426 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
427 * Each key and value in the returned map has been decoded.
428 * If there is no query the empty map is returned.
429 *
430 * Keys in the query string that have no value are mapped to the
431 * empty string.
432 * If a key occurs more than once in the query string, it is mapped to
433 * an arbitrary choice of possible value.
434 * The [queryParametersAll] getter can provide a map
435 * that maps keys to all of their values.
436 *
437 * The returned map is unmodifiable.
438 */
439 Map<String, String> get queryParameters;
440
441 /**
442 * Returns the URI query split into a map according to the rules
443 * specified for FORM post in the [HTML 4.01 specification section
444 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
445 * Each key and value in the returned map has been decoded. If there is no
446 * query the empty map is returned.
447 *
448 * Keys are mapped to lists of their values. If a key occurs only once,
449 * its value is a singleton list. If a key occurs with no value, the
450 * empty string is used as the value for that occurrence.
451 *
452 * The returned map and the lists it contains are unmodifiable.
453 */
454 Map<String, List<String>> get queryParametersAll;
455
456 /**
457 * Returns whether the URI is absolute.
458 *
459 * A URI is an absolute URI in the sense of RFC 3986 if it has a scheme
460 * and no fragment.
461 */
462 bool get isAbsolute;
463
464 /**
465 * Returns whether the URI has a [scheme] component.
466 */
467 bool get hasScheme => scheme.isNotEmpty;
468
469 /**
470 * Returns whether the URI has an [authority] component.
471 */
472 bool get hasAuthority;
473
474 /**
475 * Returns whether the URI has an explicit port.
476 *
477 * If the port number is the default port number
478 * (zero for unrecognized schemes, with http (80) and https (443) being
479 * recognized),
480 * then the port is made implicit and omitted from the URI.
481 */
482 bool get hasPort;
483
484 /**
485 * Returns whether the URI has a query part.
486 */
487 bool get hasQuery;
488
489 /**
490 * Returns whether the URI has a fragment part.
491 */
492 bool get hasFragment;
493
494 /**
495 * Returns whether the URI has an empty path.
496 */
497 bool get hasEmptyPath;
498
499 /**
500 * Returns whether the URI has an absolute path (starting with '/').
501 */
502 bool get hasAbsolutePath;
503
504 /**
505 * Returns the origin of the URI in the form scheme://host:port for the
506 * schemes http and https.
507 *
508 * It is an error if the scheme is not "http" or "https".
509 *
510 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
511 */
512 String get origin;
513
514 /**
515 * Returns the file path from a file URI.
516 *
517 * The returned path has either Windows or non-Windows
518 * semantics.
519 *
520 * For non-Windows semantics the slash ("/") is used to separate
521 * path segments.
522 *
523 * For Windows semantics the backslash ("\") separator is used to
524 * separate path segments.
525 *
526 * If the URI is absolute the path starts with a path separator
527 * unless Windows semantics is used and the first path segment is a
528 * drive letter. When Windows semantics is used a host component in
529 * the uri in interpreted as a file server and a UNC path is
530 * returned.
531 *
532 * The default for whether to use Windows or non-Windows semantics
533 * determined from the platform Dart is running on. When running in
534 * the standalone VM this is detected by the VM based on the
535 * operating system. When running in a browser non-Windows semantics
536 * is always used.
537 *
538 * To override the automatic detection of which semantics to use pass
539 * a value for [windows]. Passing `true` will use Windows
540 * semantics and passing `false` will use non-Windows semantics.
541 *
542 * If the URI ends with a slash (i.e. the last path component is
543 * empty) the returned file path will also end with a slash.
544 *
545 * With Windows semantics URIs starting with a drive letter cannot
546 * be relative to the current drive on the designated drive. That is
547 * for the URI `file:///c:abc` calling `toFilePath` will throw as a
548 * path segment cannot contain colon on Windows.
549 *
550 * Examples using non-Windows semantics (resulting of calling
551 * toFilePath in comment):
552 *
553 * Uri.parse("xxx/yyy"); // xxx/yyy
554 * Uri.parse("xxx/yyy/"); // xxx/yyy/
555 * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
556 * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
557 * Uri.parse("file:///C:"); // /C:
558 * Uri.parse("file:///C:a"); // /C:a
559 *
560 * Examples using Windows semantics (resulting URI in comment):
561 *
562 * Uri.parse("xxx/yyy"); // xxx\yyy
563 * Uri.parse("xxx/yyy/"); // xxx\yyy\
564 * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
565 * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
566 * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
567 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
568 * // cannot contain colon on Windows.
569 * Uri.parse("file://server/share/file"); // \\server\share\file
570 *
571 * If the URI is not a file URI calling this throws
572 * [UnsupportedError].
573 *
574 * If the URI cannot be converted to a file path calling this throws
575 * [UnsupportedError].
576 */
577 // TODO(lrn): Deprecate and move functionality to File class or similar.
578 // The core libraries should not worry about the platform.
579 String toFilePath({bool windows});
580
581 /**
582 * Access the structure of a `data:` URI.
583 *
584 * Returns a [UriData] object for `data:` URIs and `null` for all other
585 * URIs.
586 * The [UriData] object can be used to access the media type and data
587 * of a `data:` URI.
588 */
589 UriData get data;
590
591 /// Returns a hash code computed as `toString().hashCode`.
592 ///
593 /// This guarantees that URIs with the same normalized
594 int get hashCode;
595
596 /// A URI is equal to another URI with the same normalized representation.
597 bool operator==(Object other);
598
599 /// Returns the normalized string representation of the URI.
600 String toString();
601
602 /**
603 * Returns a new `Uri` based on this one, but with some parts replaced.
604 *
605 * This method takes the same parameters as the [new Uri] constructor,
606 * and they have the same meaning.
607 *
608 * At most one of [path] and [pathSegments] must be provided.
609 * Likewise, at most one of [query] and [queryParameters] must be provided.
610 *
611 * Each part that is not provided will default to the corresponding
612 * value from this `Uri` instead.
613 *
614 * This method is different from [Uri.resolve] which overrides in a
615 * hierarchial manner,
616 * and can instead replace each part of a `Uri` individually.
617 *
618 * Example:
619 *
620 * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
621 * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
622 * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
623 *
624 * This method acts similarly to using the `new Uri` constructor with
625 * some of the arguments taken from this `Uri` . Example:
626 *
627 * Uri uri3 = new Uri(
628 * scheme: "A",
629 * userInfo: uri1.userInfo,
630 * host: uri1.host,
631 * port: uri1.port,
632 * path: "D/E/E",
633 * query: uri1.query,
634 * fragment: "G");
635 * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
636 * print(uri2 == uri3); // prints true.
637 *
638 * Using this method can be seen as a shorthand for the `Uri` constructor
639 * call above, but may also be slightly faster because the parts taken
640 * from this `Uri` need not be checked for validity again.
641 */
642 Uri replace({String scheme,
643 String userInfo,
644 String host,
645 int port,
646 String path,
647 Iterable<String> pathSegments,
648 String query,
649 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
650 String fragment});
651
652 /**
653 * Returns a `Uri` that differs from this only in not having a fragment.
654 *
655 * If this `Uri` does not have a fragment, it is itself returned.
656 */
657 Uri removeFragment();
658
659 /**
660 * Resolve [reference] as an URI relative to `this`.
661 *
662 * First turn [reference] into a URI using [Uri.parse]. Then resolve the
663 * resulting URI relative to `this`.
664 *
665 * Returns the resolved URI.
666 *
667 * See [resolveUri] for details.
668 */
669 Uri resolve(String reference);
670
671 /**
672 * Resolve [reference] as an URI relative to `this`.
673 *
674 * Returns the resolved URI.
675 *
676 * The algorithm "Transform Reference" for resolving a reference is described
677 * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1 123").
678 *
679 * Updated to handle the case where the base URI is just a relative path -
680 * that is: when it has no scheme or authority and the path does not start
681 * with a slash.
682 * In that case, the paths are combined without removing leading "..", and
683 * an empty path is not converted to "/".
684 */
685 Uri resolveUri(Uri reference);
686
687 /**
688 * Returns a URI where the path has been normalized.
689 *
690 * A normalized path does not contain `.` segments or non-leading `..`
691 * segments.
692 * Only a relative path with no scheme or authority may contain
693 * leading `..` segments,
694 * a path that starts with `/` will also drop any leading `..` segments.
695 *
696 * This uses the same normalization strategy as `new Uri().resolve(this)`.
697 *
698 * Does not change any part of the URI except the path.
699 *
700 * The default implementation of `Uri` always normalizes paths, so calling
701 * this function has no effect.
702 */
703 Uri normalizePath();
333 704
334 /** 705 /**
335 * Creates a new `Uri` object by parsing a URI string. 706 * Creates a new `Uri` object by parsing a URI string.
336 * 707 *
337 * If [start] and [end] are provided, only the substring from `start` 708 * If [start] and [end] are provided, only the substring from `start`
338 * to `end` is parsed as a URI. 709 * to `end` is parsed as a URI.
339 * 710 *
340 * If the string is not valid as a URI or URI reference, 711 * If the string is not valid as a URI or URI reference,
341 * a [FormatException] is thrown. 712 * a [FormatException] is thrown.
342 */ 713 */
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 // segment = *pchar 758 // segment = *pchar
388 // segment-nz = 1*pchar 759 // segment-nz = 1*pchar
389 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) 760 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
390 // ; non-zero-length segment without any colon ":" 761 // ; non-zero-length segment without any colon ":"
391 // 762 //
392 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 763 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
393 // 764 //
394 // query = *( pchar / "/" / "?" ) 765 // query = *( pchar / "/" / "?" )
395 // 766 //
396 // fragment = *( pchar / "/" / "?" ) 767 // fragment = *( pchar / "/" / "?" )
397 const int EOI = -1; 768 end ??= uri.length;
398 769
399 String scheme = ""; 770 // Special case data:URIs. Ignore case when testing.
400 String userinfo = ""; 771 if (end >= start + 5) {
401 String host = null; 772 int dataDelta = _startsWithData(uri, start);
402 int port = null; 773 if (dataDelta == 0) {
403 String path = null; 774 // The case is right.
404 String query = null; 775 if (start > 0 || end < uri.length) uri = uri.substring(start, end);
405 String fragment = null; 776 return UriData._parse(uri, 5, null).uri;
406 if (end == null) end = uri.length; 777 } else if (dataDelta == 0x20) {
407 778 return UriData._parse(uri.substring(start + 5, end), 0, null).uri;
408 int index = start; 779 }
409 int pathStart = start; 780 // Otherwise the URI doesn't start with "data:" or any case variant of it.
410 // End of input-marker. 781 }
411 int char = EOI; 782
412 783 // TODO(lrn): Consider inlining _scanUri here.
413 void parseAuth() { 784 // The following index-normalization belongs with the scanning, but is
414 if (index == end) { 785 // easier to do here because we already have extracted variables from the
floitsch 2016/06/29 23:41:47 I think you should do the normalizations in _scanU
Lasse Reichstein Nielsen 2016/06/30 10:27:31 It probably shouldn't but in practice I see a ~10%
415 char = EOI; 786 // indices list.
416 return; 787 var indices = _scanUri(uri, start, end);
417 } 788
418 int authStart = index; 789 int schemeEnd = indices[_schemeEndIndex];
419 int lastColon = -1; 790 int hostStart = indices[_hostStartIndex] + 1;
420 int lastAt = -1; 791 int portStart = indices[_portStartIndex];
421 char = uri.codeUnitAt(index); 792 int pathStart = indices[_pathStartIndex];
422 while (index < end) { 793 int queryStart = indices[_queryStartIndex];
423 char = uri.codeUnitAt(index); 794 int fragmentStart = indices[_fragmentStartIndex];
424 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) { 795
425 break; 796 // We may discover scheme while handling special cases.
426 } 797 String scheme;
427 if (char == _AT_SIGN) { 798
428 lastAt = index; 799 // Derive some indices that weren't set to normalize the indices.
429 lastColon = -1; 800 // If fragment but no query, set query to start at fragment.
430 } else if (char == _COLON) { 801 if (fragmentStart < queryStart) queryStart = fragmentStart;
431 lastColon = index; 802 // If scheme but no authority, the pathStart isn't set.
432 } else if (char == _LEFT_BRACKET) { 803 if (schemeEnd >= start && hostStart <= start) pathStart = schemeEnd + 1;
433 lastColon = -1; 804 // If scheme or authority but pathStart isn't set.
434 int endBracket = uri.indexOf(']', index + 1); 805 if (pathStart == start && (schemeEnd >= start || hostStart > start)) {
435 if (endBracket == -1) { 806 pathStart = queryStart;
436 index = end; 807 }
437 char = EOI; 808 // If authority and no port.
438 break; 809 // (including when user-info contains : and portStart >= 0).
439 } else { 810 if (portStart < hostStart) portStart = pathStart;
440 index = endBracket; 811
812 assert(hostStart == start || schemeEnd <= hostStart);
813 assert(hostStart <= portStart);
814 assert(schemeEnd <= pathStart);
815 assert(portStart <= pathStart);
816 assert(pathStart <= queryStart);
817 assert(queryStart <= fragmentStart);
818
819 bool isSimple = indices[_notSimpleIndex] < start;
820
821 if (isSimple) {
822 // Check/do normalizations that weren't detected by the scanner.
823 // This includes removal of empty port or userInfo,
824 // or scheme specific port and path normalizations.
825 if (hostStart > schemeEnd + 3) {
826 // Always be non-simple if URI contains user-info.
827 // The scanner doesn't set the not-simple position in this case because
828 // it's setting the host-start position instead.
829 isSimple = false;
830 } else if (portStart > start && portStart + 1 == pathStart) {
831 // If the port is empty, it should be omitted.
832 // Pathological case, don't bother correcting it.
833 isSimple = false;
834 } else if (hostStart == schemeEnd + 4) {
835 // If the userInfo is empty, it should be omitted.
836 // (4 is length of "://@").
837 // Pathological case, don't bother correcting it.
838 isSimple = false;
839 } else if (queryStart < end &&
840 (queryStart == pathStart + 2 &&
841 uri.startsWith("..", pathStart)) ||
842 (queryStart > pathStart + 2 &&
843 uri.startsWith("/..", queryStart - 3))) {
844 // The path ends in a ".." segment. This should be normalized to "../".
845 // We didn't detect this while scanning because a query or fragment was
846 // detected at the same time (which is why we only need to check this
847 // if there is something after the path).
848 isSimple = false;
849 } else {
850 // There are a few scheme-based normalizations that
851 // the scanner couldn't check.
852 // That means that the input is very close to simple, so just do
853 // the normalizations.
854 if (schemeEnd == start + 4) {
855 // Do scheme based normalizations for file, http.
856 if (uri.startsWith("file", start)) {
857 scheme = "file";
858 if (hostStart <= start) {
859 // File URIs should have an authority.
860 // Paths after an authority should be absolute.
861 String schemeAuth = "file://";
862 int delta = 2;
863 if (!uri.startsWith("/", pathStart)) {
864 schemeAuth = "file:///";
865 delta = 3;
866 }
867 uri = schemeAuth + uri.substring(pathStart, end);
868 schemeEnd -= start;
869 hostStart = 7;
870 portStart = 7;
871 pathStart = 7;
872 queryStart += delta - start;
873 fragmentStart += delta - start;
874 start = 0;
875 end = uri.length;
876 } else if (pathStart == queryStart) {
877 // Uri has authority and empty path. Add "/" as path.
878 uri = "${uri.substring(start, pathStart)}/"
879 "${uri.substring(queryStart, end)}";
880 schemeEnd -= start;
881 hostStart -= start;
882 portStart -= start;
883 pathStart -= start;
884 queryStart += 1 - start;
885 fragmentStart += 1 - start;
886 start = 0;
887 end = uri.length;
888 }
889 } else if (uri.startsWith("http", start)) {
890 scheme = "http";
891 // HTTP URIs should not have an explicit port of 80.
892 if (portStart > start && portStart + 3 == pathStart &&
893 uri.startsWith("80", portStart + 1)) {
894 uri = uri.substring(start, portStart) +
895 uri.substring(pathStart, end);
896 schemeEnd -= start;
897 hostStart -= start;
898 portStart -= start;
899 pathStart -= 3 + start;
900 queryStart -= 3 + start;
901 fragmentStart -= 3 + start;
902 start = 0;
903 end = uri.length;
904 }
905 }
906 } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
907 scheme = "https";
908 // HTTPS URIs should not have an explicit port of 443.
909 if (portStart > start && portStart + 4 == pathStart &&
910 uri.startsWith("443", portStart + 1)) {
911 uri = uri.substring(start, portStart) +
912 uri.substring(pathStart, end);
913 schemeEnd -= start;
914 hostStart -= start;
915 portStart -= start;
916 pathStart -= 4 + start;
917 queryStart -= 4 + start;
918 fragmentStart -= 4 + start;
919 start = 0;
920 end = uri.length;
441 } 921 }
442 } 922 }
443 index++; 923 }
444 char = EOI; 924 }
445 } 925
446 int hostStart = authStart; 926 if (isSimple) {
447 int hostEnd = index; 927 if (start > 0 || end < uri.length) {
448 if (lastAt >= 0) { 928 uri = uri.substring(start, end);
449 userinfo = _makeUserInfo(uri, authStart, lastAt); 929 if (schemeEnd >= 0) schemeEnd -= start;
450 hostStart = lastAt + 1; 930 if (hostStart > 0) {
451 } 931 hostStart -= start;
452 if (lastColon >= 0) { 932 portStart -= start;
453 int portNumber; 933 }
454 if (lastColon + 1 < index) { 934 pathStart -= start;
455 portNumber = 0; 935 queryStart -= start;
456 for (int i = lastColon + 1; i < index; i++) { 936 fragmentStart -= start;
457 int digit = uri.codeUnitAt(i); 937 }
458 if (_ZERO > digit || _NINE < digit) { 938 return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
459 _fail(uri, i, "Invalid port number"); 939 queryStart, fragmentStart, scheme);
460 } 940
461 portNumber = portNumber * 10 + (digit - _ZERO); 941 }
942
943 return new _Uri.notSimple(uri, start, end, schemeEnd, hostStart, portStart,
944 pathStart, queryStart, fragmentStart, scheme);
945 }
946
947 /**
948 * Encode the string [component] using percent-encoding to make it
949 * safe for literal use as a URI component.
950 *
951 * All characters except uppercase and lowercase letters, digits and
952 * the characters `-_.!~*'()` are percent-encoded. This is the
953 * set of characters specified in RFC 2396 and the which is
954 * specified for the encodeUriComponent in ECMA-262 version 5.1.
955 *
956 * When manually encoding path segments or query components remember
957 * to encode each part separately before building the path or query
958 * string.
959 *
960 * For encoding the query part consider using
961 * [encodeQueryComponent].
962 *
963 * To avoid the need for explicitly encoding use the [pathSegments]
964 * and [queryParameters] optional named arguments when constructing
965 * a [Uri].
966 */
967 static String encodeComponent(String component) {
968 return _Uri._uriEncode(_Uri._unreserved2396Table, component, UTF8, false);
969 }
970
971 /**
972 * Encode the string [component] according to the HTML 4.01 rules
973 * for encoding the posting of a HTML form as a query string
974 * component.
975 *
976 * Encode the string [component] according to the HTML 4.01 rules
977 * for encoding the posting of a HTML form as a query string
978 * component.
979
980 * The component is first encoded to bytes using [encoding].
981 * The default is to use [UTF8] encoding, which preserves all
982 * the characters that don't need encoding.
983
984 * Then the resulting bytes are "percent-encoded". This transforms
985 * spaces (U+0020) to a plus sign ('+') and all bytes that are not
986 * the ASCII decimal digits, letters or one of '-._~' are written as
987 * a percent sign '%' followed by the two-digit hexadecimal
988 * representation of the byte.
989
990 * Note that the set of characters which are percent-encoded is a
991 * superset of what HTML 4.01 requires, since it refers to RFC 1738
992 * for reserved characters.
993 *
994 * When manually encoding query components remember to encode each
995 * part separately before building the query string.
996 *
997 * To avoid the need for explicitly encoding the query use the
998 * [queryParameters] optional named arguments when constructing a
999 * [Uri].
1000 *
1001 * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
1002 * details.
1003 */
1004 static String encodeQueryComponent(String component,
1005 {Encoding encoding: UTF8}) {
1006 return _Uri._uriEncode(_Uri._unreservedTable, component, encoding, true);
1007 }
1008
1009 /**
1010 * Decodes the percent-encoding in [encodedComponent].
1011 *
1012 * Note that decoding a URI component might change its meaning as
1013 * some of the decoded characters could be characters with are
1014 * delimiters for a given URI componene type. Always split a URI
1015 * component using the delimiters for the component before decoding
1016 * the individual parts.
1017 *
1018 * For handling the [path] and [query] components consider using
1019 * [pathSegments] and [queryParameters] to get the separated and
1020 * decoded component.
1021 */
1022 static String decodeComponent(String encodedComponent) {
1023 return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
1024 UTF8, false);
1025 }
1026
1027 /**
1028 * Decodes the percent-encoding in [encodedComponent], converting
1029 * pluses to spaces.
1030 *
1031 * It will create a byte-list of the decoded characters, and then use
1032 * [encoding] to decode the byte-list to a String. The default encoding is
1033 * UTF-8.
1034 */
1035 static String decodeQueryComponent(
1036 String encodedComponent,
1037 {Encoding encoding: UTF8}) {
1038 return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
1039 encoding, true);
1040 }
1041
1042 /**
1043 * Encode the string [uri] using percent-encoding to make it
1044 * safe for literal use as a full URI.
1045 *
1046 * All characters except uppercase and lowercase letters, digits and
1047 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
1048 * is the set of characters specified in in ECMA-262 version 5.1 for
1049 * the encodeURI function .
1050 */
1051 static String encodeFull(String uri) {
1052 return _Uri._uriEncode(_Uri._encodeFullTable, uri, UTF8, false);
1053 }
1054
1055 /**
1056 * Decodes the percent-encoding in [uri].
1057 *
1058 * Note that decoding a full URI might change its meaning as some of
1059 * the decoded characters could be reserved characters. In most
1060 * cases an encoded URI should be parsed into components using
1061 * [Uri.parse] before decoding the separate components.
1062 */
1063 static String decodeFull(String uri) {
1064 return _Uri._uriDecode(uri, 0, uri.length, UTF8, false);
1065 }
1066
1067 /**
1068 * Returns the [query] split into a map according to the rules
1069 * specified for FORM post in the [HTML 4.01 specification section
1070 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
1071 * Each key and value in the returned map has been decoded. If the [query]
1072 * is the empty string an empty map is returned.
1073 *
1074 * Keys in the query string that have no value are mapped to the
1075 * empty string.
1076 *
1077 * Each query component will be decoded using [encoding]. The default encoding
1078 * is UTF-8.
1079 */
1080 static Map<String, String> splitQueryString(String query,
1081 {Encoding encoding: UTF8}) {
1082 return query.split("&").fold({}, (map, element) {
1083 int index = element.indexOf("=");
1084 if (index == -1) {
1085 if (element != "") {
1086 map[decodeQueryComponent(element, encoding: encoding)] = "";
1087 }
1088 } else if (index != 0) {
1089 var key = element.substring(0, index);
1090 var value = element.substring(index + 1);
1091 map[decodeQueryComponent(key, encoding: encoding)] =
1092 decodeQueryComponent(value, encoding: encoding);
1093 }
1094 return map;
1095 });
1096 }
1097
1098
1099 /**
1100 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
1101 * as a list of 4 bytes in network byte order (big endian).
1102 *
1103 * Throws a [FormatException] if [host] is not a valid IPv4 address
1104 * representation.
1105 */
1106 static List<int> parseIPv4Address(String host) {
1107 void error(String msg) {
1108 throw new FormatException('Illegal IPv4 address, $msg', host);
1109 }
1110 var bytes = host.split('.');
1111 if (bytes.length != 4) {
1112 error('IPv4 address should contain exactly 4 parts');
1113 }
1114 var result = new Uint8List(4);
1115 for (int i = 0; i < 4; i++) {
1116 var byteString = bytes[i];
1117 int byte = int.parse(byteString);
1118 if (byte < 0 || byte > 255) {
1119 error('each part must be in the range of `0..255`');
1120 }
1121 result[i] = byte;
1122 }
1123 return result;
1124 }
1125
1126 /**
1127 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
1128 * as a list of 16 bytes in network byte order (big endian).
1129 *
1130 * Throws a [FormatException] if [host] is not a valid IPv6 address
1131 * representation.
1132 *
1133 * Acts on the substring from [start] to [end]. If [end] is omitted, it
1134 * defaults ot the end of the string.
1135 *
1136 * Some examples of IPv6 addresses:
1137 * * ::1
1138 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1139 * * 3ffe:2a00:100:7031::1
1140 * * ::FFFF:129.144.52.38
1141 * * 2010:836B:4179::836B:4179
1142 */
1143 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
1144 if (end == null) end = host.length;
1145 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
1146 // by `:`'s, with the following exceptions:
1147 //
1148 // - One (and only one) wildcard (`::`) may be present, representing a fill
1149 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1150 // - The last two parts may be replaced by an IPv4 address.
1151 void error(String msg, [position]) {
1152 throw new FormatException('Illegal IPv6 address, $msg', host, position);
1153 }
1154 int parseHex(int start, int end) {
floitsch 2016/06/29 23:41:47 newline before and after nested functions.
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Done.
1155 if (end - start > 4) {
1156 error('an IPv6 part can only contain a maximum of 4 hex digits', start);
1157 }
1158 int value = int.parse(host.substring(start, end), radix: 16);
1159 if (value < 0 || value > (1 << 16) - 1) {
1160 error('each part must be in the range of `0x0..0xFFFF`', start);
1161 }
1162 return value;
1163 }
1164 if (host.length < 2) error('address is too short');
1165 List<int> parts = [];
1166 bool wildcardSeen = false;
1167 int partStart = start;
1168 // Parse all parts, except a potential last one.
1169 for (int i = start; i < end; i++) {
1170 if (host.codeUnitAt(i) == _COLON) {
1171 if (i == start) {
1172 // If we see a `:` in the beginning, expect wildcard.
1173 i++;
1174 if (host.codeUnitAt(i) != _COLON) {
1175 error('invalid start colon.', i);
462 } 1176 }
1177 partStart = i;
463 } 1178 }
464 port = _makePort(portNumber, scheme); 1179 if (i == partStart) {
465 hostEnd = lastColon; 1180 // Wildcard. We only allow one.
466 } 1181 if (wildcardSeen) {
467 host = _makeHost(uri, hostStart, hostEnd, true); 1182 error('only one wildcard `::` is allowed', i);
468 if (index < end) { 1183 }
469 char = uri.codeUnitAt(index); 1184 wildcardSeen = true;
470 } 1185 parts.add(-1);
471 } 1186 } else {
472 1187 // Found a single colon. Parse [partStart..i] as a hex entry.
473 // When reaching path parsing, the current character is known to not 1188 parts.add(parseHex(partStart, i));
474 // be part of the path.
475 const int NOT_IN_PATH = 0;
476 // When reaching path parsing, the current character is part
477 // of the a non-empty path.
478 const int IN_PATH = 1;
479 // When reaching authority parsing, authority is possible.
480 // This is only true at start or right after scheme.
481 const int ALLOW_AUTH = 2;
482
483 // Current state.
484 // Initialized to the default value that is used when exiting the
485 // scheme loop by reaching the end of input.
486 // All other breaks set their own state.
487 int state = NOT_IN_PATH;
488 int i = index; // Temporary alias for index to avoid bug 19550 in dart2js.
489 while (i < end) {
490 char = uri.codeUnitAt(i);
491 if (char == _QUESTION || char == _NUMBER_SIGN) {
492 state = NOT_IN_PATH;
493 break;
494 }
495 if (char == _SLASH) {
496 state = (i == start) ? ALLOW_AUTH : IN_PATH;
497 break;
498 }
499 if (char == _COLON) {
500 if (i == start) _fail(uri, start, "Invalid empty scheme");
501 scheme = _makeScheme(uri, start, i);
502 i++;
503 if (scheme == "data") {
504 // This generates a URI that is (potentially) not path normalized.
505 // Applying part normalization to a non-hierarchial URI isn't
506 // meaningful.
507 return UriData._parse(uri, i, null).uri;
508 } 1189 }
509 pathStart = i; 1190 partStart = i + 1;
510 if (i == end) { 1191 }
511 char = EOI; 1192 }
512 state = NOT_IN_PATH; 1193 if (parts.length == 0) error('too few parts');
513 } else { 1194 bool atEnd = (partStart == end);
514 char = uri.codeUnitAt(i); 1195 bool isLastWildcard = (parts.last == -1);
515 if (char == _QUESTION || char == _NUMBER_SIGN) { 1196 if (atEnd && !isLastWildcard) {
516 state = NOT_IN_PATH; 1197 error('expected a part after last `:`', end);
517 } else if (char == _SLASH) { 1198 }
518 state = ALLOW_AUTH; 1199 if (!atEnd) {
519 } else { 1200 try {
floitsch 2016/06/29 23:41:47 I would assume that the try/catch costs performanc
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Rewritten to not use try/catch. This means that th
520 state = IN_PATH; 1201 parts.add(parseHex(partStart, end));
521 } 1202 } catch (e) {
1203 // Failed to parse the last chunk as hex. Try IPv4.
1204 try {
1205 List<int> last = parseIPv4Address(host.substring(partStart, end));
1206 parts.add(last[0] << 8 | last[1]);
1207 parts.add(last[2] << 8 | last[3]);
1208 } catch (e) {
1209 error('invalid end of IPv6 address.', partStart);
522 } 1210 }
523 break; 1211 }
524 } 1212 }
525 i++; 1213 if (wildcardSeen) {
526 char = EOI; 1214 if (parts.length > 7) {
527 } 1215 error('an address with a wildcard must have less than 7 parts');
528 index = i; // Remove alias when bug is fixed. 1216 }
529 1217 } else if (parts.length != 8) {
530 if (state == ALLOW_AUTH) { 1218 error('an address without a wildcard must contain exactly 8 parts');
531 assert(char == _SLASH); 1219 }
532 // Have seen one slash either at start or right after scheme. 1220 List<int> bytes = new Uint8List(16);
533 // If two slashes, it's an authority, otherwise it's just the path. 1221 for (int i = 0, index = 0; i < parts.length; i++) {
534 index++; 1222 int value = parts[i];
535 if (index == end) { 1223 if (value == -1) {
536 char = EOI; 1224 int wildCardLength = 9 - parts.length;
537 state = NOT_IN_PATH; 1225 for (int j = 0; j < wildCardLength; j++) {
1226 bytes[index] = 0;
1227 bytes[index + 1] = 0;
1228 index += 2;
1229 }
538 } else { 1230 } else {
539 char = uri.codeUnitAt(index); 1231 bytes[index] = value >> 8;
540 if (char == _SLASH) { 1232 bytes[index + 1] = value & 0xff;
541 index++; 1233 index += 2;
542 parseAuth(); 1234 }
543 pathStart = index; 1235 }
544 } 1236 return bytes;
545 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) { 1237 }
546 state = NOT_IN_PATH; 1238 }
547 } else { 1239
548 state = IN_PATH; 1240 class _Uri implements Uri {
549 } 1241 // We represent the missing scheme as an empty string.
550 } 1242 // A valid scheme cannot be empty.
551 } 1243 final String scheme;
552 1244
553 assert(state == IN_PATH || state == NOT_IN_PATH); 1245 /**
554 if (state == IN_PATH) { 1246 * The user-info part of the authority.
555 // Characters from pathStart to index (inclusive) are known 1247 *
556 // to be part of the path. 1248 * Does not distinguish between an empty user-info and an absent one.
557 while (++index < end) { 1249 * The value is always non-null.
558 char = uri.codeUnitAt(index); 1250 * Is considered absent if [_host] is `null`.
559 if (char == _QUESTION || char == _NUMBER_SIGN) { 1251 */
560 break; 1252 final String _userInfo;
561 } 1253
562 char = EOI; 1254 /**
563 } 1255 * The host name of the URI.
564 state = NOT_IN_PATH; 1256 *
565 } 1257 * Set to `null` if there is no authority in the URI.
566 1258 * The host name is the only mandatory part of an authority, so we use
567 assert(state == NOT_IN_PATH); 1259 * it to mark whether an authority part was present or not.
1260 */
1261 final String _host;
1262
1263 /**
1264 * The port number part of the authority.
1265 *
1266 * The port. Set to null if there is no port. Normalized to null if
1267 * the port is the default port for the scheme.
1268 */
1269 int _port;
1270
1271 /**
1272 * The path of the URI.
1273 *
1274 * Always non-null.
1275 */
1276 String _path;
1277
1278 // The query content, or null if there is no query.
1279 final String _query;
1280
1281 // The fragment content, or null if there is no fragment.
1282 final String _fragment;
1283
1284 /**
1285 * Cache the computed return value of [pathSegements].
1286 */
1287 List<String> _pathSegments;
1288
1289 /**
1290 * Cache of the full normalized text representation of the URI.
1291 */
1292 String _text;
1293
1294 /**
1295 * Cache the computed return value of [queryParameters].
1296 */
1297 Map<String, String> _queryParameters;
1298 Map<String, List<String>> _queryParameterLists;
1299
1300 /// Internal non-verifying constructor. Only call with validated arguments.
1301 _Uri._internal(this.scheme,
1302 this._userInfo,
1303 this._host,
1304 this._port,
1305 this._path,
1306 this._query,
1307 this._fragment);
1308
1309 /// Create a [_Uri] from parts of [uri].
1310 ///
1311 /// The parameters specify the start/end of particular components of the URI.
1312 /// The [scheme] may contain a string representing a normalized scheme
1313 /// component if one has already been discovered.
1314 factory _Uri.notSimple(String uri, int start, int end, int schemeEnd,
1315 int hostStart, int portStart, int pathStart,
1316 int queryStart, int fragmentStart, String scheme) {
1317 if (scheme == null) {
1318 scheme = "";
1319 if (schemeEnd > start) {
1320 scheme = _makeScheme(uri, start, schemeEnd);
1321 } else if (schemeEnd == start) {
1322 _fail(uri, start, "Invalid empty scheme");
1323 }
1324 }
1325 String userInfo = "";
1326 String host;
1327 int port;
1328 if (hostStart > start) {
1329 int userInfoStart = schemeEnd + 3;
1330 if (userInfoStart < hostStart) {
1331 userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1);
1332 }
1333 host = _makeHost(uri, hostStart, portStart, false);
1334 if (portStart + 1 < pathStart) {
1335 // Should throw because invalid.
1336 port = int.parse(uri.substring(portStart + 1, pathStart), onError: (_) {
1337 throw new FormatException("Invalid port", uri, portStart + 1);
1338 });
1339 port = _makePort(port, scheme);
1340 }
1341 }
1342 String path = _makePath(uri, pathStart, queryStart, null,
1343 scheme, host != null);
1344 String query;
1345 if (queryStart < fragmentStart) {
1346 query = _makeQuery(uri, queryStart + 1, fragmentStart, null);
1347 }
1348 String fragment;
1349 if (fragmentStart < end) {
1350 fragment = _makeFragment(uri, fragmentStart + 1, end);
1351 }
1352 return new _Uri._internal(scheme,
1353 userInfo,
1354 host,
1355 port,
1356 path,
1357 query,
1358 fragment);
1359 }
1360
1361 /// Implementation of [Uri.Uri].
1362 factory _Uri({String scheme,
1363 String userInfo,
1364 String host,
1365 int port,
1366 String path,
1367 Iterable<String> pathSegments,
1368 String query,
1369 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
1370 String fragment}) {
1371 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
1372 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
1373 host = _makeHost(host, 0, _stringOrNullLength(host), false);
1374 // Special case this constructor for backwards compatibility.
1375 if (query == "") query = null;
1376 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
1377 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
1378 port = _makePort(port, scheme);
1379 bool isFile = (scheme == "file");
1380 if (host == null &&
1381 (userInfo.isNotEmpty || port != null || isFile)) {
1382 host = "";
1383 }
568 bool hasAuthority = (host != null); 1384 bool hasAuthority = (host != null);
569 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority); 1385 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
570 1386 scheme, hasAuthority);
571 if (char == _QUESTION) { 1387 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
572 int numberSignIndex = -1; 1388 path = _normalizeRelativePath(path);
573 for (int i = index + 1; i < end; i++) { 1389 } else {
574 if (uri.codeUnitAt(i) == _NUMBER_SIGN) { 1390 path = _removeDotSegments(path);
575 numberSignIndex = i; 1391 }
576 break; 1392 return new _Uri._internal(scheme, userInfo, host, port,
577 } 1393 path, query, fragment);
578 } 1394 }
579 if (numberSignIndex < 0) { 1395
580 query = _makeQuery(uri, index + 1, end, null); 1396 /// Implementation of [Uri.http].
581 } else { 1397 factory _Uri.http(String authority,
582 query = _makeQuery(uri, index + 1, numberSignIndex, null); 1398 String unencodedPath,
583 fragment = _makeFragment(uri, numberSignIndex + 1, end); 1399 [Map<String, String> queryParameters]) {
584 } 1400 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
585 } else if (char == _NUMBER_SIGN) { 1401 }
586 fragment = _makeFragment(uri, index + 1, end); 1402
587 } 1403 /// Implementation of [Uri.https].
588 return new Uri._internal(scheme, 1404 factory _Uri.https(String authority,
589 userinfo, 1405 String unencodedPath,
590 host, 1406 [Map<String, String> queryParameters]) {
591 port, 1407 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
592 path, 1408 }
593 query, 1409
594 fragment); 1410 String get authority {
595 } 1411 if (!hasAuthority) return "";
1412 var sb = new StringBuffer();
1413 _writeAuthority(sb);
1414 return sb.toString();
1415 }
1416
1417 String get userInfo => _userInfo;
1418
1419 String get host {
1420 if (_host == null) return "";
1421 if (_host.startsWith('[')) {
1422 return _host.substring(1, _host.length - 1);
1423 }
1424 return _host;
1425 }
1426
1427 int get port {
1428 if (_port == null) return _defaultPort(scheme);
1429 return _port;
1430 }
1431
1432 // The default port for the scheme of this Uri..
floitsch 2016/06/29 23:41:47 Remove trailing ".".
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Done.
1433 static int _defaultPort(String scheme) {
1434 if (scheme == "http") return 80;
1435 if (scheme == "https") return 443;
1436 return 0;
1437 }
1438
1439 String get path => _path;
1440
1441 String get query => (_query == null) ? "" : _query;
floitsch 2016/06/29 23:41:47 _query ?? ""
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Done.
1442
1443 String get fragment => (_fragment == null) ? "" : _fragment;
floitsch 2016/06/29 23:41:47 _fragment ?? ""
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Done.
596 1444
597 // Report a parse failure. 1445 // Report a parse failure.
598 static void _fail(String uri, int index, String message) { 1446 static void _fail(String uri, int index, String message) {
599 throw new FormatException(message, uri, index); 1447 throw new FormatException(message, uri, index);
600 } 1448 }
601 1449
602 static Uri _makeHttpUri(String scheme, 1450 static Uri _makeHttpUri(String scheme,
603 String authority, 1451 String authority,
604 String unencodedPath, 1452 String unencodedPath,
605 Map<String, String> queryParameters) { 1453 Map<String, String> queryParameters) {
606 var userInfo = ""; 1454 var userInfo = "";
607 var host = null; 1455 var host = null;
608 var port = null; 1456 var port = null;
609 1457
610 if (authority != null && authority.isNotEmpty) { 1458 if (authority != null && authority.isNotEmpty) {
611 var hostStart = 0; 1459 var hostStart = 0;
612 // Split off the user info. 1460 // Split off the user info.
613 bool hasUserInfo = false; 1461 bool hasUserInfo = false;
614 for (int i = 0; i < authority.length; i++) { 1462 for (int i = 0; i < authority.length; i++) {
615 if (authority.codeUnitAt(i) == _AT_SIGN) { 1463 const int atSign = 0x40;
1464 if (authority.codeUnitAt(i) == atSign) {
616 hasUserInfo = true; 1465 hasUserInfo = true;
617 userInfo = authority.substring(0, i); 1466 userInfo = authority.substring(0, i);
618 hostStart = i + 1; 1467 hostStart = i + 1;
619 break; 1468 break;
620 } 1469 }
621 } 1470 }
622 var hostEnd = hostStart; 1471 var hostEnd = hostStart;
623 if (hostStart < authority.length && 1472 if (hostStart < authority.length &&
624 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { 1473 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
625 // IPv6 host. 1474 // IPv6 host.
626 for (; hostEnd < authority.length; hostEnd++) { 1475 for (; hostEnd < authority.length; hostEnd++) {
627 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; 1476 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
628 } 1477 }
629 if (hostEnd == authority.length) { 1478 if (hostEnd == authority.length) {
630 throw new FormatException("Invalid IPv6 host entry.", 1479 throw new FormatException("Invalid IPv6 host entry.",
631 authority, hostStart); 1480 authority, hostStart);
632 } 1481 }
633 parseIPv6Address(authority, hostStart + 1, hostEnd); 1482 Uri.parseIPv6Address(authority, hostStart + 1, hostEnd);
634 hostEnd++; // Skip the closing bracket. 1483 hostEnd++; // Skip the closing bracket.
635 if (hostEnd != authority.length && 1484 if (hostEnd != authority.length &&
636 authority.codeUnitAt(hostEnd) != _COLON) { 1485 authority.codeUnitAt(hostEnd) != _COLON) {
637 throw new FormatException("Invalid end of authority", 1486 throw new FormatException("Invalid end of authority",
638 authority, hostEnd); 1487 authority, hostEnd);
639 } 1488 }
640 } 1489 }
641 // Split host and port. 1490 // Split host and port.
642 bool hasPort = false; 1491 bool hasPort = false;
643 for (; hostEnd < authority.length; hostEnd++) { 1492 for (; hostEnd < authority.length; hostEnd++) {
644 if (authority.codeUnitAt(hostEnd) == _COLON) { 1493 if (authority.codeUnitAt(hostEnd) == _COLON) {
645 var portString = authority.substring(hostEnd + 1); 1494 var portString = authority.substring(hostEnd + 1);
646 // We allow the empty port - falling back to initial value. 1495 // We allow the empty port - falling back to initial value.
647 if (portString.isNotEmpty) port = int.parse(portString); 1496 if (portString.isNotEmpty) port = int.parse(portString);
648 break; 1497 break;
649 } 1498 }
650 } 1499 }
651 host = authority.substring(hostStart, hostEnd); 1500 host = authority.substring(hostStart, hostEnd);
652 } 1501 }
653 return new Uri(scheme: scheme, 1502 return new Uri(scheme: scheme,
654 userInfo: userInfo, 1503 userInfo: userInfo,
655 host: host, 1504 host: host,
656 port: port, 1505 port: port,
657 pathSegments: unencodedPath.split("/"), 1506 pathSegments: unencodedPath.split("/"),
658 queryParameters: queryParameters); 1507 queryParameters: queryParameters);
659 } 1508 }
660 1509
661 /** 1510 /// Implementation of [Uri.file].
662 * Creates a new file URI from an absolute or relative file path. 1511 factory _Uri.file(String path, {bool windows}) {
663 * 1512 windows = (windows == null) ? _Uri._isWindows : windows;
664 * The file path is passed in [path].
665 *
666 * This path is interpreted using either Windows or non-Windows
667 * semantics.
668 *
669 * With non-Windows semantics the slash ("/") is used to separate
670 * path segments.
671 *
672 * With Windows semantics, backslash ("\") and forward-slash ("/")
673 * are used to separate path segments, except if the path starts
674 * with "\\?\" in which case, only backslash ("\") separates path
675 * segments.
676 *
677 * If the path starts with a path separator an absolute URI is
678 * created. Otherwise a relative URI is created. One exception from
679 * this rule is that when Windows semantics is used and the path
680 * starts with a drive letter followed by a colon (":") and a
681 * path separator then an absolute URI is created.
682 *
683 * The default for whether to use Windows or non-Windows semantics
684 * determined from the platform Dart is running on. When running in
685 * the standalone VM this is detected by the VM based on the
686 * operating system. When running in a browser non-Windows semantics
687 * is always used.
688 *
689 * To override the automatic detection of which semantics to use pass
690 * a value for [windows]. Passing `true` will use Windows
691 * semantics and passing `false` will use non-Windows semantics.
692 *
693 * Examples using non-Windows semantics:
694 *
695 * ```
696 * // xxx/yyy
697 * new Uri.file("xxx/yyy", windows: false);
698 *
699 * // xxx/yyy/
700 * new Uri.file("xxx/yyy/", windows: false);
701 *
702 * // file:///xxx/yyy
703 * new Uri.file("/xxx/yyy", windows: false);
704 *
705 * // file:///xxx/yyy/
706 * new Uri.file("/xxx/yyy/", windows: false);
707 *
708 * // C:
709 * new Uri.file("C:", windows: false);
710 * ```
711 *
712 * Examples using Windows semantics:
713 *
714 * ```
715 * // xxx/yyy
716 * new Uri.file(r"xxx\yyy", windows: true);
717 *
718 * // xxx/yyy/
719 * new Uri.file(r"xxx\yyy\", windows: true);
720 *
721 * file:///xxx/yyy
722 * new Uri.file(r"\xxx\yyy", windows: true);
723 *
724 * file:///xxx/yyy/
725 * new Uri.file(r"\xxx\yyy/", windows: true);
726 *
727 * // file:///C:/xxx/yyy
728 * new Uri.file(r"C:\xxx\yyy", windows: true);
729 *
730 * // This throws an error. A path with a drive letter is not absolute.
731 * new Uri.file(r"C:", windows: true);
732 *
733 * // This throws an error. A path with a drive letter is not absolute.
734 * new Uri.file(r"C:xxx\yyy", windows: true);
735 *
736 * // file://server/share/file
737 * new Uri.file(r"\\server\share\file", windows: true);
738 * ```
739 *
740 * If the path passed is not a legal file path [ArgumentError] is thrown.
741 */
742 factory Uri.file(String path, {bool windows}) {
743 windows = (windows == null) ? Uri._isWindows : windows;
744 return windows ? _makeWindowsFileUrl(path, false) 1513 return windows ? _makeWindowsFileUrl(path, false)
745 : _makeFileUri(path, false); 1514 : _makeFileUri(path, false);
746 } 1515 }
747 1516
748 /** 1517 /// Implementation of [Uri.directory].
749 * Like [Uri.file] except that a non-empty URI path ends in a slash. 1518 factory _Uri.directory(String path, {bool windows}) {
750 * 1519 windows = (windows == null) ? _Uri._isWindows : windows;
751 * If [path] is not empty, and it doesn't end in a directory separator,
752 * then a slash is added to the returned URI's path.
753 * In all other cases, the result is the same as returned by `Uri.file`.
754 */
755 factory Uri.directory(String path, {bool windows}) {
756 windows = (windows == null) ? Uri._isWindows : windows;
757 return windows ? _makeWindowsFileUrl(path, true) 1520 return windows ? _makeWindowsFileUrl(path, true)
758 : _makeFileUri(path, true); 1521 : _makeFileUri(path, true);
759 } 1522 }
760 1523
761 /**
762 * Creates a `data:` URI containing the [content] string.
763 *
764 * Converts the content to a bytes using [encoding] or the charset specified
765 * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
766 * then encodes the bytes into the resulting data URI.
767 *
768 * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
769 * bytes is replaced by a percent encoding). If [base64] is true, the bytes
770 * are instead encoded using [BASE64].
771 *
772 * If [encoding] is not provided and [parameters] has a `charset` entry,
773 * that name is looked up using [Encoding.getByName],
774 * and if the lookup returns an encoding, that encoding is used to convert
775 * [content] to bytes.
776 * If providing both an [encoding] and a charset [parameter], they should
777 * agree, otherwise decoding won't be able to use the charset parameter
778 * to determine the encoding.
779 *
780 * If [mimeType] and/or [parameters] are supplied, they are added to the
781 * created URI. If any of these contain characters that are not allowed
782 * in the data URI, the character is percent-escaped. If the character is
783 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
784 * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
785 * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
786 *
787 * To read the content back, use [UriData.contentAsString].
788 */
789 factory Uri.dataFromString(String content,
790 {String mimeType,
791 Encoding encoding,
792 Map<String, String> parameters,
793 bool base64: false}) {
794 UriData data = new UriData.fromString(content,
795 mimeType: mimeType,
796 encoding: encoding,
797 parameters: parameters,
798 base64: base64);
799 return data.uri;
800 }
801 1524
802 /** 1525 /// Used internally in path-related constructors.
803 * Creates a `data:` URI containing an encoding of [bytes].
804 *
805 * Defaults to Base64 encoding the bytes, but if [percentEncoded]
806 * is `true`, the bytes will instead be percent encoded (any non-ASCII
807 * or non-valid-ASCII-character byte is replaced by a percent encoding).
808 *
809 * To read the bytes back, use [UriData.contentAsBytes].
810 *
811 * It defaults to having the mime-type `application/octet-stream`.
812 * The [mimeType] and [parameters] are added to the created URI.
813 * If any of these contain characters that are not allowed
814 * in the data URI, the character is percent-escaped. If the character is
815 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
816 * encoded.
817 */
818 factory Uri.dataFromBytes(List<int> bytes,
819 {mimeType: "application/octet-stream",
820 Map<String, String> parameters,
821 percentEncoded: false}) {
822 UriData data = new UriData.fromBytes(bytes,
823 mimeType: mimeType,
824 parameters: parameters,
825 percentEncoded: percentEncoded);
826 return data.uri;
827 }
828
829 /**
830 * Returns the natural base URI for the current platform.
831 *
832 * When running in a browser this is the current URL (from
833 * `window.location.href`).
834 *
835 * When not running in a browser this is the file URI referencing
836 * the current working directory.
837 */
838 external static Uri get base;
839
840 external static bool get _isWindows; 1526 external static bool get _isWindows;
841 1527
842 static _checkNonWindowsPathReservedCharacters(List<String> segments, 1528 static _checkNonWindowsPathReservedCharacters(List<String> segments,
843 bool argumentError) { 1529 bool argumentError) {
844 segments.forEach((segment) { 1530 segments.forEach((segment) {
845 if (segment.contains("/")) { 1531 if (segment.contains("/")) {
846 if (argumentError) { 1532 if (argumentError) {
847 throw new ArgumentError("Illegal path character $segment"); 1533 throw new ArgumentError("Illegal path character $segment");
848 } else { 1534 } else {
849 throw new UnsupportedError("Illegal path character $segment"); 1535 throw new UnsupportedError("Illegal path character $segment");
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
962 _checkWindowsPathReservedCharacters(pathSegments, true); 1648 _checkWindowsPathReservedCharacters(pathSegments, true);
963 if (slashTerminated && 1649 if (slashTerminated &&
964 pathSegments.isNotEmpty && 1650 pathSegments.isNotEmpty &&
965 pathSegments.last.isNotEmpty) { 1651 pathSegments.last.isNotEmpty) {
966 pathSegments.add(""); // Extra separator at end. 1652 pathSegments.add(""); // Extra separator at end.
967 } 1653 }
968 return new Uri(pathSegments: pathSegments); 1654 return new Uri(pathSegments: pathSegments);
969 } 1655 }
970 } 1656 }
971 1657
972 /**
973 * Returns a new `Uri` based on this one, but with some parts replaced.
974 *
975 * This method takes the same parameters as the [new Uri] constructor,
976 * and they have the same meaning.
977 *
978 * At most one of [path] and [pathSegments] must be provided.
979 * Likewise, at most one of [query] and [queryParameters] must be provided.
980 *
981 * Each part that is not provided will default to the corresponding
982 * value from this `Uri` instead.
983 *
984 * This method is different from [Uri.resolve] which overrides in a
985 * hierarchial manner,
986 * and can instead replace each part of a `Uri` individually.
987 *
988 * Example:
989 *
990 * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
991 * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
992 * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
993 *
994 * This method acts similarly to using the `new Uri` constructor with
995 * some of the arguments taken from this `Uri` . Example:
996 *
997 * Uri uri3 = new Uri(
998 * scheme: "A",
999 * userInfo: uri1.userInfo,
1000 * host: uri1.host,
1001 * port: uri1.port,
1002 * path: "D/E/E",
1003 * query: uri1.query,
1004 * fragment: "G");
1005 * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
1006 * print(uri2 == uri3); // prints true.
1007 *
1008 * Using this method can be seen as a shorthand for the `Uri` constructor
1009 * call above, but may also be slightly faster because the parts taken
1010 * from this `Uri` need not be checked for validity again.
1011 */
1012 Uri replace({String scheme, 1658 Uri replace({String scheme,
1013 String userInfo, 1659 String userInfo,
1014 String host, 1660 String host,
1015 int port, 1661 int port,
1016 String path, 1662 String path,
1017 Iterable<String> pathSegments, 1663 Iterable<String> pathSegments,
1018 String query, 1664 String query,
1019 Map<String, dynamic/*String|Iterable<String>*/> queryParameters, 1665 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
1020 String fragment}) { 1666 String fragment}) {
1021 // Set to true if the scheme has (potentially) changed. 1667 // Set to true if the scheme has (potentially) changed.
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
1068 } else { 1714 } else {
1069 query = this._query; 1715 query = this._query;
1070 } 1716 }
1071 1717
1072 if (fragment != null) { 1718 if (fragment != null) {
1073 fragment = _makeFragment(fragment, 0, fragment.length); 1719 fragment = _makeFragment(fragment, 0, fragment.length);
1074 } else { 1720 } else {
1075 fragment = this._fragment; 1721 fragment = this._fragment;
1076 } 1722 }
1077 1723
1078 return new Uri._internal( 1724 return new _Uri._internal(
1079 scheme, userInfo, host, port, path, query, fragment); 1725 scheme, userInfo, host, port, path, query, fragment);
1080 } 1726 }
1081 1727
1082 /**
1083 * Returns a `Uri` that differs from this only in not having a fragment.
1084 *
1085 * If this `Uri` does not have a fragment, it is itself returned.
1086 */
1087 Uri removeFragment() { 1728 Uri removeFragment() {
1088 if (!this.hasFragment) return this; 1729 if (!this.hasFragment) return this;
1089 return new Uri._internal(scheme, _userInfo, _host, _port, 1730 return new _Uri._internal(scheme, _userInfo, _host, _port,
1090 _path, _query, null); 1731 _path, _query, null);
1091 } 1732 }
1092 1733
1093 /**
1094 * Returns the URI path split into its segments. Each of the segments in the
1095 * returned list have been decoded. If the path is empty the empty list will
1096 * be returned. A leading slash `/` does not affect the segments returned.
1097 *
1098 * The returned list is unmodifiable and will throw [UnsupportedError] on any
1099 * calls that would mutate it.
1100 */
1101 List<String> get pathSegments { 1734 List<String> get pathSegments {
1102 var result = _pathSegments; 1735 var result = _pathSegments;
1103 if (result != null) return result; 1736 if (result != null) return result;
1104 1737
1105 var pathToSplit = path; 1738 var pathToSplit = path;
1106 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) { 1739 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) {
1107 pathToSplit = pathToSplit.substring(1); 1740 pathToSplit = pathToSplit.substring(1);
1108 } 1741 }
1109 result = (pathToSplit == "") 1742 result = (pathToSplit == "")
1110 ? const<String>[] 1743 ? const<String>[]
1111 : new List<String>.unmodifiable( 1744 : new List<String>.unmodifiable(
1112 pathToSplit.split("/").map(Uri.decodeComponent)); 1745 pathToSplit.split("/").map(Uri.decodeComponent));
1113 _pathSegments = result; 1746 _pathSegments = result;
1114 return result; 1747 return result;
1115 } 1748 }
1116 1749
1117 /**
1118 * Returns the URI query split into a map according to the rules
1119 * specified for FORM post in the [HTML 4.01 specification section
1120 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
1121 * Each key and value in the returned map has been decoded.
1122 * If there is no query the empty map is returned.
1123 *
1124 * Keys in the query string that have no value are mapped to the
1125 * empty string.
1126 * If a key occurs more than once in the query string, it is mapped to
1127 * an arbitrary choice of possible value.
1128 * The [queryParametersAll] getter can provide a map
1129 * that maps keys to all of their values.
1130 *
1131 * The returned map is unmodifiable.
1132 */
1133 Map<String, String> get queryParameters { 1750 Map<String, String> get queryParameters {
1134 if (_queryParameters == null) { 1751 if (_queryParameters == null) {
1135 _queryParameters = 1752 _queryParameters =
1136 new UnmodifiableMapView<String, String>(splitQueryString(query)); 1753 new UnmodifiableMapView<String, String>(Uri.splitQueryString(query));
1137 } 1754 }
1138 return _queryParameters; 1755 return _queryParameters;
1139 } 1756 }
1140 1757
1141 /**
1142 * Returns the URI query split into a map according to the rules
1143 * specified for FORM post in the [HTML 4.01 specification section
1144 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
1145 * Each key and value in the returned map has been decoded. If there is no
1146 * query the empty map is returned.
1147 *
1148 * Keys are mapped to lists of their values. If a key occurs only once,
1149 * its value is a singleton list. If a key occurs with no value, the
1150 * empty string is used as the value for that occurrence.
1151 *
1152 * The returned map and the lists it contains are unmodifiable.
1153 */
1154 Map<String, List<String>> get queryParametersAll { 1758 Map<String, List<String>> get queryParametersAll {
1155 if (_queryParameterLists == null) { 1759 if (_queryParameterLists == null) {
1156 Map queryParameterLists = _splitQueryStringAll(query); 1760 Map queryParameterLists = _splitQueryStringAll(query);
1157 for (var key in queryParameterLists.keys) { 1761 for (var key in queryParameterLists.keys) {
1158 queryParameterLists[key] = 1762 queryParameterLists[key] =
1159 new List<String>.unmodifiable(queryParameterLists[key]); 1763 new List<String>.unmodifiable(queryParameterLists[key]);
1160 } 1764 }
1161 _queryParameterLists = 1765 _queryParameterLists =
1162 new Map<String, List<String>>.unmodifiable(queryParameterLists); 1766 new Map<String, List<String>>.unmodifiable(queryParameterLists);
1163 } 1767 }
1164 return _queryParameterLists; 1768 return _queryParameterLists;
1165 } 1769 }
1166 1770
1167 /**
1168 * Returns a URI where the path has been normalized.
1169 *
1170 * A normalized path does not contain `.` segments or non-leading `..`
1171 * segments.
1172 * Only a relative path with no scheme or authority may contain
1173 * leading `..` segments,
1174 * a path that starts with `/` will also drop any leading `..` segments.
1175 *
1176 * This uses the same normalization strategy as `new Uri().resolve(this)`.
1177 *
1178 * Does not change any part of the URI except the path.
1179 *
1180 * The default implementation of `Uri` always normalizes paths, so calling
1181 * this function has no effect.
1182 */
1183 Uri normalizePath() { 1771 Uri normalizePath() {
1184 String path = _normalizePath(_path, scheme, hasAuthority); 1772 String path = _normalizePath(_path, scheme, hasAuthority);
1185 if (identical(path, _path)) return this; 1773 if (identical(path, _path)) return this;
1186 return this.replace(path: path); 1774 return this.replace(path: path);
1187 } 1775 }
1188 1776
1189 static int _makePort(int port, String scheme) { 1777 static int _makePort(int port, String scheme) {
1190 // Perform scheme specific normalization. 1778 // Perform scheme specific normalization.
1191 if (port != null && port == _defaultPort(scheme)) return null; 1779 if (port != null && port == _defaultPort(scheme)) return null;
1192 return port; 1780 return port;
(...skipping 12 matching lines...) Expand all
1205 */ 1793 */
1206 static String _makeHost(String host, int start, int end, bool strictIPv6) { 1794 static String _makeHost(String host, int start, int end, bool strictIPv6) {
1207 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952? 1795 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
1208 if (host == null) return null; 1796 if (host == null) return null;
1209 if (start == end) return ""; 1797 if (start == end) return "";
1210 // Host is an IPv6 address if it starts with '[' or contains a colon. 1798 // Host is an IPv6 address if it starts with '[' or contains a colon.
1211 if (host.codeUnitAt(start) == _LEFT_BRACKET) { 1799 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
1212 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) { 1800 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
1213 _fail(host, start, 'Missing end `]` to match `[` in host'); 1801 _fail(host, start, 'Missing end `]` to match `[` in host');
1214 } 1802 }
1215 parseIPv6Address(host, start + 1, end - 1); 1803 Uri.parseIPv6Address(host, start + 1, end - 1);
1216 // RFC 5952 requires hex digits to be lower case. 1804 // RFC 5952 requires hex digits to be lower case.
1217 return host.substring(start, end).toLowerCase(); 1805 return host.substring(start, end).toLowerCase();
1218 } 1806 }
1219 if (!strictIPv6) { 1807 if (!strictIPv6) {
1220 // TODO(lrn): skip if too short to be a valid IPv6 address? 1808 // TODO(lrn): skip if too short to be a valid IPv6 address?
1221 for (int i = start; i < end; i++) { 1809 for (int i = start; i < end; i++) {
1222 if (host.codeUnitAt(i) == _COLON) { 1810 if (host.codeUnitAt(i) == _COLON) {
1223 parseIPv6Address(host, start, end); 1811 Uri.parseIPv6Address(host, start, end);
1224 return '[$host]'; 1812 return '[$host]';
1225 } 1813 }
1226 } 1814 }
1227 } 1815 }
1228 return _normalizeRegName(host, start, end); 1816 return _normalizeRegName(host, start, end);
1229 } 1817 }
1230 1818
1231 static bool _isRegNameChar(int char) { 1819 static bool _isRegNameChar(int char) {
1232 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0; 1820 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
1233 } 1821 }
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
1326 final int codeUnit = scheme.codeUnitAt(i); 1914 final int codeUnit = scheme.codeUnitAt(i);
1327 if (!_isSchemeCharacter(codeUnit)) { 1915 if (!_isSchemeCharacter(codeUnit)) {
1328 _fail(scheme, i, "Illegal scheme character"); 1916 _fail(scheme, i, "Illegal scheme character");
1329 } 1917 }
1330 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) { 1918 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
1331 containsUpperCase = true; 1919 containsUpperCase = true;
1332 } 1920 }
1333 } 1921 }
1334 scheme = scheme.substring(start, end); 1922 scheme = scheme.substring(start, end);
1335 if (containsUpperCase) scheme = scheme.toLowerCase(); 1923 if (containsUpperCase) scheme = scheme.toLowerCase();
1924 return _canonicalizeScheme(scheme);
1925 }
1926
1927 // Canonicalize a few often-used scheme strings.
1928 //
1929 // This improves memory usage and makes comparison faster.
1930 static String _canonicalizeScheme(String scheme) {
1931 if (scheme == "http") return "http";
1932 if (scheme == "file") return "file";
1933 if (scheme == "https") return "https";
1934 if (scheme == "package") return "package";
1336 return scheme; 1935 return scheme;
1337 } 1936 }
1338 1937
1339 static String _makeUserInfo(String userInfo, int start, int end) { 1938 static String _makeUserInfo(String userInfo, int start, int end) {
1340 if (userInfo == null) return ""; 1939 if (userInfo == null) return "";
1341 return _normalize(userInfo, start, end, _userinfoTable); 1940 return _normalize(userInfo, start, end, _userinfoTable);
1342 } 1941 }
1343 1942
1344 static String _makePath(String path, int start, int end, 1943 static String _makePath(String path, int start, int end,
1345 Iterable<String> pathSegments, 1944 Iterable<String> pathSegments,
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
1412 } 2011 }
1413 }); 2012 });
1414 return result.toString(); 2013 return result.toString();
1415 } 2014 }
1416 2015
1417 static String _makeFragment(String fragment, int start, int end) { 2016 static String _makeFragment(String fragment, int start, int end) {
1418 if (fragment == null) return null; 2017 if (fragment == null) return null;
1419 return _normalize(fragment, start, end, _queryCharTable); 2018 return _normalize(fragment, start, end, _queryCharTable);
1420 } 2019 }
1421 2020
1422 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
1423
1424 /** 2021 /**
1425 * Performs RFC 3986 Percent-Encoding Normalization. 2022 * Performs RFC 3986 Percent-Encoding Normalization.
1426 * 2023 *
1427 * Returns a replacement string that should be replace the original escape. 2024 * Returns a replacement string that should be replace the original escape.
1428 * Returns null if no replacement is necessary because the escape is 2025 * Returns null if no replacement is necessary because the escape is
1429 * not for an unreserved character and is already non-lower-case. 2026 * not for an unreserved character and is already non-lower-case.
1430 * 2027 *
1431 * Returns "%" if the escape is invalid (not two valid hex digits following 2028 * Returns "%" if the escape is invalid (not two valid hex digits following
1432 * the percent sign). The calling code should replace the percent 2029 * the percent sign). The calling code should replace the percent
1433 * sign with "%25", but leave the following two characters unmodified. 2030 * sign with "%25", but leave the following two characters unmodified.
(...skipping 24 matching lines...) Expand all
1458 return source.substring(index, index + 3).toUpperCase(); 2055 return source.substring(index, index + 3).toUpperCase();
1459 } 2056 }
1460 // Escape is retained, and is already non-lower case, so return null to 2057 // Escape is retained, and is already non-lower case, so return null to
1461 // represent "no replacement necessary". 2058 // represent "no replacement necessary".
1462 return null; 2059 return null;
1463 } 2060 }
1464 2061
1465 // Converts a UTF-16 code-unit to its value as a hex digit. 2062 // Converts a UTF-16 code-unit to its value as a hex digit.
1466 // Returns -1 for non-hex digits. 2063 // Returns -1 for non-hex digits.
1467 static int _parseHexDigit(int char) { 2064 static int _parseHexDigit(int char) {
1468 int digit = char ^ Uri._ZERO; 2065 const int zeroDigit = 0x30;
2066 int digit = char ^ zeroDigit;
1469 if (digit <= 9) return digit; 2067 if (digit <= 9) return digit;
1470 int lowerCase = char | 0x20; 2068 int lowerCase = char | 0x20;
1471 if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) { 2069 if (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
1472 return lowerCase - (_LOWER_CASE_A - 10); 2070 return lowerCase - (_LOWER_CASE_A - 10);
1473 } 2071 }
1474 return -1; 2072 return -1;
1475 } 2073 }
1476 2074
1477 static String _escapeChar(int char) { 2075 static String _escapeChar(int char) {
1478 assert(char <= 0x10ffff); // It's a valid unicode code point. 2076 assert(char <= 0x10ffff); // It's a valid unicode code point.
1479 List<int> codeUnits; 2077 List<int> codeUnits;
1480 if (char < 0x80) { 2078 if (char < 0x80) {
1481 // ASCII, a single percent encoded sequence. 2079 // ASCII, a single percent encoded sequence.
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
1696 output.add(segment); 2294 output.add(segment);
1697 } 2295 }
1698 } 2296 }
1699 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) { 2297 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
1700 return "./"; 2298 return "./";
1701 } 2299 }
1702 if (appendSlash || output.last == '..') output.add(""); 2300 if (appendSlash || output.last == '..') output.add("");
1703 return output.join("/"); 2301 return output.join("/");
1704 } 2302 }
1705 2303
1706 /**
1707 * Resolve [reference] as an URI relative to `this`.
1708 *
1709 * First turn [reference] into a URI using [Uri.parse]. Then resolve the
1710 * resulting URI relative to `this`.
1711 *
1712 * Returns the resolved URI.
1713 *
1714 * See [resolveUri] for details.
1715 */
1716 Uri resolve(String reference) { 2304 Uri resolve(String reference) {
1717 return resolveUri(Uri.parse(reference)); 2305 return resolveUri(Uri.parse(reference));
1718 } 2306 }
1719 2307
1720 /**
1721 * Resolve [reference] as an URI relative to `this`.
1722 *
1723 * Returns the resolved URI.
1724 *
1725 * The algorithm "Transform Reference" for resolving a reference is described
1726 * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1 123").
1727 *
1728 * Updated to handle the case where the base URI is just a relative path -
1729 * that is: when it has no scheme or authority and the path does not start
1730 * with a slash.
1731 * In that case, the paths are combined without removing leading "..", and
1732 * an empty path is not converted to "/".
1733 */
1734 Uri resolveUri(Uri reference) { 2308 Uri resolveUri(Uri reference) {
1735 // From RFC 3986. 2309 // From RFC 3986.
1736 String targetScheme; 2310 String targetScheme;
1737 String targetUserInfo = ""; 2311 String targetUserInfo = "";
1738 String targetHost; 2312 String targetHost;
1739 int targetPort; 2313 int targetPort;
1740 String targetPath; 2314 String targetPath;
1741 String targetQuery; 2315 String targetQuery;
1742 if (reference.scheme.isNotEmpty) { 2316 if (reference.scheme.isNotEmpty) {
1743 targetScheme = reference.scheme; 2317 targetScheme = reference.scheme;
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1781 targetPath = reference.path; 2355 targetPath = reference.path;
1782 } else { 2356 } else {
1783 // Add path normalization on top of RFC algorithm. 2357 // Add path normalization on top of RFC algorithm.
1784 targetPath = _removeDotSegments("/" + reference.path); 2358 targetPath = _removeDotSegments("/" + reference.path);
1785 } 2359 }
1786 } else { 2360 } else {
1787 var mergedPath = _mergePaths(this._path, reference.path); 2361 var mergedPath = _mergePaths(this._path, reference.path);
1788 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) { 2362 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
1789 targetPath = _removeDotSegments(mergedPath); 2363 targetPath = _removeDotSegments(mergedPath);
1790 } else { 2364 } else {
1791 // Non-RFC 3986 beavior. If both base and reference are relative 2365 // Non-RFC 3986 behavior.
1792 // path, allow the merged path to start with "..". 2366 // If both base and reference are relative paths,
2367 // allow the merged path to start with "..".
1793 // The RFC only specifies the case where the base has a scheme. 2368 // The RFC only specifies the case where the base has a scheme.
1794 targetPath = _normalizeRelativePath(mergedPath); 2369 targetPath = _normalizeRelativePath(mergedPath);
1795 } 2370 }
1796 } 2371 }
1797 } 2372 }
1798 if (reference.hasQuery) targetQuery = reference.query; 2373 if (reference.hasQuery) targetQuery = reference.query;
1799 } 2374 }
1800 } 2375 }
1801 } 2376 }
1802 String fragment = reference.hasFragment ? reference.fragment : null; 2377 String fragment = reference.hasFragment ? reference.fragment : null;
1803 return new Uri._internal(targetScheme, 2378 return new _Uri._internal(targetScheme,
1804 targetUserInfo, 2379 targetUserInfo,
1805 targetHost, 2380 targetHost,
1806 targetPort, 2381 targetPort,
1807 targetPath, 2382 targetPath,
1808 targetQuery, 2383 targetQuery,
1809 fragment); 2384 fragment);
1810 } 2385 }
1811 2386
1812 /**
1813 * Returns whether the URI has a [scheme] component.
1814 */
1815 bool get hasScheme => scheme.isNotEmpty; 2387 bool get hasScheme => scheme.isNotEmpty;
1816 2388
1817 /**
1818 * Returns whether the URI has an [authority] component.
1819 */
1820 bool get hasAuthority => _host != null; 2389 bool get hasAuthority => _host != null;
1821 2390
1822 /**
1823 * Returns whether the URI has an explicit port.
1824 *
1825 * If the port number is the default port number
1826 * (zero for unrecognized schemes, with http (80) and https (443) being
1827 * recognized),
1828 * then the port is made implicit and omitted from the URI.
1829 */
1830 bool get hasPort => _port != null; 2391 bool get hasPort => _port != null;
1831 2392
1832 /**
1833 * Returns whether the URI has a query part.
1834 */
1835 bool get hasQuery => _query != null; 2393 bool get hasQuery => _query != null;
1836 2394
1837 /**
1838 * Returns whether the URI has a fragment part.
1839 */
1840 bool get hasFragment => _fragment != null; 2395 bool get hasFragment => _fragment != null;
1841 2396
1842 /**
1843 * Returns whether the URI has an empty path.
1844 */
1845 bool get hasEmptyPath => _path.isEmpty; 2397 bool get hasEmptyPath => _path.isEmpty;
1846 2398
1847 /**
1848 * Returns whether the URI has an absolute path (starting with '/').
1849 */
1850 bool get hasAbsolutePath => _path.startsWith('/'); 2399 bool get hasAbsolutePath => _path.startsWith('/');
1851 2400
1852 /**
1853 * Returns the origin of the URI in the form scheme://host:port for the
1854 * schemes http and https.
1855 *
1856 * It is an error if the scheme is not "http" or "https".
1857 *
1858 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
1859 */
1860 String get origin { 2401 String get origin {
1861 if (scheme == "" || _host == null || _host == "") { 2402 if (scheme == "" || _host == null || _host == "") {
1862 throw new StateError("Cannot use origin without a scheme: $this"); 2403 throw new StateError("Cannot use origin without a scheme: $this");
1863 } 2404 }
1864 if (scheme != "http" && scheme != "https") { 2405 if (scheme != "http" && scheme != "https") {
1865 throw new StateError( 2406 throw new StateError(
1866 "Origin is only applicable schemes http and https: $this"); 2407 "Origin is only applicable schemes http and https: $this");
1867 } 2408 }
1868 if (_port == null) return "$scheme://$_host"; 2409 if (_port == null) return "$scheme://$_host";
1869 return "$scheme://$_host:$_port"; 2410 return "$scheme://$_host:$_port";
1870 } 2411 }
1871 2412
1872 /**
1873 * Returns the file path from a file URI.
1874 *
1875 * The returned path has either Windows or non-Windows
1876 * semantics.
1877 *
1878 * For non-Windows semantics the slash ("/") is used to separate
1879 * path segments.
1880 *
1881 * For Windows semantics the backslash ("\") separator is used to
1882 * separate path segments.
1883 *
1884 * If the URI is absolute the path starts with a path separator
1885 * unless Windows semantics is used and the first path segment is a
1886 * drive letter. When Windows semantics is used a host component in
1887 * the uri in interpreted as a file server and a UNC path is
1888 * returned.
1889 *
1890 * The default for whether to use Windows or non-Windows semantics
1891 * determined from the platform Dart is running on. When running in
1892 * the standalone VM this is detected by the VM based on the
1893 * operating system. When running in a browser non-Windows semantics
1894 * is always used.
1895 *
1896 * To override the automatic detection of which semantics to use pass
1897 * a value for [windows]. Passing `true` will use Windows
1898 * semantics and passing `false` will use non-Windows semantics.
1899 *
1900 * If the URI ends with a slash (i.e. the last path component is
1901 * empty) the returned file path will also end with a slash.
1902 *
1903 * With Windows semantics URIs starting with a drive letter cannot
1904 * be relative to the current drive on the designated drive. That is
1905 * for the URI `file:///c:abc` calling `toFilePath` will throw as a
1906 * path segment cannot contain colon on Windows.
1907 *
1908 * Examples using non-Windows semantics (resulting of calling
1909 * toFilePath in comment):
1910 *
1911 * Uri.parse("xxx/yyy"); // xxx/yyy
1912 * Uri.parse("xxx/yyy/"); // xxx/yyy/
1913 * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
1914 * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
1915 * Uri.parse("file:///C:"); // /C:
1916 * Uri.parse("file:///C:a"); // /C:a
1917 *
1918 * Examples using Windows semantics (resulting URI in comment):
1919 *
1920 * Uri.parse("xxx/yyy"); // xxx\yyy
1921 * Uri.parse("xxx/yyy/"); // xxx\yyy\
1922 * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
1923 * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
1924 * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
1925 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
1926 * // cannot contain colon on Windows.
1927 * Uri.parse("file://server/share/file"); // \\server\share\file
1928 *
1929 * If the URI is not a file URI calling this throws
1930 * [UnsupportedError].
1931 *
1932 * If the URI cannot be converted to a file path calling this throws
1933 * [UnsupportedError].
1934 */
1935 String toFilePath({bool windows}) { 2413 String toFilePath({bool windows}) {
1936 if (scheme != "" && scheme != "file") { 2414 if (scheme != "" && scheme != "file") {
1937 throw new UnsupportedError( 2415 throw new UnsupportedError(
1938 "Cannot extract a file path from a $scheme URI"); 2416 "Cannot extract a file path from a $scheme URI");
1939 } 2417 }
1940 if (query != "") { 2418 if (query != "") {
1941 throw new UnsupportedError( 2419 throw new UnsupportedError(
1942 "Cannot extract a file path from a URI with a query component"); 2420 "Cannot extract a file path from a URI with a query component");
1943 } 2421 }
1944 if (fragment != "") { 2422 if (fragment != "") {
1945 throw new UnsupportedError( 2423 throw new UnsupportedError(
1946 "Cannot extract a file path from a URI with a fragment component"); 2424 "Cannot extract a file path from a URI with a fragment component");
1947 } 2425 }
1948 if (windows == null) windows = _isWindows; 2426 if (windows == null) windows = _isWindows;
1949 return windows ? _toWindowsFilePath() : _toFilePath(); 2427 return windows ? _toWindowsFilePath(this) : _toFilePath();
1950 } 2428 }
1951 2429
1952 String _toFilePath() { 2430 String _toFilePath() {
1953 if (host != "") { 2431 if (hasAuthority && host != "") {
1954 throw new UnsupportedError( 2432 throw new UnsupportedError(
1955 "Cannot extract a non-Windows file path from a file URI " 2433 "Cannot extract a non-Windows file path from a file URI "
1956 "with an authority"); 2434 "with an authority");
1957 } 2435 }
2436 // Use path segments to have any escapes unescaped.
2437 var pathSegments = this.pathSegments;
1958 _checkNonWindowsPathReservedCharacters(pathSegments, false); 2438 _checkNonWindowsPathReservedCharacters(pathSegments, false);
1959 var result = new StringBuffer(); 2439 var result = new StringBuffer();
1960 if (_isPathAbsolute) result.write("/"); 2440 if (hasAbsolutePath) result.write("/");
1961 result.writeAll(pathSegments, "/"); 2441 result.writeAll(pathSegments, "/");
1962 return result.toString(); 2442 return result.toString();
1963 } 2443 }
1964 2444
1965 String _toWindowsFilePath() { 2445 static String _toWindowsFilePath(Uri uri) {
1966 bool hasDriveLetter = false; 2446 bool hasDriveLetter = false;
1967 var segments = pathSegments; 2447 var segments = uri.pathSegments;
1968 if (segments.length > 0 && 2448 if (segments.length > 0 &&
1969 segments[0].length == 2 && 2449 segments[0].length == 2 &&
1970 segments[0].codeUnitAt(1) == _COLON) { 2450 segments[0].codeUnitAt(1) == _COLON) {
1971 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false); 2451 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
1972 _checkWindowsPathReservedCharacters(segments, false, 1); 2452 _checkWindowsPathReservedCharacters(segments, false, 1);
1973 hasDriveLetter = true; 2453 hasDriveLetter = true;
1974 } else { 2454 } else {
1975 _checkWindowsPathReservedCharacters(segments, false); 2455 _checkWindowsPathReservedCharacters(segments, false, 0);
1976 } 2456 }
1977 var result = new StringBuffer(); 2457 var result = new StringBuffer();
1978 if (_isPathAbsolute && !hasDriveLetter) result.write("\\"); 2458 if (uri.hasAbsolutePath && !hasDriveLetter) result.write(r"\");
1979 if (host != "") { 2459 if (uri.hasAuthority) {
1980 result.write("\\"); 2460 var host = uri.host;
1981 result.write(host); 2461 if (host.isNotEmpty) {
1982 result.write("\\"); 2462 result.write(r"\");
2463 result.write(host);
2464 result.write(r"\");
2465 }
1983 } 2466 }
1984 result.writeAll(segments, "\\"); 2467 result.writeAll(segments, r"\");
1985 if (hasDriveLetter && segments.length == 1) result.write("\\"); 2468 if (hasDriveLetter && segments.length == 1) result.write(r"\");
1986 return result.toString(); 2469 return result.toString();
1987 } 2470 }
1988 2471
1989 bool get _isPathAbsolute { 2472 bool get _isPathAbsolute {
1990 if (path == null || path.isEmpty) return false; 2473 return _path != null && _path.startsWith('/');
1991 return path.startsWith('/');
1992 } 2474 }
1993 2475
1994 void _writeAuthority(StringSink ss) { 2476 void _writeAuthority(StringSink ss) {
1995 if (_userInfo.isNotEmpty) { 2477 if (_userInfo.isNotEmpty) {
1996 ss.write(_userInfo); 2478 ss.write(_userInfo);
1997 ss.write("@"); 2479 ss.write("@");
1998 } 2480 }
1999 if (_host != null) ss.write(_host); 2481 if (_host != null) ss.write(_host);
2000 if (_port != null) { 2482 if (_port != null) {
2001 ss.write(":"); 2483 ss.write(":");
2002 ss.write(_port); 2484 ss.write(_port);
2003 } 2485 }
2004 } 2486 }
2005 2487
2006 /** 2488 /**
2007 * Access the structure of a `data:` URI. 2489 * Access the structure of a `data:` URI.
2008 * 2490 *
2009 * Returns a [UriData] object for `data:` URIs and `null` for all other 2491 * Returns a [UriData] object for `data:` URIs and `null` for all other
2010 * URIs. 2492 * URIs.
2011 * The [UriData] object can be used to access the media type and data 2493 * The [UriData] object can be used to access the media type and data
2012 * of a `data:` URI. 2494 * of a `data:` URI.
2013 */ 2495 */
2014 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null; 2496 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
2015 2497
2016 String toString() { 2498 String toString() {
2499 if (_text != null) return _text;
2017 StringBuffer sb = new StringBuffer(); 2500 StringBuffer sb = new StringBuffer();
2018 _addIfNonEmpty(sb, scheme, scheme, ':'); 2501 if (scheme.isNotEmpty) sb..write(scheme)..write(":");
2019 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { 2502 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
2020 // File URIS always have the authority, even if it is empty. 2503 // File URIS always have the authority, even if it is empty.
2021 // The empty URI means "localhost". 2504 // The empty URI means "localhost".
2022 sb.write("//"); 2505 sb.write("//");
2023 _writeAuthority(sb); 2506 _writeAuthority(sb);
2024 } 2507 }
2025 sb.write(path); 2508 sb.write(path);
2026 if (_query != null) { sb..write("?")..write(_query); } 2509 if (_query != null) sb..write("?")..write(_query);
2027 if (_fragment != null) { sb..write("#")..write(_fragment); } 2510 if (_fragment != null) sb..write("#")..write(_fragment);
2028 return sb.toString(); 2511 _text = sb.toString();
2512 return _text;
2029 } 2513 }
2030 2514
2031 bool operator==(other) { 2515 bool operator==(other) {
2032 if (other is! Uri) return false; 2516 if (identical(this, other)) return true;
2033 Uri uri = other; 2517 if (other is Uri) {
2034 return scheme == uri.scheme && 2518 Uri uri = other;
2035 hasAuthority == uri.hasAuthority && 2519 return scheme == uri.scheme &&
2036 userInfo == uri.userInfo && 2520 hasAuthority == uri.hasAuthority &&
2037 host == uri.host && 2521 userInfo == uri.userInfo &&
2038 port == uri.port && 2522 host == uri.host &&
2039 path == uri.path && 2523 port == uri.port &&
2040 hasQuery == uri.hasQuery && 2524 path == uri.path &&
2041 query == uri.query && 2525 hasQuery == uri.hasQuery &&
2042 hasFragment == uri.hasFragment && 2526 query == uri.query &&
2043 fragment == uri.fragment; 2527 hasFragment == uri.hasFragment &&
2528 fragment == uri.fragment;
2529 }
2530 return false;
2044 } 2531 }
2045 2532
2046 int get hashCode { 2533 int get hashCode {
2047 int combine(part, current) { 2534 return (_text ?? toString()).hashCode;
2048 // The sum is truncated to 30 bits to make sure it fits into a Smi.
2049 return (current * 31 + part.hashCode) & 0x3FFFFFFF;
2050 }
2051 return combine(scheme, combine(userInfo, combine(host, combine(port,
2052 combine(path, combine(query, combine(fragment, 1)))))));
2053 }
2054
2055 static void _addIfNonEmpty(StringBuffer sb, String test,
2056 String first, String second) {
2057 if ("" != test) {
2058 sb.write(first);
2059 sb.write(second);
2060 }
2061 }
2062
2063 /**
2064 * Encode the string [component] using percent-encoding to make it
2065 * safe for literal use as a URI component.
2066 *
2067 * All characters except uppercase and lowercase letters, digits and
2068 * the characters `-_.!~*'()` are percent-encoded. This is the
2069 * set of characters specified in RFC 2396 and the which is
2070 * specified for the encodeUriComponent in ECMA-262 version 5.1.
2071 *
2072 * When manually encoding path segments or query components remember
2073 * to encode each part separately before building the path or query
2074 * string.
2075 *
2076 * For encoding the query part consider using
2077 * [encodeQueryComponent].
2078 *
2079 * To avoid the need for explicitly encoding use the [pathSegments]
2080 * and [queryParameters] optional named arguments when constructing
2081 * a [Uri].
2082 */
2083 static String encodeComponent(String component) {
2084 return _uriEncode(_unreserved2396Table, component, UTF8, false);
2085 }
2086
2087 /**
2088 * Encode the string [component] according to the HTML 4.01 rules
2089 * for encoding the posting of a HTML form as a query string
2090 * component.
2091 *
2092 * Encode the string [component] according to the HTML 4.01 rules
2093 * for encoding the posting of a HTML form as a query string
2094 * component.
2095
2096 * The component is first encoded to bytes using [encoding].
2097 * The default is to use [UTF8] encoding, which preserves all
2098 * the characters that don't need encoding.
2099
2100 * Then the resulting bytes are "percent-encoded". This transforms
2101 * spaces (U+0020) to a plus sign ('+') and all bytes that are not
2102 * the ASCII decimal digits, letters or one of '-._~' are written as
2103 * a percent sign '%' followed by the two-digit hexadecimal
2104 * representation of the byte.
2105
2106 * Note that the set of characters which are percent-encoded is a
2107 * superset of what HTML 4.01 requires, since it refers to RFC 1738
2108 * for reserved characters.
2109 *
2110 * When manually encoding query components remember to encode each
2111 * part separately before building the query string.
2112 *
2113 * To avoid the need for explicitly encoding the query use the
2114 * [queryParameters] optional named arguments when constructing a
2115 * [Uri].
2116 *
2117 * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
2118 * details.
2119 */
2120 static String encodeQueryComponent(String component,
2121 {Encoding encoding: UTF8}) {
2122 return _uriEncode(_unreservedTable, component, encoding, true);
2123 }
2124
2125 /**
2126 * Decodes the percent-encoding in [encodedComponent].
2127 *
2128 * Note that decoding a URI component might change its meaning as
2129 * some of the decoded characters could be characters with are
2130 * delimiters for a given URI componene type. Always split a URI
2131 * component using the delimiters for the component before decoding
2132 * the individual parts.
2133 *
2134 * For handling the [path] and [query] components consider using
2135 * [pathSegments] and [queryParameters] to get the separated and
2136 * decoded component.
2137 */
2138 static String decodeComponent(String encodedComponent) {
2139 return _uriDecode(encodedComponent, 0, encodedComponent.length,
2140 UTF8, false);
2141 }
2142
2143 /**
2144 * Decodes the percent-encoding in [encodedComponent], converting
2145 * pluses to spaces.
2146 *
2147 * It will create a byte-list of the decoded characters, and then use
2148 * [encoding] to decode the byte-list to a String. The default encoding is
2149 * UTF-8.
2150 */
2151 static String decodeQueryComponent(
2152 String encodedComponent,
2153 {Encoding encoding: UTF8}) {
2154 return _uriDecode(encodedComponent, 0, encodedComponent.length,
2155 encoding, true);
2156 }
2157
2158 /**
2159 * Encode the string [uri] using percent-encoding to make it
2160 * safe for literal use as a full URI.
2161 *
2162 * All characters except uppercase and lowercase letters, digits and
2163 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
2164 * is the set of characters specified in in ECMA-262 version 5.1 for
2165 * the encodeURI function .
2166 */
2167 static String encodeFull(String uri) {
2168 return _uriEncode(_encodeFullTable, uri, UTF8, false);
2169 }
2170
2171 /**
2172 * Decodes the percent-encoding in [uri].
2173 *
2174 * Note that decoding a full URI might change its meaning as some of
2175 * the decoded characters could be reserved characters. In most
2176 * cases an encoded URI should be parsed into components using
2177 * [Uri.parse] before decoding the separate components.
2178 */
2179 static String decodeFull(String uri) {
2180 return _uriDecode(uri, 0, uri.length, UTF8, false);
2181 }
2182
2183 /**
2184 * Returns the [query] split into a map according to the rules
2185 * specified for FORM post in the [HTML 4.01 specification section
2186 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM L 4.01 section 17.13.4").
2187 * Each key and value in the returned map has been decoded. If the [query]
2188 * is the empty string an empty map is returned.
2189 *
2190 * Keys in the query string that have no value are mapped to the
2191 * empty string.
2192 *
2193 * Each query component will be decoded using [encoding]. The default encoding
2194 * is UTF-8.
2195 */
2196 static Map<String, String> splitQueryString(String query,
2197 {Encoding encoding: UTF8}) {
2198 return query.split("&").fold({}, (map, element) {
2199 int index = element.indexOf("=");
2200 if (index == -1) {
2201 if (element != "") {
2202 map[decodeQueryComponent(element, encoding: encoding)] = "";
2203 }
2204 } else if (index != 0) {
2205 var key = element.substring(0, index);
2206 var value = element.substring(index + 1);
2207 map[Uri.decodeQueryComponent(key, encoding: encoding)] =
2208 decodeQueryComponent(value, encoding: encoding);
2209 }
2210 return map;
2211 });
2212 } 2535 }
2213 2536
2214 static List _createList() => []; 2537 static List _createList() => [];
2215 2538
2216 static Map _splitQueryStringAll( 2539 static Map _splitQueryStringAll(
2217 String query, {Encoding encoding: UTF8}) { 2540 String query, {Encoding encoding: UTF8}) {
2218 Map result = {}; 2541 Map result = {};
2219 int i = 0; 2542 int i = 0;
2220 int start = 0; 2543 int start = 0;
2221 int equalsIndex = -1; 2544 int equalsIndex = -1;
(...skipping 22 matching lines...) Expand all
2244 parsePair(start, equalsIndex, i); 2567 parsePair(start, equalsIndex, i);
2245 start = i + 1; 2568 start = i + 1;
2246 equalsIndex = -1; 2569 equalsIndex = -1;
2247 } 2570 }
2248 i++; 2571 i++;
2249 } 2572 }
2250 parsePair(start, equalsIndex, i); 2573 parsePair(start, equalsIndex, i);
2251 return result; 2574 return result;
2252 } 2575 }
2253 2576
2254 /**
2255 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
2256 * as a list of 4 bytes in network byte order (big endian).
2257 *
2258 * Throws a [FormatException] if [host] is not a valid IPv4 address
2259 * representation.
2260 */
2261 static List<int> parseIPv4Address(String host) {
2262 void error(String msg) {
2263 throw new FormatException('Illegal IPv4 address, $msg');
2264 }
2265 var bytes = host.split('.');
2266 if (bytes.length != 4) {
2267 error('IPv4 address should contain exactly 4 parts');
2268 }
2269 // TODO(ajohnsen): Consider using Uint8List.
2270 return bytes
2271 .map((byteString) {
2272 int byte = int.parse(byteString);
2273 if (byte < 0 || byte > 255) {
2274 error('each part must be in the range of `0..255`');
2275 }
2276 return byte;
2277 })
2278 .toList();
2279 }
2280
2281 /**
2282 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
2283 * as a list of 16 bytes in network byte order (big endian).
2284 *
2285 * Throws a [FormatException] if [host] is not a valid IPv6 address
2286 * representation.
2287 *
2288 * Acts on the substring from [start] to [end]. If [end] is omitted, it
2289 * defaults ot the end of the string.
2290 *
2291 * Some examples of IPv6 addresses:
2292 * * ::1
2293 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
2294 * * 3ffe:2a00:100:7031::1
2295 * * ::FFFF:129.144.52.38
2296 * * 2010:836B:4179::836B:4179
2297 */
2298 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
2299 if (end == null) end = host.length;
2300 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
2301 // by `:`'s, with the following exceptions:
2302 //
2303 // - One (and only one) wildcard (`::`) may be present, representing a fill
2304 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
2305 // - The last two parts may be replaced by an IPv4 address.
2306 void error(String msg, [position]) {
2307 throw new FormatException('Illegal IPv6 address, $msg', host, position);
2308 }
2309 int parseHex(int start, int end) {
2310 if (end - start > 4) {
2311 error('an IPv6 part can only contain a maximum of 4 hex digits', start);
2312 }
2313 int value = int.parse(host.substring(start, end), radix: 16);
2314 if (value < 0 || value > (1 << 16) - 1) {
2315 error('each part must be in the range of `0x0..0xFFFF`', start);
2316 }
2317 return value;
2318 }
2319 if (host.length < 2) error('address is too short');
2320 List<int> parts = [];
2321 bool wildcardSeen = false;
2322 int partStart = start;
2323 // Parse all parts, except a potential last one.
2324 for (int i = start; i < end; i++) {
2325 if (host.codeUnitAt(i) == _COLON) {
2326 if (i == start) {
2327 // If we see a `:` in the beginning, expect wildcard.
2328 i++;
2329 if (host.codeUnitAt(i) != _COLON) {
2330 error('invalid start colon.', i);
2331 }
2332 partStart = i;
2333 }
2334 if (i == partStart) {
2335 // Wildcard. We only allow one.
2336 if (wildcardSeen) {
2337 error('only one wildcard `::` is allowed', i);
2338 }
2339 wildcardSeen = true;
2340 parts.add(-1);
2341 } else {
2342 // Found a single colon. Parse [partStart..i] as a hex entry.
2343 parts.add(parseHex(partStart, i));
2344 }
2345 partStart = i + 1;
2346 }
2347 }
2348 if (parts.length == 0) error('too few parts');
2349 bool atEnd = (partStart == end);
2350 bool isLastWildcard = (parts.last == -1);
2351 if (atEnd && !isLastWildcard) {
2352 error('expected a part after last `:`', end);
2353 }
2354 if (!atEnd) {
2355 try {
2356 parts.add(parseHex(partStart, end));
2357 } catch (e) {
2358 // Failed to parse the last chunk as hex. Try IPv4.
2359 try {
2360 List<int> last = parseIPv4Address(host.substring(partStart, end));
2361 parts.add(last[0] << 8 | last[1]);
2362 parts.add(last[2] << 8 | last[3]);
2363 } catch (e) {
2364 error('invalid end of IPv6 address.', partStart);
2365 }
2366 }
2367 }
2368 if (wildcardSeen) {
2369 if (parts.length > 7) {
2370 error('an address with a wildcard must have less than 7 parts');
2371 }
2372 } else if (parts.length != 8) {
2373 error('an address without a wildcard must contain exactly 8 parts');
2374 }
2375 List<int> bytes = new Uint8List(16);
2376 for (int i = 0, index = 0; i < parts.length; i++) {
2377 int value = parts[i];
2378 if (value == -1) {
2379 int wildCardLength = 9 - parts.length;
2380 for (int j = 0; j < wildCardLength; j++) {
2381 bytes[index] = 0;
2382 bytes[index + 1] = 0;
2383 index += 2;
2384 }
2385 } else {
2386 bytes[index] = value >> 8;
2387 bytes[index + 1] = value & 0xff;
2388 index += 2;
2389 }
2390 }
2391 return bytes;
2392 }
2393
2394 // Frequently used character codes.
2395 static const int _SPACE = 0x20;
2396 static const int _DOUBLE_QUOTE = 0x22;
2397 static const int _NUMBER_SIGN = 0x23;
2398 static const int _PERCENT = 0x25;
2399 static const int _ASTERISK = 0x2A;
2400 static const int _PLUS = 0x2B;
2401 static const int _DOT = 0x2E;
2402 static const int _SLASH = 0x2F;
2403 static const int _ZERO = 0x30;
2404 static const int _NINE = 0x39;
2405 static const int _COLON = 0x3A;
2406 static const int _LESS = 0x3C;
2407 static const int _GREATER = 0x3E;
2408 static const int _QUESTION = 0x3F;
2409 static const int _AT_SIGN = 0x40;
2410 static const int _UPPER_CASE_A = 0x41;
2411 static const int _UPPER_CASE_F = 0x46;
2412 static const int _UPPER_CASE_Z = 0x5A;
2413 static const int _LEFT_BRACKET = 0x5B;
2414 static const int _BACKSLASH = 0x5C;
2415 static const int _RIGHT_BRACKET = 0x5D;
2416 static const int _LOWER_CASE_A = 0x61;
2417 static const int _LOWER_CASE_F = 0x66;
2418 static const int _LOWER_CASE_Z = 0x7A;
2419 static const int _BAR = 0x7C;
2420
2421 static const String _hexDigits = "0123456789ABCDEF";
2422
2423 external static String _uriEncode(List<int> canonicalTable, 2577 external static String _uriEncode(List<int> canonicalTable,
2424 String text, 2578 String text,
2425 Encoding encoding, 2579 Encoding encoding,
2426 bool spaceToPlus); 2580 bool spaceToPlus);
2427 2581
2428 /** 2582 /**
2429 * Convert a byte (2 character hex sequence) in string [s] starting 2583 * Convert a byte (2 character hex sequence) in string [s] starting
2430 * at position [pos] to its ordinal value 2584 * at position [pos] to its ordinal value
2431 */ 2585 */
2432 static int _hexCharPairToByte(String s, int pos) { 2586 static int _hexCharPairToByte(String s, int pos) {
(...skipping 501 matching lines...) Expand 10 before | Expand all | Expand 10 after
2934 mimeType = ""; 3088 mimeType = "";
2935 } 3089 }
2936 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) { 3090 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
2937 buffer.write(mimeType); // Common cases need no escaping. 3091 buffer.write(mimeType); // Common cases need no escaping.
2938 } else { 3092 } else {
2939 int slashIndex = _validateMimeType(mimeType); 3093 int slashIndex = _validateMimeType(mimeType);
2940 if (slashIndex < 0) { 3094 if (slashIndex < 0) {
2941 throw new ArgumentError.value(mimeType, "mimeType", 3095 throw new ArgumentError.value(mimeType, "mimeType",
2942 "Invalid MIME type"); 3096 "Invalid MIME type");
2943 } 3097 }
2944 buffer.write(Uri._uriEncode(_tokenCharTable, 3098 buffer.write(_Uri._uriEncode(_tokenCharTable,
2945 mimeType.substring(0, slashIndex), 3099 mimeType.substring(0, slashIndex),
2946 UTF8, false)); 3100 UTF8, false));
2947 buffer.write("/"); 3101 buffer.write("/");
2948 buffer.write(Uri._uriEncode(_tokenCharTable, 3102 buffer.write(_Uri._uriEncode(_tokenCharTable,
2949 mimeType.substring(slashIndex + 1), 3103 mimeType.substring(slashIndex + 1),
2950 UTF8, false)); 3104 UTF8, false));
2951 } 3105 }
2952 if (charsetName != null) { 3106 if (charsetName != null) {
2953 if (indices != null) { 3107 if (indices != null) {
2954 indices..add(buffer.length) 3108 indices..add(buffer.length)
2955 ..add(buffer.length + 8); 3109 ..add(buffer.length + 8);
2956 } 3110 }
2957 buffer.write(";charset="); 3111 buffer.write(";charset=");
2958 buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false)); 3112 buffer.write(_Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
2959 } 3113 }
2960 parameters?.forEach((var key, var value) { 3114 parameters?.forEach((var key, var value) {
2961 if (key.isEmpty) { 3115 if (key.isEmpty) {
2962 throw new ArgumentError.value("", "Parameter names must not be empty"); 3116 throw new ArgumentError.value("", "Parameter names must not be empty");
2963 } 3117 }
2964 if (value.isEmpty) { 3118 if (value.isEmpty) {
2965 throw new ArgumentError.value("", "Parameter values must not be empty", 3119 throw new ArgumentError.value("", "Parameter values must not be empty",
2966 'parameters["$key"]'); 3120 'parameters["$key"]');
2967 } 3121 }
2968 if (indices != null) indices.add(buffer.length); 3122 if (indices != null) indices.add(buffer.length);
2969 buffer.write(';'); 3123 buffer.write(';');
2970 // Encode any non-RFC2045-token character and both '%' and '#'. 3124 // Encode any non-RFC2045-token character and both '%' and '#'.
2971 buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false)); 3125 buffer.write(_Uri._uriEncode(_tokenCharTable, key, UTF8, false));
2972 if (indices != null) indices.add(buffer.length); 3126 if (indices != null) indices.add(buffer.length);
2973 buffer.write('='); 3127 buffer.write('=');
2974 buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false)); 3128 buffer.write(_Uri._uriEncode(_tokenCharTable, value, UTF8, false));
2975 }); 3129 });
2976 } 3130 }
2977 3131
2978 /** 3132 /**
2979 * Checks mimeType is valid-ish (`token '/' token`). 3133 * Checks mimeType is valid-ish (`token '/' token`).
2980 * 3134 *
2981 * Returns the index of the slash, or -1 if the mime type is not 3135 * Returns the index of the slash, or -1 if the mime type is not
2982 * considered valid. 3136 * considered valid.
2983 * 3137 *
2984 * Currently only looks for slashes, all other characters will be 3138 * Currently only looks for slashes, all other characters will be
2985 * percent-encoded as UTF-8 if necessary. 3139 * percent-encoded as UTF-8 if necessary.
2986 */ 3140 */
2987 static int _validateMimeType(String mimeType) { 3141 static int _validateMimeType(String mimeType) {
2988 int slashIndex = -1; 3142 int slashIndex = -1;
2989 for (int i = 0; i < mimeType.length; i++) { 3143 for (int i = 0; i < mimeType.length; i++) {
2990 var char = mimeType.codeUnitAt(i); 3144 var char = mimeType.codeUnitAt(i);
2991 if (char != Uri._SLASH) continue; 3145 if (char != _SLASH) continue;
2992 if (slashIndex < 0) { 3146 if (slashIndex < 0) {
2993 slashIndex = i; 3147 slashIndex = i;
2994 continue; 3148 continue;
2995 } 3149 }
2996 return -1; 3150 return -1;
2997 } 3151 }
2998 return slashIndex; 3152 return slashIndex;
2999 } 3153 }
3000 3154
3001 /** 3155 /**
3002 * Parses a string as a `data` URI. 3156 * Parses a string as a `data` URI.
3003 * 3157 *
3004 * The string must have the format: 3158 * The string must have the format:
3005 * 3159 *
3006 * ``` 3160 * ```
3007 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a 3161 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a
3008 * ```` 3162 * ````
3009 * 3163 *
3010 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045, 3164 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
3011 * and `data` is a sequnce of URI-characters (RFC-2396 `uric`). 3165 * and `data` is a sequence of URI-characters (RFC-2396 `uric`).
3012 * 3166 *
3013 * This means that all the characters must be ASCII, but the URI may contain 3167 * This means that all the characters must be ASCII, but the URI may contain
3014 * percent-escapes for non-ASCII byte values that need an interpretation 3168 * percent-escapes for non-ASCII byte values that need an interpretation
3015 * to be converted to the corresponding string. 3169 * to be converted to the corresponding string.
3016 * 3170 *
3017 * Parsing doesn't check the validity of any part, it just checks that the 3171 * Parsing doesn't check the validity of any part, it just checks that the
3018 * input has the correct structure with the correct sequence of `/`, `;`, `=` 3172 * input has the correct structure with the correct sequence of `/`, `;`, `=`
3019 * and `,` delimiters. 3173 * and `,` delimiters.
3020 * 3174 *
3021 * Accessing the individual parts may fail later if they turn out to have 3175 * Accessing the individual parts may fail later if they turn out to have
3022 * content that can't be decoded sucessfully as a string. 3176 * content that can't be decoded successfully as a string.
3023 */ 3177 */
3024 static UriData parse(String uri) { 3178 static UriData parse(String uri) {
3025 if (!uri.startsWith("data:")) { 3179 if (uri.length >= 5) {
3026 throw new FormatException("Does not start with 'data:'", uri, 0); 3180 int dataDelta = _startsWithData(uri, 0);
floitsch 2016/06/29 23:41:47 startsWithData should return a bool, and you shoul
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Wouldn't work. I need to distinguish "data:" from
3181 if (dataDelta == 0) {
3182 // Exact match on "data:".
3183 return _parse(uri, 5, null);
3184 }
3185 if (dataDelta == 0x20) {
3186 // Starts with a non-normalized "data" scheme containing upper-case
3187 // letters. Parse anyway, but throw away the scheme.
3188 return _parse(uri.substring(5), 0, null);
3189 }
3027 } 3190 }
3028 return _parse(uri, 5, null); 3191 throw new FormatException("Does not start with 'data:'", uri, 0);
3029 } 3192 }
3030 3193
3031 /** 3194 /**
3032 * The [Uri] that this `UriData` is giving access to. 3195 * The [Uri] that this `UriData` is giving access to.
3033 * 3196 *
3034 * Returns a `Uri` with scheme `data` and the remainder of the data URI 3197 * Returns a `Uri` with scheme `data` and the remainder of the data URI
3035 * as path. 3198 * as path.
3036 */ 3199 */
3037 Uri get uri { 3200 Uri get uri {
3038 if (_uriCache != null) return _uriCache; 3201 if (_uriCache != null) return _uriCache;
3039 String path = _text; 3202 String path = _text;
3040 String query = null; 3203 String query = null;
3041 int colonIndex = _separatorIndices[0]; 3204 int colonIndex = _separatorIndices[0];
3042 int queryIndex = _text.indexOf('?', colonIndex + 1); 3205 int queryIndex = _text.indexOf('?', colonIndex + 1);
3043 int end = null; 3206 int end = null;
3044 if (queryIndex >= 0) { 3207 if (queryIndex >= 0) {
3045 query = _text.substring(queryIndex + 1); 3208 query = _text.substring(queryIndex + 1);
3046 end = queryIndex; 3209 end = queryIndex;
3047 } 3210 }
3048 path = _text.substring(colonIndex + 1, end); 3211 path = _text.substring(colonIndex + 1, end);
3049 // TODO(lrn): This can generate a URI that isn't path normalized. 3212 // TODO(lrn): This can generate a URI that isn't path normalized.
3050 // That's perfectly reasonable - data URIs are not hierarchical, 3213 // That's perfectly reasonable - data URIs are not hierarchical,
3051 // but it may make some consumers stumble. 3214 // but it may make some consumers stumble.
3052 // Should we at least do escape normalization? 3215 // Should we at least do escape normalization?
3053 _uriCache = new Uri._internal("data", "", null, null, path, query, null); 3216 _uriCache = new _Uri._internal("data", "", null, null, path, query, null);
3054 return _uriCache; 3217 return _uriCache;
3055 } 3218 }
3056 3219
3057 /** 3220 /**
3058 * The MIME type of the data URI. 3221 * The MIME type of the data URI.
3059 * 3222 *
3060 * A data URI consists of a "media type" followed by data. 3223 * A data URI consists of a "media type" followed by data.
3061 * The media type starts with a MIME type and can be followed by 3224 * The media type starts with a MIME type and can be followed by
3062 * extra parameters. 3225 * extra parameters.
3063 * 3226 *
3064 * Example: 3227 * Example:
3065 * 3228 *
3066 * data:text/plain;charset=utf-8,Hello%20World! 3229 * data:text/plain;charset=utf-8,Hello%20World!
3067 * 3230 *
3068 * This data URI has the media type `text/plain;charset=utf-8`, which is the 3231 * This data URI has the media type `text/plain;charset=utf-8`, which is the
3069 * MIME type `text/plain` with the parameter `charset` with value `utf-8`. 3232 * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
3070 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. 3233 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
3071 * 3234 *
3072 * If the first part of the data URI is empty, it defaults to `text/plain`. 3235 * If the first part of the data URI is empty, it defaults to `text/plain`.
3073 */ 3236 */
3074 String get mimeType { 3237 String get mimeType {
3075 int start = _separatorIndices[0] + 1; 3238 int start = _separatorIndices[0] + 1;
3076 int end = _separatorIndices[1]; 3239 int end = _separatorIndices[1];
3077 if (start == end) return "text/plain"; 3240 if (start == end) return "text/plain";
3078 return Uri._uriDecode(_text, start, end, UTF8, false); 3241 return _Uri._uriDecode(_text, start, end, UTF8, false);
3079 } 3242 }
3080 3243
3081 /** 3244 /**
3082 * The charset parameter of the media type. 3245 * The charset parameter of the media type.
3083 * 3246 *
3084 * If the parameters of the media type contains a `charset` parameter 3247 * If the parameters of the media type contains a `charset` parameter
3085 * then this returns its value, otherwise it returns `US-ASCII`, 3248 * then this returns its value, otherwise it returns `US-ASCII`,
3086 * which is the default charset for data URIs. 3249 * which is the default charset for data URIs.
3087 */ 3250 */
3088 String get charset { 3251 String get charset {
3089 int parameterStart = 1; 3252 int parameterStart = 1;
3090 int parameterEnd = _separatorIndices.length - 1; // The ',' before data. 3253 int parameterEnd = _separatorIndices.length - 1; // The ',' before data.
3091 if (isBase64) { 3254 if (isBase64) {
3092 // There is a ";base64" separator, so subtract one for that as well. 3255 // There is a ";base64" separator, so subtract one for that as well.
3093 parameterEnd -= 1; 3256 parameterEnd -= 1;
3094 } 3257 }
3095 for (int i = parameterStart; i < parameterEnd; i += 2) { 3258 for (int i = parameterStart; i < parameterEnd; i += 2) {
3096 var keyStart = _separatorIndices[i] + 1; 3259 var keyStart = _separatorIndices[i] + 1;
3097 var keyEnd = _separatorIndices[i + 1]; 3260 var keyEnd = _separatorIndices[i + 1];
3098 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) { 3261 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
3099 return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2], 3262 return _Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
3100 UTF8, false); 3263 UTF8, false);
3101 } 3264 }
3102 } 3265 }
3103 return "US-ASCII"; 3266 return "US-ASCII";
3104 } 3267 }
3105 3268
3106 /** 3269 /**
3107 * Whether the data is Base64 encoded or not. 3270 * Whether the data is Base64 encoded or not.
3108 */ 3271 */
3109 bool get isBase64 => _separatorIndices.length.isOdd; 3272 bool get isBase64 => _separatorIndices.length.isOdd;
3110 3273
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
3148 result.setRange(0, length, text.codeUnits, start); 3311 result.setRange(0, length, text.codeUnits, start);
3149 return result; 3312 return result;
3150 } 3313 }
3151 int index = 0; 3314 int index = 0;
3152 for (int i = start; i < text.length; i++) { 3315 for (int i = start; i < text.length; i++) {
3153 var codeUnit = text.codeUnitAt(i); 3316 var codeUnit = text.codeUnitAt(i);
3154 if (codeUnit != percent) { 3317 if (codeUnit != percent) {
3155 result[index++] = codeUnit; 3318 result[index++] = codeUnit;
3156 } else { 3319 } else {
3157 if (i + 2 < text.length) { 3320 if (i + 2 < text.length) {
3158 var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1)); 3321 var digit1 = _Uri._parseHexDigit(text.codeUnitAt(i + 1));
3159 var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2)); 3322 var digit2 = _Uri._parseHexDigit(text.codeUnitAt(i + 2));
3160 if (digit1 >= 0 && digit2 >= 0) { 3323 if (digit1 >= 0 && digit2 >= 0) {
3161 int byte = digit1 * 16 + digit2; 3324 int byte = digit1 * 16 + digit2;
3162 result[index++] = byte; 3325 result[index++] = byte;
3163 i += 2; 3326 i += 2;
3164 continue; 3327 continue;
3165 } 3328 }
3166 } 3329 }
3167 throw new FormatException("Invalid percent escape", text, i); 3330 throw new FormatException("Invalid percent escape", text, i);
3168 } 3331 }
3169 } 3332 }
3170 assert(index == result.length); 3333 assert(index == result.length);
3171 return result; 3334 return result;
3172 } 3335 }
3173 3336
3174 /** 3337 /**
3175 * Returns a string created from the content of the data URI. 3338 * Returns a string created from the content of the data URI.
3176 * 3339 *
3177 * If the content is Base64 encoded, it will be decoded to bytes and then 3340 * If the content is Base64 encoded, it will be decoded to bytes and then
3178 * decoded to a string using [encoding]. 3341 * decoded to a string using [encoding].
3179 * If encoding is omitted, the value of a `charset` parameter is used 3342 * If encoding is omitted, the value of a `charset` parameter is used
3180 * if it is recongized by [Encoding.getByName], otherwise it defaults to 3343 * if it is recognized by [Encoding.getByName], otherwise it defaults to
3181 * the [ASCII] encoding, which is the default encoding for data URIs 3344 * the [ASCII] encoding, which is the default encoding for data URIs
3182 * that do not specify an encoding. 3345 * that do not specify an encoding.
3183 * 3346 *
3184 * If the content is not Base64 encoded, it will first have percent-escapes 3347 * If the content is not Base64 encoded, it will first have percent-escapes
3185 * converted to bytes and then the character codes and byte values are 3348 * converted to bytes and then the character codes and byte values are
3186 * decoded using [encoding]. 3349 * decoded using [encoding].
3187 */ 3350 */
3188 String contentAsString({Encoding encoding}) { 3351 String contentAsString({Encoding encoding}) {
3189 if (encoding == null) { 3352 if (encoding == null) {
3190 var charset = this.charset; // Returns "US-ASCII" if not present. 3353 var charset = this.charset; // Returns "US-ASCII" if not present.
3191 encoding = Encoding.getByName(charset); 3354 encoding = Encoding.getByName(charset);
3192 if (encoding == null) { 3355 if (encoding == null) {
3193 throw new UnsupportedError("Unknown charset: $charset"); 3356 throw new UnsupportedError("Unknown charset: $charset");
3194 } 3357 }
3195 } 3358 }
3196 String text = _text; 3359 String text = _text;
3197 int start = _separatorIndices.last + 1; 3360 int start = _separatorIndices.last + 1;
3198 if (isBase64) { 3361 if (isBase64) {
3199 var converter = BASE64.decoder.fuse(encoding.decoder); 3362 var converter = BASE64.decoder.fuse(encoding.decoder);
3200 return converter.convert(text.substring(start)); 3363 return converter.convert(text.substring(start));
3201 } 3364 }
3202 return Uri._uriDecode(text, start, text.length, encoding, false); 3365 return _Uri._uriDecode(text, start, text.length, encoding, false);
3203 } 3366 }
3204 3367
3205 /** 3368 /**
3206 * A map representing the parameters of the media type. 3369 * A map representing the parameters of the media type.
3207 * 3370 *
3208 * A data URI may contain parameters between the MIME type and the 3371 * A data URI may contain parameters between the MIME type and the
3209 * data. This converts these parameters to a map from parameter name 3372 * data. This converts these parameters to a map from parameter name
3210 * to parameter value. 3373 * to parameter value.
3211 * The map only contains parameters that actually occur in the URI. 3374 * The map only contains parameters that actually occur in the URI.
3212 * The `charset` parameter has a default value even if it doesn't occur 3375 * The `charset` parameter has a default value even if it doesn't occur
3213 * in the URI, which is reflected by the [charset] getter. This means that 3376 * in the URI, which is reflected by the [charset] getter. This means that
3214 * [charset] may return a value even if `parameters["charset"]` is `null`. 3377 * [charset] may return a value even if `parameters["charset"]` is `null`.
3215 * 3378 *
3216 * If the values contain non-ASCII values or percent escapes, they default 3379 * If the values contain non-ASCII values or percent escapes, they default
3217 * to being decoded as UTF-8. 3380 * to being decoded as UTF-8.
3218 */ 3381 */
3219 Map<String, String> get parameters { 3382 Map<String, String> get parameters {
3220 var result = <String, String>{}; 3383 var result = <String, String>{};
3221 for (int i = 3; i < _separatorIndices.length; i += 2) { 3384 for (int i = 3; i < _separatorIndices.length; i += 2) {
3222 var start = _separatorIndices[i - 2] + 1; 3385 var start = _separatorIndices[i - 2] + 1;
3223 var equals = _separatorIndices[i - 1]; 3386 var equals = _separatorIndices[i - 1];
3224 var end = _separatorIndices[i]; 3387 var end = _separatorIndices[i];
3225 String key = Uri._uriDecode(_text, start, equals, UTF8, false); 3388 String key = _Uri._uriDecode(_text, start, equals, UTF8, false);
3226 String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false); 3389 String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false);
3227 result[key] = value; 3390 result[key] = value;
3228 } 3391 }
3229 return result; 3392 return result;
3230 } 3393 }
3231 3394
3232 static UriData _parse(String text, int start, Uri sourceUri) { 3395 static UriData _parse(String text, int start, Uri sourceUri) {
3233 assert(start == 0 || start == 5); 3396 assert(start == 0 || start == 5);
3234 assert((start == 5) == text.startsWith("data:")); 3397 assert((start == 5) == text.startsWith("data:"));
floitsch 2016/06/29 23:41:47 (start == 5) == text.toLowerCase().startsWith("dat
Lasse Reichstein Nielsen 2016/06/30 10:27:31 We *need* to have a lower-case scheme. We promise
3235 3398
3236 /// Character codes. 3399 /// Character codes.
3237 const int comma = 0x2c; 3400 const int comma = 0x2c;
3238 const int slash = 0x2f; 3401 const int slash = 0x2f;
3239 const int semicolon = 0x3b; 3402 const int semicolon = 0x3b;
3240 const int equals = 0x3d; 3403 const int equals = 0x3d;
3241 List<int> indices = [start - 1]; 3404 List<int> indices = [start - 1];
3242 int slashIndex = -1; 3405 int slashIndex = -1;
3243 var char; 3406 var char;
3244 int i = start; 3407 int i = start;
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
3299 // Encode the string into bytes then generate an ASCII only string 3462 // Encode the string into bytes then generate an ASCII only string
3300 // by percent encoding selected bytes. 3463 // by percent encoding selected bytes.
3301 int byteOr = 0; 3464 int byteOr = 0;
3302 for (int i = 0; i < bytes.length; i++) { 3465 for (int i = 0; i < bytes.length; i++) {
3303 int byte = bytes[i]; 3466 int byte = bytes[i];
3304 byteOr |= byte; 3467 byteOr |= byte;
3305 if (byte < 128 && 3468 if (byte < 128 &&
3306 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) { 3469 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
3307 buffer.writeCharCode(byte); 3470 buffer.writeCharCode(byte);
3308 } else { 3471 } else {
3309 buffer.writeCharCode(Uri._PERCENT); 3472 buffer.writeCharCode(_PERCENT);
3310 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4)); 3473 buffer.writeCharCode(_hexDigits.codeUnitAt(byte >> 4));
3311 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f)); 3474 buffer.writeCharCode(_hexDigits.codeUnitAt(byte & 0x0f));
3312 } 3475 }
3313 } 3476 }
3314 if ((byteOr & ~0xFF) != 0) { 3477 if ((byteOr & ~0xFF) != 0) {
3315 for (int i = 0; i < bytes.length; i++) { 3478 for (int i = 0; i < bytes.length; i++) {
3316 var byte = bytes[i]; 3479 var byte = bytes[i];
3317 if (byte < 0 || byte > 255) { 3480 if (byte < 0 || byte > 255) {
3318 throw new ArgumentError.value(byte, "non-byte value"); 3481 throw new ArgumentError.value(byte, "non-byte value");
3319 } 3482 }
3320 } 3483 }
3321 } 3484 }
(...skipping 28 matching lines...) Expand all
3350 0x7fff]; // 0x70 - 0x7f 11111111 11111110 3513 0x7fff]; // 0x70 - 0x7f 11111111 11111110
3351 3514
3352 // All non-escape RFC-2396 uric characters. 3515 // All non-escape RFC-2396 uric characters.
3353 // 3516 //
3354 // uric = reserved | unreserved | escaped 3517 // uric = reserved | unreserved | escaped
3355 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," 3518 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3356 // unreserved = alphanum | mark 3519 // unreserved = alphanum | mark
3357 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 3520 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3358 // 3521 //
3359 // This is the same characters as in a URI query (which is URI pchar plus '?') 3522 // This is the same characters as in a URI query (which is URI pchar plus '?')
3360 static const _uricTable = Uri._queryCharTable; 3523 static const _uricTable = _Uri._queryCharTable;
3361 } 3524 }
3525
3526 // --------------------------------------------------------------------
3527 // Constants used to read the scanner result.
3528 // The indices points into the table returned by [_scanUri] which contains
3529 // recognized positions in the scanned URI.
3530 // The `0` index is only used internally.
3531
3532 /// Index of the position of that `:` after a scheme.
3533 const int _schemeEndIndex = 1;
3534 /// Index of the position of the character just before the host name.
3535 const int _hostStartIndex = 2;
3536 /// Index of the position of the `:` before a port value.
3537 const int _portStartIndex = 3;
3538 /// Index of the position of the first character of a path.
3539 const int _pathStartIndex = 4;
3540 /// Index of the position of the `?` before a query.
3541 const int _queryStartIndex = 5;
3542 /// Index of the position of the `#` before a fragment.
3543 const int _fragmentStartIndex = 6;
3544 /// Index of a position where the URI was determined to be "non-simple".
3545 const int _notSimpleIndex = 7;
3546
3547 // Initial state for scanner.
3548 const int _uriStart = 00;
3549
3550 // If scanning of a URI terminates in this state or above,
3551 // consider the URI non-simple
3552 const int _nonSimpleEndStates = 14;
3553
3554 // Initial state for scheme validation.
3555 const int _schemeStart = 20;
3556
3557 /// Transition tables used to scan a URI to determine its structure.
3558 ///
3559 /// The tables represent a state machine with output.
3560 ///
3561 /// To scan the URI, start in the [_uriStart] state, then read each character
3562 /// of the URI in order, from start to end, and for each character perform a
3563 /// transition to a new state while writing the current position into the output
3564 /// buffer at a designated index.
3565 ///
3566 /// Each state, represented by an integer which is an index into
3567 /// [_scannerTables], has a set of transitions, one for each character.
3568 /// The transitions are encoded as a 5-bit integer representing the next state
3569 /// and a 3-bit index into the output table.
3570 ///
3571 /// For URI scanning, only characters in the range U+0020 through U+007E are
3572 /// interesting, all characters outside that range are treated the same.
3573 /// The tables only contain 96 entries, representing that characters in the
3574 /// interesting range, plus one more to represent all values outside the range.
3575 /// The character entries are stored in one `Uint8List` per state, with the
3576 /// transition for a character at position `character ^ 0x60`,
3577 /// which maps the range U+0020 .. U+007F into positions 0 .. 95.
3578 /// All remaining characters are mapped to position 31 (`0x7f ^ 0x60`) which
3579 /// represents the transition for all remaining characters.
3580 final List<Uint8List> _scannerTables = _createTables();
3581
3582 // ----------------------------------------------------------------------
3583 // Code to create the URI scanner table.
3584
3585 /// Creates the tables for [_scannerTables] used by [_scanUri].
3586 ///
3587 /// See [_scannerTables] for the generated format.
3588 ///
3589 /// The concrete tables are chosen as a trade-off between the number of states
3590 /// needed and the precision of the result.
3591 /// This allows definitely recognizing the general structure of the URI
3592 /// (presence and location of scheme, user-info, host, port, path, query and
3593 /// fragment) while at the same time detecting that some components are not
3594 /// in canonical form (anything containing a `%`, a host-name containing a
3595 /// capital letter). Since the scanner doesn't know whether something is a
3596 /// scheme or a path until it sees `:`, or user-info or host until it sees
3597 /// a `@`, a second pass is needed to validate the scheme and any user-info
3598 /// is considered non-canonical by default.
3599 List<Uint8List> _createTables() {
3600 // TODO(lrn): Use a precomputed table.
3601
3602 // Total number of states for the scanner.
3603 const int stateCount = 22;
3604
3605 // States used to scan a URI from scratch.
3606 const int schemeOrPath = 01;
3607 const int authOrPath = 02;
3608 const int authOrPathSlash = 03;
3609 const int uinfoOrHost0 = 04;
3610 const int uinfoOrHost = 05;
3611 const int uinfoOrPort0 = 06;
3612 const int uinfoOrPort = 07;
3613 const int ipv6Host = 08;
3614 const int relPathSeg = 09;
3615 const int pathSeg = 10;
3616 const int path = 11;
3617 const int query = 12;
3618 const int fragment = 13;
3619 const int schemeOrPathDot = 14;
3620 const int schemeOrPathDot2 = 15;
3621 const int relPathSegDot = 16;
3622 const int relPathSegDot2 = 17;
3623 const int pathSegDot = 18;
3624 const int pathSegDot2 = 19;
3625
3626 // States used to validate a scheme after its end position has been found.
3627 const int scheme0 = _schemeStart;
3628 const int scheme = 21;
3629
3630 // Constants encoding the write-index for the state transition into the top 5
3631 // bits of a byte.
3632 const int schemeEnd = _schemeEndIndex << 5;
3633 const int hostStart = _hostStartIndex << 5;
3634 const int portStart = _portStartIndex << 5;
3635 const int pathStart = _pathStartIndex << 5;
3636 const int queryStart = _queryStartIndex << 5;
3637 const int fragmentStart = _fragmentStartIndex << 5;
3638 const int notSimple = _notSimpleIndex << 5;
3639
3640 /// The `unreserved` characters of RFC 3986.
3641 const unreserved =
3642 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~" ;
3643 /// The `sub-delim` characters of RFC 3986.
3644 const subDelims = r"!$&'()*+,;=";
3645 // The `pchar` characters of RFC 3986: characters that may occur in a path,
3646 // excluding escapes.
3647 const pchar = "$unreserved$subDelims";
3648
3649 var tables = new List<Uint8List>.generate(stateCount,
3650 (_) => new Uint8List(96));
3651
3652 // Helper function which initialize the table for [state] with a default
3653 // transition and returns the table.
3654 Uint8List build(state, defaultTransition) =>
3655 tables[state]..fillRange(0, 96, defaultTransition);
3656
3657 // Helper function which sets the transition for each character in [chars]
3658 // to [transition] in the [target] table.
3659 // The [chars] string must contain only characters in the U+0020 .. U+007E
3660 // range.
3661 void setChars(Uint8List target, String chars, int transition) {
3662 for (int i = 0; i < chars.length; i++) {
3663 var char = chars.codeUnitAt(i);
3664 target[char ^ 0x60] = transition;
3665 }
3666 }
3667
3668 /// Helper function which sets the transition for all characters in the
3669 /// range from `range[0]` to `range[1]` to [transition] in the [target] table.
3670 ///
3671 /// The [range] must be a two-character string where both characters are in
3672 /// the U+0020 .. U+007E range and the former character must have a lower
3673 /// code point than the latter.
3674 void setRange(Uint8List target, String range, int transition) {
3675 for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) {
3676 target[i ^ 0x60] = transition;
3677 }
3678 }
3679
3680 // Create the transitions for each state.
3681 var b;
3682
3683 // Validate as path, if it is a scheme, we handle it later.
3684 b = build(_uriStart, schemeOrPath | notSimple);
3685 setChars(b, pchar, schemeOrPath);
3686 setChars(b, ".", schemeOrPathDot);
3687 setChars(b, ":", authOrPath | schemeEnd); // Handle later.
3688 setChars(b, "/", authOrPathSlash);
3689 setChars(b, "?", query | queryStart);
3690 setChars(b, "#", fragment | fragmentStart);
3691
3692 b = build(schemeOrPathDot, schemeOrPath | notSimple);
3693 setChars(b, pchar, schemeOrPath);
3694 setChars(b, ".", schemeOrPathDot2);
3695 setChars(b, ':', authOrPath | schemeEnd);
3696 setChars(b, "/", pathSeg | notSimple);
3697 setChars(b, "?", query | queryStart);
3698 setChars(b, "#", fragment | fragmentStart);
3699
3700 b = build(schemeOrPathDot2, schemeOrPath | notSimple);
3701 setChars(b, pchar, schemeOrPath);
3702 setChars(b, "%", schemeOrPath | notSimple);
3703 setChars(b, ':', authOrPath | schemeEnd);
3704 setChars(b, "/", relPathSeg);
3705 setChars(b, "?", query | queryStart);
3706 setChars(b, "#", fragment | fragmentStart);
3707
3708 b = build(schemeOrPath, schemeOrPath | notSimple);
3709 setChars(b, pchar, schemeOrPath);
3710 setChars(b, ':', authOrPath | schemeEnd);
3711 setChars(b, "/", pathSeg);
3712 setChars(b, "?", query | queryStart);
3713 setChars(b, "#", fragment | fragmentStart);
3714
3715 b = build(authOrPath, path | notSimple);
3716 setChars(b, pchar, path);
3717 setChars(b, "/", authOrPathSlash);
3718 setChars(b, ".", pathSegDot);
3719 setChars(b, "?", query | queryStart);
3720 setChars(b, "#", fragment | fragmentStart);
3721
3722 b = build(authOrPathSlash, path | notSimple);
3723 setChars(b, pchar, path);
3724 setChars(b, "/", uinfoOrHost0 | hostStart);
3725 setChars(b, ".", pathSegDot);
3726 setChars(b, "?", query | queryStart);
3727 setChars(b, "#", fragment | fragmentStart);
3728
3729 b = build(uinfoOrHost0, uinfoOrHost | notSimple);
3730 setChars(b, pchar, uinfoOrHost);
3731 setRange(b, "AZ", uinfoOrHost | notSimple);
3732 setChars(b, ":", uinfoOrPort0 | portStart);
3733 setChars(b, "@", uinfoOrHost0 | hostStart);
3734 setChars(b, "[", ipv6Host | notSimple);
3735 setChars(b, "/", pathSeg | pathStart);
3736 setChars(b, "?", query | queryStart);
3737 setChars(b, "#", fragment | fragmentStart);
3738
3739 b = build(uinfoOrHost, uinfoOrHost | notSimple);
3740 setChars(b, pchar, uinfoOrHost);
3741 setRange(b, "AZ", uinfoOrHost | notSimple);
3742 setChars(b, ":", uinfoOrPort0 | portStart);
3743 setChars(b, "@", uinfoOrHost0 | hostStart);
3744 setChars(b, "/", pathSeg | pathStart);
3745 setChars(b, "?", query | queryStart);
3746 setChars(b, "#", fragment | fragmentStart);
3747
3748 b = build(uinfoOrPort0, uinfoOrPort | notSimple);
3749 setRange(b, "19", uinfoOrPort);
3750 setChars(b, "@", uinfoOrHost0 | hostStart);
3751 setChars(b, "/", pathSeg | pathStart);
3752 setChars(b, "?", query | queryStart);
3753 setChars(b, "#", fragment | fragmentStart);
3754
3755 b = build(uinfoOrPort, uinfoOrPort | notSimple);
3756 setRange(b, "09", uinfoOrPort);
3757 setChars(b, "@", uinfoOrHost0 | hostStart);
3758 setChars(b, "/", pathSeg | pathStart);
3759 setChars(b, "?", query | queryStart);
3760 setChars(b, "#", fragment | fragmentStart);
3761
3762 b = build(ipv6Host, ipv6Host);
3763 setChars(b, "]", uinfoOrHost);
3764
3765 b = build(relPathSeg, path | notSimple);
3766 setChars(b, pchar, path);
3767 setChars(b, ".", relPathSegDot);
3768 setChars(b, "/", pathSeg | notSimple);
3769 setChars(b, "?", query | queryStart);
3770 setChars(b, "#", fragment | fragmentStart);
3771
3772 b = build(relPathSegDot, path | notSimple);
3773 setChars(b, pchar, path);
3774 setChars(b, ".", relPathSegDot2);
3775 setChars(b, "/", pathSeg | notSimple);
3776 setChars(b, "?", query | queryStart);
3777 setChars(b, "#", fragment | fragmentStart);
3778
3779 b = build(relPathSegDot2, path | notSimple);
3780 setChars(b, pchar, path);
3781 setChars(b, "/", relPathSeg);
3782 setChars(b, "?", query | queryStart); // This should be non-simple.
3783 setChars(b, "#", fragment | fragmentStart); // This should be non-simple.
3784
3785 b = build(pathSeg, path | notSimple);
3786 setChars(b, pchar, path);
3787 setChars(b, ".", pathSegDot);
3788 setChars(b, "/", pathSeg | notSimple);
3789 setChars(b, "?", query | queryStart);
3790 setChars(b, "#", fragment | fragmentStart);
3791
3792 b = build(pathSegDot, path | notSimple);
3793 setChars(b, pchar, path);
3794 setChars(b, ".", pathSegDot2);
3795 setChars(b, "/", pathSeg | notSimple);
3796 setChars(b, "?", query | queryStart);
3797 setChars(b, "#", fragment | fragmentStart);
3798
3799 b = build(pathSegDot2, path | notSimple);
3800 setChars(b, pchar, path);
3801 setChars(b, "/", pathSeg | notSimple);
3802 setChars(b, "?", query | queryStart);
3803 setChars(b, "#", fragment | fragmentStart);
3804
3805 b = build(path, path | notSimple);
3806 setChars(b, pchar, path);
3807 setChars(b, "/", pathSeg);
3808 setChars(b, "?", query | queryStart);
3809 setChars(b, "#", fragment | fragmentStart);
3810
3811 b = build(query, query | notSimple);
3812 setChars(b, pchar, query);
3813 setChars(b, "?", query);
3814 setChars(b, "#", fragment | fragmentStart);
3815
3816 b = build(fragment, fragment | notSimple);
3817 setChars(b, pchar, fragment);
3818 setChars(b, "?", fragment);
3819
3820 // A separate two-state validator for lower-case scheme names.
3821 // Any non-scheme character or upper-case letter is marked as non-simple.
3822 b = build(scheme0, scheme | notSimple);
3823 setRange(b, "az", scheme);
3824
3825 b = build(scheme, scheme | notSimple);
3826 setRange(b, "az", scheme);
3827 setRange(b, "09", scheme);
3828 setChars(b, "+-.", scheme);
3829
3830 return tables;
3831 }
3832
3833 // --------------------------------------------------------------------
3834 // Code that uses the URI scanner table.
3835
3836 /// Scan a string using the [_scannerTables] state machine.
3837 ///
3838 /// Scans [uri] from [start] to [end], startig in state [state] and
3839 /// writing output into [indices].
3840 ///
3841 /// Returns the final state.
3842 int _scan(String uri, int start, int end, int state, List<int> indices) {
3843 var tables = _scannerTables;
3844 assert(end <= uri.length);
3845 for (int i = start; i < end; i++) {
3846 var table = tables[state];
3847 // Xor with 0x60 to move range 0x20-0x7f into 0x00-0x5f
3848 int char = uri.codeUnitAt(i) ^ 0x60;
3849 // Use 0x1f (nee 0x7f) to represent all unhandled characters.
3850 if (char > 0x5f) char = 0x1f;
3851 int transition = table[char];
3852 state = transition & 0x1f;
3853 indices[transition >> 5] = i;
3854 }
3855 return state;
3856 }
3857
3858 /// Scan a URI to determine its structure.
3859 ///
3860 /// Scans the [uri] from [start] to [end] and returns a list of indices
3861 /// representing significant positions in the URI. The positions are
3862 /// sufficient to split the URI into its structural parts.
3863 ///
3864 /// The returned list of positions have the following values at the given
3865 /// indices:
3866 ///
3867 /// - [_schemeEndIndex]: Is `start-1` if the URI has no scheme,
3868 /// otherwise it is the position of the `:` after the scheme.
3869 /// - [_hostStartIndex]: Either [start] if the URI has no authority component,
3870 /// otherwise either the position of the `@` after a user-info part or the
3871 /// position of the final `/` in the `//` leading the authority.
3872 /// - [_portStartIndex]: Either [start] if the URI has no authority component, o r
floitsch 2016/06/29 23:41:47 long line.
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Reworded and moved after inlining _scanUri.
3873 /// the position of the `:` leading a port, or the same position as the
3874 /// start of the path if the URI has no port.
3875 /// - [_pathStartIndex]: The position of the initial character of the path.
3876 /// If the path is empty, then the position may be [start] or the position
3877 /// of the initial `/` in the `//` leading an authority.
3878 /// If there is a scheme or authority, and the path start is before the
3879 /// query end or host start, the actual path is empty.
3880 /// - [_queryStartIndex]: The position of the `?` leading a query if the URI
3881 /// contains a query, otherwise [end].
3882 /// - [_fragmentStartIndex]: The position of the `#` leading a fragment if the
3883 /// URI contains a fragment, otherwise [end].
3884 /// - [_notSimpleIndex]: Equal to `start - 1` unless the URI is considered
3885 /// "not simple". This is elaborated below.
3886 ///
3887 /// The returned positions are limited by the scanners ability to write only
3888 /// one position per character, and only the current position.
3889 /// Scanning from left to right, we only know whether something is a scheme
3890 /// or a path when we see a `:` or `/`, and likewise we only know if the first
3891 /// `/` is part of the path or is leading an authority component when we see
3892 /// the next character.
3893 ///
3894 /// # Simple URIs
3895 /// A URI is considered "simple" if it is in a normalized form containing no
3896 /// escapes. This allows us to skip normalization and checking whether escapes
3897 /// are valid, and to extract components without worrying about unescaping.
3898 ///
3899 /// The scanner computes a conservative approximation of being "simple".
3900 /// It rejects any URI with an escape, with a user-info component (mainly
3901 /// because they are rare and would increase the number of states in the
3902 /// scanner significantly), with an IPV6 host or with a capital letter in
3903 /// the scheme or host name.
3904 /// Further, paths containing `..` or `.` path segments are considered
3905 /// non-simple except for relative paths (no scheme or authority) starting
3906 /// with a sequence of "../" segments.
3907 ///
3908 /// There are some scheme-based normalizations for `file`, `http` and `https`
3909 /// URIs that have to be handled after scanning, to either consider the URI
3910 /// non-simple or normalize the string.
3911 List<int> _scanUri(String uri, int start, int end) {
3912 var indices = new List<int>.filled(8, start - 1);
3913 // For some positions, pick a better default which will be the correct value
3914 // in some cases where the value isn't updated by the scanner.
3915 indices[_portStartIndex] = start;
3916 indices[_pathStartIndex] = start;
3917 indices[_queryStartIndex] = end;
3918 indices[_fragmentStartIndex] = end;
3919 var state = _scan(uri, start, end, _uriStart, indices);
3920 // Some states that should be non-simple, but the URI ended early.
3921 // In particular paths that end at a ".." must be normalized to end in "../".
3922 if (state >= _nonSimpleEndStates) {
3923 indices[_notSimpleIndex] = end;
3924 }
3925 int schemeEnd = indices[_schemeEndIndex];
3926 if (schemeEnd >= start) {
3927 // Rescan the scheme part now that we know it's not a path.
3928 state = _scan(uri, start, schemeEnd, _schemeStart, indices);
3929 if (state == _schemeStart) {
3930 // Empty scheme.
3931 indices[_notSimpleIndex] = schemeEnd;
3932 }
3933 }
3934 return indices;
3935 }
3936
3937 class _SimpleUri implements Uri {
3938 final String _uri;
3939 final int _schemeEnd;
3940 final int _hostStart;
3941 final int _portStart;
3942 final int _pathStart;
3943 final int _queryStart;
3944 final int _fragmentStart;
3945 /// The scheme is often used to distinguish URIs.
3946 /// To make comparisons more efficient, we cache the value, and
3947 /// canonicalize a few known types.
3948 String _schemeCache;
3949
3950 _SimpleUri(
3951 this._uri,
3952 this._schemeEnd,
3953 this._hostStart,
3954 this._portStart,
3955 this._pathStart,
3956 this._queryStart,
3957 this._fragmentStart,
3958 this._schemeCache);
3959
3960 bool get hasScheme => _schemeEnd > 0;
3961 bool get hasAuthority => _hostStart > 0;
3962 bool get hasUserInfo => _hostStart > _schemeEnd + 4;
3963 bool get hasPort => _hostStart > 0 && _portStart + 1 < _pathStart;
3964 bool get hasQuery => _queryStart < _fragmentStart;
3965 bool get hasFragment => _fragmentStart < _uri.length;
3966
3967 bool get _isFile => _schemeEnd == 4 && _uri.startsWith("file");
3968 bool get _isHttp => _schemeEnd == 4 && _uri.startsWith("http");
3969 bool get _isHttps => _schemeEnd == 5 && _uri.startsWith("https");
3970 bool get _isPackage => _schemeEnd == 7 && _uri.startsWith("package");
3971 bool _isScheme(String scheme) =>
3972 _schemeEnd == scheme.length && _uri.startsWith(scheme);
3973
3974 bool get hasAbsolutePath => _uri.startsWith("/", _pathStart);
3975 bool get hasEmptyPath => _pathStart == _queryStart;
3976
3977 bool get isAbsolute => hasScheme && !hasFragment;
3978
3979 String get scheme {
3980 if (_schemeEnd <= 0) return "";
3981 if (_schemeCache != null) return _schemeCache;
3982 if (_isHttp) {
3983 _schemeCache = "http";
3984 } else if (_isHttps) {
3985 _schemeCache = "https";
3986 } else if (_isFile) {
3987 _schemeCache = "file";
3988 } else if (_isPackage) {
3989 _schemeCache = "package";
3990 } else {
3991 _schemeCache = _uri.substring(0, _schemeEnd);
3992 }
3993 return _schemeCache;
3994 }
3995 String get authority => _hostStart > 0 ?
3996 _uri.substring(_schemeEnd + 3, _pathStart) : "";
3997 String get userInfo => (_hostStart > _schemeEnd + 3) ?
3998 _uri.substring(_schemeEnd + 3, _hostStart - 1) : "";
3999 String get host =>
4000 _hostStart > 0 ? _uri.substring(_hostStart, _portStart) : "";
4001 int get port {
4002 if (hasPort) return int.parse(_uri.substring(_portStart + 1, _pathStart));
4003 if (_isHttp) return 80;
4004 if (_isHttps) return 443;
4005 return 0;
4006 }
4007 String get path =>_uri.substring(_pathStart, _queryStart);
4008 String get query => (_queryStart < _fragmentStart) ?
4009 _uri.substring(_queryStart + 1, _fragmentStart) : "";
4010 String get fragment => (_fragmentStart < _uri.length) ?
4011 _uri.substring(_fragmentStart + 1) : "";
4012
4013 String get origin {
4014 // Check original behavior - W3C spec is wonky!
4015 bool isHttp = _isHttp;
4016 if (_schemeEnd < 0 || _hostStart == _portStart) {
4017 throw new StateError("Cannot use origin without a scheme: $this");
4018 }
4019 if (!isHttp && !_isHttps) {
4020 throw new StateError(
4021 "Origin is only applicable schemes http and https: $this");
4022 }
4023 if (_hostStart == _schemeEnd + 3) {
4024 return _uri.substring(0, _pathStart);
4025 }
4026 // Need to drop anon-empty userInfo.
4027 return _uri.substring(0, _schemeEnd + 3) +
4028 _uri.substring(_hostStart, _pathStart);
4029 }
4030
4031 List<String> get pathSegments {
4032 int start = _pathStart;
4033 int end = _queryStart;
4034 if (_uri.startsWith("/", start)) start++;
4035 if (start == end) return const <String>[];
4036 List<String> parts = [];
4037 for (int i = start; i < end; i++) {
4038 var char = _uri.codeUnitAt(i);
4039 if (char == _SLASH) {
4040 parts.add(_uri.substring(start, i));
4041 start = i + 1;
4042 }
4043 }
4044 parts.add(_uri.substring(start, end));
4045 return new List<String>.unmodifiable(parts);
4046 }
4047
4048 Map<String, String> get queryParameters {
4049 if (!hasQuery) return const <String, String>{};
4050 return new UnmodifiableMapView<String, String>(
4051 Uri.splitQueryString(query));
4052 }
4053
4054 Map<String, List<String>> get queryParametersAll {
4055 if (!hasQuery) return const <String, List<String>>{};
4056 Map queryParameterLists = _Uri._splitQueryStringAll(query);
4057 for (var key in queryParameterLists.keys) {
4058 queryParameterLists[key] =
4059 new List<String>.unmodifiable(queryParameterLists[key]);
4060 }
4061 return new Map<String, List<String>>.unmodifiable(queryParameterLists);
4062 }
4063
4064 bool _isPort(String port) {
4065 int portDigitStart = _portStart + 1;
4066 return portDigitStart + port.length == _pathStart &&
4067 _uri.startsWith(port, portDigitStart);
4068 }
4069
4070 Uri normalizePath() => this;
4071
4072 Uri removeFragment() {
4073 if (!hasFragment) return this;
4074 return new _SimpleUri(
4075 _uri.substring(0, _fragmentStart),
4076 _schemeEnd, _hostStart, _portStart,
4077 _pathStart, _queryStart, _fragmentStart, _schemeCache);
4078 }
4079
4080 Uri replace({String scheme,
4081 String userInfo,
4082 String host,
4083 int port,
4084 String path,
4085 Iterable<String> pathSegments,
4086 String query,
4087 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
4088 String fragment}) {
4089 bool schemeChanged = false;
4090 if (scheme != null) {
4091 scheme = _Uri._makeScheme(scheme, 0, scheme.length);
4092 schemeChanged = !_isScheme(scheme);
4093 } else {
4094 scheme = this.scheme;
4095 }
4096 bool isFile = (scheme == "file");
4097 if (userInfo != null) {
4098 userInfo = _Uri._makeUserInfo(userInfo, 0, userInfo.length);
4099 } else if (_hostStart > 0) {
4100 userInfo = _uri.substring(_schemeEnd + 3, _hostStart);
4101 } else {
4102 userInfo = "";
4103 }
4104 if (port != null) {
4105 port = _Uri._makePort(port, scheme);
4106 } else {
4107 port = this.hasPort ? this.port : null;
4108 if (schemeChanged) {
4109 // The default port might have changed.
4110 port = _Uri._makePort(port, scheme);
4111 }
4112 }
4113 if (host != null) {
4114 host = _Uri._makeHost(host, 0, host.length, false);
4115 } else if (_hostStart > 0) {
4116 host = _uri.substring(_hostStart, _portStart);
4117 } else if (userInfo.isNotEmpty || port != null || isFile) {
4118 host = "";
4119 }
4120
4121 bool hasAuthority = host != null;
4122 if (path != null || pathSegments != null) {
4123 path = _Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments,
4124 scheme, hasAuthority);
4125 } else {
4126 path = _uri.substring(_pathStart, _queryStart);
4127 if ((isFile || (hasAuthority && !path.isEmpty)) &&
4128 !path.startsWith('/')) {
4129 path = "/" + path;
4130 }
4131 }
4132
4133 if (query != null || queryParameters != null) {
4134 query =
4135 _Uri._makeQuery(query, 0, _stringOrNullLength(query), queryParameters) ;
floitsch 2016/06/29 23:41:47 long line.
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Done.
4136 } else if (_queryStart < _fragmentStart) {
4137 query = _uri.substring(_queryStart, _fragmentStart);
4138 }
4139
4140 if (fragment != null) {
4141 fragment = _Uri._makeFragment(fragment, 0, fragment.length);
4142 } else if (_fragmentStart < _uri.length) {
4143 fragment = _uri.substring(_fragmentStart);
4144 }
4145
4146 return new _Uri._internal(
4147 scheme, userInfo, host, port, path, query, fragment);
4148 }
4149
4150 Uri resolve(String reference) {
4151 return resolveUri(Uri.parse(reference));
4152 }
4153
4154 Uri resolveUri(Uri reference) {
4155 if (reference is _SimpleUri) {
4156 return _simpleMerge(this, reference);
4157 }
4158 return _toNonSimple().resolveUri(reference);
4159 }
4160
4161 // Merge two simple URIs. This should always result in a prefix of
4162 // one concatentated with a suffix of the other, which is again simple.
4163 // In a few cases, there might be a need for extra normalization, when
4164 // resolving on top of a known scheme.
4165 Uri _simpleMerge(_SimpleUri base, _SimpleUri ref) {
4166 if (ref.hasScheme) return ref;
4167 if (ref.hasAuthority) {
4168 if (!base.hasScheme) return ref;
4169 bool isSimple = true;
4170 if (base._isFile) {
4171 isSimple = !ref.hasEmptyPath;
4172 } else if (base._isHttp) {
4173 isSimple = !ref._isPort("80");
4174 } else if (base._isHttps) {
4175 isSimple = !ref._isPort("443");
4176 }
4177 if (isSimple) {
4178 var delta = base._schemeEnd + 1;
4179 var newUri = base._uri.substring(0, base._schemeEnd + 1) +
4180 ref._uri.substring(ref._schemeEnd + 1);
4181 return new _SimpleUri(newUri,
4182 base._schemeEnd,
4183 ref._hostStart + delta,
4184 ref._portStart + delta,
4185 ref._pathStart + delta,
4186 ref._queryStart + delta,
4187 ref._fragmentStart + delta,
4188 base._schemeCache);
4189 } else {
4190 // This will require normalization, so use the _Uri implementation.
4191 return _toNonSimple().resolveUri(ref);
4192 }
4193 }
4194 if (ref.hasEmptyPath) {
4195 if (ref.hasQuery) {
4196 int delta = base._queryStart - ref._queryStart;
4197 var newUri = base._uri.substring(0, base._queryStart) +
4198 ref._uri.substring(ref._queryStart);
4199 return new _SimpleUri(newUri,
4200 base._schemeEnd,
4201 base._hostStart,
4202 base._portStart,
4203 base._pathStart,
4204 ref._queryStart + delta,
4205 ref._fragmentStart + delta,
4206 base._schemeCache);
4207 }
4208 if (ref.hasFragment) {
4209 int delta = base._fragmentStart - ref._fragmentStart;
4210 var newUri = base._uri.substring(0, base._fragmentStart) +
4211 ref._uri.substring(ref._fragmentStart);
4212 return new _SimpleUri(newUri,
4213 base._schemeEnd,
4214 base._hostStart,
4215 base._portStart,
4216 base._pathStart,
4217 base._queryStart,
4218 ref._fragmentStart + delta,
4219 base._schemeCache);
4220 }
4221 return base.removeFragment();
4222 }
4223 if (ref.hasAbsolutePath) {
4224 var delta = base._pathStart - ref._pathStart;
4225 var newUri = base._uri.substring(0, base._pathStart) +
4226 ref._uri.substring(ref._pathStart);
4227 return new _SimpleUri(newUri,
4228 base._schemeEnd,
4229 base._hostStart,
4230 base._portStart,
4231 base._pathStart,
4232 ref._queryStart + delta,
4233 ref._fragmentStart + delta,
4234 base._schemeCache);
4235 }
4236 if (base.hasEmptyPath && base.hasAuthority) {
4237 // ref has relative non-empty path.
4238 var delta = base._pathStart - ref._pathStart + 1;
4239 var newUri = "${base._uri.substring(0, base._pathStart)}/"
4240 "${ref._uri.substring(ref._pathStart)}";
4241 return new _SimpleUri(newUri,
4242 base._schemeEnd,
4243 base._hostStart,
4244 base._portStart,
4245 base._pathStart,
4246 ref._queryStart + delta,
4247 ref._fragmentStart + delta,
4248 base._schemeCache);
4249 }
4250 // Merge paths.
4251 if (base._uri.startsWith("../", base._pathStart)) {
4252 // Complex rare case, go slow.
4253 return _toNonSimple().resolveUri(ref);
4254 }
4255
4256 // The RFC 3986 algorithm merges the base path without its final segment
4257 // (anything after the final "/", or everything if the base path doesn't
4258 // contain any "/"), and the reference path.
4259 // Then it removes "." and ".." segments using the remove-dot-segment
4260 // algorithm.
4261 // This code combines the two steps. It is simplified by knowing that
4262 // the base path contains no "." or ".." segments, and the reference
4263 // path can only contain leading ".." segments.
4264
4265 String baseUri = base._uri;
4266 String refUri = ref._uri;
4267 int baseStart = base._pathStart;
4268 int baseEnd = base._queryStart;
4269 int refStart = ref._pathStart;
4270 int refEnd = ref._queryStart;
4271 int backCount = 1;
4272
4273 int slashCount = 0;
4274
4275 // Count leading ".." segments in reference path.
4276 while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
4277 refStart += 3;
4278 backCount += 1;
4279 }
4280
4281 // Extra slash inserted between base and reference path parts if
4282 // the base path contains any slashes.
4283 // (We could use a slash from the base path in most cases, but not if
4284 // we remove the entire base path).
4285 String insert = "";
4286 while (baseEnd > baseStart) {
4287 baseEnd--;
4288 int char = baseUri.codeUnitAt(baseEnd);
4289 if (char == _SLASH) {
4290 insert = "/";
4291 backCount--;
4292 if (backCount == 0) break;
4293 }
4294 }
4295
4296 var delta = baseEnd - refStart + insert.length;
4297 var newUri = "${base._uri.substring(0, baseEnd)}$insert"
4298 "${ref._uri.substring(refStart)}";
4299
4300 return new _SimpleUri(newUri,
4301 base._schemeEnd,
4302 base._hostStart,
4303 base._portStart,
4304 base._pathStart,
4305 ref._queryStart + delta,
4306 ref._fragmentStart + delta,
4307 base._schemeCache);
4308 }
4309
4310 String toFilePath({bool windows}) {
4311 if (_schemeEnd >= 0 && !_isFile) {
4312 throw new UnsupportedError(
4313 "Cannot extract a file path from a $scheme URI");
4314 }
4315 if (_queryStart < _uri.length) {
4316 if (_queryStart < _fragmentStart) {
4317 throw new UnsupportedError(
4318 "Cannot extract a file path from a URI with a query component");
4319 }
4320 throw new UnsupportedError(
4321 "Cannot extract a file path from a URI with a fragment component");
4322 }
4323 if (windows == null) windows = _Uri._isWindows;
4324 return windows ? _Uri._toWindowsFilePath(this) : _toFilePath();
4325 }
4326
4327 String _toFilePath() {
4328 if (_hostStart < _portStart) {
4329 // Has authority and non-empty host.
4330 throw new UnsupportedError(
4331 "Cannot extract a non-Windows file path from a file URI "
4332 "with an authority");
4333 }
4334 return this.path;
4335 }
4336
4337 UriData get data {
4338 assert(scheme != "data");
4339 return null;
4340 }
4341
4342 int get hashCode => _uri.hashCode;
4343
4344 bool operator==(Object other) {
4345 if (identical(this, other)) return true;
4346 if (other is Uri) return _uri == other.toString();
4347 return false;
4348 }
4349
4350 Uri _toNonSimple() {
4351 return new _Uri._internal(
4352 this.scheme,
4353 this.userInfo,
4354 this.hasAuthority ? this.host: null,
4355 this.hasPort ? this.port : null,
4356 this.path,
4357 this.hasQuery ? this.query : null,
4358 this.hasFragment ? this.fragment : null
4359 );
4360 }
4361
4362 String toString() => _uri;
4363 }
4364
4365 /// Checks whether [text] starts with "data:" at position [start].
4366 ///
4367 /// The text must be long enough to allow reading five characters
4368 /// from the [start] position.
4369 ///
4370 /// Returns an integer value which is zero if text starts with all-lowercase
4371 /// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case.
4372 /// All other values means the text starts with some other character.
4373 int _startsWithData(String text, int start) {
floitsch 2016/06/29 23:41:47 This should return a bool. (return delta == 0 || d
Lasse Reichstein Nielsen 2016/06/30 10:27:31 Again, I actually need to distinguish the two case
floitsch 2016/06/30 18:15:22 I think that would be better. Even just changing t
4374 // Multiply by 3 to avoid a non-colon character making delta be 0x20.
4375 int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3;
4376 delta |= text.codeUnitAt(start) ^ 0x64 /*d*/;
4377 delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/;
4378 delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/;
4379 delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/;
4380 return delta;
4381 }
4382
4383 /// Helper function returning the length of a string, or `0` for `null`.
4384 int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698