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, as specified by RFC-3986, http://tools.ietf.org/html/rfc3986. | 8 * A parsed URI, as specified by RFC-3986, http://tools.ietf.org/html/rfc3986. |
9 */ | 9 */ |
10 class Uri { | 10 class Uri { |
11 final String _host; | |
11 int _port; | 12 int _port; |
12 String _path; | 13 String _path; |
13 | 14 |
14 /** | 15 /** |
15 * Returns the scheme component. | 16 * Returns the scheme component. |
16 * | 17 * |
17 * Returns the empty string if there is no scheme component. | 18 * Returns the empty string if there is no scheme component. |
18 */ | 19 */ |
19 final String scheme; | 20 final String scheme; |
20 | 21 |
(...skipping 18 matching lines...) Expand all Loading... | |
39 * Returns the empty string if there is no user info in the | 40 * Returns the empty string if there is no user info in the |
40 * authority component. | 41 * authority component. |
41 */ | 42 */ |
42 final String userInfo; | 43 final String userInfo; |
43 | 44 |
44 /** | 45 /** |
45 * Returns the host part of the authority component. | 46 * Returns the host part of the authority component. |
46 * | 47 * |
47 * Returns the empty string if there is no authority component and | 48 * Returns the empty string if there is no authority component and |
48 * hence no host. | 49 * hence no host. |
50 * | |
51 * If the host is an IP version 6 address, the surrounding `[` and `]` is | |
52 * removed. | |
49 */ | 53 */ |
50 final String host; | 54 String get host { |
55 if (_host != null && _host.startsWith('[')) { | |
56 return _host.substring(1, _host.length - 1); | |
57 } | |
58 return _host; | |
59 } | |
51 | 60 |
52 /** | 61 /** |
53 * Returns the port part of the authority component. | 62 * Returns the port part of the authority component. |
54 * | 63 * |
55 * Returns 0 if there is no port in the authority component. | 64 * Returns 0 if there is no port in the authority component. |
56 */ | 65 */ |
57 int get port => _port; | 66 int get port => _port; |
58 | 67 |
59 /** | 68 /** |
60 * Returns the path component. | 69 * Returns the path component. |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
146 * [queryParameters]. When [query] is used the provided string is | 155 * [queryParameters]. When [query] is used the provided string is |
147 * expected to be fully percent-encoded and is used in its literal | 156 * expected to be fully percent-encoded and is used in its literal |
148 * form. When [queryParameters] is used the query is built from the | 157 * form. When [queryParameters] is used the query is built from the |
149 * provided map. Each key and value in the map is percent-encoded | 158 * provided map. Each key and value in the map is percent-encoded |
150 * and joined using equal and ampersand characters. The | 159 * and joined using equal and ampersand characters. The |
151 * percent-encoding of the keys and values encodes all characters | 160 * percent-encoding of the keys and values encodes all characters |
152 * except for the unreserved characters. | 161 * except for the unreserved characters. |
153 * | 162 * |
154 * The fragment component is set through [fragment]. | 163 * The fragment component is set through [fragment]. |
155 */ | 164 */ |
156 Uri({scheme, | 165 Uri({String scheme, |
157 this.userInfo: "", | 166 this.userInfo: "", |
158 this.host: "", | 167 String host: "", |
159 port: 0, | 168 port: 0, |
160 String path, | 169 String path, |
161 Iterable<String> pathSegments, | 170 Iterable<String> pathSegments, |
162 String query, | 171 String query, |
163 Map<String, String> queryParameters, | 172 Map<String, String> queryParameters, |
164 fragment: ""}) : | 173 fragment: ""}) : |
165 scheme = _makeScheme(scheme), | 174 scheme = _makeScheme(scheme), |
175 _host = _makeHost(host), | |
166 query = _makeQuery(query, queryParameters), | 176 query = _makeQuery(query, queryParameters), |
167 fragment = _makeFragment(fragment) { | 177 fragment = _makeFragment(fragment) { |
168 // Perform scheme specific normalization. | 178 // Perform scheme specific normalization. |
169 if (scheme == "http" && port == 80) { | 179 if (scheme == "http" && port == 80) { |
170 _port = 0; | 180 _port = 0; |
171 } else if (scheme == "https" && port == 443) { | 181 } else if (scheme == "https" && port == 443) { |
172 _port = 0; | 182 _port = 0; |
173 } else { | 183 } else { |
174 _port = port; | 184 _port = port; |
175 } | 185 } |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
230 // Split off the user info. | 240 // Split off the user info. |
231 bool hasUserInfo = false; | 241 bool hasUserInfo = false; |
232 for (int i = 0; i < authority.length; i++) { | 242 for (int i = 0; i < authority.length; i++) { |
233 if (authority.codeUnitAt(i) == _AT_SIGN) { | 243 if (authority.codeUnitAt(i) == _AT_SIGN) { |
234 hasUserInfo = true; | 244 hasUserInfo = true; |
235 userInfo = authority.substring(0, i); | 245 userInfo = authority.substring(0, i); |
236 hostStart = i + 1; | 246 hostStart = i + 1; |
237 break; | 247 break; |
238 } | 248 } |
239 } | 249 } |
250 var hostEnd = hostStart; | |
251 if (hostStart < authority.length && | |
252 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { | |
253 // IPv6 host. | |
254 for (; hostEnd < authority.length; hostEnd++) { | |
255 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; | |
256 } | |
257 if (hostEnd == authority.length) { | |
258 throw new FormatException("Invalid IPv6 host entry."); | |
259 } | |
260 parseIPv6Address(authority.substring(hostStart + 1, hostEnd)); | |
261 hostEnd++; // Skip the closing bracket. | |
262 if (hostEnd != authority.length && | |
263 authority.codeUnitAt(hostEnd) != _COLON) { | |
264 throw new FormatException("Invalid end of authority"); | |
265 } | |
266 } | |
240 // Split host and port. | 267 // Split host and port. |
241 bool hasPort = false; | 268 bool hasPort = false; |
242 for (int i = hostStart; i < authority.length; i++) { | 269 for (; hostEnd < authority.length; hostEnd++) { |
243 if (authority.codeUnitAt(i) == _COLON) { | 270 if (authority.codeUnitAt(hostEnd) == _COLON) { |
244 hasPort = true; | 271 var portString = authority.substring(hostEnd + 1); |
245 host = authority.substring(hostStart, i); | 272 // We allow the empty port - falling back to initial value. |
246 if (!host.isEmpty) { | 273 if (portString.isNotEmpty) port = int.parse(portString); |
247 var portString = authority.substring(i + 1); | |
248 if (portString.isNotEmpty) port = int.parse(portString); | |
249 } | |
250 break; | 274 break; |
251 } | 275 } |
252 } | 276 } |
253 if (!hasPort) { | 277 host = authority.substring(hostStart, hostEnd); |
254 host = hasUserInfo ? authority.substring(hostStart) : authority; | |
255 } | |
256 | 278 |
257 return new Uri(scheme: scheme, | 279 return new Uri(scheme: scheme, |
258 userInfo: userInfo, | 280 userInfo: userInfo, |
259 host: host, | 281 host: host, |
260 port: port, | 282 port: port, |
261 pathSegments: unencodedPath.split("/"), | 283 pathSegments: unencodedPath.split("/"), |
262 queryParameters: queryParameters); | 284 queryParameters: queryParameters); |
263 } | 285 } |
264 | 286 |
265 /** | 287 /** |
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
467 * The returned map is unmodifiable and will throw [UnsupportedError] on any | 489 * The returned map is unmodifiable and will throw [UnsupportedError] on any |
468 * calls that would mutate it. | 490 * calls that would mutate it. |
469 */ | 491 */ |
470 Map<String, String> get queryParameters { | 492 Map<String, String> get queryParameters { |
471 if (_queryParameters == null) { | 493 if (_queryParameters == null) { |
472 _queryParameters = new _UnmodifiableMap(splitQueryString(query)); | 494 _queryParameters = new _UnmodifiableMap(splitQueryString(query)); |
473 } | 495 } |
474 return _queryParameters; | 496 return _queryParameters; |
475 } | 497 } |
476 | 498 |
499 static String _makeHost(String host) { | |
500 if (host == null || host.isEmpty) return host; | |
501 if (host.codeUnitAt(0) == _LEFT_BRACKET) { | |
502 if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) { | |
503 throw new FormatException('Missing end `]` to match `[` in host'); | |
504 } | |
505 parseIPv6Address(host.substring(1, host.length - 1)); | |
506 return host; | |
507 } | |
508 for (int i = 0; i < host.length; i++) { | |
509 if (host.codeUnitAt(i) == _COLON) { | |
510 parseIPv6Address(host); | |
511 return '[$host]'; | |
512 } | |
513 } | |
514 return host; | |
515 } | |
516 | |
477 static String _makeScheme(String scheme) { | 517 static String _makeScheme(String scheme) { |
478 bool isSchemeLowerCharacter(int ch) { | 518 bool isSchemeLowerCharacter(int ch) { |
479 return ch < 128 && | 519 return ch < 128 && |
480 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 520 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
481 } | 521 } |
482 | 522 |
483 bool isSchemeCharacter(int ch) { | 523 bool isSchemeCharacter(int ch) { |
484 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); | 524 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); |
485 } | 525 } |
486 | 526 |
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
821 | 861 |
822 /** | 862 /** |
823 * Returns the origin of the URI in the form scheme://host:port for the | 863 * Returns the origin of the URI in the form scheme://host:port for the |
824 * schemes http and https. | 864 * schemes http and https. |
825 * | 865 * |
826 * It is an error if the scheme is not "http" or "https". | 866 * It is an error if the scheme is not "http" or "https". |
827 * | 867 * |
828 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin | 868 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin |
829 */ | 869 */ |
830 String get origin { | 870 String get origin { |
831 if (scheme == "" || host == null || host == "") { | 871 if (scheme == "" || _host == null || _host == "") { |
832 throw new StateError("Cannot use origin without a scheme: $this"); | 872 throw new StateError("Cannot use origin without a scheme: $this"); |
833 } | 873 } |
834 if (scheme != "http" && scheme != "https") { | 874 if (scheme != "http" && scheme != "https") { |
835 throw new StateError( | 875 throw new StateError( |
836 "Origin is only applicable schemes http and https: $this"); | 876 "Origin is only applicable schemes http and https: $this"); |
837 } | 877 } |
838 if (port == 0) return "$scheme://$host"; | 878 if (port == 0) return "$scheme://$_host"; |
839 return "$scheme://$host:$port"; | 879 return "$scheme://$_host:$port"; |
840 } | 880 } |
841 | 881 |
842 /** | 882 /** |
843 * Returns the file path from a file URI. | 883 * Returns the file path from a file URI. |
844 * | 884 * |
845 * The returned path has either Windows or non-Windows | 885 * The returned path has either Windows or non-Windows |
846 * semantics. | 886 * semantics. |
847 * | 887 * |
848 * For non-Windows semantics the slash ("/") is used to separate | 888 * For non-Windows semantics the slash ("/") is used to separate |
849 * path segments. | 889 * path segments. |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
955 result.write(host); | 995 result.write(host); |
956 result.write("\\"); | 996 result.write("\\"); |
957 } | 997 } |
958 result.writeAll(segments, "\\"); | 998 result.writeAll(segments, "\\"); |
959 if (hasDriveLetter && segments.length == 1) result.write("\\"); | 999 if (hasDriveLetter && segments.length == 1) result.write("\\"); |
960 return result.toString(); | 1000 return result.toString(); |
961 } | 1001 } |
962 | 1002 |
963 void _writeAuthority(StringSink ss) { | 1003 void _writeAuthority(StringSink ss) { |
964 _addIfNonEmpty(ss, userInfo, userInfo, "@"); | 1004 _addIfNonEmpty(ss, userInfo, userInfo, "@"); |
965 ss.write(host == null ? "null" : | 1005 ss.write(_host == null ? "null" : _host); |
966 host.contains(':') ? '[$host]' : host); | |
967 if (port != 0) { | 1006 if (port != 0) { |
968 ss.write(":"); | 1007 ss.write(":"); |
969 ss.write(port.toString()); | 1008 ss.write(port.toString()); |
970 } | 1009 } |
971 } | 1010 } |
972 | 1011 |
973 String toString() { | 1012 String toString() { |
974 StringBuffer sb = new StringBuffer(); | 1013 StringBuffer sb = new StringBuffer(); |
975 _addIfNonEmpty(sb, scheme, scheme, ':'); | 1014 _addIfNonEmpty(sb, scheme, scheme, ':'); |
976 if (hasAuthority || (scheme == "file")) { | 1015 if (hasAuthority || (scheme == "file")) { |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1141 } else if (index != 0) { | 1180 } else if (index != 0) { |
1142 var key = element.substring(0, index); | 1181 var key = element.substring(0, index); |
1143 var value = element.substring(index + 1); | 1182 var value = element.substring(index + 1); |
1144 map[Uri.decodeQueryComponent(key, decode: decode)] = | 1183 map[Uri.decodeQueryComponent(key, decode: decode)] = |
1145 decodeQueryComponent(value, decode: decode); | 1184 decodeQueryComponent(value, decode: decode); |
1146 } | 1185 } |
1147 return map; | 1186 return map; |
1148 }); | 1187 }); |
1149 } | 1188 } |
1150 | 1189 |
1190 /** | |
1191 * Parse the [host] as a IP version 4 (IPv4) address, returning the address | |
floitsch
2013/09/11 13:13:42
an IP
Anders Johnsen
2013/09/11 13:21:44
Done.
| |
1192 * as a list of 4 bytes in network byte order (big endian). | |
1193 * | |
1194 * Throws a [FormatException] if [host] is not a valid IPv4 address | |
1195 * representation. | |
1196 */ | |
1197 static List<int> parseIPv4Address(String host) { | |
1198 void error(String msg) { | |
1199 throw new FormatException('Illegal IPv4 address, $msg'); | |
1200 } | |
1201 var bytes = host.split('.'); | |
1202 if (bytes.length != 4) { | |
1203 error('IPv4 address should contain exactly 4 parts'); | |
1204 } | |
1205 // TODO: Consider using Uint8List. | |
floitsch
2013/09/11 13:13:42
TODO(issue-number) or
TODO(ldap)
Anders Johnsen
2013/09/11 13:21:44
Done.
| |
1206 return bytes | |
1207 .map((byteString) { | |
1208 int byte = int.parse(byteString); | |
1209 if (byte < 0 || byte > 255) { | |
1210 error('each part must be in the range of `0..255`'); | |
1211 } | |
1212 return byte; | |
1213 }) | |
1214 .toList(); | |
1215 } | |
1216 | |
1217 /** | |
1218 * Parse the [host] as a IP version 6 (IPv6) address, returning the address | |
floitsch
2013/09/11 13:13:42
an IP
Anders Johnsen
2013/09/11 13:21:44
Done.
| |
1219 * as a list of 16 bytes in network byte order (big endian). | |
1220 * | |
1221 * Throws a [FormatException] if [host] is not a valid IPv6 address | |
1222 * representation. | |
floitsch
2013/09/11 13:13:42
Still missing the examples in the documentation.
Anders Johnsen
2013/09/11 13:21:44
Done.
| |
1223 */ | |
1224 static List<int> parseIPv6Address(String host) { | |
1225 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated | |
1226 // by `:`'s, with the following exceptions: | |
1227 // | |
1228 // - One (and only one) wildcard (`::`) may be present, representing a fill | |
1229 // of 0's. The IPv6 `::` is thus 16 bytes of `0`. | |
1230 // - The last two parts may be replaced by an IPv4 address. | |
1231 void error(String msg) { | |
1232 throw new FormatException('Illegal IPv6 address, $msg'); | |
1233 } | |
1234 int parseHex(int start, int end) { | |
1235 if (end - start > 4) { | |
1236 error('an IPv6 part can only contain a maximum of 4 hex digits'); | |
1237 } | |
1238 int value = int.parse(host.substring(start, end), radix: 16); | |
1239 if (value < 0 || value > (1 << 16) - 1) { | |
1240 error('each part must be in the range of `0x0..0xFFFF`'); | |
1241 } | |
1242 return value; | |
1243 } | |
1244 if (host.length < 2) error('address is too short'); | |
1245 List<int> parts = []; | |
1246 bool wildcardSeen = false; | |
1247 int partStart = 0; | |
1248 // Parse all parts, except a potential last one. | |
1249 for (int i = 0; i < host.length; i++) { | |
1250 if (host.codeUnitAt(i) == _COLON) { | |
1251 if (i == 0) { | |
1252 // If we see a `:` in the beginning, expect wildcard. | |
1253 i++; | |
1254 if (host.codeUnitAt(i) != _COLON) { | |
1255 error('invalid start colon.'); | |
1256 } | |
1257 partStart = i; | |
1258 } | |
1259 if (i == partStart) { | |
1260 // Wildcard. We only allow one. | |
1261 if (wildcardSeen) { | |
1262 error('only one wildcard `::` is allowed'); | |
1263 } | |
1264 wildcardSeen = true; | |
1265 parts.add(-1); | |
1266 } else { | |
1267 // Found a single colon. Parse [partStart..i] as a hex entry. | |
1268 parts.add(parseHex(partStart, i)); | |
1269 } | |
1270 partStart = i + 1; | |
1271 } | |
1272 } | |
1273 if (parts.length == 0) error('too few parts'); | |
1274 bool atEnd = partStart == host.length; | |
1275 bool isLastWildcard = parts.last == -1; | |
1276 if (atEnd && !isLastWildcard) { | |
1277 error('expected a part after last `:`'); | |
1278 } | |
1279 if (!atEnd) { | |
1280 try { | |
1281 parts.add(parseHex(partStart, host.length)); | |
1282 } catch (e) { | |
1283 // Failed to parse the last chunk as hex. Try IPv4. | |
1284 try { | |
1285 List<int> last = parseIPv4Address(host.substring(partStart)); | |
1286 parts.add(last[0] << 8 | last[1]); | |
1287 parts.add(last[2] << 8 | last[3]); | |
1288 } catch (e) { | |
1289 error('invalid end of IPv6 address.'); | |
1290 } | |
1291 } | |
1292 } | |
1293 if (wildcardSeen) { | |
1294 if (parts.length > 7) { | |
1295 error('an address with a wildcard must have less than 7 parts'); | |
1296 } | |
1297 } else if (parts.length != 8) { | |
1298 error('an address without a wildcard must contain exactly 8 parts'); | |
1299 } | |
1300 // TODO: Consider using Uint8List. | |
floitsch
2013/09/11 13:13:42
ditto.
Anders Johnsen
2013/09/11 13:21:44
Done.
| |
1301 return parts | |
1302 .expand((value) { | |
1303 if (value == -1) { | |
1304 return new List.filled((9 - parts.length) * 2, 0); | |
1305 } else { | |
1306 return [(value >> 8) & 0xFF, value & 0xFF]; | |
1307 } | |
1308 }) | |
1309 .toList(); | |
1310 } | |
1311 | |
1151 // Frequently used character codes. | 1312 // Frequently used character codes. |
1152 static const int _DOUBLE_QUOTE = 0x22; | 1313 static const int _DOUBLE_QUOTE = 0x22; |
1153 static const int _PERCENT = 0x25; | 1314 static const int _PERCENT = 0x25; |
1154 static const int _ASTERISK = 0x2A; | 1315 static const int _ASTERISK = 0x2A; |
1155 static const int _PLUS = 0x2B; | 1316 static const int _PLUS = 0x2B; |
1156 static const int _SLASH = 0x2F; | 1317 static const int _SLASH = 0x2F; |
1157 static const int _ZERO = 0x30; | 1318 static const int _ZERO = 0x30; |
1158 static const int _NINE = 0x39; | 1319 static const int _NINE = 0x39; |
1159 static const int _COLON = 0x3A; | 1320 static const int _COLON = 0x3A; |
1160 static const int _LESS = 0x3C; | 1321 static const int _LESS = 0x3C; |
1161 static const int _GREATER = 0x3E; | 1322 static const int _GREATER = 0x3E; |
1162 static const int _QUESTION = 0x3F; | 1323 static const int _QUESTION = 0x3F; |
1163 static const int _AT_SIGN = 0x40; | 1324 static const int _AT_SIGN = 0x40; |
1164 static const int _UPPER_CASE_A = 0x41; | 1325 static const int _UPPER_CASE_A = 0x41; |
1165 static const int _UPPER_CASE_F = 0x46; | 1326 static const int _UPPER_CASE_F = 0x46; |
1166 static const int _UPPER_CASE_Z = 0x5A; | 1327 static const int _UPPER_CASE_Z = 0x5A; |
1328 static const int _LEFT_BRACKET = 0x5B; | |
1167 static const int _BACKSLASH = 0x5C; | 1329 static const int _BACKSLASH = 0x5C; |
1330 static const int _RIGHT_BRACKET = 0x5D; | |
1168 static const int _LOWER_CASE_A = 0x61; | 1331 static const int _LOWER_CASE_A = 0x61; |
1169 static const int _LOWER_CASE_F = 0x66; | 1332 static const int _LOWER_CASE_F = 0x66; |
1170 static const int _LOWER_CASE_Z = 0x7A; | 1333 static const int _LOWER_CASE_Z = 0x7A; |
1171 static const int _BAR = 0x7C; | 1334 static const int _BAR = 0x7C; |
1172 | 1335 |
1173 /** | 1336 /** |
1174 * This is the internal implementation of JavaScript's encodeURI function. | 1337 * This is the internal implementation of JavaScript's encodeURI function. |
1175 * It encodes all characters in the string [text] except for those | 1338 * It encodes all characters in the string [text] except for those |
1176 * that appear in [canonicalTable], and returns the escaped string. | 1339 * that appear in [canonicalTable], and returns the escaped string. |
1177 */ | 1340 */ |
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1465 void clear() { | 1628 void clear() { |
1466 throw new UnsupportedError("Cannot modify an unmodifiable map"); | 1629 throw new UnsupportedError("Cannot modify an unmodifiable map"); |
1467 } | 1630 } |
1468 void forEach(void f(K key, V value)) => _map.forEach(f); | 1631 void forEach(void f(K key, V value)) => _map.forEach(f); |
1469 Iterable<K> get keys => _map.keys; | 1632 Iterable<K> get keys => _map.keys; |
1470 Iterable<V> get values => _map.values; | 1633 Iterable<V> get values => _map.values; |
1471 int get length => _map.length; | 1634 int get length => _map.length; |
1472 bool get isEmpty => _map.isEmpty; | 1635 bool get isEmpty => _map.isEmpty; |
1473 bool get isNotEmpty => _map.isNotEmpty; | 1636 bool get isNotEmpty => _map.isNotEmpty; |
1474 } | 1637 } |
OLD | NEW |