Fix bugs identified by the testsuites

* Handle resolving . and .. in path properly
* Make the subtle distinction between empty and unset for the authority,
fragment and query.
This commit is contained in:
Adrien Destugues
2014-06-04 10:36:10 +02:00
parent e29e0a0d2a
commit b5bde4ad81
+154 -31
View File
@@ -30,7 +30,8 @@ BUrl::BUrl(const char* url)
fPort(0), fPort(0),
fPath(), fPath(),
fRequest(), fRequest(),
fHasAuthority(false) fHasAuthority(false),
fHasFragment(false)
{ {
SetUrlString(url); SetUrlString(url);
} }
@@ -46,7 +47,8 @@ BUrl::BUrl(BMessage* archive)
fPort(0), fPort(0),
fPath(), fPath(),
fRequest(), fRequest(),
fHasAuthority(false) fHasAuthority(false),
fHasFragment(false)
{ {
BString url; BString url;
@@ -68,7 +70,8 @@ BUrl::BUrl(const BUrl& other)
fPort(0), fPort(0),
fPath(), fPath(),
fRequest(), fRequest(),
fHasAuthority(false) fHasAuthority(false),
fHasFragment(false)
{ {
*this = other; *this = other;
} }
@@ -84,24 +87,26 @@ BUrl::BUrl(const BUrl& base, const BString& location)
fPort(0), fPort(0),
fPath(), fPath(),
fRequest(), fRequest(),
fHasAuthority(false) fHasAuthority(false),
fHasFragment(false)
{ {
// This implements the algorithm in RFC3986, Section 5.2. // This implements the algorithm in RFC3986, Section 5.2.
BUrl relative(location); BUrl relative(location);
if (relative.HasProtocol()) { if (relative.HasProtocol()) {
SetProtocol(relative.Protocol()); SetProtocol(relative.Protocol());
SetAuthority(relative.Authority()); if (relative.HasAuthority())
SetPath(relative.Path()); // TODO _RemoveDotSegments() SetAuthority(relative.Authority());
SetPath(relative.Path());
SetRequest(relative.Request()); SetRequest(relative.Request());
} else { } else {
if (relative.HasAuthority()) { if (relative.HasAuthority()) {
SetAuthority(relative.Authority()); SetAuthority(relative.Authority());
SetPath(relative.Path()); // TODO _RemoveDotSegments() SetPath(relative.Path());
SetRequest(relative.Request()); SetRequest(relative.Request());
} else { } else {
if (relative.Path().IsEmpty()) { if (relative.Path().IsEmpty()) {
SetPath(base.Path()); _SetPathUnsafe(base.Path());
if (relative.HasRequest()) if (relative.HasRequest())
SetRequest(relative.Request()); SetRequest(relative.Request());
else else
@@ -110,22 +115,20 @@ BUrl::BUrl(const BUrl& base, const BString& location)
if (relative.Path()[0] == '/') if (relative.Path()[0] == '/')
SetPath(relative.Path()); SetPath(relative.Path());
else { else {
BString path = base.Path(); BString path = base._MergePath(relative.Path());
// Remove last part of path (the file, if any) so we get the
// "current directory"
path.Truncate(path.FindLast('/') + 1);
path += relative.Path();
// TODO _RemoveDotSegments()
SetPath(path); SetPath(path);
} }
SetRequest(relative.Request()); SetRequest(relative.Request());
} }
SetAuthority(base.Authority());
if (base.HasAuthority())
SetAuthority(base.Authority());
} }
SetProtocol(base.Protocol()); SetProtocol(base.Protocol());
} }
SetFragment(relative.Fragment()); if (relative.HasFragment())
SetFragment(relative.Fragment());
} }
@@ -139,7 +142,8 @@ BUrl::BUrl()
fPort(0), fPort(0),
fPath(), fPath(),
fRequest(), fRequest(),
fHasAuthority(false) fHasAuthority(false),
fHasFragment(false)
{ {
_ResetFields(); _ResetFields();
} }
@@ -220,9 +224,80 @@ BUrl::SetPort(int port)
BUrl& BUrl&
BUrl::SetPath(const BString& path) BUrl::SetPath(const BString& path)
{ {
fPath = path; // Implements RFC3986 section 5.2.4, "Remove dot segments"
fHasPath = true; // RFC says an empty path is still a path
fUrlStringValid = false; // 1.
BString output;
BString input(path);
// 2.
while(!input.IsEmpty())
{
// 2.A.
if (input.StartsWith("./"))
{
input.Remove(0, 2);
continue;
}
if (input.StartsWith("../"))
{
input.Remove(0, 3);
continue;
}
// 2.B.
if (input.StartsWith("/./"))
{
input.Remove(0, 2);
continue;
}
if (input == "/.")
{
input.Remove(1, 1);
continue;
}
// 2.C.
if (input.StartsWith("/../"))
{
input.Remove(0, 3);
output.Truncate(output.FindLast('/'));
continue;
}
if (input == "/..")
{
input.Remove(1, 2);
output.Truncate(output.FindLast('/'));
continue;
}
// 2.D.
if (input == "." || input == "..")
{
break;
}
if (input == "/.")
{
input.Remove(1, 1);
continue;
}
// 2.E.
int slashpos = input.FindFirst('/', 1);
if (slashpos > 0) {
output.Append(input, slashpos);
input.Remove(0, slashpos);
} else {
output.Append(input);
break;
}
}
_SetPathUnsafe(output);
return *this; return *this;
} }
@@ -241,7 +316,7 @@ BUrl&
BUrl::SetFragment(const BString& fragment) BUrl::SetFragment(const BString& fragment)
{ {
fFragment = fragment; fFragment = fragment;
fHasFragment = !fFragment.IsEmpty(); fHasFragment = true;
fUrlStringValid = false; fUrlStringValid = false;
return *this; return *this;
} }
@@ -657,9 +732,13 @@ BUrl::_ExplodeUrlString(const BString& url)
fHasProtocol = true; fHasProtocol = true;
// Authority (including user credentials, host, and port // Authority (including user credentials, host, and port
url.CopyInto(fAuthority, match.GroupStartOffsetAt(3), if (match.GroupEndOffsetAt(2) - match.GroupStartOffsetAt(2) > 0)
match.GroupEndOffsetAt(3) - match.GroupStartOffsetAt(3)); {
SetAuthority(fAuthority); url.CopyInto(fAuthority, match.GroupStartOffsetAt(3),
match.GroupEndOffsetAt(3) - match.GroupStartOffsetAt(3));
SetAuthority(fAuthority);
} else
fHasAuthority = false;
// Path // Path
url.CopyInto(fPath, match.GroupStartOffsetAt(4), url.CopyInto(fPath, match.GroupStartOffsetAt(4),
@@ -668,16 +747,56 @@ BUrl::_ExplodeUrlString(const BString& url)
fHasPath = true; fHasPath = true;
// Query // Query
url.CopyInto(fRequest, match.GroupStartOffsetAt(6), if (match.GroupEndOffsetAt(5) - match.GroupStartOffsetAt(5) > 0)
match.GroupEndOffsetAt(6) - match.GroupStartOffsetAt(6)); {
if (!fRequest.IsEmpty()) url.CopyInto(fRequest, match.GroupStartOffsetAt(6),
match.GroupEndOffsetAt(6) - match.GroupStartOffsetAt(6));
fHasRequest = true; fHasRequest = true;
} else {
fRequest = "";
fHasRequest = false;
}
// Fragment // Fragment
url.CopyInto(fFragment, match.GroupStartOffsetAt(8), if (match.GroupEndOffsetAt(7) - match.GroupStartOffsetAt(7) > 0)
match.GroupEndOffsetAt(8) - match.GroupStartOffsetAt(8)); {
if (!fFragment.IsEmpty()) url.CopyInto(fFragment, match.GroupStartOffsetAt(8),
match.GroupEndOffsetAt(8) - match.GroupStartOffsetAt(8));
fHasFragment = true; fHasFragment = true;
} else {
fFragment = "";
fHasFragment = false;
}
}
BString
BUrl::_MergePath(const BString& relative) const
{
// This implements RFC3986, Section 5.2.3.
if (HasAuthority() && fPath == "")
{
BString result("/");
result << relative;
return result;
}
BString result(fPath);
result.Truncate(result.FindLast("/") + 1);
result << relative;
return result;
}
// This sets the path without normalizing it. If fed with a path that has . or
// .. segments, this would make the URL invalid.
void
BUrl::_SetPathUnsafe(const BString& path)
{
fPath = path;
fHasPath = true; // RFC says an empty path is still a path
fUrlStringValid = false;
} }
@@ -690,10 +809,14 @@ BUrl::SetAuthority(const BString& authority)
fHasUserInfo = false; fHasUserInfo = false;
fHasHost = false; fHasHost = false;
// An empty authority is still an authority, making it possible to have
// URLs such as file:///path/to/file.
// TODO however, there is no way to unset the authority once it is set...
// We may want to take a const char* parameter and allow NULL.
fHasAuthority = true;
if (fAuthority.IsEmpty()) if (fAuthority.IsEmpty())
return; return;
fHasAuthority = true;
int32 userInfoEnd = fAuthority.FindFirst('@'); int32 userInfoEnd = fAuthority.FindFirst('@');
// URL contains userinfo field // URL contains userinfo field