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

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