Skip to content

Commit 4ddadd5

Browse files
committed
Add MIME type to HTTP file transfer API
BREAKING CHANGES: ABI is broken due to function signature changes (API is preserved through the use of default parameters)
1 parent 3ebe13d commit 4ddadd5

15 files changed

+66
-33
lines changed

include/dpp/cluster.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -1169,8 +1169,9 @@ class DPP_EXPORT cluster {
11691169
* @param callback Function to call when the HTTP call completes. The callback parameter will contain amongst other things, the decoded json.
11701170
* @param filename Filename to post for POST requests (for uploading files)
11711171
* @param filecontent File content to post for POST requests (for uploading files)
1172+
* @param filemimetype File content to post for POST requests (for uploading files)
11721173
*/
1173-
void post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename = "", const std::string &filecontent = "");
1174+
void post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename = "", const std::string &filecontent = "", const std::string &filemimetype = "");
11741175

11751176
/**
11761177
* @brief Post a multipart REST request. Where possible use a helper method instead like message_create
@@ -1183,8 +1184,9 @@ class DPP_EXPORT cluster {
11831184
* @param callback Function to call when the HTTP call completes. The callback parameter will contain amongst other things, the decoded json.
11841185
* @param filename List of filenames to post for POST requests (for uploading files)
11851186
* @param filecontent List of file content to post for POST requests (for uploading files)
1187+
* @param filemimetypes List of mime types for each file to post for POST requests (for uploading files)
11861188
*/
1187-
void post_rest_multipart(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::vector<std::string> &filename = {}, const std::vector<std::string> &filecontent = {});
1189+
void post_rest_multipart(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::vector<std::string> &filename = {}, const std::vector<std::string>& filecontent = {}, const std::vector<std::string>& filemimetypes = {});
11881190

11891191
/**
11901192
* @brief Make a HTTP(S) request. For use when wanting asynchronous access to HTTP APIs outside of Discord.

include/dpp/httpsclient.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,10 @@ class DPP_EXPORT https_client : public ssl_client
248248
* @param json The json content
249249
* @param filenames File names of files to send
250250
* @param contents Contents of each of the files to send
251+
* @param contents MIME types of each of the files to send
251252
* @return multipart mime content and headers
252253
*/
253-
static multipart_content build_multipart(const std::string &json, const std::vector<std::string>& filenames = {}, const std::vector<std::string>& contents = {});
254+
static multipart_content build_multipart(const std::string &json, const std::vector<std::string>& filenames = {}, const std::vector<std::string>& contents = {}, const std::vector<std::string>& mimetypes = {});
254255

255256
/**
256257
* @brief Processes incoming data from the SSL socket input buffer.

include/dpp/message.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,9 @@ struct DPP_EXPORT message : public managed {
11831183
/** File content to upload (raw binary) */
11841184
std::vector<std::string> filecontent;
11851185

1186+
/** Mime type of files to upload */
1187+
std::vector<std::string> filemimetype;
1188+
11861189
/**
11871190
* @brief Reference to another message, e.g. a reply
11881191
*/
@@ -1466,9 +1469,10 @@ struct DPP_EXPORT message : public managed {
14661469
*
14671470
* @param filename filename
14681471
* @param filecontent raw file content contained in std::string
1472+
* @param filemimetype optional mime type of the file
14691473
* @return message& reference to self
14701474
*/
1471-
message& add_file(const std::string &filename, const std::string &filecontent);
1475+
message& add_file(const std::string &filename, const std::string &filecontent, const std::string &filemimetype = "");
14721476

14731477
/**
14741478
* @brief Set the message content

include/dpp/queues.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ class DPP_EXPORT http_request {
149149
std::vector<std::string> file_name;
150150
/** @brief Upload file contents (binary) */
151151
std::vector<std::string> file_content;
152+
/** @brief Upload file mime types (application/octet-stream if unspecified) */
153+
std::vector<std::string> file_mimetypes;
152154
/** @brief Request mime type */
153155
std::string mimetype;
154156
/** @brief Request headers (non-discord requests only) */
@@ -166,8 +168,9 @@ class DPP_EXPORT http_request {
166168
* @param audit_reason Audit log reason to send, empty to send none
167169
* @param filename The filename (server side) of any uploaded file
168170
* @param filecontent The binary content of any uploaded file for the request
171+
* @param filemimetype The MIME type of any uploaded file for the request
169172
*/
170-
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::string &filename = "", const std::string &filecontent = "");
173+
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::string &filename = "", const std::string &filecontent = "", const std::string &filemimetype = "");
171174

