Browse Source

Merge pull request #279 from yhirose/multipart

Content receiver support for multipart content (Fix #241)
yhirose 6 years ago
parent
commit
d910bfc303
3 changed files with 454 additions and 186 deletions
  1. 21 7
      README.md
  2. 355 160
      httplib.h
  3. 78 19
      test/test.cc

+ 21 - 7
README.md

@@ -90,7 +90,7 @@ svr.Post("/multipart", [&](const auto& req, auto& res) {
     const auto& file = req.get_file_value("name1");
     const auto& file = req.get_file_value("name1");
     // file.filename;
     // file.filename;
     // file.content_type;
     // file.content_type;
-    auto body = req.body.substr(file.offset, file.length);
+    // file.content;
 });
 });
 
 
 ```
 ```
@@ -118,12 +118,26 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
 ```cpp
 ```cpp
 svr.Post("/content_receiver",
 svr.Post("/content_receiver",
   [&](const Request &req, Response &res, const ContentReader &content_reader) {
   [&](const Request &req, Response &res, const ContentReader &content_reader) {
-    std::string body;
-    content_reader([&](const char *data, size_t data_length) {
-      body.append(data, data_length);
-      return true;
-    });
-    res.set_content(body, "text/plain");
+    if (req.is_multipart_form_data()) {
+      MultipartFiles files;
+      content_reader(
+        [&](const std::string &name, const char *data, size_t data_length) {
+          auto &file = files.find(name)->second;
+          file.content.append(data, data_length);
+          return true;
+        },
+        [&](const std::string &name, const MultipartFile &file) {
+          files.emplace(name, file);
+          return true;
+        });
+    } else {
+      std::string body;
+      content_reader([&](const char *data, size_t data_length) {
+        body.append(data, data_length);
+        return true;
+      });
+      res.set_content(body, "text/plain");
+    }
   });
   });
 ```
 ```
 
 

+ 355 - 160
httplib.h

@@ -119,8 +119,8 @@ using socket_t = SOCKET;
 #ifdef CPPHTTPLIB_USE_POLL
 #ifdef CPPHTTPLIB_USE_POLL
 #include <poll.h>
 #include <poll.h>
 #endif
 #endif
-#include <pthread.h>
 #include <csignal>
 #include <csignal>
+#include <pthread.h>
 #include <sys/select.h>
 #include <sys/select.h>
 #include <sys/socket.h>
 #include <sys/socket.h>
 #include <unistd.h>
 #include <unistd.h>
@@ -196,13 +196,11 @@ using DataSink = std::function<void(const char *data, size_t data_len)>;
 
 
 using Done = std::function<void()>;
 using Done = std::function<void()>;
 
 
-using ContentProvider = std::function<void(size_t offset, size_t length, DataSink sink)>;
-
-using ContentProviderWithCloser = std::function<void(size_t offset, size_t length, DataSink sink, Done done)>;
+using ContentProvider =
+    std::function<void(size_t offset, size_t length, DataSink sink)>;
 
 
-using ContentReceiver = std::function<bool(const char *data, size_t data_length)>;
-
-using ContentReader = std::function<bool(ContentReceiver receiver)>;
+using ContentProviderWithCloser =
+    std::function<void(size_t offset, size_t length, DataSink sink, Done done)>;
 
 
 using Progress = std::function<bool(uint64_t current, uint64_t total)>;
 using Progress = std::function<bool(uint64_t current, uint64_t total)>;
 
 
@@ -212,8 +210,7 @@ using ResponseHandler = std::function<bool(const Response &response)>;
 struct MultipartFile {
 struct MultipartFile {
   std::string filename;
   std::string filename;
   std::string content_type;
   std::string content_type;
-  size_t offset = 0;
-  size_t length = 0;
+  std::string content;
 };
 };
 using MultipartFiles = std::multimap<std::string, MultipartFile>;
 using MultipartFiles = std::multimap<std::string, MultipartFile>;
 
 
@@ -225,6 +222,35 @@ struct MultipartFormData {
 };
 };
 using MultipartFormDataItems = std::vector<MultipartFormData>;
 using MultipartFormDataItems = std::vector<MultipartFormData>;
 
 
+using ContentReceiver =
+    std::function<bool(const char *data, size_t data_length)>;
+
+using MultipartContentReceiver =
+    std::function<bool(const std::string& name, const char *data, size_t data_length)>;
+
+using MultipartContentHeader =
+    std::function<bool(const std::string &name, const MultipartFile &file)>;
+
+class ContentReader {
+  public:
+    using Reader = std::function<bool(ContentReceiver receiver)>;
+    using MultipartReader = std::function<bool(MultipartContentReceiver receiver, MultipartContentHeader header)>;
+
+    ContentReader(Reader reader, MultipartReader muitlpart_reader)
+      : reader_(reader), muitlpart_reader_(muitlpart_reader) {}
+
+    bool operator()(MultipartContentReceiver receiver, MultipartContentHeader header) const {
+      return muitlpart_reader_(receiver, header);
+    }
+
+    bool operator()(ContentReceiver receiver) const {
+      return reader_(receiver);
+    }
+
+    Reader reader_;
+    MultipartReader muitlpart_reader_;
+};
+
 using Range = std::pair<ssize_t, ssize_t>;
 using Range = std::pair<ssize_t, ssize_t>;
 using Ranges = std::vector<Range>;
 using Ranges = std::vector<Range>;
 
 
