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 |