From 62fd535c5f77cea773652fa2f371025c4fd1f32c Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Tue, 18 Sep 2018 16:09:03 +0200 Subject: aptwebserver: Guess Content-Type from filename extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browsing pages served via aptwebserver is working better if we tell the browser the Content-Type which for this simple usecase we can just do by guessing based on the file extension – and because hardcoding a list would be boring we just reuse the mime.types data from mime-support if available and allow it to be overridden by files and config. Gbp-Dch: Ignore --- test/interactive-helper/aptwebserver.cc | 86 ++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) (limited to 'test') diff --git a/test/interactive-helper/aptwebserver.cc b/test/interactive-helper/aptwebserver.cc index 461484f6e..4eebcaaeb 100644 --- a/test/interactive-helper/aptwebserver.cc +++ b/test/interactive-helper/aptwebserver.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include static std::string httpcodeToStr(int const httpcode) /*{{{*/ @@ -88,13 +89,29 @@ static std::string httpcodeToStr(int const httpcode) /*{{{*/ return _config->Find(codeconf, code); } /*}}}*/ -static bool chunkedTransferEncoding(std::list const &headers) { +static bool chunkedTransferEncoding(std::list const &headers)/*{{{*/ +{ if (std::find(headers.begin(), headers.end(), "Transfer-Encoding: chunked") != headers.end()) return true; if (_config->FindB("aptwebserver::chunked-transfer-encoding", false) == true) return true; return false; } + /*}}}*/ +static bool contentTypeSet(std::list const &headers) /*{{{*/ +{ + return std::any_of(headers.begin(), headers.end(), [](std::string const &h) { return APT::String::Startswith(h, "Content-Type:"); }); +} + /*}}}*/ +// contentTypeFromExtension /*{{{*/ +static std::string contentTypeFromExtension(std::string const &ext) +{ + auto t = _config->Find(std::string("aptwebserver::content-type::by-extension::").append(ext)); + if (APT::String::Startswith(t, "text/")) + return t.append("; charset=utf-8"); + return t; +} + /*}}}*/ static void addFileHeaders(std::list &headers, FileFd &data)/*{{{*/ { if (chunkedTransferEncoding(headers) == false) @@ -109,6 +126,20 @@ static void addFileHeaders(std::list &headers, FileFd &data)/*{{{*/ lastmodified.append(TimeRFC1123(data.ModificationTime(), false)); headers.push_back(lastmodified); } + if (_config->FindB("aptwebserver::content-type::guess", true) && + data.FileSize() != 0 && + contentTypeSet(headers) == false) + { + std::string const name = data.Name(); + std::string ext = flExtension(name); + if (name.empty() == false && ext.empty() == false && name != ext) + { + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + auto const type = contentTypeFromExtension(ext); + if (type.empty() == false) + headers.push_back(std::string("Content-Type: ").append(type)); + } + } } /*}}}*/ static void addDataHeaders(std::list &headers, std::string &data)/*{{{*/ @@ -241,6 +272,10 @@ static void sendError(std::ostream &log, int const client, int const httpcode, s headers.push_back("Connection: close"); } addDataHeaders(headers, response); + + if (contentTypeSet(headers) == false) + headers.push_back("Content-Type: text/html; charset=utf-8"); + sendHead(log, client, httpcode, headers); if (content == true) sendData(client, headers, response); @@ -286,6 +321,10 @@ static void sendRedirect(std::ostream &log, int const client, int const httpcode else location.append(uri); headers.push_back(location); + + if (contentTypeSet(headers) == false) + headers.push_back("Content-Type: text/html; charset=utf-8"); + sendHead(log, client, httpcode, headers); if (content == true) sendData(client, headers, response); @@ -372,6 +411,9 @@ static void sendDirectoryListing(std::ostream &log, int const client, std::strin } listing << "" << std::endl; + if (contentTypeSet(headers) == false) + headers.push_back("Content-Type: text/html; charset=utf-8"); + std::string response(listing.str()); addDataHeaders(headers, response); sendHead(log, client, 200, headers); @@ -581,6 +623,8 @@ static bool handleOnTheFlyReconfiguration(std::ostream &log, int const client,/* { std::string response = _config->Find(parts[2], parts[3]); addDataHeaders(headers, response); + if (contentTypeSet(headers) == false) + headers.push_back("Content-Type: text/plain; charset=utf-8"); sendHead(log, client, 200, headers); sendData(client, headers, response); return true; @@ -591,6 +635,8 @@ static bool handleOnTheFlyReconfiguration(std::ostream &log, int const client,/* { std::string response = _config->Find(parts[2]); addDataHeaders(headers, response); + if (contentTypeSet(headers) == false) + headers.push_back("Content-Type: text/plain; charset=utf-8"); sendHead(log, client, 200, headers); sendData(client, headers, response); return true; @@ -833,6 +879,28 @@ static void * handleClient(int const client, size_t const id) /*{{{*/ return NULL; } /*}}}*/ +static void loadMimeTypesFile(std::string const &filename) /*{{{*/ +{ + if (FileExists(filename) == false) + return; + + std::string line; + FileFd mimetypes(filename, FileFd::ReadOnly); + while (mimetypes.ReadLine(line)) + { + if (line.empty() || line[0] == '#' || line.find_first_not_of(" \t\r") == std::string::npos) + continue; + std::transform(line.begin(), line.end(), line.begin(), [](char const c) { return c == ' ' ? '\t' : c; }); + auto l = VectorizeString(line, '\t'); + l.erase(std::remove_if(l.begin(), l.end(), [](std::string const &f) { return f.empty(); }), l.end()); + if (l.size() < 2) + continue; + for (size_t i = 1; i < l.size(); ++i) + if (l[i].empty() == false) + _config->CndSet(std::string("aptwebserver::content-type::by-extension::").append(l[i]).c_str(), l[0]); + } +} + /*}}}*/ int main(int const argc, const char * argv[]) { @@ -854,6 +922,22 @@ int main(int const argc, const char * argv[]) exit(1); } + if (_config->FindB("aptwebserver::content-type::mime.types", true)) + { + if (_config->FindB("aptwebserver::content-type::mime.types::apt", true)) + loadMimeTypesFile("/etc/apt/mime.types"); + + if (_config->FindB("aptwebserver::content-type::mime.types::home", true)) + { + auto const home = getenv("HOME"); + if (home != nullptr) + loadMimeTypesFile(flCombine(home, ".mime.types")); + } + + if (_config->FindB("aptwebserver::content-type::mime.types::etc", true)) + loadMimeTypesFile("/etc/mime.types"); + } + // create socket, bind and listen to it {{{ // ignore SIGPIPE, this can happen on write() if the socket closes connection signal(SIGPIPE, SIG_IGN); -- cgit v1.2.3