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 4aea187..ff031ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ /thirdparty/cache/ /.vs/ *.user + +# Generated from the shared FormatValidation source during CMake configuration. +/.clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index a7a726a..ab13d5a 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_libcurl LANGUAGES C CXX RC) @@ -62,3 +68,6 @@ if(BUILD_TESTING) set_tests_properties(${scenario} PROPERTIES TIMEOUT 30) endforeach() endif() + +# 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 2aeef5c..fe9bbb0 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,36 @@ distributable archive. MIT; see [LICENSE](LICENSE). Third-party licenses are under `licenses/` in the repository; the release archive ships the runtime payload only. + +## 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 d52f07f..78d002e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -53,3 +53,33 @@ MetaHook 接口代码和 ScopeExit;可通过 ## 许可证 MIT,见 [LICENSE](LICENSE)。第三方许可证位于仓库的 `licenses/` 目录,发布压缩包仅含运行时负载。 + +## 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..9e7fd5b --- /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_libcurl_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_libcurl_format_validation) + set(_format_validation_source "${utilhttpclient_libcurl_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_libcurl "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/include/Interface/IUtilHTTPClient.h b/include/Interface/IUtilHTTPClient.h index 36ff9e4..d4ac1f0 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 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; + //You should immediately call Destroy() after using the IUtilHTTPRequest* instance + virtual IUtilHTTPRequest* CreateSyncRequest(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 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* GetRequestById(UtilHTTPRequestId_t id) = 0; + virtual IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) = 0; - virtual bool DestroyRequestById(UtilHTTPRequestId_t id) = 0; - - virtual bool SetCookie(const char* host, const char* url, const char* cookie) = 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 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_libcurl.cpp b/src/UtilHTTPClient_libcurl.cpp index c69161a..0730165 100644 --- a/src/UtilHTTPClient_libcurl.cpp +++ b/src/UtilHTTPClient_libcurl.cpp @@ -14,22 +14,22 @@ 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 std::string ToLowerCase(const std::string_view& sv) { - std::string result(sv); - for (auto& c : result) - { - c = static_cast(std::tolower(static_cast(c))); - } - return result; + std::string result(sv); + for (auto& c : result) + { + c = static_cast(std::tolower(static_cast(c))); + } + return result; } #include @@ -41,387 +41,412 @@ static std::string ToLowerCase(const std::string_view& sv) 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 std::string& url) { - std::regex url_regex( - R"((http|https|ws|wss|mqtt|mqtts)://([^/:]+)(?::(\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(); - 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() : ""; - - unsigned port_us = 0; - bool secure = false; - - 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; + std::regex url_regex( + R"((http|https|ws|wss|mqtt|mqtts)://([^/:]+)(?::(\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(); + 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() : ""; + + unsigned port_us = 0; + bool secure = false; + + 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; } class CUtilHTTPPayload : public IUtilHTTPPayload { private: - std::string m_payload; - std::stringstream m_stream; + std::string m_payload; + std::stringstream m_stream; public: + const char* GetBytes() const override + { + return m_payload.data(); + } - const char* GetBytes() const override - { - return m_payload.data(); - } - - size_t GetLength() const override - { - return m_payload.size(); - } + size_t GetLength() const override + { + return m_payload.size(); + } public: - - void Reset() - { - m_payload.clear(); - m_stream.str(std::string{}); - m_stream.clear(); - } - - void Write(const void* data, size_t size) - { - m_stream.write((const char*)data, size); - } - - void Finalize() - { - m_payload = m_stream.str(); - m_stream.clear(); - } + void Reset() + { + m_payload.clear(); + m_stream.str(std::string{}); + m_stream.clear(); + } + + void Write(const void* data, size_t size) + { + m_stream.write((const char*)data, size); + } + + void Finalize() + { + m_payload = m_stream.str(); + m_stream.clear(); + } }; class CUtilHTTPResponse : public IUtilHTTPResponse { private: - bool m_bResponseCompleted{}; - bool m_bResponseError{}; - bool m_bIsStream{}; - bool m_bIsHeaderProcessed{}; - int m_iResponseStatusCode{}; - std::string m_ResponseErrorMessage; - CUtilHTTPPayload* m_pResponsePayload{}; - CUtilHTTPPayload* m_pResponseHeaderPayload{}; + bool m_bResponseCompleted{}; + bool m_bResponseError{}; + bool m_bIsStream{}; + bool m_bIsHeaderProcessed{}; + int m_iResponseStatusCode{}; + std::string m_ResponseErrorMessage; + CUtilHTTPPayload* m_pResponsePayload{}; + CUtilHTTPPayload* m_pResponseHeaderPayload{}; - std::unordered_map> m_headers; + std::unordered_map> m_headers; - CURL* m_CurlEasyHandle{}; + CURL* m_CurlEasyHandle{}; public: - CUtilHTTPResponse(CURL* CurlEasyHandle) : - m_pResponsePayload(new CUtilHTTPPayload()), - m_pResponseHeaderPayload(new CUtilHTTPPayload()), - m_CurlEasyHandle(CurlEasyHandle) - { - - } - - ~CUtilHTTPResponse() - { - if (m_pResponsePayload) - { - delete m_pResponsePayload; - m_pResponsePayload = nullptr; - } - if (m_pResponseHeaderPayload) - { - delete m_pResponseHeaderPayload; - m_pResponseHeaderPayload = nullptr; - } - } - - bool GetHeaderSize(const char* name, size_t* buflen) override - { - auto lowerName = ToLowerCase(name); - auto it = m_headers.find(lowerName); - if (it != m_headers.end()) { - *buflen = it->second->length() + 1; - return true; - } - return false; - } - - bool GetHeader(const char* name, char* buf, size_t buflen) override - { - if (!name || !buf || !buflen) - return false; - - auto lowerName = ToLowerCase(name); - auto it = m_headers.find(lowerName); - if (it != m_headers.end()) { - strncpy(buf, it->second->c_str(), buflen); - buf[buflen - 1] = 0; - return true; - } - return false; - } - - const char* GetHeaderValue(const char* name) override - { - if (!name) - return nullptr; - - auto lowerName = ToLowerCase(name); - auto it = m_headers.find(lowerName); - if (it != m_headers.end()) - { - return it->second->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; - } -public: + CUtilHTTPResponse(CURL* CurlEasyHandle) : + m_pResponsePayload(new CUtilHTTPPayload()), + m_pResponseHeaderPayload(new CUtilHTTPPayload()), + m_CurlEasyHandle(CurlEasyHandle) + { + } + + ~CUtilHTTPResponse() + { + if (m_pResponsePayload) + { + delete m_pResponsePayload; + m_pResponsePayload = nullptr; + } + if (m_pResponseHeaderPayload) + { + delete m_pResponseHeaderPayload; + m_pResponseHeaderPayload = nullptr; + } + } + + bool GetHeaderSize(const char* name, size_t* buflen) override + { + auto lowerName = ToLowerCase(name); + auto it = m_headers.find(lowerName); + if (it != m_headers.end()) + { + *buflen = it->second->length() + 1; + return true; + } + return false; + } + + bool GetHeader(const char* name, char* buf, size_t buflen) override + { + if (!name || !buf || !buflen) + return false; + + auto lowerName = ToLowerCase(name); + auto it = m_headers.find(lowerName); + if (it != m_headers.end()) + { + strncpy(buf, it->second->c_str(), buflen); + buf[buflen - 1] = 0; + return true; + } + return false; + } + + const char* GetHeaderValue(const char* name) override + { + if (!name) + return nullptr; + + auto lowerName = ToLowerCase(name); + auto it = m_headers.find(lowerName); + if (it != m_headers.end()) + { + return it->second->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; + } - void FinalizeHeaders() - { - if (m_bIsHeaderProcessed) - return; - - m_pResponseHeaderPayload->Finalize(); - - const auto& headersSv = std::string_view(m_pResponseHeaderPayload->GetBytes(), m_pResponseHeaderPayload->GetLength()); - - size_t start = 0; - size_t end = 0; - - // Iterate through the string_view to parse headers - while ((end = headersSv.find("\r\n", start)) != std::string_view::npos) { - auto line = headersSv.substr(start, end - start); - if (!line.empty()) { - // Find the position of ':' - size_t delimiter_pos = line.find(':'); - if (delimiter_pos != std::string_view::npos) { - // Extract key and value - auto key = line.substr(0, delimiter_pos); - auto value = line.substr(delimiter_pos + 1); - - // Trim spaces - size_t key_end = key.find_last_not_of(" \t"); - if (key_end != std::string_view::npos) { - key = key.substr(0, key_end + 1); - } - else { - key = std::string_view(); // empty string if only whitespace - } - - size_t value_start = value.find_first_not_of(" \t"); - if (value_start != std::string_view::npos) { - value = value.substr(value_start); - size_t value_end = value.find_last_not_of(" \t"); - if (value_end != std::string_view::npos) { - value = value.substr(0, value_end + 1); - } - } - else { - value = std::string_view(); // empty string if only whitespace - } - - // Only store if both key and value are not empty - if (!key.empty()) { - std::string k = ToLowerCase(key); - auto it = m_headers.find(k); - if (it != m_headers.end()) - { - auto pstr = it->second; - (*pstr) += "\r\n"; - (*pstr) += value; - } - else - { - m_headers[k] = std::make_shared(value); - } - } - } - } - start = end + 2; // Move past "\r\n" - } - - m_bIsHeaderProcessed = true; - } - - void FinalizePayload(CURLcode curlResult = CURLE_OK) - { - m_pResponsePayload->Finalize(); - - auto code = curl_easy_getinfo(m_CurlEasyHandle, CURLINFO_RESPONSE_CODE, &m_iResponseStatusCode); - - // 优先使用 curl 结果码判断网络层错误 - if (curlResult != CURLE_OK) - { - m_bResponseError = true; - m_ResponseErrorMessage = curl_easy_strerror(curlResult); - } - else if (code != CURLE_OK || m_iResponseStatusCode >= 400 || !m_iResponseStatusCode) - { - m_bResponseError = true; - - if (code != CURLE_OK) - m_ResponseErrorMessage = curl_easy_strerror(code); - else if (m_iResponseStatusCode >= 400) - m_ResponseErrorMessage = std::format("HTTP error {}", m_iResponseStatusCode); - else - m_ResponseErrorMessage = "No response received"; - } - - m_bResponseCompleted = true; - } - - void WriteHeader(const void* data, size_t size) - { - // libcurl delivers complete lines, including each response's status line. - // Discard redirect, authentication and informational response headers. - const std::string_view line(static_cast(data), size); - if (line.starts_with("HTTP/")) - { - m_pResponseHeaderPayload->Reset(); - m_headers.clear(); - m_bIsHeaderProcessed = false; - } - - m_pResponseHeaderPayload->Write(data, size); - } - - void WritePayload(const void* data, size_t size) - { - m_pResponsePayload->Write(data, size); - } +public: + void FinalizeHeaders() + { + if (m_bIsHeaderProcessed) + return; + + m_pResponseHeaderPayload->Finalize(); + + const auto& headersSv = std::string_view(m_pResponseHeaderPayload->GetBytes(), m_pResponseHeaderPayload->GetLength()); + + size_t start = 0; + size_t end = 0; + + // Iterate through the string_view to parse headers + while ((end = headersSv.find("\r\n", start)) != std::string_view::npos) + { + auto line = headersSv.substr(start, end - start); + if (!line.empty()) + { + // Find the position of ':' + size_t delimiter_pos = line.find(':'); + if (delimiter_pos != std::string_view::npos) + { + // Extract key and value + auto key = line.substr(0, delimiter_pos); + auto value = line.substr(delimiter_pos + 1); + + // Trim spaces + size_t key_end = key.find_last_not_of(" \t"); + if (key_end != std::string_view::npos) + { + key = key.substr(0, key_end + 1); + } + else + { + key = std::string_view(); // empty string if only whitespace + } + + size_t value_start = value.find_first_not_of(" \t"); + if (value_start != std::string_view::npos) + { + value = value.substr(value_start); + size_t value_end = value.find_last_not_of(" \t"); + if (value_end != std::string_view::npos) + { + value = value.substr(0, value_end + 1); + } + } + else + { + value = std::string_view(); // empty string if only whitespace + } + + // Only store if both key and value are not empty + if (!key.empty()) + { + std::string k = ToLowerCase(key); + auto it = m_headers.find(k); + if (it != m_headers.end()) + { + auto pstr = it->second; + (*pstr) += "\r\n"; + (*pstr) += value; + } + else + { + m_headers[k] = std::make_shared(value); + } + } + } + } + start = end + 2; // Move past "\r\n" + } + + m_bIsHeaderProcessed = true; + } + + void FinalizePayload(CURLcode curlResult = CURLE_OK) + { + m_pResponsePayload->Finalize(); + + auto code = curl_easy_getinfo(m_CurlEasyHandle, CURLINFO_RESPONSE_CODE, &m_iResponseStatusCode); + + // 优先使用 curl 结果码判断网络层错误 + if (curlResult != CURLE_OK) + { + m_bResponseError = true; + m_ResponseErrorMessage = curl_easy_strerror(curlResult); + } + else if (code != CURLE_OK || m_iResponseStatusCode >= 400 || !m_iResponseStatusCode) + { + m_bResponseError = true; + + if (code != CURLE_OK) + m_ResponseErrorMessage = curl_easy_strerror(code); + else if (m_iResponseStatusCode >= 400) + m_ResponseErrorMessage = std::format("HTTP error {}", m_iResponseStatusCode); + else + m_ResponseErrorMessage = "No response received"; + } + + m_bResponseCompleted = true; + } + + void WriteHeader(const void* data, size_t size) + { + // libcurl delivers complete lines, including each response's status line. + // Discard redirect, authentication and informational response headers. + const std::string_view line(static_cast(data), size); + if (line.starts_with("HTTP/")) + { + m_pResponseHeaderPayload->Reset(); + m_headers.clear(); + m_bIsHeaderProcessed = false; + } + + m_pResponseHeaderPayload->Write(data, size); + } + + void WritePayload(const void* data, size_t size) + { + m_pResponsePayload->Write(data, size); + } }; static size_t WritePayloadStreamCallback(void* contents, size_t size, size_t nmemb, void* userp); @@ -433,757 +458,754 @@ static size_t WriteHeaderCallback(void* contents, size_t size, size_t nmemb, voi class CUtilHTTPRequest : public IUtilHTTPRequest { protected: - CURLM* m_CurlMultiHandle{ nullptr }; - CURL* m_CurlEasyHandle{ nullptr }; - CURLSH* m_CurlCookieHandle{ nullptr }; - struct curl_slist* m_CurlHeaders{ nullptr }; - - CUtilHTTPResponse* m_pResponse{ nullptr }; - IUtilHTTPCallbacks* m_Callbacks{ nullptr }; - bool m_bRequesting{ false }; - bool m_bResponding{ false }; - bool m_bFinished{ false }; - bool m_bRequestSuccessful{ false };//TODO: we need to set it to true when request become successful - bool m_bAutoDestroyOnFinish{ false }; - UtilHTTPRequestId_t m_RequestId{ UTILHTTP_REQUEST_INVALID_ID }; + CURLM* m_CurlMultiHandle{nullptr}; + CURL* m_CurlEasyHandle{nullptr}; + CURLSH* m_CurlCookieHandle{nullptr}; + struct curl_slist* m_CurlHeaders{nullptr}; + + CUtilHTTPResponse* m_pResponse{nullptr}; + IUtilHTTPCallbacks* m_Callbacks{nullptr}; + bool m_bRequesting{false}; + bool m_bResponding{false}; + bool m_bFinished{false}; + bool m_bRequestSuccessful{false}; //TODO: we need to set it to true when request become successful + bool m_bAutoDestroyOnFinish{false}; + UtilHTTPRequestId_t m_RequestId{UTILHTTP_REQUEST_INVALID_ID}; public: - - CUtilHTTPRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* callbacks, - CURLM*CurlMultiHandle, - CURLSH* CurlCookieHandle) : - m_Callbacks(callbacks), - m_CurlMultiHandle(CurlMultiHandle), - m_CurlCookieHandle(CurlCookieHandle) - { - m_CurlEasyHandle = curl_easy_init(); - - m_pResponse = new CUtilHTTPResponse(m_CurlEasyHandle); - - std::string url = std::format("{}://{}:{}{}", - secure ? "https" : "http", - host, - port, - target); - - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_PRIVATE, this); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_URL, url.c_str()); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEFUNCTION, WritePayloadCallback); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEDATA, this); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HEADERFUNCTION, WriteHeaderCallback); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HEADERDATA, this); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CONNECTTIMEOUT_MS, 60000); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_TIMEOUT_MS, 60000); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_ACCEPT_ENCODING, ""); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_COOKIEFILE, ""); - if(m_CurlCookieHandle) - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SHARE, m_CurlCookieHandle); - - // Set method - switch (method) { - case UtilHTTPMethod::Get: - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HTTPGET, 1L); - break; - case UtilHTTPMethod::Post: - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_POST, 1L); - break; - case UtilHTTPMethod::Put: - // SetPostBody uses COPYPOSTFIELDS; keep PUT when it enables POST internally. - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CUSTOMREQUEST, "PUT"); - break; - case UtilHTTPMethod::Delete: - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CUSTOMREQUEST, "DELETE"); - break; - case UtilHTTPMethod::Head: - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_NOBODY, 1L); - break; - } - - 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); - } - - SetField("Host", field_host.c_str()); - SetField("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"); - - } - - ~CUtilHTTPRequest() - { - if (m_CurlHeaders) { - curl_slist_free_all(m_CurlHeaders); - m_CurlHeaders = nullptr; - } - if (m_CurlMultiHandle && m_CurlEasyHandle) { - curl_multi_remove_handle(m_CurlMultiHandle, m_CurlEasyHandle); - } - if (m_CurlEasyHandle) { - curl_easy_cleanup(m_CurlEasyHandle); - m_CurlEasyHandle = nullptr; - } - if (m_pResponse) { - delete m_pResponse; - m_pResponse = nullptr; - } - if (m_Callbacks) { - m_Callbacks->Destroy(); - } - } - - 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_bResponding) - { - m_bFinished = true; - m_bResponding = false; - - if (m_Callbacks) - { - m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Finished); - } - } - } - - virtual void OnHTTPComplete(CURLcode curlResult = CURLE_OK) - { - m_pResponse->FinalizeHeaders(); - m_pResponse->FinalizePayload(curlResult); - - if (m_pResponse->IsResponseError()) - { - - } - else - { - m_bRequestSuccessful = true; - } - - if (m_Callbacks) - { - m_Callbacks->OnResponseComplete(this, m_pResponse); - } - - OnRespondFinish(); - } - - void WriteHeader(const void* data, size_t size) - { - m_pResponse->WriteHeader(data, size); - } - - void WritePayload(const void* data, size_t size) - { - m_pResponse->WritePayload(data, size); - } - - void FinalizeHeaders() - { - m_pResponse->FinalizeHeaders(); - } + CUtilHTTPRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + CURLM* CurlMultiHandle, + CURLSH* CurlCookieHandle) : + m_Callbacks(callbacks), + m_CurlMultiHandle(CurlMultiHandle), + m_CurlCookieHandle(CurlCookieHandle) + { + m_CurlEasyHandle = curl_easy_init(); + + m_pResponse = new CUtilHTTPResponse(m_CurlEasyHandle); + + std::string url = std::format("{}://{}:{}{}", + secure ? "https" : "http", + host, + port, + target); + + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_PRIVATE, this); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEFUNCTION, WritePayloadCallback); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEDATA, this); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HEADERFUNCTION, WriteHeaderCallback); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HEADERDATA, this); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CONNECTTIMEOUT_MS, 60000); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_TIMEOUT_MS, 60000); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_COOKIEFILE, ""); + if (m_CurlCookieHandle) + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SHARE, m_CurlCookieHandle); + + // Set method + switch (method) + { + case UtilHTTPMethod::Get: + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HTTPGET, 1L); + break; + case UtilHTTPMethod::Post: + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_POST, 1L); + break; + case UtilHTTPMethod::Put: + // SetPostBody uses COPYPOSTFIELDS; keep PUT when it enables POST internally. + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CUSTOMREQUEST, "PUT"); + break; + case UtilHTTPMethod::Delete: + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + case UtilHTTPMethod::Head: + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_NOBODY, 1L); + break; + } + + 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); + } + + SetField("Host", field_host.c_str()); + SetField("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36"); + } + + ~CUtilHTTPRequest() + { + if (m_CurlHeaders) + { + curl_slist_free_all(m_CurlHeaders); + m_CurlHeaders = nullptr; + } + if (m_CurlMultiHandle && m_CurlEasyHandle) + { + curl_multi_remove_handle(m_CurlMultiHandle, m_CurlEasyHandle); + } + if (m_CurlEasyHandle) + { + curl_easy_cleanup(m_CurlEasyHandle); + m_CurlEasyHandle = nullptr; + } + if (m_pResponse) + { + delete m_pResponse; + m_pResponse = nullptr; + } + if (m_Callbacks) + { + m_Callbacks->Destroy(); + } + } + + 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_bResponding) + { + m_bFinished = true; + m_bResponding = false; + + if (m_Callbacks) + { + m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Finished); + } + } + } + + virtual void OnHTTPComplete(CURLcode curlResult = CURLE_OK) + { + m_pResponse->FinalizeHeaders(); + m_pResponse->FinalizePayload(curlResult); + + if (m_pResponse->IsResponseError()) + { + } + else + { + m_bRequestSuccessful = true; + } + + if (m_Callbacks) + { + m_Callbacks->OnResponseComplete(this, m_pResponse); + } + + OnRespondFinish(); + } + + void WriteHeader(const void* data, size_t size) + { + m_pResponse->WriteHeader(data, size); + } + + void WritePayload(const void* data, size_t size) + { + m_pResponse->WritePayload(data, size); + } + + void FinalizeHeaders() + { + m_pResponse->FinalizeHeaders(); + } public: - - void Destroy() override - { - delete this; - } - - 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 - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_TIMEOUT_MS, secs * 1000); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CONNECTTIMEOUT_MS, secs * 1000); - } - - void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) override - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_POSTFIELDSIZE, payloadSize); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_COPYPOSTFIELDS, payload); - - if(contentType && contentType[0]) - SetField("Content-Type", contentType); - } - - void SetField(const char* field, const char* value) override - { - std::string header = std::format("{}: {}", field, value); - m_CurlHeaders = curl_slist_append(m_CurlHeaders, header.c_str()); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HTTPHEADER, m_CurlHeaders); - } - - void SetRequireCertVerification(bool b) override - { - if (b) - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYPEER, 1); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYHOST, 2); - } - else - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYHOST, 0); - } - } - - void Send() override - { - m_bRequesting = true; - - if (m_Callbacks) { - m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Requesting); - } - - curl_multi_add_handle(m_CurlMultiHandle, m_CurlEasyHandle); - } - - void SetFollowLocation(bool b) override - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_FOLLOWLOCATION, b ? 1L : 0L); - } + void Destroy() override + { + delete this; + } + + 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 + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_TIMEOUT_MS, secs * 1000); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_CONNECTTIMEOUT_MS, secs * 1000); + } + + void SetPostBody(const char* contentType, const char* payload, size_t payloadSize) override + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_POSTFIELDSIZE, payloadSize); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_COPYPOSTFIELDS, payload); + + if (contentType && contentType[0]) + SetField("Content-Type", contentType); + } + + void SetField(const char* field, const char* value) override + { + std::string header = std::format("{}: {}", field, value); + m_CurlHeaders = curl_slist_append(m_CurlHeaders, header.c_str()); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_HTTPHEADER, m_CurlHeaders); + } + + void SetRequireCertVerification(bool b) override + { + if (b) + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYPEER, 1); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYHOST, 2); + } + else + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_SSL_VERIFYHOST, 0); + } + } + + void Send() override + { + m_bRequesting = true; + + if (m_Callbacks) + { + m_Callbacks->OnUpdateState(this, m_pResponse, UtilHTTPRequestState::Requesting); + } + + curl_multi_add_handle(m_CurlMultiHandle, m_CurlEasyHandle); + } + + void SetFollowLocation(bool b) override + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_FOLLOWLOCATION, b ? 1L : 0L); + } }; 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, - CURLM* CurlMultiHandle, - CURLSH* CurlCookieHandle - ) : - CUtilHTTPRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) - { - - } - - ~CUtilHTTPSyncRequest() - { - - } - - void OnRespondFinish() override - { - CUtilHTTPRequest::OnRespondFinish(); - - { - std::lock_guard lock(m_mutex); - m_isComplete = true; - } - m_cv.notify_one(); - } - - 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; - } + CUtilHTTPSyncRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + CURLM* CurlMultiHandle, + CURLSH* CurlCookieHandle) : + CUtilHTTPRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) + { + } + + ~CUtilHTTPSyncRequest() + { + } + + void OnRespondFinish() override + { + CUtilHTTPRequest::OnRespondFinish(); + + { + std::lock_guard lock(m_mutex); + m_isComplete = true; + } + m_cv.notify_one(); + } + + 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; + } }; class CUtilHTTPAsyncRequest : public CUtilHTTPRequest { protected: - public: - CUtilHTTPAsyncRequest( - const UtilHTTPMethod method, - const std::string& host, - unsigned short port, - bool secure, - const std::string& target, - IUtilHTTPCallbacks* callbacks, - CURLM* CurlMultiHandle, - CURLSH* CurlCookieHandle) - : - CUtilHTTPRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) - { - 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, + CURLM* CurlMultiHandle, + CURLSH* CurlCookieHandle) : + CUtilHTTPRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) + { + 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* callbacks, - CURLM* CurlMultiHandle, - CURLSH* CurlCookieHandle) : - CUtilHTTPAsyncRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) - { - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEFUNCTION, WritePayloadStreamCallback); - curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEDATA, this); - } - - bool IsStream() const override - { - return true; - } + CUtilHTTPAsyncStreamRequest( + const UtilHTTPMethod method, + const std::string& host, + unsigned short port, + bool secure, + const std::string& target, + IUtilHTTPCallbacks* callbacks, + CURLM* CurlMultiHandle, + CURLSH* CurlCookieHandle) : + CUtilHTTPAsyncRequest(method, host, port, secure, target, callbacks, CurlMultiHandle, CurlCookieHandle) + { + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEFUNCTION, WritePayloadStreamCallback); + curl_easy_setopt(m_CurlEasyHandle, CURLOPT_WRITEDATA, this); + } + + bool IsStream() const override + { + return true; + } public: - - void WritePayloadStream(const void* data, size_t size) - { - FinalizeHeaders(); - - if (m_Callbacks) - { - m_Callbacks->OnReceiveData(this, m_pResponse, data, size); - } - } + void WritePayloadStream(const void* data, size_t size) + { + FinalizeHeaders(); + + if (m_Callbacks) + { + m_Callbacks->OnReceiveData(this, m_pResponse, data, size); + } + } }; -static size_t WritePayloadStreamCallback(void* contents, size_t size, size_t nmemb, void* userp) { +static size_t WritePayloadStreamCallback(void* contents, size_t size, size_t nmemb, void* userp) +{ - auto pRequest = static_cast(userp); + auto pRequest = static_cast(userp); - size_t realsize = size * nmemb; + size_t realsize = size * nmemb; - pRequest->WritePayloadStream(contents, realsize); + pRequest->WritePayloadStream(contents, realsize); - return realsize; + return realsize; } -static size_t WritePayloadCallback(void* contents, size_t size, size_t nmemb, void* userp) { +static size_t WritePayloadCallback(void* contents, size_t size, size_t nmemb, void* userp) +{ - auto pRequest = static_cast(userp); + auto pRequest = static_cast(userp); - size_t realsize = size * nmemb; + size_t realsize = size * nmemb; - pRequest->WritePayload(contents, realsize); + pRequest->WritePayload(contents, realsize); - return realsize; + return realsize; } -static size_t WriteHeaderCallback(void* contents, size_t size, size_t nmemb, void* userp) { +static size_t WriteHeaderCallback(void* contents, size_t size, size_t nmemb, void* userp) +{ - auto pRequest = static_cast(userp); + auto pRequest = static_cast(userp); - size_t realsize = size * nmemb; + size_t realsize = size * nmemb; - pRequest->OnRespondStart(); + pRequest->OnRespondStart(); - pRequest->WriteHeader(contents, realsize); + pRequest->WriteHeader(contents, realsize); - return realsize; + return realsize; } class CUtilHTTPClient : public IUtilHTTPClient { private: - std::mutex m_RequestHandleLock; - UtilHTTPRequestId_t m_RequestUsedId{ UTILHTTP_REQUEST_START_ID }; - std::unordered_map m_RequestPool; - CURLM* m_CurlMultiHandle{ }; - CURLSH* m_CurlCookieHandle{ }; + std::mutex m_RequestHandleLock; + UtilHTTPRequestId_t m_RequestUsedId{UTILHTTP_REQUEST_START_ID}; + std::unordered_map m_RequestPool; + CURLM* m_CurlMultiHandle{}; + CURLSH* m_CurlCookieHandle{}; public: + CUtilHTTPClient() + { + curl_global_init(CURL_GLOBAL_ALL); + + curl_global_sslset(CURLSSLBACKEND_SCHANNEL, NULL, NULL); + } + + ~CUtilHTTPClient() + { + curl_global_cleanup(); + } + + void Destroy() override + { + delete this; + } + + void Init(const CUtilHTTPClientCreationContext* context) override + { + if (context->m_bUseCookieContainer) + { + m_CurlCookieHandle = curl_share_init(); + + // Set share options + curl_share_setopt(m_CurlCookieHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); + } + + m_CurlMultiHandle = curl_multi_init(); + } + + void Shutdown() override + { + std::lock_guard lock(m_RequestHandleLock); + + for (auto itor = m_RequestPool.begin(); itor != m_RequestPool.end(); itor++) + { + auto pRequest = (*itor).second; + + pRequest->Destroy(); + } + + m_RequestPool.clear(); + + if (m_CurlMultiHandle) + { + curl_multi_cleanup(m_CurlMultiHandle); + m_CurlMultiHandle = nullptr; + } + + if (m_CurlCookieHandle) + { + curl_share_cleanup(m_CurlCookieHandle); + m_CurlCookieHandle = nullptr; + } + } + + void PerformMultiHandle() + { + int runningHandles = 0; + auto code = curl_multi_perform(m_CurlMultiHandle, &runningHandles); + if (code == CURLM_OK || code == CURLM_CALL_MULTI_PERFORM) + { + int msg_queued = 0; + CURLMsg* msg = NULL; + do + { + msg = curl_multi_info_read(m_CurlMultiHandle, &msg_queued); + if (msg && (msg->msg == CURLMSG_DONE)) + { + auto eh = msg->easy_handle; + + CUtilHTTPRequest* pRequest = NULL; + + curl_easy_getinfo(eh, CURLINFO_PRIVATE, &pRequest); + + if (pRequest) + { + pRequest->OnHTTPComplete(msg->data.result); + } + } + } while (msg); + } + } + + void RunFrame() override + { + PerformMultiHandle(); + + std::lock_guard lock(m_RequestHandleLock); + + for (auto itor = m_RequestPool.begin(); itor != m_RequestPool.end();) + { + auto RequestInstance = (*itor).second; + + if (RequestInstance->IsFinished() && RequestInstance->IsAutoDestroyOnFinish()) + { + RequestInstance->Destroy(); - CUtilHTTPClient() - { - curl_global_init(CURL_GLOBAL_ALL); - - curl_global_sslset(CURLSSLBACKEND_SCHANNEL, NULL, NULL); - } - - ~CUtilHTTPClient() - { - curl_global_cleanup(); - } - - void Destroy() override - { - delete this; - } - - void Init(const CUtilHTTPClientCreationContext* context) override - { - if (context->m_bUseCookieContainer) - { - m_CurlCookieHandle = curl_share_init(); - - // Set share options - curl_share_setopt(m_CurlCookieHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); - } - - m_CurlMultiHandle = curl_multi_init(); - } - - void Shutdown() override - { - std::lock_guard lock(m_RequestHandleLock); - - for (auto itor = m_RequestPool.begin(); itor != m_RequestPool.end(); itor ++) - { - auto pRequest = (*itor).second; - - pRequest->Destroy(); - } - - m_RequestPool.clear(); - - if (m_CurlMultiHandle) - { - curl_multi_cleanup(m_CurlMultiHandle); - m_CurlMultiHandle = nullptr; - } - - if (m_CurlCookieHandle) - { - curl_share_cleanup(m_CurlCookieHandle); - m_CurlCookieHandle = nullptr; - } - } - - void PerformMultiHandle() - { - int runningHandles = 0; - auto code = curl_multi_perform(m_CurlMultiHandle, &runningHandles); - if (code == CURLM_OK || code == CURLM_CALL_MULTI_PERFORM) - { - int msg_queued = 0; - CURLMsg* msg = NULL; - do - { - msg = curl_multi_info_read(m_CurlMultiHandle, &msg_queued); - if (msg && (msg->msg == CURLMSG_DONE)) - { - auto eh = msg->easy_handle; + itor = m_RequestPool.erase(itor); - CUtilHTTPRequest* pRequest = NULL; + continue; + } - curl_easy_getinfo(eh, CURLINFO_PRIVATE, &pRequest); + itor++; + } + } - if (pRequest) - { - pRequest->OnHTTPComplete(msg->data.result); - } - } - } while (msg); - } - } + IURLParsedResult* ParseUrl(const char* url) override + { + return ParseUrlInternal(url); + } - void RunFrame() override - { - PerformMultiHandle(); + 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_CurlMultiHandle, m_CurlCookieHandle); + } - std::lock_guard lock(m_RequestHandleLock); + 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_CurlMultiHandle, m_CurlCookieHandle); + } - for (auto itor = m_RequestPool.begin(); itor != m_RequestPool.end();) - { - auto RequestInstance = (*itor).second; + IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrlInternal(url); - if (RequestInstance->IsFinished() && RequestInstance->IsAutoDestroyOnFinish()) - { - RequestInstance->Destroy(); + if (!result) + return nullptr; - itor = m_RequestPool.erase(itor); + SCOPE_EXIT { result->Destroy(); }; - continue; - } + return CreateSyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } - itor++; - } - } + IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrlInternal(url); - IURLParsedResult *ParseUrl(const char *url) override - { - return ParseUrlInternal(url); - } + if (!result) + return nullptr; - 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_CurlMultiHandle, m_CurlCookieHandle); - } + SCOPE_EXIT { result->Destroy(); }; - 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_CurlMultiHandle, m_CurlCookieHandle); - } + return CreateAsyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } - IUtilHTTPRequest* CreateSyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrlInternal(url); + 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_CurlMultiHandle, m_CurlCookieHandle); + } - if (!result) - return nullptr; + IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override + { + auto result = ParseUrlInternal(url); - SCOPE_EXIT{ result->Destroy(); }; + if (!result) + return nullptr; - return CreateSyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } + SCOPE_EXIT { result->Destroy(); }; - IUtilHTTPRequest* CreateAsyncRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrlInternal(url); + return CreateAsyncStreamRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); + } - if (!result) - return nullptr; + void AddToRequestPool(IUtilHTTPRequest* RequestInstance) override + { + std::lock_guard lock(m_RequestHandleLock); - SCOPE_EXIT{ result->Destroy(); }; + if (m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) + m_RequestUsedId = UTILHTTP_REQUEST_START_ID; - return CreateAsyncRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } + auto RequestId = m_RequestUsedId; - 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_CurlMultiHandle, m_CurlCookieHandle); - } - - IUtilHTTPRequest* CreateAsyncStreamRequest(const char* url, const UtilHTTPMethod method, IUtilHTTPCallbacks* callbacks) override - { - auto result = ParseUrlInternal(url); - - if (!result) - return nullptr; - - SCOPE_EXIT{ result->Destroy(); }; - - return CreateAsyncStreamRequestEx(result->GetHost(), result->GetPort(), result->GetTarget(), result->IsSecure(), method, callbacks); - } - - void AddToRequestPool(IUtilHTTPRequest* RequestInstance) override - { - std::lock_guard lock(m_RequestHandleLock); - - if (m_RequestUsedId == UTILHTTP_REQUEST_MAX_ID) - m_RequestUsedId = UTILHTTP_REQUEST_START_ID; + RequestInstance->SetRequestId(RequestId); - auto RequestId = m_RequestUsedId; + m_RequestPool[RequestId] = RequestInstance; - RequestInstance->SetRequestId(RequestId); - - m_RequestPool[RequestId] = RequestInstance; - - m_RequestUsedId++; - } - - IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) override - { - std::lock_guard lock(m_RequestHandleLock); + m_RequestUsedId++; + } - auto itor = m_RequestPool.find(id); + IUtilHTTPRequest* GetRequestById(UtilHTTPRequestId_t id) override + { + std::lock_guard lock(m_RequestHandleLock); - if (itor != m_RequestPool.end()) - { - return itor->second; - } + auto itor = m_RequestPool.find(id); - return NULL; - } + if (itor != m_RequestPool.end()) + { + return itor->second; + } - bool DestroyRequestById(UtilHTTPRequestId_t id) override - { - std::lock_guard lock(m_RequestHandleLock); + return NULL; + } - auto itor = m_RequestPool.find(id); - - if (itor != m_RequestPool.end()) - { - auto pRequest = itor->second; - - m_RequestPool.erase(itor); - - pRequest->Destroy(); - - return true; - } - - return false; - } - - bool SetCookie(const char* host, const char* url, const char* cookie) override - { - if (!m_CurlCookieHandle) - return false; - - // Parse the URL to extract domain and path - auto parsed_url = ParseUrlInternal(url); - if (!parsed_url) - return false; - - SCOPE_EXIT{ parsed_url->Destroy(); }; + bool DestroyRequestById(UtilHTTPRequestId_t id) override + { + std::lock_guard lock(m_RequestHandleLock); - // Create a temporary CURL handle to set the cookie - CURL* temp_handle = curl_easy_init(); - if (!temp_handle) - return false; - - // Configure the handle to use our shared cookie store - curl_easy_setopt(temp_handle, CURLOPT_SHARE, m_CurlCookieHandle); - - // Set the URL to establish the domain context - curl_easy_setopt(temp_handle, CURLOPT_URL, url); - - // Build complete cookie string with domain and path - std::string cookie_str = cookie; - std::string domain = parsed_url->GetHost(); - std::string path = parsed_url->GetTarget(); - - // If path is empty, default to "/" - if (path.empty()) - path = "/"; - - // Check if cookie already contains domain or path attributes - if (cookie_str.find("domain=") == std::string::npos) - { - cookie_str += std::format("; domain={}", domain); - } - - if (cookie_str.find("path=") == std::string::npos) - { - cookie_str += std::format("; path={}", path); - } - - // Set the cookie using CURLOPT_COOKIELIST with "Set-Cookie:" format - std::string cookie_header = std::format("Set-Cookie: {}", cookie_str); - CURLcode result = curl_easy_setopt(temp_handle, CURLOPT_COOKIELIST, cookie_header.c_str()); - - // Clean up the temporary handle - curl_easy_cleanup(temp_handle); - - return (result == CURLE_OK); - } + auto itor = m_RequestPool.find(id); + if (itor != m_RequestPool.end()) + { + auto pRequest = itor->second; + + m_RequestPool.erase(itor); + + pRequest->Destroy(); + + return true; + } + + return false; + } + + bool SetCookie(const char* host, const char* url, const char* cookie) override + { + if (!m_CurlCookieHandle) + return false; + + // Parse the URL to extract domain and path + auto parsed_url = ParseUrlInternal(url); + if (!parsed_url) + return false; + + SCOPE_EXIT { parsed_url->Destroy(); }; + + // Create a temporary CURL handle to set the cookie + CURL* temp_handle = curl_easy_init(); + if (!temp_handle) + return false; + + // Configure the handle to use our shared cookie store + curl_easy_setopt(temp_handle, CURLOPT_SHARE, m_CurlCookieHandle); + + // Set the URL to establish the domain context + curl_easy_setopt(temp_handle, CURLOPT_URL, url); + + // Build complete cookie string with domain and path + std::string cookie_str = cookie; + std::string domain = parsed_url->GetHost(); + std::string path = parsed_url->GetTarget(); + + // If path is empty, default to "/" + if (path.empty()) + path = "/"; + + // Check if cookie already contains domain or path attributes + if (cookie_str.find("domain=") == std::string::npos) + { + cookie_str += std::format("; domain={}", domain); + } + + if (cookie_str.find("path=") == std::string::npos) + { + cookie_str += std::format("; path={}", path); + } + + // Set the cookie using CURLOPT_COOKIELIST with "Set-Cookie:" format + std::string cookie_header = std::format("Set-Cookie: {}", cookie_str); + CURLcode result = curl_easy_setopt(temp_handle, CURLOPT_COOKIELIST, cookie_header.c_str()); + + // Clean up the temporary handle + curl_easy_cleanup(temp_handle); + + return (result == CURLE_OK); + } }; EXPOSE_INTERFACE(CUtilHTTPClient, IUtilHTTPClient, UTIL_HTTPCLIENT_LIBCURL_INTERFACE_VERSION); @@ -1191,16 +1213,15 @@ EXPOSE_INTERFACE(CUtilHTTPClient, IUtilHTTPClient, UTIL_HTTPCLIENT_LIBCURL_INTER 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_LIBCURL_INTERFACE_VERSION); diff --git a/src/dllmain.cpp b/src/dllmain.cpp index da0c571..0d4a9f4 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -1,18 +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/RegressionTests.cpp b/tests/RegressionTests.cpp index 39dee40..05f1772 100644 --- a/tests/RegressionTests.cpp +++ b/tests/RegressionTests.cpp @@ -25,7 +25,7 @@ static void Require(bool condition, std::string_view message) throw std::runtime_error(std::string(message)); } -template +template static void Equal(const Expected& expected, const Actual& actual, std::string_view message) { Require(expected == actual, message); @@ -46,17 +46,20 @@ class Socket { public: explicit Socket(SOCKET value = INVALID_SOCKET) : value(value) {} - ~Socket() { if (value != INVALID_SOCKET) closesocket(value); } - Socket(const Socket&) = delete; + ~Socket() + { + if (value != INVALID_SOCKET) closesocket(value); + } + Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; - SOCKET value; + SOCKET value; }; static unsigned short BindLoopback(Socket& socket) { Require(socket.value != INVALID_SOCKET, "socket failed"); sockaddr_in address{}; - address.sin_family = AF_INET; + address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); Require(bind(socket.value, reinterpret_cast(&address), sizeof(address)) == 0, "bind failed"); int length = sizeof(address); @@ -80,13 +83,16 @@ class HttpServer Require(connection.value != INVALID_SOCKET, "accept failed"); constexpr DWORD receiveTimeoutMs = 3000; setsockopt(connection.value, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&receiveTimeoutMs), sizeof(receiveTimeoutMs)); + reinterpret_cast(&receiveTimeoutMs), sizeof(receiveTimeoutMs)); setsockopt(connection.value, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&receiveTimeoutMs), sizeof(receiveTimeoutMs)); + reinterpret_cast(&receiveTimeoutMs), sizeof(receiveTimeoutMs)); Respond(connection.value); } } - catch (...) { error = std::current_exception(); } + catch (...) + { + error = std::current_exception(); + } }); } @@ -125,10 +131,10 @@ class HttpServer void Respond(SOCKET connection) { std::array buffer{}; - std::string request; - size_t headerEnd = std::string::npos; - size_t bodySize = 0; - constexpr size_t maxRequestSize = 64 * 1024; + std::string request; + size_t headerEnd = std::string::npos; + size_t bodySize = 0; + constexpr size_t maxRequestSize = 64 * 1024; while (headerEnd == std::string::npos || request.size() < headerEnd + 4 + bodySize) { int received = recv(connection, buffer.data(), static_cast(buffer.size()), 0); @@ -162,18 +168,16 @@ class HttpServer } else { - const bool missing = request.starts_with("GET /missing "); - const std::string body = (request.starts_with("POST /echo ") || request.starts_with("PUT /echo ")) - ? request.substr(headerEnd + 4, bodySize) : (missing ? "missing" : "hello world"); - const std::string status = missing ? "404 Not Found" : "200 OK"; - SendAll(connection, "HTTP/1.1 " + status + "\r\nX-Port-Test: present\r\nContent-Length: " - + std::to_string(body.size()) + "\r\nConnection: close\r\n\r\n" + body); + const bool missing = request.starts_with("GET /missing "); + const std::string body = (request.starts_with("POST /echo ") || request.starts_with("PUT /echo ")) ? request.substr(headerEnd + 4, bodySize) : (missing ? "missing" : "hello world"); + const std::string status = missing ? "404 Not Found" : "200 OK"; + SendAll(connection, "HTTP/1.1 " + status + "\r\nX-Port-Test: present\r\nContent-Length: " + std::to_string(body.size()) + "\r\nConnection: close\r\n\r\n" + body); } } - Socket listener; - unsigned short port{}; - std::thread worker; + Socket listener; + unsigned short port{}; + std::thread worker; std::exception_ptr error; }; @@ -201,7 +205,7 @@ class Module Require(curl != nullptr, "installed client did not load its libcurl"); std::array loadedPath{}; Require(GetModuleFileNameW(curl, loadedPath.data(), static_cast(loadedPath.size())) != 0, - "cannot locate loaded libcurl"); + "cannot locate loaded libcurl"); Require(fs::equivalent(curlPath, loadedPath.data()), "client loaded libcurl from outside the install root"); } factory = reinterpret_cast(GetProcAddress(dll, CREATEINTERFACE_PROCNAME)); @@ -212,23 +216,24 @@ class Module if (dll) FreeLibrary(dll); if (ownCurl && curl) FreeLibrary(curl); } - Module(const Module&) = delete; + Module(const Module&) = delete; Module& operator=(const Module&) = delete; IUtilHTTPClientFactory* GetFactory() const { - int result = IFACE_FAILED; - auto value = static_cast(factory(UTIL_HTTPCLIENT_FACTORY_LIBCURL_INTERFACE_VERSION, &result)); + int result = IFACE_FAILED; + auto value = static_cast(factory(UTIL_HTTPCLIENT_FACTORY_LIBCURL_INTERFACE_VERSION, &result)); Require(value != nullptr, "missing _007 factory interface"); Equal(IFACE_OK, result, "factory return code"); return value; } - HMODULE curl{}; + HMODULE curl{}; CreateInterfaceFn factory{}; + private: HMODULE dll{}; - bool ownCurl{}; + bool ownCurl{}; }; class Client @@ -241,22 +246,26 @@ class Client CUtilHTTPClientCreationContext context; value->Init(&context); } - ~Client() { value->Shutdown(); value->Destroy(); } + ~Client() + { + value->Shutdown(); + value->Destroy(); + } IUtilHTTPClient* value{}; }; struct Results { - int completions{}; - int destroyed{}; - int status{}; - bool completed{}; - bool error{}; - std::string errorMessage; - std::string body; - std::string streamed; - std::string header; - bool streamHeaderPresent{true}; + int completions{}; + int destroyed{}; + int status{}; + bool completed{}; + bool error{}; + std::string errorMessage; + std::string body; + std::string streamed; + std::string header; + bool streamHeaderPresent{true}; std::vector states; }; @@ -264,15 +273,19 @@ class Callbacks : public IUtilHTTPCallbacks { public: explicit Callbacks(Results& results) : results(results) {} - void Destroy() override { ++results.destroyed; delete this; } + void Destroy() override + { + ++results.destroyed; + delete this; + } void OnResponseComplete(IUtilHTTPRequest*, IUtilHTTPResponse* response) override { ++results.completions; - results.status = response->GetStatusCode(); - results.completed = response->IsResponseCompleted(); - results.error = response->IsResponseError(); + results.status = response->GetStatusCode(); + results.completed = response->IsResponseCompleted(); + results.error = response->IsResponseError(); results.errorMessage = response->GetResponseErrorMessage(); - auto payload = response->GetPayload(); + auto payload = response->GetPayload(); results.body.assign(payload->GetBytes(), payload->GetLength()); if (const auto header = response->GetHeaderValue("x-port-test")) results.header = header; } @@ -286,11 +299,18 @@ class Callbacks : public IUtilHTTPCallbacks const auto header = response->GetHeaderValue("X-Port-Test"); results.streamHeaderPresent &= header && std::string_view(header) == "present"; } + private: Results& results; }; -struct DestroyRequest { void operator()(IUtilHTTPRequest* value) const { if (value) value->Destroy(); } }; +struct DestroyRequest +{ + void operator()(IUtilHTTPRequest* value) const + { + if (value) value->Destroy(); + } +}; using Request = std::unique_ptr; static void Pump(IUtilHTTPClient* client, const Results& results) @@ -310,7 +330,7 @@ static void SuccessfulResponse(const Results& results) Require(results.completed && !results.error, "response should complete successfully"); Equal(std::string("present"), results.header, "response header"); Equal(std::vector{UtilHTTPRequestState::Requesting, UtilHTTPRequestState::Responding, UtilHTTPRequestState::Finished}, - results.states, "request state callbacks"); + results.states, "request state callbacks"); } static void FactoryTest(Module& module) @@ -332,7 +352,7 @@ static void FactoryTest(Module& module) static void UrlTest(Module& module) { auto factory = module.GetFactory(); - auto parsed = factory->ParseUrl("https://example.test/resource?q=1"); + auto parsed = factory->ParseUrl("https://example.test/resource?q=1"); Require(parsed != nullptr, "valid URL rejected"); Equal(std::string_view("example.test"), std::string_view(parsed->GetHost()), "URL host"); Equal(443, parsed->GetPort(), "default HTTPS port"); @@ -346,11 +366,11 @@ static void UrlTest(Module& module) static void SyncTest(Module& module, bool post, bool missing) { HttpServer server; - Results results; - Client client(module); + Results results; + Client client(module); const auto url = server.Url(post ? "/echo" : (missing ? "/missing" : "/get")); - Request request(client.value->CreateSyncRequest(url.c_str(), post ? UtilHTTPMethod::Post : UtilHTTPMethod::Get, - new Callbacks(results))); + Request request(client.value->CreateSyncRequest(url.c_str(), post ? UtilHTTPMethod::Post : UtilHTTPMethod::Get, + new Callbacks(results))); Require(request != nullptr, "sync request creation failed"); Require(!request->WaitForCompleteTimeout(0), "unsent request must not be complete"); constexpr std::string_view posted = "port=standalone&value=123"; @@ -373,7 +393,7 @@ static void SyncTest(Module& module, bool post, bool missing) { SuccessfulResponse(results); Equal(std::string(post ? posted : "hello world"), results.body, "response body"); - auto response = request->GetResponse(); + auto response = request->GetResponse(); size_t size{}; Require(response->GetHeaderSize("X-PORT-TEST", &size), "header size lookup failed"); Equal(size_t{8}, size, "header size including terminator"); @@ -392,15 +412,15 @@ static void SyncTest(Module& module, bool post, bool missing) static void HeaderBufferTest(Module& module) { HttpServer server; - Results results; - Client client(module); - Request request(client.value->CreateSyncRequest(server.Url("/get").c_str(), UtilHTTPMethod::Get, new Callbacks(results))); + Results results; + Client client(module); + Request request(client.value->CreateSyncRequest(server.Url("/get").c_str(), UtilHTTPMethod::Get, new Callbacks(results))); Require(request != nullptr, "header request creation failed"); request->SetTimeout(2); request->Send(); Pump(client.value, results); server.Finish(); - auto response = request->GetResponse(); + auto response = request->GetResponse(); std::array guarded{'A', 'B', 'C'}; Require(!response->GetHeader("X-Port-Test", &guarded[1], 0), "zero-size buffer must be rejected"); Equal(std::array{'A', 'B', 'C'}, guarded, "zero-size lookup must not write any bytes"); @@ -423,8 +443,8 @@ static void PutTest(Module& module) for (const auto& payload : {body, std::string{}}) { HttpServer server; - Results results; - Request request(client.value->CreateSyncRequest(server.Url("/echo").c_str(), UtilHTTPMethod::Put, new Callbacks(results))); + Results results; + Request request(client.value->CreateSyncRequest(server.Url("/echo").c_str(), UtilHTTPMethod::Put, new Callbacks(results))); Require(request != nullptr, "PUT request creation failed"); request->SetPostBody(nullptr, "old", 3); auto copiedBody = payload; @@ -455,29 +475,24 @@ class RedirectCallbacks : public Callbacks Callbacks::OnReceiveData(request, response, bytes, size); } bool headersValid{true}; + private: void CheckHeaders(IUtilHTTPResponse* response) { - const auto length = response->GetHeaderValue("Content-Length"); + const auto length = response->GetHeaderValue("Content-Length"); const auto repeated = response->GetHeaderValue("X-Repeated"); - headersValid &= length && std::string_view(length) == "11" - && repeated && std::string_view(repeated) == "first\r\nsecond" - && !response->GetHeaderValue("Location") - && !response->GetHeaderValue("X-Intermediate") - && !response->GetHeaderValue("X-Interim"); + headersValid &= length && std::string_view(length) == "11" && repeated && std::string_view(repeated) == "first\r\nsecond" && !response->GetHeaderValue("Location") && !response->GetHeaderValue("X-Intermediate") && !response->GetHeaderValue("X-Interim"); } }; static void RedirectTest(Module& module, bool stream) { HttpServer server(2); - Results results; - Client client(module); - auto callbacks = new RedirectCallbacks(results); - const auto url = server.Url("/redirect"); - Request request(stream - ? client.value->CreateAsyncStreamRequest(url.c_str(), UtilHTTPMethod::Get, callbacks) - : client.value->CreateSyncRequest(url.c_str(), UtilHTTPMethod::Get, callbacks)); + Results results; + Client client(module); + auto callbacks = new RedirectCallbacks(results); + const auto url = server.Url("/redirect"); + Request request(stream ? client.value->CreateAsyncStreamRequest(url.c_str(), UtilHTTPMethod::Get, callbacks) : client.value->CreateSyncRequest(url.c_str(), UtilHTTPMethod::Get, callbacks)); Require(request != nullptr, "redirect request creation failed"); request->SetFollowLocation(true); request->SetTimeout(2); @@ -493,12 +508,11 @@ static void RedirectTest(Module& module, bool stream) static void AsyncTest(Module& module, bool stream) { HttpServer server(stream ? 1 : 2); - Results results; - Results retainedResults; - Client client(module); - const auto url = server.Url(stream ? "/stream" : "/get"); - auto request = stream ? client.value->CreateAsyncStreamRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results)) - : client.value->CreateAsyncRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results)); + Results results; + Results retainedResults; + Client client(module); + const auto url = server.Url(stream ? "/stream" : "/get"); + auto request = stream ? client.value->CreateAsyncStreamRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results)) : client.value->CreateAsyncRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results)); Require(request != nullptr, "async request creation failed"); Require(request->IsAsync() && request->IsStream() == stream, "request type"); client.value->AddToRequestPool(request); @@ -540,12 +554,12 @@ static void AsyncTest(Module& module, bool stream) static void TransportErrorTest(Module& module) { // Reserve a port without listening so connection refusal is deterministic. - Socket reserved(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); - auto port = BindLoopback(reserved); - Results results; - Client client(module); + Socket reserved(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + auto port = BindLoopback(reserved); + Results results; + Client client(module); const auto url = "http://127.0.0.1:" + std::to_string(port) + "/refused"; - Request request(client.value->CreateSyncRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results))); + Request request(client.value->CreateSyncRequest(url.c_str(), UtilHTTPMethod::Get, new Callbacks(results))); Require(request != nullptr, "error request creation failed"); request->SetTimeout(2); request->Send(); @@ -560,12 +574,12 @@ static void TransportErrorTest(Module& module) static void TlsTest(Module& module) { using VersionInfo = curl_version_info_data* (*)(CURLversion); - auto versionInfo = reinterpret_cast(GetProcAddress(module.curl, "curl_version_info")); + auto versionInfo = reinterpret_cast(GetProcAddress(module.curl, "curl_version_info")); Require(versionInfo != nullptr, "curl_version_info export missing"); const auto info = versionInfo(CURLVERSION_NOW); Require(info != nullptr && (info->features & CURL_VERSION_SSL), "libcurl SSL feature missing"); Require(info->ssl_version && std::string_view(info->ssl_version).find("Schannel") != std::string_view::npos, - "libcurl must use Schannel"); + "libcurl must use Schannel"); bool https = false; for (auto protocol = info->protocols; protocol && *protocol; ++protocol) https |= std::string_view(*protocol) == "https"; @@ -578,29 +592,42 @@ int wmain(int argc, wchar_t** argv) { Require(argc == 4, "usage: UtilHTTPClientTests "); Require(SetEnvironmentVariableW(L"NO_PROXY", L"127.0.0.1") != 0, "cannot bypass proxies for loopback tests"); - Winsock winsock; + Winsock winsock; const std::wstring scenario = argv[3]; - Module module(fs::absolute(argv[1]), fs::absolute(argv[2]), scenario == L"Installed"); + Module module(fs::absolute(argv[1]), fs::absolute(argv[2]), scenario == L"Installed"); if (scenario == L"Factory") FactoryTest(module); - else if (scenario == L"Url") UrlTest(module); - else if (scenario == L"SyncGet") SyncTest(module, false, false); - else if (scenario == L"Post") SyncTest(module, true, false); - else if (scenario == L"HeaderBuffers") HeaderBufferTest(module); - else if (scenario == L"Put") PutTest(module); - else if (scenario == L"Redirect") RedirectTest(module, false); - else if (scenario == L"RedirectStream") RedirectTest(module, true); - else if (scenario == L"AsyncPool") AsyncTest(module, false); - else if (scenario == L"Stream") AsyncTest(module, true); - else if (scenario == L"HttpError") SyncTest(module, false, true); - else if (scenario == L"TransportError") TransportErrorTest(module); - else if (scenario == L"TLS") TlsTest(module); + else if (scenario == L"Url") + UrlTest(module); + else if (scenario == L"SyncGet") + SyncTest(module, false, false); + else if (scenario == L"Post") + SyncTest(module, true, false); + else if (scenario == L"HeaderBuffers") + HeaderBufferTest(module); + else if (scenario == L"Put") + PutTest(module); + else if (scenario == L"Redirect") + RedirectTest(module, false); + else if (scenario == L"RedirectStream") + RedirectTest(module, true); + else if (scenario == L"AsyncPool") + AsyncTest(module, false); + else if (scenario == L"Stream") + AsyncTest(module, true); + else if (scenario == L"HttpError") + SyncTest(module, false, true); + else if (scenario == L"TransportError") + TransportErrorTest(module); + else if (scenario == L"TLS") + TlsTest(module); else if (scenario == L"Installed") { FactoryTest(module); TlsTest(module); SyncTest(module, false, false); } - else throw std::runtime_error("unknown scenario"); + else + throw std::runtime_error("unknown scenario"); std::wcout << scenario << L" passed\n"; return 0; }