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

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: Added library now working (thanks fschneider). Back to the main content. 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, 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) {
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 1296 matching lines...) Expand 10 before | Expand all | Expand 10 after
2315 * 2310 *
2316 * This function is similar to the JavaScript-function `decodeURI`. 2311 * This function is similar to the JavaScript-function `decodeURI`.
2317 * 2312 *
2318 * If [plusToSpace] is `true`, plus characters will be converted to spaces. 2313 * If [plusToSpace] is `true`, plus characters will be converted to spaces.
2319 * 2314 *
2320 * The decoder will create a byte-list of the percent-encoded parts, and then 2315 * 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. 2316 * decode the byte-list using [encoding]. The default encodingis UTF-8.
2322 */ 2317 */
2323 static String _uriDecode(String text, 2318 static String _uriDecode(String text,
2324 {bool plusToSpace: false, 2319 {bool plusToSpace: false,
2325 Encoding encoding: UTF8}) { 2320 Encoding encoding: UTF8,
2321 int start: 0,
2322 int end}) {
2323 if (end == null) end = text.length;
2326 // First check whether there is any characters which need special handling. 2324 // First check whether there is any characters which need special handling.
2327 bool simple = true; 2325 bool simple = true;
2328 for (int i = 0; i < text.length && simple; i++) { 2326 for (int i = start; i < end && simple; i++) {
2329 var codeUnit = text.codeUnitAt(i); 2327 var codeUnit = text.codeUnitAt(i);
2330 simple = codeUnit != _PERCENT && codeUnit != _PLUS; 2328 simple = codeUnit != _PERCENT && codeUnit != _PLUS;
2331 } 2329 }
2332 List<int> bytes; 2330 List<int> bytes;
2333 if (simple) { 2331 if (simple) {
2334 if (encoding == UTF8 || encoding == LATIN1) { 2332 if (encoding == UTF8 || encoding == LATIN1) {
2335 return text; 2333 return text.substring(start, end);
2334 } else if (start == 0 && end == text.length) {
2335 bytes = text.codeUnits;
2336 } else { 2336 } else {
2337 bytes = text.codeUnits; 2337 var decoder = encoding.decoder;
2338 var result;
2339 var conversionSink = decoder.startChunkedConversion(
2340 new ChunkedConversionSink.withCallback((list) {
2341 result = list.join();
2342 }));
2343 if (conversionSink is ByteConversionSink) {
2344 conversionSink.addSlice(text.codeUnits, start, end, true);
2345 } else {
2346 conversionSink.add(text.codeUnits.sublist(start, end));
2347 conversionSink.close();
2348 }
2349 return result;
2338 } 2350 }
2339 } else { 2351 } else {
2340 bytes = new List(); 2352 bytes = new List();
2341 for (int i = 0; i < text.length; i++) { 2353 for (int i = start; i < end; i++) {
2342 var codeUnit = text.codeUnitAt(i); 2354 var codeUnit = text.codeUnitAt(i);
2343 if (codeUnit > 127) { 2355 if (codeUnit > 127) {
2344 throw new ArgumentError("Illegal percent encoding in URI"); 2356 throw new ArgumentError("Illegal percent encoding in URI");
2345 } 2357 }
2346 if (codeUnit == _PERCENT) { 2358 if (codeUnit == _PERCENT) {
2347 if (i + 3 > text.length) { 2359 if (i + 3 > text.length) {
2348 throw new ArgumentError('Truncated URI'); 2360 throw new ArgumentError('Truncated URI');
2349 } 2361 }
2350 bytes.add(_hexCharPairToByte(text, i + 1)); 2362 bytes.add(_hexCharPairToByte(text, i + 1));
2351 i += 2; 2363 i += 2;
(...skipping 251 matching lines...) Expand 10 before | Expand all | Expand 10 after
2603 // 0123456789:; = ? 2615 // 0123456789:; = ?
2604 0xafff, // 0x30 - 0x3f 1111111111110101 2616 0xafff, // 0x30 - 0x3f 1111111111110101
2605 // @ABCDEFGHIJKLMNO 2617 // @ABCDEFGHIJKLMNO
2606 0xffff, // 0x40 - 0x4f 1111111111111111 2618 0xffff, // 0x40 - 0x4f 1111111111111111
2607 // PQRSTUVWXYZ _ 2619 // PQRSTUVWXYZ _
2608 0x87ff, // 0x50 - 0x5f 1111111111100001 2620 0x87ff, // 0x50 - 0x5f 1111111111100001
2609 // abcdefghijklmno 2621 // abcdefghijklmno
2610 0xfffe, // 0x60 - 0x6f 0111111111111111 2622 0xfffe, // 0x60 - 0x6f 0111111111111111
2611 // pqrstuvwxyz ~ 2623 // pqrstuvwxyz ~
2612 0x47ff]; // 0x70 - 0x7f 1111111111100010 2624 0x47ff]; // 0x70 - 0x7f 1111111111100010
2625
2613 } 2626 }
2627
2628 // --------------------------------------------------------------------
2629 // Data URI
2630 // --------------------------------------------------------------------
2631
2632 /**
2633 * A representation of a `data:` URI.
2634 *
2635 * Data URIs are non-hierarchial URIs that contain can contain any data.
2636 * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397).
2637 *
2638 * This class allows parsing the URI text and extracting individual parts of the
2639 * URI, as well as building the URI text from structured parts.
2640 */
2641 class DataUri {
2642 static const int _noScheme = -1;
2643 /**
2644 * Contains the text content of a `data:` URI, with or without a
2645 * leading `data:`.
2646 *
2647 * If [_separatorIndices] starts with `4` (the index of the `:`), then
2648 * there is a leading `data:`, otherwise _separatorIndices starts with
2649 * `-1`.
2650 */
2651 final String _text;
2652
2653 /**
2654 * List of the separators (';', '=' and ',') in the text.
2655 *
2656 * Starts with the index of the index of the `:` in `data:` of the mimeType.
2657 * That is always either -1 or 4, depending on whether `_text` includes the
2658 * `data:` scheme or not.
2659 *
2660 * The first speparator ends the mime type. We don't bother with finding
2661 * the '/' inside the mime type.
2662 *
2663 * Each two separators after that marks a parameter key and value.
2664 *
2665 * If there is a single separator left, it ends the "base64" marker.
2666 *
2667 * So the following separators are found for a text:
2668 *
2669 * data:text/plain;foo=bar;base64,ARGLEBARGLE=
2670 * ^ ^ ^ ^ ^
2671 *
2672 */
2673 List<int> _separatorIndices;
2674
2675 DataUri._(this._text,
2676 this._separatorIndices);
2677
2678 /** The entire content of the data URI, including the leading `data:`. */
Lasse Reichstein Nielsen 2015/10/28 13:55:47 This getter is identical to toString. Should I jus
2679 String get text => _separatorIndices[0] == _noScheme ? "data:$_text" : _text;
2680
2681 /**
2682 * Creates a `data:` URI containing the contents as percent-encoded text.
2683 */
2684 factory DataUri.fromString(String content,
2685 {mimeType: "text/plain",
2686 Iterable<DataUriParameter> parameters}) {
2687 StringBuffer buffer = new StringBuffer();
2688 List indices = [_noScheme];
2689 _writeUri(mimeType, parameters, buffer, indices);
2690 indices.add(buffer.length);
2691 buffer.write(',');
2692 buffer.write(Uri.encodeComponent(content));
2693 return new DataUri._(buffer.toString(), indices);
2694 }
2695
2696 /**
2697 * Creates a `data:` URI string containing the base-64 encoded content bytes.
2698 *
2699 * It defaults to having the mime-type `application/octet-stream`.
2700 */
2701 factory DataUri.fromBytes(List<int> bytes,
2702 {mimeType: "application/octet-stream",
2703 Iterable<DataUriParameter> parameters}) {
2704 StringBuffer buffer = new StringBuffer();
2705 List indices = [_noScheme];
2706 _writeUri(mimeType, parameters, buffer, indices);
2707 indices.add(buffer.length);
2708 buffer.write(';base64,');
2709 indices.add(buffer.length - 1);
2710 BASE64.encoder.startChunkedConversion(
2711 new StringConversionSink.fromStringSink(buffer))
2712 .addSlice(bytes, 0, bytes.length, true);
2713 return new DataUri._(buffer.toString(), indices);
2714 }
2715
2716 /**
2717 * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme].
2718 *
2719 * The [uri] must have scheme `data` and no authority, query or fragment,
2720 * and the path must be valid as a data URI.
2721 */
2722 factory DataUri.fromUri(Uri uri) {
2723 if (uri.scheme != "data") {
2724 throw new ArgumentError.value(uri, "uri",
2725 "Scheme must be 'data'");
2726 }
2727 if (uri.hasAuthority) {
2728 throw new ArgumentError.value(uri, "uri",
2729 "Data uri must not have authority");
2730 }
2731 if (uri.hasQuery) {
2732 throw new ArgumentError.value(uri, "uri",
2733 "Data uri must not have a query part");
2734 }
2735 if (uri.hasFragment) {
2736 throw new ArgumentError.value(uri, "uri",
2737 "Data uri must not have a fragment part");
2738 }
2739 return _parse(uri.path, 0);
2740 }
2741
2742 /**
2743 * Writes the initial part of a `data:` uri, from after the "data:"
2744 * until just before the ',' before the data, or before a `;base64,`
2745 * marker.
2746 *
2747 * Of an [indices] list is passed, separator indices are stored in that
2748 * list.
2749 */
2750 static void _writeUri(String mimeType,
2751 Iterable<DataUriParameter> parameters,
2752 StringBuffer buffer, List indices) {
2753 if (mimeType == null) {
2754 mimeType = "text/plain";
2755 }
2756 if (mimeType.isEmpty ||
2757 identical(mimeType, "text/plain") ||
2758 identical(mimeType, "application/octet-stream")) {
2759 buffer.write(mimeType); // Common cases need no escaping.
2760 } else {
2761 int slashIndex = _validateMimeType(mimeType);
2762 if (slashIndex < 0) {
2763 throw new ArgumentError.value(mimeType, "mimeType",
2764 "Invalid MIME type");
2765 }
2766 buffer.write(Uri._uriEncode(_tokenCharTable,
2767 mimeType.substring(0, slashIndex)));
2768 buffer.write("/");
2769 buffer.write(Uri._uriEncode(_tokenCharTable,
2770 mimeType.substring(slashIndex + 1)));
2771 }
2772 if (parameters != null) {
2773 for (var parameter in parameters) {
2774 if (indices != null) indices.add(buffer.length);
2775 buffer.write(';');
2776 // Encode any non-RFC2045-token character as well as '%' and '#'.
2777 buffer.write(Uri._uriEncode(_tokenCharTable, parameter.key));
2778 if (indices != null) indices.add(buffer.length);
2779 buffer.write('=');
2780 buffer.write(Uri._uriEncode(_tokenCharTable, parameter.value));
2781 }
2782 }
2783 }
2784
2785 /**
2786 * Checks mimeType is valid-ish (`token '/' token`).
2787 *
2788 * Returns the index of the slash, or -1 if the mime type is not
2789 * considered valid.
2790 *
2791 * Currently only looks for slashes, all other characters will be
2792 * percent-encoded as UTF-8 if necessary.
2793 */
2794 static int _validateMimeType(String mimeType) {
2795 int slashIndex = -1;
2796 for (int i = 0; i < mimeType.length; i++) {
2797 var char = mimeType.codeUnitAt(i);
2798 if (char != Uri._SLASH) continue;
2799 if (slashIndex < 0) {
2800 slashIndex = i;
2801 continue;
2802 }
2803 return -1;
2804 }
2805 return slashIndex;
2806 }
2807
2808 /**
2809 * Creates a [Uri] with the content of [DataUri.fromString].
2810 *
2811 * The resulting URI will have `data` as scheme and the remainder
2812 * of the data URI as path.
2813 *
2814 * Equivalent to creating a `DataUri` using `new DataUri.fromString` and
2815 * calling `toUri` on the result.
2816 */
2817 static Uri uriFromString(String content,
2818 {mimeType: "text/plain",
2819 Iterable<DataUriParameter> parameters}) {
2820 var buffer = new StringBuffer();
2821 _writeUri(mimeType, parameters, buffer, null);
2822 buffer.write(',');
2823 buffer.write(Uri.encodeComponent(content));
2824 return new Uri(scheme: "data", path: buffer.toString());
2825 }
2826
2827 /**
2828 * Creates a [Uri] with the content of [DataUri.fromBytes].
2829 *
2830 * The resulting URI will have `data` as scheme and the remainder
2831 * of the data URI as path.
2832 *
2833 * Equivalent to creating a `DataUri` using `new DataUri.fromBytes` and
2834 * calling `toUri` on the result.
2835 */
2836 static Uri uriFromBytes(List<int> bytes,
2837 {mimeType: "text/plain",
2838 Iterable<DataUriParameter> parameters}) {
2839 var buffer = new StringBuffer();
2840 _writeUri(mimeType, parameters, buffer, null);
2841 buffer.write(';base64,');
2842 BASE64.encoder.startChunkedConversion(buffer)
2843 .addSlice(bytes, 0, bytes.length, true);
2844 return new Uri(scheme: "data", path: buffer.toString());
2845 }
2846
2847 /**
2848 * Parses a string as a `data` URI.
2849 */
2850 static DataUri parse(String uri) {
2851 if (!uri.startsWith("data:")) {
2852 throw new FormatException("Does not start with 'data:'", uri, 0);
2853 }
2854 return _parse(uri, 5);
2855 }
2856
2857 /**
2858 * Converts a `DataUri` to a [Uri].
2859 *
2860 * Returns a `Uri` with scheme `data` and the remainder of the data URI
2861 * as path.
2862 */
2863 Uri toUri() {
2864 String content = _text;
2865 int colonIndex = _separatorIndices[0];
2866 if (colonIndex >= 0) {
2867 content = _text.substring(colonIndex + 1);
2868 }
2869 return new Uri._internal("data", null, null, null, content, null, null);
2870 }
2871
2872 /**
2873 * The MIME type of the data URI.
2874 *
2875 * A data URI consists of a "media type" followed by data.
2876 * The mediatype starts with a MIME type and can be followed by
2877 * extra parameters.
2878 *
2879 * Example:
2880 *
2881 * data:text/plain;encoding=utf-8,Hello%20World!
2882 *
2883 * This data URI has the media type `text/plain;encoding=utf-8`, which is the
2884 * MIME type `text/plain` with the parameter `encoding` with value `utf-8`.
2885 * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
2886 *
2887 * If the first part of the data URI is empty, it defaults to `text/plain`.
2888 */
2889 String get mimeType {
2890 int start = _separatorIndices[0] + 1;
2891 int end = _separatorIndices[1];
2892 if (start == end) return "text/plain";
2893 return Uri._uriDecode(_text, start: start, end: end);
2894 }
2895
2896 /**
2897 * Whether the data is base64 encoded or not.
2898 */
2899 bool get isBase64 => _separatorIndices.length.isOdd;
2900
2901 /**
2902 * The content part of the data URI, as its actual representation.
2903 *
2904 * This string may contain percent escapes.
2905 */
2906 String get contentText => _text.substring(_separatorIndices.last + 1);
2907
2908 /**
2909 * The content part of the data URI as bytes.
2910 *
2911 * If the data is base64 encoded, it will be decoded to bytes.
2912 *
2913 * If the data is not base64 encoded, it will be decoded by unescaping
2914 * percent-escaped characters and returning byte values of each unescaped
2915 * character. The bytes will not be, e.g., UTF-8 decoded.
2916 */
2917 List<int> contentAsBytes() {
Lasse Reichstein Nielsen 2015/10/28 13:55:47 This sounds like it should be a getter, but the co
2918 String text = _text;
2919 int start = _separatorIndices.last + 1;
2920 if (isBase64) {
2921 if (text.endsWith("%3D")) {
2922 return BASE64.decode(Uri._uriDecode(text, start: start,
2923 encoding: LATIN1));
2924 }
2925 return BASE64.decode(text.substring(start));
2926 }
2927
2928 // Not base64, do percent-decoding and return the remaining bytes.
2929 // Compute result size.
2930 const int percent = 0x25;
2931 int length = text.length - start;
2932 for (int i = start; i < text.length; i++) {
2933 var codeUnit = text.codeUnitAt(i);
2934 if (codeUnit == percent) {
2935 i += 2;
2936 length -= 2;
2937 }
2938 }
2939 // Fill result array.
2940 Uint8List result = new Uint8List(length);
2941 if (length == text.length) {
2942 result.setRange(0, length, text.codeUnits, start);
2943 return result;
2944 }
2945 int index = 0;
2946 for (int i = start; i < text.length; i++) {
2947 var codeUnit = text.codeUnitAt(i);
2948 if (codeUnit != percent) {
2949 result[index++] = codeUnit;
2950 } else {
2951 if (i + 2 < text.length) {
2952 var digit1 = _hexDigit(text.codeUnitAt(i + 1));
2953 var digit2 = _hexDigit(text.codeUnitAt(i + 2));
2954 if (digit1 >= 0 && digit2 >= 0) {
2955 int byte = digit1 * 16 + digit2;
2956 result[index++] = byte;
2957 i += 2;
2958 continue;
2959 }
2960 }
2961 throw new FormatException("Invalid percent escape", text, i);
2962 }
2963 }
2964 assert(index == result.length);
2965 return result;
2966 }
2967
2968 // Converts a UTF-16 code-unit to its value as a hex digit.
2969 // Returns -1 for non-hex digits.
2970 int _hexDigit(int char) {
2971 const int char_0 = 0x30;
2972 const int char_a = 0x61;
2973
2974 int digit = char ^ char_0;
2975 if (digit <= 9) return digit;
2976 char = ((char | 0x20) - char_a) & 0xFFFF;
2977 if (char < 6) return 10 + char;
2978 return -1;
2979 }
2980
2981 /**
2982 * Returns a string created from the content of the data URI.
2983 *
2984 * If the content is base64 encoded, it will be decoded to bytes and then
2985 * decoded to a string using [encoding].
2986 *
2987 * If the content is not base64 encoded, it will first have percent-escapes
2988 * converted to bytes and then the character codes and byte values are
2989 * decoded using [encoding].
2990 */
2991 String contentAsString({Encoding encoding: UTF8}) {
2992 String text = _text;
2993 int start = _separatorIndices.last + 1;
2994 if (isBase64) {
2995 var converter = BASE64.decoder.fuse(encoding.decoder);
2996 if (text.endsWith("%3D")) {
2997 return converter.convert(Uri._uriDecode(text, start: start,
2998 encoding: LATIN1));
2999 }
3000 return converter.convert(text.substring(start));
3001 }
3002 return Uri._uriDecode(text, start: start, encoding: encoding);
3003 }
3004
3005 /**
3006 * An iterable over the parameters of the data URI.
3007 *
3008 * A data URI may contain parameters between the the MIMI type and the
3009 * data. This iterates through those parameters, returning each as a
3010 * [DataUriParameter] pair of key and value.
3011 */
3012 Iterable<DataUriParameter> get parameters sync* {
3013 for (int i = 3; i < _separatorIndices.length; i += 2) {
3014 var start = _separatorIndices[i - 2] + 1;
3015 var equals = _separatorIndices[i - 1];
3016 var end = _separatorIndices[i];
3017 String key = Uri._uriDecode(_text, start: start, end: equals);
3018 String value = Uri._uriDecode(_text, start: equals + 1, end: end);
3019 yield new DataUriParameter(key, value);
3020 }
3021 }
3022
3023 static DataUri _parse(String text, int start) {
3024 assert(start == 0 || start == 5);
3025 assert((start == 5) == text.startsWith("data:"));
3026
3027 /// Character codes.
3028 const int comma = 0x2c;
3029 const int slash = 0x2f;
3030 const int semicolon = 0x3b;
3031 const int equals = 0x3d;
3032 List indices = [start - 1];
3033 int slashIndex = -1;
3034 var char;
3035 int i = start;
3036 for (; i < text.length; i++) {
3037 char = text.codeUnitAt(i);
3038 if (char == comma || char == semicolon) break;
3039 if (char == slash) {
3040 if (slashIndex < 0) {
3041 slashIndex = i;
3042 continue;
3043 }
3044 throw new FormatException("Invalid MIME type", text, i);
3045 }
3046 }
3047 if (slashIndex < 0 && i > start) {
3048 // An empty MIME type is allowed, but if non-empty it must contain
3049 // exactly one slash.
3050 throw new FormatException("Invalid MIME type", text, i);
3051 }
3052 while (char != comma) {
3053 // parse parameters and/or "base64".
3054 indices.add(i);
3055 i++;
3056 int equalsIndex = -1;
3057 for (; i < text.length; i++) {
3058 char = text.codeUnitAt(i);
3059 if (char == equals) {
3060 if (equalsIndex < 0) equalsIndex = i;
3061 } else if (char == semicolon || char == comma) {
3062 break;
3063 }
3064 }
3065 if (equalsIndex >= 0) {
3066 indices.add(equalsIndex);
3067 } else {
3068 // Have to be final "base64".
3069 var lastSeparator = indices.last;
3070 if (char != comma ||
3071 i != lastSeparator + 7 /* "base64,".length */ ||
3072 !text.startsWith("base64", lastSeparator + 1)) {
3073 throw new FormatException("Expecting '='", text, i);
3074 }
3075 break;
3076 }
3077 }
3078 indices.add(i);
3079 return new DataUri._(text, indices);
3080 }
3081
3082 String toString() => text;
3083
3084 // Table of the `token` characters of RFC 2045 in a URI.
3085 //
3086 // A token is any US-ASCII character except SPACE, control characters and
3087 // `tspecial` characters. The `tspecial` category is:
3088 // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='.
3089 //
3090 // In a data URI, we also need to escape '%' and '#' characters.
3091 static const _tokenCharTable = const [
3092 // LSB MSB
3093 // | |
3094 0x0000, // 0x00 - 0x0f 00000000 00000000
3095 0x0000, // 0x10 - 0x1f 00000000 00000000
3096 // ! $ &' *+ -.
3097 0x6cd2, // 0x20 - 0x2f 01001011 00110110
3098 // 01234567 89
3099 0x03ff, // 0x30 - 0x3f 11111111 11000000
3100 // ABCDEFG HIJKLMNO
3101 0xfffe, // 0x40 - 0x4f 01111111 11111111
3102 // PQRSTUVW XYZ ^_
3103 0xc7ff, // 0x50 - 0x5f 11111111 11100011
3104 // `abcdefg hijklmno
3105 0xffff, // 0x60 - 0x6f 11111111 11111111
3106 // pqrstuvw xyz{|}~
3107 0x7fff]; // 0x70 - 0x7f 11111111 11111110
3108 }
3109
3110 /**
3111 * A parameter of a data URI.
3112 *
3113 * A parameter is a key and a value.
3114 *
3115 * The key and value are the actual values to be encoded into the URI.
3116 * They will be escaped if necessary when creating a data URI,
3117 * and have been unescaped when extracted from a data URI.
3118 */
3119 class DataUriParameter {
3120 /** Parameter key. */
3121 final String key;
3122 /** Parameter value. */
3123 final String value;
3124 DataUriParameter(this.key, this.value);
3125
3126 /**
3127 * Creates an iterable of parameters from a map from key to value.
3128 *
3129 * Parameter keys are not required to be unique in a data URI, but
3130 * when they are, a map can be used to represent the parameters, and
3131 * this function provides a way to access the map pairs as parameter
3132 * values.
3133 */
3134 static Iterable<DataUriParameter> fromMap(Map<String, String> headers) sync* {
3135 for (String key in headers.keys) {
3136 yield new DataUriParameter(key, headers[key]);
3137 }
3138 }
3139 }
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