Unverified Commit 2ae725f3 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #651 from capnproto/harris/round-trip-eq-sign-in-url-query

Distinguish between empty- and null-valued query parameters
parents d08ec534 1065c264
...@@ -30,6 +30,9 @@ Url parseAndCheck(kj::StringPtr originalText, kj::StringPtr expectedRestringifie ...@@ -30,6 +30,9 @@ Url parseAndCheck(kj::StringPtr originalText, kj::StringPtr expectedRestringifie
if (expectedRestringified == nullptr) expectedRestringified = originalText; if (expectedRestringified == nullptr) expectedRestringified = originalText;
auto url = Url::parse(originalText); auto url = Url::parse(originalText);
KJ_EXPECT(kj::str(url) == expectedRestringified, url, originalText, expectedRestringified); KJ_EXPECT(kj::str(url) == expectedRestringified, url, originalText, expectedRestringified);
// Make sure clones also restringify to the expected string.
auto clone = url.clone();
KJ_EXPECT(kj::str(clone) == expectedRestringified, clone, originalText, expectedRestringified);
return url; return url;
} }
...@@ -104,6 +107,36 @@ KJ_TEST("parse / stringify URL") { ...@@ -104,6 +107,36 @@ KJ_TEST("parse / stringify URL") {
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply"); KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
} }
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=qux&corge=#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "qux");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == nullptr);
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{
auto url = parseAndCheck("https://capnproto.org/foo/bar?baz=&corge=grault#garply");
KJ_EXPECT(url.scheme == "https");
KJ_EXPECT(url.userInfo == nullptr);
KJ_EXPECT(url.host == "capnproto.org");
KJ_EXPECT(url.path.asPtr() == kj::ArrayPtr<const StringPtr>({"foo", "bar"}));
KJ_EXPECT(!url.hasTrailingSlash);
KJ_ASSERT(url.query.size() == 2);
KJ_EXPECT(url.query[0].name == "baz");
KJ_EXPECT(url.query[0].value == "");
KJ_EXPECT(url.query[1].name == "corge");
KJ_EXPECT(url.query[1].value == "grault");
KJ_EXPECT(KJ_ASSERT_NONNULL(url.fragment) == "garply");
}
{ {
auto url = parseAndCheck("https://capnproto.org/foo/bar/?baz=qux&corge=grault#garply"); auto url = parseAndCheck("https://capnproto.org/foo/bar/?baz=qux&corge=grault#garply");
KJ_EXPECT(url.scheme == "https"); KJ_EXPECT(url.scheme == "https");
...@@ -353,15 +386,26 @@ KJ_TEST("parse URL failure") { ...@@ -353,15 +386,26 @@ KJ_TEST("parse URL failure") {
void parseAndCheckRelative(kj::StringPtr base, kj::StringPtr relative, kj::StringPtr expected) { void parseAndCheckRelative(kj::StringPtr base, kj::StringPtr relative, kj::StringPtr expected) {
auto parsed = Url::parse(base).parseRelative(relative); auto parsed = Url::parse(base).parseRelative(relative);
KJ_EXPECT(kj::str(parsed) == expected, parsed, expected); KJ_EXPECT(kj::str(parsed) == expected, parsed, expected);
auto clone = parsed.clone();
KJ_EXPECT(kj::str(clone) == expected, clone, expected);
} }
KJ_TEST("parse relative URL") { KJ_TEST("parse relative URL") {
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"#grault", "#grault",
"https://capnproto.org/foo/bar?baz=qux#grault"); "https://capnproto.org/foo/bar?baz=qux#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz#corge",
"#grault",
"https://capnproto.org/foo/bar?baz#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=#corge",
"#grault",
"https://capnproto.org/foo/bar?baz=#grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault", "?grault",
"https://capnproto.org/foo/bar?grault"); "https://capnproto.org/foo/bar?grault");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault=",
"https://capnproto.org/foo/bar?grault=");
parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge", parseAndCheckRelative("https://capnproto.org/foo/bar?baz=qux#corge",
"?grault+garply=waldo", "?grault+garply=waldo",
"https://capnproto.org/foo/bar?grault+garply=waldo"); "https://capnproto.org/foo/bar?grault+garply=waldo");
......
...@@ -111,7 +111,9 @@ Url Url::clone() const { ...@@ -111,7 +111,9 @@ Url Url::clone() const {
KJ_MAP(part, path) { return kj::str(part); }, KJ_MAP(part, path) { return kj::str(part); },
hasTrailingSlash, hasTrailingSlash,
KJ_MAP(param, query) -> QueryParam { KJ_MAP(param, query) -> QueryParam {
return { kj::str(param.name), kj::str(param.value) }; // Preserve the "allocated-ness" of `param.value` with this careful copy.
return { kj::str(param.name), param.value.begin() == nullptr ? kj::String()
: kj::str(param.value) };
}, },
fragment.map([](const String& s) { return kj::str(s); }) fragment.map([](const String& s) { return kj::str(s); })
}; };
...@@ -349,8 +351,10 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { ...@@ -349,8 +351,10 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
} while (text.startsWith("&")); } while (text.startsWith("&"));
} else if (!hadNewAuthority && !hadNewPath) { } else if (!hadNewAuthority && !hadNewPath) {
// copy query // copy query
result.query = KJ_MAP(param, this->query) { result.query = KJ_MAP(param, this->query) -> QueryParam {
return QueryParam { kj::str(param.name), kj::str(param.value) }; // Preserve the "allocated-ness" of `param.value` with this careful copy.
return { kj::str(param.name), param.value.begin() == nullptr ? kj::String()
: kj::str(param.value) };
}; };
} }
...@@ -418,7 +422,7 @@ String Url::toString(Context context) const { ...@@ -418,7 +422,7 @@ String Url::toString(Context context) const {
chars.add(first ? '?' : '&'); chars.add(first ? '?' : '&');
first = false; first = false;
chars.addAll(encodeWwwForm(param.name)); chars.addAll(encodeWwwForm(param.name));
if (param.value.size() > 0) { if (param.value.begin() != nullptr) {
chars.add('='); chars.add('=');
chars.addAll(encodeWwwForm(param.value)); chars.addAll(encodeWwwForm(param.value));
} }
......
...@@ -63,7 +63,14 @@ struct Url { ...@@ -63,7 +63,14 @@ struct Url {
}; };
Vector<QueryParam> query; Vector<QueryParam> query;
// Query, e.g. from "?key=value&key2=value2". If a component of the query contains no '=' sign, // Query, e.g. from "?key=value&key2=value2". If a component of the query contains no '=' sign,
// it will be parsed as a key with an empty value. // it will be parsed as a key with a null value, and later serialized with no '=' sign if you call
// Url::toString().
//
// To distinguish between null-valued and empty-valued query parameters, we test whether
// QueryParam::value is an allocated or unallocated string. For example:
//
// QueryParam { kj::str("name"), nullptr } // Null-valued; will not have an '=' sign.
// QueryParam { kj::str("name"), kj::str("") } // Empty-valued; WILL have an '=' sign.
Maybe<String> fragment; Maybe<String> fragment;
// The stuff after the '#' character (not including the '#' character itself), if present. // The stuff after the '#' character (not including the '#' character itself), if present.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment