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 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 374 char = uri.codeUnitAt(index); | 374 char = uri.codeUnitAt(index); |
| 375 if (char == _QUESTION || char == _NUMBER_SIGN) { | 375 if (char == _QUESTION || char == _NUMBER_SIGN) { |
| 376 break; | 376 break; |
| 377 } | 377 } |
| 378 char = EOI; | 378 char = EOI; |
| 379 } | 379 } |
| 380 state = NOT_IN_PATH; | 380 state = NOT_IN_PATH; |
| 381 } | 381 } |
| 382 | 382 |
| 383 assert(state == NOT_IN_PATH); | 383 assert(state == NOT_IN_PATH); |
| 384 bool isFile = (scheme == "file"); | 384 bool hasAuthority = host != null; |
|
floitsch
2015/07/17 13:03:20
nit: (host != null)
Lasse Reichstein Nielsen
2015/07/17 18:40:22
Done.
| |
| 385 bool ensureLeadingSlash = host != null; | 385 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority); |
| 386 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash, isFile); | |
| 387 | 386 |
| 388 if (char == _QUESTION) { | 387 if (char == _QUESTION) { |
| 389 int numberSignIndex = -1; | 388 int numberSignIndex = -1; |
| 390 for (int i = index + 1; i < end; i++) { | 389 for (int i = index + 1; i < end; i++) { |
| 391 if (uri.codeUnitAt(i) == _NUMBER_SIGN) { | 390 if (uri.codeUnitAt(i) == _NUMBER_SIGN) { |
| 392 numberSignIndex = i; | 391 numberSignIndex = i; |
| 393 break; | 392 break; |
| 394 } | 393 } |
| 395 } | 394 } |
| 396 if (numberSignIndex < 0) { | 395 if (numberSignIndex < 0) { |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 504 // Special case this constructor for backwards compatibility. | 503 // Special case this constructor for backwards compatibility. |
| 505 if (query == "") query = null; | 504 if (query == "") query = null; |
| 506 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); | 505 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); |
| 507 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); | 506 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); |
| 508 port = _makePort(port, scheme); | 507 port = _makePort(port, scheme); |
| 509 bool isFile = (scheme == "file"); | 508 bool isFile = (scheme == "file"); |
| 510 if (host == null && | 509 if (host == null && |
| 511 (userInfo.isNotEmpty || port != null || isFile)) { | 510 (userInfo.isNotEmpty || port != null || isFile)) { |
| 512 host = ""; | 511 host = ""; |
| 513 } | 512 } |
| 514 bool ensureLeadingSlash = host != null; | 513 bool hasAuthority = host != null; |
|
floitsch
2015/07/17 13:03:20
ditto.
| |
| 515 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, | 514 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, |
| 516 ensureLeadingSlash, isFile); | 515 scheme, hasAuthority); |
| 516 if (scheme.isEmpty && host == null && !path.startsWith('/')) { | |
|
floitsch
2015/07/17 13:03:20
ditto.
Lasse Reichstein Nielsen
2015/07/17 18:40:23
Not here, no.
Reading expressions between && is no
| |
| 517 path = _normalizeRelativePath(path); | |
| 518 } else { | |
| 519 path = _removeDotSegments(path); | |
| 520 } | |
| 517 return new Uri._internal(scheme, userInfo, host, port, | 521 return new Uri._internal(scheme, userInfo, host, port, |
| 518 path, query, fragment); | 522 path, query, fragment); |
| 519 } | 523 } |
| 520 | 524 |
| 521 /** | 525 /** |
| 522 * Creates a new `http` URI from authority, path and query. | 526 * Creates a new `http` URI from authority, path and query. |
| 523 * | 527 * |
| 524 * Examples: | 528 * Examples: |
| 525 * | 529 * |
| 526 * ``` | 530 * ``` |
| (...skipping 419 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 946 } | 950 } |
| 947 } | 951 } |
| 948 if (host != null) { | 952 if (host != null) { |
| 949 host = _makeHost(host, 0, host.length, false); | 953 host = _makeHost(host, 0, host.length, false); |
| 950 } else if (this.hasAuthority) { | 954 } else if (this.hasAuthority) { |
| 951 host = this.host; | 955 host = this.host; |
| 952 } else if (userInfo.isNotEmpty || port != null || isFile) { | 956 } else if (userInfo.isNotEmpty || port != null || isFile) { |
| 953 host = ""; | 957 host = ""; |
| 954 } | 958 } |
| 955 | 959 |
| 956 bool ensureLeadingSlash = (host != null); | 960 bool hasAuthority = host != null; |
|
floitsch
2015/07/17 13:03:20
we seem to disagree :)
I like the parenthesis.
Lasse Reichstein Nielsen
2015/07/17 18:40:23
We do disagree.
Parentheses are added to change o
| |
| 957 if (path != null || pathSegments != null) { | 961 if (path != null || pathSegments != null) { |
| 958 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, | 962 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, |
| 959 ensureLeadingSlash, isFile); | 963 scheme, hasAuthority); |
| 960 } else { | 964 } else { |
| 961 path = this.path; | 965 path = this.path; |
| 962 if ((isFile || (ensureLeadingSlash && !path.isEmpty)) && | 966 if ((isFile || (hasAuthority && !path.isEmpty)) && |
| 963 !path.startsWith('/')) { | 967 !path.startsWith('/')) { |
| 964 path = "/$path"; | 968 path = "/" + path; |
| 965 } | 969 } |
| 966 } | 970 } |
| 967 | 971 |
| 968 if (query != null || queryParameters != null) { | 972 if (query != null || queryParameters != null) { |
| 969 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); | 973 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); |
| 970 } else if (this.hasQuery) { | 974 } else if (this.hasQuery) { |
| 971 query = this.query; | 975 query = this.query; |
| 972 } | 976 } |
| 973 | 977 |
| 974 if (fragment != null) { | 978 if (fragment != null) { |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1018 * calls that would mutate it. | 1022 * calls that would mutate it. |
| 1019 */ | 1023 */ |
| 1020 Map<String, String> get queryParameters { | 1024 Map<String, String> get queryParameters { |
| 1021 if (_queryParameters == null) { | 1025 if (_queryParameters == null) { |
| 1022 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); | 1026 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); |
| 1023 } | 1027 } |
| 1024 return _queryParameters; | 1028 return _queryParameters; |
| 1025 } | 1029 } |
| 1026 | 1030 |
| 1027 /** | 1031 /** |
| 1028 * Returns an URI where the path has been normalized. | 1032 * Returns an URI where the path has been normalized. |
|
floitsch
2015/07/17 13:03:20
a URI
Lasse Reichstein Nielsen
2015/07/17 18:40:22
Done.
| |
| 1029 * | 1033 * |
| 1030 * A normalized path does not contain `.` segments or non-leading `..` | 1034 * A normalized path does not contain `.` segments or non-leading `..` |
| 1031 * segments. | 1035 * segments. |
| 1032 * Only a relative path may contain leading `..` segments, | 1036 * Only a relative path with no scheme or authority may contain |
| 1037 * leading `..` segments, | |
| 1033 * a path that starts with `/` will also drop any leading `..` segments. | 1038 * a path that starts with `/` will also drop any leading `..` segments. |
| 1034 * | 1039 * |
| 1035 * This uses the same normalization strategy as [resolveUri], as specified by | 1040 * This uses the same normalization strategy as `new Uri().resolve(this)`. |
| 1036 * RFC 3986. | |
| 1037 * | 1041 * |
| 1038 * Does not change any part of the URI except the path. | 1042 * Does not change any part of the URI except the path. |
| 1039 */ | 1043 */ |
| 1040 Uri normalizePath() { | 1044 Uri normalizePath() { |
| 1041 String path = _removeDotSegments(_path); | 1045 String path = _normalizePath(_path, scheme, hasAuthority); |
| 1042 if (identical(path, _path)) return this; | 1046 if (identical(path, _path)) return this; |
| 1043 return this.replace(path: path); | 1047 return this.replace(path: path); |
| 1044 } | 1048 } |
| 1045 | 1049 |
| 1046 static int _makePort(int port, String scheme) { | 1050 static int _makePort(int port, String scheme) { |
| 1047 // Perform scheme specific normalization. | 1051 // Perform scheme specific normalization. |
| 1048 if (port != null && port == _defaultPort(scheme)) return null; | 1052 if (port != null && port == _defaultPort(scheme)) return null; |
| 1049 return port; | 1053 return port; |
| 1050 } | 1054 } |
| 1051 | 1055 |
| (...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1171 * Validates scheme characters and does case-normalization. | 1175 * Validates scheme characters and does case-normalization. |
| 1172 * | 1176 * |
| 1173 * Schemes are converted to lower case. They cannot contain escapes. | 1177 * Schemes are converted to lower case. They cannot contain escapes. |
| 1174 */ | 1178 */ |
| 1175 static String _makeScheme(String scheme, int start, int end) { | 1179 static String _makeScheme(String scheme, int start, int end) { |
| 1176 if (start == end) return ""; | 1180 if (start == end) return ""; |
| 1177 final int firstCodeUnit = scheme.codeUnitAt(start); | 1181 final int firstCodeUnit = scheme.codeUnitAt(start); |
| 1178 if (!_isAlphabeticCharacter(firstCodeUnit)) { | 1182 if (!_isAlphabeticCharacter(firstCodeUnit)) { |
| 1179 _fail(scheme, start, "Scheme not starting with alphabetic character"); | 1183 _fail(scheme, start, "Scheme not starting with alphabetic character"); |
| 1180 } | 1184 } |
| 1181 bool allLowercase = firstCodeUnit >= _LOWER_CASE_A; | 1185 int minChar = firstCodeUnit; |
| 1182 for (int i = start; i < end; i++) { | 1186 for (int i = start; i < end; i++) { |
| 1183 final int codeUnit = scheme.codeUnitAt(i); | 1187 final int codeUnit = scheme.codeUnitAt(i); |
| 1184 if (!_isSchemeCharacter(codeUnit)) { | 1188 if (!_isSchemeCharacter(codeUnit)) { |
| 1185 _fail(scheme, i, "Illegal scheme character"); | 1189 _fail(scheme, i, "Illegal scheme character"); |
| 1186 } | 1190 } |
| 1187 if (codeUnit < _LOWER_CASE_A || codeUnit > _LOWER_CASE_Z) { | 1191 if (codeUnit < minChar) minChar = codeUnit; |
| 1188 allLowercase = false; | |
| 1189 } | |
| 1190 } | 1192 } |
| 1193 // All valid scheme characters are greater than or equal to _UPPER_CASE_A. | |
| 1194 // If they are above _UPPER_CASE_Z, the scheme is already lower-case. | |
|
floitsch
2015/07/17 13:03:20
maybe add assert(_UPPER_CASE_Z < _LOWER_CASE_A);
Lasse Reichstein Nielsen
2015/07/17 18:40:23
I'll revert this, I had missed that there are vali
| |
| 1191 scheme = scheme.substring(start, end); | 1195 scheme = scheme.substring(start, end); |
| 1196 bool allLowercase = minChar > _UPPER_CASE_Z; | |
|
floitsch
2015/07/17 13:03:20
why not minChar >= _LOWER_CASE_A ?
Lasse Reichstein Nielsen
2015/07/17 18:40:23
Because we allow two non-letter characters, one of
| |
| 1192 if (!allLowercase) scheme = scheme.toLowerCase(); | 1197 if (!allLowercase) scheme = scheme.toLowerCase(); |
| 1193 return scheme; | 1198 return scheme; |
| 1194 } | 1199 } |
| 1195 | 1200 |
| 1196 static String _makeUserInfo(String userInfo, int start, int end) { | 1201 static String _makeUserInfo(String userInfo, int start, int end) { |
| 1197 if (userInfo == null) return ""; | 1202 if (userInfo == null) return ""; |
| 1198 return _normalize(userInfo, start, end, _userinfoTable); | 1203 return _normalize(userInfo, start, end, _userinfoTable); |
| 1199 } | 1204 } |
| 1200 | 1205 |
| 1201 static String _makePath(String path, int start, int end, | 1206 static String _makePath(String path, int start, int end, |
| 1202 Iterable<String> pathSegments, | 1207 Iterable<String> pathSegments, |
| 1203 bool ensureLeadingSlash, | 1208 String scheme, |
| 1204 bool isFile) { | 1209 bool hasAuthority) { |
| 1210 bool isFile = (scheme == "file"); | |
| 1211 bool ensureLeadingSlash = isFile || hasAuthority; | |
| 1205 if (path == null && pathSegments == null) return isFile ? "/" : ""; | 1212 if (path == null && pathSegments == null) return isFile ? "/" : ""; |
| 1206 if (path != null && pathSegments != null) { | 1213 if (path != null && pathSegments != null) { |
| 1207 throw new ArgumentError('Both path and pathSegments specified'); | 1214 throw new ArgumentError('Both path and pathSegments specified'); |
| 1208 } | 1215 } |
| 1209 var result; | 1216 var result; |
| 1210 if (path != null) { | 1217 if (path != null) { |
| 1211 result = _normalize(path, start, end, _pathCharOrSlashTable); | 1218 result = _normalize(path, start, end, _pathCharOrSlashTable); |
| 1212 } else { | 1219 } else { |
| 1213 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); | 1220 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); |
| 1214 } | 1221 } |
| 1215 if (result.isEmpty) { | 1222 if (result.isEmpty) { |
| 1216 if (isFile) return "/"; | 1223 if (isFile) return "/"; |
| 1217 } else if ((isFile || ensureLeadingSlash) && | 1224 } else if (ensureLeadingSlash && !result.startsWith('/')) { |
| 1218 result.codeUnitAt(0) != _SLASH) { | 1225 result = "/" + result; |
| 1219 return "/$result"; | |
| 1220 } | 1226 } |
| 1227 result = _normalizePath(result, scheme, hasAuthority); | |
| 1221 return result; | 1228 return result; |
| 1222 } | 1229 } |
| 1223 | 1230 |
| 1231 /// Performs path normalization (remove dot segments) on a path. | |
| 1232 /// | |
| 1233 /// If the URI has neither scheme nor authority, it's considered a | |
| 1234 /// "pure path" and normalization won't remove leading ".." segments. | |
| 1235 /// Otherwise it follows the RFC 3986 "remove dot segments" algorithm. | |
| 1236 static String _normalizePath(String path, String scheme, bool hasAuthority) { | |
| 1237 if (scheme.isEmpty && !hasAuthority && !path.startsWith('/')) { | |
| 1238 return _normalizeRelativePath(path); | |
| 1239 } | |
| 1240 return _removeDotSegments(path); | |
| 1241 } | |
| 1242 | |
| 1224 static String _makeQuery(String query, int start, int end, | 1243 static String _makeQuery(String query, int start, int end, |
| 1225 Map<String, String> queryParameters) { | 1244 Map<String, String> queryParameters) { |
| 1226 if (query == null && queryParameters == null) return null; | 1245 if (query == null && queryParameters == null) return null; |
| 1227 if (query != null && queryParameters != null) { | 1246 if (query != null && queryParameters != null) { |
| 1228 throw new ArgumentError('Both query and queryParameters specified'); | 1247 throw new ArgumentError('Both query and queryParameters specified'); |
| 1229 } | 1248 } |
| 1230 if (query != null) return _normalize(query, start, end, _queryCharTable); | 1249 if (query != null) return _normalize(query, start, end, _queryCharTable); |
| 1231 | 1250 |
| 1232 var result = new StringBuffer(); | 1251 var result = new StringBuffer(); |
| 1233 var first = true; | 1252 var first = true; |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1422 static bool _isGeneralDelimiter(int ch) { | 1441 static bool _isGeneralDelimiter(int ch) { |
| 1423 return ch <= _RIGHT_BRACKET && | 1442 return ch <= _RIGHT_BRACKET && |
| 1424 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 1443 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
| 1425 } | 1444 } |
| 1426 | 1445 |
| 1427 /** | 1446 /** |
| 1428 * Returns whether the URI is absolute. | 1447 * Returns whether the URI is absolute. |
| 1429 */ | 1448 */ |
| 1430 bool get isAbsolute => scheme != "" && fragment == ""; | 1449 bool get isAbsolute => scheme != "" && fragment == ""; |
| 1431 | 1450 |
| 1432 String _merge(String base, String reference) { | 1451 String _mergePaths(String base, String reference) { |
| 1433 if (base.isEmpty) return "/$reference"; | |
| 1434 // Optimize for the case: absolute base, reference beginning with "../". | 1452 // Optimize for the case: absolute base, reference beginning with "../". |
| 1435 int backCount = 0; | 1453 int backCount = 0; |
| 1436 int refStart = 0; | 1454 int refStart = 0; |
| 1437 // Count number of "../" at beginning of reference. | 1455 // Count number of "../" at beginning of reference. |
| 1438 while (reference.startsWith("../", refStart)) { | 1456 while (reference.startsWith("../", refStart)) { |
| 1439 refStart += 3; | 1457 refStart += 3; |
| 1440 backCount++; | 1458 backCount++; |
| 1441 } | 1459 } |
| 1442 | 1460 |
| 1443 // Drop last segment - everything after last '/' of base. | 1461 // Drop last segment - everything after last '/' of base. |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 1456 (delta == 2 || base.codeUnitAt(newEnd + 2) == _DOT)) { | 1474 (delta == 2 || base.codeUnitAt(newEnd + 2) == _DOT)) { |
| 1457 break; | 1475 break; |
| 1458 } | 1476 } |
| 1459 baseEnd = newEnd; | 1477 baseEnd = newEnd; |
| 1460 backCount--; | 1478 backCount--; |
| 1461 } | 1479 } |
| 1462 return base.replaceRange(baseEnd + 1, null, | 1480 return base.replaceRange(baseEnd + 1, null, |
| 1463 reference.substring(refStart - 3 * backCount)); | 1481 reference.substring(refStart - 3 * backCount)); |
| 1464 } | 1482 } |
| 1465 | 1483 |
| 1466 bool _hasDotSegments(String path) { | 1484 /// Make a guess at whether a path contains a `..` or `.` segments. |
|
floitsch
2015/07/17 13:03:20
segment.
Lasse Reichstein Nielsen
2015/07/17 18:40:22
Done.
| |
| 1485 /// | |
| 1486 /// This is a primitive test that can cause false positives. | |
| 1487 /// It's only used to avoid a more expensive operation in the case where | |
| 1488 /// it's not necessary. | |
| 1489 static bool _hasDotSegments(String path) { | |
|
floitsch
2015/07/17 13:03:20
_maybeHasDotSegments ?
Lasse Reichstein Nielsen
2015/07/17 18:40:23
_mayContainDotSegments
| |
| 1467 if (path.length > 0 && path.codeUnitAt(0) == _DOT) return true; | 1490 if (path.length > 0 && path.codeUnitAt(0) == _DOT) return true; |
| 1468 int index = path.indexOf("/."); | 1491 int index = path.indexOf("/."); |
| 1469 return index != -1; | 1492 return index != -1; |
| 1470 } | 1493 } |
| 1471 | 1494 |
| 1472 String _removeDotSegments(String path) { | 1495 /// Removes '.' and '..' segments from a path. |
| 1496 /// | |
| 1497 /// Follows the RFC 2986 "remove dot segments" algorithm. | |
| 1498 /// This algorithm is only used on paths of URIs with a scheme, | |
| 1499 /// and it treats the path as if it is absolute (leading '..' are removed). | |
| 1500 static String _removeDotSegments(String path) { | |
| 1473 if (!_hasDotSegments(path)) return path; | 1501 if (!_hasDotSegments(path)) return path; |
| 1474 List<String> output = []; | 1502 List<String> output = []; |
| 1475 bool appendSlash = false; | 1503 bool appendSlash = false; |
| 1476 for (String segment in path.split("/")) { | 1504 for (String segment in path.split("/")) { |
| 1477 appendSlash = false; | 1505 appendSlash = false; |
| 1478 if (segment == "..") { | 1506 if (segment == "..") { |
| 1479 if (!output.isEmpty && | 1507 if (output.isNotEmpty) { |
| 1480 ((output.length != 1) || (output[0] != ""))) output.removeLast(); | 1508 output.removeLast(); |
| 1509 if (output.isEmpty) { | |
| 1510 output.add(""); | |
| 1511 } | |
| 1512 } | |
| 1481 appendSlash = true; | 1513 appendSlash = true; |
| 1482 } else if ("." == segment) { | 1514 } else if ("." == segment) { |
| 1483 appendSlash = true; | 1515 appendSlash = true; |
| 1484 } else { | 1516 } else { |
| 1485 output.add(segment); | 1517 output.add(segment); |
| 1486 } | 1518 } |
| 1487 } | 1519 } |
| 1488 if (appendSlash) output.add(""); | 1520 if (appendSlash) output.add(""); |
| 1489 return output.join("/"); | 1521 return output.join("/"); |
| 1490 } | 1522 } |
| 1491 | 1523 |
| 1524 /// Removes all `.` segments and any non-leading `..` segments. | |
| 1525 /// | |
| 1526 /// Removing the ".." from a "bar/foo/.." sequence results in "bar/" | |
| 1527 /// (trailing "/"). If the entire path is removed (because it contains as | |
| 1528 /// many ".." segments as real segments), the result is "./". | |
| 1529 /// This is different from an empty string, which represents "no path", | |
| 1530 /// when you resolve it against a base URI with a path with a non-empty | |
| 1531 /// final segment. | |
| 1532 static String _normalizeRelativePath(String path) { | |
| 1533 assert(!path.startsWith('/')); // Only get called for relative paths. | |
| 1534 if (!_hasDotSegments(path)) return path; // Ensures path non-empty below. | |
|
floitsch
2015/07/17 13:03:20
If _hasDotSegments return false for the empty stri
Lasse Reichstein Nielsen
2015/07/17 18:40:23
Well, it's pretty certain that an empty path won't
| |
| 1535 List<String> output = []; | |
| 1536 bool appendSlash = false; | |
| 1537 for (String segment in path.split("/")) { | |
| 1538 appendSlash = false; | |
| 1539 if (".." == segment) { | |
| 1540 if (!output.isEmpty && output.last != "..") { | |
| 1541 output.removeLast(); | |
| 1542 appendSlash = true; | |
| 1543 } else { | |
| 1544 output.add(".."); | |
| 1545 } | |
| 1546 } else if ("." == segment) { | |
| 1547 appendSlash = true; | |
| 1548 } else { | |
| 1549 output.add(segment); | |
| 1550 } | |
| 1551 } | |
| 1552 if (output.isEmpty || output.length == 1 && output[0].isEmpty) return "./"; | |
|
floitsch
2015/07/17 13:03:20
parenthesis
Lasse Reichstein Nielsen
2015/07/17 18:40:22
Ack, with mixed || and &&, some parentheses can im
| |
| 1553 if (appendSlash || output.last == '..') output.add(""); | |
| 1554 return output.join("/"); | |
| 1555 } | |
| 1556 | |
| 1492 /** | 1557 /** |
| 1493 * Resolve [reference] as an URI relative to `this`. | 1558 * Resolve [reference] as an URI relative to `this`. |
| 1494 * | 1559 * |
| 1495 * First turn [reference] into a URI using [Uri.parse]. Then resolve the | 1560 * First turn [reference] into a URI using [Uri.parse]. Then resolve the |
| 1496 * resulting URI relative to `this`. | 1561 * resulting URI relative to `this`. |
| 1497 * | 1562 * |
| 1498 * Returns the resolved URI. | 1563 * Returns the resolved URI. |
| 1499 * | 1564 * |
| 1500 * See [resolveUri] for details. | 1565 * See [resolveUri] for details. |
| 1501 */ | 1566 */ |
| 1502 Uri resolve(String reference) { | 1567 Uri resolve(String reference) { |
| 1503 return resolveUri(Uri.parse(reference)); | 1568 return resolveUri(Uri.parse(reference)); |
| 1504 } | 1569 } |
| 1505 | 1570 |
| 1506 /** | 1571 /** |
| 1507 * Resolve [reference] as an URI relative to `this`. | 1572 * Resolve [reference] as an URI relative to `this`. |
| 1508 * | 1573 * |
| 1509 * Returns the resolved URI. | 1574 * Returns the resolved URI. |
| 1510 * | 1575 * |
| 1511 * The algorithm for resolving a reference is described in | 1576 * The algorithm "Transform Reference" for resolving a reference is |
| 1512 * [RFC-3986 Section 5] | 1577 * described in [RFC-3986 Section 5] |
| 1513 * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123"). | 1578 * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123"). |
| 1579 * | |
| 1580 * Updated to handle the case where the base URI is just a relative path - | |
| 1581 * that is: when it has no scheme or authority and the path does not start | |
| 1582 * with a slash. | |
| 1583 * In that case, the paths are combined without removing leading "..", and | |
| 1584 * an empty path is not converted to "/". | |
| 1514 */ | 1585 */ |
| 1515 Uri resolveUri(Uri reference) { | 1586 Uri resolveUri(Uri reference) { |
| 1516 // From RFC 3986. | 1587 // From RFC 3986. |
| 1517 String targetScheme; | 1588 String targetScheme; |
| 1518 String targetUserInfo = ""; | 1589 String targetUserInfo = ""; |
| 1519 String targetHost; | 1590 String targetHost; |
| 1520 int targetPort; | 1591 int targetPort; |
| 1521 String targetPath; | 1592 String targetPath; |
| 1522 String targetQuery; | 1593 String targetQuery; |
| 1523 if (reference.scheme.isNotEmpty) { | 1594 if (reference.scheme.isNotEmpty) { |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 1534 } else { | 1605 } else { |
| 1535 targetScheme = this.scheme; | 1606 targetScheme = this.scheme; |
| 1536 if (reference.hasAuthority) { | 1607 if (reference.hasAuthority) { |
| 1537 targetUserInfo = reference.userInfo; | 1608 targetUserInfo = reference.userInfo; |
| 1538 targetHost = reference.host; | 1609 targetHost = reference.host; |
| 1539 targetPort = _makePort(reference.hasPort ? reference.port : null, | 1610 targetPort = _makePort(reference.hasPort ? reference.port : null, |
| 1540 targetScheme); | 1611 targetScheme); |
| 1541 targetPath = _removeDotSegments(reference.path); | 1612 targetPath = _removeDotSegments(reference.path); |
| 1542 if (reference.hasQuery) targetQuery = reference.query; | 1613 if (reference.hasQuery) targetQuery = reference.query; |
| 1543 } else { | 1614 } else { |
| 1615 targetUserInfo = this._userInfo; | |
| 1616 targetHost = this._host; | |
| 1617 targetPort = this._port; | |
| 1544 if (reference.path == "") { | 1618 if (reference.path == "") { |
| 1545 targetPath = this._path; | 1619 targetPath = this._path; |
| 1546 if (reference.hasQuery) { | 1620 if (reference.hasQuery) { |
| 1547 targetQuery = reference.query; | 1621 targetQuery = reference.query; |
| 1548 } else { | 1622 } else { |
| 1549 targetQuery = this._query; | 1623 targetQuery = this._query; |
| 1550 } | 1624 } |
| 1551 } else { | 1625 } else { |
| 1552 if (reference.path.startsWith("/")) { | 1626 if (reference.hasAbsolutePath) { |
| 1553 targetPath = _removeDotSegments(reference.path); | 1627 targetPath = _removeDotSegments(reference.path); |
| 1554 } else { | 1628 } else { |
| 1555 targetPath = _removeDotSegments(_merge(this._path, reference.path)); | 1629 // This is the RFC 3986 behavior for merging. |
| 1630 if (this.hasEmptyPath) { | |
| 1631 if (!this.hasScheme && !this.hasAuthority) { | |
| 1632 // Keep the path relative if no scheme or authority. | |
| 1633 targetPath = reference.path; | |
| 1634 } else { | |
| 1635 // Add path normalization on top of RFC algorithm. | |
| 1636 targetPath = _removeDotSegments("/" + reference.path); | |
| 1637 } | |
| 1638 } else { | |
| 1639 var mergedPath = _mergePaths(this._path, reference.path); | |
| 1640 if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) { | |
| 1641 targetPath = _removeDotSegments(mergedPath); | |
| 1642 } else { | |
| 1643 // Non-RFC 3986 beavior. If both base and reference are relative | |
|
floitsch
2015/07/17 13:03:20
behavior.
Also state if this is violating the RFC
Lasse Reichstein Nielsen
2015/07/17 18:40:23
Not covered, will document.
| |
| 1644 // path, allow the merged path to start with "..". | |
| 1645 targetPath = _normalizeRelativePath(mergedPath); | |
| 1646 } | |
| 1647 } | |
| 1556 } | 1648 } |
| 1557 if (reference.hasQuery) targetQuery = reference.query; | 1649 if (reference.hasQuery) targetQuery = reference.query; |
| 1558 } | 1650 } |
| 1559 targetUserInfo = this._userInfo; | |
| 1560 targetHost = this._host; | |
| 1561 targetPort = this._port; | |
| 1562 } | 1651 } |
| 1563 } | 1652 } |
| 1564 String fragment = reference.hasFragment ? reference.fragment : null; | 1653 String fragment = reference.hasFragment ? reference.fragment : null; |
| 1565 return new Uri._internal(targetScheme, | 1654 return new Uri._internal(targetScheme, |
| 1566 targetUserInfo, | 1655 targetUserInfo, |
| 1567 targetHost, | 1656 targetHost, |
| 1568 targetPort, | 1657 targetPort, |
| 1569 targetPath, | 1658 targetPath, |
| 1570 targetQuery, | 1659 targetQuery, |
| 1571 fragment); | 1660 fragment); |
| 1572 } | 1661 } |
| 1573 | 1662 |
| 1574 /** | 1663 /** |
| 1664 * Returns whether the URI has a [scheme] component. | |
| 1665 */ | |
| 1666 bool get hasScheme => scheme.isNotEmpty; | |
| 1667 | |
| 1668 /** | |
| 1575 * Returns whether the URI has an [authority] component. | 1669 * Returns whether the URI has an [authority] component. |
| 1576 */ | 1670 */ |
| 1577 bool get hasAuthority => _host != null; | 1671 bool get hasAuthority => _host != null; |
| 1578 | 1672 |
| 1579 /** | 1673 /** |
| 1580 * Returns whether the URI has an explicit port. | 1674 * Returns whether the URI has an explicit port. |
| 1581 * | 1675 * |
| 1582 * If the port number is the default port number | 1676 * If the port number is the default port number |
| 1583 * (zero for unrecognized schemes, with http (80) and https (443) being | 1677 * (zero for unrecognized schemes, with http (80) and https (443) being |
| 1584 * recognized), | 1678 * recognized), |
| 1585 * then the port is made implicit and omitted from the URI. | 1679 * then the port is made implicit and omitted from the URI. |
| 1586 */ | 1680 */ |
| 1587 bool get hasPort => _port != null; | 1681 bool get hasPort => _port != null; |
| 1588 | 1682 |
| 1589 /** | 1683 /** |
| 1590 * Returns whether the URI has a query part. | 1684 * Returns whether the URI has a query part. |
| 1591 */ | 1685 */ |
| 1592 bool get hasQuery => _query != null; | 1686 bool get hasQuery => _query != null; |
| 1593 | 1687 |
| 1594 /** | 1688 /** |
| 1595 * Returns whether the URI has a fragment part. | 1689 * Returns whether the URI has a fragment part. |
| 1596 */ | 1690 */ |
| 1597 bool get hasFragment => _fragment != null; | 1691 bool get hasFragment => _fragment != null; |
| 1598 | 1692 |
| 1599 /** | 1693 /** |
| 1694 * Returns whether the URI has an empty path. | |
| 1695 */ | |
| 1696 bool get hasEmptyPath => _path.isEmpty; | |
| 1697 | |
| 1698 /** | |
| 1699 * Returns whether the URI has an absolute path (starting with '/'). | |
| 1700 */ | |
| 1701 bool get hasAbsolutePath => _path.startsWith('/'); | |
| 1702 | |
| 1703 /** | |
| 1600 * Returns the origin of the URI in the form scheme://host:port for the | 1704 * Returns the origin of the URI in the form scheme://host:port for the |
| 1601 * schemes http and https. | 1705 * schemes http and https. |
| 1602 * | 1706 * |
| 1603 * It is an error if the scheme is not "http" or "https". | 1707 * It is an error if the scheme is not "http" or "https". |
| 1604 * | 1708 * |
| 1605 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin | 1709 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin |
| 1606 */ | 1710 */ |
| 1607 String get origin { | 1711 String get origin { |
| 1608 if (scheme == "" || _host == null || _host == "") { | 1712 if (scheme == "" || _host == null || _host == "") { |
| 1609 throw new StateError("Cannot use origin without a scheme: $this"); | 1713 throw new StateError("Cannot use origin without a scheme: $this"); |
| (...skipping 860 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2470 0xafff, // 0x30 - 0x3f 1111111111110101 | 2574 0xafff, // 0x30 - 0x3f 1111111111110101 |
| 2471 // @ABCDEFGHIJKLMNO | 2575 // @ABCDEFGHIJKLMNO |
| 2472 0xffff, // 0x40 - 0x4f 1111111111111111 | 2576 0xffff, // 0x40 - 0x4f 1111111111111111 |
| 2473 // PQRSTUVWXYZ _ | 2577 // PQRSTUVWXYZ _ |
| 2474 0x87ff, // 0x50 - 0x5f 1111111111100001 | 2578 0x87ff, // 0x50 - 0x5f 1111111111100001 |
| 2475 // abcdefghijklmno | 2579 // abcdefghijklmno |
| 2476 0xfffe, // 0x60 - 0x6f 0111111111111111 | 2580 0xfffe, // 0x60 - 0x6f 0111111111111111 |
| 2477 // pqrstuvwxyz ~ | 2581 // pqrstuvwxyz ~ |
| 2478 0x47ff]; // 0x70 - 0x7f 1111111111100010 | 2582 0x47ff]; // 0x70 - 0x7f 1111111111100010 |
| 2479 } | 2583 } |
| OLD | NEW |