| Index: sdk/lib/core/uri.dart
|
| diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
|
| index 30057f1132e5fc0952ffa3172755189f76a71116..cf4bff600bc2b5dc04ae6ccca476489e9a0ace30 100644
|
| --- a/sdk/lib/core/uri.dart
|
| +++ b/sdk/lib/core/uri.dart
|
| @@ -8,6 +8,7 @@ part of dart.core;
|
| * A parsed URI, as specified by RFC-3986, http://tools.ietf.org/html/rfc3986.
|
| */
|
| class Uri {
|
| + final String _host;
|
| int _port;
|
| String _path;
|
|
|
| @@ -46,8 +47,16 @@ class Uri {
|
| *
|
| * Returns the empty string if there is no authority component and
|
| * hence no host.
|
| + *
|
| + * If the host is an IP version 6 address, the surrounding `[` and `]` is
|
| + * removed.
|
| */
|
| - final String host;
|
| + String get host {
|
| + if (_host != null && _host.startsWith('[')) {
|
| + return _host.substring(1, _host.length - 1);
|
| + }
|
| + return _host;
|
| + }
|
|
|
| /**
|
| * Returns the port part of the authority component.
|
| @@ -123,7 +132,7 @@ class Uri {
|
| * [userInfo].
|
| *
|
| * The host part of the authority component is set through
|
| - * [host]. The host can either be a hostname, a IPv4 address or an
|
| + * [host]. The host can either be a hostname, an IPv4 address or an
|
| * IPv6 address, contained in '[' and ']'. If the host contains a
|
| * ':' character, the '[' and ']' are added if not already provided.
|
| *
|
| @@ -153,9 +162,9 @@ class Uri {
|
| *
|
| * The fragment component is set through [fragment].
|
| */
|
| - Uri({scheme,
|
| + Uri({String scheme,
|
| this.userInfo: "",
|
| - this.host: "",
|
| + String host: "",
|
| port: 0,
|
| String path,
|
| Iterable<String> pathSegments,
|
| @@ -163,6 +172,7 @@ class Uri {
|
| Map<String, String> queryParameters,
|
| fragment: ""}) :
|
| scheme = _makeScheme(scheme),
|
| + _host = _makeHost(host),
|
| query = _makeQuery(query, queryParameters),
|
| fragment = _makeFragment(fragment) {
|
| // Perform scheme specific normalization.
|
| @@ -237,22 +247,34 @@ class Uri {
|
| break;
|
| }
|
| }
|
| + var hostEnd = hostStart;
|
| + if (hostStart < authority.length &&
|
| + authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
|
| + // IPv6 host.
|
| + for (; hostEnd < authority.length; hostEnd++) {
|
| + if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
|
| + }
|
| + if (hostEnd == authority.length) {
|
| + throw new FormatException("Invalid IPv6 host entry.");
|
| + }
|
| + parseIPv6Address(authority.substring(hostStart + 1, hostEnd));
|
| + hostEnd++; // Skip the closing bracket.
|
| + if (hostEnd != authority.length &&
|
| + authority.codeUnitAt(hostEnd) != _COLON) {
|
| + throw new FormatException("Invalid end of authority");
|
| + }
|
| + }
|
| // Split host and port.
|
| bool hasPort = false;
|
| - for (int i = hostStart; i < authority.length; i++) {
|
| - if (authority.codeUnitAt(i) == _COLON) {
|
| - hasPort = true;
|
| - host = authority.substring(hostStart, i);
|
| - if (!host.isEmpty) {
|
| - var portString = authority.substring(i + 1);
|
| - if (portString.isNotEmpty) port = int.parse(portString);
|
| - }
|
| + for (; hostEnd < authority.length; hostEnd++) {
|
| + if (authority.codeUnitAt(hostEnd) == _COLON) {
|
| + var portString = authority.substring(hostEnd + 1);
|
| + // We allow the empty port - falling back to initial value.
|
| + if (portString.isNotEmpty) port = int.parse(portString);
|
| break;
|
| }
|
| }
|
| - if (!hasPort) {
|
| - host = hasUserInfo ? authority.substring(hostStart) : authority;
|
| - }
|
| + host = authority.substring(hostStart, hostEnd);
|
|
|
| return new Uri(scheme: scheme,
|
| userInfo: userInfo,
|
| @@ -474,6 +496,24 @@ class Uri {
|
| return _queryParameters;
|
| }
|
|
|
| + static String _makeHost(String host) {
|
| + if (host == null || host.isEmpty) return host;
|
| + if (host.codeUnitAt(0) == _LEFT_BRACKET) {
|
| + if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) {
|
| + throw new FormatException('Missing end `]` to match `[` in host');
|
| + }
|
| + parseIPv6Address(host.substring(1, host.length - 1));
|
| + return host;
|
| + }
|
| + for (int i = 0; i < host.length; i++) {
|
| + if (host.codeUnitAt(i) == _COLON) {
|
| + parseIPv6Address(host);
|
| + return '[$host]';
|
| + }
|
| + }
|
| + return host;
|
| + }
|
| +
|
| static String _makeScheme(String scheme) {
|
| bool isSchemeLowerCharacter(int ch) {
|
| return ch < 128 &&
|
| @@ -828,15 +868,15 @@ class Uri {
|
| * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
|
| */
|
| String get origin {
|
| - if (scheme == "" || host == null || host == "") {
|
| + if (scheme == "" || _host == null || _host == "") {
|
| throw new StateError("Cannot use origin without a scheme: $this");
|
| }
|
| if (scheme != "http" && scheme != "https") {
|
| throw new StateError(
|
| "Origin is only applicable schemes http and https: $this");
|
| }
|
| - if (port == 0) return "$scheme://$host";
|
| - return "$scheme://$host:$port";
|
| + if (port == 0) return "$scheme://$_host";
|
| + return "$scheme://$_host:$port";
|
| }
|
|
|
| /**
|
| @@ -962,8 +1002,7 @@ class Uri {
|
|
|
| void _writeAuthority(StringSink ss) {
|
| _addIfNonEmpty(ss, userInfo, userInfo, "@");
|
| - ss.write(host == null ? "null" :
|
| - host.contains(':') ? '[$host]' : host);
|
| + ss.write(_host == null ? "null" : _host);
|
| if (port != 0) {
|
| ss.write(":");
|
| ss.write(port.toString());
|
| @@ -1148,6 +1187,135 @@ class Uri {
|
| });
|
| }
|
|
|
| + /**
|
| + * Parse the [host] as an IP version 4 (IPv4) address, returning the address
|
| + * as a list of 4 bytes in network byte order (big endian).
|
| + *
|
| + * Throws a [FormatException] if [host] is not a valid IPv4 address
|
| + * representation.
|
| + */
|
| + static List<int> parseIPv4Address(String host) {
|
| + void error(String msg) {
|
| + throw new FormatException('Illegal IPv4 address, $msg');
|
| + }
|
| + var bytes = host.split('.');
|
| + if (bytes.length != 4) {
|
| + error('IPv4 address should contain exactly 4 parts');
|
| + }
|
| + // TODO(ajohnsen): Consider using Uint8List.
|
| + return bytes
|
| + .map((byteString) {
|
| + int byte = int.parse(byteString);
|
| + if (byte < 0 || byte > 255) {
|
| + error('each part must be in the range of `0..255`');
|
| + }
|
| + return byte;
|
| + })
|
| + .toList();
|
| + }
|
| +
|
| + /**
|
| + * Parse the [host] as an IP version 6 (IPv6) address, returning the address
|
| + * as a list of 16 bytes in network byte order (big endian).
|
| + *
|
| + * Throws a [FormatException] if [host] is not a valid IPv6 address
|
| + * representation.
|
| + *
|
| + * Some examples of IPv6 addresses:
|
| + * * ::1
|
| + * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
|
| + * * 3ffe:2a00:100:7031::1
|
| + * * ::FFFF:129.144.52.38
|
| + * * 2010:836B:4179::836B:4179
|
| + */
|
| + static List<int> parseIPv6Address(String host) {
|
| + // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
|
| + // by `:`'s, with the following exceptions:
|
| + //
|
| + // - One (and only one) wildcard (`::`) may be present, representing a fill
|
| + // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
|
| + // - The last two parts may be replaced by an IPv4 address.
|
| + void error(String msg) {
|
| + throw new FormatException('Illegal IPv6 address, $msg');
|
| + }
|
| + int parseHex(int start, int end) {
|
| + if (end - start > 4) {
|
| + error('an IPv6 part can only contain a maximum of 4 hex digits');
|
| + }
|
| + int value = int.parse(host.substring(start, end), radix: 16);
|
| + if (value < 0 || value > (1 << 16) - 1) {
|
| + error('each part must be in the range of `0x0..0xFFFF`');
|
| + }
|
| + return value;
|
| + }
|
| + if (host.length < 2) error('address is too short');
|
| + List<int> parts = [];
|
| + bool wildcardSeen = false;
|
| + int partStart = 0;
|
| + // Parse all parts, except a potential last one.
|
| + for (int i = 0; i < host.length; i++) {
|
| + if (host.codeUnitAt(i) == _COLON) {
|
| + if (i == 0) {
|
| + // If we see a `:` in the beginning, expect wildcard.
|
| + i++;
|
| + if (host.codeUnitAt(i) != _COLON) {
|
| + error('invalid start colon.');
|
| + }
|
| + partStart = i;
|
| + }
|
| + if (i == partStart) {
|
| + // Wildcard. We only allow one.
|
| + if (wildcardSeen) {
|
| + error('only one wildcard `::` is allowed');
|
| + }
|
| + wildcardSeen = true;
|
| + parts.add(-1);
|
| + } else {
|
| + // Found a single colon. Parse [partStart..i] as a hex entry.
|
| + parts.add(parseHex(partStart, i));
|
| + }
|
| + partStart = i + 1;
|
| + }
|
| + }
|
| + if (parts.length == 0) error('too few parts');
|
| + bool atEnd = partStart == host.length;
|
| + bool isLastWildcard = parts.last == -1;
|
| + if (atEnd && !isLastWildcard) {
|
| + error('expected a part after last `:`');
|
| + }
|
| + if (!atEnd) {
|
| + try {
|
| + parts.add(parseHex(partStart, host.length));
|
| + } catch (e) {
|
| + // Failed to parse the last chunk as hex. Try IPv4.
|
| + try {
|
| + List<int> last = parseIPv4Address(host.substring(partStart));
|
| + parts.add(last[0] << 8 | last[1]);
|
| + parts.add(last[2] << 8 | last[3]);
|
| + } catch (e) {
|
| + error('invalid end of IPv6 address.');
|
| + }
|
| + }
|
| + }
|
| + if (wildcardSeen) {
|
| + if (parts.length > 7) {
|
| + error('an address with a wildcard must have less than 7 parts');
|
| + }
|
| + } else if (parts.length != 8) {
|
| + error('an address without a wildcard must contain exactly 8 parts');
|
| + }
|
| + // TODO(ajohnsen): Consider using Uint8List.
|
| + return parts
|
| + .expand((value) {
|
| + if (value == -1) {
|
| + return new List.filled((9 - parts.length) * 2, 0);
|
| + } else {
|
| + return [(value >> 8) & 0xFF, value & 0xFF];
|
| + }
|
| + })
|
| + .toList();
|
| + }
|
| +
|
| // Frequently used character codes.
|
| static const int _DOUBLE_QUOTE = 0x22;
|
| static const int _PERCENT = 0x25;
|
| @@ -1164,7 +1332,9 @@ class Uri {
|
| static const int _UPPER_CASE_A = 0x41;
|
| static const int _UPPER_CASE_F = 0x46;
|
| static const int _UPPER_CASE_Z = 0x5A;
|
| + static const int _LEFT_BRACKET = 0x5B;
|
| static const int _BACKSLASH = 0x5C;
|
| + static const int _RIGHT_BRACKET = 0x5D;
|
| static const int _LOWER_CASE_A = 0x61;
|
| static const int _LOWER_CASE_F = 0x66;
|
| static const int _LOWER_CASE_Z = 0x7A;
|
|
|