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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: sdk/lib/core/uri.dart
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index e4a31aa575fd0f40d23012d7b8f1382cf17d279c..a1a5b5d329600c268f15e42fdf08e2212dd5673a 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -76,6 +76,7 @@ class Uri {
* Cache the computed return value of [queryParameters].
*/
Map<String, String> _queryParameters;
+ Map<String, List<String>> _queryParameterLists;
/// Internal non-verifying constructor. Only call with validated arguments.
Uri._internal(this.scheme,
@@ -116,39 +117,46 @@ class Uri {
* default port.
*
* If any of `userInfo`, `host` or `port` are provided,
- * the URI will have an autority according to [hasAuthority].
+ * the URI has an autority according to [hasAuthority].
*
* The path component is set through either [path] or
- * [pathSegments]. When [path] is used, it should be a valid URI path,
+ * [pathSegments].
+ * When [path] is used, it should be a valid URI path,
* but invalid characters, except the general delimiters ':/@[]?#',
* will be escaped if necessary.
* When [pathSegments] is used, each of the provided segments
* is first percent-encoded and then joined using the forward slash
- * separator. The percent-encoding of the path segments encodes all
+ * separator.
+ *
+ * The percent-encoding of the path segments encodes all
* characters except for the unreserved characters and the following
* list of characters: `!$&'()*+,;=:@`. If the other components
- * calls for an absolute path a leading slash `/` is prepended if
+ * necessitate an absolute path, a leading slash `/` is prepended if
* not already there.
*
- * The query component is set through either [query] or
- * [queryParameters]. When [query] is used the provided string should
- * be a valid URI query, but invalid characters other than general delimiters,
+ * The query component is set through either [query] or [queryParameters].
+ * When [query] is used, the provided string should be a valid URI query,
+ * but invalid characters, other than general delimiters,
* will be escaped if necessary.
* When [queryParameters] is used the query is built from the
* provided map. Each key and value in the map is percent-encoded
- * and joined using equal and ampersand characters. The
- * percent-encoding of the keys and values encodes all characters
- * except for the unreserved characters.
+ * and joined using equal and ampersand characters.
+ * A value in the map must be either a string, or an [Iterable] of strings,
+ * where the latter corresponds to multiple values for the same key.
+ *
+ * The percent-encoding of the keys and values encodes all characters
+ * except for the unreserved characters, and replaces spaces with `+`.
* If `query` is the empty string, it is equivalent to omitting it.
* To have an actual empty query part,
* use an empty list for `queryParameters`.
- * If both `query` and `queryParameters` are omitted or `null`, the
- * URI will have no query part.
+ *
+ * If both `query` and `queryParameters` are omitted or `null`,
+ * the URI has no query part.
*
* The fragment component is set through [fragment].
* It should be a valid URI fragment, but invalid characters other than
- * general delimiters, will be escaped if necessary.
- * If `fragment` is omitted or `null`, the URI will have no fragment part.
+ * general delimiters, are escaped if necessary.
+ * If `fragment` is omitted or `null`, the URI has no fragment part.
*/
factory Uri({String scheme : "",
String userInfo : "",
@@ -157,7 +165,7 @@ class Uri {
String path,
Iterable<String> pathSegments,
String query,
- Map<String, String> queryParameters,
+ Map<String, dynamic> queryParameters,
String fragment}) {
scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
@@ -207,7 +215,7 @@ class Uri {
*
* The `userInfo`, `host` and `port` components are set from the
* [authority] argument. If `authority` is `null` or empty,
- * the created `Uri` will have no authority, and will not be directly usable
+ * the created `Uri` has no authority, and isn't directly usable
* as an HTTP URL, which must have a non-empty host.
*
* The `path` component is set from the [unencodedPath]
@@ -1104,23 +1112,53 @@ class Uri {
* Returns the URI query split into a map according to the rules
* specified for FORM post in the [HTML 4.01 specification section
* 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
- * Each key and value in the returned map has been decoded. If there is no
- * query the empty map is returned.
+ * Each key and value in the returned map has been decoded.
+ * If there is no query the empty map is returned.
*
* Keys in the query string that have no value are mapped to the
* empty string.
+ * If a key occurs more than once in the query string, it is mapped to
+ * an arbitrary choice of possible value.
+ * The [queryParametersAll] getter can provide a map
+ * that maps keys to all of their values.
*
- * The returned map is unmodifiable and will throw [UnsupportedError] on any
- * calls that would mutate it.
+ * The returned map is unmodifiable.
*/
Map<String, String> get queryParameters {
if (_queryParameters == null) {
- _queryParameters = new UnmodifiableMapView(splitQueryString(query));
+ _queryParameters =
+ new UnmodifiableMapView<String, String>(splitQueryString(query));
}
return _queryParameters;
}
/**
+ * Returns the URI query split into a map according to the rules
+ * specified for FORM post in the [HTML 4.01 specification section
+ * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
+ * Each key and value in the returned map has been decoded. If there is no
+ * query the empty map is returned.
+ *
+ * Keys are mapped to lists of their values. If a key occurs only once,
+ * its value is a singleton list. If a key occurs with no value, the
+ * empty string is used as the value for that occurrence.
+ *
+ * The returned map and the lists it contains are unmodifiable.
+ */
+ Map<String, List<String>> get queryParametersAll {
+ if (_queryParameterLists == null) {
+ Map queryParameterLists = _splitQueryStringAll(query);
+ for (var key in queryParameterLists.keys) {
+ queryParameterLists[key] =
+ new List<String>.unmodifiable(queryParameterLists[key]);
+ }
+ _queryParameterLists =
+ new Map<String, List<String>>.unmodifiable(queryParameterLists);
+ }
+ return _queryParameterLists;
+ }
+
+ /**
* Returns a URI where the path has been normalized.
*
* A normalized path does not contain `.` segments or non-leading `..`
@@ -1344,17 +1382,27 @@ class Uri {
if (query != null) return _normalize(query, start, end, _queryCharTable);
var result = new StringBuffer();
- var first = true;
- queryParameters.forEach((key, value) {
- if (!first) {
- result.write("&");
- }
- first = false;
+ var separator = "";
+
+ void writeParameter(String key, String value) {
+ result.write(separator);
+ separator = "&";
result.write(Uri.encodeQueryComponent(key));
- if (value != null && !value.isEmpty) {
+ if (value != null && value.isNotEmpty) {
result.write("=");
result.write(Uri.encodeQueryComponent(value));
}
+ }
+
+ queryParameters.forEach((key, value) {
+ if (value == null || value is String) {
+ writeParameter(key, value);
+ } else {
+ Iterable values = value;
+ for (String value in values) {
+ writeParameter(key, value);
+ }
+ }
});
return result.toString();
}
@@ -2156,6 +2204,46 @@ class Uri {
});
}
+ static List _createList() => [];
+
+ static Map _splitQueryStringAll(
+ String query, {Encoding encoding: UTF8}) {
+ Map result = {};
+ int i = 0;
+ int start = 0;
+ int equalsIndex = -1;
+
+ void parsePair(int start, int equalsIndex, int end) {
+ String key;
+ String value;
+ if (start == end) return;
+ if (equalsIndex < 0) {
+ key = _uriDecode(query, start, end, encoding, true);
+ value = "";
+ } else {
+ key = _uriDecode(query, start, equalsIndex, encoding, true);
+ value = _uriDecode(query, equalsIndex + 1, end, encoding, true);
+ }
+ result.putIfAbsent(key, _createList).add(value);
+ }
+
+ const int _equals = 0x3d;
+ const int _ampersand = 0x26;
+ while (i < query.length) {
+ int char = query.codeUnitAt(i);
+ if (char == _equals) {
+ if (equalsIndex < 0) equalsIndex = i;
+ } else if (char == _ampersand) {
+ parsePair(start, equalsIndex, i);
+ start = i + 1;
+ equalsIndex = -1;
+ }
+ i++;
+ }
+ parsePair(start, equalsIndex, i);
+ return result;
+ }
+
/**
* Parse the [host] as an IP version 4 (IPv4) address, returning the address
* as a list of 4 bytes in network byte order (big endian).
« 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