Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(549)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 1520943002: Support the same parameter key more than once in Uri query parameters. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Address comments. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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 }
OLDNEW
« CHANGELOG.md ('K') | « CHANGELOG.md ('k') | tests/corelib/uri_parameters_all_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698