url: support IPv6 literals (RFC 2732) (#292)

This commit is contained in:
Jan Engelhardt 2024-01-30 12:38:41 +01:00 committed by GitHub
parent 8bed1cc743
commit 874a1d8c33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 95 additions and 13 deletions

View File

@ -54,6 +54,12 @@ public:
return c >= '0' && c <= '9';
}
static bool isXDigit(const char_t c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
static bool isAlpha(const char_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');

View File

@ -126,7 +126,7 @@ const string url::build() const {
oss << "@";
}
oss << urlUtils::encode(m_host);
oss << urlUtils::encodeHost(m_host);
if (m_port != UNSPECIFIED_PORT) {
@ -166,6 +166,53 @@ const string url::build() const {
}
static bool extractHostIPv6(string& hostPart, string& host, string& port) {
if (hostPart[0] != '[') {
return false;
}
const auto len = hostPart.find(']');
if (len == string::npos) {
return false;
}
host.assign(&hostPart[1], len - 1);
if (hostPart[len] == '\0') {
return true;
}
if (hostPart[len + 1] != ':') {
return false;
}
port = hostPart.substr(len + 2);
return true;
}
static void extractHost(string& hostPart, string& host, string& port) {
if (extractHostIPv6(hostPart, host, port)) {
return;
}
const size_t colonPos = hostPart.find(':');
if (colonPos == string::npos) {
host = utility::stringUtils::trim(hostPart);
} else {
host = utility::stringUtils::trim(string(hostPart.begin(), hostPart.begin() + colonPos));
port = utility::stringUtils::trim(string(hostPart.begin() + colonPos + 1, hostPart.end()));
}
}
void url::parse(const string& str) {
// Protocol
@ -222,20 +269,9 @@ void url::parse(const string& str) {
}
// Host/port
const size_t colonPos = hostPart.find(':');
string host;
string port;
if (colonPos == string::npos) {
host = utility::stringUtils::trim(hostPart);
} else {
host = utility::stringUtils::trim(string(hostPart.begin(), hostPart.begin() + colonPos));
port = utility::stringUtils::trim(string(hostPart.begin() + colonPos + 1, hostPart.end()));
}
extractHost(hostPart, host, port);
// Path
string path = utility::stringUtils::trim(string(str.begin() + slashPos, str.end()));

View File

@ -66,6 +66,22 @@ const string urlUtils::encode(const string& s) {
}
const string urlUtils::encodeHost(const string& s) {
if (s.size() == 0) {
return encode(s);
}
for (auto it = s.begin() + 1 ; it != s.end() - 1 ; ++it) {
if (!parserHelpers::isXDigit(*it) && *it != ':') {
return encode(s);
}
}
return "[" + s + "]";
}
const string urlUtils::decode(const string& s) {
string result;

View File

@ -44,6 +44,10 @@ public:
*/
static const string encode(const string& s);
/** Encode the host portion of a URL string.
*/
static const string encodeHost(const string& s);
/** Decode an hex-encoded URL (see encode()).
*/
static const string decode(const string& s);

View File

@ -35,6 +35,7 @@ VMIME_TEST_SUITE_BEGIN(urlTest)
VMIME_TEST(testParse3)
VMIME_TEST(testParse4)
VMIME_TEST(testParse5)
VMIME_TEST(testParseIPv6)
VMIME_TEST(testGenerate)
VMIME_TEST(testUtilsEncode)
VMIME_TEST(testUtilsDecode)
@ -201,6 +202,18 @@ VMIME_TEST_SUITE_BEGIN(urlTest)
VASSERT_EQ("4", "myserver.com", u1.getHost());
}
void testParseIPv6() {
vmime::utility::url u1("", "");
VASSERT_EQ("1", true, parseHelper(u1, "http://a:b@[::1]:80/p"));
VASSERT_EQ("2", "a", u1.getUsername());
VASSERT_EQ("3", "b", u1.getPassword());
VASSERT_EQ("4", "::1", u1.getHost());
VASSERT_EQ("5", 80, u1.getPort());
VASSERT_EQ("6", "/p", u1.getPath());
}
void testGenerate() {
vmime::utility::url u1("proto", "host", 12345, "path", "user", "password");
@ -235,6 +248,13 @@ VMIME_TEST_SUITE_BEGIN(urlTest)
"proto://host/?%26=%3D",
static_cast <vmime::string>(u3)
);
vmime::utility::url u5("http", "::1", 80, "p");
VASSERT_EQ(
"4",
"http://[::1]:80/p",
static_cast <vmime::string>(u5)
);
}
void testUtilsEncode() {