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

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

Issue 335373003: New Uri.parse and validation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address comments Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
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 part of dart.core;
6 6
7 /** 7 /**
8 * A parsed URI, such as a URL. 8 * A parsed URI, such as a URL.
9 * 9 *
10 * **See also:** 10 * **See also:**
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 * Cache the computed return value of [pathSegements]. 109 * Cache the computed return value of [pathSegements].
110 */ 110 */
111 List<String> _pathSegments; 111 List<String> _pathSegments;
112 112
113 /** 113 /**
114 * Cache the computed return value of [queryParameters]. 114 * Cache the computed return value of [queryParameters].
115 */ 115 */
116 Map<String, String> _queryParameters; 116 Map<String, String> _queryParameters;
117 117
118 /** 118 /**
119 * Creates a new URI object by parsing a URI string. 119 * Creates a new URI object by parsing a URI string.
Søren Gjesse 2014/06/19 07:39:43 This comment needs to be updated...
Lasse Reichstein Nielsen 2014/06/19 08:44:58 Done.
120 */ 120 */
121 static Uri parse(String uri) { 121 static Uri parse(String uri) {
122 // This parsing will not validate percent-encoding, IPv6, etc. When done 122 // This parsing will not validate percent-encoding, IPv6, etc. When done
123 // it will call `new Uri(...)` which will perform these validations. 123 // it will call `new Uri(...)` which will perform these validations.
124 // This is purely splitting up the URI string into components. 124 // This is purely splitting up the URI string into components.
125 // 125 //
126 // Important parts of the RFC 3986 used here: 126 // Important parts of the RFC 3986 used here:
127 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 127 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
128 // 128 //
129 // hier-part = "//" authority path-abempty 129 // hier-part = "//" authority path-abempty
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 // ; non-zero-length segment without any colon ":" 168 // ; non-zero-length segment without any colon ":"
169 // 169 //
170 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 170 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
171 // 171 //
172 // query = *( pchar / "/" / "?" ) 172 // query = *( pchar / "/" / "?" )
173 // 173 //
174 // fragment = *( pchar / "/" / "?" ) 174 // fragment = *( pchar / "/" / "?" )
175 bool isRegName(int ch) { 175 bool isRegName(int ch) {
176 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 176 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
177 } 177 }
178 178 const int EOI = -1;
179 int ipV6Address(int index) { 179
180 // IPv6. Skip to ']'. 180 String scheme = "";
181 index = uri.indexOf(']', index); 181 String path;
182 if (index == -1) { 182 String userinfo = "";
183 throw new FormatException("Bad end of IPv6 host"); 183 String host = "";
184 } 184 int port = 0;
185 return index + 1; 185 String query = "";
186 } 186 String fragment = "";
187 187
188 int length = uri.length;
189 int index = 0; 188 int index = 0;
190 189 int pathStart = 0;
191 int schemeEndIndex = 0; 190 // End of input-marker.
192 191 int char = EOI;
193 if (length == 0) { 192
194 return new Uri(); 193 void parseAuth() {
195 } 194 if (index == uri.length) {
196 195 char = EOI;
197 if (uri.codeUnitAt(0) != _SLASH) { 196 return;
198 // Can be scheme. 197 }
199 while (index < length) { 198 int authStart = index;
200 // Look for ':'. If found, continue from the post of ':'. If not (end 199 int lastColon = -1;
201 // reached or invalid scheme char found) back up one char, and continue 200 int lastAt = -1;
202 // to path. 201 char = uri.codeUnitAt(index);
203 // Note that scheme-chars is contained in path-chars. 202 while (index < uri.length) {
204 int codeUnit = uri.codeUnitAt(index++); 203 char = uri.codeUnitAt(index);
205 if (!_isSchemeCharacter(codeUnit)) { 204 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
206 if (codeUnit == _COLON) {
207 schemeEndIndex = index;
208 } else {
209 // Back up one char, since we met an invalid scheme char.
210 index--;
211 }
212 break; 205 break;
213 } 206 }
214 } 207 if (char == _AT_SIGN) {
Søren Gjesse 2014/06/19 07:39:43 So you can have @ and : in the username?
Lasse Reichstein Nielsen 2014/06/19 08:44:58 You can have :, which you always could. You can't
215 } 208 lastAt = index;
216 209 lastColon = -1;
217 int userInfoEndIndex = -1; 210 } else if (char == _COLON) {
218 int portIndex = -1; 211 lastColon = index;
219 int authorityEndIndex = schemeEndIndex; 212 } else if (char == _LEFT_BRACKET) {
220 // If we see '//', there must be an authority. 213 lastColon = -1;
221 if (authorityEndIndex == index && 214 int endBracket = uri.indexOf(']', index + 1);
222 authorityEndIndex + 1 < length && 215 if (endBracket == -1) {
223 uri.codeUnitAt(authorityEndIndex) == _SLASH && 216 index = uri.length;
224 uri.codeUnitAt(authorityEndIndex + 1) == _SLASH) { 217 char = EOI;
225 // Skip '//'.
226 authorityEndIndex += 2;
227 // It can both be host and userInfo.
228 while (authorityEndIndex < length) {
229 int codeUnit = uri.codeUnitAt(authorityEndIndex++);
230 if (!isRegName(codeUnit)) {
231 if (codeUnit == _LEFT_BRACKET) {
232 authorityEndIndex = ipV6Address(authorityEndIndex);
233 } else if (portIndex == -1 && codeUnit == _COLON) {
234 // First time ':'.
235 portIndex = authorityEndIndex;
236 } else if (codeUnit == _AT_SIGN || codeUnit == _COLON) {
237 // Second time ':' or first '@'. Must be userInfo.
238 userInfoEndIndex = uri.indexOf('@', authorityEndIndex - 1);
239 // Not found. Must be path then.
240 if (userInfoEndIndex == -1) {
241 authorityEndIndex = index;
242 break;
243 }
244 portIndex = -1;
245 authorityEndIndex = userInfoEndIndex + 1;
246 // Now it can only be host:port.
247 while (authorityEndIndex < length) {
248 int codeUnit = uri.codeUnitAt(authorityEndIndex++);
249 if (!isRegName(codeUnit)) {
250 if (codeUnit == _LEFT_BRACKET) {
251 authorityEndIndex = ipV6Address(authorityEndIndex);
252 } else if (codeUnit == _COLON) {
253 if (portIndex != -1) {
254 throw new FormatException("Double port in host");
255 }
256 portIndex = authorityEndIndex;
257 } else {
258 authorityEndIndex--;
259 break;
260 }
261 }
262 }
263 break; 218 break;
264 } else { 219 } else {
265 authorityEndIndex--; 220 index = endBracket;
266 break; 221 }
267 } 222 }
268 } 223 index++;
269 } 224 char = EOI;
225 }
226 int hostStart = authStart;
227 int hostEnd = index;
228 if (lastAt >= 0) {
229 userinfo = _makeUserInfo(uri, authStart, lastAt);
230 hostStart = lastAt + 1;
231 }
232 if (lastColon >= 0) {
233 if (lastColon + 1 == index) {
234 _fail(uri, index, "Invalid port number");
235 }
236 int portNumber = 0;
237 for (int i = lastColon + 1; i < index; i++) {
238 int digit = uri.codeUnitAt(i);
239 if (_ZERO > digit || _NINE < digit) {
240 _fail(uri, i, "Invalid port number");
241 }
242 portNumber = portNumber * 10 + (digit - _ZERO);
243 }
244 port = _makePort(portNumber, scheme);
245 hostEnd = lastColon;
246 }
247 host = _makeHost(uri, hostStart, hostEnd);
248 if (index < uri.length) {
249 char = uri.codeUnitAt(index);
250 }
251 }
252
253 // When reaching authority parsing, authority is possible.
254 // This is only true at start or right after scheme.
255 const int STATE_AUTH_OPT = 1;
256 // When reaching path parsing, the current character is part
257 // of the path.
258 const int STATE_PATH = 2;
259 // When reaching path parsing, the current character is known to not
260 // be part of the path.
261 const int STATE_PATH_END = 3;
262
263 // Current state.
264 // Initialized to the default value that is used when exiting the
265 // scheme loop by reaching the end of input.
266 // All other breaks set their own state.
267 int state = STATE_PATH_END;
Søren Gjesse 2014/06/19 07:39:43 It is somewhat confusing that this state value doe
Lasse Reichstein Nielsen 2014/06/19 08:44:58 I only set the state on a break. This is the state
268 while (index < uri.length) {
269 char = uri.codeUnitAt(index);
270 if (char == _QUESTION || char == _NUMBER_SIGN) {
271 state = STATE_PATH_END;
272 break;
273 }
274 if (char == _SLASH) {
275 if (index == 0) {
Søren Gjesse 2014/06/19 07:39:43 Conditional expression?
Lasse Reichstein Nielsen 2014/06/19 08:44:58 Ok. I don't really like them, but I guess it's ok
276 state = STATE_AUTH_OPT;
277 } else {
278 state = STATE_PATH;
279 }
280 break;
281 }
282 if (char == _COLON) {
283 if (index == 0) _fail(uri, 0, "Invalid empty scheme");
284 scheme = _makeScheme(uri, index);
285 index++;
286 pathStart = index;
287 if (index == uri.length) {
288 char = EOI;
289 state = STATE_PATH_END;
290 } else {
291 char = uri.codeUnitAt(index);
292 if (char == _QUESTION || char == _NUMBER_SIGN) {
293 state = STATE_PATH_END;
294 } else if (char == _SLASH) {
295 state = STATE_AUTH_OPT;
296 } else {
297 state = STATE_PATH;
298 }
299 }
300 break;
301 }
302 index++;
303 char = EOI;
304 }
305 if (state == STATE_AUTH_OPT) {
306 state = STATE_PATH; // Default value when leaving authority.
307 // Have seen one slash either at start or right after scheme.
308 // If two slashes, it's an authority, otherwise it's just the path.
309 assert(char == _SLASH);
310 index++;
311 if (index == uri.length) {
312 char = EOI;
313 state = STATE_PATH_END;
314 } else {
315 char = uri.codeUnitAt(index);
316 if (char == _QUESTION || char == _NUMBER_SIGN) {
317 state = STATE_PATH_END;
318 } else if (char == _SLASH) {
319 index++;
320 parseAuth();
321 pathStart = index;
322 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
323 state = STATE_PATH_END;
324 }
325 }
326 }
327 }
328 if (state == STATE_PATH) {
329 // Characters from pathStart to index (inclusive) are known
330 // to be part of the path.
331 while (++index < uri.length) {
332 char = uri.codeUnitAt(index);
333 if (char == _QUESTION || char == _NUMBER_SIGN) {
334 state = STATE_PATH_END;
335 break;
336 }
337 char = EOI;
338 }
339 }
340 assert(state == STATE_PATH_END);
341 bool ensureLeadingSlash = (host != "" || scheme == "file");
342 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
343
344 if (char == _QUESTION) {
345 int numberSignIndex = uri.indexOf('#', index + 1);
346 if (numberSignIndex < 0) {
347 query = _makeQuery(uri, index + 1, uri.length, null);
348 } else {
349 query = _makeQuery(uri, index + 1, numberSignIndex, null);
350 fragment = _makeFragment(uri, numberSignIndex + 1, uri.length);
351 }
352 } else if (char == _NUMBER_SIGN) {
353 fragment = _makeFragment(uri, index + 1, uri.length);
354 }
355 return new Uri._internal(scheme,
356 userinfo,
357 host,
358 port,
359 path,
360 query,
361 fragment);
362 }
363
364 // Report a parse failure.
365 static void _fail(String uri, int index, String message) {
366 // TODO(lrn): Consider adding this to FormatException.
367 if (index == uri.length) {
368 message += " at end of input.";
270 } else { 369 } else {
271 authorityEndIndex = schemeEndIndex; 370 message += " at position $index.\n";
272 } 371 // Pick a slice of uri containing index and, if
273 372 // necessary, truncate the ends to ensure the entire
274 // At path now. 373 // slice fits on one line.
275 int pathEndIndex = authorityEndIndex; 374 int min = 0;
276 while (pathEndIndex < length) { 375 int max = uri.length;
277 int codeUnit = uri.codeUnitAt(pathEndIndex++); 376 String pre = "";
278 if (codeUnit == _QUESTION || codeUnit == _NUMBER_SIGN) { 377 String post = "";
279 pathEndIndex--; 378 if (uri.length > 78) {
280 break; 379 min = index - 10;
281 } 380 if (min < 0) min = 0;
282 } 381 int max = min + 72;
283 382 if (max > uri.length) {
284 // Maybe query. 383 max = uri.length;
285 int queryEndIndex = pathEndIndex; 384 min = max - 72;
286 if (queryEndIndex < length && uri.codeUnitAt(queryEndIndex) == _QUESTION) { 385 }
287 while (queryEndIndex < length) { 386 if (min != 0) pre = "...";
288 int codeUnit = uri.codeUnitAt(queryEndIndex++); 387 if (max != uri.length) post = "...";
289 if (codeUnit == _NUMBER_SIGN) { 388 }
290 queryEndIndex--; 389 // Combine message, slice and a caret pointing to the error index.
291 break; 390 message = "$message$pre${uri.substring(min, max)}$post\n"
292 } 391 "${' ' * (pre.length + index - min)}^";
293 } 392 }
294 } 393 throw new FormatException(message);
295
296 var scheme = null;
297 if (schemeEndIndex > 0) {
298 scheme = uri.substring(0, schemeEndIndex - 1);
299 }
300
301 var host = "";
302 var userInfo = "";
303 var port = 0;
304 if (schemeEndIndex != authorityEndIndex) {
305 int startIndex = schemeEndIndex + 2;
306 if (userInfoEndIndex > 0) {
307 userInfo = uri.substring(startIndex, userInfoEndIndex);
308 startIndex = userInfoEndIndex + 1;
309 }
310 if (portIndex > 0) {
311 var portStr = uri.substring(portIndex, authorityEndIndex);
312 try {
313 port = int.parse(portStr);
314 } catch (_) {
315 throw new FormatException("Invalid port: '$portStr'");
316 }
317 host = uri.substring(startIndex, portIndex - 1);
318 } else {
319 host = uri.substring(startIndex, authorityEndIndex);
320 }
321 }
322
323 var path = uri.substring(authorityEndIndex, pathEndIndex);
324 var query = "";
325 if (pathEndIndex < queryEndIndex) {
326 query = uri.substring(pathEndIndex + 1, queryEndIndex);
327 }
328 var fragment = "";
329 // If queryEndIndex is not at end (length), there is a fragment.
330 if (queryEndIndex < length) {
331 fragment = uri.substring(queryEndIndex + 1, length);
332 }
333
334 return new Uri(scheme: scheme,
335 userInfo: userInfo,
336 host: host,
337 port: port,
338 path: path,
339 query: query,
340 fragment: fragment);
341 } 394 }
342 395
396 /// Internal non-verifying constructor. Only call with validated arguments.
397 Uri._internal(this.scheme,
398 this.userInfo,
399 this._host,
400 this._port,
401 this._path,
402 this.query,
403 this.fragment);
404
343 /** 405 /**
344 * Creates a new URI from its components. 406 * Creates a new URI from its components.
345 * 407 *
346 * Each component is set through a named argument. Any number of 408 * Each component is set through a named argument. Any number of
347 * components can be provided. The default value for the components 409 * components can be provided. The default value for the components
348 * not provided is the empry string, except for [port] which has a 410 * not provided is the empry string, except for [port] which has a
349 * default value of 0. The [path] and [query] components can be set 411 * default value of 0. The [path] and [query] components can be set
350 * using two different named arguments. 412 * using two different named arguments.
351 * 413 *
352 * The scheme component is set through [scheme]. The scheme is 414 * The scheme component is set through [scheme]. The scheme is
(...skipping 26 matching lines...) Expand all
379 * [queryParameters]. When [query] is used the provided string is 441 * [queryParameters]. When [query] is used the provided string is
380 * expected to be fully percent-encoded and is used in its literal 442 * expected to be fully percent-encoded and is used in its literal
381 * form. When [queryParameters] is used the query is built from the 443 * form. When [queryParameters] is used the query is built from the
382 * provided map. Each key and value in the map is percent-encoded 444 * provided map. Each key and value in the map is percent-encoded
383 * and joined using equal and ampersand characters. The 445 * and joined using equal and ampersand characters. The
384 * percent-encoding of the keys and values encodes all characters 446 * percent-encoding of the keys and values encodes all characters
385 * except for the unreserved characters. 447 * except for the unreserved characters.
386 * 448 *
387 * The fragment component is set through [fragment]. 449 * The fragment component is set through [fragment].
388 */ 450 */
389 Uri({String scheme, 451 factory Uri({String scheme,
390 this.userInfo: "", 452 String userInfo: "",
391 String host: "", 453 String host: "",
392 port: 0, 454 port: 0,
393 String path, 455 String path,
394 Iterable<String> pathSegments, 456 Iterable<String> pathSegments,
395 String query, 457 String query,
396 Map<String, String> queryParameters, 458 Map<String, String> queryParameters,
397 fragment: ""}) : 459 fragment: ""}) {
398 scheme = _makeScheme(scheme), 460 scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
399 _host = _makeHost(host), 461 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
400 query = _makeQuery(query, queryParameters), 462 host = _makeHost(host, 0, _stringOrNullLength(host));
401 fragment = _makeFragment(fragment) { 463 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
402 // Perform scheme specific normalization. 464 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
403 if (scheme == "http" && port == 80) { 465 port = _makePort(port, scheme);
404 _port = 0; 466 bool ensureLeadingSlash = (host != "" || scheme == "file");
405 } else if (scheme == "https" && port == 443) { 467 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
406 _port = 0; 468 ensureLeadingSlash);
407 } else { 469
408 _port = port; 470 return new Uri._internal(scheme, userInfo, host, port,
409 } 471 path, query, fragment);
410 // Fill the path.
411 _path = _makePath(path, pathSegments);
412 } 472 }
413 473
414 /** 474 /**
415 * Creates a new `http` URI from authority, path and query. 475 * Creates a new `http` URI from authority, path and query.
416 * 476 *
417 * Examples: 477 * Examples:
418 * 478 *
419 * ``` 479 * ```
420 * // http://example.org/path?q=dart. 480 * // http://example.org/path?q=dart.
421 * new Uri.http("google.com", "/search", { "q" : "dart" }); 481 * new Uri.http("google.com", "/search", { "q" : "dart" });
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
482 var hostEnd = hostStart; 542 var hostEnd = hostStart;
483 if (hostStart < authority.length && 543 if (hostStart < authority.length &&
484 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { 544 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
485 // IPv6 host. 545 // IPv6 host.
486 for (; hostEnd < authority.length; hostEnd++) { 546 for (; hostEnd < authority.length; hostEnd++) {
487 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; 547 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
488 } 548 }
489 if (hostEnd == authority.length) { 549 if (hostEnd == authority.length) {
490 throw new FormatException("Invalid IPv6 host entry."); 550 throw new FormatException("Invalid IPv6 host entry.");
491 } 551 }
492 parseIPv6Address(authority.substring(hostStart + 1, hostEnd)); 552 parseIPv6Address(authority, hostStart + 1, hostEnd);
493 hostEnd++; // Skip the closing bracket. 553 hostEnd++; // Skip the closing bracket.
494 if (hostEnd != authority.length && 554 if (hostEnd != authority.length &&
495 authority.codeUnitAt(hostEnd) != _COLON) { 555 authority.codeUnitAt(hostEnd) != _COLON) {
496 throw new FormatException("Invalid end of authority"); 556 throw new FormatException("Invalid end of authority");
497 } 557 }
498 } 558 }
499 // Split host and port. 559 // Split host and port.
500 bool hasPort = false; 560 bool hasPort = false;
501 for (; hostEnd < authority.length; hostEnd++) { 561 for (; hostEnd < authority.length; hostEnd++) {
502 if (authority.codeUnitAt(hostEnd) == _COLON) { 562 if (authority.codeUnitAt(hostEnd) == _COLON) {
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after
758 * The returned map is unmodifiable and will throw [UnsupportedError] on any 818 * The returned map is unmodifiable and will throw [UnsupportedError] on any
759 * calls that would mutate it. 819 * calls that would mutate it.
760 */ 820 */
761 Map<String, String> get queryParameters { 821 Map<String, String> get queryParameters {
762 if (_queryParameters == null) { 822 if (_queryParameters == null) {
763 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); 823 _queryParameters = new UnmodifiableMapView(splitQueryString(query));
764 } 824 }
765 return _queryParameters; 825 return _queryParameters;
766 } 826 }
767 827
768 static String _makeHost(String host) { 828 static int _makePort(int port, String scheme) {
769 if (host == null || host.isEmpty) return host; 829 // Perform scheme specific normalization.
770 if (host.codeUnitAt(0) == _LEFT_BRACKET) { 830 if (port == 80 && scheme == "http") {
771 if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) { 831 return 0;
772 throw new FormatException('Missing end `]` to match `[` in host'); 832 }
833 if (port == 443 && scheme == "https") {
834 return 0;
835 }
836 return port;
837 }
838
839 static String _makeHost(String host, int start, int end) {
840 if (host == null) return null;
841 if (start == end) return "";
842 // Host is an IPv6 address if it starts with '[' or contains a colon.
843 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
844 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
845 _fail(host, start, 'Missing end `]` to match `[` in host');
773 } 846 }
774 parseIPv6Address(host.substring(1, host.length - 1)); 847 parseIPv6Address(host, start + 1, end - 1);
775 return host; 848 return host.substring(start, end);
776 } 849 }
777 for (int i = 0; i < host.length; i++) { 850 // TODO(lrn): skip if too short to be a valid IPv6 address.
851 for (int i = start; i < end; i++) {
778 if (host.codeUnitAt(i) == _COLON) { 852 if (host.codeUnitAt(i) == _COLON) {
779 parseIPv6Address(host); 853 parseIPv6Address(host, start, end);
780 return '[$host]'; 854 return '[$host]';
781 } 855 }
782 } 856 }
783 return host; 857 return _normalizeRegName(host, start, end);
784 } 858 }
785 859
786 static String _makeScheme(String scheme) { 860 static bool _isRegNameChar(int char) {
787 bool isSchemeLowerCharacter(int ch) { 861 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
788 return ch < 128 && 862 }
789 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
790 }
791 863
792 if (scheme == null) return ""; 864 /**
793 bool allLowercase = true; 865 * Validates and does case- and percent-encoding normalization.
794 int length = scheme.length; 866 *
795 for (int i = 0; i < length; i++) { 867 * The [host] must be an RFC3986 "reg-name". It is converted
796 int codeUnit = scheme.codeUnitAt(i); 868 * to lower case, and percent escapes are converted to either
797 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) { 869 * lower case unreserved characters or upper case escapes.
798 // First code unit must be an alphabetic character. 870 */
799 throw new ArgumentError('Illegal scheme: $scheme'); 871 static String _normalizeRegName(String host, int start, int end) {
800 } 872 StringBuffer buffer;
801 if (!isSchemeLowerCharacter(codeUnit)) { 873 int sectionStart = start;
802 if (_isSchemeCharacter(codeUnit)) { 874 int index = start;
803 allLowercase = false; 875 // Whether all characters between sectionStart and index are normalized,
804 } else { 876 bool isNormalized = true;
805 throw new ArgumentError('Illegal scheme: $scheme'); 877
878 while (index < end) {
879 int char = host.codeUnitAt(index);
880 if (char == _PERCENT) {
881 // The _regNameTable contains "%", so we check that first.
882 String replacement = _normalizeEscape(host, index, true);
883 if (replacement == null && isNormalized) {
884 index += 3;
885 continue;
806 } 886 }
887 if (buffer == null) buffer = new StringBuffer();
888 String slice = host.substring(sectionStart, index);
889 if (!isNormalized) slice = slice.toLowerCase();
890 buffer.write(slice);
891 int sourceLength = 3;
892 if (replacement == null) {
893 replacement = host.substring(index, index + 3);
894 } else if (replacement == "%") {
895 replacement = "%25";
896 sourceLength = 1;
897 }
898 buffer.write(replacement);
899 index += sourceLength;
900 sectionStart = index;
901 isNormalized = true;
902 } else if (_isRegNameChar(char)) {
903 if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
904 // Put initial slice in buffer and continue in non-normalized mode
905 if (buffer == null) buffer = new StringBuffer();
906 if (sectionStart < index) {
907 buffer.write(host.substring(sectionStart, index));
908 sectionStart = index;
909 }
910 isNormalized = false;
911 }
912 index++;
913 } else if (_isGeneralDelimiter(char)) {
914 _fail(host, index, "Invalid character");
915 } else {
916 int sourceLength = 1;
917 if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
918 int tail = host.codeUnitAt(index + 1);
919 if ((tail & 0xFC00) == 0xDC00) {
920 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
921 sourceLength = 2;
922 }
923 }
924 if (buffer == null) buffer = new StringBuffer();
925 String slice = host.substring(sectionStart, index);
926 if (!isNormalized) slice = slice.toLowerCase();
927 buffer.write(slice);
928 buffer.write(_escapeChar(char));
929 index += sourceLength;
930 sectionStart = index;
807 } 931 }
808 } 932 }
809 933 if (buffer == null) return host.substring(start, end);
810 return allLowercase ? scheme : scheme.toLowerCase(); 934 if (sectionStart < end) {
935 String slice = host.substring(sectionStart, end);
936 if (!isNormalized) slice = slice.toLowerCase();
937 buffer.write(slice);
938 }
939 return buffer.toString();
811 } 940 }
812 941
813 String _makePath(String path, Iterable<String> pathSegments) { 942 /**
943 * Validates scheme characters and does case-normalization.
944 *
945 * Schemes are converted to lower case. They cannot contain escapes.
946 */
947 static String _makeScheme(String scheme, int end) {
948 if (end == 0) return "";
949 int char = scheme.codeUnitAt(0);
950 if (!_isAlphabeticCharacter(char)) {
951 _fail(scheme, 0, "Scheme not starting with alphabetic character");
952 }
953 bool allLowercase = char >= _LOWER_CASE_A;
954 for (int i = 0; i < end; i++) {
955 int codeUnit = scheme.codeUnitAt(i);
956 if (!_isSchemeCharacter(codeUnit)) {
957 _fail(scheme, i, "Illegal scheme character");
958 }
959 if (_LOWER_CASE_A <= char && _LOWER_CASE_Z >= char) {
960 allLowercase = false;
961 }
962 }
963 scheme = scheme.substring(0, end);
964 if (!allLowercase) scheme = scheme.toLowerCase();
965 return scheme;
966 }
967
968 static String _makeUserInfo(String userInfo, int start, int end) {
969 if (userInfo == null) return "null";
970 return _normalize(userInfo, start, end, _userinfoTable);
971 }
972
973 static String _makePath(String path, int start, int end,
974 Iterable<String> pathSegments,
975 bool ensureLeadingSlash) {
814 if (path == null && pathSegments == null) return ""; 976 if (path == null && pathSegments == null) return "";
815 if (path != null && pathSegments != null) { 977 if (path != null && pathSegments != null) {
816 throw new ArgumentError('Both path and pathSegments specified'); 978 throw new ArgumentError('Both path and pathSegments specified');
817 } 979 }
818 var result; 980 var result;
819 if (path != null) { 981 if (path != null) {
820 result = _normalize(path); 982 result = _normalize(path, start, end, _pathCharOrSlashTable);
821 } else { 983 } else {
822 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); 984 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
823 } 985 }
824 if ((hasAuthority || (scheme == "file")) && 986 if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) {
825 result.isNotEmpty && !result.startsWith("/")) {
826 return "/$result"; 987 return "/$result";
827 } 988 }
828 return result; 989 return result;
829 } 990 }
830 991
831 static String _makeQuery(String query, Map<String, String> queryParameters) { 992 static String _makeQuery(String query, int start, int end,
993 Map<String, String> queryParameters) {
832 if (query == null && queryParameters == null) return ""; 994 if (query == null && queryParameters == null) return "";
833 if (query != null && queryParameters != null) { 995 if (query != null && queryParameters != null) {
834 throw new ArgumentError('Both query and queryParameters specified'); 996 throw new ArgumentError('Both query and queryParameters specified');
835 } 997 }
836 if (query != null) return _normalize(query); 998 if (query != null) return _normalize(query, start, end, _queryCharTable);
837 999
838 var result = new StringBuffer(); 1000 var result = new StringBuffer();
839 var first = true; 1001 var first = true;
840 queryParameters.forEach((key, value) { 1002 queryParameters.forEach((key, value) {
841 if (!first) { 1003 if (!first) {
842 result.write("&"); 1004 result.write("&");
843 } 1005 }
844 first = false; 1006 first = false;
845 result.write(Uri.encodeQueryComponent(key)); 1007 result.write(Uri.encodeQueryComponent(key));
846 if (value != null && !value.isEmpty) { 1008 if (value != null && !value.isEmpty) {
847 result.write("="); 1009 result.write("=");
848 result.write(Uri.encodeQueryComponent(value)); 1010 result.write(Uri.encodeQueryComponent(value));
849 } 1011 }
850 }); 1012 });
851 return result.toString(); 1013 return result.toString();
852 } 1014 }
853 1015
854 static String _makeFragment(String fragment) { 1016 static String _makeFragment(String fragment, int start, int end) {
855 if (fragment == null) return ""; 1017 if (fragment == null) return "";
856 return _normalize(fragment); 1018 return _normalize(fragment, start, end, _queryCharTable);
857 } 1019 }
858 1020
859 static String _normalize(String component) { 1021 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
860 int index = component.indexOf('%');
861 if (index < 0) return component;
862 1022
863 bool isNormalizedHexDigit(int digit) { 1023 static bool _isHexDigit(int char) {
864 return (_ZERO <= digit && digit <= _NINE) || 1024 if (_NINE >= char) return _ZERO <= char;
865 (_UPPER_CASE_A <= digit && digit <= _UPPER_CASE_F); 1025 char |= 0x20;
1026 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
1027 }
1028
1029 static int _hexValue(int char) {
1030 assert(_isHexDigit(char));
1031 if (_NINE >= char) return char - _ZERO;
1032 char |= 0x20;
1033 return char - (_LOWER_CASE_A - 10);
1034 }
1035
1036 /**
1037 * Performs RFC 3986 Percent-Encoding Normalization.
1038 *
1039 * Returns a replacement string that should be replace the original escape.
1040 * Returns null if no replacement is necessary because the escape is
1041 * not for an unreserved character and is already non-lower-case.
1042 *
1043 * Returns "%" if the escape is invalid (not two valid hex digits following
1044 * the percent sign). The calling code should replace the percent
1045 * sign with "%25", but leave the following two characters unmodified.
1046 *
1047 * If [lowerCase] is true, a single character returned is always lower case,
1048 */
1049 static String _normalizeEscape(String source, int index, bool lowerCase) {
1050 assert(source.codeUnitAt(index) == _PERCENT);
1051 if (index + 2 >= source.length) {
1052 return "%"; // Marks the escape as invalid.
866 } 1053 }
1054 int firstDigit = source.codeUnitAt(index + 1);
1055 int secondDigit = source.codeUnitAt(index + 2);
1056 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
1057 return "%"; // Marks the escape as invalid.
1058 }
1059 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit);
1060 if (_isUnreservedChar(value)) {
1061 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
1062 value |= 0x20;
1063 }
1064 return new String.fromCharCode(value);
1065 }
1066 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
1067 // Either digit is lower case.
1068 return source.substring(index, index + 3).toUpperCase();
1069 }
1070 // Escape is retained, and is already non-lower case, so return null to
1071 // represent "no replacement necessary".
1072 return null;
1073 }
867 1074
868 bool isLowerCaseHexDigit(int digit) { 1075 static bool _isUnreservedChar(int ch) {
869 return _LOWER_CASE_A <= digit && digit <= _LOWER_CASE_F; 1076 return ch < 127 &&
870 } 1077 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1078 }
871 1079
872 bool isUnreserved(int ch) { 1080 static String _escapeChar(char) {
873 return ch < 128 && 1081 const hexDigits = "0123456789ABCDEF";
874 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1082 List codeUnits;
875 } 1083 if (char < 0x80) {
876 1084 // ASCII, a single percent encoded sequence.
877 int normalizeHexDigit(int index) { 1085 codeUnits = new List(3);
878 var codeUnit = component.codeUnitAt(index); 1086 codeUnits[0] = _PERCENT;
879 if (isLowerCaseHexDigit(codeUnit)) { 1087 codeUnits[1] = hexDigits.codeUnitAt(char >> 4);
880 return codeUnit - 0x20; 1088 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf);
881 } else if (!isNormalizedHexDigit(codeUnit)) { 1089 } else {
882 throw new ArgumentError("Invalid URI component: $component"); 1090 // Do UTF-8 encoding of character, then percent encode bytes.
883 } else { 1091 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
884 return codeUnit; 1092 int encodedBytes = 2;
1093 if (char > 0x7ff) {
1094 flag = 0xe0;
1095 encodedBytes = 3;
1096 if (char > 0xffff) {
Søren Gjesse 2014/06/19 07:39:43 Are we sure that we cannot go beyond 4-byte encodi
Lasse Reichstein Nielsen 2014/06/19 08:44:58 Yes. We get at most 21 bits from a surrogate pair.
1097 encodedBytes = 4;
1098 flag = 0xf0;
1099 }
1100 }
1101 codeUnits = new List(3 * encodedBytes);
1102 int index = 0;
1103 while (--encodedBytes >= 0) {
1104 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
1105 codeUnits[index] = _PERCENT;
1106 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4);
1107 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf);
1108 index += 3;
1109 flag = 0x80; // Following bytes have only high bit set.
885 } 1110 }
886 } 1111 }
1112 return new String.fromCharCodes(codeUnits);
1113 }
887 1114
888 int decodeHexDigitPair(int index) { 1115 /**
889 int byte = 0; 1116 * Runs through component checking that each character is valid and
890 for (int i = 0; i < 2; i++) { 1117 * normalize percent escapes.
891 var codeUnit = component.codeUnitAt(index + i); 1118 *
892 if (_ZERO <= codeUnit && codeUnit <= _NINE) { 1119 * Uses [charTable] to check if a non-`%` character is allowed.
893 byte = byte * 16 + codeUnit - _ZERO; 1120 * Each `%` character must be followed by two hex digits.
1121 * If the hex-digits are lower case letters, they are converted to
1122 * upper case.
1123 */
1124 static String _normalize(String component, int start, int end,
1125 List<int> charTable) {
1126 StringBuffer buffer;
1127 int sectionStart = start;
1128 int index = start;
1129 // Loop while characters are valid and escapes correct and upper-case.
1130 while (index < end) {
1131 int char = component.codeUnitAt(index);
1132 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
1133 index++;
1134 } else {
1135 String replacement;
1136 int sourceLength;
1137 if (char == _PERCENT) {
1138 replacement = _normalizeEscape(component, index, false);
1139 // Returns null if we should keep the existing escape.
1140 if (replacement == null) {
1141 index += 3;
1142 continue;
1143 }
1144 // Returns "%" if we should escape the existing percent.
1145 if ("%" == replacement) {
1146 replacement = "%25";
1147 sourceLength = 1;
1148 } else {
1149 sourceLength = 3;
1150 }
1151 } else if (_isGeneralDelimiter(char)) {
1152 _fail(component, index, "Invalid character");
894 } else { 1153 } else {
895 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66). 1154 sourceLength = 1;
896 codeUnit |= 0x20; 1155 if ((char & 0xFC00) == 0xD800) {
897 if (_LOWER_CASE_A <= codeUnit && 1156 // Possible lead surrogate.
898 codeUnit <= _LOWER_CASE_F) { 1157 if (index + 1 < end) {
899 byte = byte * 16 + codeUnit - _LOWER_CASE_A + 10; 1158 int tail = component.codeUnitAt(index + 1);
900 } else { 1159 if ((tail & 0xFC00) == 0xDC00) {
901 throw new ArgumentError( 1160 // Tail surrogat.
902 "Invalid percent-encoding in URI component: $component"); 1161 sourceLength = 2;
1162 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1163 }
1164 }
903 } 1165 }
1166 replacement = _escapeChar(char);
904 } 1167 }
905 } 1168 if (buffer == null) buffer = new StringBuffer();
906 return byte; 1169 buffer.write(component.substring(sectionStart, index));
907 } 1170 buffer.write(replacement);
908 1171 index += sourceLength;
909 // Start building the normalized component string. 1172 sectionStart = index;
910 StringBuffer result;
911 int length = component.length;
912 int prevIndex = 0;
913
914 // Copy a part of the component string to the result.
915 void fillResult() {
916 if (result == null) {
917 assert(prevIndex == 0);
918 result = new StringBuffer(component.substring(prevIndex, index));
919 } else {
920 result.write(component.substring(prevIndex, index));
921 } 1173 }
922 } 1174 }
923 1175 if (buffer == null) {
924 while (index < length) { 1176 // Makes no copy if start == 0 and end == component.length.
925 // Normalize percent-encoding to uppercase and don't encode 1177 return component.substring(start, end);
926 // unreserved characters.
927 assert(component.codeUnitAt(index) == _PERCENT);
928 if (length < index + 2) {
929 throw new ArgumentError(
930 "Invalid percent-encoding in URI component: $component");
931 }
932
933 var codeUnit1 = component.codeUnitAt(index + 1);
934 var codeUnit2 = component.codeUnitAt(index + 2);
935 var decodedCodeUnit = decodeHexDigitPair(index + 1);
936 if (isNormalizedHexDigit(codeUnit1) &&
937 isNormalizedHexDigit(codeUnit2) &&
938 !isUnreserved(decodedCodeUnit)) {
939 index += 3;
940 } else {
941 fillResult();
942 if (isUnreserved(decodedCodeUnit)) {
943 result.writeCharCode(decodedCodeUnit);
944 } else {
945 result.write("%");
946 result.writeCharCode(normalizeHexDigit(index + 1));
947 result.writeCharCode(normalizeHexDigit(index + 2));
948 }
949 index += 3;
950 prevIndex = index;
951 }
952 int next = component.indexOf('%', index);
953 if (next >= index) {
954 index = next;
955 } else {
956 index = length;
957 }
958 } 1178 }
959 if (result == null) return component; 1179 if (sectionStart < end) {
960 1180 buffer.write(component.substring(sectionStart, end));
961 if (result != null && prevIndex != index) fillResult(); 1181 }
962 assert(index == length); 1182 return buffer.toString();
963
964 return result.toString();
965 } 1183 }
966 1184
967 static bool _isSchemeCharacter(int ch) { 1185 static bool _isSchemeCharacter(int ch) {
968 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1186 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
969 } 1187 }
970 1188
1189 static bool _isGeneralDelimiter(int ch) {
1190 return ch <= 64 &&
1191 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1192 }
971 1193
972 /** 1194 /**
973 * Returns whether the URI is absolute. 1195 * Returns whether the URI is absolute.
974 */ 1196 */
975 bool get isAbsolute => scheme != "" && fragment == ""; 1197 bool get isAbsolute => scheme != "" && fragment == "";
976 1198
977 String _merge(String base, String reference) { 1199 String _merge(String base, String reference) {
978 if (base == "") return "/$reference"; 1200 if (base == "") return "/$reference";
979 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; 1201 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference";
980 } 1202 }
(...skipping 477 matching lines...) Expand 10 before | Expand all | Expand 10 after
1458 .toList(); 1680 .toList();
1459 } 1681 }
1460 1682
1461 /** 1683 /**
1462 * Parse the [host] as an IP version 6 (IPv6) address, returning the address 1684 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
1463 * as a list of 16 bytes in network byte order (big endian). 1685 * as a list of 16 bytes in network byte order (big endian).
1464 * 1686 *
1465 * Throws a [FormatException] if [host] is not a valid IPv6 address 1687 * Throws a [FormatException] if [host] is not a valid IPv6 address
1466 * representation. 1688 * representation.
1467 * 1689 *
1690 * Acts on the substring from [start] to [end]. If [end] is omitted, it
1691 * defaults ot the end of the string.
1692 *
1468 * Some examples of IPv6 addresses: 1693 * Some examples of IPv6 addresses:
1469 * * ::1 1694 * * ::1
1470 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 1695 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1471 * * 3ffe:2a00:100:7031::1 1696 * * 3ffe:2a00:100:7031::1
1472 * * ::FFFF:129.144.52.38 1697 * * ::FFFF:129.144.52.38
1473 * * 2010:836B:4179::836B:4179 1698 * * 2010:836B:4179::836B:4179
1474 */ 1699 */
1475 static List<int> parseIPv6Address(String host) { 1700 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
1701 if (end == null) end = host.length;
1476 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated 1702 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
1477 // by `:`'s, with the following exceptions: 1703 // by `:`'s, with the following exceptions:
1478 // 1704 //
1479 // - One (and only one) wildcard (`::`) may be present, representing a fill 1705 // - One (and only one) wildcard (`::`) may be present, representing a fill
1480 // of 0's. The IPv6 `::` is thus 16 bytes of `0`. 1706 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1481 // - The last two parts may be replaced by an IPv4 address. 1707 // - The last two parts may be replaced by an IPv4 address.
1482 void error(String msg) { 1708 void error(String msg) {
1483 throw new FormatException('Illegal IPv6 address, $msg'); 1709 throw new FormatException('Illegal IPv6 address, $msg');
1484 } 1710 }
1485 int parseHex(int start, int end) { 1711 int parseHex(int start, int end) {
1486 if (end - start > 4) { 1712 if (end - start > 4) {
1487 error('an IPv6 part can only contain a maximum of 4 hex digits'); 1713 error('an IPv6 part can only contain a maximum of 4 hex digits');
1488 } 1714 }
1489 int value = int.parse(host.substring(start, end), radix: 16); 1715 int value = int.parse(host.substring(start, end), radix: 16);
1490 if (value < 0 || value > (1 << 16) - 1) { 1716 if (value < 0 || value > (1 << 16) - 1) {
1491 error('each part must be in the range of `0x0..0xFFFF`'); 1717 error('each part must be in the range of `0x0..0xFFFF`');
1492 } 1718 }
1493 return value; 1719 return value;
1494 } 1720 }
1495 if (host.length < 2) error('address is too short'); 1721 if (host.length < 2) error('address is too short');
1496 List<int> parts = []; 1722 List<int> parts = [];
1497 bool wildcardSeen = false; 1723 bool wildcardSeen = false;
1498 int partStart = 0; 1724 int partStart = start;
1499 // Parse all parts, except a potential last one. 1725 // Parse all parts, except a potential last one.
1500 for (int i = 0; i < host.length; i++) { 1726 for (int i = start; i < end; i++) {
1501 if (host.codeUnitAt(i) == _COLON) { 1727 if (host.codeUnitAt(i) == _COLON) {
1502 if (i == 0) { 1728 if (i == start) {
1503 // If we see a `:` in the beginning, expect wildcard. 1729 // If we see a `:` in the beginning, expect wildcard.
1504 i++; 1730 i++;
1505 if (host.codeUnitAt(i) != _COLON) { 1731 if (host.codeUnitAt(i) != _COLON) {
1506 error('invalid start colon.'); 1732 error('invalid start colon.');
1507 } 1733 }
1508 partStart = i; 1734 partStart = i;
1509 } 1735 }
1510 if (i == partStart) { 1736 if (i == partStart) {
1511 // Wildcard. We only allow one. 1737 // Wildcard. We only allow one.
1512 if (wildcardSeen) { 1738 if (wildcardSeen) {
1513 error('only one wildcard `::` is allowed'); 1739 error('only one wildcard `::` is allowed');
1514 } 1740 }
1515 wildcardSeen = true; 1741 wildcardSeen = true;
1516 parts.add(-1); 1742 parts.add(-1);
1517 } else { 1743 } else {
1518 // Found a single colon. Parse [partStart..i] as a hex entry. 1744 // Found a single colon. Parse [partStart..i] as a hex entry.
1519 parts.add(parseHex(partStart, i)); 1745 parts.add(parseHex(partStart, i));
1520 } 1746 }
1521 partStart = i + 1; 1747 partStart = i + 1;
1522 } 1748 }
1523 } 1749 }
1524 if (parts.length == 0) error('too few parts'); 1750 if (parts.length == 0) error('too few parts');
1525 bool atEnd = partStart == host.length; 1751 bool atEnd = (partStart == end);
1526 bool isLastWildcard = parts.last == -1; 1752 bool isLastWildcard = (parts.last == -1);
1527 if (atEnd && !isLastWildcard) { 1753 if (atEnd && !isLastWildcard) {
1528 error('expected a part after last `:`'); 1754 error('expected a part after last `:`');
1529 } 1755 }
1530 if (!atEnd) { 1756 if (!atEnd) {
1531 try { 1757 try {
1532 parts.add(parseHex(partStart, host.length)); 1758 parts.add(parseHex(partStart, end));
1533 } catch (e) { 1759 } catch (e) {
1534 // Failed to parse the last chunk as hex. Try IPv4. 1760 // Failed to parse the last chunk as hex. Try IPv4.
1535 try { 1761 try {
1536 List<int> last = parseIPv4Address(host.substring(partStart)); 1762 List<int> last = parseIPv4Address(host.substring(partStart, end));
1537 parts.add(last[0] << 8 | last[1]); 1763 parts.add(last[0] << 8 | last[1]);
1538 parts.add(last[2] << 8 | last[3]); 1764 parts.add(last[2] << 8 | last[3]);
1539 } catch (e) { 1765 } catch (e) {
1540 error('invalid end of IPv6 address.'); 1766 error('invalid end of IPv6 address.');
1541 } 1767 }
1542 } 1768 }
1543 } 1769 }
1544 if (wildcardSeen) { 1770 if (wildcardSeen) {
1545 if (parts.length > 7) { 1771 if (parts.length > 7) {
1546 error('an address with a wildcard must have less than 7 parts'); 1772 error('an address with a wildcard must have less than 7 parts');
1547 } 1773 }
1548 } else if (parts.length != 8) { 1774 } else if (parts.length != 8) {
1549 error('an address without a wildcard must contain exactly 8 parts'); 1775 error('an address without a wildcard must contain exactly 8 parts');
1550 } 1776 }
1551 // TODO(ajohnsen): Consider using Uint8List. 1777 // TODO(ajohnsen): Consider using Uint8List.
1552 return parts 1778 List bytes = new List<int>(16);
1553 .expand((value) { 1779 for (int i = 0, index = 0; i < parts.length; i++) {
1554 if (value == -1) { 1780 int value = parts[i];
1555 return new List.filled((9 - parts.length) * 2, 0); 1781 if (value == -1) {
1556 } else { 1782 int wildCardLength = 9 - parts.length;
1557 return [(value >> 8) & 0xFF, value & 0xFF]; 1783 for (int j = 0; j < wildCardLength; j++) {
1558 } 1784 bytes[index] = 0;
1559 }) 1785 bytes[index + 1] = 0;
1560 .toList(); 1786 index += 2;
1787 }
1788 } else {
1789 bytes[index] = value >> 8;
1790 bytes[index + 1] = value & 0xff;
1791 index += 2;
1792 }
1793 }
1794 return bytes;
1561 } 1795 }
1562 1796
1563 // Frequently used character codes. 1797 // Frequently used character codes.
1564 static const int _SPACE = 0x20; 1798 static const int _SPACE = 0x20;
1565 static const int _DOUBLE_QUOTE = 0x22; 1799 static const int _DOUBLE_QUOTE = 0x22;
1566 static const int _NUMBER_SIGN = 0x23; 1800 static const int _NUMBER_SIGN = 0x23;
1567 static const int _PERCENT = 0x25; 1801 static const int _PERCENT = 0x25;
1568 static const int _ASTERISK = 0x2A; 1802 static const int _ASTERISK = 0x2A;
1569 static const int _PLUS = 0x2B; 1803 static const int _PLUS = 0x2B;
1570 static const int _SLASH = 0x2F; 1804 static const int _SLASH = 0x2F;
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
1813 0x2bff, // 0x30 - 0x3f 1111111111010100 2047 0x2bff, // 0x30 - 0x3f 1111111111010100
1814 // ABCDEFGHIJKLMNO 2048 // ABCDEFGHIJKLMNO
1815 0xfffe, // 0x40 - 0x4f 0111111111111111 2049 0xfffe, // 0x40 - 0x4f 0111111111111111
1816 // PQRSTUVWXYZ _ 2050 // PQRSTUVWXYZ _
1817 0x87ff, // 0x50 - 0x5f 1111111111100001 2051 0x87ff, // 0x50 - 0x5f 1111111111100001
1818 // abcdefghijklmno 2052 // abcdefghijklmno
1819 0xfffe, // 0x60 - 0x6f 0111111111111111 2053 0xfffe, // 0x60 - 0x6f 0111111111111111
1820 // pqrstuvwxyz ~ 2054 // pqrstuvwxyz ~
1821 0x47ff]; // 0x70 - 0x7f 1111111111100010 2055 0x47ff]; // 0x70 - 0x7f 1111111111100010
1822 2056
2057 // General delimiter characters, RFC 3986 section 2.2.
2058 // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
2059 //
2060 static const _genDelimitersTable = const [
2061 // LSB MSB
2062 // | |
2063 0x0000, // 0x00 - 0x0f 0000000000000000
2064 0x0000, // 0x10 - 0x1f 0000000000000000
2065 // # /
2066 0x8008, // 0x20 - 0x2f 0001000000000001
2067 // : [ ]?
2068 0xe400, // 0x30 - 0x3f 0000000000101011
2069 // @
2070 0x0001, // 0x40 - 0x4f 1000000000000000
2071 //
2072 0x0000, // 0x50 - 0x5f 0000000000000000
2073 //
2074 0x0000, // 0x60 - 0x6f 0000000000000000
2075 //
2076 0x0000]; // 0x70 - 0x7f 0000000000000000
2077
2078 // Characters allowed in the userinfo as of RFC 3986.
2079 // RFC 3986 Apendix A
2080 // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
2081 static const _userinfoTable = const [
2082 // LSB MSB
2083 // | |
2084 0x0000, // 0x00 - 0x0f 0000000000000000
2085 0x0000, // 0x10 - 0x1f 0000000000000000
2086 // ! $ &'()*+,-.
2087 0x7fd2, // 0x20 - 0x2f 0100101111111110
2088 // 0123456789:; =
2089 0x2fff, // 0x30 - 0x3f 1111111111110100
2090 // ABCDEFGHIJKLMNO
2091 0xfffe, // 0x40 - 0x4f 0111111111111111
2092 // PQRSTUVWXYZ _
2093 0x87ff, // 0x50 - 0x5f 1111111111100001
2094 // abcdefghijklmno
2095 0xfffe, // 0x60 - 0x6f 0111111111111111
2096 // pqrstuvwxyz ~
2097 0x47ff]; // 0x70 - 0x7f 1111111111100010
2098
1823 // Characters allowed in the reg-name as of RFC 3986. 2099 // Characters allowed in the reg-name as of RFC 3986.
1824 // RFC 3986 Apendix A 2100 // RFC 3986 Apendix A
1825 // reg-name = *( unreserved / pct-encoded / sub-delims ) 2101 // reg-name = *( unreserved / pct-encoded / sub-delims )
1826 static const _regNameTable = const [ 2102 static const _regNameTable = const [
1827 // LSB MSB 2103 // LSB MSB
1828 // | | 2104 // | |
1829 0x0000, // 0x00 - 0x0f 0000000000000000 2105 0x0000, // 0x00 - 0x0f 0000000000000000
1830 0x0000, // 0x10 - 0x1f 0000000000000000 2106 0x0000, // 0x10 - 0x1f 0000000000000000
1831 // ! $%&'()*+,-. 2107 // ! $%&'()*+,-.
1832 0x7ff2, // 0x20 - 0x2f 0100111111111110 2108 0x7ff2, // 0x20 - 0x2f 0100111111111110
(...skipping 22 matching lines...) Expand all
1855 0x2fff, // 0x30 - 0x3f 1111111111110100 2131 0x2fff, // 0x30 - 0x3f 1111111111110100
1856 // @ABCDEFGHIJKLMNO 2132 // @ABCDEFGHIJKLMNO
1857 0xffff, // 0x40 - 0x4f 1111111111111111 2133 0xffff, // 0x40 - 0x4f 1111111111111111
1858 // PQRSTUVWXYZ _ 2134 // PQRSTUVWXYZ _
1859 0x87ff, // 0x50 - 0x5f 1111111111100001 2135 0x87ff, // 0x50 - 0x5f 1111111111100001
1860 // abcdefghijklmno 2136 // abcdefghijklmno
1861 0xfffe, // 0x60 - 0x6f 0111111111111111 2137 0xfffe, // 0x60 - 0x6f 0111111111111111
1862 // pqrstuvwxyz ~ 2138 // pqrstuvwxyz ~
1863 0x47ff]; // 0x70 - 0x7f 1111111111100010 2139 0x47ff]; // 0x70 - 0x7f 1111111111100010
1864 2140
2141 // Characters allowed in the path as of RFC 3986.
2142 // RFC 3986 section 3.3 *and* slash.
2143 static const _pathCharOrSlashTable = const [
2144 // LSB MSB
2145 // | |
2146 0x0000, // 0x00 - 0x0f 0000000000000000
2147 0x0000, // 0x10 - 0x1f 0000000000000000
2148 // ! $ &'()*+,-./
2149 0xffd2, // 0x20 - 0x2f 0100101111111111
2150 // 0123456789:; =
2151 0x2fff, // 0x30 - 0x3f 1111111111110100
2152 // @ABCDEFGHIJKLMNO
2153 0xffff, // 0x40 - 0x4f 1111111111111111
2154
2155 // PQRSTUVWXYZ _
2156 0x87ff, // 0x50 - 0x5f 1111111111100001
2157 // abcdefghijklmno
2158 0xfffe, // 0x60 - 0x6f 0111111111111111
2159 // pqrstuvwxyz ~
2160 0x47ff]; // 0x70 - 0x7f 1111111111100010
2161
1865 // Characters allowed in the query as of RFC 3986. 2162 // Characters allowed in the query as of RFC 3986.
1866 // RFC 3986 section 3.4. 2163 // RFC 3986 section 3.4.
1867 // query = *( pchar / "/" / "?" ) 2164 // query = *( pchar / "/" / "?" )
1868 static const _queryCharTable = const [ 2165 static const _queryCharTable = const [
1869 // LSB MSB 2166 // LSB MSB
1870 // | | 2167 // | |
1871 0x0000, // 0x00 - 0x0f 0000000000000000 2168 0x0000, // 0x00 - 0x0f 0000000000000000
1872 0x0000, // 0x10 - 0x1f 0000000000000000 2169 0x0000, // 0x10 - 0x1f 0000000000000000
1873 // ! $ &'()*+,-./ 2170 // ! $ &'()*+,-./
1874 0xffd2, // 0x20 - 0x2f 0100101111111111 2171 0xffd2, // 0x20 - 0x2f 0100101111111111
1875 // 0123456789:; = ? 2172 // 0123456789:; = ?
1876 0xafff, // 0x30 - 0x3f 1111111111110101 2173 0xafff, // 0x30 - 0x3f 1111111111110101
1877 // @ABCDEFGHIJKLMNO 2174 // @ABCDEFGHIJKLMNO
1878 0xffff, // 0x40 - 0x4f 1111111111111111 2175 0xffff, // 0x40 - 0x4f 1111111111111111
1879 // PQRSTUVWXYZ _ 2176 // PQRSTUVWXYZ _
1880 0x87ff, // 0x50 - 0x5f 1111111111100001 2177 0x87ff, // 0x50 - 0x5f 1111111111100001
1881 // abcdefghijklmno 2178 // abcdefghijklmno
1882 0xfffe, // 0x60 - 0x6f 0111111111111111 2179 0xfffe, // 0x60 - 0x6f 0111111111111111
1883 // pqrstuvwxyz ~ 2180 // pqrstuvwxyz ~
1884 0x47ff]; // 0x70 - 0x7f 1111111111100010 2181 0x47ff]; // 0x70 - 0x7f 1111111111100010
1885 } 2182 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698