| 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).
|
|
|