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

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: Completed merge, updated tests. 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 158 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
179 int ipV6Address(int index) { 179 String scheme = "";
180 // IPv6. Skip to ']'. 180 String path;
181 index = uri.indexOf(']', index); 181 String userinfo = "";
182 if (index == -1) { 182 String host = "";
183 throw new FormatException("Bad end of IPv6 host"); 183 int port = 0;
184 } 184 String query = "";
185 return index + 1; 185 String fragment = "";
186 } 186
187
188 int length = uri.length;
189 int index = 0; 187 int index = 0;
190 188 int pathStart = 0;
191 int schemeEndIndex = 0; 189 int char = -1;
192 190
193 if (length == 0) { 191 void parseAuth() {
194 return new Uri(); 192 if (index == uri.length) {
195 } 193 char = -1;
196 194 return;
197 if (uri.codeUnitAt(0) != _SLASH) { 195 }
198 // Can be scheme. 196 int authStart = index;
199 while (index < length) { 197 int lastColon = -1;
200 // Look for ':'. If found, continue from the post of ':'. If not (end 198 int lastAt = -1;
201 // reached or invalid scheme char found) back up one char, and continue 199 char = uri.codeUnitAt(index);
202 // to path. 200 authEnd: {
203 // Note that scheme-chars is contained in path-chars. 201 while (index < uri.length) {
204 int codeUnit = uri.codeUnitAt(index++); 202 char = uri.codeUnitAt(index);
205 if (!_isSchemeCharacter(codeUnit)) { 203 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
206 if (codeUnit == _COLON) { 204 break authEnd;
Anders Johnsen 2014/06/17 13:07:03 Consider alternative without authEnd block.
Lasse Reichstein Nielsen 2014/06/17 14:31:45 Considered. See suggestion.
207 schemeEndIndex = index; 205 }
208 } else { 206 if (char == _AT_SIGN) {
209 // Back up one char, since we met an invalid scheme char. 207 lastAt = index;
210 index--; 208 lastColon = -1;
211 } 209 } else if (char == _COLON) {
212 break; 210 lastColon = index;
213 } 211 } else if (char == _LEFT_BRACKET) {
214 } 212 lastColon = -1;
215 } 213 int endBracket = uri.indexOf(']', index + 1);
216 214 if (endBracket == -1) {
217 int userInfoEndIndex = -1; 215 index = uri.length;
218 int portIndex = -1;
219 int authorityEndIndex = schemeEndIndex;
220 // If we see '//', there must be an authority.
221 if (authorityEndIndex == index &&
222 authorityEndIndex + 1 < length &&
223 uri.codeUnitAt(authorityEndIndex) == _SLASH &&
224 uri.codeUnitAt(authorityEndIndex + 1) == _SLASH) {
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; 216 break;
217 } else {
218 index = endBracket;
243 } 219 }
244 portIndex = -1; 220 }
245 authorityEndIndex = userInfoEndIndex + 1; 221 index++;
246 // Now it can only be host:port. 222 }
247 while (authorityEndIndex < length) { 223 char = -1;
248 int codeUnit = uri.codeUnitAt(authorityEndIndex++); 224 }
249 if (!isRegName(codeUnit)) { 225 int hostStart = authStart;
250 if (codeUnit == _LEFT_BRACKET) { 226 int hostEnd = index;
251 authorityEndIndex = ipV6Address(authorityEndIndex); 227 if (lastAt >= 0) {
252 } else if (codeUnit == _COLON) { 228 userinfo = _makeUserInfo(uri, authStart, lastAt);
253 if (portIndex != -1) { 229 hostStart = lastAt + 1;
254 throw new FormatException("Double port in host"); 230 }
255 } 231 if (lastColon >= 0) {
256 portIndex = authorityEndIndex; 232 if (lastColon + 1 == index) {
257 } else { 233 _fail(uri, index, "Invalid port number");
258 authorityEndIndex--; 234 }
259 break; 235 int portNumber = 0;
260 } 236 for (int i = lastColon + 1; i < index; i++) {
237 int digit = uri.codeUnitAt(i);
238 if (_ZERO > digit || _NINE < digit) {
239 _fail(uri, i, "Invalid port number");
240 }
241 portNumber = portNumber * 10 + (digit - _ZERO);
242 }
243 port = _makePort(portNumber, scheme);
244 hostEnd = lastColon;
245 }
246 host = _makeHost(uri, hostStart, hostEnd);
247 if (index < uri.length) {
248 char = uri.codeUnitAt(index);
249 }
250 }
251
252 endPath: {
253 toPath: {
Anders Johnsen 2014/06/17 13:07:03 Consider doing: bool hasPath = true; while(...) {
Lasse Reichstein Nielsen 2014/06/17 14:31:44 Ack, my precious flow control!!! See alternative
254 toAuthOpt: {
255 while (index < uri.length) {
256 char = uri.codeUnitAt(index);
257 if (char == _QUESTION || char == _NUMBER_SIGN) break endPath;
258 if (char == _SLASH) {
259 if (index == 0) break toAuthOpt;
260 break toPath;
261 }
262 if (char == _COLON) {
263 if (index == 0) _fail(uri, 0, "Invalid empty scheme");
264 scheme = _makeScheme(uri, 0, index);
265 index++;
266 pathStart = index;
267 if (index == uri.length) {
268 char = -1;
269 break endPath;
261 } 270 }
271 char = uri.codeUnitAt(index);
272 if (char == _QUESTION || char == _NUMBER_SIGN) break endPath;
273 if (char == _SLASH) break toAuthOpt;
274 break toPath;
262 } 275 }
263 break; 276 index++;
264 } else { 277 }
265 authorityEndIndex--; 278 char = -1;
266 break; 279 break endPath;
267 } 280 } // toAuthOpt.
268 } 281 // Have seen one slash either at start or right after scheme.
269 } 282 // If two slashes, it's an authority, otherwise it's just the path.
283 assert(char == _SLASH);
284 index++;
285 if (index == uri.length) {
286 char = -1;
287 break endPath;
288 }
289 char = uri.codeUnitAt(index);
290 if (char == _QUESTION || char == _NUMBER_SIGN) break endPath;
291 if (char == _SLASH) {
292 index++;
293 parseAuth();
294 pathStart = index;
295 if (char == _QUESTION || char == _NUMBER_SIGN) break endPath;
296 } else {
297 break toPath;
298 }
299 } // toPath
300
301 // Characters from pathStart to index (inclusive) are known
302 // to be part of the path.
303 if (index < uri.length) {
304 while (++index < uri.length) {
305 char = uri.codeUnitAt(index);
306 if (char== _QUESTION || char == _NUMBER_SIGN) break endPath;
Anders Johnsen 2014/06/17 13:07:03 Add missing space.
Lasse Reichstein Nielsen 2014/06/17 14:31:45 Done.
307 }
308 char = -1;
309 }
310 } // endPath
311 bool ensureLeadingSlash = (host != "" || scheme == "file");
312 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
313
314 if (char == _QUESTION) {
315 int numberSignIndex = uri.indexOf('#', index + 1);
316 if (numberSignIndex < 0) {
317 query = _makeQuery(uri, index + 1, uri.length, null);
318 } else {
319 query = _makeQuery(uri, index + 1, numberSignIndex, null);
320 fragment = _makeFragment(uri, numberSignIndex + 1, uri.length);
321 }
322 } else if (char == _NUMBER_SIGN) {
323 fragment = _makeFragment(uri, index + 1, uri.length);
324 }
325 return new Uri._internal(scheme,
326 userinfo,
327 host,
328 port,
329 path,
330 query,
331 fragment);
332 }
333
334 // Report a parse failure.
335 static void _fail(String uri, int index, String message) {
336 // TODO(lrn): Consider adding this to FormatException.
337 if (index == uri.length) {
338 message += " at end of input.";
270 } else { 339 } else {
271 authorityEndIndex = schemeEndIndex; 340 message += " at position $index.\n";
272 } 341 // Pick a slice of uri containing index and, if
273 342 // necessary, truncate the ends to ensure the entire
274 // At path now. 343 // slice fits on one line.
275 int pathEndIndex = authorityEndIndex; 344 int min = 0;
276 while (pathEndIndex < length) { 345 int max = uri.length;
277 int codeUnit = uri.codeUnitAt(pathEndIndex++); 346 String pre = "";
278 if (codeUnit == _QUESTION || codeUnit == _NUMBER_SIGN) { 347 String post = "";
279 pathEndIndex--; 348 if (uri.length > 78) {
280 break; 349 min = index - 10;
281 } 350 if (min < 0) min = 0;
282 } 351 int max = min + 72;
283 352 if (max > uri.length) {
284 // Maybe query. 353 max = uri.length;
285 int queryEndIndex = pathEndIndex; 354 min = max - 72;
286 if (queryEndIndex < length && uri.codeUnitAt(queryEndIndex) == _QUESTION) { 355 }
287 while (queryEndIndex < length) { 356 if (min != 0) pre = "...";
288 int codeUnit = uri.codeUnitAt(queryEndIndex++); 357 if (max != uri.length) post = "...";
289 if (codeUnit == _NUMBER_SIGN) { 358 }
290 queryEndIndex--; 359 // Combine message, slice and a caret pointing to the error index.
291 break; 360 message = "$message$pre${uri.substring(min, max)}$post\n"
292 } 361 "${' ' * (pre.length + index - min)}^";
293 } 362 }
294 } 363 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 } 364 }
342 365
366 /// Internal non-verifying constructor. Only call with validated arguments.
367 Uri._internal(this.scheme,
368 this.userInfo,
369 this._host,
370 this._port,
371 this._path,
372 this.query,
373 this.fragment);
374
343 /** 375 /**
344 * Creates a new URI from its components. 376 * Creates a new URI from its components.
345 * 377 *
346 * Each component is set through a named argument. Any number of 378 * Each component is set through a named argument. Any number of
347 * components can be provided. The default value for the components 379 * components can be provided. The default value for the components
348 * not provided is the empry string, except for [port] which has a 380 * 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 381 * default value of 0. The [path] and [query] components can be set
350 * using two different named arguments. 382 * using two different named arguments.
351 * 383 *
352 * The scheme component is set through [scheme]. The scheme is 384 * 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 411 * [queryParameters]. When [query] is used the provided string is
380 * expected to be fully percent-encoded and is used in its literal 412 * expected to be fully percent-encoded and is used in its literal
381 * form. When [queryParameters] is used the query is built from the 413 * form. When [queryParameters] is used the query is built from the
382 * provided map. Each key and value in the map is percent-encoded 414 * provided map. Each key and value in the map is percent-encoded
383 * and joined using equal and ampersand characters. The 415 * and joined using equal and ampersand characters. The
384 * percent-encoding of the keys and values encodes all characters 416 * percent-encoding of the keys and values encodes all characters
385 * except for the unreserved characters. 417 * except for the unreserved characters.
386 * 418 *
387 * The fragment component is set through [fragment]. 419 * The fragment component is set through [fragment].
388 */ 420 */
389 Uri({String scheme, 421 factory Uri({String scheme,
390 this.userInfo: "", 422 String userInfo: "",
391 String host: "", 423 String host: "",
392 port: 0, 424 port: 0,
393 String path, 425 String path,
394 Iterable<String> pathSegments, 426 Iterable<String> pathSegments,
395 String query, 427 String query,
396 Map<String, String> queryParameters, 428 Map<String, String> queryParameters,
397 fragment: ""}) : 429 fragment: ""}) {
398 scheme = _makeScheme(scheme), 430 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
399 _host = _makeHost(host), 431 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
400 query = _makeQuery(query, queryParameters), 432 host = _makeHost(host, 0, _stringOrNullLength(host));
401 fragment = _makeFragment(fragment) { 433 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
402 // Perform scheme specific normalization. 434 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
403 if (scheme == "http" && port == 80) { 435 port = _makePort(port, scheme);
404 _port = 0; 436 bool ensureLeadingSlash = (host != "" || scheme == "file");
405 } else if (scheme == "https" && port == 443) { 437 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
406 _port = 0; 438 ensureLeadingSlash);
407 } else { 439
408 _port = port; 440 return new Uri._internal(scheme, userInfo, host, port,
409 } 441 path, query, fragment);
410 // Fill the path.
411 _path = _makePath(path, pathSegments);
412 } 442 }
413 443
414 /** 444 /**
415 * Creates a new `http` URI from authority, path and query. 445 * Creates a new `http` URI from authority, path and query.
416 * 446 *
417 * Examples: 447 * Examples:
418 * 448 *
419 * ``` 449 * ```
420 * // http://example.org/path?q=dart. 450 * // http://example.org/path?q=dart.
421 * new Uri.http("google.com", "/search", { "q" : "dart" }); 451 * new Uri.http("google.com", "/search", { "q" : "dart" });
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
482 var hostEnd = hostStart; 512 var hostEnd = hostStart;
483 if (hostStart < authority.length && 513 if (hostStart < authority.length &&
484 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { 514 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
485 // IPv6 host. 515 // IPv6 host.
486 for (; hostEnd < authority.length; hostEnd++) { 516 for (; hostEnd < authority.length; hostEnd++) {
487 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; 517 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
488 } 518 }
489 if (hostEnd == authority.length) { 519 if (hostEnd == authority.length) {
490 throw new FormatException("Invalid IPv6 host entry."); 520 throw new FormatException("Invalid IPv6 host entry.");
491 } 521 }
492 parseIPv6Address(authority.substring(hostStart + 1, hostEnd)); 522 parseIPv6Address(authority, hostStart + 1, hostEnd);
493 hostEnd++; // Skip the closing bracket. 523 hostEnd++; // Skip the closing bracket.
494 if (hostEnd != authority.length && 524 if (hostEnd != authority.length &&
495 authority.codeUnitAt(hostEnd) != _COLON) { 525 authority.codeUnitAt(hostEnd) != _COLON) {
496 throw new FormatException("Invalid end of authority"); 526 throw new FormatException("Invalid end of authority");
497 } 527 }
498 } 528 }
499 // Split host and port. 529 // Split host and port.
500 bool hasPort = false; 530 bool hasPort = false;
501 for (; hostEnd < authority.length; hostEnd++) { 531 for (; hostEnd < authority.length; hostEnd++) {
502 if (authority.codeUnitAt(hostEnd) == _COLON) { 532 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 788 * The returned map is unmodifiable and will throw [UnsupportedError] on any
759 * calls that would mutate it. 789 * calls that would mutate it.
760 */ 790 */
761 Map<String, String> get queryParameters { 791 Map<String, String> get queryParameters {
762 if (_queryParameters == null) { 792 if (_queryParameters == null) {
763 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); 793 _queryParameters = new UnmodifiableMapView(splitQueryString(query));
764 } 794 }
765 return _queryParameters; 795 return _queryParameters;
766 } 796 }
767 797
768 static String _makeHost(String host) { 798 static int _makePort(int port, String scheme) {
769 if (host == null || host.isEmpty) return host; 799 // Perform scheme specific normalization.
770 if (host.codeUnitAt(0) == _LEFT_BRACKET) { 800 if (port == 80 && scheme == "http") {
771 if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) { 801 return 0;
772 throw new FormatException('Missing end `]` to match `[` in host'); 802 }
803 if (port == 443 && scheme == "https") {
804 return 0;
805 }
806 return port;
807 }
808
809 static String _makeHost(String host, int start, int end) {
810 if (host == null) return null;
811 if (start == end) return "";
812 // Host is an IPv6 address if it starts with '[' or contains a colon.
813 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
814 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
815 _fail(host, start, 'Missing end `]` to match `[` in host');
773 } 816 }
774 parseIPv6Address(host.substring(1, host.length - 1)); 817 parseIPv6Address(host, start + 1, end - 1);
775 return host; 818 return host.substring(start, end);
776 } 819 }
777 for (int i = 0; i < host.length; i++) { 820 // TODO(lrn): skip if too short to be a valid IPv6 address.
821 for (int i = start; i < end; i++) {
778 if (host.codeUnitAt(i) == _COLON) { 822 if (host.codeUnitAt(i) == _COLON) {
779 parseIPv6Address(host); 823 parseIPv6Address(host, start, end);
780 return '[$host]'; 824 return '[$host]';
781 } 825 }
782 } 826 }
783 return host; 827 return _normalizeRegName(host, start, end);
784 } 828 }
785 829
786 static String _makeScheme(String scheme) { 830 static bool _isRegNameChar(int char) {
787 bool isSchemeLowerCharacter(int ch) { 831 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
788 return ch < 128 && 832 }
789 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
790 }
791 833
792 if (scheme == null) return ""; 834 /**
793 bool allLowercase = true; 835 * Validates and does case- and percent-encoding normalization.
794 int length = scheme.length; 836 *
795 for (int i = 0; i < length; i++) { 837 * The [host] must be an RFC3986 "reg-name". It is converted
796 int codeUnit = scheme.codeUnitAt(i); 838 * to lower case, and percent escapes are converted to either
797 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) { 839 * lower case unreserved characters or upper case escapes.
798 // First code unit must be an alphabetic character. 840 */
799 throw new ArgumentError('Illegal scheme: $scheme'); 841 static String _normalizeRegName(String host, int start, int end) {
800 } 842 StringBuffer buffer;
801 if (!isSchemeLowerCharacter(codeUnit)) { 843 int sectionStart = start;
802 if (_isSchemeCharacter(codeUnit)) { 844 int index = start;
803 allLowercase = false; 845 // Whether all characters between sectionStart and index are normalized,
804 } else { 846 bool isNormalized = true;
805 throw new ArgumentError('Illegal scheme: $scheme'); 847
848 while (index < end) {
849 int char = host.codeUnitAt(index);
850 if (char == _PERCENT) {
851 // The _regNameTable contains "%", so we check that first.
852 String replacement = _normalizeEscape(host, index, true);
853 if (replacement == null && isNormalized) {
854 index += 3;
855 continue;
806 } 856 }
857 if (buffer == null) buffer = new StringBuffer();
858 String slice = host.substring(sectionStart, index);
859 if (!isNormalized) slice = slice.toLowerCase();
860 buffer.write(slice);
861 int sourceLength = 3;
862 if (replacement == null) {
863 replacement = host.substring(index, index + 3);
864 } else if (replacement == "%") {
865 replacement = "%25";
866 sourceLength = 1;
867 }
868 buffer.write(replacement);
869 index += sourceLength;
870 sectionStart = index;
871 isNormalized = true;
872 } else if (_isRegNameChar(char)) {
873 if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
874 // Put initial slice in buffer and continue in non-normalized mode
875 if (buffer == null) buffer = new StringBuffer();
876 if (sectionStart < index) {
877 buffer.write(host.substring(sectionStart, index));
878 sectionStart = index;
879 }
880 isNormalized = false;
881 }
882 index++;
883 } else if (_isGeneralDelimiter(char)) {
884 _fail(host, index, "Invalid character");
885 } else {
886 int sourceLength = 1;
887 if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
888 int tail = host.codeUnitAt(index + 1);
889 if ((tail & 0xFC00) == 0xDC00) {
890 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
891 sourceLength = 2;
892 }
893 }
894 if (buffer == null) buffer = new StringBuffer();
895 String slice = host.substring(sectionStart, index);
896 if (!isNormalized) slice = slice.toLowerCase();
897 buffer.write(slice);
898 buffer.write(_escapeChar(char));
899 index += sourceLength;
900 sectionStart = index;
807 } 901 }
808 } 902 }
809 903 if (buffer == null) return host.substring(start, end);
810 return allLowercase ? scheme : scheme.toLowerCase(); 904 if (sectionStart < end) {
905 String slice = host.substring(sectionStart, end);
906 if (!isNormalized) slice = slice.toLowerCase();
907 buffer.write(slice);
908 }
909 return buffer.toString();
811 } 910 }
812 911
813 String _makePath(String path, Iterable<String> pathSegments) { 912 /**
913 * Validates scheme characters and does case-normalization.
914 *
915 * Schemes are converted to lower case. They cannot contain escapes.
916 */
917 static String _makeScheme(String scheme, int start, int end) {
Anders Johnsen 2014/06/17 13:07:03 start is redundant, as it's always 0.
Lasse Reichstein Nielsen 2014/06/17 14:31:45 True. And I know. I have a dream of making Uri.pa
918 if (start == end) return "";
919 int char = scheme.codeUnitAt(start);
920 if (!_isAlphabeticCharacter(char)) {
921 _fail(scheme, start, "Scheme not starting with alphabetic character");
922 }
923 bool allLowercase = char >= _LOWER_CASE_A;
924 for (int i = start; i < end; i++) {
925 int codeUnit = scheme.codeUnitAt(i);
926 if (!_isSchemeCharacter(codeUnit)) {
927 _fail(scheme, i, "Illegal scheme character");
928 }
929 if (_LOWER_CASE_A <= char && _LOWER_CASE_Z >= char) {
930 allLowercase = false;
931 }
932 }
933 scheme = scheme.substring(start, end);
934 if (!allLowercase) scheme = scheme.toLowerCase();
935 return scheme;
936 }
937
938 static String _makeUserInfo(String userInfo, int start, int end) {
939 if (userInfo == null) return "null";
940 return _normalize(userInfo, start, end, _userinfoTable);
941 }
942
943 static String _makePath(String path, int start, int end,
944 Iterable<String> pathSegments,
945 bool ensureLeadingSlash) {
814 if (path == null && pathSegments == null) return ""; 946 if (path == null && pathSegments == null) return "";
815 if (path != null && pathSegments != null) { 947 if (path != null && pathSegments != null) {
816 throw new ArgumentError('Both path and pathSegments specified'); 948 throw new ArgumentError('Both path and pathSegments specified');
817 } 949 }
818 var result; 950 var result;
819 if (path != null) { 951 if (path != null) {
820 result = _normalize(path); 952 result = _normalize(path, start, end, _pathCharOrSlashTable);
821 } else { 953 } else {
822 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); 954 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
823 } 955 }
824 if ((hasAuthority || (scheme == "file")) && 956 if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) {
825 result.isNotEmpty && !result.startsWith("/")) {
826 return "/$result"; 957 return "/$result";
827 } 958 }
828 return result; 959 return result;
829 } 960 }
830 961
831 static String _makeQuery(String query, Map<String, String> queryParameters) { 962 static String _makeQuery(String query, int start, int end,
963 Map<String, String> queryParameters) {
832 if (query == null && queryParameters == null) return ""; 964 if (query == null && queryParameters == null) return "";
833 if (query != null && queryParameters != null) { 965 if (query != null && queryParameters != null) {
834 throw new ArgumentError('Both query and queryParameters specified'); 966 throw new ArgumentError('Both query and queryParameters specified');
835 } 967 }
836 if (query != null) return _normalize(query); 968 if (query != null) return _normalize(query, start, end, _queryCharTable);
837 969
838 var result = new StringBuffer(); 970 var result = new StringBuffer();
839 var first = true; 971 var first = true;
840 queryParameters.forEach((key, value) { 972 queryParameters.forEach((key, value) {
841 if (!first) { 973 if (!first) {
842 result.write("&"); 974 result.write("&");
843 } 975 }
844 first = false; 976 first = false;
845 result.write(Uri.encodeQueryComponent(key)); 977 result.write(Uri.encodeQueryComponent(key));
846 if (value != null && !value.isEmpty) { 978 if (value != null && !value.isEmpty) {
847 result.write("="); 979 result.write("=");
848 result.write(Uri.encodeQueryComponent(value)); 980 result.write(Uri.encodeQueryComponent(value));
849 } 981 }
850 }); 982 });
851 return result.toString(); 983 return result.toString();
852 } 984 }
853 985
854 static String _makeFragment(String fragment) { 986 static String _makeFragment(String fragment, int start, int end) {
855 if (fragment == null) return ""; 987 if (fragment == null) return "";
856 return _normalize(fragment); 988 return _normalize(fragment, start, end, _queryCharTable);
857 } 989 }
858 990
859 static String _normalize(String component) { 991 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
860 int index = component.indexOf('%');
861 if (index < 0) return component;
862 992
863 bool isNormalizedHexDigit(int digit) { 993 static bool _isHexDigit(int char) {
864 return (_ZERO <= digit && digit <= _NINE) || 994 if (_NINE >= char) return _ZERO <= char;
865 (_UPPER_CASE_A <= digit && digit <= _UPPER_CASE_F); 995 char |= 0x20;
996 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
997 }
998
999 static int _hexValue(int char) {
1000 assert(_isHexDigit(char));
1001 if (_NINE >= char) return char - _ZERO;
1002 char |= 0x20;
1003 return char - (_LOWER_CASE_A - 10);
1004 }
1005
1006 /**
1007 * Performs RFC 3986 Percent-Encoding Normalization.
1008 *
1009 * Returns a replacement string that should be replace the original escape.
1010 * Returns null if no replacement is necessary because the escape is
1011 * not for an unreserved character and is already non-lower-case.
1012 *
1013 * Returns "%" if the escape is invalid (not two valid hex digits following
1014 * the percent sign). The calling code should replace the percent
1015 * sign with "%25", but leave the following two characters unmodified.
1016 *
1017 * If [lowerCase] is true, a single character returned is always lower case,
1018 */
1019 static String _normalizeEscape(String source, int index, bool lowerCase) {
1020 assert(source.codeUnitAt(index) == _PERCENT);
1021 if (index + 2 >= source.length) {
1022 return "%"; // Marks the escape as invalid.
866 } 1023 }
1024 int firstDigit = source.codeUnitAt(index + 1);
1025 int secondDigit = source.codeUnitAt(index + 2);
1026 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
1027 return "%"; // Marks the escape as invalid.
1028 }
1029 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit);
1030 if (_isUnreservedChar(value)) {
1031 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
1032 value |= 0x20;
1033 }
1034 return new String.fromCharCode(value);
1035 }
1036 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
1037 // Either digit is lower case.
1038 return source.substring(index, index + 3).toUpperCase();
1039 }
1040 // Escape is retained, and is already non-lower case, so return null to
1041 // represent "no replacement necessary".
1042 return null;
1043 }
867 1044
868 bool isLowerCaseHexDigit(int digit) { 1045 static bool _isUnreservedChar(int ch) {
869 return _LOWER_CASE_A <= digit && digit <= _LOWER_CASE_F; 1046 return ch < 127 &&
870 } 1047 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1048 }
871 1049
872 bool isUnreserved(int ch) { 1050 static String _escapeChar(char) {
873 return ch < 128 && 1051 const hexDigits = "0123456789ABCDEF";
874 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1052 List codeUnits;
875 } 1053 if (char < 0x80) {
876 1054 // ASCII, a single percent encoded sequence.
877 int normalizeHexDigit(int index) { 1055 codeUnits = new List(3);
878 var codeUnit = component.codeUnitAt(index); 1056 codeUnits[0] = _PERCENT;
879 if (isLowerCaseHexDigit(codeUnit)) { 1057 codeUnits[1] = hexDigits.codeUnitAt(char >> 4);
880 return codeUnit - 0x20; 1058 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf);
881 } else if (!isNormalizedHexDigit(codeUnit)) { 1059 } else {
882 throw new ArgumentError("Invalid URI component: $component"); 1060 // Do UTF-8 encoding of character, then percent encode bytes.
883 } else { 1061 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
884 return codeUnit; 1062 int encodedBytes = 2;
1063 if (char > 0x7ff) {
1064 flag = 0xe0;
1065 encodedBytes = 3;
1066 if (char > 0xffff) {
1067 encodedBytes = 4;
1068 flag = 0xf0;
1069 }
1070 }
1071 codeUnits = new List(3 * encodedBytes);
1072 int index = 0;
1073 while (--encodedBytes >= 0) {
1074 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
1075 codeUnits[index] = _PERCENT;
1076 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4);
1077 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf);
1078 index += 3;
1079 flag = 0x80; // Following bytes have only high bit set.
885 } 1080 }
886 } 1081 }
1082 return new String.fromCharCodes(codeUnits);
1083 }
887 1084
888 int decodeHexDigitPair(int index) { 1085 /**
889 int byte = 0; 1086 * Runs through component checking that each character is valid and
890 for (int i = 0; i < 2; i++) { 1087 * normalize percent escapes.
891 var codeUnit = component.codeUnitAt(index + i); 1088 *
892 if (_ZERO <= codeUnit && codeUnit <= _NINE) { 1089 * Uses [charTable] to check if a non-`%` character is allowed.
893 byte = byte * 16 + codeUnit - _ZERO; 1090 * Each `%` character must be followed by two hex digits.
1091 * If the hex-digits are lower case letters, they are converted to
1092 * upper case.
1093 */
1094 static String _normalize(String component, int start, int end,
1095 List<int> charTable) {
1096 StringBuffer buffer;
1097 int sectionStart = start;
1098 int index = start;
1099 // Loop while characters are valid and escapes correct and upper-case.
1100 while (index < end) {
1101 int char = component.codeUnitAt(index);
1102 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
1103 index++;
1104 } else {
1105 String replacement;
1106 int sourceLength;
1107 if (char == _PERCENT) {
1108 replacement = _normalizeEscape(component, index, false);
1109 // Returns null if we should keep the existing escape.
1110 if (replacement == null) {
1111 index += 3;
1112 continue;
1113 }
1114 // Returns "%" if we should escape the existing percent.
1115 if ("%" == replacement) {
1116 replacement = "%25";
1117 sourceLength = 1;
1118 } else {
1119 sourceLength = 3;
1120 }
1121 } else if (_isGeneralDelimiter(char)) {
1122 _fail(component, index, "Invalid character");
894 } else { 1123 } else {
895 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66). 1124 sourceLength = 1;
896 codeUnit |= 0x20; 1125 if ((char & 0xFC00) == 0xD800) {
897 if (_LOWER_CASE_A <= codeUnit && 1126 // Possible lead surrogate.
898 codeUnit <= _LOWER_CASE_F) { 1127 if (index + 1 < end) {
899 byte = byte * 16 + codeUnit - _LOWER_CASE_A + 10; 1128 int tail = component.codeUnitAt(index + 1);
900 } else { 1129 if ((tail & 0xFC00) == 0xDC00) {
901 throw new ArgumentError( 1130 // Tail surrogat.
902 "Invalid percent-encoding in URI component: $component"); 1131 sourceLength = 2;
1132 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1133 }
1134 }
903 } 1135 }
1136 replacement = _escapeChar(char);
904 } 1137 }
905 } 1138 if (buffer == null) buffer = new StringBuffer();
906 return byte; 1139 buffer.write(component.substring(sectionStart, index));
907 } 1140 buffer.write(replacement);
908 1141 index += sourceLength;
909 // Start building the normalized component string. 1142 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 } 1143 }
922 } 1144 }
923 1145 if (buffer == null) {
924 while (index < length) { 1146 // Makes no copy if start == 0 and end == component.length.
925 // Normalize percent-encoding to uppercase and don't encode 1147 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 } 1148 }
959 if (result == null) return component; 1149 if (sectionStart < end) {
960 1150 buffer.write(component.substring(sectionStart, end));
961 if (result != null && prevIndex != index) fillResult(); 1151 }
962 assert(index == length); 1152 return buffer.toString();
963
964 return result.toString();
965 } 1153 }
966 1154
967 static bool _isSchemeCharacter(int ch) { 1155 static bool _isSchemeCharacter(int ch) {
968 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1156 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
969 } 1157 }
970 1158
1159 static bool _isGeneralDelimiter(int ch) {
1160 return ch <= 64 &&
1161 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1162 }
971 1163
972 /** 1164 /**
973 * Returns whether the URI is absolute. 1165 * Returns whether the URI is absolute.
974 */ 1166 */
975 bool get isAbsolute => scheme != "" && fragment == ""; 1167 bool get isAbsolute => scheme != "" && fragment == "";
976 1168
977 String _merge(String base, String reference) { 1169 String _merge(String base, String reference) {
978 if (base == "") return "/$reference"; 1170 if (base == "") return "/$reference";
979 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; 1171 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference";
980 } 1172 }
(...skipping 477 matching lines...) Expand 10 before | Expand all | Expand 10 after
1458 .toList(); 1650 .toList();
1459 } 1651 }
1460 1652
1461 /** 1653 /**
1462 * Parse the [host] as an IP version 6 (IPv6) address, returning the address 1654 * 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). 1655 * as a list of 16 bytes in network byte order (big endian).
1464 * 1656 *
1465 * Throws a [FormatException] if [host] is not a valid IPv6 address 1657 * Throws a [FormatException] if [host] is not a valid IPv6 address
1466 * representation. 1658 * representation.
1467 * 1659 *
1660 * Acts on the substring from [start] to [end]. If [end] is omitted, it
1661 * defaults ot the end of the string.
1662 *
1468 * Some examples of IPv6 addresses: 1663 * Some examples of IPv6 addresses:
1469 * * ::1 1664 * * ::1
1470 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 1665 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1471 * * 3ffe:2a00:100:7031::1 1666 * * 3ffe:2a00:100:7031::1
1472 * * ::FFFF:129.144.52.38 1667 * * ::FFFF:129.144.52.38
1473 * * 2010:836B:4179::836B:4179 1668 * * 2010:836B:4179::836B:4179
1474 */ 1669 */
1475 static List<int> parseIPv6Address(String host) { 1670 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
Lasse Reichstein Nielsen 2014/06/17 14:31:44 This is a public method that I added start/end to.
1671 if (end == null) end = host.length;
1476 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated 1672 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
1477 // by `:`'s, with the following exceptions: 1673 // by `:`'s, with the following exceptions:
1478 // 1674 //
1479 // - One (and only one) wildcard (`::`) may be present, representing a fill 1675 // - One (and only one) wildcard (`::`) may be present, representing a fill
1480 // of 0's. The IPv6 `::` is thus 16 bytes of `0`. 1676 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1481 // - The last two parts may be replaced by an IPv4 address. 1677 // - The last two parts may be replaced by an IPv4 address.
1482 void error(String msg) { 1678 void error(String msg) {
1483 throw new FormatException('Illegal IPv6 address, $msg'); 1679 throw new FormatException('Illegal IPv6 address, $msg');
1484 } 1680 }
1485 int parseHex(int start, int end) { 1681 int parseHex(int start, int end) {
1486 if (end - start > 4) { 1682 if (end - start > 4) {
1487 error('an IPv6 part can only contain a maximum of 4 hex digits'); 1683 error('an IPv6 part can only contain a maximum of 4 hex digits');
1488 } 1684 }
1489 int value = int.parse(host.substring(start, end), radix: 16); 1685 int value = int.parse(host.substring(start, end), radix: 16);
1490 if (value < 0 || value > (1 << 16) - 1) { 1686 if (value < 0 || value > (1 << 16) - 1) {
1491 error('each part must be in the range of `0x0..0xFFFF`'); 1687 error('each part must be in the range of `0x0..0xFFFF`');
1492 } 1688 }
1493 return value; 1689 return value;
1494 } 1690 }
1495 if (host.length < 2) error('address is too short'); 1691 if (host.length < 2) error('address is too short');
1496 List<int> parts = []; 1692 List<int> parts = [];
1497 bool wildcardSeen = false; 1693 bool wildcardSeen = false;
1498 int partStart = 0; 1694 int partStart = start;
1499 // Parse all parts, except a potential last one. 1695 // Parse all parts, except a potential last one.
1500 for (int i = 0; i < host.length; i++) { 1696 for (int i = start; i < end; i++) {
1501 if (host.codeUnitAt(i) == _COLON) { 1697 if (host.codeUnitAt(i) == _COLON) {
1502 if (i == 0) { 1698 if (i == start) {
1503 // If we see a `:` in the beginning, expect wildcard. 1699 // If we see a `:` in the beginning, expect wildcard.
1504 i++; 1700 i++;
1505 if (host.codeUnitAt(i) != _COLON) { 1701 if (host.codeUnitAt(i) != _COLON) {
1506 error('invalid start colon.'); 1702 error('invalid start colon.');
1507 } 1703 }
1508 partStart = i; 1704 partStart = i;
1509 } 1705 }
1510 if (i == partStart) { 1706 if (i == partStart) {
1511 // Wildcard. We only allow one. 1707 // Wildcard. We only allow one.
1512 if (wildcardSeen) { 1708 if (wildcardSeen) {
1513 error('only one wildcard `::` is allowed'); 1709 error('only one wildcard `::` is allowed');
1514 } 1710 }
1515 wildcardSeen = true; 1711 wildcardSeen = true;
1516 parts.add(-1); 1712 parts.add(-1);
1517 } else { 1713 } else {
1518 // Found a single colon. Parse [partStart..i] as a hex entry. 1714 // Found a single colon. Parse [partStart..i] as a hex entry.
1519 parts.add(parseHex(partStart, i)); 1715 parts.add(parseHex(partStart, i));
1520 } 1716 }
1521 partStart = i + 1; 1717 partStart = i + 1;
1522 } 1718 }
1523 } 1719 }
1524 if (parts.length == 0) error('too few parts'); 1720 if (parts.length == 0) error('too few parts');
1525 bool atEnd = partStart == host.length; 1721 bool atEnd = (partStart == end);
1526 bool isLastWildcard = parts.last == -1; 1722 bool isLastWildcard = (parts.last == -1);
1527 if (atEnd && !isLastWildcard) { 1723 if (atEnd && !isLastWildcard) {
1528 error('expected a part after last `:`'); 1724 error('expected a part after last `:`');
1529 } 1725 }
1530 if (!atEnd) { 1726 if (!atEnd) {
1531 try { 1727 try {
1532 parts.add(parseHex(partStart, host.length)); 1728 parts.add(parseHex(partStart, end));
1533 } catch (e) { 1729 } catch (e) {
1534 // Failed to parse the last chunk as hex. Try IPv4. 1730 // Failed to parse the last chunk as hex. Try IPv4.
1535 try { 1731 try {
1536 List<int> last = parseIPv4Address(host.substring(partStart)); 1732 List<int> last = parseIPv4Address(host.substring(partStart, end));
1537 parts.add(last[0] << 8 | last[1]); 1733 parts.add(last[0] << 8 | last[1]);
1538 parts.add(last[2] << 8 | last[3]); 1734 parts.add(last[2] << 8 | last[3]);
1539 } catch (e) { 1735 } catch (e) {
1540 error('invalid end of IPv6 address.'); 1736 error('invalid end of IPv6 address.');
1541 } 1737 }
1542 } 1738 }
1543 } 1739 }
1544 if (wildcardSeen) { 1740 if (wildcardSeen) {
1545 if (parts.length > 7) { 1741 if (parts.length > 7) {
1546 error('an address with a wildcard must have less than 7 parts'); 1742 error('an address with a wildcard must have less than 7 parts');
1547 } 1743 }
1548 } else if (parts.length != 8) { 1744 } else if (parts.length != 8) {
1549 error('an address without a wildcard must contain exactly 8 parts'); 1745 error('an address without a wildcard must contain exactly 8 parts');
1550 } 1746 }
1551 // TODO(ajohnsen): Consider using Uint8List. 1747 // TODO(ajohnsen): Consider using Uint8List.
1552 return parts 1748 List bytes = new List<int>(16);
1553 .expand((value) { 1749 for (int i = 0, index = 0; i < parts.length; i++) {
1554 if (value == -1) { 1750 int value = parts[i];
1555 return new List.filled((9 - parts.length) * 2, 0); 1751 if (value == -1) {
1556 } else { 1752 int wildCardLength = 9 - parts.length;
1557 return [(value >> 8) & 0xFF, value & 0xFF]; 1753 for (int j = 0; j < wildCardLength; j++) {
1558 } 1754 bytes[index] = 0;
1559 }) 1755 bytes[index + 1] = 0;
1560 .toList(); 1756 index += 2;
1757 }
1758 } else {
1759 bytes[index] = value >> 8;
1760 bytes[index + 1] = value & 0xff;
1761 index += 2;
1762 }
1763 }
1764 return bytes;
1561 } 1765 }
1562 1766
1563 // Frequently used character codes. 1767 // Frequently used character codes.
1564 static const int _SPACE = 0x20; 1768 static const int _SPACE = 0x20;
1565 static const int _DOUBLE_QUOTE = 0x22; 1769 static const int _DOUBLE_QUOTE = 0x22;
1566 static const int _NUMBER_SIGN = 0x23; 1770 static const int _NUMBER_SIGN = 0x23;
1567 static const int _PERCENT = 0x25; 1771 static const int _PERCENT = 0x25;
1568 static const int _ASTERISK = 0x2A; 1772 static const int _ASTERISK = 0x2A;
1569 static const int _PLUS = 0x2B; 1773 static const int _PLUS = 0x2B;
1570 static const int _SLASH = 0x2F; 1774 static const int _SLASH = 0x2F;
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
1813 0x2bff, // 0x30 - 0x3f 1111111111010100 2017 0x2bff, // 0x30 - 0x3f 1111111111010100
1814 // ABCDEFGHIJKLMNO 2018 // ABCDEFGHIJKLMNO
1815 0xfffe, // 0x40 - 0x4f 0111111111111111 2019 0xfffe, // 0x40 - 0x4f 0111111111111111
1816 // PQRSTUVWXYZ _ 2020 // PQRSTUVWXYZ _
1817 0x87ff, // 0x50 - 0x5f 1111111111100001 2021 0x87ff, // 0x50 - 0x5f 1111111111100001
1818 // abcdefghijklmno 2022 // abcdefghijklmno
1819 0xfffe, // 0x60 - 0x6f 0111111111111111 2023 0xfffe, // 0x60 - 0x6f 0111111111111111
1820 // pqrstuvwxyz ~ 2024 // pqrstuvwxyz ~
1821 0x47ff]; // 0x70 - 0x7f 1111111111100010 2025 0x47ff]; // 0x70 - 0x7f 1111111111100010
1822 2026
2027 // General delimiter characters, RFC 3986 section 2.2.
2028 // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
2029 //
2030 static const _genDelimitersTable = const [
2031 // LSB MSB
2032 // | |
2033 0x0000, // 0x00 - 0x0f 0000000000000000
2034 0x0000, // 0x10 - 0x1f 0000000000000000
2035 // # /
2036 0x8008, // 0x20 - 0x2f 0001000000000001
2037 // : [ ]?
2038 0xe400, // 0x30 - 0x3f 0000000000101011
2039 // @
2040 0x0001, // 0x40 - 0x4f 1000000000000000
2041 //
2042 0x0000, // 0x50 - 0x5f 0000000000000000
2043 //
2044 0x0000, // 0x60 - 0x6f 0000000000000000
2045 //
2046 0x0000]; // 0x70 - 0x7f 0000000000000000
2047
2048 // Characters allowed in the userinfo as of RFC 3986.
2049 // RFC 3986 Apendix A
2050 // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
2051 static const _userinfoTable = const [
2052 // LSB MSB
2053 // | |
2054 0x0000, // 0x00 - 0x0f 0000000000000000
2055 0x0000, // 0x10 - 0x1f 0000000000000000
2056 // ! $ &'()*+,-.
2057 0x7fd2, // 0x20 - 0x2f 0100101111111110
2058 // 0123456789:; =
2059 0x2fff, // 0x30 - 0x3f 1111111111110100
2060 // ABCDEFGHIJKLMNO
2061 0xfffe, // 0x40 - 0x4f 0111111111111111
2062 // PQRSTUVWXYZ _
2063 0x87ff, // 0x50 - 0x5f 1111111111100001
2064 // abcdefghijklmno
2065 0xfffe, // 0x60 - 0x6f 0111111111111111
2066 // pqrstuvwxyz ~
2067 0x47ff]; // 0x70 - 0x7f 1111111111100010
2068
1823 // Characters allowed in the reg-name as of RFC 3986. 2069 // Characters allowed in the reg-name as of RFC 3986.
1824 // RFC 3986 Apendix A 2070 // RFC 3986 Apendix A
1825 // reg-name = *( unreserved / pct-encoded / sub-delims ) 2071 // reg-name = *( unreserved / pct-encoded / sub-delims )
1826 static const _regNameTable = const [ 2072 static const _regNameTable = const [
1827 // LSB MSB 2073 // LSB MSB
1828 // | | 2074 // | |
1829 0x0000, // 0x00 - 0x0f 0000000000000000 2075 0x0000, // 0x00 - 0x0f 0000000000000000
1830 0x0000, // 0x10 - 0x1f 0000000000000000 2076 0x0000, // 0x10 - 0x1f 0000000000000000
1831 // ! $%&'()*+,-. 2077 // ! $%&'()*+,-.
1832 0x7ff2, // 0x20 - 0x2f 0100111111111110 2078 0x7ff2, // 0x20 - 0x2f 0100111111111110
(...skipping 22 matching lines...) Expand all
1855 0x2fff, // 0x30 - 0x3f 1111111111110100 2101 0x2fff, // 0x30 - 0x3f 1111111111110100
1856 // @ABCDEFGHIJKLMNO 2102 // @ABCDEFGHIJKLMNO
1857 0xffff, // 0x40 - 0x4f 1111111111111111 2103 0xffff, // 0x40 - 0x4f 1111111111111111
1858 // PQRSTUVWXYZ _ 2104 // PQRSTUVWXYZ _
1859 0x87ff, // 0x50 - 0x5f 1111111111100001 2105 0x87ff, // 0x50 - 0x5f 1111111111100001
1860 // abcdefghijklmno 2106 // abcdefghijklmno
1861 0xfffe, // 0x60 - 0x6f 0111111111111111 2107 0xfffe, // 0x60 - 0x6f 0111111111111111
1862 // pqrstuvwxyz ~ 2108 // pqrstuvwxyz ~
1863 0x47ff]; // 0x70 - 0x7f 1111111111100010 2109 0x47ff]; // 0x70 - 0x7f 1111111111100010
1864 2110
2111 // Characters allowed in the path as of RFC 3986.
2112 // RFC 3986 section 3.3 *and* slash.
2113 static const _pathCharOrSlashTable = const [
2114 // LSB MSB
2115 // | |
2116 0x0000, // 0x00 - 0x0f 0000000000000000
2117 0x0000, // 0x10 - 0x1f 0000000000000000
2118 // ! $ &'()*+,-./
2119 0xffd2, // 0x20 - 0x2f 0100101111111111
2120 // 0123456789:; =
2121 0x2fff, // 0x30 - 0x3f 1111111111110100
2122 // @ABCDEFGHIJKLMNO
2123 0xffff, // 0x40 - 0x4f 1111111111111111
2124
2125 // PQRSTUVWXYZ _
2126 0x87ff, // 0x50 - 0x5f 1111111111100001
2127 // abcdefghijklmno
2128 0xfffe, // 0x60 - 0x6f 0111111111111111
2129 // pqrstuvwxyz ~
2130 0x47ff]; // 0x70 - 0x7f 1111111111100010
2131
1865 // Characters allowed in the query as of RFC 3986. 2132 // Characters allowed in the query as of RFC 3986.
1866 // RFC 3986 section 3.4. 2133 // RFC 3986 section 3.4.
1867 // query = *( pchar / "/" / "?" ) 2134 // query = *( pchar / "/" / "?" )
1868 static const _queryCharTable = const [ 2135 static const _queryCharTable = const [
1869 // LSB MSB 2136 // LSB MSB
1870 // | | 2137 // | |
1871 0x0000, // 0x00 - 0x0f 0000000000000000 2138 0x0000, // 0x00 - 0x0f 0000000000000000
1872 0x0000, // 0x10 - 0x1f 0000000000000000 2139 0x0000, // 0x10 - 0x1f 0000000000000000
1873 // ! $ &'()*+,-./ 2140 // ! $ &'()*+,-./
1874 0xffd2, // 0x20 - 0x2f 0100101111111111 2141 0xffd2, // 0x20 - 0x2f 0100101111111111
1875 // 0123456789:; = ? 2142 // 0123456789:; = ?
1876 0xafff, // 0x30 - 0x3f 1111111111110101 2143 0xafff, // 0x30 - 0x3f 1111111111110101
1877 // @ABCDEFGHIJKLMNO 2144 // @ABCDEFGHIJKLMNO
1878 0xffff, // 0x40 - 0x4f 1111111111111111 2145 0xffff, // 0x40 - 0x4f 1111111111111111
1879 // PQRSTUVWXYZ _ 2146 // PQRSTUVWXYZ _
1880 0x87ff, // 0x50 - 0x5f 1111111111100001 2147 0x87ff, // 0x50 - 0x5f 1111111111100001
1881 // abcdefghijklmno 2148 // abcdefghijklmno
1882 0xfffe, // 0x60 - 0x6f 0111111111111111 2149 0xfffe, // 0x60 - 0x6f 0111111111111111
1883 // pqrstuvwxyz ~ 2150 // pqrstuvwxyz ~
1884 0x47ff]; // 0x70 - 0x7f 1111111111100010 2151 0x47ff]; // 0x70 - 0x7f 1111111111100010
1885 } 2152 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698