172175
/**
173176
* @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().
@@ -179,8 +182,9 @@ class DPP_EXPORT http_request {
179182
* @param audit_reason Audit log reason to send, empty to send none
180183
* @param filename The filename (server side) of any uploaded file
181184
* @param filecontent The binary content of any uploaded file for the request
185+
* @param filemimetypes The MIME type of any uploaded file for the request
182186
*/
183-
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::vector<std::string> &filename = {}, const std::vector<std::string> &filecontent = {});
187+
http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata = "", http_method method = m_get, const std::string &audit_reason = "", const std::vector<std::string> &filename = {}, const std::vector<std::string> &filecontent = {}, const std::vector<std::string> &filemimetypes = {});
184188

185189
/**
186190
* @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request().

src/dpp/cluster.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ json error_response(const std::string& message, http_request_completion_t& rv)
295295
return j;
296296
}
297297

298-
void cluster::post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename, const std::string &filecontent) {
298+
void cluster::post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename, const std::string &filecontent, const std::string &filemimetype) {
299299
/* NOTE: This is not a memory leak! The request_queue will free the http_request once it reaches the end of its lifecycle */
300300
rest->post_request(new http_request(endpoint + "/" + major_parameters, parameters, [endpoint, callback](http_request_completion_t rv) {
301301
json j;
@@ -310,10 +310,10 @@ void cluster::post_rest(const std::string &endpoint, const std::string &major_pa
310310
if (callback) {
311311
callback(j, rv);
312312
}
313-
}, postdata, method, get_audit_reason(), filename, filecontent));
313+
}, postdata, method, get_audit_reason(), filename, filecontent, filemimetype));
314314
}
315315

316-
void cluster::post_rest_multipart(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::vector<std::string> &filename, const std::vector<std::string> &filecontent) {
316+
void cluster::post_rest_multipart(const std::string &endpoint, const std::string &major_parameters, const std::string &parameters, http_method method, const std::string &postdata, json_encode_t callback, const std::vector<std::string> &filename, const std::vector<std::string> &filecontent, const std::vector<std::string> &filemimetypes) {
317317
/* NOTE: This is not a memory leak! The request_queue will free the http_request once it reaches the end of its lifecycle */
318318
rest->post_request(new http_request(endpoint + "/" + major_parameters, parameters, [endpoint, callback](http_request_completion_t rv) {
319319
json j;
@@ -328,7 +328,7 @@ void cluster::post_rest_multipart(const std::string &endpoint, const std::string
328328
if (callback) {
329329
callback(j, rv);
330330
}
331-
}, postdata, method, get_audit_reason(), filename, filecontent));
331+
}, postdata, method, get_audit_reason(), filename, filecontent, filemimetypes));
332332
}
333333

334334

src/dpp/cluster/appcommand.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -132,31 +132,31 @@ void cluster::interaction_response_create(snowflake interaction_id, const std::s
132132
if (callback) {
133133
callback(confirmation_callback_t(this, confirmation(), http));
134134
}
135-
}, r.msg->filename, r.msg->filecontent);
135+
}, r.msg->filename, r.msg->filecontent, r.msg->filemimetype);
136136
}
137137

138138
void cluster::interaction_response_edit(const std::string &token, const message &m, command_completion_event_t callback) {
139139
this->post_rest_multipart(API_PATH "/webhooks", std::to_string(me.id), utility::url_encode(token) + "/messages/@original", m_patch, m.build_json(), [this, callback](json &j, const http_request_completion_t& http) {
140140
if (callback) {
141141
callback(confirmation_callback_t(this, confirmation(), http));
142142
}
143-
}, m.filename, m.filecontent);
143+
}, m.filename, m.filecontent, m.filemimetype);
144144
}
145145

146146
void cluster::interaction_followup_create(const std::string &token, const message &m, command_completion_event_t callback) {
147147
this->post_rest_multipart(API_PATH "/webhooks", std::to_string(me.id), utility::url_encode(token), m_post, m.build_json(), [this, callback](json &j, const http_request_completion_t& http) {
148148
if (callback) {
149149
callback(confirmation_callback_t(this, confirmation(), http));
150150
}
151-
}, m.filename, m.filecontent);
151+
}, m.filename, m.filecontent, m.filemimetype);
152152
}
153153

154154
void cluster::interaction_followup_edit_original(const std::string &token, const message &m, command_completion_event_t callback) {
155155
this->post_rest_multipart(API_PATH "/webhooks", std::to_string(me.id), utility::url_encode(token) + "/messages/@original", m_patch, m.build_json(), [this, callback](json &j, const http_request_completion_t& http) {
156156
if (callback) {
157157
callback(confirmation_callback_t(this, confirmation(), http));
158158
}
159-
}, m.filename, m.filecontent);
159+
}, m.filename, m.filecontent, m.filemimetype);
160160
}
161161

