#include "Http.hpp" #include #include "Caps.hpp" namespace molo::example::fw { Method parse_method(std::string_view name) { if (name == "GET") return Method::Get; if (name == "POST") return Method::Post; if (name == "PUT") return Method::Put; if (name == "PATCH") return Method::Patch; if (name == "DELETE") return Method::Delete; if (name == "HEAD") return Method::Head; if (name == "OPTIONS") return Method::Options; if (name == "PROPFIND") return Method::Propfind; if (name == "PROPPATCH") return Method::Proppatch; if (name == "MKCOL") return Method::Mkcol; if (name == "COPY") return Method::Copy; if (name == "MOVE") return Method::Move; if (name == "LOCK") return Method::Lock; if (name == "UNLOCK") return Method::Unlock; return Method::Unknown; } std::string method_name(Method method) { switch (method) { case Method::Get: return "GET"; case Method::Post: return "POST"; case Method::Put: return "PUT"; case Method::Patch: return "PATCH"; case Method::Delete: return "DELETE"; case Method::Head: return "HEAD"; case Method::Options: return "OPTIONS"; case Method::Propfind: return "PROPFIND"; case Method::Proppatch: return "PROPPATCH"; case Method::Mkcol: return "MKCOL"; case Method::Copy: return "COPY"; case Method::Move: return "MOVE"; case Method::Lock: return "LOCK"; case Method::Unlock: return "UNLOCK"; case Method::Unknown: return "UNKNOWN"; } return "UNKNOWN"; } namespace { bool name_eq(std::string_view a, std::string_view b) { if (a.size() != b.size()) return false; for (std::size_t i = 0; i < a.size(); ++i) { if (std::tolower((unsigned char)a[i]) != std::tolower((unsigned char)b[i])) return false; } return true; } } // namespace void Headers::set(std::string name, std::string value) { for (auto& e : entries_) { if (name_eq(e.first, name)) { e.second = value; return; } } entries_.emplace_back(std::move(name), std::move(value)); } std::string Headers::get(std::string_view name) const { for (const auto& e : entries_) { if (name_eq(e.first, name)) return e.second; } return ""; } bool Headers::has(std::string_view name) const { for (const auto& e : entries_) { if (name_eq(e.first, name)) return true; } return false; } const std::vector>& Headers::entries() const { return entries_; } std::string Request::query_or(std::string_view key, std::string_view fallback) const { auto it = query.find(std::string(key)); if (it == query.end()) return std::string(fallback); return it->second; } std::string Request::param_or(std::string_view key, std::string_view fallback) const { auto it = path_params.find(std::string(key)); if (it == path_params.end()) return std::string(fallback); return it->second; } std::string Request::cookie_or(std::string_view key, std::string_view fallback) const { auto it = cookies.find(std::string(key)); if (it == cookies.end()) return std::string(fallback); return it->second; } Response::Response(int status, std::string body, std::string content_type) : status(status), body(std::move(body)) { headers.set("Content-Type", std::move(content_type)); } std::string Response::content_type() const { return headers.get("Content-Type"); } TextResponse::TextResponse(std::string body, int status) : Response(status, std::move(body), "text/plain; charset=utf-8") {} HtmlResponse::HtmlResponse(std::string body, int status) : Response(status, std::move(body), "text/html; charset=utf-8") {} JsonResponse::JsonResponse(std::string body, int status) : Response(status, std::move(body), "application/json") {} XmlResponse::XmlResponse(std::string body, int status) : Response(status, std::move(body), "application/xml") {} RedirectResponse::RedirectResponse(std::string location, bool permanent) : Response(permanent ? 301 : 302, "", "text/plain; charset=utf-8") { headers.set("Location", std::move(location)); } FileResponse::FileResponse(std::string body, std::string content_type, std::string filename) : Response(200, std::move(body), std::move(content_type)) { headers.set("Content-Disposition", "attachment; filename=\"" + filename + "\""); } StreamingResponse::StreamingResponse(std::vector chunks) : Response(200, "", "text/plain; charset=utf-8"), chunks(std::move(chunks)) { for (const auto& c : this->chunks) body += c; headers.set("Transfer-Encoding", "chunked"); if (this->chunks.size() > caps::kMaxStreamChunks) { status = 500; body = "chunk cap exceeded"; } } SseResponse::SseResponse(std::vector events) : Response(200, "", "text/event-stream"), events(std::move(events)) { for (const auto& e : this->events) body += "data: " + e + "\n\n"; } std::string url_decode(std::string_view in) { std::string out; out.reserve(in.size()); for (std::size_t i = 0; i < in.size(); ++i) { if (in[i] == '%' && i + 2 < in.size() && std::isxdigit((unsigned char)in[i + 1]) && std::isxdigit((unsigned char)in[i + 2])) { int hi = std::isdigit((unsigned char)in[i + 1]) ? in[i + 1] - '0' : std::tolower((unsigned char)in[i + 1]) - 'a' + 10; int lo = std::isdigit((unsigned char)in[i + 2]) ? in[i + 2] - '0' : std::tolower((unsigned char)in[i + 2]) - 'a' + 10; out.push_back((char)(hi * 16 + lo)); i += 2; } else if (in[i] == '+') { out.push_back(' '); } else { out.push_back(in[i]); } } return out; } std::map parse_query(std::string_view qs) { std::map out; std::size_t start = 0; std::size_t fields = 0; while (start <= qs.size() && fields < caps::kMaxFormFields) { std::size_t amp = qs.find('&', start); if (amp == std::string_view::npos) amp = qs.size(); std::string_view pair = qs.substr(start, amp - start); std::size_t eq = pair.find('='); if (eq == std::string_view::npos) { if (!pair.empty()) out[url_decode(pair)] = ""; } else { out[url_decode(pair.substr(0, eq))] = url_decode(pair.substr(eq + 1)); } ++fields; start = amp + 1; } return out; } std::map parse_cookies(std::string_view header) { std::map out; std::size_t start = 0; while (start < header.size()) { std::size_t semi = header.find(';', start); if (semi == std::string_view::npos) semi = header.size(); std::string_view pair = header.substr(start, semi - start); std::size_t eq = pair.find('='); if (eq != std::string_view::npos) { std::string k(pair.substr(0, eq)); std::string v(pair.substr(eq + 1)); while (!k.empty() && k.front() == ' ') k.erase(k.begin()); while (!v.empty() && v.front() == ' ') v.erase(v.begin()); if (!k.empty()) out[k] = v; } start = semi + 1; } return out; } std::map parse_form(std::string_view body) { return parse_query(body); } std::string status_text(int status) { switch (status) { case 200: return "OK"; case 201: return "Created"; case 207: return "Multi-Status"; case 301: return "Moved Permanently"; case 302: return "Found"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 409: return "Conflict"; case 413: return "Content Too Large"; case 422: return "Unprocessable Entity"; case 429: return "Too Many Requests"; case 500: return "Internal Server Error"; case 501: return "Not Implemented"; default: return "Unknown"; } } std::string json_escape(std::string_view in) { std::string out; out.reserve(in.size() + 2); for (char c : in) { switch (c) { case '"': out += "\\\""; break; case '\\': out += "\\\\"; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: out.push_back(c); break; } } return out; } } // namespace molo::example::fw