module uselesshttpd.util; /* lost+skunk , 2025; Licensed under WTFPL */ char[] intToStr(T)(T num) { char[] buf; for(short i; num > 0; ++i) { buf = (num % 10 + '0')~buf; num /= 10; } return buf; } void err(int e, string msg) { if (e != 0) throw new Exception("Something went wrong: failed to "~msg~'.'); } // UDA struct Location { string path; } // enum method; string getStatus(short status) { static foreach(mmbr; __traits(allMembers, Statuses)) if (__traits(getMember, Statuses, mmbr) == status) return __traits(getAttributes, __traits(getMember, Statuses, mmbr))[0]; return "WTF"; } // FIXME: memory leak void append(char[]* src, char symb) @nogc { import core.memory: pureMalloc, pureFree; auto arr = cast (char[]) pureMalloc(src.length + 1) [0..src.length + 1]; arr[$-1..$] = symb; arr[0..$-1] = *src; *src = arr; arr = null; pureFree(arr.ptr); } void parseAndValidateURL(char[] url, Request* rqst) { if (url.length > 2048) throw new Exception("Too long URL"); rqst.path = null; bool notArgumentPart; scope (exit) append(&rqst.args, '&'); for (short i; i < url.length; ++i) { switch (url[i]) { case '?': if (notArgumentPart) goto default; notArgumentPart = true; break; case '/': if (url.length > i+1 && url[i+1] != '/') append(&rqst.path, '/'); break; case '=', '&': if (notArgumentPart && url[i-1] != url[i]) append(&rqst.args, url[i]); break; case 'A': .. case 'Z': case 'a': .. case 'z': case '0': .. case '9': case '-', '_', '.', '~', '!', '$', '\'', '(', ')', '*', '+', ',', ';', '@', '[', ']', '|', '%': if (notArgumentPart) append(&rqst.args, url[i]); else append(&rqst.path, url[i]); break; default: throw new Exception("Malformed URL"); } } } struct Request { enum Methods { GET = "GET", PUT = "PUT", POST = "POST", DELETE = "DELETE", OPTIONS = "OPTIONS", // остальное лень реализовывать, да и не трэба.. } Methods method; void[] body; string[string] headers; char[] path; char[] args; char[] getArgument(string arg) @nogc nothrow { short split, prev; for (short i; i < args.length; ++i) { if (args[i] == '=') split = i; else if (args[i] == '&') { if (arg == args[prev..split]) return args[split+1..i]; prev = ++i; } } return null; } } private static enum Statuses: short { // спизженно с https://github.com/zigzap/zap/blob/675c65b509d48c21a8d1fa4c5ec53fc407643a3b/src/http.zig#L6 // Information responses @("Continue") continuee = 100, @("Switching Protocols") switching_protocols = 101, @("Processing") processing = 102, // (WebDAV) @("Early Hints") early_hints = 103, // Successful responses @("OK") ok = 200, @("Created") created = 201, @("Accepted") accepted = 202, @("Non-Authoritative Information") non_authoritative_information = 203, @("No Content") no_content = 204, @("Reset Content") reset_content = 205, @("Partial Content") partial_content = 206, @("Multi-Status") multi_status = 207, // (WebDAV) @("Already Reported") already_reported = 208, // (WebDAV) @("IM Used") im_used = 226, // (HTTP Delta encoding) // Redirection messages @("Multiple Choices") multiple_choices = 300, @("Moved Permanently") moved_permanently = 301, @("Found") found = 302, @("See Other") see_other = 303, @("Not Modified") not_modified = 304, @("Use Proxy") use_proxy = 305, @("Unused") unused = 306, @("Temporary Redirect") temporary_redirect = 307, @("Permanent Redirect") permanent_redirect = 308, // Client error responses @("Bad Request") bad_request = 400, @("Unauthorized") unauthorized = 401, @("Payment Required") payment_required = 402, @("Forbidden") forbidden = 403, @("Not Found") not_found = 404, @("Method Not Allowed") method_not_allowed = 405, @("Not Acceptable") not_acceptable = 406, @("Proxy Authentication Required") proxy_authentication_required = 407, @("Request Timeout") request_timeout = 408, @("Conflict") conflict = 409, @("Gone") gone = 410, @("Length Required") length_required = 411, @("Precondition Failed") precondition_failed = 412, @("Payload Too Large") payload_too_large = 413, @("URI Too Long") uri_too_long = 414, @("Unsupported Media Type") unsupported_media_type = 415, @("Range Not Satisfiable") range_not_satisfiable = 416, @("Expectation Failed") expectation_failed = 417, @("I'm a teapot") im_a_teapot = 418, @("Misdirected Request") misdirected_request = 421, @("Unprocessable Content") unprocessable_content = 422, // (WebDAV) @("Locked") locked = 423, // (WebDAV) @("Failed Dependency") failed_dependency = 424, // (WebDAV) @("Too Early") too_early = 425, @("Upgrade Required") upgrade_required = 426, @("Precondition Required") precondition_required = 428, @("Too Many Requests") too_many_requests = 429, @("Request Header Fields Too Large") request_header_fields_too_large = 431, @("Unavailable For Legal Reasons") unavailable_for_legal_reasons = 451, // Server error responses @("Internal Server Error") internal_server_error = 500, @("Not Implemented") not_implemented = 501, @("Bad Gateway") bad_gateway = 502, @("Service Unavailable") service_unavailable = 503, @("Gateway Timeout") gateway_timeout = 504, @("HTTP Version Not Supported") http_version_not_supported = 505, @("Variant Also Negotiates") variant_also_negotiates = 506 }