Skip to content

Commit

Permalink
Fix bugs identified by the testsuites
Browse files Browse the repository at this point in the history
* Handle resolving . and .. in path properly
* Make the subtle distinction between empty and unset for the authority,
fragment and query.
  • Loading branch information
pulkomandy committed Jun 4, 2014
1 parent e29e0a0 commit b5bde4a
Showing 1 changed file with 154 additions and 31 deletions.
185 changes: 154 additions & 31 deletions src/kits/network/libnetapi/Url.cpp
Expand Up @@ -30,7 +30,8 @@ BUrl::BUrl(const char* url)
fPort(0),
fPath(),
fRequest(),
fHasAuthority(false)
fHasAuthority(false),
fHasFragment(false)
{
SetUrlString(url);
}
Expand All @@ -46,7 +47,8 @@ BUrl::BUrl(BMessage* archive)
fPort(0),
fPath(),
fRequest(),
fHasAuthority(false)
fHasAuthority(false),
fHasFragment(false)
{
BString url;

Expand All @@ -68,7 +70,8 @@ BUrl::BUrl(const BUrl& other)
fPort(0),
fPath(),
fRequest(),
fHasAuthority(false)
fHasAuthority(false),
fHasFragment(false)
{
*this = other;
}
Expand All @@ -84,24 +87,26 @@ BUrl::BUrl(const BUrl& base, const BString& location)
fPort(0),
fPath(),
fRequest(),
fHasAuthority(false)
fHasAuthority(false),
fHasFragment(false)
{
// This implements the algorithm in RFC3986, Section 5.2.

BUrl relative(location);
if (relative.HasProtocol()) {
SetProtocol(relative.Protocol());
SetAuthority(relative.Authority());
SetPath(relative.Path()); // TODO _RemoveDotSegments()
if (relative.HasAuthority())
SetAuthority(relative.Authority());
SetPath(relative.Path());
SetRequest(relative.Request());
} else {
if (relative.HasAuthority()) {
SetAuthority(relative.Authority());
SetPath(relative.Path()); // TODO _RemoveDotSegments()
SetPath(relative.Path());
SetRequest(relative.Request());
} else {
if (relative.Path().IsEmpty()) {
SetPath(base.Path());
_SetPathUnsafe(base.Path());
if (relative.HasRequest())
SetRequest(relative.Request());
else
Expand All @@ -110,22 +115,20 @@ BUrl::BUrl(const BUrl& base, const BString& location)
if (relative.Path()[0] == '/')
SetPath(relative.Path());
else {
BString path = base.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()
BString path = base._MergePath(relative.Path());
SetPath(path);
}
SetRequest(relative.Request());
}
SetAuthority(base.Authority());

if (base.HasAuthority())
SetAuthority(base.Authority());
}
SetProtocol(base.Protocol());
}

SetFragment(relative.Fragment());
if (relative.HasFragment())
SetFragment(relative.Fragment());
}


Expand All @@ -139,7 +142,8 @@ BUrl::BUrl()
fPort(0),
fPath(),
fRequest(),
fHasAuthority(false)
fHasAuthority(false),
fHasFragment(false)
{
_ResetFields();
}
Expand Down Expand Up @@ -220,9 +224,80 @@ BUrl::SetPort(int port)
BUrl&
BUrl::SetPath(const BString& path)
{
fPath = path;
fHasPath = true; // RFC says an empty path is still a path
fUrlStringValid = false;
// Implements RFC3986 section 5.2.4, "Remove dot segments"

// 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;
}

Expand All @@ -241,7 +316,7 @@ BUrl&
BUrl::SetFragment(const BString& fragment)
{
fFragment = fragment;
fHasFragment = !fFragment.IsEmpty();
fHasFragment = true;
fUrlStringValid = false;
return *this;
}
Expand Down Expand Up @@ -657,9 +732,13 @@ BUrl::_ExplodeUrlString(const BString& url)
fHasProtocol = true;

// Authority (including user credentials, host, and port
url.CopyInto(fAuthority, match.GroupStartOffsetAt(3),
match.GroupEndOffsetAt(3) - match.GroupStartOffsetAt(3));
SetAuthority(fAuthority);
if (match.GroupEndOffsetAt(2) - match.GroupStartOffsetAt(2) > 0)
{
url.CopyInto(fAuthority, match.GroupStartOffsetAt(3),
match.GroupEndOffsetAt(3) - match.GroupStartOffsetAt(3));
SetAuthority(fAuthority);
} else
fHasAuthority = false;

// Path
url.CopyInto(fPath, match.GroupStartOffsetAt(4),
Expand All @@ -668,16 +747,56 @@ BUrl::_ExplodeUrlString(const BString& url)
fHasPath = true;

// Query
url.CopyInto(fRequest, match.GroupStartOffsetAt(6),
match.GroupEndOffsetAt(6) - match.GroupStartOffsetAt(6));
if (!fRequest.IsEmpty())
if (match.GroupEndOffsetAt(5) - match.GroupStartOffsetAt(5) > 0)
{
url.CopyInto(fRequest, match.GroupStartOffsetAt(6),
match.GroupEndOffsetAt(6) - match.GroupStartOffsetAt(6));
fHasRequest = true;
} else {
fRequest = "";
fHasRequest = false;
}

// Fragment
url.CopyInto(fFragment, match.GroupStartOffsetAt(8),
match.GroupEndOffsetAt(8) - match.GroupStartOffsetAt(8));
if (!fFragment.IsEmpty())
if (match.GroupEndOffsetAt(7) - match.GroupStartOffsetAt(7) > 0)
{
url.CopyInto(fFragment, match.GroupStartOffsetAt(8),
match.GroupEndOffsetAt(8) - match.GroupStartOffsetAt(8));
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;
}


Expand All @@ -690,10 +809,14 @@ BUrl::SetAuthority(const BString& authority)
fHasUserInfo = 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())
return;

fHasAuthority = true;
int32 userInfoEnd = fAuthority.FindFirst('@');

// URL contains userinfo field
Expand Down

0 comments on commit b5bde4a

Please sign in to comment.