162162
void cluster::interaction_followup_delete(const std::string &token, command_completion_event_t callback) {
@@ -168,7 +168,7 @@ void cluster::interaction_followup_edit(const std::string &token, const message
168168
if (callback) {
169169
callback(confirmation_callback_t(this, confirmation(), http));
170170
}
171-
}, m.filename, m.filecontent);
171+
}, m.filename, m.filecontent, m.filemimetype);
172172
}
173173

174174
void cluster::interaction_followup_get(const std::string &token, snowflake message_id, command_completion_event_t callback) {

src/dpp/cluster/message.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void cluster::message_create(const message &m, command_completion_event_t callba
4040
if (callback) {
4141
callback(confirmation_callback_t(this, message(this).fill_from_json(&j), http));
4242
}
43-
}, m.filename, m.filecontent);
43+
}, m.filename, m.filecontent, m.filemimetype);
4444
}
4545

4646

@@ -116,7 +116,7 @@ void cluster::message_edit(const message &m, command_completion_event_t callback
116116
if (callback) {
117117
callback(confirmation_callback_t(this, message(this).fill_from_json(&j), http));
118118
}
119-
}, m.filename, m.filecontent);
119+
}, m.filename, m.filecontent, m.filemimetype);
120120
}
121121

122122

src/dpp/cluster/sticker.cpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,25 @@
2323

2424
namespace dpp {
2525

26+
namespace {
27+
std::string get_sticker_mimetype(const sticker &s) {
28+
static const std::map<sticker_format, std::string> mime_types = {
29+
{ sticker_format::sf_png, "image/png" },
30+
{ sticker_format::sf_apng, "image/png" },
31+
{ sticker_format::sf_lottie, "application/json" },
32+
{ sticker_format::sf_gif, "image/gif" },
33+
};
34+
35+
return mime_types.find(s.format_type)->second;
36+
}
37+
}
38+
2639
void cluster::guild_sticker_create(sticker &s, command_completion_event_t callback) {
2740
this->post_rest(API_PATH "/guilds", std::to_string(s.guild_id), "stickers", m_post, s.build_json(false), [this, callback](json &j, const http_request_completion_t& http) {
2841
if (callback) {
2942
callback(confirmation_callback_t(this, sticker().fill_from_json(&j), http));
3043
}
31-
}, s.filename, s.filecontent);
44+
}, s.filename, s.filecontent, get_sticker_mimetype(s));
3245
}
3346

3447
void cluster::guild_sticker_delete(snowflake sticker_id, snowflake guild_id, command_completion_event_t callback) {

src/dpp/cluster/thread.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ void cluster::thread_create_in_forum(const std::string& thread_name, snowflake c
118118
}
119119
callback(confirmation_callback_t(this, t, http));
120120
}
121-
}, msg.filename, msg.filecontent);
121+
}, msg.filename, msg.filecontent, msg.filemimetype);
122122
}
123123

124124
void cluster::thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user, command_completion_event_t callback)

src/dpp/cluster/webhook.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void cluster::edit_webhook_message(const class webhook &wh, const struct message
5959
if (callback) {
6060
callback(confirmation_callback_t(this, message(this).fill_from_json(&j), http));
6161
}
62-
}, m.filename, m.filecontent);
62+
}, m.filename, m.filecontent, m.filemimetype);
6363
}
6464

6565

@@ -95,7 +95,7 @@ void cluster::execute_webhook(const class webhook &wh, const struct message& m,
9595
if (callback) {
9696
callback(confirmation_callback_t(this, message(this).fill_from_json(&j), http));
9797
}
98-
}, m.filename, m.filecontent);
98+
}, m.filename, m.filecontent, m.filemimetype);
9999
}
100100

101101

src/dpp/dispatcher.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ void interaction_create_t::edit_original_response(const message & m, command_com
196196
if (callback) {
197197
callback(confirmation_callback_t(creator, message().fill_from_json(&j), http));
198198
}
199-
}, m.filename, m.filecontent);
199+
}, m.filename, m.filecontent, m.filemimetype);
200200
}
201201

202202
void interaction_create_t::delete_original_response(command_completion_event_t callback) const

src/dpp/httpsclient.cpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ void https_client::connect()
7171
}
7272
}
7373