@@ -262,6 +288,8 @@ struct Request {
   std::string get_param_value(const char *key, size_t id = 0) const;
   std::string get_param_value(const char *key, size_t id = 0) const;
   size_t get_param_value_count(const char *key) const;
   size_t get_param_value_count(const char *key) const;
 
 
+  bool is_multipart_form_data() const;
+
   bool has_file(const char *key) const;
   bool has_file(const char *key) const;
   MultipartFile get_file_value(const char *key) const;
   MultipartFile get_file_value(const char *key) const;
 
 
@@ -394,7 +422,7 @@ public:
     cond_.notify_all();
     cond_.notify_all();
 
 
     // Join...
     // Join...
-    for (auto& t : threads_) {
+    for (auto &t : threads_) {
       t.join();
       t.join();
     }
     }
   }
   }
@@ -475,20 +503,17 @@ public:
   NoThread() {}
   NoThread() {}
   virtual ~NoThread() {}
   virtual ~NoThread() {}
 
 
-  virtual void enqueue(std::function<void()> fn) override {
-    fn();
-  }
+  virtual void enqueue(std::function<void()> fn) override { fn(); }
 
 
-  virtual void shutdown() override {
-  }
+  virtual void shutdown() override {}
 };
 };
 #endif
 #endif
 
 
 class Server {
 class Server {
 public:
 public:
   using Handler = std::function<void(const Request &, Response &)>;
   using Handler = std::function<void(const Request &, Response &)>;
-  using HandlerWithContentReader = std::function<void(const Request &, Response &,
-                             const ContentReader &content_reader)>;
+  using HandlerWithContentReader = std::function<void(
+      const Request &, Response &, const ContentReader &content_reader)>;
   using Logger = std::function<void(const Request &, const Response &)>;
   using Logger = std::function<void(const Request &, const Response &)>;
 
 
   Server();
   Server();
@@ -531,7 +556,7 @@ public:
 protected:
 protected:
   bool process_request(Stream &strm, bool last_connection,
   bool process_request(Stream &strm, bool last_connection,
                        bool &connection_close,
                        bool &connection_close,
-                       const std::function<void(Request &)>& setup_request);
+                       const std::function<void(Request &)> &setup_request);
 
 
   size_t keep_alive_max_count_;
   size_t keep_alive_max_count_;
   time_t read_timeout_sec_;
   time_t read_timeout_sec_;
@@ -540,7 +565,8 @@ protected:
 
 
 private:
 private:
   using Handlers = std::vector<std::pair<std::regex, Handler>>;
   using Handlers = std::vector<std::pair<std::regex, Handler>>;
-  using HandersForContentReader = std::vector<std::pair<std::regex, HandlerWithContentReader>>;
+  using HandersForContentReader =
+      std::vector<std::pair<std::regex, HandlerWithContentReader>>;
 
 
   socket_t create_server_socket(const char *host, int port,
   socket_t create_server_socket(const char *host, int port,
                                 int socket_flags) const;
                                 int socket_flags) const;
@@ -564,7 +590,14 @@ private:
                     Response &res);
                     Response &res);
   bool read_content_with_content_receiver(Stream &strm, bool last_connection,
   bool read_content_with_content_receiver(Stream &strm, bool last_connection,
                                           Request &req, Response &res,
                                           Request &req, Response &res,
-                                          ContentReceiver reveiver);
+                                          ContentReceiver receiver,
+                                          MultipartContentReceiver multipart_receiver,
+                                          MultipartContentHeader multipart_header);
+  bool read_content_core(Stream &strm, bool last_connection,
+                         Request &req, Response &res,
+                         ContentReceiver receiver,
+                         MultipartContentReceiver multipart_receiver,
+                         MultipartContentHeader mulitpart_header);
 
 
   virtual bool process_and_close_socket(socket_t sock);
   virtual bool process_and_close_socket(socket_t sock);
 
 
@@ -574,11 +607,11 @@ private:
   Handler file_request_handler_;
   Handler file_request_handler_;
   Handlers get_handlers_;
   Handlers get_handlers_;
   Handlers post_handlers_;
   Handlers post_handlers_;
-  HandersForContentReader post_handlers_for_content_reader;
+  HandersForContentReader post_handlers_for_content_reader_;
   Handlers put_handlers_;
   Handlers put_handlers_;
-  HandersForContentReader put_handlers_for_content_reader;
+  HandersForContentReader put_handlers_for_content_reader_;
   Handlers patch_handlers_;
   Handlers patch_handlers_;
