You've already forked cpp-httplib
							
							Fix #121
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -150,6 +150,20 @@ httplib::Params params{
 | 
				
			|||||||
auto res = cli.Post("/post", params);
 | 
					auto res = cli.Post("/post", params);
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### POST with Multipart Form Data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					  httplib::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("/multipart", items);
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### PUT
 | 
					### PUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```c++
 | 
					```c++
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										108
									
								
								httplib.h
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								httplib.h
									
									
									
									
									
								
							@@ -67,6 +67,7 @@ typedef int socket_t;
 | 
				
			|||||||
#include <map>
 | 
					#include <map>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <mutex>
 | 
					#include <mutex>
 | 
				
			||||||
 | 
					#include <random>
 | 
				
			||||||
#include <regex>
 | 
					#include <regex>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
#include <sys/stat.h>
 | 
					#include <sys/stat.h>
 | 
				
			||||||
@@ -138,6 +139,14 @@ struct MultipartFile {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
typedef std::multimap<std::string, MultipartFile> MultipartFiles;
 | 
					typedef std::multimap<std::string, MultipartFile> MultipartFiles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MultipartFormData {
 | 
				
			||||||
 | 
					  std::string name;
 | 
				
			||||||
 | 
					  std::string content;
 | 
				
			||||||
 | 
					  std::string filename;
 | 
				
			||||||
 | 
					  std::string content_type;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					typedef std::vector<MultipartFormData> MultipartFormDataItems;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Request {
 | 
					struct Request {
 | 
				
			||||||
  std::string version;
 | 
					  std::string version;
 | 
				
			||||||
  std::string method;
 | 
					  std::string method;
 | 
				
			||||||
@@ -340,6 +349,11 @@ public:
 | 
				
			|||||||
  std::shared_ptr<Response> Post(const char *path, const Headers &headers,
 | 
					  std::shared_ptr<Response> Post(const char *path, const Headers &headers,
 | 
				
			||||||
                                 const Params ¶ms);
 | 
					                                 const Params ¶ms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::shared_ptr<Response> Post(const char *path,
 | 
				
			||||||
 | 
					                                 const MultipartFormDataItems &items);
 | 
				
			||||||
 | 
					  std::shared_ptr<Response> Post(const char *path, const Headers &headers,
 | 
				
			||||||
 | 
					                                 const MultipartFormDataItems &items);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::shared_ptr<Response> Put(const char *path, const std::string &body,
 | 
					  std::shared_ptr<Response> Put(const char *path, const std::string &body,
 | 
				
			||||||
                                const char *content_type);
 | 
					                                const char *content_type);
 | 
				
			||||||
  std::shared_ptr<Response> Put(const char *path, const Headers &headers,
 | 
					  std::shared_ptr<Response> Put(const char *path, const Headers &headers,
 | 
				
			||||||
@@ -551,9 +565,7 @@ inline std::string base64_encode(const std::string &in) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (valb > -6) {
 | 
					  if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
 | 
				
			||||||
    out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (out.size() % 4) {
 | 
					  while (out.size() % 4) {
 | 
				
			||||||
    out.push_back('=');
 | 
					    out.push_back('=');
 | 
				
			||||||
@@ -1231,16 +1243,13 @@ bool read_content(Stream &strm, T &x, uint64_t payload_max_length, int &status,
 | 
				
			|||||||
template <typename T> inline int write_headers(Stream &strm, const T &info) {
 | 
					template <typename T> inline int write_headers(Stream &strm, const T &info) {
 | 
				
			||||||
  auto write_len = 0;
 | 
					  auto write_len = 0;
 | 
				
			||||||
  for (const auto &x : info.headers) {
 | 
					  for (const auto &x : info.headers) {
 | 
				
			||||||
    auto len = strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
 | 
					    auto len =
 | 
				
			||||||
    if (len < 0) {
 | 
					        strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str());
 | 
				
			||||||
      return len;
 | 
					    if (len < 0) { return len; }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    write_len += len;
 | 
					    write_len += len;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  auto len = strm.write("\r\n");
 | 
					  auto len = strm.write("\r\n");
 | 
				
			||||||
  if (len < 0) {
 | 
					  if (len < 0) { return len; }
 | 
				
			||||||
    return len;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  write_len += len;
 | 
					  write_len += len;
 | 
				
			||||||
  return write_len;
 | 
					  return write_len;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1262,9 +1271,7 @@ inline int write_content_chunked(Stream &strm, const T &x) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    auto len = strm.write(chunk.c_str(), chunk.size());
 | 
					    auto len = strm.write(chunk.c_str(), chunk.size());
 | 
				
			||||||
    if (len < 0) {
 | 
					    if (len < 0) { return len; }
 | 
				
			||||||
      return len;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    write_len += len;
 | 
					    write_len += len;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return write_len;
 | 
					  return write_len;
 | 
				
			||||||
@@ -1444,6 +1451,22 @@ inline std::string to_lower(const char *beg, const char *end) {
 | 
				
			|||||||
  return out;
 | 
					  return out;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline std::string make_multipart_data_boundary() {
 | 
				
			||||||
 | 
					  static const char data[] =
 | 
				
			||||||
 | 
					      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::random_device seed_gen;
 | 
				
			||||||
 | 
					  std::mt19937 engine(seed_gen());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  std::string result = "--cpp-httplib-form-data-";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (auto i = 0; i < 16; i++) {
 | 
				
			||||||
 | 
					    result += data[engine() % (sizeof(data) - 1)];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline void make_range_header_core(std::string &) {}
 | 
					inline void make_range_header_core(std::string &) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename uint64_t>
 | 
					template <typename uint64_t>
 | 
				
			||||||
@@ -1486,9 +1509,9 @@ inline std::pair<std::string, std::string> make_range_header(uint64_t value,
 | 
				
			|||||||
  return std::make_pair("Range", field);
 | 
					  return std::make_pair("Range", field);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
inline std::pair<std::string, std::string>
 | 
					inline std::pair<std::string, std::string>
 | 
				
			||||||
make_basic_authentication_header(const std::string& username, const std::string& password) {
 | 
					make_basic_authentication_header(const std::string &username,
 | 
				
			||||||
 | 
					                                 const std::string &password) {
 | 
				
			||||||
  auto field = "Basic " + detail::base64_encode(username + ":" + password);
 | 
					  auto field = "Basic " + detail::base64_encode(username + ":" + password);
 | 
				
			||||||
  return std::make_pair("Authorization", field);
 | 
					  return std::make_pair("Authorization", field);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1583,9 +1606,7 @@ inline int Stream::write_format(const char *fmt, const Args &... args) {
 | 
				
			|||||||
#else
 | 
					#else
 | 
				
			||||||
  auto n = snprintf(buf, bufsiz - 1, fmt, args...);
 | 
					  auto n = snprintf(buf, bufsiz - 1, fmt, args...);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
  if (n <= 0) {
 | 
					  if (n <= 0) { return n; }
 | 
				
			||||||
    return n;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (n >= bufsiz - 1) {
 | 
					  if (n >= bufsiz - 1) {
 | 
				
			||||||
    std::vector<char> glowable_buf(bufsiz);
 | 
					    std::vector<char> glowable_buf(bufsiz);
 | 
				
			||||||
@@ -1811,20 +1832,14 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
 | 
				
			|||||||
    res.set_header("Content-Length", length.c_str());
 | 
					    res.set_header("Content-Length", length.c_str());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!detail::write_headers(strm, res)) {
 | 
					  if (!detail::write_headers(strm, res)) { return false; }
 | 
				
			||||||
    return false;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Body
 | 
					  // Body
 | 
				
			||||||
  if (req.method != "HEAD") {
 | 
					  if (req.method != "HEAD") {
 | 
				
			||||||
    if (!res.body.empty()) {
 | 
					    if (!res.body.empty()) {
 | 
				
			||||||
      if (!strm.write(res.body.c_str(), res.body.size())) {
 | 
					      if (!strm.write(res.body.c_str(), res.body.size())) { return false; }
 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (res.content_producer) {
 | 
					    } else if (res.content_producer) {
 | 
				
			||||||
      if (!detail::write_content_chunked(strm, res)) {
 | 
					      if (!detail::write_content_chunked(strm, res)) { return false; }
 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2325,6 +2340,45 @@ Client::Post(const char *path, const Headers &headers, const Params ¶ms) {
 | 
				
			|||||||
  return Post(path, headers, query, "application/x-www-form-urlencoded");
 | 
					  return Post(path, headers, query, "application/x-www-form-urlencoded");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline std::shared_ptr<Response>
 | 
				
			||||||
 | 
					Client::Post(const char *path, const MultipartFormDataItems &items) {
 | 
				
			||||||
 | 
					  return Post(path, Headers(), items);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inline std::shared_ptr<Response>
 | 
				
			||||||
 | 
					Client::Post(const char *path, const Headers &headers,
 | 
				
			||||||
 | 
					             const MultipartFormDataItems &items) {
 | 
				
			||||||
 | 
					  Request req;
 | 
				
			||||||
 | 
					  req.method = "POST";
 | 
				
			||||||
 | 
					  req.headers = headers;
 | 
				
			||||||
 | 
					  req.path = path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto boundary = detail::make_multipart_data_boundary();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  req.headers.emplace("Content-Type",
 | 
				
			||||||
 | 
					                      "multipart/form-data; boundary=" + boundary);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const auto &item : items) {
 | 
				
			||||||
 | 
					    req.body += "--" + boundary + "\r\n";
 | 
				
			||||||
 | 
					    req.body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
 | 
				
			||||||
 | 
					    if (!item.filename.empty()) {
 | 
				
			||||||
 | 
					      req.body += "; filename=\"" + item.filename + "\"";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    req.body += "\r\n";
 | 
				
			||||||
 | 
					    if (!item.content_type.empty()) {
 | 
				
			||||||
 | 
					      req.body += "Content-Type: " + item.content_type + "\r\n";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    req.body += "\r\n";
 | 
				
			||||||
 | 
					    req.body += item.content + "\r\n";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  req.body += "--" + boundary + "--\r\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto res = std::make_shared<Response>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return send(req, *res) ? res : nullptr;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
inline std::shared_ptr<Response> Client::Put(const char *path,
 | 
					inline std::shared_ptr<Response> Client::Put(const char *path,
 | 
				
			||||||
                                             const std::string &body,
 | 
					                                             const std::string &body,
 | 
				
			||||||
                                             const char *content_type) {
 | 
					                                             const char *content_type) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								test/test.cc
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								test/test.cc
									
									
									
									
									
								
							@@ -264,9 +264,8 @@ TEST(CancelTest, NoCancel) {
 | 
				
			|||||||
  httplib::Client cli(host, port, sec);
 | 
					  httplib::Client cli(host, port, sec);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  httplib::Headers headers;
 | 
					 | 
				
			||||||
  auto res =
 | 
					  auto res =
 | 
				
			||||||
      cli.Get("/range/32", headers, [](uint64_t, uint64_t) { return true; });
 | 
					      cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
 | 
				
			||||||
  ASSERT_TRUE(res != nullptr);
 | 
					  ASSERT_TRUE(res != nullptr);
 | 
				
			||||||
  EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
 | 
					  EXPECT_EQ(res->body, "abcdefghijklmnopqrstuvwxyzabcdef");
 | 
				
			||||||
  EXPECT_EQ(200, res->status);
 | 
					  EXPECT_EQ(200, res->status);
 | 
				
			||||||
@@ -284,9 +283,8 @@ TEST(CancelTest, WithCancelSmallPayload) {
 | 
				
			|||||||
  httplib::Client cli(host, port, sec);
 | 
					  httplib::Client cli(host, port, sec);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  httplib::Headers headers;
 | 
					 | 
				
			||||||
  auto res =
 | 
					  auto res =
 | 
				
			||||||
      cli.Get("/range/32", headers, [](uint64_t, uint64_t) { return false; });
 | 
					      cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
 | 
				
			||||||
  ASSERT_TRUE(res == nullptr);
 | 
					  ASSERT_TRUE(res == nullptr);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -964,53 +962,17 @@ TEST_F(ServerTest, EndWithPercentCharacterInQuery) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST_F(ServerTest, MultipartFormData) {
 | 
					TEST_F(ServerTest, MultipartFormData) {
 | 
				
			||||||
  Request req;
 | 
					  MultipartFormDataItems items = {
 | 
				
			||||||
  req.method = "POST";
 | 
					    { "text1", "text default", "", "" },
 | 
				
			||||||
  req.path = "/multipart";
 | 
					    { "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" },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  std::string host_and_port;
 | 
					  auto res = cli_.Post("/multipart", items);
 | 
				
			||||||
  host_and_port += HOST;
 | 
					 | 
				
			||||||
  host_and_port += ":";
 | 
					 | 
				
			||||||
  host_and_port += std::to_string(PORT);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  req.headers.emplace("Host", host_and_port.c_str());
 | 
					  ASSERT_TRUE(res != nullptr);
 | 
				
			||||||
  req.headers.emplace("Accept", "*/*");
 | 
					 | 
				
			||||||
  req.headers.emplace("User-Agent", "cpp-httplib/0.1");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  req.headers.emplace(
 | 
					 | 
				
			||||||
      "Content-Type",
 | 
					 | 
				
			||||||
      "multipart/form-data; boundary=----WebKitFormBoundarysBREP3G013oUrLB4");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  req.body =
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4\r\n"
 | 
					 | 
				
			||||||
      "Content-Disposition: form-data; name=\"text1\"\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "text default\r\n"
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4\r\n"
 | 
					 | 
				
			||||||
      "Content-Disposition: form-data; name=\"text2\"\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "aωb\r\n"
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4\r\n"
 | 
					 | 
				
			||||||
      "Content-Disposition: form-data; name=\"file1\"; filename=\"hello.txt\"\r\n"
 | 
					 | 
				
			||||||
      "Content-Type: text/plain\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "h\ne\n\nl\nl\no\n\r\n"
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4\r\n"
 | 
					 | 
				
			||||||
      "Content-Disposition: form-data; name=\"file2\"; filename=\"world.json\"\r\n"
 | 
					 | 
				
			||||||
      "Content-Type: application/json\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "{\n  \"world\", true\n}\n\r\n"
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4\r\n"
 | 
					 | 
				
			||||||
      "content-disposition: form-data; name=\"file3\"; filename=\"\"\r\n"
 | 
					 | 
				
			||||||
      "content-type: application/octet-stream\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "\r\n"
 | 
					 | 
				
			||||||
      "------WebKitFormBoundarysBREP3G013oUrLB4--\r\n";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  auto res = std::make_shared<Response>();
 | 
					 | 
				
			||||||
  auto ret = cli_.send(req, *res);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ASSERT_TRUE(ret);
 | 
					 | 
				
			||||||
  EXPECT_EQ(200, res->status);
 | 
					  EXPECT_EQ(200, res->status);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user