Skip to content

Commit 7e420ae

Browse files
authored
add support for requests with both MultipartFormDataItems and Content Providers (#1454)
* add support for requests with both MultipartFormDataItems and ContentProviders * rework implementation * use const auto & and fix offset calculation * fix zero items * snake case variables * clang-format * commonize get_multipart_content_provider, add Put() with MultipartFormDataProviderItems * fix linker multiple definition error * add test MultipartFormDataTest.DataProviderItems
1 parent 227d2c2 commit 7e420ae

File tree

2 files changed

+355
-32
lines changed

2 files changed

+355
-32
lines changed

httplib.h

Lines changed: 157 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ using ContentProviderWithoutLength =
369369

370370
using ContentProviderResourceReleaser = std::function<void(bool success)>;
371371

372+
struct MultipartFormDataProvider {
373+
std::string name;
374+
ContentProviderWithoutLength provider;
375+
std::string filename;
376+
std::string content_type;
377+
};
378+
using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>;
379+
372380
using ContentReceiverWithProgress =
373381
std::function<bool(const char *data, size_t data_length, uint64_t offset,
374382
uint64_t total_length)>;
@@ -934,6 +942,9 @@ class ClientImpl {
934942
const MultipartFormDataItems &items);
935943
Result Post(const std::string &path, const Headers &headers,
936944
const MultipartFormDataItems &items, const std::string &boundary);
945+
Result Post(const std::string &path, const Headers &headers,
946+
const MultipartFormDataItems &items,
947+
const MultipartFormDataProviderItems &provider_items);
937948

938949
Result Put(const std::string &path);
939950
Result Put(const std::string &path, const char *body, size_t content_length,
@@ -963,6 +974,9 @@ class ClientImpl {
963974
const MultipartFormDataItems &items);
964975
Result Put(const std::string &path, const Headers &headers,
965976
const MultipartFormDataItems &items, const std::string &boundary);
977+
Result Put(const std::string &path, const Headers &headers,
978+
const MultipartFormDataItems &items,
979+
const MultipartFormDataProviderItems &provider_items);
966980

967981
Result Patch(const std::string &path);
968982
Result Patch(const std::string &path, const char *body, size_t content_length,
@@ -1201,6 +1215,9 @@ class ClientImpl {
12011215
ContentProvider content_provider,
12021216
ContentProviderWithoutLength content_provider_without_length,
12031217
const std::string &content_type);
1218+
ContentProviderWithoutLength get_multipart_content_provider(
1219+
const std::string &boundary, const MultipartFormDataItems &items,
1220+
const MultipartFormDataProviderItems &provider_items);
12041221

12051222
std::string adjust_host_string(const std::string &host) const;
12061223

@@ -1296,6 +1313,10 @@ class Client {
12961313
const MultipartFormDataItems &items);
12971314
Result Post(const std::string &path, const Headers &headers,
12981315
const MultipartFormDataItems &items, const std::string &boundary);
1316+
Result Post(const std::string &path, const Headers &headers,
1317+
const MultipartFormDataItems &items,
1318+
const MultipartFormDataProviderItems &provider_items);
1319+
12991320
Result Put(const std::string &path);
13001321
Result Put(const std::string &path, const char *body, size_t content_length,
13011322
const std::string &content_type);
@@ -1324,6 +1345,10 @@ class Client {
13241345
const MultipartFormDataItems &items);
13251346
Result Put(const std::string &path, const Headers &headers,
13261347
const MultipartFormDataItems &items, const std::string &boundary);
1348+
Result Put(const std::string &path, const Headers &headers,
1349+
const MultipartFormDataItems &items,
1350+
const MultipartFormDataProviderItems &provider_items);
1351+
13271352
Result Patch(const std::string &path);
13281353
Result Patch(const std::string &path, const char *body, size_t content_length,
13291354
const std::string &content_type);
@@ -2854,8 +2879,7 @@ inline socket_t create_client_socket(
28542879
}
28552880

28562881
inline bool get_ip_and_port(const struct sockaddr_storage &addr,
2857-
socklen_t addr_len, std::string &ip,
2858-
int &port) {
2882+
socklen_t addr_len, std::string &ip, int &port) {
28592883
if (addr.ss_family == AF_INET) {
28602884
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
28612885
} else if (addr.ss_family == AF_INET6) {
@@ -4129,29 +4153,48 @@ inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
41294153
return valid;
41304154
}
41314155

4156+
template <typename T>
4157+
inline std::string
4158+
serialize_multipart_formdata_item_begin(const T &item,
4159+
const std::string &boundary) {
4160+
std::string body = "--" + boundary + "\r\n";
4161+
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
4162+
if (!item.filename.empty()) {
4163+
body += "; filename=\"" + item.filename + "\"";
4164+
}
4165+
body += "\r\n";
4166+
if (!item.content_type.empty()) {
4167+
body += "Content-Type: " + item.content_type + "\r\n";
4168+
}
4169+
body += "\r\n";
4170+
4171+
return body;
4172+
}
4173+
4174+
inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
4175+
4176+
inline std::string
4177+
serialize_multipart_formdata_finish(const std::string &boundary) {
4178+
return "--" + boundary + "--\r\n";
4179+
}
4180+
4181+
inline std::string
4182+
serialize_multipart_formdata_get_content_type(const std::string &boundary) {
4183+
return "multipart/form-data; boundary=" + boundary;
4184+
}
4185+
41324186
inline std::string
41334187
serialize_multipart_formdata(const MultipartFormDataItems &items,
4134-
const std::string &boundary,
4135-
std::string &content_type) {
4188+
const std::string &boundary, bool finish = true) {
41364189
std::string body;
41374190

41384191
for (const auto &item : items) {
4139-
body += "--" + boundary + "\r\n";
4140-
body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
4141-
if (!item.filename.empty()) {
4142-
body += "; filename=\"" + item.filename + "\"";
4143-
}
4144-
body += "\r\n";
4145-
if (!item.content_type.empty()) {
4146-
body += "Content-Type: " + item.content_type + "\r\n";
4147-
}
4148-
body += "\r\n";
4149-
body += item.content + "\r\n";
4192+
body += serialize_multipart_formdata_item_begin(item, boundary);
4193+
body += item.content + serialize_multipart_formdata_item_end();
41504194
}
41514195

4152-
body += "--" + boundary + "--\r\n";
4196+
if (finish) body += serialize_multipart_formdata_finish(boundary);
41534197

4154-
content_type = "multipart/form-data; boundary=" + boundary;
41554198
return body;
41564199
}
41574200

@@ -4536,8 +4579,8 @@ inline void hosted_at(const std::string &hostname,
45364579
*reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
45374580
std::string ip;
45384581
int dummy = -1;
4539-
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage),
4540-
ip, dummy)) {
4582+
if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
4583+
dummy)) {
45414584
addrs.push_back(ip);
45424585
}
45434586
}
@@ -6647,6 +6690,49 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
66476690
return true;
66486691
}
66496692

