Skip to content

Commit

Permalink
Deserialize request
Browse files Browse the repository at this point in the history
  • Loading branch information
durner committed Feb 18, 2024
1 parent c1586c4 commit f7891d2
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 2 deletions.
121 changes: 119 additions & 2 deletions src/network/http_request.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include "network/http_request.hpp"
#include "utils/data_vector.hpp"
#include "utils/utils.hpp"
#include <iostream>
#include <map>
#include <sstream>
#include <string>
//---------------------------------------------------------------------------
// AnyBlob - Universal Cloud Object Storage Library
// Dominik Durner, 2024
Expand All @@ -14,18 +18,131 @@ 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;
}
//---------------------------------------------------------------------------
unique_ptr<utils::DataVector<uint8_t>> HttpRequest::serialize(const HttpRequest& request)
// 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);
Expand Down
3 changes: 3 additions & 0 deletions src/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pair<unique_ptr<uint8_t[]>, uint64_t> base64Decode(const uint8_t* input, uint64_
assert(in_range<int>(length));
auto baseLength = 3 * length / 4;
auto buffer = make_unique<uint8_t[]>(baseLength + 1);
if (!length) {
return {move(buffer), 0};
}
auto decodeLength = EVP_DecodeBlock(reinterpret_cast<unsigned char*>(buffer.get()), input, static_cast<int>(length));
if (decodeLength < 0 || static_cast<unsigned>(decodeLength) != baseLength)
throw runtime_error("OpenSSL Error!");
Expand Down
41 changes: 41 additions & 0 deletions test/unit/network/http_request_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "network/http_request.hpp"
#include "utils/data_vector.hpp"
#include "catch2/single_include/catch2/catch.hpp"
#include <iostream>
//---------------------------------------------------------------------------
// 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<char*>(serialize->data()), serialize->size());

auto deserializedRequest = network::HttpRequest::deserialize(serializeView);

auto serializeAgain = network::HttpRequest::serialize(deserializedRequest);
auto serializeAgainView = std::string_view(reinterpret_cast<char*>(serializeAgain->data()), serializeAgain->size());

REQUIRE(serializeView == serializeAgainView);
}
//---------------------------------------------------------------------------
} // namespace test
} // namespace network
} // namespace anyblob

0 comments on commit f7891d2

Please sign in to comment.