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

Side by Side Diff: pkg/dev_compiler/tool/input_sdk/lib/core/uri.dart

Issue 2698353003: unfork DDC's copy of most SDK libraries (Closed)
Patch Set: revert core_patch Created 3 years, 9 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
(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]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
16 * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.ht ml
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 Map<String, List<String>> _queryParameterLists;
80
81 /// Internal non-verifying constructor. Only call with validated arguments.
82 Uri._internal(this.scheme,
83 this._userInfo,
84 this._host,
85 this._port,
86 this._path,
87 this._query,
88 this._fragment);
89
90 /**
91 * Creates a new URI from its components.
92 *
93 * Each component is set through a named argument. Any number of
94 * components can be provided. The [path] and [query] components can be set
95 * using either of two different named arguments.
96 *
97 * The scheme component is set through [scheme]. The scheme is
98 * normalized to all lowercase letters. If the scheme is omitted or empty,
99 * the URI will not have a scheme part.
100 *
101 * The user info part of the authority component is set through
102 * [userInfo]. It defaults to the empty string, which will be omitted
103 * from the string representation of the URI.
104 *
105 * The host part of the authority component is set through
106 * [host]. The host can either be a hostname, an IPv4 address or an
107 * IPv6 address, contained in '[' and ']'. If the host contains a
108 * ':' character, the '[' and ']' are added if not already provided.
109 * The host is normalized to all lowercase letters.
110 *
111 * The port part of the authority component is set through
112 * [port].
113 * If [port] is omitted or `null`, it implies the default port for
114 * the URI's scheme, and is equivalent to passing that port explicitly.
115 * The recognized schemes, and their default ports, are "http" (80) and
116 * "https" (443). All other schemes are considered as having zero as the
117 * default port.
118 *
119 * If any of `userInfo`, `host` or `port` are provided,
120 * the URI has an autority according to [hasAuthority].
121 *
122 * The path component is set through either [path] or
123 * [pathSegments].
124 * When [path] is used, it should be a valid URI path,
125 * but invalid characters, except the general delimiters ':/@[]?#',
126 * will be escaped if necessary.
127 * When [pathSegments] is used, each of the provided segments
128 * is first percent-encoded and then joined using the forward slash
129 * separator.
130 *
131 * The percent-encoding of the path segments encodes all
132 * characters except for the unreserved characters and the following
133 * list of characters: `!$&'()*+,;=:@`. If the other components
134 * necessitate an absolute path, a leading slash `/` is prepended if
135 * not already there.
136 *
137 * The query component is set through either [query] or [queryParameters].
138 * When [query] is used, the provided string should be a valid URI query,
139 * but invalid characters, other than general delimiters,
140 * will be escaped if necessary.
141 * When [queryParameters] is used the query is built from the
142 * provided map. Each key and value in the map is percent-encoded
143 * and joined using equal and ampersand characters.
144 * A value in the map must be either a string, or an [Iterable] of strings,
145 * where the latter corresponds to multiple values for the same key.
146 *
147 * The percent-encoding of the keys and values encodes all characters
148 * except for the unreserved characters, and replaces spaces with `+`.
149 * If `query` is the empty string, it is equivalent to omitting it.
150 * To have an actual empty query part,
151 * use an empty map for `queryParameters`.
152 *
153 * If both `query` and `queryParameters` are omitted or `null`,
154 * the URI has no query part.
155 *
156 * The fragment component is set through [fragment].
157 * It should be a valid URI fragment, but invalid characters other than
158 * general delimiters, are escaped if necessary.
159 * If `fragment` is omitted or `null`, the URI has no fragment part.
160 */
161 factory Uri({String scheme : "",
162 String userInfo : "",
163 String host,
164 int port,
165 String path,
166 Iterable<String> pathSegments,
167 String query,
168 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
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 }
194
195 /**
196 * Creates a new `http` URI from authority, path and query.
197 *
198 * Examples:
199 *
200 * ```
201 * // http://example.org/path?q=dart.
202 * new Uri.http("google.com", "/search", { "q" : "dart" });
203 *
204 * // http://user:pass@localhost:8080
205 * new Uri.http("user:pass@localhost:8080", "");
206 *
207 * // http://example.org/a%20b
208 * new Uri.http("example.org", "a b");
209 *
210 * // http://example.org/a%252F
211 * new Uri.http("example.org", "/a%2F");
212 * ```
213 *
214 * The `scheme` is always set to `http`.
215 *
216 * The `userInfo`, `host` and `port` components are set from the
217 * [authority] argument. If `authority` is `null` or empty,
218 * the created `Uri` has no authority, and isn't directly usable
219 * as an HTTP URL, which must have a non-empty host.
220 *
221 * The `path` component is set from the [unencodedPath]
222 * argument. The path passed must not be encoded as this constructor
223 * encodes the path.
224 *
225 * The `query` component is set from the optional [queryParameters]
226 * argument.
227 */
228 factory Uri.http(String authority,
229 String unencodedPath,
230 [Map<String, String> queryParameters]) {
231 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
232 }
233
234 /**
235 * Creates a new `https` URI from authority, path and query.
236 *
237 * This constructor is the same as [Uri.http] except for the scheme
238 * which is set to `https`.
239 */
240 factory Uri.https(String authority,
241 String unencodedPath,
242 [Map<String, String> queryParameters]) {
243 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
244 }
245
246 /**
247 * Returns the authority component.
248 *
249 * The authority is formatted from the [userInfo], [host] and [port]
250 * parts.
251 *
252 * Returns the empty string if there is no authority component.
253 */
254 String get authority {
255 if (!hasAuthority) return "";
256 var sb = new StringBuffer();
257 _writeAuthority(sb);
258 return sb.toString();
259 }
260
261 /**
262 * Returns the user info part of the authority component.
263 *
264 * Returns the empty string if there is no user info in the
265 * authority component.
266 */
267 String get userInfo => _userInfo;
268
269 /**
270 * Returns the host part of the authority component.
271 *
272 * Returns the empty string if there is no authority component and
273 * hence no host.
274 *
275 * If the host is an IP version 6 address, the surrounding `[` and `]` is
276 * removed.
277 *
278 * The host string is case-insensitive.
279 * The returned host name is canonicalized to lower-case
280 * with upper-case percent-escapes.
281 */
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 }
289
290 /**
291 * Returns the port part of the authority component.
292 *
293 * Returns the defualt port if there is no port number in the authority
294 * component. That's 80 for http, 443 for https, and 0 for everything else.
295 */
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 }
307
308 /**
309 * Returns the path component.
310 *
311 * The returned path is encoded. To get direct access to the decoded
312 * path use [pathSegments].
313 *
314 * Returns the empty string if there is no path component.
315 */
316 String get path => _path;
317
318 /**
319 * Returns the query component. The returned query is encoded. To get
320 * direct access to the decoded query use [queryParameters].
321 *
322 * Returns the empty string if there is no query component.
323 */
324 String get query => (_query == null) ? "" : _query;
325
326 /**
327 * Returns the fragment identifier component.
328 *
329 * Returns the empty string if there is no fragment identifier
330 * component.
331 */
332 String get fragment => (_fragment == null) ? "" : _fragment;
333
334 /**
335 * Creates a new `Uri` object by parsing a URI string.
336 *
337 * If [start] and [end] are provided, only the substring from `start`
338 * to `end` is parsed as a URI.
339 *
340 * If the string is not valid as a URI or URI reference,
341 * a [FormatException] is thrown.
342 */
343 static Uri parse(String uri, [int start = 0, int end]) {
344 // This parsing will not validate percent-encoding, IPv6, etc.
345 // When done splitting into parts, it will call, e.g., [_makeFragment]
346 // to do the final parsing.
347 //
348 // Important parts of the RFC 3986 used here:
349 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
350 //
351 // hier-part = "//" authority path-abempty
352 // / path-absolute
353 // / path-rootless
354 // / path-empty
355 //
356 // URI-reference = URI / relative-ref
357 //
358 // absolute-URI = scheme ":" hier-part [ "?" query ]
359 //
360 // relative-ref = relative-part [ "?" query ] [ "#" fragment ]
361 //
362 // relative-part = "//" authority path-abempty
363 // / path-absolute
364 // / path-noscheme
365 // / path-empty
366 //
367 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
368 //
369 // authority = [ userinfo "@" ] host [ ":" port ]
370 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
371 // host = IP-literal / IPv4address / reg-name
372 // port = *DIGIT
373 // reg-name = *( unreserved / pct-encoded / sub-delims )
374 //
375 // path = path-abempty ; begins with "/" or is empty
376 // / path-absolute ; begins with "/" but not "//"
377 // / path-noscheme ; begins with a non-colon segment
378 // / path-rootless ; begins with a segment
379 // / path-empty ; zero characters
380 //
381 // path-abempty = *( "/" segment )
382 // path-absolute = "/" [ segment-nz *( "/" segment ) ]
383 // path-noscheme = segment-nz-nc *( "/" segment )
384 // path-rootless = segment-nz *( "/" segment )
385 // path-empty = 0<pchar>
386 //
387 // segment = *pchar
388 // segment-nz = 1*pchar
389 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
390 // ; non-zero-length segment without any colon ":"
391 //
392 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
393 //
394 // query = *( pchar / "/" / "?" )
395 //
396 // fragment = *( pchar / "/" / "?" )
397 const int EOI = -1;
398
399 String scheme = "";
400 String userinfo = "";
401 String host = null;
402 int port = null;
403 String path = null;
404 String query = null;
405 String fragment = null;
406 if (end == null) end = uri.length;
407
408 int index = start;
409 int pathStart = start;
410 // End of input-marker.
411 int char = EOI;
412
413 void parseAuth() {
414 if (index == end) {
415 char = EOI;
416 return;
417 }
418 int authStart = index;
419 int lastColon = -1;
420 int lastAt = -1;
421 char = uri.codeUnitAt(index);
422 while (index < end) {
423 char = uri.codeUnitAt(index);
424 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
425 break;
426 }
427 if (char == _AT_SIGN) {
428 lastAt = index;
429 lastColon = -1;
430 } else if (char == _COLON) {
431 lastColon = index;
432 } else if (char == _LEFT_BRACKET) {
433 lastColon = -1;
434 int endBracket = uri.indexOf(']', index + 1);
435 if (endBracket == -1) {
436 index = end;
437 char = EOI;
438 break;
439 } else {
440 index = endBracket;
441 }
442 }
443 index++;
444 char = EOI;
445 }
446 int hostStart = authStart;
447 int hostEnd = index;
448 if (lastAt >= 0) {
449 userinfo = _makeUserInfo(uri, authStart, lastAt);
450 hostStart = lastAt + 1;
451 }
452 if (lastColon >= 0) {
453 int portNumber;
454 if (lastColon + 1 < index) {
455 portNumber = 0;
456 for (int i = lastColon + 1; i < index; i++) {
457 int digit = uri.codeUnitAt(i);
458 if (_ZERO > digit || _NINE < digit) {
459 _fail(uri, i, "Invalid port number");
460 }
461 portNumber = portNumber * 10 + (digit - _ZERO);
462 }
463 }
464 port = _makePort(portNumber, scheme);
465 hostEnd = lastColon;
466 }
467 host = _makeHost(uri, hostStart, hostEnd, true);
468 if (index < end) {
469 char = uri.codeUnitAt(index);
470 }
471 }
472
473 // When reaching path parsing, the current character is known to not
474 // be part of the path.
475 const int NOT_IN_PATH = 0;
476 // When reaching path parsing, the current character is part
477 // of the a non-empty path.
478 const int IN_PATH = 1;
479 // When reaching authority parsing, authority is possible.
480 // This is only true at start or right after scheme.
481 const int ALLOW_AUTH = 2;
482
483 // Current state.
484 // Initialized to the default value that is used when exiting the
485 // scheme loop by reaching the end of input.
486 // All other breaks set their own state.
487 int state = NOT_IN_PATH;
488 int i = index; // Temporary alias for index to avoid bug 19550 in dart2js.
489 while (i < end) {
490 char = uri.codeUnitAt(i);
491 if (char == _QUESTION || char == _NUMBER_SIGN) {
492 state = NOT_IN_PATH;
493 break;
494 }
495 if (char == _SLASH) {
496 state = (i == start) ? ALLOW_AUTH : IN_PATH;
497 break;
498 }
499 if (char == _COLON) {
500 if (i == start) _fail(uri, start, "Invalid empty scheme");
501 scheme = _makeScheme(uri, start, i);
502 i++;
503 pathStart = i;
504 if (i == end) {
505 char = EOI;
506 state = NOT_IN_PATH;
507 } else {
508 char = uri.codeUnitAt(i);
509 if (char == _QUESTION || char == _NUMBER_SIGN) {
510 state = NOT_IN_PATH;
511 } else if (char == _SLASH) {
512 state = ALLOW_AUTH;
513 } else {
514 state = IN_PATH;
515 }
516 }
517 break;
518 }
519 i++;
520 char = EOI;
521 }
522 index = i; // Remove alias when bug is fixed.
523
524 if (state == ALLOW_AUTH) {
525 assert(char == _SLASH);
526 // Have seen one slash either at start or right after scheme.
527 // If two slashes, it's an authority, otherwise it's just the path.
528 index++;
529 if (index == end) {
530 char = EOI;
531 state = NOT_IN_PATH;
532 } else {
533 char = uri.codeUnitAt(index);
534 if (char == _SLASH) {
535 index++;
536 parseAuth();
537 pathStart = index;
538 }
539 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
540 state = NOT_IN_PATH;
541 } else {
542 state = IN_PATH;
543 }
544 }
545 }
546
547 assert(state == IN_PATH || state == NOT_IN_PATH);
548 if (state == IN_PATH) {
549 // Characters from pathStart to index (inclusive) are known
550 // to be part of the path.
551 while (++index < end) {
552 char = uri.codeUnitAt(index);
553 if (char == _QUESTION || char == _NUMBER_SIGN) {
554 break;
555 }
556 char = EOI;
557 }
558 state = NOT_IN_PATH;
559 }
560
561 assert(state == NOT_IN_PATH);
562 bool hasAuthority = (host != null);
563 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
564
565 if (char == _QUESTION) {
566 int numberSignIndex = -1;
567 for (int i = index + 1; i < end; i++) {
568 if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
569 numberSignIndex = i;
570 break;
571 }
572 }
573 if (numberSignIndex < 0) {
574 query = _makeQuery(uri, index + 1, end, null);
575 } else {
576 query = _makeQuery(uri, index + 1, numberSignIndex, null);
577 fragment = _makeFragment(uri, numberSignIndex + 1, end);
578 }
579 } else if (char == _NUMBER_SIGN) {
580 fragment = _makeFragment(uri, index + 1, end);
581 }
582 return new Uri._internal(scheme,
583 userinfo,
584 host,
585 port,
586 path,
587 query,
588 fragment);
589 }
590
591 // Report a parse failure.
592 static void _fail(String uri, int index, String message) {
593 throw new FormatException(message, uri, index);
594 }
595
596 static Uri _makeHttpUri(String scheme,
597 String authority,
598 String unencodedPath,
599 Map<String, String> queryParameters) {
600 var userInfo = "";
601 var host = null;
602 var port = null;
603
604 if (authority != null && authority.isNotEmpty) {
605 var hostStart = 0;
606 // Split off the user info.
607 bool hasUserInfo = false;
608 for (int i = 0; i < authority.length; i++) {
609 if (authority.codeUnitAt(i) == _AT_SIGN) {
610 hasUserInfo = true;
611 userInfo = authority.substring(0, i);
612 hostStart = i + 1;
613 break;
614 }
615 }
616 var hostEnd = hostStart;
617 if (hostStart < authority.length &&
618 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
619 // IPv6 host.
620 for (; hostEnd < authority.length; hostEnd++) {
621 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
622 }
623 if (hostEnd == authority.length) {
624 throw new FormatException("Invalid IPv6 host entry.",
625 authority, hostStart);
626 }
627 parseIPv6Address(authority, hostStart + 1, hostEnd);
628 hostEnd++; // Skip the closing bracket.
629 if (hostEnd != authority.length &&
630 authority.codeUnitAt(hostEnd) != _COLON) {
631 throw new FormatException("Invalid end of authority",
632 authority, hostEnd);
633 }
634 }
635 // Split host and port.
636 bool hasPort = false;
637 for (; hostEnd < authority.length; hostEnd++) {
638 if (authority.codeUnitAt(hostEnd) == _COLON) {
639 var portString = authority.substring(hostEnd + 1);
640 // We allow the empty port - falling back to initial value.
641 if (portString.isNotEmpty) port = int.parse(portString);
642 break;
643 }
644 }
645 host = authority.substring(hostStart, hostEnd);
646 }
647 return new Uri(scheme: scheme,
648 userInfo: userInfo,
649 host: host,
650 port: port,
651 pathSegments: unencodedPath.split("/"),
652 queryParameters: queryParameters);
653 }
654
655 /**
656 * Creates a new file URI from an absolute or relative file path.
657 *
658 * The file path is passed in [path].
659 *
660 * This path is interpreted using either Windows or non-Windows
661 * semantics.
662 *
663 * With non-Windows semantics the slash ("/") is used to separate
664 * path segments.
665 *
666 * With Windows semantics, backslash ("\") and forward-slash ("/")
667 * are used to separate path segments, except if the path starts
668 * with "\\?\" in which case, only backslash ("\") separates path
669 * segments.
670 *
671 * If the path starts with a path separator an absolute URI is
672 * created. Otherwise a relative URI is created. One exception from
673 * this rule is that when Windows semantics is used and the path
674 * starts with a drive letter followed by a colon (":") and a
675 * path separator then an absolute URI is created.
676 *
677 * The default for whether to use Windows or non-Windows semantics
678 * determined from the platform Dart is running on. When running in
679 * the standalone VM this is detected by the VM based on the
680 * operating system. When running in a browser non-Windows semantics
681 * is always used.
682 *
683 * To override the automatic detection of which semantics to use pass
684 * a value for [windows]. Passing `true` will use Windows
685 * semantics and passing `false` will use non-Windows semantics.
686 *
687 * Examples using non-Windows semantics:
688 *
689 * ```
690 * // xxx/yyy
691 * new Uri.file("xxx/yyy", windows: false);
692 *
693 * // xxx/yyy/
694 * new Uri.file("xxx/yyy/", windows: false);
695 *
696 * // file:///xxx/yyy
697 * new Uri.file("/xxx/yyy", windows: false);
698 *
699 * // file:///xxx/yyy/
700 * new Uri.file("/xxx/yyy/", windows: false);
701 *
702 * // C:
703 * new Uri.file("C:", windows: false);
704 * ```
705 *
706 * Examples using Windows semantics:
707 *
708 * ```
709 * // xxx/yyy
710 * new Uri.file(r"xxx\yyy", windows: true);
711 *
712 * // xxx/yyy/
713 * new Uri.file(r"xxx\yyy\", windows: true);
714 *
715 * file:///xxx/yyy
716 * new Uri.file(r"\xxx\yyy", windows: true);
717 *
718 * file:///xxx/yyy/
719 * new Uri.file(r"\xxx\yyy/", windows: true);
720 *
721 * // file:///C:/xxx/yyy
722 * new Uri.file(r"C:\xxx\yyy", windows: true);
723 *
724 * // This throws an error. A path with a drive letter is not absolute.
725 * new Uri.file(r"C:", windows: true);
726 *
727 * // This throws an error. A path with a drive letter is not absolute.
728 * new Uri.file(r"C:xxx\yyy", windows: true);
729 *
730 * // file://server/share/file
731 * new Uri.file(r"\\server\share\file", windows: true);
732 * ```
733 *
734 * If the path passed is not a legal file path [ArgumentError] is thrown.
735 */
736 factory Uri.file(String path, {bool windows}) {
737 windows = (windows == null) ? Uri._isWindows : windows;
738 return windows ? _makeWindowsFileUrl(path, false)
739 : _makeFileUri(path, false);
740 }
741
742 /**
743 * Like [Uri.file] except that a non-empty URI path ends in a slash.
744 *
745 * If [path] is not empty, and it doesn't end in a directory separator,
746 * then a slash is added to the returned URI's path.
747 * In all other cases, the result is the same as returned by `Uri.file`.
748 */
749 factory Uri.directory(String path, {bool windows}) {
750 windows = (windows == null) ? Uri._isWindows : windows;
751 return windows ? _makeWindowsFileUrl(path, true)
752 : _makeFileUri(path, true);
753 }
754
755 /**
756 * Creates a `data:` URI containing the [content] string.
757 *
758 * Converts the content to a bytes using [encoding] or the charset specified
759 * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
760 * then encodes the bytes into the resulting data URI.
761 *
762 * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
763 * bytes is replaced by a percent encoding). If [base64] is true, the bytes
764 * are instead encoded using [BASE64].
765 *
766 * If [encoding] is not provided and [parameters] has a `charset` entry,
767 * that name is looked up using [Encoding.getByName],
768 * and if the lookup returns an encoding, that encoding is used to convert
769 * [content] to bytes.
770 * If providing both an [encoding] and a charset [parameter], they should
771 * agree, otherwise decoding won't be able to use the charset parameter
772 * to determine the encoding.
773 *
774 * If [mimeType] and/or [parameters] are supplied, they are added to the
775 * created URI. If any of these contain characters that are not allowed
776 * in the data URI, the character is percent-escaped. If the character is
777 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
778 * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
779 * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
780 *
781 * To read the content back, use [UriData.contentAsString].
782 */
783 factory Uri.dataFromString(String content,
784 {String mimeType,
785 Encoding encoding,
786 Map<String, String> parameters,
787 bool base64: false}) {
788 UriData data = new UriData.fromString(content,
789 mimeType: mimeType,
790 encoding: encoding,
791 parameters: parameters,
792 base64: base64);
793 return data.uri;
794 }
795
796 /**
797 * Creates a `data:` URI containing an encoding of [bytes].
798 *
799 * Defaults to Base64 encoding the bytes, but if [percentEncoded]
800 * is `true`, the bytes will instead be percent encoded (any non-ASCII
801 * or non-valid-ASCII-character byte is replaced by a percent encoding).
802 *
803 * To read the bytes back, use [UriData.contentAsBytes].
804 *
805 * It defaults to having the mime-type `application/octet-stream`.
806 * The [mimeType] and [parameters] are added to the created URI.
807 * If any of these contain characters that are not allowed
808 * in the data URI, the character is percent-escaped. If the character is
809 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
810 * encoded.
811 */
812 factory Uri.dataFromBytes(List<int> bytes,
813 {mimeType: "application/octet-stream",
814 Map<String, String> parameters,
815 percentEncoded: false}) {
816 UriData data = new UriData.fromBytes(bytes,
817 mimeType: mimeType,
818 parameters: parameters,
819 percentEncoded: percentEncoded);
820 return data.uri;
821 }
822
823 /**
824 * Returns the natural base URI for the current platform.
825 *
826 * When running in a browser this is the current URL (from
827 * `window.location.href`).
828 *
829 * When not running in a browser this is the file URI referencing
830 * the current working directory.
831 */
832 external static Uri get base;
833
834 external static bool get _isWindows;
835
836 static _checkNonWindowsPathReservedCharacters(List<String> segments,
837 bool argumentError) {
838 segments.forEach((segment) {
839 if (segment.contains("/")) {
840 if (argumentError) {
841 throw new ArgumentError("Illegal path character $segment");
842 } else {
843 throw new UnsupportedError("Illegal path character $segment");
844 }
845 }
846 });
847 }
848
849 static _checkWindowsPathReservedCharacters(List<String> segments,
850 bool argumentError,
851 [int firstSegment = 0]) {
852 for (var segment in segments.skip(firstSegment)) {
853 if (segment.contains(new RegExp(r'["*/:<>?\\|]'))) {
854 if (argumentError) {
855 throw new ArgumentError("Illegal character in path");
856 } else {
857 throw new UnsupportedError("Illegal character in path");
858 }
859 }
860 }
861 }
862
863 static _checkWindowsDriveLetter(int charCode, bool argumentError) {
864 if ((_UPPER_CASE_A <= charCode && charCode <= _UPPER_CASE_Z) ||
865 (_LOWER_CASE_A <= charCode && charCode <= _LOWER_CASE_Z)) {
866 return;
867 }
868 if (argumentError) {
869 throw new ArgumentError("Illegal drive letter " +
870 new String.fromCharCode(charCode));
871 } else {
872 throw new UnsupportedError("Illegal drive letter " +
873 new String.fromCharCode(charCode));
874 }
875 }
876
877 static _makeFileUri(String path, bool slashTerminated) {
878 const String sep = "/";
879 var segments = path.split(sep);
880 if (slashTerminated &&
881 segments.isNotEmpty &&
882 segments.last.isNotEmpty) {
883 segments.add(""); // Extra separator at end.
884 }
885 if (path.startsWith(sep)) {
886 // Absolute file:// URI.
887 return new Uri(scheme: "file", pathSegments: segments);
888 } else {
889 // Relative URI.
890 return new Uri(pathSegments: segments);
891 }
892 }
893
894 static _makeWindowsFileUrl(String path, bool slashTerminated) {
895 if (path.startsWith(r"\\?\")) {
896 if (path.startsWith(r"UNC\", 4)) {
897 path = path.replaceRange(0, 7, r'\');
898 } else {
899 path = path.substring(4);
900 if (path.length < 3 ||
901 path.codeUnitAt(1) != _COLON ||
902 path.codeUnitAt(2) != _BACKSLASH) {
903 throw new ArgumentError(
904 r"Windows paths with \\?\ prefix must be absolute");
905 }
906 }
907 } else {
908 path = path.replaceAll("/", r'\');
909 }
910 const String sep = r'\';
911 if (path.length > 1 && path.codeUnitAt(1) == _COLON) {
912 _checkWindowsDriveLetter(path.codeUnitAt(0), true);
913 if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
914 throw new ArgumentError(
915 "Windows paths with drive letter must be absolute");
916 }
917 // Absolute file://C:/ URI.
918 var pathSegments = path.split(sep);
919 if (slashTerminated &&
920 pathSegments.last.isNotEmpty) {
921 pathSegments.add(""); // Extra separator at end.
922 }
923 _checkWindowsPathReservedCharacters(pathSegments, true, 1);
924 return new Uri(scheme: "file", pathSegments: pathSegments);
925 }
926
927 if (path.startsWith(sep)) {
928 if (path.startsWith(sep, 1)) {
929 // Absolute file:// URI with host.
930 int pathStart = path.indexOf(r'\', 2);
931 String hostPart =
932 (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart);
933 String pathPart =
934 (pathStart < 0) ? "" : path.substring(pathStart + 1);
935 var pathSegments = pathPart.split(sep);
936 _checkWindowsPathReservedCharacters(pathSegments, true);
937 if (slashTerminated &&
938 pathSegments.last.isNotEmpty) {
939 pathSegments.add(""); // Extra separator at end.
940 }
941 return new Uri(
942 scheme: "file", host: hostPart, pathSegments: pathSegments);
943 } else {
944 // Absolute file:// URI.
945 var pathSegments = path.split(sep);
946 if (slashTerminated &&
947 pathSegments.last.isNotEmpty) {
948 pathSegments.add(""); // Extra separator at end.
949 }
950 _checkWindowsPathReservedCharacters(pathSegments, true);
951 return new Uri(scheme: "file", pathSegments: pathSegments);
952 }
953 } else {
954 // Relative URI.
955 var pathSegments = path.split(sep);
956 _checkWindowsPathReservedCharacters(pathSegments, true);
957 if (slashTerminated &&
958 pathSegments.isNotEmpty &&
959 pathSegments.last.isNotEmpty) {
960 pathSegments.add(""); // Extra separator at end.
961 }
962 return new Uri(pathSegments: pathSegments);
963 }
964 }
965
966 /**
967 * Returns a new `Uri` based on this one, but with some parts replaced.
968 *
969 * This method takes the same parameters as the [new Uri] constructor,
970 * and they have the same meaning.
971 *
972 * At most one of [path] and [pathSegments] must be provided.
973 * Likewise, at most one of [query] and [queryParameters] must be provided.
974 *
975 * Each part that is not provided will default to the corresponding
976 * value from this `Uri` instead.
977 *
978 * This method is different from [Uri.resolve] which overrides in a
979 * hierarchial manner,
980 * and can instead replace each part of a `Uri` individually.
981 *
982 * Example:
983 *
984 * Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
985 * Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
986 * print(uri2); // prints "A://b@c:4/D/E/E/?f#G"
987 *
988 * This method acts similarly to using the `new Uri` constructor with
989 * some of the arguments taken from this `Uri` . Example:
990 *
991 * Uri uri3 = new Uri(
992 * scheme: "A",
993 * userInfo: uri1.userInfo,
994 * host: uri1.host,
995 * port: uri1.port,
996 * path: "D/E/E",
997 * query: uri1.query,
998 * fragment: "G");
999 * print(uri3); // prints "A://b@c:4/D/E/E/?f#G"
1000 * print(uri2 == uri3); // prints true.
1001 *
1002 * Using this method can be seen as a shorthand for the `Uri` constructor
1003 * call above, but may also be slightly faster because the parts taken
1004 * from this `Uri` need not be checked for validity again.
1005 */
1006 Uri replace({String scheme,
1007 String userInfo,
1008 String host,
1009 int port,
1010 String path,
1011 Iterable<String> pathSegments,
1012 String query,
1013 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
1014 String fragment}) {
1015 // Set to true if the scheme has (potentially) changed.
1016 // In that case, the default port may also have changed and we need
1017 // to check even the existing port.
1018 bool schemeChanged = false;
1019 if (scheme != null) {
1020 scheme = _makeScheme(scheme, 0, scheme.length);
1021 schemeChanged = true;
1022 } else {
1023 scheme = this.scheme;
1024 }
1025 bool isFile = (scheme == "file");
1026 if (userInfo != null) {
1027 userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
1028 } else {
1029 userInfo = this._userInfo;
1030 }
1031 if (port != null) {
1032 port = _makePort(port, scheme);
1033 } else {
1034 port = this._port;
1035 if (schemeChanged) {
1036 // The default port might have changed.
1037 port = _makePort(port, scheme);
1038 }
1039 }
1040 if (host != null) {
1041 host = _makeHost(host, 0, host.length, false);
1042 } else if (this.hasAuthority) {
1043 host = this._host;
1044 } else if (userInfo.isNotEmpty || port != null || isFile) {
1045 host = "";
1046 }
1047
1048 bool hasAuthority = host != null;
1049 if (path != null || pathSegments != null) {
1050 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
1051 scheme, hasAuthority);
1052 } else {
1053 path = this._path;
1054 if ((isFile || (hasAuthority && !path.isEmpty)) &&
1055 !path.startsWith('/')) {
1056 path = "/" + path;
1057 }
1058 }
1059
1060 if (query != null || queryParameters != null) {
1061 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
1062 } else {
1063 query = this._query;
1064 }
1065
1066 if (fragment != null) {
1067 fragment = _makeFragment(fragment, 0, fragment.length);
1068 } else {
1069 fragment = this._fragment;
1070 }
1071
1072 return new Uri._internal(
1073 scheme, userInfo, host, port, path, query, fragment);
1074 }
1075
1076 /**
1077 * Returns a `Uri` that differs from this only in not having a fragment.
1078 *
1079 * If this `Uri` does not have a fragment, it is itself returned.
1080 */
1081 Uri removeFragment() {
1082 if (!this.hasFragment) return this;
1083 return new Uri._internal(scheme, _userInfo, _host, _port,
1084 _path, _query, null);
1085 }
1086
1087 /**
1088 * Returns the URI path split into its segments. Each of the segments in the
1089 * returned list have been decoded. If the path is empty the empty list will
1090 * be returned. A leading slash `/` does not affect the segments returned.
1091 *
1092 * The returned list is unmodifiable and will throw [UnsupportedError] on any
1093 * calls that would mutate it.
1094 */
1095 List<String> get pathSegments {
1096 var result = _pathSegments;
1097 if (result != null) return result;
1098
1099 var pathToSplit = path;
1100 if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) {
1101 pathToSplit = pathToSplit.substring(1);
1102 }
1103 result = (pathToSplit == "")
1104 ? const<String>[]
1105 : new List<String>.unmodifiable(
1106 pathToSplit.split("/").map(Uri.decodeComponent));
1107 _pathSegments = result;
1108 return result;
1109 }
1110
1111 /**
1112 * Returns the URI query split into a map according to the rules
1113 * specified for FORM post in the [HTML 4.01 specification section
1114 * 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").
1115 * Each key and value in the returned map has been decoded.
1116 * If there is no query the empty map is returned.
1117 *
1118 * Keys in the query string that have no value are mapped to the
1119 * empty string.
1120 * If a key occurs more than once in the query string, it is mapped to
1121 * an arbitrary choice of possible value.
1122 * The [queryParametersAll] getter can provide a map
1123 * that maps keys to all of their values.
1124 *
1125 * The returned map is unmodifiable.
1126 */
1127 Map<String, String> get queryParameters {
1128 if (_queryParameters == null) {
1129 _queryParameters =
1130 new UnmodifiableMapView<String, String>(splitQueryString(query));
1131 }
1132 return _queryParameters;
1133 }
1134
1135 /**
1136 * Returns the URI query split into a map according to the rules
1137 * specified for FORM post in the [HTML 4.01 specification section
1138 * 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").
1139 * Each key and value in the returned map has been decoded. If there is no
1140 * query the empty map is returned.
1141 *
1142 * Keys are mapped to lists of their values. If a key occurs only once,
1143 * its value is a singleton list. If a key occurs with no value, the
1144 * empty string is used as the value for that occurrence.
1145 *
1146 * The returned map and the lists it contains are unmodifiable.
1147 */
1148 Map<String, List<String>> get queryParametersAll {
1149 if (_queryParameterLists == null) {
1150 Map queryParameterLists = _splitQueryStringAll(query);
1151 for (var key in queryParameterLists.keys) {
1152 queryParameterLists[key] =
1153 new List<String>.unmodifiable(queryParameterLists[key]);
1154 }
1155 _queryParameterLists =
1156 new Map<String, List<String>>.unmodifiable(queryParameterLists);
1157 }
1158 return _queryParameterLists;
1159 }
1160
1161 /**
1162 * Returns a URI where the path has been normalized.
1163 *
1164 * A normalized path does not contain `.` segments or non-leading `..`
1165 * segments.
1166 * Only a relative path with no scheme or authority may contain
1167 * leading `..` segments,
1168 * a path that starts with `/` will also drop any leading `..` segments.
1169 *
1170 * This uses the same normalization strategy as `new Uri().resolve(this)`.
1171 *
1172 * Does not change any part of the URI except the path.
1173 *
1174 * The default implementation of `Uri` always normalizes paths, so calling
1175 * this function has no effect.
1176 */
1177 Uri normalizePath() {
1178 String path = _normalizePath(_path, scheme, hasAuthority);
1179 if (identical(path, _path)) return this;
1180 return this.replace(path: path);
1181 }
1182
1183 static int _makePort(int port, String scheme) {
1184 // Perform scheme specific normalization.
1185 if (port != null && port == _defaultPort(scheme)) return null;
1186 return port;
1187 }
1188
1189 /**
1190 * Check and normalize a host name.
1191 *
1192 * If the host name starts and ends with '[' and ']', it is considered an
1193 * IPv6 address. If [strictIPv6] is false, the address is also considered
1194 * an IPv6 address if it contains any ':' character.
1195 *
1196 * If it is not an IPv6 address, it is case- and escape-normalized.
1197 * This escapes all characters not valid in a reg-name,
1198 * and converts all non-escape upper-case letters to lower-case.
1199 */
1200 static String _makeHost(String host, int start, int end, bool strictIPv6) {
1201 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
1202 if (host == null) return null;
1203 if (start == end) return "";
1204 // Host is an IPv6 address if it starts with '[' or contains a colon.
1205 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
1206 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
1207 _fail(host, start, 'Missing end `]` to match `[` in host');
1208 }
1209 parseIPv6Address(host, start + 1, end - 1);
1210 // RFC 5952 requires hex digits to be lower case.
1211 return host.substring(start, end).toLowerCase();
1212 }
1213 if (!strictIPv6) {
1214 // TODO(lrn): skip if too short to be a valid IPv6 address?
1215 for (int i = start; i < end; i++) {
1216 if (host.codeUnitAt(i) == _COLON) {
1217 parseIPv6Address(host, start, end);
1218 return '[$host]';
1219 }
1220 }
1221 }
1222 return _normalizeRegName(host, start, end);
1223 }
1224
1225 static bool _isRegNameChar(int char) {
1226 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
1227 }
1228
1229 /**
1230 * Validates and does case- and percent-encoding normalization.
1231 *
1232 * The [host] must be an RFC3986 "reg-name". It is converted
1233 * to lower case, and percent escapes are converted to either
1234 * lower case unreserved characters or upper case escapes.
1235 */
1236 static String _normalizeRegName(String host, int start, int end) {
1237 StringBuffer buffer;
1238 int sectionStart = start;
1239 int index = start;
1240 // Whether all characters between sectionStart and index are normalized,
1241 bool isNormalized = true;
1242
1243 while (index < end) {
1244 int char = host.codeUnitAt(index);
1245 if (char == _PERCENT) {
1246 // The _regNameTable contains "%", so we check that first.
1247 String replacement = _normalizeEscape(host, index, true);
1248 if (replacement == null && isNormalized) {
1249 index += 3;
1250 continue;
1251 }
1252 if (buffer == null) buffer = new StringBuffer();
1253 String slice = host.substring(sectionStart, index);
1254 if (!isNormalized) slice = slice.toLowerCase();
1255 buffer.write(slice);
1256 int sourceLength = 3;
1257 if (replacement == null) {
1258 replacement = host.substring(index, index + 3);
1259 } else if (replacement == "%") {
1260 replacement = "%25";
1261 sourceLength = 1;
1262 }
1263 buffer.write(replacement);
1264 index += sourceLength;
1265 sectionStart = index;
1266 isNormalized = true;
1267 } else if (_isRegNameChar(char)) {
1268 if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
1269 // Put initial slice in buffer and continue in non-normalized mode
1270 if (buffer == null) buffer = new StringBuffer();
1271 if (sectionStart < index) {
1272 buffer.write(host.substring(sectionStart, index));
1273 sectionStart = index;
1274 }
1275 isNormalized = false;
1276 }
1277 index++;
1278 } else if (_isGeneralDelimiter(char)) {
1279 _fail(host, index, "Invalid character");
1280 } else {
1281 int sourceLength = 1;
1282 if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
1283 int tail = host.codeUnitAt(index + 1);
1284 if ((tail & 0xFC00) == 0xDC00) {
1285 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1286 sourceLength = 2;
1287 }
1288 }
1289 if (buffer == null) buffer = new StringBuffer();
1290 String slice = host.substring(sectionStart, index);
1291 if (!isNormalized) slice = slice.toLowerCase();
1292 buffer.write(slice);
1293 buffer.write(_escapeChar(char));
1294 index += sourceLength;
1295 sectionStart = index;
1296 }
1297 }
1298 if (buffer == null) return host.substring(start, end);
1299 if (sectionStart < end) {
1300 String slice = host.substring(sectionStart, end);
1301 if (!isNormalized) slice = slice.toLowerCase();
1302 buffer.write(slice);
1303 }
1304 return buffer.toString();
1305 }
1306
1307 /**
1308 * Validates scheme characters and does case-normalization.
1309 *
1310 * Schemes are converted to lower case. They cannot contain escapes.
1311 */
1312 static String _makeScheme(String scheme, int start, int end) {
1313 if (start == end) return "";
1314 final int firstCodeUnit = scheme.codeUnitAt(start);
1315 if (!_isAlphabeticCharacter(firstCodeUnit)) {
1316 _fail(scheme, start, "Scheme not starting with alphabetic character");
1317 }
1318 bool containsUpperCase = false;
1319 for (int i = start; i < end; i++) {
1320 final int codeUnit = scheme.codeUnitAt(i);
1321 if (!_isSchemeCharacter(codeUnit)) {
1322 _fail(scheme, i, "Illegal scheme character");
1323 }
1324 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
1325 containsUpperCase = true;
1326 }
1327 }
1328 scheme = scheme.substring(start, end);
1329 if (containsUpperCase) scheme = scheme.toLowerCase();
1330 return scheme;
1331 }
1332
1333 static String _makeUserInfo(String userInfo, int start, int end) {
1334 if (userInfo == null) return "";
1335 return _normalize(userInfo, start, end, _userinfoTable);
1336 }
1337
1338 static String _makePath(String path, int start, int end,
1339 Iterable<String> pathSegments,
1340 String scheme,
1341 bool hasAuthority) {
1342 bool isFile = (scheme == "file");
1343 bool ensureLeadingSlash = isFile || hasAuthority;
1344 if (path == null && pathSegments == null) return isFile ? "/" : "";
1345 if (path != null && pathSegments != null) {
1346 throw new ArgumentError('Both path and pathSegments specified');
1347 }
1348 var result;
1349 if (path != null) {
1350 result = _normalize(path, start, end, _pathCharOrSlashTable);
1351 } else {
1352 result = pathSegments.map((s) =>
1353 _uriEncode(_pathCharTable, s, UTF8, false)).join("/");
1354 }
1355 if (result.isEmpty) {
1356 if (isFile) return "/";
1357 } else if (ensureLeadingSlash && !result.startsWith('/')) {
1358 result = "/" + result;
1359 }
1360 result = _normalizePath(result, scheme, hasAuthority);
1361 return result;
1362 }
1363
1364 /// Performs path normalization (remove dot segments) on a path.
1365 ///
1366 /// If the URI has neither scheme nor authority, it's considered a
1367 /// "pure path" and normalization won't remove leading ".." segments.
1368 /// Otherwise it follows the RFC 3986 "remove dot segments" algorithm.
1369 static String _normalizePath(String path, String scheme, bool hasAuthority) {
1370 if (scheme.isEmpty && !hasAuthority && !path.startsWith('/')) {
1371 return _normalizeRelativePath(path);
1372 }
1373 return _removeDotSegments(path);
1374 }
1375
1376 static String _makeQuery(
1377 String query, int start, int end,
1378 Map<String, dynamic/*String|Iterable<String>*/> queryParameters) {
1379 if (query == null && queryParameters == null) return null;
1380 if (query != null && queryParameters != null) {
1381 throw new ArgumentError('Both query and queryParameters specified');
1382 }
1383 if (query != null) return _normalize(query, start, end, _queryCharTable);
1384
1385 var result = new StringBuffer();
1386 var separator = "";
1387
1388 void writeParameter(String key, String value) {
1389 result.write(separator);
1390 separator = "&";
1391 result.write(Uri.encodeQueryComponent(key));
1392 if (value != null && value.isNotEmpty) {
1393 result.write("=");
1394 result.write(Uri.encodeQueryComponent(value));
1395 }
1396 }
1397
1398 queryParameters.forEach((key, value) {
1399 if (value == null || value is String) {
1400 writeParameter(key, value);
1401 } else {
1402 Iterable values = value;
1403 for (String value in values) {
1404 writeParameter(key, value);
1405 }
1406 }
1407 });
1408 return result.toString();
1409 }
1410
1411 static String _makeFragment(String fragment, int start, int end) {
1412 if (fragment == null) return null;
1413 return _normalize(fragment, start, end, _queryCharTable);
1414 }
1415
1416 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
1417
1418 /**
1419 * Performs RFC 3986 Percent-Encoding Normalization.
1420 *
1421 * Returns a replacement string that should be replace the original escape.
1422 * Returns null if no replacement is necessary because the escape is
1423 * not for an unreserved character and is already non-lower-case.
1424 *
1425 * Returns "%" if the escape is invalid (not two valid hex digits following
1426 * the percent sign). The calling code should replace the percent
1427 * sign with "%25", but leave the following two characters unmodified.
1428 *
1429 * If [lowerCase] is true, a single character returned is always lower case,
1430 */
1431 static String _normalizeEscape(String source, int index, bool lowerCase) {
1432 assert(source.codeUnitAt(index) == _PERCENT);
1433 if (index + 2 >= source.length) {
1434 return "%"; // Marks the escape as invalid.
1435 }
1436 int firstDigit = source.codeUnitAt(index + 1);
1437 int secondDigit = source.codeUnitAt(index + 2);
1438 int firstDigitValue = _parseHexDigit(firstDigit);
1439 int secondDigitValue = _parseHexDigit(secondDigit);
1440 if (firstDigitValue < 0 || secondDigitValue < 0) {
1441 return "%"; // Marks the escape as invalid.
1442 }
1443 int value = firstDigitValue * 16 + secondDigitValue;
1444 if (_isUnreservedChar(value)) {
1445 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
1446 value |= 0x20;
1447 }
1448 return new String.fromCharCode(value);
1449 }
1450 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
1451 // Either digit is lower case.
1452 return source.substring(index, index + 3).toUpperCase();
1453 }
1454 // Escape is retained, and is already non-lower case, so return null to
1455 // represent "no replacement necessary".
1456 return null;
1457 }
1458
1459 // Converts a UTF-16 code-unit to its value as a hex digit.
1460 // Returns -1 for non-hex digits.
1461 static int _parseHexDigit(int char) {
1462 int digit = char ^ Uri._ZERO;
1463 if (digit <= 9) return digit;
1464 int lowerCase = char | 0x20;
1465 if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
1466 return lowerCase - (_LOWER_CASE_A - 10);
1467 }
1468 return -1;
1469 }
1470
1471 static String _escapeChar(int char) {
1472 assert(char <= 0x10ffff); // It's a valid unicode code point.
1473 List<int> codeUnits;
1474 if (char < 0x80) {
1475 // ASCII, a single percent encoded sequence.
1476 codeUnits = new List(3);
1477 codeUnits[0] = _PERCENT;
1478 codeUnits[1] = _hexDigits.codeUnitAt(char >> 4);
1479 codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf);
1480 } else {
1481 // Do UTF-8 encoding of character, then percent encode bytes.
1482 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
1483 int encodedBytes = 2;
1484 if (char > 0x7ff) {
1485 flag = 0xe0;
1486 encodedBytes = 3;
1487 if (char > 0xffff) {
1488 encodedBytes = 4;
1489 flag = 0xf0;
1490 }
1491 }
1492 codeUnits = new List(3 * encodedBytes);
1493 int index = 0;
1494 while (--encodedBytes >= 0) {
1495 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
1496 codeUnits[index] = _PERCENT;
1497 codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4);
1498 codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf);
1499 index += 3;
1500 flag = 0x80; // Following bytes have only high bit set.
1501 }
1502 }
1503 return new String.fromCharCodes(codeUnits);
1504 }
1505
1506 /**
1507 * Runs through component checking that each character is valid and
1508 * normalize percent escapes.
1509 *
1510 * Uses [charTable] to check if a non-`%` character is allowed.
1511 * Each `%` character must be followed by two hex digits.
1512 * If the hex-digits are lower case letters, they are converted to
1513 * upper case.
1514 */
1515 static String _normalize(String component, int start, int end,
1516 List<int> charTable) {
1517 StringBuffer buffer;
1518 int sectionStart = start;
1519 int index = start;
1520 // Loop while characters are valid and escapes correct and upper-case.
1521 while (index < end) {
1522 int char = component.codeUnitAt(index);
1523 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
1524 index++;
1525 } else {
1526 String replacement;
1527 int sourceLength;
1528 if (char == _PERCENT) {
1529 replacement = _normalizeEscape(component, index, false);
1530 // Returns null if we should keep the existing escape.
1531 if (replacement == null) {
1532 index += 3;
1533 continue;
1534 }
1535 // Returns "%" if we should escape the existing percent.
1536 if ("%" == replacement) {
1537 replacement = "%25";
1538 sourceLength = 1;
1539 } else {
1540 sourceLength = 3;
1541 }
1542 } else if (_isGeneralDelimiter(char)) {
1543 _fail(component, index, "Invalid character");
1544 } else {
1545 sourceLength = 1;
1546 if ((char & 0xFC00) == 0xD800) {
1547 // Possible lead surrogate.
1548 if (index + 1 < end) {
1549 int tail = component.codeUnitAt(index + 1);
1550 if ((tail & 0xFC00) == 0xDC00) {
1551 // Tail surrogat.
1552 sourceLength = 2;
1553 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1554 }
1555 }
1556 }
1557 replacement = _escapeChar(char);
1558 }
1559 if (buffer == null) buffer = new StringBuffer();
1560 buffer.write(component.substring(sectionStart, index));
1561 buffer.write(replacement);
1562 index += sourceLength;
1563 sectionStart = index;
1564 }
1565 }
1566 if (buffer == null) {
1567 // Makes no copy if start == 0 and end == component.length.
1568 return component.substring(start, end);
1569 }
1570 if (sectionStart < end) {
1571 buffer.write(component.substring(sectionStart, end));
1572 }
1573 return buffer.toString();
1574 }
1575
1576 static bool _isSchemeCharacter(int ch) {
1577 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1578 }
1579
1580 static bool _isGeneralDelimiter(int ch) {
1581 return ch <= _RIGHT_BRACKET &&
1582 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1583 }
1584
1585 /**
1586 * Returns whether the URI is absolute.
1587 */
1588 bool get isAbsolute => scheme != "" && fragment == "";
1589
1590 String _mergePaths(String base, String reference) {
1591 // Optimize for the case: absolute base, reference beginning with "../".
1592 int backCount = 0;
1593 int refStart = 0;
1594 // Count number of "../" at beginning of reference.
1595 while (reference.startsWith("../", refStart)) {
1596 refStart += 3;
1597 backCount++;
1598 }
1599
1600 // Drop last segment - everything after last '/' of base.
1601 int baseEnd = base.lastIndexOf('/');
1602 // Drop extra segments for each leading "../" of reference.
1603 while (baseEnd > 0 && backCount > 0) {
1604 int newEnd = base.lastIndexOf('/', baseEnd - 1);
1605 if (newEnd < 0) {
1606 break;
1607 }
1608 int delta = baseEnd - newEnd;
1609 // If we see a "." or ".." segment in base, stop here and let
1610 // _removeDotSegments handle it.
1611 if ((delta == 2 || delta == 3) &&
1612 base.codeUnitAt(newEnd + 1) == _DOT &&
1613 (delta == 2 || base.codeUnitAt(newEnd + 2) == _DOT)) {
1614 break;
1615 }
1616 baseEnd = newEnd;
1617 backCount--;
1618 }
1619 return base.replaceRange(baseEnd + 1, null,
1620 reference.substring(refStart - 3 * backCount));
1621 }
1622
1623 /// Make a guess at whether a path contains a `..` or `.` segment.
1624 ///
1625 /// This is a primitive test that can cause false positives.
1626 /// It's only used to avoid a more expensive operation in the case where
1627 /// it's not necessary.
1628 static bool _mayContainDotSegments(String path) {
1629 if (path.startsWith('.')) return true;
1630 int index = path.indexOf("/.");
1631 return index != -1;
1632 }
1633
1634 /// Removes '.' and '..' segments from a path.
1635 ///
1636 /// Follows the RFC 2986 "remove dot segments" algorithm.
1637 /// This algorithm is only used on paths of URIs with a scheme,
1638 /// and it treats the path as if it is absolute (leading '..' are removed).
1639 static String _removeDotSegments(String path) {
1640 if (!_mayContainDotSegments(path)) return path;
1641 assert(path.isNotEmpty); // An empty path would not have dot segments.
1642 List<String> output = [];
1643 bool appendSlash = false;
1644 for (String segment in path.split("/")) {
1645 appendSlash = false;
1646 if (segment == "..") {
1647 if (output.isNotEmpty) {
1648 output.removeLast();
1649 if (output.isEmpty) {
1650 output.add("");
1651 }
1652 }
1653 appendSlash = true;
1654 } else if ("." == segment) {
1655 appendSlash = true;
1656 } else {
1657 output.add(segment);
1658 }
1659 }
1660 if (appendSlash) output.add("");
1661 return output.join("/");
1662 }
1663
1664 /// Removes all `.` segments and any non-leading `..` segments.
1665 ///
1666 /// Removing the ".." from a "bar/foo/.." sequence results in "bar/"
1667 /// (trailing "/"). If the entire path is removed (because it contains as
1668 /// many ".." segments as real segments), the result is "./".
1669 /// This is different from an empty string, which represents "no path",
1670 /// when you resolve it against a base URI with a path with a non-empty
1671 /// final segment.
1672 static String _normalizeRelativePath(String path) {
1673 assert(!path.startsWith('/')); // Only get called for relative paths.
1674 if (!_mayContainDotSegments(path)) return path;
1675 assert(path.isNotEmpty); // An empty path would not have dot segments.
1676 List<String> output = [];
1677 bool appendSlash = false;
1678 for (String segment in path.split("/")) {
1679 appendSlash = false;
1680 if (".." == segment) {
1681 if (!output.isEmpty && output.last != "..") {
1682 output.removeLast();
1683 appendSlash = true;
1684 } else {
1685 output.add("..");
1686 }
1687 } else if ("." == segment) {
1688 appendSlash = true;
1689 } else {
1690 output.add(segment);
1691 }
1692 }
1693 if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
1694 return "./";
1695 }
1696 if (appendSlash || output.last == '..') output.add("");
1697 return output.join("/");
1698 }
1699
1700 /**
1701 * Resolve [reference] as an URI relative to `this`.
1702 *
1703 * First turn [reference] into a URI using [Uri.parse]. Then resolve the
1704 * resulting URI relative to `this`.
1705 *
1706 * Returns the resolved URI.
1707 *
1708 * See [resolveUri] for details.
1709 */
1710 Uri resolve(String reference) {
1711 return resolveUri(Uri.parse(reference));
1712 }
1713
1714 /**
1715 * Resolve [reference] as an URI relative to `this`.
1716 *
1717 * Returns the resolved URI.
1718 *
1719 * The algorithm "Transform Reference" for resolving a reference is described
1720 * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1 123").
1721 *
1722 * Updated to handle the case where the base URI is just a relative path -
1723 * that is: when it has no scheme or authority and the path does not start
1724 * with a slash.
1725 * In that case, the paths are combined without removing leading "..", and
1726 * an empty path is not converted to "/".
1727 */
1728 Uri resolveUri(Uri reference) {
1729 // From RFC 3986.
1730 String targetScheme;
1731 String targetUserInfo = "";
1732 String targetHost;
1733 int targetPort;
1734 String targetPath;
1735 String targetQuery;
1736 if (reference.scheme.isNotEmpty) {
1737 targetScheme = reference.scheme;
1738 if (reference.hasAuthority) {
1739 targetUserInfo = reference.userInfo;
1740 targetHost = reference.host;
1741 targetPort = reference.hasPort ? reference.port : null;
1742 }
1743 targetPath = _removeDotSegments(reference.path);
1744 if (reference.hasQuery) {
1745 targetQuery = reference.query;
1746 }
1747 } else {
1748 targetScheme = this.scheme;
1749 if (reference.hasAuthority) {
1750 targetUserInfo = reference.userInfo;
1751 targetHost = reference.host;
1752 targetPort = _makePort(reference.hasPort ? reference.port : null,
1753 targetScheme);
1754 targetPath = _removeDotSegments(reference.path);
1755 if (reference.hasQuery) targetQuery = reference.query;
1756 } else {
1757 targetUserInfo = this._userInfo;
1758 targetHost = this._host;
1759 targetPort = this._port;
1760 if (reference.path == "") {
1761 targetPath = this._path;
1762 if (reference.hasQuery) {
1763 targetQuery = reference.query;
1764 } else {
1765 targetQuery = this._query;
1766 }
1767 } else {
1768 if (reference.hasAbsolutePath) {
1769 targetPath = _removeDotSegments(reference.path);
1770 } else {
1771 // This is the RFC 3986 behavior for merging.
1772 if (this.hasEmptyPath) {
1773 if (!this.hasScheme && !this.hasAuthority) {
1774 // Keep the path relative if no scheme or authority.
1775 targetPath = reference.path;
1776 } else {
1777 // Add path normalization on top of RFC algorithm.
1778 targetPath = _removeDotSegments("/" + reference.path);
1779 }
1780 } else {
1781 var mergedPath = _mergePaths(this._path, reference.path);
1782 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
1783 targetPath = _removeDotSegments(mergedPath);
1784 } else {
1785 // Non-RFC 3986 beavior. If both base and reference are relative
1786 // path, allow the merged path to start with "..".
1787 // The RFC only specifies the case where the base has a scheme.
1788 targetPath = _normalizeRelativePath(mergedPath);
1789 }
1790 }
1791 }
1792 if (reference.hasQuery) targetQuery = reference.query;
1793 }
1794 }
1795 }
1796 String fragment = reference.hasFragment ? reference.fragment : null;
1797 return new Uri._internal(targetScheme,
1798 targetUserInfo,
1799 targetHost,
1800 targetPort,
1801 targetPath,
1802 targetQuery,
1803 fragment);
1804 }
1805
1806 /**
1807 * Returns whether the URI has a [scheme] component.
1808 */
1809 bool get hasScheme => scheme.isNotEmpty;
1810
1811 /**
1812 * Returns whether the URI has an [authority] component.
1813 */
1814 bool get hasAuthority => _host != null;
1815
1816 /**
1817 * Returns whether the URI has an explicit port.
1818 *
1819 * If the port number is the default port number
1820 * (zero for unrecognized schemes, with http (80) and https (443) being
1821 * recognized),
1822 * then the port is made implicit and omitted from the URI.
1823 */
1824 bool get hasPort => _port != null;
1825
1826 /**
1827 * Returns whether the URI has a query part.
1828 */
1829 bool get hasQuery => _query != null;
1830
1831 /**
1832 * Returns whether the URI has a fragment part.
1833 */
1834 bool get hasFragment => _fragment != null;
1835
1836 /**
1837 * Returns whether the URI has an empty path.
1838 */
1839 bool get hasEmptyPath => _path.isEmpty;
1840
1841 /**
1842 * Returns whether the URI has an absolute path (starting with '/').
1843 */
1844 bool get hasAbsolutePath => _path.startsWith('/');
1845
1846 /**
1847 * Returns the origin of the URI in the form scheme://host:port for the
1848 * schemes http and https.
1849 *
1850 * It is an error if the scheme is not "http" or "https".
1851 *
1852 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
1853 */
1854 String get origin {
1855 if (scheme == "" || _host == null || _host == "") {
1856 throw new StateError("Cannot use origin without a scheme: $this");
1857 }
1858 if (scheme != "http" && scheme != "https") {
1859 throw new StateError(
1860 "Origin is only applicable schemes http and https: $this");
1861 }
1862 if (_port == null) return "$scheme://$_host";
1863 return "$scheme://$_host:$_port";
1864 }
1865
1866 /**
1867 * Returns the file path from a file URI.
1868 *
1869 * The returned path has either Windows or non-Windows
1870 * semantics.
1871 *
1872 * For non-Windows semantics the slash ("/") is used to separate
1873 * path segments.
1874 *
1875 * For Windows semantics the backslash ("\") separator is used to
1876 * separate path segments.
1877 *
1878 * If the URI is absolute the path starts with a path separator
1879 * unless Windows semantics is used and the first path segment is a
1880 * drive letter. When Windows semantics is used a host component in
1881 * the uri in interpreted as a file server and a UNC path is
1882 * returned.
1883 *
1884 * The default for whether to use Windows or non-Windows semantics
1885 * determined from the platform Dart is running on. When running in
1886 * the standalone VM this is detected by the VM based on the
1887 * operating system. When running in a browser non-Windows semantics
1888 * is always used.
1889 *
1890 * To override the automatic detection of which semantics to use pass
1891 * a value for [windows]. Passing `true` will use Windows
1892 * semantics and passing `false` will use non-Windows semantics.
1893 *
1894 * If the URI ends with a slash (i.e. the last path component is
1895 * empty) the returned file path will also end with a slash.
1896 *
1897 * With Windows semantics URIs starting with a drive letter cannot
1898 * be relative to the current drive on the designated drive. That is
1899 * for the URI `file:///c:abc` calling `toFilePath` will throw as a
1900 * path segment cannot contain colon on Windows.
1901 *
1902 * Examples using non-Windows semantics (resulting of calling
1903 * toFilePath in comment):
1904 *
1905 * Uri.parse("xxx/yyy"); // xxx/yyy
1906 * Uri.parse("xxx/yyy/"); // xxx/yyy/
1907 * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
1908 * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
1909 * Uri.parse("file:///C:"); // /C:
1910 * Uri.parse("file:///C:a"); // /C:a
1911 *
1912 * Examples using Windows semantics (resulting URI in comment):
1913 *
1914 * Uri.parse("xxx/yyy"); // xxx\yyy
1915 * Uri.parse("xxx/yyy/"); // xxx\yyy\
1916 * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
1917 * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
1918 * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
1919 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
1920 * // cannot contain colon on Windows.
1921 * Uri.parse("file://server/share/file"); // \\server\share\file
1922 *
1923 * If the URI is not a file URI calling this throws
1924 * [UnsupportedError].
1925 *
1926 * If the URI cannot be converted to a file path calling this throws
1927 * [UnsupportedError].
1928 */
1929 String toFilePath({bool windows}) {
1930 if (scheme != "" && scheme != "file") {
1931 throw new UnsupportedError(
1932 "Cannot extract a file path from a $scheme URI");
1933 }
1934 if (query != "") {
1935 throw new UnsupportedError(
1936 "Cannot extract a file path from a URI with a query component");
1937 }
1938 if (fragment != "") {
1939 throw new UnsupportedError(
1940 "Cannot extract a file path from a URI with a fragment component");
1941 }
1942 if (windows == null) windows = _isWindows;
1943 return windows ? _toWindowsFilePath() : _toFilePath();
1944 }
1945
1946 String _toFilePath() {
1947 if (host != "") {
1948 throw new UnsupportedError(
1949 "Cannot extract a non-Windows file path from a file URI "
1950 "with an authority");
1951 }
1952 _checkNonWindowsPathReservedCharacters(pathSegments, false);
1953 var result = new StringBuffer();
1954 if (_isPathAbsolute) result.write("/");
1955 result.writeAll(pathSegments, "/");
1956 return result.toString();
1957 }
1958
1959 String _toWindowsFilePath() {
1960 bool hasDriveLetter = false;
1961 var segments = pathSegments;
1962 if (segments.length > 0 &&
1963 segments[0].length == 2 &&
1964 segments[0].codeUnitAt(1) == _COLON) {
1965 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
1966 _checkWindowsPathReservedCharacters(segments, false, 1);
1967 hasDriveLetter = true;
1968 } else {
1969 _checkWindowsPathReservedCharacters(segments, false);
1970 }
1971 var result = new StringBuffer();
1972 if (_isPathAbsolute && !hasDriveLetter) result.write("\\");
1973 if (host != "") {
1974 result.write("\\");
1975 result.write(host);
1976 result.write("\\");
1977 }
1978 result.writeAll(segments, "\\");
1979 if (hasDriveLetter && segments.length == 1) result.write("\\");
1980 return result.toString();
1981 }
1982
1983 bool get _isPathAbsolute {
1984 if (path == null || path.isEmpty) return false;
1985 return path.startsWith('/');
1986 }
1987
1988 void _writeAuthority(StringSink ss) {
1989 if (_userInfo.isNotEmpty) {
1990 ss.write(_userInfo);
1991 ss.write("@");
1992 }
1993 if (_host != null) ss.write(_host);
1994 if (_port != null) {
1995 ss.write(":");
1996 ss.write(_port);
1997 }
1998 }
1999
2000 /**
2001 * Access the structure of a `data:` URI.
2002 *
2003 * Returns a [UriData] object for `data:` URIs and `null` for all other
2004 * URIs.
2005 * The [UriData] object can be used to access the media type and data
2006 * of a `data:` URI.
2007 */
2008 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
2009
2010 String toString() {
2011 StringBuffer sb = new StringBuffer();
2012 _addIfNonEmpty(sb, scheme, scheme, ':');
2013 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
2014 // File URIS always have the authority, even if it is empty.
2015 // The empty URI means "localhost".
2016 sb.write("//");
2017 _writeAuthority(sb);
2018 }
2019 sb.write(path);
2020 if (_query != null) { sb..write("?")..write(_query); }
2021 if (_fragment != null) { sb..write("#")..write(_fragment); }
2022 return sb.toString();
2023 }
2024
2025 bool operator==(other) {
2026 if (other is! Uri) return false;
2027 Uri uri = other;
2028 return scheme == uri.scheme &&
2029 hasAuthority == uri.hasAuthority &&
2030 userInfo == uri.userInfo &&
2031 host == uri.host &&
2032 port == uri.port &&
2033 path == uri.path &&
2034 hasQuery == uri.hasQuery &&
2035 query == uri.query &&
2036 hasFragment == uri.hasFragment &&
2037 fragment == uri.fragment;
2038 }
2039
2040 int get hashCode {
2041 int combine(part, current) {
2042 // The sum is truncated to 30 bits to make sure it fits into a Smi.
2043 return (current * 31 + part.hashCode) & 0x3FFFFFFF;
2044 }
2045 return combine(scheme, combine(userInfo, combine(host, combine(port,
2046 combine(path, combine(query, combine(fragment, 1)))))));
2047 }
2048
2049 static void _addIfNonEmpty(StringBuffer sb, String test,
2050 String first, String second) {
2051 if ("" != test) {
2052 sb.write(first);
2053 sb.write(second);
2054 }
2055 }
2056
2057 /**
2058 * Encode the string [component] using percent-encoding to make it
2059 * safe for literal use as a URI component.
2060 *
2061 * All characters except uppercase and lowercase letters, digits and
2062 * the characters `-_.!~*'()` are percent-encoded. This is the
2063 * set of characters specified in RFC 2396 and the which is
2064 * specified for the encodeUriComponent in ECMA-262 version 5.1.
2065 *
2066 * When manually encoding path segments or query components remember
2067 * to encode each part separately before building the path or query
2068 * string.
2069 *
2070 * For encoding the query part consider using
2071 * [encodeQueryComponent].
2072 *
2073 * To avoid the need for explicitly encoding use the [pathSegments]
2074 * and [queryParameters] optional named arguments when constructing
2075 * a [Uri].
2076 */
2077 static String encodeComponent(String component) {
2078 return _uriEncode(_unreserved2396Table, component, UTF8, false);
2079 }
2080
2081 /**
2082 * Encode the string [component] according to the HTML 4.01 rules
2083 * for encoding the posting of a HTML form as a query string
2084 * component.
2085 *
2086 * Encode the string [component] according to the HTML 4.01 rules
2087 * for encoding the posting of a HTML form as a query string
2088 * component.
2089
2090 * The component is first encoded to bytes using [encoding].
2091 * The default is to use [UTF8] encoding, which preserves all
2092 * the characters that don't need encoding.
2093
2094 * Then the resulting bytes are "percent-encoded". This transforms
2095 * spaces (U+0020) to a plus sign ('+') and all bytes that are not
2096 * the ASCII decimal digits, letters or one of '-._~' are written as
2097 * a percent sign '%' followed by the two-digit hexadecimal
2098 * representation of the byte.
2099
2100 * Note that the set of characters which are percent-encoded is a
2101 * superset of what HTML 4.01 requires, since it refers to RFC 1738
2102 * for reserved characters.
2103 *
2104 * When manually encoding query components remember to encode each
2105 * part separately before building the query string.
2106 *
2107 * To avoid the need for explicitly encoding the query use the
2108 * [queryParameters] optional named arguments when constructing a
2109 * [Uri].
2110 *
2111 * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
2112 * details.
2113 */
2114 static String encodeQueryComponent(String component,
2115 {Encoding encoding: UTF8}) {
2116 return _uriEncode(_unreservedTable, component, encoding, true);
2117 }
2118
2119 /**
2120 * Decodes the percent-encoding in [encodedComponent].
2121 *
2122 * Note that decoding a URI component might change its meaning as
2123 * some of the decoded characters could be characters with are
2124 * delimiters for a given URI componene type. Always split a URI
2125 * component using the delimiters for the component before decoding
2126 * the individual parts.
2127 *
2128 * For handling the [path] and [query] components consider using
2129 * [pathSegments] and [queryParameters] to get the separated and
2130 * decoded component.
2131 */
2132 static String decodeComponent(String encodedComponent) {
2133 return _uriDecode(encodedComponent, 0, encodedComponent.length,
2134 UTF8, false);
2135 }
2136
2137 /**
2138 * Decodes the percent-encoding in [encodedComponent], converting
2139 * pluses to spaces.
2140 *
2141 * It will create a byte-list of the decoded characters, and then use
2142 * [encoding] to decode the byte-list to a String. The default encoding is
2143 * UTF-8.
2144 */
2145 static String decodeQueryComponent(
2146 String encodedComponent,
2147 {Encoding encoding: UTF8}) {
2148 return _uriDecode(encodedComponent, 0, encodedComponent.length,
2149 encoding, true);
2150 }
2151
2152 /**
2153 * Encode the string [uri] using percent-encoding to make it
2154 * safe for literal use as a full URI.
2155 *
2156 * All characters except uppercase and lowercase letters, digits and
2157 * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
2158 * is the set of characters specified in in ECMA-262 version 5.1 for
2159 * the encodeURI function .
2160 */
2161 static String encodeFull(String uri) {
2162 return _uriEncode(_encodeFullTable, uri, UTF8, false);
2163 }
2164
2165 /**
2166 * Decodes the percent-encoding in [uri].
2167 *
2168 * Note that decoding a full URI might change its meaning as some of
2169 * the decoded characters could be reserved characters. In most
2170 * cases an encoded URI should be parsed into components using
2171 * [Uri.parse] before decoding the separate components.
2172 */
2173 static String decodeFull(String uri) {
2174 return _uriDecode(uri, 0, uri.length, UTF8, false);
2175 }
2176
2177 /**
2178 * Returns the [query] split into a map according to the rules
2179 * specified for FORM post in the [HTML 4.01 specification section
2180 * 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").
2181 * Each key and value in the returned map has been decoded. If the [query]
2182 * is the empty string an empty map is returned.
2183 *
2184 * Keys in the query string that have no value are mapped to the
2185 * empty string.
2186 *
2187 * Each query component will be decoded using [encoding]. The default encoding
2188 * is UTF-8.
2189 */
2190 static Map<String, String> splitQueryString(String query,
2191 {Encoding encoding: UTF8}) {
2192 return query.split("&").fold({}, (map, element) {
2193 int index = element.indexOf("=");
2194 if (index == -1) {
2195 if (element != "") {
2196 map[decodeQueryComponent(element, encoding: encoding)] = "";
2197 }
2198 } else if (index != 0) {
2199 var key = element.substring(0, index);
2200 var value = element.substring(index + 1);
2201 map[Uri.decodeQueryComponent(key, encoding: encoding)] =
2202 decodeQueryComponent(value, encoding: encoding);
2203 }
2204 return map;
2205 });
2206 }
2207
2208 static List _createList() => [];
2209
2210 static Map _splitQueryStringAll(
2211 String query, {Encoding encoding: UTF8}) {
2212 Map result = {};
2213 int i = 0;
2214 int start = 0;
2215 int equalsIndex = -1;
2216
2217 void parsePair(int start, int equalsIndex, int end) {
2218 String key;
2219 String value;
2220 if (start == end) return;
2221 if (equalsIndex < 0) {
2222 key = _uriDecode(query, start, end, encoding, true);
2223 value = "";
2224 } else {
2225 key = _uriDecode(query, start, equalsIndex, encoding, true);
2226 value = _uriDecode(query, equalsIndex + 1, end, encoding, true);
2227 }
2228 result.putIfAbsent(key, _createList).add(value);
2229 }
2230
2231 const int _equals = 0x3d;
2232 const int _ampersand = 0x26;
2233 while (i < query.length) {
2234 int char = query.codeUnitAt(i);
2235 if (char == _equals) {
2236 if (equalsIndex < 0) equalsIndex = i;
2237 } else if (char == _ampersand) {
2238 parsePair(start, equalsIndex, i);
2239 start = i + 1;
2240 equalsIndex = -1;
2241 }
2242 i++;
2243 }
2244 parsePair(start, equalsIndex, i);
2245 return result;
2246 }
2247
2248 /**
2249 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
2250 * as a list of 4 bytes in network byte order (big endian).
2251 *
2252 * Throws a [FormatException] if [host] is not a valid IPv4 address
2253 * representation.
2254 */
2255 static List<int> parseIPv4Address(String host) {
2256 void error(String msg) {
2257 throw new FormatException('Illegal IPv4 address, $msg');
2258 }
2259 var bytes = host.split('.');
2260 if (bytes.length != 4) {
2261 error('IPv4 address should contain exactly 4 parts');
2262 }
2263 // TODO(ajohnsen): Consider using Uint8List.
2264 return bytes
2265 .map((byteString) {
2266 int byte = int.parse(byteString);
2267 if (byte < 0 || byte > 255) {
2268 error('each part must be in the range of `0..255`');
2269 }
2270 return byte;
2271 })
2272 .toList();
2273 }
2274
2275 /**
2276 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
2277 * as a list of 16 bytes in network byte order (big endian).
2278 *
2279 * Throws a [FormatException] if [host] is not a valid IPv6 address
2280 * representation.
2281 *
2282 * Acts on the substring from [start] to [end]. If [end] is omitted, it
2283 * defaults ot the end of the string.
2284 *
2285 * Some examples of IPv6 addresses:
2286 * * ::1
2287 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
2288 * * 3ffe:2a00:100:7031::1
2289 * * ::FFFF:129.144.52.38
2290 * * 2010:836B:4179::836B:4179
2291 */
2292 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
2293 if (end == null) end = host.length;
2294 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
2295 // by `:`'s, with the following exceptions:
2296 //
2297 // - One (and only one) wildcard (`::`) may be present, representing a fill
2298 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
2299 // - The last two parts may be replaced by an IPv4 address.
2300 void error(String msg, [position]) {
2301 throw new FormatException('Illegal IPv6 address, $msg', host, position);
2302 }
2303 int parseHex(int start, int end) {
2304 if (end - start > 4) {
2305 error('an IPv6 part can only contain a maximum of 4 hex digits', start);
2306 }
2307 int value = int.parse(host.substring(start, end), radix: 16);
2308 if (value < 0 || value > (1 << 16) - 1) {
2309 error('each part must be in the range of `0x0..0xFFFF`', start);
2310 }
2311 return value;
2312 }
2313 if (host.length < 2) error('address is too short');
2314 List<int> parts = [];
2315 bool wildcardSeen = false;
2316 int partStart = start;
2317 // Parse all parts, except a potential last one.
2318 for (int i = start; i < end; i++) {
2319 if (host.codeUnitAt(i) == _COLON) {
2320 if (i == start) {
2321 // If we see a `:` in the beginning, expect wildcard.
2322 i++;
2323 if (host.codeUnitAt(i) != _COLON) {
2324 error('invalid start colon.', i);
2325 }
2326 partStart = i;
2327 }
2328 if (i == partStart) {
2329 // Wildcard. We only allow one.
2330 if (wildcardSeen) {
2331 error('only one wildcard `::` is allowed', i);
2332 }
2333 wildcardSeen = true;
2334 parts.add(-1);
2335 } else {
2336 // Found a single colon. Parse [partStart..i] as a hex entry.
2337 parts.add(parseHex(partStart, i));
2338 }
2339 partStart = i + 1;
2340 }
2341 }
2342 if (parts.length == 0) error('too few parts');
2343 bool atEnd = (partStart == end);
2344 bool isLastWildcard = (parts.last == -1);
2345 if (atEnd && !isLastWildcard) {
2346 error('expected a part after last `:`', end);
2347 }
2348 if (!atEnd) {
2349 try {
2350 parts.add(parseHex(partStart, end));
2351 } catch (e) {
2352 // Failed to parse the last chunk as hex. Try IPv4.
2353 try {
2354 List<int> last = parseIPv4Address(host.substring(partStart, end));
2355 parts.add(last[0] << 8 | last[1]);
2356 parts.add(last[2] << 8 | last[3]);
2357 } catch (e) {
2358 error('invalid end of IPv6 address.', partStart);
2359 }
2360 }
2361 }
2362 if (wildcardSeen) {
2363 if (parts.length > 7) {
2364 error('an address with a wildcard must have less than 7 parts');
2365 }
2366 } else if (parts.length != 8) {
2367 error('an address without a wildcard must contain exactly 8 parts');
2368 }
2369 List<int> bytes = new Uint8List(16);
2370 for (int i = 0, index = 0; i < parts.length; i++) {
2371 int value = parts[i];
2372 if (value == -1) {
2373 int wildCardLength = 9 - parts.length;
2374 for (int j = 0; j < wildCardLength; j++) {
2375 bytes[index] = 0;
2376 bytes[index + 1] = 0;
2377 index += 2;
2378 }
2379 } else {
2380 bytes[index] = value >> 8;
2381 bytes[index + 1] = value & 0xff;
2382 index += 2;
2383 }
2384 }
2385 return bytes;
2386 }
2387
2388 // Frequently used character codes.
2389 static const int _SPACE = 0x20;
2390 static const int _DOUBLE_QUOTE = 0x22;
2391 static const int _NUMBER_SIGN = 0x23;
2392 static const int _PERCENT = 0x25;
2393 static const int _ASTERISK = 0x2A;
2394 static const int _PLUS = 0x2B;
2395 static const int _DOT = 0x2E;
2396 static const int _SLASH = 0x2F;
2397 static const int _ZERO = 0x30;
2398 static const int _NINE = 0x39;
2399 static const int _COLON = 0x3A;
2400 static const int _LESS = 0x3C;
2401 static const int _GREATER = 0x3E;
2402 static const int _QUESTION = 0x3F;
2403 static const int _AT_SIGN = 0x40;
2404 static const int _UPPER_CASE_A = 0x41;
2405 static const int _UPPER_CASE_F = 0x46;
2406 static const int _UPPER_CASE_Z = 0x5A;
2407 static const int _LEFT_BRACKET = 0x5B;
2408 static const int _BACKSLASH = 0x5C;
2409 static const int _RIGHT_BRACKET = 0x5D;
2410 static const int _LOWER_CASE_A = 0x61;
2411 static const int _LOWER_CASE_F = 0x66;
2412 static const int _LOWER_CASE_Z = 0x7A;
2413 static const int _BAR = 0x7C;
2414
2415 static const String _hexDigits = "0123456789ABCDEF";
2416
2417 external static String _uriEncode(List<int> canonicalTable,
2418 String text,
2419 Encoding encoding,
2420 bool spaceToPlus);
2421
2422 /**
2423 * Convert a byte (2 character hex sequence) in string [s] starting
2424 * at position [pos] to its ordinal value
2425 */
2426 static int _hexCharPairToByte(String s, int pos) {
2427 int byte = 0;
2428 for (int i = 0; i < 2; i++) {
2429 var charCode = s.codeUnitAt(pos + i);
2430 if (0x30 <= charCode && charCode <= 0x39) {
2431 byte = byte * 16 + charCode - 0x30;
2432 } else {
2433 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66).
2434 charCode |= 0x20;
2435 if (0x61 <= charCode && charCode <= 0x66) {
2436 byte = byte * 16 + charCode - 0x57;
2437 } else {
2438 throw new ArgumentError("Invalid URL encoding");
2439 }
2440 }
2441 }
2442 return byte;
2443 }
2444
2445 /**
2446 * Uri-decode a percent-encoded string.
2447 *
2448 * It unescapes the string [text] and returns the unescaped string.
2449 *
2450 * This function is similar to the JavaScript-function `decodeURI`.
2451 *
2452 * If [plusToSpace] is `true`, plus characters will be converted to spaces.
2453 *
2454 * The decoder will create a byte-list of the percent-encoded parts, and then
2455 * decode the byte-list using [encoding]. The default encodingis UTF-8.
2456 */
2457 static String _uriDecode(String text,
2458 int start,
2459 int end,
2460 Encoding encoding,
2461 bool plusToSpace) {
2462 assert(0 <= start);
2463 assert(start <= end);
2464 assert(end <= text.length);
2465 assert(encoding != null);
2466 // First check whether there is any characters which need special handling.
2467 bool simple = true;
2468 for (int i = start; i < end; i++) {
2469 var codeUnit = text.codeUnitAt(i);
2470 if (codeUnit > 127 ||
2471 codeUnit == _PERCENT ||
2472 (plusToSpace && codeUnit == _PLUS)) {
2473 simple = false;
2474 break;
2475 }
2476 }
2477 List<int> bytes;
2478 if (simple) {
2479 if (UTF8 == encoding || LATIN1 == encoding || ASCII == encoding) {
2480 return text.substring(start, end);
2481 } else {
2482 bytes = text.substring(start, end).codeUnits;
2483 }
2484 } else {
2485 bytes = new List();
2486 for (int i = start; i < end; i++) {
2487 var codeUnit = text.codeUnitAt(i);
2488 if (codeUnit > 127) {
2489 throw new ArgumentError("Illegal percent encoding in URI");
2490 }
2491 if (codeUnit == _PERCENT) {
2492 if (i + 3 > text.length) {
2493 throw new ArgumentError('Truncated URI');
2494 }
2495 bytes.add(_hexCharPairToByte(text, i + 1));
2496 i += 2;
2497 } else if (plusToSpace && codeUnit == _PLUS) {
2498 bytes.add(_SPACE);
2499 } else {
2500 bytes.add(codeUnit);
2501 }
2502 }
2503 }
2504 return encoding.decode(bytes);
2505 }
2506
2507 static bool _isAlphabeticCharacter(int codeUnit) {
2508 var lowerCase = codeUnit | 0x20;
2509 return (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_Z);
2510 }
2511
2512 static bool _isUnreservedChar(int char) {
2513 return char < 127 &&
2514 ((_unreservedTable[char >> 4] & (1 << (char & 0x0f))) != 0);
2515 }
2516
2517 // Tables of char-codes organized as a bit vector of 128 bits where
2518 // each bit indicate whether a character code on the 0-127 needs to
2519 // be escaped or not.
2520
2521 // The unreserved characters of RFC 3986.
2522 static const _unreservedTable = const [
2523 // LSB MSB
2524 // | |
2525 0x0000, // 0x00 - 0x0f 0000000000000000
2526 0x0000, // 0x10 - 0x1f 0000000000000000
2527 // -.
2528 0x6000, // 0x20 - 0x2f 0000000000000110
2529 // 0123456789
2530 0x03ff, // 0x30 - 0x3f 1111111111000000
2531 // ABCDEFGHIJKLMNO
2532 0xfffe, // 0x40 - 0x4f 0111111111111111
2533 // PQRSTUVWXYZ _
2534 0x87ff, // 0x50 - 0x5f 1111111111100001
2535 // abcdefghijklmno
2536 0xfffe, // 0x60 - 0x6f 0111111111111111
2537 // pqrstuvwxyz ~
2538 0x47ff]; // 0x70 - 0x7f 1111111111100010
2539
2540 // The unreserved characters of RFC 2396.
2541 static const _unreserved2396Table = const [
2542 // LSB MSB
2543 // | |
2544 0x0000, // 0x00 - 0x0f 0000000000000000
2545 0x0000, // 0x10 - 0x1f 0000000000000000
2546 // ! '()* -.
2547 0x6782, // 0x20 - 0x2f 0100000111100110
2548 // 0123456789
2549 0x03ff, // 0x30 - 0x3f 1111111111000000
2550 // ABCDEFGHIJKLMNO
2551 0xfffe, // 0x40 - 0x4f 0111111111111111
2552 // PQRSTUVWXYZ _
2553 0x87ff, // 0x50 - 0x5f 1111111111100001
2554 // abcdefghijklmno
2555 0xfffe, // 0x60 - 0x6f 0111111111111111
2556 // pqrstuvwxyz ~
2557 0x47ff]; // 0x70 - 0x7f 1111111111100010
2558
2559 // Table of reserved characters specified by ECMAScript 5.
2560 static const _encodeFullTable = const [
2561 // LSB MSB
2562 // | |
2563 0x0000, // 0x00 - 0x0f 0000000000000000
2564 0x0000, // 0x10 - 0x1f 0000000000000000
2565 // ! #$ &'()*+,-./
2566 0xffda, // 0x20 - 0x2f 0101101111111111
2567 // 0123456789:; = ?
2568 0xafff, // 0x30 - 0x3f 1111111111110101
2569 // @ABCDEFGHIJKLMNO
2570 0xffff, // 0x40 - 0x4f 1111111111111111
2571 // PQRSTUVWXYZ _
2572 0x87ff, // 0x50 - 0x5f 1111111111100001
2573 // abcdefghijklmno
2574 0xfffe, // 0x60 - 0x6f 0111111111111111
2575 // pqrstuvwxyz ~
2576 0x47ff]; // 0x70 - 0x7f 1111111111100010
2577
2578 // Characters allowed in the scheme.
2579 static const _schemeTable = const [
2580 // LSB MSB
2581 // | |
2582 0x0000, // 0x00 - 0x0f 0000000000000000
2583 0x0000, // 0x10 - 0x1f 0000000000000000
2584 // + -.
2585 0x6800, // 0x20 - 0x2f 0000000000010110
2586 // 0123456789
2587 0x03ff, // 0x30 - 0x3f 1111111111000000
2588 // ABCDEFGHIJKLMNO
2589 0xfffe, // 0x40 - 0x4f 0111111111111111
2590 // PQRSTUVWXYZ
2591 0x07ff, // 0x50 - 0x5f 1111111111100001
2592 // abcdefghijklmno
2593 0xfffe, // 0x60 - 0x6f 0111111111111111
2594 // pqrstuvwxyz
2595 0x07ff]; // 0x70 - 0x7f 1111111111100010
2596
2597 // Characters allowed in scheme except for upper case letters.
2598 static const _schemeLowerTable = const [
2599 // LSB MSB
2600 // | |
2601 0x0000, // 0x00 - 0x0f 0000000000000000
2602 0x0000, // 0x10 - 0x1f 0000000000000000
2603 // + -.
2604 0x6800, // 0x20 - 0x2f 0000000000010110
2605 // 0123456789
2606 0x03ff, // 0x30 - 0x3f 1111111111000000
2607 //
2608 0x0000, // 0x40 - 0x4f 0111111111111111
2609 //
2610 0x0000, // 0x50 - 0x5f 1111111111100001
2611 // abcdefghijklmno
2612 0xfffe, // 0x60 - 0x6f 0111111111111111
2613 // pqrstuvwxyz
2614 0x07ff]; // 0x70 - 0x7f 1111111111100010
2615
2616 // Sub delimiter characters combined with unreserved as of 3986.
2617 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
2618 // / "*" / "+" / "," / ";" / "="
2619 // RFC 3986 section 2.3.
2620 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
2621 static const _subDelimitersTable = const [
2622 // LSB MSB
2623 // | |
2624 0x0000, // 0x00 - 0x0f 0000000000000000
2625 0x0000, // 0x10 - 0x1f 0000000000000000
2626 // ! $ &'()*+,-.
2627 0x7fd2, // 0x20 - 0x2f 0100101111111110
2628 // 0123456789 ; =
2629 0x2bff, // 0x30 - 0x3f 1111111111010100
2630 // ABCDEFGHIJKLMNO
2631 0xfffe, // 0x40 - 0x4f 0111111111111111
2632 // PQRSTUVWXYZ _
2633 0x87ff, // 0x50 - 0x5f 1111111111100001
2634 // abcdefghijklmno
2635 0xfffe, // 0x60 - 0x6f 0111111111111111
2636 // pqrstuvwxyz ~
2637 0x47ff]; // 0x70 - 0x7f 1111111111100010
2638
2639 // General delimiter characters, RFC 3986 section 2.2.
2640 // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
2641 //
2642 static const _genDelimitersTable = const [
2643 // LSB MSB
2644 // | |
2645 0x0000, // 0x00 - 0x0f 0000000000000000
2646 0x0000, // 0x10 - 0x1f 0000000000000000
2647 // # /
2648 0x8008, // 0x20 - 0x2f 0001000000000001
2649 // : ?
2650 0x8400, // 0x30 - 0x3f 0000000000100001
2651 // @
2652 0x0001, // 0x40 - 0x4f 1000000000000000
2653 // [ ]
2654 0x2800, // 0x50 - 0x5f 0000000000010100
2655 //
2656 0x0000, // 0x60 - 0x6f 0000000000000000
2657 //
2658 0x0000]; // 0x70 - 0x7f 0000000000000000
2659
2660 // Characters allowed in the userinfo as of RFC 3986.
2661 // RFC 3986 Apendix A
2662 // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
2663 static const _userinfoTable = const [
2664 // LSB MSB
2665 // | |
2666 0x0000, // 0x00 - 0x0f 0000000000000000
2667 0x0000, // 0x10 - 0x1f 0000000000000000
2668 // ! $ &'()*+,-.
2669 0x7fd2, // 0x20 - 0x2f 0100101111111110
2670 // 0123456789:; =
2671 0x2fff, // 0x30 - 0x3f 1111111111110100
2672 // ABCDEFGHIJKLMNO
2673 0xfffe, // 0x40 - 0x4f 0111111111111111
2674 // PQRSTUVWXYZ _
2675 0x87ff, // 0x50 - 0x5f 1111111111100001
2676 // abcdefghijklmno
2677 0xfffe, // 0x60 - 0x6f 0111111111111111
2678 // pqrstuvwxyz ~
2679 0x47ff]; // 0x70 - 0x7f 1111111111100010
2680
2681 // Characters allowed in the reg-name as of RFC 3986.
2682 // RFC 3986 Apendix A
2683 // reg-name = *( unreserved / pct-encoded / sub-delims )
2684 static const _regNameTable = const [
2685 // LSB MSB
2686 // | |
2687 0x0000, // 0x00 - 0x0f 0000000000000000
2688 0x0000, // 0x10 - 0x1f 0000000000000000
2689 // ! $%&'()*+,-.
2690 0x7ff2, // 0x20 - 0x2f 0100111111111110
2691 // 0123456789 ; =
2692 0x2bff, // 0x30 - 0x3f 1111111111010100
2693 // ABCDEFGHIJKLMNO
2694 0xfffe, // 0x40 - 0x4f 0111111111111111
2695 // PQRSTUVWXYZ _
2696 0x87ff, // 0x50 - 0x5f 1111111111100001
2697 // abcdefghijklmno
2698 0xfffe, // 0x60 - 0x6f 0111111111111111
2699 // pqrstuvwxyz ~
2700 0x47ff]; // 0x70 - 0x7f 1111111111100010
2701
2702 // Characters allowed in the path as of RFC 3986.
2703 // RFC 3986 section 3.3.
2704 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
2705 static const _pathCharTable = const [
2706 // LSB MSB
2707 // | |
2708 0x0000, // 0x00 - 0x0f 0000000000000000
2709 0x0000, // 0x10 - 0x1f 0000000000000000
2710 // ! $ &'()*+,-.
2711 0x7fd2, // 0x20 - 0x2f 0100101111111110
2712 // 0123456789:; =
2713 0x2fff, // 0x30 - 0x3f 1111111111110100
2714 // @ABCDEFGHIJKLMNO
2715 0xffff, // 0x40 - 0x4f 1111111111111111
2716 // PQRSTUVWXYZ _
2717 0x87ff, // 0x50 - 0x5f 1111111111100001
2718 // abcdefghijklmno
2719 0xfffe, // 0x60 - 0x6f 0111111111111111
2720 // pqrstuvwxyz ~
2721 0x47ff]; // 0x70 - 0x7f 1111111111100010
2722
2723 // Characters allowed in the path as of RFC 3986.
2724 // RFC 3986 section 3.3 *and* slash.
2725 static const _pathCharOrSlashTable = const [
2726 // LSB MSB
2727 // | |
2728 0x0000, // 0x00 - 0x0f 0000000000000000
2729 0x0000, // 0x10 - 0x1f 0000000000000000
2730 // ! $ &'()*+,-./
2731 0xffd2, // 0x20 - 0x2f 0100101111111111
2732 // 0123456789:; =
2733 0x2fff, // 0x30 - 0x3f 1111111111110100
2734 // @ABCDEFGHIJKLMNO
2735 0xffff, // 0x40 - 0x4f 1111111111111111
2736
2737 // PQRSTUVWXYZ _
2738 0x87ff, // 0x50 - 0x5f 1111111111100001
2739 // abcdefghijklmno
2740 0xfffe, // 0x60 - 0x6f 0111111111111111
2741 // pqrstuvwxyz ~
2742 0x47ff]; // 0x70 - 0x7f 1111111111100010
2743
2744 // Characters allowed in the query as of RFC 3986.
2745 // RFC 3986 section 3.4.
2746 // query = *( pchar / "/" / "?" )
2747 static const _queryCharTable = const [
2748 // LSB MSB
2749 // | |
2750 0x0000, // 0x00 - 0x0f 0000000000000000
2751 0x0000, // 0x10 - 0x1f 0000000000000000
2752 // ! $ &'()*+,-./
2753 0xffd2, // 0x20 - 0x2f 0100101111111111
2754 // 0123456789:; = ?
2755 0xafff, // 0x30 - 0x3f 1111111111110101
2756 // @ABCDEFGHIJKLMNO
2757 0xffff, // 0x40 - 0x4f 1111111111111111
2758 // PQRSTUVWXYZ _
2759 0x87ff, // 0x50 - 0x5f 1111111111100001
2760 // abcdefghijklmno
2761 0xfffe, // 0x60 - 0x6f 0111111111111111
2762 // pqrstuvwxyz ~
2763 0x47ff]; // 0x70 - 0x7f 1111111111100010
2764
2765 }
2766
2767 // --------------------------------------------------------------------
2768 // Data URI
2769 // --------------------------------------------------------------------
2770
2771 /**
2772 * A way to access the structure of a `data:` URI.
2773 *
2774 * Data URIs are non-hierarchical URIs that can contain any binary data.
2775 * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397).
2776 *
2777 * This class allows parsing the URI text and extracting individual parts of the
2778 * URI, as well as building the URI text from structured parts.
2779 */
2780 class UriData {
2781 static const int _noScheme = -1;
2782 /**
2783 * Contains the text content of a `data:` URI, with or without a
2784 * leading `data:`.
2785 *
2786 * If [_separatorIndices] starts with `4` (the index of the `:`), then
2787 * there is a leading `data:`, otherwise [_separatorIndices] starts with
2788 * `-1`.
2789 */
2790 final String _text;
2791
2792 /**
2793 * List of the separators (';', '=' and ',') in the text.
2794 *
2795 * Starts with the index of the `:` in `data:` of the mimeType.
2796 * That is always either -1 or 4, depending on whether `_text` includes the
2797 * `data:` scheme or not.
2798 *
2799 * The first speparator ends the mime type. We don't bother with finding
2800 * the '/' inside the mime type.
2801 *
2802 * Each two separators after that marks a parameter key and value.
2803 *
2804 * If there is a single separator left, it ends the "base64" marker.
2805 *
2806 * So the following separators are found for a text:
2807 *
2808 * data:text/plain;foo=bar;base64,ARGLEBARGLE=
2809 * ^ ^ ^ ^ ^
2810 *
2811 */
2812 final List<int> _separatorIndices;
2813
2814 /**
2815 * Cache of the result returned by [uri].
2816 */
2817 Uri _uriCache;
2818
2819 UriData._(this._text, this._separatorIndices, this._uriCache);
2820
2821 /**
2822 * Creates a `data:` URI containing the [content] string.
2823 *
2824 * Equivalent to `new Uri.dataFromString(...).data`, but may
2825 * be more efficient if the [uri] itself isn't used.
2826 */
2827 factory UriData.fromString(String content,
2828 {String mimeType,
2829 Encoding encoding,
2830 Map<String, String> parameters,
2831 bool base64: false}) {
2832 StringBuffer buffer = new StringBuffer();
2833 List<int> indices = [_noScheme];
2834 String charsetName;
2835 String encodingName;
2836 if (parameters != null) charsetName = parameters["charset"];
2837 if (encoding == null) {
2838 if (charsetName != null) {
2839 encoding = Encoding.getByName(charsetName);
2840 }
2841 } else if (charsetName == null) {
2842 // Non-null only if parameters does not contain "charset".
2843 encodingName = encoding.name;
2844 }
2845 encoding ??= ASCII;
2846 _writeUri(mimeType, encodingName, parameters, buffer, indices);
2847 indices.add(buffer.length);
2848 if (base64) {
2849 buffer.write(';base64,');
2850 indices.add(buffer.length - 1);
2851 buffer.write(encoding.fuse(BASE64).encode(content));
2852 } else {
2853 buffer.write(',');
2854 _uriEncodeBytes(_uricTable, encoding.encode(content), buffer);
2855 }
2856 return new UriData._(buffer.toString(), indices, null);
2857 }
2858
2859 /**
2860 * Creates a `data:` URI containing an encoding of [bytes].
2861 *
2862 * Equivalent to `new Uri.dataFromBytes(...).data`, but may
2863 * be more efficient if the [uri] itself isn't used.
2864 */
2865 factory UriData.fromBytes(List<int> bytes,
2866 {mimeType: "application/octet-stream",
2867 Map<String, String> parameters,
2868 percentEncoded: false}) {
2869 StringBuffer buffer = new StringBuffer();
2870 List<int> indices = [_noScheme];
2871 _writeUri(mimeType, null, parameters, buffer, indices);
2872 indices.add(buffer.length);
2873 if (percentEncoded) {
2874 buffer.write(',');
2875 _uriEncodeBytes(_uricTable, bytes, buffer);
2876 } else {
2877 buffer.write(';base64,');
2878 indices.add(buffer.length - 1);
2879 BASE64.encoder
2880 .startChunkedConversion(
2881 new StringConversionSink.fromStringSink(buffer))
2882 .addSlice(bytes, 0, bytes.length, true);
2883 }
2884
2885 return new UriData._(buffer.toString(), indices, null);
2886 }
2887
2888 /**
2889 * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme].
2890 *
2891 * The [uri] must have scheme `data` and no authority or fragment,
2892 * and the path (concatenated with the query, if there is one) must be valid
2893 * as data URI content with the same rules as [parse].
2894 */
2895 factory UriData.fromUri(Uri uri) {
2896 if (uri.scheme != "data") {
2897 throw new ArgumentError.value(uri, "uri",
2898 "Scheme must be 'data'");
2899 }
2900 if (uri.hasAuthority) {
2901 throw new ArgumentError.value(uri, "uri",
2902 "Data uri must not have authority");
2903 }
2904 if (uri.hasFragment) {
2905 throw new ArgumentError.value(uri, "uri",
2906 "Data uri must not have a fragment part");
2907 }
2908 if (!uri.hasQuery) {
2909 return _parse(uri.path, 0, uri);
2910 }
2911 // Includes path and query (and leading "data:").
2912 return _parse("$uri", 5, uri);
2913 }
2914
2915 /**
2916 * Writes the initial part of a `data:` uri, from after the "data:"
2917 * until just before the ',' before the data, or before a `;base64,`
2918 * marker.
2919 *
2920 * Of an [indices] list is passed, separator indices are stored in that
2921 * list.
2922 */
2923 static void _writeUri(String mimeType,
2924 String charsetName,
2925 Map<String, String> parameters,
2926 StringBuffer buffer, List indices) {
2927 if (mimeType == null || mimeType == "text/plain") {
2928 mimeType = "";
2929 }
2930 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
2931 buffer.write(mimeType); // Common cases need no escaping.
2932 } else {
2933 int slashIndex = _validateMimeType(mimeType);
2934 if (slashIndex < 0) {
2935 throw new ArgumentError.value(mimeType, "mimeType",
2936 "Invalid MIME type");
2937 }
2938 buffer.write(Uri._uriEncode(_tokenCharTable,
2939 mimeType.substring(0, slashIndex),
2940 UTF8, false));
2941 buffer.write("/");
2942 buffer.write(Uri._uriEncode(_tokenCharTable,
2943 mimeType.substring(slashIndex + 1),
2944 UTF8, false));
2945 }
2946 if (charsetName != null) {
2947 if (indices != null) {
2948 indices..add(buffer.length)
2949 ..add(buffer.length + 8);
2950 }
2951 buffer.write(";charset=");
2952 buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
2953 }
2954 parameters?.forEach((var key, var value) {
2955 if (key.isEmpty) {
2956 throw new ArgumentError.value("", "Parameter names must not be empty");
2957 }
2958 if (value.isEmpty) {
2959 throw new ArgumentError.value("", "Parameter values must not be empty",
2960 'parameters["$key"]');
2961 }
2962 if (indices != null) indices.add(buffer.length);
2963 buffer.write(';');
2964 // Encode any non-RFC2045-token character and both '%' and '#'.
2965 buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false));
2966 if (indices != null) indices.add(buffer.length);
2967 buffer.write('=');
2968 buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
2969 });
2970 }
2971
2972 /**
2973 * Checks mimeType is valid-ish (`token '/' token`).
2974 *
2975 * Returns the index of the slash, or -1 if the mime type is not
2976 * considered valid.
2977 *
2978 * Currently only looks for slashes, all other characters will be
2979 * percent-encoded as UTF-8 if necessary.
2980 */
2981 static int _validateMimeType(String mimeType) {
2982 int slashIndex = -1;
2983 for (int i = 0; i < mimeType.length; i++) {
2984 var char = mimeType.codeUnitAt(i);
2985 if (char != Uri._SLASH) continue;
2986 if (slashIndex < 0) {
2987 slashIndex = i;
2988 continue;
2989 }
2990 return -1;
2991 }
2992 return slashIndex;
2993 }
2994
2995 /**
2996 * Parses a string as a `data` URI.
2997 *
2998 * The string must have the format:
2999 *
3000 * ```
3001 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a
3002 * ````
3003 *
3004 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
3005 * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
3006 *
3007 * This means that all the characters must be ASCII, but the URI may contain
3008 * percent-escapes for non-ASCII byte values that need an interpretation
3009 * to be converted to the corresponding string.
3010 *
3011 * Parsing doesn't check the validity of any part, it just checks that the
3012 * input has the correct structure with the correct sequence of `/`, `;`, `=`
3013 * and `,` delimiters.
3014 *
3015 * Accessing the individual parts may fail later if they turn out to have
3016 * content that can't be decoded sucessfully as a string.
3017 */
3018 static UriData parse(String uri) {
3019 if (!uri.startsWith("data:")) {
3020 throw new FormatException("Does not start with 'data:'", uri, 0);
3021 }
3022 return _parse(uri, 5, null);
3023 }
3024
3025 /**
3026 * The [Uri] that this `UriData` is giving access to.
3027 *
3028 * Returns a `Uri` with scheme `data` and the remainder of the data URI
3029 * as path.
3030 */
3031 Uri get uri {
3032 if (_uriCache != null) return _uriCache;
3033 String path = _text;
3034 String query = null;
3035 int colonIndex = _separatorIndices[0];
3036 int queryIndex = _text.indexOf('?', colonIndex + 1);
3037 int end = null;
3038 if (queryIndex >= 0) {
3039 query = _text.substring(queryIndex + 1);
3040 end = queryIndex;
3041 }
3042 path = _text.substring(colonIndex + 1, end);
3043 // TODO(lrn): This is probably too simple. We should ensure URI
3044 // normalization before passing in the raw strings, maybe using
3045 // Uri._makePath, Uri._makeQuery.
3046 _uriCache = new Uri._internal("data", "", null, null, path, query, null);
3047 return _uriCache;
3048 }
3049
3050 /**
3051 * The MIME type of the data URI.
3052 *
3053 * A data URI consists of a "media type" followed by data.
3054 * The media type starts with a MIME type and can be followed by
3055 * extra parameters.
3056 *
3057 * Example:
3058 *
3059 * data:text/plain;charset=utf-8,Hello%20World!
3060 *
3061 * This data URI has the media type `text/plain;charset=utf-8`, which is the
3062 * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
3063 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
3064 *
3065 * If the first part of the data URI is empty, it defaults to `text/plain`.
3066 */
3067 String get mimeType {
3068 int start = _separatorIndices[0] + 1;
3069 int end = _separatorIndices[1];
3070 if (start == end) return "text/plain";
3071 return Uri._uriDecode(_text, start, end, UTF8, false);
3072 }
3073
3074 /**
3075 * The charset parameter of the media type.
3076 *
3077 * If the parameters of the media type contains a `charset` parameter
3078 * then this returns its value, otherwise it returns `US-ASCII`,
3079 * which is the default charset for data URIs.
3080 */
3081 String get charset {
3082 int parameterStart = 1;
3083 int parameterEnd = _separatorIndices.length - 1; // The ',' before data.
3084 if (isBase64) {
3085 // There is a ";base64" separator, so subtract one for that as well.
3086 parameterEnd -= 1;
3087 }
3088 for (int i = parameterStart; i < parameterEnd; i += 2) {
3089 var keyStart = _separatorIndices[i] + 1;
3090 var keyEnd = _separatorIndices[i + 1];
3091 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
3092 return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
3093 UTF8, false);
3094 }
3095 }
3096 return "US-ASCII";
3097 }
3098
3099 /**
3100 * Whether the data is Base64 encoded or not.
3101 */
3102 bool get isBase64 => _separatorIndices.length.isOdd;
3103
3104 /**
3105 * The content part of the data URI, as its actual representation.
3106 *
3107 * This string may contain percent escapes.
3108 */
3109 String get contentText => _text.substring(_separatorIndices.last + 1);
3110
3111 /**
3112 * The content part of the data URI as bytes.
3113 *
3114 * If the data is Base64 encoded, it will be decoded to bytes.
3115 *
3116 * If the data is not Base64 encoded, it will be decoded by unescaping
3117 * percent-escaped characters and returning byte values of each unescaped
3118 * character. The bytes will not be, e.g., UTF-8 decoded.
3119 */
3120 List<int> contentAsBytes() {
3121 String text = _text;
3122 int start = _separatorIndices.last + 1;
3123 if (isBase64) {
3124 return BASE64.decoder.convert(text, start);
3125 }
3126
3127 // Not base64, do percent-decoding and return the remaining bytes.
3128 // Compute result size.
3129 const int percent = 0x25;
3130 int length = text.length - start;
3131 for (int i = start; i < text.length; i++) {
3132 var codeUnit = text.codeUnitAt(i);
3133 if (codeUnit == percent) {
3134 i += 2;
3135 length -= 2;
3136 }
3137 }
3138 // Fill result array.
3139 Uint8List result = new Uint8List(length);
3140 if (length == text.length) {
3141 result.setRange(0, length, text.codeUnits, start);
3142 return result;
3143 }
3144 int index = 0;
3145 for (int i = start; i < text.length; i++) {
3146 var codeUnit = text.codeUnitAt(i);
3147 if (codeUnit != percent) {
3148 result[index++] = codeUnit;
3149 } else {
3150 if (i + 2 < text.length) {
3151 var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1));
3152 var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2));
3153 if (digit1 >= 0 && digit2 >= 0) {
3154 int byte = digit1 * 16 + digit2;
3155 result[index++] = byte;
3156 i += 2;
3157 continue;
3158 }
3159 }
3160 throw new FormatException("Invalid percent escape", text, i);
3161 }
3162 }
3163 assert(index == result.length);
3164 return result;
3165 }
3166
3167 /**
3168 * Returns a string created from the content of the data URI.
3169 *
3170 * If the content is Base64 encoded, it will be decoded to bytes and then
3171 * decoded to a string using [encoding].
3172 * If encoding is omitted, the value of a `charset` parameter is used
3173 * if it is recongized by [Encoding.getByName], otherwise it defaults to
3174 * the [ASCII] encoding, which is the default encoding for data URIs
3175 * that do not specify an encoding.
3176 *
3177 * If the content is not Base64 encoded, it will first have percent-escapes
3178 * converted to bytes and then the character codes and byte values are
3179 * decoded using [encoding].
3180 */
3181 String contentAsString({Encoding encoding}) {
3182 if (encoding == null) {
3183 var charset = this.charset; // Returns "US-ASCII" if not present.
3184 encoding = Encoding.getByName(charset);
3185 if (encoding == null) {
3186 throw new UnsupportedError("Unknown charset: $charset");
3187 }
3188 }
3189 String text = _text;
3190 int start = _separatorIndices.last + 1;
3191 if (isBase64) {
3192 var converter = BASE64.decoder.fuse(encoding.decoder);
3193 return converter.convert(text.substring(start));
3194 }
3195 return Uri._uriDecode(text, start, text.length, encoding, false);
3196 }
3197
3198 /**
3199 * A map representing the parameters of the media type.
3200 *
3201 * A data URI may contain parameters between the MIME type and the
3202 * data. This converts these parameters to a map from parameter name
3203 * to parameter value.
3204 * The map only contains parameters that actually occur in the URI.
3205 * The `charset` parameter has a default value even if it doesn't occur
3206 * in the URI, which is reflected by the [charset] getter. This means that
3207 * [charset] may return a value even if `parameters["charset"]` is `null`.
3208 *
3209 * If the values contain non-ASCII values or percent escapes, they default
3210 * to being decoded as UTF-8.
3211 */
3212 Map<String, String> get parameters {
3213 var result = <String, String>{};
3214 for (int i = 3; i < _separatorIndices.length; i += 2) {
3215 var start = _separatorIndices[i - 2] + 1;
3216 var equals = _separatorIndices[i - 1];
3217 var end = _separatorIndices[i];
3218 String key = Uri._uriDecode(_text, start, equals, UTF8, false);
3219 String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false);
3220 result[key] = value;
3221 }
3222 return result;
3223 }
3224
3225 static UriData _parse(String text, int start, Uri sourceUri) {
3226 assert(start == 0 || start == 5);
3227 assert((start == 5) == text.startsWith("data:"));
3228
3229 /// Character codes.
3230 const int comma = 0x2c;
3231 const int slash = 0x2f;
3232 const int semicolon = 0x3b;
3233 const int equals = 0x3d;
3234 List<int> indices = [start - 1];
3235 int slashIndex = -1;
3236 var char;
3237 int i = start;
3238 for (; i < text.length; i++) {
3239 char = text.codeUnitAt(i);
3240 if (char == comma || char == semicolon) break;
3241 if (char == slash) {
3242 if (slashIndex < 0) {
3243 slashIndex = i;
3244 continue;
3245 }
3246 throw new FormatException("Invalid MIME type", text, i);
3247 }
3248 }
3249 if (slashIndex < 0 && i > start) {
3250 // An empty MIME type is allowed, but if non-empty it must contain
3251 // exactly one slash.
3252 throw new FormatException("Invalid MIME type", text, i);
3253 }
3254 while (char != comma) {
3255 // Parse parameters and/or "base64".
3256 indices.add(i);
3257 i++;
3258 int equalsIndex = -1;
3259 for (; i < text.length; i++) {
3260 char = text.codeUnitAt(i);
3261 if (char == equals) {
3262 if (equalsIndex < 0) equalsIndex = i;
3263 } else if (char == semicolon || char == comma) {
3264 break;
3265 }
3266 }
3267 if (equalsIndex >= 0) {
3268 indices.add(equalsIndex);
3269 } else {
3270 // Have to be final "base64".
3271 var lastSeparator = indices.last;
3272 if (char != comma ||
3273 i != lastSeparator + 7 /* "base64,".length */ ||
3274 !text.startsWith("base64", lastSeparator + 1)) {
3275 throw new FormatException("Expecting '='", text, i);
3276 }
3277 break;
3278 }
3279 }
3280 indices.add(i);
3281 return new UriData._(text, indices, sourceUri);
3282 }
3283
3284 /**
3285 * Like [Uri._uriEncode] but takes the input as bytes, not a string.
3286 *
3287 * Encodes into [buffer] instead of creating its own buffer.
3288 */
3289 static void _uriEncodeBytes(List<int> canonicalTable,
3290 List<int> bytes,
3291 StringSink buffer) {
3292 // Encode the string into bytes then generate an ASCII only string
3293 // by percent encoding selected bytes.
3294 int byteOr = 0;
3295 for (int i = 0; i < bytes.length; i++) {
3296 int byte = bytes[i];
3297 byteOr |= byte;
3298 if (byte < 128 &&
3299 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
3300 buffer.writeCharCode(byte);
3301 } else {
3302 buffer.writeCharCode(Uri._PERCENT);
3303 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
3304 buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
3305 }
3306 }
3307 if ((byteOr & ~0xFF) != 0) {
3308 for (int i = 0; i < bytes.length; i++) {
3309 var byte = bytes[i];
3310 if (byte < 0 || byte > 255) {
3311 throw new ArgumentError.value(byte, "non-byte value");
3312 }
3313 }
3314 }
3315 }
3316
3317 String toString() =>
3318 (_separatorIndices[0] == _noScheme) ? "data:$_text" : _text;
3319
3320 // Table of the `token` characters of RFC 2045 in a URI.
3321 //
3322 // A token is any US-ASCII character except SPACE, control characters and
3323 // `tspecial` characters. The `tspecial` category is:
3324 // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='.
3325 //
3326 // In a data URI, we also need to escape '%' and '#' characters.
3327 static const _tokenCharTable = const [
3328 // LSB MSB
3329 // | |
3330 0x0000, // 0x00 - 0x0f 00000000 00000000
3331 0x0000, // 0x10 - 0x1f 00000000 00000000
3332 // ! $ &' *+ -.
3333 0x6cd2, // 0x20 - 0x2f 01001011 00110110
3334 // 01234567 89
3335 0x03ff, // 0x30 - 0x3f 11111111 11000000
3336 // ABCDEFG HIJKLMNO
3337 0xfffe, // 0x40 - 0x4f 01111111 11111111
3338 // PQRSTUVW XYZ ^_
3339 0xc7ff, // 0x50 - 0x5f 11111111 11100011
3340 // `abcdefg hijklmno
3341 0xffff, // 0x60 - 0x6f 11111111 11111111
3342 // pqrstuvw xyz{|}~
3343 0x7fff]; // 0x70 - 0x7f 11111111 11111110
3344
3345 // All non-escape RFC-2396 uric characters.
3346 //
3347 // uric = reserved | unreserved | escaped
3348 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3349 // unreserved = alphanum | mark
3350 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3351 //
3352 // This is the same characters as in a URI query (which is URI pchar plus '?')
3353 static const _uricTable = Uri._queryCharTable;
3354 }
OLDNEW
« no previous file with comments | « pkg/dev_compiler/tool/input_sdk/lib/core/type.dart ('k') | pkg/dev_compiler/tool/input_sdk/lib/developer/developer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698