| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart.uri; | 5 library dart.uri; |
| 6 | 6 |
| 7 import 'dart:math'; | 7 import 'dart:math'; |
| 8 import 'dart:utf'; | 8 import 'dart:utf'; |
| 9 | 9 |
| 10 part 'uri_base.dart'; |
| 10 part 'encode_decode.dart'; | 11 part 'encode_decode.dart'; |
| 11 part 'helpers.dart'; | 12 part 'helpers.dart'; |
| 12 | |
| 13 /** | |
| 14 * A parsed URI, inspired by Closure's [URI][] class. Implements [RFC-3986][]. | |
| 15 * [uri]: http://closure-library.googlecode.com/svn/docs/class_goog_Uri.html | |
| 16 * [RFC-3986]: http://tools.ietf.org/html/rfc3986#section-4.3) | |
| 17 */ | |
| 18 class Uri { | |
| 19 final String scheme; | |
| 20 final String userInfo; | |
| 21 final String domain; | |
| 22 final int port; | |
| 23 final String path; | |
| 24 final String query; | |
| 25 final String fragment; | |
| 26 | |
| 27 /** | |
| 28 * Deprecated. Please use [parse] instead. | |
| 29 */ | |
| 30 Uri.fromString(String uri) : this._fromMatch(_splitRe.firstMatch(uri)); | |
| 31 | |
| 32 static Uri parse(String uri) => new Uri._fromMatch(_splitRe.firstMatch(uri)); | |
| 33 | |
| 34 Uri._fromMatch(Match m) : | |
| 35 this.fromComponents(scheme: _emptyIfNull(m[_COMPONENT_SCHEME]), | |
| 36 userInfo: _emptyIfNull(m[_COMPONENT_USER_INFO]), | |
| 37 domain: _emptyIfNull(m[_COMPONENT_DOMAIN]), | |
| 38 port: _parseIntOrZero(m[_COMPONENT_PORT]), | |
| 39 path: _emptyIfNull(m[_COMPONENT_PATH]), | |
| 40 query: _emptyIfNull(m[_COMPONENT_QUERY_DATA]), | |
| 41 fragment: _emptyIfNull(m[_COMPONENT_FRAGMENT])); | |
| 42 | |
| 43 const Uri.fromComponents({this.scheme: "", | |
| 44 this.userInfo: "", | |
| 45 this.domain: "", | |
| 46 this.port: 0, | |
| 47 this.path: "", | |
| 48 this.query: "", | |
| 49 this.fragment: ""}); | |
| 50 | |
| 51 Uri(String uri) : this.fromString(uri); | |
| 52 | |
| 53 static String _emptyIfNull(String val) => val != null ? val : ''; | |
| 54 | |
| 55 static int _parseIntOrZero(String val) { | |
| 56 if (val != null && val != '') { | |
| 57 return int.parse(val); | |
| 58 } else { | |
| 59 return 0; | |
| 60 } | |
| 61 } | |
| 62 | |
| 63 // NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js | |
| 64 static final RegExp _splitRe = new RegExp( | |
| 65 '^' | |
| 66 '(?:' | |
| 67 '([^:/?#.]+)' // scheme - ignore special characters | |
| 68 // used by other URL parts such as :, | |
| 69 // ?, /, #, and . | |
| 70 ':)?' | |
| 71 '(?://' | |
| 72 '(?:([^/?#]*)@)?' // userInfo | |
| 73 '([\\w\\d\\-\\u0100-\\uffff.%]*)' | |
| 74 // domain - restrict to letters, | |
| 75 // digits, dashes, dots, percent | |
| 76 // escapes, and unicode characters. | |
| 77 '(?::([0-9]+))?' // port | |
| 78 ')?' | |
| 79 '([^?#]+)?' // path | |
| 80 '(?:\\?([^#]*))?' // query | |
| 81 '(?:#(.*))?' // fragment | |
| 82 '\$'); | |
| 83 | |
| 84 static const _COMPONENT_SCHEME = 1; | |
| 85 static const _COMPONENT_USER_INFO = 2; | |
| 86 static const _COMPONENT_DOMAIN = 3; | |
| 87 static const _COMPONENT_PORT = 4; | |
| 88 static const _COMPONENT_PATH = 5; | |
| 89 static const _COMPONENT_QUERY_DATA = 6; | |
| 90 static const _COMPONENT_FRAGMENT = 7; | |
| 91 | |
| 92 /** | |
| 93 * Returns `true` if the URI is absolute. | |
| 94 */ | |
| 95 bool get isAbsolute { | |
| 96 if ("" == scheme) return false; | |
| 97 if ("" != fragment) return false; | |
| 98 return true; | |
| 99 | |
| 100 /* absolute-URI = scheme ":" hier-part [ "?" query ] | |
| 101 * hier-part = "//" authority path-abempty | |
| 102 * / path-absolute | |
| 103 * / path-rootless | |
| 104 * / path-empty | |
| 105 * | |
| 106 * path = path-abempty ; begins with "/" or is empty | |
| 107 * / path-absolute ; begins with "/" but not "//" | |
| 108 * / path-noscheme ; begins with a non-colon segment | |
| 109 * / path-rootless ; begins with a segment | |
| 110 * / path-empty ; zero characters | |
| 111 * | |
| 112 * path-abempty = *( "/" segment ) | |
| 113 * path-absolute = "/" [ segment-nz *( "/" segment ) ] | |
| 114 * path-noscheme = segment-nz-nc *( "/" segment ) | |
| 115 * path-rootless = segment-nz *( "/" segment ) | |
| 116 * path-empty = 0<pchar> | |
| 117 * segment = *pchar | |
| 118 * segment-nz = 1*pchar | |
| 119 * segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) | |
| 120 * ; non-zero-length segment without any colon ":" | |
| 121 * | |
| 122 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" | |
| 123 */ | |
| 124 } | |
| 125 | |
| 126 Uri resolve(String uri) { | |
| 127 return resolveUri(Uri.parse(uri)); | |
| 128 } | |
| 129 | |
| 130 Uri resolveUri(Uri reference) { | |
| 131 // From RFC 3986. | |
| 132 String targetScheme; | |
| 133 String targetUserInfo; | |
| 134 String targetDomain; | |
| 135 int targetPort; | |
| 136 String targetPath; | |
| 137 String targetQuery; | |
| 138 if (reference.scheme != "") { | |
| 139 targetScheme = reference.scheme; | |
| 140 targetUserInfo = reference.userInfo; | |
| 141 targetDomain = reference.domain; | |
| 142 targetPort = reference.port; | |
| 143 targetPath = removeDotSegments(reference.path); | |
| 144 targetQuery = reference.query; | |
| 145 } else { | |
| 146 if (reference.hasAuthority) { | |
| 147 targetUserInfo = reference.userInfo; | |
| 148 targetDomain = reference.domain; | |
| 149 targetPort = reference.port; | |
| 150 targetPath = removeDotSegments(reference.path); | |
| 151 targetQuery = reference.query; | |
| 152 } else { | |
| 153 if (reference.path == "") { | |
| 154 targetPath = this.path; | |
| 155 if (reference.query != "") { | |
| 156 targetQuery = reference.query; | |
| 157 } else { | |
| 158 targetQuery = this.query; | |
| 159 } | |
| 160 } else { | |
| 161 if (reference.path.startsWith("/")) { | |
| 162 targetPath = removeDotSegments(reference.path); | |
| 163 } else { | |
| 164 targetPath = removeDotSegments(merge(this.path, reference.path)); | |
| 165 } | |
| 166 targetQuery = reference.query; | |
| 167 } | |
| 168 targetUserInfo = this.userInfo; | |
| 169 targetDomain = this.domain; | |
| 170 targetPort = this.port; | |
| 171 } | |
| 172 targetScheme = this.scheme; | |
| 173 } | |
| 174 return new Uri.fromComponents(scheme: targetScheme, | |
| 175 userInfo: targetUserInfo, | |
| 176 domain: targetDomain, | |
| 177 port: targetPort, | |
| 178 path: targetPath, | |
| 179 query: targetQuery, | |
| 180 fragment: reference.fragment); | |
| 181 } | |
| 182 | |
| 183 bool get hasAuthority { | |
| 184 return (userInfo != "") || (domain != "") || (port != 0); | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * For http/https schemes returns URI's [origin][] - scheme://domain:port. | |
| 189 * For all other schemes throws ArgumentError. | |
| 190 * [origin]: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin | |
| 191 */ | |
| 192 String get origin { | |
| 193 if (scheme == "") { | |
| 194 // TODO(aprelev@gmail.com): Use StateException instead | |
| 195 throw new ArgumentError("Cannot use origin without a scheme"); | |
| 196 } | |
| 197 if (scheme != "http" && scheme != "https") { | |
| 198 // TODO(aprelev@gmail.com): Use StateException instead | |
| 199 throw new ArgumentError( | |
| 200 "origin is applicable to http/https schemes only. Not \'$scheme\'"); | |
| 201 } | |
| 202 StringBuffer sb = new StringBuffer(); | |
| 203 sb.add(scheme); | |
| 204 sb.add(":"); | |
| 205 if (domain == null || domain == "") { | |
| 206 // TODO(aprelev@gmail.com): Use StateException instead | |
| 207 throw new ArgumentError("Cannot use origin without a domain"); | |
| 208 } | |
| 209 | |
| 210 sb.add("//"); | |
| 211 sb.add(domain); | |
| 212 if (port != 0) { | |
| 213 sb.add(":"); | |
| 214 sb.add(port); | |
| 215 } | |
| 216 return sb.toString(); | |
| 217 } | |
| 218 | |
| 219 String toString() { | |
| 220 StringBuffer sb = new StringBuffer(); | |
| 221 _addIfNonEmpty(sb, scheme, scheme, ':'); | |
| 222 if (hasAuthority || (scheme == "file")) { | |
| 223 sb.add("//"); | |
| 224 _addIfNonEmpty(sb, userInfo, userInfo, "@"); | |
| 225 sb.add(domain == null ? "null" : domain); | |
| 226 if (port != 0) { | |
| 227 sb.add(":"); | |
| 228 sb.add(port.toString()); | |
| 229 } | |
| 230 } | |
| 231 sb.add(path == null ? "null" : path); | |
| 232 _addIfNonEmpty(sb, query, "?", query); | |
| 233 _addIfNonEmpty(sb, fragment, "#", fragment); | |
| 234 return sb.toString(); | |
| 235 } | |
| 236 | |
| 237 bool operator==(other) { | |
| 238 if (other is! Uri) return false; | |
| 239 Uri uri = other; | |
| 240 return scheme == uri.scheme && | |
| 241 userInfo == uri.userInfo && | |
| 242 domain == uri.domain && | |
| 243 port == uri.port && | |
| 244 path == uri.path && | |
| 245 query == uri.query && | |
| 246 fragment == uri.fragment; | |
| 247 } | |
| 248 | |
| 249 int get hashCode { | |
| 250 int combine(part, current) { | |
| 251 // The sum is truncated to 30 bits to make sure it fits into a Smi. | |
| 252 return (current * 31 + part.hashCode) & 0x3FFFFFFF; | |
| 253 } | |
| 254 return combine(scheme, combine(userInfo, combine(domain, combine(port, | |
| 255 combine(path, combine(query, combine(fragment, 1))))))); | |
| 256 } | |
| 257 | |
| 258 static void _addIfNonEmpty(StringBuffer sb, String test, | |
| 259 String first, String second) { | |
| 260 if ("" != test) { | |
| 261 sb.add(first == null ? "null" : first); | |
| 262 sb.add(second == null ? "null" : second); | |
| 263 } | |
| 264 } | |
| 265 } | |
| OLD | NEW |