From 5dd605d3a2b25bf38eafcc93428d30afb1b78cbb Mon Sep 17 00:00:00 2001
From: yhirose <yuji.hirose.bug@gmail.com>
Date: Mon, 30 Nov 2020 21:46:36 -0500
Subject: [PATCH] Fix #762

---
 httplib.h                     | 58 +++++++++++++++++++---------
 test/fuzzing/server_fuzzer.cc | 72 +++++++++++++++++------------------
 2 files changed, 74 insertions(+), 56 deletions(-)

diff --git a/httplib.h b/httplib.h
index cfbf5ab..d5dda01 100644
--- a/httplib.h
+++ b/httplib.h
@@ -1340,14 +1340,6 @@ inline std::string from_i_to_hex(size_t n) {
   return ret;
 }
 
-inline bool start_with(const std::string &a, const std::string &b) {
-  if (a.size() < b.size()) { return false; }
-  for (size_t i = 0; i < b.size(); i++) {
-    if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
-  }
-  return true;
-}
-
 inline size_t to_utf8(int code, char *buff) {
   if (code < 0x0080) {
     buff[0] = (code & 0x7F);
@@ -3126,7 +3118,7 @@ public:
 
           static const std::string header_name = "content-type:";
           const auto header = buf_.substr(0, pos);
-          if (start_with(header, header_name)) {
+          if (start_with_case_ignore(header, header_name)) {
             file_.content_type = trim_copy(header.substr(header_name.size()));
           } else {
             std::smatch m;
@@ -3148,15 +3140,7 @@ public:
           auto pattern = crlf_ + dash_;
           if (pattern.size() > buf_.size()) { return true; }
 
-          auto pos = buf_.find(pattern);
-          if (pos == std::string::npos) {
-            pos = buf_.size();
-            while (pos > 0) {
-              auto c = buf_[pos - 1];
-              if (c != '\r' && c != '\n' && c != '-') { break; }
-              pos--;
-            }
-          }
+          auto pos = find_string(buf_, pattern);
 
           if (!content_callback(buf_.data(), pos)) {
             is_valid_ = false;
@@ -3166,7 +3150,6 @@ public:
           off_ += pos;
           buf_.erase(0, pos);
         }
-
         {
           auto pattern = crlf_ + dash_ + boundary_;
           if (pattern.size() > buf_.size()) { return true; }
@@ -3230,6 +3213,43 @@ private:
     file_.content_type.clear();
   }
 
+  bool start_with_case_ignore(const std::string &a,
+                              const std::string &b) const {
+    if (a.size() < b.size()) { return false; }
+    for (size_t i = 0; i < b.size(); i++) {
+      if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
+    }
+    return true;
+  }
+
+  bool start_with(const std::string &a, size_t off,
+                  const std::string &b) const {
+    if (a.size() - off < b.size()) { return false; }
+    for (size_t i = 0; i < b.size(); i++) {
+      if (a[i + off] != b[i]) { return false; }
+    }
+    return true;
+  }
+
+  size_t find_string(const std::string &s, const std::string &pattern) const {
+    auto c = pattern.front();
+
+    size_t off = 0;
+    while (off < s.size()) {
+      auto pos = s.find(c, off);
+      if (pos == std::string::npos) { return s.size(); }
+
+      auto rem = s.size() - pos;
+      if (pattern.size() > rem) { return pos; }
+
+      if (start_with(s, pos, pattern)) { return pos; }
+
+      off = pos + 1;
+    }
+
+    return s.size();
+  }
+
   std::string boundary_;
 
   std::string buf_;
diff --git a/test/fuzzing/server_fuzzer.cc b/test/fuzzing/server_fuzzer.cc
index 5ea1032..9fb4d4b 100644
--- a/test/fuzzing/server_fuzzer.cc
+++ b/test/fuzzing/server_fuzzer.cc
@@ -1,28 +1,26 @@
-#include <memory>
 #include <httplib.h>
+#include <memory>
 
 class FuzzedStream : public httplib::Stream {
- public:
-  FuzzedStream(const uint8_t* data, size_t size)
+public:
+  FuzzedStream(const uint8_t *data, size_t size)
       : data_(data), size_(size), read_pos_(0) {}
 
-  ssize_t read(char* ptr, size_t size) override {
-    if (size + read_pos_ > size_) {
-      size = size_ - read_pos_;
-    }
+  ssize_t read(char *ptr, size_t size) override {
+    if (size + read_pos_ > size_) { size = size_ - read_pos_; }
     memcpy(ptr, data_ + read_pos_, size);
     read_pos_ += size;
-    return size;
+    return static_cast<ssize_t>(size);
   }
 
-  ssize_t write(const char* ptr, size_t size) override {
+  ssize_t write(const char *ptr, size_t size) override {
     response_.append(ptr, size);
     return static_cast<int>(size);
   }
 
-  int write(const char* ptr) { return write(ptr, strlen(ptr)); }
+  ssize_t write(const char *ptr) { return write(ptr, strlen(ptr)); }
 
-  int write(const std::string& s) { return write(s.data(), s.size()); }
+  ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
 
   std::string get_remote_addr() const { return ""; }
 
@@ -37,16 +35,16 @@ class FuzzedStream : public httplib::Stream {
 
   socket_t socket() const override { return 0; }
 
- private:
-  const uint8_t* data_;
+private:
+  const uint8_t *data_;
   size_t size_;
   size_t read_pos_;
   std::string response_;
 };
 
 class FuzzableServer : public httplib::Server {
- public:
-  void ProcessFuzzedRequest(FuzzedStream& stream) {
+public:
+  void ProcessFuzzedRequest(FuzzedStream &stream) {
     bool connection_close = false;
     process_request(stream, /*last_connection=*/false, connection_close,
                     nullptr);
@@ -55,36 +53,36 @@ class FuzzableServer : public httplib::Server {
 
 static FuzzableServer g_server;
 
-extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+extern "C" int LLVMFuzzerInitialize(int * /*argc*/, char *** /*argv*/) {
   g_server.Get(R"(.*)",
-                [&](const httplib::Request& req, httplib::Response& res) {
+               [&](const httplib::Request & /*req*/, httplib::Response &res) {
+                 res.set_content("response content", "text/plain");
+               });
+  g_server.Post(R"(.*)",
+                [&](const httplib::Request & /*req*/, httplib::Response &res) {
                   res.set_content("response content", "text/plain");
                 });
-  g_server.Post(R"(.*)",
-                 [&](const httplib::Request& req, httplib::Response& res) {
+  g_server.Put(R"(.*)",
+               [&](const httplib::Request & /*req*/, httplib::Response &res) {
+                 res.set_content("response content", "text/plain");
+               });
+  g_server.Patch(R"(.*)",
+                 [&](const httplib::Request & /*req*/, httplib::Response &res) {
                    res.set_content("response content", "text/plain");
                  });
-  g_server.Put(R"(.*)",
-                [&](const httplib::Request& req, httplib::Response& res) {
-                  res.set_content("response content", "text/plain");
-                });
-  g_server.Patch(R"(.*)",
-                  [&](const httplib::Request& req, httplib::Response& res) {
-                    res.set_content("response content", "text/plain");
-                  });
-  g_server.Delete(R"(.*)",
-                   [&](const httplib::Request& req, httplib::Response& res) {
-                     res.set_content("response content", "text/plain");
-                   });
-  g_server.Options(R"(.*)",
-                    [&](const httplib::Request& req, httplib::Response& res) {
-                      res.set_content("response content", "text/plain");
-                    });
+  g_server.Delete(
+      R"(.*)", [&](const httplib::Request & /*req*/, httplib::Response &res) {
+        res.set_content("response content", "text/plain");
+      });
+  g_server.Options(
+      R"(.*)", [&](const httplib::Request & /*req*/, httplib::Response &res) {
+        res.set_content("response content", "text/plain");
+      });
   return 0;
 }
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
   FuzzedStream stream{data, size};
   g_server.ProcessFuzzedRequest(stream);
   return 0;
-}
\ No newline at end of file
+}