Index: dart/utils/uri/uri.dart |
diff --git a/dart/utils/uri/uri.dart b/dart/utils/uri/uri.dart |
index 665367bfa0e577d1f63da7db6da955afd3ff580b..d06042142e76c27ef6e2c6b59f50c029e79b0178 100644 |
--- a/dart/utils/uri/uri.dart |
+++ b/dart/utils/uri/uri.dart |
@@ -2,102 +2,37 @@ |
// for details. All rights reserved. Use of this source code is governed by a |
// BSD-style license that can be found in the LICENSE file. |
+#library('uri'); |
+ |
/** |
* A parsed URI, inspired by: |
* http://closure-library.googlecode.com/svn/docs/class_goog_Uri.html |
*/ |
class Uri { |
- /** |
- * Parses a URL query string into a map. Because you can have multiple values |
- * for the same parameter name, each parameter name maps to a list of |
- * values. For example, '?a=b&c=d&a=e' would be parsed as |
- * [{'a':['b','e'],'c':['d']}]. |
- */ |
- // TODO(jmesserly): consolidate with new Uri.fromString(...) |
- static Map<String, List<String>> parseQuery(String queryString) { |
- final queryParams = new Map<String, List<String>>(); |
- if (queryString.startsWith('?')) { |
- final params = queryString.substring(1, queryString.length).split('&'); |
- for (final param in params) { |
- List<String> parts = param.split('='); |
- if (parts.length == 2) { |
- // TODO(hiltonc) the name and value should be URL decoded. |
- String name = parts[0]; |
- String value = parts[1]; |
- |
- // Create a list of values for this name if not yet done. |
- List values = queryParams[name]; |
- if (values === null) { |
- values = new List(); |
- queryParams[name] = values; |
- } |
+ final String scheme; |
+ final String userInfo; |
+ final String domain; |
+ final int port; |
+ final String path; |
+ final String query; |
+ final String fragment; |
- values.add(value); |
- } |
- } |
- } |
- return queryParams; |
- } |
+ Uri.fromString(String uri) : this._fromMatch(_splitRe.firstMatch(uri)); |
- /** |
- * Percent-encodes a string for use as a query parameter in a URI. |
- */ |
- // TODO(rnystrom): Get rid of this when the real encodeURIComponent() |
- // function is available within Dart. |
- static String encodeComponent(String component) { |
- if (component == null) return component; |
- |
- // TODO(terry): Added b/5096547 to track replace should by default behave |
- // like replaceAll to avoid a problematic usage pattern. |
- return component.replaceAll(':', '%3A') |
- .replaceAll('/', '%2F') |
- .replaceAll('?', '%3F') |
- .replaceAll('=', '%3D') |
- .replaceAll('&', '%26') |
- .replaceAll(' ', '%20'); |
- } |
+ Uri._fromMatch(Match m) : this(_emptyIfNull(m[_COMPONENT_SCHEME]), |
+ _emptyIfNull(m[_COMPONENT_USER_INFO]), |
+ _emptyIfNull(m[_COMPONENT_DOMAIN]), |
+ _parseIntOrZero(m[_COMPONENT_PORT]), |
+ _emptyIfNull(m[_COMPONENT_PATH]), |
+ _emptyIfNull(m[_COMPONENT_QUERY_DATA]), |
+ _emptyIfNull(m[_COMPONENT_FRAGMENT])); |
- /** |
- * Decodes a string used a query parameter by replacing percent-encoded |
- * sequences with their original characters. |
- */ |
- // TODO(jmesserly): replace this with a better implementation |
- static String decodeComponent(String component) { |
- if (component == null) return component; |
- |
- return component.replaceAll('%3A', ':') |
- .replaceAll('%2F', '/') |
- .replaceAll('%3F', '?') |
- .replaceAll('%3D', '=') |
- .replaceAll('%26', '&') |
- .replaceAll('%20', ' '); |
- } |
- |
- String scheme; |
- String userInfo; |
- String domain; |
- int port; |
- String path; |
- String query; |
- String fragment; |
- |
- Uri.fromString(String uri) { |
- final m = _splitRe.firstMatch(uri); |
- |
- scheme = _decodeOrEmpty(m[_COMPONENT_SCHEME]); |
- userInfo = _decodeOrEmpty(m[_COMPONENT_USER_INFO]); |
- domain = _decodeOrEmpty(m[_COMPONENT_DOMAIN]); |
- port = _parseIntOrZero(m[_COMPONENT_PORT]); |
- path = _decodeOrEmpty(m[_COMPONENT_PATH]); |
- query = _decodeOrEmpty(m[_COMPONENT_QUERY_DATA]); |
- fragment = _decodeOrEmpty(m[_COMPONENT_FRAGMENT]); |
- } |
+ const Uri([String this.scheme = "", String this.userInfo ="", |
+ String this.domain = "", int this.port = 0, |
+ String this.path = "", String this.query = "", |
+ String this.fragment = ""]); |
- static String _decodeOrEmpty(String val) { |
- // TODO(jmesserly): use Uri.decodeComponent when available |
- //return val ? Uri.decodeComponent(val) : ''; |
- return val != null ? val : ''; |
- } |
+ static String _emptyIfNull(String val) => val != null ? val : ''; |
static int _parseIntOrZero(String val) { |
if (val !== null && val != '') { |
@@ -108,32 +43,25 @@ class Uri { |
} |
// NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js |
- static RegExp _splitReLazy; |
- |
- static RegExp get _splitRe() { |
- if (_splitReLazy == null) { |
- _splitReLazy = new RegExp( |
- '^' + |
- '(?:' + |
- '([^:/?#.]+)' + // scheme - ignore special characters |
- // used by other URL parts such as :, |
- // ?, /, #, and . |
- ':)?' + |
- '(?://' + |
- '(?:([^/?#]*)@)?' + // userInfo |
- '([\\w\\d\\-\\u0100-\\uffff.%]*)' + |
- // domain - restrict to letters, |
- // digits, dashes, dots, percent |
- // escapes, and unicode characters. |
- '(?::([0-9]+))?' + // port |
- ')?' + |
- '([^?#]+)?' + // path |
- '(?:\\?([^#]*))?' + // query |
- '(?:#(.*))?' + // fragment |
- '\$'); |
- } |
- return _splitReLazy; |
- } |
+ static final RegExp _splitRe = const RegExp( |
+ '^' + |
+ '(?:' + |
+ '([^:/?#.]+)' + // scheme - ignore special characters |
+ // used by other URL parts such as :, |
+ // ?, /, #, and . |
+ ':)?' + |
+ '(?://' + |
+ '(?:([^/?#]*)@)?' + // userInfo |
+ '([\\w\\d\\-\\u0100-\\uffff.%]*)' + |
+ // domain - restrict to letters, |
+ // digits, dashes, dots, percent |
+ // escapes, and unicode characters. |
+ '(?::([0-9]+))?' + // port |
+ ')?' + |
+ '([^?#]+)?' + // path |
+ '(?:\\?([^#]*))?' + // query |
+ '(?:#(.*))?' + // fragment |
+ '\$'); |
static final _COMPONENT_SCHEME = 1; |
static final _COMPONENT_USER_INFO = 2; |
@@ -142,4 +70,146 @@ class Uri { |
static final _COMPONENT_PATH = 5; |
static final _COMPONENT_QUERY_DATA = 6; |
static final _COMPONENT_FRAGMENT = 7; |
+ |
+ /** |
+ * Determines whether a URI is absolute. |
+ * |
+ * See: http://tools.ietf.org/html/rfc3986#section-4.3 |
+ */ |
+ bool isAbsolute() { |
+ if ("" == scheme) return false; |
+ if ("" != fragment) return false; |
+ return true; |
+ |
+ /* absolute-URI = scheme ":" hier-part [ "?" query ] |
+ * hier-part = "//" authority path-abempty |
+ * / path-absolute |
+ * / path-rootless |
+ * / path-empty |
+ * |
+ * path = path-abempty ; begins with "/" or is empty |
+ * / path-absolute ; begins with "/" but not "//" |
+ * / path-noscheme ; begins with a non-colon segment |
+ * / path-rootless ; begins with a segment |
+ * / path-empty ; zero characters |
+ * |
+ * path-abempty = *( "/" segment ) |
+ * path-absolute = "/" [ segment-nz *( "/" segment ) ] |
+ * path-noscheme = segment-nz-nc *( "/" segment ) |
+ * path-rootless = segment-nz *( "/" segment ) |
+ * path-empty = 0<pchar> |
+ * segment = *pchar |
+ * segment-nz = 1*pchar |
+ * segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) |
+ * ; non-zero-length segment without any colon ":" |
+ * |
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
+ */ |
+ } |
+ |
+ Uri resolve(String uri) { |
+ return resolveUri(new Uri.fromString(uri)); |
+ } |
+ |
+ Uri resolveUri(Uri reference) { |
+ // From RFC 3986. |
+ String targetScheme; |
+ String targetUserInfo; |
+ String targetDomain; |
+ int targetPort; |
+ String targetPath; |
+ String targetQuery; |
+ if (reference.scheme != "") { |
+ targetScheme = reference.scheme; |
+ targetUserInfo = reference.userInfo; |
+ targetDomain = reference.domain; |
+ targetPort = reference.port; |
+ targetPath = removeDotSegments(reference.path); |
+ targetQuery = reference.query; |
+ } else { |
+ if (reference.hasAuthority()) { |
+ targetUserInfo = reference.userInfo; |
+ targetDomain = reference.domain; |
+ targetPort = reference.port; |
+ targetPath = removeDotSegments(reference.path); |
+ targetQuery = reference.query; |
+ } else { |
+ if (reference.path == "") { |
+ targetPath = this.path; |
+ if (reference.query != "") { |
+ targetQuery = reference.query; |
+ } else { |
+ targetQuery = this.query; |
+ } |
+ } else { |
+ if (reference.path.startsWith("/")) { |
+ targetPath = removeDotSegments(reference.path); |
+ } else { |
+ targetPath = removeDotSegments(merge(this.path, reference.path)); |
+ } |
+ targetQuery = reference.query; |
+ } |
+ targetUserInfo = this.userInfo; |
+ targetDomain = this.domain; |
+ targetPort = this.port; |
+ } |
+ targetScheme = this.scheme; |
+ } |
+ return new Uri(targetScheme, targetUserInfo, targetDomain, targetPort, |
+ targetPath, targetQuery, reference.fragment); |
+ } |
+ |
+ bool hasAuthority() { |
+ return (userInfo != "") || (domain != "") || (port != 0); |
+ } |
+ |
+ String toString() { |
+ StringBuffer sb = new StringBuffer(); |
+ _addIfNonEmpty(sb, scheme, scheme, ':'); |
+ if (hasAuthority() || (scheme == "file")) { |
+ sb.add("//"); |
+ _addIfNonEmpty(sb, userInfo, userInfo, "@"); |
+ sb.add(domain); |
+ if (port != 0) { |
+ sb.add(":"); |
+ sb.add(port.toString()); |
+ } |
+ } |
+ sb.add(path); |
+ _addIfNonEmpty(sb, query, "?", query); |
+ _addIfNonEmpty(sb, fragment, "#", fragment); |
+ return sb.toString(); |
+ } |
+ |
+ static void _addIfNonEmpty(StringBuffer sb, String test, |
+ String first, String second) { |
+ if ("" != test) { |
+ sb.add(first); |
+ sb.add(second); |
+ } |
+ } |
+} |
+ |
+String merge(String base, String reference) { |
+ if (base == "") return "/$reference"; |
+ return base.substring(0, base.lastIndexOf("/") + 1) + "$reference"; |
+} |
+ |
+String removeDotSegments(String path) { |
+ List<String> output = []; |
+ bool appendSlash = false; |
+ for (String segment in path.split("/")) { |
+ appendSlash = false; |
+ if (segment == "..") { |
+ if (!output.isEmpty() && |
+ ((output.length != 1) || (output[0] != ""))) output.removeLast(); |
+ appendSlash = true; |
+ } else if ("." == segment) { |
+ appendSlash = true; |
+ } else { |
+ output.add(segment); |
+ } |
+ } |
+ if (appendSlash) output.add(""); |
+ return Strings.join(output, "/"); |
} |