| 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, "/");
|
| }
|
|
|