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

Unified Diff: sdk/lib/core/uri.dart

Issue 352093003: Try to retain original structure of URI. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/core/uri.dart
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index 73a97d80e1c3288aac66beca9735b5bf3d59c4c8..6abca996f6049b3796e894f9a8fa25fdb6e367ca 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -16,8 +16,14 @@ part of dart.core;
* [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html
*/
class Uri {
+ // The host name of the URI.
+ // Set to `null` if there is no authority in a URI.
final String _host;
+ // The port. Set to null if there is no port. Normalized to null if
+ // the port is the default port for the scheme.
+ // Set to the value of the default port if an empty port was supplied.
int _port;
+ // The path. Always non-null.
String _path;
/**
@@ -25,6 +31,8 @@ class Uri {
*
* Returns the empty string if there is no scheme component.
*/
+ // We represent the missing scheme as an empty string.
+ // A valid scheme cannot be empty.
final String scheme;
/**
@@ -43,12 +51,22 @@ class Uri {
}
/**
+ * The user-info part of the authority.
+ *
+ * Does not distinguish between an empty user-info and an absent one.
+ * The value is always non-null.
Anders Johnsen 2014/06/25 09:18:00 This contradicts with the getter below.
Lasse Reichstein Nielsen 2014/06/25 10:49:18 True, I don't need the getter anyway.
+ */
+ final String _userInfo;
+
+ /**
* Returns the user info part of the authority component.
*
* Returns the empty string if there is no user info in the
* authority component.
*/
- final String userInfo;
+ String get userInfo {
+ return (_userInfo != null) ? _userInfo : "";
+ }
/**
* Returns the host part of the authority component.
@@ -60,7 +78,8 @@ class Uri {
* removed.
*/
String get host {
- if (_host != null && _host.startsWith('[')) {
+ if (_host == null) return "";
+ if (_host.startsWith('[')) {
return _host.substring(1, _host.length - 1);
}
return _host;
@@ -69,16 +88,20 @@ class Uri {
/**
* Returns the port part of the authority component.
*
- * Returns 0 if there is no port in the authority component.
+ * Also returns 0 if there is no port number in the authority component.
Anders Johnsen 2014/06/25 09:18:00 remove 'Also', it only returns once.
Lasse Reichstein Nielsen 2014/06/25 10:49:18 Done.
*/
int get port {
- if (_port == 0) {
- if (scheme == "http") return 80;
- if (scheme == "https") return 443;
- }
+ if (_port == null) return _defaultPort(scheme);
return _port;
}
+ // The default port for the scheme of this Uri..
+ static int _defaultPort(String scheme) {
+ if (scheme == "http") return 80;
+ if (scheme == "https") return 443;
+ return 0;
+ }
+
/**
* Returns the path component.
*
@@ -89,13 +112,19 @@ class Uri {
*/
String get path => _path;
+ // The query content, or null if there is no query.
+ final String _query;
+
/**
* Returns the query component. The returned query is encoded. To get
* direct access to the decoded query use [queryParameters].
*
* Returns the empty string if there is no query component.
*/
- final String query;
+ String get query => (_query == null) ? "" : _query;
+
+ // The fragment content, or null if there is no fragment.
+ final String _fragment;
/**
* Returns the fragment identifier component.
@@ -103,7 +132,7 @@ class Uri {
* Returns the empty string if there is no fragment identifier
* component.
*/
- final String fragment;
+ String get fragment => (_fragment == null) ? "" : _fragment;
/**
* Cache the computed return value of [pathSegements].
@@ -182,12 +211,12 @@ class Uri {
const int EOI = -1;
String scheme = "";
- String path;
String userinfo = "";
- String host = "";
- int port = 0;
- String query = "";
- String fragment = "";
+ String host = null;
+ int port = null;
+ String path = null;
+ String query = null;
+ String fragment = null;
int index = 0;
int pathStart = 0;
@@ -234,13 +263,16 @@ class Uri {
hostStart = lastAt + 1;
}
if (lastColon >= 0) {
- int portNumber = 0;
- for (int i = lastColon + 1; i < index; i++) {
- int digit = uri.codeUnitAt(i);
- if (_ZERO > digit || _NINE < digit) {
- _fail(uri, i, "Invalid port number");
+ int portNumber;
+ if (lastColon + 1 < index) {
+ portNumber = 0;
+ for (int i = lastColon + 1; i < index; i++) {
+ int digit = uri.codeUnitAt(i);
+ if (_ZERO > digit || _NINE < digit) {
+ _fail(uri, i, "Invalid port number");
+ }
+ portNumber = portNumber * 10 + (digit - _ZERO);
}
- portNumber = portNumber * 10 + (digit - _ZERO);
}
port = _makePort(portNumber, scheme);
hostEnd = lastColon;
@@ -340,7 +372,7 @@ class Uri {
}
assert(state == NOT_IN_PATH);
- bool ensureLeadingSlash = (host != "" || scheme == "file");
+ bool ensureLeadingSlash = (host != null || scheme == "file");
path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
if (char == _QUESTION) {
@@ -397,27 +429,27 @@ class Uri {
/// Internal non-verifying constructor. Only call with validated arguments.
Uri._internal(this.scheme,
- this.userInfo,
+ this._userInfo,
this._host,
this._port,
this._path,
- this.query,
- this.fragment);
+ this._query,
+ this._fragment);
/**
* Creates a new URI from its components.
*
* Each component is set through a named argument. Any number of
- * components can be provided. The default value for the components
- * not provided is the empry string, except for [port] which has a
- * default value of 0. The [path] and [query] components can be set
- * using two different named arguments.
+ * components can be provided. The [path] and [query] components can be set
+ * using either of two different named arguments.
*
* The scheme component is set through [scheme]. The scheme is
- * normalized to all lowercase letters.
+ * normalized to all lowercase letters. If the scheme is omitted or empty,
+ * the URI will not have a scheme part.
Anders Johnsen 2014/06/25 09:18:00 will have an empty scheme part?
Lasse Reichstein Nielsen 2014/06/25 10:49:18 That sounds like an empty string before a colon, w
*
* The user info part of the authority component is set through
- * [userInfo].
+ * [userInfo]. It defaults to the empty string, which will be omitted
+ * from the string representation of the URI.
*
* The host part of the authority component is set through
* [host]. The host can either be a hostname, an IPv4 address or an
@@ -426,14 +458,22 @@ class Uri {
* The host is normalized to all lowercase letters.
*
* The port part of the authority component is set through
- * [port]. The port is normalized for scheme http and https where
- * port 80 and port 443 respectively is set.
+ * [port].
+ * If [port] is omitted or `null`, it implies the default port for
+ * the URI's scheme, and is equivalent to passing that port explicitly.
+ * The recognized schemes, and their default ports, are "http" (80) and
+ * "https" (443). All other schemes are considered as having zero as the
+ * default port.
+ *
+ * If any of `userInfo`, `host` or `port` are provided,
+ * the URI will have an autority according to [hasAuthority].
*
* The path component is set through either [path] or
- * [pathSegments]. When [path] is used, the provided string is
- * expected to be fully percent-encoded, and is used in its literal
- * form. When [pathSegments] is used, each of the provided segments
- * is percent-encoded and joined using the forward slash
+ * [pathSegments]. When [path] is used, it should be a valid URI path,
+ * but invalid characters, except the general delimiters ':/@[]?#',
+ * will be escaped if necessary.
+ * When [pathSegments] is used, each of the provided segments
+ * is first percent-encoded and then joined using the forward slash
* separator. The percent-encoding of the path segments encodes all
* characters except for the unreserved characters and the following
* list of characters: `!$&'()*+,;=:@`. If the other components
@@ -441,32 +481,43 @@ class Uri {
* not already there.
*
* The query component is set through either [query] or
- * [queryParameters]. When [query] is used the provided string is
- * expected to be fully percent-encoded and is used in its literal
- * form. When [queryParameters] is used the query is built from the
+ * [queryParameters]. When [query] is used the provided string should
+ * be a valid URI query, but invalid characters other than general delimiters,
+ * will be escaped if necessary.
+ * When [queryParameters] is used the query is built from the
* provided map. Each key and value in the map is percent-encoded
* and joined using equal and ampersand characters. The
* percent-encoding of the keys and values encodes all characters
* except for the unreserved characters.
+ * If both `query` and `queryParameters` are omitted or `null`, the
+ * URI will have no query part.
*
* The fragment component is set through [fragment].
+ * It should be a valid URI fragment, but invalid characters other than
+ * general delimiters, will be escaped if necessary.
+ * If `fragment` is omitted or `null`, the URI will have no fragment part.
*/
- factory Uri({String scheme,
- String userInfo: "",
- String host: "",
- port: 0,
- String path,
- Iterable<String> pathSegments,
- String query,
- Map<String, String> queryParameters,
- fragment: ""}) {
+ factory Uri({String scheme : "",
+ String userInfo : "",
+ String host,
+ port,
Anders Johnsen 2014/06/25 09:18:00 int port
Lasse Reichstein Nielsen 2014/06/25 10:49:18 Done.
+ String path,
+ Iterable<String> pathSegments,
+ String query,
+ Map<String, String> queryParameters,
+ fragment}) {
scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
host = _makeHost(host, 0, _stringOrNullLength(host), false);
query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
port = _makePort(port, scheme);
- bool ensureLeadingSlash = (host != "" || scheme == "file");
+ bool isFile = (scheme == "file");
+ if (host == null &&
+ (userInfo.isNotEmpty || port != null || isFile)) {
+ host = "";
+ }
+ bool ensureLeadingSlash = (host != null || isFile);
path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
ensureLeadingSlash);
@@ -496,7 +547,9 @@ class Uri {
* The `scheme` is always set to `http`.
*
* The `userInfo`, `host` and `port` components are set from the
- * [authority] argument.
+ * [authority] argument. If `authority` is `null` or empty,
+ * the created `Uri` will have no authority, and will not be directly usable
+ * as an HTTP URL, which must have a non-empty host.
*
* The `path` component is set from the [unencodedPath]
* argument. The path passed must not be encoded as this constructor
@@ -527,50 +580,51 @@ class Uri {
String authority,
String unencodedPath,
Map<String, String> queryParameters) {
- var userInfo = "";
- var host = "";
- var port = 0;
-
- var hostStart = 0;
- // Split off the user info.
- bool hasUserInfo = false;
- for (int i = 0; i < authority.length; i++) {
- if (authority.codeUnitAt(i) == _AT_SIGN) {
- hasUserInfo = true;
- userInfo = authority.substring(0, i);
- hostStart = i + 1;
- 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.");
+ var userInfo = null;
+ var host = null;
+ var port = null;
+
+ if (authority != null && authority.isNotEmpty) {
+ var hostStart = 0;
+ // Split off the user info.
+ bool hasUserInfo = false;
+ for (int i = 0; i < authority.length; i++) {
+ if (authority.codeUnitAt(i) == _AT_SIGN) {
+ hasUserInfo = true;
+ userInfo = authority.substring(0, i);
+ hostStart = i + 1;
+ break;
+ }
}
- parseIPv6Address(authority, hostStart + 1, hostEnd);
- hostEnd++; // Skip the closing bracket.
- if (hostEnd != authority.length &&
- authority.codeUnitAt(hostEnd) != _COLON) {
- throw new FormatException("Invalid end of authority");
+ 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, 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 (; 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;
+ // Split host and port.
+ bool hasPort = false;
+ 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;
+ }
}
+ host = authority.substring(hostStart, hostEnd);
}
- host = authority.substring(hostStart, hostEnd);
-
return new Uri(scheme: scheme,
userInfo: userInfo,
host: host,
@@ -721,7 +775,7 @@ class Uri {
static _makeFileUri(String path) {
String sep = "/";
- if (path.length > 0 && path[0] == sep) {
+ if (path.startsWith(sep)) {
// Absolute file:// URI.
return new Uri(scheme: "file", pathSegments: path.split(sep));
} else {
@@ -830,12 +884,7 @@ class Uri {
static int _makePort(int port, String scheme) {
// Perform scheme specific normalization.
- if (port == 80 && scheme == "http") {
- return 0;
- }
- if (port == 443 && scheme == "https") {
- return 0;
- }
+ if (port != null && port == _defaultPort(scheme)) return null;
return port;
}
@@ -852,7 +901,6 @@ class Uri {
*/
static String _makeHost(String host, int start, int end, bool strictIPv6) {
// TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
-
if (host == null) return null;
if (start == end) return "";
// Host is an IPv6 address if it starts with '[' or contains a colon.
@@ -985,7 +1033,7 @@ class Uri {
}
static String _makeUserInfo(String userInfo, int start, int end) {
- if (userInfo == null) return "null";
+ if (userInfo == null) return "";
return _normalize(userInfo, start, end, _userinfoTable);
}
@@ -1010,7 +1058,7 @@ class Uri {
static String _makeQuery(String query, int start, int end,
Map<String, String> queryParameters) {
- if (query == null && queryParameters == null) return "";
+ if (query == null && queryParameters == null) return null;
if (query != null && queryParameters != null) {
throw new ArgumentError('Both query and queryParameters specified');
}
@@ -1033,7 +1081,7 @@ class Uri {
}
static String _makeFragment(String fragment, int start, int end) {
- if (fragment == null) return "";
+ if (fragment == null) return null;
return _normalize(fragment, start, end, _queryCharTable);
}
@@ -1278,55 +1326,81 @@ class Uri {
int targetPort;
String targetPath;
String targetQuery;
- if (reference.scheme != "") {
+ if (reference.scheme.isNotEmpty) {
targetScheme = reference.scheme;
- targetUserInfo = reference.userInfo;
- targetHost = reference.host;
- targetPort = reference.port;
+ if (reference.hasAuthority) {
+ targetUserInfo = reference.userInfo;
+ targetHost = reference.host;
+ targetPort = reference.hasPort ? reference.port : null;
+ }
targetPath = _removeDotSegments(reference.path);
- targetQuery = reference.query;
+ if (reference.hasQuery) {
+ targetQuery = reference.query;
+ }
} else {
+ targetScheme = this.scheme;
if (reference.hasAuthority) {
targetUserInfo = reference.userInfo;
targetHost = reference.host;
- targetPort = reference.port;
+ targetPort = _makePort(reference.hasPort ? reference.port : null,
+ targetScheme);
targetPath = _removeDotSegments(reference.path);
- targetQuery = reference.query;
+ if (reference.hasQuery) targetQuery = reference.query;
} else {
if (reference.path == "") {
- targetPath = this.path;
- if (reference.query != "") {
+ targetPath = this._path;
+ if (reference.hasQuery) {
targetQuery = reference.query;
} else {
- targetQuery = this.query;
+ targetQuery = this._query;
}
} else {
if (reference.path.startsWith("/")) {
targetPath = _removeDotSegments(reference.path);
} else {
- targetPath = _removeDotSegments(_merge(this.path, reference.path));
+ targetPath = _removeDotSegments(_merge(this._path, reference.path));
}
- targetQuery = reference.query;
+ if (reference.hasQuery) targetQuery = reference.query;
}
- targetUserInfo = this.userInfo;
- targetHost = this.host;
- targetPort = this.port;
+ targetUserInfo = this._userInfo;
+ targetHost = this._host;
+ targetPort = this._port;
}
- targetScheme = this.scheme;
}
- return new Uri(scheme: targetScheme,
- userInfo: targetUserInfo,
- host: targetHost,
- port: targetPort,
- path: targetPath,
- query: targetQuery,
- fragment: reference.fragment);
+ String fragment = reference.hasFragment ? reference.fragment : null;
+ return new Uri._internal(targetScheme,
+ targetUserInfo,
+ targetHost,
+ targetPort,
+ targetPath,
+ targetQuery,
+ fragment);
}
/**
* Returns whether the URI has an [authority] component.
*/
- bool get hasAuthority => host != "";
+ bool get hasAuthority => _host != null;
+
+ /**
+ * Returns whether the URI has an explicit port.
+ *
+ * If the port number is the default port number
+ * (zero for unrecognized schemes, with http (80) and https (443) being
+ * recognized),
+ * then the port is made implicit and omitted from the URI.
+ */
+ bool get hasPort => _port != null;
+
+ /**
+ * Returns whether the URI has a query part.
+ */
+ bool get hasQuery => _query != null;
+
+ /**
+ * Returns whether the URI has a fragment part.
+ */
+ bool get hasFragment => _fragment != null;
/**
* Returns the origin of the URI in the form scheme://host:port for the
@@ -1344,7 +1418,7 @@ class Uri {
throw new StateError(
"Origin is only applicable schemes http and https: $this");
}
- if (_port == 0) return "$scheme://$_host";
+ if (_port == null) return "$scheme://$_host";
Anders Johnsen 2014/06/25 09:18:00 Maybe use the new getters?
Lasse Reichstein Nielsen 2014/06/25 10:49:18 hasPort? I *try* not to use public overridable m
return "$scheme://$_host:$_port";
}
@@ -1471,11 +1545,14 @@ class Uri {
}
void _writeAuthority(StringSink ss) {
- _addIfNonEmpty(ss, userInfo, userInfo, "@");
- ss.write(_host == null ? "null" : _host);
- if (_port != 0) {
+ if (_userInfo.isNotEmpty) {
+ ss.write(_userInfo);
+ ss.write("@");
+ }
+ if (_host != null) ss.write(_host);
+ if (_port != null) {
ss.write(":");
- ss.write(_port.toString());
+ ss.write(_port);
}
}
@@ -1489,8 +1566,8 @@ class Uri {
_writeAuthority(sb);
}
sb.write(path);
- _addIfNonEmpty(sb, query, "?", query);
- _addIfNonEmpty(sb, fragment, "#", fragment);
+ if (_query != null) { sb..write("?")..write(_query); }
+ if (_fragment != null) { sb..write("#")..write(_fragment); }
return sb.toString();
}
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698