6693+
inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
6694+
const std::string &boundary, const MultipartFormDataItems &items,
6695+
const MultipartFormDataProviderItems &provider_items) {
6696+
size_t cur_item = 0, cur_start = 0;
6697+
// cur_item and cur_start are copied to within the std::function and maintain
6698+
// state between successive calls
6699+
return [&, cur_item, cur_start](size_t offset,
6700+
DataSink &sink) mutable -> bool {
6701+
if (!offset && items.size()) {
6702+
sink.os << detail::serialize_multipart_formdata(items, boundary, false);
6703+
return true;
6704+
} else if (cur_item < provider_items.size()) {
6705+
if (!cur_start) {
6706+
const auto &begin = detail::serialize_multipart_formdata_item_begin(
6707+
provider_items[cur_item], boundary);
6708+
offset += begin.size();
6709+
cur_start = offset;
6710+
sink.os << begin;
6711+
}
6712+
6713+
DataSink cur_sink;
6714+
bool has_data = true;
6715+
cur_sink.write = sink.write;
6716+
cur_sink.done = [&]() { has_data = false; };
6717+
cur_sink.is_writable = sink.is_writable;
6718+
6719+
if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
6720+
return false;
6721+
6722+
if (!has_data) {
6723+
sink.os << detail::serialize_multipart_formdata_item_end();
6724+
cur_item++;
6725+
cur_start = 0;
6726+
}
6727+
return true;
6728+
} else {
6729+
sink.os << detail::serialize_multipart_formdata_finish(boundary);
6730+
sink.done();
6731+
return true;
6732+
}
6733+
};
6734+
}
6735+
66506736
inline bool
66516737
ClientImpl::process_socket(const Socket &socket,
66526738
std::function<bool(Stream &strm)> callback) {
@@ -6869,9 +6955,10 @@ inline Result ClientImpl::Post(const std::string &path,
68696955

68706956
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
68716957
const MultipartFormDataItems &items) {
6872-
std::string content_type;
6873-
const auto &body = detail::serialize_multipart_formdata(
6874-
items, detail::make_multipart_data_boundary(), content_type);
6958+
const auto &boundary = detail::make_multipart_data_boundary();
6959+
const auto &content_type =
6960+
detail::serialize_multipart_formdata_get_content_type(boundary);
6961+
const auto &body = detail::serialize_multipart_formdata(items, boundary);
68756962
return Post(path, headers, body, content_type.c_str());
68766963
}
68776964

@@ -6882,12 +6969,25 @@ inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
68826969
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
68836970
}
68846971

