Make HTTP header parsing case-insensitive.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 9 Apr 2018 20:56:24 +0000 (22:56 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 9 Apr 2018 20:56:24 +0000 (22:56 +0200)
httpinput.cpp
parse.cpp
parse.h
server.cpp

index 7967c76..bac6b05 100644 (file)
@@ -249,17 +249,15 @@ bool HTTPInput::parse_response(const string &request)
                return false;
        }
 
-       unordered_multimap<string, string> parameters = extract_headers(lines, url);
+       HTTPHeaderMultimap parameters = extract_headers(lines, url);
 
        // Remove “Content-encoding: metacube”.
-       // TODO: Make case-insensitive.
-       const auto encoding_it = parameters.find("Content-encoding");
+       const auto encoding_it = parameters.find("Content-Encoding");
        if (encoding_it != parameters.end() && encoding_it->second == "metacube") {
                parameters.erase(encoding_it);
        }
 
        // Change “Server: foo” to “Server: metacube/0.1 (reflecting: foo)”
-       // TODO: Make case-insensitive.
        // XXX: Use a Via: instead?
        if (parameters.count("Server") == 0) {
                parameters.insert(make_pair("Server", SERVER_IDENTIFICATION));
@@ -273,7 +271,6 @@ bool HTTPInput::parse_response(const string &request)
        }
 
        // Erase “Connection: close”; we'll set it on the sending side if needed.
-       // TODO: Make case-insensitive.
        parameters.erase("Connection");
 
        // Construct the new HTTP header.
index bec742b..3f373c4 100644 (file)
--- a/parse.cpp
+++ b/parse.cpp
@@ -56,9 +56,9 @@ vector<string> split_lines(const string &str)
        return ret;
 }
 
-unordered_multimap<string, string> extract_headers(const vector<string> &lines, const string &log_context)
+HTTPHeaderMultimap extract_headers(const vector<string> &lines, const string &log_context)
 {
-       unordered_multimap<string, string> parameters;
+       HTTPHeaderMultimap parameters;
        for (size_t i = 1; i < lines.size(); ++i) {
                size_t split = lines[i].find(":");
                if (split == string::npos) {
diff --git a/parse.h b/parse.h
index 1924cc0..a2e3580 100644 (file)
--- a/parse.h
+++ b/parse.h
@@ -5,9 +5,42 @@
 
 #include <stddef.h>
 #include <string>
+#include <algorithm>
+#include <string>
 #include <unordered_map>
 #include <vector>
 
+// Locale-unaware tolower(); matches RFC 2616 no matter what the locale is set to.
+static inline char ascii_tolower(const char ch)
+{
+       if (ch >= 'A' && ch <= 'Z') {
+               return ch + 'a' - 'A';
+       } else {
+               return ch;
+       }
+}
+
+// Case-insensitive header comparison and hashing.
+struct HTTPLess {
+       bool operator() (const std::string &a, const std::string &b) const
+       {
+               return std::lexicographical_compare(
+                       begin(a), end(a), begin(b), end(b),
+                       [](char a, char b) {
+                               return ascii_tolower(a) < ascii_tolower(b);
+                       });
+       }
+};
+struct HTTPHash {
+       size_t operator() (const std::string &s) const
+       {
+               std::string s_low = s;
+               for (char &ch : s_low) { ch = ascii_tolower(ch); }
+               return std::hash<std::string>() (s_low);
+       }
+};
+using HTTPHeaderMultimap = std::unordered_multimap<std::string, std::string, HTTPHash, HTTPLess>;
+
 // Split a line on whitespace, e.g. "foo  bar baz" -> {"foo", "bar", "baz"}.
 std::vector<std::string> split_tokens(const std::string &line);
 
@@ -16,8 +49,7 @@ std::vector<std::string> split_lines(const std::string &str);
 
 // Extract HTTP headers from a request or response. Ignores the first line,
 // where the verb or the return code is.
-std::unordered_multimap<std::string, std::string> extract_headers(
-       const std::vector<std::string> &lines, const std::string &log_context);
+HTTPHeaderMultimap extract_headers(const std::vector<std::string> &lines, const std::string &log_context);
 
 // Add the new data to an existing string, looking for \r\n\r\n
 // (typical of HTTP requests and/or responses). Will return one
index 40f3b48..0e056fb 100644 (file)
@@ -911,8 +911,7 @@ int Server::parse_request(Client *client)
        }
 
        // Parse the headers, for logging purposes.
-       // TODO: Case-insensitivity.
-       unordered_multimap<string, string> headers = extract_headers(lines, client->remote_addr);
+       HTTPHeaderMultimap headers = extract_headers(lines, client->remote_addr);
        const auto referer_it = headers.find("Referer");
        if (referer_it != headers.end()) {
                client->referer = referer_it->second;