From f29ccc9c208fd67749a7a1a3accc1ad871c2962d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 29 Oct 2015 00:21:36 +0900 Subject: [PATCH] nghttpd: Read /etc/mime.types to set content-type header field User can change file name using --mime-types-file option. --- src/HttpServer.cc | 48 ++++++++++++++++++++++++++++++++++++----------- src/HttpServer.h | 9 +++++++-- src/nghttpd.cc | 11 +++++++++++ src/util.cc | 38 +++++++++++++++++++++++++++++++++++++ src/util.h | 3 +++ 5 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 7b55fe61..2e313d85 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -100,11 +100,12 @@ template void append_nv(Stream *stream, const Array &nva) { } // namespace Config::Config() - : stream_read_timeout(1_min), stream_write_timeout(1_min), - data_ptr(nullptr), padding(0), num_worker(1), max_concurrent_streams(100), - header_table_size(-1), port(0), verbose(false), daemon(false), - verify_client(false), no_tls(false), error_gzip(false), - early_response(false), hexdump(false), echo_upload(false) {} + : mime_types_file("/etc/mime.types"), stream_read_timeout(1_min), + stream_write_timeout(1_min), data_ptr(nullptr), padding(0), num_worker(1), + max_concurrent_streams(100), header_table_size(-1), port(0), + verbose(false), daemon(false), verify_client(false), no_tls(false), + error_gzip(false), early_response(false), hexdump(false), + echo_upload(false) {} Config::~Config() {} @@ -739,6 +740,7 @@ int Http2Handler::verify_npn_result() { int Http2Handler::submit_file_response(const std::string &status, Stream *stream, time_t last_modified, off_t file_length, + const std::string *content_type, nghttp2_data_provider *data_prd) { std::string content_length = util::utos(file_length); std::string last_modified_str; @@ -747,12 +749,16 @@ int Http2Handler::submit_file_response(const std::string &status, http2::make_nv_ls("content-length", content_length), http2::make_nv_ll("cache-control", "max-age=3600"), http2::make_nv_ls("date", sessions_->get_cached_date()), - http2::make_nv_ll("", ""), http2::make_nv_ll("", "")); + http2::make_nv_ll("", ""), http2::make_nv_ll("", ""), + http2::make_nv_ll("", "")); size_t nvlen = 5; if (last_modified != 0) { last_modified_str = util::http_date(last_modified); nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str); } + if (content_type) { + nva[nvlen++] = http2::make_nv_ls("content-type", *content_type); + } auto &trailer = get_config()->trailer; std::string trailer_names; if (!trailer.empty()) { @@ -974,7 +980,7 @@ bool prepare_upload_temp_store(Stream *stream, Http2Handler *hd) { // Ordinary request never start with "echo:". The length is 0 for // now. We will update it when we get whole request body. stream->file_ent = sessions->cache_fd(std::string("echo:") + tempfn, - FileEntry(tempfn, 0, 0, fd)); + FileEntry(tempfn, 0, 0, fd, nullptr)); stream->echo_upload = true; return true; } @@ -1100,8 +1106,28 @@ void prepare_response(Stream *stream, Http2Handler *hd, return; } + const std::string *content_type = nullptr; + + if (path[path.size() - 1] == '/') { + static const std::string TEXT_HTML = "text/html"; + content_type = &TEXT_HTML; + } else { + auto ext = path.c_str() + path.size() - 1; + for (; path.c_str() < ext && *ext != '.' && *ext != '/'; --ext) + ; + if (*ext == '.') { + ++ext; + + const auto &mime_types = hd->get_config()->mime_types; + auto content_type_itr = mime_types.find(ext); + if (content_type_itr != std::end(mime_types)) { + content_type = &(*content_type_itr).second; + } + } + } + file_ent = sessions->cache_fd( - path, FileEntry(path, buf.st_size, buf.st_mtime, file)); + path, FileEntry(path, buf.st_size, buf.st_mtime, file, content_type)); } stream->file_ent = file_ent; @@ -1116,7 +1142,7 @@ void prepare_response(Stream *stream, Http2Handler *hd, stream->headers)->value; if (method == "HEAD") { hd->submit_file_response("200", stream, file_ent->mtime, file_ent->length, - nullptr); + file_ent->content_type, nullptr); return; } @@ -1128,7 +1154,7 @@ void prepare_response(Stream *stream, Http2Handler *hd, data_prd.read_callback = file_read_callback; hd->submit_file_response("200", stream, file_ent->mtime, file_ent->length, - &data_prd); + file_ent->content_type, &data_prd); } } // namespace @@ -1625,7 +1651,7 @@ FileEntry make_status_body(int status, uint16_t port) { assert(0); } - return FileEntry(util::utos(status), nwrite, 0, fd); + return FileEntry(util::utos(status), nwrite, 0, fd, nullptr); } } // namespace diff --git a/src/HttpServer.h b/src/HttpServer.h index 7128ddb2..de43eeec 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -51,6 +51,7 @@ namespace nghttp2 { struct Config { std::map> push; + std::map mime_types; Headers trailer; std::string htdocs; std::string host; @@ -58,6 +59,7 @@ struct Config { std::string cert_file; std::string dh_param_file; std::string address; + std::string mime_types_file; ev_tstamp stream_read_timeout; ev_tstamp stream_write_timeout; void *data_ptr; @@ -81,13 +83,15 @@ struct Config { class Http2Handler; struct FileEntry { - FileEntry(std::string path, int64_t length, int64_t mtime, int fd) + FileEntry(std::string path, int64_t length, int64_t mtime, int fd, + const std::string *content_type) : path(std::move(path)), length(length), mtime(mtime), dlprev(nullptr), - dlnext(nullptr), fd(fd), usecount(1) {} + dlnext(nullptr), content_type(content_type), fd(fd), usecount(1) {} std::string path; int64_t length; int64_t mtime; FileEntry *dlprev, *dlnext; + const std::string *content_type; int fd; int usecount; }; @@ -123,6 +127,7 @@ public: int submit_file_response(const std::string &status, Stream *stream, time_t last_modified, off_t file_length, + const std::string *content_type, nghttp2_data_provider *data_prd); int submit_response(const std::string &status, int32_t stream_id, diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 93dafe74..ba41d132 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -160,6 +160,10 @@ Options: are used. --echo-upload Send back uploaded content if method is POST or PUT. + --mime-types-file= + Path to file that contains MIME media types and the + extensions that represent them. + Default: )" << config.mime_types_file << R"( --version Display version information and exit. -h, --help Display this help and exit. @@ -202,6 +206,7 @@ int main(int argc, char **argv) { {"trailer", required_argument, &flag, 6}, {"hexdump", no_argument, &flag, 7}, {"echo-upload", no_argument, &flag, 8}, + {"mime-types-file", required_argument, &flag, 9}, {nullptr, 0, nullptr, 0}}; int option_index = 0; int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:", long_options, @@ -328,6 +333,10 @@ int main(int argc, char **argv) { // echo-upload option config.echo_upload = true; break; + case 9: + // mime-types-file option + config.mime_types_file = optarg; + break; } break; default: @@ -366,6 +375,8 @@ int main(int argc, char **argv) { config.htdocs = "./"; } + config.mime_types = util::read_mime_types(config.mime_types_file.c_str()); + set_color_output(color || isatty(fileno(stdout))); struct sigaction act {}; diff --git a/src/util.cc b/src/util.cc index 6b304344..756a3e16 100644 --- a/src/util.cc +++ b/src/util.cc @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -1217,6 +1218,43 @@ uint64_t get_uint64(const uint8_t *data) { return n; } +std::map read_mime_types(const char *filename) { + std::map res; + + std::ifstream infile(filename); + if (!infile) { + std::cerr << "Could not open mime types file: " << filename << std::endl; + return res; + } + + auto delim_pred = [](char c) { return c == ' ' || c == '\t'; }; + + std::string line; + while (std::getline(infile, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + + auto type_end = std::find_if(std::begin(line), std::end(line), delim_pred); + if (type_end == std::begin(line)) { + continue; + } + + auto ext_end = type_end; + for (;;) { + auto ext_start = std::find_if_not(ext_end, std::end(line), delim_pred); + if (ext_start == std::end(line)) { + break; + } + ext_end = std::find_if(ext_start, std::end(line), delim_pred); + res.emplace(std::string(ext_start, ext_end), + std::string(std::begin(line), type_end)); + } + } + + return res; +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index 101911cb..c386cf52 100644 --- a/src/util.h +++ b/src/util.h @@ -44,6 +44,7 @@ #include #include #include +#include #include "http-parser/http_parser.h" @@ -706,6 +707,8 @@ uint32_t get_uint32(const uint8_t *data); // order and returns it in host byte order. uint64_t get_uint64(const uint8_t *data); +std::map read_mime_types(const char *filename); + } // namespace util } // namespace nghttp2