6885-
std::string content_type;
6886-
const auto &body =
6887-
detail::serialize_multipart_formdata(items, boundary, content_type);
6972+
const auto &content_type =
6973+
detail::serialize_multipart_formdata_get_content_type(boundary);
6974+
const auto &body = detail::serialize_multipart_formdata(items, boundary);
68886975
return Post(path, headers, body, content_type.c_str());
68896976
}
68906977

6978+
inline Result
6979+
ClientImpl::Post(const std::string &path, const Headers &headers,
6980+
const MultipartFormDataItems &items,
6981+
const MultipartFormDataProviderItems &provider_items) {
6982+
const auto &boundary = detail::make_multipart_data_boundary();
6983+
const auto &content_type =
6984+
detail::serialize_multipart_formdata_get_content_type(boundary);
6985+
return send_with_content_provider(
6986+
"POST", path, headers, nullptr, 0, nullptr,
6987+
get_multipart_content_provider(boundary, items, provider_items),
6988+
content_type);
6989+
}
6990+
68916991
inline Result ClientImpl::Put(const std::string &path) {
68926992
return Put(path, std::string(), std::string());
68936993
}
@@ -6964,9 +7064,10 @@ inline Result ClientImpl::Put(const std::string &path,
69647064

69657065
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
69667066
const MultipartFormDataItems &items) {
6967-
std::string content_type;
6968-
const auto &body = detail::serialize_multipart_formdata(
6969-
items, detail::make_multipart_data_boundary(), content_type);
7067+
const auto &boundary = detail::make_multipart_data_boundary();
7068+
const auto &content_type =
7069+
detail::serialize_multipart_formdata_get_content_type(boundary);
7070+
const auto &body = detail::serialize_multipart_formdata(items, boundary);
69707071
return Put(path, headers, body, content_type);
69717072
}
69727073

@@ -6977,12 +7078,24 @@ inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
69777078
return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
69787079
}
69797080

6980-
std::string content_type;
6981-
const auto &body =
6982-
detail::serialize_multipart_formdata(items, boundary, content_type);
7081+
const auto &content_type =
7082+
detail::serialize_multipart_formdata_get_content_type(boundary);
7083+
const auto &body = detail::serialize_multipart_formdata(items, boundary);
69837084
return Put(path, headers, body, content_type);
69847085
}
69857086

7087+
inline Result
7088+
ClientImpl::Put(const std::string &path, const Headers &headers,
7089+
const MultipartFormDataItems &items,
7090+
const MultipartFormDataProviderItems &provider_items) {
7091+
const auto &boundary = detail::make_multipart_data_boundary();
7092+
const auto &content_type =
7093+
detail::serialize_multipart_formdata_get_content_type(boundary);
7094+
return send_with_content_provider(
7095+
"PUT", path, headers, nullptr, 0, nullptr,
7096+
get_multipart_content_provider(boundary, items, provider_items),
7097+
content_type);
7098+
}
69867099
inline Result ClientImpl::Patch(const std::string &path) {
69877100
return Patch(path, std::string(), std::string());
69887101
}
@@ -7443,7 +7556,7 @@ inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
74437556
}
74447557

74457558
inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
7446-
int &port) const {
7559+
int &port) const {
74477560
detail::get_local_ip_and_port(sock_, ip, port);
74487561
}
74497562

@@ -8147,6 +8260,12 @@ inline Result Client::Post(const std::string &path, const Headers &headers,
81478260
const std::string &boundary) {
81488261
return cli_->Post(path, headers, items, boundary);
81498262
}
8263+
inline Result
8264+
Client::Post(const std::string &path, const Headers &headers,
8265+
const MultipartFormDataItems &items,
8266+
const MultipartFormDataProviderItems &provider_items) {
8267+
return cli_->Post(path, headers, items, provider_items);
8268+
}
81508269
inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
81518270
inline Result Client::Put(const std::string &path, const char *body,
81528271
size_t content_length,
@@ -8210,6 +8329,12 @@ inline Result Client::Put(const std::string &path, const Headers &headers,
82108329
const std::string &boundary) {
82118330
return cli_->Put(path, headers, items, boundary);
82128331
}
8332+
inline Result
8333+
Client::Put(const std::string &path, const Headers &headers,
8334+
const MultipartFormDataItems &items,
8335+
const MultipartFormDataProviderItems &provider_items) {
8336+
return cli_->Put(path, headers, items, provider_items);
8337+
}
82138338
inline Result Client::Patch(const std::string &path) {
82148339
return cli_->Patch(path);
82158340
}

0 commit comments

Comments
 (0)