From 79e6912b5698cb2264860c08eed534d3be8ed98f Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Wed, 8 Jan 2025 15:48:02 -0500 Subject: [PATCH] Added IPv6 toString implementation. --- .gitignore | 1 + dub.json | 2 +- source/handy_http_primitives/address.d | 57 ++++++++++++++----------- source/handy_http_primitives/request.d | 3 +- source/handy_http_primitives/response.d | 14 +++++- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index cc93e58..71011c0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ handy-http-primitives-test-* *.o *.obj *.lst +*.a diff --git a/dub.json b/dub.json index acb0565..f28ec8b 100644 --- a/dub.json +++ b/dub.json @@ -7,6 +7,6 @@ "streams": "~>3.5.0" }, "description": "Basic HTTP types that can be shared among various projects.", - "license": "MIT", + "license": "CC0", "name": "handy-http-primitives" } \ No newline at end of file diff --git a/source/handy_http_primitives/address.d b/source/handy_http_primitives/address.d index 17f22b6..756bcd2 100644 --- a/source/handy_http_primitives/address.d +++ b/source/handy_http_primitives/address.d @@ -10,18 +10,6 @@ module handy_http_primitives.address; struct IPv4InternetAddress { const ubyte[4] bytes; const ushort port; - - string toString() const { - char[21] buffer; - size_t idx; - for (size_t i = 0; i < 4; i++) { - writeUIntToBuffer(bytes[i], buffer, idx); - if (i < 3) buffer[idx++] = '.'; - } - buffer[idx++] = ':'; - writeUIntToBuffer(port, buffer, idx); - return buffer[0 .. idx].idup; - } } /** @@ -31,10 +19,6 @@ struct IPv4InternetAddress { struct IPv6InternetAddress { const ubyte[16] bytes; const ushort port; - - string toString() const { - return "Not implemented!"; - } } /** @@ -43,10 +27,6 @@ struct IPv6InternetAddress { */ struct UnixSocketAddress { const string path; - - string toString() const { - return path; - } } /// Defines the different possible address types, used by `ClientAddress`. @@ -66,14 +46,33 @@ struct ClientAddress { const IPv6InternetAddress ipv6InternetAddress; const UnixSocketAddress unixSocketAddress; + /** + * Serializes this address in a human-readable string representation. + * Returns: The string representation of this address. + */ string toString() const { - if (type == ClientAddressType.IPv4) { - return ipv4InternetAddress.toString(); - } else if (type == ClientAddressType.IPv6) { - return ipv6InternetAddress.toString(); - } else { - return unixSocketAddress.toString(); + if (type == ClientAddressType.UNIX) return unixSocketAddress.path; + version (Posix) { import core.sys.posix.arpa.inet : inet_ntop, AF_INET, AF_INET6; } + version (Windows) { import core.sys.windows.winsock2 : inet_ntop, AF_INET, AF_INET6; } + const int addressFamily = type == ClientAddressType.IPv4 + ? AF_INET + :AF_INET6; + const scope void* inputBytes = type == ClientAddressType.IPv4 + ? ipv4InternetAddress.bytes.ptr + : ipv6InternetAddress.bytes.ptr; + const ushort port = type == ClientAddressType.IPv4 + ? ipv4InternetAddress.port + : ipv6InternetAddress.port; + char[45] buf; // Buffer is sized to maximum possible IPv6 length (39 chars), plus 6 chars for port string. + auto ret = inet_ntop(addressFamily, inputBytes, buf.ptr, buf.length); + if (ret is null) { + throw new Exception("Failed to serialize address."); } + size_t strLength = 0; + while (buf[strLength] != '\0') strLength++; + buf[strLength++] = ':'; + writeUIntToBuffer(port, buf, strLength); + return buf[0..strLength].idup; } static ClientAddress ofIPv4(IPv4InternetAddress addr) { @@ -92,6 +91,12 @@ struct ClientAddress { unittest { ClientAddress addr = ClientAddress.ofIPv4(IPv4InternetAddress([127, 0, 0, 1], 8000)); assert(addr.toString == "127.0.0.1:8000"); + ClientAddress addr6 = ClientAddress.ofIPv6(IPv6InternetAddress( + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + 8000 + )); + assert(addr6.toString == ":::8000"); + // TODO: Add more comprehensive testing. } /** diff --git a/source/handy_http_primitives/request.d b/source/handy_http_primitives/request.d index 1140656..d3eaa69 100644 --- a/source/handy_http_primitives/request.d +++ b/source/handy_http_primitives/request.d @@ -3,7 +3,6 @@ module handy_http_primitives.request; import streams : InputStream; import std.traits : EnumMembers; -import handy_http_primitives.multivalue_map; import handy_http_primitives.optional; import handy_http_primitives.address; @@ -21,7 +20,7 @@ struct ServerHttpRequest { /// The URL that was requested. const string url = ""; /// A case-insensitive map of all request headers. - const(CaseInsensitiveStringMultiValueMap) headers; + const(string[][string]) headers; /// The underlying stream used to read the body from the request. InputStream!ubyte inputStream; } diff --git a/source/handy_http_primitives/response.d b/source/handy_http_primitives/response.d index a787232..9a31924 100644 --- a/source/handy_http_primitives/response.d +++ b/source/handy_http_primitives/response.d @@ -21,8 +21,8 @@ struct ServerHttpResponse { * A struct containing basic information about a response status. */ struct StatusInfo { - const ushort code; - const string text; + ushort code; + string text; } /** @@ -101,3 +101,13 @@ enum HttpStatus : StatusInfo { NOT_EXTENDED = StatusInfo(510, "Not Extended"), NETWORK_AUTHENTICATION_REQUIRED = StatusInfo(511, "Network Authentication Required") } + +/// Common "Content-Type" header values. +enum ContentTypes : string { + APPLICATION_JSON = "application/json", + APPLICATION_XML = "application/xml", + + TEXT_PLAIN = "text/plain", + TEXT_HTML = "text/html", + TEXT_CSS = "text/css" +}