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 |