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 part of dart.core; | 5 part of dart.core; |
6 | 6 |
7 /** | 7 /** |
8 * A parsed URI, such as a URL. | 8 * A parsed URI, such as a URL. |
9 * | 9 * |
10 * **See also:** | 10 * **See also:** |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
69 | 69 |
70 /** | 70 /** |
71 * Cache the computed return value of [pathSegements]. | 71 * Cache the computed return value of [pathSegements]. |
72 */ | 72 */ |
73 List<String> _pathSegments; | 73 List<String> _pathSegments; |
74 | 74 |
75 /** | 75 /** |
76 * Cache the computed return value of [queryParameters]. | 76 * Cache the computed return value of [queryParameters]. |
77 */ | 77 */ |
78 Map<String, String> _queryParameters; | 78 Map<String, String> _queryParameters; |
| 79 Map<String, List<String>> _queryParameterLists; |
79 | 80 |
80 /// Internal non-verifying constructor. Only call with validated arguments. | 81 /// Internal non-verifying constructor. Only call with validated arguments. |
81 Uri._internal(this.scheme, | 82 Uri._internal(this.scheme, |
82 this._userInfo, | 83 this._userInfo, |
83 this._host, | 84 this._host, |
84 this._port, | 85 this._port, |
85 this._path, | 86 this._path, |
86 this._query, | 87 this._query, |
87 this._fragment); | 88 this._fragment); |
88 | 89 |
(...skipping 20 matching lines...) Expand all Loading... |
109 * | 110 * |
110 * The port part of the authority component is set through | 111 * The port part of the authority component is set through |
111 * [port]. | 112 * [port]. |
112 * If [port] is omitted or `null`, it implies the default port for | 113 * If [port] is omitted or `null`, it implies the default port for |
113 * the URI's scheme, and is equivalent to passing that port explicitly. | 114 * the URI's scheme, and is equivalent to passing that port explicitly. |
114 * The recognized schemes, and their default ports, are "http" (80) and | 115 * The recognized schemes, and their default ports, are "http" (80) and |
115 * "https" (443). All other schemes are considered as having zero as the | 116 * "https" (443). All other schemes are considered as having zero as the |
116 * default port. | 117 * default port. |
117 * | 118 * |
118 * If any of `userInfo`, `host` or `port` are provided, | 119 * If any of `userInfo`, `host` or `port` are provided, |
119 * the URI will have an autority according to [hasAuthority]. | 120 * the URI has an autority according to [hasAuthority]. |
120 * | 121 * |
121 * The path component is set through either [path] or | 122 * The path component is set through either [path] or |
122 * [pathSegments]. When [path] is used, it should be a valid URI path, | 123 * [pathSegments]. |
| 124 * When [path] is used, it should be a valid URI path, |
123 * but invalid characters, except the general delimiters ':/@[]?#', | 125 * but invalid characters, except the general delimiters ':/@[]?#', |
124 * will be escaped if necessary. | 126 * will be escaped if necessary. |
125 * When [pathSegments] is used, each of the provided segments | 127 * When [pathSegments] is used, each of the provided segments |
126 * is first percent-encoded and then joined using the forward slash | 128 * is first percent-encoded and then joined using the forward slash |
127 * separator. The percent-encoding of the path segments encodes all | 129 * separator. |
| 130 * |
| 131 * The percent-encoding of the path segments encodes all |
128 * characters except for the unreserved characters and the following | 132 * characters except for the unreserved characters and the following |
129 * list of characters: `!$&'()*+,;=:@`. If the other components | 133 * list of characters: `!$&'()*+,;=:@`. If the other components |
130 * calls for an absolute path a leading slash `/` is prepended if | 134 * necessitate an absolute path, a leading slash `/` is prepended if |
131 * not already there. | 135 * not already there. |
132 * | 136 * |
133 * The query component is set through either [query] or | 137 * The query component is set through either [query] or [queryParameters]. |
134 * [queryParameters]. When [query] is used the provided string should | 138 * When [query] is used, the provided string should be a valid URI query, |
135 * be a valid URI query, but invalid characters other than general delimiters, | 139 * but invalid characters, other than general delimiters, |
136 * will be escaped if necessary. | 140 * will be escaped if necessary. |
137 * When [queryParameters] is used the query is built from the | 141 * When [queryParameters] is used the query is built from the |
138 * provided map. Each key and value in the map is percent-encoded | 142 * provided map. Each key and value in the map is percent-encoded |
139 * and joined using equal and ampersand characters. The | 143 * and joined using equal and ampersand characters. |
140 * percent-encoding of the keys and values encodes all characters | 144 * A value in the map must be either a string, or an [Iterable] of strings, |
141 * except for the unreserved characters. | 145 * where the latter corresponds to multiple values for the same key. |
| 146 * |
| 147 * The percent-encoding of the keys and values encodes all characters |
| 148 * except for the unreserved characters, and replaces spaces with `+`. |
142 * If `query` is the empty string, it is equivalent to omitting it. | 149 * If `query` is the empty string, it is equivalent to omitting it. |
143 * To have an actual empty query part, | 150 * To have an actual empty query part, |
144 * use an empty list for `queryParameters`. | 151 * use an empty list for `queryParameters`. |
145 * If both `query` and `queryParameters` are omitted or `null`, the | 152 * |
146 * URI will have no query part. | 153 * If both `query` and `queryParameters` are omitted or `null`, |
| 154 * the URI has no query part. |
147 * | 155 * |
148 * The fragment component is set through [fragment]. | 156 * The fragment component is set through [fragment]. |
149 * It should be a valid URI fragment, but invalid characters other than | 157 * It should be a valid URI fragment, but invalid characters other than |
150 * general delimiters, will be escaped if necessary. | 158 * general delimiters, are escaped if necessary. |
151 * If `fragment` is omitted or `null`, the URI will have no fragment part. | 159 * If `fragment` is omitted or `null`, the URI has no fragment part. |
152 */ | 160 */ |
153 factory Uri({String scheme : "", | 161 factory Uri({String scheme : "", |
154 String userInfo : "", | 162 String userInfo : "", |
155 String host, | 163 String host, |
156 int port, | 164 int port, |
157 String path, | 165 String path, |
158 Iterable<String> pathSegments, | 166 Iterable<String> pathSegments, |
159 String query, | 167 String query, |
160 Map<String, String> queryParameters, | 168 Map<String, dynamic> queryParameters, |
161 String fragment}) { | 169 String fragment}) { |
162 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme)); | 170 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme)); |
163 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo)); | 171 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo)); |
164 host = _makeHost(host, 0, _stringOrNullLength(host), false); | 172 host = _makeHost(host, 0, _stringOrNullLength(host), false); |
165 // Special case this constructor for backwards compatibility. | 173 // Special case this constructor for backwards compatibility. |
166 if (query == "") query = null; | 174 if (query == "") query = null; |
167 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); | 175 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); |
168 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); | 176 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); |
169 port = _makePort(port, scheme); | 177 port = _makePort(port, scheme); |
170 bool isFile = (scheme == "file"); | 178 bool isFile = (scheme == "file"); |
(...skipping 29 matching lines...) Expand all Loading... |
200 * new Uri.http("example.org", "a b"); | 208 * new Uri.http("example.org", "a b"); |
201 * | 209 * |
202 * // http://example.org/a%252F | 210 * // http://example.org/a%252F |
203 * new Uri.http("example.org", "/a%2F"); | 211 * new Uri.http("example.org", "/a%2F"); |
204 * ``` | 212 * ``` |
205 * | 213 * |
206 * The `scheme` is always set to `http`. | 214 * The `scheme` is always set to `http`. |
207 * | 215 * |
208 * The `userInfo`, `host` and `port` components are set from the | 216 * The `userInfo`, `host` and `port` components are set from the |
209 * [authority] argument. If `authority` is `null` or empty, | 217 * [authority] argument. If `authority` is `null` or empty, |
210 * the created `Uri` will have no authority, and will not be directly usable | 218 * the created `Uri` has no authority, and isn't directly usable |
211 * as an HTTP URL, which must have a non-empty host. | 219 * as an HTTP URL, which must have a non-empty host. |
212 * | 220 * |
213 * The `path` component is set from the [unencodedPath] | 221 * The `path` component is set from the [unencodedPath] |
214 * argument. The path passed must not be encoded as this constructor | 222 * argument. The path passed must not be encoded as this constructor |
215 * encodes the path. | 223 * encodes the path. |
216 * | 224 * |
217 * The `query` component is set from the optional [queryParameters] | 225 * The `query` component is set from the optional [queryParameters] |
218 * argument. | 226 * argument. |
219 */ | 227 */ |
220 factory Uri.http(String authority, | 228 factory Uri.http(String authority, |
(...skipping 876 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1097 : new List<String>.unmodifiable( | 1105 : new List<String>.unmodifiable( |
1098 pathToSplit.split("/").map(Uri.decodeComponent)); | 1106 pathToSplit.split("/").map(Uri.decodeComponent)); |
1099 _pathSegments = result; | 1107 _pathSegments = result; |
1100 return result; | 1108 return result; |
1101 } | 1109 } |
1102 | 1110 |
1103 /** | 1111 /** |
1104 * Returns the URI query split into a map according to the rules | 1112 * Returns the URI query split into a map according to the rules |
1105 * specified for FORM post in the [HTML 4.01 specification section | 1113 * specified for FORM post in the [HTML 4.01 specification section |
1106 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM
L 4.01 section 17.13.4"). | 1114 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM
L 4.01 section 17.13.4"). |
| 1115 * Each key and value in the returned map has been decoded. |
| 1116 * If there is no query the empty map is returned. |
| 1117 * |
| 1118 * Keys in the query string that have no value are mapped to the |
| 1119 * empty string. |
| 1120 * If a key occurs more than once in the query string, it is mapped to |
| 1121 * an arbitrary choice of possible value. |
| 1122 * The [queryParametersAll] getter can provide a map |
| 1123 * that maps keys to all of their values. |
| 1124 * |
| 1125 * The returned map is unmodifiable. |
| 1126 */ |
| 1127 Map<String, String> get queryParameters { |
| 1128 if (_queryParameters == null) { |
| 1129 _queryParameters = |
| 1130 new UnmodifiableMapView<String, String>(splitQueryString(query)); |
| 1131 } |
| 1132 return _queryParameters; |
| 1133 } |
| 1134 |
| 1135 /** |
| 1136 * Returns the URI query split into a map according to the rules |
| 1137 * specified for FORM post in the [HTML 4.01 specification section |
| 1138 * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTM
L 4.01 section 17.13.4"). |
1107 * Each key and value in the returned map has been decoded. If there is no | 1139 * Each key and value in the returned map has been decoded. If there is no |
1108 * query the empty map is returned. | 1140 * query the empty map is returned. |
1109 * | 1141 * |
1110 * Keys in the query string that have no value are mapped to the | 1142 * Keys are mapped to lists of their values. If a key occurs only once, |
1111 * empty string. | 1143 * its value is a singleton list. If a key occurs with no value, the |
| 1144 * empty string is used as the value for that occurrence. |
1112 * | 1145 * |
1113 * The returned map is unmodifiable and will throw [UnsupportedError] on any | 1146 * The returned map and the lists it contains are unmodifiable. |
1114 * calls that would mutate it. | |
1115 */ | 1147 */ |
1116 Map<String, String> get queryParameters { | 1148 Map<String, List<String>> get queryParametersAll { |
1117 if (_queryParameters == null) { | 1149 if (_queryParameterLists == null) { |
1118 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); | 1150 Map queryParameterLists = _splitQueryStringAll(query); |
| 1151 for (var key in queryParameterLists.keys) { |
| 1152 queryParameterLists[key] = |
| 1153 new List<String>.unmodifiable(queryParameterLists[key]); |
| 1154 } |
| 1155 _queryParameterLists = |
| 1156 new Map<String, List<String>>.unmodifiable(queryParameterLists); |
1119 } | 1157 } |
1120 return _queryParameters; | 1158 return _queryParameterLists; |
1121 } | 1159 } |
1122 | 1160 |
1123 /** | 1161 /** |
1124 * Returns a URI where the path has been normalized. | 1162 * Returns a URI where the path has been normalized. |
1125 * | 1163 * |
1126 * A normalized path does not contain `.` segments or non-leading `..` | 1164 * A normalized path does not contain `.` segments or non-leading `..` |
1127 * segments. | 1165 * segments. |
1128 * Only a relative path with no scheme or authority may contain | 1166 * Only a relative path with no scheme or authority may contain |
1129 * leading `..` segments, | 1167 * leading `..` segments, |
1130 * a path that starts with `/` will also drop any leading `..` segments. | 1168 * a path that starts with `/` will also drop any leading `..` segments. |
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1337 | 1375 |
1338 static String _makeQuery(String query, int start, int end, | 1376 static String _makeQuery(String query, int start, int end, |
1339 Map<String, String> queryParameters) { | 1377 Map<String, String> queryParameters) { |
1340 if (query == null && queryParameters == null) return null; | 1378 if (query == null && queryParameters == null) return null; |
1341 if (query != null && queryParameters != null) { | 1379 if (query != null && queryParameters != null) { |
1342 throw new ArgumentError('Both query and queryParameters specified'); | 1380 throw new ArgumentError('Both query and queryParameters specified'); |
1343 } | 1381 } |
1344 if (query != null) return _normalize(query, start, end, _queryCharTable); | 1382 if (query != null) return _normalize(query, start, end, _queryCharTable); |
1345 | 1383 |
1346 var result = new StringBuffer(); | 1384 var result = new StringBuffer(); |
1347 var first = true; | 1385 var separator = ""; |
1348 queryParameters.forEach((key, value) { | 1386 |
1349 if (!first) { | 1387 void writeParameter(String key, String value) { |
1350 result.write("&"); | 1388 result.write(separator); |
1351 } | 1389 separator = "&"; |
1352 first = false; | |
1353 result.write(Uri.encodeQueryComponent(key)); | 1390 result.write(Uri.encodeQueryComponent(key)); |
1354 if (value != null && !value.isEmpty) { | 1391 if (value != null && value.isNotEmpty) { |
1355 result.write("="); | 1392 result.write("="); |
1356 result.write(Uri.encodeQueryComponent(value)); | 1393 result.write(Uri.encodeQueryComponent(value)); |
1357 } | 1394 } |
| 1395 } |
| 1396 |
| 1397 queryParameters.forEach((key, value) { |
| 1398 if (value == null || value is String) { |
| 1399 writeParameter(key, value); |
| 1400 } else { |
| 1401 Iterable values = value; |
| 1402 for (String value in values) { |
| 1403 writeParameter(key, value); |
| 1404 } |
| 1405 } |
1358 }); | 1406 }); |
1359 return result.toString(); | 1407 return result.toString(); |
1360 } | 1408 } |
1361 | 1409 |
1362 static String _makeFragment(String fragment, int start, int end) { | 1410 static String _makeFragment(String fragment, int start, int end) { |
1363 if (fragment == null) return null; | 1411 if (fragment == null) return null; |
1364 return _normalize(fragment, start, end, _queryCharTable); | 1412 return _normalize(fragment, start, end, _queryCharTable); |
1365 } | 1413 } |
1366 | 1414 |
1367 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; | 1415 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; |
(...skipping 781 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2149 } else if (index != 0) { | 2197 } else if (index != 0) { |
2150 var key = element.substring(0, index); | 2198 var key = element.substring(0, index); |
2151 var value = element.substring(index + 1); | 2199 var value = element.substring(index + 1); |
2152 map[Uri.decodeQueryComponent(key, encoding: encoding)] = | 2200 map[Uri.decodeQueryComponent(key, encoding: encoding)] = |
2153 decodeQueryComponent(value, encoding: encoding); | 2201 decodeQueryComponent(value, encoding: encoding); |
2154 } | 2202 } |
2155 return map; | 2203 return map; |
2156 }); | 2204 }); |
2157 } | 2205 } |
2158 | 2206 |
| 2207 static List _createList() => []; |
| 2208 |
| 2209 static Map _splitQueryStringAll( |
| 2210 String query, {Encoding encoding: UTF8}) { |
| 2211 Map result = {}; |
| 2212 int i = 0; |
| 2213 int start = 0; |
| 2214 int equalsIndex = -1; |
| 2215 |
| 2216 void parsePair(int start, int equalsIndex, int end) { |
| 2217 String key; |
| 2218 String value; |
| 2219 if (start == end) return; |
| 2220 if (equalsIndex < 0) { |
| 2221 key = _uriDecode(query, start, end, encoding, true); |
| 2222 value = ""; |
| 2223 } else { |
| 2224 key = _uriDecode(query, start, equalsIndex, encoding, true); |
| 2225 value = _uriDecode(query, equalsIndex + 1, end, encoding, true); |
| 2226 } |
| 2227 result.putIfAbsent(key, _createList).add(value); |
| 2228 } |
| 2229 |
| 2230 const int _equals = 0x3d; |
| 2231 const int _ampersand = 0x26; |
| 2232 while (i < query.length) { |
| 2233 int char = query.codeUnitAt(i); |
| 2234 if (char == _equals) { |
| 2235 if (equalsIndex < 0) equalsIndex = i; |
| 2236 } else if (char == _ampersand) { |
| 2237 parsePair(start, equalsIndex, i); |
| 2238 start = i + 1; |
| 2239 equalsIndex = -1; |
| 2240 } |
| 2241 i++; |
| 2242 } |
| 2243 parsePair(start, equalsIndex, i); |
| 2244 return result; |
| 2245 } |
| 2246 |
2159 /** | 2247 /** |
2160 * Parse the [host] as an IP version 4 (IPv4) address, returning the address | 2248 * Parse the [host] as an IP version 4 (IPv4) address, returning the address |
2161 * as a list of 4 bytes in network byte order (big endian). | 2249 * as a list of 4 bytes in network byte order (big endian). |
2162 * | 2250 * |
2163 * Throws a [FormatException] if [host] is not a valid IPv4 address | 2251 * Throws a [FormatException] if [host] is not a valid IPv4 address |
2164 * representation. | 2252 * representation. |
2165 */ | 2253 */ |
2166 static List<int> parseIPv4Address(String host) { | 2254 static List<int> parseIPv4Address(String host) { |
2167 void error(String msg) { | 2255 void error(String msg) { |
2168 throw new FormatException('Illegal IPv4 address, $msg'); | 2256 throw new FormatException('Illegal IPv4 address, $msg'); |
(...skipping 1088 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3257 // All non-escape RFC-2396 uric characters. | 3345 // All non-escape RFC-2396 uric characters. |
3258 // | 3346 // |
3259 // uric = reserved | unreserved | escaped | 3347 // uric = reserved | unreserved | escaped |
3260 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," | 3348 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," |
3261 // unreserved = alphanum | mark | 3349 // unreserved = alphanum | mark |
3262 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" | 3350 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" |
3263 // | 3351 // |
3264 // This is the same characters as in a URI query (which is URI pchar plus '?') | 3352 // This is the same characters as in a URI query (which is URI pchar plus '?') |
3265 static const _uricTable = Uri._queryCharTable; | 3353 static const _uricTable = Uri._queryCharTable; |
3266 } | 3354 } |
OLD | NEW |