74-
multipart_content https_client::build_multipart(const std::string &json, const std::vector<std::string>& filenames, const std::vector<std::string>& contents) {
74+
multipart_content https_client::build_multipart(const std::string &json, const std::vector<std::string>& filenames, const std::vector<std::string>& contents, const std::vector<std::string>& mimetypes) {
7575
if (filenames.empty() && contents.empty()) {
7676
if (!json.empty()) {
7777
return { json, "application/json" };
@@ -84,20 +84,24 @@ multipart_content https_client::build_multipart(const std::string &json, const s
8484
time_t dummy2 = time(nullptr) * time(nullptr);
8585
const std::string two_cr("\r\n\r\n");
8686
const std::string boundary("-------------" + to_hex(dummy1) + to_hex(dummy2));
87-
const std::string mime_part_start("--" + boundary + "\r\nContent-Type: application/octet-stream\r\nContent-Disposition: form-data; ");
87+
const std::string part_start("--" + boundary + "\r\nContent-Disposition: form-data; ");
88+
const std::string mime_type_start("\r\nContent-Type: ");
89+
const std::string default_mime_type("application/octet-stream");
8890

8991
std::string content("--" + boundary);
9092

9193
/* Special case, single file */
9294
content += "\r\nContent-Type: application/json\r\nContent-Disposition: form-data; name=\"payload_json\"" + two_cr;
9395
content += json + "\r\n";
9496
if (filenames.size() == 1 && contents.size() == 1) {
95-
content += mime_part_start + "name=\"file\"; filename=\"" + filenames[0] + "\"" + two_cr;
97+
content += part_start + "name=\"file\"; filename=\"" + filenames[0] + "\"";
98+
content += mime_type_start + (mimetypes.empty() || mimetypes[0].empty() ? default_mime_type : mimetypes[0]) + two_cr;
9699
content += contents[0];
97100
} else {
98101
/* Multiple files */
99102
for (size_t i = 0; i < filenames.size(); ++i) {
100-
content += mime_part_start + "name=\"files[" + std::to_string(i) + "]\"; filename=\"" + filenames[i] + "\"" + two_cr;
103+
content += part_start + "name=\"files[" + std::to_string(i) + "]\"; filename=\"" + filenames[i] + "\"";
104+
content += "\r\nContent-Type: " + (mimetypes.size() < i || mimetypes[i].empty() ? default_mime_type : mimetypes[i]) + two_cr;
101105
content += contents[i];
102106
content += "\r\n";
103107
}

src/dpp/message.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,10 @@ message& message::set_file_content(const std::string &fc)
534534
return *this;
535535
}
536536

537-
message& message::add_file(const std::string &fn, const std::string &fc) {
538-
filecontent.push_back(fc);
537+
message& message::add_file(const std::string &fn, const std::string &fc, const std::string &fm) {
539538
filename.push_back(fn);
539+
filecontent.push_back(fc);
540+
filemimetype.push_back(fm);
540541
return *this;
541542
}
542543

src/dpp/queues.cpp

+8-4
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,22 @@ namespace dpp {
3535
static std::string http_version = "DiscordBot (https://github.com/brainboxdotcc/DPP, " + std::to_string(DPP_VERSION_MAJOR) + "." + std::to_string(DPP_VERSION_MINOR) + "." + std::to_string(DPP_VERSION_PATCH) + ")";
3636
static const char* DISCORD_HOST = "https://discord.com";
3737

38-
http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent)
38+
http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method _method, const std::string &audit_reason, const std::string &filename, const std::string &filecontent, const std::string &filemimetype)
3939
: complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false)
4040
{
4141
if (!filename.empty())
4242
file_name.push_back(filename);
4343
if (!filecontent.empty())
4444
file_content.push_back(filecontent);
45+
if (!filemimetype.empty())
46+
file_mimetypes.push_back(filemimetype);
4547
}
4648

47-
http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method method, const std::string &audit_reason, const std::vector<std::string> &filename, const std::vector<std::string> &filecontent)
48-
: complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), mimetype("application/json"), waiting(false)
49+
http_request::http_request(const std::string &_endpoint, const std::string &_parameters, http_completion_event completion, const std::string &_postdata, http_method method, const std::string &audit_reason, const std::vector<std::string> &filename, const std::vector<std::string> &filecontent, const std::vector<std::string> &filemimetypes)
50+
: complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(method), reason(audit_reason), file_name(filename), file_content(filecontent), file_mimetypes(filemimetypes), mimetype("application/json"), waiting(false)
4951
{
52+
if (filecontent.size() && !filemimetypes.size())
53+
std::cout << "hi" << std::endl;
5054
}
5155

5256

@@ -167,7 +171,7 @@ http_request_completion_t http_request::run(cluster* owner) {
167171
multipart = { postdata, "" };
168172
} else {
169173

170-
multipart = https_client::build_multipart(postdata, file_name, file_content);
174+
multipart = https_client::build_multipart(postdata, file_name, file_content, file_mimetypes);
171175
if (!multipart.mimetype.empty()) {
172176
headers.emplace("Content-Type", multipart.mimetype);
173177
}

0 commit comments

Comments
 (0)