2025-02-06 22:45:14 +00:00
|
|
|
|
module uselesshttpd.util;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
lost+skunk <git.macaw.me/skunky>, 2025;
|
|
|
|
|
Licensed under WTFPL
|
|
|
|
|
*/
|
2025-01-25 22:19:53 +00:00
|
|
|
|
|
|
|
|
|
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];
|
2025-02-06 22:45:14 +00:00
|
|
|
|
return "WTF";
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-08 18:13:13 +00:00
|
|
|
|
short parseAndValidateURL(char[] url, Request* rqst) {
|
|
|
|
|
if (url.length > 2048) return 1; // too long url
|
2025-02-06 22:45:14 +00:00
|
|
|
|
rqst.path = null;
|
|
|
|
|
bool notArgumentPart;
|
|
|
|
|
for (short i; i < url.length; ++i) {
|
|
|
|
|
switch (url[i]) {
|
|
|
|
|
case '?':
|
|
|
|
|
if (notArgumentPart) goto default;
|
|
|
|
|
notArgumentPart = true;
|
|
|
|
|
break;
|
|
|
|
|
case '/':
|
2025-02-08 18:13:13 +00:00
|
|
|
|
if (url.length > i+1 && url[i+1] != '/') rqst.path ~= '/';
|
2025-02-06 22:45:14 +00:00
|
|
|
|
break;
|
|
|
|
|
case '=', '&':
|
2025-02-08 18:13:13 +00:00
|
|
|
|
if (notArgumentPart && url[i-1] != url[i]) rqst.args ~= url[i];
|
2025-02-06 22:45:14 +00:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'A': .. case 'Z':
|
|
|
|
|
case 'a': .. case 'z':
|
|
|
|
|
case '0': .. case '9':
|
|
|
|
|
case '-', '_', '.', '~', '!', '$', '\'', '(', ')', '*', '+', ',', ';', '@', '[', ']', '|', '%':
|
2025-02-08 18:13:13 +00:00
|
|
|
|
if (notArgumentPart) rqst.args ~= url[i];
|
|
|
|
|
else rqst.path ~= url[i];
|
2025-02-06 22:45:14 +00:00
|
|
|
|
break;
|
2025-02-08 18:13:13 +00:00
|
|
|
|
default: return 1; // malformed url
|
2025-02-06 22:45:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-08 18:13:13 +00:00
|
|
|
|
rqst.args ~= '&';
|
|
|
|
|
return 0;
|
2025-02-06 22:45:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2025-01-25 22:19:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2025-02-08 18:13:13 +00:00
|
|
|
|
@("Variant Also Negotiates") variant_also_negotiates = 506,
|
|
|
|
|
@("Insufficient Storage") insufficient_storage = 507, // (WebDAV)
|
|
|
|
|
@("Loop Detected") loop_detected = 508, // (WebDAV)
|
|
|
|
|
@("Not Extended") not_extended = 510,
|
|
|
|
|
@("Network Authentication Required") network_authentication_required = 511
|
2025-01-25 22:19:53 +00:00
|
|
|
|
}
|