Added some basic HTTP and ip address parsing.

This commit is contained in:
Patrick 2024-08-18 23:06:09 +02:00
parent 03c899f17e
commit 35e7131780
9 changed files with 497 additions and 35 deletions

View File

@ -7,6 +7,8 @@ mijin_sources = Split("""
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/net/http.cpp
source/mijin/net/socket.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp
source/mijin/virtual_filesystem/filesystem.cpp

View File

@ -10,6 +10,7 @@ mijin_sources = Split("""
source/mijin/debug/symbol_info.cpp
source/mijin/io/process.cpp
source/mijin/io/stream.cpp
source/mijin/net/http.cpp
source/mijin/net/socket.cpp
source/mijin/util/os.cpp
source/mijin/types/name.cpp

View File

@ -36,20 +36,12 @@ void Stream::flush() {}
mijin::Task<StreamError> Stream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
{
(void) buffer;
(void) options;
(void) outBytesRead;
MIJIN_ASSERT(!getFeatures().async || !getFeatures().read, "Stream advertises async read, but doesn't implement it.");
co_return StreamError::NOT_SUPPORTED;
co_return readRaw(buffer, options, outBytesRead);
}
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
{
(void) buffer;
MIJIN_ASSERT(!getFeatures().async || !getFeatures().write, "Stream advertises async write, but doesn't implement it.");
co_return StreamError::NOT_SUPPORTED;
co_return writeRaw(buffer);
}
StreamError Stream::readBinaryString(std::string& outString)

View File

@ -68,6 +68,7 @@ enum class [[nodiscard]] StreamError
IO_ERROR = 1,
NOT_SUPPORTED = 2,
CONNECTION_CLOSED = 3,
PROTOCOL_ERROR = 4,
UNKNOWN_ERROR = -1
};
@ -430,6 +431,8 @@ inline const char* errorName(StreamError error) noexcept
return "not supported";
case StreamError::CONNECTION_CLOSED:
return "connection closed";
case StreamError::PROTOCOL_ERROR:
return "protocol error";
case StreamError::UNKNOWN_ERROR:
return "unknown error";
}

169
source/mijin/net/http.cpp Normal file
View File

