mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-24 05:14:40 -04:00
remove old web utils
This commit is contained in:
parent
8ea17f3d38
commit
b129808723
7 changed files with 445 additions and 1552 deletions
loader
include/Geode/utils
src
|
@ -1,390 +1,134 @@
|
|||
#pragma once
|
||||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "MiniFunction.hpp"
|
||||
#include <Geode/loader/Loader.hpp> // another great circular dependency fix
|
||||
#include <matjson.hpp>
|
||||
#include "Result.hpp"
|
||||
#include "general.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include "Task.hpp"
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
namespace geode::utils::web {
|
||||
GEODE_DLL void openLinkInBrowser(std::string const& url);
|
||||
|
||||
// https://curl.se/libcurl/c/CURLOPT_HTTPAUTH.html
|
||||
namespace http_auth {
|
||||
constexpr static long BASIC = 0x0001;
|
||||
constexpr static long DIGEST = 0x0002;
|
||||
constexpr static long DIGEST_IE = 0x0004;
|
||||
constexpr static long BEARER = 0x0008;
|
||||
constexpr static long NEGOTIATE = 0x0010;
|
||||
constexpr static long NTLM = 0x0020;
|
||||
constexpr static long NTLM_WB = 0x0040;
|
||||
constexpr static long ANY = 0x0080;
|
||||
constexpr static long ANYSAFE = 0x0100;
|
||||
constexpr static long ONLY = 0x0200;
|
||||
constexpr static long AWS_SIGV4 = 0x0400;
|
||||
}
|
||||
|
||||
using FileProgressCallback = utils::MiniFunction<bool(double, double)>;
|
||||
// https://curl.se/libcurl/c/CURLOPT_PROXYTYPE.html
|
||||
enum class ProxyType {
|
||||
HTTP, // HTTP
|
||||
HTTPS, // HTTPS
|
||||
HTTPS2, // HTTPS (attempt to use HTTP/2)
|
||||
SOCKS4, // Socks4
|
||||
SOCKS4A, // Socks4 with hostname resolution
|
||||
SOCKS5, // Socks5
|
||||
SOCKS5H, // Socks5 with hostname resolution
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously fetch data from the internet
|
||||
* @param url URL to fetch
|
||||
* @returns Returned data as bytes, or error on error
|
||||
*/
|
||||
[[deprecated("Use the WebRequest class from the web2.hpp header instead")]]
|
||||
GEODE_DLL Result<ByteVector> fetchBytes(std::string const& url);
|
||||
struct ProxyOpts {
|
||||
std::string address; // Proxy address/FQDN
|
||||
std::optional<std::uint16_t> port; // Proxy port
|
||||
ProxyType type = ProxyType::HTTP; // Proxy type
|
||||
long auth = http_auth::BASIC; // HTTP proxy auth method
|
||||
std::string username; // Proxy username
|
||||
std::string password; // Proxy password
|
||||
bool tunneling = false; // Enable HTTP tunneling
|
||||
bool certVerification = true; // Enable HTTPS certificate verification
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronously fetch data from the internet
|
||||
* @param url URL to fetch
|
||||
* @returns Returned data as string, or error on error
|
||||
*/
|
||||
[[deprecated("Use the WebRequest class from the web2.hpp header instead")]]
|
||||
GEODE_DLL Result<std::string> fetch(std::string const& url);
|
||||
class WebRequest;
|
||||
|
||||
/**
|
||||
* Syncronously download a file from the internet
|
||||
* @param url URL to fetch
|
||||
* @param into Path to download file into
|
||||
* @param prog Progress function; first parameter is bytes downloaded so
|
||||
* far, and second is total bytes to download. Return true to continue
|
||||
* downloading, and false to interrupt. Note that interrupting does not
|
||||
* automatically remove the file that was being downloaded
|
||||
* @returns Returned data as JSON, or error on error
|
||||
*/
|
||||
[[deprecated("Use the WebRequest class from the web2.hpp header instead")]]
|
||||
GEODE_DLL Result<> fetchFile(
|
||||
std::string const& url, std::filesystem::path const& into, FileProgressCallback prog = nullptr
|
||||
);
|
||||
|
||||
/**
|
||||
* Synchronously fetch data from the internet and parse it as JSON
|
||||
* @param url URL to fetch
|
||||
* @returns Returned data as JSON, or error on error
|
||||
*/
|
||||
[[deprecated("Use the WebRequest class from the web2.hpp header instead")]]
|
||||
GEODE_DLL Result<matjson::Value> fetchJSON(std::string const& url);
|
||||
|
||||
class SentAsyncWebRequest;
|
||||
template <class T>
|
||||
class AsyncWebResult;
|
||||
class AsyncWebResponse;
|
||||
class AsyncWebRequest;
|
||||
|
||||
using AsyncProgress = utils::MiniFunction<void(SentAsyncWebRequest&, double, double)>;
|
||||
using AsyncExpect = utils::MiniFunction<void(std::string const&)>;
|
||||
using AsyncExpectCode = utils::MiniFunction<void(std::string const&, int)>;
|
||||
using AsyncThen = utils::MiniFunction<void(SentAsyncWebRequest&, ByteVector const&)>;
|
||||
using AsyncCancelled = utils::MiniFunction<void(SentAsyncWebRequest&)>;
|
||||
|
||||
/**
|
||||
* A handle to an in-progress sent asynchronous web request. Use this to
|
||||
* cancel the request / query information about it
|
||||
*/
|
||||
class GEODE_DLL [[deprecated("Use the WebRequest class from the web2.hpp header instead")]] SentAsyncWebRequest {
|
||||
class GEODE_DLL WebResponse final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
template <class T>
|
||||
friend class AsyncWebResult;
|
||||
friend class AsyncWebRequest;
|
||||
|
||||
void pause();
|
||||
void resume();
|
||||
void error(std::string const& error, int code);
|
||||
void doCancel();
|
||||
friend class WebRequest;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Do not call these manually.
|
||||
*/
|
||||
SentAsyncWebRequest();
|
||||
~SentAsyncWebRequest();
|
||||
static std::shared_ptr<SentAsyncWebRequest> create(AsyncWebRequest const&, std::string const& id);
|
||||
// Must be default-constructible for use in Promise
|
||||
WebResponse();
|
||||
|
||||
/**
|
||||
* Cancel the request. Cleans up any downloaded files, but if you run
|
||||
* extra code in `then`, you will have to clean it up manually in
|
||||
* `cancelled`
|
||||
*/
|
||||
void cancel();
|
||||
/**
|
||||
* Check if the request is finished
|
||||
*/
|
||||
bool finished() const;
|
||||
bool ok() const;
|
||||
int code() const;
|
||||
|
||||
/**
|
||||
* Get the response header of the request. Only valid after the request
|
||||
*/
|
||||
std::string getResponseHeader(std::string_view header) const;
|
||||
Result<std::string> string() const;
|
||||
Result<matjson::Value> json() const;
|
||||
ByteVector data() const;
|
||||
|
||||
std::vector<std::string> headers() const;
|
||||
std::optional<std::string> header(std::string_view name) const;
|
||||
};
|
||||
|
||||
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;
|
||||
|
||||
template <class T>
|
||||
using DataConverter = Result<T> (*)(ByteVector const&);
|
||||
|
||||
/**
|
||||
* An asynchronous, thread-safe web request. Downloads data from the
|
||||
* internet without slowing the main thread. All callbacks are run in the
|
||||
* GD thread, so interacting with the Cocos2d UI is perfectly safe
|
||||
*/
|
||||
class GEODE_DLL [[deprecated("Use the WebRequest class from the web2.hpp header instead")]] AsyncWebRequest {
|
||||
class GEODE_DLL WebProgress final {
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
template <class T>
|
||||
friend class AsyncWebResult;
|
||||
friend class SentAsyncWebRequest;
|
||||
friend class AsyncWebResponse;
|
||||
friend class SentAsyncWebRequest::Impl;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
AsyncWebRequest& setThen(AsyncThen);
|
||||
friend class WebRequest;
|
||||
|
||||
public:
|
||||
/**
|
||||
* An asynchronous, thread-safe web request. Downloads data from the
|
||||
* internet without slowing the main thread. All callbacks are run in the
|
||||
* GD thread, so interacting with the Cocos2d UI is perfectly safe
|
||||
*/
|
||||
AsyncWebRequest();
|
||||
~AsyncWebRequest();
|
||||
// Must be default-constructible for use in Promise
|
||||
WebProgress();
|
||||
|
||||
/**
|
||||
* URL to fetch from the internet asynchronously
|
||||
* @param url URL of the data to download. Redirects will be
|
||||
* automatically followed
|
||||
* @returns An AsyncWebResponse object
|
||||
*/
|
||||
AsyncWebResponse fetch(std::string_view const url);
|
||||
/**
|
||||
* Begin the web request. It's not always necessary to call this as the
|
||||
* destructor calls it automatically, but if you need access to the
|
||||
* handle of the sent request, use this
|
||||
* @returns Handle to the sent web request
|
||||
*/
|
||||
SentAsyncWebRequestHandle send();
|
||||
size_t downloaded() const;
|
||||
size_t downloadTotal() const;
|
||||
std::optional<float> downloadProgress() const;
|
||||
|
||||
/**
|
||||
* Shorthand for a GET request to the given url.
|
||||
* Same return value as fetch.
|
||||
*/
|
||||
AsyncWebResponse get(std::string_view const url);
|
||||
/**
|
||||
* Shorthand for a POST request to the given url.
|
||||
* Same return value as fetch.
|
||||
*/
|
||||
AsyncWebResponse post(std::string_view const url);
|
||||
/**
|
||||
* Shorthand for a PUT request to the given url.
|
||||
* Same return value as fetch.
|
||||
*/
|
||||
AsyncWebResponse put(std::string_view const url);
|
||||
/**
|
||||
* Shorthand for a PATCH request to the given url.
|
||||
* Same return value as fetch.
|
||||
*/
|
||||
AsyncWebResponse patch(std::string_view const url);
|
||||
|
||||
/**
|
||||
* If you only want one instance of this web request to run (for example,
|
||||
* you're downloading some global data for a manager), then use this
|
||||
* to specify a Join ID. If another request with the same ID is
|
||||
* already running, this request's callbacks will be appended to the
|
||||
* existing one instead of creating a new request
|
||||
* @param requestID The Join ID of the request. Can be anything,
|
||||
* recommended to be something unique
|
||||
* @returns Same AsyncWebRequest
|
||||
*/
|
||||
AsyncWebRequest& join(std::string_view const requestID);
|
||||
|
||||
// Request parameters
|
||||
|
||||
/**
|
||||
* In order to specify a http header to the request, give it here.
|
||||
* Can be called more than once.
|
||||
*/
|
||||
AsyncWebRequest& header(std::string_view const header);
|
||||
/**
|
||||
* In order to specify a http header to the request, give it here.
|
||||
* Can be called more than once.
|
||||
*/
|
||||
AsyncWebRequest& header(std::string_view const headerName, std::string_view const headerValue);
|
||||
/**
|
||||
* In order to specify an user agent to the request, give it here.
|
||||
*/
|
||||
AsyncWebRequest& userAgent(std::string_view const userAgent);
|
||||
/**
|
||||
* Sets the Content-Type header to the specified value.
|
||||
*/
|
||||
AsyncWebRequest& contentType(std::string_view const contentType);
|
||||
/**
|
||||
* Specify that the request is a POST request.
|
||||
*/
|
||||
AsyncWebRequest& postRequest();
|
||||
/**
|
||||
* Specify the HTTP method for the request, like PUT and DELETE.
|
||||
*/
|
||||
AsyncWebRequest& method(std::string_view const method);
|
||||
/**
|
||||
* Specify the raw request body to send with the request.
|
||||
* Content type is unchanged.
|
||||
*/
|
||||
AsyncWebRequest& bodyRaw(std::string_view const content);
|
||||
/**
|
||||
* Specify a json request body. Additionally sets the content type to
|
||||
* application/json.
|
||||
*/
|
||||
AsyncWebRequest& body(matjson::Value const& value);
|
||||
/**
|
||||
* Specify a timeout, in seconds, in which the request will fail.
|
||||
*/
|
||||
AsyncWebRequest& timeout(std::chrono::seconds seconds);
|
||||
|
||||
// Callbacks
|
||||
|
||||
/**
|
||||
* Specify a callback to run if the download fails. The callback is
|
||||
* always ran in the GD thread, so interacting with UI is safe
|
||||
* @param handler Callback to run if the download fails
|
||||
* @returns Same AsyncWebRequest
|
||||
*/
|
||||
AsyncWebRequest& expect(AsyncExpect handler);
|
||||
/**
|
||||
* Specify a callback to run if the download fails. The callback is
|
||||
* always ran in the GD thread, so interacting with UI is safe
|
||||
* @param handler Callback to run if the download fails
|
||||
* @returns Same AsyncWebRequest
|
||||
*/
|
||||
AsyncWebRequest& expect(AsyncExpectCode handler);
|
||||
/**
|
||||
* Specify a callback to run when the download progresses. The callback is
|
||||
* always ran in the GD thread, so interacting with UI is safe
|
||||
* @param handler Callback to run when the download progresses
|
||||
* @returns Same AsyncWebRequest
|
||||
*/
|
||||
AsyncWebRequest& progress(AsyncProgress handler);
|
||||
/**
|
||||
* Specify a callback to run if the download is cancelled. The callback is
|
||||
* always ran in the GD thread, so interacting with UI is safe. Web
|
||||
* requests may be cancelled after they are finished (for example, if
|
||||
* downloading files in bulk and one fails). In that case, handle
|
||||
* freeing up the results of `then` in this handler
|
||||
* @param handler Callback to run if the download is cancelled
|
||||
* @returns Same AsyncWebRequest
|
||||
*/
|
||||
AsyncWebRequest& cancelled(AsyncCancelled handler);
|
||||
size_t uploaded() const;
|
||||
size_t uploadTotal() const;
|
||||
std::optional<float> uploadProgress() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class [[deprecated("Use the WebRequest class from the web2.hpp header instead")]] AsyncWebResult {
|
||||
using WebTask = Task<WebResponse, WebProgress>;
|
||||
|
||||
class GEODE_DLL WebRequest final {
|
||||
private:
|
||||
AsyncWebRequest& m_request;
|
||||
DataConverter<T> m_converter;
|
||||
class Impl;
|
||||
|
||||
AsyncWebResult(AsyncWebRequest& request, DataConverter<T> converter) :
|
||||
m_request(request), m_converter(converter) {}
|
||||
|
||||
friend class AsyncWebResponse;
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Specify a callback to run after a download is finished. Runs in the
|
||||
* GD thread, so interacting with UI is safe
|
||||
* @param handle Callback to run
|
||||
* @returns The original AsyncWebRequest, where you can specify more
|
||||
* aspects about the request like failure and progress callbacks
|
||||
*/
|
||||
AsyncWebRequest& then(utils::MiniFunction<void(T)> handle);
|
||||
/**
|
||||
* Specify a callback to run after a download is finished. Runs in the
|
||||
* GD thread, so interacting with UI is safe
|
||||
* @param handle Callback to run
|
||||
* @returns The original AsyncWebRequest, where you can specify more
|
||||
* aspects about the request like failure and progress callbacks
|
||||
*/
|
||||
AsyncWebRequest& then(utils::MiniFunction<void(SentAsyncWebRequest&, T)> handle);
|
||||
};
|
||||
WebRequest();
|
||||
~WebRequest();
|
||||
|
||||
class GEODE_DLL [[deprecated("Use the WebRequest class from the web2.hpp header instead")]] AsyncWebResponse {
|
||||
private:
|
||||
AsyncWebRequest& m_request;
|
||||
WebTask send(std::string_view method, std::string_view url);
|
||||
WebTask post(std::string_view url);
|
||||
WebTask get(std::string_view url);
|
||||
WebTask put(std::string_view url);
|
||||
WebTask patch(std::string_view url);
|
||||
|
||||
inline AsyncWebResponse(AsyncWebRequest& request) : m_request(request) {}
|
||||
WebRequest& header(std::string_view name, std::string_view value);
|
||||
WebRequest& param(std::string_view name, std::string_view value);
|
||||
|
||||
friend class AsyncWebRequest;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Download into a stream. Make sure the stream lives for the entire
|
||||
* duration of the request. If you want to download a file, use the
|
||||
* `std::filesystem::path` overload of `into` instead
|
||||
* @param stream Stream to download into. Make sure it lives long
|
||||
* enough, otherwise the web request will crash
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished. The result has a `std::monostate`
|
||||
* template parameter, as it can be assumed you know what you passed
|
||||
* into `into`
|
||||
*/
|
||||
AsyncWebResult<std::monostate> into(std::ostream& stream);
|
||||
/**
|
||||
* Download into a file
|
||||
* @param path File to download into. If it already exists, it will
|
||||
* be overwritten.
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished. The result has a `std::monostate`
|
||||
* template parameter, as it can be assumed you know what you passed
|
||||
* into `into`
|
||||
*/
|
||||
AsyncWebResult<std::monostate> into(std::filesystem::path const& path);
|
||||
/**
|
||||
* Download into memory as a string
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished
|
||||
*/
|
||||
AsyncWebResult<std::string> text();
|
||||
/**
|
||||
* Download into memory as a byte array
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished
|
||||
*/
|
||||
AsyncWebResult<ByteVector> bytes();
|
||||
/**
|
||||
* Download into memory as JSON
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished
|
||||
*/
|
||||
AsyncWebResult<matjson::Value> json();
|
||||
|
||||
/**
|
||||
* Download into memory as a custom type. The data will first be
|
||||
* downloaded into memory as a byte array, and then converted using
|
||||
* the specified converter function
|
||||
* @param converter Function that converts the data from a byte array
|
||||
* to the desired type
|
||||
* @returns AsyncWebResult, where you can specify the `then` action for
|
||||
* after the download is finished
|
||||
*/
|
||||
template <class T>
|
||||
AsyncWebResult<T> as(DataConverter<T> converter) {
|
||||
return AsyncWebResult(m_request, converter);
|
||||
template <std::integral T>
|
||||
WebRequest& param(std::string_view name, T value) {
|
||||
return this->param(name, std::to_string(value));
|
||||
}
|
||||
|
||||
WebRequest& userAgent(std::string_view name);
|
||||
|
||||
WebRequest& timeout(std::chrono::seconds time);
|
||||
|
||||
WebRequest& certVerification(bool enabled);
|
||||
WebRequest& CABundleContent(std::string_view content);
|
||||
WebRequest& proxyOpts(ProxyOpts const& proxyOpts);
|
||||
|
||||
WebRequest& body(ByteVector raw);
|
||||
WebRequest& bodyString(std::string_view str);
|
||||
WebRequest& bodyJSON(matjson::Value const& json);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(T)> handle) {
|
||||
return m_request.setThen([converter = m_converter,
|
||||
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
|
||||
auto conv = converter(arr);
|
||||
if (conv) {
|
||||
handle(conv.unwrap());
|
||||
}
|
||||
else {
|
||||
req.error("Unable to convert value: " + conv.unwrapErr(), -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
AsyncWebRequest& AsyncWebResult<T>::then(utils::MiniFunction<void(SentAsyncWebRequest&, T)> handle) {
|
||||
return m_request.setThen([converter = m_converter,
|
||||
handle](SentAsyncWebRequest& req, ByteVector const& arr) {
|
||||
auto conv = converter(arr);
|
||||
if (conv) {
|
||||
handle(req, conv.value());
|
||||
}
|
||||
else {
|
||||
req.error("Unable to convert value: " + conv.error(), -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/loader/Loader.hpp> // another great circular dependency fix
|
||||
#include <matjson.hpp>
|
||||
#include "Result.hpp"
|
||||
#include "Task.hpp"
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
|
||||
namespace geode::utils::web {
|
||||
// https://curl.se/libcurl/c/CURLOPT_HTTPAUTH.html
|
||||
namespace http_auth {
|
||||
constexpr static long BASIC = 0x0001;
|
||||
constexpr static long DIGEST = 0x0002;
|
||||
constexpr static long DIGEST_IE = 0x0004;
|
||||
constexpr static long BEARER = 0x0008;
|
||||
constexpr static long NEGOTIATE = 0x0010;
|
||||
constexpr static long NTLM = 0x0020;
|
||||
constexpr static long NTLM_WB = 0x0040;
|
||||
constexpr static long ANY = 0x0080;
|
||||
constexpr static long ANYSAFE = 0x0100;
|
||||
constexpr static long ONLY = 0x0200;
|
||||
constexpr static long AWS_SIGV4 = 0x0400;
|
||||
}
|
||||
|
||||
// https://curl.se/libcurl/c/CURLOPT_PROXYTYPE.html
|
||||
enum class ProxyType {
|
||||
HTTP, // HTTP
|
||||
HTTPS, // HTTPS
|
||||
HTTPS2, // HTTPS (attempt to use HTTP/2)
|
||||
SOCKS4, // Socks4
|
||||
SOCKS4A, // Socks4 with hostname resolution
|
||||
SOCKS5, // Socks5
|
||||
SOCKS5H, // Socks5 with hostname resolution
|
||||
};
|
||||
|
||||
struct ProxyOpts {
|
||||
std::string address; // Proxy address/FQDN
|
||||
std::optional<std::uint16_t> port; // Proxy port
|
||||
ProxyType type = ProxyType::HTTP; // Proxy type
|
||||
long auth = http_auth::BASIC; // HTTP proxy auth method
|
||||
std::string username; // Proxy username
|
||||
std::string password; // Proxy password
|
||||
bool tunneling = false; // Enable HTTP tunneling
|
||||
bool certVerification = true; // Enable HTTPS certificate verification
|
||||
};
|
||||
|
||||
class WebRequest;
|
||||
|
||||
class GEODE_DLL WebResponse final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
friend class WebRequest;
|
||||
|
||||
public:
|
||||
// Must be default-constructible for use in Promise
|
||||
WebResponse();
|
||||
|
||||
bool ok() const;
|
||||
int code() const;
|
||||
|
||||
Result<std::string> string() const;
|
||||
Result<matjson::Value> json() const;
|
||||
ByteVector data() const;
|
||||
|
||||
std::vector<std::string> headers() const;
|
||||
std::optional<std::string> header(std::string_view name) const;
|
||||
};
|
||||
|
||||
class GEODE_DLL WebProgress final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
friend class WebRequest;
|
||||
|
||||
public:
|
||||
// Must be default-constructible for use in Promise
|
||||
WebProgress();
|
||||
|
||||
size_t downloaded() const;
|
||||
size_t downloadTotal() const;
|
||||
std::optional<float> downloadProgress() const;
|
||||
|
||||
size_t uploaded() const;
|
||||
size_t uploadTotal() const;
|
||||
std::optional<float> uploadProgress() const;
|
||||
};
|
||||
|
||||
using WebTask = Task<WebResponse, WebProgress>;
|
||||
|
||||
class GEODE_DLL WebRequest final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
public:
|
||||
WebRequest();
|
||||
~WebRequest();
|
||||
|
||||
WebTask send(std::string_view method, std::string_view url);
|
||||
WebTask post(std::string_view url);
|
||||
WebTask get(std::string_view url);
|
||||
WebTask put(std::string_view url);
|
||||
WebTask patch(std::string_view url);
|
||||
|
||||
WebRequest& header(std::string_view name, std::string_view value);
|
||||
WebRequest& param(std::string_view name, std::string_view value);
|
||||
|
||||
template <std::integral T>
|
||||
WebRequest& param(std::string_view name, T value) {
|
||||
return this->param(name, std::to_string(value));
|
||||
}
|
||||
|
||||
WebRequest& userAgent(std::string_view name);
|
||||
|
||||
WebRequest& timeout(std::chrono::seconds time);
|
||||
|
||||
WebRequest& certVerification(bool enabled);
|
||||
WebRequest& CABundleContent(std::string_view content);
|
||||
WebRequest& proxyOpts(ProxyOpts const& proxyOpts);
|
||||
|
||||
WebRequest& body(ByteVector raw);
|
||||
WebRequest& bodyString(std::string_view str);
|
||||
WebRequest& bodyJSON(matjson::Value const& json);
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
#include "updater.hpp"
|
||||
#include <Geode/utils/web2.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <resources.hpp>
|
||||
#include <hash.hpp>
|
||||
#include <utility>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "Geode/utils/VersionInfo.hpp"
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <Geode/utils/web2.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <matjson.hpp>
|
||||
#include <vector>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <Geode/modify/MenuLayer.hpp>
|
||||
#include <Geode/ui/Popup.hpp>
|
||||
#include <Geode/ui/BasedButtonSprite.hpp>
|
||||
#include <Geode/utils/web2.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <server/Server.hpp>
|
||||
#include "../sources/ModListSource.hpp"
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,442 +0,0 @@
|
|||
#include "Geode/utils/general.hpp"
|
||||
#include <matjson.hpp>
|
||||
#define CURL_STATICLIB
|
||||
#include <curl/curl.h>
|
||||
#include <ca_bundle.h>
|
||||
|
||||
#include <Geode/utils/web2.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/terminate.hpp>
|
||||
#include <sstream>
|
||||
|
||||
using namespace geode::prelude;
|
||||
using namespace geode::utils::web;
|
||||
|
||||
static long unwrapProxyType(ProxyType type) {
|
||||
switch (type) {
|
||||
using enum ProxyType;
|
||||
|
||||
case HTTP:
|
||||
return CURLPROXY_HTTP;
|
||||
case HTTPS:
|
||||
return CURLPROXY_HTTPS;
|
||||
case HTTPS2:
|
||||
return CURLPROXY_HTTPS2;
|
||||
case SOCKS4:
|
||||
return CURLPROXY_SOCKS4;
|
||||
case SOCKS4A:
|
||||
return CURLPROXY_SOCKS4A;
|
||||
case SOCKS5:
|
||||
return CURLPROXY_SOCKS5;
|
||||
case SOCKS5H:
|
||||
return CURLPROXY_SOCKS5_HOSTNAME;
|
||||
}
|
||||
|
||||
// Shouldn't happen.
|
||||
unreachable("Unexpected proxy type!");
|
||||
}
|
||||
|
||||
static long unwrapHttpAuth(long auth) {
|
||||
long unwrapped = 0;
|
||||
|
||||
#define SET_AUTH(name) \
|
||||
if (auth & http_auth::name) \
|
||||
unwrapped |= CURLAUTH_##name;
|
||||
|
||||
SET_AUTH(BASIC);
|
||||
SET_AUTH(DIGEST);
|
||||
SET_AUTH(DIGEST_IE);
|
||||
SET_AUTH(BEARER);
|
||||
SET_AUTH(NEGOTIATE);
|
||||
SET_AUTH(NTLM);
|
||||
SET_AUTH(NTLM_WB);
|
||||
SET_AUTH(ANY);
|
||||
SET_AUTH(ANYSAFE);
|
||||
SET_AUTH(ONLY);
|
||||
SET_AUTH(AWS_SIGV4);
|
||||
|
||||
#undef SET_AUTH
|
||||
|
||||
return unwrapped;
|
||||
}
|
||||
|
||||
class WebResponse::Impl {
|
||||
public:
|
||||
int m_code;
|
||||
ByteVector m_data;
|
||||
std::unordered_map<std::string, std::string> m_headers;
|
||||
};
|
||||
|
||||
WebResponse::WebResponse() : m_impl(std::make_shared<Impl>()) {}
|
||||
|
||||
bool WebResponse::ok() const {
|
||||
return 200 <= m_impl->m_code && m_impl->m_code < 300;
|
||||
}
|
||||
int WebResponse::code() const {
|
||||
return m_impl->m_code;
|
||||
}
|
||||
|
||||
Result<std::string> WebResponse::string() const {
|
||||
return Ok(std::string(m_impl->m_data.begin(), m_impl->m_data.end()));
|
||||
}
|
||||
Result<matjson::Value> WebResponse::json() const {
|
||||
GEODE_UNWRAP_INTO(auto value, this->string());
|
||||
std::string error;
|
||||
auto res = matjson::parse(value, error);
|
||||
if (error.size() > 0) {
|
||||
return Err("Error parsing JSON: " + error);
|
||||
}
|
||||
return Ok(res.value());
|
||||
}
|
||||
ByteVector WebResponse::data() const {
|
||||
return m_impl->m_data;
|
||||
}
|
||||
|
||||
std::vector<std::string> WebResponse::headers() const {
|
||||
return map::keys(m_impl->m_headers);
|
||||
}
|
||||
|
||||
std::optional<std::string> WebResponse::header(std::string_view name) const {
|
||||
auto str = std::string(name);
|
||||
if (m_impl->m_headers.contains(str)) {
|
||||
return m_impl->m_headers.at(str);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
class WebProgress::Impl {
|
||||
public:
|
||||
size_t m_downloadCurrent;
|
||||
size_t m_downloadTotal;
|
||||
size_t m_uploadCurrent;
|
||||
size_t m_uploadTotal;
|
||||
};
|
||||
|
||||
WebProgress::WebProgress() : m_impl(std::make_shared<Impl>()) {}
|
||||
|
||||
size_t WebProgress::downloaded() const {
|
||||
return m_impl->m_downloadCurrent;
|
||||
}
|
||||
size_t WebProgress::downloadTotal() const {
|
||||
return m_impl->m_downloadTotal;
|
||||
}
|
||||
std::optional<float> WebProgress::downloadProgress() const {
|
||||
return downloadTotal() > 0 ? std::optional(downloaded() * 100.f / downloadTotal()) : std::nullopt;
|
||||
}
|
||||
|
||||
size_t WebProgress::uploaded() const {
|
||||
return m_impl->m_uploadCurrent;
|
||||
}
|
||||
size_t WebProgress::uploadTotal() const {
|
||||
return m_impl->m_uploadTotal;
|
||||
}
|
||||
std::optional<float> WebProgress::uploadProgress() const {
|
||||
return uploadTotal() > 0 ? std::optional(uploaded() * 100.f / uploadTotal()) : std::nullopt;
|
||||
}
|
||||
|
||||
class WebRequest::Impl {
|
||||
public:
|
||||
std::string m_method;
|
||||
std::string m_url;
|
||||
std::unordered_map<std::string, std::string> m_headers;
|
||||
std::unordered_map<std::string, std::string> m_urlParameters;
|
||||
std::optional<std::string> m_userAgent;
|
||||
std::optional<ByteVector> m_body;
|
||||
std::optional<std::chrono::seconds> m_timeout;
|
||||
bool m_certVerification = true;
|
||||
std::string m_CABundleContent;
|
||||
ProxyOpts m_proxyOpts = {};
|
||||
|
||||
WebResponse makeError(int code, std::string const& msg) {
|
||||
auto res = WebResponse();
|
||||
res.m_impl->m_code = code;
|
||||
res.m_impl->m_data = ByteVector(msg.begin(), msg.end());
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
WebRequest::WebRequest() : m_impl(std::make_shared<Impl>()) {}
|
||||
WebRequest::~WebRequest() {}
|
||||
|
||||
// Encodes a url param
|
||||
std::string urlParamEncode(std::string_view const input) {
|
||||
std::ostringstream ss;
|
||||
ss << std::hex << std::uppercase;
|
||||
for (char c : input) {
|
||||
if (std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||||
ss << c;
|
||||
} else {
|
||||
ss << '%' << static_cast<int>(c);
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
WebTask WebRequest::send(std::string_view method, std::string_view url) {
|
||||
m_impl->m_method = method;
|
||||
m_impl->m_url = url;
|
||||
return WebTask::run([impl = m_impl](auto progress, auto hasBeenCancelled) -> WebTask::Result {
|
||||
// Init Curl
|
||||
auto curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
return impl->makeError(-1, "Curl not initialized");
|
||||
}
|
||||
|
||||
// todo: in the future, we might want to support downloading directly into
|
||||
// files / in-memory streams like the old AsyncWebRequest class
|
||||
|
||||
// Struct that holds values for the curl callbacks
|
||||
struct ResponseData {
|
||||
WebResponse response;
|
||||
Impl* impl;
|
||||
WebTask::PostProgress progress;
|
||||
WebTask::HasBeenCancelled hasBeenCancelled;
|
||||
} responseData = {
|
||||
.response = WebResponse(),
|
||||
.impl = impl.get(),
|
||||
.progress = progress,
|
||||
.hasBeenCancelled = hasBeenCancelled,
|
||||
};
|
||||
|
||||
// Store downloaded response data into a byte vector
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char* data, size_t size, size_t nmemb, void* ptr) {
|
||||
auto& target = static_cast<ResponseData*>(ptr)->response.m_impl->m_data;
|
||||
target.insert(target.end(), data, data + size * nmemb);
|
||||
return size * nmemb;
|
||||
});
|
||||
|
||||
// Set headers
|
||||
curl_slist* headers = nullptr;
|
||||
for (auto& [name, value] : impl->m_headers) {
|
||||
// Sanitize header name
|
||||
auto header = name;
|
||||
header.erase(std::remove_if(header.begin(), header.end(), [](char c) {
|
||||
return c == '\r' || c == '\n';
|
||||
}), header.end());
|
||||
// Append value
|
||||
header += ": " + value;
|
||||
headers = curl_slist_append(headers, header.c_str());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
// Add parameters to the URL and pass it to curl
|
||||
auto url = impl->m_url;
|
||||
bool first = true;
|
||||
for (auto param : impl->m_urlParameters) {
|
||||
url += (first ? "?" : "&") + urlParamEncode(param.first) + "=" + urlParamEncode(param.second);
|
||||
first = false;
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
|
||||
// Set request method
|
||||
if (impl->m_method != "GET") {
|
||||
if (impl->m_method == "POST") {
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
}
|
||||
else {
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, impl->m_method.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Set body if provided
|
||||
if (impl->m_body) {
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, impl->m_body->data());
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, impl->m_body->size());
|
||||
} else if (impl->m_method == "POST") {
|
||||
// curl_easy_perform would freeze on a POST request with no fields, so set it to an empty string
|
||||
// why? god knows
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
|
||||
}
|
||||
|
||||
// Cert verification
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, impl->m_certVerification ? 1 : 0);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
|
||||
if (impl->m_certVerification) {
|
||||
curl_blob caBundleBlob = {};
|
||||
|
||||
if (impl->m_CABundleContent.empty()) {
|
||||
impl->m_CABundleContent = CA_BUNDLE_CONTENT;
|
||||
}
|
||||
|
||||
caBundleBlob.data = reinterpret_cast<void*>(impl->m_CABundleContent.data());
|
||||
caBundleBlob.len = impl->m_CABundleContent.size();
|
||||
caBundleBlob.flags = CURL_BLOB_COPY;
|
||||
curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &caBundleBlob);
|
||||
}
|
||||
|
||||
// Set user agent if provided
|
||||
if (impl->m_userAgent) {
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, impl->m_userAgent->c_str());
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
if (impl->m_timeout) {
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, impl->m_timeout->count());
|
||||
}
|
||||
|
||||
// Set proxy options
|
||||
auto const& proxyOpts = impl->m_proxyOpts;
|
||||
if (!proxyOpts.address.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_PROXY, proxyOpts.address.c_str());
|
||||
|
||||
if (proxyOpts.port.has_value()) {
|
||||
curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxyOpts.port.value());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_PROXYTYPE, unwrapProxyType(proxyOpts.type));
|
||||
|
||||
if (!proxyOpts.username.empty() || !proxyOpts.username.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_PROXYAUTH, unwrapHttpAuth(proxyOpts.auth));
|
||||
curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD,
|
||||
fmt::format("{}:{}", proxyOpts.username, proxyOpts.password).c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, proxyOpts.tunneling ? 1 : 0);
|
||||
curl_easy_setopt(curl, CURLOPT_PROXY_SSL_VERIFYPEER, proxyOpts.certVerification ? 1 : 0);
|
||||
curl_easy_setopt(curl, CURLOPT_PROXY_SSL_VERIFYHOST, 2);
|
||||
}
|
||||
|
||||
// Track progress
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
// Follow redirects
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
// Do not fail if response code is 4XX or 5XX
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 0L);
|
||||
|
||||
// Get headers from the response
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &responseData);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (+[](char* buffer, size_t size, size_t nitems, void* ptr) {
|
||||
auto& headers = static_cast<ResponseData*>(ptr)->response.m_impl->m_headers;
|
||||
std::string line;
|
||||
std::stringstream ss(std::string(buffer, size * nitems));
|
||||
while (std::getline(ss, line)) {
|
||||
auto colon = line.find(':');
|
||||
if (colon == std::string::npos) continue;
|
||||
auto key = line.substr(0, colon);
|
||||
auto value = line.substr(colon + 2);
|
||||
if (value.ends_with('\r')) {
|
||||
value = value.substr(0, value.size() - 1);
|
||||
}
|
||||
headers.insert_or_assign(key, value);
|
||||
}
|
||||
return size * nitems;
|
||||
}));
|
||||
|
||||
// Track & post progress on the Promise
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &responseData);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, +[](void* ptr, double dtotal, double dnow, double utotal, double unow) -> int {
|
||||
auto data = static_cast<ResponseData*>(ptr);
|
||||
|
||||
// Check for cancellation and abort if so
|
||||
if (data->hasBeenCancelled()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Post progress to Promise listener
|
||||
auto progress = WebProgress();
|
||||
progress.m_impl->m_downloadTotal = dtotal;
|
||||
progress.m_impl->m_downloadCurrent = dnow;
|
||||
progress.m_impl->m_uploadTotal = utotal;
|
||||
progress.m_impl->m_uploadCurrent = unow;
|
||||
data->progress(std::move(progress));
|
||||
|
||||
// Continue as normal
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Make the actual web request
|
||||
auto curlResponse = curl_easy_perform(curl);
|
||||
|
||||
// Get the response code; note that this will be invalid if the
|
||||
// curlResponse is not CURLE_OK
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
responseData.response.m_impl->m_code = static_cast<int>(code);
|
||||
|
||||
// Free up curl memory
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
// Check if the request failed on curl's side or because of cancellation
|
||||
if (curlResponse != CURLE_OK) {
|
||||
if (hasBeenCancelled()) {
|
||||
return WebTask::Cancel();
|
||||
}
|
||||
else {
|
||||
return impl->makeError(-1, "Curl failed: " + std::string(curl_easy_strerror(curlResponse)));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the response was an error code
|
||||
if (code >= 400 && code <= 600) {
|
||||
return std::move(responseData.response);
|
||||
}
|
||||
|
||||
// Otherwise resolve with success :-)
|
||||
return std::move(responseData.response);
|
||||
}, fmt::format("{} request to {}", method, url));
|
||||
}
|
||||
WebTask WebRequest::post(std::string_view url) {
|
||||
return this->send("POST", url);
|
||||
}
|
||||
WebTask WebRequest::get(std::string_view url) {
|
||||
return this->send("GET", url);
|
||||
}
|
||||
WebTask WebRequest::put(std::string_view url) {
|
||||
return this->send("PUT", url);
|
||||
}
|
||||
WebTask WebRequest::patch(std::string_view url) {
|
||||
return this->send("PATCH", url);
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::header(std::string_view name, std::string_view value) {
|
||||
m_impl->m_headers.insert_or_assign(std::string(name), std::string(value));
|
||||
return *this;
|
||||
}
|
||||
WebRequest& WebRequest::param(std::string_view name, std::string_view value) {
|
||||
m_impl->m_urlParameters.insert_or_assign(std::string(name), std::string(value));
|
||||
return *this;
|
||||
}
|
||||
WebRequest& WebRequest::userAgent(std::string_view name) {
|
||||
m_impl->m_userAgent = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::timeout(std::chrono::seconds time) {
|
||||
m_impl->m_timeout = time;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::certVerification(bool enabled) {
|
||||
m_impl->m_certVerification = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::CABundleContent(std::string_view content) {
|
||||
m_impl->m_CABundleContent = content;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::proxyOpts(ProxyOpts const& proxyOpts) {
|
||||
m_impl->m_proxyOpts = proxyOpts;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WebRequest& WebRequest::body(ByteVector raw) {
|
||||
m_impl->m_body = raw;
|
||||
return *this;
|
||||
}
|
||||
WebRequest& WebRequest::bodyString(std::string_view str) {
|
||||
m_impl->m_body = ByteVector { str.begin(), str.end() };
|
||||
return *this;
|
||||
}
|
||||
WebRequest& WebRequest::bodyJSON(matjson::Value const& json) {
|
||||
this->header("Content-Type", "application/json");
|
||||
std::string str = json.dump(matjson::NO_INDENTATION);
|
||||
m_impl->m_body = ByteVector { str.begin(), str.end() };
|
||||
return *this;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue