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