@ -0,0 +1,169 @@
#include "./http.hpp"
#include "../util/iterators.hpp"
#include "../util/string.hpp"
#define MIJIN_HTTP_WRITE(text) \
do \
{ \
if (const StreamError error = co_await base_->c_writeText(text); error != StreamError::SUCCESS) \
{ \
co_return error; \
} \
} while(false)
#define MIJIN_HTTP_CHECKREAD(read) \
do \
{ \
if (const StreamError error = co_await read; error != StreamError::SUCCESS) \
{ \
co_return error; \
} \
} while(false)
#define MIJIN_HTTP_READLINE(text) MIJIN_HTTP_CHECKREAD(base_->c_readLine(text)); text = trim(text)
namespace mijin
{
namespace
{
inline constexpr std::size_t CONTENT_LENGTH_LIMIT = 100 << 20; // 100MiB
bool parseHTTPVersion(std::string_view version, HTTPVersion& outVersion) noexcept
{
std::vector<std::string_view> parts = split(version, ".");
if (parts.size() != 2)
{
return false;
}
return toNumber(parts[0], outVersion.major) && toNumber(parts[1], outVersion.minor);
}
}
Task<StreamResult<HTTPResponse>> HTTPStream::c_request(HTTPRequest request) noexcept
{
if (const StreamError error = co_await c_writeRequest(request); error != StreamError::SUCCESS)
{
co_return error;
}
co_return co_await c_readResponse();
}
Task<StreamError> HTTPStream::c_writeRequest(const mijin::HTTPRequest& request) noexcept
{
std::map<std::string, std::string> moreHeaders;
if (!request.body.empty())
{
auto itLength = request.headers.find("content-length");
if (itLength == request.headers.end())
{
moreHeaders.emplace("content-length", std::to_string(request.body.size()));
}
else
{
std::size_t headerValue = 0;
if (!toNumber(itLength->second, headerValue) || headerValue != request.body.size())
{
co_return StreamError::PROTOCOL_ERROR;
}
}
}
MIJIN_HTTP_WRITE(request.method);
MIJIN_HTTP_WRITE(" ");
MIJIN_HTTP_WRITE(request.address);
MIJIN_HTTP_WRITE(" HTTP/1.0\n");
for (const auto& [key, value] : moreHeaders)
{
MIJIN_HTTP_WRITE(key);
MIJIN_HTTP_WRITE(": ");
MIJIN_HTTP_WRITE(value);
MIJIN_HTTP_WRITE("\n");
}
for (const auto& [key, value] : request.headers)
{
MIJIN_HTTP_WRITE(key);
MIJIN_HTTP_WRITE(": ");
MIJIN_HTTP_WRITE(value);
MIJIN_HTTP_WRITE("\n");
}
MIJIN_HTTP_WRITE("\n");
if (!request.body.empty())
{
MIJIN_HTTP_WRITE(request.body);
}
co_return StreamError::SUCCESS;
}
Task<StreamResult<HTTPResponse>> HTTPStream::c_readResponse() noexcept
{
std::string line;
MIJIN_HTTP_READLINE(line);
std::vector<std::string_view> parts = split(line, " ", {.limitParts = 3});
if (parts.size() != 3)
{
co_return StreamError::PROTOCOL_ERROR;
}
if (!parts[0].starts_with("HTTP/"))
{
co_return StreamError::PROTOCOL_ERROR;
}
HTTPResponse response;
if (!parseHTTPVersion(parts[0].substr(5), response.version)
|| !toNumber(parts[1], response.status))
{
co_return StreamError::PROTOCOL_ERROR;
}
response.statusMessage = parts[2];
decltype(response.headers)::iterator lastHeader;
while (true)
{
MIJIN_HTTP_READLINE(line);
if (line.empty()) {
break;
}
if (line[0] == ' ' || line[0] == '\t')
{
// continuation
if (lastHeader == response.headers.end())
{
co_return StreamError::PROTOCOL_ERROR;
}
lastHeader->second.push_back(' ');
lastHeader->second.append(trim(line));
}
parts = split(line, ":", {.limitParts = 2});
if (parts.size() != 2)
{
co_return StreamError::PROTOCOL_ERROR;
}
lastHeader = response.headers.emplace(toLower(trim(parts[0])), trim(parts[1]));
}
auto itContentLength = response.headers.find("content-length");
if (itContentLength != response.headers.end())
{
std::size_t contentLength = 0;
if (!toNumber(itContentLength->second, contentLength))
{
co_return StreamError::PROTOCOL_ERROR;
}
if (contentLength > CONTENT_LENGTH_LIMIT)
{
co_return StreamError::PROTOCOL_ERROR;
}
response.content.resize(contentLength);
MIJIN_HTTP_CHECKREAD(base_->c_readRaw(response.content));
}
co_return response;
}
}
#undef MIJIN_HTTP_WRITE
#undef MIJIN_HTTP_READLINE

55
source/mijin/net/http.hpp Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#if !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
#define MIJIN_NET_HTTP_HPP_INCLUDED 1
#include <memory>
#include <string>
#include <map>
#include "../io/stream.hpp"
namespace mijin
{
struct HTTPVersion
{
unsigned major;
unsigned minor;
};
struct HTTPRequest
{
std::string address;
std::string method = "GET";
std::multimap<std::string, std::string> headers;
std::string body;
explicit HTTPRequest(std::string address_) noexcept : address(std::move(address_)) {}
};
struct HTTPResponse
{
HTTPVersion version;
unsigned status;
std::string statusMessage;
std::multimap<std::string, std::string> headers;
std::string content;
};
class HTTPStream
{
private:
Stream* base_;
public:
HTTPStream(Stream& base) noexcept : base_(&base)
{
MIJIN_ASSERT(base_ != nullptr, "Invalid parameter for base.");
}
Task<StreamResult<HTTPResponse>> c_request(HTTPRequest request) noexcept;
private:
Task<StreamError> c_writeRequest(const HTTPRequest& request) noexcept;
Task<StreamResult<HTTPResponse>> c_readResponse() noexcept;
};
}
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)

View File

