Added some basic HTTP and ip address parsing.
This commit is contained in:
parent
03c899f17e
commit
35e7131780
2
LibConf
2
LibConf
@ -7,6 +7,8 @@ mijin_sources = Split("""
|
|||||||
source/mijin/debug/symbol_info.cpp
|
source/mijin/debug/symbol_info.cpp
|
||||||
source/mijin/io/process.cpp
|
source/mijin/io/process.cpp
|
||||||
source/mijin/io/stream.cpp
|
source/mijin/io/stream.cpp
|
||||||
|
source/mijin/net/http.cpp
|
||||||
|
source/mijin/net/socket.cpp
|
||||||
source/mijin/util/os.cpp
|
source/mijin/util/os.cpp
|
||||||
source/mijin/types/name.cpp
|
source/mijin/types/name.cpp
|
||||||
source/mijin/virtual_filesystem/filesystem.cpp
|
source/mijin/virtual_filesystem/filesystem.cpp
|
||||||
|
1
SModule
1
SModule
@ -10,6 +10,7 @@ mijin_sources = Split("""
|
|||||||
source/mijin/debug/symbol_info.cpp
|
source/mijin/debug/symbol_info.cpp
|
||||||
source/mijin/io/process.cpp
|
source/mijin/io/process.cpp
|
||||||
source/mijin/io/stream.cpp
|
source/mijin/io/stream.cpp
|
||||||
|
source/mijin/net/http.cpp
|
||||||
source/mijin/net/socket.cpp
|
source/mijin/net/socket.cpp
|
||||||
source/mijin/util/os.cpp
|
source/mijin/util/os.cpp
|
||||||
source/mijin/types/name.cpp
|
source/mijin/types/name.cpp
|
||||||
|
@ -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)
|
mijin::Task<StreamError> Stream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||||
{
|
{
|
||||||
(void) buffer;
|
co_return readRaw(buffer, options, outBytesRead);
|
||||||
(void) options;
|
|
||||||
(void) outBytesRead;
|
|
||||||
|
|
||||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().read, "Stream advertises async read, but doesn't implement it.");
|
|
||||||
co_return StreamError::NOT_SUPPORTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
||||||
{
|
{
|
||||||
(void) buffer;
|
co_return writeRaw(buffer);
|
||||||
|
|
||||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().write, "Stream advertises async write, but doesn't implement it.");
|
|
||||||
co_return StreamError::NOT_SUPPORTED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamError Stream::readBinaryString(std::string& outString)
|
StreamError Stream::readBinaryString(std::string& outString)
|
||||||
|
@ -68,6 +68,7 @@ enum class [[nodiscard]] StreamError
|
|||||||
IO_ERROR = 1,
|
IO_ERROR = 1,
|
||||||
NOT_SUPPORTED = 2,
|
NOT_SUPPORTED = 2,
|
||||||
CONNECTION_CLOSED = 3,
|
CONNECTION_CLOSED = 3,
|
||||||
|
PROTOCOL_ERROR = 4,
|
||||||
UNKNOWN_ERROR = -1
|
UNKNOWN_ERROR = -1
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -430,6 +431,8 @@ inline const char* errorName(StreamError error) noexcept
|
|||||||
return "not supported";
|
return "not supported";
|
||||||
case StreamError::CONNECTION_CLOSED:
|
case StreamError::CONNECTION_CLOSED:
|
||||||
return "connection closed";
|
return "connection closed";
|
||||||
|
case StreamError::PROTOCOL_ERROR:
|
||||||
|
return "protocol error";
|
||||||
case StreamError::UNKNOWN_ERROR:
|
case StreamError::UNKNOWN_ERROR:
|
||||||
return "unknown error";
|
return "unknown error";
|
||||||
}
|
}
|
||||||
|
169
source/mijin/net/http.cpp
Normal file
169
source/mijin/net/http.cpp
Normal 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
55
source/mijin/net/http.hpp
Normal 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)
|
@ -1,13 +1,17 @@
|
|||||||
|
|
||||||
#include "./socket.hpp"
|
#include "./socket.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "../detect.hpp"
|
#include "../detect.hpp"
|
||||||
|
#include "../util/string.hpp"
|
||||||
|
|
||||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include "../util/variant.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace mijin
|
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)
|
StreamError TCPStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||||
{
|
{
|
||||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
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.");
|
MIJIN_ASSERT(!isOpen(), "Socket is already open.");
|
||||||
|
|
||||||
@ -183,13 +258,31 @@ StreamError TCPStream::open(const char* address, std::uint16_t port) noexcept
|
|||||||
{
|
{
|
||||||
return translateErrno();
|
return translateErrno();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 =
|
sockaddr_in connectAddress =
|
||||||
{
|
{
|
||||||
.sin_family = AF_INET,
|
.sin_family = AF_INET,
|
||||||
.sin_port = htons(port),
|
.sin_port = htons(port),
|
||||||
.sin_addr = {inet_addr(address)}
|
.sin_addr = {.s_addr = std::bit_cast<in_addr_t>(address4)}
|
||||||
};
|
};
|
||||||
if (connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) < 0)
|
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_);
|
::close(handle_);
|
||||||
handle_ = -1;
|
handle_ = -1;
|
||||||
|
@ -4,7 +4,10 @@
|
|||||||
#if !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
|
#if !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
|
||||||
#define MIJIN_NET_SOCKET_HPP_INCLUDED 1
|
#define MIJIN_NET_SOCKET_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <variant>
|
||||||
#include "../async/coroutine.hpp"
|
#include "../async/coroutine.hpp"
|
||||||
|
#include "../container/optional.hpp"
|
||||||
#include "../io/stream.hpp"
|
#include "../io/stream.hpp"
|
||||||
|
|
||||||
namespace mijin
|
namespace mijin
|
||||||
@ -14,6 +17,41 @@ namespace mijin
|
|||||||
// public types
|
// 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
|
class Socket
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
@ -61,7 +99,7 @@ public:
|
|||||||
bool isAtEnd() override;
|
bool isAtEnd() override;
|
||||||
StreamFeatures getFeatures() 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;
|
void close() noexcept;
|
||||||
[[nodiscard]] bool isOpen() const noexcept { return handle_ >= 0; }
|
[[nodiscard]] bool isOpen() const noexcept { return handle_ >= 0; }
|
||||||
private:
|
private:
|
||||||
@ -77,7 +115,15 @@ private:
|
|||||||
public:
|
public:
|
||||||
TCPStream& getStream() noexcept override;
|
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(); }
|
void close() noexcept { stream_.close(); }
|
||||||
[[nodiscard]] bool isOpen() const noexcept { return stream_.isOpen(); }
|
[[nodiscard]] bool isOpen() const noexcept { return stream_.isOpen(); }
|
||||||
|
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
||||||
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
|
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <charconv>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@ -24,6 +27,13 @@ namespace mijin
|
|||||||
// public constants
|
// public constants
|
||||||
//
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// public traits
|
||||||
|
//
|
||||||
|
|
||||||
|
template<typename TString>
|
||||||
|
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
|
||||||
|
|
||||||
//
|
//
|
||||||
// public types
|
// public types
|
||||||
//
|
//
|
||||||
@ -151,8 +161,35 @@ bool equalsIgnoreCaseImpl(std::basic_string_view<TChar, TTraitsA> stringA, std::
|
|||||||
return true;
|
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>
|
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>
|
template<typename TLeft, typename TRight>
|
||||||
@ -162,27 +199,46 @@ template<typename TLeft, typename TRight>
|
|||||||
std::basic_string_view(std::forward<TRight>(separator)), options);
|
std::basic_string_view(std::forward<TRight>(separator)), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TChar, typename TTraits>
|
template<typename TString, typename TChars>
|
||||||
std::basic_string_view<TChar, TTraits> trimPrefix(std::basic_string_view<TChar, TTraits> stringView,
|
[[nodiscard]]
|
||||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
auto trimPrefix(TString&& string, TChars&& chars)
|
||||||
{
|
{
|
||||||
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
|
return detail::trimPrefixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||||
return stringView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TChar, typename TTraits>
|
template<typename TString>
|
||||||
std::basic_string_view<TChar, TTraits> trimSuffix(std::basic_string_view<TChar, TTraits> stringView,
|
[[nodiscard]]
|
||||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
auto trimPrefix(TString&& string)
|
||||||
{
|
{
|
||||||
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
|
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||||
return stringView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TChar, typename TTraits>
|
template<typename TString, typename TChars>
|
||||||
std::basic_string_view<TChar, TTraits> trim(std::basic_string_view<TChar, TTraits> stringView,
|
[[nodiscard]]
|
||||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
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>
|
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));
|
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
|
namespace pipe
|
||||||
{
|
{
|
||||||
struct Join
|
struct Join
|
||||||
|
Loading…
x
Reference in New Issue
Block a user