diff options
Diffstat (limited to 'src/transport.cc')
| -rw-r--r-- | src/transport.cc | 195 |
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); +} |
