summaryrefslogtreecommitdiff
path: root/src/transport.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/transport.cc')
-rw-r--r--src/transport.cc195
1 files changed, 195 insertions, 0 deletions
diff --git a/src/transport.cc b/src/transport.cc
new file mode 100644
index 0000000..8195db7
--- /dev/null
+++ b/src/transport.cc
@@ -0,0 +1,195 @@
+#include "common.hh"
+
+#include "pathutil.hh"
+#include "strutil.hh"
+#include "transport.hh"
+
+#include <utility>
+
+namespace {
+
+class NoContentInput : public Transport::Input {
+public:
+ Return fill(Buffer*, size_t) override {
+ return Return::END;
+ }
+
+ void wait_once(std::shared_ptr<Looper>, std::function<void()> callback)
+ override {
+ assert(false);
+ callback();
+ }
+};
+
+class NoContentResponse : public Transport::Response {
+public:
+ explicit NoContentResponse(std::unique_ptr<Transport::Response> response)
+ : response_(std::move(response)) {
+ }
+
+ uint16_t code() const override {
+ return response_->code();
+ }
+
+ std::vector<std::pair<std::string, std::string>> const&
+ headers() const override {
+ return response_->headers();
+ }
+
+ std::unique_ptr<Transport::Input> open_content() override {
+ return std::make_unique<NoContentInput>();
+ }
+
+ void add_header(std::string name, std::string value) override {
+ response_->add_header(std::move(name), std::move(value));
+ }
+
+private:
+ std::unique_ptr<Transport::Response> response_;
+};
+
+void copy_headers(Transport::Response const* src, Transport::Response* dst,
+ std::string_view name) {
+ for (auto const& header_pair : src->headers()) {
+ if (header_pair.first == name) {
+ dst->add_header(std::string(header_pair.first),
+ std::string(header_pair.second));
+ }
+ }
+}
+
+std::optional<std::string_view> get_first_header(
+ Transport::Response const* resp, std::string_view name) {
+ for (auto const& header_pair : resp->headers()) {
+ if (header_pair.first == name) {
+ return header_pair.second;
+ }
+ }
+ return std::nullopt;
+}
+
+class DefaultHandler : public Transport::Handler {
+public:
+ DefaultHandler(std::shared_ptr<Logger> logger, Transport::Handler* handler)
+ : logger_(logger), handler_(handler) {}
+
+ std::unique_ptr<Transport::Response> request(
+ Transport* transport,
+ Transport::Request const* request) override {
+ bool include_content;
+ if (request->method() == "GET") {
+ include_content = true;
+ } else if (request->method() == "HEAD") {
+ include_content = false;
+ } else {
+ return transport->create_data(405, "");
+ }
+
+ auto clean_path = path::cleanup(request->path());
+ if (clean_path != request->path()) {
+ auto response = transport->create_redirect(clean_path, false);
+ return response;
+ }
+
+ auto response = handler_->request(transport, request);
+
+ if (!response)
+ return nullptr;
+
+ bool not_modified = false;
+
+ {
+ auto values = request->header_all("if-none-match");
+ auto etag = get_first_header(response.get(), "ETag");
+
+ bool match = false;
+ for (auto const& value : values) {
+ if (value == "*") {
+ match = true;
+ break;
+ }
+ if (!etag)
+ continue;
+ if (str::starts_with(value, "W/") || str::starts_with(value, "w/")) {
+ if (value.compare(2, value.size() - 2, etag.value()) == 0) {
+ match = true;
+ break;
+ }
+ } else {
+ if (value == etag.value()) {
+ match = true;
+ break;
+ }
+ }
+ }
+
+ if (match)
+ not_modified = true;
+ }
+
+ // TODO: Add If-Modified-Since? Not useful right now tho as we have
+ // no content that returns Date: headers.
+
+ if (not_modified) {
+ auto new_response = transport->create_data(304, "");
+ copy_headers(response.get(), new_response.get(), "Cache-Control");
+ copy_headers(response.get(), new_response.get(), "Content-Location");
+ copy_headers(response.get(), new_response.get(), "Date");
+ copy_headers(response.get(), new_response.get(), "ETag");
+ copy_headers(response.get(), new_response.get(), "Expires");
+ copy_headers(response.get(), new_response.get(), "Vary");
+ return new_response;
+ }
+
+ return include_content
+ ? std::move(response)
+ : std::make_unique<NoContentResponse>(std::move(response));
+ }
+
+private:
+ std::shared_ptr<Logger> logger_;
+ Transport::Handler* const handler_;
+};
+
+} // namespace
+
+void Transport::Response::open_content_async(
+ std::shared_ptr<TaskRunner>,
+ std::function<void(std::unique_ptr<Transport::Input>)> callback) {
+ assert(false);
+ callback(open_content());
+}
+
+std::unique_ptr<Transport::Response> Transport::create_ok_data(
+ std::string data) {
+ if (data.empty())
+ return create_data(204, std::move(data));
+ return create_data(200, std::move(data));
+}
+
+std::unique_ptr<Transport::Response> Transport::create_ok_file(
+ std::filesystem::path path) {
+ return create_file(200, std::move(path));
+}
+
+std::unique_ptr<Transport::Response> Transport::create_ok_exif_thumbnail(
+ std::filesystem::path path) {
+ return create_exif_thumbnail(200, std::move(path));
+}
+
+std::unique_ptr<Transport::Response> Transport::create_not_found() {
+ return create_data(404, std::string());
+}
+
+std::unique_ptr<Transport::Response> Transport::create_redirect(
+ std::string location, bool temporary) {
+ auto ret = create_data(temporary ? 302 : 301, std::string());
+ ret->add_header("Location", std::move(location));
+ return ret;
+}
+
+std::unique_ptr<Transport::Handler> Transport::create_default_handler(
+ std::shared_ptr<Logger> logger,
+ Handler* handler) {
+ return std::make_unique<DefaultHandler>(logger, handler);
+}