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

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

Issue 2167663002: Revert "Add fast-mode Uri class." (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: 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
25 /** 7 /**
26 * A parsed URI, such as a URL. 8 * A parsed URI, such as a URL.
27 * 9 *
28 * **See also:** 10 * **See also:**
29 * 11 *
30 * * [URIs][uris] in the [library tour][libtour] 12 * * [URIs][uris] in the [library tour][libtour]
31 * * [RFC-3986](http://tools.ietf.org/html/rfc3986) 13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
32 * 14 *
33 * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris 15 * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
34 * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.ht ml 16 * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.ht ml
35 */ 17 */
36 abstract class Uri { 18 class Uri {
37 /** 19 /**
38 * Returns the natural base URI for the current platform. 20 * The scheme component of the URI.
39 * 21 *
40 * When running in a browser this is the current URL of the current page 22 * Returns the empty string if there is no scheme component.
41 * (from `window.location.href`).
42 * 23 *
43 * When not running in a browser this is the file URI referencing 24 * A URI scheme is case insensitive.
44 * the current working directory. 25 * The returned scheme is canonicalized to lowercase letters.
45 */ 26 */
46 external static Uri get base; 27 // We represent the missing scheme as an empty string.
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);
47 89
48 /** 90 /**
49 * Creates a new URI from its components. 91 * Creates a new URI from its components.
50 * 92 *
51 * Each component is set through a named argument. Any number of 93 * Each component is set through a named argument. Any number of
52 * components can be provided. The [path] and [query] components can be set 94 * components can be provided. The [path] and [query] components can be set
53 * using either of two different named arguments. 95 * using either of two different named arguments.
54 * 96 *
55 * The scheme component is set through [scheme]. The scheme is 97 * The scheme component is set through [scheme]. The scheme is
56 * normalized to all lowercase letters. If the scheme is omitted or empty, 98 * normalized to all lowercase letters. If the scheme is omitted or empty,
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 * use an empty map for `queryParameters`. 151 * use an empty map for `queryParameters`.
110 * 152 *
111 * If both `query` and `queryParameters` are omitted or `null`, 153 * If both `query` and `queryParameters` are omitted or `null`,
112 * the URI has no query part. 154 * the URI has no query part.
113 * 155 *
114 * The fragment component is set through [fragment]. 156 * The fragment component is set through [fragment].
115 * It should be a valid URI fragment, but invalid characters other than 157 * It should be a valid URI fragment, but invalid characters other than
116 * general delimiters, are escaped if necessary. 158 * general delimiters, are escaped if necessary.
117 * If `fragment` is omitted or `null`, the URI has no fragment part. 159 * If `fragment` is omitted or `null`, the URI has no fragment part.
118 */ 160 */
119 factory Uri({String scheme, 161 factory Uri({String scheme : "",
120 String userInfo, 162 String userInfo : "",
121 String host, 163 String host,
122 int port, 164 int port,
123 String path, 165 String path,
124 Iterable<String> pathSegments, 166 Iterable<String> pathSegments,
125 String query, 167 String query,
126 Map<String, dynamic/*String|Iterable<String>*/> queryParameters, 168 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
127 String fragment}) = _Uri; 169 String fragment}) {
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 }
128 194
129 /** 195 /**
130 * Creates a new `http` URI from authority, path and query. 196 * Creates a new `http` URI from authority, path and query.
131 * 197 *
132 * Examples: 198 * Examples:
133 * 199 *
134 * ``` 200 * ```
135 * // http://example.org/path?q=dart. 201 * // http://example.org/path?q=dart.
136 * new Uri.http("google.com", "/search", { "q" : "dart" }); 202 * new Uri.http("google.com", "/search", { "q" : "dart" });
137 * 203 *
(...skipping 16 matching lines...) Expand all
154 * 220 *
155 * The `path` component is set from the [unencodedPath] 221 * The `path` component is set from the [unencodedPath]
156 * argument. The path passed must not be encoded as this constructor 222 * argument. The path passed must not be encoded as this constructor
157 * encodes the path. 223 * encodes the path.
158 * 224 *
159 * The `query` component is set from the optional [queryParameters] 225 * The `query` component is set from the optional [queryParameters]
160 * argument. 226 * argument.
161 */ 227 */
162 factory Uri.http(String authority, 228 factory Uri.http(String authority,
163 String unencodedPath, 229 String unencodedPath,
164 [Map<String, String> queryParameters]) = _Uri.http; 230 [Map<String, String> queryParameters]) {
231 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
232 }
165 233
166 /** 234 /**
167 * Creates a new `https` URI from authority, path and query. 235 * Creates a new `https` URI from authority, path and query.
168 * 236 *
169 * This constructor is the same as [Uri.http] except for the scheme 237 * This constructor is the same as [Uri.http] except for the scheme
170 * which is set to `https`. 238 * which is set to `https`.
171 */ 239 */
172 factory Uri.https(String authority, 240 factory Uri.https(String authority,
173 String unencodedPath, 241 String unencodedPath,
174 [Map<String, String> queryParameters]) = _Uri.https; 242 [Map<String, String> queryParameters]) {
175 243 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
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;
307 } 244 }
308 245
309 /** 246 /**
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 /**
347 * Returns the authority component. 247 * Returns the authority component.
348 * 248 *
349 * The authority is formatted from the [userInfo], [host] and [port] 249 * The authority is formatted from the [userInfo], [host] and [port]
350 * parts. 250 * parts.
351 * 251 *
352 * Returns the empty string if there is no authority component. 252 * Returns the empty string if there is no authority component.
353 */ 253 */
354 String get authority; 254 String get authority {
255 if (!hasAuthority) return "";
256 var sb = new StringBuffer();
257 _writeAuthority(sb);
258 return sb.toString();
259 }
355 260
356 /** 261 /**
357 * Returns the user info part of the authority component. 262 * Returns the user info part of the authority component.
358 * 263 *
359 * Returns the empty string if there is no user info in the 264 * Returns the empty string if there is no user info in the
360 * authority component. 265 * authority component.
361 */ 266 */
362 String get userInfo; 267 String get userInfo => _userInfo;
363 268
364 /** 269 /**
365 * Returns the host part of the authority component. 270 * Returns the host part of the authority component.
366 * 271 *
367 * Returns the empty string if there is no authority component and 272 * Returns the empty string if there is no authority component and
368 * hence no host. 273 * hence no host.
369 * 274 *
370 * If the host is an IP version 6 address, the surrounding `[` and `]` is 275 * If the host is an IP version 6 address, the surrounding `[` and `]` is
371 * removed. 276 * removed.
372 * 277 *
373 * The host string is case-insensitive. 278 * The host string is case-insensitive.
374 * The returned host name is canonicalized to lower-case 279 * The returned host name is canonicalized to lower-case
375 * with upper-case percent-escapes. 280 * with upper-case percent-escapes.
376 */ 281 */
377 String get host; 282 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 }
378 289
379 /** 290 /**
380 * Returns the port part of the authority component. 291 * Returns the port part of the authority component.
381 * 292 *
382 * Returns the defualt port if there is no port number in the authority 293 * Returns the defualt port if there is no port number in the authority
383 * component. That's 80 for http, 443 for https, and 0 for everything else. 294 * component. That's 80 for http, 443 for https, and 0 for everything else.
384 */ 295 */
385 int get port; 296 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 }
386 307
387 /** 308 /**
388 * Returns the path component. 309 * Returns the path component.
389 * 310 *
390 * The returned path is encoded. To get direct access to the decoded 311 * The returned path is encoded. To get direct access to the decoded
391 * path use [pathSegments]. 312 * path use [pathSegments].
392 * 313 *
393 * Returns the empty string if there is no path component. 314 * Returns the empty string if there is no path component.
394 */ 315 */
395 String get path; 316 String get path => _path;
396 317
397 /** 318 /**
398 * Returns the query component. The returned query is encoded. To get 319 * Returns the query component. The returned query is encoded. To get
399 * direct access to the decoded query use [queryParameters]. 320 * direct access to the decoded query use [queryParameters].
400 * 321 *
401 * Returns the empty string if there is no query component. 322 * Returns the empty string if there is no query component.
402 */ 323 */
403 String get query; 324 String get query => (_query == null) ? "" : _query;
404 325
405 /** 326 /**
406 * Returns the fragment identifier component. 327 * Returns the fragment identifier component.
407 * 328 *
408 * Returns the empty string if there is no fragment identifier 329 * Returns the empty string if there is no fragment identifier
409 * component. 330 * component.
410 */ 331 */
411 String get fragment; 332 String get fragment => (_fragment == null) ? "" : _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();
704 333
705 /** 334 /**
706 * Creates a new `Uri` object by parsing a URI string. 335 * Creates a new `Uri` object by parsing a URI string.
707 * 336 *
708 * If [start] and [end] are provided, only the substring from `start` 337 * If [start] and [end] are provided, only the substring from `start`
709 * to `end` is parsed as a URI. 338 * to `end` is parsed as a URI.
710 * 339 *
711 * If the string is not valid as a URI or URI reference, 340 * If the string is not valid as a URI or URI reference,
712 * a [FormatException] is thrown. 341 * a [FormatException] is thrown.
713 */ 342 */
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
758 // segment = *pchar 387 // segment = *pchar
759 // segment-nz = 1*pchar 388 // segment-nz = 1*pchar
760 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) 389 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
761 // ; non-zero-length segment without any colon ":" 390 // ; non-zero-length segment without any colon ":"
762 // 391 //
763 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 392 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
764 // 393 //
765 // query = *( pchar / "/" / "?" ) 394 // query = *( pchar / "/" / "?" )
766 // 395 //
767 // fragment = *( pchar / "/" / "?" ) 396 // fragment = *( pchar / "/" / "?" )
768 end ??= uri.length; 397 const int EOI = -1;
769 398
770 // Special case data:URIs. Ignore case when testing. 399 String scheme = "";
771 if (end >= start + 5) { 400 String userinfo = "";
772 int dataDelta = _startsWithData(uri, start); 401 String host = null;
773 if (dataDelta == 0) { 402 int port = null;
774 // The case is right. 403 String path = null;
775 if (start > 0 || end < uri.length) uri = uri.substring(start, end); 404 String query = null;
776 return UriData._parse(uri, 5, null).uri; 405 String fragment = null;
777 } else if (dataDelta == 0x20) { 406 if (end == null) end = uri.length;
778 return UriData._parse(uri.substring(start + 5, end), 0, null).uri; 407
779 } 408 int index = start;
780 // Otherwise the URI doesn't start with "data:" or any case variant of it. 409 int pathStart = start;
781 } 410 // End of input-marker.
782 411 int char = EOI;
783 // The following index-normalization belongs with the scanning, but is 412
784 // easier to do here because we already have extracted variables from the 413 void parseAuth() {
785 // indices list. 414 if (index == end) {
786 var indices = new List<int>(8);//new List<int>.filled(8, start - 1); 415 char = EOI;
787 416 return;
788 // Set default values for each position. 417 }
789 // The value will either be correct in some cases where it isn't set 418 int authStart = index;
790 // by the scanner, or it is clearly recognizable as an unset value. 419 int lastColon = -1;
791 indices 420 int lastAt = -1;
792 ..[0] = 0 421 char = uri.codeUnitAt(index);
793 ..[_schemeEndIndex] = start - 1 422 while (index < end) {
794 ..[_hostStartIndex] = start - 1 423 char = uri.codeUnitAt(index);
795 ..[_notSimpleIndex] = start - 1 424 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
796 ..[_portStartIndex] = start 425 break;
797 ..[_pathStartIndex] = start 426 }
798 ..[_queryStartIndex] = end 427 if (char == _AT_SIGN) {
799 ..[_fragmentStartIndex] = end; 428 lastAt = index;
800 var state = _scan(uri, start, end, _uriStart, indices); 429 lastColon = -1;
801 // Some states that should be non-simple, but the URI ended early. 430 } else if (char == _COLON) {
802 // Paths that end at a ".." must be normalized to end in "../". 431 lastColon = index;
803 if (state >= _nonSimpleEndStates) { 432 } else if (char == _LEFT_BRACKET) {
804 indices[_notSimpleIndex] = end; 433 lastColon = -1;
805 } 434 int endBracket = uri.indexOf(']', index + 1);
806 int schemeEnd = indices[_schemeEndIndex]; 435 if (endBracket == -1) {
807 if (schemeEnd >= start) { 436 index = end;
808 // Rescan the scheme part now that we know it's not a path. 437 char = EOI;
809 state = _scan(uri, start, schemeEnd, _schemeStart, indices); 438 break;
810 if (state == _schemeStart) { 439 } else {
811 // Empty scheme. 440 index = endBracket;
812 indices[_notSimpleIndex] = schemeEnd; 441 }
813 } 442 }
814 } 443 index++;
815 // The returned positions are limited by the scanners ability to write only 444 char = EOI;
816 // one position per character, and only the current position. 445 }
817 // Scanning from left to right, we only know whether something is a scheme 446 int hostStart = authStart;
818 // or a path when we see a `:` or `/`, and likewise we only know if the firs t 447 int hostEnd = index;
819 // `/` is part of the path or is leading an authority component when we see 448 if (lastAt >= 0) {
820 // the next character. 449 userinfo = _makeUserInfo(uri, authStart, lastAt);
821 450 hostStart = lastAt + 1;
822 int hostStart = indices[_hostStartIndex] + 1; 451 }
823 int portStart = indices[_portStartIndex]; 452 if (lastColon >= 0) {
824 int pathStart = indices[_pathStartIndex]; 453 int portNumber;
825 int queryStart = indices[_queryStartIndex]; 454 if (lastColon + 1 < index) {
826 int fragmentStart = indices[_fragmentStartIndex]; 455 portNumber = 0;
827 456 for (int i = lastColon + 1; i < index; i++) {
828 // We may discover scheme while handling special cases. 457 int digit = uri.codeUnitAt(i);
829 String scheme; 458 if (_ZERO > digit || _NINE < digit) {
830 459 _fail(uri, i, "Invalid port number");
831 // Derive some positions that weren't set to normalize the indices. 460 }
832 // If pathStart isn't set (it's before scheme end or host start), then 461 portNumber = portNumber * 10 + (digit - _ZERO);
833 // the path is empty. 462 }
834 if (fragmentStart < queryStart) queryStart = fragmentStart; 463 }
835 if (pathStart < hostStart || pathStart <= schemeEnd) { 464 port = _makePort(portNumber, scheme);
836 pathStart = queryStart; 465 hostEnd = lastColon;
837 } 466 }
838 // If there is an authority with no port, set the port position 467 host = _makeHost(uri, hostStart, hostEnd, true);
839 // to be at the end of the authority (equal to pathStart). 468 if (index < end) {
840 // This also handles a ":" in a user-info component incorrectly setting 469 char = uri.codeUnitAt(index);
841 // the port start position. 470 }
842 if (portStart < hostStart) portStart = pathStart; 471 }
843 472
844 assert(hostStart == start || schemeEnd <= hostStart); 473 // When reaching path parsing, the current character is known to not
845 assert(hostStart <= portStart); 474 // be part of the path.
846 assert(schemeEnd <= pathStart); 475 const int NOT_IN_PATH = 0;
847 assert(portStart <= pathStart); 476 // When reaching path parsing, the current character is part
848 assert(pathStart <= queryStart); 477 // of the a non-empty path.
849 assert(queryStart <= fragmentStart); 478 const int IN_PATH = 1;
850 479 // When reaching authority parsing, authority is possible.
851 bool isSimple = indices[_notSimpleIndex] < start; 480 // This is only true at start or right after scheme.
852 481 const int ALLOW_AUTH = 2;
853 if (isSimple) { 482
854 // Check/do normalizations that weren't detected by the scanner. 483 // Current state.
855 // This includes removal of empty port or userInfo, 484 // Initialized to the default value that is used when exiting the
856 // or scheme specific port and path normalizations. 485 // scheme loop by reaching the end of input.
857 if (hostStart > schemeEnd + 3) { 486 // All other breaks set their own state.
858 // Always be non-simple if URI contains user-info. 487 int state = NOT_IN_PATH;
859 // The scanner doesn't set the not-simple position in this case because 488 int i = index; // Temporary alias for index to avoid bug 19550 in dart2js.
860 // it's setting the host-start position instead. 489 while (i < end) {
861 isSimple = false; 490 char = uri.codeUnitAt(i);
862 } else if (portStart > start && portStart + 1 == pathStart) { 491 if (char == _QUESTION || char == _NUMBER_SIGN) {
863 // If the port is empty, it should be omitted. 492 state = NOT_IN_PATH;
864 // Pathological case, don't bother correcting it. 493 break;
865 isSimple = false; 494 }
866 } else if (queryStart < end && 495 if (char == _SLASH) {
867 (queryStart == pathStart + 2 && 496 state = (i == start) ? ALLOW_AUTH : IN_PATH;
868 uri.startsWith("..", pathStart)) || 497 break;
869 (queryStart > pathStart + 2 && 498 }
870 uri.startsWith("/..", queryStart - 3))) { 499 if (char == _COLON) {
871 // The path ends in a ".." segment. This should be normalized to "../". 500 if (i == start) _fail(uri, start, "Invalid empty scheme");
872 // We didn't detect this while scanning because a query or fragment was 501 scheme = _makeScheme(uri, start, i);
873 // detected at the same time (which is why we only need to check this 502 i++;
874 // if there is something after the path). 503 if (scheme == "data") {
875 isSimple = false; 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 }
509 pathStart = i;
510 if (i == end) {
511 char = EOI;
512 state = NOT_IN_PATH;
513 } else {
514 char = uri.codeUnitAt(i);
515 if (char == _QUESTION || char == _NUMBER_SIGN) {
516 state = NOT_IN_PATH;
517 } else if (char == _SLASH) {
518 state = ALLOW_AUTH;
519 } else {
520 state = IN_PATH;
521 }
522 }
523 break;
524 }
525 i++;
526 char = EOI;
527 }
528 index = i; // Remove alias when bug is fixed.
529
530 if (state == ALLOW_AUTH) {
531 assert(char == _SLASH);
532 // Have seen one slash either at start or right after scheme.
533 // If two slashes, it's an authority, otherwise it's just the path.
534 index++;
535 if (index == end) {
536 char = EOI;
537 state = NOT_IN_PATH;
876 } else { 538 } else {
877 // There are a few scheme-based normalizations that 539 char = uri.codeUnitAt(index);
878 // the scanner couldn't check. 540 if (char == _SLASH) {
879 // That means that the input is very close to simple, so just do 541 index++;
880 // the normalizations. 542 parseAuth();
881 if (schemeEnd == start + 4) { 543 pathStart = index;
882 // Do scheme based normalizations for file, http. 544 }
883 if (uri.startsWith("file", start)) { 545 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
884 scheme = "file"; 546 state = NOT_IN_PATH;
885 if (hostStart <= start) { 547 } else {
886 // File URIs should have an authority. 548 state = IN_PATH;
887 // Paths after an authority should be absolute. 549 }
888 String schemeAuth = "file://"; 550 }
889 int delta = 2; 551 }
890 if (!uri.startsWith("/", pathStart)) { 552
891 schemeAuth = "file:///"; 553 assert(state == IN_PATH || state == NOT_IN_PATH);
892 delta = 3; 554 if (state == IN_PATH) {
893 } 555 // Characters from pathStart to index (inclusive) are known
894 uri = schemeAuth + uri.substring(pathStart, end); 556 // to be part of the path.
895 schemeEnd -= start; 557 while (++index < end) {
896 hostStart = 7; 558 char = uri.codeUnitAt(index);
897 portStart = 7; 559 if (char == _QUESTION || char == _NUMBER_SIGN) {
898 pathStart = 7; 560 break;
899 queryStart += delta - start; 561 }
900 fragmentStart += delta - start; 562 char = EOI;
901 start = 0; 563 }
902 end = uri.length; 564 state = NOT_IN_PATH;
903 } else if (pathStart == queryStart) { 565 }
904 // Uri has authority and empty path. Add "/" as path. 566
905 if (start == 0 && end == uri.length) { 567 assert(state == NOT_IN_PATH);
906 uri = uri.replaceRange(pathStart, queryStart, "/"); 568 bool hasAuthority = (host != null);
907 queryStart += 1; 569 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
908 fragmentStart += 1; 570
909 end += 1; 571 if (char == _QUESTION) {
910 } else { 572 int numberSignIndex = -1;
911 uri = "${uri.substring(start, pathStart)}/" 573 for (int i = index + 1; i < end; i++) {
912 "${uri.substring(queryStart, end)}"; 574 if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
913 schemeEnd -= start; 575 numberSignIndex = i;
914 hostStart -= start; 576 break;
915 portStart -= start; 577 }
916 pathStart -= start; 578 }
917 queryStart += 1 - start; 579 if (numberSignIndex < 0) {
918 fragmentStart += 1 - start; 580 query = _makeQuery(uri, index + 1, end, null);
919 start = 0; 581 } else {
920 end = uri.length; 582 query = _makeQuery(uri, index + 1, numberSignIndex, null);
921 } 583 fragment = _makeFragment(uri, numberSignIndex + 1, end);
922 } 584 }
923 } else if (uri.startsWith("http", start)) { 585 } else if (char == _NUMBER_SIGN) {
924 scheme = "http"; 586 fragment = _makeFragment(uri, index + 1, end);
925 // HTTP URIs should not have an explicit port of 80. 587 }
926 if (portStart > start && portStart + 3 == pathStart && 588 return new Uri._internal(scheme,
927 uri.startsWith("80", portStart + 1)) { 589 userinfo,
928 if (start == 0 && end == uri.length) { 590 host,
929 uri = uri.replaceRange(portStart, pathStart, ""); 591 port,
930 pathStart -= 3; 592 path,
931 queryStart -= 3; 593 query,
932 fragmentStart -= 3; 594 fragment);
933 end -= 3;
934 } else {
935 uri = uri.substring(start, portStart) +
936 uri.substring(pathStart, end);
937 schemeEnd -= start;
938 hostStart -= start;
939 portStart -= start;
940 pathStart -= 3 + start;
941 queryStart -= 3 + start;
942 fragmentStart -= 3 + start;
943 start = 0;
944 end = uri.length;
945 }
946 }
947 }
948 } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
949 scheme = "https";
950 // HTTPS URIs should not have an explicit port of 443.
951 if (portStart > start && portStart + 4 == pathStart &&
952 uri.startsWith("443", portStart + 1)) {
953 if (start == 0 && end == uri.length) {
954 uri = uri.replaceRange(portStart, pathStart, "");
955 pathStart -= 4;
956 queryStart -= 4;
957 fragmentStart -= 4;
958 end -= 3;
959 } else {
960 uri = uri.substring(start, portStart) +
961 uri.substring(pathStart, end);
962 schemeEnd -= start;
963 hostStart -= start;
964 portStart -= start;
965 pathStart -= 4 + start;
966 queryStart -= 4 + start;
967 fragmentStart -= 4 + start;
968 start = 0;
969 end = uri.length;
970 }
971 }
972 }
973 }
974 }
975
976 if (isSimple) {
977 if (start > 0 || end < uri.length) {
978 uri = uri.substring(start, end);
979 schemeEnd -= start;
980 hostStart -= start;
981 portStart -= start;
982 pathStart -= start;
983 queryStart -= start;
984 fragmentStart -= start;
985 }
986 return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
987 queryStart, fragmentStart, scheme);
988
989 }
990
991 return new _Uri.notSimple(uri, start, end, schemeEnd, hostStart, portStart,
992 pathStart, queryStart, fragmentStart, scheme);
993 } 595 }
994 596
995 /**
996 * Encode the string [component] using percent-encoding to make it
997 * safe for literal use as a URI component.
998 *
999 * All characters except uppercase and lowercase letters, digits and
1000 * the characters `-_.!~*'()` are percent-encoded. This is the
1001 * set of characters specified in RFC 2396 and the which is
1002 * specified for the encodeUriComponent in ECMA-262 version 5.1.
1003 *
1004 * When manually encoding path segments or query components remember
1005 * to encode each part separately before building the path or query
1006 * string.
1007 *
1008 * For encoding the query part consider using
1009 * [encodeQueryComponent].
1010 *
1011 * To avoid the need for explicitly encoding use the [pathSegments]
1012 * and [queryParameters] optional named arguments when constructing
1013 * a [Uri].
1014 */
1015 static String encodeComponent(String component) {
1016 return _Uri._uriEncode(_Uri._unreserved2396Table, component, UTF8, false);
1017 }
1018
1019 /**
1020 * Encode the string [component] according to the HTML 4.01 rules
1021 * for encoding the posting of a HTML form as a query string
1022 * component.
1023 *
1024 * Encode the string [component] according to the HTML 4.01 rules
1025 * for encoding the posting of a HTML form as a query string
1026 * component.
1027
1028 * The component is first encoded to bytes using [encoding].
1029 * The default is to use [UTF8] encoding, which preserves all
1030 * the characters that don't need encoding.
1031
1032 * Then the resulting bytes are "percent-encoded". This transforms
1033 * spaces (U+0020) to a plus sign ('+') and all bytes that are not
1034 * the ASCII decimal digits, letters or one of '-._~' are written as
1035 * a percent sign '%' followed by the two-digit hexadecimal
1036 * representation of the byte.
1037
1038 * Note that the set of characters which are percent-encoded is a
1039 * superset of what HTML 4.01 requires, since it refers to RFC 1738
1040 * for reserved characters.
1041 *
1042 * When manually encoding query components remember to encode each
1043 * part separately before building the query string.
1044 *
1045 * To avoid the need for explicitly encoding the query use the
1046 * [queryParameters] optional named arguments when constructing a
1047 * [Uri].
1048 *
1049 * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
1050 * details.
1051 */
1052 static String encodeQueryComponent(String component,
1053 {Encoding encoding: UTF8}) {
1054 return _Uri._uriEncode(_Uri._unreservedTable, component, encoding, true);
1055 }
1056
1057 /**
1058 * Decodes the percent-encoding in [encodedComponent].
1059 *
1060 * Note that decoding a URI component might change its meaning as
1061 * some of the decoded characters could be characters with are
1062 * delimiters for a given URI componene type. Always split a URI
1063 * component using the delimiters for the component before decoding
1064 * the individual parts.
1065 *
1066 * For handling the [path] and [query] components consider using
1067 * [pathSegments] and [queryParameters] to get the separated and
1068 * decoded component.
1069 */
1070 static String decodeComponent(String encodedComponent) {
1071 return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
1072 UTF8, false);
1073 }
1074
1075 /**
1076 * Decodes the percent-encoding in [encodedComponent], converting
1077 * pluses to spaces.
1078 *
1079 * It will create a byte-list of the decoded characters, and then use
1080 * [encoding] to decode the byte-list to a String. The default encoding is
1081 * UTF-8.
1082 */
1083 static String decodeQueryComponent(
1084 String encodedComponent,
1085 {Encoding encoding: UTF8}) {
1086 return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
1087 encoding, true);
1088 }
1089
1090 /**
1091 * Encode the string [uri] using percent-encoding to make it
1092 * safe for literal use as a full URI.
1093 *
1094 * All characters except uppercase and lowercase letters, digits and
1095 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
1096 * is the set of characters specified in in ECMA-262 version 5.1 for
1097 * the encodeURI function .
1098 */
1099 static String encodeFull(String uri) {
1100 return _Uri._uriEncode(_Uri._encodeFullTable, uri, UTF8, false);
1101 }
1102
1103 /**
1104 * Decodes the percent-encoding in [uri].
1105 *
1106 * Note that decoding a full URI might change its meaning as some of
1107 * the decoded characters could be reserved characters. In most
1108 * cases an encoded URI should be parsed into components using
1109 * [Uri.parse] before decoding the separate components.
1110 */
1111 static String decodeFull(String uri) {
1112 return _Uri._uriDecode(uri, 0, uri.length, UTF8, false);
1113 }
1114
1115 /**
1116 * Returns the [query] split into a map according to the rules
1117 * specified for FORM post in the [HTML 4.01 specification section
1118 * 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").
1119 * Each key and value in the returned map has been decoded. If the [query]
1120 * is the empty string an empty map is returned.
1121 *
1122 * Keys in the query string that have no value are mapped to the
1123 * empty string.
1124 *
1125 * Each query component will be decoded using [encoding]. The default encoding
1126 * is UTF-8.
1127 */
1128 static Map<String, String> splitQueryString(String query,
1129 {Encoding encoding: UTF8}) {
1130 return query.split("&").fold({}, (map, element) {
1131 int index = element.indexOf("=");
1132 if (index == -1) {
1133 if (element != "") {
1134 map[decodeQueryComponent(element, encoding: encoding)] = "";
1135 }
1136 } else if (index != 0) {
1137 var key = element.substring(0, index);
1138 var value = element.substring(index + 1);
1139 map[decodeQueryComponent(key, encoding: encoding)] =
1140 decodeQueryComponent(value, encoding: encoding);
1141 }
1142 return map;
1143 });
1144 }
1145
1146
1147 /**
1148 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
1149 * as a list of 4 bytes in network byte order (big endian).
1150 *
1151 * Throws a [FormatException] if [host] is not a valid IPv4 address
1152 * representation.
1153 */
1154 static List<int> parseIPv4Address(String host) =>
1155 _parseIPv4Address(host, 0, host.length);
1156
1157 /// Implementation of [parseIPv4Address] that can work on a substring.
1158 static List<int> _parseIPv4Address(String host, int start, int end) {
1159 void error(String msg, int position) {
1160 throw new FormatException('Illegal IPv4 address, $msg', host, position);
1161 }
1162
1163 var result = new Uint8List(4);
1164 int partIndex = 0;
1165 int partStart = start;
1166 for (int i = start; i < end; i++) {
1167 int char = host.codeUnitAt(i);
1168 if (char != _DOT) {
1169 if (char ^ 0x30 > 9) {
1170 // Fail on a non-digit character.
1171 error("invalid character", i);
1172 }
1173 } else {
1174 if (partIndex == 3) {
1175 error('IPv4 address should contain exactly 4 parts', i);
1176 }
1177 int part = int.parse(host.substring(partStart, i));
1178 if (part > 255) {
1179 error("each part must be in the range 0..255", partStart);
1180 }
1181 result[partIndex++] = part;
1182 partStart = i + 1;
1183 }
1184 }
1185
1186 if (partIndex != 3) {
1187 error('IPv4 address should contain exactly 4 parts', end);
1188 }
1189
1190 int part = int.parse(host.substring(partStart, end));
1191 if (part > 255) {
1192 error("each part must be in the range 0..255", partStart);
1193 }
1194 result[partIndex] = part;
1195
1196 return result;
1197 }
1198
1199 /**
1200 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
1201 * as a list of 16 bytes in network byte order (big endian).
1202 *
1203 * Throws a [FormatException] if [host] is not a valid IPv6 address
1204 * representation.
1205 *
1206 * Acts on the substring from [start] to [end]. If [end] is omitted, it
1207 * defaults ot the end of the string.
1208 *
1209 * Some examples of IPv6 addresses:
1210 * * ::1
1211 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1212 * * 3ffe:2a00:100:7031::1
1213 * * ::FFFF:129.144.52.38
1214 * * 2010:836B:4179::836B:4179
1215 */
1216 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
1217 if (end == null) end = host.length;
1218 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, separated
1219 // by `:`'s, with the following exceptions:
1220 //
1221 // - One (and only one) wildcard (`::`) may be present, representing a fill
1222 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1223 // - The last two parts may be replaced by an IPv4 "dotted-quad" address.
1224
1225 // Helper function for reporting a badly formatted IPv6 address.
1226 void error(String msg, [position]) {
1227 throw new FormatException('Illegal IPv6 address, $msg', host, position);
1228 }
1229
1230 // Parse a hex block.
1231 int parseHex(int start, int end) {
1232 if (end - start > 4) {
1233 error('an IPv6 part can only contain a maximum of 4 hex digits', start);
1234 }
1235 int value = int.parse(host.substring(start, end), radix: 16);
1236 if (value < 0 || value > 0xFFFF) {
1237 error('each part must be in the range of `0x0..0xFFFF`', start);
1238 }
1239 return value;
1240 }
1241
1242 if (host.length < 2) error('address is too short');
1243 List<int> parts = [];
1244 bool wildcardSeen = false;
1245 // Set if seeing a ".", suggesting that there is an IPv4 address.
1246 bool seenDot = false;
1247 int partStart = start;
1248 // Parse all parts, except a potential last one.
1249 for (int i = start; i < end; i++) {
1250 int char = host.codeUnitAt(i);
1251 if (char == _COLON) {
1252 if (i == start) {
1253 // If we see a `:` in the beginning, expect wildcard.
1254 i++;
1255 if (host.codeUnitAt(i) != _COLON) {
1256 error('invalid start colon.', i);
1257 }
1258 partStart = i;
1259 }
1260 if (i == partStart) {
1261 // Wildcard. We only allow one.
1262 if (wildcardSeen) {
1263 error('only one wildcard `::` is allowed', i);
1264 }
1265 wildcardSeen = true;
1266 parts.add(-1);
1267 } else {
1268 // Found a single colon. Parse [partStart..i] as a hex entry.
1269 parts.add(parseHex(partStart, i));
1270 }
1271 partStart = i + 1;
1272 } else if (char == _DOT) {
1273 seenDot = true;
1274 }
1275 }
1276 if (parts.length == 0) error('too few parts');
1277 bool atEnd = (partStart == end);
1278 bool isLastWildcard = (parts.last == -1);
1279 if (atEnd && !isLastWildcard) {
1280 error('expected a part after last `:`', end);
1281 }
1282 if (!atEnd) {
1283 if (!seenDot) {
1284 parts.add(parseHex(partStart, end));
1285 } else {
1286 List<int> last = _parseIPv4Address(host, partStart, end);
1287 parts.add(last[0] << 8 | last[1]);
1288 parts.add(last[2] << 8 | last[3]);
1289 }
1290 }
1291 if (wildcardSeen) {
1292 if (parts.length > 7) {
1293 error('an address with a wildcard must have less than 7 parts');
1294 }
1295 } else if (parts.length != 8) {
1296 error('an address without a wildcard must contain exactly 8 parts');
1297 }
1298 List<int> bytes = new Uint8List(16);
1299 for (int i = 0, index = 0; i < parts.length; i++) {
1300 int value = parts[i];
1301 if (value == -1) {
1302 int wildCardLength = 9 - parts.length;
1303 for (int j = 0; j < wildCardLength; j++) {
1304 bytes[index] = 0;
1305 bytes[index + 1] = 0;
1306 index += 2;
1307 }
1308 } else {
1309 bytes[index] = value >> 8;
1310 bytes[index + 1] = value & 0xff;
1311 index += 2;
1312 }
1313 }
1314 return bytes;
1315 }
1316 }
1317
1318 class _Uri implements Uri {
1319 // We represent the missing scheme as an empty string.
1320 // A valid scheme cannot be empty.
1321 final String scheme;
1322
1323 /**
1324 * The user-info part of the authority.
1325 *
1326 * Does not distinguish between an empty user-info and an absent one.
1327 * The value is always non-null.
1328 * Is considered absent if [_host] is `null`.
1329 */
1330 final String _userInfo;
1331
1332 /**
1333 * The host name of the URI.
1334 *
1335 * Set to `null` if there is no authority in the URI.
1336 * The host name is the only mandatory part of an authority, so we use
1337 * it to mark whether an authority part was present or not.
1338 */
1339 final String _host;
1340
1341 /**
1342 * The port number part of the authority.
1343 *
1344 * The port. Set to null if there is no port. Normalized to null if
1345 * the port is the default port for the scheme.
1346 */
1347 int _port;
1348
1349 /**
1350 * The path of the URI.
1351 *
1352 * Always non-null.
1353 */
1354 String _path;
1355
1356 // The query content, or null if there is no query.
1357 final String _query;
1358
1359 // The fragment content, or null if there is no fragment.
1360 final String _fragment;
1361
1362 /**
1363 * Cache the computed return value of [pathSegements].
1364 */
1365 List<String> _pathSegments;
1366
1367 /**
1368 * Cache of the full normalized text representation of the URI.
1369 */
1370 String _text;
1371
1372 /**
1373 * Cache of the hashCode of [_text].
1374 *
1375 * Is null until computed.
1376 */
1377 int _hashCodeCache;
1378
1379 /**
1380 * Cache the computed return value of [queryParameters].
1381 */
1382 Map<String, String> _queryParameters;
1383 Map<String, List<String>> _queryParameterLists;
1384
1385 /// Internal non-verifying constructor. Only call with validated arguments.
1386 _Uri._internal(this.scheme,
1387 this._userInfo,
1388 this._host,
1389 this._port,
1390 this._path,
1391 this._query,
1392 this._fragment);
1393
1394 /// Create a [_Uri] from parts of [uri].
1395 ///
1396 /// The parameters specify the start/end of particular components of the URI.
1397 /// The [scheme] may contain a string representing a normalized scheme
1398 /// component if one has already been discovered.
1399 factory _Uri.notSimple(String uri, int start, int end, int schemeEnd,
1400 int hostStart, int portStart, int pathStart,
1401 int queryStart, int fragmentStart, String scheme) {
1402 if (scheme == null) {
1403 scheme = "";
1404 if (schemeEnd > start) {
1405 scheme = _makeScheme(uri, start, schemeEnd);
1406 } else if (schemeEnd == start) {
1407 _fail(uri, start, "Invalid empty scheme");
1408 }
1409 }
1410 String userInfo = "";
1411 String host;
1412 int port;
1413 if (hostStart > start) {
1414 int userInfoStart = schemeEnd + 3;
1415 if (userInfoStart < hostStart) {
1416 userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1);
1417 }
1418 host = _makeHost(uri, hostStart, portStart, false);
1419 if (portStart + 1 < pathStart) {
1420 // Should throw because invalid.
1421 port = int.parse(uri.substring(portStart + 1, pathStart), onError: (_) {
1422 throw new FormatException("Invalid port", uri, portStart + 1);
1423 });
1424 port = _makePort(port, scheme);
1425 }
1426 }
1427 String path = _makePath(uri, pathStart, queryStart, null,
1428 scheme, host != null);
1429 String query;
1430 if (queryStart < fragmentStart) {
1431 query = _makeQuery(uri, queryStart + 1, fragmentStart, null);
1432 }
1433 String fragment;
1434 if (fragmentStart < end) {
1435 fragment = _makeFragment(uri, fragmentStart + 1, end);
1436 }
1437 return new _Uri._internal(scheme,
1438 userInfo,
1439 host,
1440 port,
1441 path,
1442 query,
1443 fragment);
1444 }
1445
1446 /// Implementation of [Uri.Uri].
1447 factory _Uri({String scheme,
1448 String userInfo,
1449 String host,
1450 int port,
1451 String path,
1452 Iterable<String> pathSegments,
1453 String query,
1454 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
1455 String fragment}) {
1456 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
1457 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
1458 host = _makeHost(host, 0, _stringOrNullLength(host), false);
1459 // Special case this constructor for backwards compatibility.
1460 if (query == "") query = null;
1461 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
1462 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
1463 port = _makePort(port, scheme);
1464 bool isFile = (scheme == "file");
1465 if (host == null &&
1466 (userInfo.isNotEmpty || port != null || isFile)) {
1467 host = "";
1468 }
1469 bool hasAuthority = (host != null);
1470 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
1471 scheme, hasAuthority);
1472 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
1473 path = _normalizeRelativePath(path);
1474 } else {
1475 path = _removeDotSegments(path);
1476 }
1477 return new _Uri._internal(scheme, userInfo, host, port,
1478 path, query, fragment);
1479 }
1480
1481 /// Implementation of [Uri.http].
1482 factory _Uri.http(String authority,
1483 String unencodedPath,
1484 [Map<String, String> queryParameters]) {
1485 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
1486 }
1487
1488 /// Implementation of [Uri.https].
1489 factory _Uri.https(String authority,
1490 String unencodedPath,
1491 [Map<String, String> queryParameters]) {
1492 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
1493 }
1494
1495 String get authority {
1496 if (!hasAuthority) return "";
1497 var sb = new StringBuffer();
1498 _writeAuthority(sb);
1499 return sb.toString();
1500 }
1501
1502 String get userInfo => _userInfo;
1503
1504 String get host {
1505 if (_host == null) return "";
1506 if (_host.startsWith('[')) {
1507 return _host.substring(1, _host.length - 1);
1508 }
1509 return _host;
1510 }
1511
1512 int get port {
1513 if (_port == null) return _defaultPort(scheme);
1514 return _port;
1515 }
1516
1517 // The default port for the scheme of this Uri.
1518 static int _defaultPort(String scheme) {
1519 if (scheme == "http") return 80;
1520 if (scheme == "https") return 443;
1521 return 0;
1522 }
1523
1524 String get path => _path;
1525
1526 String get query => _query ?? "";
1527
1528 String get fragment => _fragment ?? "";
1529
1530 // Report a parse failure. 597 // Report a parse failure.
1531 static void _fail(String uri, int index, String message) { 598 static void _fail(String uri, int index, String message) {
1532 throw new FormatException(message, uri, index); 599 throw new FormatException(message, uri, index);
1533 } 600 }
1534 601
1535 static Uri _makeHttpUri(String scheme, 602 static Uri _makeHttpUri(String scheme,
1536 String authority, 603 String authority,
1537 String unencodedPath, 604 String unencodedPath,
1538 Map<String, String> queryParameters) { 605 Map<String, String> queryParameters) {
1539 var userInfo = ""; 606 var userInfo = "";
1540 var host = null; 607 var host = null;
1541 var port = null; 608 var port = null;
1542 609
1543 if (authority != null && authority.isNotEmpty) { 610 if (authority != null && authority.isNotEmpty) {
1544 var hostStart = 0; 611 var hostStart = 0;
1545 // Split off the user info. 612 // Split off the user info.
1546 bool hasUserInfo = false; 613 bool hasUserInfo = false;
1547 for (int i = 0; i < authority.length; i++) { 614 for (int i = 0; i < authority.length; i++) {
1548 const int atSign = 0x40; 615 if (authority.codeUnitAt(i) == _AT_SIGN) {
1549 if (authority.codeUnitAt(i) == atSign) {
1550 hasUserInfo = true; 616 hasUserInfo = true;
1551 userInfo = authority.substring(0, i); 617 userInfo = authority.substring(0, i);
1552 hostStart = i + 1; 618 hostStart = i + 1;
1553 break; 619 break;
1554 } 620 }
1555 } 621 }
1556 var hostEnd = hostStart; 622 var hostEnd = hostStart;
1557 if (hostStart < authority.length && 623 if (hostStart < authority.length &&
1558 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { 624 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
1559 // IPv6 host. 625 // IPv6 host.
1560 for (; hostEnd < authority.length; hostEnd++) { 626 for (; hostEnd < authority.length; hostEnd++) {
1561 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; 627 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
1562 } 628 }
1563 if (hostEnd == authority.length) { 629 if (hostEnd == authority.length) {
1564 throw new FormatException("Invalid IPv6 host entry.", 630 throw new FormatException("Invalid IPv6 host entry.",
1565 authority, hostStart); 631 authority, hostStart);
1566 } 632 }
1567 Uri.parseIPv6Address(authority, hostStart + 1, hostEnd); 633 parseIPv6Address(authority, hostStart + 1, hostEnd);
1568 hostEnd++; // Skip the closing bracket. 634 hostEnd++; // Skip the closing bracket.
1569 if (hostEnd != authority.length && 635 if (hostEnd != authority.length &&
1570 authority.codeUnitAt(hostEnd) != _COLON) { 636 authority.codeUnitAt(hostEnd) != _COLON) {
1571 throw new FormatException("Invalid end of authority", 637 throw new FormatException("Invalid end of authority",
1572 authority, hostEnd); 638 authority, hostEnd);
1573 } 639 }
1574 } 640 }
1575 // Split host and port. 641 // Split host and port.
1576 bool hasPort = false; 642 bool hasPort = false;
1577 for (; hostEnd < authority.length; hostEnd++) { 643 for (; hostEnd < authority.length; hostEnd++) {
1578 if (authority.codeUnitAt(hostEnd) == _COLON) { 644 if (authority.codeUnitAt(hostEnd) == _COLON) {
1579 var portString = authority.substring(hostEnd + 1); 645 var portString = authority.substring(hostEnd + 1);
1580 // We allow the empty port - falling back to initial value. 646 // We allow the empty port - falling back to initial value.
1581 if (portString.isNotEmpty) port = int.parse(portString); 647 if (portString.isNotEmpty) port = int.parse(portString);
1582 break; 648 break;
1583 } 649 }
1584 } 650 }
1585 host = authority.substring(hostStart, hostEnd); 651 host = authority.substring(hostStart, hostEnd);
1586 } 652 }
1587 return new Uri(scheme: scheme, 653 return new Uri(scheme: scheme,
1588 userInfo: userInfo, 654 userInfo: userInfo,
1589 host: host, 655 host: host,
1590 port: port, 656 port: port,
1591 pathSegments: unencodedPath.split("/"), 657 pathSegments: unencodedPath.split("/"),
1592 queryParameters: queryParameters); 658 queryParameters: queryParameters);
1593 } 659 }
1594 660
1595 /// Implementation of [Uri.file]. 661 /**
1596 factory _Uri.file(String path, {bool windows}) { 662 * Creates a new file URI from an absolute or relative file path.
1597 windows = (windows == null) ? _Uri._isWindows : windows; 663 *
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;
1598 return windows ? _makeWindowsFileUrl(path, false) 744 return windows ? _makeWindowsFileUrl(path, false)
1599 : _makeFileUri(path, false); 745 : _makeFileUri(path, false);
1600 } 746 }
1601 747
1602 /// Implementation of [Uri.directory]. 748 /**
1603 factory _Uri.directory(String path, {bool windows}) { 749 * Like [Uri.file] except that a non-empty URI path ends in a slash.
1604 windows = (windows == null) ? _Uri._isWindows : windows; 750 *
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;
1605 return windows ? _makeWindowsFileUrl(path, true) 757 return windows ? _makeWindowsFileUrl(path, true)
1606 : _makeFileUri(path, true); 758 : _makeFileUri(path, true);
1607 } 759 }
1608 760
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 }
1609 801
1610 /// Used internally in path-related constructors. 802 /**
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
1611 external static bool get _isWindows; 840 external static bool get _isWindows;
1612 841
1613 static _checkNonWindowsPathReservedCharacters(List<String> segments, 842 static _checkNonWindowsPathReservedCharacters(List<String> segments,
1614 bool argumentError) { 843 bool argumentError) {
1615 segments.forEach((segment) { 844 segments.forEach((segment) {
1616 if (segment.contains("/")) { 845 if (segment.contains("/")) {
1617 if (argumentError) { 846 if (argumentError) {
1618 throw new ArgumentError("Illegal path character $segment"); 847 throw new ArgumentError("Illegal path character $segment");
1619 } else { 848 } else {
1620 throw new UnsupportedError("Illegal path character $segment"); 849 throw new UnsupportedError("Illegal path character $segment");
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
1733 _checkWindowsPathReservedCharacters(pathSegments, true); 962 _checkWindowsPathReservedCharacters(pathSegments, true);
1734 if (slashTerminated && 963 if (slashTerminated &&
1735 pathSegments.isNotEmpty && 964 pathSegments.isNotEmpty &&
1736 pathSegments.last.isNotEmpty) { 965 pathSegments.last.isNotEmpty) {
1737 pathSegments.add(""); // Extra separator at end. 966 pathSegments.add(""); // Extra separator at end.
1738 } 967 }
1739 return new Uri(pathSegments: pathSegments); 968 return new Uri(pathSegments: pathSegments);
1740 } 969 }
1741 } 970 }
1742 971
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 */
1743 Uri replace({String scheme, 1012 Uri replace({String scheme,
1744 String userInfo, 1013 String userInfo,
1745 String host, 1014 String host,
1746 int port, 1015 int port,
1747 String path, 1016 String path,
1748 Iterable<String> pathSegments, 1017 Iterable<String> pathSegments,
1749 String query, 1018 String query,
1750 Map<String, dynamic/*String|Iterable<String>*/> queryParameters, 1019 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
1751 String fragment}) { 1020 String fragment}) {
1752 // Set to true if the scheme has (potentially) changed. 1021 // Set to true if the scheme has (potentially) changed.
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
1799 } else { 1068 } else {
1800 query = this._query; 1069 query = this._query;
1801 } 1070 }
1802 1071
1803 if (fragment != null) { 1072 if (fragment != null) {
1804 fragment = _makeFragment(fragment, 0, fragment.length); 1073 fragment = _makeFragment(fragment, 0, fragment.length);
1805 } else { 1074 } else {
1806 fragment = this._fragment; 1075 fragment = this._fragment;
1807 } 1076 }
1808 1077
1809 return new _Uri._internal( 1078 return new Uri._internal(
1810 scheme, userInfo, host, port, path, query, fragment); 1079 scheme, userInfo, host, port, path, query, fragment);
1811 } 1080 }
1812 1081
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 */
1813 Uri removeFragment() { 1087 Uri removeFragment() {
1814 if (!this.hasFragment) return this; 1088 if (!this.hasFragment) return this;
1815 return new _Uri._internal(scheme, _userInfo, _host, _port, 1089 return new Uri._internal(scheme, _userInfo, _host, _port,
1816 _path, _query, null); 1090 _path, _query, null);
1817 } 1091 }
1818 1092
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 */
1819 List<String> get pathSegments { 1101 List<String> get pathSegments {
1820 var result = _pathSegments; 1102 var result = _pathSegments;
1821 if (result != null) return result; 1103 if (result != null) return result;
1822 1104
1823 var pathToSplit = path; 1105 var pathToSplit = path;
1824 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) { 1106 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) {
1825 pathToSplit = pathToSplit.substring(1); 1107 pathToSplit = pathToSplit.substring(1);
1826 } 1108 }
1827 result = (pathToSplit == "") 1109 result = (pathToSplit == "")
1828 ? const<String>[] 1110 ? const<String>[]
1829 : new List<String>.unmodifiable( 1111 : new List<String>.unmodifiable(
1830 pathToSplit.split("/").map(Uri.decodeComponent)); 1112 pathToSplit.split("/").map(Uri.decodeComponent));
1831 _pathSegments = result; 1113 _pathSegments = result;
1832 return result; 1114 return result;
1833 } 1115 }
1834 1116
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 */
1835 Map<String, String> get queryParameters { 1133 Map<String, String> get queryParameters {
1836 if (_queryParameters == null) { 1134 if (_queryParameters == null) {
1837 _queryParameters = 1135 _queryParameters =
1838 new UnmodifiableMapView<String, String>(Uri.splitQueryString(query)); 1136 new UnmodifiableMapView<String, String>(splitQueryString(query));
1839 } 1137 }
1840 return _queryParameters; 1138 return _queryParameters;
1841 } 1139 }
1842 1140
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 */
1843 Map<String, List<String>> get queryParametersAll { 1154 Map<String, List<String>> get queryParametersAll {
1844 if (_queryParameterLists == null) { 1155 if (_queryParameterLists == null) {
1845 Map queryParameterLists = _splitQueryStringAll(query); 1156 Map queryParameterLists = _splitQueryStringAll(query);
1846 for (var key in queryParameterLists.keys) { 1157 for (var key in queryParameterLists.keys) {
1847 queryParameterLists[key] = 1158 queryParameterLists[key] =
1848 new List<String>.unmodifiable(queryParameterLists[key]); 1159 new List<String>.unmodifiable(queryParameterLists[key]);
1849 } 1160 }
1850 _queryParameterLists = 1161 _queryParameterLists =
1851 new Map<String, List<String>>.unmodifiable(queryParameterLists); 1162 new Map<String, List<String>>.unmodifiable(queryParameterLists);
1852 } 1163 }
1853 return _queryParameterLists; 1164 return _queryParameterLists;
1854 } 1165 }
1855 1166
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 */
1856 Uri normalizePath() { 1183 Uri normalizePath() {
1857 String path = _normalizePath(_path, scheme, hasAuthority); 1184 String path = _normalizePath(_path, scheme, hasAuthority);
1858 if (identical(path, _path)) return this; 1185 if (identical(path, _path)) return this;
1859 return this.replace(path: path); 1186 return this.replace(path: path);
1860 } 1187 }
1861 1188
1862 static int _makePort(int port, String scheme) { 1189 static int _makePort(int port, String scheme) {
1863 // Perform scheme specific normalization. 1190 // Perform scheme specific normalization.
1864 if (port != null && port == _defaultPort(scheme)) return null; 1191 if (port != null && port == _defaultPort(scheme)) return null;
1865 return port; 1192 return port;
(...skipping 12 matching lines...) Expand all
1878 */ 1205 */
1879 static String _makeHost(String host, int start, int end, bool strictIPv6) { 1206 static String _makeHost(String host, int start, int end, bool strictIPv6) {
1880 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952? 1207 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
1881 if (host == null) return null; 1208 if (host == null) return null;
1882 if (start == end) return ""; 1209 if (start == end) return "";
1883 // Host is an IPv6 address if it starts with '[' or contains a colon. 1210 // Host is an IPv6 address if it starts with '[' or contains a colon.
1884 if (host.codeUnitAt(start) == _LEFT_BRACKET) { 1211 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
1885 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) { 1212 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
1886 _fail(host, start, 'Missing end `]` to match `[` in host'); 1213 _fail(host, start, 'Missing end `]` to match `[` in host');
1887 } 1214 }
1888 Uri.parseIPv6Address(host, start + 1, end - 1); 1215 parseIPv6Address(host, start + 1, end - 1);
1889 // RFC 5952 requires hex digits to be lower case. 1216 // RFC 5952 requires hex digits to be lower case.
1890 return host.substring(start, end).toLowerCase(); 1217 return host.substring(start, end).toLowerCase();
1891 } 1218 }
1892 if (!strictIPv6) { 1219 if (!strictIPv6) {
1893 // TODO(lrn): skip if too short to be a valid IPv6 address? 1220 // TODO(lrn): skip if too short to be a valid IPv6 address?
1894 for (int i = start; i < end; i++) { 1221 for (int i = start; i < end; i++) {
1895 if (host.codeUnitAt(i) == _COLON) { 1222 if (host.codeUnitAt(i) == _COLON) {
1896 Uri.parseIPv6Address(host, start, end); 1223 parseIPv6Address(host, start, end);
1897 return '[$host]'; 1224 return '[$host]';
1898 } 1225 }
1899 } 1226 }
1900 } 1227 }
1901 return _normalizeRegName(host, start, end); 1228 return _normalizeRegName(host, start, end);
1902 } 1229 }
1903 1230
1904 static bool _isRegNameChar(int char) { 1231 static bool _isRegNameChar(int char) {
1905 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0; 1232 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
1906 } 1233 }
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
1999 final int codeUnit = scheme.codeUnitAt(i); 1326 final int codeUnit = scheme.codeUnitAt(i);
2000 if (!_isSchemeCharacter(codeUnit)) { 1327 if (!_isSchemeCharacter(codeUnit)) {
2001 _fail(scheme, i, "Illegal scheme character"); 1328 _fail(scheme, i, "Illegal scheme character");
2002 } 1329 }
2003 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) { 1330 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
2004 containsUpperCase = true; 1331 containsUpperCase = true;
2005 } 1332 }
2006 } 1333 }
2007 scheme = scheme.substring(start, end); 1334 scheme = scheme.substring(start, end);
2008 if (containsUpperCase) scheme = scheme.toLowerCase(); 1335 if (containsUpperCase) scheme = scheme.toLowerCase();
2009 return _canonicalizeScheme(scheme);
2010 }
2011
2012 // Canonicalize a few often-used scheme strings.
2013 //
2014 // This improves memory usage and makes comparison faster.
2015 static String _canonicalizeScheme(String scheme) {
2016 if (scheme == "http") return "http";
2017 if (scheme == "file") return "file";
2018 if (scheme == "https") return "https";
2019 if (scheme == "package") return "package";
2020 return scheme; 1336 return scheme;
2021 } 1337 }
2022 1338
2023 static String _makeUserInfo(String userInfo, int start, int end) { 1339 static String _makeUserInfo(String userInfo, int start, int end) {
2024 if (userInfo == null) return ""; 1340 if (userInfo == null) return "";
2025 return _normalize(userInfo, start, end, _userinfoTable); 1341 return _normalize(userInfo, start, end, _userinfoTable);
2026 } 1342 }
2027 1343
2028 static String _makePath(String path, int start, int end, 1344 static String _makePath(String path, int start, int end,
2029 Iterable<String> pathSegments, 1345 Iterable<String> pathSegments,
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
2096 } 1412 }
2097 }); 1413 });
2098 return result.toString(); 1414 return result.toString();
2099 } 1415 }
2100 1416
2101 static String _makeFragment(String fragment, int start, int end) { 1417 static String _makeFragment(String fragment, int start, int end) {
2102 if (fragment == null) return null; 1418 if (fragment == null) return null;
2103 return _normalize(fragment, start, end, _queryCharTable); 1419 return _normalize(fragment, start, end, _queryCharTable);
2104 } 1420 }
2105 1421
1422 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
1423
2106 /** 1424 /**
2107 * Performs RFC 3986 Percent-Encoding Normalization. 1425 * Performs RFC 3986 Percent-Encoding Normalization.
2108 * 1426 *
2109 * Returns a replacement string that should be replace the original escape. 1427 * Returns a replacement string that should be replace the original escape.
2110 * Returns null if no replacement is necessary because the escape is 1428 * Returns null if no replacement is necessary because the escape is
2111 * not for an unreserved character and is already non-lower-case. 1429 * not for an unreserved character and is already non-lower-case.
2112 * 1430 *
2113 * Returns "%" if the escape is invalid (not two valid hex digits following 1431 * Returns "%" if the escape is invalid (not two valid hex digits following
2114 * the percent sign). The calling code should replace the percent 1432 * the percent sign). The calling code should replace the percent
2115 * sign with "%25", but leave the following two characters unmodified. 1433 * sign with "%25", but leave the following two characters unmodified.
(...skipping 24 matching lines...) Expand all
2140 return source.substring(index, index + 3).toUpperCase(); 1458 return source.substring(index, index + 3).toUpperCase();
2141 } 1459 }
2142 // Escape is retained, and is already non-lower case, so return null to 1460 // Escape is retained, and is already non-lower case, so return null to
2143 // represent "no replacement necessary". 1461 // represent "no replacement necessary".
2144 return null; 1462 return null;
2145 } 1463 }
2146 1464
2147 // Converts a UTF-16 code-unit to its value as a hex digit. 1465 // Converts a UTF-16 code-unit to its value as a hex digit.
2148 // Returns -1 for non-hex digits. 1466 // Returns -1 for non-hex digits.
2149 static int _parseHexDigit(int char) { 1467 static int _parseHexDigit(int char) {
2150 const int zeroDigit = 0x30; 1468 int digit = char ^ Uri._ZERO;
2151 int digit = char ^ zeroDigit;
2152 if (digit <= 9) return digit; 1469 if (digit <= 9) return digit;
2153 int lowerCase = char | 0x20; 1470 int lowerCase = char | 0x20;
2154 if (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) { 1471 if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
2155 return lowerCase - (_LOWER_CASE_A - 10); 1472 return lowerCase - (_LOWER_CASE_A - 10);
2156 } 1473 }
2157 return -1; 1474 return -1;
2158 } 1475 }
2159 1476
2160 static String _escapeChar(int char) { 1477 static String _escapeChar(int char) {
2161 assert(char <= 0x10ffff); // It's a valid unicode code point. 1478 assert(char <= 0x10ffff); // It's a valid unicode code point.
2162 List<int> codeUnits; 1479 List<int> codeUnits;
2163 if (char < 0x80) { 1480 if (char < 0x80) {
2164 // ASCII, a single percent encoded sequence. 1481 // ASCII, a single percent encoded sequence.
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
2230 } 1547 }
2231 } else if (_isGeneralDelimiter(char)) { 1548 } else if (_isGeneralDelimiter(char)) {
2232 _fail(component, index, "Invalid character"); 1549 _fail(component, index, "Invalid character");
2233 } else { 1550 } else {
2234 sourceLength = 1; 1551 sourceLength = 1;
2235 if ((char & 0xFC00) == 0xD800) { 1552 if ((char & 0xFC00) == 0xD800) {
2236 // Possible lead surrogate. 1553 // Possible lead surrogate.
2237 if (index + 1 < end) { 1554 if (index + 1 < end) {
2238 int tail = component.codeUnitAt(index + 1); 1555 int tail = component.codeUnitAt(index + 1);
2239 if ((tail & 0xFC00) == 0xDC00) { 1556 if ((tail & 0xFC00) == 0xDC00) {
2240 // Tail surrogate. 1557 // Tail surrogat.
2241 sourceLength = 2; 1558 sourceLength = 2;
2242 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff); 1559 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
2243 } 1560 }
2244 } 1561 }
2245 } 1562 }
2246 replacement = _escapeChar(char); 1563 replacement = _escapeChar(char);
2247 } 1564 }
2248 if (buffer == null) buffer = new StringBuffer(); 1565 if (buffer == null) buffer = new StringBuffer();
2249 buffer.write(component.substring(sectionStart, index)); 1566 buffer.write(component.substring(sectionStart, index));
2250 buffer.write(replacement); 1567 buffer.write(replacement);
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after
2379 output.add(segment); 1696 output.add(segment);
2380 } 1697 }
2381 } 1698 }
2382 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) { 1699 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
2383 return "./"; 1700 return "./";
2384 } 1701 }
2385 if (appendSlash || output.last == '..') output.add(""); 1702 if (appendSlash || output.last == '..') output.add("");
2386 return output.join("/"); 1703 return output.join("/");
2387 } 1704 }
2388 1705
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 */
2389 Uri resolve(String reference) { 1716 Uri resolve(String reference) {
2390 return resolveUri(Uri.parse(reference)); 1717 return resolveUri(Uri.parse(reference));
2391 } 1718 }
2392 1719
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 */
2393 Uri resolveUri(Uri reference) { 1734 Uri resolveUri(Uri reference) {
2394 // From RFC 3986. 1735 // From RFC 3986.
2395 String targetScheme; 1736 String targetScheme;
2396 String targetUserInfo = ""; 1737 String targetUserInfo = "";
2397 String targetHost; 1738 String targetHost;
2398 int targetPort; 1739 int targetPort;
2399 String targetPath; 1740 String targetPath;
2400 String targetQuery; 1741 String targetQuery;
2401 if (reference.scheme.isNotEmpty) { 1742 if (reference.scheme.isNotEmpty) {
2402 targetScheme = reference.scheme; 1743 targetScheme = reference.scheme;
(...skipping 25 matching lines...) Expand all
2428 targetQuery = reference.query; 1769 targetQuery = reference.query;
2429 } else { 1770 } else {
2430 targetQuery = this._query; 1771 targetQuery = this._query;
2431 } 1772 }
2432 } else { 1773 } else {
2433 if (reference.hasAbsolutePath) { 1774 if (reference.hasAbsolutePath) {
2434 targetPath = _removeDotSegments(reference.path); 1775 targetPath = _removeDotSegments(reference.path);
2435 } else { 1776 } else {
2436 // This is the RFC 3986 behavior for merging. 1777 // This is the RFC 3986 behavior for merging.
2437 if (this.hasEmptyPath) { 1778 if (this.hasEmptyPath) {
2438 if (!this.hasAuthority) { 1779 if (!this.hasScheme && !this.hasAuthority) {
2439 if (!this.hasScheme) { 1780 // Keep the path relative if no scheme or authority.
2440 // Keep the path relative if no scheme or authority. 1781 targetPath = reference.path;
2441 targetPath = reference.path;
2442 } else {
2443 // Remove leading dot-segments if the path is put
2444 // beneath a scheme.
2445 targetPath = _removeDotSegments(reference.path);
2446 }
2447 } else { 1782 } else {
2448 // RFC algorithm for base with authority and empty path. 1783 // Add path normalization on top of RFC algorithm.
2449 targetPath = _removeDotSegments("/" + reference.path); 1784 targetPath = _removeDotSegments("/" + reference.path);
2450 } 1785 }
2451 } else { 1786 } else {
2452 var mergedPath = _mergePaths(this._path, reference.path); 1787 var mergedPath = _mergePaths(this._path, reference.path);
2453 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) { 1788 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
2454 targetPath = _removeDotSegments(mergedPath); 1789 targetPath = _removeDotSegments(mergedPath);
2455 } else { 1790 } else {
2456 // Non-RFC 3986 behavior. 1791 // Non-RFC 3986 beavior. If both base and reference are relative
2457 // If both base and reference are relative paths, 1792 // path, allow the merged path to start with "..".
2458 // allow the merged path to start with "..".
2459 // The RFC only specifies the case where the base has a scheme. 1793 // The RFC only specifies the case where the base has a scheme.
2460 targetPath = _normalizeRelativePath(mergedPath); 1794 targetPath = _normalizeRelativePath(mergedPath);
2461 } 1795 }
2462 } 1796 }
2463 } 1797 }
2464 if (reference.hasQuery) targetQuery = reference.query; 1798 if (reference.hasQuery) targetQuery = reference.query;
2465 } 1799 }
2466 } 1800 }
2467 } 1801 }
2468 String fragment = reference.hasFragment ? reference.fragment : null; 1802 String fragment = reference.hasFragment ? reference.fragment : null;
2469 return new _Uri._internal(targetScheme, 1803 return new Uri._internal(targetScheme,
2470 targetUserInfo, 1804 targetUserInfo,
2471 targetHost, 1805 targetHost,
2472 targetPort, 1806 targetPort,
2473 targetPath, 1807 targetPath,
2474 targetQuery, 1808 targetQuery,
2475 fragment); 1809 fragment);
2476 } 1810 }
2477 1811
1812 /**
1813 * Returns whether the URI has a [scheme] component.
1814 */
2478 bool get hasScheme => scheme.isNotEmpty; 1815 bool get hasScheme => scheme.isNotEmpty;
2479 1816
1817 /**
1818 * Returns whether the URI has an [authority] component.
1819 */
2480 bool get hasAuthority => _host != null; 1820 bool get hasAuthority => _host != null;
2481 1821
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 */
2482 bool get hasPort => _port != null; 1830 bool get hasPort => _port != null;
2483 1831
1832 /**
1833 * Returns whether the URI has a query part.
1834 */
2484 bool get hasQuery => _query != null; 1835 bool get hasQuery => _query != null;
2485 1836
1837 /**
1838 * Returns whether the URI has a fragment part.
1839 */
2486 bool get hasFragment => _fragment != null; 1840 bool get hasFragment => _fragment != null;
2487 1841
1842 /**
1843 * Returns whether the URI has an empty path.
1844 */
2488 bool get hasEmptyPath => _path.isEmpty; 1845 bool get hasEmptyPath => _path.isEmpty;
2489 1846
1847 /**
1848 * Returns whether the URI has an absolute path (starting with '/').
1849 */
2490 bool get hasAbsolutePath => _path.startsWith('/'); 1850 bool get hasAbsolutePath => _path.startsWith('/');
2491 1851
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 */
2492 String get origin { 1860 String get origin {
2493 if (scheme == "" || _host == null || _host == "") { 1861 if (scheme == "" || _host == null || _host == "") {
2494 throw new StateError("Cannot use origin without a scheme: $this"); 1862 throw new StateError("Cannot use origin without a scheme: $this");
2495 } 1863 }
2496 if (scheme != "http" && scheme != "https") { 1864 if (scheme != "http" && scheme != "https") {
2497 throw new StateError( 1865 throw new StateError(
2498 "Origin is only applicable schemes http and https: $this"); 1866 "Origin is only applicable schemes http and https: $this");
2499 } 1867 }
2500 if (_port == null) return "$scheme://$_host"; 1868 if (_port == null) return "$scheme://$_host";
2501 return "$scheme://$_host:$_port"; 1869 return "$scheme://$_host:$_port";
2502 } 1870 }
2503 1871
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 */
2504 String toFilePath({bool windows}) { 1935 String toFilePath({bool windows}) {
2505 if (scheme != "" && scheme != "file") { 1936 if (scheme != "" && scheme != "file") {
2506 throw new UnsupportedError( 1937 throw new UnsupportedError(
2507 "Cannot extract a file path from a $scheme URI"); 1938 "Cannot extract a file path from a $scheme URI");
2508 } 1939 }
2509 if (query != "") { 1940 if (query != "") {
2510 throw new UnsupportedError( 1941 throw new UnsupportedError(
2511 "Cannot extract a file path from a URI with a query component"); 1942 "Cannot extract a file path from a URI with a query component");
2512 } 1943 }
2513 if (fragment != "") { 1944 if (fragment != "") {
2514 throw new UnsupportedError( 1945 throw new UnsupportedError(
2515 "Cannot extract a file path from a URI with a fragment component"); 1946 "Cannot extract a file path from a URI with a fragment component");
2516 } 1947 }
2517 if (windows == null) windows = _isWindows; 1948 if (windows == null) windows = _isWindows;
2518 return windows ? _toWindowsFilePath(this) : _toFilePath(); 1949 return windows ? _toWindowsFilePath() : _toFilePath();
2519 } 1950 }
2520 1951
2521 String _toFilePath() { 1952 String _toFilePath() {
2522 if (hasAuthority && host != "") { 1953 if (host != "") {
2523 throw new UnsupportedError( 1954 throw new UnsupportedError(
2524 "Cannot extract a non-Windows file path from a file URI " 1955 "Cannot extract a non-Windows file path from a file URI "
2525 "with an authority"); 1956 "with an authority");
2526 } 1957 }
2527 // Use path segments to have any escapes unescaped.
2528 var pathSegments = this.pathSegments;
2529 _checkNonWindowsPathReservedCharacters(pathSegments, false); 1958 _checkNonWindowsPathReservedCharacters(pathSegments, false);
2530 var result = new StringBuffer(); 1959 var result = new StringBuffer();
2531 if (hasAbsolutePath) result.write("/"); 1960 if (_isPathAbsolute) result.write("/");
2532 result.writeAll(pathSegments, "/"); 1961 result.writeAll(pathSegments, "/");
2533 return result.toString(); 1962 return result.toString();
2534 } 1963 }
2535 1964
2536 static String _toWindowsFilePath(Uri uri) { 1965 String _toWindowsFilePath() {
2537 bool hasDriveLetter = false; 1966 bool hasDriveLetter = false;
2538 var segments = uri.pathSegments; 1967 var segments = pathSegments;
2539 if (segments.length > 0 && 1968 if (segments.length > 0 &&
2540 segments[0].length == 2 && 1969 segments[0].length == 2 &&
2541 segments[0].codeUnitAt(1) == _COLON) { 1970 segments[0].codeUnitAt(1) == _COLON) {
2542 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false); 1971 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
2543 _checkWindowsPathReservedCharacters(segments, false, 1); 1972 _checkWindowsPathReservedCharacters(segments, false, 1);
2544 hasDriveLetter = true; 1973 hasDriveLetter = true;
2545 } else { 1974 } else {
2546 _checkWindowsPathReservedCharacters(segments, false, 0); 1975 _checkWindowsPathReservedCharacters(segments, false);
2547 } 1976 }
2548 var result = new StringBuffer(); 1977 var result = new StringBuffer();
2549 if (uri.hasAbsolutePath && !hasDriveLetter) result.write(r"\"); 1978 if (_isPathAbsolute && !hasDriveLetter) result.write("\\");
2550 if (uri.hasAuthority) { 1979 if (host != "") {
2551 var host = uri.host; 1980 result.write("\\");
2552 if (host.isNotEmpty) { 1981 result.write(host);
2553 result.write(r"\"); 1982 result.write("\\");
2554 result.write(host); 1983 }
2555 result.write(r"\"); 1984 result.writeAll(segments, "\\");
2556 } 1985 if (hasDriveLetter && segments.length == 1) result.write("\\");
2557 }
2558 result.writeAll(segments, r"\");
2559 if (hasDriveLetter && segments.length == 1) result.write(r"\");
2560 return result.toString(); 1986 return result.toString();
2561 } 1987 }
2562 1988
2563 bool get _isPathAbsolute { 1989 bool get _isPathAbsolute {
2564 return _path != null && _path.startsWith('/'); 1990 if (path == null || path.isEmpty) return false;
1991 return path.startsWith('/');
2565 } 1992 }
2566 1993
2567 void _writeAuthority(StringSink ss) { 1994 void _writeAuthority(StringSink ss) {
2568 if (_userInfo.isNotEmpty) { 1995 if (_userInfo.isNotEmpty) {
2569 ss.write(_userInfo); 1996 ss.write(_userInfo);
2570 ss.write("@"); 1997 ss.write("@");
2571 } 1998 }
2572 if (_host != null) ss.write(_host); 1999 if (_host != null) ss.write(_host);
2573 if (_port != null) { 2000 if (_port != null) {
2574 ss.write(":"); 2001 ss.write(":");
2575 ss.write(_port); 2002 ss.write(_port);
2576 } 2003 }
2577 } 2004 }
2578 2005
2579 /** 2006 /**
2580 * Access the structure of a `data:` URI. 2007 * Access the structure of a `data:` URI.
2581 * 2008 *
2582 * Returns a [UriData] object for `data:` URIs and `null` for all other 2009 * Returns a [UriData] object for `data:` URIs and `null` for all other
2583 * URIs. 2010 * URIs.
2584 * The [UriData] object can be used to access the media type and data 2011 * The [UriData] object can be used to access the media type and data
2585 * of a `data:` URI. 2012 * of a `data:` URI.
2586 */ 2013 */
2587 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null; 2014 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
2588 2015
2589 String toString() { 2016 String toString() {
2590 return _text ??= _initializeText();
2591 }
2592
2593 String _initializeText() {
2594 assert(_text == null);
2595 StringBuffer sb = new StringBuffer(); 2017 StringBuffer sb = new StringBuffer();
2596 if (scheme.isNotEmpty) sb..write(scheme)..write(":"); 2018 _addIfNonEmpty(sb, scheme, scheme, ':');
2597 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { 2019 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
2598 // File URIS always have the authority, even if it is empty. 2020 // File URIS always have the authority, even if it is empty.
2599 // The empty URI means "localhost". 2021 // The empty URI means "localhost".
2600 sb.write("//"); 2022 sb.write("//");
2601 _writeAuthority(sb); 2023 _writeAuthority(sb);
2602 } 2024 }
2603 sb.write(path); 2025 sb.write(path);
2604 if (_query != null) sb..write("?")..write(_query); 2026 if (_query != null) { sb..write("?")..write(_query); }
2605 if (_fragment != null) sb..write("#")..write(_fragment); 2027 if (_fragment != null) { sb..write("#")..write(_fragment); }
2606 return sb.toString(); 2028 return sb.toString();
2607 } 2029 }
2608 2030
2609 bool operator==(other) { 2031 bool operator==(other) {
2610 if (identical(this, other)) return true; 2032 if (other is! Uri) return false;
2611 if (other is Uri) { 2033 Uri uri = other;
2612 Uri uri = other; 2034 return scheme == uri.scheme &&
2613 return scheme == uri.scheme && 2035 hasAuthority == uri.hasAuthority &&
2614 hasAuthority == uri.hasAuthority && 2036 userInfo == uri.userInfo &&
2615 userInfo == uri.userInfo && 2037 host == uri.host &&
2616 host == uri.host && 2038 port == uri.port &&
2617 port == uri.port && 2039 path == uri.path &&
2618 path == uri.path && 2040 hasQuery == uri.hasQuery &&
2619 hasQuery == uri.hasQuery && 2041 query == uri.query &&
2620 query == uri.query && 2042 hasFragment == uri.hasFragment &&
2621 hasFragment == uri.hasFragment && 2043 fragment == uri.fragment;
2622 fragment == uri.fragment;
2623 }
2624 return false;
2625 } 2044 }
2626 2045
2627 int get hashCode { 2046 int get hashCode {
2628 return _hashCodeCache ??= toString().hashCode; 2047 int combine(part, current) {
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 });
2629 } 2212 }
2630 2213
2631 static List _createList() => []; 2214 static List _createList() => [];
2632 2215
2633 static Map _splitQueryStringAll( 2216 static Map _splitQueryStringAll(
2634 String query, {Encoding encoding: UTF8}) { 2217 String query, {Encoding encoding: UTF8}) {
2635 Map result = {}; 2218 Map result = {};
2636 int i = 0; 2219 int i = 0;
2637 int start = 0; 2220 int start = 0;
2638 int equalsIndex = -1; 2221 int equalsIndex = -1;
(...skipping 22 matching lines...) Expand all
2661 parsePair(start, equalsIndex, i); 2244 parsePair(start, equalsIndex, i);
2662 start = i + 1; 2245 start = i + 1;
2663 equalsIndex = -1; 2246 equalsIndex = -1;
2664 } 2247 }
2665 i++; 2248 i++;
2666 } 2249 }
2667 parsePair(start, equalsIndex, i); 2250 parsePair(start, equalsIndex, i);
2668 return result; 2251 return result;
2669 } 2252 }
2670 2253
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
2671 external static String _uriEncode(List<int> canonicalTable, 2423 external static String _uriEncode(List<int> canonicalTable,
2672 String text, 2424 String text,
2673 Encoding encoding, 2425 Encoding encoding,
2674 bool spaceToPlus); 2426 bool spaceToPlus);
2675 2427
2676 /** 2428 /**
2677 * Convert a byte (2 character hex sequence) in string [s] starting 2429 * Convert a byte (2 character hex sequence) in string [s] starting
2678 * at position [pos] to its ordinal value 2430 * at position [pos] to its ordinal value
2679 */ 2431 */
2680 static int _hexCharPairToByte(String s, int pos) { 2432 static int _hexCharPairToByte(String s, int pos) {
(...skipping 501 matching lines...) Expand 10 before | Expand all | Expand 10 after
3182 mimeType = ""; 2934 mimeType = "";
3183 } 2935 }
3184 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) { 2936 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
3185 buffer.write(mimeType); // Common cases need no escaping. 2937 buffer.write(mimeType); // Common cases need no escaping.
3186 } else { 2938 } else {
3187 int slashIndex = _validateMimeType(mimeType); 2939 int slashIndex = _validateMimeType(mimeType);
3188 if (slashIndex < 0) { 2940 if (slashIndex < 0) {
3189 throw new ArgumentError.value(mimeType, "mimeType", 2941 throw new ArgumentError.value(mimeType, "mimeType",
3190 "Invalid MIME type"); 2942 "Invalid MIME type");
3191 } 2943 }
3192 buffer.write(_Uri._uriEncode(_tokenCharTable, 2944 buffer.write(Uri._uriEncode(_tokenCharTable,
3193 mimeType.substring(0, slashIndex), 2945 mimeType.substring(0, slashIndex),
3194 UTF8, false)); 2946 UTF8, false));
3195 buffer.write("/"); 2947 buffer.write("/");
3196 buffer.write(_Uri._uriEncode(_tokenCharTable, 2948 buffer.write(Uri._uriEncode(_tokenCharTable,
3197 mimeType.substring(slashIndex + 1), 2949 mimeType.substring(slashIndex + 1),
3198 UTF8, false)); 2950 UTF8, false));
3199 } 2951 }
3200 if (charsetName != null) { 2952 if (charsetName != null) {
3201 if (indices != null) { 2953 if (indices != null) {
3202 indices..add(buffer.length) 2954 indices..add(buffer.length)
3203 ..add(buffer.length + 8); 2955 ..add(buffer.length + 8);
3204 } 2956 }
3205 buffer.write(";charset="); 2957 buffer.write(";charset=");
3206 buffer.write(_Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false)); 2958 buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
3207 } 2959 }
3208 parameters?.forEach((var key, var value) { 2960 parameters?.forEach((var key, var value) {
3209 if (key.isEmpty) { 2961 if (key.isEmpty) {
3210 throw new ArgumentError.value("", "Parameter names must not be empty"); 2962 throw new ArgumentError.value("", "Parameter names must not be empty");
3211 } 2963 }
3212 if (value.isEmpty) { 2964 if (value.isEmpty) {
3213 throw new ArgumentError.value("", "Parameter values must not be empty", 2965 throw new ArgumentError.value("", "Parameter values must not be empty",
3214 'parameters["$key"]'); 2966 'parameters["$key"]');
3215 } 2967 }
3216 if (indices != null) indices.add(buffer.length); 2968 if (indices != null) indices.add(buffer.length);
3217 buffer.write(';'); 2969 buffer.write(';');
3218 // Encode any non-RFC2045-token character and both '%' and '#'. 2970 // Encode any non-RFC2045-token character and both '%' and '#'.
3219 buffer.write(_Uri._uriEncode(_tokenCharTable, key, UTF8, false)); 2971 buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false));
3220 if (indices != null) indices.add(buffer.length); 2972 if (indices != null) indices.add(buffer.length);
3221 buffer.write('='); 2973 buffer.write('=');
3222 buffer.write(_Uri._uriEncode(_tokenCharTable, value, UTF8, false)); 2974 buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
3223 }); 2975 });
3224 } 2976 }
3225 2977
3226 /** 2978 /**
3227 * Checks mimeType is valid-ish (`token '/' token`). 2979 * Checks mimeType is valid-ish (`token '/' token`).
3228 * 2980 *
3229 * Returns the index of the slash, or -1 if the mime type is not 2981 * Returns the index of the slash, or -1 if the mime type is not
3230 * considered valid. 2982 * considered valid.
3231 * 2983 *
3232 * Currently only looks for slashes, all other characters will be 2984 * Currently only looks for slashes, all other characters will be
3233 * percent-encoded as UTF-8 if necessary. 2985 * percent-encoded as UTF-8 if necessary.
3234 */ 2986 */
3235 static int _validateMimeType(String mimeType) { 2987 static int _validateMimeType(String mimeType) {
3236 int slashIndex = -1; 2988 int slashIndex = -1;
3237 for (int i = 0; i < mimeType.length; i++) { 2989 for (int i = 0; i < mimeType.length; i++) {
3238 var char = mimeType.codeUnitAt(i); 2990 var char = mimeType.codeUnitAt(i);
3239 if (char != _SLASH) continue; 2991 if (char != Uri._SLASH) continue;
3240 if (slashIndex < 0) { 2992 if (slashIndex < 0) {
3241 slashIndex = i; 2993 slashIndex = i;
3242 continue; 2994 continue;
3243 } 2995 }
3244 return -1; 2996 return -1;
3245 } 2997 }
3246 return slashIndex; 2998 return slashIndex;
3247 } 2999 }
3248 3000
3249 /** 3001 /**
3250 * Parses a string as a `data` URI. 3002 * Parses a string as a `data` URI.
3251 * 3003 *
3252 * The string must have the format: 3004 * The string must have the format:
3253 * 3005 *
3254 * ``` 3006 * ```
3255 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a 3007 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a
3256 * ```` 3008 * ````
3257 * 3009 *
3258 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045, 3010 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
3259 * and `data` is a sequence of URI-characters (RFC-2396 `uric`). 3011 * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
3260 * 3012 *
3261 * This means that all the characters must be ASCII, but the URI may contain 3013 * This means that all the characters must be ASCII, but the URI may contain
3262 * percent-escapes for non-ASCII byte values that need an interpretation 3014 * percent-escapes for non-ASCII byte values that need an interpretation
3263 * to be converted to the corresponding string. 3015 * to be converted to the corresponding string.
3264 * 3016 *
3265 * Parsing doesn't check the validity of any part, it just checks that the 3017 * Parsing doesn't check the validity of any part, it just checks that the
3266 * input has the correct structure with the correct sequence of `/`, `;`, `=` 3018 * input has the correct structure with the correct sequence of `/`, `;`, `=`
3267 * and `,` delimiters. 3019 * and `,` delimiters.
3268 * 3020 *
3269 * Accessing the individual parts may fail later if they turn out to have 3021 * Accessing the individual parts may fail later if they turn out to have
3270 * content that can't be decoded successfully as a string. 3022 * content that can't be decoded sucessfully as a string.
3271 */ 3023 */
3272 static UriData parse(String uri) { 3024 static UriData parse(String uri) {
3273 if (uri.length >= 5) { 3025 if (!uri.startsWith("data:")) {
3274 int dataDelta = _startsWithData(uri, 0); 3026 throw new FormatException("Does not start with 'data:'", uri, 0);
3275 if (dataDelta == 0) {
3276 // Exact match on "data:".
3277 return _parse(uri, 5, null);
3278 }
3279 if (dataDelta == 0x20) {
3280 // Starts with a non-normalized "data" scheme containing upper-case
3281 // letters. Parse anyway, but throw away the scheme.
3282 return _parse(uri.substring(5), 0, null);
3283 }
3284 } 3027 }
3285 throw new FormatException("Does not start with 'data:'", uri, 0); 3028 return _parse(uri, 5, null);
3286 } 3029 }
3287 3030
3288 /** 3031 /**
3289 * The [Uri] that this `UriData` is giving access to. 3032 * The [Uri] that this `UriData` is giving access to.
3290 * 3033 *
3291 * Returns a `Uri` with scheme `data` and the remainder of the data URI 3034 * Returns a `Uri` with scheme `data` and the remainder of the data URI
3292 * as path. 3035 * as path.
3293 */ 3036 */
3294 Uri get uri { 3037 Uri get uri {
3295 if (_uriCache != null) return _uriCache; 3038 if (_uriCache != null) return _uriCache;
3296 String path = _text; 3039 String path = _text;
3297 String query = null; 3040 String query = null;
3298 int colonIndex = _separatorIndices[0]; 3041 int colonIndex = _separatorIndices[0];
3299 int queryIndex = _text.indexOf('?', colonIndex + 1); 3042 int queryIndex = _text.indexOf('?', colonIndex + 1);
3300 int end = null; 3043 int end = null;
3301 if (queryIndex >= 0) { 3044 if (queryIndex >= 0) {
3302 query = _text.substring(queryIndex + 1); 3045 query = _text.substring(queryIndex + 1);
3303 end = queryIndex; 3046 end = queryIndex;
3304 } 3047 }
3305 path = _text.substring(colonIndex + 1, end); 3048 path = _text.substring(colonIndex + 1, end);
3306 // TODO(lrn): This can generate a URI that isn't path normalized. 3049 // TODO(lrn): This can generate a URI that isn't path normalized.
3307 // That's perfectly reasonable - data URIs are not hierarchical, 3050 // That's perfectly reasonable - data URIs are not hierarchical,
3308 // but it may make some consumers stumble. 3051 // but it may make some consumers stumble.
3309 // Should we at least do escape normalization? 3052 // Should we at least do escape normalization?
3310 _uriCache = new _Uri._internal("data", "", null, null, path, query, null); 3053 _uriCache = new Uri._internal("data", "", null, null, path, query, null);
3311 return _uriCache; 3054 return _uriCache;
3312 } 3055 }
3313 3056
3314 /** 3057 /**
3315 * The MIME type of the data URI. 3058 * The MIME type of the data URI.
3316 * 3059 *
3317 * A data URI consists of a "media type" followed by data. 3060 * A data URI consists of a "media type" followed by data.
3318 * The media type starts with a MIME type and can be followed by 3061 * The media type starts with a MIME type and can be followed by
3319 * extra parameters. 3062 * extra parameters.
3320 * 3063 *
3321 * Example: 3064 * Example:
3322 * 3065 *
3323 * data:text/plain;charset=utf-8,Hello%20World! 3066 * data:text/plain;charset=utf-8,Hello%20World!
3324 * 3067 *
3325 * This data URI has the media type `text/plain;charset=utf-8`, which is the 3068 * This data URI has the media type `text/plain;charset=utf-8`, which is the
3326 * MIME type `text/plain` with the parameter `charset` with value `utf-8`. 3069 * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
3327 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail. 3070 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
3328 * 3071 *
3329 * If the first part of the data URI is empty, it defaults to `text/plain`. 3072 * If the first part of the data URI is empty, it defaults to `text/plain`.
3330 */ 3073 */
3331 String get mimeType { 3074 String get mimeType {
3332 int start = _separatorIndices[0] + 1; 3075 int start = _separatorIndices[0] + 1;
3333 int end = _separatorIndices[1]; 3076 int end = _separatorIndices[1];
3334 if (start == end) return "text/plain"; 3077 if (start == end) return "text/plain";
3335 return _Uri._uriDecode(_text, start, end, UTF8, false); 3078 return Uri._uriDecode(_text, start, end, UTF8, false);
3336 } 3079 }
3337 3080
3338 /** 3081 /**
3339 * The charset parameter of the media type. 3082 * The charset parameter of the media type.
3340 * 3083 *
3341 * If the parameters of the media type contains a `charset` parameter 3084 * If the parameters of the media type contains a `charset` parameter
3342 * then this returns its value, otherwise it returns `US-ASCII`, 3085 * then this returns its value, otherwise it returns `US-ASCII`,
3343 * which is the default charset for data URIs. 3086 * which is the default charset for data URIs.
3344 */ 3087 */
3345 String get charset { 3088 String get charset {
3346 int parameterStart = 1; 3089 int parameterStart = 1;
3347 int parameterEnd = _separatorIndices.length - 1; // The ',' before data. 3090 int parameterEnd = _separatorIndices.length - 1; // The ',' before data.
3348 if (isBase64) { 3091 if (isBase64) {
3349 // There is a ";base64" separator, so subtract one for that as well. 3092 // There is a ";base64" separator, so subtract one for that as well.
3350 parameterEnd -= 1; 3093 parameterEnd -= 1;
3351 } 3094 }
3352 for (int i = parameterStart; i < parameterEnd; i += 2) { 3095 for (int i = parameterStart; i < parameterEnd; i += 2) {
3353 var keyStart = _separatorIndices[i] + 1; 3096 var keyStart = _separatorIndices[i] + 1;
3354 var keyEnd = _separatorIndices[i + 1]; 3097 var keyEnd = _separatorIndices[i + 1];
3355 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) { 3098 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
3356 return _Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2], 3099 return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
3357 UTF8, false); 3100 UTF8, false);
3358 } 3101 }
3359 } 3102 }
3360 return "US-ASCII"; 3103 return "US-ASCII";
3361 } 3104 }
3362 3105
3363 /** 3106 /**
3364 * Whether the data is Base64 encoded or not. 3107 * Whether the data is Base64 encoded or not.
3365 */ 3108 */
3366 bool get isBase64 => _separatorIndices.length.isOdd; 3109 bool get isBase64 => _separatorIndices.length.isOdd;
3367 3110
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
3405 result.setRange(0, length, text.codeUnits, start); 3148 result.setRange(0, length, text.codeUnits, start);
3406 return result; 3149 return result;
3407 } 3150 }
3408 int index = 0; 3151 int index = 0;
3409 for (int i = start; i < text.length; i++) { 3152 for (int i = start; i < text.length; i++) {
3410 var codeUnit = text.codeUnitAt(i); 3153 var codeUnit = text.codeUnitAt(i);
3411 if (codeUnit != percent) { 3154 if (codeUnit != percent) {
3412 result[index++] = codeUnit; 3155 result[index++] = codeUnit;
3413 } else { 3156 } else {
3414 if (i + 2 < text.length) { 3157 if (i + 2 < text.length) {
3415 var digit1 = _Uri._parseHexDigit(text.codeUnitAt(i + 1)); 3158 var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1));
3416 var digit2 = _Uri._parseHexDigit(text.codeUnitAt(i + 2)); 3159 var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2));
3417 if (digit1 >= 0 && digit2 >= 0) { 3160 if (digit1 >= 0 && digit2 >= 0) {
3418 int byte = digit1 * 16 + digit2; 3161 int byte = digit1 * 16 + digit2;
3419 result[index++] = byte; 3162 result[index++] = byte;
3420 i += 2; 3163 i += 2;
3421 continue; 3164 continue;
3422 } 3165 }
3423 } 3166 }
3424 throw new FormatException("Invalid percent escape", text, i); 3167 throw new FormatException("Invalid percent escape", text, i);
3425 } 3168 }
3426 } 3169 }
3427 assert(index == result.length); 3170 assert(index == result.length);
3428 return result; 3171 return result;
3429 } 3172 }
3430 3173
3431 /** 3174 /**
3432 * Returns a string created from the content of the data URI. 3175 * Returns a string created from the content of the data URI.
3433 * 3176 *
3434 * If the content is Base64 encoded, it will be decoded to bytes and then 3177 * If the content is Base64 encoded, it will be decoded to bytes and then
3435 * decoded to a string using [encoding]. 3178 * decoded to a string using [encoding].
3436 * If encoding is omitted, the value of a `charset` parameter is used 3179 * If encoding is omitted, the value of a `charset` parameter is used
3437 * if it is recognized by [Encoding.getByName], otherwise it defaults to 3180 * if it is recongized by [Encoding.getByName], otherwise it defaults to
3438 * the [ASCII] encoding, which is the default encoding for data URIs 3181 * the [ASCII] encoding, which is the default encoding for data URIs
3439 * that do not specify an encoding. 3182 * that do not specify an encoding.
3440 * 3183 *
3441 * If the content is not Base64 encoded, it will first have percent-escapes 3184 * If the content is not Base64 encoded, it will first have percent-escapes
3442 * converted to bytes and then the character codes and byte values are 3185 * converted to bytes and then the character codes and byte values are
3443 * decoded using [encoding]. 3186 * decoded using [encoding].
3444 */ 3187 */
3445 String contentAsString({Encoding encoding}) { 3188 String contentAsString({Encoding encoding}) {
3446 if (encoding == null) { 3189 if (encoding == null) {
3447 var charset = this.charset; // Returns "US-ASCII" if not present. 3190 var charset = this.charset; // Returns "US-ASCII" if not present.
3448 encoding = Encoding.getByName(charset); 3191 encoding = Encoding.getByName(charset);
3449 if (encoding == null) { 3192 if (encoding == null) {
3450 throw new UnsupportedError("Unknown charset: $charset"); 3193 throw new UnsupportedError("Unknown charset: $charset");
3451 } 3194 }
3452 } 3195 }
3453 String text = _text; 3196 String text = _text;
3454 int start = _separatorIndices.last + 1; 3197 int start = _separatorIndices.last + 1;
3455 if (isBase64) { 3198 if (isBase64) {
3456 var converter = BASE64.decoder.fuse(encoding.decoder); 3199 var converter = BASE64.decoder.fuse(encoding.decoder);
3457 return converter.convert(text.substring(start)); 3200 return converter.convert(text.substring(start));
3458 } 3201 }
3459 return _Uri._uriDecode(text, start, text.length, encoding, false); 3202 return Uri._uriDecode(text, start, text.length, encoding, false);
3460 } 3203 }
3461 3204
3462 /** 3205 /**
3463 * A map representing the parameters of the media type. 3206 * A map representing the parameters of the media type.
3464 * 3207 *
3465 * A data URI may contain parameters between the MIME type and the 3208 * A data URI may contain parameters between the MIME type and the
3466 * data. This converts these parameters to a map from parameter name 3209 * data. This converts these parameters to a map from parameter name
3467 * to parameter value. 3210 * to parameter value.
3468 * The map only contains parameters that actually occur in the URI. 3211 * The map only contains parameters that actually occur in the URI.
3469 * The `charset` parameter has a default value even if it doesn't occur 3212 * The `charset` parameter has a default value even if it doesn't occur
3470 * in the URI, which is reflected by the [charset] getter. This means that 3213 * in the URI, which is reflected by the [charset] getter. This means that
3471 * [charset] may return a value even if `parameters["charset"]` is `null`. 3214 * [charset] may return a value even if `parameters["charset"]` is `null`.
3472 * 3215 *
3473 * If the values contain non-ASCII values or percent escapes, they default 3216 * If the values contain non-ASCII values or percent escapes, they default
3474 * to being decoded as UTF-8. 3217 * to being decoded as UTF-8.
3475 */ 3218 */
3476 Map<String, String> get parameters { 3219 Map<String, String> get parameters {
3477 var result = <String, String>{}; 3220 var result = <String, String>{};
3478 for (int i = 3; i < _separatorIndices.length; i += 2) { 3221 for (int i = 3; i < _separatorIndices.length; i += 2) {
3479 var start = _separatorIndices[i - 2] + 1; 3222 var start = _separatorIndices[i - 2] + 1;
3480 var equals = _separatorIndices[i - 1]; 3223 var equals = _separatorIndices[i - 1];
3481 var end = _separatorIndices[i]; 3224 var end = _separatorIndices[i];
3482 String key = _Uri._uriDecode(_text, start, equals, UTF8, false); 3225 String key = Uri._uriDecode(_text, start, equals, UTF8, false);
3483 String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false); 3226 String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false);
3484 result[key] = value; 3227 result[key] = value;
3485 } 3228 }
3486 return result; 3229 return result;
3487 } 3230 }
3488 3231
3489 static UriData _parse(String text, int start, Uri sourceUri) { 3232 static UriData _parse(String text, int start, Uri sourceUri) {
3490 assert(start == 0 || start == 5); 3233 assert(start == 0 || start == 5);
3491 assert((start == 5) == text.startsWith("data:")); 3234 assert((start == 5) == text.startsWith("data:"));
3492 3235
3493 /// Character codes. 3236 /// Character codes.
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
3556 // Encode the string into bytes then generate an ASCII only string 3299 // Encode the string into bytes then generate an ASCII only string
3557 // by percent encoding selected bytes. 3300 // by percent encoding selected bytes.
3558 int byteOr = 0; 3301 int byteOr = 0;
3559 for (int i = 0; i < bytes.length; i++) { 3302 for (int i = 0; i < bytes.length; i++) {
3560 int byte = bytes[i]; 3303 int byte = bytes[i];
3561 byteOr |= byte; 3304 byteOr |= byte;
3562 if (byte < 128 && 3305 if (byte < 128 &&
3563 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) { 3306 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
3564 buffer.writeCharCode(byte); 3307 buffer.writeCharCode(byte);
3565 } else { 3308 } else {
3566 buffer.writeCharCode(_PERCENT); 3309 buffer.writeCharCode(Uri._PERCENT);
3567 buffer.writeCharCode(_hexDigits.codeUnitAt(byte >> 4)); 3310 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
3568 buffer.writeCharCode(_hexDigits.codeUnitAt(byte & 0x0f)); 3311 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
3569 } 3312 }
3570 } 3313 }
3571 if ((byteOr & ~0xFF) != 0) { 3314 if ((byteOr & ~0xFF) != 0) {
3572 for (int i = 0; i < bytes.length; i++) { 3315 for (int i = 0; i < bytes.length; i++) {
3573 var byte = bytes[i]; 3316 var byte = bytes[i];
3574 if (byte < 0 || byte > 255) { 3317 if (byte < 0 || byte > 255) {
3575 throw new ArgumentError.value(byte, "non-byte value"); 3318 throw new ArgumentError.value(byte, "non-byte value");
3576 } 3319 }
3577 } 3320 }
3578 } 3321 }
(...skipping 28 matching lines...) Expand all
3607 0x7fff]; // 0x70 - 0x7f 11111111 11111110 3350 0x7fff]; // 0x70 - 0x7f 11111111 11111110
3608 3351
3609 // All non-escape RFC-2396 uric characters. 3352 // All non-escape RFC-2396 uric characters.
3610 // 3353 //
3611 // uric = reserved | unreserved | escaped 3354 // uric = reserved | unreserved | escaped
3612 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," 3355 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3613 // unreserved = alphanum | mark 3356 // unreserved = alphanum | mark
3614 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 3357 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3615 // 3358 //
3616 // This is the same characters as in a URI query (which is URI pchar plus '?') 3359 // This is the same characters as in a URI query (which is URI pchar plus '?')
3617 static const _uricTable = _Uri._queryCharTable; 3360 static const _uricTable = Uri._queryCharTable;
3618 } 3361 }
3619
3620 // --------------------------------------------------------------------
3621 // Constants used to read the scanner result.
3622 // The indices points into the table filled by [_scan] which contains
3623 // recognized positions in the scanned URI.
3624 // The `0` index is only used internally.
3625
3626 /// Index of the position of that `:` after a scheme.
3627 const int _schemeEndIndex = 1;
3628 /// Index of the position of the character just before the host name.
3629 const int _hostStartIndex = 2;
3630 /// Index of the position of the `:` before a port value.
3631 const int _portStartIndex = 3;
3632 /// Index of the position of the first character of a path.
3633 const int _pathStartIndex = 4;
3634 /// Index of the position of the `?` before a query.
3635 const int _queryStartIndex = 5;
3636 /// Index of the position of the `#` before a fragment.
3637 const int _fragmentStartIndex = 6;
3638 /// Index of a position where the URI was determined to be "non-simple".
3639 const int _notSimpleIndex = 7;
3640
3641 // Initial state for scanner.
3642 const int _uriStart = 00;
3643
3644 // If scanning of a URI terminates in this state or above,
3645 // consider the URI non-simple
3646 const int _nonSimpleEndStates = 14;
3647
3648 // Initial state for scheme validation.
3649 const int _schemeStart = 20;
3650
3651 /// Transition tables used to scan a URI to determine its structure.
3652 ///
3653 /// The tables represent a state machine with output.
3654 ///
3655 /// To scan the URI, start in the [_uriStart] state, then read each character
3656 /// of the URI in order, from start to end, and for each character perform a
3657 /// transition to a new state while writing the current position into the output
3658 /// buffer at a designated index.
3659 ///
3660 /// Each state, represented by an integer which is an index into
3661 /// [_scannerTables], has a set of transitions, one for each character.
3662 /// The transitions are encoded as a 5-bit integer representing the next state
3663 /// and a 3-bit index into the output table.
3664 ///
3665 /// For URI scanning, only characters in the range U+0020 through U+007E are
3666 /// interesting, all characters outside that range are treated the same.
3667 /// The tables only contain 96 entries, representing that characters in the
3668 /// interesting range, plus one more to represent all values outside the range.
3669 /// The character entries are stored in one `Uint8List` per state, with the
3670 /// transition for a character at position `character ^ 0x60`,
3671 /// which maps the range U+0020 .. U+007F into positions 0 .. 95.
3672 /// All remaining characters are mapped to position 31 (`0x7f ^ 0x60`) which
3673 /// represents the transition for all remaining characters.
3674 final List<Uint8List> _scannerTables = _createTables();
3675
3676 // ----------------------------------------------------------------------
3677 // Code to create the URI scanner table.
3678
3679 /// Creates the tables for [_scannerTables] used by [Uri.parse].
3680 ///
3681 /// See [_scannerTables] for the generated format.
3682 ///
3683 /// The concrete tables are chosen as a trade-off between the number of states
3684 /// needed and the precision of the result.
3685 /// This allows definitely recognizing the general structure of the URI
3686 /// (presence and location of scheme, user-info, host, port, path, query and
3687 /// fragment) while at the same time detecting that some components are not
3688 /// in canonical form (anything containing a `%`, a host-name containing a
3689 /// capital letter). Since the scanner doesn't know whether something is a
3690 /// scheme or a path until it sees `:`, or user-info or host until it sees
3691 /// a `@`, a second pass is needed to validate the scheme and any user-info
3692 /// is considered non-canonical by default.
3693 ///
3694 /// The states (starting from [_uriStart]) write positions while scanning
3695 /// a string from `start` to `end` as follows:
3696 ///
3697 /// - [_schemeEndIndex]: Should be initialized to `start-1`.
3698 /// If the URI has a scheme, it is set to the position of the `:` after
3699 /// the scheme.
3700 /// - [_hostStartIndex]: Should be initialized to `start - 1`.
3701 /// If the URI has an authority, it is set to the character before the
3702 /// host name - either the second `/` in the `//` leading the authority,
3703 /// or the `@` after a user-info. Comparing this value to the scheme end
3704 /// position can be used to detect that there is a user-info component.
3705 /// - [_portStartIndex]: Should be initialized to `start`.
3706 /// Set to the position of the last `:` in an authority, and unchanged
3707 /// if there is no authority or no `:` in an authority.
3708 /// If this position is after the host start, there is a port, otherwise it
3709 /// is just marking a colon in the user-info component.
3710 /// - [_pathStartIndex]: Should be initialized to `start`.
3711 /// Is set to the first path character unless the path is empty.
3712 /// If the path is empty, the position is either unchanged (`start`) or
3713 /// the first slash of an authority. So, if the path start is before a
3714 /// host start or scheme end, the path is empty.
3715 /// - [_queryStartIndex]: Should be initialized to `end`.
3716 /// The position of the `?` leading a query if the URI contains a query.
3717 /// - [_fragmentStartIndex]: Should be initialized to `end`.
3718 /// The position of the `#` leading a fragment if the URI contains a fragment.
3719 /// - [_notSimpleIndex]: Should be initialized to `start - 1`.
3720 /// Set to another value if the URI is considered "not simple".
3721 /// This is elaborated below.
3722 ///
3723 /// # Simple URIs
3724 /// A URI is considered "simple" if it is in a normalized form containing no
3725 /// escapes. This allows us to skip normalization and checking whether escapes
3726 /// are valid, and to extract components without worrying about unescaping.
3727 ///
3728 /// The scanner computes a conservative approximation of being "simple".
3729 /// It rejects any URI with an escape, with a user-info component (mainly
3730 /// because they are rare and would increase the number of states in the
3731 /// scanner significantly), with an IPV6 host or with a capital letter in
3732 /// the scheme or host name (the scheme is handled in a second scan using
3733 /// a separate two-state table).
3734 /// Further, paths containing `..` or `.` path segments are considered
3735 /// non-simple except for pure relative paths (no scheme or authority) starting
3736 /// with a sequence of "../" segments.
3737 ///
3738 /// The transition tables cannot detect a trailing ".." in the path,
3739 /// followed by a query or fragment, because the segment is not known to be
3740 /// complete until we are past it, and we then need to store the query/fragment
3741 /// start instead. This cast is checked manually post-scanning (such a path
3742 /// needs to be normalized to end in "../", so the URI shouldn't be considered
3743 /// simple).
3744 List<Uint8List> _createTables() {
3745 // TODO(lrn): Use a precomputed table.
3746
3747 // Total number of states for the scanner.
3748 const int stateCount = 22;
3749
3750 // States used to scan a URI from scratch.
3751 const int schemeOrPath = 01;
3752 const int authOrPath = 02;
3753 const int authOrPathSlash = 03;
3754 const int uinfoOrHost0 = 04;
3755 const int uinfoOrHost = 05;
3756 const int uinfoOrPort0 = 06;
3757 const int uinfoOrPort = 07;
3758 const int ipv6Host = 08;
3759 const int relPathSeg = 09;
3760 const int pathSeg = 10;
3761 const int path = 11;
3762 const int query = 12;
3763 const int fragment = 13;
3764 const int schemeOrPathDot = 14;
3765 const int schemeOrPathDot2 = 15;
3766 const int relPathSegDot = 16;
3767 const int relPathSegDot2 = 17;
3768 const int pathSegDot = 18;
3769 const int pathSegDot2 = 19;
3770
3771 // States used to validate a scheme after its end position has been found.
3772 const int scheme0 = _schemeStart;
3773 const int scheme = 21;
3774
3775 // Constants encoding the write-index for the state transition into the top 5
3776 // bits of a byte.
3777 const int schemeEnd = _schemeEndIndex << 5;
3778 const int hostStart = _hostStartIndex << 5;
3779 const int portStart = _portStartIndex << 5;
3780 const int pathStart = _pathStartIndex << 5;
3781 const int queryStart = _queryStartIndex << 5;
3782 const int fragmentStart = _fragmentStartIndex << 5;
3783 const int notSimple = _notSimpleIndex << 5;
3784
3785 /// The `unreserved` characters of RFC 3986.
3786 const unreserved =
3787 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~" ;
3788 /// The `sub-delim` characters of RFC 3986.
3789 const subDelims = r"!$&'()*+,;=";
3790 // The `pchar` characters of RFC 3986: characters that may occur in a path,
3791 // excluding escapes.
3792 const pchar = "$unreserved$subDelims";
3793
3794 var tables = new List<Uint8List>.generate(stateCount,
3795 (_) => new Uint8List(96));
3796
3797 // Helper function which initialize the table for [state] with a default
3798 // transition and returns the table.
3799 Uint8List build(state, defaultTransition) =>
3800 tables[state]..fillRange(0, 96, defaultTransition);
3801
3802 // Helper function which sets the transition for each character in [chars]
3803 // to [transition] in the [target] table.
3804 // The [chars] string must contain only characters in the U+0020 .. U+007E
3805 // range.
3806 void setChars(Uint8List target, String chars, int transition) {
3807 for (int i = 0; i < chars.length; i++) {
3808 var char = chars.codeUnitAt(i);
3809 target[char ^ 0x60] = transition;
3810 }
3811 }
3812
3813 /// Helper function which sets the transition for all characters in the
3814 /// range from `range[0]` to `range[1]` to [transition] in the [target] table.
3815 ///
3816 /// The [range] must be a two-character string where both characters are in
3817 /// the U+0020 .. U+007E range and the former character must have a lower
3818 /// code point than the latter.
3819 void setRange(Uint8List target, String range, int transition) {
3820 for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) {
3821 target[i ^ 0x60] = transition;
3822 }
3823 }
3824
3825 // Create the transitions for each state.
3826 var b;
3827
3828 // Validate as path, if it is a scheme, we handle it later.
3829 b = build(_uriStart, schemeOrPath | notSimple);
3830 setChars(b, pchar, schemeOrPath);
3831 setChars(b, ".", schemeOrPathDot);
3832 setChars(b, ":", authOrPath | schemeEnd); // Handle later.
3833 setChars(b, "/", authOrPathSlash);
3834 setChars(b, "?", query | queryStart);
3835 setChars(b, "#", fragment | fragmentStart);
3836
3837 b = build(schemeOrPathDot, schemeOrPath | notSimple);
3838 setChars(b, pchar, schemeOrPath);
3839 setChars(b, ".", schemeOrPathDot2);
3840 setChars(b, ':', authOrPath | schemeEnd);
3841 setChars(b, "/", pathSeg | notSimple);
3842 setChars(b, "?", query | queryStart);
3843 setChars(b, "#", fragment | fragmentStart);
3844
3845 b = build(schemeOrPathDot2, schemeOrPath | notSimple);
3846 setChars(b, pchar, schemeOrPath);
3847 setChars(b, "%", schemeOrPath | notSimple);
3848 setChars(b, ':', authOrPath | schemeEnd);
3849 setChars(b, "/", relPathSeg);
3850 setChars(b, "?", query | queryStart);
3851 setChars(b, "#", fragment | fragmentStart);
3852
3853 b = build(schemeOrPath, schemeOrPath | notSimple);
3854 setChars(b, pchar, schemeOrPath);
3855 setChars(b, ':', authOrPath | schemeEnd);
3856 setChars(b, "/", pathSeg);
3857 setChars(b, "?", query | queryStart);
3858 setChars(b, "#", fragment | fragmentStart);
3859
3860 b = build(authOrPath, path | notSimple);
3861 setChars(b, pchar, path | pathStart);
3862 setChars(b, "/", authOrPathSlash | pathStart);
3863 setChars(b, ".", pathSegDot | pathStart);
3864 setChars(b, "?", query | queryStart);
3865 setChars(b, "#", fragment | fragmentStart);
3866
3867 b = build(authOrPathSlash, path | notSimple);
3868 setChars(b, pchar, path);
3869 setChars(b, "/", uinfoOrHost0 | hostStart);
3870 setChars(b, ".", pathSegDot);
3871 setChars(b, "?", query | queryStart);
3872 setChars(b, "#", fragment | fragmentStart);
3873
3874 b = build(uinfoOrHost0, uinfoOrHost | notSimple);
3875 setChars(b, pchar, uinfoOrHost);
3876 setRange(b, "AZ", uinfoOrHost | notSimple);
3877 setChars(b, ":", uinfoOrPort0 | portStart);
3878 setChars(b, "@", uinfoOrHost0 | hostStart);
3879 setChars(b, "[", ipv6Host | notSimple);
3880 setChars(b, "/", pathSeg | pathStart);
3881 setChars(b, "?", query | queryStart);
3882 setChars(b, "#", fragment | fragmentStart);
3883
3884 b = build(uinfoOrHost, uinfoOrHost | notSimple);
3885 setChars(b, pchar, uinfoOrHost);
3886 setRange(b, "AZ", uinfoOrHost | notSimple);
3887 setChars(b, ":", uinfoOrPort0 | portStart);
3888 setChars(b, "@", uinfoOrHost0 | hostStart);
3889 setChars(b, "/", pathSeg | pathStart);
3890 setChars(b, "?", query | queryStart);
3891 setChars(b, "#", fragment | fragmentStart);
3892
3893 b = build(uinfoOrPort0, uinfoOrPort | notSimple);
3894 setRange(b, "19", uinfoOrPort);
3895 setChars(b, "@", uinfoOrHost0 | hostStart);
3896 setChars(b, "/", pathSeg | pathStart);
3897 setChars(b, "?", query | queryStart);
3898 setChars(b, "#", fragment | fragmentStart);
3899
3900 b = build(uinfoOrPort, uinfoOrPort | notSimple);
3901 setRange(b, "09", uinfoOrPort);
3902 setChars(b, "@", uinfoOrHost0 | hostStart);
3903 setChars(b, "/", pathSeg | pathStart);
3904 setChars(b, "?", query | queryStart);
3905 setChars(b, "#", fragment | fragmentStart);
3906
3907 b = build(ipv6Host, ipv6Host);
3908 setChars(b, "]", uinfoOrHost);
3909
3910 b = build(relPathSeg, path | notSimple);
3911 setChars(b, pchar, path);
3912 setChars(b, ".", relPathSegDot);
3913 setChars(b, "/", pathSeg | notSimple);
3914 setChars(b, "?", query | queryStart);
3915 setChars(b, "#", fragment | fragmentStart);
3916
3917 b = build(relPathSegDot, path | notSimple);
3918 setChars(b, pchar, path);
3919 setChars(b, ".", relPathSegDot2);
3920 setChars(b, "/", pathSeg | notSimple);
3921 setChars(b, "?", query | queryStart);
3922 setChars(b, "#", fragment | fragmentStart);
3923
3924 b = build(relPathSegDot2, path | notSimple);
3925 setChars(b, pchar, path);
3926 setChars(b, "/", relPathSeg);
3927 setChars(b, "?", query | queryStart); // This should be non-simple.
3928 setChars(b, "#", fragment | fragmentStart); // This should be non-simple.
3929
3930 b = build(pathSeg, path | notSimple);
3931 setChars(b, pchar, path);
3932 setChars(b, ".", pathSegDot);
3933 setChars(b, "/", pathSeg | notSimple);
3934 setChars(b, "?", query | queryStart);
3935 setChars(b, "#", fragment | fragmentStart);
3936
3937 b = build(pathSegDot, path | notSimple);
3938 setChars(b, pchar, path);
3939 setChars(b, ".", pathSegDot2);
3940 setChars(b, "/", pathSeg | notSimple);
3941 setChars(b, "?", query | queryStart);
3942 setChars(b, "#", fragment | fragmentStart);
3943
3944 b = build(pathSegDot2, path | notSimple);
3945 setChars(b, pchar, path);
3946 setChars(b, "/", pathSeg | notSimple);
3947 setChars(b, "?", query | queryStart);
3948 setChars(b, "#", fragment | fragmentStart);
3949
3950 b = build(path, path | notSimple);
3951 setChars(b, pchar, path);
3952 setChars(b, "/", pathSeg);
3953 setChars(b, "?", query | queryStart);
3954 setChars(b, "#", fragment | fragmentStart);
3955
3956 b = build(query, query | notSimple);
3957 setChars(b, pchar, query);
3958 setChars(b, "?", query);
3959 setChars(b, "#", fragment | fragmentStart);
3960
3961 b = build(fragment, fragment | notSimple);
3962 setChars(b, pchar, fragment);
3963 setChars(b, "?", fragment);
3964
3965 // A separate two-state validator for lower-case scheme names.
3966 // Any non-scheme character or upper-case letter is marked as non-simple.
3967 b = build(scheme0, scheme | notSimple);
3968 setRange(b, "az", scheme);
3969
3970 b = build(scheme, scheme | notSimple);
3971 setRange(b, "az", scheme);
3972 setRange(b, "09", scheme);
3973 setChars(b, "+-.", scheme);
3974
3975 return tables;
3976 }
3977
3978 // --------------------------------------------------------------------
3979 // Code that uses the URI scanner table.
3980
3981 /// Scan a string using the [_scannerTables] state machine.
3982 ///
3983 /// Scans [uri] from [start] to [end], startig in state [state] and
3984 /// writing output into [indices].
3985 ///
3986 /// Returns the final state.
3987 int _scan(String uri, int start, int end, int state, List<int> indices) {
3988 var tables = _scannerTables;
3989 assert(end <= uri.length);
3990 for (int i = start; i < end; i++) {
3991 var table = tables[state];
3992 // Xor with 0x60 to move range 0x20-0x7f into 0x00-0x5f
3993 int char = uri.codeUnitAt(i) ^ 0x60;
3994 // Use 0x1f (nee 0x7f) to represent all unhandled characters.
3995 if (char > 0x5f) char = 0x1f;
3996 int transition = table[char];
3997 state = transition & 0x1f;
3998 indices[transition >> 5] = i;
3999 }
4000 return state;
4001 }
4002
4003 class _SimpleUri implements Uri {
4004 final String _uri;
4005 final int _schemeEnd;
4006 final int _hostStart;
4007 final int _portStart;
4008 final int _pathStart;
4009 final int _queryStart;
4010 final int _fragmentStart;
4011 /// The scheme is often used to distinguish URIs.
4012 /// To make comparisons more efficient, we cache the value, and
4013 /// canonicalize a few known types.
4014 String _schemeCache;
4015 int _hashCodeCache;
4016
4017 _SimpleUri(
4018 this._uri,
4019 this._schemeEnd,
4020 this._hostStart,
4021 this._portStart,
4022 this._pathStart,
4023 this._queryStart,
4024 this._fragmentStart,
4025 this._schemeCache);
4026
4027 bool get hasScheme => _schemeEnd > 0;
4028 bool get hasAuthority => _hostStart > 0;
4029 bool get hasUserInfo => _hostStart > _schemeEnd + 4;
4030 bool get hasPort => _hostStart > 0 && _portStart + 1 < _pathStart;
4031 bool get hasQuery => _queryStart < _fragmentStart;
4032 bool get hasFragment => _fragmentStart < _uri.length;
4033
4034 bool get _isFile => _schemeEnd == 4 && _uri.startsWith("file");
4035 bool get _isHttp => _schemeEnd == 4 && _uri.startsWith("http");
4036 bool get _isHttps => _schemeEnd == 5 && _uri.startsWith("https");
4037 bool get _isPackage => _schemeEnd == 7 && _uri.startsWith("package");
4038 bool _isScheme(String scheme) =>
4039 _schemeEnd == scheme.length && _uri.startsWith(scheme);
4040
4041 bool get hasAbsolutePath => _uri.startsWith("/", _pathStart);
4042 bool get hasEmptyPath => _pathStart == _queryStart;
4043
4044 bool get isAbsolute => hasScheme && !hasFragment;
4045
4046 String get scheme {
4047 if (_schemeEnd <= 0) return "";
4048 if (_schemeCache != null) return _schemeCache;
4049 if (_isHttp) {
4050 _schemeCache = "http";
4051 } else if (_isHttps) {
4052 _schemeCache = "https";
4053 } else if (_isFile) {
4054 _schemeCache = "file";
4055 } else if (_isPackage) {
4056 _schemeCache = "package";
4057 } else {
4058 _schemeCache = _uri.substring(0, _schemeEnd);
4059 }
4060 return _schemeCache;
4061 }
4062 String get authority => _hostStart > 0 ?
4063 _uri.substring(_schemeEnd + 3, _pathStart) : "";
4064 String get userInfo => (_hostStart > _schemeEnd + 3) ?
4065 _uri.substring(_schemeEnd + 3, _hostStart - 1) : "";
4066 String get host =>
4067 _hostStart > 0 ? _uri.substring(_hostStart, _portStart) : "";
4068 int get port {
4069 if (hasPort) return int.parse(_uri.substring(_portStart + 1, _pathStart));
4070 if (_isHttp) return 80;
4071 if (_isHttps) return 443;
4072 return 0;
4073 }
4074 String get path =>_uri.substring(_pathStart, _queryStart);
4075 String get query => (_queryStart < _fragmentStart) ?
4076 _uri.substring(_queryStart + 1, _fragmentStart) : "";
4077 String get fragment => (_fragmentStart < _uri.length) ?
4078 _uri.substring(_fragmentStart + 1) : "";
4079
4080 String get origin {
4081 // Check original behavior - W3C spec is wonky!
4082 bool isHttp = _isHttp;
4083 if (_schemeEnd < 0 || _hostStart == _portStart) {
4084 throw new StateError("Cannot use origin without a scheme: $this");
4085 }
4086 if (!isHttp && !_isHttps) {
4087 throw new StateError(
4088 "Origin is only applicable schemes http and https: $this");
4089 }
4090 if (_hostStart == _schemeEnd + 3) {
4091 return _uri.substring(0, _pathStart);
4092 }
4093 // Need to drop anon-empty userInfo.
4094 return _uri.substring(0, _schemeEnd + 3) +
4095 _uri.substring(_hostStart, _pathStart);
4096 }
4097
4098 List<String> get pathSegments {
4099 int start = _pathStart;
4100 int end = _queryStart;
4101 if (_uri.startsWith("/", start)) start++;
4102 if (start == end) return const <String>[];
4103 List<String> parts = [];
4104 for (int i = start; i < end; i++) {
4105 var char = _uri.codeUnitAt(i);
4106 if (char == _SLASH) {
4107 parts.add(_uri.substring(start, i));
4108 start = i + 1;
4109 }
4110 }
4111 parts.add(_uri.substring(start, end));
4112 return new List<String>.unmodifiable(parts);
4113 }
4114
4115 Map<String, String> get queryParameters {
4116 if (!hasQuery) return const <String, String>{};
4117 return new UnmodifiableMapView<String, String>(
4118 Uri.splitQueryString(query));
4119 }
4120
4121 Map<String, List<String>> get queryParametersAll {
4122 if (!hasQuery) return const <String, List<String>>{};
4123 Map queryParameterLists = _Uri._splitQueryStringAll(query);
4124 for (var key in queryParameterLists.keys) {
4125 queryParameterLists[key] =
4126 new List<String>.unmodifiable(queryParameterLists[key]);
4127 }
4128 return new Map<String, List<String>>.unmodifiable(queryParameterLists);
4129 }
4130
4131 bool _isPort(String port) {
4132 int portDigitStart = _portStart + 1;
4133 return portDigitStart + port.length == _pathStart &&
4134 _uri.startsWith(port, portDigitStart);
4135 }
4136
4137 Uri normalizePath() => this;
4138
4139 Uri removeFragment() {
4140 if (!hasFragment) return this;
4141 return new _SimpleUri(
4142 _uri.substring(0, _fragmentStart),
4143 _schemeEnd, _hostStart, _portStart,
4144 _pathStart, _queryStart, _fragmentStart, _schemeCache);
4145 }
4146
4147 Uri replace({String scheme,
4148 String userInfo,
4149 String host,
4150 int port,
4151 String path,
4152 Iterable<String> pathSegments,
4153 String query,
4154 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
4155 String fragment}) {
4156 bool schemeChanged = false;
4157 if (scheme != null) {
4158 scheme = _Uri._makeScheme(scheme, 0, scheme.length);
4159 schemeChanged = !_isScheme(scheme);
4160 } else {
4161 scheme = this.scheme;
4162 }
4163 bool isFile = (scheme == "file");
4164 if (userInfo != null) {
4165 userInfo = _Uri._makeUserInfo(userInfo, 0, userInfo.length);
4166 } else if (_hostStart > 0) {
4167 userInfo = _uri.substring(_schemeEnd + 3, _hostStart);
4168 } else {
4169 userInfo = "";
4170 }
4171 if (port != null) {
4172 port = _Uri._makePort(port, scheme);
4173 } else {
4174 port = this.hasPort ? this.port : null;
4175 if (schemeChanged) {
4176 // The default port might have changed.
4177 port = _Uri._makePort(port, scheme);
4178 }
4179 }
4180 if (host != null) {
4181 host = _Uri._makeHost(host, 0, host.length, false);
4182 } else if (_hostStart > 0) {
4183 host = _uri.substring(_hostStart, _portStart);
4184 } else if (userInfo.isNotEmpty || port != null || isFile) {
4185 host = "";
4186 }
4187
4188 bool hasAuthority = host != null;
4189 if (path != null || pathSegments != null) {
4190 path = _Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments,
4191 scheme, hasAuthority);
4192 } else {
4193 path = _uri.substring(_pathStart, _queryStart);
4194 if ((isFile || (hasAuthority && !path.isEmpty)) &&
4195 !path.startsWith('/')) {
4196 path = "/" + path;
4197 }
4198 }
4199
4200 if (query != null || queryParameters != null) {
4201 query = _Uri._makeQuery(
4202 query, 0, _stringOrNullLength(query), queryParameters);
4203 } else if (_queryStart < _fragmentStart) {
4204 query = _uri.substring(_queryStart, _fragmentStart);
4205 }
4206
4207 if (fragment != null) {
4208 fragment = _Uri._makeFragment(fragment, 0, fragment.length);
4209 } else if (_fragmentStart < _uri.length) {
4210 fragment = _uri.substring(_fragmentStart + 1);
4211 }
4212
4213 return new _Uri._internal(
4214 scheme, userInfo, host, port, path, query, fragment);
4215 }
4216
4217 Uri resolve(String reference) {
4218 return resolveUri(Uri.parse(reference));
4219 }
4220
4221 Uri resolveUri(Uri reference) {
4222 if (reference is _SimpleUri) {
4223 return _simpleMerge(this, reference);
4224 }
4225 return _toNonSimple().resolveUri(reference);
4226 }
4227
4228 // Merge two simple URIs. This should always result in a prefix of
4229 // one concatentated with a suffix of the other, possibly with a `/` in
4230 // the middle of two merged paths, which is again simple.
4231 // In a few cases, there might be a need for extra normalization, when
4232 // resolving on top of a known scheme.
4233 Uri _simpleMerge(_SimpleUri base, _SimpleUri ref) {
4234 if (ref.hasScheme) return ref;
4235 if (ref.hasAuthority) {
4236 if (!base.hasScheme) return ref;
4237 bool isSimple = true;
4238 if (base._isFile) {
4239 isSimple = !ref.hasEmptyPath;
4240 } else if (base._isHttp) {
4241 isSimple = !ref._isPort("80");
4242 } else if (base._isHttps) {
4243 isSimple = !ref._isPort("443");
4244 }
4245 if (isSimple) {
4246 var delta = base._schemeEnd + 1;
4247 var newUri = base._uri.substring(0, base._schemeEnd + 1) +
4248 ref._uri.substring(ref._schemeEnd + 1);
4249 return new _SimpleUri(newUri,
4250 base._schemeEnd,
4251 ref._hostStart + delta,
4252 ref._portStart + delta,
4253 ref._pathStart + delta,
4254 ref._queryStart + delta,
4255 ref._fragmentStart + delta,
4256 base._schemeCache);
4257 } else {
4258 // This will require normalization, so use the _Uri implementation.
4259 return _toNonSimple().resolveUri(ref);
4260 }
4261 }
4262 if (ref.hasEmptyPath) {
4263 if (ref.hasQuery) {
4264 int delta = base._queryStart - ref._queryStart;
4265 var newUri = base._uri.substring(0, base._queryStart) +
4266 ref._uri.substring(ref._queryStart);
4267 return new _SimpleUri(newUri,
4268 base._schemeEnd,
4269 base._hostStart,
4270 base._portStart,
4271 base._pathStart,
4272 ref._queryStart + delta,
4273 ref._fragmentStart + delta,
4274 base._schemeCache);
4275 }
4276 if (ref.hasFragment) {
4277 int delta = base._fragmentStart - ref._fragmentStart;
4278 var newUri = base._uri.substring(0, base._fragmentStart) +
4279 ref._uri.substring(ref._fragmentStart);
4280 return new _SimpleUri(newUri,
4281 base._schemeEnd,
4282 base._hostStart,
4283 base._portStart,
4284 base._pathStart,
4285 base._queryStart,
4286 ref._fragmentStart + delta,
4287 base._schemeCache);
4288 }
4289 return base.removeFragment();
4290 }
4291 if (ref.hasAbsolutePath) {
4292 var delta = base._pathStart - ref._pathStart;
4293 var newUri = base._uri.substring(0, base._pathStart) +
4294 ref._uri.substring(ref._pathStart);
4295 return new _SimpleUri(newUri,
4296 base._schemeEnd,
4297 base._hostStart,
4298 base._portStart,
4299 base._pathStart,
4300 ref._queryStart + delta,
4301 ref._fragmentStart + delta,
4302 base._schemeCache);
4303 }
4304 if (base.hasEmptyPath && base.hasAuthority) {
4305 // ref has relative non-empty path.
4306 // Add a "/" in front, then leading "/../" segments are folded to "/".
4307 int refStart = ref._pathStart;
4308 while (ref._uri.startsWith("../", refStart)) {
4309 refStart += 3;
4310 }
4311 var delta = base._pathStart - refStart + 1;
4312 var newUri = "${base._uri.substring(0, base._pathStart)}/"
4313 "${ref._uri.substring(refStart)}";
4314 return new _SimpleUri(newUri,
4315 base._schemeEnd,
4316 base._hostStart,
4317 base._portStart,
4318 base._pathStart,
4319 ref._queryStart + delta,
4320 ref._fragmentStart + delta,
4321 base._schemeCache);
4322 }
4323 // Merge paths.
4324 if (base._uri.startsWith("../", base._pathStart)) {
4325 // Complex rare case, go slow.
4326 return _toNonSimple().resolveUri(ref);
4327 }
4328
4329 // The RFC 3986 algorithm merges the base path without its final segment
4330 // (anything after the final "/", or everything if the base path doesn't
4331 // contain any "/"), and the reference path.
4332 // Then it removes "." and ".." segments using the remove-dot-segment
4333 // algorithm.
4334 // This code combines the two steps. It is simplified by knowing that
4335 // the base path contains no "." or ".." segments, and the reference
4336 // path can only contain leading ".." segments.
4337
4338 String baseUri = base._uri;
4339 String refUri = ref._uri;
4340 int baseStart = base._pathStart;
4341 int baseEnd = base._queryStart;
4342 int refStart = ref._pathStart;
4343 int refEnd = ref._queryStart;
4344 int backCount = 1;
4345
4346 int slashCount = 0;
4347
4348 // Count leading ".." segments in reference path.
4349 while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
4350 refStart += 3;
4351 backCount += 1;
4352 }
4353
4354 // Extra slash inserted between base and reference path parts if
4355 // the base path contains any slashes.
4356 // (We could use a slash from the base path in most cases, but not if
4357 // we remove the entire base path).
4358 String insert = "";
4359 while (baseEnd > baseStart) {
4360 baseEnd--;
4361 int char = baseUri.codeUnitAt(baseEnd);
4362 if (char == _SLASH) {
4363 insert = "/";
4364 backCount--;
4365 if (backCount == 0) break;
4366 }
4367 }
4368 // If the base URI has no scheme or authority (`_pathStart == 0`)
4369 // and a relative path, and we reached the beginning of the path,
4370 // we have a special case.
4371 if (baseEnd == 0 && !base.hasAbsolutePath) {
4372 // Non-RFC 3986 behavior when resolving a purely relative path on top of
4373 // another relative path: Don't make the result absolute.
4374 insert = "";
4375 }
4376
4377 var delta = baseEnd - refStart + insert.length;
4378 var newUri = "${base._uri.substring(0, baseEnd)}$insert"
4379 "${ref._uri.substring(refStart)}";
4380
4381 return new _SimpleUri(newUri,
4382 base._schemeEnd,
4383 base._hostStart,
4384 base._portStart,
4385 base._pathStart,
4386 ref._queryStart + delta,
4387 ref._fragmentStart + delta,
4388 base._schemeCache);
4389 }
4390
4391 String toFilePath({bool windows}) {
4392 if (_schemeEnd >= 0 && !_isFile) {
4393 throw new UnsupportedError(
4394 "Cannot extract a file path from a $scheme URI");
4395 }
4396 if (_queryStart < _uri.length) {
4397 if (_queryStart < _fragmentStart) {
4398 throw new UnsupportedError(
4399 "Cannot extract a file path from a URI with a query component");
4400 }
4401 throw new UnsupportedError(
4402 "Cannot extract a file path from a URI with a fragment component");
4403 }
4404 if (windows == null) windows = _Uri._isWindows;
4405 return windows ? _Uri._toWindowsFilePath(this) : _toFilePath();
4406 }
4407
4408 String _toFilePath() {
4409 if (_hostStart < _portStart) {
4410 // Has authority and non-empty host.
4411 throw new UnsupportedError(
4412 "Cannot extract a non-Windows file path from a file URI "
4413 "with an authority");
4414 }
4415 return this.path;
4416 }
4417
4418 UriData get data {
4419 assert(scheme != "data");
4420 return null;
4421 }
4422
4423 int get hashCode => _hashCodeCache ??= _uri.hashCode;
4424
4425 bool operator==(Object other) {
4426 if (identical(this, other)) return true;
4427 if (other is Uri) return _uri == other.toString();
4428 return false;
4429 }
4430
4431 Uri _toNonSimple() {
4432 return new _Uri._internal(
4433 this.scheme,
4434 this.userInfo,
4435 this.hasAuthority ? this.host: null,
4436 this.hasPort ? this.port : null,
4437 this.path,
4438 this.hasQuery ? this.query : null,
4439 this.hasFragment ? this.fragment : null
4440 );
4441 }
4442
4443 String toString() => _uri;
4444 }
4445
4446 /// Checks whether [text] starts with "data:" at position [start].
4447 ///
4448 /// The text must be long enough to allow reading five characters
4449 /// from the [start] position.
4450 ///
4451 /// Returns an integer value which is zero if text starts with all-lowercase
4452 /// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case.
4453 /// All other values means the text starts with some other character.
4454 int _startsWithData(String text, int start) {
4455 // Multiply by 3 to avoid a non-colon character making delta be 0x20.
4456 int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3;
4457 delta |= text.codeUnitAt(start) ^ 0x64 /*d*/;
4458 delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/;
4459 delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/;
4460 delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/;
4461 return delta;
4462 }
4463
4464 /// Helper function returning the length of a string, or `0` for `null`.
4465 int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
OLDNEW
« no previous file with comments | « sdk/lib/_internal/js_runtime/lib/core_patch.dart ('k') | tests/compiler/dart2js/compiler_helper.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698