├── .github └── workflows │ └── build.yml ├── .gitignore ├── Android.mk ├── CMakeLists.txt ├── cmake └── FindLuaJIT.cmake ├── example ├── example.lua └── test.lua ├── java.txt ├── license.txt ├── readme.md └── src ├── CMakeLists.txt ├── android ├── AndroidClient.cpp ├── AndroidClient.h └── java │ └── org │ └── love2d │ └── luahttps │ └── LuaHTTPS.java ├── apple ├── NSURLClient.h └── NSURLClient.mm ├── common ├── Connection.h ├── ConnectionClient.h ├── HTTPRequest.cpp ├── HTTPRequest.h ├── HTTPS.cpp ├── HTTPS.h ├── HTTPSClient.cpp ├── HTTPSClient.h ├── LibraryLoader.h ├── PlaintextConnection.cpp ├── PlaintextConnection.h ├── config-generated.h.in └── config.h ├── generic ├── CurlClient.cpp ├── CurlClient.h ├── LinktimeLibraryLoader.cpp ├── OpenSSLConnection.cpp ├── OpenSSLConnection.h └── UnixLibraryLoader.cpp ├── lua └── main.cpp └── windows ├── SChannelConnection.cpp ├── SChannelConnection.h ├── WinINetClient.cpp ├── WinINetClient.h └── WindowsLibraryLoader.cpp /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "Build & Test" 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | macOS: 6 | runs-on: macos-latest 7 | timeout-minutes: 30 8 | env: 9 | MACOSX_DEPLOYMENT_TARGET: "10.9" 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Download LuaJIT 14 | uses: actions/checkout@v4 15 | with: 16 | repository: LuaJIT/LuaJIT 17 | ref: v2.1 18 | path: LuaJIT 19 | - name: Compile universal LuaJIT 20 | working-directory: ./LuaJIT 21 | run: | 22 | TARGET_FLAGS="-arch x86_64" make 23 | cp ./src/libluajit.so ./src/libluajit_x64.dylib 24 | cp ./src/luajit ./src/luajit_x64 25 | 26 | make clean 27 | 28 | TARGET_FLAGS="-arch arm64" make 29 | cp ./src/libluajit.so ./src/libluajit_arm.dylib 30 | cp ./src/luajit ./src/luajit_arm 31 | 32 | lipo -create -output ./src/libluajit.dylib ./src/libluajit_x64.dylib ./src/libluajit_arm.dylib 33 | lipo -create -output ./src/luajit ./src/luajit_x64 ./src/luajit_arm 34 | - name: Configure 35 | run: cmake -Bbuild -S. -G Xcode -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLUA_INCLUDE_DIR=$PWD/LuaJIT/src -DLUA_LIBRARIES=$PWD/LuaJIT/src/lua 36 | - name: Build 37 | working-directory: build 38 | run: xcodebuild -configuration Release -scheme https -destination generic/platform=macOS 39 | - name: Test 40 | working-directory: ./build/src/Release 41 | run: ../../../LuaJIT/src/luajit -l "https" ../../../example/test.lua 42 | - name: Artifact 43 | if: always() 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: https-macos.zip 47 | path: build/src/**/https.so 48 | 49 | Linux: 50 | name: ${{ matrix.mode.name }} 51 | runs-on: ubuntu-20.04 52 | timeout-minutes: 30 53 | strategy: 54 | matrix: 55 | mode: 56 | - name: Linux cURL 57 | curl: 1 58 | openssl: 0 59 | artifact: 0 60 | - name: Linux OpenSSL 61 | curl: 0 62 | openssl: 1 63 | artifact: 0 64 | - name: Linux cURL & OpenSSL 65 | curl: 1 66 | openssl: 1 67 | artifact: 1 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | - name: Update APT Repository 72 | run: sudo apt-get update 73 | - name: Install Dependencies 74 | run: sudo apt-get install -y lua5.1 luajit liblua5.1-0-dev libcurl4-openssl-dev g++ libssl-dev 75 | - name: Configure 76 | run: cmake -Bbuild -S. -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_BUILD_TYPE=Release -DUSE_CURL_BACKEND=${{ matrix.mode.curl }} -DUSE_OPENSSL_BACKEND=${{ matrix.mode.openssl }} 77 | - name: Build 78 | run: cmake --build build --config Release --target install -j$(nproc) 79 | - name: Test (Lua) 80 | if: matrix.mode.artifact == 0 81 | working-directory: ./install 82 | run: lua -l "https" ../example/test.lua 83 | - name: Test (LuaJIT) 84 | if: matrix.mode.artifact == 0 85 | working-directory: ./install 86 | run: luajit -l "https" ../example/test.lua 87 | - name: Artifact 88 | if: matrix.mode.artifact == 1 89 | uses: actions/upload-artifact@v4 90 | with: 91 | name: https-ubuntu.zip 92 | path: install/https.so 93 | 94 | Windows: 95 | runs-on: windows-latest 96 | strategy: 97 | matrix: 98 | arch: 99 | - Win32 100 | - x64 101 | defaults: 102 | run: 103 | shell: cmd 104 | steps: 105 | - name: Checkout 106 | uses: actions/checkout@v4 107 | - name: Download LuaJIT 108 | uses: actions/checkout@v4 109 | with: 110 | repository: LuaJIT/LuaJIT 111 | ref: v2.1 112 | path: LuaJIT 113 | - name: Configure MSVC Developer Command Prompt 114 | uses: ilammy/msvc-dev-cmd@v1 115 | with: 116 | arch: ${{ matrix.arch }} 117 | - name: Compile LuaJIT 118 | working-directory: ./LuaJIT/src 119 | run: msvcbuild.bat amalg 120 | - name: Configure 121 | run: cmake -Bbuild -S. -DCMAKE_INSTALL_PREFIX=%CD%\install -A ${{ matrix.arch }} -DLUA_INCLUDE_DIR=%CD%\LuaJIT\src -DLUA_LIBRARIES=%CD%\LuaJIT\src\lua51.lib 122 | - name: Build 123 | run: cmake --build build --config Release --target install 124 | - name: Test 125 | working-directory: ./install 126 | run: ..\LuaJIT\src\luajit ..\example\test.lua 127 | - name: Artifact 128 | if: always() 129 | uses: actions/upload-artifact@v4 130 | with: 131 | name: https-windows-${{ matrix.arch }}.zip 132 | path: install/https.dll 133 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.o 3 | *.dll 4 | *.exe 5 | *.so 6 | .vscode/ 7 | build*/ 8 | -------------------------------------------------------------------------------- /Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | include $(CLEAR_VARS) 3 | 4 | LOCAL_MODULE := https 5 | LOCAL_MODULE_FILENAME := https 6 | 7 | LOCAL_CFLAGS := -DNOMINMAX 8 | LOCAL_CPPFLAGS := -std=c++11 9 | 10 | LOCAL_ARM_NEON := true 11 | 12 | LOCAL_C_INCLUDES := \ 13 | ${LOCAL_PATH}/src \ 14 | ${LOCAL_PATH}/src/android 15 | 16 | LOCAL_SRC_FILES := \ 17 | src/lua/main.cpp \ 18 | src/common/HTTPS.cpp \ 19 | src/common/HTTPRequest.cpp \ 20 | src/common/HTTPSClient.cpp \ 21 | src/common/PlaintextConnection.cpp \ 22 | src/android/AndroidClient.cpp \ 23 | src/generic/UnixLibraryLoader.cpp 24 | 25 | LOCAL_SHARED_LIBRARIES := liblove 26 | 27 | include $(BUILD_SHARED_LIBRARY) 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.13) 2 | 3 | project (Https CXX) 4 | 5 | set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") 6 | 7 | add_subdirectory (src) 8 | 9 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT https) 10 | -------------------------------------------------------------------------------- /cmake/FindLuaJIT.cmake: -------------------------------------------------------------------------------- 1 | # Sets the following variables: 2 | # 3 | # LUAJIT_FOUND 4 | # LUAJIT_INCLUDE_DIR 5 | # LUAJIT_LIBRARY 6 | 7 | set(LUAJIT_SEARCH_PATHS 8 | /usr/local 9 | /usr 10 | $ENV{LUAJIT_DIR} 11 | ${LUAJIT_DIR} 12 | ) 13 | 14 | find_path(LUAJIT_INCLUDE_DIR 15 | NAMES luajit.h lua.h 16 | PATH_SUFFIXES include/luajit-2.1 include/luajit2.1 include/luajit-2.0 include/luajit2.0 include src 17 | PATHS ${LUAJIT_SEARCH_PATHS} 18 | ) 19 | 20 | find_library(LUAJIT_LIBRARY 21 | NAMES luajit5.1 luajit-5.1 lua51 22 | PATH_SUFFIXES lib 23 | PATHS ${LUAJIT_SEARCH_PATHS} 24 | ) 25 | 26 | include(FindPackageHandleStandardArgs) 27 | find_package_handle_standard_args(LuaJIT DEFAULT_MSG LUAJIT_LIBRARY LUAJIT_INCLUDE_DIR) 28 | 29 | mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIBRARY) 30 | -------------------------------------------------------------------------------- /example/example.lua: -------------------------------------------------------------------------------- 1 | local https = require "https" 2 | 3 | do 4 | local code, body = https.request("https://example.com") 5 | assert(code == 200, body) 6 | end 7 | 8 | do 9 | local code, body, headers = https.request("http://example.com", {method = "post", headers = {}, data = "cake"}) 10 | assert(code == 200 and headers, body) 11 | 12 | for i, v in pairs(headers) do 13 | print(i, v) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /example/test.lua: -------------------------------------------------------------------------------- 1 | local https = require "https" 2 | local json 3 | 4 | -- Helper 5 | 6 | local function hexencode(c) 7 | return string.format("%%%02x", string.byte(c)) 8 | end 9 | 10 | local function escape(s) 11 | return (string.gsub(s, "([^A-Za-z0-9_])", hexencode)) 12 | end 13 | 14 | local function urlencode(list) 15 | local result = {} 16 | 17 | for k, v in pairs(list) do 18 | result[#result + 1] = escape(k).."="..escape(v) 19 | end 20 | 21 | return table.concat(result, "&") 22 | end 23 | 24 | local function checkcode(code, expected) 25 | if code ~= expected then 26 | error("expected code "..expected..", got "..tostring(code)) 27 | end 28 | end 29 | 30 | math.randomseed(os.time()) 31 | 32 | -- Tests function 33 | 34 | local function test_download_json() 35 | local code, response = https.request("https://raw.githubusercontent.com/rxi/json.lua/master/json.lua") 36 | checkcode(code, 200) 37 | json = assert(loadstring(response, "=json.lua"))() 38 | end 39 | 40 | local function test_head() 41 | local code, response = https.request("https://postman-echo.com/get", {method = "HEAD"}) 42 | assert(code == 200, "expected code 200, got "..code) 43 | assert(#response == 0, "expected empty response") 44 | end 45 | 46 | local function test_custom_header() 47 | local headerName = "RandomNumber" 48 | local random = math.random(1, 1000) 49 | local code, response = https.request("https://postman-echo.com/get", { 50 | headers = { 51 | [headerName] = tostring(random) 52 | } 53 | }) 54 | checkcode(code, 200) 55 | local root = json.decode(response) 56 | 57 | -- Headers are case-insensitive 58 | local found = false 59 | for k, v in pairs(root.headers) do 60 | if k:lower() == headerName:lower() then 61 | assert(tonumber(v) == random, "random number does not match, expected "..random..", got "..v) 62 | found = true 63 | end 64 | end 65 | 66 | assert(found, "custom header RandomNumber not found") 67 | end 68 | 69 | local function test_send(method, kind) 70 | local data = {Foo = "Bar", Key = "Value"} 71 | local input, contentType 72 | if kind == "json" then 73 | input = json.encode 74 | contentType = "application/json" 75 | else 76 | input = urlencode 77 | contentType = "application/x-www-form-urlencoded" 78 | end 79 | 80 | local code, response = https.request("https://postman-echo.com/"..method:lower(), { 81 | headers = {["Content-Type"] = contentType}, 82 | data = input(data), 83 | method = method 84 | }) 85 | 86 | checkcode(code, 200) 87 | local root = json.decode(response) 88 | 89 | for k, v in pairs(data) do 90 | local v0 = assert(root[kind][k], "Missing key "..k.." for "..kind) 91 | assert(v0 == v, "Key "..k.." value mismatch, expected '"..v.."' got '"..v0.."'") 92 | end 93 | end 94 | 95 | -- Tests call 96 | print("test downloading json library") test_download_json() 97 | print("test custom header") test_custom_header() 98 | print("test HEAD") test_head() 99 | 100 | for _, method in ipairs({"POST", "PUT", "PATCH", "DELETE"}) do 101 | for _, kind in ipairs({"form", "json"}) do 102 | print("test "..method.." with data send as "..kind) 103 | test_send(method, kind) 104 | end 105 | end 106 | 107 | print("Test successful!") 108 | -------------------------------------------------------------------------------- /java.txt: -------------------------------------------------------------------------------- 1 | src/android/java -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2025 LOVE Development Team 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # lua-https 2 | 3 | lua-https is a simple Lua HTTPS module using native platform backends 4 | specifically written for [LÖVE](https://love2d.org) 12.0 and supports 5 | Windows, Linux, macOS, iOS, and Android. 6 | 7 | ## Reference 8 | 9 | To use lua-https, load it with require like `local https = require("https")`. 10 | lua-https does not create global variables! 11 | 12 | The https module exposes a single function: `https.request`. 13 | 14 | ## Synopsis 15 | 16 | Simplified form: 17 | 18 | ```lua 19 | code, body = https.request( url ) 20 | ``` 21 | 22 | If you need to specify headers in the request or get them in the 23 | response, you can use the full form: 24 | 25 | ```lua 26 | code, body, headers = https.request( url, options ) 27 | ``` 28 | 29 | ### Arguments 30 | 31 | * string `url`: HTTP or HTTPS URL to access. 32 | * table `options`: Optional options for advanced mode. 33 | * string `data`: Additional data to send as application/x-www-form-urlencoded (unless specified otherwise in Content-Type header). 34 | * string `method`: HTTP method. If absent, it's either "GET" or "POST" depending on the data field above. 35 | * table `headers`: Additional headers to add to the request as key-value pairs. 36 | 37 | ### Return values 38 | 39 | * number `code`: HTTP status code, or 0 on failure. 40 | * string `body`: HTTP response body or nil on failure. 41 | * table `headers`: HTTP response headers as key-value pairs or nil on failure or option parameter above is nil. 42 | 43 | ## Compile From Source 44 | 45 | While lua-https is bundled in LÖVE 12.0 by default, it's possible to 46 | compile the module from source and use it on earlier version of LÖVE 47 | or Lua. All compilation requires Lua 5.1 (or LuaJIT) headers and libraries. 48 | 49 | ### Linux 50 | 51 | Ensure you have CMake as well as the OpenSSL and cURL development 52 | libraries installed. 53 | 54 | ``` 55 | cmake -Bbuild -S. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$PWD/install 56 | cmake --build build --target install 57 | ``` 58 | 59 | `https.so` can be found in the `install` folder. 60 | 61 | ### Windows 62 | 63 | Compilation is done using CMake. This assume MSVC toolchain is 64 | used. Change "x64" to "Win32" to compile for x86 32-bit platform or 65 | "ARM64" for ARM64 platform. 66 | 67 | ``` 68 | cmake -Bbuild -S. -A x64 -DCMAKE_INSTALL_PREFIX=%CD%\install 69 | cmake --build build --config Release --target install 70 | ``` 71 | 72 | https.dll can be found in the install folder. 73 | 74 | ### Android 75 | 76 | Available since LÖVE 11.4 77 | Proper 3rd-party C module support requires this LÖVE version. 78 | 79 | Compilation is done by placing lua-https source code in 80 | `/love/src/jni/lua-modules`. The structure will look like this: 81 | 82 | `/love/src/jni/lua-modules/lua-https` 83 | + example 84 | + src 85 | + Android.mk 86 | + CMakeLists.txt 87 | + java.txt 88 | + license.txt 89 | 90 | Afterwards compile love-android as usual. The modules will be 91 | automatically embedded to the APK. This can be verified by checking 92 | the APK with Zip viewer application and inspecting files in 93 | lib/arm64-v8a and lib/armeabi-v7a. 94 | 95 | ## Copyright 96 | 97 | Copyright © 2019-2025 LOVE Development Team 98 | 99 | lua-https is licensed under [zLib license](license.txt), same as LÖVE. 100 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.13) 2 | 3 | ### Basic compilation settings 4 | set (CMAKE_POSITION_INDEPENDENT_CODE TRUE) 5 | set (CMAKE_CXX_STANDARD 14) 6 | 7 | include_directories ( 8 | ${CMAKE_CURRENT_SOURCE_DIR} 9 | ${CMAKE_CURRENT_BINARY_DIR} 10 | ) 11 | 12 | ### Compiler-specific flags 13 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 14 | add_compile_options ("-fvisibility=hidden") 15 | add_link_options ("-fvisibility=hidden") 16 | endif () 17 | 18 | if (LOVR) 19 | set(LUA_LIBRARIES ${LOVR_LUA}) 20 | else() 21 | ### Dependencies 22 | find_package (LuaJIT) 23 | if (LUAJIT_FOUND) 24 | set(LUA_INCLUDE_DIR ${LUAJIT_INCLUDE_DIR}) 25 | set(LUA_LIBRARIES ${LUAJIT_LIBRARY}) 26 | else () 27 | find_package (Lua 5.1 REQUIRED) 28 | endif () 29 | 30 | include_directories (${LUA_INCLUDE_DIR}) 31 | endif () 32 | 33 | 34 | 35 | ### "Libraries" 36 | add_library (https MODULE 37 | lua/main.cpp 38 | ) 39 | 40 | add_library (https-common STATIC 41 | common/HTTPS.cpp 42 | common/HTTPRequest.cpp 43 | common/HTTPSClient.cpp 44 | common/PlaintextConnection.cpp 45 | ) 46 | 47 | add_library (https-windows-libraryloader STATIC EXCLUDE_FROM_ALL 48 | windows/WindowsLibraryLoader.cpp 49 | ) 50 | 51 | add_library (https-unix-libraryloader STATIC EXCLUDE_FROM_ALL 52 | generic/UnixLibraryLoader.cpp 53 | ) 54 | 55 | add_library (https-linktime-libraryloader STATIC EXCLUDE_FROM_ALL 56 | generic/LinktimeLibraryLoader.cpp 57 | ) 58 | 59 | add_library (https-curl STATIC EXCLUDE_FROM_ALL 60 | generic/CurlClient.cpp 61 | ) 62 | 63 | add_library (https-openssl STATIC EXCLUDE_FROM_ALL 64 | generic/OpenSSLConnection.cpp 65 | ) 66 | 67 | add_library (https-schannel STATIC EXCLUDE_FROM_ALL 68 | windows/SChannelConnection.cpp 69 | ) 70 | 71 | add_library (https-nsurl STATIC EXCLUDE_FROM_ALL 72 | apple/NSURLClient.mm 73 | ) 74 | 75 | add_library (https-android STATIC EXCLUDE_FROM_ALL 76 | android/AndroidClient.cpp 77 | ) 78 | 79 | add_library (https-wininet STATIC EXCLUDE_FROM_ALL 80 | windows/WinINetClient.cpp 81 | ) 82 | 83 | ### Flags 84 | set (LIBRARY_LOADER_DEFAULT "unix") 85 | 86 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") 87 | option (USE_CURL_BACKEND "Use the libcurl backend" ON) 88 | option (USE_OPENSSL_BACKEND "Use the openssl backend" ON) 89 | option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF) 90 | option (USE_NSURL_BACKEND "Use the NSUrl backend (apple-only)" OFF) 91 | option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF) 92 | option (USE_WININET_BACKEND "Use the WinINet backend (windows-only)" OFF) 93 | 94 | option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF) 95 | elseif (WIN32) 96 | option (USE_CURL_BACKEND "Use the libcurl backend" OFF) 97 | option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF) 98 | option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" ON) 99 | option (USE_NSURL_BACKEND "Use the NSUrl backend (apple-only)" OFF) 100 | option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF) 101 | 102 | if (CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") 103 | option (USE_WININET_BACKEND "Use the WinINet backend (windows-only)" OFF) 104 | else () 105 | option (USE_WININET_BACKEND "Use the WinINet backend (windows-only)" ON) 106 | endif () 107 | 108 | option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" ON) 109 | 110 | set (LIBRARY_LOADER_DEFAULT "windows") 111 | 112 | # Windows needs to link with Lua libraries 113 | target_link_libraries(https ${LUA_LIBRARIES}) 114 | elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") 115 | option (USE_CURL_BACKEND "Use the libcurl backend" OFF) 116 | option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF) 117 | option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF) 118 | option (USE_NSURL_BACKEND "Use the NSUrl backend (apple-only)" ON) 119 | 120 | # Enabling ARC for apple/NSURLClient.mm 121 | set_source_files_properties(apple/NSURLClient.mm PROPERTIES COMPILE_FLAGS "-fobjc-arc") 122 | 123 | option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" OFF) 124 | option (USE_WININET_BACKEND "Use the WinINet backend (windows-only)" OFF) 125 | 126 | option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF) 127 | 128 | if (NOT IOS) 129 | # macOS needs -undefined dynamic_lookup 130 | target_link_options(https PRIVATE -undefined dynamic_lookup) 131 | else () 132 | target_link_libraries(https ${LUA_LIBRARIES}) 133 | endif () 134 | elseif (ANDROID) 135 | option (USE_CURL_BACKEND "Use the libcurl backend" OFF) 136 | option (USE_OPENSSL_BACKEND "Use the openssl backend" OFF) 137 | option (USE_SCHANNEL_BACKEND "Use the schannel backend (windows-only)" OFF) 138 | option (USE_NSURL_BACKEND "Use the NSUrl backend (apple-only)" OFF) 139 | option (USE_ANDROID_BACKEND "Use the Android Java backend (Android-only)" ON) 140 | option (USE_WININET_BACKEND "Use the WinINet backend (windows-only)" OFF) 141 | 142 | option (USE_WINSOCK "Use winsock instead of BSD sockets (windows-only)" OFF) 143 | 144 | # Android needs to link with Lua libraries 145 | target_link_libraries(https ${LUA_LIBRARIES}) 146 | endif () 147 | option (DEBUG_SCHANNEL "Enable debug output in schannel backend" OFF) 148 | set (LIBRARY_LOADER ${LIBRARY_LOADER_DEFAULT} CACHE STRING "Which method to use to dynamically load libraries") 149 | set_property (CACHE LIBRARY_LOADER PROPERTY STRINGS "unix;windows;linktime") 150 | 151 | set_target_properties(https PROPERTIES PREFIX "") 152 | 153 | target_link_libraries (https https-common) 154 | 155 | if (USE_CURL_BACKEND) 156 | set(HTTPS_BACKEND_CURL ON) 157 | find_package (CURL REQUIRED) 158 | include_directories (${CURL_INCLUDE_DIRS}) 159 | target_link_libraries (https https-curl) 160 | target_link_libraries (https-linktime-libraryloader ${CURL_LIBRARY}) 161 | endif () 162 | 163 | if (USE_OPENSSL_BACKEND) 164 | set(HTTPS_BACKEND_OPENSSL ON) 165 | find_package (OpenSSL REQUIRED) 166 | include_directories (${OPENSSL_INCLUDE_DIR}) 167 | target_link_libraries (https https-openssl) 168 | endif () 169 | 170 | if (USE_SCHANNEL_BACKEND) 171 | set(HTTPS_BACKEND_SCHANNEL ON) 172 | target_link_libraries (https https-schannel ws2_32 secur32) 173 | endif () 174 | 175 | if (USE_NSURL_BACKEND) 176 | set(HTTPS_BACKEND_NSURL ON) 177 | set_target_properties( 178 | https-nsurl 179 | PROPERTIES 180 | MACOSX_BUNDLE YES 181 | XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES 182 | ) 183 | target_link_libraries (https "-framework Foundation") 184 | target_link_libraries (https https-nsurl) 185 | endif () 186 | 187 | if (USE_ANDROID_BACKEND) 188 | set(HTTPS_BACKEND_ANDROID ON) 189 | target_link_libraries (https https-android) 190 | message(STATUS "Ensure to add the Java files to your project too!") 191 | endif () 192 | 193 | if (USE_WININET_BACKEND) 194 | set(HTTPS_BACKEND_WININET ON) 195 | target_link_libraries (https https-wininet wininet) 196 | endif () 197 | 198 | if (USE_WINSOCK) 199 | set(HTTPS_USE_WINSOCK ON) 200 | endif () 201 | 202 | if ("${LIBRARY_LOADER}" STREQUAL "unix") 203 | set(HTTPS_LIBRARY_LOADER_UNIX ON) 204 | target_link_libraries (https https-unix-libraryloader) 205 | elseif ("${LIBRARY_LOADER}" STREQUAL "windows") 206 | set(HTTPS_LIBRARY_LOADER_WINDOWS ON) 207 | target_link_libraries (https https-windows-libraryloader) 208 | elseif ("${LIBRARY_LOADER}" STREQUAL "linktime") 209 | set(HTTPS_LIBRARY_LOADER_LINKTIME ON) 210 | target_link_libraries (https https-linktime-libraryloader) 211 | else () 212 | message(WARNING "No library loader selected, backends that depend on dynamic loading will be broken") 213 | endif () 214 | 215 | ### Generate config-generated.h 216 | add_compile_definitions(HTTPS_HAVE_CONFIG_GENERATED_H) 217 | configure_file ( 218 | common/config-generated.h.in 219 | common/config-generated.h 220 | ) 221 | 222 | ### Install target 223 | install(TARGETS https DESTINATION .) 224 | -------------------------------------------------------------------------------- /src/android/AndroidClient.cpp: -------------------------------------------------------------------------------- 1 | #include "AndroidClient.h" 2 | 3 | #ifdef HTTPS_BACKEND_ANDROID 4 | 5 | #include 6 | #include 7 | 8 | #include "../common/LibraryLoader.h" 9 | 10 | // We want std::string that contains null byte, hence length of 1. 11 | // NOLINTNEXTLINE 12 | static std::string null("", 1); 13 | 14 | static std::string replace(const std::string &str, const std::string &from, const std::string &to) 15 | { 16 | std::stringstream ss; 17 | size_t oldpos = 0; 18 | 19 | while (true) 20 | { 21 | size_t pos = str.find(from, oldpos); 22 | 23 | if (pos == std::string::npos) 24 | { 25 | ss << str.substr(oldpos); 26 | break; 27 | } 28 | 29 | ss << str.substr(oldpos, pos - oldpos) << to; 30 | oldpos = pos + from.length(); 31 | } 32 | 33 | return ss.str(); 34 | } 35 | 36 | static jstring newStringUTF(JNIEnv *env, const std::string &str) 37 | { 38 | std::string newStr = replace(str, null, "\xC0\x80"); 39 | jstring jstr = env->NewStringUTF(newStr.c_str()); 40 | return jstr; 41 | } 42 | 43 | static std::string getStringUTF(JNIEnv *env, jstring str) 44 | { 45 | const char *c = env->GetStringUTFChars(str, nullptr); 46 | std::string result = replace(c, "\xC0\x80", null); 47 | 48 | env->ReleaseStringUTFChars(str, c); 49 | return result; 50 | } 51 | 52 | AndroidClient::AndroidClient() 53 | : HTTPSClient() 54 | { 55 | LibraryLoader::handle *library = LibraryLoader::GetCurrentProcessHandle(); 56 | 57 | // Look for SDL_GetAndroidJNIEnv and SDL_GetAndroidActivity (SDL3) 58 | if (!( 59 | LibraryLoader::LoadSymbol(SDL_AndroidGetJNIEnv, library, "SDL_GetAndroidJNIEnv") && 60 | LibraryLoader::LoadSymbol(SDL_AndroidGetActivity, library, "SDL_GetAndroidActivity") 61 | )) 62 | { 63 | // Probably running SDL2. 64 | LibraryLoader::LoadSymbol(SDL_AndroidGetJNIEnv, library, "SDL_AndroidGetJNIEnv"); 65 | LibraryLoader::LoadSymbol(SDL_AndroidGetActivity, library, "SDL_AndroidGetActivity"); 66 | } 67 | } 68 | 69 | bool AndroidClient::valid() const 70 | { 71 | if (SDL_AndroidGetJNIEnv && SDL_AndroidGetActivity) 72 | { 73 | JNIEnv *env = SDL_AndroidGetJNIEnv(); 74 | 75 | if (env) 76 | { 77 | jclass httpsClass = getHTTPSClass(); 78 | if (env->ExceptionCheck()) 79 | { 80 | env->ExceptionClear(); 81 | return false; 82 | } 83 | 84 | env->DeleteLocalRef(httpsClass); 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req) 93 | { 94 | JNIEnv *env = SDL_AndroidGetJNIEnv(); 95 | jclass httpsClass = getHTTPSClass(); 96 | 97 | if (httpsClass == nullptr) 98 | { 99 | env->ExceptionClear(); 100 | throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'"); 101 | } 102 | 103 | jmethodID constructor = env->GetMethodID(httpsClass, "", "()V"); 104 | jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V"); 105 | jmethodID setMethod = env->GetMethodID(httpsClass, "setMethod", "(Ljava/lang/String;)V"); 106 | jmethodID request = env->GetMethodID(httpsClass, "request", "()Z"); 107 | jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;"); 108 | jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B"); 109 | jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I"); 110 | 111 | jobject httpsObject = env->NewObject(httpsClass, constructor); 112 | 113 | // Set URL 114 | jstring url = env->NewStringUTF(req.url.c_str()); 115 | env->CallVoidMethod(httpsObject, setURL, url); 116 | env->DeleteLocalRef(url); 117 | 118 | // Set method 119 | jstring method = env->NewStringUTF(req.method.c_str()); 120 | env->CallVoidMethod(httpsObject, setMethod, method); 121 | env->DeleteLocalRef(method); 122 | 123 | // Set post data 124 | if (!req.postdata.empty()) 125 | { 126 | jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V"); 127 | jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length()); 128 | jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr); 129 | 130 | // The usage of memcpy is intentional. 131 | // NOLINTNEXTLINE 132 | memcpy(byteArrayData, req.postdata.data(), req.postdata.length()); 133 | env->ReleaseByteArrayElements(byteArray, byteArrayData, 0); 134 | 135 | env->CallVoidMethod(httpsObject, setPostData, byteArray); 136 | env->DeleteLocalRef(byteArray); 137 | } 138 | 139 | // Set headers 140 | if (!req.headers.empty()) 141 | { 142 | jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V"); 143 | 144 | for (auto &header : req.headers) 145 | { 146 | jstring headerKey = newStringUTF(env, header.first); 147 | jstring headerValue = newStringUTF(env, header.second); 148 | 149 | env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue); 150 | env->DeleteLocalRef(headerKey); 151 | env->DeleteLocalRef(headerValue); 152 | } 153 | } 154 | 155 | // Do request 156 | HTTPSClient::Reply response; 157 | jboolean status = env->CallBooleanMethod(httpsObject, request); 158 | 159 | // Get response 160 | response.responseCode = env->CallIntMethod(httpsObject, getResponseCode); 161 | 162 | if (status) 163 | { 164 | // Get headers 165 | jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders); 166 | int headerLen = env->GetArrayLength(interleavedHeaders); 167 | 168 | for (int i = 0; i < headerLen; i += 2) 169 | { 170 | jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i); 171 | jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1); 172 | 173 | response.headers[getStringUTF(env, key)] = getStringUTF(env, value); 174 | 175 | env->DeleteLocalRef(key); 176 | env->DeleteLocalRef(value); 177 | } 178 | 179 | env->DeleteLocalRef(interleavedHeaders); 180 | 181 | // Get response data 182 | jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse); 183 | 184 | if (responseData) 185 | { 186 | int responseLen = env->GetArrayLength(responseData); 187 | jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr); 188 | 189 | response.body = std::string((char *) responseByte, responseLen); 190 | 191 | env->DeleteLocalRef(responseData); 192 | } 193 | } 194 | 195 | env->DeleteLocalRef(httpsObject); 196 | 197 | return response; 198 | } 199 | 200 | jclass AndroidClient::getHTTPSClass() const 201 | { 202 | JNIEnv *env = SDL_AndroidGetJNIEnv(); 203 | 204 | jclass classLoaderClass = env->FindClass("java/lang/ClassLoader"); 205 | jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); 206 | 207 | jobject activity = SDL_AndroidGetActivity(); 208 | 209 | if (activity == nullptr) 210 | return nullptr; 211 | 212 | jclass gameActivity = env->GetObjectClass(activity); 213 | jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;"); 214 | jobject classLoader = env->CallObjectMethod(activity, getLoader); 215 | 216 | jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS"); 217 | jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName); 218 | 219 | env->DeleteLocalRef(gameActivity); 220 | env->DeleteLocalRef(httpsClassName); 221 | env->DeleteLocalRef(activity); 222 | env->DeleteLocalRef(classLoaderClass); 223 | 224 | return httpsClass; 225 | } 226 | 227 | #endif // HTTPS_BACKEND_ANDROID 228 | -------------------------------------------------------------------------------- /src/android/AndroidClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_ANDROID 6 | 7 | #include 8 | 9 | #include "../common/HTTPSClient.h" 10 | 11 | class AndroidClient: public HTTPSClient 12 | { 13 | public: 14 | AndroidClient(); 15 | 16 | bool valid() const override; 17 | HTTPSClient::Reply request(const HTTPSClient::Request &req) override; 18 | 19 | private: 20 | JNIEnv *(*SDL_AndroidGetJNIEnv)(); 21 | jobject (*SDL_AndroidGetActivity)(); 22 | 23 | jclass getHTTPSClass() const; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/android/java/org/love2d/luahttps/LuaHTTPS.java: -------------------------------------------------------------------------------- 1 | package org.love2d.luahttps; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.Keep; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.net.HttpURLConnection; 13 | import java.net.MalformedURLException; 14 | import java.net.ProtocolException; 15 | import java.net.URL; 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | @Keep 22 | class LuaHTTPS { 23 | static private String TAG = "LuaHTTPS"; 24 | 25 | private String urlString; 26 | private String method; 27 | private byte[] postData; 28 | private byte[] response; 29 | private int responseCode; 30 | private HashMap headers; 31 | 32 | public LuaHTTPS() { 33 | headers = new HashMap(); 34 | reset(); 35 | } 36 | 37 | public void reset() { 38 | urlString = null; 39 | method = "GET"; 40 | postData = null; 41 | response = null; 42 | responseCode = 0; 43 | headers.clear(); 44 | } 45 | 46 | @Keep 47 | public void setUrl(String url) { 48 | urlString = url; 49 | } 50 | 51 | @Keep 52 | public void setPostData(byte[] postData) { 53 | this.postData = postData; 54 | } 55 | 56 | @Keep 57 | public void setMethod(String method) { 58 | this.method = method.toUpperCase(); 59 | } 60 | 61 | @Keep 62 | public void addHeader(String key, String value) { 63 | headers.put(key, value); 64 | } 65 | 66 | @Keep 67 | public String[] getInterleavedHeaders() { 68 | ArrayList resultInterleaved = new ArrayList(); 69 | 70 | for (Map.Entry header: headers.entrySet()) { 71 | String key = header.getKey(); 72 | String value = header.getValue(); 73 | 74 | if (key != null && value != null) { 75 | resultInterleaved.add(key); 76 | resultInterleaved.add(value); 77 | } 78 | } 79 | 80 | String[] result = new String[resultInterleaved.size()]; 81 | resultInterleaved.toArray(result); 82 | return result; 83 | } 84 | 85 | @Keep 86 | public int getResponseCode() { 87 | return responseCode; 88 | } 89 | 90 | @Keep 91 | public byte[] getResponse() { 92 | return response; 93 | } 94 | 95 | @Keep 96 | public boolean request() { 97 | if (urlString == null) { 98 | return false; 99 | } 100 | 101 | URL url; 102 | try { 103 | url = new URL(urlString); 104 | 105 | if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) { 106 | return false; 107 | } 108 | } catch (MalformedURLException e) { 109 | Log.e(TAG, "Error", e); 110 | return false; 111 | } 112 | 113 | HttpURLConnection connection; 114 | try { 115 | connection = (HttpURLConnection) url.openConnection(); 116 | } catch (IOException e) { 117 | Log.e(TAG, "Error", e); 118 | return false; 119 | } 120 | 121 | // Set request method 122 | try { 123 | connection.setRequestMethod(method); 124 | } catch (ProtocolException e) { 125 | Log.e(TAG, "Error", e); 126 | return false; 127 | } 128 | 129 | // Set header 130 | for (Map.Entry headerData: headers.entrySet()) { 131 | connection.setRequestProperty(headerData.getKey(), headerData.getValue()); 132 | } 133 | 134 | // Set post data 135 | if (postData != null && canSendData()) { 136 | connection.setDoOutput(true); 137 | 138 | try { 139 | OutputStream out = connection.getOutputStream(); 140 | out.write(postData); 141 | } catch (Exception e) { 142 | Log.e(TAG, "Error", e); 143 | connection.disconnect(); 144 | return false; 145 | } 146 | } 147 | 148 | // Request 149 | try { 150 | InputStream in; 151 | 152 | // Set response code 153 | responseCode = connection.getResponseCode(); 154 | if (responseCode >= 400) { 155 | in = connection.getErrorStream(); 156 | } else { 157 | in = connection.getInputStream(); 158 | } 159 | 160 | // Read response 161 | int readed; 162 | byte[] temp = new byte[4096]; 163 | ByteArrayOutputStream response = new ByteArrayOutputStream(); 164 | 165 | while ((readed = in.read(temp)) != -1) { 166 | response.write(temp, 0, readed); 167 | } 168 | 169 | this.response = response.toByteArray(); 170 | response.close(); 171 | 172 | // Read headers 173 | headers.clear(); 174 | for (Map.Entry> header: connection.getHeaderFields().entrySet()) { 175 | headers.put(header.getKey(), TextUtils.join(", ", header.getValue())); 176 | } 177 | } catch (Exception e) { 178 | Log.e(TAG, "Error", e); 179 | connection.disconnect(); 180 | return false; 181 | } 182 | 183 | connection.disconnect(); 184 | return true; 185 | } 186 | 187 | private boolean canSendData() { 188 | return !method.equals("GET") && !method.equals("HEAD"); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/apple/NSURLClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_NSURL 6 | 7 | #include "../common/HTTPSClient.h" 8 | 9 | class NSURLClient : public HTTPSClient 10 | { 11 | public: 12 | virtual bool valid() const override; 13 | virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; 14 | }; 15 | 16 | #endif // HTTPS_BACKEND_NSURL 17 | -------------------------------------------------------------------------------- /src/apple/NSURLClient.mm: -------------------------------------------------------------------------------- 1 | #include "NSURLClient.h" 2 | 3 | #ifdef HTTPS_BACKEND_NSURL 4 | 5 | #import 6 | 7 | #if ! __has_feature(objc_arc) 8 | #error "ARC is off" 9 | #endif 10 | 11 | bool NSURLClient::valid() const 12 | { 13 | return true; 14 | } 15 | 16 | static std::string toCppString(NSData *data) 17 | { 18 | return std::string((const char*) data.bytes, (size_t) data.length); 19 | } 20 | 21 | static std::string toCppString(NSString *str) 22 | { 23 | return std::string([str UTF8String]); 24 | } 25 | 26 | HTTPSClient::Reply NSURLClient::request(const HTTPSClient::Request &req) 27 | { @autoreleasepool { 28 | NSURL *url = [NSURL URLWithString:@(req.url.c_str())]; 29 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 30 | 31 | NSData *bodydata = nil; 32 | [request setHTTPMethod:@(req.method.c_str())]; 33 | 34 | if (req.postdata.size() > 0 && (req.method != "GET" && req.method != "HEAD")) 35 | { 36 | bodydata = [NSData dataWithBytesNoCopy:(void*) req.postdata.data() length:req.postdata.size() freeWhenDone:NO]; 37 | [request setHTTPBody:bodydata]; 38 | } 39 | 40 | for (auto &header : req.headers) 41 | [request setValue:@(header.second.c_str()) forHTTPHeaderField:@(header.first.c_str())]; 42 | 43 | __block NSHTTPURLResponse *response = nil; 44 | __block NSError *error = nil; 45 | __block NSData *body = nil; 46 | 47 | dispatch_semaphore_t sem = dispatch_semaphore_create(0); 48 | 49 | NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request 50 | completionHandler:^(NSData *data, NSURLResponse *resp, NSError *err) { 51 | body = data; 52 | response = (NSHTTPURLResponse *)resp; 53 | error = err; 54 | dispatch_semaphore_signal(sem); 55 | }]; 56 | 57 | [task resume]; 58 | 59 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 60 | 61 | HTTPSClient::Reply reply; 62 | reply.responseCode = 0; 63 | 64 | if (body) 65 | { 66 | reply.body = toCppString(body); 67 | } 68 | 69 | if (response) 70 | { 71 | reply.responseCode = [response statusCode]; 72 | 73 | NSDictionary *headers = [response allHeaderFields]; 74 | for (NSString *key in headers) 75 | { 76 | NSString *value = headers[key]; 77 | reply.headers[toCppString(key)] = toCppString(value); 78 | } 79 | } 80 | 81 | if (reply.responseCode == 0 && body == nil && error != nil) 82 | { 83 | reply.body = toCppString(error.localizedDescription); 84 | } 85 | 86 | return reply; 87 | }} 88 | 89 | #endif // HTTPS_BACKEND_NSURL 90 | -------------------------------------------------------------------------------- /src/common/Connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Connection 7 | { 8 | public: 9 | virtual bool connect(const std::string &hostname, uint16_t port) = 0; 10 | virtual size_t read(char *buffer, size_t size) = 0; 11 | virtual size_t write(const char *buffer, size_t size) = 0; 12 | virtual void close() = 0; 13 | virtual ~Connection() {}; 14 | }; 15 | -------------------------------------------------------------------------------- /src/common/ConnectionClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "HTTPSClient.h" 4 | #include "HTTPRequest.h" 5 | #include "Connection.h" 6 | 7 | template 8 | class ConnectionClient : public HTTPSClient 9 | { 10 | public: 11 | virtual bool valid() const override; 12 | virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; 13 | 14 | private: 15 | static Connection *factory(); 16 | }; 17 | 18 | template 19 | bool ConnectionClient::valid() const 20 | { 21 | return Connection::valid(); 22 | } 23 | 24 | template 25 | Connection *ConnectionClient::factory() 26 | { 27 | return new Connection(); 28 | } 29 | 30 | template 31 | HTTPSClient::Reply ConnectionClient::request(const HTTPSClient::Request &req) 32 | { 33 | HTTPRequest request(factory); 34 | return request.request(req); 35 | } 36 | -------------------------------------------------------------------------------- /src/common/HTTPRequest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "HTTPRequest.h" 8 | #include "PlaintextConnection.h" 9 | 10 | HTTPRequest::HTTPRequest(ConnectionFactory factory) 11 | : factory(factory) 12 | { 13 | } 14 | 15 | HTTPSClient::Reply HTTPRequest::request(const HTTPSClient::Request &req) 16 | { 17 | HTTPSClient::Reply reply; 18 | reply.responseCode = 0; 19 | 20 | auto info = parseUrl(req.url); 21 | if (!info.valid) 22 | return reply; 23 | 24 | std::unique_ptr conn; 25 | if (info.schema == "http") 26 | conn.reset(new PlaintextConnection()); 27 | else if (info.schema == "https") 28 | conn.reset(factory()); 29 | else 30 | throw std::runtime_error("Unknown url schema"); 31 | 32 | if (!conn->connect(info.hostname, info.port)) 33 | return reply; 34 | 35 | // Build the request 36 | { 37 | std::stringstream request; 38 | std::string method = req.method; 39 | bool hasData = req.postdata.length() > 0; 40 | 41 | if (method.length() == 0) 42 | method = hasData ? "POST" : "GET"; 43 | 44 | request << method << " " << info.query << " HTTP/1.1\r\n"; 45 | 46 | for (auto &header : req.headers) 47 | request << header.first << ": " << header.second << "\r\n"; 48 | 49 | request << "Connection: Close\r\n"; 50 | 51 | request << "Host: " << info.hostname << "\r\n"; 52 | 53 | if (hasData) 54 | request << "Content-Length: " << req.postdata.size() << "\r\n"; 55 | 56 | request << "\r\n"; 57 | 58 | if (hasData) 59 | request << req.postdata; 60 | 61 | // Send it 62 | std::string requestData = request.str(); 63 | conn->write(requestData.c_str(), requestData.size()); 64 | } 65 | 66 | // Now receive the reply 67 | std::stringstream response; 68 | { 69 | char buffer[8192]; 70 | 71 | while (true) 72 | { 73 | size_t read = conn->read(buffer, sizeof(buffer)); 74 | response.write(buffer, read); 75 | if (read == 0) 76 | break; 77 | } 78 | 79 | conn->close(); 80 | } 81 | 82 | reply.responseCode = 500; 83 | // And parse it 84 | { 85 | std::string protocol; 86 | response >> protocol; 87 | if (protocol != "HTTP/1.1") 88 | return reply; 89 | 90 | response >> reply.responseCode; 91 | response.ignore(std::numeric_limits::max(), '\n'); 92 | 93 | for (std::string line; getline(response, line, '\n') && line != "\r"; ) 94 | { 95 | auto sep = line.find(':'); 96 | reply.headers[line.substr(0, sep)] = line.substr(sep+1, line.size()-sep-1); 97 | } 98 | 99 | auto begin = std::istreambuf_iterator(response); 100 | auto end = std::istreambuf_iterator(); 101 | reply.body = std::string(begin, end); 102 | } 103 | 104 | return reply; 105 | } 106 | 107 | HTTPRequest::DissectedURL HTTPRequest::parseUrl(const std::string &url) 108 | { 109 | DissectedURL dis; 110 | dis.valid = false; 111 | 112 | // Schema 113 | auto schemaStart = 0; 114 | auto schemaEnd = url.find("://"); 115 | dis.schema = url.substr(schemaStart, schemaEnd-schemaStart); 116 | 117 | // Auth+Hostname+Port 118 | auto connStart = schemaEnd+3; 119 | auto connEnd = url.find('/', connStart); 120 | if (connEnd == std::string::npos) 121 | connEnd = url.size(); 122 | 123 | // TODO: Auth 124 | if (url.find("@", connStart, connEnd-connStart) != std::string::npos) 125 | return dis; 126 | 127 | // Port 128 | auto portStart = url.find(':', connStart); 129 | auto portEnd = connEnd; 130 | if (portStart == std::string::npos || portStart > portEnd) 131 | { 132 | dis.port = dis.schema == "http" ? 80 : 443; 133 | portStart = portEnd; 134 | } 135 | else 136 | dis.port = std::stoi(url.substr(portStart+1, portEnd-portStart-1)); 137 | 138 | // Hostname 139 | auto hostnameStart = connStart; 140 | auto hostnameEnd = portStart; 141 | dis.hostname = url.substr(hostnameStart, hostnameEnd-hostnameStart); 142 | 143 | // And the query 144 | dis.query = url.substr(connEnd); 145 | if (dis.query.size() == 0) 146 | dis.query = "/"; 147 | 148 | dis.valid = true; 149 | 150 | return dis; 151 | } 152 | -------------------------------------------------------------------------------- /src/common/HTTPRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "HTTPSClient.h" 6 | #include "Connection.h" 7 | 8 | class HTTPRequest 9 | { 10 | public: 11 | struct DissectedURL 12 | { 13 | bool valid; 14 | std::string schema; 15 | std::string hostname; 16 | uint16_t port; 17 | std::string query; 18 | // TODO: Auth? 19 | }; 20 | typedef std::function ConnectionFactory; 21 | 22 | HTTPRequest(ConnectionFactory factory); 23 | 24 | HTTPSClient::Reply request(const HTTPSClient::Request &req); 25 | 26 | static DissectedURL parseUrl(const std::string &url); 27 | 28 | private: 29 | ConnectionFactory factory; 30 | }; 31 | -------------------------------------------------------------------------------- /src/common/HTTPS.cpp: -------------------------------------------------------------------------------- 1 | #include "HTTPS.h" 2 | #include "config.h" 3 | #include "ConnectionClient.h" 4 | #include "LibraryLoader.h" 5 | 6 | #include 7 | 8 | #ifdef HTTPS_BACKEND_CURL 9 | # include "../generic/CurlClient.h" 10 | #endif 11 | #ifdef HTTPS_BACKEND_OPENSSL 12 | # include "../generic/OpenSSLConnection.h" 13 | #endif 14 | #ifdef HTTPS_BACKEND_SCHANNEL 15 | # include "../windows/SChannelConnection.h" 16 | #endif 17 | #ifdef HTTPS_BACKEND_NSURL 18 | # include "../apple/NSURLClient.h" 19 | #endif 20 | #ifdef HTTPS_BACKEND_ANDROID 21 | # include "../android/AndroidClient.h" 22 | #endif 23 | #ifdef HTTPS_BACKEND_WININET 24 | # include "../windows/WinINetClient.h" 25 | #endif 26 | 27 | #ifdef HTTPS_BACKEND_CURL 28 | static CurlClient curlclient; 29 | #endif 30 | #ifdef HTTPS_BACKEND_OPENSSL 31 | static ConnectionClient opensslclient; 32 | #endif 33 | #ifdef HTTPS_BACKEND_SCHANNEL 34 | static ConnectionClient schannelclient; 35 | #endif 36 | #ifdef HTTPS_BACKEND_NSURL 37 | static NSURLClient nsurlclient; 38 | #endif 39 | #ifdef HTTPS_BACKEND_ANDROID 40 | static AndroidClient androidclient; 41 | #endif 42 | #ifdef HTTPS_BACKEND_WININET 43 | static WinINetClient wininetclient; 44 | #endif 45 | 46 | static HTTPSClient *clients[] = { 47 | #ifdef HTTPS_BACKEND_CURL 48 | &curlclient, 49 | #endif 50 | #ifdef HTTPS_BACKEND_OPENSSL 51 | &opensslclient, 52 | #endif 53 | // WinINet must be above SChannel 54 | #ifdef HTTPS_BACKEND_WININET 55 | &wininetclient, 56 | #endif 57 | #ifdef HTTPS_BACKEND_SCHANNEL 58 | &schannelclient, 59 | #endif 60 | #ifdef HTTPS_BACKEND_NSURL 61 | &nsurlclient, 62 | #endif 63 | #ifdef HTTPS_BACKEND_ANDROID 64 | &androidclient, 65 | #endif 66 | nullptr, 67 | }; 68 | 69 | // Call into the library loader to make sure it is linked in 70 | static LibraryLoader::handle* dummyProcessHandle = LibraryLoader::GetCurrentProcessHandle(); 71 | 72 | HTTPSClient::Reply request(const HTTPSClient::Request &req) 73 | { 74 | for (size_t i = 0; clients[i]; ++i) 75 | { 76 | HTTPSClient &client = *clients[i]; 77 | 78 | if (client.valid()) 79 | return client.request(req); 80 | } 81 | 82 | throw std::runtime_error("No applicable HTTPS implementation found"); 83 | } 84 | -------------------------------------------------------------------------------- /src/common/HTTPS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "HTTPSClient.h" 4 | 5 | HTTPSClient::Reply request(const HTTPSClient::Request &req); 6 | -------------------------------------------------------------------------------- /src/common/HTTPSClient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "HTTPSClient.h" 5 | 6 | // This may not be the order you expect, as shorter strings always compare less, 7 | // but it's sufficient for our map 8 | bool HTTPSClient::ci_string_less::operator()(const std::string &lhs, const std::string &rhs) const 9 | { 10 | const size_t lhs_size = lhs.size(); 11 | const size_t rhs_size = rhs.size(); 12 | const size_t steps = std::min(lhs_size, rhs_size); 13 | 14 | if (lhs_size < rhs_size) 15 | return true; 16 | else if (lhs_size > rhs_size) 17 | return false; 18 | 19 | for (size_t i = 0; i < steps; ++i) 20 | { 21 | char l = std::tolower(lhs[i]); 22 | char r = std::tolower(rhs[i]); 23 | if (l < r) 24 | return true; 25 | else if (l > r) 26 | return false; 27 | } 28 | 29 | return false; 30 | } 31 | 32 | HTTPSClient::Request::Request(const std::string &url) 33 | : url(url) 34 | , method("GET") 35 | { 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/common/HTTPSClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class HTTPSClient 8 | { 9 | public: 10 | struct ci_string_less 11 | { 12 | bool operator()(const std::string &lhs, const std::string &rhs) const; 13 | }; 14 | using header_map = std::map; 15 | 16 | struct Request 17 | { 18 | Request(const std::string &url); 19 | 20 | header_map headers; 21 | std::string url; 22 | std::string postdata; 23 | std::string method; 24 | }; 25 | 26 | struct Reply 27 | { 28 | header_map headers; 29 | std::string body; 30 | int responseCode; 31 | }; 32 | 33 | virtual ~HTTPSClient() {} 34 | virtual bool valid() const = 0; 35 | virtual Reply request(const Request &req) = 0; 36 | }; 37 | -------------------------------------------------------------------------------- /src/common/LibraryLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace LibraryLoader 4 | { 5 | using handle = void; 6 | using function = void(); 7 | 8 | handle *OpenLibrary(const char *name); 9 | void CloseLibrary(handle *handle); 10 | handle* GetCurrentProcessHandle(); 11 | 12 | function *GetFunction(handle *handle, const char *name); 13 | 14 | template 15 | inline bool LoadSymbol(T& var, handle *handle, const char *name) 16 | { 17 | var = reinterpret_cast(GetFunction(handle, name)); 18 | return var != nullptr; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/PlaintextConnection.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #ifndef HTTPS_USE_WINSOCK 4 | # include 5 | # include 6 | # include 7 | # include 8 | #else 9 | # include 10 | # include 11 | #endif // HTTPS_USE_WINSOCK 12 | 13 | #include "PlaintextConnection.h" 14 | 15 | #ifdef HTTPS_USE_WINSOCK 16 | static void close(int fd) 17 | { 18 | closesocket(fd); 19 | } 20 | #endif // HTTPS_USE_WINSOCK 21 | 22 | PlaintextConnection::PlaintextConnection() 23 | : fd(-1) 24 | { 25 | #ifdef HTTPS_USE_WINSOCK 26 | static bool wsaInit = false; 27 | if (!wsaInit) 28 | { 29 | WSADATA data; 30 | WSAStartup(MAKEWORD(2, 2), &data); 31 | } 32 | #endif // HTTPS_USE_WINSOCK 33 | } 34 | 35 | PlaintextConnection::~PlaintextConnection() 36 | { 37 | if (fd != -1) 38 | ::close(fd); 39 | } 40 | 41 | bool PlaintextConnection::connect(const std::string &hostname, uint16_t port) 42 | { 43 | addrinfo hints; 44 | std::memset(&hints, 0, sizeof(hints)); 45 | hints.ai_flags = hints.ai_protocol = 0; 46 | hints.ai_family = AF_UNSPEC; 47 | hints.ai_socktype = SOCK_STREAM; 48 | 49 | addrinfo *addrs = nullptr; 50 | std::string portString = std::to_string(port); 51 | getaddrinfo(hostname.c_str(), portString.c_str(), &hints, &addrs); 52 | 53 | // Try all addresses returned 54 | bool connected = false; 55 | for (addrinfo *addr = addrs; !connected && addr; addr = addr->ai_next) 56 | { 57 | fd = socket(addr->ai_family, SOCK_STREAM, 0); 58 | connected = ::connect(fd, addr->ai_addr, addr->ai_addrlen) == 0; 59 | if (!connected) 60 | ::close(fd); 61 | } 62 | 63 | freeaddrinfo(addrs); 64 | 65 | if (!connected) 66 | { 67 | fd = -1; 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | size_t PlaintextConnection::read(char *buffer, size_t size) 75 | { 76 | auto read = ::recv(fd, buffer, size, 0); 77 | if (read < 0) 78 | read = 0; 79 | return static_cast(read); 80 | } 81 | 82 | size_t PlaintextConnection::write(const char *buffer, size_t size) 83 | { 84 | auto written = ::send(fd, buffer, size, 0); 85 | if (written < 0) 86 | written = 0; 87 | return static_cast(written); 88 | } 89 | 90 | void PlaintextConnection::close() 91 | { 92 | ::close(fd); 93 | fd = -1; 94 | } 95 | 96 | int PlaintextConnection::getFd() const 97 | { 98 | return fd; 99 | } 100 | -------------------------------------------------------------------------------- /src/common/PlaintextConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Connection.h" 4 | 5 | class PlaintextConnection : public Connection 6 | { 7 | public: 8 | PlaintextConnection(); 9 | virtual bool connect(const std::string &hostname, uint16_t port); 10 | virtual size_t read(char *buffer, size_t size); 11 | virtual size_t write(const char *buffer, size_t size); 12 | virtual void close(); 13 | virtual ~PlaintextConnection(); 14 | 15 | int getFd() const; 16 | 17 | private: 18 | int fd; 19 | }; 20 | -------------------------------------------------------------------------------- /src/common/config-generated.h.in: -------------------------------------------------------------------------------- 1 | #cmakedefine HTTPS_BACKEND_CURL 2 | #cmakedefine HTTPS_BACKEND_OPENSSL 3 | #cmakedefine HTTPS_BACKEND_SCHANNEL 4 | #cmakedefine HTTPS_BACKEND_NSURL 5 | #cmakedefine HTTPS_BACKEND_ANDROID 6 | #cmakedefine HTTPS_BACKEND_WININET 7 | #cmakedefine HTTPS_USE_WINSOCK 8 | #cmakedefine DEBUG_SCHANNEL 9 | #cmakedefine HTTPS_LIBRARY_LOADER_WINDOWS 10 | #cmakedefine HTTPS_LIBRARY_LOADER_UNIX 11 | #cmakedefine HTTPS_LIBRARY_LOADER_LINKTIME 12 | -------------------------------------------------------------------------------- /src/common/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // MSVC warnings 4 | #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 5 | #define _CRT_SECURE_NO_WARNINGS 6 | #endif 7 | 8 | #if defined(HTTPS_HAVE_CONFIG_GENERATED_H) 9 | #include "common/config-generated.h" 10 | #elif defined(WIN32) || defined(_WIN32) 11 | #define HTTPS_BACKEND_SCHANNEL 12 | #define HTTPS_USE_WINSOCK 13 | #define HTTPS_LIBRARY_LOADER_WINDOWS 14 | #include 15 | #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) 16 | // WinINet is only supported on desktop. 17 | #define HTTPS_BACKEND_WININET 18 | #endif 19 | // Visual Studio 2017 supports __has_include 20 | #if defined __has_include 21 | #if __has_include() 22 | #define HTTPS_BACKEND_CURL 23 | #endif 24 | #endif 25 | #elif defined(__ANDROID__) 26 | #define HTTPS_BACKEND_ANDROID 27 | #define HTTPS_LIBRARY_LOADER_UNIX 28 | #elif defined(__APPLE__) 29 | #define HTTPS_BACKEND_NSURL 30 | #define HTTPS_LIBRARY_LOADER_UNIX 31 | #elif defined(linux) || defined(__linux) || defined(__linux__) 32 | #define HTTPS_LIBRARY_LOADER_UNIX 33 | 34 | #if defined __has_include 35 | #if __has_include() 36 | #define HTTPS_BACKEND_CURL 37 | #endif 38 | #if __has_include() 39 | #define HTTPS_BACKEND_OPENSSL 40 | #endif 41 | #else 42 | // Hope for the best... 43 | #define HTTPS_BACKEND_CURL 44 | #define HTTPS_BACKEND_OPENSSL 45 | #endif 46 | #endif 47 | 48 | #if defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__) 49 | #define HTTPS_DLLEXPORT __declspec(dllexport) 50 | #elif defined(__GNUC__) || defined(__clang__) 51 | #define HTTPS_DLLEXPORT __attribute__ ((visibility("default"))) 52 | #else 53 | #define HTTPS_DLLEXPORT 54 | #endif 55 | -------------------------------------------------------------------------------- /src/generic/CurlClient.cpp: -------------------------------------------------------------------------------- 1 | #include "CurlClient.h" 2 | 3 | #ifdef HTTPS_BACKEND_CURL 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct StringReader 11 | { 12 | const std::string *str; 13 | size_t pos; 14 | } StringReader; 15 | 16 | CurlClient::Curl::Curl() 17 | : handle(nullptr) 18 | , loaded(false) 19 | , global_cleanup(nullptr) 20 | , easy_init(nullptr) 21 | , easy_cleanup(nullptr) 22 | , easy_setopt(nullptr) 23 | , easy_perform(nullptr) 24 | , easy_getinfo(nullptr) 25 | , slist_append(nullptr) 26 | , slist_free_all(nullptr) 27 | { 28 | using namespace LibraryLoader; 29 | 30 | #ifdef _WIN32 31 | handle = OpenLibrary("libcurl.dll"); 32 | #else 33 | handle = OpenLibrary("libcurl.so.4"); 34 | #endif 35 | if (!handle) 36 | return; 37 | 38 | // Load symbols 39 | decltype(&curl_global_init) global_init = nullptr; 40 | if (!LoadSymbol(global_init, handle, "curl_global_init")) 41 | return; 42 | if (!LoadSymbol(global_cleanup, handle, "curl_global_cleanup")) 43 | return; 44 | if (!LoadSymbol(easy_init, handle, "curl_easy_init")) 45 | return; 46 | if (!LoadSymbol(easy_cleanup, handle, "curl_easy_cleanup")) 47 | return; 48 | if (!LoadSymbol(easy_setopt, handle, "curl_easy_setopt")) 49 | return; 50 | if (!LoadSymbol(easy_perform, handle, "curl_easy_perform")) 51 | return; 52 | if (!LoadSymbol(easy_getinfo, handle, "curl_easy_getinfo")) 53 | return; 54 | if (!LoadSymbol(slist_append, handle, "curl_slist_append")) 55 | return; 56 | if (!LoadSymbol(slist_free_all, handle, "curl_slist_free_all")) 57 | return; 58 | 59 | global_init(CURL_GLOBAL_DEFAULT); 60 | loaded = true; 61 | } 62 | 63 | CurlClient::Curl::~Curl() 64 | { 65 | if (loaded) 66 | global_cleanup(); 67 | 68 | if (handle) 69 | LibraryLoader::CloseLibrary(handle); 70 | } 71 | 72 | static char toUppercase(char c) 73 | { 74 | int ch = (unsigned char) c; 75 | return toupper(ch); 76 | } 77 | 78 | static size_t stringReader(char *ptr, size_t size, size_t nmemb, StringReader *reader) 79 | { 80 | const char *data = reader->str->data(); 81 | size_t len = reader->str->length(); 82 | size_t maxCount = (len - reader->pos) / size; 83 | size_t desiredCount = std::min(maxCount, nmemb); 84 | size_t desiredBytes = desiredCount * size; 85 | 86 | std::copy(data + reader->pos, data + desiredBytes, ptr); 87 | reader->pos += desiredBytes; 88 | 89 | return desiredCount; 90 | } 91 | 92 | static size_t stringstreamWriter(char *ptr, size_t size, size_t nmemb, std::stringstream *ss) 93 | { 94 | size_t count = size*nmemb; 95 | ss->write(ptr, count); 96 | return count; 97 | } 98 | 99 | static size_t headerWriter(char *ptr, size_t size, size_t nmemb, std::map *userdata) 100 | { 101 | std::map &headers = *userdata; 102 | size_t count = size*nmemb; 103 | std::string line(ptr, count); 104 | size_t split = line.find(':'); 105 | size_t newline = line.find('\r'); 106 | if (newline == std::string::npos) 107 | newline = line.size(); 108 | 109 | if (split != std::string::npos) 110 | headers[line.substr(0, split)] = line.substr(split+1, newline-split-1); 111 | return count; 112 | } 113 | 114 | bool CurlClient::valid() const 115 | { 116 | return curl.loaded; 117 | } 118 | 119 | HTTPSClient::Reply CurlClient::request(const HTTPSClient::Request &req) 120 | { 121 | Reply reply; 122 | reply.responseCode = 0; 123 | 124 | // Use sensible default header for later 125 | HTTPSClient::header_map newHeaders = req.headers; 126 | 127 | CURL *handle = curl.easy_init(); 128 | if (!handle) 129 | throw std::runtime_error("Could not create curl request"); 130 | 131 | curl.easy_setopt(handle, CURLOPT_URL, req.url.c_str()); 132 | curl.easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); 133 | curl.easy_setopt(handle, CURLOPT_CUSTOMREQUEST, req.method.c_str()); 134 | 135 | StringReader reader {}; 136 | 137 | if (req.postdata.size() > 0 && (req.method != "GET" && req.method != "HEAD")) 138 | { 139 | reader.str = &req.postdata; 140 | reader.pos = 0; 141 | curl.easy_setopt(handle, CURLOPT_UPLOAD, 1L); 142 | curl.easy_setopt(handle, CURLOPT_READFUNCTION, stringReader); 143 | curl.easy_setopt(handle, CURLOPT_READDATA, &reader); 144 | curl.easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, (curl_off_t) req.postdata.length()); 145 | } 146 | 147 | if (req.method == "HEAD") 148 | curl.easy_setopt(handle, CURLOPT_NOBODY, 1L); 149 | 150 | // Curl doesn't copy memory, keep the strings around 151 | std::vector lines; 152 | for (auto &header : newHeaders) 153 | { 154 | std::stringstream line; 155 | line << header.first << ": " << header.second; 156 | lines.push_back(line.str()); 157 | } 158 | 159 | curl_slist *sendHeaders = nullptr; 160 | for (auto &line : lines) 161 | sendHeaders = curl.slist_append(sendHeaders, line.c_str()); 162 | 163 | if (sendHeaders) 164 | curl.easy_setopt(handle, CURLOPT_HTTPHEADER, sendHeaders); 165 | 166 | std::stringstream body; 167 | 168 | curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, stringstreamWriter); 169 | curl.easy_setopt(handle, CURLOPT_WRITEDATA, &body); 170 | 171 | curl.easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerWriter); 172 | curl.easy_setopt(handle, CURLOPT_HEADERDATA, &reply.headers); 173 | 174 | curl.easy_perform(handle); 175 | 176 | if (sendHeaders) 177 | curl.slist_free_all(sendHeaders); 178 | 179 | { 180 | long responseCode; 181 | curl.easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); 182 | reply.responseCode = (int) responseCode; 183 | } 184 | 185 | reply.body = body.str(); 186 | 187 | curl.easy_cleanup(handle); 188 | return reply; 189 | } 190 | 191 | CurlClient::Curl CurlClient::curl; 192 | 193 | #endif // HTTPS_BACKEND_CURL 194 | -------------------------------------------------------------------------------- /src/generic/CurlClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_CURL 6 | 7 | #include 8 | 9 | #include "../common/HTTPSClient.h" 10 | #include "../common/LibraryLoader.h" 11 | 12 | class CurlClient : public HTTPSClient 13 | { 14 | public: 15 | virtual bool valid() const override; 16 | virtual HTTPSClient::Reply request(const HTTPSClient::Request &req) override; 17 | 18 | private: 19 | static struct Curl 20 | { 21 | Curl(); 22 | ~Curl(); 23 | LibraryLoader::handle *handle; 24 | bool loaded; 25 | 26 | decltype(&curl_global_cleanup) global_cleanup; 27 | 28 | decltype(&curl_easy_init) easy_init; 29 | decltype(&curl_easy_cleanup) easy_cleanup; 30 | decltype(&curl_easy_setopt) easy_setopt; 31 | decltype(&curl_easy_perform) easy_perform; 32 | decltype(&curl_easy_getinfo) easy_getinfo; 33 | 34 | decltype(&curl_slist_append) slist_append; 35 | decltype(&curl_slist_free_all) slist_free_all; 36 | } curl; 37 | }; 38 | 39 | #endif // HTTPS_BACKEND_CURL 40 | -------------------------------------------------------------------------------- /src/generic/LinktimeLibraryLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "../common/config.h" 2 | #include "../common/LibraryLoader.h" 3 | 4 | #ifdef HTTPS_LIBRARY_LOADER_LINKTIME 5 | 6 | #include 7 | 8 | #ifdef HTTPS_BACKEND_CURL 9 | #include 10 | 11 | static char CurlHandle; 12 | #endif 13 | 14 | #if defined(HTTPS_BACKEND_OPENSSL) || defined(HTTPS_BACKEND_ANDROID) 15 | # error "Selected backends that are not compatible with this loader" 16 | #endif 17 | 18 | namespace LibraryLoader 19 | { 20 | handle *OpenLibrary(const char *name) 21 | { 22 | #ifdef HTTPS_BACKEND_CURL 23 | if (strstr(name, "libcurl") == name) 24 | return reinterpret_cast(&CurlHandle); 25 | #endif 26 | return nullptr; 27 | } 28 | 29 | void CloseLibrary(handle *) 30 | { 31 | } 32 | 33 | handle* GetCurrentProcessHandle() 34 | { 35 | return nullptr; 36 | } 37 | 38 | function *GetFunction(handle *handle, const char *name) 39 | { 40 | #define RETURN_MATCHING_FUNCTION(func) \ 41 | if (strcmp(name, #func) == 0) \ 42 | return reinterpret_cast(&func); 43 | 44 | #ifdef HTTPS_BACKEND_CURL 45 | if (handle == &CurlHandle) 46 | { 47 | RETURN_MATCHING_FUNCTION(curl_global_init); 48 | RETURN_MATCHING_FUNCTION(curl_global_cleanup); 49 | RETURN_MATCHING_FUNCTION(curl_easy_init); 50 | RETURN_MATCHING_FUNCTION(curl_easy_cleanup); 51 | RETURN_MATCHING_FUNCTION(curl_easy_setopt); 52 | RETURN_MATCHING_FUNCTION(curl_easy_perform); 53 | RETURN_MATCHING_FUNCTION(curl_easy_getinfo); 54 | RETURN_MATCHING_FUNCTION(curl_slist_append); 55 | RETURN_MATCHING_FUNCTION(curl_slist_free_all); 56 | } 57 | #endif 58 | 59 | #undef RETURN_MATCHING_FUNCTION 60 | 61 | return nullptr; 62 | } 63 | } 64 | 65 | #endif // HTTPS_LIBRARY_LOADER_LINKTIME 66 | 67 | -------------------------------------------------------------------------------- /src/generic/OpenSSLConnection.cpp: -------------------------------------------------------------------------------- 1 | #include "OpenSSLConnection.h" 2 | 3 | #ifdef HTTPS_BACKEND_OPENSSL 4 | 5 | #include "../common/LibraryLoader.h" 6 | 7 | // Not present in openssl 1.1 headers 8 | #define SSL_CTRL_OPTIONS 32 9 | 10 | static bool TryOpenLibraries(const char *sslName, LibraryLoader::handle *& sslHandle, const char *cryptoName, LibraryLoader::handle *&cryptoHandle) 11 | { 12 | sslHandle = LibraryLoader::OpenLibrary(sslName); 13 | cryptoHandle = LibraryLoader::OpenLibrary(cryptoName); 14 | 15 | if (sslHandle && cryptoHandle) 16 | return true; 17 | 18 | if (sslHandle) 19 | LibraryLoader::CloseLibrary(sslHandle); 20 | if (cryptoHandle) 21 | LibraryLoader::CloseLibrary(cryptoHandle); 22 | return false; 23 | } 24 | 25 | OpenSSLConnection::SSLFuncs::SSLFuncs() 26 | { 27 | using namespace LibraryLoader; 28 | 29 | handle *sslhandle = nullptr; 30 | handle *cryptohandle = nullptr; 31 | 32 | valid = TryOpenLibraries("libssl.so.3", sslhandle, "libcrypto.so.3", cryptohandle) 33 | || TryOpenLibraries("libssl.so.1.1", sslhandle, "libcrypto.so.1.1", cryptohandle) 34 | || TryOpenLibraries("libssl.so.1.0.0", sslhandle, "libcrypto.so.1.0.0", cryptohandle) 35 | // Try the version-less name last, it may not be compatible or tested 36 | || TryOpenLibraries("libssl.so", sslhandle, "libcrypto.so", cryptohandle); 37 | if (!valid) 38 | return; 39 | 40 | valid = true; 41 | valid = valid && ( 42 | LoadSymbol(init_ssl, sslhandle, "OPENSSL_init_ssl") || 43 | LoadSymbol(library_init, sslhandle, "SSL_library_init")); 44 | 45 | valid = valid && LoadSymbol(CTX_new, sslhandle, "SSL_CTX_new"); 46 | valid = valid && LoadSymbol(CTX_ctrl, sslhandle, "SSL_CTX_ctrl"); 47 | if (valid) 48 | LoadSymbol(CTX_set_options, sslhandle, "SSL_CTX_set_options"); 49 | valid = valid && LoadSymbol(CTX_set_verify, sslhandle, "SSL_CTX_set_verify"); 50 | valid = valid && LoadSymbol(CTX_set_default_verify_paths, sslhandle, "SSL_CTX_set_default_verify_paths"); 51 | valid = valid && LoadSymbol(CTX_free, sslhandle, "SSL_CTX_free"); 52 | 53 | valid = valid && LoadSymbol(SSL_new, sslhandle, "SSL_new"); 54 | valid = valid && LoadSymbol(SSL_free, sslhandle, "SSL_free"); 55 | valid = valid && LoadSymbol(set_fd, sslhandle, "SSL_set_fd"); 56 | valid = valid && LoadSymbol(connect, sslhandle, "SSL_connect"); 57 | valid = valid && LoadSymbol(read, sslhandle, "SSL_read"); 58 | valid = valid && LoadSymbol(write, sslhandle, "SSL_write"); 59 | valid = valid && LoadSymbol(shutdown, sslhandle, "SSL_shutdown"); 60 | valid = valid && LoadSymbol(get_verify_result, sslhandle, "SSL_get_verify_result"); 61 | valid = valid && (LoadSymbol(get_peer_certificate, sslhandle, "SSL_get1_peer_certificate") || 62 | LoadSymbol(get_peer_certificate, sslhandle, "SSL_get_peer_certificate")); 63 | 64 | valid = valid && ( 65 | LoadSymbol(SSLv23_method, sslhandle, "TLS_client_method") || 66 | LoadSymbol(SSLv23_method, sslhandle, "TLS_method") || 67 | LoadSymbol(SSLv23_method, sslhandle, "SSLv23_method")); 68 | 69 | valid = valid && LoadSymbol(check_host, cryptohandle, "X509_check_host"); 70 | valid = valid && LoadSymbol(X509_free, cryptohandle, "X509_free"); 71 | 72 | if (library_init) 73 | library_init(); 74 | else if(init_ssl) 75 | init_ssl(0, nullptr); 76 | // else not valid 77 | } 78 | 79 | bool OpenSSLConnection::valid() 80 | { 81 | return ssl.valid; 82 | } 83 | 84 | OpenSSLConnection::OpenSSLConnection() 85 | : conn(nullptr) 86 | { 87 | context = ssl.CTX_new(ssl.SSLv23_method()); 88 | if (!context) 89 | return; 90 | 91 | if (ssl.CTX_set_options) 92 | ssl.CTX_set_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); 93 | else 94 | ssl.CTX_ctrl(context, SSL_CTRL_OPTIONS, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3, nullptr); 95 | ssl.CTX_set_verify(context, SSL_VERIFY_PEER, nullptr); 96 | ssl.CTX_set_default_verify_paths(context); 97 | } 98 | 99 | OpenSSLConnection::~OpenSSLConnection() 100 | { 101 | if (conn) 102 | ssl.SSL_free(conn); 103 | 104 | if (context) 105 | ssl.CTX_free(context); 106 | } 107 | 108 | bool OpenSSLConnection::connect(const std::string &hostname, uint16_t port) 109 | { 110 | if (!context) 111 | return false; 112 | 113 | if (!socket.connect(hostname, port)) 114 | return false; 115 | 116 | conn = ssl.SSL_new(context); 117 | if (!conn) 118 | { 119 | socket.close(); 120 | return false; 121 | } 122 | 123 | ssl.set_fd(conn, socket.getFd()); 124 | if (ssl.connect(conn) != 1 || ssl.get_verify_result(conn) != X509_V_OK) 125 | { 126 | socket.close(); 127 | return false; 128 | } 129 | 130 | X509 *cert = ssl.get_peer_certificate(conn); 131 | if (ssl.check_host(cert, hostname.c_str(), hostname.size(), 0, nullptr) != 1) 132 | { 133 | close(); 134 | return false; 135 | } 136 | ssl.X509_free(cert); 137 | 138 | return true; 139 | } 140 | 141 | size_t OpenSSLConnection::read(char *buffer, size_t size) 142 | { 143 | return ssl.read(conn, buffer, (int) size); 144 | } 145 | 146 | size_t OpenSSLConnection::write(const char *buffer, size_t size) 147 | { 148 | return ssl.write(conn, buffer, (int) size); 149 | } 150 | 151 | void OpenSSLConnection::close() 152 | { 153 | ssl.shutdown(conn); 154 | socket.close(); 155 | } 156 | 157 | OpenSSLConnection::SSLFuncs OpenSSLConnection::ssl; 158 | 159 | #endif // HTTPS_BACKEND_OPENSSL 160 | -------------------------------------------------------------------------------- /src/generic/OpenSSLConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_OPENSSL 6 | 7 | #include 8 | 9 | #include "../common/Connection.h" 10 | #include "../common/PlaintextConnection.h" 11 | 12 | class OpenSSLConnection : public Connection 13 | { 14 | public: 15 | OpenSSLConnection(); 16 | virtual bool connect(const std::string &hostname, uint16_t port) override; 17 | virtual size_t read(char *buffer, size_t size) override; 18 | virtual size_t write(const char *buffer, size_t size) override; 19 | virtual void close() override; 20 | virtual ~OpenSSLConnection(); 21 | 22 | static bool valid(); 23 | 24 | private: 25 | PlaintextConnection socket; 26 | SSL_CTX *context; 27 | SSL *conn; 28 | 29 | struct SSLFuncs 30 | { 31 | SSLFuncs(); 32 | bool valid; 33 | 34 | int (*library_init)(); 35 | int (*init_ssl)(uint64_t opts, const void *settings); 36 | 37 | SSL_CTX *(*CTX_new)(const SSL_METHOD *method); 38 | long (*CTX_ctrl)(SSL_CTX *ctx, int cmd, long larg, void *parg); 39 | long (*CTX_set_options)(SSL_CTX *ctx, long options); 40 | void (*CTX_set_verify)(SSL_CTX *ctx, int mode, void *verify_callback); 41 | int (*CTX_set_default_verify_paths)(SSL_CTX *ctx); 42 | void (*CTX_free)(SSL_CTX *ctx); 43 | 44 | SSL *(*SSL_new)(SSL_CTX *ctx); 45 | void (*SSL_free)(SSL *ctx); 46 | int (*set_fd)(SSL *ssl, int fd); 47 | int (*connect)(SSL *ssl); 48 | int (*read)(SSL *ssl, void *buf, int num); 49 | int (*write)(SSL *ssl, const void *buf, int num); 50 | int (*shutdown)(SSL *ssl); 51 | long (*get_verify_result)(const SSL *ssl); 52 | X509 *(*get_peer_certificate)(const SSL *ssl); 53 | 54 | const SSL_METHOD *(*SSLv23_method)(); 55 | 56 | int (*check_host)(X509 *cert, const char *name, size_t namelen, unsigned int flags, char **peername); 57 | void (*X509_free)(X509* cert); 58 | }; 59 | static SSLFuncs ssl; 60 | }; 61 | 62 | #endif // HTTPS_BACKEND_OPENSSL 63 | -------------------------------------------------------------------------------- /src/generic/UnixLibraryLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "../common/config.h" 2 | #include "../common/LibraryLoader.h" 3 | 4 | #ifdef HTTPS_LIBRARY_LOADER_UNIX 5 | 6 | #include 7 | 8 | namespace LibraryLoader 9 | { 10 | handle *OpenLibrary(const char *name) 11 | { 12 | return dlopen(name, RTLD_LAZY); 13 | } 14 | 15 | void CloseLibrary(handle *handle) 16 | { 17 | if (handle) 18 | dlclose(handle); 19 | } 20 | 21 | handle* GetCurrentProcessHandle() 22 | { 23 | return RTLD_DEFAULT; 24 | } 25 | 26 | function *GetFunction(handle *handle, const char *name) 27 | { 28 | return reinterpret_cast(dlsym(handle, name)); 29 | } 30 | } 31 | 32 | #endif // HTTPS_LIBRARY_LOADER_UNIX 33 | 34 | -------------------------------------------------------------------------------- /src/lua/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" 5 | { 6 | #include 7 | #include 8 | } 9 | 10 | #include "../common/HTTPS.h" 11 | #include "../common/config.h" 12 | 13 | static std::string validMethod[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"}; 14 | 15 | static int str_toupper(char c) 16 | { 17 | unsigned char uc = (unsigned char) c; 18 | return toupper(uc); 19 | } 20 | 21 | static std::string w_checkstring(lua_State *L, int idx) 22 | { 23 | size_t len; 24 | const char *str = luaL_checklstring(L, idx, &len); 25 | return std::string(str, len); 26 | } 27 | 28 | static void w_pushstring(lua_State *L, const std::string &str) 29 | { 30 | lua_pushlstring(L, str.data(), str.size()); 31 | } 32 | 33 | static void w_readheaders(lua_State *L, int idx, HTTPSClient::header_map &headers) 34 | { 35 | if (idx < 0) 36 | idx += lua_gettop(L) + 1; 37 | 38 | lua_pushnil(L); 39 | while (lua_next(L, idx)) 40 | { 41 | auto header = w_checkstring(L, -2); 42 | headers[header] = w_checkstring(L, -1); 43 | lua_pop(L, 1); 44 | } 45 | lua_pop(L, 1); 46 | } 47 | 48 | static std::string w_optmethod(lua_State *L, int idx, const std::string &defaultMethod) 49 | { 50 | std::string *const validMethodEnd = validMethod + sizeof(validMethod) / sizeof(std::string); 51 | 52 | if (lua_isnoneornil(L, idx)) 53 | return defaultMethod; 54 | 55 | std::string str = w_checkstring(L, idx); 56 | std::transform(str.begin(), str.end(), str.begin(), str_toupper); 57 | 58 | if (std::find(validMethod, validMethodEnd, str) == validMethodEnd) 59 | luaL_argerror(L, idx, "expected one of \"get\", \"head\", \"post\", \"put\", \"delete\", or \"patch\""); 60 | 61 | return str; 62 | } 63 | 64 | static int w_request(lua_State *L) 65 | { 66 | auto url = w_checkstring(L, 1); 67 | HTTPSClient::Request req(url); 68 | 69 | bool advanced = false; 70 | 71 | if (lua_istable(L, 2)) 72 | { 73 | advanced = true; 74 | 75 | std::string defaultMethod = "GET"; 76 | 77 | lua_getfield(L, 2, "data"); 78 | if (!lua_isnoneornil(L, -1)) 79 | { 80 | req.postdata = w_checkstring(L, -1); 81 | req.headers["Content-Type"] = "application/x-www-form-urlencoded"; 82 | defaultMethod = "POST"; 83 | } 84 | lua_pop(L, 1); 85 | 86 | lua_getfield(L, 2, "method"); 87 | req.method = w_optmethod(L, -1, defaultMethod); 88 | lua_pop(L, 1); 89 | 90 | lua_getfield(L, 2, "headers"); 91 | if (!lua_isnoneornil(L, -1)) 92 | w_readheaders(L, -1, req.headers); 93 | lua_pop(L, 1); 94 | } 95 | 96 | HTTPSClient::Reply reply; 97 | 98 | try 99 | { 100 | reply = request(req); 101 | } 102 | catch (const std::exception& e) 103 | { 104 | std::string errorMessage = e.what(); 105 | lua_pushnil(L); 106 | lua_pushstring(L, errorMessage.c_str()); 107 | return 2; 108 | } 109 | 110 | lua_pushinteger(L, reply.responseCode); 111 | w_pushstring(L, reply.body); 112 | 113 | if (advanced) 114 | { 115 | lua_newtable(L); 116 | for (const auto &header : reply.headers) 117 | { 118 | w_pushstring(L, header.first); 119 | w_pushstring(L, header.second); 120 | lua_settable(L, -3); 121 | } 122 | } 123 | 124 | return advanced ? 3 : 2; 125 | } 126 | 127 | extern "C" int HTTPS_DLLEXPORT luaopen_https(lua_State *L) 128 | { 129 | lua_newtable(L); 130 | 131 | lua_pushcfunction(L, w_request); 132 | lua_setfield(L, -2, "request"); 133 | 134 | return 1; 135 | } 136 | -------------------------------------------------------------------------------- /src/windows/SChannelConnection.cpp: -------------------------------------------------------------------------------- 1 | #define SECURITY_WIN32 2 | #define NOMINMAX 3 | 4 | #include "SChannelConnection.h" 5 | 6 | #ifdef HTTPS_BACKEND_SCHANNEL 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef SCH_USE_STRONG_CRYPTO 17 | # define SCH_USE_STRONG_CRYPTO 0x00400000 18 | #endif 19 | #ifndef SP_PROT_TLS1_1_CLIENT 20 | # define SP_PROT_TLS1_1_CLIENT 0x00000200 21 | #endif 22 | #ifndef SP_PROT_TLS1_2_CLIENT 23 | # define SP_PROT_TLS1_2_CLIENT 0x00000800 24 | #endif 25 | 26 | #ifdef DEBUG_SCHANNEL 27 | #include 28 | std::ostream &debug = std::cout; 29 | #else 30 | struct Debug 31 | { 32 | template 33 | Debug &operator<<(const T&) { return *this; } 34 | } debug; 35 | #endif 36 | 37 | static void enqueue(std::vector &buffer, char *data, size_t size) 38 | { 39 | size_t oldSize = buffer.size(); 40 | buffer.resize(oldSize + size); 41 | memcpy(&buffer[oldSize], data, size); 42 | } 43 | 44 | static void enqueue_prepend(std::vector &buffer, char *data, size_t size) 45 | { 46 | size_t oldSize = buffer.size(); 47 | buffer.resize(oldSize + size); 48 | if (oldSize > 0) 49 | memmove(&buffer[size], &buffer[0], oldSize); 50 | memcpy(&buffer[0], data, size); 51 | } 52 | 53 | static size_t dequeue(std::vector &buffer, char *data, size_t size) 54 | { 55 | size = std::min(size, buffer.size()); 56 | size_t remaining = buffer.size() - size; 57 | 58 | memcpy(data, &buffer[0], size); 59 | 60 | if (remaining > 0) 61 | { 62 | memmove(&buffer[0], &buffer[size], remaining); 63 | buffer.resize(remaining); 64 | } 65 | else 66 | { 67 | buffer.resize(0); 68 | } 69 | 70 | return size; 71 | } 72 | 73 | SChannelConnection::SChannelConnection() 74 | : context(nullptr) 75 | { 76 | } 77 | 78 | SChannelConnection::~SChannelConnection() 79 | { 80 | destroyContext(); 81 | } 82 | 83 | SECURITY_STATUS InitializeSecurityContext(CredHandle *phCredential, std::unique_ptr& phContext, const std::string& szTargetName, ULONG fContextReq, std::vector& inputBuffer, std::vector& outputBuffer, ULONG *pfContextAttr) 84 | { 85 | std::array recvBuffers; 86 | recvBuffers[0].BufferType = SECBUFFER_TOKEN; 87 | recvBuffers[0].pvBuffer = outputBuffer.data(); 88 | recvBuffers[0].cbBuffer = outputBuffer.size(); 89 | 90 | std::array sendBuffers; 91 | sendBuffers[0].BufferType = SECBUFFER_TOKEN; 92 | sendBuffers[0].pvBuffer = inputBuffer.data(); 93 | sendBuffers[0].cbBuffer = inputBuffer.size(); 94 | sendBuffers[1].BufferType = SECBUFFER_EMPTY; 95 | sendBuffers[1].pvBuffer = nullptr; 96 | sendBuffers[1].cbBuffer = 0; 97 | 98 | SecBufferDesc recvBufferDesc, sendBufferDesc; 99 | recvBufferDesc.ulVersion = sendBufferDesc.ulVersion = SECBUFFER_VERSION; 100 | recvBufferDesc.pBuffers = &recvBuffers[0]; 101 | recvBufferDesc.cBuffers = recvBuffers.size(); 102 | 103 | if (!inputBuffer.empty()) 104 | { 105 | sendBufferDesc.pBuffers = &sendBuffers[0]; 106 | sendBufferDesc.cBuffers = sendBuffers.size(); 107 | } 108 | else 109 | { 110 | sendBufferDesc.pBuffers = nullptr; 111 | sendBufferDesc.cBuffers = 0; 112 | } 113 | 114 | CtxtHandle* phOldContext = nullptr; 115 | CtxtHandle* phNewContext = nullptr; 116 | if (!phContext) 117 | { 118 | phContext = std::make_unique(); 119 | phNewContext = phContext.get(); 120 | } 121 | else 122 | { 123 | phOldContext = phContext.get(); 124 | } 125 | 126 | auto ret = InitializeSecurityContext(phCredential, phOldContext, const_cast(szTargetName.c_str()), fContextReq, 0, 0, &sendBufferDesc, 0, phNewContext, &recvBufferDesc, pfContextAttr, nullptr); 127 | 128 | outputBuffer.resize(recvBuffers[0].cbBuffer); 129 | 130 | // Clear the input buffer, so the reader can append 131 | // If we have unprocessed data, leave it in the buffer 132 | size_t unprocessed = 0; 133 | if (sendBuffers[1].BufferType == SECBUFFER_EXTRA) 134 | unprocessed = sendBuffers[1].cbBuffer; 135 | 136 | if (unprocessed > 0) 137 | memmove(inputBuffer.data(), inputBuffer.data() + inputBuffer.size() - unprocessed, unprocessed); 138 | 139 | inputBuffer.resize(unprocessed); 140 | 141 | return ret; 142 | } 143 | 144 | bool SChannelConnection::connect(const std::string &hostname, uint16_t port) 145 | { 146 | debug << "Trying to connect to " << hostname << ":" << port << "\n"; 147 | if (!socket.connect(hostname, port)) 148 | return false; 149 | debug << "Connected\n"; 150 | 151 | SCHANNEL_CRED cred; 152 | memset(&cred, 0, sizeof(cred)); 153 | 154 | cred.dwVersion = SCHANNEL_CRED_VERSION; 155 | cred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; 156 | cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO | SCH_CRED_REVOCATION_CHECK_CHAIN; 157 | 158 | CredHandle credHandle; 159 | if (AcquireCredentialsHandle(nullptr, (char*) UNISP_NAME, SECPKG_CRED_OUTBOUND, nullptr, &cred, nullptr, nullptr, &credHandle, nullptr) != SEC_E_OK) 160 | { 161 | debug << "Failed to acquire handle\n"; 162 | socket.close(); 163 | return false; 164 | } 165 | debug << "Acquired handle\n"; 166 | 167 | 168 | static constexpr size_t bufferSize = 8192; 169 | bool done = false, success = false, contextCreated = false; 170 | 171 | ULONG contextAttr; 172 | std::unique_ptr context; 173 | std::vector inputBuffer; 174 | std::vector outputBuffer; 175 | 176 | do 177 | { 178 | outputBuffer.resize(bufferSize); 179 | 180 | bool recvData = false; 181 | bool sendData = false; 182 | auto ret = InitializeSecurityContext(&credHandle, context, hostname, ISC_REQ_STREAM, inputBuffer, outputBuffer, &contextAttr); 183 | switch (ret) 184 | { 185 | /*case SEC_I_COMPLETE_NEEDED: 186 | case SEC_I_COMPLETE_AND_CONTINUE: 187 | if (CompleteAuthToken(context.get(), &outputBuffer) != SEC_E_OK) 188 | done = true; 189 | else if (ret == SEC_I_COMPLETE_NEEDED) 190 | success = done = true; 191 | break;*/ 192 | case SEC_I_CONTINUE_NEEDED: 193 | debug << "Initialize: continue needed\n"; 194 | recvData = true; 195 | sendData = true; 196 | break; 197 | case SEC_E_INCOMPLETE_CREDENTIALS: 198 | debug << "Initialize failed: incomplete credentials\n"; 199 | done = true; 200 | break; 201 | case SEC_E_INCOMPLETE_MESSAGE: 202 | debug << "Initialize: incomplete message\n"; 203 | recvData = true; 204 | break; 205 | case SEC_E_OK: 206 | debug << "Initialize succeeded\n"; 207 | success = done = true; 208 | sendData = true; 209 | break; 210 | default: 211 | debug << "Initialize done: " << outputBuffer.size() << " bytes of output and unknown status " << ret << "\n"; 212 | done = true; 213 | success = false; 214 | break; 215 | } 216 | 217 | if (!done) 218 | contextCreated = true; 219 | 220 | if (sendData && !outputBuffer.empty()) 221 | { 222 | socket.write(outputBuffer.data(), outputBuffer.size()); 223 | debug << "Sent " << outputBuffer.size() << " bytes of data\n"; 224 | } 225 | 226 | if (recvData) 227 | { 228 | size_t unprocessed = inputBuffer.size(); 229 | inputBuffer.resize(unprocessed + bufferSize); 230 | size_t actual = socket.read(inputBuffer.data() + unprocessed, bufferSize); 231 | inputBuffer.resize(actual + unprocessed); 232 | 233 | debug << "Received " << actual << " bytes of data\n"; 234 | if (unprocessed > 0) 235 | debug << " had " << unprocessed << " bytes of remaining, unprocessed data\n"; 236 | 237 | if (actual + unprocessed == 0) 238 | { 239 | debug << "No data to submit, break\n"; 240 | break; 241 | } 242 | } 243 | } while (!done); 244 | 245 | debug << "Done!\n"; 246 | 247 | if (success) 248 | { 249 | SecPkgContext_Flags resultFlags; 250 | QueryContextAttributes(context.get(), SECPKG_ATTR_FLAGS, &resultFlags); 251 | if (resultFlags.Flags & ISC_REQ_CONFIDENTIALITY == 0) 252 | { 253 | debug << "Resulting context is not encrypted, marking as failed\n"; 254 | success = false; 255 | } 256 | if (resultFlags.Flags & ISC_REQ_INTEGRITY == 0) 257 | { 258 | debug << "Resulting context is not signed, marking as failed\n"; 259 | success = false; 260 | } 261 | } 262 | 263 | if (success) 264 | this->context = context.release(); 265 | else if (contextCreated) 266 | DeleteSecurityContext(context.get()); 267 | 268 | return success; 269 | } 270 | 271 | size_t SChannelConnection::read(char *buffer, size_t size) 272 | { 273 | if (decRecvBuffer.size() > 0) 274 | { 275 | size = dequeue(decRecvBuffer, buffer, size); 276 | debug << "Read " << size << " bytes of previously decoded data\n"; 277 | return size; 278 | } 279 | else if (encRecvBuffer.size() > 0) 280 | { 281 | size = dequeue(encRecvBuffer, buffer, size); 282 | debug << "Read " << size << " bytes of extra data\n"; 283 | } 284 | else 285 | { 286 | size = socket.read(buffer, size); 287 | debug << "Received " << size << " bytes of data\n"; 288 | } 289 | 290 | return decrypt(buffer, size); 291 | } 292 | 293 | size_t SChannelConnection::decrypt(char *buffer, size_t size, bool recurse) 294 | { 295 | if (size == 0) 296 | return 0; 297 | 298 | SecBuffer secBuffers[4]; 299 | secBuffers[0].cbBuffer = size; 300 | secBuffers[0].BufferType = SECBUFFER_DATA; 301 | secBuffers[0].pvBuffer = buffer; 302 | 303 | for (size_t i = 1; i < 4; ++i) 304 | { 305 | secBuffers[i].BufferType = SECBUFFER_EMPTY; 306 | secBuffers[i].pvBuffer = nullptr; 307 | secBuffers[i].cbBuffer = 0; 308 | } 309 | 310 | SecBufferDesc secBufferDesc; 311 | secBufferDesc.ulVersion = SECBUFFER_VERSION; 312 | secBufferDesc.cBuffers = 4; 313 | secBufferDesc.pBuffers = &secBuffers[0]; 314 | 315 | auto ret = DecryptMessage(static_cast(context), &secBufferDesc, 0, nullptr); // FIXME 316 | debug << "DecryptMessage returns: " << ret << "\n"; 317 | switch (ret) 318 | { 319 | case SEC_E_OK: 320 | { 321 | void *actualDataStart = buffer; 322 | for (size_t i = 0; i < 4; ++i) 323 | { 324 | auto &buffer = secBuffers[i]; 325 | if (buffer.BufferType == SECBUFFER_DATA) 326 | { 327 | actualDataStart = buffer.pvBuffer; 328 | size = buffer.cbBuffer; 329 | } 330 | else if (buffer.BufferType == SECBUFFER_EXTRA) 331 | { 332 | debug << "\tExtra data in buffer " << i << " (" << buffer.cbBuffer << " bytes)\n"; 333 | enqueue(encRecvBuffer, static_cast(buffer.pvBuffer), buffer.cbBuffer); 334 | } 335 | else if (buffer.BufferType != SECBUFFER_EMPTY) 336 | debug << "\tBuffer of type " << buffer.BufferType << "\n"; 337 | } 338 | 339 | if (actualDataStart) 340 | memmove(buffer, actualDataStart, size); 341 | 342 | break; 343 | } 344 | case SEC_E_INCOMPLETE_MESSAGE: 345 | { 346 | // Move all our current data to encRecvBuffer 347 | enqueue(encRecvBuffer, buffer, size); 348 | 349 | // Now try to read some more data from the socket 350 | size_t bufferSize = encRecvBuffer.size() + 8192; 351 | char *recvBuffer = new char[bufferSize]; 352 | size_t recvd = socket.read(recvBuffer+encRecvBuffer.size(), 8192); 353 | debug << recvd << " bytes of extra data read from socket\n"; 354 | 355 | if (recvd == 0 && !recurse) 356 | { 357 | debug << "Recursion prevented, bailing\n"; 358 | return 0; 359 | } 360 | 361 | // Fill our buffer with the queued data and the newly received data 362 | size_t totalSize = encRecvBuffer.size() + recvd; 363 | dequeue(encRecvBuffer, recvBuffer, encRecvBuffer.size()); 364 | debug << "Trying to decrypt with " << totalSize << " bytes of data\n"; 365 | 366 | // Now try to decrypt that 367 | size_t decrypted = decrypt(recvBuffer, totalSize, false); 368 | debug << "\tObtained " << decrypted << " bytes of decrypted data\n"; 369 | 370 | // Copy the first size bytes to the output buffer 371 | size = std::min(size, decrypted); 372 | memcpy(buffer, recvBuffer, size); 373 | 374 | // And write the remainder to our queued decrypted data... 375 | // Note: we prepend, since our recursive call may already have written 376 | // something and we can be sure decrypt wasn't called if the buffer was 377 | // non-empty in read 378 | enqueue_prepend(decRecvBuffer, recvBuffer+size, decrypted-size); 379 | debug << "\tStoring " << decrypted-size << " bytes of extra decrypted data\n"; 380 | return size; 381 | } 382 | // TODO: More? 383 | default: 384 | size = 0; 385 | break; 386 | } 387 | 388 | debug << "\tDecrypted " << size << " bytes of data\n"; 389 | 390 | return size; 391 | } 392 | 393 | size_t SChannelConnection::write(const char *buffer, size_t size) 394 | { 395 | static constexpr size_t bufferSize = 8192; 396 | assert(size <= bufferSize); 397 | 398 | SecPkgContext_StreamSizes Sizes; 399 | QueryContextAttributes( 400 | static_cast(context), 401 | SECPKG_ATTR_STREAM_SIZES, 402 | &Sizes); 403 | debug << "stream sizes:\n\theader: " << Sizes.cbHeader << "\n\tfooter: " << Sizes.cbTrailer << "\n"; 404 | 405 | char *sendBuffer = new char[bufferSize + Sizes.cbHeader + Sizes.cbTrailer]; 406 | memcpy(sendBuffer+Sizes.cbHeader, buffer, size); 407 | 408 | SecBuffer secBuffers[4]; 409 | secBuffers[0].cbBuffer = Sizes.cbHeader; 410 | secBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; 411 | secBuffers[0].pvBuffer = sendBuffer; 412 | 413 | secBuffers[1].cbBuffer = size; 414 | secBuffers[1].BufferType = SECBUFFER_DATA; 415 | secBuffers[1].pvBuffer = sendBuffer+Sizes.cbHeader; 416 | 417 | secBuffers[2].cbBuffer = Sizes.cbTrailer; 418 | secBuffers[2].pvBuffer = sendBuffer+Sizes.cbHeader+size; 419 | secBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; 420 | 421 | secBuffers[3].cbBuffer = 0; 422 | secBuffers[3].BufferType = SECBUFFER_EMPTY; 423 | secBuffers[3].pvBuffer = nullptr; 424 | 425 | SecBufferDesc secBufferDesc; 426 | secBufferDesc.ulVersion = SECBUFFER_VERSION; 427 | secBufferDesc.cBuffers = 4; 428 | secBufferDesc.pBuffers = secBuffers; 429 | 430 | auto ret = EncryptMessage(static_cast(context), 0, &secBufferDesc, 0); // FIXME 431 | debug << "Send:\n\tHeader size: " << secBuffers[0].cbBuffer << "\n\t\ttype: " << secBuffers[0].BufferType << "\n\tData size: " << secBuffers[1].cbBuffer << "\n\t\ttype: " << secBuffers[1].BufferType << "\n\tFooter size: " << secBuffers[2].cbBuffer << "\n\t\ttype: " << secBuffers[2].BufferType << "\n"; 432 | 433 | size_t sendSize = 0; 434 | for (size_t i = 0; i < 4; ++i) 435 | if (secBuffers[i].cbBuffer != bufferSize) 436 | sendSize += secBuffers[i].cbBuffer; 437 | 438 | debug << "\tReal length? " << sendSize << "\n"; 439 | switch (ret) 440 | { 441 | case SEC_E_OK: 442 | socket.write(sendBuffer, sendSize); 443 | break; 444 | // TODO: More? 445 | default: 446 | size = 0; 447 | break; 448 | } 449 | 450 | delete[] sendBuffer; 451 | return size; 452 | } 453 | 454 | void SChannelConnection::destroyContext() 455 | { 456 | if (context) 457 | { 458 | DeleteSecurityContext(context); 459 | delete context; 460 | context = nullptr; 461 | } 462 | } 463 | 464 | void SChannelConnection::close() 465 | { 466 | destroyContext(); 467 | socket.close(); 468 | } 469 | 470 | bool SChannelConnection::valid() 471 | { 472 | return true; 473 | } 474 | 475 | #endif // HTTPS_BACKEND_SCHANNEL 476 | -------------------------------------------------------------------------------- /src/windows/SChannelConnection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_SCHANNEL 6 | 7 | #include "../common/Connection.h" 8 | #include "../common/PlaintextConnection.h" 9 | 10 | #include 11 | 12 | struct _SecHandle; 13 | using CtxtHandle = _SecHandle; 14 | 15 | class SChannelConnection : public Connection 16 | { 17 | public: 18 | SChannelConnection(); 19 | virtual bool connect(const std::string &hostname, uint16_t port) override; 20 | virtual size_t read(char *buffer, size_t size) override; 21 | virtual size_t write(const char *buffer, size_t size) override; 22 | virtual void close() override; 23 | virtual ~SChannelConnection(); 24 | 25 | static bool valid(); 26 | 27 | private: 28 | PlaintextConnection socket; 29 | CtxtHandle *context; 30 | std::vector encRecvBuffer; 31 | std::vector decRecvBuffer; 32 | 33 | size_t decrypt(char *buffer, size_t size, bool recurse = true); 34 | void destroyContext(); 35 | }; 36 | 37 | #endif // HTTPS_BACKEND_SCHANNEL 38 | -------------------------------------------------------------------------------- /src/windows/WinINetClient.cpp: -------------------------------------------------------------------------------- 1 | #include "WinINetClient.h" 2 | 3 | #ifdef HTTPS_BACKEND_WININET 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "../common/HTTPRequest.h" 14 | 15 | class LazyHInternetLoader final 16 | { 17 | public: 18 | LazyHInternetLoader(): hInternet(nullptr) { } 19 | ~LazyHInternetLoader() 20 | { 21 | if (hInternet) 22 | InternetCloseHandle(hInternet); 23 | } 24 | 25 | HINTERNET getInstance() 26 | { 27 | if (!init) 28 | { 29 | hInternet = InternetOpenA("", INTERNET_OPEN_TYPE_PRECONFIG, nullptr, nullptr, 0); 30 | if (hInternet) 31 | { 32 | // Try to enable HTTP2 33 | DWORD httpProtocol = HTTP_PROTOCOL_FLAG_HTTP2; 34 | InternetSetOptionA(hInternet, INTERNET_OPTION_ENABLE_HTTP_PROTOCOL, &httpProtocol, sizeof(DWORD)); 35 | SetLastError(0); // If it errors, ignore. 36 | } 37 | } 38 | 39 | return hInternet; 40 | } 41 | 42 | private: 43 | bool init; 44 | HINTERNET hInternet; 45 | }; 46 | 47 | static thread_local LazyHInternetLoader hInternetCache; 48 | 49 | bool WinINetClient::valid() const 50 | { 51 | // Allow disablement of WinINet backend. 52 | const char *disabler = getenv("LUAHTTPS_DISABLE_WININET"); 53 | if (disabler && strcmp(disabler, "1") == 0) 54 | return false; 55 | 56 | return hInternetCache.getInstance() != nullptr; 57 | } 58 | 59 | HTTPSClient::Reply WinINetClient::request(const HTTPSClient::Request &req) 60 | { 61 | Reply reply; 62 | reply.responseCode = 0; 63 | 64 | // Parse URL 65 | auto parsedUrl = HTTPRequest::parseUrl(req.url); 66 | 67 | // Default flags 68 | DWORD inetFlags = 69 | INTERNET_FLAG_NO_AUTH | 70 | INTERNET_FLAG_NO_CACHE_WRITE | 71 | INTERNET_FLAG_NO_COOKIES | 72 | INTERNET_FLAG_NO_UI; 73 | 74 | if (parsedUrl.schema == "https") 75 | inetFlags |= INTERNET_FLAG_SECURE; 76 | else if (parsedUrl.schema != "http") 77 | return reply; 78 | 79 | // Keep-Alive 80 | auto connectHeader = req.headers.find("Connection"); 81 | auto headerEnd = req.headers.end(); 82 | if ((connectHeader != headerEnd && connectHeader->second != "close") || connectHeader == headerEnd) 83 | inetFlags |= INTERNET_FLAG_KEEP_CONNECTION; 84 | 85 | // Open internet 86 | HINTERNET hInternet = hInternetCache.getInstance(); 87 | if (hInternet == nullptr) 88 | return reply; 89 | 90 | // Connect 91 | HINTERNET hConnect = InternetConnectA( 92 | hInternet, 93 | parsedUrl.hostname.c_str(), 94 | parsedUrl.port, 95 | nullptr, nullptr, 96 | INTERNET_SERVICE_HTTP, 97 | INTERNET_FLAG_EXISTING_CONNECT, 98 | (DWORD_PTR) this 99 | ); 100 | if (!hConnect) 101 | return reply; 102 | 103 | std::string httpMethod = req.method; 104 | std::transform( 105 | httpMethod.begin(), 106 | httpMethod.end(), 107 | httpMethod.begin(), 108 | [](char c) {return (char)toupper((unsigned char) c); } 109 | ); 110 | 111 | // Open HTTP request 112 | HINTERNET hHTTP = HttpOpenRequestA( 113 | hConnect, 114 | httpMethod.c_str(), 115 | parsedUrl.query.c_str(), 116 | nullptr, 117 | nullptr, 118 | nullptr, 119 | inetFlags, 120 | (DWORD_PTR) this 121 | ); 122 | if (!hHTTP) 123 | { 124 | InternetCloseHandle(hConnect); 125 | return reply; 126 | } 127 | 128 | // Send additional headers 129 | HttpAddRequestHeadersA(hHTTP, "User-Agent:", 0, HTTP_ADDREQ_FLAG_REPLACE); 130 | for (const auto &header: req.headers) 131 | { 132 | std::string headerString = header.first + ": " + header.second + "\r\n"; 133 | HttpAddRequestHeadersA(hHTTP, headerString.c_str(), headerString.length(), HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); 134 | } 135 | 136 | // POST data 137 | const char *postData = nullptr; 138 | if (req.postdata.length() > 0 && (httpMethod != "GET" && httpMethod != "HEAD")) 139 | { 140 | char temp[48]; 141 | int len = sprintf(temp, "Content-Length: %u\r\n", (unsigned int) req.postdata.length()); 142 | postData = req.postdata.c_str(); 143 | 144 | HttpAddRequestHeadersA(hHTTP, temp, len, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); 145 | } 146 | 147 | // Send away! 148 | BOOL result = HttpSendRequestA(hHTTP, nullptr, 0, (void *) postData, (DWORD) req.postdata.length()); 149 | if (!result) 150 | { 151 | InternetCloseHandle(hHTTP); 152 | InternetCloseHandle(hConnect); 153 | return reply; 154 | } 155 | 156 | DWORD bufferLength = sizeof(DWORD); 157 | DWORD headerCounter = 0; 158 | 159 | // Status code 160 | DWORD statusCode = 0; 161 | if (!HttpQueryInfoA(hHTTP, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &bufferLength, &headerCounter)) 162 | { 163 | InternetCloseHandle(hHTTP); 164 | InternetCloseHandle(hConnect); 165 | return reply; 166 | } 167 | 168 | // Query headers 169 | std::vector responseHeaders; 170 | bufferLength = 0; 171 | HttpQueryInfoA(hHTTP, HTTP_QUERY_RAW_HEADERS, responseHeaders.data(), &bufferLength, &headerCounter); 172 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) 173 | { 174 | InternetCloseHandle(hHTTP); 175 | InternetCloseHandle(hConnect); 176 | return reply; 177 | } 178 | 179 | responseHeaders.resize(bufferLength); 180 | if (!HttpQueryInfoA(hHTTP, HTTP_QUERY_RAW_HEADERS, responseHeaders.data(), &bufferLength, &headerCounter)) 181 | { 182 | InternetCloseHandle(hHTTP); 183 | InternetCloseHandle(hConnect); 184 | return reply; 185 | } 186 | 187 | for (const char *headerData = responseHeaders.data(); *headerData; headerData += strlen(headerData) + 1) 188 | { 189 | const char *value = strchr(headerData, ':'); 190 | if (value) 191 | { 192 | ptrdiff_t keyLen = (ptrdiff_t) (value - headerData); 193 | reply.headers[std::string(headerData, keyLen)] = value + 2; // +2, colon and 1 space character. 194 | } 195 | } 196 | responseHeaders.resize(1); 197 | 198 | // Read response 199 | std::stringstream responseData; 200 | for (;;) 201 | { 202 | constexpr DWORD BUFFER_SIZE = 4096; 203 | char buffer[BUFFER_SIZE]; 204 | DWORD readed = 0; 205 | 206 | BOOL ret = InternetQueryDataAvailable(hHTTP, &readed, 0, 0); 207 | if (!ret || readed == 0) 208 | break; 209 | 210 | if (!InternetReadFile(hHTTP, buffer, BUFFER_SIZE, &readed)) 211 | break; 212 | 213 | responseData.write(buffer, readed); 214 | } 215 | 216 | reply.body = responseData.str(); 217 | reply.responseCode = statusCode; 218 | 219 | InternetCloseHandle(hHTTP); 220 | InternetCloseHandle(hConnect); 221 | return reply; 222 | } 223 | 224 | #endif // HTTPS_BACKEND_WININET 225 | -------------------------------------------------------------------------------- /src/windows/WinINetClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common/config.h" 4 | 5 | #ifdef HTTPS_BACKEND_WININET 6 | 7 | #include "../common/HTTPSClient.h" 8 | 9 | class WinINetClient: public HTTPSClient 10 | { 11 | public: 12 | bool valid() const override; 13 | HTTPSClient::Reply request(const HTTPSClient::Request &req) override; 14 | }; 15 | 16 | #endif // HTTPS_BACKEND_WININET 17 | -------------------------------------------------------------------------------- /src/windows/WindowsLibraryLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "../common/config.h" 2 | #include "../common/LibraryLoader.h" 3 | 4 | #ifdef HTTPS_LIBRARY_LOADER_WINDOWS 5 | #define NOMINMAX 6 | #define WIN32_LEAN_AND_MEAN 7 | 8 | #include 9 | 10 | namespace LibraryLoader 11 | { 12 | handle *OpenLibrary(const char *name) 13 | { 14 | return reinterpret_cast(LoadLibraryA(name)); 15 | } 16 | 17 | void CloseLibrary(handle *handle) 18 | { 19 | if (handle) 20 | FreeLibrary(reinterpret_cast(handle)); 21 | } 22 | 23 | handle* GetCurrentProcessHandle() 24 | { 25 | return reinterpret_cast(GetModuleHandle(nullptr)); 26 | } 27 | 28 | function *GetFunction(handle *handle, const char *name) 29 | { 30 | HMODULE nativeHandle = reinterpret_cast(handle); 31 | return reinterpret_cast(GetProcAddress(nativeHandle, name)); 32 | } 33 | } 34 | 35 | #endif // HTTPS_LIBRARY_LOADER_WINDOWS 36 | --------------------------------------------------------------------------------