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

Side by Side Diff: test/generated_sdk/lib/core/uri.dart

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

Powered by Google App Engine
This is Rietveld 408576698