description

# parse method Null safety

Uri parse (

1. String uri,
2. [int start = 0,
3. int? end]

)

Creates a new `Uri` object by parsing a URI string.

If `start` and `end` are provided, they must specify a valid substring of `uri`, and only the substring from `start` to `end` is parsed as a URI.

The `uri` must not be `null`. If the `uri` string is not valid as a URI or URI reference, a FormatException is thrown.

## Implementation

    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 ]
      //
      // hier-part     = "//" authority path-abempty
      //               / path-absolute
      //               / path-rootless
      //               / path-empty
      //
      // URI-reference = URI / relative-ref
      //
      // absolute-URI  = scheme ":" hier-part [ "?" query ]
      //
      // relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
      //
      // relative-part = "//" authority path-abempty
      //               / path-absolute
      //               / path-noscheme
      //               / path-empty
      //
      // scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
      //
      // authority     = [ userinfo "@" ] host [ ":" port ]
      // userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
      // host          = IP-literal / IPv4address / reg-name
      // IP-literal    = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
      // IPv6addrz     = IPv6address "%25" ZoneID
      // ZoneID        = 1*( unreserved / pct-encoded )
      // port          = *DIGIT
      // reg-name      = *( unreserved / pct-encoded / sub-delims )
      //
      // path          = path-abempty    ; begins with "/" or is empty
      //               / path-absolute   ; begins with "/" but not "//"
      //               / path-noscheme   ; begins with a non-colon segment
      //               / path-rootless   ; begins with a segment
      //               / path-empty      ; zero characters
      //
      // path-abempty  = *( "/" segment )
      // path-absolute = "/" [ segment-nz *( "/" segment ) ]
      // path-noscheme = segment-nz-nc *( "/" segment )
      // path-rootless = segment-nz *( "/" segment )
      // path-empty    = 0<pchar>
      //
      // segment       = *pchar
      // segment-nz    = 1*pchar
      // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
      //               ; non-zero-length segment without any colon ":"
      //
      // pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
      //
      // query         = *( pchar / "/" / "?" )
      //
      // fragment      = *( pchar / "/" / "?" )
      end ??= uri.length;

      // Special case data:URIs. Ignore case when testing.
      if (end >= start + 5) {
        int dataDelta = _startsWithData(uri, start);
        if (dataDelta == 0) {
          // The case is right.
          if (start > 0 || end < uri.length) uri = uri.substring(start, end);
          return UriData._parse(uri, 5, null).uri;
        } else if (dataDelta == 0x20) {
          return UriData._parse(uri.substring(start + 5, end), 0, null).uri;
        }
        // Otherwise the URI doesn't start with "data:" or any case variant of it.
      }

      // The following index-normalization belongs with the scanning, but is
      // easier to do here because we already have extracted variables from the
      // indices list.
      var indices = List<int>.filled(8, 0, growable: false);

      // Set default values for each position.
      // The value will either be correct in some cases where it isn't set
      // by the scanner, or it is clearly recognizable as an unset value.
      indices
        ..[0] = 0
        ..[_schemeEndIndex] = start - 1
        ..[_hostStartIndex] = start - 1
        ..[_notSimpleIndex] = start - 1
        ..[_portStartIndex] = start
        ..[_pathStartIndex] = start
        ..[_queryStartIndex] = end
        ..[_fragmentStartIndex] = end;
      var state = _scan(uri, start, end, _uriStart, indices);
      // Some states that should be non-simple, but the URI ended early.
      // Paths that end at a ".." must be normalized to end in "../".
      if (state >= _nonSimpleEndStates) {
        indices[_notSimpleIndex] = end;
      }
      int schemeEnd = indices[_schemeEndIndex];
      if (schemeEnd >= start) {
        // Rescan the scheme part now that we know it's not a path.
        state = _scan(uri, start, schemeEnd, _schemeStart, indices);
        if (state == _schemeStart) {
          // Empty scheme.
          indices[_notSimpleIndex] = schemeEnd;
        }
      }
      // The returned positions are limited by the scanners ability to write only
      // one position per character, and only the current position.
      // Scanning from left to right, we only know whether something is a scheme
      // or a path when we see a `:` or `/`, and likewise we only know if the first
      // `/` is part of the path or is leading an authority component when we see
      // the next character.

      int hostStart = indices[_hostStartIndex] + 1;
      int portStart = indices[_portStartIndex];
      int pathStart = indices[_pathStartIndex];
      int queryStart = indices[_queryStartIndex];
      int fragmentStart = indices[_fragmentStartIndex];

      // We may discover the scheme while handling special cases.
      String? scheme;

      // Derive some positions that weren't set to normalize the indices.
      if (fragmentStart < queryStart) queryStart = fragmentStart;
      // If pathStart isn't set (it's before scheme end or host start), then
      // the path is empty, or there is no authority part and the path
      // starts with a non-simple character.
      if (pathStart < hostStart) {
        // There is an authority, but no path. The path would start with `/`
        // if it was there.
        pathStart = queryStart;
      } else if (pathStart <= schemeEnd) {
        // There is a scheme, but no authority.
        pathStart = schemeEnd + 1;
      }
      // If there is an authority with no port, set the port position
      // to be at the end of the authority (equal to pathStart).
      // This also handles a ":" in a user-info component incorrectly setting
      // the port start position.
      if (portStart < hostStart) portStart = pathStart;

      assert(hostStart == start || schemeEnd <= hostStart);
      assert(hostStart <= portStart);
      assert(schemeEnd <= pathStart);
      assert(portStart <= pathStart);
      assert(pathStart <= queryStart);
      assert(queryStart <= fragmentStart);

      bool isSimple = indices[_notSimpleIndex] < start;

      if (isSimple) {
        // Check/do normalizations that weren't detected by the scanner.
        // This includes removal of empty port or userInfo,
        // or scheme specific port and path normalizations.
        if (hostStart > schemeEnd + 3) {
          // Always be non-simple if URI contains user-info.
          // The scanner doesn't set the not-simple position in this case because
          // it's setting the host-start position instead.
          isSimple = false;
        } else if (portStart > start && portStart + 1 == pathStart) {
          // If the port is empty, it should be omitted.
          // Pathological case, don't bother correcting it.
          isSimple = false;
        } else if (queryStart < end &&
                (queryStart == pathStart + 2 &&
                    uri.startsWith("..", pathStart)) ||
            (queryStart > pathStart + 2 &&
                uri.startsWith("/..", queryStart - 3))) {
          // The path ends in a ".." segment. This should be normalized to "../".
          // We didn't detect this while scanning because a query or fragment was
          // detected at the same time (which is why we only need to check this
          // if there is something after the path).
          isSimple = false;
        } else {
          // There are a few scheme-based normalizations that
          // the scanner couldn't check.
          // That means that the input is very close to simple, so just do
          // the normalizations.
          if (schemeEnd == start + 4) {
            // Do scheme based normalizations for file, http.
            if (uri.startsWith("file", start)) {
              scheme = "file";
              if (hostStart <= start) {
                // File URIs should have an authority.
                // Paths after an authority should be absolute.
                String schemeAuth = "file://";
                int delta = 2;
                if (!uri.startsWith("/", pathStart)) {
                  schemeAuth = "file:///";
                  delta = 3;
                }
                uri = schemeAuth + uri.substring(pathStart, end);
                schemeEnd -= start;
                hostStart = 7;
                portStart = 7;
                pathStart = 7;
                queryStart += delta - start;
                fragmentStart += delta - start;
                start = 0;
                end = uri.length;
              } else if (pathStart == queryStart) {
                // Uri has authority and empty path. Add "/" as path.
                if (start == 0 && end == uri.length) {
                  uri = uri.replaceRange(pathStart, queryStart, "/");
                  queryStart += 1;
                  fragmentStart += 1;
                  end += 1;
                } else {
                  uri = "${uri.substring(start, pathStart)}/"
                      "${uri.substring(queryStart, end)}";
                  schemeEnd -= start;
                  hostStart -= start;
                  portStart -= start;
                  pathStart -= start;
                  queryStart += 1 - start;
                  fragmentStart += 1 - start;
                  start = 0;
                  end = uri.length;
                }
              }
            } else if (uri.startsWith("http", start)) {
              scheme = "http";
              // HTTP URIs should not have an explicit port of 80.
              if (portStart > start &&
                  portStart + 3 == pathStart &&
                  uri.startsWith("80", portStart + 1)) {
                if (start == 0 && end == uri.length) {
                  uri = uri.replaceRange(portStart, pathStart, "");
                  pathStart -= 3;
                  queryStart -= 3;
                  fragmentStart -= 3;
                  end -= 3;
                } else {
                  uri = uri.substring(start, portStart) +
                      uri.substring(pathStart, end);
                  schemeEnd -= start;
                  hostStart -= start;
                  portStart -= start;
                  pathStart -= 3 + start;
                  queryStart -= 3 + start;
                  fragmentStart -= 3 + start;
                  start = 0;
                  end = uri.length;
                }
              }
            }
          } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
            scheme = "https";
            // HTTPS URIs should not have an explicit port of 443.
            if (portStart > start &&
                portStart + 4 == pathStart &&
                uri.startsWith("443", portStart + 1)) {
              if (start == 0 && end == uri.length) {
                uri = uri.replaceRange(portStart, pathStart, "");
                pathStart -= 4;
                queryStart -= 4;
                fragmentStart -= 4;
                end -= 3;
              } else {
                uri = uri.substring(start, portStart) +
                    uri.substring(pathStart, end);
                schemeEnd -= start;
                hostStart -= start;
                portStart -= start;
                pathStart -= 4 + start;
                queryStart -= 4 + start;
                fragmentStart -= 4 + start;
                start = 0;
                end = uri.length;
              }
            }
          }
        }
      }

      if (isSimple) {
        if (start > 0 || end < uri.length) {
          uri = uri.substring(start, end);
          schemeEnd -= start;
          hostStart -= start;
          portStart -= start;
          pathStart -= start;
          queryStart -= start;
          fragmentStart -= start;
        }
        return _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
            queryStart, fragmentStart, scheme);
      }

      return _Uri.notSimple(uri, start, end, schemeEnd, hostStart, portStart,
          pathStart, queryStart, fragmentStart, scheme);
    }