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

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

Issue 1381033002: Add data-URI support class to dart:core (next to Uri). (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Add more tests, refactor, rename to DataUriHelper. Need better name! Created 5 years, 1 month 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
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of dart.core;
6
7 /**
8 * A parsed URI, such as a URL.
9 *
10 * **See also:**
11 *
12 * * [URIs][uris] in the [library tour][libtour]
13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
14 *
15 * [uris]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html#c h03-uri
16 * [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.htm l
17 */
18 class Uri {
19 /**
20 * The scheme component of the URI.
21 *
22 * Returns the empty string if there is no scheme component.
23 *
24 * A URI scheme is case insensitive.
25 * The returned scheme is canonicalized to lowercase letters.
26 */
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
80 /// Internal non-verifying constructor. Only call with validated arguments.
81 Uri._internal(this.scheme,
82 this._userInfo,
83 this._host,
84 this._port,
85 this._path,
86 this._query,
87 this._fragment);
88
89 /**
90 * Creates a new URI from its components.
91 *
92 * Each component is set through a named argument. Any number of
93 * components can be provided. The [path] and [query] components can be set
94 * using either of two different named arguments.
95 *
96 * The scheme component is set through [scheme]. The scheme is
97 * normalized to all lowercase letters. If the scheme is omitted or empty,
98 * the URI will not have a scheme part.
99 *
100 * The user info part of the authority component is set through
101 * [userInfo]. It defaults to the empty string, which will be omitted
102 * from the string representation of the URI.
103 *
104 * The host part of the authority component is set through
105 * [host]. The host can either be a hostname, an IPv4 address or an
106 * IPv6 address, contained in '[' and ']'. If the host contains a
107 * ':' character, the '[' and ']' are added if not already provided.
108 * The host is normalized to all lowercase letters.
109 *
110 * The port part of the authority component is set through
111 * [port].
112 * If [port] is omitted or `null`, it implies the default port for
113 * the URI's scheme, and is equivalent to passing that port explicitly.
114 * The recognized schemes, and their default ports, are "http" (80) and
115 * "https" (443). All other schemes are considered as having zero as the
116 * default port.
117 *
118 * If any of `userInfo`, `host` or `port` are provided,
119 * the URI will have an autority according to [hasAuthority].
120 *
121 * The path component is set through either [path] or
122 * [pathSegments]. When [path] is used, it should be a valid URI path,
123 * but invalid characters, except the general delimiters ':/@[]?#',
124 * will be escaped if necessary.
125 * When [pathSegments] is used, each of the provided segments
126 * is first percent-encoded and then joined using the forward slash
127 * separator. The percent-encoding of the path segments encodes all
128 * characters except for the unreserved characters and the following
129 * list of characters: `!$&'()*+,;=:@`. If the other components
130 * calls for an absolute path a leading slash `/` is prepended if
131 * not already there.
132 *
133 * The query component is set through either [query] or
134 * [queryParameters]. When [query] is used the provided string should
135 * be a valid URI query, but invalid characters other than general delimiters,
136 * will be escaped if necessary.
137 * When [queryParameters] is used the query is built from the
138 * provided map. Each key and value in the map is percent-encoded
139 * and joined using equal and ampersand characters. The
140 * percent-encoding of the keys and values encodes all characters
141 * except for the unreserved characters.
142 * If `query` is the empty string, it is equivalent to omitting it.
143 * To have an actual empty query part,
144 * use an empty list for `queryParameters`.
145 * If both `query` and `queryParameters` are omitted or `null`, the
146 * URI will have no query part.
147 *
148 * The fragment component is set through [fragment].
149 * It should be a valid URI fragment, but invalid characters other than
150 * general delimiters, will be escaped if necessary.
151 * If `fragment` is omitted or `null`, the URI will have no fragment part.
152 */
153 factory Uri({String scheme : "",
154 String userInfo : "",
155 String host,
156 int port,
157 String path,
158 Iterable<String> pathSegments,
159 String query,
160 Map<String, String> queryParameters,
161 String fragment}) {
162 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
163 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
164 host = _makeHost(host, 0, _stringOrNullLength(host), false);
165 // Special case this constructor for backwards compatibility.
166 if (query == "") query = null;
167 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
168 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
169 port = _makePort(port, scheme);
170 bool isFile = (scheme == "file");
171 if (host == null &&
172 (userInfo.isNotEmpty || port != null || isFile)) {
173 host = "";
174 }
175 bool hasAuthority = (host != null);
176 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
177 scheme, hasAuthority);
178 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
179 path = _normalizeRelativePath(path);
180 } else {
181 path = _removeDotSegments(path);
182 }
183 return new Uri._internal(scheme, userInfo, host, port,
184 path, query, fragment);
185 }
186
187 /**
188 * Creates a new `http` URI from authority, path and query.
189 *
190 * Examples:
191 *
192 * ```
193 * // http://example.org/path?q=dart.
194 * new Uri.http("google.com", "/search", { "q" : "dart" });
195 *
196 * // http://user:pass@localhost:8080
197 * new Uri.http("user:pass@localhost:8080", "");
198 *
199 * // http://example.org/a%20b
200 * new Uri.http("example.org", "a b");
201 *
202 * // http://example.org/a%252F
203 * new Uri.http("example.org", "/a%2F");
204 * ```
205 *
206 * The `scheme` is always set to `http`.
207 *
208 * The `userInfo`, `host` and `port` components are set from the
209 * [authority] argument. If `authority` is `null` or empty,
210 * the created `Uri` will have no authority, and will not be directly usable
211 * as an HTTP URL, which must have a non-empty host.
212 *
213 * The `path` component is set from the [unencodedPath]
214 * argument. The path passed must not be encoded as this constructor
215 * encodes the path.
216 *
217 * The `query` component is set from the optional [queryParameters]
218 * argument.
219 */
220 factory Uri.http(String authority,
221 String unencodedPath,
222 [Map<String, String> queryParameters]) {
223 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
224 }
225
226 /**
227 * Creates a new `https` URI from authority, path and query.
228 *
229 * This constructor is the same as [Uri.http] except for the scheme
230 * which is set to `https`.
231 */
232 factory Uri.https(String authority,
233 String unencodedPath,
234 [Map<String, String> queryParameters]) {
235 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
236 }
237
238 /**
239 * Returns the authority component.
240 *
241 * The authority is formatted from the [userInfo], [host] and [port]
242 * parts.
243 *
244 * Returns the empty string if there is no authority component.
245 */
246 String get authority {
247 if (!hasAuthority) return "";
248 var sb = new StringBuffer();
249 _writeAuthority(sb);
250 return sb.toString();
251 }
252
253 /**
254 * Returns the user info part of the authority component.
255 *
256 * Returns the empty string if there is no user info in the
257 * authority component.
258 */
259 String get userInfo => _userInfo;
260
261 /**
262 * Returns the host part of the authority component.
263 *
264 * Returns the empty string if there is no authority component and
265 * hence no host.
266 *
267 * If the host is an IP version 6 address, the surrounding `[` and `]` is
268 * removed.
269 *
270 * The host string is case-insensitive.
271 * The returned host name is canonicalized to lower-case
272 * with upper-case percent-escapes.
273 */
274 String get host {
275 if (_host == null) return "";
276 if (_host.startsWith('[')) {
277 return _host.substring(1, _host.length - 1);
278 }
279 return _host;
280 }
281
282 /**
283 * Returns the port part of the authority component.
284 *
285 * Returns the defualt port if there is no port number in the authority
286 * component. That's 80 for http, 443 for https, and 0 for everything else.
287 */
288 int get port {
289 if (_port == null) return _defaultPort(scheme);
290 return _port;
291 }
292
293 // The default port for the scheme of this Uri..
294 static int _defaultPort(String scheme) {
295 if (scheme == "http") return 80;
296 if (scheme == "https") return 443;
297 return 0;
298 }
299
300 /**
301 * Returns the path component.
302 *
303 * The returned path is encoded. To get direct access to the decoded
304 * path use [pathSegments].
305 *
306 * Returns the empty string if there is no path component.
307 */
308 String get path => _path;
309
310 /**
311 * Returns the query component. The returned query is encoded. To get
312 * direct access to the decoded query use [queryParameters].
313 *
314 * Returns the empty string if there is no query component.
315 */
316 String get query => (_query == null) ? "" : _query;
317
318 /**
319 * Returns the fragment identifier component.
320 *
321 * Returns the empty string if there is no fragment identifier
322 * component.
323 */
324 String get fragment => (_fragment == null) ? "" : _fragment;
325
326 /**
327 * Creates a new `Uri` object by parsing a URI string.
328 *
329 * If [start] and [end] are provided, only the substring from `start`
330 * to `end` is parsed as a URI.
331 *
332 * If the string is not valid as a URI or URI reference,
333 * a [FormatException] is thrown.
334 */
335 static Uri parse(String uri, [int start = 0, int end]) {
336 // This parsing will not validate percent-encoding, IPv6, etc.
337 // When done splitting into parts, it will call, e.g., [_makeFragment]
338 // to do the final parsing.
339 //
340 // Important parts of the RFC 3986 used here:
341 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
342 //
343 // hier-part = "//" authority path-abempty
344 // / path-absolute
345 // / path-rootless
346 // / path-empty
347 //
348 // URI-reference = URI / relative-ref
349 //
350 // absolute-URI = scheme ":" hier-part [ "?" query ]
351 //
352 // relative-ref = relative-part [ "?" query ] [ "#" fragment ]
353 //
354 // relative-part = "//" authority path-abempty
355 // / path-absolute
356 // / path-noscheme
357 // / path-empty
358 //
359 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
360 //
361 // authority = [ userinfo "@" ] host [ ":" port ]
362 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
363 // host = IP-literal / IPv4address / reg-name
364 // port = *DIGIT
365 // reg-name = *( unreserved / pct-encoded / sub-delims )
366 //
367 // path = path-abempty ; begins with "/" or is empty
368 // / path-absolute ; begins with "/" but not "//"
369 // / path-noscheme ; begins with a non-colon segment
370 // / path-rootless ; begins with a segment
371 // / path-empty ; zero characters
372 //
373 // path-abempty = *( "/" segment )
374 // path-absolute = "/" [ segment-nz *( "/" segment ) ]
375 // path-noscheme = segment-nz-nc *( "/" segment )
376 // path-rootless = segment-nz *( "/" segment )
377 // path-empty = 0<pchar>
378 //
379 // segment = *pchar
380 // segment-nz = 1*pchar
381 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
382 // ; non-zero-length segment without any colon ":"
383 //
384 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
385 //
386 // query = *( pchar / "/" / "?" )
387 //
388 // fragment = *( pchar / "/" / "?" )
389 const int EOI = -1;
390
391 String scheme = "";
392 String userinfo = "";
393 String host = null;
394 int port = null;
395 String path = null;
396 String query = null;
397 String fragment = null;
398 if (end == null) end = uri.length;
399
400 int index = start;
401 int pathStart = start;
402 // End of input-marker.
403 int char = EOI;
404
405 void parseAuth() {
406 if (index == end) {
407 char = EOI;
408 return;
409 }
410 int authStart = index;
411 int lastColon = -1;
412 int lastAt = -1;
413 char = uri.codeUnitAt(index);
414 while (index < end) {
415 char = uri.codeUnitAt(index);
416 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
417 break;
418 }
419 if (char == _AT_SIGN) {
420 lastAt = index;
421 lastColon = -1;
422 } else if (char == _COLON) {
423 lastColon = index;
424 } else if (char == _LEFT_BRACKET) {
425 lastColon = -1;
426 int endBracket = uri.indexOf(']', index + 1);
427 if (endBracket == -1) {
428 index = end;
429 char = EOI;
430 break;
431 } else {
432 index = endBracket;
433 }
434 }
435 index++;
436 char = EOI;
437 }
438 int hostStart = authStart;
439 int hostEnd = index;
440 if (lastAt >= 0) {
441 userinfo = _makeUserInfo(uri, authStart, lastAt);
442 hostStart = lastAt + 1;
443 }
444 if (lastColon >= 0) {
445 int portNumber;
446 if (lastColon + 1 < index) {
447 portNumber = 0;
448 for (int i = lastColon + 1; i < index; i++) {
449 int digit = uri.codeUnitAt(i);
450 if (_ZERO > digit || _NINE < digit) {
451 _fail(uri, i, "Invalid port number");
452 }
453 portNumber = portNumber * 10 + (digit - _ZERO);
454 }
455 }
456 port = _makePort(portNumber, scheme);
457 hostEnd = lastColon;
458 }
459 host = _makeHost(uri, hostStart, hostEnd, true);
460 if (index < end) {
461 char = uri.codeUnitAt(index);
462 }
463 }
464
465 // When reaching path parsing, the current character is known to not
466 // be part of the path.
467 const int NOT_IN_PATH = 0;
468 // When reaching path parsing, the current character is part
469 // of the a non-empty path.
470 const int IN_PATH = 1;
471 // When reaching authority parsing, authority is possible.
472 // This is only true at start or right after scheme.
473 const int ALLOW_AUTH = 2;
474
475 // Current state.
476 // Initialized to the default value that is used when exiting the
477 // scheme loop by reaching the end of input.
478 // All other breaks set their own state.
479 int state = NOT_IN_PATH;
480 int i = index; // Temporary alias for index to avoid bug 19550 in dart2js.
481 while (i < end) {
482 char = uri.codeUnitAt(i);
483 if (char == _QUESTION || char == _NUMBER_SIGN) {
484 state = NOT_IN_PATH;
485 break;
486 }
487 if (char == _SLASH) {
488 state = (i == start) ? ALLOW_AUTH : IN_PATH;
489 break;
490 }
491 if (char == _COLON) {
492 if (i == start) _fail(uri, start, "Invalid empty scheme");
493 scheme = _makeScheme(uri, start, i);
494 i++;
495 pathStart = i;
496 if (i == end) {
497 char = EOI;
498 state = NOT_IN_PATH;
499 } else {
500 char = uri.codeUnitAt(i);
501 if (char == _QUESTION || char == _NUMBER_SIGN) {
502 state = NOT_IN_PATH;
503 } else if (char == _SLASH) {
504 state = ALLOW_AUTH;
505 } else {
506 state = IN_PATH;
507 }
508 }
509 break;
510 }
511 i++;
512 char = EOI;
513 }
514 index = i; // Remove alias when bug is fixed.
515
516 if (state == ALLOW_AUTH) {
517 assert(char == _SLASH);
518 // Have seen one slash either at start or right after scheme.
519 // If two slashes, it's an authority, otherwise it's just the path.
520 index++;
521 if (index == end) {
522 char = EOI;
523 state = NOT_IN_PATH;
524 } else {
525 char = uri.codeUnitAt(index);
526 if (char == _SLASH) {
527 index++;
528 parseAuth();
529 pathStart = index;
530 }
531 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
532 state = NOT_IN_PATH;
533 } else {
534 state = IN_PATH;
535 }
536 }
537 }
538
539 assert(state == IN_PATH || state == NOT_IN_PATH);
540 if (state == IN_PATH) {
541 // Characters from pathStart to index (inclusive) are known
542 // to be part of the path.
543 while (++index < end) {
544 char = uri.codeUnitAt(index);
545 if (char == _QUESTION || char == _NUMBER_SIGN) {
546 break;
547 }
548 char = EOI;
549 }
550 state = NOT_IN_PATH;
551 }
552
553 assert(state == NOT_IN_PATH);
554 bool hasAuthority = (host != null);
555 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
556
557 if (char == _QUESTION) {
558 int numberSignIndex = -1;
559 for (int i = index + 1; i < end; i++) {
560 if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
561 numberSignIndex = i;
562 break;
563 }
564 }
565 if (numberSignIndex < 0) {
566 query = _makeQuery(uri, index + 1, end, null);
567 } else {
568 query = _makeQuery(uri, index + 1, numberSignIndex, null);
569 fragment = _makeFragment(uri, numberSignIndex + 1, end);
570 }
571 } else if (char == _NUMBER_SIGN) {
572 fragment = _makeFragment(uri, index + 1, end);
573 }
574 return new Uri._internal(scheme,
575 userinfo,
576 host,
577 port,
578 path,
579 query,
580 fragment);
581 }
582
583 // Report a parse failure.
584 static void _fail(String uri, int index, String message) {
585 throw new FormatException(message, uri, index);
586 }
587
588 static Uri _makeHttpUri(String scheme,
589 String authority,
590 String unencodedPath,
591 Map<String, String> queryParameters) {
592 var userInfo = "";
593 var host = null;
594 var port = null;
595
596 if (authority != null && authority.isNotEmpty) {
597 var hostStart = 0;
598 // Split off the user info.
599 bool hasUserInfo = false;
600 for (int i = 0; i < authority.length; i++) {
601 if (authority.codeUnitAt(i) == _AT_SIGN) {
602 hasUserInfo = true;
603 userInfo = authority.substring(0, i);
604 hostStart = i + 1;
605 break;
606 }
607 }
608 var hostEnd = hostStart;
609 if (hostStart < authority.length &&
610 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
611 // IPv6 host.
612 for (; hostEnd < authority.length; hostEnd++) {
613 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
614 }
615 if (hostEnd == authority.length) {
616 throw new FormatException("Invalid IPv6 host entry.",
617 authority, hostStart);
618 }
619 parseIPv6Address(authority, hostStart + 1, hostEnd);
620 hostEnd++; // Skip the closing bracket.
621 if (hostEnd != authority.length &&
622 authority.codeUnitAt(hostEnd) != _COLON) {
623 throw new FormatException("Invalid end of authority",
624 authority, hostEnd);
625 }
626 }
627 // Split host and port.
628 bool hasPort = false;
629 for (; hostEnd < authority.length; hostEnd++) {
630 if (authority.codeUnitAt(hostEnd) == _COLON) {
631 var portString = authority.substring(hostEnd + 1);
632 // We allow the empty port - falling back to initial value.
633 if (portString.isNotEmpty) port = int.parse(portString);
634 break;
635 }
636 }
637 host = authority.substring(hostStart, hostEnd);
638 }
639 return new Uri(scheme: scheme,
640 userInfo: userInfo,
641 host: host,
642 port: port,
643 pathSegments: unencodedPath.split("/"),
644 queryParameters: queryParameters);
645 }
646
647 /**
648 * Creates a new file URI from an absolute or relative file path.
649 *
650 * The file path is passed in [path].
651 *
652 * This path is interpreted using either Windows or non-Windows
653 * semantics.
654 *
655 * With non-Windows semantics the slash ("/") is used to separate
656 * path segments.
657 *
658 * With Windows semantics, backslash ("\") and forward-slash ("/")
659 * are used to separate path segments, except if the path starts
660 * with "\\?\" in which case, only backslash ("\") separates path
661 * segments.
662 *
663 * If the path starts with a path separator an absolute URI is
664 * created. Otherwise a relative URI is created. One exception from
665 * this rule is that when Windows semantics is used and the path
666 * starts with a drive letter followed by a colon (":") and a
667 * path separator then an absolute URI is created.
668 *
669 * The default for whether to use Windows or non-Windows semantics
670 * determined from the platform Dart is running on. When running in
671 * the standalone VM this is detected by the VM based on the
672 * operating system. When running in a browser non-Windows semantics
673 * is always used.
674 *
675 * To override the automatic detection of which semantics to use pass
676 * a value for [windows]. Passing `true` will use Windows
677 * semantics and passing `false` will use non-Windows semantics.
678 *
679 * Examples using non-Windows semantics:
680 *
681 * ```
682 * // xxx/yyy
683 * new Uri.file("xxx/yyy", windows: false);
684 *
685 * // xxx/yyy/
686 * new Uri.file("xxx/yyy/", windows: false);
687 *
688 * // file:///xxx/yyy
689 * new Uri.file("/xxx/yyy", windows: false);
690 *
691 * // file:///xxx/yyy/
692 * new Uri.file("/xxx/yyy/", windows: false);
693 *
694 * // C:
695 * new Uri.file("C:", windows: false);
696 * ```
697 *
698 * Examples using Windows semantics:
699 *
700 * ```
701 * // xxx/yyy
702 * new Uri.file(r"xxx\yyy", windows: true);
703 *
704 * // xxx/yyy/
705 * new Uri.file(r"xxx\yyy\", windows: true);
706 *
707 * file:///xxx/yyy
708 * new Uri.file(r"\xxx\yyy", windows: true);
709 *
710 * file:///xxx/yyy/
711 * new Uri.file(r"\xxx\yyy/", windows: true);
712 *
713 * // file:///C:/xxx/yyy
714 * new Uri.file(r"C:\xxx\yyy", windows: true);
715 *
716 * // This throws an error. A path with a drive letter is not absolute.
717 * new Uri.file(r"C:", windows: true);
718 *
719 * // This throws an error. A path with a drive letter is not absolute.
720 * new Uri.file(r"C:xxx\yyy", windows: true);
721 *
722 * // file://server/share/file
723 * new Uri.file(r"\\server\share\file", windows: true);
724 * ```
725 *
726 * If the path passed is not a legal file path [ArgumentError] is thrown.
727 */
728 factory Uri.file(String path, {bool windows}) {
729 windows = (windows == null) ? Uri._isWindows : windows;
730 return windows ? _makeWindowsFileUrl(path, false)
731 : _makeFileUri(path, false);
732 }
733
734 /**
735 * Like [Uri.file] except that a non-empty URI path ends in a slash.
736 *
737 * If [path] is not empty, and it doesn't end in a directory separator,
738 * then a slash is added to the returned URI's path.
739 * In all other cases, the result is the same as returned by `Uri.file`.
740 */
741 factory Uri.directory(String path, {bool windows}) {
742 windows = (windows == null) ? Uri._isWindows : windows;
743 return windows ? _makeWindowsFileUrl(path, true)
744 : _makeFileUri(path, true);
745 }
746
747 /**
748 * Returns the natural base URI for the current platform.
749 *
750 * When running in a browser this is the current URL (from
751 * `window.location.href`).
752 *
753 * When not running in a browser this is the file URI referencing
754 * the current working directory.
755 */
756 external static Uri get base;
757
758 external static bool get _isWindows;
759
760 static _checkNonWindowsPathReservedCharacters(List<String> segments,
761 bool argumentError) {
762 segments.forEach((segment) {
763 if (segment.contains("/")) {
764 if (argumentError) {
765 throw new ArgumentError("Illegal path character $segment");
766 } else {
767 throw new UnsupportedError("Illegal path character $segment");
768 }
769 }
770 });
771 }
772
773 static _checkWindowsPathReservedCharacters(List<String> segments,
774 bool argumentError,
775 [int firstSegment = 0]) {
776 for (var segment in segments.skip(firstSegment)) {
777 if (segment.contains(new RegExp(r'["*/:<>?\\|]'))) {
778 if (argumentError) {
779 throw new ArgumentError("Illegal character in path");
780 } else {
781 throw new UnsupportedError("Illegal character in path");
782 }
783 }
784 }
785 }
786
787 static _checkWindowsDriveLetter(int charCode, bool argumentError) {
788 if ((_UPPER_CASE_A <= charCode && charCode <= _UPPER_CASE_Z) ||
789 (_LOWER_CASE_A <= charCode && charCode <= _LOWER_CASE_Z)) {
790 return;
791 }
792 if (argumentError) {
793 throw new ArgumentError("Illegal drive letter " +
794 new String.fromCharCode(charCode));
795 } else {
796 throw new UnsupportedError("Illegal drive letter " +
797 new String.fromCharCode(charCode));
798 }
799 }
800
801 static _makeFileUri(String path, bool slashTerminated) {
802 const String sep = "/";
803 var segments = path.split(sep);
804 if (slashTerminated &&
805 segments.isNotEmpty &&
806 segments.last.isNotEmpty) {
807 segments.add(""); // Extra separator at end.
808 }
809 if (path.startsWith(sep)) {
810 // Absolute file:// URI.
811 return new Uri(scheme: "file", pathSegments: segments);
812 } else {
813 // Relative URI.
814 return new Uri(pathSegments: segments);
815 }
816 }
817
818 static _makeWindowsFileUrl(String path, bool slashTerminated) {
819 if (path.startsWith(r"\\?\")) {
820 if (path.startsWith(r"UNC\", 4)) {
821 path = path.replaceRange(0, 7, r'\');
822 } else {
823 path = path.substring(4);
824 if (path.length < 3 ||
825 path.codeUnitAt(1) != _COLON ||
826 path.codeUnitAt(2) != _BACKSLASH) {
827 throw new ArgumentError(
828 r"Windows paths with \\?\ prefix must be absolute");
829 }
830 }
831 } else {
832 path = path.replaceAll("/", r'\');
833 }
834 const String sep = r'\';
835 if (path.length > 1 && path.codeUnitAt(1) == _COLON) {
836 _checkWindowsDriveLetter(path.codeUnitAt(0), true);
837 if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
838 throw new ArgumentError(
839 "Windows paths with drive letter must be absolute");
840 }
841 // Absolute file://C:/ URI.
842 var pathSegments = path.split(sep);
843 if (slashTerminated &&
844 pathSegments.last.isNotEmpty) {
845 pathSegments.add(""); // Extra separator at end.
846 }
847 _checkWindowsPathReservedCharacters(pathSegments, true, 1);
848 return new Uri(scheme: "file", pathSegments: pathSegments);
849 }
850
851 if (path.startsWith(sep)) {
852 if (path.startsWith(sep, 1)) {
853 // Absolute file:// URI with host.
854 int pathStart = path.indexOf(r'\', 2);
855 String hostPart =
856 (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart);
857 String pathPart =
858 (pathStart < 0) ? "" : path.substring(pathStart + 1);
859 var pathSegments = pathPart.split(sep);
860 _checkWindowsPathReservedCharacters(pathSegments, true);
861 if (slashTerminated &&
862 pathSegments.last.isNotEmpty) {
863 pathSegments.add(""); // Extra separator at end.
864 }
865 return new Uri(
866 scheme: "file", host: hostPart, pathSegments: pathSegments);
867 } else {
868 // Absolute file:// URI.
869 var pathSegments = path.split(sep);
870 if (slashTerminated &&
871 pathSegments.last.isNotEmpty) {
872 pathSegments.add(""); // Extra separator at end.
873 }
874 _checkWindowsPathReservedCharacters(pathSegments, true);
875 return new Uri(scheme: "file", pathSegments: pathSegments);
876 }
877 } else {
878 // Relative URI.
879 var pathSegments = path.split(sep);
880 _checkWindowsPathReservedCharacters(pathSegments, true);
881 if (slashTerminated &&
882 pathSegments.isNotEmpty &&
883 pathSegments.last.isNotEmpty) {
884 pathSegments.add(""); // Extra separator at end.
885 }
886 return new Uri(pathSegments: pathSegments);
887 }
888 }
889
890 /**
891 * Returns a new `Uri` based on this one, but with some parts replaced.
892 *
893 * This method takes the same parameters as the [new Uri] constructor,
894 * and they have the same meaning.
895 *
896 * At most one of [path] and [pathSegments] must be provided.
897 * Likewise, at most one of [query] and [queryParameters] must be provided.
898 *
899 * Each part that is not provided will default to the corresponding
900 * value from this `Uri` instead.
901 *
902 * This method is different from [Uri.resolve] which overrides in a
903 * hierarchial manner,
904 * and can instead replace each part of a `Uri` individually.
905 *
906 * Example:
907 *
908 * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
909 * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
910 * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
911 *
912 * This method acts similarly to using the `new Uri` constructor with
913 * some of the arguments taken from this `Uri` . Example:
914 *
915 * Uri uri3 = new Uri(
916 * scheme: "A",
917 * userInfo: uri1.userInfo,
918 * host: uri1.host,
919 * port: uri1.port,
920 * path: "D/E/E",
921 * query: uri1.query,
922 * fragment: "G");
923 * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
924 * print(uri2 == uri3); // prints true.
925 *
926 * Using this method can be seen as a shorthand for the `Uri` constructor
927 * call above, but may also be slightly faster because the parts taken
928 * from this `Uri` need not be checked for validity again.
929 */
930 Uri replace({String scheme,
931 String userInfo,
932 String host,
933 int port,
934 String path,
935 Iterable<String> pathSegments,
936 String query,
937 Map<String, String> queryParameters,
938 String fragment}) {
939 // Set to true if the scheme has (potentially) changed.
940 // In that case, the default port may also have changed and we need
941 // to check even the existing port.
942 bool schemeChanged = false;
943 if (scheme != null) {
944 scheme = _makeScheme(scheme, 0, scheme.length);
945 schemeChanged = true;
946 } else {
947 scheme = this.scheme;
948 }
949 bool isFile = (scheme == "file");
950 if (userInfo != null) {
951 userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
952 } else {
953 userInfo = this._userInfo;
954 }
955 if (port != null) {
956 port = _makePort(port, scheme);
957 } else {
958 port = this._port;
959 if (schemeChanged) {
960 // The default port might have changed.
961 port = _makePort(port, scheme);
962 }
963 }
964 if (host != null) {
965 host = _makeHost(host, 0, host.length, false);
966 } else if (this.hasAuthority) {
967 host = this._host;
968 } else if (userInfo.isNotEmpty || port != null || isFile) {
969 host = "";
970 }
971
972 bool hasAuthority = host != null;
973 if (path != null || pathSegments != null) {
974 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
975 scheme, hasAuthority);
976 } else {
977 path = this._path;
978 if ((isFile || (hasAuthority && !path.isEmpty)) &&
979 !path.startsWith('/')) {
980 path = "/" + path;
981 }
982 }
983
984 if (query != null || queryParameters != null) {
985 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
986 } else {
987 query = this._query;
988 }
989
990 if (fragment != null) {
991 fragment = _makeFragment(fragment, 0, fragment.length);
992 } else {
993 fragment = this._fragment;
994 }
995
996 return new Uri._internal(
997 scheme, userInfo, host, port, path, query, fragment);
998 }
999
1000 /**
1001 * Returns a `Uri` that differs from this only in not having a fragment.
1002 *
1003 * If this `Uri` does not have a fragment, it is itself returned.
1004 */
1005 Uri removeFragment() {
1006 if (!this.hasFragment) return this;
1007 return new Uri._internal(scheme, _userInfo, _host, _port,
1008 _path, _query, null);
1009 }
1010
1011 /**
1012 * Returns the URI path split into its segments. Each of the
1013 * segments in the returned list have been decoded. If the path is
1014 * empty the empty list will be returned. A leading slash `/` does
1015 * not affect the segments returned.
1016 *
1017 * The returned list is unmodifiable and will throw [UnsupportedError] on any
1018 * calls that would mutate it.
1019 */
1020 List<String> get pathSegments {
1021 if (_pathSegments == null) {
1022 var pathToSplit = !path.isEmpty && path.codeUnitAt(0) == _SLASH
1023 ? path.substring(1)
1024 : path;
1025 _pathSegments = new UnmodifiableListView(
1026 pathToSplit == "" ? const<String>[]
1027 : pathToSplit.split("/")
1028 .map(Uri.decodeComponent)
1029 .toList(growable: false));
1030 }
1031 return _pathSegments;
1032 }
1033
1034 /**
1035 * Returns the URI query split into a map according to the rules
1036 * specified for FORM post in the [HTML 4.01 specification section 17.13.4]
1037 * (http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
1038 * "HTML 4.01 section 17.13.4"). Each key and value in the returned map
1039 * has been decoded. If there is no query the empty map is returned.
1040 *
1041 * Keys in the query string that have no value are mapped to the
1042 * empty string.
1043 *
1044 * The returned map is unmodifiable and will throw [UnsupportedError] on any
1045 * calls that would mutate it.
1046 */
1047 Map<String, String> get queryParameters {
1048 if (_queryParameters == null) {
1049 _queryParameters = new UnmodifiableMapView(splitQueryString(query));
1050 }
1051 return _queryParameters;
1052 }
1053
1054 /**
1055 * Returns a URI where the path has been normalized.
1056 *
1057 * A normalized path does not contain `.` segments or non-leading `..`
1058 * segments.
1059 * Only a relative path with no scheme or authority may contain
1060 * leading `..` segments,
1061 * a path that starts with `/` will also drop any leading `..` segments.
1062 *
1063 * This uses the same normalization strategy as `new Uri().resolve(this)`.
1064 *
1065 * Does not change any part of the URI except the path.
1066 *
1067 * The default implementation of `Uri` always normalizes paths, so calling
1068 * this function has no effect.
1069 */
1070 Uri normalizePath() {
1071 String path = _normalizePath(_path, scheme, hasAuthority);
1072 if (identical(path, _path)) return this;
1073 return this.replace(path: path);
1074 }
1075
1076 static int _makePort(int port, String scheme) {
1077 // Perform scheme specific normalization.
1078 if (port != null && port == _defaultPort(scheme)) return null;
1079 return port;
1080 }
1081
1082 /**
1083 * Check and normalize a host name.
1084 *
1085 * If the host name starts and ends with '[' and ']', it is considered an
1086 * IPv6 address. If [strictIPv6] is false, the address is also considered
1087 * an IPv6 address if it contains any ':' character.
1088 *
1089 * If it is not an IPv6 address, it is case- and escape-normalized.
1090 * This escapes all characters not valid in a reg-name,
1091 * and converts all non-escape upper-case letters to lower-case.
1092 */
1093 static String _makeHost(String host, int start, int end, bool strictIPv6) {
1094 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
1095 if (host == null) return null;
1096 if (start == end) return "";
1097 // Host is an IPv6 address if it starts with '[' or contains a colon.
1098 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
1099 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
1100 _fail(host, start, 'Missing end `]` to match `[` in host');
1101 }
1102 parseIPv6Address(host, start + 1, end - 1);
1103 // RFC 5952 requires hex digits to be lower case.
1104 return host.substring(start, end).toLowerCase();
1105 }
1106 if (!strictIPv6) {
1107 // TODO(lrn): skip if too short to be a valid IPv6 address?
1108 for (int i = start; i < end; i++) {
1109 if (host.codeUnitAt(i) == _COLON) {
1110 parseIPv6Address(host, start, end);
1111 return '[$host]';
1112 }
1113 }
1114 }
1115 return _normalizeRegName(host, start, end);
1116 }
1117
1118 static bool _isRegNameChar(int char) {
1119 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
1120 }
1121
1122 /**
1123 * Validates and does case- and percent-encoding normalization.
1124 *
1125 * The [host] must be an RFC3986 "reg-name". It is converted
1126 * to lower case, and percent escapes are converted to either
1127 * lower case unreserved characters or upper case escapes.
1128 */
1129 static String _normalizeRegName(String host, int start, int end) {
1130 StringBuffer buffer;
1131 int sectionStart = start;
1132 int index = start;
1133 // Whether all characters between sectionStart and index are normalized,
1134 bool isNormalized = true;
1135
1136 while (index < end) {
1137 int char = host.codeUnitAt(index);
1138 if (char == _PERCENT) {
1139 // The _regNameTable contains "%", so we check that first.
1140 String replacement = _normalizeEscape(host, index, true);
1141 if (replacement == null && isNormalized) {
1142 index += 3;
1143 continue;
1144 }
1145 if (buffer == null) buffer = new StringBuffer();
1146 String slice = host.substring(sectionStart, index);
1147 if (!isNormalized) slice = slice.toLowerCase();
1148 buffer.write(slice);
1149 int sourceLength = 3;
1150 if (replacement == null) {
1151 replacement = host.substring(index, index + 3);
1152 } else if (replacement == "%") {
1153 replacement = "%25";
1154 sourceLength = 1;
1155 }
1156 buffer.write(replacement);
1157 index += sourceLength;
1158 sectionStart = index;
1159 isNormalized = true;
1160 } else if (_isRegNameChar(char)) {
1161 if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
1162 // Put initial slice in buffer and continue in non-normalized mode
1163 if (buffer == null) buffer = new StringBuffer();
1164 if (sectionStart < index) {
1165 buffer.write(host.substring(sectionStart, index));
1166 sectionStart = index;
1167 }
1168 isNormalized = false;
1169 }
1170 index++;
1171 } else if (_isGeneralDelimiter(char)) {
1172 _fail(host, index, "Invalid character");
1173 } else {
1174 int sourceLength = 1;
1175 if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
1176 int tail = host.codeUnitAt(index + 1);
1177 if ((tail & 0xFC00) == 0xDC00) {
1178 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1179 sourceLength = 2;
1180 }
1181 }
1182 if (buffer == null) buffer = new StringBuffer();
1183 String slice = host.substring(sectionStart, index);
1184 if (!isNormalized) slice = slice.toLowerCase();
1185 buffer.write(slice);
1186 buffer.write(_escapeChar(char));
1187 index += sourceLength;
1188 sectionStart = index;
1189 }
1190 }
1191 if (buffer == null) return host.substring(start, end);
1192 if (sectionStart < end) {
1193 String slice = host.substring(sectionStart, end);
1194 if (!isNormalized) slice = slice.toLowerCase();
1195 buffer.write(slice);
1196 }
1197 return buffer.toString();
1198 }
1199
1200 /**
1201 * Validates scheme characters and does case-normalization.
1202 *
1203 * Schemes are converted to lower case. They cannot contain escapes.
1204 */
1205 static String _makeScheme(String scheme, int start, int end) {
1206 if (start == end) return "";
1207 final int firstCodeUnit = scheme.codeUnitAt(start);
1208 if (!_isAlphabeticCharacter(firstCodeUnit)) {
1209 _fail(scheme, start, "Scheme not starting with alphabetic character");
1210 }
1211 bool containsUpperCase = false;
1212 for (int i = start; i < end; i++) {
1213 final int codeUnit = scheme.codeUnitAt(i);
1214 if (!_isSchemeCharacter(codeUnit)) {
1215 _fail(scheme, i, "Illegal scheme character");
1216 }
1217 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
1218 containsUpperCase = true;
1219 }
1220 }
1221 scheme = scheme.substring(start, end);
1222 if (containsUpperCase) scheme = scheme.toLowerCase();
1223 return scheme;
1224 }
1225
1226 static String _makeUserInfo(String userInfo, int start, int end) {
1227 if (userInfo == null) return "";
1228 return _normalize(userInfo, start, end, _userinfoTable);
1229 }
1230
1231 static String _makePath(String path, int start, int end,
1232 Iterable<String> pathSegments,
1233 String scheme,
1234 bool hasAuthority) {
1235 bool isFile = (scheme == "file");
1236 bool ensureLeadingSlash = isFile || hasAuthority;
1237 if (path == null && pathSegments == null) return isFile ? "/" : "";
1238 if (path != null && pathSegments != null) {
1239 throw new ArgumentError('Both path and pathSegments specified');
1240 }
1241 var result;
1242 if (path != null) {
1243 result = _normalize(path, start, end, _pathCharOrSlashTable);
1244 } else {
1245 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
1246 }
1247 if (result.isEmpty) {
1248 if (isFile) return "/";
1249 } else if (ensureLeadingSlash && !result.startsWith('/')) {
1250 result = "/" + result;
1251 }
1252 result = _normalizePath(result, scheme, hasAuthority);
1253 return result;
1254 }
1255
1256 /// Performs path normalization (remove dot segments) on a path.
1257 ///
1258 /// If the URI has neither scheme nor authority, it's considered a
1259 /// "pure path" and normalization won't remove leading ".." segments.
1260 /// Otherwise it follows the RFC 3986 "remove dot segments" algorithm.
1261 static String _normalizePath(String path, String scheme, bool hasAuthority) {
1262 if (scheme.isEmpty && !hasAuthority && !path.startsWith('/')) {
1263 return _normalizeRelativePath(path);
1264 }
1265 return _removeDotSegments(path);
1266 }
1267
1268 static String _makeQuery(String query, int start, int end,
1269 Map<String, String> queryParameters) {
1270 if (query == null && queryParameters == null) return null;
1271 if (query != null && queryParameters != null) {
1272 throw new ArgumentError('Both query and queryParameters specified');
1273 }
1274 if (query != null) return _normalize(query, start, end, _queryCharTable);
1275
1276 var result = new StringBuffer();
1277 var first = true;
1278 queryParameters.forEach((key, value) {
1279 if (!first) {
1280 result.write("&");
1281 }
1282 first = false;
1283 result.write(Uri.encodeQueryComponent(key));
1284 if (value != null && !value.isEmpty) {
1285 result.write("=");
1286 result.write(Uri.encodeQueryComponent(value));
1287 }
1288 });
1289 return result.toString();
1290 }
1291
1292 static String _makeFragment(String fragment, int start, int end) {
1293 if (fragment == null) return null;
1294 return _normalize(fragment, start, end, _queryCharTable);
1295 }
1296
1297 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
1298
1299 static bool _isHexDigit(int char) {
1300 if (_NINE >= char) return _ZERO <= char;
1301 char |= 0x20;
1302 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
1303 }
1304
1305 static int _hexValue(int char) {
1306 assert(_isHexDigit(char));
1307 if (_NINE >= char) return char - _ZERO;
1308 char |= 0x20;
1309 return char - (_LOWER_CASE_A - 10);
1310 }
1311
1312 /**
1313 * Performs RFC 3986 Percent-Encoding Normalization.
1314 *
1315 * Returns a replacement string that should be replace the original escape.
1316 * Returns null if no replacement is necessary because the escape is
1317 * not for an unreserved character and is already non-lower-case.
1318 *
1319 * Returns "%" if the escape is invalid (not two valid hex digits following
1320 * the percent sign). The calling code should replace the percent
1321 * sign with "%25", but leave the following two characters unmodified.
1322 *
1323 * If [lowerCase] is true, a single character returned is always lower case,
1324 */
1325 static String _normalizeEscape(String source, int index, bool lowerCase) {
1326 assert(source.codeUnitAt(index) == _PERCENT);
1327 if (index + 2 >= source.length) {
1328 return "%"; // Marks the escape as invalid.
1329 }
1330 int firstDigit = source.codeUnitAt(index + 1);
1331 int secondDigit = source.codeUnitAt(index + 2);
1332 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
1333 return "%"; // Marks the escape as invalid.
1334 }
1335 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit);
1336 if (_isUnreservedChar(value)) {
1337 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
1338 value |= 0x20;
1339 }
1340 return new String.fromCharCode(value);
1341 }
1342 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
1343 // Either digit is lower case.
1344 return source.substring(index, index + 3).toUpperCase();
1345 }
1346 // Escape is retained, and is already non-lower case, so return null to
1347 // represent "no replacement necessary".
1348 return null;
1349 }
1350
1351 static bool _isUnreservedChar(int ch) {
1352 return ch < 127 &&
1353 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1354 }
1355
1356 static String _escapeChar(char) {
1357 assert(char <= 0x10ffff); // It's a valid unicode code point.
1358 const hexDigits = "0123456789ABCDEF";
1359 List codeUnits;
1360 if (char < 0x80) {
1361 // ASCII, a single percent encoded sequence.
1362 codeUnits = new List(3);
1363 codeUnits[0] = _PERCENT;
1364 codeUnits[1] = hexDigits.codeUnitAt(char >> 4);
1365 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf);
1366 } else {
1367 // Do UTF-8 encoding of character, then percent encode bytes.
1368 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
1369 int encodedBytes = 2;
1370 if (char > 0x7ff) {
1371 flag = 0xe0;
1372 encodedBytes = 3;
1373 if (char > 0xffff) {
1374 encodedBytes = 4;
1375 flag = 0xf0;
1376 }
1377 }
1378 codeUnits = new List(3 * encodedBytes);
1379 int index = 0;
1380 while (--encodedBytes >= 0) {
1381 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
1382 codeUnits[index] = _PERCENT;
1383 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4);
1384 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf);
1385 index += 3;
1386 flag = 0x80; // Following bytes have only high bit set.
1387 }
1388 }
1389 return new String.fromCharCodes(codeUnits);
1390 }
1391
1392 /**
1393 * Runs through component checking that each character is valid and
1394 * normalize percent escapes.
1395 *
1396 * Uses [charTable] to check if a non-`%` character is allowed.
1397 * Each `%` character must be followed by two hex digits.
1398 * If the hex-digits are lower case letters, they are converted to
1399 * upper case.
1400 */
1401 static String _normalize(String component, int start, int end,
1402 List<int> charTable) {
1403 StringBuffer buffer;
1404 int sectionStart = start;
1405 int index = start;
1406 // Loop while characters are valid and escapes correct and upper-case.
1407 while (index < end) {
1408 int char = component.codeUnitAt(index);
1409 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
1410 index++;
1411 } else {
1412 String replacement;
1413 int sourceLength;
1414 if (char == _PERCENT) {
1415 replacement = _normalizeEscape(component, index, false);
1416 // Returns null if we should keep the existing escape.
1417 if (replacement == null) {
1418 index += 3;
1419 continue;
1420 }
1421 // Returns "%" if we should escape the existing percent.
1422 if ("%" == replacement) {
1423 replacement = "%25";
1424 sourceLength = 1;
1425 } else {
1426 sourceLength = 3;
1427 }
1428 } else if (_isGeneralDelimiter(char)) {
1429 _fail(component, index, "Invalid character");
1430 } else {
1431 sourceLength = 1;
1432 if ((char & 0xFC00) == 0xD800) {
1433 // Possible lead surrogate.
1434 if (index + 1 < end) {
1435 int tail = component.codeUnitAt(index + 1);
1436 if ((tail & 0xFC00) == 0xDC00) {
1437 // Tail surrogat.
1438 sourceLength = 2;
1439 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1440 }
1441 }
1442 }
1443 replacement = _escapeChar(char);
1444 }
1445 if (buffer == null) buffer = new StringBuffer();
1446 buffer.write(component.substring(sectionStart, index));
1447 buffer.write(replacement);
1448 index += sourceLength;
1449 sectionStart = index;
1450 }
1451 }
1452 if (buffer == null) {
1453 // Makes no copy if start == 0 and end == component.length.
1454 return component.substring(start, end);
1455 }
1456 if (sectionStart < end) {
1457 buffer.write(component.substring(sectionStart, end));
1458 }
1459 return buffer.toString();
1460 }
1461
1462 static bool _isSchemeCharacter(int ch) {
1463 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1464 }
1465
1466 static bool _isGeneralDelimiter(int ch) {
1467 return ch <= _RIGHT_BRACKET &&
1468 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1469 }
1470
1471 /**
1472 * Returns whether the URI is absolute.
1473 */
1474 bool get isAbsolute => scheme != "" && fragment == "";
1475
1476 String _mergePaths(String base, String reference) {
1477 // Optimize for the case: absolute base, reference beginning with "../".
1478 int backCount = 0;
1479 int refStart = 0;
1480 // Count number of "../" at beginning of reference.
1481 while (reference.startsWith("../", refStart)) {
1482 refStart += 3;
1483 backCount++;
1484 }
1485
1486 // Drop last segment - everything after last '/' of base.
1487 int baseEnd = base.lastIndexOf('/');
1488 // Drop extra segments for each leading "../" of reference.
1489 while (baseEnd > 0 && backCount > 0) {
1490 int newEnd = base.lastIndexOf('/', baseEnd - 1);
1491 if (newEnd < 0) {
1492 break;
1493 }
1494 int delta = baseEnd - newEnd;
1495 // If we see a "." or ".." segment in base, stop here and let
1496 // _removeDotSegments handle it.
1497 if ((delta == 2 || delta == 3) &&
1498 base.codeUnitAt(newEnd + 1) == _DOT &&
1499 (delta == 2 || base.codeUnitAt(newEnd + 2) == _DOT)) {
1500 break;
1501 }
1502 baseEnd = newEnd;
1503 backCount--;
1504 }
1505 return base.replaceRange(baseEnd + 1, null,
1506 reference.substring(refStart - 3 * backCount));
1507 }
1508
1509 /// Make a guess at whether a path contains a `..` or `.` segment.
1510 ///
1511 /// This is a primitive test that can cause false positives.
1512 /// It's only used to avoid a more expensive operation in the case where
1513 /// it's not necessary.
1514 static bool _mayContainDotSegments(String path) {
1515 if (path.startsWith('.')) return true;
1516 int index = path.indexOf("/.");
1517 return index != -1;
1518 }
1519
1520 /// Removes '.' and '..' segments from a path.
1521 ///
1522 /// Follows the RFC 2986 "remove dot segments" algorithm.
1523 /// This algorithm is only used on paths of URIs with a scheme,
1524 /// and it treats the path as if it is absolute (leading '..' are removed).
1525 static String _removeDotSegments(String path) {
1526 if (!_mayContainDotSegments(path)) return path;
1527 assert(path.isNotEmpty); // An empty path would not have dot segments.
1528 List<String> output = [];
1529 bool appendSlash = false;
1530 for (String segment in path.split("/")) {
1531 appendSlash = false;
1532 if (segment == "..") {
1533 if (output.isNotEmpty) {
1534 output.removeLast();
1535 if (output.isEmpty) {
1536 output.add("");
1537 }
1538 }
1539 appendSlash = true;
1540 } else if ("." == segment) {
1541 appendSlash = true;
1542 } else {
1543 output.add(segment);
1544 }
1545 }
1546 if (appendSlash) output.add("");
1547 return output.join("/");
1548 }
1549
1550 /// Removes all `.` segments and any non-leading `..` segments.
1551 ///
1552 /// Removing the ".." from a "bar/foo/.." sequence results in "bar/"
1553 /// (trailing "/"). If the entire path is removed (because it contains as
1554 /// many ".." segments as real segments), the result is "./".
1555 /// This is different from an empty string, which represents "no path",
1556 /// when you resolve it against a base URI with a path with a non-empty
1557 /// final segment.
1558 static String _normalizeRelativePath(String path) {
1559 assert(!path.startsWith('/')); // Only get called for relative paths.
1560 if (!_mayContainDotSegments(path)) return path;
1561 assert(path.isNotEmpty); // An empty path would not have dot segments.
1562 List<String> output = [];
1563 bool appendSlash = false;
1564 for (String segment in path.split("/")) {
1565 appendSlash = false;
1566 if (".." == segment) {
1567 if (!output.isEmpty && output.last != "..") {
1568 output.removeLast();
1569 appendSlash = true;
1570 } else {
1571 output.add("..");
1572 }
1573 } else if ("." == segment) {
1574 appendSlash = true;
1575 } else {
1576 output.add(segment);
1577 }
1578 }
1579 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
1580 return "./";
1581 }
1582 if (appendSlash || output.last == '..') output.add("");
1583 return output.join("/");
1584 }
1585
1586 /**
1587 * Resolve [reference] as an URI relative to `this`.
1588 *
1589 * First turn [reference] into a URI using [Uri.parse]. Then resolve the
1590 * resulting URI relative to `this`.
1591 *
1592 * Returns the resolved URI.
1593 *
1594 * See [resolveUri] for details.
1595 */
1596 Uri resolve(String reference) {
1597 return resolveUri(Uri.parse(reference));
1598 }
1599
1600 /**
1601 * Resolve [reference] as an URI relative to `this`.
1602 *
1603 * Returns the resolved URI.
1604 *
1605 * The algorithm "Transform Reference" for resolving a reference is
1606 * described in [RFC-3986 Section 5]
1607 * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
1608 *
1609 * Updated to handle the case where the base URI is just a relative path -
1610 * that is: when it has no scheme or authority and the path does not start
1611 * with a slash.
1612 * In that case, the paths are combined without removing leading "..", and
1613 * an empty path is not converted to "/".
1614 */
1615 Uri resolveUri(Uri reference) {
1616 // From RFC 3986.
1617 String targetScheme;
1618 String targetUserInfo = "";
1619 String targetHost;
1620 int targetPort;
1621 String targetPath;
1622 String targetQuery;
1623 if (reference.scheme.isNotEmpty) {
1624 targetScheme = reference.scheme;
1625 if (reference.hasAuthority) {
1626 targetUserInfo = reference.userInfo;
1627 targetHost = reference.host;
1628 targetPort = reference.hasPort ? reference.port : null;
1629 }
1630 targetPath = _removeDotSegments(reference.path);
1631 if (reference.hasQuery) {
1632 targetQuery = reference.query;
1633 }
1634 } else {
1635 targetScheme = this.scheme;
1636 if (reference.hasAuthority) {
1637 targetUserInfo = reference.userInfo;
1638 targetHost = reference.host;
1639 targetPort = _makePort(reference.hasPort ? reference.port : null,
1640 targetScheme);
1641 targetPath = _removeDotSegments(reference.path);
1642 if (reference.hasQuery) targetQuery = reference.query;
1643 } else {
1644 targetUserInfo = this._userInfo;
1645 targetHost = this._host;
1646 targetPort = this._port;
1647 if (reference.path == "") {
1648 targetPath = this._path;
1649 if (reference.hasQuery) {
1650 targetQuery = reference.query;
1651 } else {
1652 targetQuery = this._query;
1653 }
1654 } else {
1655 if (reference.hasAbsolutePath) {
1656 targetPath = _removeDotSegments(reference.path);
1657 } else {
1658 // This is the RFC 3986 behavior for merging.
1659 if (this.hasEmptyPath) {
1660 if (!this.hasScheme && !this.hasAuthority) {
1661 // Keep the path relative if no scheme or authority.
1662 targetPath = reference.path;
1663 } else {
1664 // Add path normalization on top of RFC algorithm.
1665 targetPath = _removeDotSegments("/" + reference.path);
1666 }
1667 } else {
1668 var mergedPath = _mergePaths(this._path, reference.path);
1669 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
1670 targetPath = _removeDotSegments(mergedPath);
1671 } else {
1672 // Non-RFC 3986 beavior. If both base and reference are relative
1673 // path, allow the merged path to start with "..".
1674 // The RFC only specifies the case where the base has a scheme.
1675 targetPath = _normalizeRelativePath(mergedPath);
1676 }
1677 }
1678 }
1679 if (reference.hasQuery) targetQuery = reference.query;
1680 }
1681 }
1682 }
1683 String fragment = reference.hasFragment ? reference.fragment : null;
1684 return new Uri._internal(targetScheme,
1685 targetUserInfo,
1686 targetHost,
1687 targetPort,
1688 targetPath,
1689 targetQuery,
1690 fragment);
1691 }
1692
1693 /**
1694 * Returns whether the URI has a [scheme] component.
1695 */
1696 bool get hasScheme => scheme.isNotEmpty;
1697
1698 /**
1699 * Returns whether the URI has an [authority] component.
1700 */
1701 bool get hasAuthority => _host != null;
1702
1703 /**
1704 * Returns whether the URI has an explicit port.
1705 *
1706 * If the port number is the default port number
1707 * (zero for unrecognized schemes, with http (80) and https (443) being
1708 * recognized),
1709 * then the port is made implicit and omitted from the URI.
1710 */
1711 bool get hasPort => _port != null;
1712
1713 /**
1714 * Returns whether the URI has a query part.
1715 */
1716 bool get hasQuery => _query != null;
1717
1718 /**
1719 * Returns whether the URI has a fragment part.
1720 */
1721 bool get hasFragment => _fragment != null;
1722
1723 /**
1724 * Returns whether the URI has an empty path.
1725 */
1726 bool get hasEmptyPath => _path.isEmpty;
1727
1728 /**
1729 * Returns whether the URI has an absolute path (starting with '/').
1730 */
1731 bool get hasAbsolutePath => _path.startsWith('/');
1732
1733 /**
1734 * Returns the origin of the URI in the form scheme://host:port for the
1735 * schemes http and https.
1736 *
1737 * It is an error if the scheme is not "http" or "https".
1738 *
1739 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
1740 */
1741 String get origin {
1742 if (scheme == "" || _host == null || _host == "") {
1743 throw new StateError("Cannot use origin without a scheme: $this");
1744 }
1745 if (scheme != "http" && scheme != "https") {
1746 throw new StateError(
1747 "Origin is only applicable schemes http and https: $this");
1748 }
1749 if (_port == null) return "$scheme://$_host";
1750 return "$scheme://$_host:$_port";
1751 }
1752
1753 /**
1754 * Returns the file path from a file URI.
1755 *
1756 * The returned path has either Windows or non-Windows
1757 * semantics.
1758 *
1759 * For non-Windows semantics the slash ("/") is used to separate
1760 * path segments.
1761 *
1762 * For Windows semantics the backslash ("\") separator is used to
1763 * separate path segments.
1764 *
1765 * If the URI is absolute the path starts with a path separator
1766 * unless Windows semantics is used and the first path segment is a
1767 * drive letter. When Windows semantics is used a host component in
1768 * the uri in interpreted as a file server and a UNC path is
1769 * returned.
1770 *
1771 * The default for whether to use Windows or non-Windows semantics
1772 * determined from the platform Dart is running on. When running in
1773 * the standalone VM this is detected by the VM based on the
1774 * operating system. When running in a browser non-Windows semantics
1775 * is always used.
1776 *
1777 * To override the automatic detection of which semantics to use pass
1778 * a value for [windows]. Passing `true` will use Windows
1779 * semantics and passing `false` will use non-Windows semantics.
1780 *
1781 * If the URI ends with a slash (i.e. the last path component is
1782 * empty) the returned file path will also end with a slash.
1783 *
1784 * With Windows semantics URIs starting with a drive letter cannot
1785 * be relative to the current drive on the designated drive. That is
1786 * for the URI `file:///c:abc` calling `toFilePath` will throw as a
1787 * path segment cannot contain colon on Windows.
1788 *
1789 * Examples using non-Windows semantics (resulting of calling
1790 * toFilePath in comment):
1791 *
1792 * Uri.parse("xxx/yyy"); // xxx/yyy
1793 * Uri.parse("xxx/yyy/"); // xxx/yyy/
1794 * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
1795 * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
1796 * Uri.parse("file:///C:"); // /C:
1797 * Uri.parse("file:///C:a"); // /C:a
1798 *
1799 * Examples using Windows semantics (resulting URI in comment):
1800 *
1801 * Uri.parse("xxx/yyy"); // xxx\yyy
1802 * Uri.parse("xxx/yyy/"); // xxx\yyy\
1803 * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
1804 * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
1805 * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
1806 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
1807 * // cannot contain colon on Windows.
1808 * Uri.parse("file://server/share/file"); // \\server\share\file
1809 *
1810 * If the URI is not a file URI calling this throws
1811 * [UnsupportedError].
1812 *
1813 * If the URI cannot be converted to a file path calling this throws
1814 * [UnsupportedError].
1815 */
1816 String toFilePath({bool windows}) {
1817 if (scheme != "" && scheme != "file") {
1818 throw new UnsupportedError(
1819 "Cannot extract a file path from a $scheme URI");
1820 }
1821 if (query != "") {
1822 throw new UnsupportedError(
1823 "Cannot extract a file path from a URI with a query component");
1824 }
1825 if (fragment != "") {
1826 throw new UnsupportedError(
1827 "Cannot extract a file path from a URI with a fragment component");
1828 }
1829 if (windows == null) windows = _isWindows;
1830 return windows ? _toWindowsFilePath() : _toFilePath();
1831 }
1832
1833 String _toFilePath() {
1834 if (host != "") {
1835 throw new UnsupportedError(
1836 "Cannot extract a non-Windows file path from a file URI "
1837 "with an authority");
1838 }
1839 _checkNonWindowsPathReservedCharacters(pathSegments, false);
1840 var result = new StringBuffer();
1841 if (_isPathAbsolute) result.write("/");
1842 result.writeAll(pathSegments, "/");
1843 return result.toString();
1844 }
1845
1846 String _toWindowsFilePath() {
1847 bool hasDriveLetter = false;
1848 var segments = pathSegments;
1849 if (segments.length > 0 &&
1850 segments[0].length == 2 &&
1851 segments[0].codeUnitAt(1) == _COLON) {
1852 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
1853 _checkWindowsPathReservedCharacters(segments, false, 1);
1854 hasDriveLetter = true;
1855 } else {
1856 _checkWindowsPathReservedCharacters(segments, false);
1857 }
1858 var result = new StringBuffer();
1859 if (_isPathAbsolute && !hasDriveLetter) result.write("\\");
1860 if (host != "") {
1861 result.write("\\");
1862 result.write(host);
1863 result.write("\\");
1864 }
1865 result.writeAll(segments, "\\");
1866 if (hasDriveLetter && segments.length == 1) result.write("\\");
1867 return result.toString();
1868 }
1869
1870 bool get _isPathAbsolute {
1871 if (path == null || path.isEmpty) return false;
1872 return path.startsWith('/');
1873 }
1874
1875 void _writeAuthority(StringSink ss) {
1876 if (_userInfo.isNotEmpty) {
1877 ss.write(_userInfo);
1878 ss.write("@");
1879 }
1880 if (_host != null) ss.write(_host);
1881 if (_port != null) {
1882 ss.write(":");
1883 ss.write(_port);
1884 }
1885 }
1886
1887 String toString() {
1888 StringBuffer sb = new StringBuffer();
1889 _addIfNonEmpty(sb, scheme, scheme, ':');
1890 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
1891 // File URIS always have the authority, even if it is empty.
1892 // The empty URI means "localhost".
1893 sb.write("//");
1894 _writeAuthority(sb);
1895 }
1896 sb.write(path);
1897 if (_query != null) { sb..write("?")..write(_query); }
1898 if (_fragment != null) { sb..write("#")..write(_fragment); }
1899 return sb.toString();
1900 }
1901
1902 bool operator==(other) {
1903 if (other is! Uri) return false;
1904 Uri uri = other;
1905 return scheme == uri.scheme &&
1906 hasAuthority == uri.hasAuthority &&
1907 userInfo == uri.userInfo &&
1908 host == uri.host &&
1909 port == uri.port &&
1910 path == uri.path &&
1911 hasQuery == uri.hasQuery &&
1912 query == uri.query &&
1913 hasFragment == uri.hasFragment &&
1914 fragment == uri.fragment;
1915 }
1916
1917 int get hashCode {
1918 int combine(part, current) {
1919 // The sum is truncated to 30 bits to make sure it fits into a Smi.
1920 return (current * 31 + part.hashCode) & 0x3FFFFFFF;
1921 }
1922 return combine(scheme, combine(userInfo, combine(host, combine(port,
1923 combine(path, combine(query, combine(fragment, 1)))))));
1924 }
1925
1926 static void _addIfNonEmpty(StringBuffer sb, String test,
1927 String first, String second) {
1928 if ("" != test) {
1929 sb.write(first);
1930 sb.write(second);
1931 }
1932 }
1933
1934 /**
1935 * Encode the string [component] using percent-encoding to make it
1936 * safe for literal use as a URI component.
1937 *
1938 * All characters except uppercase and lowercase letters, digits and
1939 * the characters `-_.!~*'()` are percent-encoded. This is the
1940 * set of characters specified in RFC 2396 and the which is
1941 * specified for the encodeUriComponent in ECMA-262 version 5.1.
1942 *
1943 * When manually encoding path segments or query components remember
1944 * to encode each part separately before building the path or query
1945 * string.
1946 *
1947 * For encoding the query part consider using
1948 * [encodeQueryComponent].
1949 *
1950 * To avoid the need for explicitly encoding use the [pathSegments]
1951 * and [queryParameters] optional named arguments when constructing
1952 * a [Uri].
1953 */
1954 static String encodeComponent(String component) {
1955 return _uriEncode(_unreserved2396Table, component);
1956 }
1957
1958 /**
1959 * Encode the string [component] according to the HTML 4.01 rules
1960 * for encoding the posting of a HTML form as a query string
1961 * component.
1962 *
1963 * Encode the string [component] according to the HTML 4.01 rules
1964 * for encoding the posting of a HTML form as a query string
1965 * component.
1966
1967 * The component is first encoded to bytes using [encoding].
1968 * The default is to use [UTF8] encoding, which preserves all
1969 * the characters that don't need encoding.
1970
1971 * Then the resulting bytes are "percent-encoded". This transforms
1972 * spaces (U+0020) to a plus sign ('+') and all bytes that are not
1973 * the ASCII decimal digits, letters or one of '-._~' are written as
1974 * a percent sign '%' followed by the two-digit hexadecimal
1975 * representation of the byte.
1976
1977 * Note that the set of characters which are percent-encoded is a
1978 * superset of what HTML 4.01 requires, since it refers to RFC 1738
1979 * for reserved characters.
1980 *
1981 * When manually encoding query components remember to encode each
1982 * part separately before building the query string.
1983 *
1984 * To avoid the need for explicitly encoding the query use the
1985 * [queryParameters] optional named arguments when constructing a
1986 * [Uri].
1987 *
1988 * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
1989 * details.
1990 */
1991 static String encodeQueryComponent(String component,
1992 {Encoding encoding: UTF8}) {
1993 return _uriEncode(
1994 _unreservedTable, component, encoding: encoding, spaceToPlus: true);
1995 }
1996
1997 /**
1998 * Decodes the percent-encoding in [encodedComponent].
1999 *
2000 * Note that decoding a URI component might change its meaning as
2001 * some of the decoded characters could be characters with are
2002 * delimiters for a given URI componene type. Always split a URI
2003 * component using the delimiters for the component before decoding
2004 * the individual parts.
2005 *
2006 * For handling the [path] and [query] components consider using
2007 * [pathSegments] and [queryParameters] to get the separated and
2008 * decoded component.
2009 */
2010 static String decodeComponent(String encodedComponent) {
2011 return _uriDecode(encodedComponent);
2012 }
2013
2014 /**
2015 * Decodes the percent-encoding in [encodedComponent], converting
2016 * pluses to spaces.
2017 *
2018 * It will create a byte-list of the decoded characters, and then use
2019 * [encoding] to decode the byte-list to a String. The default encoding is
2020 * UTF-8.
2021 */
2022 static String decodeQueryComponent(
2023 String encodedComponent,
2024 {Encoding encoding: UTF8}) {
2025 return _uriDecode(encodedComponent, plusToSpace: true, encoding: encoding);
2026 }
2027
2028 /**
2029 * Encode the string [uri] using percent-encoding to make it
2030 * safe for literal use as a full URI.
2031 *
2032 * All characters except uppercase and lowercase letters, digits and
2033 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
2034 * is the set of characters specified in in ECMA-262 version 5.1 for
2035 * the encodeURI function .
2036 */
2037 static String encodeFull(String uri) {
2038 return _uriEncode(_encodeFullTable, uri);
2039 }
2040
2041 /**
2042 * Decodes the percent-encoding in [uri].
2043 *
2044 * Note that decoding a full URI might change its meaning as some of
2045 * the decoded characters could be reserved characters. In most
2046 * cases an encoded URI should be parsed into components using
2047 * [Uri.parse] before decoding the separate components.
2048 */
2049 static String decodeFull(String uri) {
2050 return _uriDecode(uri);
2051 }
2052
2053 /**
2054 * Returns the [query] split into a map according to the rules
2055 * specified for FORM post in the
2056 * [HTML 4.01 specification section 17.13.4]
2057 * (http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4
2058 * "HTML 4.01 section 17.13.4"). Each key and value in the returned
2059 * map has been decoded. If the [query]
2060 * is the empty string an empty map is returned.
2061 *
2062 * Keys in the query string that have no value are mapped to the
2063 * empty string.
2064 *
2065 * Each query component will be decoded using [encoding]. The default encoding
2066 * is UTF-8.
2067 */
2068 static Map<String, String> splitQueryString(String query,
2069 {Encoding encoding: UTF8}) {
2070 return query.split("&").fold({}, (map, element) {
2071 int index = element.indexOf("=");
2072 if (index == -1) {
2073 if (element != "") {
2074 map[decodeQueryComponent(element, encoding: encoding)] = "";
2075 }
2076 } else if (index != 0) {
2077 var key = element.substring(0, index);
2078 var value = element.substring(index + 1);
2079 map[Uri.decodeQueryComponent(key, encoding: encoding)] =
2080 decodeQueryComponent(value, encoding: encoding);
2081 }
2082 return map;
2083 });
2084 }
2085
2086 /**
2087 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
2088 * as a list of 4 bytes in network byte order (big endian).
2089 *
2090 * Throws a [FormatException] if [host] is not a valid IPv4 address
2091 * representation.
2092 */
2093 static List<int> parseIPv4Address(String host) {
2094 void error(String msg) {
2095 throw new FormatException('Illegal IPv4 address, $msg');
2096 }
2097 var bytes = host.split('.');
2098 if (bytes.length != 4) {
2099 error('IPv4 address should contain exactly 4 parts');
2100 }
2101 // TODO(ajohnsen): Consider using Uint8List.
2102 return bytes
2103 .map((byteString) {
2104 int byte = int.parse(byteString);
2105 if (byte < 0 || byte > 255) {
2106 error('each part must be in the range of `0..255`');
2107 }
2108 return byte;
2109 })
2110 .toList();
2111 }
2112
2113 /**
2114 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
2115 * as a list of 16 bytes in network byte order (big endian).
2116 *
2117 * Throws a [FormatException] if [host] is not a valid IPv6 address
2118 * representation.
2119 *
2120 * Acts on the substring from [start] to [end]. If [end] is omitted, it
2121 * defaults ot the end of the string.
2122 *
2123 * Some examples of IPv6 addresses:
2124 * * ::1
2125 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
2126 * * 3ffe:2a00:100:7031::1
2127 * * ::FFFF:129.144.52.38
2128 * * 2010:836B:4179::836B:4179
2129 */
2130 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
2131 if (end == null) end = host.length;
2132 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
2133 // by `:`'s, with the following exceptions:
2134 //
2135 // - One (and only one) wildcard (`::`) may be present, representing a fill
2136 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
2137 // - The last two parts may be replaced by an IPv4 address.
2138 void error(String msg, [position]) {
2139 throw new FormatException('Illegal IPv6 address, $msg', host, position);
2140 }
2141 int parseHex(int start, int end) {
2142 if (end - start > 4) {
2143 error('an IPv6 part can only contain a maximum of 4 hex digits', start);
2144 }
2145 int value = int.parse(host.substring(start, end), radix: 16);
2146 if (value < 0 || value > (1 << 16) - 1) {
2147 error('each part must be in the range of `0x0..0xFFFF`', start);
2148 }
2149 return value;
2150 }
2151 if (host.length < 2) error('address is too short');
2152 List<int> parts = [];
2153 bool wildcardSeen = false;
2154 int partStart = start;
2155 // Parse all parts, except a potential last one.
2156 for (int i = start; i < end; i++) {
2157 if (host.codeUnitAt(i) == _COLON) {
2158 if (i == start) {
2159 // If we see a `:` in the beginning, expect wildcard.
2160 i++;
2161 if (host.codeUnitAt(i) != _COLON) {
2162 error('invalid start colon.', i);
2163 }
2164 partStart = i;
2165 }
2166 if (i == partStart) {
2167 // Wildcard. We only allow one.
2168 if (wildcardSeen) {
2169 error('only one wildcard `::` is allowed', i);
2170 }
2171 wildcardSeen = true;
2172 parts.add(-1);
2173 } else {
2174 // Found a single colon. Parse [partStart..i] as a hex entry.
2175 parts.add(parseHex(partStart, i));
2176 }
2177 partStart = i + 1;
2178 }
2179 }
2180 if (parts.length == 0) error('too few parts');
2181 bool atEnd = (partStart == end);
2182 bool isLastWildcard = (parts.last == -1);
2183 if (atEnd && !isLastWildcard) {
2184 error('expected a part after last `:`', end);
2185 }
2186 if (!atEnd) {
2187 try {
2188 parts.add(parseHex(partStart, end));
2189 } catch (e) {
2190 // Failed to parse the last chunk as hex. Try IPv4.
2191 try {
2192 List<int> last = parseIPv4Address(host.substring(partStart, end));
2193 parts.add(last[0] << 8 | last[1]);
2194 parts.add(last[2] << 8 | last[3]);
2195 } catch (e) {
2196 error('invalid end of IPv6 address.', partStart);
2197 }
2198 }
2199 }
2200 if (wildcardSeen) {
2201 if (parts.length > 7) {
2202 error('an address with a wildcard must have less than 7 parts');
2203 }
2204 } else if (parts.length != 8) {
2205 error('an address without a wildcard must contain exactly 8 parts');
2206 }
2207 // TODO(ajohnsen): Consider using Uint8List.
2208 List bytes = new List<int>(16);
2209 for (int i = 0, index = 0; i < parts.length; i++) {
2210 int value = parts[i];
2211 if (value == -1) {
2212 int wildCardLength = 9 - parts.length;
2213 for (int j = 0; j < wildCardLength; j++) {
2214 bytes[index] = 0;
2215 bytes[index + 1] = 0;
2216 index += 2;
2217 }
2218 } else {
2219 bytes[index] = value >> 8;
2220 bytes[index + 1] = value & 0xff;
2221 index += 2;
2222 }
2223 }
2224 return bytes;
2225 }
2226
2227 // Frequently used character codes.
2228 static const int _SPACE = 0x20;
2229 static const int _DOUBLE_QUOTE = 0x22;
2230 static const int _NUMBER_SIGN = 0x23;
2231 static const int _PERCENT = 0x25;
2232 static const int _ASTERISK = 0x2A;
2233 static const int _PLUS = 0x2B;
2234 static const int _DOT = 0x2E;
2235 static const int _SLASH = 0x2F;
2236 static const int _ZERO = 0x30;
2237 static const int _NINE = 0x39;
2238 static const int _COLON = 0x3A;
2239 static const int _LESS = 0x3C;
2240 static const int _GREATER = 0x3E;
2241 static const int _QUESTION = 0x3F;
2242 static const int _AT_SIGN = 0x40;
2243 static const int _UPPER_CASE_A = 0x41;
2244 static const int _UPPER_CASE_F = 0x46;
2245 static const int _UPPER_CASE_Z = 0x5A;
2246 static const int _LEFT_BRACKET = 0x5B;
2247 static const int _BACKSLASH = 0x5C;
2248 static const int _RIGHT_BRACKET = 0x5D;
2249 static const int _LOWER_CASE_A = 0x61;
2250 static const int _LOWER_CASE_F = 0x66;
2251 static const int _LOWER_CASE_Z = 0x7A;
2252 static const int _BAR = 0x7C;
2253
2254 /**
2255 * This is the internal implementation of JavaScript's encodeURI function.
2256 * It encodes all characters in the string [text] except for those
2257 * that appear in [canonicalTable], and returns the escaped string.
2258 */
2259 static String _uriEncode(List<int> canonicalTable,
2260 String text,
2261 {Encoding encoding: UTF8,
2262 bool spaceToPlus: false}) {
2263 byteToHex(byte, buffer) {
2264 const String hex = '0123456789ABCDEF';
2265 buffer.writeCharCode(hex.codeUnitAt(byte >> 4));
2266 buffer.writeCharCode(hex.codeUnitAt(byte & 0x0f));
2267 }
2268
2269 // Encode the string into bytes then generate an ASCII only string
2270 // by percent encoding selected bytes.
2271 StringBuffer result = new StringBuffer();
2272 var bytes = encoding.encode(text);
2273 for (int i = 0; i < bytes.length; i++) {
2274 int byte = bytes[i];
2275 if (byte < 128 &&
2276 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
2277 result.writeCharCode(byte);
2278 } else if (spaceToPlus && byte == _SPACE) {
2279 result.writeCharCode(_PLUS);
2280 } else {
2281 result.writeCharCode(_PERCENT);
2282 byteToHex(byte, result);
2283 }
2284 }
2285 return result.toString();
2286 }
2287
2288 /**
2289 * Convert a byte (2 character hex sequence) in string [s] starting
2290 * at position [pos] to its ordinal value
2291 */
2292 static int _hexCharPairToByte(String s, int pos) {
2293 int byte = 0;
2294 for (int i = 0; i < 2; i++) {
2295 var charCode = s.codeUnitAt(pos + i);
2296 if (0x30 <= charCode && charCode <= 0x39) {
2297 byte = byte * 16 + charCode - 0x30;
2298 } else {
2299 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66).
2300 charCode |= 0x20;
2301 if (0x61 <= charCode && charCode <= 0x66) {
2302 byte = byte * 16 + charCode - 0x57;
2303 } else {
2304 throw new ArgumentError("Invalid URL encoding");
2305 }
2306 }
2307 }
2308 return byte;
2309 }
2310
2311 /**
2312 * Uri-decode a percent-encoded string.
2313 *
2314 * It unescapes the string [text] and returns the unescaped string.
2315 *
2316 * This function is similar to the JavaScript-function `decodeURI`.
2317 *
2318 * If [plusToSpace] is `true`, plus characters will be converted to spaces.
2319 *
2320 * The decoder will create a byte-list of the percent-encoded parts, and then
2321 * decode the byte-list using [encoding]. The default encodingis UTF-8.
2322 */
2323 static String _uriDecode(String text,
2324 {bool plusToSpace: false,
2325 Encoding encoding: UTF8}) {
2326 // First check whether there is any characters which need special handling.
2327 bool simple = true;
2328 for (int i = 0; i < text.length && simple; i++) {
2329 var codeUnit = text.codeUnitAt(i);
2330 simple = codeUnit != _PERCENT && codeUnit != _PLUS;
2331 }
2332 List<int> bytes;
2333 if (simple) {
2334 if (encoding == UTF8 || encoding == LATIN1) {
2335 return text;
2336 } else {
2337 bytes = text.codeUnits;
2338 }
2339 } else {
2340 bytes = new List();
2341 for (int i = 0; i < text.length; i++) {
2342 var codeUnit = text.codeUnitAt(i);
2343 if (codeUnit > 127) {
2344 throw new ArgumentError("Illegal percent encoding in URI");
2345 }
2346 if (codeUnit == _PERCENT) {
2347 if (i + 3 > text.length) {
2348 throw new ArgumentError('Truncated URI');
2349 }
2350 bytes.add(_hexCharPairToByte(text, i + 1));
2351 i += 2;
2352 } else if (plusToSpace && codeUnit == _PLUS) {
2353 bytes.add(_SPACE);
2354 } else {
2355 bytes.add(codeUnit);
2356 }
2357 }
2358 }
2359 return encoding.decode(bytes);
2360 }
2361
2362 static bool _isAlphabeticCharacter(int codeUnit)
2363 => (codeUnit >= _LOWER_CASE_A && codeUnit <= _LOWER_CASE_Z) ||
2364 (codeUnit >= _UPPER_CASE_A && codeUnit <= _UPPER_CASE_Z);
2365
2366 // Tables of char-codes organized as a bit vector of 128 bits where
2367 // each bit indicate whether a character code on the 0-127 needs to
2368 // be escaped or not.
2369
2370 // The unreserved characters of RFC 3986.
2371 static const _unreservedTable = const [
2372 // LSB MSB
2373 // | |
2374 0x0000, // 0x00 - 0x0f 0000000000000000
2375 0x0000, // 0x10 - 0x1f 0000000000000000
2376 // -.
2377 0x6000, // 0x20 - 0x2f 0000000000000110
2378 // 0123456789
2379 0x03ff, // 0x30 - 0x3f 1111111111000000
2380 // ABCDEFGHIJKLMNO
2381 0xfffe, // 0x40 - 0x4f 0111111111111111
2382 // PQRSTUVWXYZ _
2383 0x87ff, // 0x50 - 0x5f 1111111111100001
2384 // abcdefghijklmno
2385 0xfffe, // 0x60 - 0x6f 0111111111111111
2386 // pqrstuvwxyz ~
2387 0x47ff]; // 0x70 - 0x7f 1111111111100010
2388
2389 // The unreserved characters of RFC 2396.
2390 static const _unreserved2396Table = const [
2391 // LSB MSB
2392 // | |
2393 0x0000, // 0x00 - 0x0f 0000000000000000
2394 0x0000, // 0x10 - 0x1f 0000000000000000
2395 // ! '()* -.
2396 0x6782, // 0x20 - 0x2f 0100000111100110
2397 // 0123456789
2398 0x03ff, // 0x30 - 0x3f 1111111111000000
2399 // ABCDEFGHIJKLMNO
2400 0xfffe, // 0x40 - 0x4f 0111111111111111
2401 // PQRSTUVWXYZ _
2402 0x87ff, // 0x50 - 0x5f 1111111111100001
2403 // abcdefghijklmno
2404 0xfffe, // 0x60 - 0x6f 0111111111111111
2405 // pqrstuvwxyz ~
2406 0x47ff]; // 0x70 - 0x7f 1111111111100010
2407
2408 // Table of reserved characters specified by ECMAScript 5.
2409 static const _encodeFullTable = const [
2410 // LSB MSB
2411 // | |
2412 0x0000, // 0x00 - 0x0f 0000000000000000
2413 0x0000, // 0x10 - 0x1f 0000000000000000
2414 // ! #$ &'()*+,-./
2415 0xffda, // 0x20 - 0x2f 0101101111111111
2416 // 0123456789:; = ?
2417 0xafff, // 0x30 - 0x3f 1111111111110101
2418 // @ABCDEFGHIJKLMNO
2419 0xffff, // 0x40 - 0x4f 1111111111111111
2420 // PQRSTUVWXYZ _
2421 0x87ff, // 0x50 - 0x5f 1111111111100001
2422 // abcdefghijklmno
2423 0xfffe, // 0x60 - 0x6f 0111111111111111
2424 // pqrstuvwxyz ~
2425 0x47ff]; // 0x70 - 0x7f 1111111111100010
2426
2427 // Characters allowed in the scheme.
2428 static const _schemeTable = const [
2429 // LSB MSB
2430 // | |
2431 0x0000, // 0x00 - 0x0f 0000000000000000
2432 0x0000, // 0x10 - 0x1f 0000000000000000
2433 // + -.
2434 0x6800, // 0x20 - 0x2f 0000000000010110
2435 // 0123456789
2436 0x03ff, // 0x30 - 0x3f 1111111111000000
2437 // ABCDEFGHIJKLMNO
2438 0xfffe, // 0x40 - 0x4f 0111111111111111
2439 // PQRSTUVWXYZ
2440 0x07ff, // 0x50 - 0x5f 1111111111100001
2441 // abcdefghijklmno
2442 0xfffe, // 0x60 - 0x6f 0111111111111111
2443 // pqrstuvwxyz
2444 0x07ff]; // 0x70 - 0x7f 1111111111100010
2445
2446 // Characters allowed in scheme except for upper case letters.
2447 static const _schemeLowerTable = const [
2448 // LSB MSB
2449 // | |
2450 0x0000, // 0x00 - 0x0f 0000000000000000
2451 0x0000, // 0x10 - 0x1f 0000000000000000
2452 // + -.
2453 0x6800, // 0x20 - 0x2f 0000000000010110
2454 // 0123456789
2455 0x03ff, // 0x30 - 0x3f 1111111111000000
2456 //
2457 0x0000, // 0x40 - 0x4f 0111111111111111
2458 //
2459 0x0000, // 0x50 - 0x5f 1111111111100001
2460 // abcdefghijklmno
2461 0xfffe, // 0x60 - 0x6f 0111111111111111
2462 // pqrstuvwxyz
2463 0x07ff]; // 0x70 - 0x7f 1111111111100010
2464
2465 // Sub delimiter characters combined with unreserved as of 3986.
2466 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
2467 // / "*" / "+" / "," / ";" / "="
2468 // RFC 3986 section 2.3.
2469 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
2470 static const _subDelimitersTable = const [
2471 // LSB MSB
2472 // | |
2473 0x0000, // 0x00 - 0x0f 0000000000000000
2474 0x0000, // 0x10 - 0x1f 0000000000000000
2475 // ! $ &'()*+,-.
2476 0x7fd2, // 0x20 - 0x2f 0100101111111110
2477 // 0123456789 ; =
2478 0x2bff, // 0x30 - 0x3f 1111111111010100
2479 // ABCDEFGHIJKLMNO
2480 0xfffe, // 0x40 - 0x4f 0111111111111111
2481 // PQRSTUVWXYZ _
2482 0x87ff, // 0x50 - 0x5f 1111111111100001
2483 // abcdefghijklmno
2484 0xfffe, // 0x60 - 0x6f 0111111111111111
2485 // pqrstuvwxyz ~
2486 0x47ff]; // 0x70 - 0x7f 1111111111100010
2487
2488 // General delimiter characters, RFC 3986 section 2.2.
2489 // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
2490 //
2491 static const _genDelimitersTable = const [
2492 // LSB MSB
2493 // | |
2494 0x0000, // 0x00 - 0x0f 0000000000000000
2495 0x0000, // 0x10 - 0x1f 0000000000000000
2496 // # /
2497 0x8008, // 0x20 - 0x2f 0001000000000001
2498 // : ?
2499 0x8400, // 0x30 - 0x3f 0000000000100001
2500 // @
2501 0x0001, // 0x40 - 0x4f 1000000000000000
2502 // [ ]
2503 0x2800, // 0x50 - 0x5f 0000000000010100
2504 //
2505 0x0000, // 0x60 - 0x6f 0000000000000000
2506 //
2507 0x0000]; // 0x70 - 0x7f 0000000000000000
2508
2509 // Characters allowed in the userinfo as of RFC 3986.
2510 // RFC 3986 Apendix A
2511 // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
2512 static const _userinfoTable = const [
2513 // LSB MSB
2514 // | |
2515 0x0000, // 0x00 - 0x0f 0000000000000000
2516 0x0000, // 0x10 - 0x1f 0000000000000000
2517 // ! $ &'()*+,-.
2518 0x7fd2, // 0x20 - 0x2f 0100101111111110
2519 // 0123456789:; =
2520 0x2fff, // 0x30 - 0x3f 1111111111110100
2521 // ABCDEFGHIJKLMNO
2522 0xfffe, // 0x40 - 0x4f 0111111111111111
2523 // PQRSTUVWXYZ _
2524 0x87ff, // 0x50 - 0x5f 1111111111100001
2525 // abcdefghijklmno
2526 0xfffe, // 0x60 - 0x6f 0111111111111111
2527 // pqrstuvwxyz ~
2528 0x47ff]; // 0x70 - 0x7f 1111111111100010
2529
2530 // Characters allowed in the reg-name as of RFC 3986.
2531 // RFC 3986 Apendix A
2532 // reg-name = *( unreserved / pct-encoded / sub-delims )
2533 static const _regNameTable = const [
2534 // LSB MSB
2535 // | |
2536 0x0000, // 0x00 - 0x0f 0000000000000000
2537 0x0000, // 0x10 - 0x1f 0000000000000000
2538 // ! $%&'()*+,-.
2539 0x7ff2, // 0x20 - 0x2f 0100111111111110
2540 // 0123456789 ; =
2541 0x2bff, // 0x30 - 0x3f 1111111111010100
2542 // ABCDEFGHIJKLMNO
2543 0xfffe, // 0x40 - 0x4f 0111111111111111
2544 // PQRSTUVWXYZ _
2545 0x87ff, // 0x50 - 0x5f 1111111111100001
2546 // abcdefghijklmno
2547 0xfffe, // 0x60 - 0x6f 0111111111111111
2548 // pqrstuvwxyz ~
2549 0x47ff]; // 0x70 - 0x7f 1111111111100010
2550
2551 // Characters allowed in the path as of RFC 3986.
2552 // RFC 3986 section 3.3.
2553 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
2554 static const _pathCharTable = const [
2555 // LSB MSB
2556 // | |
2557 0x0000, // 0x00 - 0x0f 0000000000000000
2558 0x0000, // 0x10 - 0x1f 0000000000000000
2559 // ! $ &'()*+,-.
2560 0x7fd2, // 0x20 - 0x2f 0100101111111110
2561 // 0123456789:; =
2562 0x2fff, // 0x30 - 0x3f 1111111111110100
2563 // @ABCDEFGHIJKLMNO
2564 0xffff, // 0x40 - 0x4f 1111111111111111
2565 // PQRSTUVWXYZ _
2566 0x87ff, // 0x50 - 0x5f 1111111111100001
2567 // abcdefghijklmno
2568 0xfffe, // 0x60 - 0x6f 0111111111111111
2569 // pqrstuvwxyz ~
2570 0x47ff]; // 0x70 - 0x7f 1111111111100010
2571
2572 // Characters allowed in the path as of RFC 3986.
2573 // RFC 3986 section 3.3 *and* slash.
2574 static const _pathCharOrSlashTable = const [
2575 // LSB MSB
2576 // | |
2577 0x0000, // 0x00 - 0x0f 0000000000000000
2578 0x0000, // 0x10 - 0x1f 0000000000000000
2579 // ! $ &'()*+,-./
2580 0xffd2, // 0x20 - 0x2f 0100101111111111
2581 // 0123456789:; =
2582 0x2fff, // 0x30 - 0x3f 1111111111110100
2583 // @ABCDEFGHIJKLMNO
2584 0xffff, // 0x40 - 0x4f 1111111111111111
2585
2586 // PQRSTUVWXYZ _
2587 0x87ff, // 0x50 - 0x5f 1111111111100001
2588 // abcdefghijklmno
2589 0xfffe, // 0x60 - 0x6f 0111111111111111
2590 // pqrstuvwxyz ~
2591 0x47ff]; // 0x70 - 0x7f 1111111111100010
2592
2593 // Characters allowed in the query as of RFC 3986.
2594 // RFC 3986 section 3.4.
2595 // query = *( pchar / "/" / "?" )
2596 static const _queryCharTable = const [
2597 // LSB MSB
2598 // | |
2599 0x0000, // 0x00 - 0x0f 0000000000000000
2600 0x0000, // 0x10 - 0x1f 0000000000000000
2601 // ! $ &'()*+,-./
2602 0xffd2, // 0x20 - 0x2f 0100101111111111
2603 // 0123456789:; = ?
2604 0xafff, // 0x30 - 0x3f 1111111111110101
2605 // @ABCDEFGHIJKLMNO
2606 0xffff, // 0x40 - 0x4f 1111111111111111
2607 // PQRSTUVWXYZ _
2608 0x87ff, // 0x50 - 0x5f 1111111111100001
2609 // abcdefghijklmno
2610 0xfffe, // 0x60 - 0x6f 0111111111111111
2611 // pqrstuvwxyz ~
2612 0x47ff]; // 0x70 - 0x7f 1111111111100010
2613 }
OLDNEW
« no previous file with comments | « sdk/lib/core/core_sources.gypi ('k') | sdk/lib/uri/uri.dart » ('j') | sdk/lib/uri/uri.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698