summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Smith <glenn@vector35.com>2021-08-13 16:14:48 -0400
committerGlenn Smith <glenn@vector35.com>2021-08-17 16:50:10 -0400
commit54c78775ab4eb53c991ed17e4317bf876dea5bb0 (patch)
treee3852837d01b1c1b6d683692a5ec92b764724195
parent176cc79219cdf015cbcf7f90bb6eb5f6a8c59940 (diff)
HTTP C++ library via downloadprovider
-rw-r--r--http.cpp444
-rw-r--r--http.h361
2 files changed, 805 insertions, 0 deletions
diff --git a/http.cpp b/http.cpp
new file mode 100644
index 00000000..a7753f0f
--- /dev/null
+++ b/http.cpp
@@ -0,0 +1,444 @@
+// Copyright (c) 2015-2021 Vector 35 Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <string.h>
+#include <chrono>
+#include <thread>
+#include <math.h>
+#include "http.h"
+
+using namespace BinaryNinja;
+using namespace std;
+
+namespace BinaryNinja::Http
+{
+ #define HTTP_MAX_RETRIES 3
+ #define HTTP_BACKOFF_FACTOR 1
+
+ struct RequestContext
+ {
+ size_t uploadOffset;
+ size_t downloadLength;
+ bool cancelled;
+ const Request& request;
+ Response& response;
+
+ RequestContext(const Request& request, Response& response):
+ uploadOffset(0), downloadLength(0), cancelled(false), request(request), response(response)
+ {
+
+ }
+ };
+
+
+ int64_t HttpReadCallback(uint8_t* data, uint64_t len, void* ctxt)
+ {
+ auto* request = reinterpret_cast<RequestContext*>(ctxt);
+ uint64_t remain = request->request.m_body.size() - request->uploadOffset;
+ if (len < remain)
+ {
+ memcpy(data, &request->request.m_body[request->uploadOffset], len);
+ request->uploadOffset += len;
+ if (request->request.m_uploadProgress)
+ {
+ if (!request->request.m_uploadProgress(request->uploadOffset, request->request.m_body.size()))
+ {
+ request->cancelled = true;
+ return -1;
+ }
+ }
+ return len;
+ }
+ else if (remain > 0)
+ {
+ memcpy(data, &request->request.m_body[request->uploadOffset], remain);
+ request->uploadOffset += remain;
+ if (request->request.m_uploadProgress)
+ {
+ if (!request->request.m_uploadProgress(request->uploadOffset, request->request.m_body.size()))
+ {
+ request->cancelled = true;
+ return -1;
+ }
+ }
+ return remain;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+
+ uint64_t HttpWriteCallback(uint8_t* data, uint64_t len, void* ctxt)
+ {
+ auto* request = reinterpret_cast<RequestContext*>(ctxt);
+ // copy can totally take pointers, pretty cool
+ copy(data, &data[len], back_inserter(request->response.body));
+
+ // Detect content length if it has not been found yet
+ if (request->downloadLength == 0)
+ {
+ const auto& headers = request->response.response.headers;
+ auto found = headers.find("Content-Length");
+ if (found != headers.end())
+ {
+ request->downloadLength = strtoll(found->second.c_str(), nullptr, 10);
+ request->response.body.reserve(request->downloadLength);
+ }
+ else
+ {
+ request->downloadLength = -1;
+ }
+ }
+
+ if (request->request.m_downloadProgress)
+ {
+ if (!request->request.m_downloadProgress(request->response.body.size(), request->downloadLength))
+ {
+ // Signal error by returning non-len
+ request->cancelled = true;
+ return 0;
+ }
+ }
+
+ return len;
+ }
+
+
+ string UrlEncode(const string& str)
+ {
+ string outStr;
+ outStr.reserve(str.size());
+ for (auto& ch: str)
+ {
+ if (isalnum(ch))
+ {
+ outStr += ch;
+ }
+ else
+ {
+ char buf[8];
+ snprintf(buf, 8, "%%%02hhx", ch);
+ outStr += string(buf);
+ }
+ }
+ return outStr;
+ }
+
+
+ string UrlEncode(const vector<pair<string, string>>& fields)
+ {
+ string outStr;
+
+ bool first = true;
+ for (auto& field: fields)
+ {
+ if (!first)
+ {
+ outStr += "&";
+ }
+ outStr += UrlEncode(field.first);
+ outStr += "=";
+ outStr += UrlEncode(field.second);
+ first = false;
+ }
+
+ return outStr;
+ }
+
+
+ vector<uint8_t> MultipartEncode(const vector<MultipartField>& fields, string& boundary)
+ {
+ boundary = string(4, '-') + "MultipartFormBoundary" + GetUniqueIdentifierString();
+
+ vector<uint8_t> boundaryVec;
+ boundaryVec.reserve(boundary.size());
+ copy(boundary.begin(), boundary.end(), back_inserter(boundaryVec));
+
+ vector<uint8_t> result;
+ size_t expectedSize = boundaryVec.size() * fields.size();
+ for (const auto& field: fields)
+ {
+ expectedSize += field.name.size() + field.content.size();
+ }
+ result.reserve(expectedSize);
+
+ for (const auto& field: fields)
+ {
+ result.push_back('-');
+ result.push_back('-');
+ copy(boundaryVec.begin(), boundaryVec.end(), back_inserter(result));
+ result.push_back('\r');
+ result.push_back('\n');
+ string disposition;
+ if (field.filename)
+ {
+ disposition =
+ string("Content-Disposition: form-data; name=\"") + field.name + "\"; filename=\"" +
+ *field.filename + "\"";
+ disposition += string("\r\nContent-Type: application/octet-stream");
+ }
+ else
+ {
+ disposition = string("Content-Disposition: form-data; name=\"") + field.name + "\"";
+ }
+ disposition += "\r\n\r\n";
+
+ copy(disposition.begin(), disposition.end(), back_inserter(result));
+ copy(field.content.begin(), field.content.end(), back_inserter(result));
+
+ result.push_back('\r');
+ result.push_back('\n');
+ }
+ result.push_back('-');
+ result.push_back('-');
+ copy(boundaryVec.begin(), boundaryVec.end(), back_inserter(result));
+ result.push_back('-');
+ result.push_back('-');
+ result.push_back('\r');
+ result.push_back('\n');
+
+ return result;
+ }
+
+
+ Request::Request(string method, string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress):
+ m_method(method), m_url(url), m_headers(headers),
+ m_downloadProgress(downloadProgress), m_uploadProgress(uploadProgress)
+ {
+ if (!params.empty())
+ {
+ m_url += "?";
+ m_url += UrlEncode(params);
+ }
+
+ if (m_headers.find("Content-Length") == m_headers.end())
+ {
+ m_headers.insert({"Content-Length", to_string(m_body.size())});
+ }
+ if (m_headers.find("Content-Type") == m_headers.end())
+ {
+ m_headers.insert({"Content-Type", "application/octet-stream"});
+ }
+ }
+
+
+ Request::Request(string method, string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<uint8_t> body,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress):
+ m_method(method), m_url(url), m_headers(headers), m_body(body),
+ m_downloadProgress(downloadProgress), m_uploadProgress(uploadProgress)
+ {
+ if (!params.empty())
+ {
+ m_url += "?";
+ m_url += UrlEncode(params);
+ }
+
+ if (m_headers.find("Content-Length") == m_headers.end())
+ {
+ m_headers.insert({"Content-Length", to_string(m_body.size())});
+ }
+ if (m_headers.find("Content-Type") == m_headers.end())
+ {
+ m_headers.insert({"Content-Type", "application/octet-stream"});
+ }
+ }
+
+
+ Request::Request(string method, string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<pair<string, string>> formFields,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress):
+ m_method(method), m_url(url), m_headers(headers),
+ m_downloadProgress(downloadProgress), m_uploadProgress(uploadProgress)
+ {
+ if (!params.empty())
+ {
+ m_url += "?";
+ m_url += UrlEncode(params);
+ }
+
+ string encoded = UrlEncode(formFields);
+ copy(encoded.begin(), encoded.end(), back_inserter(m_body));
+ m_headers.insert({"Content-Type", "application/x-www-form-urlencoded"});
+
+ if (m_headers.find("Content-Length") == m_headers.end())
+ {
+ m_headers.insert({"Content-Length", to_string(m_body.size())});
+ }
+ if (m_headers.find("Content-Type") == m_headers.end())
+ {
+ m_headers.insert({"Content-Type", "application/octet-stream"});
+ }
+ }
+
+
+ Request::Request(string method, string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<MultipartField> formFields,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress):
+ m_method(method), m_url(url), m_headers(headers),
+ m_downloadProgress(downloadProgress), m_uploadProgress(uploadProgress)
+ {
+ if (!params.empty())
+ {
+ m_url += "?";
+ m_url += UrlEncode(params);
+ }
+
+ string boundary;
+ m_body = MultipartEncode(formFields, boundary);
+
+ m_headers.insert({"Content-Type", string("multipart/form-data; boundary=\"") + boundary + "\""});
+
+ if (m_headers.find("Content-Length") == m_headers.end())
+ {
+ m_headers.insert({"Content-Length", to_string(m_body.size())});
+ }
+ if (m_headers.find("Content-Type") == m_headers.end())
+ {
+ m_headers.insert({"Content-Type", "application/octet-stream"});
+ }
+ }
+
+
+ Request Request::Get(string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress)
+ {
+ return Request("GET", url, headers, params, downloadProgress, uploadProgress);
+ }
+
+
+ Request Request::Post(string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<uint8_t> body,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress)
+ {
+ return Request("POST", url, headers, params, body, downloadProgress, uploadProgress);
+ }
+
+
+ Request Request::Post(string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<pair<string, string>> formFields,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress)
+ {
+ return Request("POST", url, headers, params, formFields, downloadProgress, uploadProgress);
+ }
+
+
+ Request Request::Post(string url,
+ unordered_map<string, string> headers,
+ vector<pair<string, string>> params,
+ vector<MultipartField> formFields,
+ function<bool(size_t, size_t)> downloadProgress,
+ function<bool(size_t, size_t)> uploadProgress)
+ {
+ return Request("POST", url, headers, params, formFields, downloadProgress, uploadProgress);
+ }
+
+
+ int Perform(const Ref<DownloadInstance>& instance, const Request& request, Response& response)
+ {
+ int result = -1;
+ int retry = 0;
+ while (true)
+ {
+ RequestContext context{request, response};
+ BNDownloadInstanceInputOutputCallbacks callbacks{};
+ memset(&callbacks, 0, sizeof(BNDownloadInstanceInputOutputCallbacks));
+ callbacks.readContext = &context;
+ callbacks.readCallback = &HttpReadCallback;
+ callbacks.writeContext = &context;
+ callbacks.writeCallback = &HttpWriteCallback;
+ result = instance->PerformCustomRequest(request.m_method, request.m_url, request.m_headers, response.response, &callbacks);
+ if (result >= 0)
+ break;
+
+ // Request failed, grab its error and try again
+ response.error = instance->GetError();
+ if (retry == HTTP_MAX_RETRIES || context.cancelled)
+ break;
+ size_t backoff = 1000 * HTTP_BACKOFF_FACTOR * (2 * pow(2, retry - 1));
+ retry += 1;
+ LogWarn("Attempt %d to %s %s failed, trying again in %zums\n", retry, request.m_method.data(), request.m_url.data(), backoff);
+ this_thread::sleep_for(chrono::milliseconds(backoff));
+ }
+ return result;
+ }
+
+ vector<uint8_t> Response::GetRaw() const noexcept
+ {
+ return body;
+ }
+
+
+ string Response::GetString() const noexcept
+ {
+ string str;
+ copy(body.begin(), body.end(), back_inserter(str));
+ return str;
+ }
+
+
+ Json::Value Response::GetJson() const
+ {
+ string str = GetString();
+
+ unique_ptr<Json::CharReader> reader(Json::CharReaderBuilder().newCharReader());
+ string errors;
+ Json::Value value;
+ if (!reader->parse(str.data(), str.data() + str.size(), &value, &errors))
+ {
+ throw runtime_error(std::string("Could not parse JSON: ") + errors);
+ }
+ return value;
+ }
+
+
+ bool Response::GetJson(Json::Value& value) const noexcept
+ {
+ string str = GetString();
+
+ unique_ptr<Json::CharReader> reader(Json::CharReaderBuilder().newCharReader());
+ string errors;
+ return reader->parse(str.data(), str.data() + str.size(), &value, &errors);
+ }
+}
diff --git a/http.h b/http.h
new file mode 100644
index 00000000..c3f0b5a9
--- /dev/null
+++ b/http.h
@@ -0,0 +1,361 @@
+// Copyright (c) 2015-2021 Vector 35 Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#pragma once
+
+#include <vector>
+#include <string>
+#include <optional>
+#include <unordered_map>
+#include <functional>
+#include <utility>
+#include <stdint.h>
+#include "binaryninjaapi.h"
+
+
+namespace BinaryNinja::Http
+{
+
+ enum ResponseCode
+ {
+ Continue = 100,
+ SwitchingProtocols = 101,
+ OK = 200,
+ Created = 201,
+ Accepted = 202,
+ NonAuthoritativeInformation = 203,
+ NoContent = 204,
+ ResetContent = 205,
+ PartialContent = 206,
+ MultipleChoices = 300,
+ MovedPermanently = 301,
+ Found = 302,
+ SeeOther = 303,
+ NotModified = 304,
+ UseProxy = 305,
+ TemporaryRedirect = 307,
+ BadRequest = 400,
+ Unauthorized = 401,
+ PaymentRequired = 402,
+ Forbidden = 403,
+ NotFound = 404,
+ MethodNotAllowed = 405,
+ NotAcceptable = 406,
+ ProxyAuthenticationRequired = 407,
+ RequestTimeout = 408,
+ Conflict = 409,
+ Gone = 410,
+ LengthRequired = 411,
+ PreconditionFailed = 412,
+ RequestEntityTooLarge = 413,
+ RequestURITooLong = 414,
+ UnsupportedMediaType = 415,
+ RequestedRangeNotSatisfiable = 416,
+ ExpectationFailed = 417,
+ ImATeapot = 418,
+ InternalServerError = 500,
+ NotImplemented = 501,
+ BadGateway = 502,
+ ServiceUnavailable = 503,
+ GatewayTimeout = 504,
+ HTTPVersionNotSupported = 505,
+ };
+
+
+ /*!
+ Basic HTTP response structure
+ */
+ struct Response
+ {
+ BinaryNinja::DownloadInstance::Response response;
+ std::vector<uint8_t> body;
+ std::string error;
+
+ /*!
+ Get response body as uint8_t vector
+ \return Response body bytes
+ */
+ std::vector<uint8_t> GetRaw() const noexcept;
+ /*!
+ Get response body as text string
+ \return Response body string
+ */
+ std::string GetString() const noexcept;
+ /*!
+ Get response body as a json value
+ \return Response body json
+ \throws runtime_error On JSON parse error
+ */
+ Json::Value GetJson() const;
+ /*!
+ Get response body as a json value without throwing
+ \param value Output json value
+ \return True if successful
+ */
+ bool GetJson(Json::Value& value) const noexcept;
+ };
+
+
+ /*!
+ Structure for multipart form fields
+ */
+ struct MultipartField
+ {
+ std::string name;
+ std::vector<uint8_t> content;
+ std::optional<std::string> filename;
+
+ /*!
+ Construct a Multipart Field structure with a UTF-8 string encoded body
+ \param name Name of the field to be sent in a POST request
+ \param content Contents of the field
+ */
+ MultipartField(std::string name, const std::string& content):
+ name(std::move(name)), content({}), filename({})
+ {
+ std::copy(content.begin(), content.end(), std::back_inserter(this->content));
+ }
+
+ /*!
+ Construct a Multipart Field structure for a file with a UTF-8 string encoded body
+ \param name Name of the field to be sent in a POST request
+ \param content Contents of the field
+ \param filename Filename associated with the contents
+ */
+ MultipartField(std::string name, const std::string& content, std::string filename):
+ name(std::move(name)), content({}), filename(std::move(filename))
+ {
+ std::copy(content.begin(), content.end(), std::back_inserter(this->content));
+ }
+
+ /*!
+ Construct a Multipart Field structure with a binary blob body
+ \param name Name of the field to be sent in a POST request
+ \param content Contents of the field
+ */
+ MultipartField(std::string name, std::vector<uint8_t> content):
+ name(std::move(name)), content(std::move(content)), filename({})
+ {
+
+ }
+
+ /*!
+ Construct a Multipart Field structure for a file with a binary blob body
+ \param name Name of the field to be sent in a POST request
+ \param content Contents of the field
+ \param filename Filename associated with the contents
+ */
+ MultipartField(std::string name, std::vector<uint8_t> content, std::string filename):
+ name(std::move(name)), content(std::move(content)), filename(std::move(filename))
+ {
+
+ }
+ };
+
+
+ /*!
+ Structure containing HTTP metadata for requests
+ */
+ struct Request
+ {
+ std::string m_method;
+ std::string m_url;
+ std::unordered_map<std::string, std::string> m_headers;
+ std::vector<uint8_t> m_body;
+
+ std::function<bool(size_t, size_t)> m_downloadProgress;
+ std::function<bool(size_t, size_t)> m_uploadProgress;
+
+ /*!
+ Construct an arbitrary HTTP request with an empty body
+ \param method Request method eg GET
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ */
+ Request(std::string method, std::string url,
+ std::unordered_map<std::string, std::string> headers = {},
+ std::vector<std::pair<std::string, std::string>> params = {},
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an arbitrary HTTP request with a binary data body
+ \param method Request method eg GET
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param body Content body (binary data)
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ */
+ Request(std::string method, std::string url,
+ std::unordered_map<std::string, std::string> headers,
+ std::vector<std::pair<std::string, std::string>> params,
+ std::vector<uint8_t> body,
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an arbitrary HTTP request with url encoded form fields as the body
+ \param method Request method eg GET
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param formFields HTTP form fields, keys/values (both must be strings)
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ */
+ Request(std::string method, std::string url,
+ std::unordered_map<std::string, std::string> headers,
+ std::vector<std::pair<std::string, std::string>> params,
+ std::vector<std::pair<std::string, std::string>> formFields,
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an arbitrary HTTP request with Multipart encoded form fields as the body
+ \param method Request method eg GET
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param formFields HTTP form fields, keys/values (values can be arbitrary data)
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ */
+ Request(std::string method, std::string url,
+ std::unordered_map<std::string, std::string> headers,
+ std::vector<std::pair<std::string, std::string>> params,
+ std::vector<MultipartField> formFields,
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an HTTP GET request
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ \return Request structure with specified fields
+ */
+ static Request Get(std::string url,
+ std::unordered_map<std::string, std::string> headers = {},
+ std::vector<std::pair<std::string, std::string>> params = {},
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an HTTP POST request with a binary data body
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param body Request body data
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ \return Request structure with specified fields
+ */
+ static Request Post(std::string url,
+ std::unordered_map<std::string, std::string> headers = {},
+ std::vector<std::pair<std::string, std::string>> params = {},
+ std::vector<uint8_t> body = {},
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an HTTP POST request with url encoded form fields as the body
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param formFields HTTP form fields, keys/values (both must be strings)
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ \return Request structure with specified fields
+ */
+ static Request Post(std::string url,
+ std::unordered_map<std::string, std::string> headers,
+ std::vector<std::pair<std::string, std::string>> params,
+ std::vector<std::pair<std::string, std::string>> formFields,
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+
+
+ /*!
+ Construct an HTTP POST request with Multipart encoded form fields as the body
+ \param url Target URL eg https://binary.ninja
+ \param headers Header keys/values
+ \param params Query parameters, keys/values
+ \param formFields HTTP form fields, keys/values (values can be arbitrary data)
+ \param downloadProgress Function to call for download progress updates
+ \param uploadProgress Function to call for upload progress updates
+ \return Request structure with specified fields
+ */
+ static Request Post(std::string url,
+ std::unordered_map<std::string, std::string> headers,
+ std::vector<std::pair<std::string, std::string>> params,
+ std::vector<MultipartField> formFields,
+ std::function<bool(size_t, size_t)> downloadProgress = {},
+ std::function<bool(size_t, size_t)> uploadProgress = {});
+ };
+
+
+ /*!
+ Convert a string to a URLEncoded form-field safe form
+ \param str Input string
+ \return URLEncoded string
+ */
+ std::string UrlEncode(const std::string& str);
+
+
+ /*!
+ Convert a list of key/value pair strings into a URLEncoded form body
+ \param fields Input key/value pairs
+ \return URLEncoded form body
+ */
+ std::string UrlEncode(const std::vector<std::pair<std::string, std::string>>& fields);
+
+
+ /*!
+ Convert a list of form fields (potentially containing binary data) into a multipart encoded
+ form body
+ \param fields Input fields
+ \param boundary Output boundary between fields in the body (for Content-Type header)
+ \return Multipart encoded form body
+ */
+ std::vector<uint8_t> MultipartEncode(const std::vector<MultipartField>& fields, std::string& boundary);
+
+
+ /*!
+ Perform an HTTP request as specified by a Request, storing results in a Response
+ \param instance DownloadInstance instance
+ \param request Input Request structure with fields
+ \param response Output Response structure with body
+ \return Zero or greater on success
+ */
+ int Perform(const BinaryNinja::Ref<BinaryNinja::DownloadInstance>& instance, const Request& request, Response& response);
+} \ No newline at end of file