mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 21:30:17 +00:00
Fix bug in _SimpleUri.resolve.
In some cases it didn't follow our non-RFC behavior for resolving a relative path on top of another relative path. Fixes issue #27447 BUG= http://dartbug.com/27447 R=eernst@google.com Review URL: https://codereview.chromium.org/2374253004 .
This commit is contained in:
parent
2ca7ae5e0e
commit
9bd4406d4b
|
@ -4323,10 +4323,6 @@ class _SimpleUri implements Uri {
|
|||
base._schemeCache);
|
||||
}
|
||||
// Merge paths.
|
||||
if (base._uri.startsWith("../", base._pathStart)) {
|
||||
// Complex rare case, go slow.
|
||||
return _toNonSimple().resolveUri(ref);
|
||||
}
|
||||
|
||||
// The RFC 3986 algorithm merges the base path without its final segment
|
||||
// (anything after the final "/", or everything if the base path doesn't
|
||||
|
@ -4341,39 +4337,53 @@ class _SimpleUri implements Uri {
|
|||
String refUri = ref._uri;
|
||||
int baseStart = base._pathStart;
|
||||
int baseEnd = base._queryStart;
|
||||
while (baseUri.startsWith("../", baseStart)) baseStart += 3;
|
||||
int refStart = ref._pathStart;
|
||||
int refEnd = ref._queryStart;
|
||||
int backCount = 1;
|
||||
|
||||
int slashCount = 0;
|
||||
|
||||
// Count leading ".." segments in reference path.
|
||||
/// Count of leading ".." segments in reference path.
|
||||
/// The count is decremented when the segment is matched with a
|
||||
/// segment of the base path, and both are then omitted from the result.
|
||||
int backCount = 0;
|
||||
/// Count "../" segments and advance `refStart` to after the segments.
|
||||
while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
|
||||
refStart += 3;
|
||||
backCount += 1;
|
||||
}
|
||||
|
||||
// Extra slash inserted between base and reference path parts if
|
||||
// the base path contains any slashes.
|
||||
// the base path contains any slashes, or empty string if none.
|
||||
// (We could use a slash from the base path in most cases, but not if
|
||||
// we remove the entire base path).
|
||||
String insert = "";
|
||||
|
||||
/// Remove segments from the base path.
|
||||
/// Start with the segment trailing the last slash,
|
||||
/// then remove segments for each leading "../" segment
|
||||
/// from the reference path, or as many of them as are available.
|
||||
while (baseEnd > baseStart) {
|
||||
baseEnd--;
|
||||
int char = baseUri.codeUnitAt(baseEnd);
|
||||
if (char == _SLASH) {
|
||||
insert = "/";
|
||||
backCount--;
|
||||
if (backCount == 0) break;
|
||||
backCount--;
|
||||
}
|
||||
}
|
||||
// If the base URI has no scheme or authority (`_pathStart == 0`)
|
||||
// and a relative path, and we reached the beginning of the path,
|
||||
// we have a special case.
|
||||
if (baseEnd == 0 && !base.hasAbsolutePath) {
|
||||
// Non-RFC 3986 behavior when resolving a purely relative path on top of
|
||||
// another relative path: Don't make the result absolute.
|
||||
|
||||
if (baseEnd == baseStart && !base.hasScheme && !base.hasAbsolutePath) {
|
||||
// If the base is *just* a relative path (no scheme or authority),
|
||||
// then merging with another relative path doesn't follow the
|
||||
// RFC-3986 behavior.
|
||||
// Don't need to check `base.hasAuthority` since the base path is
|
||||
// non-empty - if there is an authority, a non-empty path is absolute.
|
||||
|
||||
// We reached the start of the base path, and want to stay relative,
|
||||
// so don't insert a slash.
|
||||
insert = "";
|
||||
// If we reached the start of the base path with more "../" left over
|
||||
// in the reference path, include those segments in the result.
|
||||
refStart -= backCount * 3;
|
||||
}
|
||||
|
||||
var delta = baseEnd - refStart + insert.length;
|
||||
|
|
|
@ -85,14 +85,42 @@ testEncodeDecodeQueryComponent(String orig,
|
|||
}
|
||||
|
||||
testUriPerRFCs() {
|
||||
final urisSample = "http://a/b/c/d;p?q";
|
||||
Uri base = Uri.parse(urisSample);
|
||||
// Convert a Uri to a guaranteed "non simple" URI with the same content.
|
||||
toComplex(Uri uri) {
|
||||
Uri complex = new Uri(
|
||||
scheme: uri.scheme,
|
||||
userInfo: uri.hasAuthority ? uri.userInfo : null,
|
||||
host: uri.hasAuthority ? uri.host : null,
|
||||
port: uri.hasAuthority ? uri.port : null,
|
||||
path: uri.path,
|
||||
query: uri.hasQuery ? uri.query : null,
|
||||
fragment: uri.hasFragment ? uri.fragment : null,
|
||||
);
|
||||
assert(complex.toString() == uri.toString());
|
||||
return complex;
|
||||
}
|
||||
|
||||
Uri base;
|
||||
Uri complexBase;
|
||||
// Sets the [base] and [complexBase] to the parse of the URI and a
|
||||
// guaranteed non-simple version of the same URI.
|
||||
setBase(String uri) {
|
||||
base = Uri.parse(uri);
|
||||
complexBase = toComplex(base);
|
||||
}
|
||||
|
||||
testResolve(expect, relative) {
|
||||
String name = "$base << $relative";
|
||||
Expect.stringEquals(expect, base.resolve(relative).toString(), name);
|
||||
|
||||
Expect.stringEquals(expect, complexBase.resolve(relative).toString(),
|
||||
name + " (complex base)");
|
||||
}
|
||||
|
||||
// From RFC 3986.
|
||||
final urisSample = "http://a/b/c/d;p?q";
|
||||
setBase(urisSample);
|
||||
|
||||
testResolve("g:h", "g:h");
|
||||
testResolve("http://a/b/c/g", "g");
|
||||
testResolve("http://a/b/c/g", "./g");
|
||||
|
@ -139,47 +167,66 @@ testUriPerRFCs() {
|
|||
// Additional tests (not from RFC 3986).
|
||||
testResolve("http://a/b/g;p/h;s", "../g;p/h;s");
|
||||
|
||||
base = Uri.parse("s:a/b");
|
||||
setBase("s:a/b");
|
||||
testResolve("s:a/c", "c");
|
||||
testResolve("s:/c", "../c");
|
||||
|
||||
base = Uri.parse("S:a/b");
|
||||
setBase("S:a/b");
|
||||
testResolve("s:a/c", "c");
|
||||
testResolve("s:/c", "../c");
|
||||
|
||||
base = Uri.parse("s:foo");
|
||||
setBase("s:foo");
|
||||
testResolve("s:bar", "bar");
|
||||
testResolve("s:bar", "../bar");
|
||||
|
||||
base = Uri.parse("S:foo");
|
||||
setBase("S:foo");
|
||||
testResolve("s:bar", "bar");
|
||||
testResolve("s:bar", "../bar");
|
||||
|
||||
// Special-case (deliberate non-RFC behavior).
|
||||
base = Uri.parse("foo/bar");
|
||||
setBase("foo/bar");
|
||||
testResolve("foo/baz", "baz");
|
||||
testResolve("baz", "../baz");
|
||||
|
||||
base = Uri.parse("s:/foo");
|
||||
setBase("s:/foo");
|
||||
testResolve("s:/bar", "bar");
|
||||
testResolve("s:/bar", "../bar");
|
||||
|
||||
base = Uri.parse("S:/foo");
|
||||
setBase("S:/foo");
|
||||
testResolve("s:/bar", "bar");
|
||||
testResolve("s:/bar", "../bar");
|
||||
|
||||
// Test non-URI base (no scheme, no authority, relative path).
|
||||
base = Uri.parse("a/b/c?_#_");
|
||||
setBase("a/b/c?_#_");
|
||||
testResolve("a/b/g?q#f", "g?q#f");
|
||||
testResolve("./", "../..");
|
||||
testResolve("../", "../../..");
|
||||
testResolve("a/b/", ".");
|
||||
testResolve("c", "../../c"); // Deliberate non-RFC behavior.
|
||||
base = Uri.parse("../../a/b/c?_#_"); // Initial ".." in base url.
|
||||
setBase("../../a/b/c?_#_"); // Initial ".." in base url.
|
||||
testResolve("../../a/d", "../d");
|
||||
testResolve("../../d", "../../d");
|
||||
testResolve("../../../d", "../../../d");
|
||||
setBase("../../a/b");
|
||||
testResolve("../../a/d", "d");
|
||||
testResolve("../../d", "../d");
|
||||
testResolve("../../../d", "../../d");
|
||||
setBase("../../a");
|
||||
testResolve("../../d", "d");
|
||||
testResolve("../../../d", "../d");
|
||||
testResolve("../../../../d", "../../d");
|
||||
|
||||
base = Uri.parse("s://h/p?q#f"); // A simple base.
|
||||
// Absoluyte path, not scheme or authority.
|
||||
setBase("/a");
|
||||
testResolve("/b", "b");
|
||||
testResolve("/b", "../b");
|
||||
testResolve("/b", "../../b");
|
||||
setBase("/a/b");
|
||||
testResolve("/a/c", "c");
|
||||
testResolve("/c", "../c");
|
||||
testResolve("/c", "../../c");
|
||||
|
||||
setBase("s://h/p?q#f"); // A simple base.
|
||||
// Simple references:
|
||||
testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
|
||||
testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
|
||||
|
@ -195,7 +242,7 @@ testUriPerRFCs() {
|
|||
testResolve("s://h/p?Q#F%20", "?Q#F%20");
|
||||
testResolve("s://h/p?q#F%20", "#F%20");
|
||||
|
||||
base = Uri.parse("s://h/p1/p2/p3"); // A simple base with a path.
|
||||
setBase("s://h/p1/p2/p3"); // A simple base with a path.
|
||||
testResolve("s://h/p1/p2/", ".");
|
||||
testResolve("s://h/p1/p2/", "./");
|
||||
testResolve("s://h/p1/", "..");
|
||||
|
@ -206,7 +253,7 @@ testUriPerRFCs() {
|
|||
testResolve("s://h/", "../../../..");
|
||||
testResolve("s://h/", "../../../../");
|
||||
|
||||
base = Uri.parse("s://h/p?q#f%20"); // A non-simpe base.
|
||||
setBase("s://h/p?q#f%20"); // A non-simpe base.
|
||||
// Simple references:
|
||||
testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
|
||||
testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
|
||||
|
@ -222,7 +269,7 @@ testUriPerRFCs() {
|
|||
testResolve("s://h/p?Q#F%20", "?Q#F%20");
|
||||
testResolve("s://h/p?q#F%20", "#F%20");
|
||||
|
||||
base = Uri.parse("S://h/p1/p2/p3"); // A non-simple base with a path.
|
||||
setBase("S://h/p1/p2/p3"); // A non-simple base with a path.
|
||||
testResolve("s://h/p1/p2/", ".");
|
||||
testResolve("s://h/p1/p2/", "./");
|
||||
testResolve("s://h/p1/", "..");
|
||||
|
@ -233,7 +280,7 @@ testUriPerRFCs() {
|
|||
testResolve("s://h/", "../../../..");
|
||||
testResolve("s://h/", "../../../../");
|
||||
|
||||
base = Uri.parse("../../../"); // A simple relative path.
|
||||
setBase("../../../"); // A simple relative path.
|
||||
testResolve("../../../a", "a");
|
||||
testResolve("../../../../a", "../a");
|
||||
testResolve("../../../a%20", "a%20");
|
||||
|
@ -242,8 +289,7 @@ testUriPerRFCs() {
|
|||
// Tests covering the branches of the merge algorithm in RFC 3986
|
||||
// with both simple and complex base URIs.
|
||||
for (var b in ["s://a/pa/pb?q#f", "s://a/pa/pb?q#f%20"]) {
|
||||
var origBase = Uri.parse(b);
|
||||
base = origBase;
|
||||
setBase(b);
|
||||
|
||||
// if defined(R.scheme) then ...
|
||||
testResolve("s2://a2/p2?q2#f2", "s2://a2/p2?q2#f2");
|
||||
|
@ -271,40 +317,40 @@ testUriPerRFCs() {
|
|||
// (Cover the merge function and the remove-dot-fragments functions too).
|
||||
|
||||
// If base has authority and empty path ...
|
||||
var emptyPathBase = Uri.parse(b.replaceFirst("/pa/pb", ""));
|
||||
base = emptyPathBase;
|
||||
var emptyPathBase = b.replaceFirst("/pa/pb", "");
|
||||
setBase(emptyPathBase);
|
||||
testResolve("s://a/p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s://a/p2#f2", "p2#f2");
|
||||
testResolve("s://a/p2", "p2");
|
||||
|
||||
base = origBase;
|
||||
setBase(b);
|
||||
// otherwise
|
||||
// (Cover both no authority and non-empty path and both).
|
||||
var noAuthEmptyPathBase = Uri.parse(b.replaceFirst("//a/pa/pb", ""));
|
||||
var noAuthAbsPathBase = Uri.parse(b.replaceFirst("//a", ""));
|
||||
var noAuthRelPathBase = Uri.parse(b.replaceFirst("//a/", ""));
|
||||
var noAuthRelSinglePathBase = Uri.parse(b.replaceFirst("//a/pa/", ""));
|
||||
var noAuthEmptyPathBase = b.replaceFirst("//a/pa/pb", "");
|
||||
var noAuthAbsPathBase = b.replaceFirst("//a", "");
|
||||
var noAuthRelPathBase = b.replaceFirst("//a/", "");
|
||||
var noAuthRelSinglePathBase = b.replaceFirst("//a/pa/", "");
|
||||
|
||||
testResolve("s://a/pa/p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s://a/pa/p2#f2", "p2#f2");
|
||||
testResolve("s://a/pa/p2", "p2");
|
||||
|
||||
base = noAuthEmptyPathBase;
|
||||
setBase(noAuthEmptyPathBase);
|
||||
testResolve("s:p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s:p2#f2", "p2#f2");
|
||||
testResolve("s:p2", "p2");
|
||||
|
||||
base = noAuthAbsPathBase;
|
||||
setBase(noAuthAbsPathBase);
|
||||
testResolve("s:/pa/p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s:/pa/p2#f2", "p2#f2");
|
||||
testResolve("s:/pa/p2", "p2");
|
||||
|
||||
base = noAuthRelPathBase;
|
||||
setBase(noAuthRelPathBase);
|
||||
testResolve("s:pa/p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s:pa/p2#f2", "p2#f2");
|
||||
testResolve("s:pa/p2", "p2");
|
||||
|
||||
base = noAuthRelSinglePathBase;
|
||||
setBase(noAuthRelSinglePathBase);
|
||||
testResolve("s:p2?q2#f2", "p2?q2#f2");
|
||||
testResolve("s:p2#f2", "p2#f2");
|
||||
testResolve("s:p2", "p2");
|
||||
|
@ -314,7 +360,7 @@ testUriPerRFCs() {
|
|||
// A. if input buffer starts with "../" or "./".
|
||||
// This only happens if base has only a single (may be empty) segment and
|
||||
// no slash.
|
||||
base = emptyPathBase;
|
||||
setBase(emptyPathBase);
|
||||
testResolve("s://a/p2", "../p2");
|
||||
testResolve("s://a/", "../");
|
||||
testResolve("s://a/", "..");
|
||||
|
@ -324,7 +370,7 @@ testUriPerRFCs() {
|
|||
testResolve("s://a/p2", "../../p2");
|
||||
testResolve("s://a/p2", "../../././p2");
|
||||
|
||||
base = noAuthRelSinglePathBase;
|
||||
setBase(noAuthRelSinglePathBase);
|
||||
testResolve("s:p2", "../p2");
|
||||
testResolve("s:", "../");
|
||||
testResolve("s:", "..");
|
||||
|
@ -337,31 +383,30 @@ testUriPerRFCs() {
|
|||
// B. if input buffer starts with "/./" or is "/.". replace with "/".
|
||||
// (The URI implementation removes the "." path segments when parsing,
|
||||
// so this case isn't handled by merge).
|
||||
base = origBase;
|
||||
setBase(b);
|
||||
testResolve("s://a/pa/p2", "./p2");
|
||||
|
||||
// C. if input buffer starts with "/../" or is "/..", replace with "/"
|
||||
// and remove preceeding segment.
|
||||
testResolve("s://a/p2", "../p2");
|
||||
var longPathBase = Uri.parse(b.replaceFirst("/pb", "/pb/pc/pd"));
|
||||
base = longPathBase;
|
||||
var longPathBase = b.replaceFirst("/pb", "/pb/pc/pd");
|
||||
setBase(longPathBase);
|
||||
testResolve("s://a/pa/pb/p2", "../p2");
|
||||
testResolve("s://a/pa/p2", "../../p2");
|
||||
testResolve("s://a/p2", "../../../p2");
|
||||
testResolve("s://a/p2", "../../../../p2");
|
||||
var noAuthRelLongPathBase =
|
||||
Uri.parse(b.replaceFirst("//a/pa/pb", "pa/pb/pc/pd"));
|
||||
base = noAuthRelLongPathBase;
|
||||
var noAuthRelLongPathBase = b.replaceFirst("//a/pa/pb", "pa/pb/pc/pd");
|
||||
setBase(noAuthRelLongPathBase);
|
||||
testResolve("s:pa/pb/p2", "../p2");
|
||||
testResolve("s:pa/p2", "../../p2");
|
||||
testResolve("s:/p2", "../../../p2");
|
||||
testResolve("s:/p2", "../../../../p2");
|
||||
|
||||
// D. if the input buffer contains only ".." or ".", remove it.
|
||||
base = noAuthEmptyPathBase;
|
||||
setBase(noAuthEmptyPathBase);
|
||||
testResolve("s:", "..");
|
||||
testResolve("s:", ".");
|
||||
base = noAuthRelSinglePathBase;
|
||||
setBase(noAuthRelSinglePathBase);
|
||||
testResolve("s:", "..");
|
||||
testResolve("s:", ".");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue