diff --git a/CHANGELOG.md b/CHANGELOG.md index 025e6c79..b94c3573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Implement UI for selection of downloading specific mod versions (5d15eb0) * Change install & uninstall popups to reflect the new changes (d40f467) * Keep the scroll when enabling, installing etc. a mod (b3d444a) + * Update MacOS crashlog to include base and offset (7816c43) ## v1.2.1 * Mods now target macOS 10.13 instead of 10.14 (7cc1cd4) diff --git a/loader/include/Geode/utils/web.hpp b/loader/include/Geode/utils/web.hpp index 4bf2d140..17896041 100644 --- a/loader/include/Geode/utils/web.hpp +++ b/loader/include/Geode/utils/web.hpp @@ -104,6 +104,16 @@ namespace geode::utils::web { template <class T> using DataConverter = Result<T> (*)(ByteVector const&); + // Hack until 2.0.0 to store extra members in AsyncWebRequest + struct AsyncWebRequestData { + std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target; + std::string m_userAgent; + std::string m_customRequest; + bool m_isPostRequest = false; + std::string m_postFields; + bool m_isJsonRequest = false; + }; + /** * An asynchronous, thread-safe web request. Downloads data from the * internet without slowing the main thread. All callbacks are run in the @@ -111,6 +121,9 @@ namespace geode::utils::web { */ class GEODE_DLL AsyncWebRequest { private: + // i want to cry whose idea was to not make this pimpl + // For 2.0.0: make this pimpl + std::optional<std::string> m_joinID; std::string m_url; AsyncThen m_then = nullptr; @@ -118,9 +131,12 @@ namespace geode::utils::web { AsyncProgress m_progress = nullptr; AsyncCancelled m_cancelled = nullptr; bool m_sent = false; - std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target; + mutable std::variant<std::monostate, AsyncWebRequestData> m_extra; std::vector<std::string> m_httpHeaders; + AsyncWebRequestData& extra(); + AsyncWebRequestData const& extra() const; + template <class T> friend class AsyncWebResult; friend class SentAsyncWebRequest; @@ -151,6 +167,29 @@ namespace geode::utils::web { * Can be called more than once. */ AsyncWebRequest& header(std::string const& header); + /** + * In order to specify an user agent to the request, give it here. + */ + AsyncWebRequest& userAgent(std::string const& userAgent); + /** + * Specify that the request is a POST request. + */ + AsyncWebRequest& postRequest(); + /** + * Specify that the request is a custom request like PUT and DELETE. + */ + AsyncWebRequest& customRequest(std::string const& request); + /** + * Specify the post fields to send with the request. Only valid if + * `postRequest` or `customRequest` was called before. + */ + AsyncWebRequest& postFields(std::string const& fields); + /** + * Specify the post fields to send with the request. Only valid if + * `postRequest` or `customRequest` was called before. Additionally + * sets the content type to application/json. + */ + AsyncWebRequest& postFields(json::Value const& fields); /** * URL to fetch from the internet asynchronously * @param url URL of the data to download. Redirects will be diff --git a/loader/src/loader/Index.cpp b/loader/src/loader/Index.cpp index 97a2c410..d223ae0c 100644 --- a/loader/src/loader/Index.cpp +++ b/loader/src/loader/Index.cpp @@ -361,6 +361,7 @@ void Index::Impl::checkForUpdates() { auto oldSHA = file::readString(checksum).unwrapOr(""); web::AsyncWebRequest() .join("index-update") + .userAgent("github_api/1.0") .header(fmt::format("If-None-Match: \"{}\"", oldSHA)) .header("Accept: application/vnd.github.sha") .fetch("https://api.github.com/repos/geode-sdk/mods/commits/main") diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp index ec523193..61afcb13 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -700,6 +700,7 @@ void Loader::Impl::fetchLatestGithubRelease( // TODO: add header to not get rate limited web::AsyncWebRequest() .join("loader-auto-update-check") + .userAgent("github_api/1.0") .fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest") .json() .then([this, then](json::Value const& json) { @@ -768,6 +769,7 @@ void Loader::Impl::downloadLoaderResources(bool useLatestRelease) { if (!useLatestRelease) { web::AsyncWebRequest() .join("loader-tag-exists-check") + .userAgent("github_api/1.0") .fetch(fmt::format( "https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}", this->getVersion().toString() diff --git a/loader/src/utils/web.cpp b/loader/src/utils/web.cpp index 72ca2eea..aab89b60 100644 --- a/loader/src/utils/web.cpp +++ b/loader/src/utils/web.cpp @@ -48,7 +48,6 @@ Result<> web::fetchFile( curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0"); if (prog) { curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, utils::fetch::progress); @@ -81,7 +80,6 @@ Result<ByteVector> web::fetchBytes(std::string const& url) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0"); auto res = curl_easy_perform(curl); if (res != CURLE_OK) { curl_easy_cleanup(curl); @@ -120,7 +118,6 @@ Result<std::string> web::fetch(std::string const& url) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0"); auto res = curl_easy_perform(curl); if (res != CURLE_OK) { curl_easy_cleanup(curl); @@ -162,9 +159,9 @@ private: SentAsyncWebRequest* m_self; mutable std::mutex m_mutex; - std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target = - std::monostate(); + AsyncWebRequestData m_extra; std::vector<std::string> m_httpHeaders; + template <class T> friend class AsyncWebResult; @@ -187,7 +184,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES static std::mutex RUNNING_REQUESTS_MUTEX; SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) : - m_id(id), m_url(req.m_url), m_target(req.m_target), m_httpHeaders(req.m_httpHeaders) { + m_id(id), m_url(req.m_url), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) { #define AWAIT_RESUME() \ {\ @@ -221,16 +218,16 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const std::unique_ptr<std::ofstream> file = nullptr; // into file - if (std::holds_alternative<ghc::filesystem::path>(m_target)) { + if (std::holds_alternative<ghc::filesystem::path>(m_extra.m_target)) { file = std::make_unique<std::ofstream>( - std::get<ghc::filesystem::path>(m_target), std::ios::out | std::ios::binary + std::get<ghc::filesystem::path>(m_extra.m_target), std::ios::out | std::ios::binary ); curl_easy_setopt(curl, CURLOPT_WRITEDATA, file.get()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData); } // into stream - else if (std::holds_alternative<std::ostream*>(m_target)) { - curl_easy_setopt(curl, CURLOPT_WRITEDATA, std::get<std::ostream*>(m_target)); + else if (std::holds_alternative<std::ostream*>(m_extra.m_target)) { + curl_easy_setopt(curl, CURLOPT_WRITEDATA, std::get<std::ostream*>(m_extra.m_target)); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData); } // into memory @@ -242,8 +239,30 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const // No need to verify SSL, we trust our domains :-) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); - // Github User Agent - curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0"); + // User Agent + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_extra.m_userAgent.c_str()); + + // Headers + curl_slist* headers = nullptr; + for (auto& header : m_httpHeaders) { + headers = curl_slist_append(headers, header.c_str()); + } + + // Post request + if (m_extra.m_isPostRequest || m_extra.m_customRequest.size()) { + if (m_extra.m_isPostRequest) { + curl_easy_setopt(curl, CURLOPT_POST, 1L); + } + else { + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, m_extra.m_customRequest.c_str()); + } + if (m_extra.m_isJsonRequest) { + headers = curl_slist_append(headers, "Content-Type: application/json"); + } + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, m_extra.m_postFields.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, m_extra.m_postFields.size()); + } + // Track progress curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); // Follow redirects @@ -251,10 +270,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const // Fail if response code is 4XX or 5XX curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); - curl_slist* headers = nullptr; - for (auto& header : m_httpHeaders) { - headers = curl_slist_append(headers, header.c_str()); - } + // Headers end curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); struct ProgressData { @@ -318,8 +334,8 @@ void SentAsyncWebRequest::Impl::doCancel() { m_cleanedUp = true; // remove file if downloaded to one - if (std::holds_alternative<ghc::filesystem::path>(m_target)) { - auto path = std::get<ghc::filesystem::path>(m_target); + if (std::holds_alternative<ghc::filesystem::path>(m_extra.m_target)) { + auto path = std::get<ghc::filesystem::path>(m_extra.m_target); if (ghc::filesystem::exists(path)) { try { ghc::filesystem::remove(path); @@ -410,11 +426,50 @@ void SentAsyncWebRequest::error(std::string const& error, int code) { return m_impl->error(error, code); } +AsyncWebRequestData& AsyncWebRequest::extra() { + if (!std::holds_alternative<AsyncWebRequestData>(m_extra)) { + m_extra = AsyncWebRequestData(); + } + return std::get<AsyncWebRequestData>(m_extra); +} + +AsyncWebRequestData const& AsyncWebRequest::extra() const { + if (!std::holds_alternative<AsyncWebRequestData>(m_extra)) { + m_extra = AsyncWebRequestData(); + } + return std::get<AsyncWebRequestData>(m_extra); +} + AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) { m_joinID = requestID; return *this; } +AsyncWebRequest& AsyncWebRequest::userAgent(std::string const& userAgent) { + this->extra().m_userAgent = userAgent; + return *this; +} + +AsyncWebRequest& AsyncWebRequest::postRequest() { + this->extra().m_isPostRequest = true; + return *this; +} + +AsyncWebRequest& AsyncWebRequest::customRequest(std::string const& request) { + this->extra().m_customRequest = request; + return *this; +} + +AsyncWebRequest& AsyncWebRequest::postFields(std::string const& fields) { + this->extra().m_postFields = fields; + return *this; +} + +AsyncWebRequest& AsyncWebRequest::postFields(json::Value const& fields) { + this->extra().m_isJsonRequest = true; + return this->postFields(fields.dump()); +} + AsyncWebRequest& AsyncWebRequest::header(std::string const& header) { m_httpHeaders.push_back(header); return *this; @@ -489,14 +544,14 @@ AsyncWebRequest::~AsyncWebRequest() { } AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) { - m_request.m_target = &stream; + m_request.extra().m_target = &stream; return this->as(+[](ByteVector const&) -> Result<std::monostate> { return Ok(std::monostate()); }); } AsyncWebResult<std::monostate> AsyncWebResponse::into(ghc::filesystem::path const& path) { - m_request.m_target = path; + m_request.extra().m_target = path; return this->as(+[](ByteVector const&) -> Result<std::monostate> { return Ok(std::monostate()); });