-  HandersForContentReader patch_handlers_for_content_reader;
+  HandersForContentReader patch_handlers_for_content_reader_;
   Handlers delete_handlers_;
   Handlers delete_handlers_;
   Handlers options_handlers_;
   Handlers options_handlers_;
   Handler error_handler_;
   Handler error_handler_;
@@ -1191,7 +1224,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
       (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
       (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
     int error = 0;
     int error = 0;
     socklen_t len = sizeof(error);
     socklen_t len = sizeof(error);
-    return getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&error), &len) >= 0 &&
+    return getsockopt(sock, SOL_SOCKET, SO_ERROR,
+                      reinterpret_cast<char *>(&error), &len) >= 0 &&
            !error;
            !error;
   }
   }
   return false;
   return false;
@@ -1330,8 +1364,8 @@ inline std::string get_remote_addr(socket_t sock) {
   if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
   if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
     std::array<char, NI_MAXHOST> ipstr{};
     std::array<char, NI_MAXHOST> ipstr{};
 
 
-    if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len, ipstr.data(), ipstr.size(),
-                     nullptr, 0, NI_NUMERICHOST)) {
+    if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
+                     ipstr.data(), ipstr.size(), nullptr, 0, NI_NUMERICHOST)) {
       return ipstr.data();
       return ipstr.data();
     }
     }
   }
   }
@@ -1420,7 +1454,7 @@ inline bool compress(std::string &content) {
   std::array<char, 16384> buff{};
   std::array<char, 16384> buff{};
   do {
   do {
     strm.avail_out = buff.size();
     strm.avail_out = buff.size();
-    strm.next_out = reinterpret_cast<Bytef*>(buff.data());
+    strm.next_out = reinterpret_cast<Bytef *>(buff.data());
     ret = deflate(&strm, Z_FINISH);
     ret = deflate(&strm, Z_FINISH);
     assert(ret != Z_STREAM_ERROR);
     assert(ret != Z_STREAM_ERROR);
     compressed.append(buff.data(), buff.size() - strm.avail_out);
     compressed.append(buff.data(), buff.size() - strm.avail_out);
@@ -1462,7 +1496,7 @@ public:
     std::array<char, 16384> buff{};
     std::array<char, 16384> buff{};
     do {
     do {
       strm.avail_out = buff.size();
       strm.avail_out = buff.size();
-      strm.next_out = reinterpret_cast<Bytef*>(buff.data());
+      strm.next_out = reinterpret_cast<Bytef *>(buff.data());
 
 
       ret = inflate(&strm, Z_NO_FLUSH);
       ret = inflate(&strm, Z_NO_FLUSH);
       assert(ret != Z_STREAM_ERROR);
       assert(ret != Z_STREAM_ERROR);
@@ -1472,7 +1506,9 @@ public:
       case Z_MEM_ERROR: inflateEnd(&strm); return false;
       case Z_MEM_ERROR: inflateEnd(&strm); return false;
       }
       }
 
 
-      if (!callback(buff.data(), buff.size() - strm.avail_out)) { return false; }
+      if (!callback(buff.data(), buff.size() - strm.avail_out)) {
+        return false;
+      }
     } while (strm.avail_out == 0);
     } while (strm.avail_out == 0);
 
 
     return ret == Z_OK || ret == Z_STREAM_END;
     return ret == Z_OK || ret == Z_STREAM_END;
@@ -1844,79 +1880,6 @@ inline bool parse_multipart_boundary(const std::string &content_type,
   return true;
   return true;
 }
 }
 
 
-inline bool parse_multipart_formdata(const std::string &boundary,
-                                     const std::string &body,
-                                     MultipartFiles &files) {
-  static std::string dash = "--";
-  static std::string crlf = "\r\n";
-
-  static std::regex re_content_type("Content-Type: (.*?)$",
-                                    std::regex_constants::icase);
-
-  static std::regex re_content_disposition(
-      "Content-Disposition: form-data; name=\"(.*?)\"(?:; filename=\"(.*?)\")?",
-      std::regex_constants::icase);
-
-  auto dash_boundary = dash + boundary;
-
-  auto pos = body.find(dash_boundary);
-  if (pos != 0) { return false; }
-
-  pos += dash_boundary.size();
-
-  auto next_pos = body.find(crlf, pos);
-  if (next_pos == std::string::npos) { return false; }
-
-  pos = next_pos + crlf.size();
-
-  while (pos < body.size()) {
-    next_pos = body.find(crlf, pos);
-    if (next_pos == std::string::npos) { return false; }
-
-    std::string name;
-    MultipartFile file;
-
-    auto header = body.substr(pos, (next_pos - pos));
-
-    while (pos != next_pos) {
-      std::smatch m;
-      if (std::regex_match(header, m, re_content_type)) {
-        file.content_type = m[1];
-      } else if (std::regex_match(header, m, re_content_disposition)) {
-        name = m[1];
-        file.filename = m[2];
-      }
-
-      pos = next_pos + crlf.size();
-
-      next_pos = body.find(crlf, pos);
-      if (next_pos == std::string::npos) { return false; }
-
-      header = body.substr(pos, (next_pos - pos));
-    }
-
-    pos = next_pos + crlf.size();
-
-    next_pos = body.find(crlf + dash_boundary, pos);
-
-    if (next_pos == std::string::npos) { return false; }
-
-    file.offset = pos;
-    file.length = next_pos - pos;
-
-    pos = next_pos + crlf.size() + dash_boundary.size();
-
-    next_pos = body.find(crlf, pos);
-    if (next_pos == std::string::npos) { return false; }
-
-    files.emplace(name, file);
-
-    pos = next_pos + crlf.size();
-  }
-
-  return true;
-}
-
 inline bool parse_range_header(const std::string &s, Ranges &ranges) {
 inline bool parse_range_header(const std::string &s, Ranges &ranges) {
   try {
   try {
     static auto re_first_range =
     static auto re_first_range =
@@ -1952,6 +1915,178 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) {
   } catch (...) { return false; }
   } catch (...) { return false; }
 }
 }
 
 
