@@ -67,6 +67,7 @@ typedef int socket_t;
6767#include < map>
6868#include < memory>
6969#include < mutex>
70+ #include < random>
7071#include < regex>
7172#include < string>
7273#include < sys/stat.h>
@@ -138,6 +139,14 @@ struct MultipartFile {
138139};
139140typedef std::multimap<std::string, MultipartFile> MultipartFiles;
140141
142+ struct MultipartFormData {
143+ std::string name;
144+ std::string content;
145+ std::string filename;
146+ std::string content_type;
147+ };
148+ typedef std::vector<MultipartFormData> MultipartFormDataItems;
149+
141150struct Request {
142151 std::string version;
143152 std::string method;
@@ -340,6 +349,11 @@ class Client {
340349 std::shared_ptr<Response> Post (const char *path, const Headers &headers,
341350 const Params ¶ms);
342351
352+ std::shared_ptr<Response> Post (const char *path,
353+ const MultipartFormDataItems &items);
354+ std::shared_ptr<Response> Post (const char *path, const Headers &headers,
355+ const MultipartFormDataItems &items);
356+
343357 std::shared_ptr<Response> Put (const char *path, const std::string &body,
344358 const char *content_type);
345359 std::shared_ptr<Response> Put (const char *path, const Headers &headers,
@@ -551,9 +565,7 @@ inline std::string base64_encode(const std::string &in) {
551565 }
552566 }
553567
554- if (valb > -6 ) {
555- out.push_back (lookup[((val << 8 ) >> (valb + 8 )) & 0x3F ]);
556- }
568+ if (valb > -6 ) { out.push_back (lookup[((val << 8 ) >> (valb + 8 )) & 0x3F ]); }
557569
558570 while (out.size () % 4 ) {
559571 out.push_back (' =' );
@@ -1231,16 +1243,13 @@ bool read_content(Stream &strm, T &x, uint64_t payload_max_length, int &status,
12311243template <typename T> inline int write_headers (Stream &strm, const T &info) {
12321244 auto write_len = 0 ;
12331245 for (const auto &x : info.headers ) {
1234- auto len = strm.write_format (" %s: %s\r\n " , x.first .c_str (), x.second .c_str ());
1235- if (len < 0 ) {
1236- return len;
1237- }
1246+ auto len =
1247+ strm.write_format (" %s: %s\r\n " , x.first .c_str (), x.second .c_str ());
1248+ if (len < 0 ) { return len; }
12381249 write_len += len;
12391250 }
12401251 auto len = strm.write (" \r\n " );
1241- if (len < 0 ) {
1242- return len;
1243- }
1252+ if (len < 0 ) { return len; }
12441253 write_len += len;
12451254 return write_len;
12461255}
@@ -1262,9 +1271,7 @@ inline int write_content_chunked(Stream &strm, const T &x) {
12621271 }
12631272
12641273 auto len = strm.write (chunk.c_str (), chunk.size ());
1265- if (len < 0 ) {
1266- return len;
1267- }
1274+ if (len < 0 ) { return len; }
12681275 write_len += len;
12691276 }
12701277 return write_len;
@@ -1444,6 +1451,22 @@ inline std::string to_lower(const char *beg, const char *end) {
14441451 return out;
14451452}
14461453
1454+ inline std::string make_multipart_data_boundary () {
1455+ static const char data[] =
1456+ " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ;
1457+
1458+ std::random_device seed_gen;
1459+ std::mt19937 engine (seed_gen ());
1460+
1461+ std::string result = " --cpp-httplib-form-data-" ;
1462+
1463+ for (auto i = 0 ; i < 16 ; i++) {
1464+ result += data[engine () % (sizeof (data) - 1 )];
1465+ }
1466+
1467+ return result;
1468+ }
1469+
14471470inline void make_range_header_core (std::string &) {}
14481471
14491472template <typename uint64_t >
@@ -1486,9 +1509,9 @@ inline std::pair<std::string, std::string> make_range_header(uint64_t value,
14861509 return std::make_pair (" Range" , field);
14871510}
14881511
1489-
1490- inline std::pair<std:: string, std::string>
1491- make_basic_authentication_header ( const std::string& username, const std::string& password) {
1512+ inline std::pair<std::string, std::string>
1513+ make_basic_authentication_header ( const std::string &username,
1514+ const std::string & password) {
14921515 auto field = " Basic " + detail::base64_encode (username + " :" + password);
14931516 return std::make_pair (" Authorization" , field);
14941517}
@@ -1583,9 +1606,7 @@ inline int Stream::write_format(const char *fmt, const Args &... args) {
15831606#else
15841607 auto n = snprintf (buf, bufsiz - 1 , fmt, args...);
15851608#endif
1586- if (n <= 0 ) {
1587- return n;
1588- }
1609+ if (n <= 0 ) { return n; }
15891610
15901611 if (n >= bufsiz - 1 ) {
15911612 std::vector<char > glowable_buf (bufsiz);
@@ -1769,7 +1790,7 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
17691790
17701791 // Response line
17711792 if (!strm.write_format (" HTTP/1.1 %d %s\r\n " , res.status ,
1772- detail::status_message (res.status ))) {
1793+ detail::status_message (res.status ))) {
17731794 return false ;
17741795 }
17751796
@@ -1811,20 +1832,14 @@ inline bool Server::write_response(Stream &strm, bool last_connection,
18111832 res.set_header (" Content-Length" , length.c_str ());
18121833 }
18131834
1814- if (!detail::write_headers (strm, res)) {
1815- return false ;
1816- }
1835+ if (!detail::write_headers (strm, res)) { return false ; }
18171836
18181837 // Body
18191838 if (req.method != " HEAD" ) {
18201839 if (!res.body .empty ()) {
1821- if (!strm.write (res.body .c_str (), res.body .size ())) {
1822- return false ;
1823- }
1840+ if (!strm.write (res.body .c_str (), res.body .size ())) { return false ; }
18241841 } else if (res.content_producer ) {
1825- if (!detail::write_content_chunked (strm, res)) {
1826- return false ;
1827- }
1842+ if (!detail::write_content_chunked (strm, res)) { return false ; }
18281843 }
18291844 }
18301845
@@ -2325,6 +2340,45 @@ Client::Post(const char *path, const Headers &headers, const Params ¶ms) {
23252340 return Post (path, headers, query, " application/x-www-form-urlencoded" );
23262341}
23272342
2343+ inline std::shared_ptr<Response>
2344+ Client::Post (const char *path, const MultipartFormDataItems &items) {
2345+ return Post (path, Headers (), items);
2346+ }
2347+
2348+ inline std::shared_ptr<Response>
2349+ Client::Post (const char *path, const Headers &headers,
2350+ const MultipartFormDataItems &items) {
2351+ Request req;
2352+ req.method = " POST" ;
2353+ req.headers = headers;
2354+ req.path = path;
2355+
2356+ auto boundary = detail::make_multipart_data_boundary ();
2357+
2358+ req.headers .emplace (" Content-Type" ,
2359+ " multipart/form-data; boundary=" + boundary);
2360+
2361+ for (const auto &item : items) {
2362+ req.body += " --" + boundary + " \r\n " ;
2363+ req.body += " Content-Disposition: form-data; name=\" " + item.name + " \" " ;
2364+ if (!item.filename .empty ()) {
2365+ req.body += " ; filename=\" " + item.filename + " \" " ;
2366+ }
2367+ req.body += " \r\n " ;
2368+ if (!item.content_type .empty ()) {
2369+ req.body += " Content-Type: " + item.content_type + " \r\n " ;
2370+ }
2371+ req.body += " \r\n " ;
2372+ req.body += item.content + " \r\n " ;
2373+ }
2374+
2375+ req.body += " --" + boundary + " --\r\n " ;
2376+
2377+ auto res = std::make_shared<Response>();
2378+
2379+ return send (req, *res) ? res : nullptr ;
2380+ }
2381+
23282382inline std::shared_ptr<Response> Client::Put (const char *path,
23292383 const std::string &body,
23302384 const char *content_type) {
0 commit comments