Commit ee380345 authored by Kenton Varda's avatar Kenton Varda

Have kj::Url use Vectors instead of Arrays for convenience, and other tweaks.

parent 40b90e4c
...@@ -171,42 +171,36 @@ Maybe<Url> Url::tryParse(StringPtr text, Context context) { ...@@ -171,42 +171,36 @@ Maybe<Url> Url::tryParse(StringPtr text, Context context) {
} }
} }
{ while (text.startsWith("/")) {
Vector<String> path; text = text.slice(1);
while (text.startsWith("/")) { auto part = split(text, END_PATH_PART);
text = text.slice(1); if (part.size() == 2 && part[0] == '.' && part[1] == '.') {
auto part = split(text, END_PATH_PART); if (result.path.size() != 0) {
if (part.size() == 2 && part[0] == '.' && part[1] == '.') { result.path.removeLast();
if (path.size() != 0) {
path.removeLast();
}
result.hasTrailingSlash = true;
} else if (part.size() == 0 || (part.size() == 1 && part[0] == '.')) {
// Collapse consecutive slashes and "/./".
result.hasTrailingSlash = true;
} else {
path.add(percentDecode(part, err));
result.hasTrailingSlash = false;
} }
result.hasTrailingSlash = true;
} else if (part.size() == 0 || (part.size() == 1 && part[0] == '.')) {
// Collapse consecutive slashes and "/./".
result.hasTrailingSlash = true;
} else {
result.path.add(percentDecode(part, err));
result.hasTrailingSlash = false;
} }
result.path = path.releaseAsArray();
} }
if (text.startsWith("?")) { if (text.startsWith("?")) {
Vector<QueryParam> params;
do { do {
text = text.slice(1); text = text.slice(1);
auto part = split(text, END_QUERY_PART); auto part = split(text, END_QUERY_PART);
if (part.size() > 0) { if (part.size() > 0) {
KJ_IF_MAYBE(key, trySplit(part, '=')) { KJ_IF_MAYBE(key, trySplit(part, '=')) {
params.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) }); result.query.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) });
} else { } else {
params.add(QueryParam { percentDecode(part, err), nullptr }); result.query.add(QueryParam { percentDecode(part, err), nullptr });
} }
} }
} while (text.startsWith("&")); } while (text.startsWith("&"));
result.query = params.releaseAsArray();
} }
if (text.startsWith("#")) { if (text.startsWith("#")) {
...@@ -293,7 +287,6 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { ...@@ -293,7 +287,6 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
bool hadNewPath = text.size() > 0 && text[0] != '?' && text[0] != '#'; bool hadNewPath = text.size() > 0 && text[0] != '?' && text[0] != '#';
if (hadNewPath) { if (hadNewPath) {
// There's a new path. // There's a new path.
Vector<String> path(this->path.size());
if (text[0] == '/') { if (text[0] == '/') {
// New path is absolute, so don't copy the old path. // New path is absolute, so don't copy the old path.
...@@ -303,9 +296,7 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { ...@@ -303,9 +296,7 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
// New path is relative, so start from the old path, dropping everything after the last // New path is relative, so start from the old path, dropping everything after the last
// slash. // slash.
auto slice = this->path.slice(0, this->path.size() - (this->hasTrailingSlash ? 0 : 1)); auto slice = this->path.slice(0, this->path.size() - (this->hasTrailingSlash ? 0 : 1));
for (auto& part: slice) { result.path = KJ_MAP(part, slice) { return kj::str(part); };
path.add(kj::str(part));
}
result.hasTrailingSlash = true; result.hasTrailingSlash = true;
} }
...@@ -313,22 +304,20 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { ...@@ -313,22 +304,20 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
auto part = split(text, END_PATH_PART); auto part = split(text, END_PATH_PART);
if (part.size() == 2 && part[0] == '.' && part[1] == '.') { if (part.size() == 2 && part[0] == '.' && part[1] == '.') {
if (path.size() != 0) { if (path.size() != 0) {
path.removeLast(); result.path.removeLast();
} }
result.hasTrailingSlash = true; result.hasTrailingSlash = true;
} else if (part.size() == 0 || (part.size() == 1 && part[0] == '.')) { } else if (part.size() == 0 || (part.size() == 1 && part[0] == '.')) {
// Collapse consecutive slashes and "/./". // Collapse consecutive slashes and "/./".
result.hasTrailingSlash = true; result.hasTrailingSlash = true;
} else { } else {
path.add(percentDecode(part, err)); result.path.add(percentDecode(part, err));
result.hasTrailingSlash = false; result.hasTrailingSlash = false;
} }
if (!text.startsWith("/")) break; if (!text.startsWith("/")) break;
text = text.slice(1); text = text.slice(1);
} }
result.path = path.releaseAsArray();
} else if (!hadNewAuthority) { } else if (!hadNewAuthority) {
// copy path // copy path
result.path = KJ_MAP(part, this->path) { return kj::str(part); }; result.path = KJ_MAP(part, this->path) { return kj::str(part); };
...@@ -336,20 +325,18 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { ...@@ -336,20 +325,18 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const {
} }
if (text.startsWith("?")) { if (text.startsWith("?")) {
Vector<QueryParam> params;
do { do {
text = text.slice(1); text = text.slice(1);
auto part = split(text, END_QUERY_PART); auto part = split(text, END_QUERY_PART);
if (part.size() > 0) { if (part.size() > 0) {
KJ_IF_MAYBE(key, trySplit(part, '=')) { KJ_IF_MAYBE(key, trySplit(part, '=')) {
params.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) }); result.query.add(QueryParam { percentDecode(*key, err), percentDecode(part, err) });
} else { } else {
params.add(QueryParam { percentDecode(part, err), nullptr }); result.query.add(QueryParam { percentDecode(part, err), nullptr });
} }
} }
} while (text.startsWith("&")); } while (text.startsWith("&"));
result.query = params.releaseAsArray();
} 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) {
...@@ -404,6 +391,11 @@ String Url::toString(Context context) const { ...@@ -404,6 +391,11 @@ String Url::toString(Context context) const {
} }
for (auto& pathPart: path) { for (auto& pathPart: path) {
// Protect against path injection.
KJ_REQUIRE(pathPart != "" && pathPart != "." && pathPart != "..",
"invalid name in URL path", *this) {
continue;
}
chars.add('/'); chars.add('/');
chars.addAll(encodeUriComponent(pathPart)); chars.addAll(encodeUriComponent(pathPart));
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#define KJ_COMPAT_URL_H_ #define KJ_COMPAT_URL_H_
#include <kj/string.h> #include <kj/string.h>
#include <kj/vector.h>
#include <inttypes.h> #include <inttypes.h>
namespace kj { namespace kj {
...@@ -48,16 +49,20 @@ struct Url { ...@@ -48,16 +49,20 @@ struct Url {
// network address parsing functions already accept addresses containing port numbers, and // network address parsing functions already accept addresses containing port numbers, and
// because most web standards don't actually want to separate host and port. // because most web standards don't actually want to separate host and port.
Array<String> path; Vector<String> path;
bool hasTrailingSlash = false; bool hasTrailingSlash = false;
// Path, split on '/' characters. Note that the individual components of `path` could contain // Path, split on '/' characters. Note that the individual components of `path` could contain
// '/' characters if they were percent-encoded in the original URL. // '/' characters if they were percent-encoded in the original URL.
//
// No component of the path is allowed to be "", ".", nor ".."; if such components are present,
// toString() will throw. Note that parse() and parseRelative() automatically resolve such
// components.
struct QueryParam { struct QueryParam {
String name; String name;
String value; String value;
}; };
Array<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 an empty value.
...@@ -71,12 +76,14 @@ struct Url { ...@@ -71,12 +76,14 @@ struct Url {
~Url() noexcept(false); ~Url() noexcept(false);
Url& operator=(Url&&) = default; Url& operator=(Url&&) = default;
inline Url(String&& scheme, Maybe<UserInfo>&& userInfo, String&& host, Array<String>&& path, #if __cplusplus < 201402L
bool hasTrailingSlash, Array<QueryParam>&& query, Maybe<String>&& fragment) inline Url(String&& scheme, Maybe<UserInfo>&& userInfo, String&& host, Vector<String>&& path,
bool hasTrailingSlash, Vector<QueryParam>&& query, Maybe<String>&& fragment)
: scheme(kj::mv(scheme)), userInfo(kj::mv(userInfo)), host(kj::mv(host)), path(kj::mv(path)), : scheme(kj::mv(scheme)), userInfo(kj::mv(userInfo)), host(kj::mv(host)), path(kj::mv(path)),
hasTrailingSlash(hasTrailingSlash), query(kj::mv(query)), fragment(kj::mv(fragment)) {} hasTrailingSlash(hasTrailingSlash), query(kj::mv(query)), fragment(kj::mv(fragment)) {}
// TODO(cleanup): This constructor is only here to support brace initialization in C++11. It // TODO(cleanup): This constructor is only here to support brace initialization in C++11. It
// should be removed once we upgrade to C++14. // should be removed once we upgrade to C++14.
#endif
Url clone() const; Url clone() const;
......
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