Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 } |
| OLD | NEW |