mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
129 lines
3.8 KiB
C++
129 lines
3.8 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include <http/private/http_parsing.h>
|
|
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <string_view>
|
|
|
|
namespace {
|
|
|
|
std::string trim(const std::string& text) {
|
|
const auto start = text.find_first_not_of(" \t");
|
|
if (start == std::string::npos) {
|
|
return "";
|
|
}
|
|
const auto end = text.find_last_not_of(" \t");
|
|
return text.substr(start, end - start + 1);
|
|
}
|
|
|
|
bool is_digits(const std::string& text) {
|
|
if (text.empty()) {
|
|
return false;
|
|
}
|
|
for (const char c : text) {
|
|
if (!std::isdigit(static_cast<unsigned char>(c))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
bool http_parse_https_url(const std::string& url, HttpUrl& out) {
|
|
constexpr std::string_view scheme = "https://";
|
|
if (url.compare(0, scheme.size(), scheme) != 0) {
|
|
return false;
|
|
}
|
|
|
|
const auto authorityStart = scheme.size();
|
|
const auto pathStart = url.find_first_of("/?#", authorityStart);
|
|
const std::string authority = url.substr(authorityStart, pathStart == std::string::npos ? std::string::npos : pathStart - authorityStart);
|
|
if (authority.empty() || authority.find('@') != std::string::npos) {
|
|
return false;
|
|
}
|
|
|
|
// An IPv6 address is enclosed in brackets, as it contains colons itself
|
|
std::string host;
|
|
std::string rest;
|
|
if (authority[0] == '[') {
|
|
const auto close = authority.find(']');
|
|
if (close == std::string::npos) {
|
|
return false;
|
|
}
|
|
host = authority.substr(1, close - 1);
|
|
rest = authority.substr(close + 1);
|
|
} else {
|
|
const auto colon = authority.find(':');
|
|
host = authority.substr(0, colon);
|
|
rest = colon == std::string::npos ? "" : authority.substr(colon);
|
|
}
|
|
if (host.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::string port = "443";
|
|
if (!rest.empty()) {
|
|
if (rest[0] != ':' || !is_digits(rest.substr(1))) {
|
|
return false;
|
|
}
|
|
port = rest.substr(1);
|
|
}
|
|
|
|
std::string path = pathStart == std::string::npos ? "/" : url.substr(pathStart);
|
|
// The fragment isn't sent to the server
|
|
path = path.substr(0, path.find('#'));
|
|
if (path.empty() || path[0] != '/') {
|
|
path.insert(0, "/");
|
|
}
|
|
|
|
out = { .host = host, .port = port, .path = path };
|
|
return true;
|
|
}
|
|
|
|
bool http_parse_status_line(const std::string& line, int& statusCode) {
|
|
if (line.compare(0, 5, "HTTP/") != 0) {
|
|
return false;
|
|
}
|
|
const auto space = line.find(' ');
|
|
if (space == std::string::npos) {
|
|
return false;
|
|
}
|
|
const std::string code = line.substr(space + 1, 3);
|
|
if (code.size() != 3 || !is_digits(code) || (line.size() > space + 4 && line[space + 4] != ' ')) {
|
|
return false;
|
|
}
|
|
statusCode = std::atoi(code.c_str());
|
|
return true;
|
|
}
|
|
|
|
bool http_parse_header_line(const std::string& line, std::string& name, std::string& value) {
|
|
const auto colon = line.find(':');
|
|
if (colon == std::string::npos || colon == 0) {
|
|
return false;
|
|
}
|
|
name = trim(line.substr(0, colon));
|
|
for (auto& c : name) {
|
|
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
}
|
|
value = trim(line.substr(colon + 1));
|
|
return !name.empty();
|
|
}
|
|
|
|
bool http_parse_chunk_size(const std::string& line, size_t& size) {
|
|
const std::string digits = trim(line.substr(0, line.find(';')));
|
|
if (digits.empty() || digits.size() > sizeof(size_t) * 2) {
|
|
return false;
|
|
}
|
|
size_t result = 0;
|
|
for (const char c : digits) {
|
|
if (!std::isxdigit(static_cast<unsigned char>(c))) {
|
|
return false;
|
|
}
|
|
const char lower = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
result = result * 16 + static_cast<size_t>(lower <= '9' ? lower - '0' : lower - 'a' + 10);
|
|
}
|
|
size = result;
|
|
return true;
|
|
}
|