@ -1,13 +1,17 @@
#include "./socket.hpp"
#include <iostream>
#include "../detect.hpp"
#include "../util/string.hpp"
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include "../util/variant.hpp"
#endif
namespace mijin
@ -51,6 +55,77 @@ int readFlags(const ReadOptions& options)
}
}
Optional<IPv4Address> IPv4Address::fromString(std::string_view stringView) noexcept
{
std::vector<std::string_view> parts = split(stringView, ".", {.limitParts = 4});
if (parts.size() != 4) {
return NULL_OPTIONAL;
}
IPv4Address address;
for (int idx = 0; idx < 4; ++idx)
{
if (!toNumber(parts[idx], address.octets[idx]))
{
return NULL_OPTIONAL;
}
}
return address;
}
Optional<IPv6Address> IPv6Address::fromString(std::string_view stringView) noexcept
{
// very specific edge case
if (stringView.contains(":::"))
{
return NULL_OPTIONAL;
}
std::vector<std::string_view> parts = split(stringView, "::", {.ignoreEmpty = false});
if (parts.size() > 2)
{
return NULL_OPTIONAL;
}
if (parts.size() == 1)
{
parts.emplace_back("");
}
std::vector<std::string_view> partsLeft = split(parts[0], ":");
std::vector<std::string_view> partsRight = split(parts[1], ":");
std::erase_if(partsLeft, std::mem_fn(&std::string_view::empty));
std::erase_if(partsRight, std::mem_fn(&std::string_view::empty));
if (partsLeft.size() + partsRight.size() > 8)
{
return NULL_OPTIONAL;
}
IPv6Address address;
unsigned hextet = 0;
for (std::string_view part : partsLeft)
{
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
{
return NULL_OPTIONAL;
}
++hextet;
}
for (; hextet < (8 - partsRight.size()); ++hextet)
{
address.hextets[hextet] = 0;
}
for (std::string_view part : partsRight)
{
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
{
return NULL_OPTIONAL;
}
++hextet;
}
return address;
}
StreamError TCPStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
{
MIJIN_ASSERT(isOpen(), "Socket is not open.");
@ -174,7 +249,7 @@ StreamFeatures TCPStream::getFeatures()
};
}
StreamError TCPStream::open(const char* address, std::uint16_t port) noexcept
StreamError TCPStream::open(ip_address_t address, std::uint16_t port) noexcept
{
MIJIN_ASSERT(!isOpen(), "Socket is already open.");
@ -183,13 +258,31 @@ StreamError TCPStream::open(const char* address, std::uint16_t port) noexcept
{
return translateErrno();
}
sockaddr_in connectAddress =
{
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {inet_addr(address)}
};
if (connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) < 0)
const bool connected = std::visit(Visitor{
[&](const IPv4Address& address4)
{
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
#error "TODO: swap byte orderof thre address"
#endif
sockaddr_in connectAddress =
{
.sin_family = AF_INET,
.sin_port = htons(port),
.sin_addr = {.s_addr = std::bit_cast<in_addr_t>(address4)}
};
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) == 0;
},
[&](const IPv6Address& address6) {
sockaddr_in6 connectAddress =
{
.sin6_family = AF_INET,
.sin6_port = htons(port),
.sin6_addr = std::bit_cast<in6_addr>(address6)
};
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in6)) == 0;}
}, address);
if (!connected)
{
::close(handle_);
handle_ = -1;

View File

@ -4,7 +4,10 @@
#if !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
#define MIJIN_NET_SOCKET_HPP_INCLUDED 1
#include <array>
#include <variant>
#include "../async/coroutine.hpp"
#include "../container/optional.hpp"
#include "../io/stream.hpp"
namespace mijin
@ -14,6 +17,41 @@ namespace mijin
// public types
//
struct IPv4Address
{
std::array<std::uint8_t, 4> octets;
auto operator<=>(const IPv4Address&) const noexcept = default;
[[nodiscard]]
static Optional<IPv4Address> fromString(std::string_view stringView) noexcept;
};
struct IPv6Address
{
std::array<std::uint16_t, 8> hextets;
auto operator<=>(const IPv6Address&) const noexcept = default;
[[nodiscard]]
static Optional<IPv6Address> fromString(std::string_view stringView) noexcept;
};
using ip_address_t = std::variant<IPv4Address, IPv6Address>;
[[nodiscard]]
inline Optional<ip_address_t> ipAddressFromString(std::string_view stringView) noexcept
{
if (Optional<IPv4Address> ipv4Address = IPv4Address::fromString(stringView); !ipv4Address.empty())
{
return ip_address_t(*ipv4Address);
}
if (Optional<IPv6Address> ipv6Address = IPv6Address::fromString(stringView); !ipv6Address.empty())
{
return ip_address_t(*ipv6Address);
}
return NULL_OPTIONAL;
}
class Socket
{
protected:
@ -61,7 +99,7 @@ public:
bool isAtEnd() override;
StreamFeatures getFeatures() override;
StreamError open(const char* address, std::uint16_t port) noexcept;
StreamError open(ip_address_t address, std::uint16_t port) noexcept;
void close() noexcept;
[[nodiscard]] bool isOpen() const noexcept { return handle_ >= 0; }
private:
@ -77,7 +115,15 @@ private:
public:
TCPStream& getStream() noexcept override;
StreamError open(const char* address, std::uint16_t port) noexcept { return stream_.open(address, port); }
StreamError open(ip_address_t address, std::uint16_t port) noexcept { return stream_.open(address, port); }
StreamError open(std::string_view addressText, std::uint16_t port) noexcept
{
if (Optional<ip_address_t> address = ipAddressFromString(addressText); !address.empty())
{
return open(*address, port);
}
return StreamError::UNKNOWN_ERROR;
}
void close() noexcept { stream_.close(); }
[[nodiscard]] bool isOpen() const noexcept { return stream_.isOpen(); }

View File

@ -4,8 +4,11 @@
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
#include <array>
#include <charconv>
#include <iterator>
#include <limits>
#include <locale>
#include <sstream>
#include <string>
#include <string_view>
@ -24,6 +27,13 @@ namespace mijin
// public constants
//
//
// public traits
//
template<typename TString>
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
//
// public types
//
@ -151,8 +161,35 @@ bool equalsIgnoreCaseImpl(std::basic_string_view<TChar, TTraitsA> stringA, std::
return true;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimPrefixImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
return stringView;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimSuffixImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
return stringView;
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim)
{
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
}
template<typename TChar>
static const TChar SPACE = TChar(' ');
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
template<typename TChar>
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
}
template<typename TLeft, typename TRight>
@ -162,27 +199,46 @@ template<typename TLeft, typename TRight>
std::basic_string_view(std::forward<TRight>(separator)), options);
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimPrefix(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
template<typename TString, typename TChars>
[[nodiscard]]
auto trimPrefix(TString&& string, TChars&& chars)
{
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
return stringView;
return detail::trimPrefixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trimSuffix(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
template<typename TString>
[[nodiscard]]
auto trimPrefix(TString&& string)
{
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
return stringView;
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TChar, typename TTraits>
std::basic_string_view<TChar, TTraits> trim(std::basic_string_view<TChar, TTraits> stringView,
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
template<typename TString, typename TChars>
[[nodiscard]]
auto trimSuffix(TString&& string, TChars&& chars)
{
return trimPrefix(trimSuffix(stringView, charsToTrim), charsToTrim);
return detail::trimSuffixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TString>
[[nodiscard]]
auto trimSuffix(TString&& string)
{
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TString, typename TChars>
[[nodiscard]]
auto trim(TString&& string, TChars&& chars)
{
return detail::trimImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
}
template<typename TString>
[[nodiscard]]
auto trim(TString&& string)
{
return trim(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
}
template<typename TLeft, typename TRight>
@ -191,6 +247,51 @@ template<typename TLeft, typename TRight>
return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right));
}
template<typename TChar, typename TTraits, typename TAllocator>
void makeLower(std::basic_string<TChar, TTraits, TAllocator>& string)
{
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
{
return std::tolower<TChar>(chr, locale);
});
}
template<typename TChar, typename TTraits, typename TAllocator>
void makeUpper(std::basic_string<TChar, TTraits, TAllocator>& string)
{
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
{
return std::toupper<TChar>(chr, locale);
});
}
template<typename... TArgs>
[[nodiscard]]
auto toLower(TArgs&&... args)
{
std::basic_string string(std::forward<TArgs>(args)...);
makeLower(string);
return string;
}
template<typename... TArgs>
[[nodiscard]]
auto toUpper(TArgs&&... args)
{
std::basic_string string(std::forward<TArgs>(args)...);
makeUpper(string);
return string;
}
template<typename TNumber>
[[nodiscard]]
bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) noexcept
{
const std::from_chars_result res = std::from_chars(&*stringView.begin(), &*stringView.end(), outNumber, base);
return res.ec == std::errc{} && res.ptr == &*stringView.end();
}
namespace pipe
{
struct Join