diff --git a/src/network/http_request.cpp b/src/network/http_request.cpp index 5174420..c25fbc4 100644 --- a/src/network/http_request.cpp +++ b/src/network/http_request.cpp @@ -1,6 +1,10 @@ #include "network/http_request.hpp" #include "utils/data_vector.hpp" #include "utils/utils.hpp" +#include +#include +#include +#include //--------------------------------------------------------------------------- // AnyBlob - Universal Cloud Object Storage Library // Dominik Durner, 2024 @@ -14,10 +18,121 @@ namespace network { //--------------------------------------------------------------------------- using namespace std; //--------------------------------------------------------------------------- -HttpRequest HttpRequest::deserialize(string_view /*data*/) +HttpRequest HttpRequest::deserialize(string_view data) // Deserialize the http header { + static constexpr string_view strHttp1_0 = "HTTP/1.0"; + static constexpr string_view strHttp1_1 = "HTTP/1.1"; + static constexpr string_view strGet = "GET"; + static constexpr string_view strPost = "POST"; + static constexpr string_view strPut = "PUT"; + static constexpr string_view strDelete = "DELETE"; + static constexpr string_view strNewline = "\r\n"; + static constexpr string_view strHeaderSeperator = ": "; + static constexpr string_view strQuerySeperator = "="; + static constexpr string_view strQueryStart = "?"; + static constexpr string_view strQueryAnd = "&"; + HttpRequest request; + + string_view line; + auto firstLine = true; + while (true) { + auto pos = data.find(strNewline); + if (pos == data.npos) + break; + line = data.substr(0, pos); + data = data.substr(pos + strNewline.size()); + if (line.empty()) { + if (!firstLine) + break; + else + throw runtime_error("Invalid HttpRequest: Missing first line!"); + } + if (firstLine) { + // parse method + if (line.starts_with(strGet)) { + firstLine = false; + request.method = HttpRequest::Method::GET; + line = line.substr(strGet.size()); + } else if (line.starts_with(strPost)) { + firstLine = false; + request.method = HttpRequest::Method::POST; + line = line.substr(strPost.size()); + } else if (line.starts_with(strPut)) { + firstLine = false; + request.method = HttpRequest::Method::PUT; + line = line.substr(strPut.size()); + } else if (line.starts_with(strDelete)) { + firstLine = false; + request.method = HttpRequest::Method::DELETE; + line = line.substr(strDelete.size()); + } else { + throw runtime_error("Invalid HttpRequest: Needs to start with request method!"); + } + + // parse path, requires HTTP type, otherwise invalid + pos = line.find(" ", 1); + if (pos == line.npos) + throw runtime_error("Invalid HttpRequest: Could not find path, or missing HTTP type!"); + auto pathQuery = line.substr(1, pos - 1); + // the http type + line = line.substr(pos + 1); + + // split path and query + auto queriesPos = pathQuery.find(strQueryStart); + if (queriesPos != pathQuery.npos) { + // with query + request.path = pathQuery.substr(0, queriesPos); + auto queries = pathQuery.substr(queriesPos + 1); + while (true) { + auto queryPos = queries.find(strQueryAnd); + string_view query; + if (queryPos == queries.npos) + query = queries; + else + query = queries.substr(0, queryPos); // skip the ? or the & + + // split between key and value (value might be unnecassary) + auto keyPos = query.find(strQuerySeperator); + string_view key, value = ""; + if (keyPos == query.npos) { + key = query; + } else { + key = query.substr(0, keyPos); + value = query.substr(keyPos + 1); + } + request.queries.emplace(key, value); + if (queryPos == queries.npos) + break; + queries = queries.substr(queryPos + 1); + } + } else { + request.path = pathQuery; + } + + // the http type + if (line.starts_with(strHttp1_0)) { + request.type = HttpRequest::Type::HTTP_1_0; + } else if (line.starts_with(strHttp1_1)) { + request.type = HttpRequest::Type::HTTP_1_1; + } else { + throw runtime_error("Invalid HttpRequest: Needs to be a HTTP type 1.0 or 1.1!"); + } + } else { + // headers + auto keyPos = line.find(strHeaderSeperator); + string_view key, value = ""; + if (keyPos == line.npos) { + throw runtime_error("Invalid HttpRequest: Headers need key and value!"); + } else { + key = line.substr(0, keyPos); + value = line.substr(keyPos + strHeaderSeperator.size()); + } + request.headers.emplace(key, value); + } + } + return request; } //--------------------------------------------------------------------------- @@ -25,7 +140,9 @@ unique_ptr> HttpRequest::serialize(const HttpRequest& // Serialize an http header { string httpHeader = getRequestMethod(request); - httpHeader += " "; + httpHeader += " " + request.path; + if (request.queries.size()) + httpHeader += "?"; auto it = request.queries.begin(); while (it != request.queries.end()) { httpHeader += utils::encodeUrlParameters(it->first) + "=" + utils::encodeUrlParameters(it->second); diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index bf56708..1888438 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -45,6 +45,9 @@ pair, uint64_t> base64Decode(const uint8_t* input, uint64_ assert(in_range(length)); auto baseLength = 3 * length / 4; auto buffer = make_unique(baseLength + 1); + if (!length) { + return {move(buffer), 0}; + } auto decodeLength = EVP_DecodeBlock(reinterpret_cast(buffer.get()), input, static_cast(length)); if (decodeLength < 0 || static_cast(decodeLength) != baseLength) throw runtime_error("OpenSSL Error!"); diff --git a/test/unit/network/http_request_test.cpp b/test/unit/network/http_request_test.cpp new file mode 100644 index 0000000..d68e26f --- /dev/null +++ b/test/unit/network/http_request_test.cpp @@ -0,0 +1,41 @@ +#include "network/http_request.hpp" +#include "utils/data_vector.hpp" +#include "catch2/single_include/catch2/catch.hpp" +#include +//--------------------------------------------------------------------------- +// AnyBlob - Universal Cloud Object Storage Library +// Dominik Durner, 2022 +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 +//--------------------------------------------------------------------------- +namespace anyblob { +namespace network { +namespace test { +//--------------------------------------------------------------------------- +TEST_CASE("http_request") { + network::HttpRequest request; + + request.method = network::HttpRequest::Method::GET; + request.path = "/test"; + request.type = network::HttpRequest::Type::HTTP_1_1; + request.queries.emplace("key", "value"); + request.queries.emplace("key2", "value2"); + request.headers.emplace("Authorization", "test"); + request.headers.emplace("Timestamp", "2024-02-18 00:00:00"); + + auto serialize = network::HttpRequest::serialize(request); + auto serializeView = std::string_view(reinterpret_cast(serialize->data()), serialize->size()); + + auto deserializedRequest = network::HttpRequest::deserialize(serializeView); + + auto serializeAgain = network::HttpRequest::serialize(deserializedRequest); + auto serializeAgainView = std::string_view(reinterpret_cast(serializeAgain->data()), serializeAgain->size()); + + REQUIRE(serializeView == serializeAgainView); +} +//--------------------------------------------------------------------------- +} // namespace test +} // namespace network +} // namespace anyblob