Support : Validation for Host Portion of URL

Partly Fixes #14377

Change-Id: Ia611d3653d2c16c6dcdc48ce57bd61bb6e6db366
Reviewed-on: https://review.haiku-os.org/476
Reviewed-by: Adrien Destugues <[email protected]>
This commit is contained in:
Andrew Lindesay
2018-08-25 18:50:36 +00:00
committed by waddlesplash
parent df8b692ac0
commit 8f30879b98
4 changed files with 150 additions and 56 deletions
+10 -2
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2016 Haiku Inc. All rights reserved. * Copyright 2010-2018 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
#ifndef _B_URL_H_ #ifndef _B_URL_H_
@@ -113,10 +113,18 @@ private:
static BString _DoUrlDecodeChunk(const BString& chunk, static BString _DoUrlDecodeChunk(const BString& chunk,
bool strict); bool strict);
bool _IsProtocolValid(); bool _IsHostValid() const;
bool _IsHostIPV6Valid(size_t offset,
int32 length) const;
bool _IsProtocolValid() const;
static bool _IsUnreserved(char c); static bool _IsUnreserved(char c);
static bool _IsGenDelim(char c); static bool _IsGenDelim(char c);
static bool _IsSubDelim(char c); static bool _IsSubDelim(char c);
static bool _IsIPV6Char(char c);
static bool _IsUsernameChar(char c);
static bool _IsPasswordChar(char c);
static bool _IsHostChar(char c);
static bool _IsPortChar(char c);
BString _UrlMimeType() const; BString _UrlMimeType() const;
+96 -46
View File
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2016 Haiku Inc. All rights reserved. * Copyright 2010-2018 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
* *
* Authors: * Authors:
@@ -497,11 +497,19 @@ BUrl::IsValid() const
if (!fHasProtocol) if (!fHasProtocol)
return false; return false;
if (!_IsProtocolValid())
return false;
// it is possible that there can be an authority but no host.
// wierd://tea:tree@/x
if (HasHost() && !(fHost.IsEmpty() && HasAuthority()) && !_IsHostValid())
return false;
if (fProtocol == "http" || fProtocol == "https" || fProtocol == "ftp" if (fProtocol == "http" || fProtocol == "https" || fProtocol == "ftp"
|| fProtocol == "ipp" || fProtocol == "afp" || fProtocol == "telnet" || fProtocol == "ipp" || fProtocol == "afp" || fProtocol == "telnet"
|| fProtocol == "gopher" || fProtocol == "nntp" || fProtocol == "sftp" || fProtocol == "gopher" || fProtocol == "nntp" || fProtocol == "sftp"
|| fProtocol == "finger" || fProtocol == "pop" || fProtocol == "imap") { || fProtocol == "finger" || fProtocol == "pop" || fProtocol == "imap") {
return fHasHost && !fHost.IsEmpty(); return HasHost() && !fHost.IsEmpty();
} }
if (fProtocol == "file") if (fProtocol == "file")
@@ -1124,40 +1132,6 @@ enum authority_parse_state {
AUTHORITY_COMPLETE AUTHORITY_COMPLETE
}; };
static bool
authority_is_username_char(char c)
{
return !(c == ':' || c == '@');
}
static bool
authority_is_password_char(char c)
{
return !(c == '@');
}
static bool
authority_is_ipv6_host_char(char c) {
return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')
|| (c >= '0' && c <= '9') || c == ':';
}
static bool
authority_is_host_char(char c) {
return !(c == ':' || c == '/');
}
static bool
authority_is_port_char(char c) {
return c >= '0' && c <= '9';
}
void void
BUrl::SetAuthority(const BString& authority) BUrl::SetAuthority(const BString& authority)
{ {
@@ -1185,8 +1159,7 @@ BUrl::SetAuthority(const BString& authority)
{ {
if (hasUsernamePassword) { if (hasUsernamePassword) {
int32 end_username = char_offset_until_fn_false( int32 end_username = char_offset_until_fn_false(
authority_c, length, offset, authority_c, length, offset, _IsUsernameChar);
authority_is_username_char);
SetUserName(BString(&authority_c[offset], SetUserName(BString(&authority_c[offset],
end_username - offset)); end_username - offset));
@@ -1204,8 +1177,7 @@ BUrl::SetAuthority(const BString& authority)
if (hasUsernamePassword && ':' == authority[offset]) { if (hasUsernamePassword && ':' == authority[offset]) {
offset++; // move past the delimiter offset++; // move past the delimiter
int32 end_password = char_offset_until_fn_false( int32 end_password = char_offset_until_fn_false(
authority_c, length, offset, authority_c, length, offset, _IsPasswordChar);
authority_is_password_char);
SetPassword(BString(&authority_c[offset], SetPassword(BString(&authority_c[offset],
end_password - offset)); end_password - offset));
@@ -1232,8 +1204,7 @@ BUrl::SetAuthority(const BString& authority)
if (authority_c[offset] == '[') { if (authority_c[offset] == '[') {
int32 end_ipv6_host = char_offset_until_fn_false( int32 end_ipv6_host = char_offset_until_fn_false(
authority_c, length, offset + 1, authority_c, length, offset + 1, _IsIPV6Char);
authority_is_ipv6_host_char);
if (authority_c[end_ipv6_host] == ']') { if (authority_c[end_ipv6_host] == ']') {
SetHost(BString(&authority_c[offset], SetHost(BString(&authority_c[offset],
@@ -1247,7 +1218,7 @@ BUrl::SetAuthority(const BString& authority)
if (AUTHORITY_HOST == state) { if (AUTHORITY_HOST == state) {
int32 end_host = char_offset_until_fn_false( int32 end_host = char_offset_until_fn_false(
authority_c, length, offset, authority_is_host_char); authority_c, length, offset, _IsHostChar);
SetHost(BString(&authority_c[offset], end_host - offset)); SetHost(BString(&authority_c[offset], end_host - offset));
state = AUTHORITY_PORT; state = AUTHORITY_PORT;
@@ -1262,7 +1233,7 @@ BUrl::SetAuthority(const BString& authority)
if (authority_c[offset] == ':') { if (authority_c[offset] == ':') {
offset++; offset++;
int32 end_port = char_offset_until_fn_false( int32 end_port = char_offset_until_fn_false(
authority_c, length, offset, authority_is_port_char); authority_c, length, offset, _IsPortChar);
SetPort(atoi(&authority_c[offset])); SetPort(atoi(&authority_c[offset]));
offset = end_port; offset = end_port;
} }
@@ -1345,7 +1316,50 @@ BUrl::_DoUrlDecodeChunk(const BString& chunk, bool strict)
bool bool
BUrl::_IsProtocolValid() BUrl::_IsHostIPV6Valid(size_t offset, int32 length) const
{
for (int32 i = 0; i < length; i++) {
char c = fHost[offset + i];
if (!_IsIPV6Char(c))
return false;
}
return length > 0;
}
bool
BUrl::_IsHostValid() const
{
if (fHost.StartsWith("[") && fHost.EndsWith("]"))
return _IsHostIPV6Valid(1, fHost.Length() - 2);
bool lastWasDot = false;
for (int32 i = 0; i < fHost.Length(); i++) {
char c = fHost[i];
if (c == '.') {
if (lastWasDot || i == 0)
return false;
lastWasDot = true;
} else {
lastWasDot = false;
}
if (!_IsHostChar(c) && c != '.') {
// the underscore is technically not allowed, but occurs sometimes
// in the wild.
return false;
}
}
return true;
}
bool
BUrl::_IsProtocolValid() const
{ {
for (int8 index = 0; index < fProtocol.Length(); index++) { for (int8 index = 0; index < fProtocol.Length(); index++) {
char c = fProtocol[index]; char c = fProtocol[index];
@@ -1356,7 +1370,7 @@ BUrl::_IsProtocolValid()
return false; return false;
} }
return fProtocol.Length() > 0; return !fProtocol.IsEmpty();
} }
@@ -1384,6 +1398,42 @@ BUrl::_IsSubDelim(char c)
} }
bool
BUrl::_IsUsernameChar(char c)
{
return !(c == ':' || c == '@');
}
bool
BUrl::_IsPasswordChar(char c)
{
return !(c == '@');
}
bool
BUrl::_IsHostChar(char c)
{
return ((uint8) c) > 127 || isalnum(c) || c == '-' || c == '_' || c == '.'
|| c == '%';
}
bool
BUrl::_IsPortChar(char c)
{
return isdigit(c);
}
bool
BUrl::_IsIPV6Char(char c)
{
return c == ':' || isxdigit(c);
}
BString BString
BUrl::_UrlMimeType() const BUrl::_UrlMimeType() const
{ {
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016, Andrew Lindesay, [email protected]. * Copyright 2016-2018, Andrew Lindesay, [email protected].
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
@@ -34,7 +34,7 @@ NetworkUrlTest::tearDown()
} }
// General Tests --------------------------------------------------------------- // #pragma mark - General Tests
/* /*
This is the "happy days" tests that checks that a URL featuring all of the This is the "happy days" tests that checks that a URL featuring all of the
@@ -103,13 +103,13 @@ void NetworkUrlTest::TestHostWithFragment()
void NetworkUrlTest::TestIpv6HostPortPathAndRequest() void NetworkUrlTest::TestIpv6HostPortPathAndRequest()
{ {
BUrl url("http://[123:123:0:123::123]:8080/some/path?key1=value1"); BUrl url("http://[123:a3:0:E3::123]:8080/some/path?key1=value1");
CPPUNIT_ASSERT(url.IsValid()); CPPUNIT_ASSERT(url.IsValid());
CPPUNIT_ASSERT(url.Protocol() == "http"); CPPUNIT_ASSERT(url.Protocol() == "http");
CPPUNIT_ASSERT(url.HasProtocol()); CPPUNIT_ASSERT(url.HasProtocol());
CPPUNIT_ASSERT(!url.HasUserName()); CPPUNIT_ASSERT(!url.HasUserName());
CPPUNIT_ASSERT(!url.HasPassword()); CPPUNIT_ASSERT(!url.HasPassword());
CPPUNIT_ASSERT(url.Host() == "[123:123:0:123::123]"); CPPUNIT_ASSERT(url.Host() == "[123:a3:0:E3::123]");
CPPUNIT_ASSERT(url.HasHost()); CPPUNIT_ASSERT(url.HasHost());
CPPUNIT_ASSERT(url.Port() == 8080); CPPUNIT_ASSERT(url.Port() == 8080);
CPPUNIT_ASSERT(url.HasPort()); CPPUNIT_ASSERT(url.HasPort());
@@ -154,7 +154,7 @@ void NetworkUrlTest::TestDataUrl()
} }
// Authority Tests (UserName, Password, Host, Port) ---------------------------- // #pragma mark - Authority Tests (UserName, Password, Host, Port)
void NetworkUrlTest::TestWithUserNameAndPasswordNoHostAndPort() void NetworkUrlTest::TestWithUserNameAndPasswordNoHostAndPort()
@@ -299,7 +299,7 @@ void NetworkUrlTest::TestMailTo()
} }
// Various Authority Checks ---------------------------------------------------- // #pragma mark - Various Authority Checks
void NetworkUrlTest::TestAuthorityNoUserName() void NetworkUrlTest::TestAuthorityNoUserName()
@@ -353,7 +353,7 @@ void NetworkUrlTest::TestAuthorityBadPort()
} }
// Invalid Forms --------------------------------------------------------------- // #pragma mark - Invalid Forms
void NetworkUrlTest::TestWhitespaceBefore() void NetworkUrlTest::TestWhitespaceBefore()
@@ -391,7 +391,40 @@ void NetworkUrlTest::TestEmpty()
} }
// Control --------------------------------------------------------------------- // #pragma mark - Host validation
void NetworkUrlTest::TestBadHosts()
{
CPPUNIT_ASSERT_MESSAGE("control check",
BUrl("http://host.example.com").IsValid());
CPPUNIT_ASSERT_MESSAGE("hyphen in middle",
(BUrl("http://host.peppermint_tea.com").IsValid()));
CPPUNIT_ASSERT_MESSAGE("dot at end",
(BUrl("http://host.camomile.co.nz.").IsValid()));
CPPUNIT_ASSERT_MESSAGE("simple host",
(BUrl("http://tumeric").IsValid()));
CPPUNIT_ASSERT_MESSAGE("idn domain encoded",
(BUrl("http://xn--bcher-kva.tld").IsValid()));
CPPUNIT_ASSERT_MESSAGE("idn domain unencoded",
(BUrl("http://www.b\xc3\xbcch.at").IsValid()));
CPPUNIT_ASSERT_MESSAGE("dot at start",
!(BUrl("http://.host.example.com").IsValid()));
CPPUNIT_ASSERT_MESSAGE("double dot in domain",
!(BUrl("http://host.example..com").IsValid()));
CPPUNIT_ASSERT_MESSAGE("double dot",
!(BUrl("http://host.example..com").IsValid()));
CPPUNIT_ASSERT_MESSAGE("unexpected characters",
!(BUrl("http://<unexpected.characters>").IsValid()));
CPPUNIT_ASSERT_MESSAGE("whitespace",
!(BUrl("http://host.exa ple.com").IsValid()));
}
// #pragma mark - Control
/*static*/ void /*static*/ void
@@ -467,6 +500,8 @@ NetworkUrlTest::AddTests(BTestSuite& parent)
&NetworkUrlTest::TestFileUrl)); &NetworkUrlTest::TestFileUrl));
suite.addTest(new CppUnit::TestCaller<NetworkUrlTest>( suite.addTest(new CppUnit::TestCaller<NetworkUrlTest>(
"NetworkUrlTest::TestValidFullUrl", &NetworkUrlTest::TestValidFullUrl)); "NetworkUrlTest::TestValidFullUrl", &NetworkUrlTest::TestValidFullUrl));
suite.addTest(new CppUnit::TestCaller<NetworkUrlTest>(
"NetworkUrlTest::TestBadHosts", &NetworkUrlTest::TestBadHosts));
parent.addTest("NetworkUrlTest", &suite); parent.addTest("NetworkUrlTest", &suite);
} }
@@ -45,6 +45,7 @@ public:
void TestWhitespaceMiddle(); void TestWhitespaceMiddle();
void TestHttpNoHost(); void TestHttpNoHost();
void TestEmpty(); void TestEmpty();
void TestBadHosts();
static void AddTests(BTestSuite& suite); static void AddTests(BTestSuite& suite);