+class MultipartFormDataParser {
+public:
+  MultipartFormDataParser() {}
+
+  void set_boundary(const std::string &boundary) {
+    boundary_ = boundary;
+  }
+
+  bool is_valid() const { return is_valid_; }
+
+  template <typename T, typename U>
+  bool parse(const char *buf, size_t n, T content_callback, U header_callback) {
+    static const std::regex re_content_type(R"(^Content-Type:\s*(.*?)\s*$)",
+                                            std::regex_constants::icase);
+
+    static const std::regex re_content_disposition(
+        "^Content-Disposition:\\s*form-data;\\s*name=\"(.*?)\"(?:;\\s*filename="
+        "\"(.*?)\")?\\s*$",
+        std::regex_constants::icase);
+
+    buf_.append(buf, n); // TODO: performance improvement
+
+    while (!buf_.empty()) {
+      switch (state_) {
+      case 0: { // Initial boundary
+        auto pattern = dash_ + boundary_ + crlf_;
+        if (pattern.size() > buf_.size()) { return true; }
+        auto pos = buf_.find(pattern);
+        if (pos != 0) {
+          is_done_ = true;
+          return false;
+        }
+        buf_.erase(0, pattern.size());
+        off_ += pattern.size();
+        state_ = 1;
+        break;
+      }
+      case 1: { // New entry
+        clear_file_info();
+        state_ = 2;
+        break;
+      }
+      case 2: { // Headers
+        auto pos = buf_.find(crlf_);
+        while (pos != std::string::npos) {
+          if (pos == 0) {
+            if (!header_callback(name_, file_)) {
+              is_valid_ = false;
+              is_done_ = false;
+              return false;
+            }
+            buf_.erase(0, crlf_.size());
+            off_ += crlf_.size();
+            state_ = 3;
+            break;
+          }
+
+          auto header = buf_.substr(0, pos);
+          {
+            std::smatch m;
+            if (std::regex_match(header, m, re_content_type)) {
+              file_.content_type = m[1];
+            } else if (std::regex_match(header, m, re_content_disposition)) {
+              name_ = m[1];
+              file_.filename = m[2];
+            }
+          }
+
+          buf_.erase(0, pos + crlf_.size());
+          off_ += pos + crlf_.size();
+          pos = buf_.find(crlf_);
+        }
+        break;
+      }
+      case 3: { // Body
+        {
+          auto pattern = crlf_ + dash_;
+          auto pos = buf_.find(pattern);
+          if (pos == std::string::npos) {
+            pos = buf_.size();
+          }
+          if (!content_callback(name_, buf_.data(), pos)) {
+            is_valid_ = false;
+            is_done_ = false;
+            return false;
+          }
+
+          off_ += pos;
+          buf_.erase(0, pos);
+        }
+
+        {
+          auto pattern = crlf_ + dash_ + boundary_;
+          if (pattern.size() > buf_.size()) { return true; }
+
+          auto pos = buf_.find(pattern);
+          if (pos != std::string::npos) {
+            if (!content_callback(name_, buf_.data(), pos)) {
+              is_valid_ = false;
+              is_done_ = false;
+              return false;
+            }
+
+            off_ += pos + pattern.size();
+            buf_.erase(0, pos + pattern.size());
+            state_ = 4;
+          } else {
+            if (!content_callback(name_, buf_.data(), pattern.size())) {
+              is_valid_ = false;
+              is_done_ = false;
+              return false;
+            }
+
+            off_ += pattern.size();
+            buf_.erase(0, pattern.size());
+          }
+        }
+        break;
+      }
+      case 4: { // Boundary
+        auto pos = buf_.find(crlf_);
+        if (crlf_.size() > buf_.size()) { return true; }
+        if (pos == 0) {
+          buf_.erase(0, crlf_.size());
+          off_ += crlf_.size();
+          state_ = 1;
+        } else {
+          auto pattern = dash_ + crlf_;
+          if (pattern.size() > buf_.size()) { return true; }
+          auto pos = buf_.find(pattern);
+          if (pos == 0) {
+            buf_.erase(0, pattern.size());
+            off_ += pattern.size();
+            is_valid_ = true;
+            state_ = 5;
+          } else {
+            is_done_ = true;
+            return true;
+          }
+        }
+        break;
+      }
+      case 5: { // Done
+        is_valid_ = false;
+        return false;
+      }
+      }
+    }
+
+    return true;
+  }
+
+private:
+  void clear_file_info() {
+    name_.clear();
+    file_.filename.clear();
+    file_.content_type.clear();
+  }
+
+  const std::string dash_ = "--";
+  const std::string crlf_ = "\r\n";
+  std::string boundary_;
+
+  std::string buf_;
+  size_t state_ = 0;
+  size_t is_valid_ = false;
+  size_t is_done_ = false;
+  size_t off_ = 0;
+  std::string name_;
+  MultipartFile file_;
+};
+
 inline std::string to_lower(const char *beg, const char *end) {
 inline std::string to_lower(const char *beg, const char *end) {
   std::string out;
   std::string out;
   auto it = beg;
   auto it = beg;
@@ -2102,6 +2237,15 @@ get_range_offset_and_length(const Request &req, const Response &res,
   return std::make_pair(r.first, r.second - r.first + 1);
   return std::make_pair(r.first, r.second - r.first + 1);
 }
 }
 
 
+inline bool expect_content(const Request &req) {
+  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
+      req.method == "PRI") {
+    return true;
+  }
+  // TODO: check if Content-Length is set
+  return false;
+}
+
 #ifdef _WIN32
 #ifdef _WIN32
 class WSInit {
 class WSInit {
 public:
 public:
@@ -2177,6 +2321,11 @@ inline size_t Request::get_param_value_count(const char *key) const {
   return std::distance(r.first, r.second);
   return std::distance(r.first, r.second);
 }
 }
 
 
+inline bool Request::is_multipart_form_data() const {
+  const auto &content_type = get_header_value("Content-Type");
+  return !content_type.find("multipart/form-data");
+}
+
 inline bool Request::has_file(const char *key) const {
 inline bool Request::has_file(const char *key) const {
   return files.find(key) != files.end();
   return files.find(key) != files.end();
 }
 }
@@ -2369,7 +2518,7 @@ inline Server &Server::Post(const char *pattern, Handler handler) {
 
 
 inline Server &Server::Post(const char *pattern,
 inline Server &Server::Post(const char *pattern,
                             HandlerWithContentReader handler) {
                             HandlerWithContentReader handler) {
-  post_handlers_for_content_reader.push_back(
+  post_handlers_for_content_reader_.push_back(
       std::make_pair(std::regex(pattern), handler));
       std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
 }
 }
@@ -2381,7 +2530,7 @@ inline Server &Server::Put(const char *pattern, Handler handler) {
 
 
 inline Server &Server::Put(const char *pattern,
 inline Server &Server::Put(const char *pattern,
                            HandlerWithContentReader handler) {
                            HandlerWithContentReader handler) {
-  put_handlers_for_content_reader.push_back(
+  put_handlers_for_content_reader_.push_back(
       std::make_pair(std::regex(pattern), handler));
       std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
 }
 }
@@ -2393,7 +2542,7 @@ inline Server &Server::Patch(const char *pattern, Handler handler) {
 
 
 inline Server &Server::Patch(const char *pattern,
 inline Server &Server::Patch(const char *pattern,
                              HandlerWithContentReader handler) {
                              HandlerWithContentReader handler) {
-  patch_handlers_for_content_reader.push_back(
+  patch_handlers_for_content_reader_.push_back(
       std::make_pair(std::regex(pattern), handler));
       std::make_pair(std::regex(pattern), handler));
   return *this;
   return *this;
 }
 }
@@ -2646,50 +2795,88 @@ Server::write_content_with_provider(Stream &strm, const Request &req,
 
 
 inline bool Server::read_content(Stream &strm, bool last_connection,
 inline bool Server::read_content(Stream &strm, bool last_connection,
                                  Request &req, Response &res) {
                                  Request &req, Response &res) {
-  if (!detail::read_content(strm, req, payload_max_length_, res.status,
-                            Progress(), [&](const char *buf, size_t n) {
-                              if (req.body.size() + n > req.body.max_size()) {
-                                return false;
-                              }
-                              req.body.append(buf, n);
-                              return true;
-                            })) {
-    return write_response(strm, last_connection, req, res);
-  }
+  auto ret = read_content_core(strm, last_connection, req, res,
+    [&](const char *buf, size_t n) {
+      if (req.body.size() + n > req.body.max_size()) { return false; }
+      req.body.append(buf, n);
+      return true;
+    },
+    [&](const std::string &name, const char *buf, size_t n) {
+      // TODO: handle elements with a same key
+      auto it = req.files.find(name);
+      auto &content = it->second.content;
+      if (content.size() + n > content.max_size()) { return false; }
+      content.append(buf, n);
+      return true;
+    },
+    [&](const std::string &name, const MultipartFile &file) {
+      req.files.emplace(name, file);
+      return true;
+    }
+  );
 
 
   const auto &content_type = req.get_header_value("Content-Type");
   const auto &content_type = req.get_header_value("Content-Type");
-
   if (!content_type.find("application/x-www-form-urlencoded")) {
   if (!content_type.find("application/x-www-form-urlencoded")) {
     detail::parse_query_text(req.body, req.params);
     detail::parse_query_text(req.body, req.params);
-  } else if (!content_type.find("multipart/form-data")) {
-    std::string boundary;
-    if (!detail::parse_multipart_boundary(content_type, boundary) ||
-        !detail::parse_multipart_formdata(boundary, req.body, req.files)) {
-      res.status = 400;
-      return write_response(strm, last_connection, req, res);
-    }
   }
   }
 
 
-  return true;
+  return ret;
 }
 }
 
 
 inline bool
 inline bool
 Server::read_content_with_content_receiver(Stream &strm, bool last_connection,
 Server::read_content_with_content_receiver(Stream &strm, bool last_connection,
                                            Request &req, Response &res,
                                            Request &req, Response &res,
-                                           ContentReceiver receiver) {
-  if (!detail::read_content(
-          strm, req, payload_max_length_, res.status, Progress(),
-          [&](const char *buf, size_t n) { return receiver(buf, n); })) {
+                                           ContentReceiver receiver,
+                                           MultipartContentReceiver multipart_receiver,
+                                           MultipartContentHeader multipart_header) {
+  return read_content_core(strm, last_connection, req, res,
+      receiver, multipart_receiver, multipart_header);
+}
+
+inline bool
+Server::read_content_core(Stream &strm, bool last_connection,
+                          Request &req, Response &res,
+                          ContentReceiver receiver,
+                          MultipartContentReceiver multipart_receiver,
+                          MultipartContentHeader mulitpart_header) {
+  detail::MultipartFormDataParser multipart_form_data_parser;
+  ContentReceiver out;
+
+  if (req.is_multipart_form_data()) {
+    const auto &content_type = req.get_header_value("Content-Type");
+    std::string boundary;
+    if (!detail::parse_multipart_boundary(content_type, boundary)) {
+      res.status = 400;
+      return write_response(strm, last_connection, req, res);
+    }
+
+    multipart_form_data_parser.set_boundary(boundary);
+    out = [&](const char *buf, size_t n) {
+      return multipart_form_data_parser.parse(buf, n, multipart_receiver, mulitpart_header);
+    };
+  } else {
+    out = receiver;
+  }
+
+  if (!detail::read_content(strm, req, payload_max_length_, res.status,
+                            Progress(), out)) {
     return write_response(strm, last_connection, req, res);
     return write_response(strm, last_connection, req, res);
   }
   }
 
 
+  if (req.is_multipart_form_data()) {
+    if (!multipart_form_data_parser.is_valid()) {
+      res.status = 400;
+      return write_response(strm, last_connection, req, res);
+    }
+  }
+
   return true;
   return true;
 }
 }
 
 
 inline bool Server::handle_file_request(Request &req, Response &res) {
 inline bool Server::handle_file_request(Request &req, Response &res) {
-  for (const auto& kv: base_dirs_) {
-    const auto& mount_point = kv.first;
-    const auto& base_dir = kv.second;
+  for (const auto &kv : base_dirs_) {
+    const auto &mount_point = kv.first;
+    const auto &base_dir = kv.second;
 
 
     // Prefix match
     // Prefix match
     if (!req.path.find(mount_point)) {
     if (!req.path.find(mount_point)) {
@@ -2744,7 +2931,8 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) {
     if (address.ss_family == AF_INET) {
     if (address.ss_family == AF_INET) {
       return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
       return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
     } else if (address.ss_family == AF_INET6) {
     } else if (address.ss_family == AF_INET6) {
-      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
+      return ntohs(
+          reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
     } else {
     } else {
       return -1;
       return -1;
     }
     }
@@ -2800,39 +2988,45 @@ inline bool Server::listen_internal() {
   return ret;
   return ret;
 }
 }
 
 
-inline bool Server::routing(Request &req, Response &res, Stream &strm, bool last_connection) {
+inline bool Server::routing(Request &req, Response &res, Stream &strm,
+                            bool last_connection) {
   // File handler
   // File handler
   if (req.method == "GET" && handle_file_request(req, res)) { return true; }
   if (req.method == "GET" && handle_file_request(req, res)) { return true; }
 
 
-  // Content reader handler
-  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
-    ContentReader content_reader = [&](ContentReceiver receiver) {
-      return read_content_with_content_receiver(strm, last_connection, req, res, receiver);
-    };
+  if (detail::expect_content(req)) {
+    // Content reader handler
+    {
+      ContentReader reader(
+        [&](ContentReceiver receiver) {
+          return read_content_with_content_receiver(strm, last_connection, req, res,
+                                                    receiver, nullptr, nullptr);
+        },
+        [&](MultipartContentReceiver receiver, MultipartContentHeader header) {
+          return read_content_with_content_receiver(strm, last_connection, req, res,
+                                                    nullptr, receiver, header);
+        }
+      );
 
 
-    if (req.method == "POST") {
-      if (dispatch_request_for_content_reader(req, res, content_reader,
-                                              post_handlers_for_content_reader)) {
-        return true;
-      }
-    } else if (req.method == "PUT") {
-      if (dispatch_request_for_content_reader(req, res, content_reader,
-                                              put_handlers_for_content_reader)) {
-        return true;
-      }
-    } else if (req.method == "PATCH") {
-      if (dispatch_request_for_content_reader(
-              req, res, content_reader, patch_handlers_for_content_reader)) {
-        return true;
+      if (req.method == "POST") {
+        if (dispatch_request_for_content_reader(
+                req, res, reader, post_handlers_for_content_reader_)) {
+          return true;
+        }
+      } else if (req.method == "PUT") {
+        if (dispatch_request_for_content_reader(
+                req, res, reader, put_handlers_for_content_reader_)) {
+          return true;
+        }
+      } else if (req.method == "PATCH") {
+        if (dispatch_request_for_content_reader(
+                req, res, reader, patch_handlers_for_content_reader_)) {
+          return true;
+        }
       }
       }
     }
     }
-  }
 
 
-  // Read content into `req.body`
-  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || req.method == "PRI") {
-    if (!read_content(strm, last_connection, req, res)) {
-      return false;
-    }
+    // Read content into `req.body`
+    if (!read_content(strm, last_connection, req, res)) { return false; }
   }
   }
 
 
   // Regular handler
   // Regular handler
@@ -2887,7 +3081,7 @@ Server::dispatch_request_for_content_reader(Request &req, Response &res,
 inline bool
 inline bool
 Server::process_request(Stream &strm, bool last_connection,
 Server::process_request(Stream &strm, bool last_connection,
                         bool &connection_close,
                         bool &connection_close,
-                        const std::function<void(Request &)>& setup_request) {
+                        const std::function<void(Request &)> &setup_request) {
   std::array<char, 2048> buf{};
   std::array<char, 2048> buf{};
 
 
   detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
   detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
@@ -3342,7 +3536,8 @@ inline std::shared_ptr<Response> Client::Get(const char *path,
                                              ResponseHandler response_handler,
                                              ResponseHandler response_handler,
                                              ContentReceiver content_receiver) {
                                              ContentReceiver content_receiver) {
   Progress dummy;
   Progress dummy;
-  return Get(path, headers, std::move(response_handler), content_receiver, dummy);
+  return Get(path, headers, std::move(response_handler), content_receiver,
+             dummy);
 }
 }
 
 
 inline std::shared_ptr<Response> Client::Get(const char *path,
 inline std::shared_ptr<Response> Client::Get(const char *path,

+ 78 - 19
test/test.cc

@@ -30,6 +30,12 @@ const std::string JSON_DATA = "{\"hello\":\"world\"}";
 
 
 const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
 const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB
 
 
+MultipartFile& get_file_value(MultipartFiles &files, const char *key) {
+  auto it = files.find(key);
+  if (it != files.end()) { return it->second; }
+  throw std::runtime_error("invalid mulitpart form data name error");
+}
+
 #ifdef _WIN32
 #ifdef _WIN32
 TEST(StartupTest, WSAStartup) {
 TEST(StartupTest, WSAStartup) {
   WSADATA wsaData;
   WSADATA wsaData;
@@ -676,29 +682,27 @@ protected:
                 {
                 {
                   const auto &file = req.get_file_value("text1");
                   const auto &file = req.get_file_value("text1");
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("", file.filename);
-                  EXPECT_EQ("text default",
-                            req.body.substr(file.offset, file.length));
+                  EXPECT_EQ("text default", file.content);
                 }
                 }
 
 
                 {
                 {
                   const auto &file = req.get_file_value("text2");
                   const auto &file = req.get_file_value("text2");
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("", file.filename);
-                  EXPECT_EQ("aωb", req.body.substr(file.offset, file.length));
+                  EXPECT_EQ("aωb", file.content);
                 }
                 }
 
 
                 {
                 {
                   const auto &file = req.get_file_value("file1");
                   const auto &file = req.get_file_value("file1");
                   EXPECT_EQ("hello.txt", file.filename);
                   EXPECT_EQ("hello.txt", file.filename);
                   EXPECT_EQ("text/plain", file.content_type);
                   EXPECT_EQ("text/plain", file.content_type);
-                  EXPECT_EQ("h\ne\n\nl\nl\no\n",
-                            req.body.substr(file.offset, file.length));
+                  EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content);
                 }
                 }
 
 
                 {
                 {
                   const auto &file = req.get_file_value("file3");
                   const auto &file = req.get_file_value("file3");
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("application/octet-stream", file.content_type);
                   EXPECT_EQ("application/octet-stream", file.content_type);
-                  EXPECT_EQ(0u, file.length);
+                  EXPECT_EQ(0u, file.content.size());
                 }
                 }
               })
               })
         .Post("/empty",
         .Post("/empty",
@@ -753,16 +757,57 @@ protected:
                 EXPECT_EQ("5", req.get_header_value("Content-Length"));
                 EXPECT_EQ("5", req.get_header_value("Content-Length"));
               })
               })
         .Post("/content_receiver",
         .Post("/content_receiver",
-              [&](const Request & /*req*/, Response &res,
-                  const ContentReader &content_reader) {
-                std::string body;
-                content_reader([&](const char *data, size_t data_length) {
-                  EXPECT_EQ(data_length, 7);
-                  body.append(data, data_length);
-                  return true;
-                });
-                EXPECT_EQ(body, "content");
-                res.set_content(body, "text/plain");
+              [&](const Request & req, Response &res, const ContentReader &content_reader) {
+                if (req.is_multipart_form_data()) {
+                  MultipartFiles files;
+                  content_reader(
+                    [&](const std::string &name, const char *data, size_t data_length) {
+                      auto &file = files.find(name)->second;
+                      file.content.append(data, data_length);
+                      return true;
+                    },
+                    [&](const std::string &name, const MultipartFile &file) {
+                      files.emplace(name, file);
+                      return true;
+                    });
+
+                  EXPECT_EQ(5u, files.size());
+
+                  {
+                    const auto &file = get_file_value(files, "text1");
+                    EXPECT_EQ("", file.filename);
+                    EXPECT_EQ("text default", file.content);
+                  }
+
+                  {
+                    const auto &file = get_file_value(files, "text2");
+                    EXPECT_EQ("", file.filename);
+                    EXPECT_EQ("aωb", file.content);
+                  }
+
+                  {
+                    const auto &file = get_file_value(files, "file1");
+                    EXPECT_EQ("hello.txt", file.filename);
+                    EXPECT_EQ("text/plain", file.content_type);
+                    EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content);
+                  }
+
+                  {
+                    const auto &file = get_file_value(files, "file3");
+                    EXPECT_EQ("", file.filename);
+                    EXPECT_EQ("application/octet-stream", file.content_type);
+                    EXPECT_EQ(0u, file.content.size());
+                  }
+                } else {
+                  std::string body;
+                  content_reader([&](const char *data, size_t data_length) {
+                    EXPECT_EQ(data_length, 7);
+                    body.append(data, data_length);
+                    return true;
+                  });
+                  EXPECT_EQ(body, "content");
+                  res.set_content(body, "text/plain");
+                }
               })
               })
         .Put("/content_receiver",
         .Put("/content_receiver",
              [&](const Request & /*req*/, Response &res,
              [&](const Request & /*req*/, Response &res,
@@ -809,14 +854,13 @@ protected:
                 {
                 {
                   const auto &file = req.get_file_value("key1");
                   const auto &file = req.get_file_value("key1");
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("", file.filename);
-                  EXPECT_EQ("test", req.body.substr(file.offset, file.length));
+                  EXPECT_EQ("test", file.content);
                 }
                 }
 
 
                 {
                 {
                   const auto &file = req.get_file_value("key2");
                   const auto &file = req.get_file_value("key2");
                   EXPECT_EQ("", file.filename);
                   EXPECT_EQ("", file.filename);
-                  EXPECT_EQ("--abcdefg123",
-                            req.body.substr(file.offset, file.length));
+                  EXPECT_EQ("--abcdefg123", file.content);
                 }
                 }
               })
               })
 #endif
 #endif
@@ -1518,6 +1562,21 @@ TEST_F(ServerTest, PostContentReceiver) {
   ASSERT_EQ("content", res->body);
   ASSERT_EQ("content", res->body);
 }
 }
 
 
+TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
+  MultipartFormDataItems items = {
+      {"text1", "text default", "", ""},
+      {"text2", "aωb", "", ""},
+      {"file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
+      {"file2", "{\n  \"world\", true\n}\n", "world.json", "application/json"},
+      {"file3", "", "", "application/octet-stream"},
+  };
+
+  auto res = cli_.Post("/content_receiver", items);
+
+  ASSERT_TRUE(res != nullptr);
+  EXPECT_EQ(200, res->status);
+}
+
 TEST_F(ServerTest, PostContentReceiverGzip) {
 TEST_F(ServerTest, PostContentReceiverGzip) {
   auto res = cli_.Post("/content_receiver", "content", "text/plain", true);
   auto res = cli_.Post("/content_receiver", "content", "text/plain", true);
   ASSERT_TRUE(res != nullptr);
   ASSERT_TRUE(res != nullptr);