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

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

Issue 1381033002: Add data-URI support class to dart:core (next to Uri). (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Add more tests, refactor, rename to DataUriHelper. Need better name! Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sdk/lib/core/uri.dart ('k') | sdk/lib/uri/uri_sources.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.core; 5 /**
6 * URI related classes and functionality.
7 */
8 library dart.uri;
6 9
10 import "dart:convert" show UTF8, ASCII, LATIN1, BASE64, Encoding,
11 ChunkedConversionSink, ByteConversionSink,
12 StringConversionSink;
13 import "dart:typed_data" show Uint8List;
14 import "dart:collection" show UnmodifiableListView, UnmodifiableMapView;
7 /** 15 /**
8 * A parsed URI, such as a URL. 16 * A parsed URI, such as a URL.
9 * 17 *
10 * **See also:** 18 * **See also:**
11 * 19 *
12 * * [URIs][uris] in the [library tour][libtour] 20 * * [URIs][uris] in the [library tour][libtour]
13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986) 21 * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
14 * 22 *
15 * [uris]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html#c h03-uri 23 * [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 24 * [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.htm l
17 */ 25 */
18 class Uri { 26 class Uri {
27 // The host name of the URI.
28 // Set to `null` if there is no authority in a URI.
29 final String _host;
30 // The port. Set to null if there is no port. Normalized to null if
31 // the port is the default port for the scheme.
32 // Set to the value of the default port if an empty port was supplied.
33 int _port;
34 // The path. Always non-null.
35 String _path;
36
19 /** 37 /**
20 * The scheme component of the URI. 38 * Returns the scheme component.
21 * 39 *
22 * Returns the empty string if there is no scheme component. 40 * Returns the empty string if there is no scheme component.
23 * 41 *
24 * A URI scheme is case insensitive. 42 * A URI scheme is case insensitive.
25 * The returned scheme is canonicalized to lowercase letters. 43 * The returned scheme is canonicalized to lowercase letters.
26 */ 44 */
27 // We represent the missing scheme as an empty string. 45 // We represent the missing scheme as an empty string.
28 // A valid scheme cannot be empty. 46 // A valid scheme cannot be empty.
29 final String scheme; 47 final String scheme;
30 48
31 /** 49 /**
32 * The user-info part of the authority.
33 *
34 * Does not distinguish between an empty user-info and an absent one.
35 * The value is always non-null.
36 * Is considered absent if [_host] is `null`.
37 */
38 final String _userInfo;
39
40 /**
41 * The host name of the URI.
42 *
43 * Set to `null` if there is no authority in the URI.
44 * The host name is the only mandatory part of an authority, so we use
45 * it to mark whether an authority part was present or not.
46 */
47 final String _host;
48
49 /**
50 * The port number part of the authority.
51 *
52 * The port. Set to null if there is no port. Normalized to null if
53 * the port is the default port for the scheme.
54 */
55 int _port;
56
57 /**
58 * The path of the URI.
59 *
60 * Always non-null.
61 */
62 String _path;
63
64 // The query content, or null if there is no query.
65 final String _query;
66
67 // The fragment content, or null if there is no fragment.
68 final String _fragment;
69
70 /**
71 * Cache the computed return value of [pathSegements].
72 */
73 List<String> _pathSegments;
74
75 /**
76 * Cache the computed return value of [queryParameters].
77 */
78 Map<String, String> _queryParameters;
79
80 /// Internal non-verifying constructor. Only call with validated arguments.
81 Uri._internal(this.scheme,
82 this._userInfo,
83 this._host,
84 this._port,
85 this._path,
86 this._query,
87 this._fragment);
88
89 /**
90 * Creates a new URI from its components.
91 *
92 * Each component is set through a named argument. Any number of
93 * components can be provided. The [path] and [query] components can be set
94 * using either of two different named arguments.
95 *
96 * The scheme component is set through [scheme]. The scheme is
97 * normalized to all lowercase letters. If the scheme is omitted or empty,
98 * the URI will not have a scheme part.
99 *
100 * The user info part of the authority component is set through
101 * [userInfo]. It defaults to the empty string, which will be omitted
102 * from the string representation of the URI.
103 *
104 * The host part of the authority component is set through
105 * [host]. The host can either be a hostname, an IPv4 address or an
106 * IPv6 address, contained in '[' and ']'. If the host contains a
107 * ':' character, the '[' and ']' are added if not already provided.
108 * The host is normalized to all lowercase letters.
109 *
110 * The port part of the authority component is set through
111 * [port].
112 * If [port] is omitted or `null`, it implies the default port for
113 * the URI's scheme, and is equivalent to passing that port explicitly.
114 * The recognized schemes, and their default ports, are "http" (80) and
115 * "https" (443). All other schemes are considered as having zero as the
116 * default port.
117 *
118 * If any of `userInfo`, `host` or `port` are provided,
119 * the URI will have an autority according to [hasAuthority].
120 *
121 * The path component is set through either [path] or
122 * [pathSegments]. When [path] is used, it should be a valid URI path,
123 * but invalid characters, except the general delimiters ':/@[]?#',
124 * will be escaped if necessary.
125 * When [pathSegments] is used, each of the provided segments
126 * is first percent-encoded and then joined using the forward slash
127 * separator. The percent-encoding of the path segments encodes all
128 * characters except for the unreserved characters and the following
129 * list of characters: `!$&'()*+,;=:@`. If the other components
130 * calls for an absolute path a leading slash `/` is prepended if
131 * not already there.
132 *
133 * The query component is set through either [query] or
134 * [queryParameters]. When [query] is used the provided string should
135 * be a valid URI query, but invalid characters other than general delimiters,
136 * will be escaped if necessary.
137 * When [queryParameters] is used the query is built from the
138 * provided map. Each key and value in the map is percent-encoded
139 * and joined using equal and ampersand characters. The
140 * percent-encoding of the keys and values encodes all characters
141 * except for the unreserved characters.
142 * If `query` is the empty string, it is equivalent to omitting it.
143 * To have an actual empty query part,
144 * use an empty list for `queryParameters`.
145 * If both `query` and `queryParameters` are omitted or `null`, the
146 * URI will have no query part.
147 *
148 * The fragment component is set through [fragment].
149 * It should be a valid URI fragment, but invalid characters other than
150 * general delimiters, will be escaped if necessary.
151 * If `fragment` is omitted or `null`, the URI will have no fragment part.
152 */
153 factory Uri({String scheme : "",
154 String userInfo : "",
155 String host,
156 int port,
157 String path,
158 Iterable<String> pathSegments,
159 String query,
160 Map<String, String> queryParameters,
161 String fragment}) {
162 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
163 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
164 host = _makeHost(host, 0, _stringOrNullLength(host), false);
165 // Special case this constructor for backwards compatibility.
166 if (query == "") query = null;
167 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
168 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
169 port = _makePort(port, scheme);
170 bool isFile = (scheme == "file");
171 if (host == null &&
172 (userInfo.isNotEmpty || port != null || isFile)) {
173 host = "";
174 }
175 bool hasAuthority = (host != null);
176 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
177 scheme, hasAuthority);
178 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
179 path = _normalizeRelativePath(path);
180 } else {
181 path = _removeDotSegments(path);
182 }
183 return new Uri._internal(scheme, userInfo, host, port,
184 path, query, fragment);
185 }
186
187 /**
188 * Creates a new `http` URI from authority, path and query.
189 *
190 * Examples:
191 *
192 * ```
193 * // http://example.org/path?q=dart.
194 * new Uri.http("google.com", "/search", { "q" : "dart" });
195 *
196 * // http://user:pass@localhost:8080
197 * new Uri.http("user:pass@localhost:8080", "");
198 *
199 * // http://example.org/a%20b
200 * new Uri.http("example.org", "a b");
201 *
202 * // http://example.org/a%252F
203 * new Uri.http("example.org", "/a%2F");
204 * ```
205 *
206 * The `scheme` is always set to `http`.
207 *
208 * The `userInfo`, `host` and `port` components are set from the
209 * [authority] argument. If `authority` is `null` or empty,
210 * the created `Uri` will have no authority, and will not be directly usable
211 * as an HTTP URL, which must have a non-empty host.
212 *
213 * The `path` component is set from the [unencodedPath]
214 * argument. The path passed must not be encoded as this constructor
215 * encodes the path.
216 *
217 * The `query` component is set from the optional [queryParameters]
218 * argument.
219 */
220 factory Uri.http(String authority,
221 String unencodedPath,
222 [Map<String, String> queryParameters]) {
223 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
224 }
225
226 /**
227 * Creates a new `https` URI from authority, path and query.
228 *
229 * This constructor is the same as [Uri.http] except for the scheme
230 * which is set to `https`.
231 */
232 factory Uri.https(String authority,
233 String unencodedPath,
234 [Map<String, String> queryParameters]) {
235 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
236 }
237
238 /**
239 * Returns the authority component. 50 * Returns the authority component.
240 * 51 *
241 * The authority is formatted from the [userInfo], [host] and [port] 52 * The authority is formatted from the [userInfo], [host] and [port]
242 * parts. 53 * parts.
243 * 54 *
244 * Returns the empty string if there is no authority component. 55 * Returns the empty string if there is no authority component.
245 */ 56 */
246 String get authority { 57 String get authority {
247 if (!hasAuthority) return ""; 58 if (!hasAuthority) return "";
248 var sb = new StringBuffer(); 59 var sb = new StringBuffer();
249 _writeAuthority(sb); 60 _writeAuthority(sb);
250 return sb.toString(); 61 return sb.toString();
251 } 62 }
252 63
253 /** 64 /**
65 * The user-info part of the authority.
66 *
67 * Does not distinguish between an empty user-info and an absent one.
68 * The value is always non-null.
69 */
70 final String _userInfo;
71
72 /**
254 * Returns the user info part of the authority component. 73 * Returns the user info part of the authority component.
255 * 74 *
256 * Returns the empty string if there is no user info in the 75 * Returns the empty string if there is no user info in the
257 * authority component. 76 * authority component.
258 */ 77 */
259 String get userInfo => _userInfo; 78 String get userInfo => _userInfo;
260 79
261 /** 80 /**
262 * Returns the host part of the authority component. 81 * Returns the host part of the authority component.
263 * 82 *
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
300 /** 119 /**
301 * Returns the path component. 120 * Returns the path component.
302 * 121 *
303 * The returned path is encoded. To get direct access to the decoded 122 * The returned path is encoded. To get direct access to the decoded
304 * path use [pathSegments]. 123 * path use [pathSegments].
305 * 124 *
306 * Returns the empty string if there is no path component. 125 * Returns the empty string if there is no path component.
307 */ 126 */
308 String get path => _path; 127 String get path => _path;
309 128
129 // The query content, or null if there is no query.
130 final String _query;
131
310 /** 132 /**
311 * Returns the query component. The returned query is encoded. To get 133 * Returns the query component. The returned query is encoded. To get
312 * direct access to the decoded query use [queryParameters]. 134 * direct access to the decoded query use [queryParameters].
313 * 135 *
314 * Returns the empty string if there is no query component. 136 * Returns the empty string if there is no query component.
315 */ 137 */
316 String get query => (_query == null) ? "" : _query; 138 String get query => (_query == null) ? "" : _query;
317 139
140 // The fragment content, or null if there is no fragment.
141 final String _fragment;
142
318 /** 143 /**
319 * Returns the fragment identifier component. 144 * Returns the fragment identifier component.
320 * 145 *
321 * Returns the empty string if there is no fragment identifier 146 * Returns the empty string if there is no fragment identifier
322 * component. 147 * component.
323 */ 148 */
324 String get fragment => (_fragment == null) ? "" : _fragment; 149 String get fragment => (_fragment == null) ? "" : _fragment;
325 150
326 /** 151 /**
152 * Cache the computed return value of [pathSegements].
153 */
154 List<String> _pathSegments;
155
156 /**
157 * Cache the computed return value of [queryParameters].
158 */
159 Map<String, String> _queryParameters;
160
161 /**
327 * Creates a new `Uri` object by parsing a URI string. 162 * Creates a new `Uri` object by parsing a URI string.
328 * 163 *
329 * If [start] and [end] are provided, only the substring from `start` 164 * If [start] and [end] are provided, only the substring from `start`
330 * to `end` is parsed as a URI. 165 * to `end` is parsed as a URI.
331 * 166 *
332 * If the string is not valid as a URI or URI reference, 167 * If the string is not valid as a URI or URI reference,
333 * a [FormatException] is thrown. 168 * a [FormatException] is thrown.
334 */ 169 */
335 static Uri parse(String uri, [int start = 0, int end]) { 170 static Uri parse(String uri, [int start = 0, int end]) {
336 // This parsing will not validate percent-encoding, IPv6, etc. 171 // This parsing will not validate percent-encoding, IPv6, etc.
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 // segment = *pchar 214 // segment = *pchar
380 // segment-nz = 1*pchar 215 // segment-nz = 1*pchar
381 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) 216 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
382 // ; non-zero-length segment without any colon ":" 217 // ; non-zero-length segment without any colon ":"
383 // 218 //
384 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 219 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
385 // 220 //
386 // query = *( pchar / "/" / "?" ) 221 // query = *( pchar / "/" / "?" )
387 // 222 //
388 // fragment = *( pchar / "/" / "?" ) 223 // fragment = *( pchar / "/" / "?" )
224 bool isRegName(int ch) {
Ivan Posva 2015/11/03 17:04:20 Why is this not a static private function? Where i
Lasse Reichstein Nielsen 2015/11/03 18:02:52 I don't think it is being used at all. That's actu
225 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
226 }
389 const int EOI = -1; 227 const int EOI = -1;
390 228
391 String scheme = ""; 229 String scheme = "";
392 String userinfo = ""; 230 String userinfo = "";
393 String host = null; 231 String host = null;
394 int port = null; 232 int port = null;
395 String path = null; 233 String path = null;
396 String query = null; 234 String query = null;
397 String fragment = null; 235 String fragment = null;
398 if (end == null) end = uri.length; 236 if (end == null) end = uri.length;
(...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after
578 path, 416 path,
579 query, 417 query,
580 fragment); 418 fragment);
581 } 419 }
582 420
583 // Report a parse failure. 421 // Report a parse failure.
584 static void _fail(String uri, int index, String message) { 422 static void _fail(String uri, int index, String message) {
585 throw new FormatException(message, uri, index); 423 throw new FormatException(message, uri, index);
586 } 424 }
587 425
426 /// Internal non-verifying constructor. Only call with validated arguments.
427 Uri._internal(this.scheme,
428 this._userInfo,
429 this._host,
430 this._port,
431 this._path,
432 this._query,
433 this._fragment);
434
435 /**
436 * Creates a new URI from its components.
437 *
438 * Each component is set through a named argument. Any number of
439 * components can be provided. The [path] and [query] components can be set
440 * using either of two different named arguments.
441 *
442 * The scheme component is set through [scheme]. The scheme is
443 * normalized to all lowercase letters. If the scheme is omitted or empty,
444 * the URI will not have a scheme part.
445 *
446 * The user info part of the authority component is set through
447 * [userInfo]. It defaults to the empty string, which will be omitted
448 * from the string representation of the URI.
449 *
450 * The host part of the authority component is set through
451 * [host]. The host can either be a hostname, an IPv4 address or an
452 * IPv6 address, contained in '[' and ']'. If the host contains a
453 * ':' character, the '[' and ']' are added if not already provided.
454 * The host is normalized to all lowercase letters.
455 *
456 * The port part of the authority component is set through
457 * [port].
458 * If [port] is omitted or `null`, it implies the default port for
459 * the URI's scheme, and is equivalent to passing that port explicitly.
460 * The recognized schemes, and their default ports, are "http" (80) and
461 * "https" (443). All other schemes are considered as having zero as the
462 * default port.
463 *
464 * If any of `userInfo`, `host` or `port` are provided,
465 * the URI will have an autority according to [hasAuthority].
466 *
467 * The path component is set through either [path] or
468 * [pathSegments]. When [path] is used, it should be a valid URI path,
469 * but invalid characters, except the general delimiters ':/@[]?#',
470 * will be escaped if necessary.
471 * When [pathSegments] is used, each of the provided segments
472 * is first percent-encoded and then joined using the forward slash
473 * separator. The percent-encoding of the path segments encodes all
474 * characters except for the unreserved characters and the following
475 * list of characters: `!$&'()*+,;=:@`. If the other components
476 * calls for an absolute path a leading slash `/` is prepended if
477 * not already there.
478 *
479 * The query component is set through either [query] or
480 * [queryParameters]. When [query] is used the provided string should
481 * be a valid URI query, but invalid characters other than general delimiters,
482 * will be escaped if necessary.
483 * When [queryParameters] is used the query is built from the
484 * provided map. Each key and value in the map is percent-encoded
485 * and joined using equal and ampersand characters. The
486 * percent-encoding of the keys and values encodes all characters
487 * except for the unreserved characters.
488 * If `query` is the empty string, it is equivalent to omitting it.
489 * To have an actual empty query part,
490 * use an empty list for `queryParameters`.
491 * If both `query` and `queryParameters` are omitted or `null`, the
492 * URI will have no query part.
493 *
494 * The fragment component is set through [fragment].
495 * It should be a valid URI fragment, but invalid characters other than
496 * general delimiters, will be escaped if necessary.
497 * If `fragment` is omitted or `null`, the URI will have no fragment part.
498 */
499 factory Uri({String scheme : "",
500 String userInfo : "",
501 String host,
502 int port,
503 String path,
504 Iterable<String> pathSegments,
505 String query,
506 Map<String, String> queryParameters,
507 String fragment}) {
508 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
509 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
510 host = _makeHost(host, 0, _stringOrNullLength(host), false);
511 // Special case this constructor for backwards compatibility.
512 if (query == "") query = null;
513 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
514 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
515 port = _makePort(port, scheme);
516 bool isFile = (scheme == "file");
517 if (host == null &&
518 (userInfo.isNotEmpty || port != null || isFile)) {
519 host = "";
520 }
521 bool hasAuthority = (host != null);
522 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
523 scheme, hasAuthority);
524 if (scheme.isEmpty && host == null && !path.startsWith('/')) {
525 path = _normalizeRelativePath(path);
526 } else {
527 path = _removeDotSegments(path);
528 }
529 return new Uri._internal(scheme, userInfo, host, port,
530 path, query, fragment);
531 }
532
533 /**
534 * Creates a new `http` URI from authority, path and query.
535 *
536 * Examples:
537 *
538 * ```
539 * // http://example.org/path?q=dart.
540 * new Uri.http("google.com", "/search", { "q" : "dart" });
541 *
542 * // http://user:pass@localhost:8080
543 * new Uri.http("user:pass@localhost:8080", "");
544 *
545 * // http://example.org/a%20b
546 * new Uri.http("example.org", "a b");
547 *
548 * // http://example.org/a%252F
549 * new Uri.http("example.org", "/a%2F");
550 * ```
551 *
552 * The `scheme` is always set to `http`.
553 *
554 * The `userInfo`, `host` and `port` components are set from the
555 * [authority] argument. If `authority` is `null` or empty,
556 * the created `Uri` will have no authority, and will not be directly usable
557 * as an HTTP URL, which must have a non-empty host.
558 *
559 * The `path` component is set from the [unencodedPath]
560 * argument. The path passed must not be encoded as this constructor
561 * encodes the path.
562 *
563 * The `query` component is set from the optional [queryParameters]
564 * argument.
565 */
566 factory Uri.http(String authority,
567 String unencodedPath,
568 [Map<String, String> queryParameters]) {
569 return _makeHttpUri("http", authority, unencodedPath, queryParameters);
570 }
571
572 /**
573 * Creates a new `https` URI from authority, path and query.
574 *
575 * This constructor is the same as [Uri.http] except for the scheme
576 * which is set to `https`.
577 */
578 factory Uri.https(String authority,
579 String unencodedPath,
580 [Map<String, String> queryParameters]) {
581 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
582 }
583
588 static Uri _makeHttpUri(String scheme, 584 static Uri _makeHttpUri(String scheme,
589 String authority, 585 String authority,
590 String unencodedPath, 586 String unencodedPath,
591 Map<String, String> queryParameters) { 587 Map<String, String> queryParameters) {
592 var userInfo = ""; 588 var userInfo = "";
593 var host = null; 589 var host = null;
594 var port = null; 590 var port = null;
595 591
596 if (authority != null && authority.isNotEmpty) { 592 if (authority != null && authority.isNotEmpty) {
597 var hostStart = 0; 593 var hostStart = 0;
(...skipping 345 matching lines...) Expand 10 before | Expand all | Expand 10 after
943 if (scheme != null) { 939 if (scheme != null) {
944 scheme = _makeScheme(scheme, 0, scheme.length); 940 scheme = _makeScheme(scheme, 0, scheme.length);
945 schemeChanged = true; 941 schemeChanged = true;
946 } else { 942 } else {
947 scheme = this.scheme; 943 scheme = this.scheme;
948 } 944 }
949 bool isFile = (scheme == "file"); 945 bool isFile = (scheme == "file");
950 if (userInfo != null) { 946 if (userInfo != null) {
951 userInfo = _makeUserInfo(userInfo, 0, userInfo.length); 947 userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
952 } else { 948 } else {
953 userInfo = this._userInfo; 949 userInfo = this.userInfo;
954 } 950 }
955 if (port != null) { 951 if (port != null) {
956 port = _makePort(port, scheme); 952 port = _makePort(port, scheme);
957 } else { 953 } else {
958 port = this._port; 954 port = this._port;
959 if (schemeChanged) { 955 if (schemeChanged) {
960 // The default port might have changed. 956 // The default port might have changed.
961 port = _makePort(port, scheme); 957 port = _makePort(port, scheme);
962 } 958 }
963 } 959 }
964 if (host != null) { 960 if (host != null) {
965 host = _makeHost(host, 0, host.length, false); 961 host = _makeHost(host, 0, host.length, false);
966 } else if (this.hasAuthority) { 962 } else if (this.hasAuthority) {
967 host = this._host; 963 host = this.host;
968 } else if (userInfo.isNotEmpty || port != null || isFile) { 964 } else if (userInfo.isNotEmpty || port != null || isFile) {
969 host = ""; 965 host = "";
970 } 966 }
971 967
972 bool hasAuthority = host != null; 968 bool hasAuthority = host != null;
973 if (path != null || pathSegments != null) { 969 if (path != null || pathSegments != null) {
974 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, 970 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
975 scheme, hasAuthority); 971 scheme, hasAuthority);
976 } else { 972 } else {
977 path = this._path; 973 path = this.path;
978 if ((isFile || (hasAuthority && !path.isEmpty)) && 974 if ((isFile || (hasAuthority && !path.isEmpty)) &&
979 !path.startsWith('/')) { 975 !path.startsWith('/')) {
980 path = "/" + path; 976 path = "/" + path;
981 } 977 }
982 } 978 }
983 979
984 if (query != null || queryParameters != null) { 980 if (query != null || queryParameters != null) {
985 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); 981 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
986 } else { 982 } else if (this.hasQuery) {
987 query = this._query; 983 query = this.query;
988 } 984 }
989 985
990 if (fragment != null) { 986 if (fragment != null) {
991 fragment = _makeFragment(fragment, 0, fragment.length); 987 fragment = _makeFragment(fragment, 0, fragment.length);
992 } else { 988 } else if (this.hasFragment) {
993 fragment = this._fragment; 989 fragment = this.fragment;
994 } 990 }
995 991
996 return new Uri._internal( 992 return new Uri._internal(
997 scheme, userInfo, host, port, path, query, fragment); 993 scheme, userInfo, host, port, path, query, fragment);
998 } 994 }
999 995
1000 /** 996 /**
1001 * Returns a `Uri` that differs from this only in not having a fragment. 997 * Returns a `Uri` that differs from this only in not having a fragment.
1002 * 998 *
1003 * If this `Uri` does not have a fragment, it is itself returned. 999 * If this `Uri` does not have a fragment, it is itself returned.
1004 */ 1000 */
1005 Uri removeFragment() { 1001 Uri removeFragment() {
1006 if (!this.hasFragment) return this; 1002 if (!this.hasFragment) return this;
1007 return new Uri._internal(scheme, _userInfo, _host, _port, 1003 return new Uri._internal(scheme, userInfo, host, port, path, query, null);
1008 _path, _query, null);
1009 } 1004 }
1010 1005
1011 /** 1006 /**
1012 * Returns the URI path split into its segments. Each of the 1007 * Returns the URI path split into its segments. Each of the
1013 * segments in the returned list have been decoded. If the path is 1008 * segments in the returned list have been decoded. If the path is
1014 * empty the empty list will be returned. A leading slash `/` does 1009 * empty the empty list will be returned. A leading slash `/` does
1015 * not affect the segments returned. 1010 * not affect the segments returned.
1016 * 1011 *
1017 * The returned list is unmodifiable and will throw [UnsupportedError] on any 1012 * The returned list is unmodifiable and will throw [UnsupportedError] on any
1018 * calls that would mutate it. 1013 * calls that would mutate it.
(...skipping 1234 matching lines...) Expand 10 before | Expand all | Expand 10 after
2253 2248
2254 /** 2249 /**
2255 * This is the internal implementation of JavaScript's encodeURI function. 2250 * This is the internal implementation of JavaScript's encodeURI function.
2256 * It encodes all characters in the string [text] except for those 2251 * It encodes all characters in the string [text] except for those
2257 * that appear in [canonicalTable], and returns the escaped string. 2252 * that appear in [canonicalTable], and returns the escaped string.
2258 */ 2253 */
2259 static String _uriEncode(List<int> canonicalTable, 2254 static String _uriEncode(List<int> canonicalTable,
2260 String text, 2255 String text,
2261 {Encoding encoding: UTF8, 2256 {Encoding encoding: UTF8,
2262 bool spaceToPlus: false}) { 2257 bool spaceToPlus: false}) {
2263 byteToHex(byte, buffer) {
2264 const String hex = '0123456789ABCDEF';
2265 buffer.writeCharCode(hex.codeUnitAt(byte >> 4));
2266 buffer.writeCharCode(hex.codeUnitAt(byte & 0x0f));
2267 }
2268
2269 // Encode the string into bytes then generate an ASCII only string 2258 // Encode the string into bytes then generate an ASCII only string
2270 // by percent encoding selected bytes. 2259 // by percent encoding selected bytes.
2260 List<int> bytes = encoding.encode(text);
2271 StringBuffer result = new StringBuffer(); 2261 StringBuffer result = new StringBuffer();
2272 var bytes = encoding.encode(text); 2262 _uriEncodeBytes(canonicalTable, bytes, result, spaceToPlus);
2273 for (int i = 0; i < bytes.length; i++) {
2274 int byte = bytes[i];
2275 if (byte < 128 &&
2276 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
2277 result.writeCharCode(byte);
2278 } else if (spaceToPlus && byte == _SPACE) {
2279 result.writeCharCode(_PLUS);
2280 } else {
2281 result.writeCharCode(_PERCENT);
2282 byteToHex(byte, result);
2283 }
2284 }
2285 return result.toString(); 2263 return result.toString();
2286 } 2264 }
2287 2265
2288 /** 2266 /**
2267 * Like [_uriEncode] but takes the input as bytes, not a string.
2268 *
2269 * Encodes into [buffer] instead of creating its own buffer.
2270 */
2271 static void _uriEncodeBytes(List<int> canonicalTable,
2272 List<int> bytes,
2273 StringSink buffer,
2274 bool spaceToPlus) {
2275 // Encode the string into bytes then generate an ASCII only string
2276 // by percent encoding selected bytes.
2277 int byteOr = 0;
2278 for (int i = 0; i < bytes.length; i++) {
2279 int byte = bytes[i];
2280 byteOr |= byte;
2281 if (byte < 128 &&
2282 ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
2283 buffer.writeCharCode(byte);
2284 } else if (spaceToPlus && byte == _SPACE) {
2285 result.writeCharCode(_PLUS);
2286 } else {
2287 buffer.writeCharCode(_PERCENT);
2288 const String hex = '0123456789ABCDEF';
2289 buffer.writeCharCode(hex.codeUnitAt(byte >> 4));
2290 buffer.writeCharCode(hex.codeUnitAt(byte & 0x0f));
2291 }
2292 }
2293 if ((byteOr & ~0xFF) != 0) {
2294 for (int i = 0; i < bytes.length; i++) {
2295 var byte = bytes[i];
2296 if (byte < 0 || byte > 255) {
2297 throw new ArgumentError.value(byte, "non-byte value");
2298 }
2299 }
2300 }
2301 }
2302
2303 /**
2289 * Convert a byte (2 character hex sequence) in string [s] starting 2304 * Convert a byte (2 character hex sequence) in string [s] starting
2290 * at position [pos] to its ordinal value 2305 * at position [pos] to its ordinal value
2291 */ 2306 */
2292 static int _hexCharPairToByte(String s, int pos) { 2307 static int _hexCharPairToByte(String s, int pos) {
2293 int byte = 0; 2308 int byte = 0;
2294 for (int i = 0; i < 2; i++) { 2309 for (int i = 0; i < 2; i++) {
2295 var charCode = s.codeUnitAt(pos + i); 2310 var charCode = s.codeUnitAt(pos + i);
2296 if (0x30 <= charCode && charCode <= 0x39) { 2311 int digit = charCode ^ 0x30;
2297 byte = byte * 16 + charCode - 0x30; 2312 if (digit <= 9) {
2313 byte = byte * 16 + digit;
2298 } else { 2314 } else {
2299 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66). 2315 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66).
2300 charCode |= 0x20; 2316 charCode |= 0x20;
2301 if (0x61 <= charCode && charCode <= 0x66) { 2317 if (0x61 <= charCode && charCode <= 0x66) {
2302 byte = byte * 16 + charCode - 0x57; 2318 byte = byte * 16 + charCode - 0x57;
2303 } else { 2319 } else {
2304 throw new ArgumentError("Invalid URL encoding"); 2320 throw new ArgumentError("Invalid URL encoding");
2305 } 2321 }
2306 } 2322 }
2307 } 2323 }
2308 return byte; 2324 return byte;
2309 } 2325 }
2310 2326
2311 /** 2327 /**
2312 * Uri-decode a percent-encoded string. 2328 * Uri-decode a percent-encoded string.
2313 * 2329 *
2314 * It unescapes the string [text] and returns the unescaped string. 2330 * It unescapes the string [text] and returns the unescaped string.
2315 * 2331 *
2316 * This function is similar to the JavaScript-function `decodeURI`. 2332 * This function is similar to the JavaScript-function `decodeURI`.
2317 * 2333 *
2318 * If [plusToSpace] is `true`, plus characters will be converted to spaces. 2334 * If [plusToSpace] is `true`, plus characters will be converted to spaces.
2319 * 2335 *
2320 * The decoder will create a byte-list of the percent-encoded parts, and then 2336 * The decoder will create a byte-list of the percent-encoded parts, and then
2321 * decode the byte-list using [encoding]. The default encodingis UTF-8. 2337 * decode the byte-list using [encoding]. The default encodingis UTF-8.
2322 */ 2338 */
2323 static String _uriDecode(String text, 2339 static String _uriDecode(String text,
2324 {bool plusToSpace: false, 2340 {bool plusToSpace: false,
2325 Encoding encoding: UTF8}) { 2341 Encoding encoding: UTF8,
2342 int start: 0,
2343 int end}) {
2344 if (end == null) end = text.length;
2326 // First check whether there is any characters which need special handling. 2345 // First check whether there is any characters which need special handling.
2327 bool simple = true; 2346 bool simple = true;
2328 for (int i = 0; i < text.length && simple; i++) { 2347 for (int i = start; i < end && simple; i++) {
2329 var codeUnit = text.codeUnitAt(i); 2348 var codeUnit = text.codeUnitAt(i);
2330 simple = codeUnit != _PERCENT && codeUnit != _PLUS; 2349 simple = codeUnit != _PERCENT && codeUnit != _PLUS;
2331 } 2350 }
2332 List<int> bytes; 2351 List<int> bytes;
2333 if (simple) { 2352 if (simple) {
2334 if (encoding == UTF8 || encoding == LATIN1) { 2353 if (encoding == UTF8 || encoding == LATIN1) {
2335 return text; 2354 return text.substring(start, end);
2355 } else if (start == 0 && end == text.length) {
2356 bytes = text.codeUnits;
2336 } else { 2357 } else {
2337 bytes = text.codeUnits; 2358 var decoder = encoding.decoder;
2359 var result;
2360 var conversionSink = decoder.startChunkedConversion(
2361 new ChunkedConversionSink.withCallback((list) {
2362 result = list.join();
2363 }));
2364 if (conversionSink is ByteConversionSink) {
2365 conversionSink.addSlice(text.codeUnits, start, end, true);
2366 } else {
2367 conversionSink.add(text.codeUnits.sublist(start, end));
2368 conversionSink.close();
2369 }
2370 return result;
2338 } 2371 }
2339 } else { 2372 } else {
2340 bytes = new List(); 2373 bytes = new List();
2341 for (int i = 0; i < text.length; i++) { 2374 for (int i = start; i < end; i++) {
2342 var codeUnit = text.codeUnitAt(i); 2375 var codeUnit = text.codeUnitAt(i);
2343 if (codeUnit > 127) { 2376 if (codeUnit > 127) {
2344 throw new ArgumentError("Illegal percent encoding in URI"); 2377 throw new ArgumentError("Illegal percent encoding in URI");
2345 } 2378 }
2346 if (codeUnit == _PERCENT) { 2379 if (codeUnit == _PERCENT) {
2347 if (i + 3 > text.length) { 2380 if (i + 3 > text.length) {
2348 throw new ArgumentError('Truncated URI'); 2381 throw new ArgumentError('Truncated URI');
2349 } 2382 }
2350 bytes.add(_hexCharPairToByte(text, i + 1)); 2383 bytes.add(_hexCharPairToByte(text, i + 1));
2351 i += 2; 2384 i += 2;
(...skipping 251 matching lines...) Expand 10 before | Expand all | Expand 10 after
2603 // 0123456789:; = ? 2636 // 0123456789:; = ?
2604 0xafff, // 0x30 - 0x3f 1111111111110101 2637 0xafff, // 0x30 - 0x3f 1111111111110101
2605 // @ABCDEFGHIJKLMNO 2638 // @ABCDEFGHIJKLMNO
2606 0xffff, // 0x40 - 0x4f 1111111111111111 2639 0xffff, // 0x40 - 0x4f 1111111111111111
2607 // PQRSTUVWXYZ _ 2640 // PQRSTUVWXYZ _
2608 0x87ff, // 0x50 - 0x5f 1111111111100001 2641 0x87ff, // 0x50 - 0x5f 1111111111100001
2609 // abcdefghijklmno 2642 // abcdefghijklmno
2610 0xfffe, // 0x60 - 0x6f 0111111111111111 2643 0xfffe, // 0x60 - 0x6f 0111111111111111
2611 // pqrstuvwxyz ~ 2644 // pqrstuvwxyz ~
2612 0x47ff]; // 0x70 - 0x7f 1111111111100010 2645 0x47ff]; // 0x70 - 0x7f 1111111111100010
2646
2647 }
2648
2649 // --------------------------------------------------------------------
2650 // Data URI
2651 // --------------------------------------------------------------------
2652
2653 /**
2654 * A way to create and access the structure of a `data:` URI.
2655 *
2656 * Data URIs are non-hierarchial URIs that contain can contain any data.
2657 * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397).
2658 *
2659 * This class allows parsing the URI text and extracting individual parts of the
2660 * URI, as well as building the URI text from structured parts.
2661 */
2662 class DataUriHelper {
2663 static const int _noScheme = -1;
2664 /**
2665 * Contains the text content of a `data:` URI, with or without a
2666 * leading `data:`.
2667 *
2668 * If [_separatorIndices] starts with `4` (the index of the `:`), then
2669 * there is a leading `data:`, otherwise [_separatorIndices] starts with
2670 * `-1`.
2671 */
2672 final String _text;
2673
2674 /**
2675 * List of the separators (';', '=' and ',') in the text.
2676 *
2677 * Starts with the index of the index of the `:` in `data:` of the mimeType.
2678 * That is always either -1 or 4, depending on whether `_text` includes the
2679 * `data:` scheme or not.
2680 *
2681 * The first speparator ends the mime type. We don't bother with finding
2682 * the '/' inside the mime type.
2683 *
2684 * Each two separators after that marks a parameter key and value.
2685 *
2686 * If there is a single separator left, it ends the "base64" marker.
2687 *
2688 * So the following separators are found for a text:
2689 *
2690 * data:text/plain;foo=bar;base64,ARGLEBARGLE=
2691 * ^ ^ ^ ^ ^
2692 *
2693 */
2694 List<int> _separatorIndices;
2695
2696 DataUriHelper._(this._text, this._separatorIndices);
2697
2698 /** The entire content of the data URI, including the leading `data:`. */
2699 String get text => _separatorIndices[0] == _noScheme ? "data:$_text" : _text;
2700
2701 /**
2702 * Creates a `data:` URI containing the [content] string.
2703 *
2704 * Converts the content to a bytes using [encoiding] or the charset specified
2705 * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
2706 * then encodes the bytes into the resulting data URI.
2707 *
2708 * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
2709 * bytes is replaced by a percent encoding). If [base64] is true, the bytes
2710 * are instead encoded using [BASE64].
2711 *
2712 * If [encoding] is not provided and [parameters] has a `charset` entry,
2713 * that name is looked up using [Encoding.getByName],
2714 * and if the lookup returns an encoding, that encoding is used to convert
2715 * [content] to bytes.
2716 * If providing both an [encoding] and a charset [parameter], they should
2717 * agree, otherwise decoding won't be able to use the charset parameter
2718 * to determind the encoding.
2719 *
2720 * If [mimeType] and/or [parameters] are supplied, they are added to the
2721 * created URI. If any of these contain characters that are not allowed
2722 * in the data URI, the character is percent-escaped. If the character is
2723 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
2724 * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
2725 * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
2726 *
2727 * To read the content back, use [contentAsString].
2728 */
2729 factory DataUriHelper.fromString(String content,
2730 {String mimeType,
2731 Encoding encoding,
2732 Map<String, String> parameters,
2733 bool base64: false}) {
2734 StringBuffer buffer = new StringBuffer();
2735 List indices = [_noScheme];
2736 String charsetName;
2737 String encodingName;
2738 if (parameters != null) charsetName = parameters["charset"];
2739 if (encoding == null) {
2740 if (charsetName != null) {
2741 encoding = Encoding.getByName(charsetName);
2742 }
2743 } else if (charsetName == null) {
2744 // Non-null only if parameters does not contain "charset".
2745 encodingName = encoding.name;
2746 }
2747 encoding ??= ASCII;
2748 _writeUri(mimeType, encodingName, parameters, buffer, indices);
2749 indices.add(buffer.length);
2750 if (base64) {
2751 buffer.write(';base64,');
2752 indices.add(buffer.length - 1);
2753 buffer.write(encoding.fuse(BASE64).encode(content));
2754 } else {
2755 buffer.write(',');
2756 Uri._uriEncodeBytes(_uricTable, encoding.encode(content), buffer, false);
2757 }
2758 return new DataUriHelper._(buffer.toString(), indices);
2759 }
2760
2761 /**
2762 * Creates a `data:` URI string containing an encoding of [bytes].
2763 *
2764 * Defaults to Base64 encoding the bytes, but if [percentEncoded]
2765 * is `true`, the bytes will instead be percent encoded (any non-ASCII
2766 * or non-valid byte is replaced by a percent encoding).
2767 *
2768 * To read the bytes back, use [contentAsBytes].
2769 *
2770 * It defaults to having the mime-type `application/octet-stream`.
2771 * The [mimeType] and [parameters] are added to the created URI.
2772 * If any of these contain characters that are not allowed
2773 * in the data URI, the character is percent-escaped. If the character is
2774 * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
2775 * encoded.
2776 */
2777 factory DataUriHelper.fromBytes(List<int> bytes,
2778 {mimeType: "application/octet-stream",
2779 Map<String, String> parameters,
2780 percentEncoded: false}) {
2781 StringBuffer buffer = new StringBuffer();
2782 List indices = [_noScheme];
2783 _writeUri(mimeType, null, parameters, buffer, indices);
2784 indices.add(buffer.length);
2785 if (percentEncoded) {
2786 buffer.write(',');
2787 Uri._uriEncodeBytes(_uricTable, bytes, buffer, false);
2788 } else {
2789 buffer.write(';base64,');
2790 indices.add(buffer.length - 1);
2791 BASE64.encoder.startChunkedConversion(
2792 new StringConversionSink.fromStringSink(buffer))
2793 .addSlice(bytes, 0, bytes.length, true);
2794 }
2795
2796 return new DataUriHelper._(buffer.toString(), indices);
2797 }
2798
2799 /**
2800 * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme].
2801 *
2802 * The [uri] must have scheme `data` and no authority or fragment,
2803 * and the path (concatenated with the query, if there is one) must be valid
2804 * as data URI content with the same rules as [parse].
2805 */
2806 factory DataUriHelper.fromUri(Uri uri) {
2807 if (uri.scheme != "data") {
2808 throw new ArgumentError.value(uri, "uri",
2809 "Scheme must be 'data'");
2810 }
2811 if (uri.hasAuthority) {
2812 throw new ArgumentError.value(uri, "uri",
2813 "Data uri must not have authority");
2814 }
2815 if (uri.hasFragment) {
2816 throw new ArgumentError.value(uri, "uri",
2817 "Data uri must not have a fragment part");
2818 }
2819 if (!uri.hasQuery) {
2820 return _parse(uri.path, 0);
2821 }
2822 // Includes path and query (and leading "data:").
2823 return _parse("$uri", 5);
2824 }
2825
2826 /**
2827 * Writes the initial part of a `data:` uri, from after the "data:"
2828 * until just before the ',' before the data, or before a `;base64,`
2829 * marker.
2830 *
2831 * Of an [indices] list is passed, separator indices are stored in that
2832 * list.
2833 */
2834 static void _writeUri(String mimeType,
2835 String charsetName,
2836 Map<String, String> parameters,
2837 StringBuffer buffer, List indices) {
2838 if (mimeType == null || mimeType == "text/plain") {
2839 mimeType = "";
2840 }
2841 if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
2842 buffer.write(mimeType); // Common cases need no escaping.
2843 } else {
2844 int slashIndex = _validateMimeType(mimeType);
2845 if (slashIndex < 0) {
2846 throw new ArgumentError.value(mimeType, "mimeType",
2847 "Invalid MIME type");
2848 }
2849 buffer.write(Uri._uriEncode(_tokenCharTable,
2850 mimeType.substring(0, slashIndex)));
2851 buffer.write("/");
2852 buffer.write(Uri._uriEncode(_tokenCharTable,
2853 mimeType.substring(slashIndex + 1)));
2854 }
2855 if (charsetName != null) {
2856 if (indices != null) {
2857 indices..add(buffer.length)
2858 ..add(buffer.length + 8);
2859 }
2860 buffer.write(";charset=");
2861 buffer.write(Uri._uriEncode(_tokenCharTable, charsetName));
2862 }
2863 parameters?.forEach((var key, var value) {
2864 if (key.isEmpty) {
2865 throw new ArgumentError.value("", "Parameter names must not be empty");
2866 }
2867 if (value.isEmpty) {
2868 throw new ArgumentError.value("", "Parameter values must not be empty",
2869 'parameters["$key"]');
2870 }
2871 if (indices != null) indices.add(buffer.length);
2872 buffer.write(';');
2873 // Encode any non-RFC2045-token character and both '%' and '#'.
2874 buffer.write(Uri._uriEncode(_tokenCharTable, key));
2875 if (indices != null) indices.add(buffer.length);
2876 buffer.write('=');
2877 buffer.write(Uri._uriEncode(_tokenCharTable, value));
2878 });
2879 }
2880
2881 /**
2882 * Checks mimeType is valid-ish (`token '/' token`).
2883 *
2884 * Returns the index of the slash, or -1 if the mime type is not
2885 * considered valid.
2886 *
2887 * Currently only looks for slashes, all other characters will be
2888 * percent-encoded as UTF-8 if necessary.
2889 */
2890 static int _validateMimeType(String mimeType) {
2891 int slashIndex = -1;
2892 for (int i = 0; i < mimeType.length; i++) {
2893 var char = mimeType.codeUnitAt(i);
2894 if (char != Uri._SLASH) continue;
2895 if (slashIndex < 0) {
2896 slashIndex = i;
2897 continue;
2898 }
2899 return -1;
2900 }
2901 return slashIndex;
2902 }
2903
2904 /**
2905 * Parses a string as a `data` URI.
2906 *
2907 * The string must have the format:
2908 *
2909 * ```
2910 * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' dat a
2911 * ````
2912 *
2913 * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
2914 * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
2915 *
2916 * This means that all the characters must be ASCII, but the URI may contain
2917 * percent-escapes for non-ASCII byte values that need an interpretation
2918 * to be converted to the corresponding string.
2919 *
2920 * Parsing doesn't check the validity of any part, it just checks that the
2921 * input has the correct structure with the correct sequence of `/`, `;`, `=`
2922 * and `,` delimiters.
2923 *
2924 * Accessing the individual parts may fail later if they turn out to have
2925 * content that can't be decoded sucessfully as a string.
2926 */
2927 static DataUriHelper parse(String uri) {
2928 if (!uri.startsWith("data:")) {
2929 throw new FormatException("Does not start with 'data:'", uri, 0);
2930 }
2931 return _parse(uri, 5);
2932 }
2933
2934 /**
2935 * Converts a `DataUri` to a [Uri].
2936 *
2937 * Returns a `Uri` with scheme `data` and the remainder of the data URI
2938 * as path.
2939 */
2940 Uri toUri() {
2941 String path = _text;
2942 String query = null;
2943 int colonIndex = _separatorIndices[0];
2944 int queryIndex = _text.indexOf('?', colonIndex + 1);
2945 int end = null;
2946 if (queryIndex >= 0) {
2947 query = _text.substring(queryIndex + 1);
2948 end = queryIndex;
2949 }
2950 path = _text.substring(colonIndex + 1, end);
2951 // TODO(lrn): This is probably too simple. We should ensure URI
2952 // normalization before passing in the raw strings.
2953 return new Uri._internal("data", "", null, null, path, query, null);
2954 }
2955
2956 /**
2957 * The MIME type of the data URI.
2958 *
2959 * A data URI consists of a "media type" followed by data.
2960 * The media type starts with a MIME type and can be followed by
2961 * extra parameters.
2962 *
2963 * Example:
2964 *
2965 * data:text/plain;charset=utf-8,Hello%20World!
2966 *
2967 * This data URI has the media type `text/plain;charset=utf-8`, which is the
2968 * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
2969 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
2970 *
2971 * If the first part of the data URI is empty, it defaults to `text/plain`.
2972 */
2973 String get mimeType {
2974 int start = _separatorIndices[0] + 1;
2975 int end = _separatorIndices[1];
2976 if (start == end) return "text/plain";
2977 return Uri._uriDecode(_text, start: start, end: end);
2978 }
2979
2980 /**
2981 * The charset parameter of the media type.
2982 *
2983 * If the parameters of the media type contains a `charset` parameter
2984 * then this returns its value, otherwise it returns `US-ASCII`,
2985 * which is the default charset for data URIs.
2986 */
2987 String get charset {
2988 int parameterStart = 1;
2989 int parameterEnd = _separatorIndices.length - 1; // The ',' before data.
2990 if (isBase64) {
2991 // There is a ";base64" separator, so subtract one for that as well.
2992 parameterEnd -= 1;
2993 }
2994 for (int i = parameterStart; i < parameterEnd; i += 2) {
2995 var keyStart = _separatorIndices[i] + 1;
2996 var keyEnd = _separatorIndices[i + 1];
2997 if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
2998 return Uri._uriDecode(_text, start: keyEnd + 1,
2999 end: _separatorIndices[i + 2]);
3000 }
3001 }
3002 return "US-ASCII";
3003 }
3004
3005 /**
3006 * Whether the data is Base64 encoded or not.
3007 */
3008 bool get isBase64 => _separatorIndices.length.isOdd;
3009
3010 /**
3011 * The content part of the data URI, as its actual representation.
3012 *
3013 * This string may contain percent escapes.
3014 */
3015 String get contentText => _text.substring(_separatorIndices.last + 1);
3016
3017 /**
3018 * The content part of the data URI as bytes.
3019 *
3020 * If the data is Base64 encoded, it will be decoded to bytes.
3021 *
3022 * If the data is not Base64 encoded, it will be decoded by unescaping
3023 * percent-escaped characters and returning byte values of each unescaped
3024 * character. The bytes will not be, e.g., UTF-8 decoded.
3025 */
3026 List<int> contentAsBytes() {
3027 String text = _text;
3028 int start = _separatorIndices.last + 1;
3029 if (isBase64) {
3030 return BASE64.decode(text.substring(start));
3031 }
3032
3033 // Not base64, do percent-decoding and return the remaining bytes.
3034 // Compute result size.
3035 const int percent = 0x25;
3036 int length = text.length - start;
3037 for (int i = start; i < text.length; i++) {
3038 var codeUnit = text.codeUnitAt(i);
3039 if (codeUnit == percent) {
3040 i += 2;
3041 length -= 2;
3042 }
3043 }
3044 // Fill result array.
3045 Uint8List result = new Uint8List(length);
3046 if (length == text.length) {
3047 result.setRange(0, length, text.codeUnits, start);
3048 return result;
3049 }
3050 int index = 0;
3051 for (int i = start; i < text.length; i++) {
3052 var codeUnit = text.codeUnitAt(i);
3053 if (codeUnit != percent) {
3054 result[index++] = codeUnit;
3055 } else {
3056 if (i + 2 < text.length) {
3057 var digit1 = _hexDigit(text.codeUnitAt(i + 1));
3058 var digit2 = _hexDigit(text.codeUnitAt(i + 2));
3059 if (digit1 >= 0 && digit2 >= 0) {
3060 int byte = digit1 * 16 + digit2;
3061 result[index++] = byte;
3062 i += 2;
3063 continue;
3064 }
3065 }
3066 throw new FormatException("Invalid percent escape", text, i);
3067 }
3068 }
3069 assert(index == result.length);
3070 return result;
3071 }
3072
3073 // Converts a UTF-16 code-unit to its value as a hex digit.
3074 // Returns -1 for non-hex digits.
3075 int _hexDigit(int char) {
3076 const int char_0 = 0x30;
3077 const int char_a = 0x61;
3078 const int char_f = 0x66;
3079
3080 int digit = char ^ char_0;
3081 if (digit <= 9) return digit;
3082 int lowerCase = char | 0x20;
3083 if (char_a <= lowerCase && lowerCase <= char_f) {
3084 return lowerCase - (char_a - 10);
3085 }
3086 return -1;
3087 }
3088
3089 /**
3090 * Returns a string created from the content of the data URI.
3091 *
3092 * If the content is Base64 encoded, it will be decoded to bytes and then
3093 * decoded to a string using [encoding].
3094 * If encoding is omitted, the value of a `charset` parameter is used
3095 * if it is recongized by [Encoding.getByName], otherwise it defaults to
3096 * the [ASCII] encoding, which is the default encoding for data URIs
3097 * that do not specify an encoding.
3098 *
3099 * If the content is not Base64 encoded, it will first have percent-escapes
3100 * converted to bytes and then the character codes and byte values are
3101 * decoded using [encoding].
3102 */
3103 String contentAsString({Encoding encoding}) {
3104 if (encoding == null) {
3105 var charset = this.charset; // Returns "US-ASCII" if not present.
3106 encoding = Encoding.getByName(charset);
3107 if (encoding == null) {
3108 throw new UnsupportedError("Unknown charset: $charset");
3109 }
3110 }
3111 String text = _text;
3112 int start = _separatorIndices.last + 1;
3113 if (isBase64) {
3114 var converter = BASE64.decoder.fuse(encoding.decoder);
3115 return converter.convert(text.substring(start));
3116 }
3117 return Uri._uriDecode(text, start: start, encoding: encoding);
3118 }
3119
3120 /**
3121 * A map representing the parameters of the media type.
3122 *
3123 * A data URI may contain parameters between the the MIME type and the
3124 * data. This converts these parameters to a map from parameter name
3125 * to parameter value.
3126 * The map only contains parameters that actually occur in the URI.
3127 * The `charset` parameter has a default value even if it doesn't occur
3128 * in the URI, which is reflected by the [charset] getter. This means that
3129 * [charset] may return a value even if `parameters["charset"]` is `null`.
3130 *
3131 * If the values contain non-ASCII values or percent escapes, they default
3132 * to being decoded as UTF-8.
3133 */
3134 Map<String, String> get parameters {
3135 var result = <String, String>{};
3136 for (int i = 3; i < _separatorIndices.length; i += 2) {
3137 var start = _separatorIndices[i - 2] + 1;
3138 var equals = _separatorIndices[i - 1];
3139 var end = _separatorIndices[i];
3140 String key = Uri._uriDecode(_text, start: start, end: equals);
3141 String value = Uri._uriDecode(_text, start: equals + 1, end: end);
3142 result[key] = value;
3143 }
3144 return result;
3145 }
3146
3147 static DataUriHelper _parse(String text, int start) {
3148 assert(start == 0 || start == 5);
3149 assert((start == 5) == text.startsWith("data:"));
3150
3151 /// Character codes.
3152 const int comma = 0x2c;
3153 const int slash = 0x2f;
3154 const int semicolon = 0x3b;
3155 const int equals = 0x3d;
3156 List indices = [start - 1];
3157 int slashIndex = -1;
3158 var char;
3159 int i = start;
3160 for (; i < text.length; i++) {
3161 char = text.codeUnitAt(i);
3162 if (char == comma || char == semicolon) break;
3163 if (char == slash) {
3164 if (slashIndex < 0) {
3165 slashIndex = i;
3166 continue;
3167 }
3168 throw new FormatException("Invalid MIME type", text, i);
3169 }
3170 }
3171 if (slashIndex < 0 && i > start) {
3172 // An empty MIME type is allowed, but if non-empty it must contain
3173 // exactly one slash.
3174 throw new FormatException("Invalid MIME type", text, i);
3175 }
3176 while (char != comma) {
3177 // parse parameters and/or "base64".
3178 indices.add(i);
3179 i++;
3180 int equalsIndex = -1;
3181 for (; i < text.length; i++) {
3182 char = text.codeUnitAt(i);
3183 if (char == equals) {
3184 if (equalsIndex < 0) equalsIndex = i;
3185 } else if (char == semicolon || char == comma) {
3186 break;
3187 }
3188 }
3189 if (equalsIndex >= 0) {
3190 indices.add(equalsIndex);
3191 } else {
3192 // Have to be final "base64".
3193 var lastSeparator = indices.last;
3194 if (char != comma ||
3195 i != lastSeparator + 7 /* "base64,".length */ ||
3196 !text.startsWith("base64", lastSeparator + 1)) {
3197 throw new FormatException("Expecting '='", text, i);
3198 }
3199 break;
3200 }
3201 }
3202 indices.add(i);
3203 return new DataUriHelper._(text, indices);
3204 }
3205
3206 String toString() => text;
3207
3208 // Table of the `token` characters of RFC 2045 in a URI.
3209 //
3210 // A token is any US-ASCII character except SPACE, control characters and
3211 // `tspecial` characters. The `tspecial` category is:
3212 // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='.
3213 //
3214 // In a data URI, we also need to escape '%' and '#' characters.
3215 static const _tokenCharTable = const [
3216 // LSB MSB
3217 // | |
3218 0x0000, // 0x00 - 0x0f 00000000 00000000
3219 0x0000, // 0x10 - 0x1f 00000000 00000000
3220 // ! $ &' *+ -.
3221 0x6cd2, // 0x20 - 0x2f 01001011 00110110
3222 // 01234567 89
3223 0x03ff, // 0x30 - 0x3f 11111111 11000000
3224 // ABCDEFG HIJKLMNO
3225 0xfffe, // 0x40 - 0x4f 01111111 11111111
3226 // PQRSTUVW XYZ ^_
3227 0xc7ff, // 0x50 - 0x5f 11111111 11100011
3228 // `abcdefg hijklmno
3229 0xffff, // 0x60 - 0x6f 11111111 11111111
3230 // pqrstuvw xyz{|}~
3231 0x7fff]; // 0x70 - 0x7f 11111111 11111110
3232
3233 // All non-escape RFC-2396 uric characters.
3234 //
3235 // uric = reserved | unreserved | escaped
3236 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3237 // unreserved = alphanum | mark
3238 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3239 static const _uricTable = const [
3240 // LSB MSB
3241 // | |
3242 0x0000, // 0x00 - 0x0f 0000000000000000
3243 0x0000, // 0x10 - 0x1f 0000000000000000
3244 // ! $ &'()*+,-./
3245 0xffd2, // 0x20 - 0x2f 0100101111111111
3246 // 0123456789:; = ?
3247 0xafff, // 0x30 - 0x3f 1111111111110101
3248 // @ABCDEFGHIJKLMNO
3249 0xffff, // 0x40 - 0x4f 1111111111111111
3250 // PQRSTUVWXYZ _
3251 0x87ff, // 0x50 - 0x5f 1111111111100001
3252 // abcdefghijklmno
3253 0xfffe, // 0x60 - 0x6f 0111111111111111
3254 // pqrstuvwxyz ~
3255 0x47ff]; // 0x70 - 0x7f 1111111111100010
2613 } 3256 }
OLDNEW
« no previous file with comments | « sdk/lib/core/uri.dart ('k') | sdk/lib/uri/uri_sources.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698