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('uri'); | |
6 | |
7 /** | |
8 * A parsed URI, inspired by: | |
9 * http://closure-library.googlecode.com/svn/docs/class_goog_Uri.html | |
10 */ | |
11 class Uri { | |
12 final String scheme; | |
13 final String userInfo; | |
14 final String domain; | |
15 final int port; | |
16 final String path; | |
17 final String query; | |
18 final String fragment; | |
19 | |
20 /** | |
21 * Determines whether a URI is absolute. | |
22 * | |
23 * See: http://tools.ietf.org/html/rfc3986#section-4.3 | |
24 */ | |
25 bool isAbsolute() { | |
26 if ("" == scheme) return false; | |
27 if ("" != fragment) return false; | |
28 return true; | |
29 | |
30 /* absolute-URI = scheme ":" hier-part [ "?" query ] | |
31 * hier-part = "//" authority path-abempty | |
32 * / path-absolute | |
33 * / path-rootless | |
34 * / path-empty | |
35 * | |
36 * path = path-abempty ; begins with "/" or is empty | |
37 * / path-absolute ; begins with "/" but not "//" | |
38 * / path-noscheme ; begins with a non-colon segment | |
39 * / path-rootless ; begins with a segment | |
40 * / path-empty ; zero characters | |
41 * | |
42 * path-abempty = *( "/" segment ) | |
43 * path-absolute = "/" [ segment-nz *( "/" segment ) ] | |
44 * path-noscheme = segment-nz-nc *( "/" segment ) | |
45 * path-rootless = segment-nz *( "/" segment ) | |
46 * path-empty = 0<pchar> | |
47 * segment = *pchar | |
48 * segment-nz = 1*pchar | |
49 * segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) | |
50 * ; non-zero-length segment without any colon ":" | |
51 * | |
52 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" | |
53 */ | |
54 } | |
55 | |
56 Uri resolve(String uri) { | |
57 return resolveUri(new Uri.fromString(uri)); | |
58 } | |
59 | |
60 Uri resolveUri(Uri reference) { | |
61 // From RFC 3986. | |
62 String targetScheme; | |
63 String targetUserInfo; | |
64 String targetDomain; | |
65 int targetPort; | |
66 String targetPath; | |
67 String targetQuery; | |
68 if (reference.scheme != "") { | |
69 targetScheme = reference.scheme; | |
70 targetUserInfo = reference.userInfo; | |
71 targetDomain = reference.domain; | |
72 targetPort = reference.port; | |
73 targetPath = removeDotSegments(reference.path); | |
74 targetQuery = reference.query; | |
75 } else { | |
76 if (reference.hasAuthority()) { | |
77 targetUserInfo = reference.userInfo; | |
78 targetDomain = reference.domain; | |
79 targetPort = reference.port; | |
80 targetPath = removeDotSegments(reference.path); | |
81 targetQuery = reference.query; | |
82 } else { | |
83 if (reference.path == "") { | |
84 targetPath = this.path; | |
85 if (reference.query != "") { | |
86 targetQuery = reference.query; | |
87 } else { | |
88 targetQuery = this.query; | |
89 } | |
90 } else { | |
91 if (reference.path.startsWith("/")) { | |
92 targetPath = removeDotSegments(reference.path); | |
93 } else { | |
94 targetPath = removeDotSegments(merge(this.path, reference.path)); | |
95 } | |
96 targetQuery = reference.query; | |
97 } | |
98 targetUserInfo = this.userInfo; | |
99 targetDomain = this.domain; | |
100 targetPort = this.port; | |
101 } | |
102 targetScheme = this.scheme; | |
103 } | |
104 return new Uri(targetScheme, targetUserInfo, targetDomain, targetPort, | |
105 targetPath, targetQuery, reference.fragment); | |
106 } | |
107 | |
108 bool hasAuthority() { | |
109 return (userInfo != "") || (domain != "") || (port != 0); | |
110 } | |
111 | |
112 String toString() { | |
113 StringBuffer sb = new StringBuffer(); | |
114 _addIfNonEmpty(sb, scheme, scheme, ':'); | |
115 if (hasAuthority() || (scheme == "file")) { | |
116 sb.add("//"); | |
117 _addIfNonEmpty(sb, userInfo, userInfo, "@"); | |
118 sb.add(domain); | |
119 if (port != 0) { | |
120 sb.add(":"); | |
121 sb.add(port.toString()); | |
122 } | |
123 } | |
124 sb.add(path); | |
125 _addIfNonEmpty(sb, query, "?", query); | |
126 _addIfNonEmpty(sb, fragment, "#", fragment); | |
127 return sb.toString(); | |
128 } | |
129 | |
130 static void _addIfNonEmpty(StringBuffer sb, String test, | |
131 String first, String second) { | |
132 if ("" != test) { | |
133 sb.add(first); | |
134 sb.add(second); | |
135 } | |
136 } | |
137 | |
138 Uri.fromString(String uri) : this._fromMatch(_splitRe.firstMatch(uri)); | |
139 | |
140 Uri._fromMatch(Match m) : this(_emptyIfNull(m[_COMPONENT_SCHEME]), | |
141 _emptyIfNull(m[_COMPONENT_USER_INFO]), | |
142 _emptyIfNull(m[_COMPONENT_DOMAIN]), | |
143 _parseIntOrZero(m[_COMPONENT_PORT]), | |
144 _emptyIfNull(m[_COMPONENT_PATH]), | |
145 _emptyIfNull(m[_COMPONENT_QUERY_DATA]), | |
146 _emptyIfNull(m[_COMPONENT_FRAGMENT])); | |
147 | |
148 const Uri([String this.scheme = "", String this.userInfo ="", | |
149 String this.domain = "", int this.port = 0, | |
150 String this.path = "", String this.query = "", | |
151 String this.fragment = ""]); | |
152 | |
153 static String _emptyIfNull(String val) => val != null ? val : ''; | |
154 | |
155 static int _parseIntOrZero(String val) { | |
156 if (val !== null && val != '') { | |
157 return Math.parseInt(val); | |
158 } else { | |
159 return 0; | |
160 } | |
161 } | |
162 | |
163 // NOTE: This code was ported from: closure-library/closure/goog/uri/utils.js | |
164 static RegExp _splitReLazy; | |
165 | |
166 static RegExp get _splitRe() { | |
167 if (_splitReLazy == null) { | |
168 _splitReLazy = new RegExp( | |
169 '^' + | |
170 '(?:' + | |
171 '([^:/?#.]+)' + // scheme - ignore special characters | |
172 // used by other URL parts such as :, | |
173 // ?, /, #, and . | |
174 ':)?' + | |
175 '(?://' + | |
176 '(?:([^/?#]*)@)?' + // userInfo | |
177 '([\\w\\d\\-\\u0100-\\uffff.%]*)' + | |
178 // domain - restrict to letters, | |
179 // digits, dashes, dots, percent | |
180 // escapes, and unicode characters. | |
181 '(?::([0-9]+))?' + // port | |
182 ')?' + | |
183 '([^?#]+)?' + // path | |
184 '(?:\\?([^#]*))?' + // query | |
185 '(?:#(.*))?' + // fragment | |
186 '\$'); | |
187 } | |
188 return _splitReLazy; | |
189 } | |
190 | |
191 static final _COMPONENT_SCHEME = 1; | |
192 static final _COMPONENT_USER_INFO = 2; | |
193 static final _COMPONENT_DOMAIN = 3; | |
194 static final _COMPONENT_PORT = 4; | |
195 static final _COMPONENT_PATH = 5; | |
196 static final _COMPONENT_QUERY_DATA = 6; | |
197 static final _COMPONENT_FRAGMENT = 7; | |
198 } | |
199 | |
200 testUri(String uri, bool isAbsolute) { | |
201 Expect.equals(isAbsolute, new Uri.fromString(uri).isAbsolute()); | |
202 Expect.stringEquals(uri, new Uri.fromString(uri).toString()); | |
203 } | |
204 | |
205 main() { | |
ahe
2012/01/29 14:38:00
I'll move this method and testUri to a test case b
| |
206 testUri("http:", true); | |
207 testUri("file://", true); | |
208 testUri("file", false); | |
209 testUri("http://user@example.com:80/fisk?query=89&hest=silas", true); | |
210 testUri("http://user@example.com:80/fisk?query=89&hest=silas#fragment", | |
211 false); | |
212 Expect.stringEquals("http://user@example.com:80/a/b/c?query#fragment", | |
213 const Uri("http", "user", "example.com", 80, "/a/b/c", | |
214 "query", "fragment").toString()); | |
215 Expect.stringEquals("null://null@null/a/b/c/?null#null", | |
216 const Uri(null, null, null, 0, "/a/b/c/", | |
217 null, null).toString()); | |
218 Expect.stringEquals("file://", new Uri.fromString("file:").toString()); | |
219 Expect.stringEquals("/a/g", removeDotSegments("/a/b/c/./../../g")); | |
220 Expect.stringEquals("mid/6", removeDotSegments("mid/content=5/../6")); | |
221 Expect.stringEquals("a/b/e", removeDotSegments("a/b/c/d/../../e")); | |
222 Expect.stringEquals("a/b/e", removeDotSegments("../a/b/c/d/../../e")); | |
223 Expect.stringEquals("a/b/e", removeDotSegments("./a/b/c/d/../../e")); | |
224 Expect.stringEquals("a/b/e", removeDotSegments("../a/b/./c/d/../../e")); | |
225 Expect.stringEquals("a/b/e", removeDotSegments("./a/b/./c/d/../../e")); | |
226 Expect.stringEquals("a/b/e/", removeDotSegments("./a/b/./c/d/../../e/.")); | |
227 Expect.stringEquals("a/b/e/", removeDotSegments("./a/b/./c/d/../../e/./.")); | |
228 Expect.stringEquals("a/b/e/", removeDotSegments("./a/b/./c/d/../../e/././.")); | |
229 | |
230 // From RFC 3986. | |
231 Uri base = new Uri.fromString("http://a/b/c/d;p?q"); | |
232 Expect.stringEquals("g:h", base.resolve("g:h").toString()); | |
233 Expect.stringEquals("http://a/b/c/g", base.resolve("g").toString()); | |
234 Expect.stringEquals("http://a/b/c/g", base.resolve("./g").toString()); | |
235 Expect.stringEquals("http://a/b/c/g/", base.resolve("g/").toString()); | |
236 Expect.stringEquals("http://a/g", base.resolve("/g").toString()); | |
237 Expect.stringEquals("http://g", base.resolve("//g").toString()); | |
238 Expect.stringEquals("http://a/b/c/d;p?y", base.resolve("?y").toString()); | |
239 Expect.stringEquals("http://a/b/c/g?y", base.resolve("g?y").toString()); | |
240 Expect.stringEquals("http://a/b/c/d;p?q#s", base.resolve("#s").toString()); | |
241 Expect.stringEquals("http://a/b/c/g#s", base.resolve("g#s").toString()); | |
242 Expect.stringEquals("http://a/b/c/g?y#s", base.resolve("g?y#s").toString()); | |
243 Expect.stringEquals("http://a/b/c/;x", base.resolve(";x").toString()); | |
244 Expect.stringEquals("http://a/b/c/g;x", base.resolve("g;x").toString()); | |
245 Expect.stringEquals("http://a/b/c/g;x?y#s", | |
246 base.resolve("g;x?y#s").toString()); | |
247 Expect.stringEquals("http://a/b/c/d;p?q", base.resolve("").toString()); | |
248 Expect.stringEquals("http://a/b/c/", base.resolve(".").toString()); | |
249 Expect.stringEquals("http://a/b/c/", base.resolve("./").toString()); | |
250 Expect.stringEquals("http://a/b/", base.resolve("..").toString()); | |
251 Expect.stringEquals("http://a/b/", base.resolve("../").toString()); | |
252 Expect.stringEquals("http://a/b/g", base.resolve("../g").toString()); | |
253 Expect.stringEquals("http://a/", base.resolve("../..").toString()); | |
254 Expect.stringEquals("http://a/", base.resolve("../../").toString()); | |
255 Expect.stringEquals("http://a/g", base.resolve("../../g").toString()); | |
256 Expect.stringEquals("http://a/g", base.resolve("../../../g").toString()); | |
257 Expect.stringEquals("http://a/g", base.resolve("../../../../g").toString()); | |
258 Expect.stringEquals("http://a/g", base.resolve("/./g").toString()); | |
259 Expect.stringEquals("http://a/g", base.resolve("/../g").toString()); | |
260 Expect.stringEquals("http://a/b/c/g.", base.resolve("g.").toString()); | |
261 Expect.stringEquals("http://a/b/c/.g", base.resolve(".g").toString()); | |
262 Expect.stringEquals("http://a/b/c/g..", base.resolve("g..").toString()); | |
263 Expect.stringEquals("http://a/b/c/..g", base.resolve("..g").toString()); | |
264 Expect.stringEquals("http://a/b/g", base.resolve("./../g").toString()); | |
265 Expect.stringEquals("http://a/b/c/g/", base.resolve("./g/.").toString()); | |
266 Expect.stringEquals("http://a/b/c/g/h", base.resolve("g/./h").toString()); | |
267 Expect.stringEquals("http://a/b/c/h", base.resolve("g/../h").toString()); | |
268 Expect.stringEquals("http://a/b/c/g;x=1/y", | |
269 base.resolve("g;x=1/./y").toString()); | |
270 Expect.stringEquals("http://a/b/c/y", base.resolve("g;x=1/../y").toString()); | |
271 Expect.stringEquals("http://a/b/c/g?y/./x", | |
272 base.resolve("g?y/./x").toString()); | |
273 Expect.stringEquals("http://a/b/c/g?y/../x", | |
274 base.resolve("g?y/../x").toString()); | |
275 Expect.stringEquals("http://a/b/c/g#s/./x", | |
276 base.resolve("g#s/./x").toString()); | |
277 Expect.stringEquals("http://a/b/c/g#s/../x", | |
278 base.resolve("g#s/../x").toString()); | |
Bill Hesse
2012/01/31 10:37:57
Should you also test parameters in the path elemen
ahe
2012/01/31 11:43:00
That's being tested on lines:
243-246
269-270
Bu
| |
279 Expect.stringEquals("http:g", base.resolve("http:g").toString()); | |
280 } | |
281 | |
282 String merge(String base, String reference) { | |
283 if (base == "") return "/$reference"; | |
284 return base.substring(0, base.lastIndexOf("/") + 1) + "$reference"; | |
285 } | |
286 | |
287 String removeDotSegments(String path) { | |
288 List<String> output = []; | |
289 bool appendSlash = false; | |
290 for (String segment in path.split("/")) { | |
291 appendSlash = false; | |
292 if (segment == "..") { | |
293 if (!output.isEmpty() && | |
294 ((output.length != 1) || (output[0] != ""))) output.removeLast(); | |
295 appendSlash = true; | |
296 } else if ("." == segment) { | |
297 appendSlash = true; | |
298 } else { | |
299 output.add(segment); | |
300 } | |
301 } | |
302 if (appendSlash) output.add(""); | |
303 return Strings.join(output, "/"); | |
304 } | |
OLD | NEW |