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

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: Adding tests. Created 5 years 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
Lasse Reichstein Nielsen 2015/12/14 13:16:54 I'm wondering if these can share something, but I
floitsch 2015/12/14 17:24:54 I don't really like expandos, but they would save
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 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 * list of characters: `!$&'()*+,;=:@`. If the other components 130 * list of characters: `!$&'()*+,;=:@`. If the other components
130 * calls for an absolute path a leading slash `/` is prepended if 131 * calls for an absolute path a leading slash `/` is prepended if
131 * not already there. 132 * not already there.
132 * 133 *
133 * The query component is set through either [query] or 134 * The query component is set through either [query] or
134 * [queryParameters]. When [query] is used the provided string should 135 * [queryParameters]. When [query] is used the provided string should
135 * be a valid URI query, but invalid characters other than general delimiters, 136 * be a valid URI query, but invalid characters other than general delimiters,
136 * will be escaped if necessary. 137 * will be escaped if necessary.
137 * When [queryParameters] is used the query is built from the 138 * When [queryParameters] is used the query is built from the
138 * provided map. Each key and value in the map is percent-encoded 139 * provided map. Each key and value in the map is percent-encoded
139 * and joined using equal and ampersand characters. The 140 * and joined using equal and ampersand characters.
140 * percent-encoding of the keys and values encodes all characters 141 * A value in the map must be either a string, or a an [Iterable] of strings,
141 * except for the unreserved characters. 142 * where the latter corresponds to multiple values for the same key.
143 * The percent-encoding of the keys and values encodes all characters
144 * except for the unreserved characters, and replaces spaces with `+`.
142 * If `query` is the empty string, it is equivalent to omitting it. 145 * If `query` is the empty string, it is equivalent to omitting it.
143 * To have an actual empty query part, 146 * To have an actual empty query part,
144 * use an empty list for `queryParameters`. 147 * use an empty list for `queryParameters`.
145 * If both `query` and `queryParameters` are omitted or `null`, the 148 * If both `query` and `queryParameters` are omitted or `null`,
146 * URI will have no query part. 149 * the URI will have no query part.
147 * 150 *
148 * The fragment component is set through [fragment]. 151 * The fragment component is set through [fragment].
149 * It should be a valid URI fragment, but invalid characters other than 152 * It should be a valid URI fragment, but invalid characters other than
150 * general delimiters, will be escaped if necessary. 153 * general delimiters, will be escaped if necessary.
151 * If `fragment` is omitted or `null`, the URI will have no fragment part. 154 * If `fragment` is omitted or `null`, the URI will have no fragment part.
152 */ 155 */
153 factory Uri({String scheme : "", 156 factory Uri({String scheme : "",
154 String userInfo : "", 157 String userInfo : "",
155 String host, 158 String host,
156 int port, 159 int port,
157 String path, 160 String path,
158 Iterable<String> pathSegments, 161 Iterable<String> pathSegments,
159 String query, 162 String query,
160 Map<String, String> queryParameters, 163 Map<String, dynamic> queryParameters,
161 String fragment}) { 164 String fragment}) {
162 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme)); 165 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
163 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo)); 166 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
164 host = _makeHost(host, 0, _stringOrNullLength(host), false); 167 host = _makeHost(host, 0, _stringOrNullLength(host), false);
165 // Special case this constructor for backwards compatibility. 168 // Special case this constructor for backwards compatibility.
166 if (query == "") query = null; 169 if (query == "") query = null;
167 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); 170 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
168 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); 171 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
169 port = _makePort(port, scheme); 172 port = _makePort(port, scheme);
170 bool isFile = (scheme == "file"); 173 bool isFile = (scheme == "file");
(...skipping 926 matching lines...) Expand 10 before | Expand all | Expand 10 after
1097 : new List<String>.unmodifiable( 1100 : new List<String>.unmodifiable(
1098 pathToSplit.split("/").map(Uri.decodeComponent)); 1101 pathToSplit.split("/").map(Uri.decodeComponent));
1099 _pathSegments = result; 1102 _pathSegments = result;
1100 return result; 1103 return result;
1101 } 1104 }
1102 1105
1103 /** 1106 /**
1104 * Returns the URI query split into a map according to the rules 1107 * Returns the URI query split into a map according to the rules
1105 * specified for FORM post in the [HTML 4.01 specification section 1108 * 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"). 1109 * 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").
1110 * Each key and value in the returned map has been decoded.
1111 * If there is no query the empty map is returned.
1112 *
1113 * Keys in the query string that have no value are mapped to the
1114 * empty string.
1115 * If a key occurs more than once in the query string, it is mapped to
1116 * one of the values. The [queryParameterValues] getter can provide a
1117 * map that maps to all the values.
1118 *
1119 * The returned map is unmodifiable and will throw [UnsupportedError] on any
1120 * calls that would mutate it.
1121 */
1122 Map<String, String> get queryParameters {
1123 if (_queryParameters == null) {
1124 _queryParameters =
1125 new UnmodifiableMapView<String, String>(splitQueryString(query));
1126 }
1127 return _queryParameters;
1128 }
1129
1130 /**
1131 * Returns the URI query split into a map according to the rules
1132 * specified for FORM post in the [HTML 4.01 specification section
1133 * 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 1134 * Each key and value in the returned map has been decoded. If there is no
1108 * query the empty map is returned. 1135 * query the empty map is returned.
1109 * 1136 *
1110 * Keys in the query string that have no value are mapped to the 1137 * Keys are mapped to lists of their values. If a key occurs only once,
1111 * empty string. 1138 * its value is a singleton list. If a key occurs with no value, the
1139 * empty string is used as the value for that occurrence.
1112 * 1140 *
1113 * The returned map is unmodifiable and will throw [UnsupportedError] on any 1141 * The returned map and the lists it contains are unmodifiable
1114 * calls that would mutate it. 1142 * and will throw [UnsupportedError] on any calls that would mutate them.
1115 */ 1143 */
1116 Map<String, String> get queryParameters { 1144 Map<String, List<String>> get queryParameterLists {
Lasse Reichstein Nielsen 2015/12/14 13:16:54 I'm not particularly happy with the name. Suggesti
floitsch 2015/12/14 17:24:54 multiQueryParameters?
Søren Gjesse 2015/12/15 07:27:42 +1
Lasse Reichstein Nielsen 2015/12/15 08:11:28 I don't think that works very well. It sounds like
floitsch 2015/12/15 09:17:52 alternatively we could go the long way: queryPara
Lasse Reichstein Nielsen 2015/12/16 13:06:21 Longer could do it, but "with duplicates" doesn't
Lasse Reichstein Nielsen 2016/01/12 13:34:12 Going with queryParametersAll - which isn't perfec
1117 if (_queryParameters == null) { 1145 if (_queryParameterLists == null) {
1118 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); 1146 Map queryParameterLists = _splitQueryStringAll(query);
1147 for (var key in queryParameterLists.keys) {
1148 queryParameterLists[key] =
1149 new List<String>.unmodifiable(queryParameterLists[key]);
1150 }
1151 _queryParameterLists = new Map<String, List<String>>.unmodifiable(
1152 queryParameterLists);
1119 } 1153 }
1120 return _queryParameters; 1154 return _queryParameterLists;
1121 } 1155 }
1122 1156
1123 /** 1157 /**
1124 * Returns a URI where the path has been normalized. 1158 * Returns a URI where the path has been normalized.
1125 * 1159 *
1126 * A normalized path does not contain `.` segments or non-leading `..` 1160 * A normalized path does not contain `.` segments or non-leading `..`
1127 * segments. 1161 * segments.
1128 * Only a relative path with no scheme or authority may contain 1162 * Only a relative path with no scheme or authority may contain
1129 * leading `..` segments, 1163 * leading `..` segments,
1130 * a path that starts with `/` will also drop any leading `..` segments. 1164 * a path that starts with `/` will also drop any leading `..` segments.
(...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after
1343 } 1377 }
1344 if (query != null) return _normalize(query, start, end, _queryCharTable); 1378 if (query != null) return _normalize(query, start, end, _queryCharTable);
1345 1379
1346 var result = new StringBuffer(); 1380 var result = new StringBuffer();
1347 var first = true; 1381 var first = true;
1348 queryParameters.forEach((key, value) { 1382 queryParameters.forEach((key, value) {
1349 if (!first) { 1383 if (!first) {
1350 result.write("&"); 1384 result.write("&");
1351 } 1385 }
1352 first = false; 1386 first = false;
1353 result.write(Uri.encodeQueryComponent(key)); 1387 if (value == null) {
1354 if (value != null && !value.isEmpty) { 1388 result.write(Uri.encodeQueryComponent(key));
1355 result.write("="); 1389 } else if (value is String) {
1356 result.write(Uri.encodeQueryComponent(value)); 1390 result.write(Uri.encodeQueryComponent(key));
1391 if (value.isNotEmpty) {
1392 result.write("=");
1393 result.write(Uri.encodeQueryComponent(value));
1394 }
1395 } else {
1396 Iterable values = value;
1397 bool first = true;
1398 for (String value in values) {
1399 if (!first) {
1400 result.write("&");
1401 }
1402 first = false;
1403 result.write(Uri.encodeQueryComponent(key));
1404 if (value != null && value.isNotEmpty) {
1405 result.write("=");
1406 result.write(Uri.encodeQueryComponent(value));
1407 }
1408 }
Lasse Reichstein Nielsen 2015/12/14 13:16:54 Maybe I should extract some of this into a helper
Lasse Reichstein Nielsen 2016/01/12 13:34:11 Done.
1357 } 1409 }
1358 }); 1410 });
1359 return result.toString(); 1411 return result.toString();
1360 } 1412 }
1361 1413
1362 static String _makeFragment(String fragment, int start, int end) { 1414 static String _makeFragment(String fragment, int start, int end) {
1363 if (fragment == null) return null; 1415 if (fragment == null) return null;
1364 return _normalize(fragment, start, end, _queryCharTable); 1416 return _normalize(fragment, start, end, _queryCharTable);
1365 } 1417 }
1366 1418
(...skipping 782 matching lines...) Expand 10 before | Expand all | Expand 10 after
2149 } else if (index != 0) { 2201 } else if (index != 0) {
2150 var key = element.substring(0, index); 2202 var key = element.substring(0, index);
2151 var value = element.substring(index + 1); 2203 var value = element.substring(index + 1);
2152 map[Uri.decodeQueryComponent(key, encoding: encoding)] = 2204 map[Uri.decodeQueryComponent(key, encoding: encoding)] =
2153 decodeQueryComponent(value, encoding: encoding); 2205 decodeQueryComponent(value, encoding: encoding);
2154 } 2206 }
2155 return map; 2207 return map;
2156 }); 2208 });
2157 } 2209 }
2158 2210
2211 static List _createList() => [];
2212
2213 static Map _splitQueryStringAll(
Lasse Reichstein Nielsen 2015/12/14 13:16:54 This one is private, while splitQueryString is pub
2214 String query, {Encoding encoding: UTF8}) {
2215 Map result = {};
2216 int i = 0;
2217 int start = 0;
2218 int equalsIndex = -1;
2219 void parsePair(int start, int equalsIndex, int end) {
2220 String key;
2221 String value;
2222 if (start == end) return;
2223 if (equalsIndex < 0) {
2224 key = _uriDecode(query, start, end, encoding, true);
2225 value = "";
2226 } else {
2227 key =
2228 _uriDecode(query, start, equalsIndex, encoding, true);
2229 value =
2230 _uriDecode(query, equalsIndex + 1, end, encoding, true);
2231 }
2232 result.putIfAbsent(key, _createList).add(value);
2233 }
2234 const int _equals = 0x3d;
2235 const int _ampersand = 0x26;
2236 while (i < query.length) {
2237 int char = query.codeUnitAt(i);
2238 if (char == _equals) {
2239 if (equalsIndex < 0) equalsIndex = i;
2240 } else if (char == _ampersand) {
2241 parsePair(start, equalsIndex, i);
2242 start = i + 1;
2243 equalsIndex = -1;
2244 }
2245 i++;
2246 }
2247 parsePair(start, equalsIndex, i);
2248 return result;
2249 }
2250
2159 /** 2251 /**
2160 * Parse the [host] as an IP version 4 (IPv4) address, returning the address 2252 * 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). 2253 * as a list of 4 bytes in network byte order (big endian).
2162 * 2254 *
2163 * Throws a [FormatException] if [host] is not a valid IPv4 address 2255 * Throws a [FormatException] if [host] is not a valid IPv4 address
2164 * representation. 2256 * representation.
2165 */ 2257 */
2166 static List<int> parseIPv4Address(String host) { 2258 static List<int> parseIPv4Address(String host) {
2167 void error(String msg) { 2259 void error(String msg) {
2168 throw new FormatException('Illegal IPv4 address, $msg'); 2260 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. 3349 // All non-escape RFC-2396 uric characters.
3258 // 3350 //
3259 // uric = reserved | unreserved | escaped 3351 // uric = reserved | unreserved | escaped
3260 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," 3352 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3261 // unreserved = alphanum | mark 3353 // unreserved = alphanum | mark
3262 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 3354 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3263 // 3355 //
3264 // This is the same characters as in a URI query (which is URI pchar plus '?') 3356 // This is the same characters as in a URI query (which is URI pchar plus '?')
3265 static const _uricTable = Uri._queryCharTable; 3357 static const _uricTable = Uri._queryCharTable;
3266 } 3358 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698