Index: sdk/lib/uri/uri_base.dart |
=================================================================== |
--- sdk/lib/uri/uri_base.dart (revision 0) |
+++ sdk/lib/uri/uri_base.dart (revision 0) |
@@ -0,0 +1,259 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// 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. |
+ |
+part of dart.uri; |
+ |
+/** |
+ * A parsed URI, inspired by Closure's [URI][] class. Implements [RFC-3986][]. |
+ * [uri]: http://closure-library.googlecode.com/svn/docs/class_goog_Uri.html |
+ * [RFC-3986]: http://tools.ietf.org/html/rfc3986#section-4.3) |
+ */ |
+class Uri { |
+ final String scheme; |
+ final String userInfo; |
+ final String domain; |
+ final int port; |
+ final String path; |
+ final String query; |
+ final String fragment; |
+ |
+ /** |
+ * Deprecated. Please use [parse] instead. |
+ */ |
+ Uri.fromString(String uri) : this._fromMatch(_splitRe.firstMatch(uri)); |
+ |
+ static Uri parse(String uri) => new Uri._fromMatch(_splitRe.firstMatch(uri)); |
+ |
+ Uri._fromMatch(Match m) : |
+ this.fromComponents(scheme: _emptyIfNull(m[_COMPONENT_SCHEME]), |
+ userInfo: _emptyIfNull(m[_COMPONENT_USER_INFO]), |
+ domain: _emptyIfNull(m[_COMPONENT_DOMAIN]), |
+ port: _parseIntOrZero(m[_COMPONENT_PORT]), |
+ path: _emptyIfNull(m[_COMPONENT_PATH]), |
+ query: _emptyIfNull(m[_COMPONENT_QUERY_DATA]), |
+ fragment: _emptyIfNull(m[_COMPONENT_FRAGMENT])); |
+ |
+ const Uri.fromComponents({this.scheme: "", |
+ this.userInfo: "", |
+ this.domain: "", |
+ this.port: 0, |
+ this.path: "", |
+ this.query: "", |
+ this.fragment: ""}); |
+ |
+ Uri(String uri) : this.fromString(uri); |
+ |
+ static String _emptyIfNull(String val) => val != null ? val : ''; |
+ |
+ static int _parseIntOrZero(String val) { |
+ if (val != null && val != '') { |
+ return int.parse(val); |
+ } else { |
+ return 0; |
+ } |
+ } |
+ |
+ // NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js |
+ static final RegExp _splitRe = 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 |
+ '\$'); |
+ |
+ static const _COMPONENT_SCHEME = 1; |
+ static const _COMPONENT_USER_INFO = 2; |
+ static const _COMPONENT_DOMAIN = 3; |
+ static const _COMPONENT_PORT = 4; |
+ static const _COMPONENT_PATH = 5; |
+ static const _COMPONENT_QUERY_DATA = 6; |
+ static const _COMPONENT_FRAGMENT = 7; |
+ |
+ /** |
+ * Returns `true` if the URI is absolute. |
+ */ |
+ bool get 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(Uri.parse(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.fromComponents(scheme: targetScheme, |
+ userInfo: targetUserInfo, |
+ domain: targetDomain, |
+ port: targetPort, |
+ path: targetPath, |
+ query: targetQuery, |
+ fragment: reference.fragment); |
+ } |
+ |
+ bool get hasAuthority { |
+ return (userInfo != "") || (domain != "") || (port != 0); |
+ } |
+ |
+ /** |
+ * For http/https schemes returns URI's [origin][] - scheme://domain:port. |
+ * For all other schemes throws ArgumentError. |
+ * [origin]: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin |
+ */ |
+ String get origin { |
+ if (scheme == "") { |
+ // TODO(aprelev@gmail.com): Use StateException instead |
+ throw new ArgumentError("Cannot use origin without a scheme"); |
+ } |
+ if (scheme != "http" && scheme != "https") { |
+ // TODO(aprelev@gmail.com): Use StateException instead |
+ throw new ArgumentError( |
+ "origin is applicable to http/https schemes only. Not \'$scheme\'"); |
+ } |
+ StringBuffer sb = new StringBuffer(); |
+ sb.add(scheme); |
+ sb.add(":"); |
+ if (domain == null || domain == "") { |
+ // TODO(aprelev@gmail.com): Use StateException instead |
+ throw new ArgumentError("Cannot use origin without a domain"); |
+ } |
+ |
+ sb.add("//"); |
+ sb.add(domain); |
+ if (port != 0) { |
+ sb.add(":"); |
+ sb.add(port); |
+ } |
+ return sb.toString(); |
+ } |
+ |
+ String toString() { |
+ StringBuffer sb = new StringBuffer(); |
+ _addIfNonEmpty(sb, scheme, scheme, ':'); |
+ if (hasAuthority || (scheme == "file")) { |
+ sb.add("//"); |
+ _addIfNonEmpty(sb, userInfo, userInfo, "@"); |
+ sb.add(domain == null ? "null" : domain); |
+ if (port != 0) { |
+ sb.add(":"); |
+ sb.add(port.toString()); |
+ } |
+ } |
+ sb.add(path == null ? "null" : path); |
+ _addIfNonEmpty(sb, query, "?", query); |
+ _addIfNonEmpty(sb, fragment, "#", fragment); |
+ return sb.toString(); |
+ } |
+ |
+ bool operator==(other) { |
+ if (other is! Uri) return false; |
+ Uri uri = other; |
+ return scheme == uri.scheme && |
+ userInfo == uri.userInfo && |
+ domain == uri.domain && |
+ port == uri.port && |
+ path == uri.path && |
+ query == uri.query && |
+ fragment == uri.fragment; |
+ } |
+ |
+ int get hashCode { |
+ int combine(part, current) { |
+ // The sum is truncated to 30 bits to make sure it fits into a Smi. |
+ return (current * 31 + part.hashCode) & 0x3FFFFFFF; |
+ } |
+ return combine(scheme, combine(userInfo, combine(domain, combine(port, |
+ combine(path, combine(query, combine(fragment, 1))))))); |
+ } |
+ |
+ static void _addIfNonEmpty(StringBuffer sb, String test, |
+ String first, String second) { |
+ if ("" != test) { |
+ sb.add(first == null ? "null" : first); |
+ sb.add(second == null ? "null" : second); |
+ } |
+ } |
+} |