From ee0b5f685fd97cc8a02d0af1dd58d885f655b95b Mon Sep 17 00:00:00 2001 From: "Craig Edwards (Brain)" Date: Tue, 17 Oct 2023 20:04:26 +0100 Subject: [PATCH] feat: allow changing of HTTP protocol to 1.0 for backwards API endpoints and broken servers (#951) --- include/dpp/cluster.h | 6 ++++-- include/dpp/httpsclient.h | 8 +++++++- include/dpp/queues.h | 11 ++++++++--- src/dpp/cluster.cpp | 8 ++++---- src/dpp/httpsclient.cpp | 5 +++-- src/dpp/queues.cpp | 14 +++++++------- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index 70104b8f23..219e60800b 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -1325,8 +1325,9 @@ class DPP_EXPORT cluster { * @param filename Filename to post for POST requests (for uploading files) * @param filecontent File content to post for POST requests (for uploading files) * @param filemimetype File content to post for POST requests (for uploading files) + * @param protocol HTTP protocol to use (1.0 and 1.1 are supported) */ - void post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string ¶meters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename = "", const std::string &filecontent = "", const std::string &filemimetype = ""); + void post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string ¶meters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename = "", const std::string &filecontent = "", const std::string &filemimetype = "", const std::string& protocol = "1.1"); /** * @brief Post a multipart REST request. Where possible use a helper method instead like message_create @@ -1352,8 +1353,9 @@ class DPP_EXPORT cluster { * @param postdata POST data * @param mimetype MIME type of POST data * @param headers Headers to send with the request + * @param protocol HTTP protocol to use (1.1 and 1.0 are supported) */ - void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}); + void request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata = "", const std::string &mimetype = "text/plain", const std::multimap &headers = {}, const std::string &protocol = "1.1"); /** * @brief Respond to a slash command diff --git a/include/dpp/httpsclient.h b/include/dpp/httpsclient.h index c2733b0b04..d9ca9e582a 100644 --- a/include/dpp/httpsclient.h +++ b/include/dpp/httpsclient.h @@ -175,6 +175,11 @@ class DPP_EXPORT https_client : public ssl_client */ uint16_t status; + /** + * @brief The HTTP protocol to use + */ + std::string http_protocol; + /** * @brief Time at which the request should be abandoned */ @@ -243,8 +248,9 @@ class DPP_EXPORT https_client : public ssl_client * @param extra_headers Additional request headers, e.g. user-agent, authorization, etc * @param plaintext_connection Set to true to make the connection plaintext (turns off SSL) * @param request_timeout How many seconds before the connection is considered failed if not finished + * @param http_protocol Request HTTP protocol */ - https_client(const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5); + https_client(const std::string &hostname, uint16_t port = 443, const std::string &urlpath = "/", const std::string &verb = "GET", const std::string &req_body = "", const http_headers& extra_headers = {}, bool plaintext_connection = false, uint16_t request_timeout = 5, const std::string &protocol = "1.1"); /** * @brief Destroy the https client object diff --git a/include/dpp/queues.h b/include/dpp/queues.h index 61fde7c64c..232cbe98eb 100644 --- a/include/dpp/queues.h +++ b/include/dpp/queues.h @@ -158,6 +158,8 @@ class DPP_EXPORT http_request { std::multimap req_headers; /** @brief Waiting for rate limit to expire */ bool waiting; + /** @brief HTTP protocol */ + std::string protocol; /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). @@ -170,8 +172,9 @@ class DPP_EXPORT http_request { * @param filename The filename (server side) of any uploaded file * @param filecontent The binary content of any uploaded file for the request * @param filemimetype The MIME type of any uploaded file for the request + * @param http_protocol HTTP protocol */ - 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 = ""); + 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 = "", const std::string &http_protocol = "1.1"); /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). @@ -184,8 +187,9 @@ class DPP_EXPORT http_request { * @param filename The filename (server side) of any uploaded file * @param filecontent The binary content of any uploaded file for the request * @param filemimetypes The MIME type of any uploaded file for the request + * @param http_protocol HTTP protocol */ - 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 &filename = {}, const std::vector &filecontent = {}, const std::vector &filemimetypes = {}); + 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 &filename = {}, const std::vector &filecontent = {}, const std::vector &filemimetypes = {}, const std::string &http_protocol = "1.1"); /** * @brief Constructor. When constructing one of these objects it should be passed to request_queue::post_request(). @@ -195,8 +199,9 @@ class DPP_EXPORT http_request { * @param _postdata Data to send in POST and PUT requests * @param _mimetype POST data mime type * @param _headers HTTP headers to send + * @param http_protocol HTTP protocol */ - http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}); + http_request(const std::string &_url, http_completion_event completion, http_method method = m_get, const std::string &_postdata = "", const std::string &_mimetype = "text/plain", const std::multimap &_headers = {}, const std::string &http_protocol = "1.1"); /** * @brief Destroy the http request object diff --git a/src/dpp/cluster.cpp b/src/dpp/cluster.cpp index 16d3a777f3..05e5e9ba17 100644 --- a/src/dpp/cluster.cpp +++ b/src/dpp/cluster.cpp @@ -299,7 +299,7 @@ json error_response(const std::string& message, http_request_completion_t& rv) return j; } -void cluster::post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string ¶meters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename, const std::string &filecontent, const std::string &filemimetype) { +void cluster::post_rest(const std::string &endpoint, const std::string &major_parameters, const std::string ¶meters, http_method method, const std::string &postdata, json_encode_t callback, const std::string &filename, const std::string &filecontent, const std::string &filemimetype, const std::string &protocol) { /* NOTE: This is not a memory leak! The request_queue will free the http_request once it reaches the end of its lifecycle */ rest->post_request(new http_request(endpoint + (!major_parameters.empty() ? "/" : "") + major_parameters, parameters, [endpoint, callback](http_request_completion_t rv) { json j; @@ -314,7 +314,7 @@ void cluster::post_rest(const std::string &endpoint, const std::string &major_pa if (callback) { callback(j, rv); } - }, postdata, method, get_audit_reason(), filename, filecontent, filemimetype)); + }, postdata, method, get_audit_reason(), filename, filecontent, filemimetype, protocol)); } void cluster::post_rest_multipart(const std::string &endpoint, const std::string &major_parameters, const std::string ¶meters, http_method method, const std::string &postdata, json_encode_t callback, const std::vector &filename, const std::vector &filecontent, const std::vector &filemimetypes) { @@ -336,9 +336,9 @@ void cluster::post_rest_multipart(const std::string &endpoint, const std::string } -void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers) { +void cluster::request(const std::string &url, http_method method, http_completion_event callback, const std::string &postdata, const std::string &mimetype, const std::multimap &headers, const std::string &protocol) { /* NOTE: This is not a memory leak! The request_queue will free the http_request once it reaches the end of its lifecycle */ - raw_rest->post_request(new http_request(url, callback, method, postdata, mimetype, headers)); + raw_rest->post_request(new http_request(url, callback, method, postdata, mimetype, headers, protocol)); } gateway::gateway() : shards(0), session_start_total(0), session_start_remaining(0), session_start_reset_after(0), session_start_max_concurrency(0) { diff --git a/src/dpp/httpsclient.cpp b/src/dpp/httpsclient.cpp index 44ed466a0a..31ef37e49c 100644 --- a/src/dpp/httpsclient.cpp +++ b/src/dpp/httpsclient.cpp @@ -32,7 +32,7 @@ namespace dpp { -https_client::https_client(const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout) +https_client::https_client(const std::string &hostname, uint16_t port, const std::string &urlpath, const std::string &verb, const std::string &req_body, const http_headers& extra_headers, bool plaintext_connection, uint16_t request_timeout, const std::string &protocol) : ssl_client(hostname, std::to_string(port), plaintext_connection, false), state(HTTPS_HEADERS), request_type(verb), @@ -41,6 +41,7 @@ https_client::https_client(const std::string &hostname, uint16_t port, const st content_length(0), request_headers(extra_headers), status(0), + http_protocol(protocol), timeout(request_timeout) { nonblocking = false; @@ -57,7 +58,7 @@ void https_client::connect() } if (this->sfd != SOCKET_ERROR) { this->write( - this->request_type + " " + this->path + " HTTP/1.1\r\n" + this->request_type + " " + this->path + " HTTP/" + http_protocol + "\r\n" "Host: " + this->hostname + "\r\n" "pragma: no-cache\r\n" "Connection: keep-alive\r\n" diff --git a/src/dpp/queues.cpp b/src/dpp/queues.cpp index ba230f1d4a..0d28de6b88 100644 --- a/src/dpp/queues.cpp +++ b/src/dpp/queues.cpp @@ -32,8 +32,8 @@ namespace dpp { -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) - : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false) +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, const std::string &http_protocol) + : complete_handler(completion), completed(false), non_discord(false), endpoint(_endpoint), parameters(_parameters), postdata(_postdata), method(_method), reason(audit_reason), mimetype("application/json"), waiting(false), protocol(http_protocol) { if (!filename.empty()) { file_name.push_back(filename); @@ -46,14 +46,14 @@ http_request::http_request(const std::string &_endpoint, const std::string &_par } } -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 &filename, const std::vector &filecontent, const std::vector &filemimetypes) - : 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) +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 &filename, const std::vector &filecontent, const std::vector &filemimetypes, const std::string &http_protocol) + : 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), protocol(http_protocol) { } -http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers) - : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false) +http_request::http_request(const std::string &_url, http_completion_event completion, http_method _method, const std::string &_postdata, const std::string &_mimetype, const std::multimap &_headers, const std::string &http_protocol) + : complete_handler(completion), completed(false), non_discord(true), endpoint(_url), postdata(_postdata), method(_method), mimetype(_mimetype), req_headers(_headers), waiting(false), protocol(http_protocol) { } @@ -176,7 +176,7 @@ http_request_completion_t http_request::run(cluster* owner) { } http_connect_info hci = https_client::get_host_info(_host); try { - https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl); + https_client cli(hci.hostname, hci.port, _url, request_verb[method], multipart.body, headers, !hci.is_ssl, 5, protocol); rv.latency = dpp::utility::time_f() - start; if (cli.get_status() < 100) { rv.error = h_connection;