// Matrix Construct // // Copyright (C) Matrix Construct Developers, Authors & Contributors // Copyright (C) 2016-2018 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. namespace ircd::http { using reason_codes = std::unordered_map; using expectation_failure = spirit::qi::expectation_failure; [[clang::internal_linkage, clang::always_destroy]] extern const reason_codes reason; [[noreturn]] static void throw_error(const expectation_failure &, const bool & = false); } BOOST_FUSION_ADAPT_STRUCT ( ircd::http::query, ( decltype(ircd::http::query::first), first ) ( decltype(ircd::http::query::second), second ) ) BOOST_FUSION_ADAPT_STRUCT ( ircd::http::header, ( decltype(ircd::http::header::first), first ) ( decltype(ircd::http::header::second), second ) ) BOOST_FUSION_ADAPT_STRUCT ( ircd::http::line::response, ( decltype(ircd::http::line::response::version), version ) ( decltype(ircd::http::line::response::status), status ) ( decltype(ircd::http::line::response::reason), reason ) ) BOOST_FUSION_ADAPT_STRUCT ( ircd::http::line::request, ( decltype(ircd::http::line::request::method), method ) ( decltype(ircd::http::line::request::path), path ) ( decltype(ircd::http::line::request::query), query ) ( decltype(ircd::http::line::request::fragment), fragment ) ( decltype(ircd::http::line::request::version), version ) ) decltype(ircd::http::reason) ircd::http::reason { { code::CONTINUE, "Continue" }, { code::SWITCHING_PROTOCOLS, "Switching Protocols" }, { code::PROCESSING, "Processing" }, { code::EARLY_HINTS, "Early Hints" }, { code::OK, "OK" }, { code::CREATED, "Created" }, { code::ACCEPTED, "Accepted" }, { code::NON_AUTHORITATIVE_INFORMATION, "Non-Authoritative Information" }, { code::NO_CONTENT, "No Content" }, { code::PARTIAL_CONTENT, "Partial Content" }, { code::MULTIPLE_CHOICES, "Multiple Choices" }, { code::MOVED_PERMANENTLY, "Moved Permanently" }, { code::FOUND, "Found" }, { code::SEE_OTHER, "See Other" }, { code::NOT_MODIFIED, "Not Modified" }, { code::USE_PROXY, "Use Proxy" }, { code::SWITCH_PROXY, "Switch Proxy" }, { code::TEMPORARY_REDIRECT, "Temporary Redirect" }, { code::PERMANENT_REDIRECT, "Permanent Redirect" }, { code::BAD_REQUEST, "Bad Request" }, { code::UNAUTHORIZED, "Unauthorized" }, { code::FORBIDDEN, "Forbidden" }, { code::NOT_FOUND, "Not Found" }, { code::METHOD_NOT_ALLOWED, "Method Not Allowed" }, { code::NOT_ACCEPTABLE, "Not Acceptable" }, { code::REQUEST_TIMEOUT, "Request Time-out" }, { code::CONFLICT, "Conflict" }, { code::GONE, "Gone" }, { code::LENGTH_REQUIRED, "Length Required" }, { code::PAYLOAD_TOO_LARGE, "Payload Too Large" }, { code::REQUEST_URI_TOO_LONG, "Request URI Too Long" }, { code::UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" }, { code::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable" }, { code::EXPECTATION_FAILED, "Expectation Failed" }, { code::IM_A_TEAPOT, "Negative, I Am A Meat Popsicle" }, { code::UNPROCESSABLE_ENTITY, "Unprocessable Entity" }, { code::PRECONDITION_REQUIRED, "Precondition Required" }, { code::TOO_MANY_REQUESTS, "Too Many Requests" }, { code::REQUEST_HEADER_FIELDS_TOO_LARGE, "Request Header Fields Too Large" }, { code::INTERNAL_SERVER_ERROR, "Internal Server Error" }, { code::NOT_IMPLEMENTED, "Not Implemented" }, { code::BAD_GATEWAY, "Bad Gateway" }, { code::SERVICE_UNAVAILABLE, "Service Unavailable" }, { code::GATEWAY_TIMEOUT, "Gateway Timeout" }, { code::HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported" }, { code::INSUFFICIENT_STORAGE, "Insufficient Storage" }, { code::CLOUDFLARE_REFUSED, "Cloudflare Customer Connection Refused" }, { code::CLOUDFLARE_TIMEDOUT, "Cloudflare Customer Connection Timed-out" }, { code::CLOUDFLARE_UNREACHABLE, "Cloudflare Customer Unreachable" }, { code::CLOUDFLARE_REQUEST_TIMEOUT, "Cloudflare Customer Request Time-out" }, }; namespace ircd::http::parser { using namespace ircd::spirit; template struct [[clang::internal_linkage]] rule :qi::rule { using qi::rule::rule; }; const expr NUL { lit('\0') ,"nul" }; // insignificant whitespaces const expr SP { lit('\x20') ,"space" }; const expr HT { lit('\x09') ,"horizontal tab" }; const expr ws { SP | HT ,"whitespace" }; const expr CR { lit('\x0D') ,"carriage return" }; const expr LF { lit('\x0A') ,"line feed" }; const expr CRLF { CR >> LF ,"carriage return, line feed" }; const expr illegal { NUL | CR | LF ,"illegal" }; const expr colon { lit(':') ,"colon" }; const expr slash { lit('/') ,"forward solidus" }; const expr question { lit('?') ,"question mark" }; const expr pound { lit('#') ,"pound sign" }; const expr equal { lit('=') ,"equal sign" }; const expr ampersand { lit('&') ,"ampersand" }; const rule token { raw[+(char_ - (illegal | ws))] ,"token" }; const rule str { raw[+(char_ - illegal)] ,"string" }; const rule line { *ws >> -str >> CRLF ,"line" }; const rule status { raw[repeat(3)[char_('0','9')]] ,"status" }; const rule reason { str ,"reason" }; const rule head_key { raw[+(char_ - (illegal | ws | colon))] ,"head key" }; const rule head_val { -str ,"head value" }; const rule header { head_key >> *ws >> colon >> *ws >> head_val ,"header" }; const rule<> headers { header % (*ws >> CRLF) ,"headers" }; const expr query_terminator { equal | question | ampersand | pound ,"query terminator" }; const expr query_illegal { illegal | ws | query_terminator ,"query illegal" }; const rule query_key { raw[+(char_ - query_illegal)] ,"query key" }; const rule query_val { raw[*(char_ - query_illegal)] ,"query value" }; const rule method { token ,"method" }; const rule path { raw[-slash >> *(char_ - query_illegal)] ,"path" }; const rule fragment { pound >> -token ,"fragment" }; const rule version { token ,"version" }; const rule content_size { ulong_ ,"content length" }; const rule chunk_size { qi::uint_parser{} ,"chunk size" }; const rule chunk_extensions { ';' >> raw[str] //TODO: extensions ,"chunk extensions" }; const rule query { query_key >> -(equal >> query_val) ,"query" }; const rule query_string { question >> -raw[(query_key >> -(equal >> query_val)) % ampersand] ,"query string" }; const rule request_line { method >> +SP >> path >> -query_string >> -fragment >> +SP >> version ,"request line" }; const rule response_line { version >> +SP >> status >> -(+SP >> -reason) ,"response line" }; const rule<> request { request_line >> *ws >> CRLF >> -headers >> CRLF ,"request" }; const rule<> response { response_line >> *ws >> CRLF >> -headers >> CRLF ,"response" }; static size_t content_length(const string_view &val); } /// Compose a request. This prints an HTTP head into the buffer. No real IO is /// done here. After composing into the buffer, the user can then drive the /// socket by sending the header and the content as specified. /// /// If termination is false, no extra CRLF is printed to the buffer allowing /// additional headers not specified to be appended later. ircd::http::request::request(window_buffer &out, const string_view &host, const string_view &method, const string_view &uri, const size_t &content_length, const string_view &content_type, const vector_view &headers, const bool &termination) { writeline(out, [&method, &uri](const mutable_buffer &out) -> size_t { assert(!method.empty()); assert(!uri.empty()); return fmt::sprintf { out, "%s %s HTTP/1.1", method, uri }; }); if(!has(headers, "host")) writeline(out, [&host](const mutable_buffer &out) -> size_t { assert(!host.empty()); return fmt::sprintf { out, "Host: %s", host }; }); if(content_length && !has(headers, "content-type")) writeline(out, [&content_type](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8" }; }); if(!has(headers, "content-length")) writeline(out, [&content_length](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "Content-Length: %zu", content_length }; }); // Note: pass your own user-agent with an empty value to mute this header. if(!has(headers, "user-agent") && info::user_agent) writeline(out, [](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "User-Agent: %s", info::user_agent }; }); write(out, headers); if(termination) writeline(out); } namespace ircd::http { static void assign(request::head &, const header &); } ircd::http::request::head::head(parse::capstan &pc, const headers::closure &closure) :line::request{pc} ,uri { fragment? string_view { begin(path), end(fragment) }: query? string_view { begin(path), end(query) }: string_view { begin(path), end(path) } } ,headers{[this, &pc, &closure] { const bool version_compatible { this->version == "HTTP/1.0" || this->version == "HTTP/1.1" }; if(unlikely(!version_compatible)) throw error { HTTP_VERSION_NOT_SUPPORTED, fmt::snstringf { 128, "Sorry, no speak '%s'. Try HTTP/1.1 here.", this->version } }; return http::headers { pc, [this, &closure](const auto &header) { assign(*this, header); if(likely(closure)) closure(header); } }; }()} { } ircd::http::request::head::operator string_view() const { const string_view request_line { static_cast(*this) }; if(request_line.empty() || headers.empty()) return request_line; return string_view { request_line.begin(), headers.end() }; } void ircd::http::assign(request::head &head, const header &header) { char buf[64]; const auto &[key_, val] {header}; assert(size(key_) <= sizeof(buf)); const auto &key { tolower(buf, key_) }; if(key == "content-length"_sv) head.content_length = parser::content_length(val); else if(key == "host"_sv) head.host = val; else if(key == "expect"_sv) head.expect = val; else if(key == "te"_sv) head.te = val; else if(key == "authorization"_sv) head.authorization = val; else if(key == "connection"_sv) head.connection = val; else if(key == "content-type"_sv) head.content_type = val; else if(key == "user-agent"_sv) head.user_agent = val; else if(key == "upgrade"_sv) head.upgrade = val; else if(key == "range"_sv) head.range = val; else if(key == "if-range"_sv) head.if_range = val; else if(key == "forwarded"_sv) head.forwarded[0] = val; else if(key == "x-forwarded-for"_sv) head.forwarded_for[0] = val; else if(key == "x-forwarded-host"_sv) head.forwarded_host[0] = val; } ircd::http::response::response(window_buffer &out, const code &code, const size_t &content_length, const string_view &content_type, const http::headers &headers_s, const vector_view &headers_v, const bool &termination) { const auto has_header{[&headers_s, &headers_v] (const string_view &key) -> bool { return has(headers_v, key) || has(headers_s, key); }}; writeline(out, [&code](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "HTTP/1.1 %u %s", uint(code), status(code) }; }); const bool write_server_header { code >= 200 && code < 300 && !has_header("server") }; if(write_server_header) writeline(out, [&code](const mutable_buffer &out) -> size_t { size_t ret{0}; ret += copy(out, "Server: "_sv); ret += copy(out + ret, ircd::info::server_agent); return ret; }); const bool write_date_header { code < 400 && !has_header("date") }; if(write_date_header) writeline(out, [](const mutable_buffer &out) -> size_t { char date_buf[96]; return fmt::sprintf { out, "Date: %s", timef(date_buf, ircd::localtime) }; }); const bool write_content_type_header { code != NO_CONTENT && content_type && content_length && !has_header("content-type") }; if(write_content_type_header) writeline(out, [&content_type](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "Content-Type: %s", content_type?: "text/plain; charset=utf-8" }; }); const bool write_content_length_header { code != NO_CONTENT && content_length != size_t(-1) // chunked encoding indication && !has_header("content-length") }; if(write_content_length_header) writeline(out, [&content_length](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "Content-Length: %zu", content_length }; }); const bool write_transfer_encoding_chunked { content_length == size_t(-1) && !has_header("transfer-encoding") }; if(write_transfer_encoding_chunked) writeline(out, [&content_length](const mutable_buffer &out) -> size_t { return copy(out, "Transfer-Encoding: chunked"_sv); }); if(!headers_s.empty()) out([&headers_s](const mutable_buffer &out) { assert(endswith(headers_s, "\r\n")); return copy(out, headers_s); }); if(!headers_v.empty()) write(out, headers_v); if(termination) writeline(out); } namespace ircd::http { static void assign(response::head &, const header &); } ircd::http::response::head::head(parse::capstan &pc) try :line::response{pc} ,headers { pc, [this](const auto &header) { assign(*this, header); } } { } catch(const expectation_failure &e) { throw_error(e, true); } ircd::http::response::head::head(parse::capstan &pc, const headers::closure &closure) try :line::response{pc} ,headers { pc, [this, &closure](const auto &header) { assign(*this, header); closure(header); } } { } catch(const expectation_failure &e) { throw_error(e, true); } void ircd::http::assign(response::head &head, const header &header) { char buf[96]; const auto &[key_, val] {header}; assert(size(key_) <= sizeof(buf)); if(unlikely(size(key_) > sizeof(buf))) throw error { REQUEST_HEADER_FIELDS_TOO_LARGE }; const auto &key { tolower(buf, key_) }; if(key == "content-length"_sv) head.content_length = parser::content_length(val); else if(key == "content-type"_sv) head.content_type = val; else if(key == "content-range"_sv) head.content_range = val; else if(key == "accept-range"_sv) head.content_range = val; else if(key == "transfer-encoding"_sv) head.transfer_encoding = val; else if(key == "server"_sv) head.server = val; else if(key == "location"_sv) head.location = val; } namespace ircd::http::parser { extern const rule parse_chunk_head; } decltype(ircd::http::parser::parse_chunk_head) ircd::http::parser::parse_chunk_head { eoi | (eps > (chunk_size >> -chunk_extensions)) ,"chunk head" }; ircd::http::response::chunk::chunk(parse::capstan &pc) try :line{pc} { const char *start(line::begin()), *const stop(line::end()); const auto res { ircd::parse(start, stop, parser::parse_chunk_head, this->size) }; assert(res == true); } catch(const expectation_failure &e) { throw_error(e, true); } // // headers // bool ircd::http::has(const vector_view &headers, const string_view &key) { return end(headers) != std::find_if(begin(headers), end(headers), [&key] (const header &header) { return header == key; }); } bool ircd::http::has(const headers &headers, const string_view &key) { return headers.has(key); } // // headers::headers // decltype(ircd::http::headers::terminator) ircd::http::headers::terminator { "\r\n\r\n" }; ircd::http::headers::headers(parse::capstan &pc, closure c) :string_view{[&pc, &c] () -> string_view { header h{pc}; const char *const &started{h.first.data()}, *stopped{started}; for(; !h.first.empty(); stopped = pc.parsed, h = header{pc}) if(c && !c(h)) c = {}; return { started, stopped }; }()} { } bool ircd::http::headers::has(const string_view &key) const { // has header if break early from for_each return !for_each([&key] (const header &header) { // true to continue; false to break (for_each protocol) return iequals(header.first, key)? false : true; }); } ircd::string_view ircd::http::headers::at(const string_view &key) const { const string_view ret { this->operator[](key) }; if(unlikely(!ret)) throw std::out_of_range{key}; return ret; } ircd::string_view ircd::http::headers::operator[](const string_view &key) const { string_view ret; for_each([&key, &ret](const auto &header) { if(iequals(header.first, key)) { ret = header.second; return false; } else return true; }); return ret; } bool ircd::http::headers::for_each(const closure &closure) const { if(empty()) return true; parse::buffer pb{const_buffer{*this}}; parse::capstan pc{pb}; header h{pc}; for(; !h.first.empty(); h = header{pc}) if(!closure(h)) return false; return true; } // // header // namespace ircd::http::parser { extern const rule parse_header; extern const rule parse_request; extern const rule parse_response; } decltype(ircd::http::parser::parse_header) ircd::http::parser::parse_header { eoi | expect[header] ,"header" }; decltype(ircd::http::parser::parse_request) ircd::http::parser::parse_request { expect[request_line] ,"request head line" }; decltype(ircd::http::parser::parse_response) ircd::http::parser::parse_response { expect[response_line] ,"response head line" }; ircd::http::header::header(const line &line) try { const char *start(line.data()), *const stop(line.data() + line.size()); const auto ok { ircd::parse(start, stop, parser::parse_header, *this) }; assert(ok); assert(start == stop); } catch(const expectation_failure &e) { throw_error(e); } ircd::http::line::response::response(const line &line) try { const char *start(line.data()), *const stop(line.data() + line.size()); const auto ok { ircd::parse(start, stop, parser::parse_response, *this) }; assert(ok); assert(start == stop); } catch(const expectation_failure &e) { throw_error(e, true); } ircd::http::line::request::request(const line &line) try { const char *start(line.data()), *const stop(line.data() + line.size()); const auto ok { ircd::parse(start, stop, parser::parse_request, *this) }; assert(ok); assert(start == stop); } catch(const expectation_failure &e) { throw_error(e); } ircd::http::line::request::operator string_view() const { if(method.empty()) return string_view{}; assert(!version.empty()); assert(method.begin() < version.end()); return string_view { method.begin(), version.end() }; } // // http::line // namespace ircd::http::parser { extern const rule parse_line; } decltype(ircd::http::parser::parse_line) ircd::http::parser::parse_line { expect[line] ,"line" }; decltype(ircd::http::line::terminator) ircd::http::line::terminator { "\r\n" }; ircd::http::line::line(parse::capstan &pc) { auto &ret(static_cast(*this)); pc([&ret](const char *&start, const char *const &stop) { if(start == stop) return false; const bool ok { ircd::parse(start, stop, parser::parse_line, ret) }; assert(ok); return ok; }); } // // query::string // bool ircd::http::query::string::has(const string_view &key) const { bool ret{false}; for_each(key, [&ret] (const auto &) noexcept { ret = true; return false; }); return ret; } size_t ircd::http::query::string::count(const string_view &key) const { size_t ret{0}; for_each(key, [&ret] (const auto &) noexcept { ++ret; return true; }); return ret; } ircd::vector_view ircd::http::query::string::array(const mutable_buffer &buf, const string_view &key, string_view *const &out, const size_t &max) const { if(unlikely(!max)) return {}; size_t ret(0); window_buffer window(buf); for_each(key, [&out, &max, &ret, &window] (const auto &query) { window([&out, &max, &ret, &query] (const mutable_buffer &buf) { assert(ret < max); const auto &[_, val] {query}; out[ret] = url::decode(buf, val); return out[ret]; }); return ++ret < max; }); return vector_view { out, ret }; } ircd::string_view ircd::http::query::string::at(const string_view &key, const size_t &idx) const { const string_view &ret { _get(key, idx) }; if(unlikely(!ret)) { char buf[1024]; const string_view msg { fmt::sprintf { buf, "Failed to find value for required query string key '%s' #%zu.", key, idx } }; throw std::out_of_range { msg.c_str() // fmt::sprintf() will null terminate msg }; } return ret; } ircd::string_view ircd::http::query::string::operator[](const string_view &key) const { return _get(key, 0); } ircd::string_view ircd::http::query::string::_get(const string_view &key, size_t idx) const { string_view ret; for_each(key, [&idx, &ret] (const auto &query) noexcept { if(!idx--) { ret = query.second; return false; } else return true; }); return ret; } bool ircd::http::query::string::for_each(const string_view &key, const closure &closure) const { return for_each([&key, &closure] (const auto &query) { if(query.first != key) return true; return closure(query); }); } bool ircd::http::query::string::for_each(const closure &view) const { bool ret{true}; const auto action{[&view, &ret] (const auto &attribute, const auto &context, auto &continue_) { ret = view(attribute); continue_ = ret; }}; const parser::rule<> grammar { -parser::question >> (parser::query[action] % parser::ampersand) ,"query strings" }; const string_view &s(*this); const char *start(s.begin()), *const stop(s.end()); ircd::parse(start, stop, grammar); return ret; } // // parser util // size_t ircd::http::parser::content_length(const string_view &str) { const char *start(str.data()), *const stop(start + str.size()); size_t ret; const bool parsed { ircd::parse(start, stop, parser::content_size, ret) }; if(!parsed || ret >= 256_GiB) throw error { BAD_REQUEST, "Invalid content-length value" }; return ret; } // // util // ircd::const_buffer ircd::http::writechunk(const mutable_buffer &buf, const uint32_t &chunk_size) { window_buffer window{buf}; writechunk(window, chunk_size); return window.completed(); } void ircd::http::writechunk(window_buffer &buf, const uint32_t &chunk_size) { writeline(buf, [&chunk_size] (const mutable_buffer &out) -> size_t { assert(size(out) >= (8 + 1)); return ::snprintf(data(out), size(out), "%08x", chunk_size); }); } std::string ircd::http::strung(const vector_view &headers) { return ircd::string(serialized(headers), [&] (window_buffer out) { write(out, headers); return out.consumed(); }); } /// Indicates the buffer size required to write these headers. This size /// may include room for a terminating null character which may be written /// by write(headers). Only use write(headers) to know the actually written /// string size (without null) not this. size_t ircd::http::serialized(const vector_view &headers) { // Because the write(header) functions use fmt::sprintf we have to // indicate an extra space for a null string terminator to not overlof const size_t initial{!headers.empty()}; return std::accumulate(std::begin(headers), std::end(headers), initial, [] (auto &&ret, const auto &pair) { // key : SP value CRLF return ret += pair.first.size() + 1 + 1 + pair.second.size() + 2; }); } void ircd::http::write(window_buffer &out, const vector_view &headers) { for(const auto &header : headers) write(out, header); } void ircd::http::write(window_buffer &out, const header &header) { if(header.second.empty()) return; assert(!header.first.empty()); if(unlikely(header.first.empty())) return; writeline(out, [&header](const mutable_buffer &out) -> size_t { return fmt::sprintf { out, "%s: %s", header.first, header.second }; }); } /// Close over the user's closure to append a newline. void ircd::http::writeline(window_buffer &write, const window_buffer::closure &closure) { // A new window_buffer is implicit constructed out of the mutable_buffer // otherwise presented to this closure as its write window. write([&closure](window_buffer write) { const auto newline{[](const mutable_buffer &out) { return copy(out, line::terminator); }}; write(closure); write(newline); return write.consumed(); }); } void ircd::http::writeline(window_buffer &write) { writeline(write, [] (const mutable_buffer &out) noexcept { return 0; }); } /// Called to translate a grammar exception into an http::error within our /// system. This will then usually propagate back to our client. /// /// If we are a client to another server, set internal=true. Even though this /// still generates an HTTP error, the code is 500 so if it propagates back to /// a client it does not indicate to *that* client that *they* made a bad /// request from a 400 back to them. void ircd::http::throw_error(const expectation_failure &e, const bool &internal) { const auto &code_ { internal? code::INTERNAL_SERVER_ERROR: code::BAD_REQUEST }; const char *const &fmtstr { internal? "I expected a valid HTTP %s. Server sent %zu invalid characters starting with `%s'.": "I require a valid HTTP %s. You sent %zu invalid characters starting with `%s'." }; const auto &rule { ircd::string(e.what_) }; throw error { code_, fmt::snstringf { 512, fmtstr, between(rule, "<", ">"), size_t(e.last - e.first), string_view{e.first, e.last} } }; } // // error // ircd::http::error::error(const http::code &code, std::string content, const vector_view &headers) :http::error { code, std::move(content), strung(headers) } { } ircd::http::error::error(const http::code &code, std::string content, std::string headers) :ircd::error { generate_skip } ,content { std::move(content) } ,headers { std::move(headers) } ,code{code} { auto &buf(ircd::error::buf); const auto msg(status(code)); ::snprintf ( buf, sizeof(buf), "%u %s", uint(code), msg? msg.c_str(): "" ); } // Out-of-line placement. ircd::http::error::~error() noexcept { } // // status // namespace ircd::http::parser { extern const rule status_codepoint; extern const rule status_category; extern const rule status_code; } decltype(ircd::http::parser::status_codepoint) ircd::http::parser::status_codepoint { qi::uint_parser{} ,"status codepoint" }; decltype(ircd::http::parser::status_category) ircd::http::parser::status_category { &char_('1','9') >> status_codepoint >> omit[repeat(2)[char_('0','9')] >> (eoi | ws)] ,"status category" }; decltype(ircd::http::parser::status_code) ircd::http::parser::status_code { qi::uint_parser{} >> omit[eoi | ws] ,"status code" }; enum ircd::http::code ircd::http::status(const string_view &str) { short ret; const char *start(begin(str)), *const stop(end(str)); const bool parsed { ircd::parse(start, stop, parser::status_code, ret) }; if(!parsed) throw ircd::error { "Invalid HTTP status code" }; assert(ret >= 0 && ret < 1000); return http::code(ret); } ircd::string_view ircd::http::status(const enum code &code) noexcept try { return reason.at(code); } catch(const std::out_of_range &e) { log::dwarning { "No reason string for HTTP status code %u", uint(code) }; return ""_sv; } enum ircd::log::level ircd::http::severity(const enum category &category) noexcept { switch(category) { case http::category::NONE: return log::level::DERROR; case http::category::SUCCESS: return log::level::DEBUG; case http::category::REDIRECT: return log::level::DWARNING; case http::category::ERROR: return log::level::DERROR; case http::category::SERVER: return log::level::DERROR; case http::category::INTERNAL: return log::level::ERROR; default: case http::category::UNKNOWN: return log::level::DWARNING; } } enum ircd::http::category ircd::http::category(const string_view &str) noexcept { enum category ret; const char *start(begin(str)), *const stop(end(str)); const bool parsed { ircd::parse(start, stop, parser::status_category, ret) }; if(!parsed || ret > category::UNKNOWN) ret = category::UNKNOWN; assert(uint8_t(ret) <= uint8_t(category::UNKNOWN)); return ret; } enum ircd::http::category ircd::http::category(const enum code &code) noexcept { if(ushort(code) == 0) return category::NONE; if(ushort(code) < 200) return category::INFO; if(ushort(code) < 300) return category::SUCCESS; if(ushort(code) < 400) return category::REDIRECT; if(ushort(code) < 500) return category::ERROR; if(ushort(code) == 500) return category::INTERNAL; if(ushort(code) < 600) return category::SERVER; return category::UNKNOWN; } ircd::string_view ircd::http::category(const enum category &category) noexcept { switch(category) { case category::NONE: return "NONE"; case category::INFO: return "INFO"; case category::SUCCESS: return "SUCCESS"; case category::REDIRECT: return "REDIRECT"; case category::ERROR: return "ERROR"; case category::SERVER: return "SERVER"; case category::INTERNAL: return "INTERNAL"; case category::UNKNOWN: break; } return "UNKNOWN"; }