diff --git a/c++/src/kj/array-test.c++ b/c++/src/kj/array-test.c++ index 17ae15b8c012b12f9f6f3f33e6d9f15af540c626..b0a0a036c136ea8d4aebfcdd2e2e70efad38b8b5 100644 --- a/c++/src/kj/array-test.c++ +++ b/c++/src/kj/array-test.c++ @@ -378,5 +378,12 @@ TEST(Array, ReleaseAsBytesOrChars) { } } +#if __cplusplus > 201402L +KJ_TEST("kj::arr()") { + kj::Array<kj::String> array = kj::arr(kj::str("foo"), kj::str(123)); + KJ_EXPECT(array == kj::ArrayPtr<const kj::StringPtr>({"foo", "123"})); +} +#endif + } // namespace } // namespace kj diff --git a/c++/src/kj/array.h b/c++/src/kj/array.h index 51b5dcf31949abe5924c23eb5c79fc6c20670e84..3aa4ae2bf8f9e461a8a614120322ab2f9b3c9fd0 100644 --- a/c++/src/kj/array.h +++ b/c++/src/kj/array.h @@ -177,6 +177,11 @@ public: inline T& front() { return *ptr; } inline T& back() { return *(ptr + size_ - 1); } + template <typename U> + inline bool operator==(const U& other) const { return asPtr() == other; } + template <typename U> + inline bool operator!=(const U& other) const { return asPtr() != other; } + inline ArrayPtr<T> slice(size_t start, size_t end) { KJ_IREQUIRE(start <= end && end <= size_, "Out-of-bounds Array::slice()."); return ArrayPtr<T>(ptr + start, end - start); @@ -249,6 +254,8 @@ private: template <typename U> friend class Array; + template <typename U> + friend class ArrayBuilder; }; static_assert(!canMemcpy<Array<char>>(), "canMemcpy<>() is broken"); @@ -321,6 +328,13 @@ public: other.pos = nullptr; other.endPtr = nullptr; } + ArrayBuilder(Array<T>&& other) + : ptr(other.ptr), pos(other.ptr + other.size_), endPtr(pos), disposer(other.disposer) { + // Create an already-full ArrayBuilder from an Array of the same type. This constructor + // primarily exists to enable Vector<T> to be constructed from Array<T>. + other.ptr = nullptr; + other.size_ = 0; + } KJ_DISALLOW_COPY(ArrayBuilder); inline ~ArrayBuilder() noexcept(false) { dispose(); } @@ -808,6 +822,15 @@ inline Array<T> heapArray(std::initializer_list<T> init) { return heapArray<T>(init.begin(), init.end()); } +#if __cplusplus > 201402L +template <typename T, typename... Params> +inline Array<Decay<T>> arr(T&& param1, Params&&... params) { + ArrayBuilder<Decay<T>> builder = heapArrayBuilder<Decay<T>>(sizeof...(params) + 1); + (builder.add(kj::fwd<T>(param1)), ... , builder.add(kj::fwd<Params>(params))); + return builder.finish(); +} +#endif + } // namespace kj #endif // KJ_ARRAY_H_ diff --git a/c++/src/kj/async-io.h b/c++/src/kj/async-io.h index cd441e87330933e47949493993c06f1464e3fe5b..2804ed7289603dff35d298257a741052cf631307 100644 --- a/c++/src/kj/async-io.h +++ b/c++/src/kj/async-io.h @@ -86,8 +86,9 @@ class AsyncOutputStream { // Asynchronous equivalent of OutputStream (from io.h). public: - virtual Promise<void> write(const void* buffer, size_t size) = 0; - virtual Promise<void> write(ArrayPtr<const ArrayPtr<const byte>> pieces) = 0; + virtual Promise<void> write(const void* buffer, size_t size) KJ_WARN_UNUSED_RESULT = 0; + virtual Promise<void> write(ArrayPtr<const ArrayPtr<const byte>> pieces) + KJ_WARN_UNUSED_RESULT = 0; virtual Maybe<Promise<uint64_t>> tryPumpFrom( AsyncInputStream& input, uint64_t amount = kj::maxValue); diff --git a/c++/src/kj/compat/http.c++ b/c++/src/kj/compat/http.c++ index c17d859e668e5f45fc04d097802bf7b95f6236ad..eceb9dffdd664b0da8ad5cbb73bbd9ed40e921da 100644 --- a/c++/src/kj/compat/http.c++ +++ b/c++/src/kj/compat/http.c++ @@ -1297,7 +1297,7 @@ public: KJ_REQUIRE(inBody) { return kj::READY_NOW; } auto fork = writeQueue.then([this,buffer,size]() { - inner.write(buffer, size); + return inner.write(buffer, size); }).fork(); writeQueue = fork.addBranch(); @@ -1308,7 +1308,7 @@ public: KJ_REQUIRE(inBody) { return kj::READY_NOW; } auto fork = writeQueue.then([this,pieces]() { - inner.write(pieces); + return inner.write(pieces); }).fork(); writeQueue = fork.addBranch(); diff --git a/c++/src/kj/compat/url.c++ b/c++/src/kj/compat/url.c++ index 752f77495acdbaeec1e0d693e0a09f8ce5ba4603..969dad64bd59d448514847eaaf3c2ba22ad45b91 100644 --- a/c++/src/kj/compat/url.c++ +++ b/c++/src/kj/compat/url.c++ @@ -171,42 +171,36 @@ Maybe<Url> Url::tryParse(StringPtr text, Context context) { } } - { - Vector<String> path; - while (text.startsWith("/")) { - text = text.slice(1); - auto part = split(text, END_PATH_PART); - if (part.size() == 2 && part[0] == '.' && part[1] == '.') { - 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; + while (text.startsWith("/")) { + text = text.slice(1); + auto part = split(text, END_PATH_PART); + if (part.size() == 2 && part[0] == '.' && part[1] == '.') { + if (result.path.size() != 0) { + result.path.removeLast(); } + 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("?")) { - Vector<QueryParam> params; do { text = text.slice(1); auto part = split(text, END_QUERY_PART); if (part.size() > 0) { 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 { - params.add(QueryParam { percentDecode(part, err), nullptr }); + result.query.add(QueryParam { percentDecode(part, err), nullptr }); } } } while (text.startsWith("&")); - result.query = params.releaseAsArray(); } if (text.startsWith("#")) { @@ -293,7 +287,6 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { bool hadNewPath = text.size() > 0 && text[0] != '?' && text[0] != '#'; if (hadNewPath) { // There's a new path. - Vector<String> path(this->path.size()); if (text[0] == '/') { // New path is absolute, so don't copy the old path. @@ -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 // slash. auto slice = this->path.slice(0, this->path.size() - (this->hasTrailingSlash ? 0 : 1)); - for (auto& part: slice) { - path.add(kj::str(part)); - } + result.path = KJ_MAP(part, slice) { return kj::str(part); }; result.hasTrailingSlash = true; } @@ -313,22 +304,20 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { auto part = split(text, END_PATH_PART); if (part.size() == 2 && part[0] == '.' && part[1] == '.') { if (path.size() != 0) { - path.removeLast(); + result.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.path.add(percentDecode(part, err)); result.hasTrailingSlash = false; } if (!text.startsWith("/")) break; text = text.slice(1); } - - result.path = path.releaseAsArray(); } else if (!hadNewAuthority) { // copy path result.path = KJ_MAP(part, this->path) { return kj::str(part); }; @@ -336,20 +325,18 @@ Maybe<Url> Url::tryParseRelative(StringPtr text) const { } if (text.startsWith("?")) { - Vector<QueryParam> params; do { text = text.slice(1); auto part = split(text, END_QUERY_PART); if (part.size() > 0) { 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 { - params.add(QueryParam { percentDecode(part, err), nullptr }); + result.query.add(QueryParam { percentDecode(part, err), nullptr }); } } } while (text.startsWith("&")); - result.query = params.releaseAsArray(); } else if (!hadNewAuthority && !hadNewPath) { // copy query result.query = KJ_MAP(param, this->query) { @@ -404,6 +391,11 @@ String Url::toString(Context context) const { } for (auto& pathPart: path) { + // Protect against path injection. + KJ_REQUIRE(pathPart != "" && pathPart != "." && pathPart != "..", + "invalid name in URL path", *this) { + continue; + } chars.add('/'); chars.addAll(encodeUriComponent(pathPart)); } diff --git a/c++/src/kj/compat/url.h b/c++/src/kj/compat/url.h index b888ec7c019fff42baf290c69689d5af5cd0b854..ed3dc64b1865fbb7f85b8f99750c9417280d7b0e 100644 --- a/c++/src/kj/compat/url.h +++ b/c++/src/kj/compat/url.h @@ -23,6 +23,7 @@ #define KJ_COMPAT_URL_H_ #include <kj/string.h> +#include <kj/vector.h> #include <inttypes.h> namespace kj { @@ -48,16 +49,20 @@ struct Url { // network address parsing functions already accept addresses containing port numbers, and // because most web standards don't actually want to separate host and port. - Array<String> path; + Vector<String> path; bool hasTrailingSlash = false; // Path, split on '/' characters. Note that the individual components of `path` could contain // '/' 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 { String name; 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, // it will be parsed as a key with an empty value. @@ -71,12 +76,14 @@ struct Url { ~Url() noexcept(false); Url& operator=(Url&&) = default; - inline Url(String&& scheme, Maybe<UserInfo>&& userInfo, String&& host, Array<String>&& path, - bool hasTrailingSlash, Array<QueryParam>&& query, Maybe<String>&& fragment) +#if __cplusplus < 201402L + 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)), 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 // should be removed once we upgrade to C++14. +#endif Url clone() const; diff --git a/c++/src/kj/vector.h b/c++/src/kj/vector.h index 44613f333173cdd3aff70f274b3a3bf179aeb64f..8752e6ea6998b6c07df1e9c430263c6d8a243f2e 100644 --- a/c++/src/kj/vector.h +++ b/c++/src/kj/vector.h @@ -43,6 +43,7 @@ class Vector { public: inline Vector() = default; inline explicit Vector(size_t capacity): builder(heapArrayBuilder<T>(capacity)) {} + inline Vector(Array<T>&& array): builder(kj::mv(array)) {} inline operator ArrayPtr<T>() { return builder; } inline operator ArrayPtr<const T>() const { return builder; } @@ -71,6 +72,18 @@ public: return builder.finish(); } + template <typename U> + inline bool operator==(const U& other) const { return asPtr() == other; } + template <typename U> + inline bool operator!=(const U& other) const { return asPtr() != other; } + + inline ArrayPtr<T> slice(size_t start, size_t end) { + return asPtr().slice(start, end); + } + inline ArrayPtr<const T> slice(size_t start, size_t end) const { + return asPtr().slice(start, end); + } + template <typename... Params> inline T& add(Params&&... params) { if (builder.isFull()) grow();