| Index: tool/input_sdk/lib/core/uri.dart
 | 
| diff --git a/tool/input_sdk/lib/core/uri.dart b/tool/input_sdk/lib/core/uri.dart
 | 
| index a6c69bb457b96053d6e49ee901febecd40338263..0cfecebe472a9a36983842d3ce30ffdf6d95deb8 100644
 | 
| --- a/tool/input_sdk/lib/core/uri.dart
 | 
| +++ b/tool/input_sdk/lib/core/uri.dart
 | 
| @@ -12,30 +12,238 @@ part of dart.core;
 | 
|   * * [URIs][uris] in the [library tour][libtour]
 | 
|   * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
 | 
|   *
 | 
| - * [uris]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html#ch03-uri
 | 
| - * [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html
 | 
| + * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
 | 
| + * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html
 | 
|   */
 | 
|  class Uri {
 | 
| -  // The host name of the URI.
 | 
| -  // Set to `null` if there is no authority in a URI.
 | 
| -  final String _host;
 | 
| -  // The port. Set to null if there is no port. Normalized to null if
 | 
| -  // the port is the default port for the scheme.
 | 
| -  // Set to the value of the default port if an empty port was supplied.
 | 
| -  num _port;
 | 
| -  // The path. Always non-null.
 | 
| -  String _path;
 | 
| -
 | 
|    /**
 | 
| -   * Returns the scheme component.
 | 
| +   * The scheme component of the URI.
 | 
|     *
 | 
|     * Returns the empty string if there is no scheme component.
 | 
| +   *
 | 
| +   * A URI scheme is case insensitive.
 | 
| +   * The returned scheme is canonicalized to lowercase letters.
 | 
|     */
 | 
|    // We represent the missing scheme as an empty string.
 | 
|    // A valid scheme cannot be empty.
 | 
|    final String scheme;
 | 
|  
 | 
|    /**
 | 
| +   * The user-info part of the authority.
 | 
| +   *
 | 
| +   * Does not distinguish between an empty user-info and an absent one.
 | 
| +   * The value is always non-null.
 | 
| +   * Is considered absent if [_host] is `null`.
 | 
| +   */
 | 
| +  final String _userInfo;
 | 
| +
 | 
| +  /**
 | 
| +   * The host name of the URI.
 | 
| +   *
 | 
| +   * Set to `null` if there is no authority in the URI.
 | 
| +   * The host name is the only mandatory part of an authority, so we use
 | 
| +   * it to mark whether an authority part was present or not.
 | 
| +   */
 | 
| +  final String _host;
 | 
| +
 | 
| +  /**
 | 
| +   * The port number part of the authority.
 | 
| +   *
 | 
| +   * The port. Set to null if there is no port. Normalized to null if
 | 
| +   * the port is the default port for the scheme.
 | 
| +   */
 | 
| +  int _port;
 | 
| +
 | 
| +  /**
 | 
| +   * The path of the URI.
 | 
| +   *
 | 
| +   * Always non-null.
 | 
| +   */
 | 
| +  String _path;
 | 
| +
 | 
| +  // The query content, or null if there is no query.
 | 
| +  final String _query;
 | 
| +
 | 
| +  // The fragment content, or null if there is no fragment.
 | 
| +  final String _fragment;
 | 
| +
 | 
| +  /**
 | 
| +   * Cache the computed return value of [pathSegements].
 | 
| +   */
 | 
| +  List<String> _pathSegments;
 | 
| +
 | 
| +  /**
 | 
| +   * 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,
 | 
| +                this._userInfo,
 | 
| +                this._host,
 | 
| +                this._port,
 | 
| +                this._path,
 | 
| +                this._query,
 | 
| +                this._fragment);
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a new URI from its components.
 | 
| +   *
 | 
| +   * Each component is set through a named argument. Any number of
 | 
| +   * components can be provided. The [path] and [query] components can be set
 | 
| +   * using either of two different named arguments.
 | 
| +   *
 | 
| +   * The scheme component is set through [scheme]. The scheme is
 | 
| +   * normalized to all lowercase letters. If the scheme is omitted or empty,
 | 
| +   * the URI will not have a scheme part.
 | 
| +   *
 | 
| +   * The user info part of the authority component is set through
 | 
| +   * [userInfo]. It defaults to the empty string, which will be omitted
 | 
| +   * from the string representation of the URI.
 | 
| +   *
 | 
| +   * The host part of the authority component is set through
 | 
| +   * [host]. The host can either be a hostname, an IPv4 address or an
 | 
| +   * IPv6 address, contained in '[' and ']'. If the host contains a
 | 
| +   * ':' character, the '[' and ']' are added if not already provided.
 | 
| +   * The host is normalized to all lowercase letters.
 | 
| +   *
 | 
| +   * The port part of the authority component is set through
 | 
| +   * [port].
 | 
| +   * If [port] is omitted or `null`, it implies the default port for
 | 
| +   * the URI's scheme, and is equivalent to passing that port explicitly.
 | 
| +   * The recognized schemes, and their default ports, are "http" (80) and
 | 
| +   * "https" (443). All other schemes are considered as having zero as the
 | 
| +   * default port.
 | 
| +   *
 | 
| +   * If any of `userInfo`, `host` or `port` are provided,
 | 
| +   * 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,
 | 
| +   * 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
 | 
| +   * characters except for the unreserved characters and the following
 | 
| +   * list of characters: `!$&'()*+,;=:@`. If the other components
 | 
| +   * 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,
 | 
| +   * 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.
 | 
| +   * 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 map for `queryParameters`.
 | 
| +   *
 | 
| +   * 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, are escaped if necessary.
 | 
| +   * If `fragment` is omitted or `null`, the URI has no fragment part.
 | 
| +   */
 | 
| +  factory Uri({String scheme : "",
 | 
| +               String userInfo : "",
 | 
| +               String host,
 | 
| +               int port,
 | 
| +               String path,
 | 
| +               Iterable<String> pathSegments,
 | 
| +               String query,
 | 
| +               Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
 | 
| +               String fragment}) {
 | 
| +    scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
 | 
| +    userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
 | 
| +    host = _makeHost(host, 0, _stringOrNullLength(host), false);
 | 
| +    // Special case this constructor for backwards compatibility.
 | 
| +    if (query == "") query = null;
 | 
| +    query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
 | 
| +    fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
 | 
| +    port = _makePort(port, scheme);
 | 
| +    bool isFile = (scheme == "file");
 | 
| +    if (host == null &&
 | 
| +        (userInfo.isNotEmpty || port != null || isFile)) {
 | 
| +      host = "";
 | 
| +    }
 | 
| +    bool hasAuthority = (host != null);
 | 
| +    path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
 | 
| +                     scheme, hasAuthority);
 | 
| +    if (scheme.isEmpty && host == null && !path.startsWith('/')) {
 | 
| +      path = _normalizeRelativePath(path);
 | 
| +    } else {
 | 
| +      path = _removeDotSegments(path);
 | 
| +    }
 | 
| +    return new Uri._internal(scheme, userInfo, host, port,
 | 
| +                             path, query, fragment);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a new `http` URI from authority, path and query.
 | 
| +   *
 | 
| +   * Examples:
 | 
| +   *
 | 
| +   * ```
 | 
| +   * // http://example.org/path?q=dart.
 | 
| +   * new Uri.http("google.com", "/search", { "q" : "dart" });
 | 
| +   *
 | 
| +   * // http://user:pass@localhost:8080
 | 
| +   * new Uri.http("user:pass@localhost:8080", "");
 | 
| +   *
 | 
| +   * // http://example.org/a%20b
 | 
| +   * new Uri.http("example.org", "a b");
 | 
| +   *
 | 
| +   * // http://example.org/a%252F
 | 
| +   * new Uri.http("example.org", "/a%2F");
 | 
| +   * ```
 | 
| +   *
 | 
| +   * The `scheme` is always set to `http`.
 | 
| +   *
 | 
| +   * The `userInfo`, `host` and `port` components are set from the
 | 
| +   * [authority] argument. If `authority` is `null` or empty,
 | 
| +   * 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]
 | 
| +   * argument. The path passed must not be encoded as this constructor
 | 
| +   * encodes the path.
 | 
| +   *
 | 
| +   * The `query` component is set from the optional [queryParameters]
 | 
| +   * argument.
 | 
| +   */
 | 
| +  factory Uri.http(String authority,
 | 
| +                   String unencodedPath,
 | 
| +                   [Map<String, String> queryParameters]) {
 | 
| +    return _makeHttpUri("http", authority, unencodedPath, queryParameters);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a new `https` URI from authority, path and query.
 | 
| +   *
 | 
| +   * This constructor is the same as [Uri.http] except for the scheme
 | 
| +   * which is set to `https`.
 | 
| +   */
 | 
| +  factory Uri.https(String authority,
 | 
| +                    String unencodedPath,
 | 
| +                    [Map<String, String> queryParameters]) {
 | 
| +    return _makeHttpUri("https", authority, unencodedPath, queryParameters);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
|     * Returns the authority component.
 | 
|     *
 | 
|     * The authority is formatted from the [userInfo], [host] and [port]
 | 
| @@ -51,14 +259,6 @@ class Uri {
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| -   * The user-info part of the authority.
 | 
| -   *
 | 
| -   * Does not distinguish between an empty user-info and an absent one.
 | 
| -   * The value is always non-null.
 | 
| -   */
 | 
| -  final String _userInfo;
 | 
| -
 | 
| -  /**
 | 
|     * Returns the user info part of the authority component.
 | 
|     *
 | 
|     * Returns the empty string if there is no user info in the
 | 
| @@ -74,6 +274,10 @@ class Uri {
 | 
|     *
 | 
|     * If the host is an IP version 6 address, the surrounding `[` and `]` is
 | 
|     * removed.
 | 
| +   *
 | 
| +   * The host string is case-insensitive.
 | 
| +   * The returned host name is canonicalized to lower-case
 | 
| +   * with upper-case percent-escapes.
 | 
|     */
 | 
|    String get host {
 | 
|      if (_host == null) return "";
 | 
| @@ -111,9 +315,6 @@ class Uri {
 | 
|     */
 | 
|    String get path => _path;
 | 
|  
 | 
| -  // The query content, or null if there is no query.
 | 
| -  final String _query;
 | 
| -
 | 
|    /**
 | 
|     * Returns the query component. The returned query is encoded. To get
 | 
|     * direct access to the decoded query use [queryParameters].
 | 
| @@ -122,9 +323,6 @@ class Uri {
 | 
|     */
 | 
|    String get query => (_query == null) ? "" : _query;
 | 
|  
 | 
| -  // The fragment content, or null if there is no fragment.
 | 
| -  final String _fragment;
 | 
| -
 | 
|    /**
 | 
|     * Returns the fragment identifier component.
 | 
|     *
 | 
| @@ -134,26 +332,18 @@ class Uri {
 | 
|    String get fragment => (_fragment == null) ? "" : _fragment;
 | 
|  
 | 
|    /**
 | 
| -   * Cache the computed return value of [pathSegements].
 | 
| -   */
 | 
| -  List<String> _pathSegments;
 | 
| -
 | 
| -  /**
 | 
| -   * Cache the computed return value of [queryParameters].
 | 
| -   */
 | 
| -  Map<String, String> _queryParameters;
 | 
| -
 | 
| -  /**
 | 
|     * Creates a new `Uri` object by parsing a URI string.
 | 
|     *
 | 
| +   * If [start] and [end] are provided, only the substring from `start`
 | 
| +   * to `end` is parsed as a URI.
 | 
| +   *
 | 
|     * If the string is not valid as a URI or URI reference,
 | 
| -   * invalid characters will be percent escaped where possible.
 | 
| -   * The resulting `Uri` will represent a valid URI or URI reference.
 | 
| +   * a [FormatException] is thrown.
 | 
|     */
 | 
| -  static Uri parse(String uri) {
 | 
| -    // This parsing will not validate percent-encoding, IPv6, etc. When done
 | 
| -    // it will call `new Uri(...)` which will perform these validations.
 | 
| -    // This is purely splitting up the URI string into components.
 | 
| +  static Uri parse(String uri, [int start = 0, int end]) {
 | 
| +    // This parsing will not validate percent-encoding, IPv6, etc.
 | 
| +    // When done splitting into parts, it will call, e.g., [_makeFragment]
 | 
| +    // to do the final parsing.
 | 
|      //
 | 
|      // Important parts of the RFC 3986 used here:
 | 
|      // URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
 | 
| @@ -204,26 +394,24 @@ class Uri {
 | 
|      // query         = *( pchar / "/" / "?" )
 | 
|      //
 | 
|      // fragment      = *( pchar / "/" / "?" )
 | 
| -    bool isRegName(int ch) {
 | 
| -      return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
 | 
| -    }
 | 
|      const int EOI = -1;
 | 
|  
 | 
|      String scheme = "";
 | 
|      String userinfo = "";
 | 
|      String host = null;
 | 
| -    num port = null;
 | 
| +    int port = null;
 | 
|      String path = null;
 | 
|      String query = null;
 | 
|      String fragment = null;
 | 
| +    if (end == null) end = uri.length;
 | 
|  
 | 
| -    int index = 0;
 | 
| -    int pathStart = 0;
 | 
| +    int index = start;
 | 
| +    int pathStart = start;
 | 
|      // End of input-marker.
 | 
|      int char = EOI;
 | 
|  
 | 
|      void parseAuth() {
 | 
| -      if (index == uri.length) {
 | 
| +      if (index == end) {
 | 
|          char = EOI;
 | 
|          return;
 | 
|        }
 | 
| @@ -231,7 +419,7 @@ class Uri {
 | 
|        int lastColon = -1;
 | 
|        int lastAt = -1;
 | 
|        char = uri.codeUnitAt(index);
 | 
| -      while (index < uri.length) {
 | 
| +      while (index < end) {
 | 
|          char = uri.codeUnitAt(index);
 | 
|          if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
 | 
|            break;
 | 
| @@ -245,7 +433,7 @@ class Uri {
 | 
|            lastColon = -1;
 | 
|            int endBracket = uri.indexOf(']', index + 1);
 | 
|            if (endBracket == -1) {
 | 
| -            index = uri.length;
 | 
| +            index = end;
 | 
|              char = EOI;
 | 
|              break;
 | 
|            } else {
 | 
| @@ -277,7 +465,7 @@ class Uri {
 | 
|          hostEnd = lastColon;
 | 
|        }
 | 
|        host = _makeHost(uri, hostStart, hostEnd, true);
 | 
| -      if (index < uri.length) {
 | 
| +      if (index < end) {
 | 
|          char = uri.codeUnitAt(index);
 | 
|        }
 | 
|      }
 | 
| @@ -298,22 +486,22 @@ class Uri {
 | 
|      // All other breaks set their own state.
 | 
|      int state = NOT_IN_PATH;
 | 
|      int i = index;  // Temporary alias for index to avoid bug 19550 in dart2js.
 | 
| -    while (i < uri.length) {
 | 
| +    while (i < end) {
 | 
|        char = uri.codeUnitAt(i);
 | 
|        if (char == _QUESTION || char == _NUMBER_SIGN) {
 | 
|          state = NOT_IN_PATH;
 | 
|          break;
 | 
|        }
 | 
|        if (char == _SLASH) {
 | 
| -        state = (i == 0) ? ALLOW_AUTH : IN_PATH;
 | 
| +        state = (i == start) ? ALLOW_AUTH : IN_PATH;
 | 
|          break;
 | 
|        }
 | 
|        if (char == _COLON) {
 | 
| -        if (i == 0) _fail(uri, 0, "Invalid empty scheme");
 | 
| -        scheme = _makeScheme(uri, i);
 | 
| +        if (i == start) _fail(uri, start, "Invalid empty scheme");
 | 
| +        scheme = _makeScheme(uri, start, i);
 | 
|          i++;
 | 
|          pathStart = i;
 | 
| -        if (i == uri.length) {
 | 
| +        if (i == end) {
 | 
|            char = EOI;
 | 
|            state = NOT_IN_PATH;
 | 
|          } else {
 | 
| @@ -338,7 +526,7 @@ class Uri {
 | 
|        // Have seen one slash either at start or right after scheme.
 | 
|        // If two slashes, it's an authority, otherwise it's just the path.
 | 
|        index++;
 | 
| -      if (index == uri.length) {
 | 
| +      if (index == end) {
 | 
|          char = EOI;
 | 
|          state = NOT_IN_PATH;
 | 
|        } else {
 | 
| @@ -360,7 +548,7 @@ class Uri {
 | 
|      if (state == IN_PATH) {
 | 
|        // Characters from pathStart to index (inclusive) are known
 | 
|        // to be part of the path.
 | 
| -      while (++index < uri.length) {
 | 
| +      while (++index < end) {
 | 
|          char = uri.codeUnitAt(index);
 | 
|          if (char == _QUESTION || char == _NUMBER_SIGN) {
 | 
|            break;
 | 
| @@ -371,20 +559,25 @@ class Uri {
 | 
|      }
 | 
|  
 | 
|      assert(state == NOT_IN_PATH);
 | 
| -    bool isFile = (scheme == "file");
 | 
| -    bool ensureLeadingSlash = host != null;
 | 
| -    path = _makePath(uri, pathStart, index, null, ensureLeadingSlash, isFile);
 | 
| +    bool hasAuthority = (host != null);
 | 
| +    path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
 | 
|  
 | 
|      if (char == _QUESTION) {
 | 
| -      int numberSignIndex = uri.indexOf('#', index + 1);
 | 
| +      int numberSignIndex = -1;
 | 
| +      for (int i = index + 1; i < end; i++) {
 | 
| +        if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
 | 
| +          numberSignIndex = i;
 | 
| +          break;
 | 
| +        }
 | 
| +      }
 | 
|        if (numberSignIndex < 0) {
 | 
| -        query = _makeQuery(uri, index + 1, uri.length, null);
 | 
| +        query = _makeQuery(uri, index + 1, end, null);
 | 
|        } else {
 | 
|          query = _makeQuery(uri, index + 1, numberSignIndex, null);
 | 
| -        fragment = _makeFragment(uri, numberSignIndex + 1, uri.length);
 | 
| +        fragment = _makeFragment(uri, numberSignIndex + 1, end);
 | 
|        }
 | 
|      } else if (char == _NUMBER_SIGN) {
 | 
| -      fragment = _makeFragment(uri, index + 1, uri.length);
 | 
| +      fragment = _makeFragment(uri, index + 1, end);
 | 
|      }
 | 
|      return new Uri._internal(scheme,
 | 
|                               userinfo,
 | 
| @@ -400,159 +593,6 @@ class Uri {
 | 
|      throw new FormatException(message, uri, index);
 | 
|    }
 | 
|  
 | 
| -  /// Internal non-verifying constructor. Only call with validated arguments.
 | 
| -  Uri._internal(this.scheme,
 | 
| -                this._userInfo,
 | 
| -                this._host,
 | 
| -                this._port,
 | 
| -                this._path,
 | 
| -                this._query,
 | 
| -                this._fragment);
 | 
| -
 | 
| -  /**
 | 
| -   * Creates a new URI from its components.
 | 
| -   *
 | 
| -   * Each component is set through a named argument. Any number of
 | 
| -   * components can be provided. The [path] and [query] components can be set
 | 
| -   * using either of two different named arguments.
 | 
| -   *
 | 
| -   * The scheme component is set through [scheme]. The scheme is
 | 
| -   * normalized to all lowercase letters. If the scheme is omitted or empty,
 | 
| -   * the URI will not have a scheme part.
 | 
| -   *
 | 
| -   * The user info part of the authority component is set through
 | 
| -   * [userInfo]. It defaults to the empty string, which will be omitted
 | 
| -   * from the string representation of the URI.
 | 
| -   *
 | 
| -   * The host part of the authority component is set through
 | 
| -   * [host]. The host can either be a hostname, an IPv4 address or an
 | 
| -   * IPv6 address, contained in '[' and ']'. If the host contains a
 | 
| -   * ':' character, the '[' and ']' are added if not already provided.
 | 
| -   * The host is normalized to all lowercase letters.
 | 
| -   *
 | 
| -   * The port part of the authority component is set through
 | 
| -   * [port].
 | 
| -   * If [port] is omitted or `null`, it implies the default port for
 | 
| -   * the URI's scheme, and is equivalent to passing that port explicitly.
 | 
| -   * The recognized schemes, and their default ports, are "http" (80) and
 | 
| -   * "https" (443). All other schemes are considered as having zero as the
 | 
| -   * default port.
 | 
| -   *
 | 
| -   * If any of `userInfo`, `host` or `port` are provided,
 | 
| -   * the URI will have 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,
 | 
| -   * 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
 | 
| -   * 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
 | 
| -   * 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,
 | 
| -   * 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.
 | 
| -   * 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.
 | 
| -   *
 | 
| -   * 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.
 | 
| -   */
 | 
| -  factory Uri({String scheme : "",
 | 
| -               String userInfo : "",
 | 
| -               String host,
 | 
| -               int port,
 | 
| -               String path,
 | 
| -               Iterable<String> pathSegments,
 | 
| -               String query,
 | 
| -               Map<String, String> queryParameters,
 | 
| -               String fragment}) {
 | 
| -    scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
 | 
| -    userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
 | 
| -    host = _makeHost(host, 0, _stringOrNullLength(host), false);
 | 
| -    // Special case this constructor for backwards compatibility.
 | 
| -    if (query == "") query = null;
 | 
| -    query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
 | 
| -    fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
 | 
| -    port = _makePort(port, scheme);
 | 
| -    bool isFile = (scheme == "file");
 | 
| -    if (host == null &&
 | 
| -        (userInfo.isNotEmpty || port != null || isFile)) {
 | 
| -      host = "";
 | 
| -    }
 | 
| -    bool ensureLeadingSlash = host != null;
 | 
| -    path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
 | 
| -                     ensureLeadingSlash, isFile);
 | 
| -    return new Uri._internal(scheme, userInfo, host, port,
 | 
| -                             path, query, fragment);
 | 
| -  }
 | 
| -
 | 
| -  /**
 | 
| -   * Creates a new `http` URI from authority, path and query.
 | 
| -   *
 | 
| -   * Examples:
 | 
| -   *
 | 
| -   * ```
 | 
| -   * // http://example.org/path?q=dart.
 | 
| -   * new Uri.http("google.com", "/search", { "q" : "dart" });
 | 
| -   *
 | 
| -   * // http://user:pass@localhost:8080
 | 
| -   * new Uri.http("user:pass@localhost:8080", "");
 | 
| -   *
 | 
| -   * // http://example.org/a%20b
 | 
| -   * new Uri.http("example.org", "a b");
 | 
| -   *
 | 
| -   * // http://example.org/a%252F
 | 
| -   * new Uri.http("example.org", "/a%2F");
 | 
| -   * ```
 | 
| -   *
 | 
| -   * The `scheme` is always set to `http`.
 | 
| -   *
 | 
| -   * 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
 | 
| -   * as an HTTP URL, which must have a non-empty host.
 | 
| -   *
 | 
| -   * The `path` component is set from the [unencodedPath]
 | 
| -   * argument. The path passed must not be encoded as this constructor
 | 
| -   * encodes the path.
 | 
| -   *
 | 
| -   * The `query` component is set from the optional [queryParameters]
 | 
| -   * argument.
 | 
| -   */
 | 
| -  factory Uri.http(String authority,
 | 
| -                   String unencodedPath,
 | 
| -                   [Map<String, String> queryParameters]) {
 | 
| -    return _makeHttpUri("http", authority, unencodedPath, queryParameters);
 | 
| -  }
 | 
| -
 | 
| -  /**
 | 
| -   * Creates a new `https` URI from authority, path and query.
 | 
| -   *
 | 
| -   * This constructor is the same as [Uri.http] except for the scheme
 | 
| -   * which is set to `https`.
 | 
| -   */
 | 
| -  factory Uri.https(String authority,
 | 
| -                    String unencodedPath,
 | 
| -                    [Map<String, String> queryParameters]) {
 | 
| -    return _makeHttpUri("https", authority, unencodedPath, queryParameters);
 | 
| -  }
 | 
| -
 | 
|    static Uri _makeHttpUri(String scheme,
 | 
|                            String authority,
 | 
|                            String unencodedPath,
 | 
| @@ -694,8 +734,90 @@ class Uri {
 | 
|     * If the path passed is not a legal file path [ArgumentError] is thrown.
 | 
|     */
 | 
|    factory Uri.file(String path, {bool windows}) {
 | 
| -    windows = windows == null ? Uri._isWindows : windows;
 | 
| -    return windows ? _makeWindowsFileUrl(path) : _makeFileUri(path);
 | 
| +    windows = (windows == null) ? Uri._isWindows : windows;
 | 
| +    return windows ? _makeWindowsFileUrl(path, false)
 | 
| +                   : _makeFileUri(path, false);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Like [Uri.file] except that a non-empty URI path ends in a slash.
 | 
| +   *
 | 
| +   * If [path] is not empty, and it doesn't end in a directory separator,
 | 
| +   * then a slash is added to the returned URI's path.
 | 
| +   * In all other cases, the result is the same as returned by `Uri.file`.
 | 
| +   */
 | 
| +  factory Uri.directory(String path, {bool windows}) {
 | 
| +    windows = (windows == null) ? Uri._isWindows : windows;
 | 
| +    return windows ? _makeWindowsFileUrl(path, true)
 | 
| +                   : _makeFileUri(path, true);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a `data:` URI containing the [content] string.
 | 
| +   *
 | 
| +   * Converts the content to a bytes using [encoding] or the charset specified
 | 
| +   * in [parameters] (defaulting to US-ASCII if not specified or unrecognized),
 | 
| +   * then encodes the bytes into the resulting data URI.
 | 
| +   *
 | 
| +   * Defaults to encoding using percent-encoding (any non-ASCII or non-URI-valid
 | 
| +   * bytes is replaced by a percent encoding). If [base64] is true, the bytes
 | 
| +   * are instead encoded using [BASE64].
 | 
| +   *
 | 
| +   * If [encoding] is not provided and [parameters] has a `charset` entry,
 | 
| +   * that name is looked up using [Encoding.getByName],
 | 
| +   * and if the lookup returns an encoding, that encoding is used to convert
 | 
| +   * [content] to bytes.
 | 
| +   * If providing both an [encoding] and a charset [parameter], they should
 | 
| +   * agree, otherwise decoding won't be able to use the charset parameter
 | 
| +   * to determine the encoding.
 | 
| +   *
 | 
| +   * If [mimeType] and/or [parameters] are supplied, they are added to the
 | 
| +   * created URI. If any of these contain characters that are not allowed
 | 
| +   * in the data URI, the character is percent-escaped. If the character is
 | 
| +   * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
 | 
| +   * encoded. An omitted [mimeType] in a data URI means `text/plain`, just
 | 
| +   * as an omitted `charset` parameter defaults to meaning `US-ASCII`.
 | 
| +   *
 | 
| +   * To read the content back, use [UriData.contentAsString].
 | 
| +   */
 | 
| +  factory Uri.dataFromString(String content,
 | 
| +                             {String mimeType,
 | 
| +                              Encoding encoding,
 | 
| +                              Map<String, String> parameters,
 | 
| +                              bool base64: false}) {
 | 
| +    UriData data =  new UriData.fromString(content,
 | 
| +                                           mimeType: mimeType,
 | 
| +                                           encoding: encoding,
 | 
| +                                           parameters: parameters,
 | 
| +                                           base64: base64);
 | 
| +    return data.uri;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a `data:` URI containing an encoding of [bytes].
 | 
| +   *
 | 
| +   * Defaults to Base64 encoding the bytes, but if [percentEncoded]
 | 
| +   * is `true`, the bytes will instead be percent encoded (any non-ASCII
 | 
| +   * or non-valid-ASCII-character byte is replaced by a percent encoding).
 | 
| +   *
 | 
| +   * To read the bytes back, use [UriData.contentAsBytes].
 | 
| +   *
 | 
| +   * It defaults to having the mime-type `application/octet-stream`.
 | 
| +   * The [mimeType] and [parameters] are added to the created URI.
 | 
| +   * If any of these contain characters that are not allowed
 | 
| +   * in the data URI, the character is percent-escaped. If the character is
 | 
| +   * non-ASCII, it is first UTF-8 encoded and then the bytes are percent
 | 
| +   * encoded.
 | 
| +   */
 | 
| +  factory Uri.dataFromBytes(List<int> bytes,
 | 
| +                            {mimeType: "application/octet-stream",
 | 
| +                             Map<String, String> parameters,
 | 
| +                             percentEncoded: false}) {
 | 
| +    UriData data = new UriData.fromBytes(bytes,
 | 
| +                                         mimeType: mimeType,
 | 
| +                                         parameters: parameters,
 | 
| +                                         percentEncoded: percentEncoded);
 | 
| +    return data.uri;
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -727,7 +849,7 @@ class Uri {
 | 
|    static _checkWindowsPathReservedCharacters(List<String> segments,
 | 
|                                               bool argumentError,
 | 
|                                               [int firstSegment = 0]) {
 | 
| -    segments.skip(firstSegment).forEach((segment) {
 | 
| +    for (var segment in segments.skip(firstSegment)) {
 | 
|        if (segment.contains(new RegExp(r'["*/:<>?\\|]'))) {
 | 
|          if (argumentError) {
 | 
|            throw new ArgumentError("Illegal character in path");
 | 
| @@ -735,7 +857,7 @@ class Uri {
 | 
|            throw new UnsupportedError("Illegal character in path");
 | 
|          }
 | 
|        }
 | 
| -    });
 | 
| +    }
 | 
|    }
 | 
|  
 | 
|    static _checkWindowsDriveLetter(int charCode, bool argumentError) {
 | 
| @@ -752,35 +874,41 @@ class Uri {
 | 
|      }
 | 
|    }
 | 
|  
 | 
| -  static _makeFileUri(String path) {
 | 
| -    String sep = "/";
 | 
| -  if (path.startsWith(sep)) {
 | 
| +  static _makeFileUri(String path, bool slashTerminated) {
 | 
| +    const String sep = "/";
 | 
| +    var segments = path.split(sep);
 | 
| +    if (slashTerminated &&
 | 
| +        segments.isNotEmpty &&
 | 
| +        segments.last.isNotEmpty) {
 | 
| +      segments.add("");  // Extra separator at end.
 | 
| +    }
 | 
| +    if (path.startsWith(sep)) {
 | 
|        // Absolute file:// URI.
 | 
| -      return new Uri(scheme: "file", pathSegments: path.split(sep));
 | 
| +      return new Uri(scheme: "file", pathSegments: segments);
 | 
|      } else {
 | 
|        // Relative URI.
 | 
| -      return new Uri(pathSegments: path.split(sep));
 | 
| +      return new Uri(pathSegments: segments);
 | 
|      }
 | 
|    }
 | 
|  
 | 
| -  static _makeWindowsFileUrl(String path) {
 | 
| -    if (path.startsWith("\\\\?\\")) {
 | 
| -      if (path.startsWith("\\\\?\\UNC\\")) {
 | 
| -        path = "\\${path.substring(7)}";
 | 
| +  static _makeWindowsFileUrl(String path, bool slashTerminated) {
 | 
| +    if (path.startsWith(r"\\?\")) {
 | 
| +      if (path.startsWith(r"UNC\", 4)) {
 | 
| +        path = path.replaceRange(0, 7, r'\');
 | 
|        } else {
 | 
|          path = path.substring(4);
 | 
|          if (path.length < 3 ||
 | 
|              path.codeUnitAt(1) != _COLON ||
 | 
|              path.codeUnitAt(2) != _BACKSLASH) {
 | 
|            throw new ArgumentError(
 | 
| -              "Windows paths with \\\\?\\ prefix must be absolute");
 | 
| +              r"Windows paths with \\?\ prefix must be absolute");
 | 
|          }
 | 
|        }
 | 
|      } else {
 | 
| -      path = path.replaceAll("/", "\\");
 | 
| +      path = path.replaceAll("/", r'\');
 | 
|      }
 | 
| -    String sep = "\\";
 | 
| -    if (path.length > 1 && path[1] == ":") {
 | 
| +    const String sep = r'\';
 | 
| +    if (path.length > 1 && path.codeUnitAt(1) == _COLON) {
 | 
|        _checkWindowsDriveLetter(path.codeUnitAt(0), true);
 | 
|        if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
 | 
|          throw new ArgumentError(
 | 
| @@ -788,25 +916,37 @@ class Uri {
 | 
|        }
 | 
|        // Absolute file://C:/ URI.
 | 
|        var pathSegments = path.split(sep);
 | 
| +      if (slashTerminated &&
 | 
| +          pathSegments.last.isNotEmpty) {
 | 
| +        pathSegments.add("");  // Extra separator at end.
 | 
| +      }
 | 
|        _checkWindowsPathReservedCharacters(pathSegments, true, 1);
 | 
|        return new Uri(scheme: "file", pathSegments: pathSegments);
 | 
|      }
 | 
|  
 | 
| -    if (path.length > 0 && path[0] == sep) {
 | 
| -      if (path.length > 1 && path[1] == sep) {
 | 
| +    if (path.startsWith(sep)) {
 | 
| +      if (path.startsWith(sep, 1)) {
 | 
|          // Absolute file:// URI with host.
 | 
| -        int pathStart = path.indexOf("\\", 2);
 | 
| +        int pathStart = path.indexOf(r'\', 2);
 | 
|          String hostPart =
 | 
| -            pathStart == -1 ? path.substring(2) : path.substring(2, pathStart);
 | 
| +            (pathStart < 0) ? path.substring(2) : path.substring(2, pathStart);
 | 
|          String pathPart =
 | 
| -            pathStart == -1 ? "" : path.substring(pathStart + 1);
 | 
| +            (pathStart < 0) ? "" : path.substring(pathStart + 1);
 | 
|          var pathSegments = pathPart.split(sep);
 | 
|          _checkWindowsPathReservedCharacters(pathSegments, true);
 | 
| +        if (slashTerminated &&
 | 
| +            pathSegments.last.isNotEmpty) {
 | 
| +          pathSegments.add("");  // Extra separator at end.
 | 
| +        }
 | 
|          return new Uri(
 | 
|              scheme: "file", host: hostPart, pathSegments: pathSegments);
 | 
|        } else {
 | 
|          // Absolute file:// URI.
 | 
|          var pathSegments = path.split(sep);
 | 
| +        if (slashTerminated &&
 | 
| +            pathSegments.last.isNotEmpty) {
 | 
| +          pathSegments.add("");  // Extra separator at end.
 | 
| +        }
 | 
|          _checkWindowsPathReservedCharacters(pathSegments, true);
 | 
|          return new Uri(scheme: "file", pathSegments: pathSegments);
 | 
|        }
 | 
| @@ -814,6 +954,11 @@ class Uri {
 | 
|        // Relative URI.
 | 
|        var pathSegments = path.split(sep);
 | 
|        _checkWindowsPathReservedCharacters(pathSegments, true);
 | 
| +      if (slashTerminated &&
 | 
| +          pathSegments.isNotEmpty &&
 | 
| +          pathSegments.last.isNotEmpty) {
 | 
| +        pathSegments.add("");  // Extra separator at end.
 | 
| +      }
 | 
|        return new Uri(pathSegments: pathSegments);
 | 
|      }
 | 
|    }
 | 
| @@ -865,14 +1010,14 @@ class Uri {
 | 
|                 String path,
 | 
|                 Iterable<String> pathSegments,
 | 
|                 String query,
 | 
| -               Map<String, String> queryParameters,
 | 
| +               Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
 | 
|                 String fragment}) {
 | 
|      // Set to true if the scheme has (potentially) changed.
 | 
|      // In that case, the default port may also have changed and we need
 | 
|      // to check even the existing port.
 | 
|      bool schemeChanged = false;
 | 
|      if (scheme != null) {
 | 
| -      scheme = _makeScheme(scheme, scheme.length);
 | 
| +      scheme = _makeScheme(scheme, 0, scheme.length);
 | 
|        schemeChanged = true;
 | 
|      } else {
 | 
|        scheme = this.scheme;
 | 
| @@ -881,7 +1026,7 @@ class Uri {
 | 
|      if (userInfo != null) {
 | 
|        userInfo = _makeUserInfo(userInfo, 0, userInfo.length);
 | 
|      } else {
 | 
| -      userInfo = this.userInfo;
 | 
| +      userInfo = this._userInfo;
 | 
|      }
 | 
|      if (port != null) {
 | 
|        port = _makePort(port, scheme);
 | 
| @@ -895,33 +1040,33 @@ class Uri {
 | 
|      if (host != null) {
 | 
|        host = _makeHost(host, 0, host.length, false);
 | 
|      } else if (this.hasAuthority) {
 | 
| -      host = this.host;
 | 
| +      host = this._host;
 | 
|      } else if (userInfo.isNotEmpty || port != null || isFile) {
 | 
|        host = "";
 | 
|      }
 | 
|  
 | 
| -    bool ensureLeadingSlash = (host != null);
 | 
| +    bool hasAuthority = host != null;
 | 
|      if (path != null || pathSegments != null) {
 | 
|        path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
 | 
| -                       ensureLeadingSlash, isFile);
 | 
| +                       scheme, hasAuthority);
 | 
|      } else {
 | 
| -      path = this.path;
 | 
| -      if ((isFile || (ensureLeadingSlash && !path.isEmpty)) &&
 | 
| +      path = this._path;
 | 
| +      if ((isFile || (hasAuthority && !path.isEmpty)) &&
 | 
|            !path.startsWith('/')) {
 | 
| -        path = "/$path";
 | 
| +        path = "/" + path;
 | 
|        }
 | 
|      }
 | 
|  
 | 
|      if (query != null || queryParameters != null) {
 | 
|        query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
 | 
| -    } else if (this.hasQuery) {
 | 
| -      query = this.query;
 | 
| +    } else {
 | 
| +      query = this._query;
 | 
|      }
 | 
|  
 | 
|      if (fragment != null) {
 | 
|        fragment = _makeFragment(fragment, 0, fragment.length);
 | 
| -    } else if (this.hasFragment) {
 | 
| -      fragment = this.fragment;
 | 
| +    } else {
 | 
| +      fragment = this._fragment;
 | 
|      }
 | 
|  
 | 
|      return new Uri._internal(
 | 
| @@ -929,49 +1074,112 @@ class Uri {
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| -   * Returns the URI path split into its segments. Each of the
 | 
| -   * segments in the returned list have been decoded. If the path is
 | 
| -   * empty the empty list will be returned. A leading slash `/` does
 | 
| -   * not affect the segments returned.
 | 
| +   * Returns a `Uri` that differs from this only in not having a fragment.
 | 
| +   *
 | 
| +   * If this `Uri` does not have a fragment, it is itself returned.
 | 
| +   */
 | 
| +  Uri removeFragment() {
 | 
| +    if (!this.hasFragment) return this;
 | 
| +    return new Uri._internal(scheme, _userInfo, _host, _port,
 | 
| +                             _path, _query, null);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Returns the URI path split into its segments. Each of the segments in the
 | 
| +   * returned list have been decoded. If the path is empty the empty list will
 | 
| +   * be returned. A leading slash `/` does not affect the segments returned.
 | 
|     *
 | 
|     * The returned list is unmodifiable and will throw [UnsupportedError] on any
 | 
|     * calls that would mutate it.
 | 
|     */
 | 
|    List<String> get pathSegments {
 | 
| -    if (_pathSegments == null) {
 | 
| -      var pathToSplit = !path.isEmpty && path.codeUnitAt(0) == _SLASH
 | 
| -                        ? path.substring(1)
 | 
| -                        : path;
 | 
| -      _pathSegments = new UnmodifiableListView(
 | 
| -        pathToSplit == "" ? const<String>[]
 | 
| -                          : new List<String>.from(
 | 
| -                              pathToSplit.split("/")
 | 
| -                                         .map(Uri.decodeComponent),
 | 
| -                              growable: false));
 | 
| -    }
 | 
| -    return _pathSegments;
 | 
| +    var result = _pathSegments;
 | 
| +    if (result != null) return result;
 | 
| +
 | 
| +    var pathToSplit = path;
 | 
| +    if (pathToSplit.isNotEmpty && pathToSplit.codeUnitAt(0) == _SLASH) {
 | 
| +      pathToSplit = pathToSplit.substring(1);
 | 
| +    }
 | 
| +    result = (pathToSplit == "")
 | 
| +        ? const<String>[]
 | 
| +        : new List<String>.unmodifiable(
 | 
| +              pathToSplit.split("/").map(Uri.decodeComponent));
 | 
| +    _pathSegments = result;
 | 
| +    return result;
 | 
|    }
 | 
|  
 | 
|    /**
 | 
|     * 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.
 | 
| +   * 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 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 `..`
 | 
| +   * segments.
 | 
| +   * Only a relative path with no scheme or authority may contain
 | 
| +   * leading `..` segments,
 | 
| +   * a path that starts with `/` will also drop any leading `..` segments.
 | 
| +   *
 | 
| +   * This uses the same normalization strategy as `new Uri().resolve(this)`.
 | 
| +   *
 | 
| +   * Does not change any part of the URI except the path.
 | 
| +   *
 | 
| +   * The default implementation of `Uri` always normalizes paths, so calling
 | 
| +   * this function has no effect.
 | 
| +   */
 | 
| +  Uri normalizePath() {
 | 
| +    String path = _normalizePath(_path, scheme, hasAuthority);
 | 
| +    if (identical(path, _path)) return this;
 | 
| +    return this.replace(path: path);
 | 
| +  }
 | 
| +
 | 
|    static int _makePort(int port, String scheme) {
 | 
|      // Perform scheme specific normalization.
 | 
|      if (port != null && port == _defaultPort(scheme)) return null;
 | 
| @@ -979,7 +1187,7 @@ class Uri {
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| -   * Check and normalize a most name.
 | 
| +   * Check and normalize a host name.
 | 
|     *
 | 
|     * If the host name starts and ends with '[' and ']', it is considered an
 | 
|     * IPv6 address. If [strictIPv6] is false, the address is also considered
 | 
| @@ -1101,24 +1309,24 @@ class Uri {
 | 
|     *
 | 
|     * Schemes are converted to lower case. They cannot contain escapes.
 | 
|     */
 | 
| -  static String _makeScheme(String scheme, int end) {
 | 
| -    if (end == 0) return "";
 | 
| -    final int firstCodeUnit = scheme.codeUnitAt(0);
 | 
| +  static String _makeScheme(String scheme, int start, int end) {
 | 
| +    if (start == end) return "";
 | 
| +    final int firstCodeUnit = scheme.codeUnitAt(start);
 | 
|      if (!_isAlphabeticCharacter(firstCodeUnit)) {
 | 
| -      _fail(scheme, 0, "Scheme not starting with alphabetic character");
 | 
| +      _fail(scheme, start, "Scheme not starting with alphabetic character");
 | 
|      }
 | 
| -    bool allLowercase = firstCodeUnit >= _LOWER_CASE_A;
 | 
| -    for (int i = 0; i < end; i++) {
 | 
| +    bool containsUpperCase = false;
 | 
| +    for (int i = start; i < end; i++) {
 | 
|        final int codeUnit = scheme.codeUnitAt(i);
 | 
|        if (!_isSchemeCharacter(codeUnit)) {
 | 
|          _fail(scheme, i, "Illegal scheme character");
 | 
|        }
 | 
| -      if (codeUnit < _LOWER_CASE_A || codeUnit > _LOWER_CASE_Z) {
 | 
| -        allLowercase = false;
 | 
| +      if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
 | 
| +        containsUpperCase = true;
 | 
|        }
 | 
|      }
 | 
| -    scheme = scheme.substring(0, end);
 | 
| -    if (!allLowercase) scheme = scheme.toLowerCase();
 | 
| +    scheme = scheme.substring(start, end);
 | 
| +    if (containsUpperCase) scheme = scheme.toLowerCase();
 | 
|      return scheme;
 | 
|    }
 | 
|  
 | 
| @@ -1129,8 +1337,10 @@ class Uri {
 | 
|  
 | 
|    static String _makePath(String path, int start, int end,
 | 
|                            Iterable<String> pathSegments,
 | 
| -                          bool ensureLeadingSlash,
 | 
| -                          bool isFile) {
 | 
| +                          String scheme,
 | 
| +                          bool hasAuthority) {
 | 
| +    bool isFile = (scheme == "file");
 | 
| +    bool ensureLeadingSlash = isFile || hasAuthority;
 | 
|      if (path == null && pathSegments == null) return isFile ? "/" : "";
 | 
|      if (path != null && pathSegments != null) {
 | 
|        throw new ArgumentError('Both path and pathSegments specified');
 | 
| @@ -1139,19 +1349,33 @@ class Uri {
 | 
|      if (path != null) {
 | 
|        result = _normalize(path, start, end, _pathCharOrSlashTable);
 | 
|      } else {
 | 
| -      result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
 | 
| +      result = pathSegments.map((s) =>
 | 
| +          _uriEncode(_pathCharTable, s, UTF8, false)).join("/");
 | 
|      }
 | 
|      if (result.isEmpty) {
 | 
|        if (isFile) return "/";
 | 
| -    } else if ((isFile || ensureLeadingSlash) &&
 | 
| -               result.codeUnitAt(0) != _SLASH) {
 | 
| -      return "/$result";
 | 
| +    } else if (ensureLeadingSlash && !result.startsWith('/')) {
 | 
| +      result = "/" + result;
 | 
|      }
 | 
| +    result = _normalizePath(result, scheme, hasAuthority);
 | 
|      return result;
 | 
|    }
 | 
|  
 | 
| -  static String _makeQuery(String query, int start, int end,
 | 
| -                           Map<String, String> queryParameters) {
 | 
| +  /// Performs path normalization (remove dot segments) on a path.
 | 
| +  ///
 | 
| +  /// If the URI has neither scheme nor authority, it's considered a
 | 
| +  /// "pure path" and normalization won't remove leading ".." segments.
 | 
| +  /// Otherwise it follows the RFC 3986 "remove dot segments" algorithm.
 | 
| +  static String _normalizePath(String path, String scheme, bool hasAuthority) {
 | 
| +    if (scheme.isEmpty && !hasAuthority && !path.startsWith('/')) {
 | 
| +      return _normalizeRelativePath(path);
 | 
| +    }
 | 
| +    return _removeDotSegments(path);
 | 
| +  }
 | 
| +
 | 
| +  static String _makeQuery(
 | 
| +      String query, int start, int end,
 | 
| +      Map<String, dynamic/*String|Iterable<String>*/> queryParameters) {
 | 
|      if (query == null && queryParameters == null) return null;
 | 
|      if (query != null && queryParameters != null) {
 | 
|        throw new ArgumentError('Both query and queryParameters specified');
 | 
| @@ -1159,17 +1383,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();
 | 
|    }
 | 
| @@ -1179,20 +1413,7 @@ class Uri {
 | 
|      return _normalize(fragment, start, end, _queryCharTable);
 | 
|    }
 | 
|  
 | 
| -  static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
 | 
| -
 | 
| -  static bool _isHexDigit(int char) {
 | 
| -    if (_NINE >= char) return _ZERO <= char;
 | 
| -    char |= 0x20;
 | 
| -    return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
 | 
| -  }
 | 
| -
 | 
| -  static int _hexValue(int char) {
 | 
| -    assert(_isHexDigit(char));
 | 
| -    if (_NINE >= char) return char - _ZERO;
 | 
| -    char |= 0x20;
 | 
| -    return char - (_LOWER_CASE_A - 10);
 | 
| -  }
 | 
| +  static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
 | 
|  
 | 
|    /**
 | 
|     * Performs RFC 3986 Percent-Encoding Normalization.
 | 
| @@ -1214,10 +1435,12 @@ class Uri {
 | 
|      }
 | 
|      int firstDigit = source.codeUnitAt(index + 1);
 | 
|      int secondDigit = source.codeUnitAt(index + 2);
 | 
| -    if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
 | 
| +    int firstDigitValue = _parseHexDigit(firstDigit);
 | 
| +    int secondDigitValue = _parseHexDigit(secondDigit);
 | 
| +    if (firstDigitValue < 0 || secondDigitValue < 0) {
 | 
|        return "%";  // Marks the escape as invalid.
 | 
|      }
 | 
| -    int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit);
 | 
| +    int value = firstDigitValue * 16 + secondDigitValue;
 | 
|      if (_isUnreservedChar(value)) {
 | 
|        if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
 | 
|          value |= 0x20;
 | 
| @@ -1233,21 +1456,27 @@ class Uri {
 | 
|      return null;
 | 
|    }
 | 
|  
 | 
| -  static bool _isUnreservedChar(int ch) {
 | 
| -    return ch < 127 &&
 | 
| -           ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
 | 
| +  // Converts a UTF-16 code-unit to its value as a hex digit.
 | 
| +  // Returns -1 for non-hex digits.
 | 
| +  static int _parseHexDigit(int char) {
 | 
| +    int digit = char ^ Uri._ZERO;
 | 
| +    if (digit <= 9) return digit;
 | 
| +    int lowerCase = char | 0x20;
 | 
| +    if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
 | 
| +      return lowerCase - (_LOWER_CASE_A - 10);
 | 
| +    }
 | 
| +    return -1;
 | 
|    }
 | 
|  
 | 
| -  static String _escapeChar(char) {
 | 
| +  static String _escapeChar(int char) {
 | 
|      assert(char <= 0x10ffff);  // It's a valid unicode code point.
 | 
| -    const hexDigits = "0123456789ABCDEF";
 | 
| -    List codeUnits;
 | 
| +    List<int> codeUnits;
 | 
|      if (char < 0x80) {
 | 
|        // ASCII, a single percent encoded sequence.
 | 
|        codeUnits = new List(3);
 | 
|        codeUnits[0] = _PERCENT;
 | 
| -      codeUnits[1] = hexDigits.codeUnitAt(char >> 4);
 | 
| -      codeUnits[2] = hexDigits.codeUnitAt(char & 0xf);
 | 
| +      codeUnits[1] = _hexDigits.codeUnitAt(char >> 4);
 | 
| +      codeUnits[2] = _hexDigits.codeUnitAt(char & 0xf);
 | 
|      } else {
 | 
|        // Do UTF-8 encoding of character, then percent encode bytes.
 | 
|        int flag = 0xc0;  // The high-bit markers on the first byte of UTF-8.
 | 
| @@ -1265,8 +1494,8 @@ class Uri {
 | 
|        while (--encodedBytes >= 0) {
 | 
|          int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
 | 
|          codeUnits[index] = _PERCENT;
 | 
| -        codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4);
 | 
| -        codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf);
 | 
| +        codeUnits[index + 1] = _hexDigits.codeUnitAt(byte >> 4);
 | 
| +        codeUnits[index + 2] = _hexDigits.codeUnitAt(byte & 0xf);
 | 
|          index += 3;
 | 
|          flag = 0x80;  // Following bytes have only high bit set.
 | 
|        }
 | 
| @@ -1358,8 +1587,7 @@ class Uri {
 | 
|     */
 | 
|    bool get isAbsolute => scheme != "" && fragment == "";
 | 
|  
 | 
| -  String _merge(String base, String reference) {
 | 
| -    if (base.isEmpty) return "/$reference";
 | 
| +  String _mergePaths(String base, String reference) {
 | 
|      // Optimize for the case: absolute base, reference beginning with "../".
 | 
|      int backCount = 0;
 | 
|      int refStart = 0;
 | 
| @@ -1388,25 +1616,40 @@ class Uri {
 | 
|        baseEnd = newEnd;
 | 
|        backCount--;
 | 
|      }
 | 
| -    return base.substring(0, baseEnd + 1) +
 | 
| -           reference.substring(refStart - 3 * backCount);
 | 
| +    return base.replaceRange(baseEnd + 1, null,
 | 
| +                             reference.substring(refStart - 3 * backCount));
 | 
|    }
 | 
|  
 | 
| -  bool _hasDotSegments(String path) {
 | 
| -    if (path.length > 0 && path.codeUnitAt(0) == _DOT) return true;
 | 
| +  /// Make a guess at whether a path contains a `..` or `.` segment.
 | 
| +  ///
 | 
| +  /// This is a primitive test that can cause false positives.
 | 
| +  /// It's only used to avoid a more expensive operation in the case where
 | 
| +  /// it's not necessary.
 | 
| +  static bool _mayContainDotSegments(String path) {
 | 
| +    if (path.startsWith('.')) return true;
 | 
|      int index = path.indexOf("/.");
 | 
|      return index != -1;
 | 
|    }
 | 
|  
 | 
| -  String _removeDotSegments(String path) {
 | 
| -    if (!_hasDotSegments(path)) return path;
 | 
| +  /// Removes '.' and '..' segments from a path.
 | 
| +  ///
 | 
| +  /// Follows the RFC 2986 "remove dot segments" algorithm.
 | 
| +  /// This algorithm is only used on paths of URIs with a scheme,
 | 
| +  /// and it treats the path as if it is absolute (leading '..' are removed).
 | 
| +  static String _removeDotSegments(String path) {
 | 
| +    if (!_mayContainDotSegments(path)) return path;
 | 
| +    assert(path.isNotEmpty);  // An empty path would not have dot segments.
 | 
|      List<String> output = [];
 | 
|      bool appendSlash = false;
 | 
|      for (String segment in path.split("/")) {
 | 
|        appendSlash = false;
 | 
|        if (segment == "..") {
 | 
| -        if (!output.isEmpty &&
 | 
| -            ((output.length != 1) || (output[0] != ""))) output.removeLast();
 | 
| +        if (output.isNotEmpty) {
 | 
| +          output.removeLast();
 | 
| +          if (output.isEmpty) {
 | 
| +            output.add("");
 | 
| +          }
 | 
| +        }
 | 
|          appendSlash = true;
 | 
|        } else if ("." == segment) {
 | 
|          appendSlash = true;
 | 
| @@ -1418,6 +1661,42 @@ class Uri {
 | 
|      return output.join("/");
 | 
|    }
 | 
|  
 | 
| +  /// Removes all `.` segments and any non-leading `..` segments.
 | 
| +  ///
 | 
| +  /// Removing the ".." from a "bar/foo/.." sequence results in "bar/"
 | 
| +  /// (trailing "/"). If the entire path is removed (because it contains as
 | 
| +  /// many ".." segments as real segments), the result is "./".
 | 
| +  /// This is different from an empty string, which represents "no path",
 | 
| +  /// when you resolve it against a base URI with a path with a non-empty
 | 
| +  /// final segment.
 | 
| +  static String _normalizeRelativePath(String path) {
 | 
| +    assert(!path.startsWith('/'));  // Only get called for relative paths.
 | 
| +    if (!_mayContainDotSegments(path)) return path;
 | 
| +    assert(path.isNotEmpty);  // An empty path would not have dot segments.
 | 
| +    List<String> output = [];
 | 
| +    bool appendSlash = false;
 | 
| +    for (String segment in path.split("/")) {
 | 
| +      appendSlash = false;
 | 
| +      if (".." == segment) {
 | 
| +        if (!output.isEmpty && output.last != "..") {
 | 
| +          output.removeLast();
 | 
| +          appendSlash = true;
 | 
| +        } else {
 | 
| +          output.add("..");
 | 
| +        }
 | 
| +      } else if ("." == segment) {
 | 
| +        appendSlash = true;
 | 
| +      } else {
 | 
| +        output.add(segment);
 | 
| +      }
 | 
| +    }
 | 
| +    if (output.isEmpty || (output.length == 1 && output[0].isEmpty)) {
 | 
| +      return "./";
 | 
| +    }
 | 
| +    if (appendSlash || output.last == '..') output.add("");
 | 
| +    return output.join("/");
 | 
| +  }
 | 
| +
 | 
|    /**
 | 
|     * Resolve [reference] as an URI relative to `this`.
 | 
|     *
 | 
| @@ -1437,9 +1716,14 @@ class Uri {
 | 
|     *
 | 
|     * Returns the resolved URI.
 | 
|     *
 | 
| -   * The algorithm for resolving a reference is described in
 | 
| -   * [RFC-3986 Section 5]
 | 
| -   * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
 | 
| +   * The algorithm "Transform Reference" for resolving a reference is described
 | 
| +   * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
 | 
| +   *
 | 
| +   * Updated to handle the case where the base URI is just a relative path -
 | 
| +   * that is: when it has no scheme or authority and the path does not start
 | 
| +   * with a slash.
 | 
| +   * In that case, the paths are combined without removing leading "..", and
 | 
| +   * an empty path is not converted to "/".
 | 
|     */
 | 
|    Uri resolveUri(Uri reference) {
 | 
|      // From RFC 3986.
 | 
| @@ -1470,6 +1754,9 @@ class Uri {
 | 
|          targetPath = _removeDotSegments(reference.path);
 | 
|          if (reference.hasQuery) targetQuery = reference.query;
 | 
|        } else {
 | 
| +        targetUserInfo = this._userInfo;
 | 
| +        targetHost = this._host;
 | 
| +        targetPort = this._port;
 | 
|          if (reference.path == "") {
 | 
|            targetPath = this._path;
 | 
|            if (reference.hasQuery) {
 | 
| @@ -1478,16 +1765,32 @@ class Uri {
 | 
|              targetQuery = this._query;
 | 
|            }
 | 
|          } else {
 | 
| -          if (reference.path.startsWith("/")) {
 | 
| +          if (reference.hasAbsolutePath) {
 | 
|              targetPath = _removeDotSegments(reference.path);
 | 
|            } else {
 | 
| -            targetPath = _removeDotSegments(_merge(this._path, reference.path));
 | 
| +            // This is the RFC 3986 behavior for merging.
 | 
| +            if (this.hasEmptyPath) {
 | 
| +              if (!this.hasScheme && !this.hasAuthority) {
 | 
| +                // Keep the path relative if no scheme or authority.
 | 
| +                targetPath = reference.path;
 | 
| +              } else {
 | 
| +                // Add path normalization on top of RFC algorithm.
 | 
| +                targetPath = _removeDotSegments("/" + reference.path);
 | 
| +              }
 | 
| +            } else {
 | 
| +              var mergedPath = _mergePaths(this._path, reference.path);
 | 
| +              if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
 | 
| +                targetPath = _removeDotSegments(mergedPath);
 | 
| +              } else {
 | 
| +                // Non-RFC 3986 beavior. If both base and reference are relative
 | 
| +                // path, allow the merged path to start with "..".
 | 
| +                // The RFC only specifies the case where the base has a scheme.
 | 
| +                targetPath = _normalizeRelativePath(mergedPath);
 | 
| +              }
 | 
| +            }
 | 
|            }
 | 
|            if (reference.hasQuery) targetQuery = reference.query;
 | 
|          }
 | 
| -        targetUserInfo = this._userInfo;
 | 
| -        targetHost = this._host;
 | 
| -        targetPort = this._port;
 | 
|        }
 | 
|      }
 | 
|      String fragment = reference.hasFragment ? reference.fragment : null;
 | 
| @@ -1501,6 +1804,11 @@ class Uri {
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| +   * Returns whether the URI has a [scheme] component.
 | 
| +   */
 | 
| +  bool get hasScheme => scheme.isNotEmpty;
 | 
| +
 | 
| +  /**
 | 
|     * Returns whether the URI has an [authority] component.
 | 
|     */
 | 
|    bool get hasAuthority => _host != null;
 | 
| @@ -1526,6 +1834,16 @@ class Uri {
 | 
|    bool get hasFragment => _fragment != null;
 | 
|  
 | 
|    /**
 | 
| +   * Returns whether the URI has an empty path.
 | 
| +   */
 | 
| +  bool get hasEmptyPath => _path.isEmpty;
 | 
| +
 | 
| +  /**
 | 
| +   * Returns whether the URI has an absolute path (starting with '/').
 | 
| +   */
 | 
| +  bool get hasAbsolutePath => _path.startsWith('/');
 | 
| +
 | 
| +  /**
 | 
|     * Returns the origin of the URI in the form scheme://host:port for the
 | 
|     * schemes http and https.
 | 
|     *
 | 
| @@ -1679,6 +1997,16 @@ class Uri {
 | 
|      }
 | 
|    }
 | 
|  
 | 
| +  /**
 | 
| +   * Access the structure of a `data:` URI.
 | 
| +   *
 | 
| +   * Returns a [UriData] object for `data:` URIs and `null` for all other
 | 
| +   * URIs.
 | 
| +   * The [UriData] object can be used to access the media type and data
 | 
| +   * of a `data:` URI.
 | 
| +   */
 | 
| +  UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
 | 
| +
 | 
|    String toString() {
 | 
|      StringBuffer sb = new StringBuffer();
 | 
|      _addIfNonEmpty(sb, scheme, scheme, ':');
 | 
| @@ -1697,16 +2025,16 @@ class Uri {
 | 
|    bool operator==(other) {
 | 
|      if (other is! Uri) return false;
 | 
|      Uri uri = other;
 | 
| -    return scheme == uri.scheme &&
 | 
| -        hasAuthority == uri.hasAuthority &&
 | 
| -        userInfo == uri.userInfo &&
 | 
| -        host == uri.host &&
 | 
| -        port == uri.port &&
 | 
| -        path == uri.path &&
 | 
| -        hasQuery == uri.hasQuery &&
 | 
| -        query == uri.query &&
 | 
| -        hasFragment == uri.hasFragment &&
 | 
| -        fragment == uri.fragment;
 | 
| +    return scheme       == uri.scheme       &&
 | 
| +           hasAuthority == uri.hasAuthority &&
 | 
| +           userInfo     == uri.userInfo     &&
 | 
| +           host         == uri.host         &&
 | 
| +           port         == uri.port         &&
 | 
| +           path         == uri.path         &&
 | 
| +           hasQuery     == uri.hasQuery     &&
 | 
| +           query        == uri.query        &&
 | 
| +           hasFragment  == uri.hasFragment  &&
 | 
| +           fragment     == uri.fragment;
 | 
|    }
 | 
|  
 | 
|    int get hashCode {
 | 
| @@ -1747,7 +2075,7 @@ class Uri {
 | 
|     * a [Uri].
 | 
|     */
 | 
|    static String encodeComponent(String component) {
 | 
| -    return _uriEncode(_unreserved2396Table, component);
 | 
| +    return _uriEncode(_unreserved2396Table, component, UTF8, false);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -1785,8 +2113,7 @@ class Uri {
 | 
|     */
 | 
|    static String encodeQueryComponent(String component,
 | 
|                                       {Encoding encoding: UTF8}) {
 | 
| -    return _uriEncode(
 | 
| -        _unreservedTable, component, encoding: encoding, spaceToPlus: true);
 | 
| +    return _uriEncode(_unreservedTable, component, encoding, true);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -1803,7 +2130,8 @@ class Uri {
 | 
|     * decoded component.
 | 
|     */
 | 
|    static String decodeComponent(String encodedComponent) {
 | 
| -    return _uriDecode(encodedComponent);
 | 
| +    return _uriDecode(encodedComponent, 0, encodedComponent.length,
 | 
| +                      UTF8, false);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -1817,7 +2145,8 @@ class Uri {
 | 
|    static String decodeQueryComponent(
 | 
|        String encodedComponent,
 | 
|        {Encoding encoding: UTF8}) {
 | 
| -    return _uriDecode(encodedComponent, plusToSpace: true, encoding: encoding);
 | 
| +    return _uriDecode(encodedComponent, 0, encodedComponent.length,
 | 
| +                      encoding, true);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -1830,7 +2159,7 @@ class Uri {
 | 
|     * the encodeURI function .
 | 
|     */
 | 
|    static String encodeFull(String uri) {
 | 
| -    return _uriEncode(_encodeFullTable, uri);
 | 
| +    return _uriEncode(_encodeFullTable, uri, UTF8, false);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -1842,16 +2171,14 @@ class Uri {
 | 
|     * [Uri.parse] before decoding the separate components.
 | 
|     */
 | 
|    static String decodeFull(String uri) {
 | 
| -    return _uriDecode(uri);
 | 
| +    return _uriDecode(uri, 0, uri.length, UTF8, false);
 | 
|    }
 | 
|  
 | 
|    /**
 | 
|     * Returns the [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 the [query]
 | 
| +   * 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 the [query]
 | 
|     * is the empty string an empty map is returned.
 | 
|     *
 | 
|     * Keys in the query string that have no value are mapped to the
 | 
| @@ -1878,6 +2205,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).
 | 
| @@ -1999,8 +2366,7 @@ class Uri {
 | 
|      } else if (parts.length != 8) {
 | 
|        error('an address without a wildcard must contain exactly 8 parts');
 | 
|      }
 | 
| -    // TODO(ajohnsen): Consider using Uint8List.
 | 
| -    List bytes = new List<int>(16);
 | 
| +    List<int> bytes = new Uint8List(16);
 | 
|      for (int i = 0, index = 0; i < parts.length; i++) {
 | 
|        int value = parts[i];
 | 
|        if (value == -1) {
 | 
| @@ -2046,39 +2412,12 @@ class Uri {
 | 
|    static const int _LOWER_CASE_Z = 0x7A;
 | 
|    static const int _BAR = 0x7C;
 | 
|  
 | 
| -  /**
 | 
| -   * This is the internal implementation of JavaScript's encodeURI function.
 | 
| -   * It encodes all characters in the string [text] except for those
 | 
| -   * that appear in [canonicalTable], and returns the escaped string.
 | 
| -   */
 | 
| -  static String _uriEncode(List<int> canonicalTable,
 | 
| -                           String text,
 | 
| -                           {Encoding encoding: UTF8,
 | 
| -                            bool spaceToPlus: false}) {
 | 
| -    byteToHex(byte, buffer) {
 | 
| -      const String hex = '0123456789ABCDEF';
 | 
| -      buffer.writeCharCode(hex.codeUnitAt(byte >> 4));
 | 
| -      buffer.writeCharCode(hex.codeUnitAt(byte & 0x0f));
 | 
| -    }
 | 
| +  static const String _hexDigits = "0123456789ABCDEF";
 | 
|  
 | 
| -    // Encode the string into bytes then generate an ASCII only string
 | 
| -    // by percent encoding selected bytes.
 | 
| -    StringBuffer result = new StringBuffer();
 | 
| -    var bytes = encoding.encode(text);
 | 
| -    for (int i = 0; i < bytes.length; i++) {
 | 
| -      int byte = bytes[i];
 | 
| -      if (byte < 128 &&
 | 
| -          ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
 | 
| -        result.writeCharCode(byte);
 | 
| -      } else if (spaceToPlus && byte == _SPACE) {
 | 
| -        result.writeCharCode(_PLUS);
 | 
| -      } else {
 | 
| -        result.writeCharCode(_PERCENT);
 | 
| -        byteToHex(byte, result);
 | 
| -      }
 | 
| -    }
 | 
| -    return result.toString();
 | 
| -  }
 | 
| +  external static String _uriEncode(List<int> canonicalTable,
 | 
| +                                    String text,
 | 
| +                                    Encoding encoding,
 | 
| +                                    bool spaceToPlus);
 | 
|  
 | 
|    /**
 | 
|     * Convert a byte (2 character hex sequence) in string [s] starting
 | 
| @@ -2116,24 +2455,35 @@ class Uri {
 | 
|     * decode the byte-list using [encoding]. The default encodingis UTF-8.
 | 
|     */
 | 
|    static String _uriDecode(String text,
 | 
| -                           {bool plusToSpace: false,
 | 
| -                            Encoding encoding: UTF8}) {
 | 
| +                           int start,
 | 
| +                           int end,
 | 
| +                           Encoding encoding,
 | 
| +                           bool plusToSpace) {
 | 
| +    assert(0 <= start);
 | 
| +    assert(start <= end);
 | 
| +    assert(end <= text.length);
 | 
| +    assert(encoding != null);
 | 
|      // First check whether there is any characters which need special handling.
 | 
|      bool simple = true;
 | 
| -    for (int i = 0; i < text.length && simple; i++) {
 | 
| +    for (int i = start; i < end; i++) {
 | 
|        var codeUnit = text.codeUnitAt(i);
 | 
| -      simple = codeUnit != _PERCENT && codeUnit != _PLUS;
 | 
| +      if (codeUnit > 127 ||
 | 
| +          codeUnit == _PERCENT ||
 | 
| +          (plusToSpace && codeUnit == _PLUS)) {
 | 
| +        simple = false;
 | 
| +        break;
 | 
| +      }
 | 
|      }
 | 
|      List<int> bytes;
 | 
|      if (simple) {
 | 
| -      if (encoding == UTF8 || encoding == LATIN1) {
 | 
| -        return text;
 | 
| +      if (UTF8 == encoding || LATIN1 == encoding || ASCII == encoding) {
 | 
| +        return text.substring(start, end);
 | 
|        } else {
 | 
| -        bytes = text.codeUnits;
 | 
| +        bytes = text.substring(start, end).codeUnits;
 | 
|        }
 | 
|      } else {
 | 
|        bytes = new List();
 | 
| -      for (int i = 0; i < text.length; i++) {
 | 
| +      for (int i = start; i < end; i++) {
 | 
|          var codeUnit = text.codeUnitAt(i);
 | 
|          if (codeUnit > 127) {
 | 
|            throw new ArgumentError("Illegal percent encoding in URI");
 | 
| @@ -2154,9 +2504,15 @@ class Uri {
 | 
|      return encoding.decode(bytes);
 | 
|    }
 | 
|  
 | 
| -  static bool _isAlphabeticCharacter(int codeUnit)
 | 
| -    => (codeUnit >= _LOWER_CASE_A && codeUnit <= _LOWER_CASE_Z) ||
 | 
| -       (codeUnit >= _UPPER_CASE_A && codeUnit <= _UPPER_CASE_Z);
 | 
| +  static bool _isAlphabeticCharacter(int codeUnit) {
 | 
| +    var lowerCase = codeUnit | 0x20;
 | 
| +    return (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_Z);
 | 
| +  }
 | 
| +
 | 
| +  static bool _isUnreservedChar(int char) {
 | 
| +    return char < 127 &&
 | 
| +           ((_unreservedTable[char >> 4] & (1 << (char & 0x0f))) != 0);
 | 
| +  }
 | 
|  
 | 
|    // Tables of char-codes organized as a bit vector of 128 bits where
 | 
|    // each bit indicate whether a character code on the 0-127 needs to
 | 
| @@ -2405,4 +2761,594 @@ class Uri {
 | 
|        0xfffe,   // 0x60 - 0x6f  0111111111111111
 | 
|                  //              pqrstuvwxyz   ~
 | 
|        0x47ff];  // 0x70 - 0x7f  1111111111100010
 | 
| +
 | 
| +}
 | 
| +
 | 
| +// --------------------------------------------------------------------
 | 
| +// Data URI
 | 
| +// --------------------------------------------------------------------
 | 
| +
 | 
| +/**
 | 
| + * A way to access the structure of a `data:` URI.
 | 
| + *
 | 
| + * Data URIs are non-hierarchical URIs that can contain any binary data.
 | 
| + * They are defined by [RFC 2397](https://tools.ietf.org/html/rfc2397).
 | 
| + *
 | 
| + * This class allows parsing the URI text and extracting individual parts of the
 | 
| + * URI, as well as building the URI text from structured parts.
 | 
| + */
 | 
| +class UriData {
 | 
| +  static const int _noScheme = -1;
 | 
| +  /**
 | 
| +   * Contains the text content of a `data:` URI, with or without a
 | 
| +   * leading `data:`.
 | 
| +   *
 | 
| +   * If [_separatorIndices] starts with `4` (the index of the `:`), then
 | 
| +   * there is a leading `data:`, otherwise [_separatorIndices] starts with
 | 
| +   * `-1`.
 | 
| +   */
 | 
| +  final String _text;
 | 
| +
 | 
| +  /**
 | 
| +   * List of the separators (';', '=' and ',') in the text.
 | 
| +   *
 | 
| +   * Starts with the index of the `:` in `data:` of the mimeType.
 | 
| +   * That is always either -1 or 4, depending on whether `_text` includes the
 | 
| +   * `data:` scheme or not.
 | 
| +   *
 | 
| +   * The first speparator ends the mime type. We don't bother with finding
 | 
| +   * the '/' inside the mime type.
 | 
| +   *
 | 
| +   * Each two separators after that marks a parameter key and value.
 | 
| +   *
 | 
| +   * If there is a single separator left, it ends the "base64" marker.
 | 
| +   *
 | 
| +   * So the following separators are found for a text:
 | 
| +   *
 | 
| +   *     data:text/plain;foo=bar;base64,ARGLEBARGLE=
 | 
| +   *         ^          ^   ^   ^      ^
 | 
| +   *
 | 
| +   */
 | 
| +  final List<int> _separatorIndices;
 | 
| +
 | 
| +  /**
 | 
| +   * Cache of the result returned by [uri].
 | 
| +   */
 | 
| +  Uri _uriCache;
 | 
| +
 | 
| +  UriData._(this._text, this._separatorIndices, this._uriCache);
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a `data:` URI containing the [content] string.
 | 
| +   *
 | 
| +   * Equivalent to `new Uri.dataFromString(...).data`, but may
 | 
| +   * be more efficient if the [uri] itself isn't used.
 | 
| +   */
 | 
| +  factory UriData.fromString(String content,
 | 
| +                             {String mimeType,
 | 
| +                              Encoding encoding,
 | 
| +                              Map<String, String> parameters,
 | 
| +                              bool base64: false}) {
 | 
| +    StringBuffer buffer = new StringBuffer();
 | 
| +    List<int> indices = [_noScheme];
 | 
| +    String charsetName;
 | 
| +    String encodingName;
 | 
| +    if (parameters != null) charsetName = parameters["charset"];
 | 
| +    if (encoding == null) {
 | 
| +      if (charsetName != null) {
 | 
| +        encoding = Encoding.getByName(charsetName);
 | 
| +      }
 | 
| +    } else if (charsetName == null) {
 | 
| +      // Non-null only if parameters does not contain "charset".
 | 
| +      encodingName = encoding.name;
 | 
| +    }
 | 
| +    encoding ??= ASCII;
 | 
| +    _writeUri(mimeType, encodingName, parameters, buffer, indices);
 | 
| +    indices.add(buffer.length);
 | 
| +    if (base64) {
 | 
| +      buffer.write(';base64,');
 | 
| +      indices.add(buffer.length - 1);
 | 
| +      buffer.write(encoding.fuse(BASE64).encode(content));
 | 
| +    } else {
 | 
| +      buffer.write(',');
 | 
| +      _uriEncodeBytes(_uricTable, encoding.encode(content), buffer);
 | 
| +    }
 | 
| +    return new UriData._(buffer.toString(), indices, null);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a `data:` URI containing an encoding of [bytes].
 | 
| +   *
 | 
| +   * Equivalent to `new Uri.dataFromBytes(...).data`, but may
 | 
| +   * be more efficient if the [uri] itself isn't used.
 | 
| +   */
 | 
| +  factory UriData.fromBytes(List<int> bytes,
 | 
| +                            {mimeType: "application/octet-stream",
 | 
| +                             Map<String, String> parameters,
 | 
| +                             percentEncoded: false}) {
 | 
| +    StringBuffer buffer = new StringBuffer();
 | 
| +    List<int> indices = [_noScheme];
 | 
| +    _writeUri(mimeType, null, parameters, buffer, indices);
 | 
| +    indices.add(buffer.length);
 | 
| +    if (percentEncoded) {
 | 
| +      buffer.write(',');
 | 
| +      _uriEncodeBytes(_uricTable, bytes, buffer);
 | 
| +    } else {
 | 
| +      buffer.write(';base64,');
 | 
| +      indices.add(buffer.length - 1);
 | 
| +      BASE64.encoder
 | 
| +            .startChunkedConversion(
 | 
| +                new StringConversionSink.fromStringSink(buffer))
 | 
| +            .addSlice(bytes, 0, bytes.length, true);
 | 
| +    }
 | 
| +
 | 
| +    return new UriData._(buffer.toString(), indices, null);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Creates a `DataUri` from a [Uri] which must have `data` as [Uri.scheme].
 | 
| +   *
 | 
| +   * The [uri] must have scheme `data` and no authority or fragment,
 | 
| +   * and the path (concatenated with the query, if there is one) must be valid
 | 
| +   * as data URI content with the same rules as [parse].
 | 
| +   */
 | 
| +  factory UriData.fromUri(Uri uri) {
 | 
| +    if (uri.scheme != "data") {
 | 
| +      throw new ArgumentError.value(uri, "uri",
 | 
| +                                    "Scheme must be 'data'");
 | 
| +    }
 | 
| +    if (uri.hasAuthority) {
 | 
| +      throw new ArgumentError.value(uri, "uri",
 | 
| +                                    "Data uri must not have authority");
 | 
| +    }
 | 
| +    if (uri.hasFragment) {
 | 
| +      throw new ArgumentError.value(uri, "uri",
 | 
| +                                    "Data uri must not have a fragment part");
 | 
| +    }
 | 
| +    if (!uri.hasQuery) {
 | 
| +      return _parse(uri.path, 0, uri);
 | 
| +    }
 | 
| +    // Includes path and query (and leading "data:").
 | 
| +    return _parse("$uri", 5, uri);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Writes the initial part of a `data:` uri, from after the "data:"
 | 
| +   * until just before the ',' before the data, or before a `;base64,`
 | 
| +   * marker.
 | 
| +   *
 | 
| +   * Of an [indices] list is passed, separator indices are stored in that
 | 
| +   * list.
 | 
| +   */
 | 
| +  static void _writeUri(String mimeType,
 | 
| +                        String charsetName,
 | 
| +                        Map<String, String> parameters,
 | 
| +                        StringBuffer buffer, List indices) {
 | 
| +    if (mimeType == null || mimeType == "text/plain") {
 | 
| +      mimeType = "";
 | 
| +    }
 | 
| +    if (mimeType.isEmpty || identical(mimeType, "application/octet-stream")) {
 | 
| +      buffer.write(mimeType);  // Common cases need no escaping.
 | 
| +    } else {
 | 
| +      int slashIndex = _validateMimeType(mimeType);
 | 
| +      if (slashIndex < 0) {
 | 
| +        throw new ArgumentError.value(mimeType, "mimeType",
 | 
| +                                      "Invalid MIME type");
 | 
| +      }
 | 
| +      buffer.write(Uri._uriEncode(_tokenCharTable,
 | 
| +                                  mimeType.substring(0, slashIndex),
 | 
| +                                  UTF8, false));
 | 
| +      buffer.write("/");
 | 
| +      buffer.write(Uri._uriEncode(_tokenCharTable,
 | 
| +                                  mimeType.substring(slashIndex + 1),
 | 
| +                                  UTF8, false));
 | 
| +    }
 | 
| +    if (charsetName != null) {
 | 
| +      if (indices != null) {
 | 
| +        indices..add(buffer.length)
 | 
| +               ..add(buffer.length + 8);
 | 
| +      }
 | 
| +      buffer.write(";charset=");
 | 
| +      buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
 | 
| +    }
 | 
| +    parameters?.forEach((var key, var value) {
 | 
| +      if (key.isEmpty) {
 | 
| +        throw new ArgumentError.value("", "Parameter names must not be empty");
 | 
| +      }
 | 
| +      if (value.isEmpty) {
 | 
| +        throw new ArgumentError.value("", "Parameter values must not be empty",
 | 
| +                                      'parameters["$key"]');
 | 
| +      }
 | 
| +      if (indices != null) indices.add(buffer.length);
 | 
| +      buffer.write(';');
 | 
| +      // Encode any non-RFC2045-token character and both '%' and '#'.
 | 
| +      buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false));
 | 
| +      if (indices != null) indices.add(buffer.length);
 | 
| +      buffer.write('=');
 | 
| +      buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
 | 
| +    });
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Checks mimeType is valid-ish (`token '/' token`).
 | 
| +   *
 | 
| +   * Returns the index of the slash, or -1 if the mime type is not
 | 
| +   * considered valid.
 | 
| +   *
 | 
| +   * Currently only looks for slashes, all other characters will be
 | 
| +   * percent-encoded as UTF-8 if necessary.
 | 
| +   */
 | 
| +  static int _validateMimeType(String mimeType) {
 | 
| +    int slashIndex = -1;
 | 
| +    for (int i = 0; i < mimeType.length; i++) {
 | 
| +      var char = mimeType.codeUnitAt(i);
 | 
| +      if (char != Uri._SLASH) continue;
 | 
| +      if (slashIndex < 0) {
 | 
| +        slashIndex = i;
 | 
| +        continue;
 | 
| +      }
 | 
| +      return -1;
 | 
| +    }
 | 
| +    return slashIndex;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Parses a string as a `data` URI.
 | 
| +   *
 | 
| +   * The string must have the format:
 | 
| +   *
 | 
| +   * ```
 | 
| +   * 'data:' (type '/' subtype)? (';' attribute '=' value)* (';base64')? ',' data
 | 
| +   * ````
 | 
| +   *
 | 
| +   * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
 | 
| +   * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
 | 
| +   *
 | 
| +   * This means that all the characters must be ASCII, but the URI may contain
 | 
| +   * percent-escapes for non-ASCII byte values that need an interpretation
 | 
| +   * to be converted to the corresponding string.
 | 
| +   *
 | 
| +   * Parsing doesn't check the validity of any part, it just checks that the
 | 
| +   * input has the correct structure with the correct sequence of `/`, `;`, `=`
 | 
| +   * and `,` delimiters.
 | 
| +   *
 | 
| +   * Accessing the individual parts may fail later if they turn out to have
 | 
| +   * content that can't be decoded sucessfully as a string.
 | 
| +   */
 | 
| +  static UriData parse(String uri) {
 | 
| +    if (!uri.startsWith("data:")) {
 | 
| +      throw new FormatException("Does not start with 'data:'", uri, 0);
 | 
| +    }
 | 
| +    return _parse(uri, 5, null);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * The [Uri] that this `UriData` is giving access to.
 | 
| +   *
 | 
| +   * Returns a `Uri` with scheme `data` and the remainder of the data URI
 | 
| +   * as path.
 | 
| +   */
 | 
| +  Uri get uri {
 | 
| +    if (_uriCache != null) return _uriCache;
 | 
| +    String path = _text;
 | 
| +    String query = null;
 | 
| +    int colonIndex = _separatorIndices[0];
 | 
| +    int queryIndex = _text.indexOf('?', colonIndex + 1);
 | 
| +    int end = null;
 | 
| +    if (queryIndex >= 0) {
 | 
| +      query = _text.substring(queryIndex + 1);
 | 
| +      end = queryIndex;
 | 
| +    }
 | 
| +    path = _text.substring(colonIndex + 1, end);
 | 
| +    // TODO(lrn): This is probably too simple. We should ensure URI
 | 
| +    // normalization before passing in the raw strings, maybe using
 | 
| +    // Uri._makePath, Uri._makeQuery.
 | 
| +    _uriCache = new Uri._internal("data", "", null, null, path, query, null);
 | 
| +    return _uriCache;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * The MIME type of the data URI.
 | 
| +   *
 | 
| +   * A data URI consists of a "media type" followed by data.
 | 
| +   * The media type starts with a MIME type and can be followed by
 | 
| +   * extra parameters.
 | 
| +   *
 | 
| +   * Example:
 | 
| +   *
 | 
| +   *     data:text/plain;charset=utf-8,Hello%20World!
 | 
| +   *
 | 
| +   * This data URI has the media type `text/plain;charset=utf-8`, which is the
 | 
| +   * MIME type `text/plain` with the parameter `charset` with value `utf-8`.
 | 
| +   * See [RFC 2045](https://tools.ietf.org/html/rfc2045) for more detail.
 | 
| +   *
 | 
| +   * If the first part of the data URI is empty, it defaults to `text/plain`.
 | 
| +   */
 | 
| +  String get mimeType {
 | 
| +    int start = _separatorIndices[0] + 1;
 | 
| +    int end = _separatorIndices[1];
 | 
| +    if (start == end) return "text/plain";
 | 
| +    return Uri._uriDecode(_text, start, end, UTF8, false);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * The charset parameter of the media type.
 | 
| +   *
 | 
| +   * If the parameters of the media type contains a `charset` parameter
 | 
| +   * then this returns its value, otherwise it returns `US-ASCII`,
 | 
| +   * which is the default charset for data URIs.
 | 
| +   */
 | 
| +  String get charset {
 | 
| +    int parameterStart = 1;
 | 
| +    int parameterEnd = _separatorIndices.length - 1;  // The ',' before data.
 | 
| +    if (isBase64) {
 | 
| +      // There is a ";base64" separator, so subtract one for that as well.
 | 
| +      parameterEnd -= 1;
 | 
| +    }
 | 
| +    for (int i = parameterStart; i < parameterEnd; i += 2) {
 | 
| +      var keyStart = _separatorIndices[i] + 1;
 | 
| +      var keyEnd = _separatorIndices[i + 1];
 | 
| +      if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
 | 
| +        return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
 | 
| +                              UTF8, false);
 | 
| +      }
 | 
| +    }
 | 
| +    return "US-ASCII";
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Whether the data is Base64 encoded or not.
 | 
| +   */
 | 
| +  bool get isBase64 => _separatorIndices.length.isOdd;
 | 
| +
 | 
| +  /**
 | 
| +   * The content part of the data URI, as its actual representation.
 | 
| +   *
 | 
| +   * This string may contain percent escapes.
 | 
| +   */
 | 
| +  String get contentText => _text.substring(_separatorIndices.last + 1);
 | 
| +
 | 
| +  /**
 | 
| +   * The content part of the data URI as bytes.
 | 
| +   *
 | 
| +   * If the data is Base64 encoded, it will be decoded to bytes.
 | 
| +   *
 | 
| +   * If the data is not Base64 encoded, it will be decoded by unescaping
 | 
| +   * percent-escaped characters and returning byte values of each unescaped
 | 
| +   * character. The bytes will not be, e.g., UTF-8 decoded.
 | 
| +   */
 | 
| +  List<int> contentAsBytes() {
 | 
| +    String text = _text;
 | 
| +    int start = _separatorIndices.last + 1;
 | 
| +    if (isBase64) {
 | 
| +      return BASE64.decoder.convert(text, start);
 | 
| +    }
 | 
| +
 | 
| +    // Not base64, do percent-decoding and return the remaining bytes.
 | 
| +    // Compute result size.
 | 
| +    const int percent = 0x25;
 | 
| +    int length = text.length - start;
 | 
| +    for (int i = start; i < text.length; i++) {
 | 
| +      var codeUnit = text.codeUnitAt(i);
 | 
| +      if (codeUnit == percent) {
 | 
| +        i += 2;
 | 
| +        length -= 2;
 | 
| +      }
 | 
| +    }
 | 
| +    // Fill result array.
 | 
| +    Uint8List result = new Uint8List(length);
 | 
| +    if (length == text.length) {
 | 
| +      result.setRange(0, length, text.codeUnits, start);
 | 
| +      return result;
 | 
| +    }
 | 
| +    int index = 0;
 | 
| +    for (int i = start; i < text.length; i++) {
 | 
| +      var codeUnit = text.codeUnitAt(i);
 | 
| +      if (codeUnit != percent) {
 | 
| +        result[index++] = codeUnit;
 | 
| +      } else {
 | 
| +        if (i + 2 < text.length) {
 | 
| +          var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1));
 | 
| +          var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2));
 | 
| +          if (digit1 >= 0 && digit2 >= 0) {
 | 
| +            int byte = digit1 * 16 + digit2;
 | 
| +            result[index++] = byte;
 | 
| +            i += 2;
 | 
| +            continue;
 | 
| +          }
 | 
| +        }
 | 
| +        throw new FormatException("Invalid percent escape", text, i);
 | 
| +      }
 | 
| +    }
 | 
| +    assert(index == result.length);
 | 
| +    return result;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Returns a string created from the content of the data URI.
 | 
| +   *
 | 
| +   * If the content is Base64 encoded, it will be decoded to bytes and then
 | 
| +   * decoded to a string using [encoding].
 | 
| +   * If encoding is omitted, the value of a `charset` parameter is used
 | 
| +   * if it is recongized by [Encoding.getByName], otherwise it defaults to
 | 
| +   * the [ASCII] encoding, which is the default encoding for data URIs
 | 
| +   * that do not specify an encoding.
 | 
| +   *
 | 
| +   * If the content is not Base64 encoded, it will first have percent-escapes
 | 
| +   * converted to bytes and then the character codes and byte values are
 | 
| +   * decoded using [encoding].
 | 
| +   */
 | 
| +  String contentAsString({Encoding encoding}) {
 | 
| +    if (encoding == null) {
 | 
| +      var charset = this.charset;  // Returns "US-ASCII" if not present.
 | 
| +      encoding = Encoding.getByName(charset);
 | 
| +      if (encoding == null) {
 | 
| +        throw new UnsupportedError("Unknown charset: $charset");
 | 
| +      }
 | 
| +    }
 | 
| +    String text = _text;
 | 
| +    int start = _separatorIndices.last + 1;
 | 
| +    if (isBase64) {
 | 
| +      var converter = BASE64.decoder.fuse(encoding.decoder);
 | 
| +      return converter.convert(text.substring(start));
 | 
| +    }
 | 
| +    return Uri._uriDecode(text, start, text.length, encoding, false);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * A map representing the parameters of the media type.
 | 
| +   *
 | 
| +   * A data URI may contain parameters between the the MIME type and the
 | 
| +   * data. This converts these parameters to a map from parameter name
 | 
| +   * to parameter value.
 | 
| +   * The map only contains parameters that actually occur in the URI.
 | 
| +   * The `charset` parameter has a default value even if it doesn't occur
 | 
| +   * in the URI, which is reflected by the [charset] getter. This means that
 | 
| +   * [charset] may return a value even if `parameters["charset"]` is `null`.
 | 
| +   *
 | 
| +   * If the values contain non-ASCII values or percent escapes, they default
 | 
| +   * to being decoded as UTF-8.
 | 
| +   */
 | 
| +  Map<String, String> get parameters {
 | 
| +    var result = <String, String>{};
 | 
| +    for (int i = 3; i < _separatorIndices.length; i += 2) {
 | 
| +      var start = _separatorIndices[i - 2] + 1;
 | 
| +      var equals = _separatorIndices[i - 1];
 | 
| +      var end = _separatorIndices[i];
 | 
| +      String key = Uri._uriDecode(_text, start, equals, UTF8, false);
 | 
| +      String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false);
 | 
| +      result[key] = value;
 | 
| +    }
 | 
| +    return result;
 | 
| +  }
 | 
| +
 | 
| +  static UriData _parse(String text, int start, Uri sourceUri) {
 | 
| +    assert(start == 0 || start == 5);
 | 
| +    assert((start == 5) == text.startsWith("data:"));
 | 
| +
 | 
| +    /// Character codes.
 | 
| +    const int comma     = 0x2c;
 | 
| +    const int slash     = 0x2f;
 | 
| +    const int semicolon = 0x3b;
 | 
| +    const int equals    = 0x3d;
 | 
| +    List<int> indices = [start - 1];
 | 
| +    int slashIndex = -1;
 | 
| +    var char;
 | 
| +    int i = start;
 | 
| +    for (; i < text.length; i++) {
 | 
| +      char = text.codeUnitAt(i);
 | 
| +      if (char == comma || char == semicolon) break;
 | 
| +      if (char == slash) {
 | 
| +        if (slashIndex < 0) {
 | 
| +          slashIndex = i;
 | 
| +          continue;
 | 
| +        }
 | 
| +        throw new FormatException("Invalid MIME type", text, i);
 | 
| +      }
 | 
| +    }
 | 
| +    if (slashIndex < 0 && i > start) {
 | 
| +      // An empty MIME type is allowed, but if non-empty it must contain
 | 
| +      // exactly one slash.
 | 
| +      throw new FormatException("Invalid MIME type", text, i);
 | 
| +    }
 | 
| +    while (char != comma) {
 | 
| +      // Parse parameters and/or "base64".
 | 
| +      indices.add(i);
 | 
| +      i++;
 | 
| +      int equalsIndex = -1;
 | 
| +      for (; i < text.length; i++) {
 | 
| +        char = text.codeUnitAt(i);
 | 
| +        if (char == equals) {
 | 
| +          if (equalsIndex < 0) equalsIndex = i;
 | 
| +        } else if (char == semicolon || char == comma) {
 | 
| +          break;
 | 
| +        }
 | 
| +      }
 | 
| +      if (equalsIndex >= 0) {
 | 
| +        indices.add(equalsIndex);
 | 
| +      } else {
 | 
| +        // Have to be final "base64".
 | 
| +        var lastSeparator = indices.last;
 | 
| +        if (char != comma ||
 | 
| +            i != lastSeparator + 7 /* "base64,".length */ ||
 | 
| +            !text.startsWith("base64", lastSeparator + 1)) {
 | 
| +          throw new FormatException("Expecting '='", text, i);
 | 
| +        }
 | 
| +        break;
 | 
| +      }
 | 
| +    }
 | 
| +    indices.add(i);
 | 
| +    return new UriData._(text, indices, sourceUri);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Like [Uri._uriEncode] but takes the input as bytes, not a string.
 | 
| +   *
 | 
| +   * Encodes into [buffer] instead of creating its own buffer.
 | 
| +   */
 | 
| +  static void _uriEncodeBytes(List<int> canonicalTable,
 | 
| +                              List<int> bytes,
 | 
| +                              StringSink buffer) {
 | 
| +    // Encode the string into bytes then generate an ASCII only string
 | 
| +    // by percent encoding selected bytes.
 | 
| +    int byteOr = 0;
 | 
| +    for (int i = 0; i < bytes.length; i++) {
 | 
| +      int byte = bytes[i];
 | 
| +      byteOr |= byte;
 | 
| +      if (byte < 128 &&
 | 
| +          ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
 | 
| +        buffer.writeCharCode(byte);
 | 
| +      } else {
 | 
| +        buffer.writeCharCode(Uri._PERCENT);
 | 
| +        buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
 | 
| +        buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
 | 
| +      }
 | 
| +    }
 | 
| +    if ((byteOr & ~0xFF) != 0) {
 | 
| +      for (int i = 0; i < bytes.length; i++) {
 | 
| +        var byte = bytes[i];
 | 
| +        if (byte < 0 || byte > 255) {
 | 
| +          throw new ArgumentError.value(byte, "non-byte value");
 | 
| +        }
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  String toString() =>
 | 
| +      (_separatorIndices[0] == _noScheme) ? "data:$_text" : _text;
 | 
| +
 | 
| +  // Table of the `token` characters of RFC 2045 in a URI.
 | 
| +  //
 | 
| +  // A token is any US-ASCII character except SPACE, control characters and
 | 
| +  // `tspecial` characters. The `tspecial` category is:
 | 
| +  // '(', ')', '<', '>', '@', ',', ';', ':', '\', '"', '/', '[, ']', '?', '='.
 | 
| +  //
 | 
| +  // In a data URI, we also need to escape '%' and '#' characters.
 | 
| +  static const _tokenCharTable = const [
 | 
| +                //             LSB             MSB
 | 
| +                //              |               |
 | 
| +      0x0000,   // 0x00 - 0x0f  00000000 00000000
 | 
| +      0x0000,   // 0x10 - 0x1f  00000000 00000000
 | 
| +                //               !  $ &'   *+ -.
 | 
| +      0x6cd2,   // 0x20 - 0x2f  01001011 00110110
 | 
| +                //              01234567 89
 | 
| +      0x03ff,   // 0x30 - 0x3f  11111111 11000000
 | 
| +                //               ABCDEFG HIJKLMNO
 | 
| +      0xfffe,   // 0x40 - 0x4f  01111111 11111111
 | 
| +                //              PQRSTUVW XYZ   ^_
 | 
| +      0xc7ff,   // 0x50 - 0x5f  11111111 11100011
 | 
| +                //              `abcdefg hijklmno
 | 
| +      0xffff,   // 0x60 - 0x6f  11111111 11111111
 | 
| +                //              pqrstuvw xyz{|}~
 | 
| +      0x7fff];  // 0x70 - 0x7f  11111111 11111110
 | 
| +
 | 
| +  // All non-escape RFC-2396 uric characters.
 | 
| +  //
 | 
| +  //  uric        =  reserved | unreserved | escaped
 | 
| +  //  reserved    =  ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
 | 
| +  //  unreserved  =  alphanum | mark
 | 
| +  //  mark        =  "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
 | 
| +  //
 | 
| +  // This is the same characters as in a URI query (which is URI pchar plus '?')
 | 
| +  static const _uricTable = Uri._queryCharTable;
 | 
|  }
 | 
| 
 |