Files
moloapp/example/fw/Http.cpp
T

267 lines
8.2 KiB
C++
Raw Normal View History

#include "Http.hpp"
#include <cctype>
#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<std::pair<std::string, std::string>>& 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<std::string> 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<std::string> 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<std::string, std::string> parse_query(std::string_view qs) {
std::map<std::string, std::string> 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<std::string, std::string> parse_cookies(std::string_view header) {
std::map<std::string, std::string> 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<std::string, std::string> 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