From 341fc0297206aea241f9742184d71060a9d20666 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 8 Oct 2026 20:24:59 +0800 Subject: [PATCH 1/2] feat(build): add shared clang-format targets and CI Co-Authored-By: Codex --- .clang-format-ignore | 5 +++++ .github/workflows/clang-format.yml | 31 ++++++++++++++++++++++++++++ .gitignore | 3 +++ CMakeLists.txt | 9 ++++++++ README.md | 33 ++++++++++++++++++++++++++++++ README.zh-CN.md | 30 +++++++++++++++++++++++++++ cmake/FormatValidation.cmake | 26 +++++++++++++++++++++++ 7 files changed, 137 insertions(+) create mode 100644 .clang-format-ignore create mode 100644 .github/workflows/clang-format.yml create mode 100644 cmake/FormatValidation.cmake diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000..d2ff733 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,5 @@ +# Repository-relative exclusions for shared FormatValidation. +# Only src/, include/, and tests/ are selected; dependencies and artifacts are excluded. +**/thirdparty/** +**/build/** +**/install/** diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..e7736b0 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,31 @@ +name: clang-format + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: clang-format-${{ github.ref }} + cancel-in-progress: true + +jobs: + clang-format: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install pinned formatter + run: python -m pip install clang-format==23.1.3 + - name: Configure formatting targets + run: cmake -S . -B build/format -DFORMAT_VALIDATION_ONLY=ON + - name: Check all owned C/C++ sources + run: cmake --build build/format --target format-check diff --git a/.gitignore b/.gitignore index 4fcc402..6e75f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ /install/ /thirdparty/cache/ /.vs/ + +# Generated from the shared FormatValidation source during CMake configuration. +/.clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index c0788b6..8585a84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,10 @@ cmake_minimum_required(VERSION 3.21) +option(FORMAT_VALIDATION_ONLY "Configure only clang-format targets without native dependencies" OFF) +if(FORMAT_VALIDATION_ONLY) + project(FormatValidation LANGUAGES NONE) + include(cmake/FormatValidation.cmake) + return() +endif() cmake_policy(SET CMP0091 NEW) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") project(UtilHTTPClient_SteamAPI LANGUAGES CXX) @@ -78,3 +84,6 @@ endif() install(TARGETS UtilHTTPClient_SteamAPI RUNTIME DESTINATION svencoop/metahook/dlls) install(FILES "$" DESTINATION svencoop/metahook/dlls) # steam_api.dll is provided and initialized by the host, never installed here. + +# Shared formatting targets are explicit; ordinary native builds do not run them. +include(cmake/FormatValidation.cmake) diff --git a/README.md b/README.md index 236d4fd..463b88c 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,36 @@ to reuse local copies for offline builds. MIT for the client code; each dependency keeps its own license. See [LICENSE](LICENSE); dependency terms are documented in the upstream repositories. + +## C/C++ formatting + +Formatting uses [MetaHookSv/FormatValidation](https://github.com/MetaHookSv/FormatValidation) +and clang-format **23.1.3**, with the DiligentCore style (4 spaces, preserved include +order). Install the formatter for the Python interpreter used by CMake: + +```sh +python -m pip install clang-format==23.1.3 +cmake -S . -B build/format "-DFORMAT_VALIDATION_ONLY=ON" +cmake --build build/format --target format-check +cmake --build build/format --target format +``` + +The format-only configuration needs CMake 3.21+, Git, Python 3.9+ (CI uses 3.12), +and a build generator; `-G Ninja` works without Visual Studio. It prepares no native +SDK or game dependencies. Formatting targets are explicit and are not part of a +normal DLL build. With a Visual Studio generator, add `--config Debug` or +`--config Release` when building a formatting target. + +The aggregate provides `FORMAT_VALIDATION_SOURCE_PATH=thirdparty/FormatValidation`. +Standalone components accept that CMake variable or its environment counterpart; +if empty, FetchContent downloads the fixed tooling commit. Quote relative paths, +for example `"-DFORMAT_VALIDATION_SOURCE_PATH=../../thirdparty/FormatValidation"`. +Configuration generates the ignored root `.clang-format` for editors; change the +shared style rather than that generated copy. An optional +`FORMAT_VALIDATION_CLANG_FORMAT_EXECUTABLE` selects an explicit formatter, whose +version must still match the pin. + +Checks cover owned C/C++ files in `src/`, `include/`, and `tests/`, including +non-ignored new files. Repository-relative exclusions live in `.clang-format-ignore`. +Third-party sources and build artifacts are excluded. The `clang-format` workflow +checks the full scope on pushes, pull requests, and manual runs. diff --git a/README.zh-CN.md b/README.zh-CN.md index 5febe16..35e5e3c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -55,3 +55,33 @@ ScopeExit;可通过 `-DSTEAMAPIBRIDGE_SOURCE_PATH=... -DSTEAMSDK_SOURCE_PATH=. 客户端代码采用 MIT;各依赖保留自身许可证,见 [LICENSE](LICENSE),依赖条款请查阅 各自上游仓库。 + +## C/C++ 格式化 + +使用 [MetaHookSv/FormatValidation](https://github.com/MetaHookSv/FormatValidation) +共享工具及固定版本 **clang-format 23.1.3**,采用 DiligentCore 风格(4 空格,保留 +include 顺序)。为 CMake 使用的 Python 解释器安装格式工具: + +```sh +python -m pip install clang-format==23.1.3 +cmake -S . -B build/format "-DFORMAT_VALIDATION_ONLY=ON" +cmake --build build/format --target format-check +cmake --build build/format --target format +``` + +格式专用配置需要 CMake 3.21+、Git、Python 3.9+(CI 使用 3.12)及构建生成器; +使用 `-G Ninja` 可无需 Visual Studio。它不准备原生 SDK 或游戏依赖。 +格式目标需显式执行,不加入普通 DLL 构建。使用 Visual Studio 生成器时,执行目标 +需追加 `--config Debug` 或 `--config Release`。 + +聚合仓库注入 `FORMAT_VALIDATION_SOURCE_PATH=thirdparty/FormatValidation`。 +独立组件支持该 CMake 参数及同名环境变量;为空时通过 FetchContent 获取固定工具 +提交。相对路径应加引号,例如 +`"-DFORMAT_VALIDATION_SOURCE_PATH=../../thirdparty/FormatValidation"`。 +配置时在仓库根目录生成被 gitignore 的 `.clang-format` 供编辑器使用;格式规则应 +在共享仓库修改,不修改生成副本。可通过 `FORMAT_VALIDATION_CLANG_FORMAT_EXECUTABLE` +指定工具路径,但版本仍须与固定版本一致。 + +检查覆盖 `src/`、`include/`、`tests/` 中维护的 C/C++ 文件,包括未被 Git 忽略的新文件。 +相对仓库根目录的排除规则位于 `.clang-format-ignore`;第三方源和构建产物不纳入检查。 +`clang-format` workflow 在 push、pull request 和手动运行时执行全量检查。 diff --git a/cmake/FormatValidation.cmake b/cmake/FormatValidation.cmake new file mode 100644 index 0000000..bea6871 --- /dev/null +++ b/cmake/FormatValidation.cmake @@ -0,0 +1,26 @@ +# Shared tool implementation; only this dependency bootstrap belongs to the consumer. +if(NOT DEFINED FORMAT_VALIDATION_SOURCE_PATH) + set(FORMAT_VALIDATION_SOURCE_PATH "$ENV{FORMAT_VALIDATION_SOURCE_PATH}" CACHE PATH + "FormatValidation source tree; empty fetches the pinned tooling") +endif() +if(FORMAT_VALIDATION_SOURCE_PATH) + get_filename_component(_format_validation_source "${FORMAT_VALIDATION_SOURCE_PATH}" + ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}/..") +else() + include(FetchContent) + FetchContent_Declare(utilhttpclient_steamapi_format_validation + GIT_REPOSITORY https://github.com/MetaHookSv/FormatValidation.git + GIT_TAG 13c9fabe058e1f887ad1b03bb6884de911192c6a # v1.0.0; retained immutable tooling commit + GIT_SUBMODULES "" + # Populate the tooling without configuring its own test project. + SOURCE_SUBDIR cmake) + FetchContent_MakeAvailable(utilhttpclient_steamapi_format_validation) + set(_format_validation_source "${utilhttpclient_steamapi_format_validation_SOURCE_DIR}") +endif() +foreach(_format_validation_file .clang-format clang-format-validate.py cmake/FormatValidation.cmake) + if(NOT EXISTS "${_format_validation_source}/${_format_validation_file}") + message(FATAL_ERROR "FORMAT_VALIDATION_SOURCE_PATH is missing ${_format_validation_file}: ${_format_validation_source}") + endif() +endforeach() +include("${_format_validation_source}/cmake/FormatValidation.cmake") +format_validation_add_component(UtilHTTPClient_SteamAPI "${CMAKE_CURRENT_SOURCE_DIR}") From ab3011fa41455aaf5df2bb95e7b52bb8f469aa23 Mon Sep 17 00:00:00 2001 From: hzqst <113660872@qq.com> Date: Thu, 8 Oct 2026 20:24:59 +0800 Subject: [PATCH 2/2] style: apply shared clang-format style Format maintained C/C++ sources with clang-format 23.1.3 while retaining include order. Co-Authored-By: Codex --- include/Interface/IUtilHTTPClient.h | 169 ++- src/UtilHTTPClient_SteamAPI.cpp | 1874 ++++++++++++++------------- src/dllmain.cpp | 17 +- tests/BridgeTests.cpp | 241 ++-- tests/HTTPRuntime.cpp | 154 ++- tests/SmokeTests.cpp | 48 +- 6 files changed, 1333 insertions(+), 1170 deletions(-) diff --git a/include/Interface/IUtilHTTPClient.h b/include/Interface/IUtilHTTPClient.h index ece38b9..5480311 100644 --- a/include/Interface/IUtilHTTPClient.h +++ b/include/Interface/IUtilHTTPClient.h @@ -7,162 +7,161 @@ typedef uint32_t UtilHTTPRequestId_t; #define UTILHTTP_REQUEST_INVALID_ID (UtilHTTPRequestId_t)(0) -#define UTILHTTP_REQUEST_START_ID (UtilHTTPRequestId_t)(1) -#define UTILHTTP_REQUEST_MAX_ID (UtilHTTPRequestId_t)(0xffffffff) +#define UTILHTTP_REQUEST_START_ID (UtilHTTPRequestId_t)(1) +#define UTILHTTP_REQUEST_MAX_ID (UtilHTTPRequestId_t)(0xffffffff) enum class UtilHTTPMethod { - Unknown = 0, - Delete, - Get, - Head, - Post, - Put, + Unknown = 0, + Delete, + Get, + Head, + Post, + Put, }; enum class UtilHTTPRequestState { - Idle = 0, - Requesting, - Responding, - Finished + Idle = 0, + Requesting, + Responding, + Finished }; class IUtilHTTPPayload : public IBaseInterface { public: - virtual const char* GetBytes() const = 0; - virtual size_t GetLength() const = 0; + virtual const char* GetBytes() const = 0; + virtual size_t GetLength() const = 0; }; class IUtilHTTPResponse : public IBaseInterface { public: - virtual int GetStatusCode() const = 0; - virtual IUtilHTTPPayload* GetPayload() const = 0; - virtual bool GetHeaderSize(const char* name, size_t* buflen) = 0; - virtual bool GetHeader(const char* name, char* buf, size_t buflen) = 0; - virtual bool IsResponseCompleted() const = 0; - virtual bool IsResponseError() const = 0; - virtual const char* GetResponseErrorMessage() const = 0; - virtual const char* GetHeaderValue(const char* name) = 0; + virtual int GetStatusCode() const = 0; + virtual IUtilHTTPPayload* GetPayload() const = 0; + virtual bool GetHeaderSize(const char* name, size_t* buflen) = 0; + virtual bool GetHeader(const char* name, char* buf, size_t buflen) = 0; + virtual bool IsResponseCompleted() const = 0; + virtual bool IsResponseError() const = 0; + virtual const char* GetResponseErrorMessage() const = 0; + virtual const char* GetHeaderValue(const char* name) = 0; }; class IUtilHTTPRequest : public IBaseInterface { public: - virtual void Destroy() = 0; + virtual void Destroy() = 0; - //You should call this to actually send the network request. otherwise nothing happens - virtual void Send() = 0; + //You should call this to actually send the network request. otherwise nothing happens + virtual void Send() = 0; - virtual void SetTimeout(int secs) = 0; - virtual void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) = 0; - virtual void SetField(const char* field, const char* value) = 0; - virtual void SetRequireCertVerification(bool b) = 0; + virtual void SetTimeout(int secs) = 0; + virtual void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) = 0; + virtual void SetField(const char* field, const char* value) = 0; + virtual void SetRequireCertVerification(bool b) = 0; - //Async request is auto-destroy-on-finish by default. - virtual void SetAutoDestroyOnFinish(bool b) = 0; + //Async request is auto-destroy-on-finish by default. + virtual void SetAutoDestroyOnFinish(bool b) = 0; - virtual bool IsRequesting() const = 0; - virtual bool IsResponding() const = 0; - virtual bool IsRequestSuccessful() const = 0; - virtual bool IsFinished() const = 0; - virtual bool IsAutoDestroyOnFinish() const = 0; - virtual bool IsAsync() const = 0; - virtual bool IsStream() const = 0; - virtual UtilHTTPRequestState GetRequestState() const = 0; + virtual bool IsRequesting() const = 0; + virtual bool IsResponding() const = 0; + virtual bool IsRequestSuccessful() const = 0; + virtual bool IsFinished() const = 0; + virtual bool IsAutoDestroyOnFinish() const = 0; + virtual bool IsAsync() const = 0; + virtual bool IsStream() const = 0; + virtual UtilHTTPRequestState GetRequestState() const = 0; - virtual void SetRequestId(UtilHTTPRequestId_t id) = 0; - virtual UtilHTTPRequestId_t GetRequestId() const = 0; + virtual void SetRequestId(UtilHTTPRequestId_t id) = 0; + virtual UtilHTTPRequestId_t GetRequestId() const = 0; - //Only available for SyncRequest - virtual void WaitForComplete() = 0; - virtual bool WaitForCompleteTimeout(int timeout_ms) = 0; - virtual IUtilHTTPResponse* GetResponse() = 0; + //Only available for SyncRequest + virtual void WaitForComplete() = 0; + virtual bool WaitForCompleteTimeout(int timeout_ms) = 0; + virtual IUtilHTTPResponse* GetResponse() = 0; - virtual void SetFollowLocation(bool b) = 0; + virtual void SetFollowLocation(bool b) = 0; }; class IUtilHTTPCallbacks : public IBaseInterface { public: - virtual void Destroy() = 0; + virtual void Destroy() = 0; - //Called when response complete - virtual void OnResponseComplete(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance) = 0; + //Called when response complete + virtual void OnResponseComplete(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance) = 0; - //Called when request state change, updated since 2025-04-24 ,API Version 006 -> 007 - virtual void OnUpdateState(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance, UtilHTTPRequestState NewState) = 0; + //Called when request state change, updated since 2025-04-24 ,API Version 006 -> 007 + virtual void OnUpdateState(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance, UtilHTTPRequestState NewState) = 0; - //Called when receive chunked payload data - virtual void OnReceiveData(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance, const void* pData, size_t cbSize) = 0; + //Called when receive chunked payload data + virtual void OnReceiveData(IUtilHTTPRequest* RequestInstance, IUtilHTTPResponse* ResponseInstance, const void* pData, size_t cbSize) = 0; }; class IURLParsedResult : public IBaseInterface { public: - virtual void Destroy() = 0; - virtual const char* GetScheme() const = 0; - virtual const char* GetHost() const = 0; - virtual const char* GetTarget() const = 0; - virtual const char* GetPortString() const = 0; - virtual unsigned short GetPort() const = 0; - virtual bool IsSecure() const = 0; + virtual void Destroy() = 0; + virtual const char* GetScheme() const = 0; + virtual const char* GetHost() const = 0; + virtual const char* GetTarget() const = 0; + virtual const char* GetPortString() const = 0; + virtual unsigned short GetPort() const = 0; + virtual bool IsSecure() const = 0; }; class CUtilHTTPClientCreationContext { public: - bool m_bUseCookieContainer{}; - bool m_bAllowResponseToModifyCookie{}; + bool m_bUseCookieContainer{}; + bool m_bAllowResponseToModifyCookie{}; }; class IUtilHTTPClient : public IBaseInterface { public: - virtual void Destroy() = 0; + virtual void Destroy() = 0; - virtual void Init(const CUtilHTTPClientCreationContext* context) = 0; + virtual void Init(const CUtilHTTPClientCreationContext* context) = 0; - virtual void Shutdown() = 0; + virtual void Shutdown() = 0; - //You should call this function every frame - virtual void RunFrame() = 0; + //You should call this function every frame + virtual void RunFrame() = 0; - //Note that this has been marked as deprecated and should not be used - virtual IURLParsedResult* ParseUrl(const char* url) = 0; + //Note that this has been marked as deprecated and should not be used + virtual IURLParsedResult* ParseUrl(const char* url) = 0; - //You should immediately call Destroy() after using the IUtilHTTPRequest* instance - virtual IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; + //You should immediately call Destroy() after using the IUtilHTTPRequest* instance + virtual IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; - //You should never keep IUtilHTTPRequest* pointer between frames, instead you should use the request id to retrieve the request instance - virtual IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; + //You should never keep IUtilHTTPRequest* pointer between frames, instead you should use the request id to retrieve the request instance + virtual IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; - virtual IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; + virtual IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; - //You will have to either add async request instance to the request pool, or manage the request instance by yourself (e.g. Destroy after finish). - virtual void AddToRequestPool(IUtilHTTPRequest* RequestInstance) = 0; + //You will have to either add async request instance to the request pool, or manage the request instance by yourself (e.g. Destroy after finish). + virtual void AddToRequestPool(IUtilHTTPRequest* RequestInstance) = 0; - virtual IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) = 0; + virtual IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) = 0; - virtual bool DestroyRequestById(UtilHTTPRequestId_t id) = 0; - - virtual bool SetCookie(const char* host, const char* url, const char* cookie) = 0; + virtual bool DestroyRequestById(UtilHTTPRequestId_t id) = 0; + virtual bool SetCookie(const char* host, const char* url, const char* cookie) = 0; }; #define UTIL_HTTPCLIENT_STEAMAPI_INTERFACE_VERSION "UtilHTTPClient_SteamAPI_007" -#define UTIL_HTTPCLIENT_LIBCURL_INTERFACE_VERSION "UtilHTTPClient_libcurl_007" +#define UTIL_HTTPCLIENT_LIBCURL_INTERFACE_VERSION "UtilHTTPClient_libcurl_007" class IUtilHTTPClientFactory : public IBaseInterface { public: - virtual IUtilHTTPClient* CreateUtilHTTPClient() = 0; + virtual IUtilHTTPClient* CreateUtilHTTPClient() = 0; - //You are responsible for destroying the IURLParsedResult* instance - virtual IURLParsedResult* ParseUrl(const char* url) = 0; + //You are responsible for destroying the IURLParsedResult* instance + virtual IURLParsedResult* ParseUrl(const char* url) = 0; }; #define UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION "UtilHTTPClientFactory_SteamAPI_007" -#define UTIL_HTTPCLIENT_FACTORY_LIBCURL_INTERFACE_VERSION "UtilHTTPClientFactory_libcurl_007" +#define UTIL_HTTPCLIENT_FACTORY_LIBCURL_INTERFACE_VERSION "UtilHTTPClientFactory_libcurl_007" diff --git a/src/UtilHTTPClient_SteamAPI.cpp b/src/UtilHTTPClient_SteamAPI.cpp index cf7d090..30d0cb3 100644 --- a/src/UtilHTTPClient_SteamAPI.cpp +++ b/src/UtilHTTPClient_SteamAPI.cpp @@ -23,1010 +23,1033 @@ using BridgeContext = std::shared_ptr; EHTTPMethod UTIL_ConvertUtilHTTPMethodToSteamHTTPMethod(const UtilHTTPMethod method) { - switch (method) - { - case UtilHTTPMethod::Get: - return EHTTPMethod::k_EHTTPMethodGET; - case UtilHTTPMethod::Head: - return EHTTPMethod::k_EHTTPMethodHEAD; - case UtilHTTPMethod::Post: - return EHTTPMethod::k_EHTTPMethodPOST; - case UtilHTTPMethod::Put: - return EHTTPMethod::k_EHTTPMethodPUT; - case UtilHTTPMethod::Delete: - return EHTTPMethod::k_EHTTPMethodDELETE; - } - - return EHTTPMethod::k_EHTTPMethodInvalid; + switch (method) + { + case UtilHTTPMethod::Get: + return EHTTPMethod::k_EHTTPMethodGET; + case UtilHTTPMethod::Head: + return EHTTPMethod::k_EHTTPMethodHEAD; + case UtilHTTPMethod::Post: + return EHTTPMethod::k_EHTTPMethodPOST; + case UtilHTTPMethod::Put: + return EHTTPMethod::k_EHTTPMethodPUT; + case UtilHTTPMethod::Delete: + return EHTTPMethod::k_EHTTPMethodDELETE; + } + + return EHTTPMethod::k_EHTTPMethodInvalid; } class CURLParsedResult : public IURLParsedResult { public: - CURLParsedResult( - const std::string &scheme, - const std::string& host, - unsigned short port_us, - const std::string& target, - bool secure) : m_scheme(scheme), m_host(host), m_port_us(port_us), m_target(target), m_secure(secure) - { - - m_port_str = std::format("{0}", port_us); - } - - void Destroy() override - { - delete this; - } - - const char* GetScheme() const override - { - return m_scheme.c_str(); - } - - const char* GetHost() const override - { - return m_host.c_str(); - } - - const char* GetTarget() const override - { - return m_target.c_str(); - } - - const char* GetPortString() const override - { - return m_port_str.c_str(); - } - - unsigned short GetPort() const override - { - return m_port_us; - } - - bool IsSecure() const override - { - return m_secure; - } + CURLParsedResult( + const std::string& scheme, + const std::string& host, + unsigned short port_us, + const std::string& target, + bool secure) : m_scheme(scheme), + m_host(host), + m_port_us(port_us), + m_target(target), + m_secure(secure) + { + + m_port_str = std::format("{0}", port_us); + } + + void Destroy() override + { + delete this; + } + + const char* GetScheme() const override + { + return m_scheme.c_str(); + } + + const char* GetHost() const override + { + return m_host.c_str(); + } + + const char* GetTarget() const override + { + return m_target.c_str(); + } + + const char* GetPortString() const override + { + return m_port_str.c_str(); + } + + unsigned short GetPort() const override + { + return m_port_us; + } + + bool IsSecure() const override + { + return m_secure; + } private: - std::string m_scheme; - std::string m_host; - std::string m_port_str; - std::string m_target; - unsigned short m_port_us{}; - bool m_secure{}; + std::string m_scheme; + std::string m_host; + std::string m_port_str; + std::string m_target; + unsigned short m_port_us{}; + bool m_secure{}; }; IURLParsedResult* ParseUrlInternal(const char* input) { - if (!input) - return nullptr; - const std::string url(input); - for (unsigned char ch : url) - if (ch <= 0x20 || ch == 0x7f || ch == '\\') - return nullptr; - static const std::regex url_regex( - R"((http|https|ws|wss|mqtt|mqtts)://(\[[0-9a-f:.]+\]|[^/:?#@\[\]]+)(?::(\d+))?([^#]*)(?:#.*)?)", - std::regex_constants::icase - ); - - std::smatch url_match_result; - - if (std::regex_match(url, url_match_result, url_regex)) { - // If we found a match - if (url_match_result.size() >= 4) { - // Extract the matched groups - std::string scheme = url_match_result[1].str(); - for (auto& ch : scheme) - ch = static_cast(std::tolower(static_cast(ch))); - std::string host = url_match_result[2].str(); - std::string port_str = url_match_result[3].str(); - std::string target = (url_match_result.size() >= 5) ? url_match_result[4].str() : ""; - if (!target.empty() && target.front() == '?') - target.insert(target.begin(), '/'); - if (!target.empty() && target.front() != '/') - return nullptr; - - unsigned port_us = 0; - bool secure = scheme == "https" || scheme == "wss" || scheme == "mqtts"; - - if (!port_str.empty()) { - try { - size_t pos; - int port = std::stoi(port_str, &pos); - if (pos != port_str.size() || port <= 0 || port > 65535) { - return nullptr; - } - port_us = static_cast(port); - } - catch (const std::invalid_argument&) { - return nullptr; - } - catch (const std::out_of_range&) { - return nullptr; - } - } - else { - if (scheme == "http") { - port_us = 80; - } - else if (scheme == "https") { - port_us = 443; - secure = true; - } - else if (scheme == "ws") { - port_us = 80; - } - else if (scheme == "wss") { - port_us = 443; - secure = true; - } - else if (scheme == "mqtt") { - port_us = 1883; - } - else if (scheme == "mqtts") { - port_us = 8883; - secure = true; - } - } - - return new CURLParsedResult(scheme, host, port_us, target, secure); - } - } - - return nullptr; + if (!input) + return nullptr; + const std::string url(input); + for (unsigned char ch : url) + if (ch <= 0x20 || ch == 0x7f || ch == '\\') + return nullptr; + static const std::regex url_regex( + R"((http|https|ws|wss|mqtt|mqtts)://(\[[0-9a-f:.]+\]|[^/:?#@\[\]]+)(?::(\d+))?([^#]*)(?:#.*)?)", + std::regex_constants::icase); + + std::smatch url_match_result; + + if (std::regex_match(url, url_match_result, url_regex)) + { + // If we found a match + if (url_match_result.size() >= 4) + { + // Extract the matched groups + std::string scheme = url_match_result[1].str(); + for (auto& ch : scheme) + ch = static_cast(std::tolower(static_cast(ch))); + std::string host = url_match_result[2].str(); + std::string port_str = url_match_result[3].str(); + std::string target = (url_match_result.size() >= 5) ? url_match_result[4].str() : ""; + if (!target.empty() && target.front() == '?') + target.insert(target.begin(), '/'); + if (!target.empty() && target.front() != '/') + return nullptr; + + unsigned port_us = 0; + bool secure = scheme == "https" || scheme == "wss" || scheme == "mqtts"; + + if (!port_str.empty()) + { + try + { + size_t pos; + int port = std::stoi(port_str, &pos); + if (pos != port_str.size() || port <= 0 || port > 65535) + { + return nullptr; + } + port_us = static_cast(port); + } + catch (const std::invalid_argument&) + { + return nullptr; + } + catch (const std::out_of_range&) + { + return nullptr; + } + } + else + { + if (scheme == "http") + { + port_us = 80; + } + else if (scheme == "https") + { + port_us = 443; + secure = true; + } + else if (scheme == "ws") + { + port_us = 80; + } + else if (scheme == "wss") + { + port_us = 443; + secure = true; + } + else if (scheme == "mqtt") + { + port_us = 1883; + } + else if (scheme == "mqtts") + { + port_us = 8883; + secure = true; + } + } + + return new CURLParsedResult(scheme, host, port_us, target, secure); + } + } + + return nullptr; } static std::string ToLowerCase(const char* str) { - std::string result(str); - for (auto& c : result) - { - c = static_cast(std::tolower(static_cast(c))); - } - return result; + std::string result(str); + for (auto& c : result) + { + c = static_cast(std::tolower(static_cast(c))); + } + return result; } static bool IsHTTPURL(const IURLParsedResult* result) { - const std::string_view scheme(result->GetScheme()); - return scheme == "http" || scheme == "https"; + const std::string_view scheme(result->GetScheme()); + return scheme == "http" || scheme == "https"; } class CUtilHTTPPayload : public IUtilHTTPPayload { private: - std::string m_payload; - SteamBridgeContextHandle m_Bridge; + std::string m_payload; + SteamBridgeContextHandle m_Bridge; public: - explicit CUtilHTTPPayload(SteamBridgeContextHandle bridge) : m_Bridge(bridge) {} + explicit CUtilHTTPPayload(SteamBridgeContextHandle bridge) : m_Bridge(bridge) {} - const char* GetBytes() const - { - return m_payload.data(); - } + const char* GetBytes() const + { + return m_payload.data(); + } - size_t GetLength() const - { - return m_payload.size(); - } + size_t GetLength() const + { + return m_payload.size(); + } public: - - bool ReadPayloadFromRequestHandle(HTTPRequestHandle handle) - { - m_payload.clear(); - uint32_t responseSize = 0; - if (SteamBridge_HTTPGetBodySize(m_Bridge, handle, &responseSize) == SB_OK) - { - std::string payload(responseSize, '\0'); - if (SteamBridge_HTTPGetBody(m_Bridge, handle, payload.data(), responseSize) == SB_OK) - { - m_payload.swap(payload); - return true; - } - } - - return false; - } - + bool ReadPayloadFromRequestHandle(HTTPRequestHandle handle) + { + m_payload.clear(); + uint32_t responseSize = 0; + if (SteamBridge_HTTPGetBodySize(m_Bridge, handle, &responseSize) == SB_OK) + { + std::string payload(responseSize, '\0'); + if (SteamBridge_HTTPGetBody(m_Bridge, handle, payload.data(), responseSize) == SB_OK) + { + m_payload.swap(payload); + return true; + } + } + + return false; + } }; class CUtilHTTPResponse : public IUtilHTTPResponse { private: - SteamBridgeContextHandle m_Bridge; - bool m_bResponseCompleted{}; - bool m_bResponseError{}; - bool m_bIsStreamPayload{}; - int m_iResponseStatusCode{}; - std::string m_ResponseErrorMessage; + SteamBridgeContextHandle m_Bridge; + bool m_bResponseCompleted{}; + bool m_bResponseError{}; + bool m_bIsStreamPayload{}; + int m_iResponseStatusCode{}; + std::string m_ResponseErrorMessage; - //cached headers - std::unordered_map> m_headers; + //cached headers + std::unordered_map> m_headers; - CUtilHTTPPayload* m_pResponsePayload{}; + CUtilHTTPPayload* m_pResponsePayload{}; - HTTPRequestHandle m_RequestHandle{ INVALID_HTTPREQUEST_HANDLE }; + HTTPRequestHandle m_RequestHandle{INVALID_HTTPREQUEST_HANDLE}; public: - void SetRequestHandle(HTTPRequestHandle handle) { m_RequestHandle = handle; } + void SetRequestHandle(HTTPRequestHandle handle) { m_RequestHandle = handle; } - void SetStreamPayload(bool bIsStreamPayload) - { - m_bIsStreamPayload = bIsStreamPayload; - } + void SetStreamPayload(bool bIsStreamPayload) + { + m_bIsStreamPayload = bIsStreamPayload; + } - bool OnSteamHTTPDataReceived(HTTPRequestDataReceived_t* pResult, bool bHasError, std::string &buf) - { - m_RequestHandle = pResult->m_hRequest; + bool OnSteamHTTPDataReceived(HTTPRequestDataReceived_t* pResult, bool bHasError, std::string& buf) + { + m_RequestHandle = pResult->m_hRequest; - if (m_bIsStreamPayload && !bHasError) - { - buf.resize(pResult->m_cBytesReceived, '\0'); + if (m_bIsStreamPayload && !bHasError) + { + buf.resize(pResult->m_cBytesReceived, '\0'); - if (SteamBridge_HTTPGetStreamData(m_Bridge, pResult->m_hRequest, pResult->m_cOffset, buf.data(), pResult->m_cBytesReceived) == SB_OK) - { - return true; - } - } + if (SteamBridge_HTTPGetStreamData(m_Bridge, pResult->m_hRequest, pResult->m_cOffset, buf.data(), pResult->m_cBytesReceived) == SB_OK) + { + return true; + } + } - return false; - } + return false; + } - void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) - { - m_RequestHandle = pResult->m_hRequest; + void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) + { + m_RequestHandle = pResult->m_hRequest; - m_bResponseCompleted = true; - m_bResponseError = bHasError || !pResult->m_bRequestSuccessful; - m_iResponseStatusCode = (int)pResult->m_eStatusCode; + m_bResponseCompleted = true; + m_bResponseError = bHasError || !pResult->m_bRequestSuccessful; + m_iResponseStatusCode = (int)pResult->m_eStatusCode; - if (pResult->m_bRequestSuccessful && !bHasError && !m_bIsStreamPayload) - { - m_bResponseError = !m_pResponsePayload->ReadPayloadFromRequestHandle(pResult->m_hRequest); - } - } + if (pResult->m_bRequestSuccessful && !bHasError && !m_bIsStreamPayload) + { + m_bResponseError = !m_pResponsePayload->ReadPayloadFromRequestHandle(pResult->m_hRequest); + } + } public: - explicit CUtilHTTPResponse(SteamBridgeContextHandle bridge) : m_Bridge(bridge), m_pResponsePayload(new CUtilHTTPPayload(bridge)) - { - - } - - ~CUtilHTTPResponse() - { - if (m_pResponsePayload) - { - delete m_pResponsePayload; - m_pResponsePayload = nullptr; - } - } - - bool GetHeaderSize(const char* name, size_t *buflen) override - { - return SteamBridge_HTTPGetHeaderSize(m_Bridge, m_RequestHandle, name, buflen) == SB_OK; - } - - bool GetHeader(const char* name, char* buf, size_t buflen) override - { - return SteamBridge_HTTPGetHeader(m_Bridge, m_RequestHandle, name, buf, static_cast(buflen)) == SB_OK; - } - - const char* GetHeaderValue(const char* name) override - { - auto lowerName = ToLowerCase(name); - - auto it = m_headers.find(lowerName); - if (it != m_headers.end()) - { - return it->second->c_str(); - } - - size_t valueLength{ 0 }; - if (GetHeaderSize(name, &valueLength) && valueLength > 0) - { - std::string value(valueLength, '\0'); - if (GetHeader(name, value.data(), valueLength)) - { - m_headers[lowerName] = std::make_shared(value); - - return m_headers[lowerName]->c_str(); - } - } - - return nullptr; - } - - bool IsResponseCompleted() const override - { - return m_bResponseCompleted; - } - - bool IsResponseError() const override - { - return m_bResponseError; - } - - const char* GetResponseErrorMessage() const override - { - return m_ResponseErrorMessage.c_str(); - } - - int GetStatusCode() const override - { - return m_iResponseStatusCode; - } - - IUtilHTTPPayload* GetPayload() const override - { - return m_pResponsePayload; - } + explicit CUtilHTTPResponse(SteamBridgeContextHandle bridge) : m_Bridge(bridge), m_pResponsePayload(new CUtilHTTPPayload(bridge)) + { + } + + ~CUtilHTTPResponse() + { + if (m_pResponsePayload) + { + delete m_pResponsePayload; + m_pResponsePayload = nullptr; + } + } + + bool GetHeaderSize(const char* name, size_t* buflen) override + { + return SteamBridge_HTTPGetHeaderSize(m_Bridge, m_RequestHandle, name, buflen) == SB_OK; + } + + bool GetHeader(const char* name, char* buf, size_t buflen) override + { + return SteamBridge_HTTPGetHeader(m_Bridge, m_RequestHandle, name, buf, static_cast(buflen)) == SB_OK; + } + + const char* GetHeaderValue(const char* name) override + { + auto lowerName = ToLowerCase(name); + + auto it = m_headers.find(lowerName); + if (it != m_headers.end()) + { + return it->second->c_str(); + } + + size_t valueLength{0}; + if (GetHeaderSize(name, &valueLength) && valueLength > 0) + { + std::string value(valueLength, '\0'); + if (GetHeader(name, value.data(), valueLength)) + { + m_headers[lowerName] = std::make_shared(value); + + return m_headers[lowerName]->c_str(); + } + } + + return nullptr; + } + + bool IsResponseCompleted() const override + { + return m_bResponseCompleted; + } + + bool IsResponseError() const override + { + return m_bResponseError; + } + + const char* GetResponseErrorMessage() const override + { + return m_ResponseErrorMessage.c_str(); + } + + int GetStatusCode() const override + { + return m_iResponseStatusCode; + } + + IUtilHTTPPayload* GetPayload() const override + { + return m_pResponsePayload; + } }; struct RequestPool { - std::mutex mutex; - std::unordered_map requests; + std::mutex mutex; + std::unordered_map requests; }; class CUtilHTTPRequest : public IUtilHTTPRequest { protected: - BridgeContext m_Bridge; - std::weak_ptr m_Pool; - UtilHTTPRequestId_t m_PoolId{}; - bool m_bSetupFailed{}; - unsigned m_DispatchDepth{}; - bool m_DestroyPending{}; - struct DispatchScope - { - CUtilHTTPRequest* request; - explicit DispatchScope(CUtilHTTPRequest* value) : request(value) { ++request->m_DispatchDepth; } - ~DispatchScope() { if (!--request->m_DispatchDepth && request->m_DestroyPending) delete request; } - }; - UtilHTTPRequestId_t m_RequestId{ UTILHTTP_REQUEST_INVALID_ID }; - HTTPRequestHandle m_RequestHandle{}; - bool m_bRequesting{}; - bool m_bResponding{}; - bool m_bRequestSuccessful{}; - bool m_bFinished{}; - bool m_bAutoDestroyOnFinish{}; - IUtilHTTPCallbacks* m_Callbacks{}; - CUtilHTTPResponse* m_pResponse{}; + BridgeContext m_Bridge; + std::weak_ptr m_Pool; + UtilHTTPRequestId_t m_PoolId{}; + bool m_bSetupFailed{}; + unsigned m_DispatchDepth{}; + bool m_DestroyPending{}; + struct DispatchScope + { + CUtilHTTPRequest* request; + explicit DispatchScope(CUtilHTTPRequest* value) : request(value) { ++request->m_DispatchDepth; } + ~DispatchScope() + { + if (!--request->m_DispatchDepth && request->m_DestroyPending) delete request; + } + }; + UtilHTTPRequestId_t m_RequestId{UTILHTTP_REQUEST_INVALID_ID}; + HTTPRequestHandle m_RequestHandle{}; + bool m_bRequesting{}; + bool m_bResponding{}; + bool m_bRequestSuccessful{}; + bool m_bFinished{}; + bool m_bAutoDestroyOnFinish{}; + IUtilHTTPCallbacks* m_Callbacks{}; + CUtilHTTPResponse* m_pResponse{}; public: - - CUtilHTTPRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* callbacks, - HTTPCookieContainerHandle hCookieHandle, BridgeContext bridge, bool cookieFailed) : - m_Bridge(std::move(bridge)), - m_bSetupFailed(cookieFailed), - m_Callbacks(callbacks), - m_pResponse(new CUtilHTTPResponse(m_Bridge.get())) - { - std::string field_host = host; - - if (secure && port != 443) - { - field_host = std::format("{0}:{1}", host, port); - } - else if (!secure && port != 80) - { - field_host = std::format("{0}:{1}", host, port); - } - - std::string url = std::format("{0}://{1}{2}", secure ? "https" :"http", field_host, target.empty() ? "/" : target); - - if (SteamBridge_HTTPCreate(m_Bridge.get(), UTIL_ConvertUtilHTTPMethodToSteamHTTPMethod(method), url.c_str(), &m_RequestHandle) != SB_OK) - m_bSetupFailed = true; - - if(hCookieHandle != INVALID_HTTPCOOKIE_HANDLE) - if (SteamBridge_HTTPSetCookies(m_Bridge.get(), m_RequestHandle, hCookieHandle) != SB_OK) - m_bSetupFailed = true; - - SetField("Host", field_host.c_str()); - } - - virtual ~CUtilHTTPRequest() - { - if (m_RequestHandle != INVALID_HTTPREQUEST_HANDLE) - { - SteamBridge_HTTPRelease(m_Bridge.get(), m_RequestHandle); - m_RequestHandle = INVALID_HTTPREQUEST_HANDLE; - } - - if (m_Callbacks) - { - m_Callbacks->Destroy(); - m_Callbacks = nullptr; - } - delete m_pResponse; - } - - virtual void OnRespondStart() - { - if (!m_bResponding) - { - m_bRequesting = false; - m_bResponding = true; - - if (m_Callbacks) - { - m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Responding); - } - } - } - - virtual void OnRespondFinish() - { - if (!m_bFinished) - { - m_bFinished = true; - m_bRequesting = false; - m_bResponding = false; - - if (m_Callbacks) - { - m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Finished); - } - } - } - - virtual void OnSteamHTTPHeaderReceived(HTTPRequestHeadersReceived_t* pResult, bool bHasError) - { - m_pResponse->SetRequestHandle(pResult->m_hRequest); - OnRespondStart(); - } - - virtual void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) - { - if (m_bFinished) - return; - m_bRequestSuccessful = pResult->m_bRequestSuccessful && !bHasError; - - m_pResponse->OnSteamHTTPCompleted(pResult, bHasError); - m_bRequestSuccessful = m_bRequestSuccessful && !m_pResponse->IsResponseError(); - - OnRespondFinish(); - if (m_Callbacks) - { - m_Callbacks->OnResponseComplete(this, m_pResponse); - } - } - - virtual void OnSteamHTTPDataReceived(HTTPRequestDataReceived_t*, bool) {} - - static void __cdecl OnBridgeEvent(void* opaque, const SteamBridgeHTTPEvent* event) - { - auto self = static_cast(opaque); - DispatchScope dispatch(self); - if (self->m_bFinished || self->m_DestroyPending) - return; - if (event->kind == SB_HTTP_HEADERS) - { - HTTPRequestHeadersReceived_t value{}; value.m_hRequest = event->request; - self->OnSteamHTTPHeaderReceived(&value, event->ioFailure != 0); - } - else if (event->kind == SB_HTTP_DATA) - { - HTTPRequestDataReceived_t value{}; value.m_hRequest = event->request; - value.m_cOffset = event->offset; value.m_cBytesReceived = event->bytes; - self->OnSteamHTTPDataReceived(&value, event->ioFailure != 0); - } - else if (event->kind == SB_HTTP_COMPLETE) - { - HTTPRequestCompleted_t value{}; value.m_hRequest = event->request; - value.m_bRequestSuccessful = event->successful != 0; - value.m_eStatusCode = static_cast(event->statusCode); - self->OnSteamHTTPCompleted(&value, event->ioFailure != 0); - } - } + CUtilHTTPRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + HTTPCookieContainerHandle hCookieHandle, + BridgeContext bridge, + bool cookieFailed) : + m_Bridge(std::move(bridge)), + m_bSetupFailed(cookieFailed), + m_Callbacks(callbacks), + m_pResponse(new CUtilHTTPResponse(m_Bridge.get())) + { + std::string field_host = host; + + if (secure && port != 443) + { + field_host = std::format("{0}:{1}", host, port); + } + else if (!secure && port != 80) + { + field_host = std::format("{0}:{1}", host, port); + } + + std::string url = std::format("{0}://{1}{2}", secure ? "https" : "http", field_host, target.empty() ? "/" : target); + + if (SteamBridge_HTTPCreate(m_Bridge.get(), UTIL_ConvertUtilHTTPMethodToSteamHTTPMethod(method), url.c_str(), &m_RequestHandle) != SB_OK) + m_bSetupFailed = true; + + if (hCookieHandle != INVALID_HTTPCOOKIE_HANDLE) + if (SteamBridge_HTTPSetCookies(m_Bridge.get(), m_RequestHandle, hCookieHandle) != SB_OK) + m_bSetupFailed = true; + + SetField("Host", field_host.c_str()); + } + + virtual ~CUtilHTTPRequest() + { + if (m_RequestHandle != INVALID_HTTPREQUEST_HANDLE) + { + SteamBridge_HTTPRelease(m_Bridge.get(), m_RequestHandle); + m_RequestHandle = INVALID_HTTPREQUEST_HANDLE; + } + + if (m_Callbacks) + { + m_Callbacks->Destroy(); + m_Callbacks = nullptr; + } + delete m_pResponse; + } + + virtual void OnRespondStart() + { + if (!m_bResponding) + { + m_bRequesting = false; + m_bResponding = true; + + if (m_Callbacks) + { + m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Responding); + } + } + } + + virtual void OnRespondFinish() + { + if (!m_bFinished) + { + m_bFinished = true; + m_bRequesting = false; + m_bResponding = false; + + if (m_Callbacks) + { + m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Finished); + } + } + } + + virtual void OnSteamHTTPHeaderReceived(HTTPRequestHeadersReceived_t* pResult, bool bHasError) + { + m_pResponse->SetRequestHandle(pResult->m_hRequest); + OnRespondStart(); + } + + virtual void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) + { + if (m_bFinished) + return; + m_bRequestSuccessful = pResult->m_bRequestSuccessful && !bHasError; + + m_pResponse->OnSteamHTTPCompleted(pResult, bHasError); + m_bRequestSuccessful = m_bRequestSuccessful && !m_pResponse->IsResponseError(); + + OnRespondFinish(); + if (m_Callbacks) + { + m_Callbacks->OnResponseComplete(this, m_pResponse); + } + } + + virtual void OnSteamHTTPDataReceived(HTTPRequestDataReceived_t*, bool) {} + + static void __cdecl OnBridgeEvent(void* opaque, const SteamBridgeHTTPEvent* event) + { + auto self = static_cast(opaque); + DispatchScope dispatch(self); + if (self->m_bFinished || self->m_DestroyPending) + return; + if (event->kind == SB_HTTP_HEADERS) + { + HTTPRequestHeadersReceived_t value{}; + value.m_hRequest = event->request; + self->OnSteamHTTPHeaderReceived(&value, event->ioFailure != 0); + } + else if (event->kind == SB_HTTP_DATA) + { + HTTPRequestDataReceived_t value{}; + value.m_hRequest = event->request; + value.m_cOffset = event->offset; + value.m_cBytesReceived = event->bytes; + self->OnSteamHTTPDataReceived(&value, event->ioFailure != 0); + } + else if (event->kind == SB_HTTP_COMPLETE) + { + HTTPRequestCompleted_t value{}; + value.m_hRequest = event->request; + value.m_bRequestSuccessful = event->successful != 0; + value.m_eStatusCode = static_cast(event->statusCode); + self->OnSteamHTTPCompleted(&value, event->ioFailure != 0); + } + } public: - bool AttachToPool(const std::shared_ptr& pool, UtilHTTPRequestId_t id) - { - if (!m_Pool.expired() || m_DestroyPending) - return false; - m_Pool = pool; - m_PoolId = id; - return true; - } - - void Destroy() override - { - if (m_DestroyPending) - return; - m_DestroyPending = true; - if (auto pool = m_Pool.lock()) - { - std::lock_guard lock(pool->mutex); - auto it = pool->requests.find(m_PoolId); - if (it != pool->requests.end() && it->second == this) - pool->requests.erase(it); - m_Pool.reset(); - } - if (!m_DispatchDepth) - delete this; - } - - void Send() override - { - DispatchScope dispatch(this); - if (m_bRequesting || m_bResponding || m_bFinished) - return; - m_bRequesting = true; - - if (m_Callbacks) - { - m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Requesting); - } - if (m_DestroyPending) - return; - if (m_bSetupFailed || SteamBridge_HTTPSend(m_Bridge.get(), m_RequestHandle, IsStream(), OnBridgeEvent, this) != SB_OK) - { - HTTPRequestCompleted_t failure{}; - failure.m_hRequest = m_RequestHandle; - OnSteamHTTPCompleted(&failure, true); - } - } - - bool IsRequesting() const override - { - return m_bRequesting; - } - - bool IsResponding() const override - { - return m_bResponding; - } - - bool IsRequestSuccessful() const override - { - return m_bRequestSuccessful; - } - - bool IsFinished() const override - { - return m_bFinished; - } - - bool IsAutoDestroyOnFinish() const override - { - return m_bAutoDestroyOnFinish; - } - - UtilHTTPRequestState GetRequestState() const override - { - if (IsFinished()) - return UtilHTTPRequestState::Finished; - - if (IsRequesting()) - return UtilHTTPRequestState::Requesting; - - if (IsResponding()) - return UtilHTTPRequestState::Responding; - - return UtilHTTPRequestState::Idle; - } - - void SetRequestId(UtilHTTPRequestId_t id) override - { - m_RequestId = id; - } - - UtilHTTPRequestId_t GetRequestId() const override - { - return m_RequestId; - } - - void SetAutoDestroyOnFinish(bool b) override - { - m_bAutoDestroyOnFinish = b; - } - - void SetTimeout(int secs) override - { - if (secs <= 0 || SteamBridge_HTTPSetTimeout(m_Bridge.get(), m_RequestHandle, secs) != SB_OK) - m_bSetupFailed = true; - } - - void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) override - { - if (!contentType) - contentType = "application/octet-stream"; - - if (SteamBridge_HTTPSetBody(m_Bridge.get(), m_RequestHandle, contentType, payload, static_cast(payloadSize)) != SB_OK) - m_bSetupFailed = true; - } - - void SetField(const char* field, const char* value) override - { - if (SteamBridge_HTTPSetHeader(m_Bridge.get(), m_RequestHandle, field, value) != SB_OK) - m_bSetupFailed = true; - } - - void SetRequireCertVerification(bool b) override - { - if (SteamBridge_HTTPSetCertificateVerification(m_Bridge.get(), m_RequestHandle, b) != SB_OK) - m_bSetupFailed = true; - } - - void SetFollowLocation(bool b) override - { - //not supported - } + bool AttachToPool(const std::shared_ptr& pool, UtilHTTPRequestId_t id) + { + if (!m_Pool.expired() || m_DestroyPending) + return false; + m_Pool = pool; + m_PoolId = id; + return true; + } + + void Destroy() override + { + if (m_DestroyPending) + return; + m_DestroyPending = true; + if (auto pool = m_Pool.lock()) + { + std::lock_guard lock(pool->mutex); + auto it = pool->requests.find(m_PoolId); + if (it != pool->requests.end() && it->second == this) + pool->requests.erase(it); + m_Pool.reset(); + } + if (!m_DispatchDepth) + delete this; + } + + void Send() override + { + DispatchScope dispatch(this); + if (m_bRequesting || m_bResponding || m_bFinished) + return; + m_bRequesting = true; + + if (m_Callbacks) + { + m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Requesting); + } + if (m_DestroyPending) + return; + if (m_bSetupFailed || SteamBridge_HTTPSend(m_Bridge.get(), m_RequestHandle, IsStream(), OnBridgeEvent, this) != SB_OK) + { + HTTPRequestCompleted_t failure{}; + failure.m_hRequest = m_RequestHandle; + OnSteamHTTPCompleted(&failure, true); + } + } + + bool IsRequesting() const override + { + return m_bRequesting; + } + + bool IsResponding() const override + { + return m_bResponding; + } + + bool IsRequestSuccessful() const override + { + return m_bRequestSuccessful; + } + + bool IsFinished() const override + { + return m_bFinished; + } + + bool IsAutoDestroyOnFinish() const override + { + return m_bAutoDestroyOnFinish; + } + + UtilHTTPRequestState GetRequestState() const override + { + if (IsFinished()) + return UtilHTTPRequestState::Finished; + + if (IsRequesting()) + return UtilHTTPRequestState::Requesting; + + if (IsResponding()) + return UtilHTTPRequestState::Responding; + + return UtilHTTPRequestState::Idle; + } + + void SetRequestId(UtilHTTPRequestId_t id) override + { + m_RequestId = id; + } + + UtilHTTPRequestId_t GetRequestId() const override + { + return m_RequestId; + } + + void SetAutoDestroyOnFinish(bool b) override + { + m_bAutoDestroyOnFinish = b; + } + + void SetTimeout(int secs) override + { + if (secs <= 0 || SteamBridge_HTTPSetTimeout(m_Bridge.get(), m_RequestHandle, secs) != SB_OK) + m_bSetupFailed = true; + } + + void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) override + { + if (!contentType) + contentType = "application/octet-stream"; + + if (SteamBridge_HTTPSetBody(m_Bridge.get(), m_RequestHandle, contentType, payload, static_cast(payloadSize)) != SB_OK) + m_bSetupFailed = true; + } + + void SetField(const char* field, const char* value) override + { + if (SteamBridge_HTTPSetHeader(m_Bridge.get(), m_RequestHandle, field, value) != SB_OK) + m_bSetupFailed = true; + } + + void SetRequireCertVerification(bool b) override + { + if (SteamBridge_HTTPSetCertificateVerification(m_Bridge.get(), m_RequestHandle, b) != SB_OK) + m_bSetupFailed = true; + } + + void SetFollowLocation(bool b) override + { + //not supported + } }; class CUtilHTTPSyncRequest : public CUtilHTTPRequest { private: - std::mutex m_mutex; - std::condition_variable m_cv; - bool m_isComplete{ false }; + std::mutex m_mutex; + std::condition_variable m_cv; + bool m_isComplete{false}; public: - CUtilHTTPSyncRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* callbacks, - HTTPCookieContainerHandle hCookieHandle, BridgeContext bridge, bool cookieFailed) : - CUtilHTTPRequest(method, host, port, secure, target, callbacks, hCookieHandle, std::move(bridge), cookieFailed) - { - } - - bool IsAsync() const override - { - return false; - } - - bool IsStream() const override - { - return false; - } - - void WaitForComplete() override - { - std::unique_lock lock(m_mutex); - m_cv.wait(lock, [this]() { return m_isComplete; }); - } - - bool WaitForCompleteTimeout(int timeout_ms) override - { - std::unique_lock lock(m_mutex); - return m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this]() { return m_isComplete; }); - } - - IUtilHTTPResponse* GetResponse() override - { - return m_pResponse; - } - - void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) override - { - CUtilHTTPRequest::OnSteamHTTPCompleted(pResult, bHasError); - - - { - std::lock_guard lock(m_mutex); - m_isComplete = true; - } - m_cv.notify_one(); - } + CUtilHTTPSyncRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + HTTPCookieContainerHandle hCookieHandle, + BridgeContext bridge, + bool cookieFailed) : + CUtilHTTPRequest(method, host, port, secure, target, callbacks, hCookieHandle, std::move(bridge), cookieFailed) + { + } + + bool IsAsync() const override + { + return false; + } + + bool IsStream() const override + { + return false; + } + + void WaitForComplete() override + { + std::unique_lock lock(m_mutex); + m_cv.wait(lock, [this]() { return m_isComplete; }); + } + + bool WaitForCompleteTimeout(int timeout_ms) override + { + std::unique_lock lock(m_mutex); + return m_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this]() { return m_isComplete; }); + } + + IUtilHTTPResponse* GetResponse() override + { + return m_pResponse; + } + + void OnSteamHTTPCompleted(HTTPRequestCompleted_t* pResult, bool bHasError) override + { + CUtilHTTPRequest::OnSteamHTTPCompleted(pResult, bHasError); + + + { + std::lock_guard lock(m_mutex); + m_isComplete = true; + } + m_cv.notify_one(); + } }; class CUtilHTTPAsyncRequest : public CUtilHTTPRequest { private: - public: - CUtilHTTPAsyncRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* callbacks, - HTTPCookieContainerHandle hCookieHandle, BridgeContext bridge, bool cookieFailed) : - CUtilHTTPRequest(method, host, port, secure, target, callbacks, hCookieHandle, std::move(bridge), cookieFailed) - { - m_bAutoDestroyOnFinish = true; - } - - bool IsAsync() const override - { - return true; - } - - bool IsStream() const override - { - return false; - } - - void WaitForComplete() override - { - - } - - bool WaitForCompleteTimeout(int timeout_ms) override - { - return false; - } - - IUtilHTTPResponse* GetResponse() override - { - return nullptr; - } + CUtilHTTPAsyncRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + HTTPCookieContainerHandle hCookieHandle, + BridgeContext bridge, + bool cookieFailed) : + CUtilHTTPRequest(method, host, port, secure, target, callbacks, hCookieHandle, std::move(bridge), cookieFailed) + { + m_bAutoDestroyOnFinish = true; + } + + bool IsAsync() const override + { + return true; + } + + bool IsStream() const override + { + return false; + } + + void WaitForComplete() override + { + } + + bool WaitForCompleteTimeout(int timeout_ms) override + { + return false; + } + + IUtilHTTPResponse* GetResponse() override + { + return nullptr; + } }; class CUtilHTTPAsyncStreamRequest : public CUtilHTTPAsyncRequest { public: - CUtilHTTPAsyncStreamRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* StreamCallbacks, - HTTPCookieContainerHandle hCookieHandle, BridgeContext bridge, bool cookieFailed) : - CUtilHTTPAsyncRequest(method, host, port, secure, target, StreamCallbacks, hCookieHandle, std::move(bridge), cookieFailed) - { - m_pResponse->SetStreamPayload(true); - } - - bool IsStream() const override - { - return true; - } - - void OnSteamHTTPHeaderReceived(HTTPRequestHeadersReceived_t* pResult, bool bHasError) override - { - CUtilHTTPRequest::OnSteamHTTPHeaderReceived(pResult, bHasError); - } - - void OnSteamHTTPDataReceived(HTTPRequestDataReceived_t* pResult, bool bHasError) override - { - std::string buf; - if (!m_pResponse->OnSteamHTTPDataReceived(pResult, bHasError, buf)) - { - HTTPRequestCompleted_t failure{}; failure.m_hRequest = m_RequestHandle; - OnSteamHTTPCompleted(&failure, true); - return; - } - - if (m_Callbacks) - { - m_Callbacks->OnReceiveData( - this, - m_pResponse, - buf.data(), - buf.size() - ); - } - } - + CUtilHTTPAsyncStreamRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* StreamCallbacks, + HTTPCookieContainerHandle hCookieHandle, + BridgeContext bridge, + bool cookieFailed) : + CUtilHTTPAsyncRequest(method, host, port, secure, target, StreamCallbacks, hCookieHandle, std::move(bridge), cookieFailed) + { + m_pResponse->SetStreamPayload(true); + } + + bool IsStream() const override + { + return true; + } + + void OnSteamHTTPHeaderReceived(HTTPRequestHeadersReceived_t* pResult, bool bHasError) override + { + CUtilHTTPRequest::OnSteamHTTPHeaderReceived(pResult, bHasError); + } + + void OnSteamHTTPDataReceived(HTTPRequestDataReceived_t* pResult, bool bHasError) override + { + std::string buf; + if (!m_pResponse->OnSteamHTTPDataReceived(pResult, bHasError, buf)) + { + HTTPRequestCompleted_t failure{}; + failure.m_hRequest = m_RequestHandle; + OnSteamHTTPCompleted(&failure, true); + return; + } + + if (m_Callbacks) + { + m_Callbacks->OnReceiveData( + this, + m_pResponse, + buf.data(), + buf.size()); + } + } }; class CUtilHTTPClient : public IUtilHTTPClient { private: - BridgeContext m_Bridge{ SteamBridge_CreateContext(), SteamBridge_DestroyContext }; - bool m_bCookieFailed{}; - std::shared_ptr m_Pool{ std::make_shared() }; - UtilHTTPRequestId_t m_RequestUsedId{ UTILHTTP_REQUEST_START_ID }; - HTTPCookieContainerHandle m_CookieHandle{ INVALID_HTTPCOOKIE_HANDLE }; + BridgeContext m_Bridge{SteamBridge_CreateContext(), SteamBridge_DestroyContext}; + bool m_bCookieFailed{}; + std::shared_ptr m_Pool{std::make_shared()}; + UtilHTTPRequestId_t m_RequestUsedId{UTILHTTP_REQUEST_START_ID}; + HTTPCookieContainerHandle m_CookieHandle{INVALID_HTTPCOOKIE_HANDLE}; public: - - ~CUtilHTTPClient() - { - Shutdown(); - if (m_CookieHandle) - { - SteamBridge_HTTPReleaseCookies(m_Bridge.get(), m_CookieHandle); - m_CookieHandle = INVALID_HTTPCOOKIE_HANDLE; - } - } - - void Destroy() override - { - delete this; - } - - void Init(const CUtilHTTPClientCreationContext* context) override - { - if (m_CookieHandle) - SteamBridge_HTTPReleaseCookies(m_Bridge.get(), m_CookieHandle); - m_CookieHandle = INVALID_HTTPCOOKIE_HANDLE; - m_bCookieFailed = false; - if (context->m_bUseCookieContainer) - { - m_bCookieFailed = SteamBridge_HTTPCreateCookies(m_Bridge.get(), context->m_bAllowResponseToModifyCookie, &m_CookieHandle) != SB_OK; - } - } - - void Shutdown() override - { - auto pool = m_Pool; - for (;;) - { - IUtilHTTPRequest* request{}; - { - std::lock_guard lock(pool->mutex); - if (pool->requests.empty()) - break; - auto it = pool->requests.begin(); - request = it->second; - pool->requests.erase(it); - } - request->Destroy(); - } - } - - void RunFrame() override - { - // Remove one request before destruction. A consumer destructor may reenter - // the client and destroy other requests, so do not retain a pointer batch. - auto pool = m_Pool; - for (;;) - { - IUtilHTTPRequest* finished{}; - { - std::lock_guard lock(pool->mutex); - for (auto it = pool->requests.begin(); it != pool->requests.end(); ++it) - { - if (it->second->IsFinished() && it->second->IsAutoDestroyOnFinish()) - { - finished = it->second; - pool->requests.erase(it); - break; - } - } - } - if (!finished) - break; - finished->Destroy(); - } - } - - IURLParsedResult *ParseUrl(const char *url) override - { - return ParseUrlInternal(url); - } - - IUtilHTTPRequest* CreateSyncRequestEx(const char* host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) - { - return new CUtilHTTPSyncRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); - } - - IUtilHTTPRequest* CreateAsyncRequestEx(const char * host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) - { - return new CUtilHTTPAsyncRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); - } - - IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrlInternal(url); - - if (!result) - return nullptr; - - SCOPE_EXIT{ result->Destroy(); }; - - if (!IsHTTPURL(result)) - return nullptr; - return CreateSyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } - - IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrlInternal(url); - - if (!result) - return nullptr; - - SCOPE_EXIT{ result->Destroy(); }; - - if (!IsHTTPURL(result)) - return nullptr; - return CreateAsyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } - - IUtilHTTPRequest* CreateAsyncStreamRequestEx(const char* host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) - { - return new CUtilHTTPAsyncStreamRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); - } - - IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrl(url); - - if (!result) - return nullptr; - - SCOPE_EXIT{ result->Destroy(); }; - - if (!IsHTTPURL(result)) - return nullptr; - return CreateAsyncStreamRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } - - void AddToRequestPool(IUtilHTTPRequest* RequestInstance) override - { - auto request = dynamic_cast(RequestInstance); - if (!request) - return; - std::lock_guard lock(m_Pool->mutex); - - if (m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) - m_RequestUsedId = UTILHTTP_REQUEST_START_ID; - - while (m_Pool->requests.contains(m_RequestUsedId)) - { - if (++m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) - m_RequestUsedId = UTILHTTP_REQUEST_START_ID; - } - auto RequestId = m_RequestUsedId; - if (!request->AttachToPool(m_Pool, RequestId)) - return; - - RequestInstance->SetRequestId(RequestId); - - m_Pool->requests[RequestId] = RequestInstance; - - m_RequestUsedId++; - } - - IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) override - { - std::lock_guard lock(m_Pool->mutex); - - auto itor = m_Pool->requests.find(id); - - if (itor != m_Pool->requests.end()) - { - return itor->second; - } - - return NULL; - } - - bool DestroyRequestById(UtilHTTPRequestId_t id) override - { - IUtilHTTPRequest* request{}; - { - std::lock_guard lock(m_Pool->mutex); - auto it = m_Pool->requests.find(id); - if (it == m_Pool->requests.end()) - return false; - request = it->second; - m_Pool->requests.erase(it); - } - request->Destroy(); - return true; - } - - bool SetCookie(const char* host, const char* url, const char* cookie) override - { - if (m_CookieHandle != INVALID_HTTPCOOKIE_HANDLE) - { - return SteamBridge_HTTPSetCookie(m_Bridge.get(), m_CookieHandle, host, url, cookie) == SB_OK; - } - - return false; - } - + ~CUtilHTTPClient() + { + Shutdown(); + if (m_CookieHandle) + { + SteamBridge_HTTPReleaseCookies(m_Bridge.get(), m_CookieHandle); + m_CookieHandle = INVALID_HTTPCOOKIE_HANDLE; + } + } + + void Destroy() override + { + delete this; + } + + void Init(const CUtilHTTPClientCreationContext* context) override + { + if (m_CookieHandle) + SteamBridge_HTTPReleaseCookies(m_Bridge.get(), m_CookieHandle); + m_CookieHandle = INVALID_HTTPCOOKIE_HANDLE; + m_bCookieFailed = false; + if (context->m_bUseCookieContainer) + { + m_bCookieFailed = SteamBridge_HTTPCreateCookies(m_Bridge.get(), context->m_bAllowResponseToModifyCookie, &m_CookieHandle) != SB_OK; + } + } + + void Shutdown() override + { + auto pool = m_Pool; + for (;;) + { + IUtilHTTPRequest* request{}; + { + std::lock_guard lock(pool->mutex); + if (pool->requests.empty()) + break; + auto it = pool->requests.begin(); + request = it->second; + pool->requests.erase(it); + } + request->Destroy(); + } + } + + void RunFrame() override + { + // Remove one request before destruction. A consumer destructor may reenter + // the client and destroy other requests, so do not retain a pointer batch. + auto pool = m_Pool; + for (;;) + { + IUtilHTTPRequest* finished{}; + { + std::lock_guard lock(pool->mutex); + for (auto it = pool->requests.begin(); it != pool->requests.end(); ++it) + { + if (it->second->IsFinished() && it->second->IsAutoDestroyOnFinish()) + { + finished = it->second; + pool->requests.erase(it); + break; + } + } + } + if (!finished) + break; + finished->Destroy(); + } + } + + IURLParsedResult* ParseUrl(const char* url) override + { + return ParseUrlInternal(url); + } + + IUtilHTTPRequest* CreateSyncRequestEx(const char* host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) + { + return new CUtilHTTPSyncRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); + } + + IUtilHTTPRequest* CreateAsyncRequestEx(const char* host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) + { + return new CUtilHTTPAsyncRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); + } + + IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrlInternal(url); + + if (!result) + return nullptr; + + SCOPE_EXIT { result->Destroy(); }; + + if (!IsHTTPURL(result)) + return nullptr; + return CreateSyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } + + IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrlInternal(url); + + if (!result) + return nullptr; + + SCOPE_EXIT { result->Destroy(); }; + + if (!IsHTTPURL(result)) + return nullptr; + return CreateAsyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } + + IUtilHTTPRequest* CreateAsyncStreamRequestEx(const char* host, unsigned short port_us, const char* target, bool secure, const UtilHTTPMethod method, IUtilHTTPCallbacks* callback) + { + return new CUtilHTTPAsyncStreamRequest(method, host, port_us, secure, target, callback, m_CookieHandle, m_Bridge, m_bCookieFailed); + } + + IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrl(url); + + if (!result) + return nullptr; + + SCOPE_EXIT { result->Destroy(); }; + + if (!IsHTTPURL(result)) + return nullptr; + return CreateAsyncStreamRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } + + void AddToRequestPool(IUtilHTTPRequest* RequestInstance) override + { + auto request = dynamic_cast(RequestInstance); + if (!request) + return; + std::lock_guard lock(m_Pool->mutex); + + if (m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) + m_RequestUsedId = UTILHTTP_REQUEST_START_ID; + + while (m_Pool->requests.contains(m_RequestUsedId)) + { + if (++m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) + m_RequestUsedId = UTILHTTP_REQUEST_START_ID; + } + auto RequestId = m_RequestUsedId; + if (!request->AttachToPool(m_Pool, RequestId)) + return; + + RequestInstance->SetRequestId(RequestId); + + m_Pool->requests[RequestId] = RequestInstance; + + m_RequestUsedId++; + } + + IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) override + { + std::lock_guard lock(m_Pool->mutex); + + auto itor = m_Pool->requests.find(id); + + if (itor != m_Pool->requests.end()) + { + return itor->second; + } + + return NULL; + } + + bool DestroyRequestById(UtilHTTPRequestId_t id) override + { + IUtilHTTPRequest* request{}; + { + std::lock_guard lock(m_Pool->mutex); + auto it = m_Pool->requests.find(id); + if (it == m_Pool->requests.end()) + return false; + request = it->second; + m_Pool->requests.erase(it); + } + request->Destroy(); + return true; + } + + bool SetCookie(const char* host, const char* url, const char* cookie) override + { + if (m_CookieHandle != INVALID_HTTPCOOKIE_HANDLE) + { + return SteamBridge_HTTPSetCookie(m_Bridge.get(), m_CookieHandle, host, url, cookie) == SB_OK; + } + + return false; + } }; EXPOSE_INTERFACE(CUtilHTTPClient, IUtilHTTPClient, UTIL_HTTPCLIENT_STEAMAPI_INTERFACE_VERSION); @@ -1034,16 +1057,15 @@ EXPOSE_INTERFACE(CUtilHTTPClient, IUtilHTTPClient, UTIL_HTTPCLIENT_STEAMAPI_INTE class CUtilHTTPClientFactory : public IUtilHTTPClientFactory { public: - - IUtilHTTPClient* CreateUtilHTTPClient() override - { - return new (std::nothrow) CUtilHTTPClient(); - } - - IURLParsedResult* ParseUrl(const char* url) override - { - return ParseUrlInternal(url); - } + IUtilHTTPClient* CreateUtilHTTPClient() override + { + return new (std::nothrow) CUtilHTTPClient(); + } + + IURLParsedResult* ParseUrl(const char* url) override + { + return ParseUrlInternal(url); + } }; EXPOSE_SINGLE_INTERFACE(CUtilHTTPClientFactory, IUtilHTTPClientFactory, UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION); diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 7dd43be..0d4a9f4 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -1,17 +1,16 @@ #include -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) { switch (ul_reason_for_call) { - case DLL_PROCESS_ATTACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; } return TRUE; } diff --git a/tests/BridgeTests.cpp b/tests/BridgeTests.cpp index ad7d161..bccb000 100644 --- a/tests/BridgeTests.cpp +++ b/tests/BridgeTests.cpp @@ -3,92 +3,185 @@ #include #include -struct Counts { int complete{}, data{}, destroyed{}; bool destroyRequest{}; IUtilHTTPClient* reenter{}; bool readHeaders{}; }; -class Callbacks : public IUtilHTTPCallbacks { +struct Counts +{ + int complete{}, data{}, destroyed{}; + bool destroyRequest{}; + IUtilHTTPClient* reenter{}; + bool readHeaders{}; +}; +class Callbacks : public IUtilHTTPCallbacks +{ Counts& counts; + public: - explicit Callbacks(Counts& value): counts(value) {} - void Destroy() override { ++counts.destroyed; if(counts.reenter) { counts.reenter->GetRequestById(1); counts.reenter->RunFrame(); } delete this; } - void OnResponseComplete(IUtilHTTPRequest* request,IUtilHTTPResponse*) override { ++counts.complete; if(counts.destroyRequest) request->Destroy(); } - void OnUpdateState(IUtilHTTPRequest*,IUtilHTTPResponse* response,UtilHTTPRequestState state) override { - if(counts.readHeaders && state==UtilHTTPRequestState::Responding) { - auto header=response->GetHeaderValue("Content-Type"); assert(header && std::string(header)=="ok"); + explicit Callbacks(Counts& value) : counts(value) {} + void Destroy() override + { + ++counts.destroyed; + if (counts.reenter) + { + counts.reenter->GetRequestById(1); + counts.reenter->RunFrame(); } + delete this; + } + void OnResponseComplete(IUtilHTTPRequest* request, IUtilHTTPResponse*) override + { + ++counts.complete; + if (counts.destroyRequest) request->Destroy(); + } + void OnUpdateState(IUtilHTTPRequest*, IUtilHTTPResponse* response, UtilHTTPRequestState state) override + { + if (counts.readHeaders && state == UtilHTTPRequestState::Responding) + { + auto header = response->GetHeaderValue("Content-Type"); + assert(header && std::string(header) == "ok"); + } + } + void OnReceiveData(IUtilHTTPRequest*, IUtilHTTPResponse*, const void* data, size_t size) override + { + assert(std::string(static_cast(data), size) == "ok"); + ++counts.data; } - void OnReceiveData(IUtilHTTPRequest*,IUtilHTTPResponse*,const void* data,size_t size) override { assert(std::string(static_cast(data),size)=="ok"); ++counts.data; } }; -int wmain(int argc,wchar_t** argv) { - assert(argc==4); - auto bridge=LoadLibraryW(argv[2]); assert(bridge); - auto module=LoadLibraryW(argv[1]); assert(module); - auto factoryFn=(CreateInterfaceFn)GetProcAddress(module,"CreateInterface"); assert(factoryFn); - auto factory=(IUtilHTTPClientFactory*)factoryFn(UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION,nullptr); assert(factory); - auto client=factory->CreateUtilHTTPClient(); assert(client); - CUtilHTTPClientCreationContext options; client->Init(&options); - assert(nullptr==client->CreateSyncRequest("wss://example.invalid/",UtilHTTPMethod::Get,nullptr)); - assert(nullptr==client->CreateAsyncRequest("mqtt://example.invalid/",UtilHTTPMethod::Get,nullptr)); - assert(nullptr==client->CreateAsyncStreamRequest("ws://example.invalid/",UtilHTTPMethod::Get,nullptr)); - assert(nullptr==client->CreateSyncRequest(nullptr,UtilHTTPMethod::Get,nullptr)); +int wmain(int argc, wchar_t** argv) +{ + assert(argc == 4); + auto bridge = LoadLibraryW(argv[2]); + assert(bridge); + auto module = LoadLibraryW(argv[1]); + assert(module); + auto factoryFn = (CreateInterfaceFn)GetProcAddress(module, "CreateInterface"); + assert(factoryFn); + auto factory = (IUtilHTTPClientFactory*)factoryFn(UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION, nullptr); + assert(factory); + auto client = factory->CreateUtilHTTPClient(); + assert(client); + CUtilHTTPClientCreationContext options; + client->Init(&options); + assert(nullptr == client->CreateSyncRequest("wss://example.invalid/", UtilHTTPMethod::Get, nullptr)); + assert(nullptr == client->CreateAsyncRequest("mqtt://example.invalid/", UtilHTTPMethod::Get, nullptr)); + assert(nullptr == client->CreateAsyncStreamRequest("ws://example.invalid/", UtilHTTPMethod::Get, nullptr)); + assert(nullptr == client->CreateSyncRequest(nullptr, UtilHTTPMethod::Get, nullptr)); Counts unavailable; - auto request=client->CreateSyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(unavailable)); assert(request); - request->Send(); assert(request->WaitForCompleteTimeout(100)); assert(request->IsFinished()); assert(!request->IsRequestSuccessful()); - assert(request->GetResponse()->IsResponseError()); assert(1==unavailable.complete); - request->Destroy(); assert(1==unavailable.destroyed); - auto runtime=LoadLibraryW(argv[3]); assert(runtime); - auto mode=(void(__cdecl*)(int))GetProcAddress(runtime,"MockMode"); - auto emit=(void(__cdecl*)(int,unsigned))GetProcAddress(runtime,"MockEmit"); - auto last=(unsigned(__cdecl*)())GetProcAddress(runtime,"MockLastRequest"); assert(mode && emit && last); + auto request = client->CreateSyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(unavailable)); + assert(request); + request->Send(); + assert(request->WaitForCompleteTimeout(100)); + assert(request->IsFinished()); + assert(!request->IsRequestSuccessful()); + assert(request->GetResponse()->IsResponseError()); + assert(1 == unavailable.complete); + request->Destroy(); + assert(1 == unavailable.destroyed); + auto runtime = LoadLibraryW(argv[3]); + assert(runtime); + auto mode = (void(__cdecl*)(int))GetProcAddress(runtime, "MockMode"); + auto emit = (void(__cdecl*)(int, unsigned))GetProcAddress(runtime, "MockEmit"); + auto last = (unsigned(__cdecl*)())GetProcAddress(runtime, "MockLastRequest"); + assert(mode && emit && last); // Immediate send failure, then setup failure; both must wake synchronous waiters. - for(int flags : {15,23}) { - mode(flags); Counts failed; - request=client->CreateSyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(failed)); - request->Send(); assert(request->WaitForCompleteTimeout(100)); assert(request->IsFinished()); assert(!request->IsRequestSuccessful()); - request->Send(); assert(1==failed.complete); request->Destroy(); assert(1==failed.destroyed); + for (int flags : {15, 23}) + { + mode(flags); + Counts failed; + request = client->CreateSyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(failed)); + request->Send(); + assert(request->WaitForCompleteTimeout(100)); + assert(request->IsFinished()); + assert(!request->IsRequestSuccessful()); + request->Send(); + assert(1 == failed.complete); + request->Destroy(); + assert(1 == failed.destroyed); } - mode(7); Counts success; - request=client->CreateSyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(success)); - request->Send(); emit(3,last()); // No header callback is required for completion. - assert(request->WaitForCompleteTimeout(100)); assert(request->IsFinished()); assert(request->IsRequestSuccessful()); - assert(1==success.complete); assert(2==request->GetResponse()->GetPayload()->GetLength()); request->Destroy(); - for(int flags : {39,71}) { - mode(flags); Counts failed; - request=client->CreateSyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(failed)); - request->Send(); emit(3,last()); - assert(request->WaitForCompleteTimeout(100)); assert(request->IsFinished()); + mode(7); + Counts success; + request = client->CreateSyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(success)); + request->Send(); + emit(3, last()); // No header callback is required for completion. + assert(request->WaitForCompleteTimeout(100)); + assert(request->IsFinished()); + assert(request->IsRequestSuccessful()); + assert(1 == success.complete); + assert(2 == request->GetResponse()->GetPayload()->GetLength()); + request->Destroy(); + for (int flags : {39, 71}) + { + mode(flags); + Counts failed; + request = client->CreateSyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(failed)); + request->Send(); + emit(3, last()); + assert(request->WaitForCompleteTimeout(100)); + assert(request->IsFinished()); assert(!request->IsRequestSuccessful() && request->GetResponse()->IsResponseError()); - assert(0==request->GetResponse()->GetPayload()->GetLength()); assert(1==failed.complete); request->Destroy(); + assert(0 == request->GetResponse()->GetPayload()->GetLength()); + assert(1 == failed.complete); + request->Destroy(); } mode(7); - auto lastURL=(const char*(__cdecl*)())GetProcAddress(runtime,"MockLastURL"); assert(lastURL); - request=client->CreateSyncRequest("HTTPS://example.invalid:8443?x=1#private",UtilHTTPMethod::Get,nullptr); - assert(std::string(lastURL())=="https://example.invalid:8443/?x=1"); request->Destroy(); - Counts stream; stream.readHeaders=true; - request=client->CreateAsyncStreamRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(stream)); - request->Send(); emit(1,last()); emit(2,last()); emit(2,last()); emit(3,last()); - assert(request->IsFinished()); assert(2==stream.data && 1==stream.complete); request->Destroy(); - Counts reentrant; reentrant.destroyRequest=true; - request=client->CreateAsyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(reentrant)); - request->Send(); emit(3,last()); assert(1==reentrant.complete && 1==reentrant.destroyed); - Counts pooled; pooled.destroyRequest=true; - request=client->CreateAsyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(pooled)); - client->AddToRequestPool(request); auto id=request->GetRequestId(); - client->AddToRequestPool(request); assert(id==request->GetRequestId()); - request->Send(); emit(3,last()); assert(1==pooled.destroyed); - assert(nullptr==client->GetRequestById(id)); client->RunFrame(); - for(int cleanupPath : {0,1,2}) { - Counts cleanup; cleanup.reenter=client; - request=client->CreateAsyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(cleanup)); - client->AddToRequestPool(request); id=request->GetRequestId(); - if(cleanupPath==1) { - request->Send(); emit(3,last()); - auto second=client->CreateAsyncRequest("https://example.invalid/",UtilHTTPMethod::Get,new Callbacks(cleanup)); - client->AddToRequestPool(second); second->Send(); emit(3,last()); + auto lastURL = (const char*(__cdecl*)())GetProcAddress(runtime, "MockLastURL"); + assert(lastURL); + request = client->CreateSyncRequest("HTTPS://example.invalid:8443?x=1#private", UtilHTTPMethod::Get, nullptr); + assert(std::string(lastURL()) == "https://example.invalid:8443/?x=1"); + request->Destroy(); + Counts stream; + stream.readHeaders = true; + request = client->CreateAsyncStreamRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(stream)); + request->Send(); + emit(1, last()); + emit(2, last()); + emit(2, last()); + emit(3, last()); + assert(request->IsFinished()); + assert(2 == stream.data && 1 == stream.complete); + request->Destroy(); + Counts reentrant; + reentrant.destroyRequest = true; + request = client->CreateAsyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(reentrant)); + request->Send(); + emit(3, last()); + assert(1 == reentrant.complete && 1 == reentrant.destroyed); + Counts pooled; + pooled.destroyRequest = true; + request = client->CreateAsyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(pooled)); + client->AddToRequestPool(request); + auto id = request->GetRequestId(); + client->AddToRequestPool(request); + assert(id == request->GetRequestId()); + request->Send(); + emit(3, last()); + assert(1 == pooled.destroyed); + assert(nullptr == client->GetRequestById(id)); + client->RunFrame(); + for (int cleanupPath : {0, 1, 2}) + { + Counts cleanup; + cleanup.reenter = client; + request = client->CreateAsyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(cleanup)); + client->AddToRequestPool(request); + id = request->GetRequestId(); + if (cleanupPath == 1) + { + request->Send(); + emit(3, last()); + auto second = client->CreateAsyncRequest("https://example.invalid/", UtilHTTPMethod::Get, new Callbacks(cleanup)); + client->AddToRequestPool(second); + second->Send(); + emit(3, last()); client->RunFrame(); } - else if(cleanupPath==2) client->Shutdown(); - else assert(client->DestroyRequestById(id)); - assert((cleanupPath==1 ? 2 : 1)==cleanup.destroyed && nullptr==client->GetRequestById(id)); + else if (cleanupPath == 2) + client->Shutdown(); + else + assert(client->DestroyRequestById(id)); + assert((cleanupPath == 1 ? 2 : 1) == cleanup.destroyed && nullptr == client->GetRequestById(id)); } - client->Shutdown(); client->Destroy(); - FreeLibrary(module); FreeLibrary(bridge); FreeLibrary(runtime); + client->Shutdown(); + client->Destroy(); + FreeLibrary(module); + FreeLibrary(bridge); + FreeLibrary(runtime); } diff --git a/tests/HTTPRuntime.cpp b/tests/HTTPRuntime.cpp index 81ed08c..9908c47 100644 --- a/tests/HTTPRuntime.cpp +++ b/tests/HTTPRuntime.cpp @@ -5,68 +5,116 @@ #include #include -namespace { -enum { Ready = 1, HTTPAvailable = 2, SendFailure = 8, HeaderFailure = 16, - BodySizeFailure = 32, BodyDataFailure = 64 }; -int mode; -uint32 nextRequest = 10; -std::string lastURL; -std::vector callbacks; +namespace +{ +enum +{ + Ready = 1, + HTTPAvailable = 2, + SendFailure = 8, + HeaderFailure = 16, + BodySizeFailure = 32, + BodyDataFailure = 64 +}; +int mode; +uint32 nextRequest = 10; +std::string lastURL; +std::vector callbacks; std::map results; -class HTTP : public ISteamHTTP { +class HTTP : public ISteamHTTP +{ public: - HTTPRequestHandle CreateHTTPRequest(EHTTPMethod, const char* url) override { lastURL=url; return ++nextRequest; } + HTTPRequestHandle CreateHTTPRequest(EHTTPMethod, const char* url) override + { + lastURL = url; + return ++nextRequest; + } bool SetHTTPRequestContextValue(HTTPRequestHandle, uint64) override { return true; } bool SetHTTPRequestNetworkActivityTimeout(HTTPRequestHandle, uint32 seconds) override { return seconds != 0; } - bool SetHTTPRequestHeaderValue(HTTPRequestHandle, const char* name, const char* value) override { - return name && value && _stricmp(name,"User-Agent") && !(mode & HeaderFailure); + bool SetHTTPRequestHeaderValue(HTTPRequestHandle, const char* name, const char* value) override + { + return name && value && _stricmp(name, "User-Agent") && !(mode & HeaderFailure); } bool SetHTTPRequestGetOrPostParameter(HTTPRequestHandle, const char*, const char*) override { return true; } - bool SendHTTPRequest(HTTPRequestHandle h, SteamAPICall_t* call) override { *call=h+1000; return !(mode & SendFailure); } - bool SendHTTPRequestAndStreamResponse(HTTPRequestHandle h, SteamAPICall_t* call) override { return SendHTTPRequest(h,call); } + bool SendHTTPRequest(HTTPRequestHandle h, SteamAPICall_t* call) override + { + *call = h + 1000; + return !(mode & SendFailure); + } + bool SendHTTPRequestAndStreamResponse(HTTPRequestHandle h, SteamAPICall_t* call) override { return SendHTTPRequest(h, call); } bool DeferHTTPRequest(HTTPRequestHandle) override { return true; } bool PrioritizeHTTPRequest(HTTPRequestHandle) override { return true; } - bool GetHTTPResponseHeaderSize(HTTPRequestHandle, const char*, uint32* size) override { *size=3; return true; } - bool GetHTTPResponseHeaderValue(HTTPRequestHandle, const char*, uint8* buffer, uint32 size) override { if(size<3) return false; memcpy(buffer,"ok",3); return true; } - bool GetHTTPResponseBodySize(HTTPRequestHandle, uint32* size) override { *size=2; return !(mode & BodySizeFailure); } - bool GetHTTPResponseBodyData(HTTPRequestHandle, uint8* data, uint32 size) override { if(size!=2 || (mode & BodyDataFailure)) return false; memcpy(data,"ok",2); return true; } - bool GetHTTPStreamingResponseBodyData(HTTPRequestHandle h, uint32, uint8* data, uint32 size) override { return GetHTTPResponseBodyData(h,data,size); } - bool ReleaseHTTPRequest(HTTPRequestHandle) override { return true; } - bool GetHTTPDownloadProgressPct(HTTPRequestHandle, float*) override { return true; } - bool SetHTTPRequestRawPostBody(HTTPRequestHandle, const char*, uint8*, uint32) override { return true; } + bool GetHTTPResponseHeaderSize(HTTPRequestHandle, const char*, uint32* size) override + { + *size = 3; + return true; + } + bool GetHTTPResponseHeaderValue(HTTPRequestHandle, const char*, uint8* buffer, uint32 size) override + { + if (size < 3) return false; + memcpy(buffer, "ok", 3); + return true; + } + bool GetHTTPResponseBodySize(HTTPRequestHandle, uint32* size) override + { + *size = 2; + return !(mode & BodySizeFailure); + } + bool GetHTTPResponseBodyData(HTTPRequestHandle, uint8* data, uint32 size) override + { + if (size != 2 || (mode & BodyDataFailure)) return false; + memcpy(data, "ok", 2); + return true; + } + bool GetHTTPStreamingResponseBodyData(HTTPRequestHandle h, uint32, uint8* data, uint32 size) override { return GetHTTPResponseBodyData(h, data, size); } + bool ReleaseHTTPRequest(HTTPRequestHandle) override { return true; } + bool GetHTTPDownloadProgressPct(HTTPRequestHandle, float*) override { return true; } + bool SetHTTPRequestRawPostBody(HTTPRequestHandle, const char*, uint8*, uint32) override { return true; } HTTPCookieContainerHandle CreateCookieContainer(bool) override { return 100; } - bool ReleaseCookieContainer(HTTPCookieContainerHandle) override { return true; } - bool SetCookie(HTTPCookieContainerHandle, const char*, const char*, const char*) override { return true; } - bool SetHTTPRequestCookieContainer(HTTPRequestHandle, HTTPCookieContainerHandle) override { return true; } - bool SetHTTPRequestUserAgentInfo(HTTPRequestHandle, const char*) override { return true; } - bool SetHTTPRequestRequiresVerifiedCertificate(HTTPRequestHandle, bool) override { return true; } - bool SetHTTPRequestAbsoluteTimeoutMS(HTTPRequestHandle, uint32) override { return true; } - bool GetHTTPRequestWasTimedOut(HTTPRequestHandle, bool*) override { return true; } + bool ReleaseCookieContainer(HTTPCookieContainerHandle) override { return true; } + bool SetCookie(HTTPCookieContainerHandle, const char*, const char*, const char*) override { return true; } + bool SetHTTPRequestCookieContainer(HTTPRequestHandle, HTTPCookieContainerHandle) override { return true; } + bool SetHTTPRequestUserAgentInfo(HTTPRequestHandle, const char*) override { return true; } + bool SetHTTPRequestRequiresVerifiedCertificate(HTTPRequestHandle, bool) override { return true; } + bool SetHTTPRequestAbsoluteTimeoutMS(HTTPRequestHandle, uint32) override { return true; } + bool GetHTTPRequestWasTimedOut(HTTPRequestHandle, bool*) override { return true; } } http; -} -extern "C" { -int __cdecl MockGetUser() { return mode & Ready ? 1 : 0; } -int __cdecl MockGetPipe() { return mode & Ready ? 2 : 0; } -void* __cdecl MockFind(int, const char* version) { return (mode & Ready) && (mode & HTTPAvailable) && !strcmp(version,STEAMHTTP_INTERFACE_VERSION) ? &http : nullptr; } -void __cdecl MockRegister(CCallbackBase* cb, int) { callbacks.push_back(cb); } -void __cdecl MockUnregister(CCallbackBase* cb) { std::erase(callbacks,cb); } -void __cdecl MockRegisterResult(CCallbackBase* cb, SteamAPICall_t call) { results[cb]=call; } -void __cdecl MockUnregisterResult(CCallbackBase* cb, SteamAPICall_t) { results.erase(cb); } -__declspec(dllexport) void __cdecl MockMode(int value) { mode=value; } -__declspec(dllexport) uint32 __cdecl MockLastRequest() { return nextRequest; } -__declspec(dllexport) const char* __cdecl MockLastURL() { return lastURL.c_str(); } -__declspec(dllexport) void __cdecl MockEmit(int kind, uint32 request) { - if(kind==3) { - HTTPRequestCompleted_t p{}; p.m_hRequest=request; p.m_bRequestSuccessful=true; p.m_eStatusCode=k_EHTTPStatusCode200OK; p.m_unBodySize=2; - auto snapshot=results; - for(auto [cb,call]:snapshot) if(call==request+1000 && results.contains(cb)) cb->Run(&p,false,call); - return; +} // namespace +extern "C" +{ + int __cdecl MockGetUser() { return mode & Ready ? 1 : 0; } + int __cdecl MockGetPipe() { return mode & Ready ? 2 : 0; } + void* __cdecl MockFind(int, const char* version) { return (mode & Ready) && (mode & HTTPAvailable) && !strcmp(version, STEAMHTTP_INTERFACE_VERSION) ? &http : nullptr; } + void __cdecl MockRegister(CCallbackBase* cb, int) { callbacks.push_back(cb); } + void __cdecl MockUnregister(CCallbackBase* cb) { std::erase(callbacks, cb); } + void __cdecl MockRegisterResult(CCallbackBase* cb, SteamAPICall_t call) { results[cb] = call; } + void __cdecl MockUnregisterResult(CCallbackBase* cb, SteamAPICall_t) { results.erase(cb); } + __declspec(dllexport) void __cdecl MockMode(int value) { mode = value; } + __declspec(dllexport) uint32 __cdecl MockLastRequest() { return nextRequest; } + __declspec(dllexport) const char* __cdecl MockLastURL() { return lastURL.c_str(); } + __declspec(dllexport) void __cdecl MockEmit(int kind, uint32 request) + { + if (kind == 3) + { + HTTPRequestCompleted_t p{}; + p.m_hRequest = request; + p.m_bRequestSuccessful = true; + p.m_eStatusCode = k_EHTTPStatusCode200OK; + p.m_unBodySize = 2; + auto snapshot = results; + for (auto [cb, call] : snapshot) + if (call == request + 1000 && results.contains(cb)) cb->Run(&p, false, call); + return; + } + HTTPRequestHeadersReceived_t header{}; + header.m_hRequest = request; + HTTPRequestDataReceived_t data{}; + data.m_hRequest = request; + data.m_cBytesReceived = 2; + auto snapshot = callbacks; + int id = kind == 1 ? HTTPRequestHeadersReceived_t::k_iCallback : HTTPRequestDataReceived_t::k_iCallback; + for (auto cb : snapshot) + if (std::find(callbacks.begin(), callbacks.end(), cb) != callbacks.end() && cb->GetICallback() == id) + cb->Run(kind == 1 ? (void*)&header : (void*)&data); } - HTTPRequestHeadersReceived_t header{}; header.m_hRequest=request; - HTTPRequestDataReceived_t data{}; data.m_hRequest=request; data.m_cBytesReceived=2; - auto snapshot=callbacks; - int id=kind==1 ? HTTPRequestHeadersReceived_t::k_iCallback : HTTPRequestDataReceived_t::k_iCallback; - for(auto cb:snapshot) if(std::find(callbacks.begin(),callbacks.end(),cb)!=callbacks.end() && cb->GetICallback()==id) - cb->Run(kind==1 ? (void*)&header : (void*)&data); -} } diff --git a/tests/SmokeTests.cpp b/tests/SmokeTests.cpp index 639edc9..e0c2c38 100644 --- a/tests/SmokeTests.cpp +++ b/tests/SmokeTests.cpp @@ -9,7 +9,7 @@ namespace { -template +template void ExpectEqual(const Expected& expected, const Actual& actual, const char* message) { if (!(expected == actual)) @@ -26,7 +26,7 @@ class Module } ~Module() { FreeLibrary(m_handle); } - Module(const Module&) = delete; + Module(const Module&) = delete; Module& operator=(const Module&) = delete; CreateInterfaceFn GetFactory() const @@ -43,16 +43,16 @@ class Module struct DestroyObject { - template + template void operator()(T* object) const { object->Destroy(); } }; -template +template using Owned = std::unique_ptr; IUtilHTTPClientFactory* GetClientFactory(CreateInterfaceFn createInterface) { - int result = IFACE_FAILED; + int result = IFACE_FAILED; auto factory = static_cast( createInterface(UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION, &result)); ExpectEqual(IFACE_OK, result, "Factory interface lookup failed"); @@ -65,7 +65,7 @@ void TestFactory(CreateInterfaceFn createInterface) auto factory = GetClientFactory(createInterface); ExpectEqual(factory, GetClientFactory(createInterface), "Factory must be a singleton"); ExpectEqual(factory, createInterface(UTIL_HTTPCLIENT_FACTORY_STEAMAPI_INTERFACE_VERSION, nullptr), - "Factory lookup without a return code failed"); + "Factory lookup without a return code failed"); int result = IFACE_OK; ExpectEqual(nullptr, createInterface("UnknownInterface_999", &result), "Unknown interface must be rejected"); @@ -81,7 +81,7 @@ void TestFactory(CreateInterfaceFn createInterface) void TestClient(CreateInterfaceFn createInterface) { - auto factory = GetClientFactory(createInterface); + auto factory = GetClientFactory(createInterface); Owned first(factory->CreateUtilHTTPClient()); Owned second(factory->CreateUtilHTTPClient()); ExpectEqual(true, first != nullptr && second != nullptr, "Client creation failed"); @@ -106,12 +106,12 @@ void TestUrl(CreateInterfaceFn createInterface) auto factory = GetClientFactory(createInterface); struct UrlCase { - const char* url; - const char* scheme; - const char* host; + const char* url; + const char* scheme; + const char* host; unsigned short port; - const char* target; - bool secure; + const char* target; + bool secure; }; const UrlCase cases[] = { {"http://example.test", "http", "example.test", 80, "", false}, @@ -127,8 +127,7 @@ void TestUrl(CreateInterfaceFn createInterface) {"ws://example.test/socket", "ws", "example.test", 80, "/socket", false}, {"wss://example.test/socket", "wss", "example.test", 443, "/socket", true}, {"mqtt://example.test/topic", "mqtt", "example.test", 1883, "/topic", false}, - {"mqtts://example.test/topic", "mqtts", "example.test", 8883, "/topic", true} - }; + {"mqtts://example.test/topic", "mqtts", "example.test", 8883, "/topic", true}}; for (const auto& test : cases) { Owned result(factory->ParseUrl(test.url)); @@ -141,14 +140,14 @@ void TestUrl(CreateInterfaceFn createInterface) ExpectEqual(test.secure, result->IsSecure(), "Secure flag mismatch"); } for (const char* url : {"", "example.test", "ftp://example.test/", "http:///path", - "http://example.test:65536/", "http://example.test:999999999999999999999/", "http://example.test:abc/", - "http://example.test:0/", "http://user@example.test/", "http://example.test/a b", "http://example.test/\r\nX-Test:1"}) + "http://example.test:65536/", "http://example.test:999999999999999999999/", "http://example.test:abc/", + "http://example.test:0/", "http://user@example.test/", "http://example.test/a b", "http://example.test/\r\nX-Test:1"}) { Owned result(factory->ParseUrl(url)); ExpectEqual(nullptr, result.get(), "Invalid URL was accepted"); } } -} +} // namespace int wmain(int argc, wchar_t* argv[]) { @@ -156,14 +155,17 @@ int wmain(int argc, wchar_t* argv[]) { if (argc != 4) throw std::runtime_error("Usage: SmokeTests "); - Module steam(argv[2]); - Module client(argv[1]); - auto createInterface = client.GetFactory(); + Module steam(argv[2]); + Module client(argv[1]); + auto createInterface = client.GetFactory(); const std::wstring_view scenario(argv[3]); if (scenario == L"Factory") TestFactory(createInterface); - else if (scenario == L"Client") TestClient(createInterface); - else if (scenario == L"Url") TestUrl(createInterface); - else throw std::runtime_error("Unknown test scenario"); + else if (scenario == L"Client") + TestClient(createInterface); + else if (scenario == L"Url") + TestUrl(createInterface); + else + throw std::runtime_error("Unknown test scenario"); std::cout << "PASS\n"; return 0; }