├── .clang-format ├── .github ├── actions │ ├── badge │ │ ├── action.yml │ │ └── write-json-object.js │ └── process-linting-results │ │ └── action.yml ├── scripts │ ├── Dockerfile-autobahn-ws │ ├── add-newline-if-missing.sh │ └── check-autobahn-result.php └── workflows │ ├── autobahn-ws.yml │ ├── cmake.yml │ ├── compiler-support.yml │ └── lint.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Fetch_asyncpp.cmake ├── GetCURL.cmake └── GetZLIB.cmake ├── include └── asyncpp │ └── curl │ ├── base64.h │ ├── cookie.h │ ├── curl.h │ ├── exception.h │ ├── executor.h │ ├── handle.h │ ├── multi.h │ ├── sha1.h │ ├── slist.h │ ├── tcp_client.h │ ├── uri.h │ ├── util.h │ ├── version.h │ ├── webclient.h │ └── websocket.h ├── src └── curl │ ├── base64.cpp │ ├── exception.cpp │ ├── executor.cpp │ ├── handle.cpp │ ├── multi.cpp │ ├── sha1.cpp │ ├── slist.cpp │ ├── tcp_client.cpp │ ├── uri.cpp │ ├── version.cpp │ ├── webclient.cpp │ └── websocket.cpp └── test ├── base64.cpp ├── cookie.cpp ├── slist.cpp ├── tcp_client.cpp ├── uri.cpp ├── util.cpp ├── version.cpp ├── webclient.cpp └── ws-autobahn.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | BreakBeforeBraces: Attach 3 | 4 | ColumnLimit: 160 # Match GitHub UI 5 | 6 | UseTab: Always 7 | TabWidth: 4 8 | IndentWidth: 4 9 | AccessModifierOffset: -4 10 | ContinuationIndentWidth: 4 11 | NamespaceIndentation: All 12 | IndentCaseLabels: false 13 | 14 | PointerAlignment: Left 15 | AlwaysBreakTemplateDeclarations: Yes 16 | SpaceAfterTemplateKeyword: false 17 | AllowShortCaseLabelsOnASingleLine: true 18 | AllowShortIfStatementsOnASingleLine: WithoutElse 19 | AllowShortBlocksOnASingleLine: Always 20 | 21 | FixNamespaceComments: true 22 | ReflowComments: false 23 | -------------------------------------------------------------------------------- /.github/actions/badge/action.yml: -------------------------------------------------------------------------------- 1 | name: Regular badging sequence 2 | description: Publishes a badge based on the job status 3 | inputs: 4 | category: 5 | description: The subfolder where to group the badges 6 | required: true 7 | badges: 8 | description: A json object of label => status for all badges 9 | required: true 10 | github_token: 11 | description: The token to use to publish the changes 12 | required: false 13 | default: ${{ github.token }} 14 | runs: 15 | using: composite 16 | steps: 17 | - run: | 18 | node ./.github/actions/badge/write-json-object.js ${{ inputs.category }} '${{ inputs.badges }}' 19 | shell: bash 20 | - uses: peaceiris/actions-gh-pages@v3 21 | with: 22 | github_token: ${{ inputs.github_token }} 23 | publish_branch: badges 24 | publish_dir: ./badges 25 | keep_files: true 26 | user_name: "github-actions[bot]" 27 | user_email: "github-actions[bot]@users.noreply.github.com" -------------------------------------------------------------------------------- /.github/actions/badge/write-json-object.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const category = process.argv[2]; 3 | const status = JSON.parse(process.argv[3]); 4 | 5 | if (!fs.existsSync("./badges")) fs.mkdirSync("./badges"); 6 | if (!fs.existsSync("./badges/" + category)) fs.mkdirSync("./badges/" + category); 7 | for (let e in status) { 8 | const path = "./badges/" + category + "/" + e; 9 | if (!fs.existsSync(path)) fs.mkdirSync(path); 10 | const ok = status[e] == "success"; 11 | fs.writeFileSync(path + "/shields.json", JSON.stringify({ 12 | "schemaVersion": 1, 13 | "label": e, 14 | "message": ok ? "Passing" : "Failing", 15 | "color": ok ? "brightgreen" : "red" 16 | })); 17 | } 18 | -------------------------------------------------------------------------------- /.github/actions/process-linting-results/action.yml: -------------------------------------------------------------------------------- 1 | name: Process Linting Results 2 | description: Add a comment to a pull request with when `git diff` present and save the changes as an artifact so they can be applied manually 3 | inputs: 4 | linter_name: 5 | description: The name of the tool to credit in the comment 6 | required: true 7 | runs: 8 | using: "composite" 9 | steps: 10 | - run: git add --update 11 | shell: bash 12 | - id: stage 13 | #continue-on-error: true 14 | uses: Thalhammer/patch-generator-action@v2 15 | 16 | # Unfortunately the previous action reports a failure so nothing else can run 17 | # partially a limitation on composite actions since `continue-on-error` is not 18 | # yet supported 19 | - if: steps.stage.outputs.result == 'dirty' 20 | uses: actions-ecosystem/action-create-comment@v1 21 | with: 22 | github_token: ${{ github.token }} 23 | body: | 24 | Hello, @${{ github.actor }}! `${{ inputs.linter_name }}` had some concerns :scream: 25 | - run: exit $(git status -uno -s | wc -l) 26 | shell: bash -------------------------------------------------------------------------------- /.github/scripts/Dockerfile-autobahn-ws: -------------------------------------------------------------------------------- 1 | # docker build -f Dockerfile-autobahn-ws -t ghcr.io/asyncpp/autobahn-testsuite-server:latest . 2 | # docker push ghcr.io/asyncpp/autobahn-testsuite-server:latest 3 | 4 | FROM crossbario/autobahn-testsuite 5 | CMD ["wstest", "--mode", "fuzzingserver"] -------------------------------------------------------------------------------- /.github/scripts/add-newline-if-missing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ -f "$1" && -s "$1" ]]; then 3 | if [[ -n "$(tail -c 1 "$1")" ]]; then 4 | echo "Fixed missing newline in file $1" 5 | sed -i -e '$a\' $1 6 | fi 7 | fi -------------------------------------------------------------------------------- /.github/scripts/check-autobahn-result.php: -------------------------------------------------------------------------------- 1 | $v) { 20 | $ok = true; 21 | if($v["behavior"] == "UNIMPLEMENTED" || $v["behaviorClose"] == "UNIMPLEMENTED") { 22 | $ok = false; 23 | } else if($v["behavior"] == "NON-STRICT" || $v["behaviorClose"] == "NON-STRICT") { 24 | if(!in_array($k, $non_strict)) $ok = false; 25 | } else if($v["behavior"] == "INFORMATIONAL" || $v["behaviorClose"] == "INFORMATIONAL") { 26 | if(!in_array($k, $informational)) $ok = false; 27 | } else if($v["behavior"] != "OK" || $v["behaviorClose"] != "OK") { 28 | $ok = false; 29 | } 30 | if(!$ok) { 31 | echo("Test ".$k." failed\n"); 32 | var_dump($v); 33 | $failed = true; 34 | } 35 | } 36 | if($failed) exit(-1); 37 | else exit(0); -------------------------------------------------------------------------------- /.github/workflows/autobahn-ws.yml: -------------------------------------------------------------------------------- 1 | name: Autobahn WS CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | paths: 9 | - "CMakeLists.txt" 10 | - "cmake/**" 11 | - "include/**" 12 | - "src/**" 13 | - "test/**" 14 | - ".github/actions/**" 15 | - ".github/workflows/autobahn-ws.yml" 16 | env: 17 | CXX: /usr/bin/clang++-15 18 | CC: /usr/bin/clang-15 19 | jobs: 20 | autobahn-ws: 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Configure 25 | run: cmake . -DASYNCPP_BUILD_TEST=ON -G 'Unix Makefiles' 26 | - name: Build 27 | run: cmake --build . --target asyncpp_curl-ws-autobahn 28 | - name: Test 29 | run: ./asyncpp_curl-ws-autobahn ws://127.0.0.1:9001 30 | - name: Analyze result 31 | run: php ./.github/scripts/check-autobahn-result.php http://localhost:8080/cwd/reports/clients/index.json 32 | services: 33 | autobahn-ws: 34 | image: ghcr.io/asyncpp/autobahn-testsuite-server 35 | ports: 36 | - 9001:9001 37 | - 8080:8080 38 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | paths: 9 | - "CMakeLists.txt" 10 | - "cmake/**" 11 | - "include/**" 12 | - "src/**" 13 | - "test/**" 14 | - ".github/actions/**" 15 | - ".github/workflows/cmake.yml" 16 | 17 | env: 18 | CXX: /usr/bin/clang++-12 19 | CC: /usr/bin/clang-12 20 | RUN_ON: ubuntu-20.04 21 | CMAKE_URL: https://cmake.org/files/v3.15/cmake-3.15.7.tar.gz 22 | CMAKE_VERSION: 3.15.7 23 | 24 | jobs: 25 | min-req: 26 | runs-on: ubuntu-20.04 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Cache CMake 30 | id: cache-cmake 31 | uses: actions/cache@v3 32 | with: 33 | path: cmake-${{ env.CMAKE_VERSION }} 34 | key: ubuntu-20.04-${{ github.job }}-cmake-${{ env.CMAKE_VERSION }} 35 | - name: Build cmake 36 | if: steps.cache-cmake.outputs.cache-hit != 'true' 37 | run: | 38 | wget ${{ env.CMAKE_URL }} 39 | tar -zxf cmake-${{ env.CMAKE_VERSION }}.tar.gz 40 | cd cmake-${{ env.CMAKE_VERSION }} 41 | ./bootstrap 42 | make -j $(nproc) 43 | - name: Install cmake 44 | run: | 45 | cd cmake-${{ env.CMAKE_VERSION }} 46 | sudo make install 47 | - name: Build Tests 48 | run: | 49 | mkdir build 50 | cd build 51 | cmake .. -DASYNCPP_BUILD_TEST=ON -G 'Unix Makefiles' 52 | cmake --build . 53 | - name: Run Tests 54 | run: | 55 | cd build 56 | ./asyncpp_curl-test -------------------------------------------------------------------------------- /.github/workflows/compiler-support.yml: -------------------------------------------------------------------------------- 1 | name: Compiler Compatibility CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | compiler: 14 | - { tag: "ubuntu-2204_clang-13", name: "Ubuntu 22.04 Clang 13", cxx: "/usr/bin/clang++-13", cc: "/usr/bin/clang-13", runs-on: "ubuntu-22.04" } 15 | - { tag: "ubuntu-2204_clang-14", name: "Ubuntu 22.04 Clang 14", cxx: "/usr/bin/clang++-14", cc: "/usr/bin/clang-14", runs-on: "ubuntu-22.04" } 16 | - { tag: "ubuntu-2204_clang-15", name: "Ubuntu 22.04 Clang 15", cxx: "/usr/bin/clang++-15", cc: "/usr/bin/clang-15", runs-on: "ubuntu-22.04" } 17 | - { tag: "ubuntu-2204_gcc-10", name: "Ubuntu 22.04 G++ 10", cxx: "/usr/bin/g++-10", cc: "/usr/bin/gcc-10", runs-on: "ubuntu-22.04" } 18 | - { tag: "ubuntu-2204_gcc-11", name: "Ubuntu 22.04 G++ 11", cxx: "/usr/bin/g++-11", cc: "/usr/bin/gcc-11", runs-on: "ubuntu-22.04" } 19 | - { tag: "ubuntu-2004_clang-12", name: "Ubuntu 20.04 Clang 12", cxx: "/usr/bin/clang++-12", cc: "/usr/bin/clang-12", runs-on: "ubuntu-20.04" } 20 | - { tag: "ubuntu-2004_clang-11", name: "Ubuntu 20.04 Clang 11", cxx: "/usr/bin/clang++-11", cc: "/usr/bin/clang-11", runs-on: "ubuntu-20.04" } 21 | - { tag: "ubuntu-2004_clang-10", name: "Ubuntu 20.04 Clang 10", cxx: "/usr/bin/clang++-10", cc: "/usr/bin/clang-10", runs-on: "ubuntu-20.04" } 22 | - { tag: "ubuntu-2004_gcc-10", name: "Ubuntu 20.04 G++ 10", cxx: "/usr/bin/g++-10", cc: "/usr/bin/gcc-10", runs-on: "ubuntu-20.04" } 23 | - { tag: "windows-2022_msvc17", name: "Windows Server 2022 MSVC 17", cxx: "", cc: "", runs-on: "windows-2022" } 24 | - { tag: "windows-2019_msvc16", name: "Windows Server 2019 MSVC 16", cxx: "", cc: "", runs-on: "windows-2019" } 25 | runs-on: ${{ matrix.compiler.runs-on }} 26 | name: Compiler ${{ matrix.compiler.name }} 27 | env: 28 | CXX: ${{ matrix.compiler.cxx }} 29 | CC: ${{ matrix.compiler.cc }} 30 | outputs: 31 | # Because github wants us to suffer we need to list out every output instead of using a matrix statement or some kind of dynamic setting 32 | ubuntu-2204_clang-13: ${{ steps.status.outputs.ubuntu-2204_clang-13 }} 33 | ubuntu-2204_clang-14: ${{ steps.status.outputs.ubuntu-2204_clang-14 }} 34 | ubuntu-2204_clang-15: ${{ steps.status.outputs.ubuntu-2204_clang-15 }} 35 | ubuntu-2204_gcc-10: ${{ steps.status.outputs.ubuntu-2204_gcc-10 }} 36 | ubuntu-2204_gcc-11: ${{ steps.status.outputs.ubuntu-2204_gcc-11 }} 37 | ubuntu-2004_clang-12: ${{ steps.status.outputs.ubuntu-2004_clang-12 }} 38 | ubuntu-2004_clang-11: ${{ steps.status.outputs.ubuntu-2004_clang-11 }} 39 | ubuntu-2004_clang-10: ${{ steps.status.outputs.ubuntu-2004_clang-10 }} 40 | ubuntu-2004_gcc-10: ${{ steps.status.outputs.ubuntu-2004_gcc-10 }} 41 | windows-2022_msvc17: ${{ steps.status.outputs.windows-2022_msvc17 }} 42 | windows-2019_msvc16: ${{ steps.status.outputs.windows-2019_msvc16 }} 43 | defaults: 44 | run: 45 | shell: bash -l {0} 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v3 49 | # Ubuntu 22.04 container has libstdc++13 installed which is incompatible with clang < 15 in C++20 50 | - name: Uninstall libstdc++-13-dev 51 | if: (matrix.compiler.tag == 'ubuntu-2204_clang-14') || (matrix.compiler.tag == 'ubuntu-2204_clang-13') 52 | run: | 53 | sudo apt autoremove libstdc++-13-dev gcc-13 libgcc-13-dev 54 | sudo apt install libstdc++-12-dev gcc-12 libgcc-12-dev 55 | - name: Configure 56 | if: contains(matrix.compiler.runs-on, 'ubuntu') 57 | run: cmake -S. -Bbuild -DASYNCPP_BUILD_TEST=ON -DASYNCPP_WITH_ASAN=ON 58 | - name: Configure 59 | if: contains(matrix.compiler.runs-on, 'ubuntu') != true 60 | run: cmake -S. -Bbuild -DASYNCPP_BUILD_TEST=ON -DASYNCPP_WITH_ASAN=OFF 61 | - name: Build 62 | run: cmake --build build --config Debug 63 | - name: Test 64 | working-directory: ${{ github.workspace }}/build 65 | if: contains(matrix.compiler.runs-on, 'windows') != true 66 | run: ./asyncpp_curl-test 67 | - name: Test 68 | if: contains(matrix.compiler.runs-on, 'windows') 69 | working-directory: ${{ github.workspace }}/build 70 | run: Debug/asyncpp_curl-test.exe 71 | - name: Update Result 72 | id: status 73 | if: ${{ always() }} 74 | run: echo "${{ matrix.compiler.tag }}=${{ job.status }}" >> $GITHUB_OUTPUT 75 | 76 | badge-upload: 77 | if: ${{ github.event_name == 'push' && always() }} 78 | needs: [build] 79 | runs-on: ubuntu-20.04 80 | name: Publish badges 81 | steps: 82 | - name: Checkout repository 83 | uses: actions/checkout@v3 84 | - name: Publish Badges 85 | uses: ./.github/actions/badge 86 | with: 87 | category: compiler 88 | badges: ${{ toJson(needs.build.outputs) }} 89 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | clang-format: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - run: | 13 | sudo apt-get update && sudo apt-get install clang-format-14 14 | shopt -s globstar 15 | - uses: actions/checkout@v3 16 | - run: find \( -name "*.h" -or -name "*.cpp" \) -exec clang-format-14 -i {} \; 17 | - run: find \( -name "*.h" -or -name "*.cpp" \) -exec ./.github/scripts/add-newline-if-missing.sh {} \; 18 | - uses: ./.github/actions/process-linting-results 19 | with: 20 | linter_name: clang-format 21 | 22 | cmake-format: 23 | runs-on: ubuntu-20.04 24 | steps: 25 | - uses: actions/setup-python@v4.3.0 26 | with: 27 | python-version: "3.x" 28 | - run: | 29 | pip install cmakelang 30 | shopt -s globstar 31 | - uses: actions/checkout@v3 32 | - run: find \( -name "CMakeLists.txt" -or -name "*.cmake" \) -exec cmake-format -i {} \; 33 | - uses: ./.github/actions/process-linting-results 34 | with: 35 | linter_name: cmake-format 36 | 37 | line-ending: 38 | runs-on: ubuntu-20.04 39 | steps: 40 | - uses: actions/checkout@v3 41 | - run: git add --renormalize . 42 | - uses: ./.github/actions/process-linting-results 43 | with: 44 | linter_name: line-ending -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.vscode 3 | # MSVC 4 | /.vs 5 | /out/Build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) # Because we need 2 | # FetchContent_MakeAvailable 3 | cmake_policy(VERSION 3.15) 4 | if(POLICY CMP0135) # DOWNLOAD_EXTRACT_TIMESTAMP 5 | cmake_policy(SET CMP0135 OLD) 6 | endif() 7 | 8 | project(AsyncppCURL) 9 | 10 | find_package(Threads REQUIRED) 11 | 12 | include(cmake/GetZLIB.cmake) 13 | include(cmake/GetCURL.cmake) 14 | 15 | option(ASYNCPP_BUILD_TEST "Enable test builds" ON) 16 | option(ASYNCPP_WITH_ASAN "Enable asan for test builds" ON) 17 | 18 | if(TARGET asyncpp) 19 | message(STATUS "Using existing asyncpp target.") 20 | else() 21 | message(STATUS "Missing asyncpp, using Fetch to import it.") 22 | include(cmake/Fetch_asyncpp.cmake) 23 | endif() 24 | 25 | add_library( 26 | asyncpp_curl 27 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/base64.cpp 28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/exception.cpp 29 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/executor.cpp 30 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/handle.cpp 31 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/multi.cpp 32 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/sha1.cpp 33 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/slist.cpp 34 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/tcp_client.cpp 35 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/uri.cpp 36 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/version.cpp 37 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/webclient.cpp 38 | ${CMAKE_CURRENT_SOURCE_DIR}/src/curl/websocket.cpp) 39 | target_link_libraries(asyncpp_curl PUBLIC asyncpp) 40 | target_link_libraries(asyncpp_curl PUBLIC CURL::libcurl ZLIB::ZLIB 41 | Threads::Threads) 42 | target_include_directories(asyncpp_curl 43 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) 44 | target_compile_features(asyncpp_curl PUBLIC cxx_std_20) 45 | if(NOT HUNTER_ENABLED AND NOT WIN32) 46 | find_package(OpenSSL REQUIRED) 47 | target_link_libraries(asyncpp_curl PUBLIC OpenSSL::Crypto OpenSSL::SSL) 48 | endif() 49 | if(ASYNCPP_WITH_ASAN) 50 | message(STATUS "Building with asan enabled") 51 | if(MSVC) 52 | target_compile_options(asyncpp_curl PRIVATE -fsanitize=address /Zi) 53 | target_compile_definitions(asyncpp_curl PRIVATE _DISABLE_VECTOR_ANNOTATION) 54 | target_compile_definitions(asyncpp_curl PRIVATE _DISABLE_STRING_ANNOTATION) 55 | target_link_libraries(asyncpp_curl PRIVATE libsancov.lib) 56 | else() 57 | target_compile_options(asyncpp_curl PRIVATE -fsanitize=address) 58 | target_link_options(asyncpp_curl PRIVATE -fsanitize=address) 59 | endif() 60 | endif() 61 | 62 | if(ASYNCPP_BUILD_TEST) 63 | enable_testing() 64 | include(GoogleTest) 65 | if(HUNTER_ENABLED) 66 | hunter_add_package(GTest) 67 | find_package(GTest CONFIG REQUIRED) 68 | else() 69 | include(FetchContent) 70 | FetchContent_Declare( 71 | googletest 72 | GIT_REPOSITORY https://github.com/google/googletest.git 73 | GIT_TAG release-1.12.1) 74 | if(WIN32) 75 | set(gtest_force_shared_crt 76 | ON 77 | CACHE BOOL "" FORCE) 78 | set(BUILD_GMOCK 79 | OFF 80 | CACHE BOOL "" FORCE) 81 | endif() 82 | FetchContent_MakeAvailable(googletest) 83 | endif() 84 | 85 | add_executable( 86 | asyncpp_curl-test 87 | ${CMAKE_CURRENT_SOURCE_DIR}/test/base64.cpp 88 | ${CMAKE_CURRENT_SOURCE_DIR}/test/cookie.cpp 89 | ${CMAKE_CURRENT_SOURCE_DIR}/test/slist.cpp 90 | ${CMAKE_CURRENT_SOURCE_DIR}/test/tcp_client.cpp 91 | ${CMAKE_CURRENT_SOURCE_DIR}/test/uri.cpp 92 | ${CMAKE_CURRENT_SOURCE_DIR}/test/util.cpp 93 | ${CMAKE_CURRENT_SOURCE_DIR}/test/version.cpp 94 | ${CMAKE_CURRENT_SOURCE_DIR}/test/webclient.cpp) 95 | target_link_libraries( 96 | asyncpp_curl-test PRIVATE asyncpp_curl GTest::gtest GTest::gtest_main 97 | Threads::Threads) 98 | 99 | if(ASYNCPP_WITH_ASAN) 100 | if(MSVC) 101 | target_compile_options(asyncpp_curl-test PRIVATE -fsanitize=address /Zi) 102 | target_compile_definitions(asyncpp_curl-test 103 | PRIVATE _DISABLE_VECTOR_ANNOTATION) 104 | target_compile_definitions(asyncpp_curl-test 105 | PRIVATE _DISABLE_STRING_ANNOTATION) 106 | target_link_libraries(asyncpp_curl-test PRIVATE libsancov.lib) 107 | else() 108 | target_compile_options(asyncpp_curl-test PRIVATE -fsanitize=address) 109 | target_link_options(asyncpp_curl-test PRIVATE -fsanitize=address) 110 | endif() 111 | endif() 112 | 113 | gtest_discover_tests(asyncpp_curl-test) 114 | 115 | add_executable(asyncpp_curl-ws-autobahn 116 | ${CMAKE_CURRENT_SOURCE_DIR}/test/ws-autobahn.cpp) 117 | target_link_libraries(asyncpp_curl-ws-autobahn PRIVATE asyncpp_curl 118 | Threads::Threads) 119 | 120 | if(ASYNCPP_WITH_ASAN) 121 | if(MSVC) 122 | target_compile_options(asyncpp_curl-ws-autobahn PRIVATE -fsanitize=address 123 | /Zi) 124 | target_compile_definitions(asyncpp_curl-ws-autobahn 125 | PRIVATE _DISABLE_VECTOR_ANNOTATION) 126 | target_compile_definitions(asyncpp_curl-ws-autobahn 127 | PRIVATE _DISABLE_STRING_ANNOTATION) 128 | target_link_libraries(asyncpp_curl-ws-autobahn PRIVATE libsancov.lib) 129 | else() 130 | target_compile_options(asyncpp_curl-ws-autobahn 131 | PRIVATE -fsanitize=address) 132 | target_link_options(asyncpp_curl-ws-autobahn PRIVATE -fsanitize=address) 133 | endif() 134 | endif() 135 | endif() 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dominik Thalhammer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async++ CURL library 2 | 3 | [![License Badge](https://img.shields.io/github/license/asyncpp/asyncpp-curl)](https://github.com/asyncpp/asyncpp-curl/blob/master/LICENSE) 4 | [![Stars Badge](https://img.shields.io/github/stars/asyncpp/asyncpp-curl)](https://github.com/asyncpp/asyncpp-curl/stargazers) 5 | 6 | This library provides a c++ wrapper around libcurl supporting async usage with c++20 coroutines. 7 | It is an addition to [async++](https://github.com/asyncpp/asyncpp) which provides general coroutine tasks and support classes. 8 | 9 | Tested and supported compilers: 10 | | Linux | Windows | 11 | |-----------------------------------------------------------------------|-----------------------------------------------------------------------| 12 | | [![ubuntu-2004_clang-10][img_ubuntu-2004_clang-10]][Compiler-Support] | [![windows-2019_msvc16][img_windows-2019_msvc16]][Compiler-Support] | 13 | | [![ubuntu-2004_clang-11][img_ubuntu-2004_clang-11]][Compiler-Support] | [![windows-2022_msvc17][img_windows-2022_msvc17]][Compiler-Support] | 14 | | [![ubuntu-2004_clang-12][img_ubuntu-2004_clang-12]][Compiler-Support] | | 15 | | [![ubuntu-2004_gcc-10][img_ubuntu-2004_gcc-10]][Compiler-Support] | | 16 | | [![ubuntu-2204_clang-13][img_ubuntu-2204_clang-13]][Compiler-Support] | | 17 | | [![ubuntu-2204_clang-14][img_ubuntu-2204_clang-14]][Compiler-Support] | | 18 | | [![ubuntu-2204_clang-15][img_ubuntu-2204_clang-15]][Compiler-Support] | | 19 | | [![ubuntu-2204_gcc-10][img_ubuntu-2204_gcc-10]][Compiler-Support] | | 20 | | [![ubuntu-2204_gcc-11][img_ubuntu-2204_gcc-11]][Compiler-Support] | | 21 | 22 | [img_ubuntu-2004_clang-10]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2004_clang-10/shields.json 23 | [img_ubuntu-2004_clang-11]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2004_clang-11/shields.json 24 | [img_ubuntu-2004_clang-12]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2004_clang-12/shields.json 25 | [img_ubuntu-2004_gcc-10]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2004_gcc-10/shields.json 26 | [img_ubuntu-2204_clang-13]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2204_clang-13/shields.json 27 | [img_ubuntu-2204_clang-14]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2204_clang-14/shields.json 28 | [img_ubuntu-2204_clang-15]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2204_clang-15/shields.json 29 | [img_ubuntu-2204_gcc-10]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2204_gcc-10/shields.json 30 | [img_ubuntu-2204_gcc-11]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/ubuntu-2204_gcc-11/shields.json 31 | [img_windows-2019_msvc16]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/windows-2019_msvc16/shields.json 32 | [img_windows-2022_msvc17]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/asyncpp/asyncpp-curl/badges/compiler/windows-2022_msvc17/shields.json 33 | [Compiler-Support]: https://github.com/asyncpp/asyncpp-curl/actions/workflows/compiler-support.yml 34 | 35 | In addition the websocket client is tested for compliance with [RFC6455](https://www.rfc-editor.org/rfc/rfc6455) using the [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite) project: 36 | 37 | [![Autobahn WS CI](https://github.com/asyncpp/asyncpp-curl/actions/workflows/autobahn-ws.yml/badge.svg)](https://github.com/asyncpp/asyncpp-curl/actions/workflows/autobahn-ws.yml) 38 | 39 | ### Provided classes 40 | * `base64` and `base64url` provides base64 encode and decode helpers 41 | * `cookie` provides cookie handling and parsing 42 | * `executor` is used for running a curl multi loop in an extra thread and providing a dispatcher interface for use with `defer` 43 | * `handle` is a wrapper around a curl easy handle 44 | * `multi` is a wrapper around a curl multi handle 45 | * `sha1` is a standalone sha1 implementation mainly used for implementing the websocket client 46 | * `slist` is a wrapper around curl slist's used for e.g. headers. Provides a stl container like interface 47 | * `tcp_client` is a wrapper using `CURLOPT_CONNECT_ONLY` to establish a raw tcp/ssl connection to a remote host 48 | * `uri` provides URI parsing and building 49 | * `utf8_validator` allows validation of utf8 text for compliance 50 | * `http_request` and `http_response` provide a simplified interface to `handle` for doing normal HTTP transfers 51 | * `websocket` provides a generic websocket client implementation based on `tcp_client` 52 | -------------------------------------------------------------------------------- /cmake/Fetch_asyncpp.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare(asyncpp 4 | GIT_REPOSITORY "https://github.com/asyncpp/asyncpp.git") 5 | FetchContent_MakeAvailable(asyncpp) 6 | -------------------------------------------------------------------------------- /cmake/GetCURL.cmake: -------------------------------------------------------------------------------- 1 | if(TARGET libcurl) 2 | message(STATUS "Using existing libcurl target.") 3 | elseif(HUNTER_ENABLED) 4 | hunter_add_package(CURL) 5 | hunter_add_package(OpenSSL) 6 | find_package(CURL CONFIG REQUIRED) 7 | find_package(OpenSSL REQUIRED) 8 | else() 9 | find_package(CURL) 10 | find_package(OpenSSL) 11 | 12 | if(NOT CURL_FOUND OR NOT OPENSSL_FOUND) 13 | unset(CURL_FOUND) 14 | unset(OPENSSL_FOUND) 15 | endif() 16 | 17 | if(NOT CURL_FOUND) 18 | # We only need HTTP (and HTTPS) support: 19 | set(HTTP_ONLY 20 | ON 21 | CACHE INTERNAL "" FORCE) 22 | set(BUILD_CURL_EXE 23 | OFF 24 | CACHE INTERNAL "" FORCE) 25 | set(BUILD_SHARED_LIBS 26 | OFF 27 | CACHE INTERNAL "" FORCE) 28 | set(BUILD_TESTING OFF) 29 | 30 | if(WIN32) 31 | set(CMAKE_USE_SCHANNEL 32 | ON 33 | CACHE INTERNAL "" FORCE) 34 | else() 35 | set(CMAKE_USE_OPENSSL 36 | ON 37 | CACHE INTERNAL "" FORCE) 38 | endif() 39 | 40 | include(FetchContent) 41 | FetchContent_Declare( 42 | curl 43 | URL https://github.com/curl/curl/releases/download/curl-7_80_0/curl-7.80.0.tar.xz 44 | URL_HASH 45 | SHA256=a132bd93188b938771135ac7c1f3ac1d3ce507c1fcbef8c471397639214ae2ab # the file 46 | # hash for curl-7.80.0.tar.xz 47 | USES_TERMINAL_DOWNLOAD TRUE) 48 | FetchContent_MakeAvailable(curl) 49 | set_property(TARGET libcurl PROPERTY FOLDER "external") 50 | message(STATUS "Building libcurl using FetchContent") 51 | endif() 52 | endif() 53 | -------------------------------------------------------------------------------- /cmake/GetZLIB.cmake: -------------------------------------------------------------------------------- 1 | if(TARGET ZLIB::ZLIB) 2 | message(STATUS "Using existing ZLIB target.") 3 | else() 4 | find_package(ZLIB) 5 | 6 | if(NOT ZLIB_FOUND) 7 | set(ZLIB_BUILD_EXAMPLES 8 | OFF 9 | CACHE INTERNAL "" FORCE) 10 | 11 | include(FetchContent) 12 | FetchContent_Declare( 13 | zlib 14 | URL https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.xz 15 | URL_HASH 16 | SHA256=38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32 17 | USES_TERMINAL_DOWNLOAD TRUE) 18 | FetchContent_MakeAvailable(zlib) 19 | set_property(TARGET zlib PROPERTY FOLDER "external") 20 | add_library(ZLIB::ZLIB ALIAS zlibstatic) 21 | message(STATUS "Building zlib using FetchContent") 22 | endif() 23 | endif() 24 | -------------------------------------------------------------------------------- /include/asyncpp/curl/base64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace asyncpp::curl { 5 | class base64 { 6 | public: 7 | static std::string encode(std::string_view data); 8 | static std::string decode(std::string_view data); 9 | }; 10 | 11 | class base64url { 12 | public: 13 | static std::string encode(std::string_view data); 14 | static std::string decode(std::string_view data); 15 | }; 16 | } // namespace asyncpp::curl 17 | -------------------------------------------------------------------------------- /include/asyncpp/curl/cookie.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asyncpp::curl { 10 | class cookie { 11 | public: 12 | cookie() = default; 13 | cookie(std::string_view str) { 14 | static constexpr auto trim = [](std::string_view str) { 15 | auto pos = str.find_first_not_of(" \t\n\v\f\r"); 16 | if (pos == std::string::npos) pos = str.size(); 17 | str.remove_prefix(pos); 18 | pos = str.find_last_not_of(" \t\n\v\f\r"); 19 | if (pos == std::string::npos) return std::string_view{}; 20 | return str.substr(0, pos + 1); 21 | }; 22 | static constexpr auto pull_part = [](std::string_view& str) -> std::string_view { 23 | auto pos = str.find("\t"); 24 | if (pos == std::string::npos) throw std::invalid_argument("invalid cookie string"); 25 | auto res = str.substr(0, pos); 26 | str.remove_prefix(pos + 1); 27 | return res; 28 | }; 29 | static constexpr auto iequals = [](std::string_view a, std::string_view b) { 30 | return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); 31 | }; 32 | m_domain = trim(pull_part(str)); 33 | m_include_subdomains = iequals(trim(pull_part(str)), "TRUE"); 34 | m_path = trim(pull_part(str)); 35 | m_secure = iequals(trim(pull_part(str)), "TRUE"); 36 | m_expires = std::chrono::system_clock::from_time_t(std::stoull(std::string{trim(pull_part(str))})); 37 | m_name = trim(pull_part(str)); 38 | m_value = trim(str); 39 | } 40 | cookie(const char* str) : cookie(std::string_view{str}) {} 41 | cookie(const std::string& str) : cookie(std::string_view{str}) {} 42 | cookie(std::string name, std::string value) : m_name{std::move(name)}, m_value{value} {} 43 | cookie(std::string domain, bool subdomain, std::string path, bool secure, std::chrono::system_clock::time_point expires, std::string name, 44 | std::string value) 45 | : m_domain{std::move(domain)}, m_path{std::move(path)}, m_expires{expires}, m_name{std::move(name)}, m_value{std::move(value)}, 46 | m_include_subdomains{subdomain}, m_secure{secure} {} 47 | cookie(const cookie&) = default; 48 | cookie(cookie&&) = default; 49 | cookie& operator=(const cookie&) = default; 50 | cookie& operator=(cookie&&) = default; 51 | 52 | const std::string& domain() const noexcept { return m_domain; }; 53 | const std::string& path() const noexcept { return m_path; }; 54 | std::chrono::system_clock::time_point expires() const noexcept { return m_expires; }; 55 | const std::string& name() const noexcept { return m_name; }; 56 | const std::string& value() const noexcept { return m_value; }; 57 | bool include_subdomains() const noexcept { return m_include_subdomains; }; 58 | bool secure() const noexcept { return m_secure; }; 59 | 60 | void domain(std::string val) { m_domain = std::move(val); } 61 | void path(std::string val) { m_path = std::move(val); } 62 | void expires(std::chrono::system_clock::time_point val) { m_expires = val; } 63 | void name(std::string val) { m_name = std::move(val); } 64 | void value(std::string val) { m_value = std::move(val); } 65 | void include_subdomains(bool val) { m_include_subdomains = val; } 66 | void secure(bool val) { m_secure = val; } 67 | 68 | bool is_valid() const noexcept { return !m_domain.empty() && !m_path.empty() && !m_name.empty(); } 69 | bool is_expired() const noexcept { return m_expires <= std::chrono::system_clock::now(); } 70 | 71 | std::string to_string() const { 72 | std::stringstream res; 73 | res << m_domain << "\t"; 74 | res << (m_include_subdomains ? "TRUE" : "FALSE") << "\t"; 75 | res << m_path << "\t"; 76 | res << (m_secure ? "TRUE" : "FALSE") << "\t"; 77 | res << std::chrono::system_clock::to_time_t(m_expires) << "\t"; 78 | res << m_name << "\t"; 79 | res << m_value; 80 | return res.str(); 81 | } 82 | 83 | friend bool operator==(const cookie& lhs, const cookie& rhs) noexcept { 84 | return std::tie(lhs.m_domain, lhs.m_include_subdomains, lhs.m_path, lhs.m_secure, lhs.m_expires, lhs.m_name, lhs.m_value) == 85 | std::tie(rhs.m_domain, rhs.m_include_subdomains, rhs.m_path, rhs.m_secure, rhs.m_expires, rhs.m_name, rhs.m_value); 86 | } 87 | friend bool operator!=(const cookie& lhs, const cookie& rhs) noexcept { return !(lhs == rhs); } 88 | friend bool operator<(const cookie& lhs, const cookie& rhs) noexcept { 89 | return std::tie(lhs.m_domain, lhs.m_include_subdomains, lhs.m_path, lhs.m_secure, lhs.m_expires, lhs.m_name, lhs.m_value) < 90 | std::tie(rhs.m_domain, rhs.m_include_subdomains, rhs.m_path, rhs.m_secure, rhs.m_expires, rhs.m_name, rhs.m_value); 91 | } 92 | friend bool operator>(const cookie& lhs, const cookie& rhs) noexcept { 93 | return std::tie(lhs.m_domain, lhs.m_include_subdomains, lhs.m_path, lhs.m_secure, lhs.m_expires, lhs.m_name, lhs.m_value) > 94 | std::tie(rhs.m_domain, rhs.m_include_subdomains, rhs.m_path, rhs.m_secure, rhs.m_expires, rhs.m_name, rhs.m_value); 95 | } 96 | friend bool operator<=(const cookie& lhs, const cookie& rhs) noexcept { 97 | return std::tie(lhs.m_domain, lhs.m_include_subdomains, lhs.m_path, lhs.m_secure, lhs.m_expires, lhs.m_name, lhs.m_value) <= 98 | std::tie(rhs.m_domain, rhs.m_include_subdomains, rhs.m_path, rhs.m_secure, rhs.m_expires, rhs.m_name, rhs.m_value); 99 | } 100 | friend bool operator>=(const cookie& lhs, const cookie& rhs) noexcept { 101 | return std::tie(lhs.m_domain, lhs.m_include_subdomains, lhs.m_path, lhs.m_secure, lhs.m_expires, lhs.m_name, lhs.m_value) >= 102 | std::tie(rhs.m_domain, rhs.m_include_subdomains, rhs.m_path, rhs.m_secure, rhs.m_expires, rhs.m_name, rhs.m_value); 103 | } 104 | 105 | private: 106 | std::string m_domain; 107 | std::string m_path; 108 | std::chrono::system_clock::time_point m_expires; 109 | std::string m_name; 110 | std::string m_value; 111 | bool m_include_subdomains; 112 | bool m_secure; 113 | }; 114 | } // namespace asyncpp::curl 115 | -------------------------------------------------------------------------------- /include/asyncpp/curl/curl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /include/asyncpp/curl/exception.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace asyncpp::curl { 5 | class exception : public std::exception { 6 | int m_code = -1; 7 | bool m_is_multi = false; 8 | 9 | public: 10 | exception(int code, bool is_multi = false) : m_code{code}, m_is_multi{is_multi} {} 11 | const char* what() const noexcept override; 12 | constexpr int code() const noexcept { return m_code; } 13 | }; 14 | } // namespace asyncpp::curl 15 | -------------------------------------------------------------------------------- /include/asyncpp/curl/executor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace asyncpp::curl { 18 | class handle; 19 | /** 20 | * \brief Curl Executor class, implements a dispatcher on top of curl_multi_*. 21 | */ 22 | class executor : public dispatcher { 23 | multi m_multi; 24 | std::thread m_thread; 25 | std::mutex m_mtx; 26 | std::atomic m_exit; 27 | threadsafe_queue> m_queue; 28 | std::multimap> m_scheduled; 29 | std::set m_connect_only_handles; 30 | 31 | void worker_thread() noexcept; 32 | 33 | public: 34 | /** 35 | * \brief Construct a new executor object 36 | */ 37 | executor(); 38 | /** 39 | * \brief Destroy the executor object 40 | */ 41 | ~executor() noexcept; 42 | executor(const executor&) = delete; 43 | executor& operator=(const executor&) = delete; 44 | executor(executor&&) = delete; 45 | executor& operator=(executor&&) = delete; 46 | 47 | /** 48 | * \brief Add an easy handle to this executor. 49 | * \param hdl The handle to add 50 | * \note Due to a bug in libcurl a connect_only handle can not use asynchronous connect and has to use perform() instead. 51 | * \note Adding it to the executor is still supported and will poll the handle for readiness, causeing the read/write callback to be invoked. 52 | */ 53 | void add_handle(handle& hdl); 54 | /** 55 | * \brief Remove an easy handle from this executor. 56 | * \param hdl The handle to remove 57 | */ 58 | void remove_handle(handle& hdl); 59 | 60 | /** \brief coroutine awaiter for an easy transfer */ 61 | struct exec_awaiter { 62 | executor* const m_parent; 63 | handle* const m_handle; 64 | struct stop_callback { 65 | executor* const m_parent; 66 | handle* const m_handle; 67 | void operator()(); 68 | }; 69 | std::stop_callback m_callback; 70 | int m_result{0}; 71 | 72 | exec_awaiter(executor* exec, handle* hdl, std::stop_token st) 73 | : m_parent(exec), m_handle(hdl), m_callback(std::move(st), stop_callback{exec, hdl}) {} 74 | 75 | constexpr bool await_ready() const noexcept { return false; } 76 | void await_suspend(coroutine_handle<> h) noexcept; 77 | constexpr int await_resume() const noexcept { return m_result; } 78 | }; 79 | 80 | /** 81 | * \brief Return a awaitable that suspends till the handle is finished. 82 | * \param hdl The handle to await 83 | * \return An awaitable 84 | */ 85 | exec_awaiter exec(handle& hdl, std::stop_token st = {}); 86 | 87 | /** 88 | * \brief Push a invocable to be executed on the executor thread. 89 | * \param fn Invocable to call 90 | */ 91 | void push(std::function fn) override; 92 | /** 93 | * \brief Schedule an invocable in a certain time from now. 94 | * \param fn Invocable to execute on the executor thread 95 | * \param timeout Timeout to execute the invocable at 96 | */ 97 | void schedule(std::function fn, std::chrono::milliseconds timeout); 98 | /** 99 | * \brief Schedule an invocable at a certain timepoint. 100 | * \param fn Invocable to execute on the executor thread 101 | * \param time Timestamp at which to execute the invocable 102 | */ 103 | void schedule(std::function fn, std::chrono::steady_clock::time_point time); 104 | 105 | /** 106 | * \brief Run an invocable on the executor thread and return the result. 107 | * \param fn Invocable to call 108 | */ 109 | template 110 | std::invoke_result_t push_wait(FN&& fn) { 111 | if (m_thread.get_id() == std::this_thread::get_id()) { 112 | return fn(); 113 | } else { 114 | std::promise> promise; 115 | auto result = promise.get_future(); 116 | this->push([&promise, &fn]() { 117 | try { 118 | if constexpr (std::is_same_v, void>) { 119 | fn(); 120 | promise.set_value(); 121 | } else 122 | promise.set_value(fn()); 123 | } catch (...) { promise.set_exception(std::current_exception()); } 124 | }); 125 | return result.get(); 126 | } 127 | } 128 | 129 | /** 130 | * \brief Wake the underlying multi handle 131 | */ 132 | void wakeup(); 133 | 134 | /** 135 | * \brief Get a global default executor 136 | * \return A global executor instance 137 | * \note Do not keep references to this executor past the end of main because the destruction order is not predictable. 138 | */ 139 | static executor& get_default(); 140 | }; 141 | } // namespace asyncpp::curl 142 | -------------------------------------------------------------------------------- /include/asyncpp/curl/handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asyncpp::curl { 10 | class multi; 11 | class executor; 12 | class slist; 13 | /** 14 | * \brief Wrapper around a libcurl handle. 15 | * 16 | * This is a fat wrapper, it contains additional members to allow usage of C++ functions and handle raii. 17 | */ 18 | class handle { 19 | mutable std::recursive_mutex m_mtx; 20 | void* m_instance; 21 | multi* m_multi; 22 | executor* m_executor; 23 | std::function m_done_callback{}; 24 | std::function m_header_callback{}; 25 | std::function m_progress_callback{}; 26 | std::function m_read_callback{}; 27 | std::function m_write_callback{}; 28 | std::map m_owned_slists; 29 | uint32_t m_flags; 30 | 31 | friend class multi; 32 | friend class executor; 33 | 34 | public: 35 | /** \brief Construct a new libcurl handle */ 36 | handle(); 37 | /** \brief Destructor */ 38 | ~handle() noexcept; 39 | handle(const handle&) = delete; 40 | handle& operator=(const handle&) = delete; 41 | handle(handle&&) = delete; 42 | handle& operator=(handle&&) = delete; 43 | 44 | /** 45 | * \brief Get the raw CURL handle. Use with caution. 46 | */ 47 | void* raw() const noexcept { return m_instance; } 48 | 49 | /** 50 | * \brief Set an option of type long 51 | * \param opt Curl option to set 52 | * \param val Value to use for option 53 | */ 54 | void set_option_long(int opt, long val); 55 | /** 56 | * \brief Set an option of type offset 57 | * \param opt Curl option to set 58 | * \param val Value to use for option 59 | */ 60 | void set_option_offset(int opt, long val); 61 | /** 62 | * \brief Set an option of type pointer (callback fn, void*) 63 | * \param opt Curl option to set 64 | * \param val Value to use for option 65 | */ 66 | void set_option_ptr(int opt, const void* ptr); 67 | /** 68 | * \brief Set an option of type string 69 | * \param opt Curl option to set 70 | * \param val Value to use for option 71 | */ 72 | void set_option_string(int opt, const char* str); 73 | /** 74 | * \brief Set an option of type blob 75 | * \param opt Curl option to set 76 | * \param data The pointer to the data used to set 77 | * \param data_size The size of the data pointed to by data in bytes 78 | * \param copy Copy the data into curl. If false the memory pointed to by data needs to stay valid until the handle is destroyed or this option is set again. 79 | */ 80 | void set_option_blob(int opt, void* data, size_t data_size, bool copy = true); 81 | /** 82 | * \brief Set an option of type bool/long(0,1) 83 | * \param opt Curl option to set 84 | * \param val Value to use for option 85 | */ 86 | void set_option_bool(int opt, bool on); 87 | /** 88 | * \brief Set an option of type slist 89 | * \param opt Curl option to set 90 | * \param val Value to use for option 91 | * \note The handle class copies the slist and keeps it alive until this option is changed. 92 | */ 93 | void set_option_slist(int opt, slist list); 94 | 95 | /** 96 | * \brief Set the handle url 97 | * \param url The handle url 98 | */ 99 | void set_url(const char* url); 100 | /** 101 | * \brief Set the handle url 102 | * \param url The handle url 103 | */ 104 | void set_url(const std::string& url); 105 | /** 106 | * \brief Set if curl should follow locations 107 | * \param on true to follow 108 | */ 109 | void set_follow_location(bool on); 110 | /** 111 | * \brief Enable/Disable verbose output 112 | * \param on true to enable 113 | */ 114 | void set_verbose(bool on); 115 | /** 116 | * \brief Set the request headers 117 | * \param list slist containing all request headers 118 | */ 119 | void set_headers(slist list); 120 | 121 | /** 122 | * \brief Set a function to be called by curl if data is received 123 | * \param cb Callback to be called 124 | * \note ptr points to a block os size bytes without zero termination. 125 | * \note The function can return CURL_WRITEFUNC_PAUSE to pause the transfer. 126 | * \note If the value returned differs from size the transfer is aborted with CURLE_WRITE_ERROR. 127 | * \note If the handle uses CURLOPT_CONNECT_ONLY and passed to a executor instance this function gets invoked with nullptr when the socket is readable. 128 | */ 129 | void set_writefunction(std::function cb); 130 | /** 131 | * \brief Set a stream to write received data to. This removes any set writefunction. 132 | * \param stream Output stream used for writing. 133 | * \note The stream need to stay valid until the transfer is done 134 | */ 135 | void set_writestream(std::ostream& stream); 136 | /** 137 | * \brief Set a string variable to store receivd data into. This removes any set writefunction. 138 | * \param str String variable used to store received data. 139 | * \note The string need to stay valid until the transfer is done 140 | */ 141 | void set_writestring(std::string& str); 142 | 143 | /** 144 | * \brief Set a function to be called by curl if data is sent to the remote 145 | * \param cb Callback to be called 146 | * \note ptr points to a block os size bytes without zero termination. 147 | * \note The function can return CURL_READFUNC_PAUSE to pause the transfer or CURL_READFUNC_ABORT to abort. 148 | * \note The function shall return the number of bytes stored in ptr or 0 to signal EOF. 149 | * \note If the handle uses CURLOPT_CONNECT_ONLY and passed to a executor instance this function gets invoked with nullptr when the socket is writable. 150 | */ 151 | void set_readfunction(std::function cb); 152 | /** 153 | * \brief Set a stream to read transmitted data from. This removes any set readfunction. 154 | * \param stream Input stream used for reading. 155 | * \note The stream need to stay valid until the transfer is done 156 | */ 157 | void set_readstream(std::istream& stream); 158 | /** 159 | * \brief Set a string variable to read transmitted data from. This removes any set readfunction. 160 | * \param str String variable used to transmitt. 161 | * \note The string need to stay valid until the transfer is done 162 | */ 163 | void set_readstring(const std::string& str); 164 | 165 | /** 166 | * \brief Set a function to be called with progress information 167 | * \param cb Callback to be called 168 | * \note The function should return 0. Returning any non-zero value aborts the transfer. 169 | */ 170 | void set_progressfunction(std::function cb); 171 | /** 172 | * \brief Set a function to be called by curl if a header is received 173 | * \param cb Callback to be called 174 | * \note buffer points to a block os size bytes without zero termination. 175 | * \note If the value returned differs from size the transfer is aborted with CURLE_WRITE_ERROR. 176 | */ 177 | void set_headerfunction(std::function cb); 178 | /** 179 | * \brief Set a slist instance to store received headers into. 180 | * \param list The slist to store headers into. 181 | * \note The library detects line breaks and emits full lines to the slist. 182 | * \note The slist needs to stay valid for the entire duration of the transfer. 183 | */ 184 | void set_headerfunction_slist(slist& list); 185 | /** 186 | * \brief Set a function to invoke once the transfer is completed 187 | * \param cb Callback being invoked after completion with the result code. 188 | * \note This callback is mostly usefull with the multi interface, but it is also called in synchronous operation. 189 | */ 190 | void set_donefunction(std::function cb); 191 | 192 | /** 193 | * \brief Perform the transfer synchronously. This blocks until the operation finished. 194 | * \note Use the multi interface if you need asynchronous operation. 195 | */ 196 | void perform(); 197 | /** 198 | * \brief Reset the handle to the same state a freshly constructed handle has. 199 | * \note If the handle has an ongoing multi transfer, it is stopped and removed from the multi handle. 200 | */ 201 | void reset(); 202 | 203 | /** 204 | * \brief Perform connection upkeep work (keep alives) if supported. 205 | */ 206 | void upkeep(); 207 | /** 208 | * \brief Receive data on a CURLOPT_CONNECT_ONLY connection 209 | * \param buffer Buffer to read data into 210 | * \param buflen Size of the buffer to read into 211 | * \return ssize_t Number of bytes read, or 212 | * - -1 if no data is available (EAGAIN) 213 | * - 0 if the connection was closed 214 | */ 215 | std::ptrdiff_t recv(void* buffer, size_t buflen); 216 | /** 217 | * \brief Send data on a CURLOPT_CONNECT_ONLY connection 218 | * \param buffer Buffer to send 219 | * \param buflen Size of the buffer to send 220 | * \return ssize_t Number of bytes sent, or 221 | * - -1 if no the connection buffer is full (EAGAIN) 222 | * - 0 if the connection was closed 223 | */ 224 | std::ptrdiff_t send(const void* buffer, size_t buflen); 225 | /** 226 | * \brief Check if this handle has the CURLOPT_CONNECT_ONLY option set. 227 | * If this option is set the handle wont be automatically removed 228 | * from a executor when the done callback is executed. 229 | */ 230 | bool is_connect_only() const noexcept; 231 | /** 232 | * \brief Check if this handle has the CURLOPT_VERBOSE option set. 233 | */ 234 | bool is_verbose() const noexcept; 235 | 236 | /** 237 | * \brief Pause the transfer. 238 | * \param dirs Directions to pause (CURLPAUSE_RECV, CURLPAUSE_SEND or CURLPAUSE_ALL) 239 | * \note Unlike the regular curl_easy_pause function this keeps track of previous pauses and can not be used for unpausing. 240 | */ 241 | void pause(int dirs); 242 | /** 243 | * \brief Unpause the transfer. 244 | * \param dirs Directions to unpause (CURLPAUSE_RECV, CURLPAUSE_SEND or CURLPAUSE_ALL) 245 | * \note See pause() for details. 246 | */ 247 | void unpause(int dirs); 248 | /** 249 | * \brief Check if the transfer is paused. 250 | * \param dir CURLPAUSE_RECV or CURLPAUSE_SEND 251 | * \return true if the transfer is paused in this direction 252 | */ 253 | bool is_paused(int dir); 254 | 255 | /** 256 | * \brief Get a info element of type long 257 | * \param info The id of the info to get 258 | * \return The requested info 259 | */ 260 | long get_info_long(int info) const; 261 | /** 262 | * \brief Get a info element of type socket 263 | * \param info The id of the info to get 264 | * \return The requested socket 265 | */ 266 | uint64_t get_info_socket(int info) const; 267 | /** 268 | * \brief Get a info element of type double 269 | * \param info The id of the info to get 270 | * \return The requested info 271 | */ 272 | double get_info_double(int info) const; 273 | /** 274 | * \brief Get a info element of type string 275 | * \param info The id of the info to get 276 | * \return The requested info (do not free the returned value) 277 | */ 278 | const char* get_info_string(int info) const; 279 | /** 280 | * \brief Get a info element of type slist 281 | * \param info The id of the info to get 282 | * \return The requested info 283 | */ 284 | slist get_info_slist(int info) const; 285 | 286 | /** 287 | * \brief Get the http code of the last transfer. 288 | * \return The http status code 289 | */ 290 | long get_response_code() const; 291 | 292 | /** 293 | * \brief Get a pointer to the handle class from a raw curl structure. 294 | * \note Make sure the curl handle is actually part of a handle class, otherwise this will result in memory corruption. 295 | * \param curl The raw handle previously retrieved using raw(). 296 | * \return handle* A pointer to the wrapping handle. 297 | */ 298 | static handle* get_handle_from_raw(void* curl); 299 | }; 300 | } // namespace asyncpp::curl 301 | -------------------------------------------------------------------------------- /include/asyncpp/curl/multi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef __linux__ 7 | #include 8 | #elif _WIN32 9 | #include 10 | #endif 11 | 12 | struct curl_waitfd; 13 | namespace asyncpp::curl { 14 | class handle; 15 | class multi { 16 | std::recursive_mutex m_mtx; 17 | void* m_instance; 18 | int m_wakeup; // eventfd used to implement poll & wakeup on pre 7.68.0 19 | public: 20 | multi(); 21 | ~multi() noexcept; 22 | multi(const multi&) = delete; 23 | multi& operator=(const multi&) = delete; 24 | multi(multi&&) = delete; 25 | multi& operator=(multi&&) = delete; 26 | 27 | operator bool() const noexcept { return m_instance != nullptr; } 28 | bool operator!() const noexcept { return m_instance == nullptr; } 29 | 30 | void* raw() const noexcept { return m_instance; } 31 | 32 | void add_handle(curl::handle& hdl); 33 | void remove_handle(curl::handle& hdl); 34 | 35 | std::chrono::milliseconds timeout(); 36 | void perform(int* still_running); 37 | void wait(std::span extra_fds, int timeout_ms, int* num_fds); 38 | void poll(std::span extra_fds, int timeout_ms, int* num_fds); 39 | void wakeup(); 40 | void fdset(fd_set& read_set, fd_set& write_set, fd_set& exc_set, int& max_fd); 41 | 42 | enum class event_code { done = 1 }; 43 | struct event { 44 | event_code code; 45 | curl::handle* handle; 46 | int result_code; 47 | }; 48 | bool next_event(event& evt); 49 | 50 | void set_option_long(int opt, long val); 51 | void set_option_ptr(int opt, const void* str); 52 | void set_option_bool(int opt, bool on); 53 | }; 54 | } // namespace asyncpp::curl 55 | -------------------------------------------------------------------------------- /include/asyncpp/curl/sha1.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace asyncpp::curl { 7 | class sha1 { 8 | uint32_t m_state[5]; 9 | uint32_t m_count[2]{}; 10 | unsigned char m_buffer[64]{}; 11 | 12 | public: 13 | sha1() noexcept; 14 | void update(const void* data, size_t len) noexcept; 15 | void finish(void* digest) noexcept; 16 | 17 | void operator()(const void* data, size_t len) noexcept { update(data, len); } 18 | 19 | static std::string hash(const void* data, size_t len) { 20 | sha1 h; 21 | h(data, len); 22 | std::string res; 23 | res.resize(20); 24 | h.finish(res.data()); 25 | return res; 26 | } 27 | static std::string hash(const std::string& data) { return hash(data.data(), data.size()); } 28 | 29 | private: 30 | }; 31 | } // namespace asyncpp::curl 32 | -------------------------------------------------------------------------------- /include/asyncpp/curl/slist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace asyncpp::curl { 7 | class slist; 8 | class handle; 9 | class slist_iterator { 10 | curl_slist* m_current = nullptr; 11 | friend class slist; 12 | 13 | public: 14 | using iterator_category = std::forward_iterator_tag; 15 | using difference_type = std::ptrdiff_t; // This is useless, but c++ requires it 16 | using value_type = char*; 17 | using pointer = value_type*; 18 | using reference = value_type&; 19 | 20 | constexpr explicit slist_iterator(curl_slist* elem = nullptr) : m_current{elem} {} 21 | 22 | reference operator*() const noexcept { return m_current->data; } 23 | pointer operator->() const noexcept { return &m_current->data; } 24 | slist_iterator& operator++() noexcept { 25 | if (m_current != nullptr) m_current = m_current->next; 26 | return *this; 27 | } 28 | slist_iterator operator++(int) noexcept { 29 | slist_iterator tmp = *this; 30 | ++(*this); 31 | return tmp; 32 | } 33 | friend bool operator==(const slist_iterator& a, const slist_iterator& b) { return a.m_current == b.m_current; } 34 | friend bool operator!=(const slist_iterator& a, const slist_iterator& b) { return a.m_current != b.m_current; } 35 | }; 36 | 37 | class slist { 38 | curl_slist* m_first_node = nullptr; 39 | curl_slist* m_last_node = nullptr; // Used to speed up append 40 | friend class handle; 41 | 42 | public: 43 | inline static const struct ownership_take_tag { 44 | } ownership_take; 45 | inline static const struct ownership_copy_tag { 46 | } ownership_copy; 47 | constexpr slist() = default; 48 | // Takes ownership 49 | constexpr slist(curl_slist* raw, ownership_take_tag) noexcept : m_first_node{raw} { 50 | while (raw != nullptr && raw->next != nullptr) 51 | raw = raw->next; 52 | m_last_node = raw; 53 | } 54 | slist(const curl_slist* raw, ownership_copy_tag); 55 | slist(const slist& other); 56 | slist& operator=(const slist& other); 57 | constexpr slist(slist&& other) noexcept : m_first_node{other.m_first_node}, m_last_node{other.m_last_node} { 58 | other.m_first_node = nullptr; 59 | other.m_last_node = nullptr; 60 | } 61 | constexpr slist& operator=(slist&& other) noexcept { 62 | m_first_node = std::exchange(other.m_first_node, m_first_node); 63 | m_last_node = std::exchange(other.m_last_node, m_last_node); 64 | return *this; 65 | } 66 | ~slist() noexcept { clear(); } 67 | 68 | slist_iterator append(const char* str) { return insert_after(end(), str); } 69 | slist_iterator prepend(const char* str); 70 | [[nodiscard]] constexpr slist_iterator index(size_t idx) const noexcept { 71 | auto node = m_first_node; 72 | for (size_t i = 0; i != idx && node != nullptr; i++) 73 | node = node->next; 74 | return slist_iterator{node}; 75 | } 76 | slist_iterator insert(size_t idx, const char* str) { 77 | if (idx == 0) return prepend(str); 78 | return insert_after(index(idx - 1), str); 79 | } 80 | slist_iterator insert_after(slist_iterator it, const char* str); 81 | void remove(size_t index); 82 | void remove(slist_iterator it); 83 | void clear(); 84 | [[nodiscard]] constexpr bool empty() const noexcept { return m_first_node == nullptr; } 85 | 86 | [[nodiscard]] constexpr slist_iterator begin() const noexcept { return slist_iterator{m_first_node}; } 87 | [[nodiscard]] constexpr slist_iterator end() const noexcept { return slist_iterator{nullptr}; } 88 | 89 | [[nodiscard]] constexpr curl_slist* release() noexcept { 90 | m_last_node = nullptr; 91 | return std::exchange(m_first_node, nullptr); 92 | } 93 | }; 94 | } // namespace asyncpp::curl 95 | -------------------------------------------------------------------------------- /include/asyncpp/curl/tcp_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asyncpp::curl { 10 | class executor; 11 | class tcp_client { 12 | public: 13 | /** 14 | * \brief Construct a new tcp client 15 | * \param e The executor to use for the connection 16 | */ 17 | tcp_client(executor& e); 18 | /** \brief Construct a new tcp client using the default executor */ 19 | tcp_client(); 20 | /** \brief Destructor */ 21 | ~tcp_client() noexcept; 22 | tcp_client(const tcp_client&) = delete; 23 | tcp_client& operator=(const tcp_client&) = delete; 24 | tcp_client(tcp_client&&) = delete; 25 | tcp_client& operator=(tcp_client&&) = delete; 26 | 27 | executor& get_executor() noexcept { return m_executor; } 28 | handle& get_handle() noexcept { return m_handle; } 29 | 30 | bool is_connected() const noexcept; 31 | 32 | enum class callback_result : uint32_t { 33 | none = 0, 34 | /** \brief Pause receiving data, you can use pause_receive(bool) to unpause the receiving again. */ 35 | pause = 1, 36 | /** \brief Clear the callback. Receiving will be pause and your callback is cleared. */ 37 | clear = 2, 38 | }; 39 | 40 | /** 41 | * \brief Set a callback to be invoked when data for reading is availble on the socket. 42 | * 43 | * Use recv_raw to retrieve (some) of the data. 44 | * The callback is passed a bool which gets set to true if the socket was disconnected. 45 | * The return value is a bitset of callback_result and can be used to pause and clear the callback if necessary. 46 | * \note It is not possible to use set_on_data_available() and recv()/recv_all() concurrently. 47 | * \note There might not be data available even if this callback is invoked, you need to check the result of recv_raw(). 48 | * \note The only recv call valid inside the callback is recv_raw(). 49 | * 50 | * \param cb The callback to invoke when data is ready. 51 | */ 52 | void set_on_data_available(std::function cb); 53 | 54 | /** 55 | * \brief Receive data 56 | * \param buffer Buffer to read data into 57 | * \param buflen Size of the buffer to read into 58 | * \return ssize_t Number of bytes read, or 59 | * - -1 if no data is available (EAGAIN) 60 | * - 0 if the connection was closed 61 | */ 62 | std::ptrdiff_t recv_raw(void* buffer, size_t buflen); 63 | 64 | /** 65 | * \brief Pause or unpause the receiving of data. 66 | * \param paused true => Receiving is paused, false => Data is received 67 | */ 68 | void pause_receive(bool paused); 69 | 70 | /** 71 | * \brief Connect to the given remote server 72 | * \param remote The remote server, can be an ip or domain name 73 | * \param port The remote port 74 | * \param ssl Use ssl for connecting 75 | * \param cb Callback to invoke after the connection attempt was made 76 | * \note Due to a bug in libcurl this is not actually async. 77 | */ 78 | void connect(std::string remote, uint16_t port, bool ssl, std::function cb); 79 | 80 | /** 81 | * \brief Connect to the given remote server 82 | * \param remote The remote server, can be an ip or domain name 83 | * \param port The remote port 84 | * \param ssl Use ssl for connecting 85 | * \return auto A awaitable type 86 | * \note Due to a bug in libcurl this is not actually async. 87 | */ 88 | [[nodiscard]] auto connect(std::string remote, uint16_t port, bool ssl) { 89 | struct awaiter { 90 | tcp_client* const m_that; 91 | std::string m_remote; 92 | uint16_t const m_port; 93 | bool const m_ssl; 94 | int m_result{0}; 95 | 96 | constexpr bool await_ready() const noexcept { return false; } 97 | void await_suspend(coroutine_handle<> h) noexcept { 98 | m_that->connect(std::move(m_remote), m_port, m_ssl, [this, h](int code) { 99 | m_result = code; 100 | h.resume(); 101 | }); 102 | } 103 | constexpr void await_resume() const { 104 | if (m_result != 0) throw exception(m_result, false); 105 | } 106 | }; 107 | return awaiter{this, remote, port, ssl}; 108 | } 109 | 110 | /** 111 | * \brief Close the currently active connection 112 | * \param cb Invoked after the connection is closed 113 | */ 114 | void disconnect(std::function cb); 115 | 116 | /** 117 | * \brief Disconnect from the server. This will reset the contained handle. 118 | * \return auto A awaitable type 119 | */ 120 | [[nodiscard]] auto disconnect() { 121 | struct awaiter { 122 | tcp_client* const m_that; 123 | constexpr bool await_ready() const noexcept { return false; } 124 | void await_suspend(coroutine_handle<> h) noexcept { 125 | m_that->disconnect([h]() { h.resume(); }); 126 | } 127 | constexpr void await_resume() const noexcept {} 128 | }; 129 | return awaiter{this}; 130 | } 131 | 132 | /** 133 | * \brief Send data to the remote host. 134 | * \param buffer Pointer to the data to send 135 | * \param size Size of the data to send in bytes. 136 | * \param cb Callback to be invoke with the result. The callback is passed the number of bytes transmitted or 0 if the connection was closed. 137 | */ 138 | void send(const void* buffer, size_t size, std::function cb); 139 | 140 | /** 141 | * \brief Send data to the remote host. 142 | * \param buffer Pointer to the data to send 143 | * \param size Size of the data to send in bytes. 144 | * \return auto An awaitable for the send operation. The return value indicates the number of bytes transmitted or 0 if the connection was closed. 145 | */ 146 | [[nodiscard]] auto send(const void* buffer, size_t size) { 147 | struct awaiter { 148 | tcp_client* const m_that; 149 | const void* const m_buffer; 150 | const size_t m_size; 151 | size_t m_result{}; 152 | constexpr bool await_ready() const noexcept { return false; } 153 | void await_suspend(coroutine_handle<> h) noexcept { 154 | m_that->send(m_buffer, m_size, [this, h](size_t res) { 155 | m_result = res; 156 | h.resume(); 157 | }); 158 | } 159 | constexpr size_t await_resume() const noexcept { return m_result; } 160 | }; 161 | return awaiter{this, buffer, size}; 162 | } 163 | 164 | /** 165 | * \brief Send all provided data to the remote host. 166 | * \param buffer Pointer to the data to send 167 | * \param size Size of the data to send in bytes. 168 | * \param cb Callback to be invoke with the result. The callback is passed the number of bytes transmitted or 0 if the connection was closed. 169 | * \note This differs from send() and will not call the callback until either the connection is closed or all data has been sent. 170 | */ 171 | void send_all(const void* buffer, size_t size, std::function cb); 172 | 173 | /** 174 | * \brief Send all provided data to the remote host. 175 | * \param buffer Pointer to the data to send 176 | * \param size Size of the data to send in bytes. 177 | * \return auto An awaitable for the send_all operation. The return value indicates the number of bytes transmitted or 0 if the connection was closed. 178 | * \note This differs from send() and will not return until either the connection is closed or all data has been sent. 179 | */ 180 | [[nodiscard]] auto send_all(const void* buffer, size_t size) { 181 | struct awaiter { 182 | tcp_client* const m_that; 183 | const void* const m_buffer; 184 | const size_t m_size; 185 | size_t m_result{}; 186 | constexpr bool await_ready() const noexcept { return false; } 187 | void await_suspend(coroutine_handle<> h) noexcept { 188 | m_that->send_all(m_buffer, m_size, [this, h](size_t res) { 189 | m_result = res; 190 | h.resume(); 191 | }); 192 | } 193 | constexpr size_t await_resume() const noexcept { return m_result; } 194 | }; 195 | return awaiter{this, buffer, size}; 196 | } 197 | 198 | /** 199 | * \brief Receive some data from the remote host. 200 | * \param buffer Pointer to the data to store received data at 201 | * \param size Size of the buffer to store data at 202 | * \param cb Callback to get invoked once data is received. The size of the received data is passed, or zero if the connection was closed. 203 | */ 204 | void recv(void* buffer, size_t size, std::function cb); 205 | 206 | /** 207 | * \brief Receive some data from the remote host. 208 | * \param buffer Pointer to the data to store received data at 209 | * \param size Size of the buffer to store data at 210 | * \return auto An awaitable for the recv operation. The return value indicates the size of the received data, or zero if the connection was closed. 211 | */ 212 | [[nodiscard]] auto recv(void* buffer, size_t size) { 213 | struct awaiter { 214 | tcp_client* const m_that; 215 | void* const m_buffer; 216 | const size_t m_size; 217 | size_t m_result{}; 218 | constexpr bool await_ready() const noexcept { return false; } 219 | void await_suspend(coroutine_handle<> h) noexcept { 220 | m_that->recv(m_buffer, m_size, [this, h](size_t res) { 221 | m_result = res; 222 | h.resume(); 223 | }); 224 | } 225 | constexpr size_t await_resume() const noexcept { return m_result; } 226 | }; 227 | return awaiter{this, buffer, size}; 228 | } 229 | 230 | /** 231 | * \brief Receive the specified amount of data from the remote host. 232 | * \param buffer Pointer to the data to store received data at 233 | * \param size Size of the buffer to store data at 234 | * \param cb Callback to get invoked once data is received. The return value indicates the size of the received data, or zero if the connection was closed. 235 | * \note This differs from recv() and will not return until either the connection is closed or all data has been received. 236 | */ 237 | void recv_all(void* buffer, size_t size, std::function cb); 238 | 239 | /** 240 | * \brief Receive the specified amount of data from the remote host. 241 | * \param buffer Pointer to the data to store received data at 242 | * \param size Size of the buffer to store data at 243 | * \return auto An awaitable for the recv_all operation. The return value indicates the size of the received data, or zero if the connection was closed. 244 | * \note This differs from recv() and will not return until either the connection is closed or all data has been received. 245 | */ 246 | [[nodiscard]] auto recv_all(void* buffer, size_t size) { 247 | struct awaiter { 248 | tcp_client* const m_that; 249 | void* const m_buffer; 250 | const size_t m_size; 251 | size_t m_result{}; 252 | constexpr bool await_ready() const noexcept { return false; } 253 | void await_suspend(coroutine_handle<> h) noexcept { 254 | m_that->recv_all(m_buffer, m_size, [this, h](size_t res) { 255 | m_result = res; 256 | h.resume(); 257 | }); 258 | } 259 | constexpr size_t await_resume() const noexcept { return m_result; } 260 | }; 261 | return awaiter{this, buffer, size}; 262 | } 263 | 264 | private: 265 | curl::executor& m_executor; 266 | curl::handle m_handle; 267 | 268 | mutable std::recursive_mutex m_mtx; 269 | bool m_is_connected; 270 | std::function m_send_handler; 271 | std::function m_recv_handler; 272 | }; 273 | 274 | inline constexpr tcp_client::callback_result operator|(tcp_client::callback_result lhs, tcp_client::callback_result rhs) noexcept { 275 | return static_cast(static_cast(lhs) | static_cast(rhs)); 276 | } 277 | 278 | inline constexpr tcp_client::callback_result operator&(tcp_client::callback_result lhs, tcp_client::callback_result rhs) noexcept { 279 | return static_cast(static_cast(lhs) & static_cast(rhs)); 280 | } 281 | 282 | inline constexpr bool operator!(tcp_client::callback_result lhs) noexcept { return lhs == tcp_client::callback_result::none; } 283 | } // namespace asyncpp::curl 284 | -------------------------------------------------------------------------------- /include/asyncpp/curl/uri.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asyncpp::curl { 10 | class uri { 11 | public: 12 | uri() = default; 13 | uri(std::string_view str); 14 | uri(const char* str) : uri(std::string_view{str}) {} 15 | uri(const std::string& str) : uri(std::string_view{str}) {} 16 | uri(const uri&) = default; 17 | uri(uri&&) = default; 18 | uri& operator=(const uri&) = default; 19 | uri& operator=(uri&&) = default; 20 | 21 | const std::string& scheme() const noexcept { return m_scheme; } 22 | const std::string& auth() const noexcept { return m_auth; } 23 | const std::string& host() const noexcept { return m_host; } 24 | int port() const noexcept { return m_port; } 25 | const std::string& path() const noexcept { return m_path; } 26 | const std::string& query() const noexcept { return m_query; } 27 | const std::unordered_multimap& query_parsed() const { 28 | if (m_parsed_query.has_value()) return *m_parsed_query; 29 | m_parsed_query = parse_formdata(m_query); 30 | return *m_parsed_query; 31 | } 32 | const std::string& fragment() const noexcept { return m_fragment; } 33 | 34 | std::string path_query() const { return m_query.empty() ? m_path : (m_path + '?' + m_query); } 35 | 36 | void scheme(std::string str) { m_scheme = std::move(str); } 37 | void scheme(std::string_view str) { m_scheme = std::string(str); } 38 | void auth(std::string str) { m_auth = std::move(str); } 39 | void auth(std::string_view str) { m_auth = std::string(str); } 40 | void host(std::string str) { m_host = std::move(str); } 41 | void host(std::string_view str) { m_host = std::string(str); } 42 | void port(int port) { m_port = (std::max)(-1, (std::min)(port, 65535)); } 43 | void path(std::string str) { m_path = std::move(str); } 44 | void path(std::string_view str) { m_path = std::string(str); } 45 | void query(std::string str) { m_query = std::move(str); } 46 | void query(std::string_view str) { m_query = std::string(str); } 47 | void fragment(std::string str) { m_fragment = std::move(str); } 48 | void fragment(std::string_view str) { m_fragment = std::string(str); } 49 | 50 | bool is_empty() const noexcept { 51 | return m_scheme.empty() && m_auth.empty() && m_host.empty() && m_port == -1 && m_path.empty() && m_query.empty() && m_fragment.empty(); 52 | } 53 | bool is_relative() const noexcept { return m_host.empty(); } 54 | bool is_port_default() const noexcept { return port() == -1; } 55 | bool is_authority() const noexcept { return m_path.empty() && m_query.empty() && m_fragment.empty(); } 56 | std::string to_string() const; 57 | 58 | static std::string encode(std::string_view str); 59 | static std::string decode(std::string_view str); 60 | static std::unordered_multimap parse_formdata(std::string_view data); 61 | template 62 | static std::string build_formdata(ItBegin it, ItEnd end) { 63 | std::string formdata; 64 | for (; it != end; ++it) { 65 | if (!formdata.empty()) formdata += "&"; 66 | formdata += encode(it->first); 67 | formdata += "="; 68 | formdata += encode(it->second); 69 | } 70 | return formdata; 71 | } 72 | 73 | static std::vector split_path(std::string_view path) { 74 | std::vector result; 75 | size_t offset = 0; 76 | auto pos = path.find('/', offset); 77 | while (pos != std::string_view::npos) { 78 | if (auto len = pos - offset; len != 0) result.emplace_back(path.substr(offset, len)); 79 | offset = pos + 1; 80 | pos = path.find('/', offset); 81 | } 82 | if (auto len = path.size() - offset; len != 0) result.emplace_back(path.substr(offset, len)); 83 | return result; 84 | } 85 | 86 | template 87 | static std::string merge_path(ItBegin it, ItEnd end) { 88 | std::string result = "/"; 89 | for (; it != end; ++it) { 90 | result += *it; 91 | result += "/"; 92 | } 93 | return result; 94 | } 95 | 96 | static std::optional parse(std::string_view str); 97 | 98 | friend bool operator==(const uri& lhs, const uri& rhs) noexcept { 99 | return std::tie(lhs.m_scheme, lhs.m_auth, lhs.m_host, lhs.m_port, lhs.m_path, lhs.m_query, lhs.m_fragment) == 100 | std::tie(rhs.m_scheme, rhs.m_auth, rhs.m_host, rhs.m_port, rhs.m_path, rhs.m_query, rhs.m_fragment); 101 | } 102 | friend bool operator!=(const uri& lhs, const uri& rhs) noexcept { return !(lhs == rhs); } 103 | friend bool operator<(const uri& lhs, const uri& rhs) noexcept { 104 | return std::tie(lhs.m_scheme, lhs.m_auth, lhs.m_host, lhs.m_port, lhs.m_path, lhs.m_query, lhs.m_fragment) < 105 | std::tie(rhs.m_scheme, rhs.m_auth, rhs.m_host, rhs.m_port, rhs.m_path, rhs.m_query, rhs.m_fragment); 106 | } 107 | friend bool operator>(const uri& lhs, const uri& rhs) noexcept { 108 | return std::tie(lhs.m_scheme, lhs.m_auth, lhs.m_host, lhs.m_port, lhs.m_path, lhs.m_query, lhs.m_fragment) > 109 | std::tie(rhs.m_scheme, rhs.m_auth, rhs.m_host, rhs.m_port, rhs.m_path, rhs.m_query, rhs.m_fragment); 110 | } 111 | friend bool operator<=(const uri& lhs, const uri& rhs) noexcept { 112 | return std::tie(lhs.m_scheme, lhs.m_auth, lhs.m_host, lhs.m_port, lhs.m_path, lhs.m_query, lhs.m_fragment) <= 113 | std::tie(rhs.m_scheme, rhs.m_auth, rhs.m_host, rhs.m_port, rhs.m_path, rhs.m_query, rhs.m_fragment); 114 | } 115 | friend bool operator>=(const uri& lhs, const uri& rhs) noexcept { 116 | return std::tie(lhs.m_scheme, lhs.m_auth, lhs.m_host, lhs.m_port, lhs.m_path, lhs.m_query, lhs.m_fragment) >= 117 | std::tie(rhs.m_scheme, rhs.m_auth, rhs.m_host, rhs.m_port, rhs.m_path, rhs.m_query, rhs.m_fragment); 118 | } 119 | 120 | private: 121 | std::string m_scheme; 122 | std::string m_auth; 123 | std::string m_host; 124 | int m_port{-1}; 125 | std::string m_path; 126 | std::string m_query; 127 | std::string m_fragment; 128 | mutable std::optional> m_parsed_query; 129 | }; 130 | } // namespace asyncpp::curl 131 | -------------------------------------------------------------------------------- /include/asyncpp/curl/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace asyncpp::curl { 9 | struct case_insensitive_less { 10 | bool operator()(const std::string& lhs, const std::string& rhs) const { 11 | return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), 12 | [](const char c1, const char c2) { return tolower(c1) < tolower(c2); }); 13 | } 14 | }; 15 | 16 | template 17 | inline void string_ltrim(T& s) { 18 | auto last = s.find_first_not_of(" \t\f\v\n\r"); 19 | s.erase(0, last); 20 | } 21 | 22 | template 23 | inline void string_rtrim(T& s) { 24 | auto last = s.find_last_not_of(" \t\f\v\n\r"); 25 | if (last != T::npos) s.erase(last + 1); 26 | } 27 | 28 | template 29 | inline void string_trim(T& s) { 30 | string_rtrim(s); 31 | string_ltrim(s); 32 | } 33 | 34 | template 35 | inline std::vector string_split(const TStr& str, const TDelim& delim) { 36 | std::vector res; 37 | size_t offset = 0; 38 | do { 39 | size_t pos = str.find(delim, offset); 40 | res.emplace_back(str.substr(offset, (pos == TStr::npos) ? pos : (pos - offset))); 41 | offset = (pos != TStr::npos) ? pos + delim.length() : pos; 42 | } while (offset != TStr::npos); 43 | return res; 44 | } 45 | 46 | struct utf8_validator { 47 | enum class mode { none, normal, strict, pedantic, extreme }; 48 | enum class result { invalid, valid, valid_incomplete }; 49 | /** 50 | * \brief Validate a string_view in the specified mode 51 | * 52 | * \tparam m The check mode 53 | * \param sv The string to validate 54 | * \param last_pos if non nullptr this is set to the start of the first erroneous codepoint or sv.end() if the string is valid 55 | * \return Result of the check 56 | */ 57 | template 58 | constexpr result validate(std::string_view sv, std::string_view::const_iterator* last_pos = nullptr) const noexcept { 59 | if constexpr (m == mode::none) { 60 | if (last_pos) *last_pos = sv.end(); 61 | return result::valid; 62 | } 63 | for (auto it = sv.begin(); it != sv.end(); it++) { 64 | if (last_pos) *last_pos = it; 65 | unsigned char c = static_cast(*it); 66 | auto bits = std::countl_one(c); 67 | // Note: We parse the codepoint on every mode, but the compiler is 68 | // smart enough to optimize it away even at -O1 if its never read. 69 | uint32_t codepoint = (c & ((1 << (7 - bits)) - 1)); 70 | switch (bits) { 71 | case 0: continue; 72 | case 1: return result::invalid; // 10xxxxxx is continuation but not at the start 73 | case 6: 74 | if constexpr (m != mode::normal) return result::invalid; 75 | if (++it == sv.end()) { 76 | if constexpr (m == mode::normal || m == mode::strict) 77 | return result::valid_incomplete; 78 | else 79 | return result::invalid; 80 | } 81 | c = static_cast(*it); 82 | if ((c & 0xc0) != 0x80) return result::invalid; 83 | codepoint = (codepoint << 6) | (c & 0x3f); 84 | [[fallthrough]]; 85 | case 5: 86 | if constexpr (m != mode::normal) return result::invalid; 87 | if (++it == sv.end()) { 88 | if constexpr (m == mode::normal || m == mode::strict) 89 | return result::valid_incomplete; 90 | else 91 | return result::invalid; 92 | } 93 | c = static_cast(*it); 94 | if ((c & 0xc0) != 0x80) return result::invalid; 95 | codepoint = (codepoint << 6) | (c & 0x3f); 96 | [[fallthrough]]; 97 | case 4: 98 | if (++it == sv.end()) { 99 | if constexpr (m == mode::normal || m == mode::strict) 100 | return result::valid_incomplete; 101 | else 102 | return ((codepoint << 18) > 0x10FFFF) ? result::invalid : result::valid_incomplete; 103 | } 104 | c = static_cast(*it); 105 | if ((c & 0xc0) != 0x80) return result::invalid; 106 | codepoint = (codepoint << 6) | (c & 0x3f); 107 | [[fallthrough]]; 108 | case 3: 109 | if (++it == sv.end()) { 110 | if constexpr (m == mode::normal || m == mode::strict) 111 | return result::valid_incomplete; 112 | else 113 | return ((codepoint << 12) > 0x10FFFF) ? result::invalid : result::valid_incomplete; 114 | } 115 | c = static_cast(*it); 116 | if ((c & 0xc0) != 0x80) return result::invalid; 117 | codepoint = (codepoint << 6) | (c & 0x3f); 118 | [[fallthrough]]; 119 | case 2: 120 | if (++it == sv.end()) { 121 | if constexpr (m == mode::normal || m == mode::strict) 122 | return result::valid_incomplete; 123 | else 124 | return ((codepoint << 6) > 0x10FFFF) ? result::invalid : result::valid_incomplete; 125 | } 126 | c = static_cast(*it); 127 | if ((c & 0xc0) != 0x80) return result::invalid; 128 | codepoint = (codepoint << 6) | (c & 0x3f); 129 | break; 130 | default: return result::invalid; 131 | } 132 | if constexpr (m == mode::strict || m == mode::pedantic || m == mode::extreme) { 133 | //std::cout << codepoint << " " << bits << std::endl; 134 | // Validate that the smallest possible encoding was used 135 | if (codepoint < 128 && bits != 0) return result::invalid; 136 | if (codepoint < 2048 && bits > 2) return result::invalid; 137 | if (codepoint < 65536 && bits > 3) return result::invalid; 138 | if (codepoint < 1048576 && bits > 4) return result::invalid; 139 | } 140 | if constexpr (m == mode::pedantic || m == mode::extreme) { 141 | // Surrogate values are disallowed 142 | if (codepoint >= 0xd800 && codepoint <= 0xdfff) return result::invalid; 143 | // 0xfeff should never appear in a valid utf-8 sequence 144 | if ((codepoint & 0xffe0ffffu) == 0xfeff) return result::invalid; 145 | // 0xfdd0 - 0xfdef are allocated for "application specific purposes" 146 | if (codepoint >= 0xfdd0 && codepoint <= 0xfdef) return result::invalid; 147 | // Values above 0x10FFFF are disallowed since 2000 because of UTF-16 148 | // See http://www.unicode.org/L2/L2000/00079-n2175.htm 149 | if (codepoint > 0x10FFFF) return result::invalid; 150 | } 151 | if constexpr (m == mode::extreme) { 152 | // 0xffff and 0xfeff should never appear in a valid utf-8 sequence 153 | if ((codepoint & 0xffe0ffffu) == 0xffff || (codepoint & 0xffe0ffffu) == 0xfffe) return result::invalid; 154 | } 155 | } 156 | if (last_pos) *last_pos = sv.end(); 157 | return result::valid; 158 | } 159 | /** 160 | * \brief Validate a string_view in the specified mode 161 | * 162 | * \param sv The string to validate 163 | * \param m The check mode 164 | * \param last_pos if non nullptr this is set to the start of the first erroneous codepoint or sv.end() if the string is valid 165 | * \return Result of the check 166 | */ 167 | constexpr result operator()(std::string_view sv, mode m = mode::normal, std::string_view::const_iterator* last_pos = nullptr) const noexcept { 168 | switch (m) { 169 | case mode::none: return validate(sv, last_pos); 170 | case mode::normal: return validate(sv, last_pos); 171 | case mode::strict: return validate(sv, last_pos); 172 | case mode::pedantic: return validate(sv, last_pos); 173 | case mode::extreme: return validate(sv, last_pos); 174 | default: return result::invalid; 175 | } 176 | } 177 | }; 178 | 179 | } // namespace asyncpp::curl 180 | -------------------------------------------------------------------------------- /include/asyncpp/curl/version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | namespace asyncpp::curl { 7 | class cstring_array_iterator { 8 | const char* const* m_pos{nullptr}; 9 | 10 | public: 11 | struct helper; 12 | constexpr cstring_array_iterator() noexcept = default; 13 | constexpr cstring_array_iterator(const char* const* array) noexcept : m_pos(array) {} 14 | constexpr cstring_array_iterator(const cstring_array_iterator& other) noexcept = default; 15 | constexpr cstring_array_iterator& operator=(const cstring_array_iterator& other) noexcept = default; 16 | 17 | constexpr cstring_array_iterator& operator++() noexcept { 18 | if (m_pos && *m_pos) m_pos++; 19 | return *this; 20 | } 21 | constexpr cstring_array_iterator operator++(int) noexcept { 22 | auto temp = *this; 23 | ++(*this); 24 | return temp; 25 | } 26 | 27 | constexpr bool operator==(const cstring_array_iterator& other) const noexcept { 28 | if (other.m_pos == nullptr) return m_pos == nullptr || *m_pos == nullptr; 29 | return m_pos == other.m_pos; 30 | } 31 | constexpr bool operator!=(const cstring_array_iterator& other) const noexcept { return !(*this == other); } 32 | 33 | constexpr std::string_view operator*() const noexcept { 34 | if (m_pos == nullptr || *m_pos == nullptr) return {}; 35 | return *m_pos; 36 | } 37 | }; 38 | struct cstring_array_iterator::helper { 39 | cstring_array_iterator iterator; 40 | constexpr cstring_array_iterator begin() const noexcept { return iterator; } 41 | constexpr cstring_array_iterator end() const noexcept { return {}; } 42 | }; 43 | class version { 44 | void* m_info; 45 | 46 | public: 47 | enum class feature; 48 | version() noexcept; 49 | constexpr version(const version& other) noexcept : m_info(other.m_info) {} 50 | constexpr version& operator=(const version& other) noexcept { 51 | m_info = other.m_info; 52 | return *this; 53 | } 54 | 55 | std::string_view curl_version() const noexcept; 56 | unsigned int curl_version_num() const noexcept; 57 | std::string_view host() const noexcept; 58 | bool has_feature(feature f) const noexcept; 59 | std::string_view ssl_version() const noexcept; 60 | std::string_view libz_version() const noexcept; 61 | cstring_array_iterator::helper protocols() const noexcept; 62 | std::string_view protocol(size_t index) const noexcept; 63 | size_t protocol_count() const noexcept; 64 | 65 | /* The fields below this were added in CURLVERSION_SECOND */ 66 | std::string_view ares_version() const noexcept; 67 | int ares_version_num() const noexcept; 68 | 69 | /* This field was added in CURLVERSION_THIRD */ 70 | std::string_view libidn_version() const noexcept; 71 | 72 | /* These field were added in CURLVERSION_FOURTH */ 73 | std::string_view iconv_version() const noexcept; 74 | int iconv_num() const noexcept; 75 | std::string_view libssh_version() const noexcept; 76 | 77 | /* These fields were added in CURLVERSION_FIFTH */ 78 | unsigned int brotli_version_num() const noexcept; 79 | std::string_view brotli_version() const noexcept; 80 | 81 | /* These fields were added in CURLVERSION_SIXTH */ 82 | unsigned int nghttp2_version_num() const noexcept; 83 | std::string_view nghttp2_version() const noexcept; 84 | std::string_view quic_version() const noexcept; 85 | 86 | /* These fields were added in CURLVERSION_SEVENTH */ 87 | std::string_view cainfo() const noexcept; 88 | std::string_view capath() const noexcept; 89 | 90 | /* These fields were added in CURLVERSION_EIGHTH */ 91 | unsigned int zstd_version_num() const noexcept; 92 | std::string_view zstd_version() const noexcept; 93 | 94 | /* These fields were added in CURLVERSION_NINTH */ 95 | std::string_view hyper_version() const noexcept; 96 | 97 | /* These fields were added in CURLVERSION_TENTH */ 98 | std::string_view gsasl_version() const noexcept; 99 | 100 | enum class feature { 101 | ipv6, 102 | kerberos4, 103 | ssl, 104 | libz, 105 | ntlm, 106 | gssnegotiate, 107 | debug, 108 | asynchdns, 109 | spnego, 110 | largefile, 111 | idn, 112 | sspi, 113 | conv, 114 | curldebug, 115 | tlsauth_srp, 116 | ntlm_wb, 117 | http2, 118 | gssapi, 119 | kerberos5, 120 | unix_sockets, 121 | psl, 122 | https_proxy, 123 | multi_ssl, 124 | brotli, 125 | altsvc, 126 | http3, 127 | zstd, 128 | unicode, 129 | hsts, 130 | gsasl 131 | }; 132 | static std::span features() noexcept; 133 | static std::string_view feature_name(feature f) noexcept; 134 | static feature next_feature(feature f) noexcept; 135 | }; 136 | } // namespace asyncpp::curl 137 | -------------------------------------------------------------------------------- /include/asyncpp/curl/webclient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace asyncpp::curl { 15 | class executor; 16 | class handle; 17 | 18 | struct http_response { 19 | struct ignore_body {}; 20 | struct inline_body {}; 21 | using body_storage_t = std::variant>; 22 | 23 | /** \brief The HTTP status code returned from the transfer */ 24 | int status_code; 25 | /** \brief The HTTP status message returned from the transfer */ 26 | std::string status_message; 27 | /** \brief Headers received */ 28 | std::multimap headers; 29 | /** \brief Cookies received/persisted */ 30 | std::vector cookies; 31 | /** \brief The response body if the store mode was inline_body */ 32 | std::string body; 33 | }; 34 | 35 | struct http_request { 36 | struct no_body {}; 37 | using body_provider_t = std::variant>; 38 | 39 | /** \brief Method to use for the request */ 40 | std::string request_method{}; 41 | /** \brief The url to request */ 42 | uri url{}; 43 | /** \brief Outgoing headers */ 44 | std::multimap headers{}; 45 | /** \brief Cookies to send along the request */ 46 | std::vector cookies; 47 | /** \brief Upload body policy */ 48 | body_provider_t body_provider{}; 49 | /** \brief Follow http redirects and return the last requests info */ 50 | bool follow_redirects{true}; 51 | /** \brief Enable verbose logging of curl */ 52 | bool verbose{false}; 53 | /** \brief Timeout for the entire operation, set to 0 to disable */ 54 | std::chrono::milliseconds timeout{0}; 55 | /** \brief Timeout for connecting (namelookup, proxy handling, connect), set to 0 to disable */ 56 | std::chrono::milliseconds timeout_connect = std::chrono::seconds{30}; 57 | /** \brief Hook executed right before performing the request. Can be used to set custom curl options if needed. */ 58 | std::function configure_hook; 59 | /** \brief Hook executed right after performing the request. Can be used to read custom curl info if needed. */ 60 | std::function result_hook; 61 | 62 | static http_request make_get(uri url); 63 | static http_request make_head(uri url); 64 | static http_request make_post(uri url, body_provider_t body = no_body{}); 65 | static http_request make_patch(uri url, body_provider_t body = no_body{}); 66 | static http_request make_put(uri url, body_provider_t body = no_body{}); 67 | static http_request make_delete(uri url); 68 | 69 | http_response execute_sync(http_response::body_storage_t body_store_method = http_response::inline_body{}); 70 | struct execute_awaiter { 71 | execute_awaiter(http_request& req, http_response::body_storage_t storage, executor* exec, std::stop_token st = {}); 72 | ~execute_awaiter(); 73 | execute_awaiter(const execute_awaiter&) = delete; 74 | execute_awaiter(execute_awaiter&& other) : m_impl{other.m_impl} { other.m_impl = nullptr; } 75 | execute_awaiter& operator=(const execute_awaiter&) = delete; 76 | execute_awaiter& operator=(execute_awaiter&& other) { 77 | std::swap(m_impl, other.m_impl); 78 | return *this; 79 | } 80 | 81 | struct data; 82 | data* m_impl; 83 | 84 | constexpr bool await_ready() const noexcept { return false; } 85 | void await_suspend(coroutine_handle<> h) noexcept; 86 | http_response await_resume() const; 87 | }; 88 | execute_awaiter execute_async(http_response::body_storage_t body_store_method = http_response::inline_body{}) { 89 | return execute_awaiter{*this, std::move(body_store_method), nullptr}; 90 | } 91 | execute_awaiter execute_async(http_response::body_storage_t body_store_method, executor& exec) { 92 | return execute_awaiter{*this, std::move(body_store_method), &exec}; 93 | } 94 | execute_awaiter execute_async(std::stop_token st, http_response::body_storage_t body_store_method = http_response::inline_body{}) { 95 | return execute_awaiter{*this, std::move(body_store_method), nullptr, std::move(st)}; 96 | } 97 | execute_awaiter execute_async(std::stop_token st, http_response::body_storage_t body_store_method, executor& exec) { 98 | return execute_awaiter{*this, std::move(body_store_method), &exec, std::move(st)}; 99 | } 100 | }; 101 | } // namespace asyncpp::curl 102 | -------------------------------------------------------------------------------- /include/asyncpp/curl/websocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace asyncpp::curl { 10 | class executor; 11 | class uri; 12 | namespace detail { 13 | struct websocket_state; 14 | void refcounted_add_ref(const websocket_state* ptr) noexcept; 15 | void refcounted_remove_ref(const websocket_state* ptr) noexcept; 16 | } // namespace detail 17 | class websocket { 18 | public: 19 | enum class opcode : uint8_t { 20 | continuation = 0x00, 21 | text, 22 | binary, 23 | close = 0x08, 24 | ping, 25 | pong, 26 | 27 | compressed = 0x40, 28 | fin = 0x80, 29 | }; 30 | using header_map = std::multimap; 31 | using buffer = std::span; 32 | 33 | websocket(executor& e); 34 | websocket(); 35 | ~websocket(); 36 | 37 | void connect(const uri& url); 38 | void close(uint16_t code, std::string_view reason); 39 | bool is_connected() const noexcept; 40 | 41 | void send(buffer data, bool binary, std::function cb); 42 | void send_text(std::string_view sv, std::function cb); 43 | void send_binary(buffer data, std::function cb); 44 | void send_ping(buffer data, std::function cb); 45 | void send_pong(buffer data, std::function cb); 46 | 47 | void set_on_open(std::function cb); 48 | void set_on_close(std::function cb); 49 | void set_on_message(std::function cb); 50 | void set_on_ping(std::function cb); 51 | void set_on_pong(std::function cb); 52 | 53 | header_map& request_headers() noexcept; 54 | header_map& response_headers() noexcept; 55 | const header_map& request_headers() const noexcept; 56 | const header_map& response_headers() const noexcept; 57 | 58 | void set_utf8_validation_mode(utf8_validator::mode m) noexcept; 59 | utf8_validator::mode utf8_validation_mode() const noexcept; 60 | 61 | private: 62 | void send_frame(opcode op, buffer data, std::function cb); 63 | ref m_state; 64 | }; 65 | 66 | inline constexpr websocket::opcode operator|(websocket::opcode lhs, websocket::opcode rhs) noexcept { 67 | return static_cast(static_cast(lhs) | static_cast(rhs)); 68 | } 69 | 70 | inline constexpr websocket::opcode operator&(websocket::opcode lhs, websocket::opcode rhs) noexcept { 71 | return static_cast(static_cast(lhs) & static_cast(rhs)); 72 | } 73 | 74 | inline constexpr bool operator!(websocket::opcode lhs) noexcept { return lhs == websocket::opcode{}; } 75 | 76 | template 77 | std::span as_bytes(const std::basic_string& str) noexcept { 78 | return std::as_bytes(std::span(str.data(), str.size())); 79 | } 80 | 81 | template 82 | std::span as_writable_bytes(std::basic_string& str) noexcept { 83 | return std::as_bytes(std::span(str.data(), str.size())); 84 | } 85 | 86 | template 87 | std::span as_bytes(std::basic_string_view str) noexcept { 88 | return std::as_bytes(std::span(str.data(), str.size())); 89 | } 90 | 91 | inline void websocket::send(buffer data, bool binary, std::function cb) { 92 | send_frame(opcode::fin | (binary ? opcode::binary : opcode::text), data, std::move(cb)); 93 | } 94 | 95 | inline void websocket::send_binary(buffer data, std::function cb) { send_frame(opcode::fin | opcode::binary, data, std::move(cb)); } 96 | 97 | inline void websocket::send_text(std::string_view sv, std::function cb) { send_frame(opcode::fin | opcode::text, as_bytes(sv), std::move(cb)); } 98 | 99 | inline void websocket::send_ping(buffer data, std::function cb) { send_frame(opcode::fin | opcode::ping, data, std::move(cb)); } 100 | 101 | inline void websocket::send_pong(buffer data, std::function cb) { send_frame(opcode::fin | opcode::pong, data, std::move(cb)); } 102 | 103 | } // namespace asyncpp::curl 104 | -------------------------------------------------------------------------------- /src/curl/base64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace asyncpp::curl { 6 | 7 | std::string base64::encode(const std::string_view data) { 8 | static constexpr char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 9 | 10 | auto olen = 4 * ((data.size() + 2) / 3); 11 | if (olen < data.size()) throw std::logic_error("internal error"); 12 | 13 | std::string res; 14 | res.resize(olen); 15 | auto out = res.data(); 16 | 17 | const auto end = data.data() + data.size(); 18 | auto in = data.data(); 19 | while (end - in >= 3) { 20 | *out++ = base64_table[static_cast(in[0]) >> 2]; 21 | *out++ = base64_table[((static_cast(in[0]) & 0x03) << 4) | (static_cast(in[1]) >> 4)]; 22 | *out++ = base64_table[((static_cast(in[1]) & 0x0f) << 2) | (static_cast(in[2]) >> 6)]; 23 | *out++ = base64_table[static_cast(in[2]) & 0x3f]; 24 | in += 3; 25 | } 26 | 27 | if (end - in) { 28 | *out++ = base64_table[static_cast(in[0]) >> 2]; 29 | if (end - in == 1) { 30 | *out++ = base64_table[(static_cast(in[0]) & 0x03) << 4]; 31 | *out++ = '='; 32 | } else { 33 | *out++ = base64_table[((static_cast(in[0]) & 0x03) << 4) | (static_cast(in[1]) >> 4)]; 34 | *out++ = base64_table[(static_cast(in[1]) & 0x0f) << 2]; 35 | } 36 | *out++ = '='; 37 | } 38 | 39 | return res; 40 | } 41 | 42 | std::string base64::decode(const std::string_view data) { 43 | static constexpr unsigned char B64index[256] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 45 | 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 46 | 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 47 | 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; 48 | 49 | unsigned char* p = (unsigned char*)data.data(); 50 | int pad = data.size() > 0 && (data.size() % 4 || p[data.size() - 1] == '='); 51 | const size_t L = ((data.size() + 3) / 4 - pad) * 4; 52 | std::string str; 53 | str.reserve(((L / 4) + 1) * 3 + pad); 54 | str.resize(L / 4 * 3 + pad, '\0'); 55 | 56 | for (size_t i = 0, j = 0; i < L; i += 4) { 57 | int n = static_cast(B64index[p[i]]) << 18 | static_cast(B64index[p[i + 1]]) << 12 | static_cast(B64index[p[i + 2]]) << 6 | 58 | static_cast(B64index[p[i + 3]]); 59 | str[j++] = n >> 16; 60 | str[j++] = n >> 8 & 0xFF; 61 | str[j++] = n & 0xFF; 62 | } 63 | if (pad) { 64 | int n = static_cast(B64index[p[L]]) << 18 | static_cast(B64index[p[L + 1]]) << 12; 65 | str[str.size() - 1] = n >> 16; 66 | 67 | if (data.size() > L + 2 && p[L + 2] != '=') { 68 | n |= static_cast(B64index[p[L + 2]]) << 6; 69 | str.push_back(n >> 8 & 0xFF); 70 | } 71 | } 72 | return str; 73 | } 74 | 75 | std::string base64url::encode(const std::string_view data) { 76 | auto res = base64::encode(data); 77 | for (auto& e : res) { 78 | if (e == '+') 79 | e = '-'; 80 | else if (e == '/') 81 | e = '_'; 82 | } 83 | auto pos = res.find('='); 84 | if (pos != std::string::npos) res.resize(pos); 85 | return res; 86 | } 87 | 88 | std::string base64url::decode(const std::string_view data) { 89 | std::string d{data}; 90 | for (auto& e : d) { 91 | if (e == '-') 92 | e = '+'; 93 | else if (e == '_') 94 | e = '/'; 95 | } 96 | d.resize((d.size() + 3) & ~0x3, '='); 97 | return base64::decode(d); 98 | } 99 | 100 | } // namespace asyncpp::curl 101 | -------------------------------------------------------------------------------- /src/curl/exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace asyncpp::curl { 5 | const char* exception::what() const noexcept { 6 | if (m_is_multi) 7 | return curl_multi_strerror(static_cast(m_code)); 8 | else 9 | return curl_easy_strerror(static_cast(m_code)); 10 | } 11 | } // namespace asyncpp::curl 12 | -------------------------------------------------------------------------------- /src/curl/executor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace asyncpp::curl { 9 | executor::executor() : m_multi{}, m_thread{}, m_mtx{}, m_exit{false}, m_queue{} { 10 | m_thread = std::thread([this]() { this->worker_thread(); }); 11 | } 12 | 13 | executor::~executor() noexcept { 14 | m_exit.store(true); 15 | m_multi.wakeup(); 16 | if (m_thread.joinable()) m_thread.join(); 17 | } 18 | 19 | void executor::worker_thread() noexcept { 20 | dispatcher::current(this); 21 | std::vector fds; 22 | std::vector handles; 23 | while (true) { 24 | int still_running = 0; 25 | { 26 | std::unique_lock lck(m_mtx); 27 | m_multi.perform(&still_running); 28 | multi::event evt; 29 | while (m_multi.next_event(evt)) { 30 | std::unique_lock lck_hdl(evt.handle->m_mtx); 31 | if (evt.code == multi::event_code::done) { 32 | auto cb = std::exchange(evt.handle->m_done_callback, {}); 33 | if (!evt.handle->is_connect_only()) m_multi.remove_handle(*evt.handle); 34 | if (cb) m_queue.emplace([cb = std::move(cb), res = evt.result_code]() { cb(res); }); 35 | } 36 | } 37 | } 38 | while (true) { 39 | auto fn = m_queue.pop(); 40 | if (!fn) { 41 | if (m_exit && still_running == 0) return; 42 | break; 43 | } 44 | if ((*fn)) (*fn)(); 45 | } 46 | auto timeout = m_multi.timeout().count(); 47 | if (timeout == 0) continue; 48 | if (timeout < 0) timeout = 500; 49 | { 50 | std::unique_lock lck(m_mtx); 51 | if (!m_scheduled.empty()) { 52 | auto now = std::chrono::steady_clock::now(); 53 | while (!m_scheduled.empty()) { 54 | auto elem = m_scheduled.begin(); 55 | auto diff = std::chrono::duration_cast(elem->first - now).count(); 56 | if (diff > 100) { 57 | if (timeout > diff) timeout = diff; 58 | break; 59 | } 60 | auto fn = std::move(elem->second); 61 | m_scheduled.erase(elem); 62 | lck.unlock(); 63 | fn(); 64 | lck.lock(); 65 | } 66 | } 67 | } 68 | { 69 | auto num_handles = m_connect_only_handles.size(); 70 | fds.resize(num_handles); 71 | handles.resize(num_handles); 72 | for (size_t i = 0; auto e : m_connect_only_handles) { 73 | auto fd = e->get_info_socket(CURLINFO_ACTIVESOCKET); 74 | fds[i].events = e->is_paused(CURLPAUSE_RECV) ? 0 : (CURL_WAIT_POLLIN | CURL_WAIT_POLLPRI); 75 | fds[i].events |= e->is_paused(CURLPAUSE_SEND) ? 0 : CURL_WAIT_POLLOUT; 76 | if (fd == (std::numeric_limits::max)() || fds[i].events == 0) { 77 | num_handles--; 78 | continue; 79 | } 80 | fds[i].fd = fd; 81 | fds[i].revents = 0; 82 | handles[i] = e; 83 | i++; 84 | } 85 | int num_fds = 0; 86 | m_multi.poll({fds.data(), num_handles}, timeout, &num_fds); 87 | if (num_fds != 0) { 88 | for (size_t i = 0; i < num_handles; i++) { 89 | if (fds[i].revents & CURL_WAIT_POLLIN) { 90 | if (!handles[i]->m_write_callback || handles[i]->m_write_callback(nullptr, 0) == CURL_WRITEFUNC_PAUSE) 91 | handles[i]->pause(CURLPAUSE_RECV); 92 | } else if (fds[i].revents & CURL_WAIT_POLLOUT) { 93 | if (!handles[i]->m_read_callback || handles[i]->m_read_callback(nullptr, 0) == CURL_READFUNC_PAUSE) 94 | handles[i]->pause(CURLPAUSE_SEND); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | dispatcher::current(nullptr); 101 | } 102 | 103 | void executor::add_handle(handle& hdl) { 104 | push_wait([this, &hdl]() { 105 | hdl.m_executor = this; 106 | if (hdl.is_connect_only()) 107 | m_connect_only_handles.insert(&hdl); 108 | else 109 | m_multi.add_handle(hdl); 110 | }); 111 | } 112 | 113 | void executor::remove_handle(handle& hdl) { 114 | push_wait([this, &hdl]() { 115 | hdl.m_executor = nullptr; 116 | if (hdl.is_connect_only()) 117 | m_connect_only_handles.erase(&hdl); 118 | else 119 | m_multi.remove_handle(hdl); 120 | }); 121 | } 122 | 123 | void executor::exec_awaiter::await_suspend(coroutine_handle<> h) noexcept { 124 | m_handle->set_donefunction([this, h](int result) { 125 | m_result = result; 126 | h.resume(); 127 | }); 128 | m_parent->add_handle(*m_handle); 129 | } 130 | 131 | void executor::exec_awaiter::stop_callback::operator()() { 132 | std::unique_lock lck{m_handle->m_mtx}; 133 | auto cb = std::exchange(m_handle->m_done_callback, {}); 134 | if (cb) { 135 | lck.unlock(); 136 | m_parent->push([this, cb = std::move(cb)]() { 137 | std::unique_lock lck{m_handle->m_mtx}; 138 | m_handle->m_executor = nullptr; 139 | m_parent->m_multi.remove_handle(*m_handle); 140 | lck.unlock(); 141 | cb(CURLE_ABORTED_BY_CALLBACK); 142 | }); 143 | } 144 | } 145 | 146 | executor::exec_awaiter executor::exec(handle& hdl, std::stop_token st) { 147 | if (hdl.is_connect_only()) throw std::logic_error("unsupported"); 148 | return exec_awaiter{this, &hdl, std::move(st)}; 149 | } 150 | 151 | void executor::push(std::function fn) { 152 | m_queue.emplace(std::move(fn)); 153 | if (m_thread.get_id() != std::this_thread::get_id()) m_multi.wakeup(); 154 | } 155 | 156 | void executor::schedule(std::function fn, std::chrono::milliseconds timeout) { 157 | auto now = std::chrono::steady_clock::now(); 158 | this->schedule(std::move(fn), now + timeout); 159 | } 160 | 161 | void executor::schedule(std::function fn, std::chrono::steady_clock::time_point time) { 162 | if (m_thread.get_id() == std::this_thread::get_id()) { 163 | m_scheduled.emplace(time, std::move(fn)); 164 | } else { 165 | std::unique_lock lck(m_mtx); 166 | m_scheduled.emplace(time, std::move(fn)); 167 | lck.unlock(); 168 | m_multi.wakeup(); 169 | } 170 | } 171 | 172 | void executor::wakeup() { 173 | if (m_thread.get_id() != std::this_thread::get_id()) m_multi.wakeup(); 174 | } 175 | 176 | executor& executor::get_default() { 177 | static executor instance{}; 178 | return instance; 179 | } 180 | } // namespace asyncpp::curl 181 | -------------------------------------------------------------------------------- /src/curl/handle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace asyncpp::curl { 14 | constexpr static uint32_t FLAG_is_connect_only = 1 << 1; 15 | static_assert((FLAG_is_connect_only & CURLPAUSE_ALL) == 0, "Overlap between custom flags and CURLPAUSE_ALL"); 16 | constexpr static uint32_t FLAG_is_verbose = 1 << 3; 17 | static_assert((FLAG_is_verbose & CURLPAUSE_ALL) == 0, "Overlap between custom flags and CURLPAUSE_ALL"); 18 | 19 | handle::handle() : m_instance{nullptr}, m_multi{nullptr}, m_executor{nullptr}, m_flags{0} { 20 | m_instance = curl_easy_init(); 21 | if (!m_instance) throw std::runtime_error("failed to create curl handle"); 22 | set_option_ptr(CURLOPT_PRIVATE, this); 23 | // We set CURLOPT_NOSIGNAL to avoid errors in MT, this is fine in most cases. 24 | set_option_bool(CURLOPT_NOSIGNAL, true); 25 | } 26 | 27 | handle::~handle() noexcept { 28 | if (m_executor) m_executor->remove_handle(*this); 29 | if (m_multi) m_multi->remove_handle(*this); 30 | if (m_instance) curl_easy_cleanup(m_instance); 31 | } 32 | 33 | void handle::set_option_long(int opt, long val) { 34 | // TODO: Evaluate curl_easy_option_by_id for checking 35 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_LONG) throw std::invalid_argument("invalid option supplied to set_option_long"); 36 | std::scoped_lock lck{m_mtx}; 37 | auto res = curl_easy_setopt(m_instance, static_cast(opt), val); 38 | if (res != CURLE_OK) throw exception{res}; 39 | if (opt == CURLOPT_CONNECT_ONLY) 40 | m_flags = val ? (m_flags | FLAG_is_connect_only) : (m_flags & ~FLAG_is_connect_only); 41 | else if (opt == CURLOPT_VERBOSE) 42 | m_flags = val ? (m_flags | FLAG_is_verbose) : (m_flags & ~FLAG_is_verbose); 43 | } 44 | 45 | void handle::set_option_offset(int opt, long val) { 46 | // TODO: Evaluate curl_easy_option_by_id for checking 47 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_OFF_T) throw std::invalid_argument("invalid option supplied to set_option_long"); 48 | std::scoped_lock lck{m_mtx}; 49 | auto res = curl_easy_setopt(m_instance, static_cast(opt), static_cast(val)); 50 | if (res != CURLE_OK) throw exception{res}; 51 | } 52 | 53 | void handle::set_option_ptr(int opt, const void* ptr) { 54 | // TODO: Evaluate curl_easy_option_by_id for checking 55 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_OBJECTPOINT && base != CURLOPTTYPE_FUNCTIONPOINT) 56 | throw std::invalid_argument("invalid option supplied to set_option_ptr"); 57 | std::scoped_lock lck{m_mtx}; 58 | auto res = curl_easy_setopt(m_instance, static_cast(opt), ptr); 59 | if (res != CURLE_OK) throw exception{res}; 60 | } 61 | 62 | void handle::set_option_string(int opt, const char* str) { 63 | // TODO: Evaluate curl_easy_option_by_id for checking 64 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_STRINGPOINT) throw std::invalid_argument("invalid option supplied to set_option_ptr"); 65 | std::scoped_lock lck{m_mtx}; 66 | auto res = curl_easy_setopt(m_instance, static_cast(opt), str); 67 | if (res != CURLE_OK) throw exception{res}; 68 | } 69 | 70 | void handle::set_option_blob(int opt, void* data, size_t data_size, bool copy) { 71 | #if CURL_AT_LEAST_VERSION(7, 71, 0) 72 | // TODO: Evaluate curl_easy_option_by_id for checking 73 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_BLOB) throw std::invalid_argument("invalid option supplied to set_option_blob"); 74 | curl_blob b{.data = data, .len = data_size, .flags = static_cast(copy ? CURL_BLOB_COPY : CURL_BLOB_NOCOPY)}; 75 | std::scoped_lock lck{m_mtx}; 76 | auto res = curl_easy_setopt(m_instance, static_cast(opt), &b); 77 | if (res != CURLE_OK) throw exception{res}; 78 | #else 79 | // This was only introduced in 7.71.0 80 | throw std::runtime_error("unsupported option"); 81 | #endif 82 | } 83 | 84 | void handle::set_option_bool(int opt, bool on) { set_option_long(opt, static_cast(on ? 1 : 0)); } 85 | 86 | void handle::set_option_slist(int opt, slist list) { 87 | // TODO: Evaluate curl_easy_option_by_id for checking 88 | if (int base = (opt / 10000) * 10000; base != CURLOPTTYPE_SLISTPOINT) throw std::invalid_argument("invalid option supplied to set_option_ptr"); 89 | std::scoped_lock lck{m_mtx}; 90 | auto res = curl_easy_setopt(m_instance, static_cast(opt), list.m_first_node); 91 | if (res != CURLE_OK) throw exception{res}; 92 | m_owned_slists[opt] = std::move(list); 93 | } 94 | 95 | void handle::set_url(const char* url) { set_option_ptr(CURLOPT_URL, url); } 96 | 97 | void handle::set_url(const std::string& url) { return set_url(url.c_str()); } 98 | 99 | void handle::set_follow_location(bool on) { set_option_bool(CURLOPT_FOLLOWLOCATION, on); } 100 | 101 | void handle::set_verbose(bool on) { set_option_bool(CURLOPT_VERBOSE, on); } 102 | 103 | void handle::set_headers(slist list) { set_option_slist(CURLOPT_HTTPHEADER, std::move(list)); } 104 | 105 | void handle::set_writefunction(std::function cb) { 106 | constexpr curl_write_callback real_cb = [](char* buffer, size_t size, size_t nmemb, void* udata) -> size_t { 107 | auto res = static_cast(udata)->m_write_callback(buffer, size * nmemb); 108 | if (res == CURL_WRITEFUNC_PAUSE) static_cast(udata)->m_flags |= CURLPAUSE_RECV; 109 | return res; 110 | }; 111 | 112 | std::scoped_lock lck{m_mtx}; 113 | m_write_callback = cb; 114 | auto res = curl_easy_setopt(m_instance, CURLOPT_WRITEFUNCTION, real_cb); 115 | if (res != CURLE_OK) throw exception{res}; 116 | set_option_ptr(CURLOPT_WRITEDATA, this); 117 | } 118 | 119 | void handle::set_writestream(std::ostream& stream) { 120 | set_writefunction([&stream](char* ptr, size_t size) -> size_t { 121 | if (size == 0) return 0; 122 | stream.write(ptr, size); 123 | return size; 124 | }); 125 | } 126 | 127 | void handle::set_writestring(std::string& str) { 128 | set_writefunction([&str](char* ptr, size_t size) -> size_t { 129 | if (size == 0) return 0; 130 | str.append(ptr, size); 131 | return size; 132 | }); 133 | } 134 | 135 | void handle::set_readfunction(std::function cb) { 136 | curl_read_callback real_cb = [](char* buffer, size_t size, size_t nmemb, void* udata) -> size_t { 137 | auto res = static_cast(udata)->m_read_callback(buffer, size * nmemb); 138 | if (res == CURL_READFUNC_PAUSE) static_cast(udata)->m_flags |= CURLPAUSE_SEND; 139 | return res; 140 | }; 141 | 142 | std::scoped_lock lck{m_mtx}; 143 | m_read_callback = cb; 144 | auto res = curl_easy_setopt(m_instance, CURLOPT_READFUNCTION, real_cb); 145 | if (res != CURLE_OK) throw exception{res}; 146 | set_option_ptr(CURLOPT_READDATA, this); 147 | } 148 | 149 | void handle::set_readstream(std::istream& stream) { 150 | set_readfunction([&stream](char* ptr, size_t size) -> size_t { 151 | if (size == 0 || stream.eof()) return 0; 152 | if (stream.fail()) return CURLE_ABORTED_BY_CALLBACK; 153 | return stream.read(ptr, size).gcount(); 154 | }); 155 | } 156 | 157 | void handle::set_readstring(const std::string& str) { 158 | size_t pos = 0; 159 | set_readfunction([&str, pos](char* ptr, size_t size) mutable -> size_t { 160 | if (size == 0 || str.size() == pos) return 0; 161 | auto read = (std::min)(size, str.size() - pos); 162 | memcpy(ptr, str.data() + pos, read); 163 | pos += read; 164 | return read; 165 | }); 166 | } 167 | 168 | void handle::set_progressfunction(std::function cb) { 169 | constexpr curl_xferinfo_callback real_cb = [](void* udata, int64_t dltotal, int64_t dlnow, int64_t ultotal, int64_t ulnow) -> int { 170 | return static_cast(udata)->m_progress_callback(dltotal, dlnow, ultotal, ulnow); 171 | }; 172 | 173 | std::scoped_lock lck{m_mtx}; 174 | m_progress_callback = cb; 175 | auto res = curl_easy_setopt(m_instance, CURLOPT_XFERINFOFUNCTION, real_cb); 176 | if (res != CURLE_OK) throw exception{res}; 177 | set_option_ptr(CURLOPT_XFERINFODATA, this); 178 | set_option_bool(CURLOPT_NOPROGRESS, false); 179 | } 180 | 181 | void handle::set_headerfunction(std::function cb) { 182 | constexpr curl_read_callback real_cb = [](char* buffer, size_t size, size_t nmemb, void* udata) -> size_t { 183 | return static_cast(udata)->m_header_callback(buffer, size * nmemb); 184 | }; 185 | 186 | std::scoped_lock lck{m_mtx}; 187 | m_header_callback = cb; 188 | auto res = curl_easy_setopt(m_instance, CURLOPT_HEADERFUNCTION, real_cb); 189 | if (res != CURLE_OK) throw exception{res}; 190 | set_option_ptr(CURLOPT_HEADERDATA, this); 191 | } 192 | 193 | void handle::set_headerfunction_slist(slist& list) { 194 | set_headerfunction([&list](char* buffer, size_t size) -> size_t { 195 | if (size == 0 || size == 1) return size; 196 | // A complete HTTP header [...] includes the final line terminator. 197 | // We temporarily replace it with a null character for append. 198 | // This saves us from having to copy the string. 199 | auto c = buffer[size - 1]; 200 | buffer[size - 1] = '\0'; 201 | try { 202 | list.append(buffer); 203 | } catch (...) { 204 | buffer[size - 1] = c; 205 | return 0; // Write error 206 | } 207 | buffer[size - 1] = c; 208 | return size; 209 | }); 210 | } 211 | 212 | void handle::set_donefunction(std::function cb) { 213 | std::scoped_lock lck{m_mtx}; 214 | m_done_callback = cb; 215 | } 216 | 217 | void handle::perform() { 218 | std::unique_lock lck{m_mtx}; 219 | if (m_multi) throw std::logic_error("perform called on handle in multi"); 220 | auto res = curl_easy_perform(m_instance); 221 | auto cb = m_done_callback; 222 | lck.unlock(); 223 | if (cb) cb(res); 224 | if (res != CURLE_OK) throw exception{res}; 225 | } 226 | 227 | void handle::reset() { 228 | if (m_executor) m_executor->remove_handle(*this); 229 | if (m_multi) m_multi->remove_handle(*this); 230 | std::scoped_lock lck{m_mtx}; 231 | curl_easy_reset(m_instance); 232 | set_option_ptr(CURLOPT_PRIVATE, this); 233 | m_done_callback = {}; 234 | m_header_callback = {}; 235 | m_progress_callback = {}; 236 | m_read_callback = {}; 237 | m_write_callback = {}; 238 | m_flags = 0; 239 | m_owned_slists.clear(); 240 | } 241 | 242 | void handle::upkeep() { 243 | #if CURL_AT_LEAST_VERSION(7, 62, 0) 244 | std::scoped_lock lck{m_mtx}; 245 | auto res = curl_easy_upkeep(m_instance); 246 | if (res != CURLE_OK) throw exception{res}; 247 | #endif 248 | } 249 | 250 | std::ptrdiff_t handle::recv(void* buffer, size_t buflen) { 251 | std::scoped_lock lck{m_mtx}; 252 | size_t read{}; 253 | auto res = curl_easy_recv(m_instance, buffer, buflen, &read); 254 | if (res == CURLE_AGAIN) return -1; 255 | if (res != CURLE_OK) throw exception{res}; 256 | return read; 257 | } 258 | 259 | std::ptrdiff_t handle::send(const void* buffer, size_t buflen) { 260 | std::scoped_lock lck{m_mtx}; 261 | size_t sent{}; 262 | auto res = curl_easy_send(m_instance, buffer, buflen, &sent); 263 | if (res == CURLE_AGAIN) return -1; 264 | if (res != CURLE_OK) throw exception{res}; 265 | return sent; 266 | } 267 | 268 | bool handle::is_connect_only() const noexcept { 269 | std::scoped_lock lck{m_mtx}; 270 | return m_flags & FLAG_is_connect_only; 271 | } 272 | 273 | bool handle::is_verbose() const noexcept { 274 | std::scoped_lock lck{m_mtx}; 275 | return m_flags & FLAG_is_verbose; 276 | } 277 | 278 | void handle::pause(int dirs) { 279 | std::scoped_lock lck{m_mtx}; 280 | auto old = m_flags; 281 | m_flags |= (dirs & CURLPAUSE_ALL); 282 | if (m_flags == old) return; 283 | curl_easy_pause(m_instance, m_flags & CURLPAUSE_ALL); 284 | } 285 | 286 | void handle::unpause(int dirs) { 287 | std::unique_lock lck{m_mtx}; 288 | auto old = m_flags; 289 | m_flags &= ~(dirs & CURLPAUSE_ALL); 290 | if (m_flags == old) return; 291 | curl_easy_pause(m_instance, m_flags & CURLPAUSE_ALL); 292 | // Wake the executor/multi if theres any to make sure it gets polled soon 293 | if (m_executor) 294 | m_executor->wakeup(); 295 | else if (m_multi) 296 | m_multi->wakeup(); 297 | } 298 | 299 | bool handle::is_paused(int dir) { 300 | std::scoped_lock lck{m_mtx}; 301 | return (m_flags & dir) != 0; 302 | } 303 | 304 | long handle::get_info_long(int info) const { 305 | if ((info & CURLINFO_TYPEMASK) != CURLINFO_LONG) throw std::invalid_argument("invalid info supplied to get_info_long"); 306 | std::scoped_lock lck{m_mtx}; 307 | long p; 308 | auto res = curl_easy_getinfo(m_instance, static_cast(info), &p); 309 | if (res != CURLE_OK) throw exception{res}; 310 | return p; 311 | } 312 | 313 | uint64_t handle::get_info_socket(int info) const { 314 | if ((info & CURLINFO_TYPEMASK) != CURLINFO_SOCKET) throw std::invalid_argument("invalid info supplied to get_info_socket"); 315 | std::scoped_lock lck{m_mtx}; 316 | curl_socket_t p; 317 | auto res = curl_easy_getinfo(m_instance, static_cast(info), &p); 318 | if (res != CURLE_OK) throw exception{res}; 319 | if (p == CURL_SOCKET_BAD) return (std::numeric_limits::max)(); 320 | return p; 321 | } 322 | 323 | double handle::get_info_double(int info) const { 324 | if ((info & CURLINFO_TYPEMASK) != CURLINFO_DOUBLE) throw std::invalid_argument("invalid info supplied to get_info_double"); 325 | std::scoped_lock lck{m_mtx}; 326 | double p; 327 | auto res = curl_easy_getinfo(m_instance, static_cast(info), &p); 328 | if (res != CURLE_OK) throw exception{res}; 329 | return p; 330 | } 331 | 332 | const char* handle::get_info_string(int info) const { 333 | if ((info & CURLINFO_TYPEMASK) != CURLINFO_STRING) throw std::invalid_argument("invalid info supplied to get_info_string"); 334 | std::scoped_lock lck{m_mtx}; 335 | char* p; 336 | auto res = curl_easy_getinfo(m_instance, static_cast(info), &p); 337 | if (res != CURLE_OK) throw exception{res}; 338 | return p; 339 | } 340 | 341 | slist handle::get_info_slist(int info) const { 342 | if ((info & CURLINFO_TYPEMASK) != CURLINFO_SLIST) throw std::invalid_argument("invalid info supplied to get_info_slist"); 343 | std::scoped_lock lck{m_mtx}; 344 | struct curl_slist* p; 345 | auto res = curl_easy_getinfo(m_instance, static_cast(info), &p); 346 | if (res != CURLE_OK) throw exception{res}; 347 | return slist{p, slist::ownership_take}; 348 | } 349 | 350 | long handle::get_response_code() const { return get_info_long(CURLINFO_RESPONSE_CODE); } 351 | 352 | handle* handle::get_handle_from_raw(void* curl) { 353 | if (!curl) return nullptr; 354 | char* ptr = nullptr; 355 | auto res = curl_easy_getinfo(curl, CURLINFO_PRIVATE, &ptr); 356 | if (res != CURLE_OK) throw exception{res}; 357 | return reinterpret_cast(ptr); 358 | } 359 | } // namespace asyncpp::curl 360 | -------------------------------------------------------------------------------- /src/curl/multi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #if !CURL_AT_LEAST_VERSION(7, 68, 0) 9 | #ifdef __linux__ 10 | #include 11 | #include 12 | #else 13 | #error "Unsupported os / curl combination" 14 | #endif 15 | #endif 16 | 17 | namespace asyncpp::curl { 18 | multi::multi() : m_mtx{}, m_instance{nullptr}, m_wakeup{-1} { 19 | // TODO: Needs a mutex 20 | m_instance = curl_multi_init(); 21 | if (!m_instance) throw std::runtime_error("failed to create curl handle"); 22 | #if !CURL_AT_LEAST_VERSION(7, 68, 0) 23 | m_wakeup = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 24 | if (m_wakeup < 0) { 25 | curl_multi_cleanup(m_instance); 26 | throw std::runtime_error("failed to create eventfd"); 27 | } 28 | #endif 29 | } 30 | 31 | multi::~multi() noexcept { 32 | if (m_instance) curl_multi_cleanup(m_instance); 33 | #if !CURL_AT_LEAST_VERSION(7, 68, 0) 34 | if (m_wakeup >= 0) close(m_wakeup); 35 | #endif 36 | } 37 | 38 | void multi::add_handle(handle& hdl) { 39 | std::scoped_lock lck{m_mtx, hdl.m_mtx}; 40 | if (hdl.m_multi == this) return; 41 | if (hdl.m_multi != nullptr) throw std::logic_error("handle is already part of a multi"); 42 | auto res = curl_multi_add_handle(m_instance, hdl.raw()); 43 | if (res != CURLM_OK) throw exception{res, true}; 44 | hdl.m_multi = this; 45 | } 46 | 47 | void multi::remove_handle(handle& hdl) { 48 | std::scoped_lock lck{m_mtx, hdl.m_mtx}; 49 | if (hdl.m_multi == nullptr) return; 50 | if (hdl.m_multi != this) throw std::logic_error("attempt to remove curl_handle from wrong multi"); 51 | auto res = curl_multi_remove_handle(m_instance, hdl.raw()); 52 | if (res != CURLM_OK) throw exception{res, true}; 53 | hdl.m_multi = nullptr; 54 | } 55 | 56 | std::chrono::milliseconds multi::timeout() { 57 | std::scoped_lock lck{m_mtx}; 58 | long timeout = -1; 59 | auto res = curl_multi_timeout(m_instance, &timeout); 60 | if (res != CURLM_OK) throw exception{res, true}; 61 | return std::chrono::milliseconds{timeout}; 62 | } 63 | 64 | void multi::perform(int* still_running) { 65 | std::scoped_lock lck{m_mtx}; 66 | auto res = curl_multi_perform(m_instance, still_running); 67 | if (res != CURLM_OK) throw exception{res, true}; 68 | } 69 | 70 | void multi::wait(std::span extra_fds, int timeout_ms, int* num_fds) { 71 | std::scoped_lock lck{m_mtx}; 72 | auto res = curl_multi_wait(m_instance, extra_fds.data(), extra_fds.size(), timeout_ms, num_fds); 73 | if (res != CURLM_OK) throw exception{res, true}; 74 | } 75 | 76 | void multi::poll(std::span extra_fds, int timeout_ms, int* num_fds) { 77 | #if !CURL_AT_LEAST_VERSION(7, 68, 0) 78 | curl_waitfd extra[extra_fds.size() + 1]; 79 | memcpy(extra, extra_fds.data(), extra_fds.size_bytes()); 80 | extra[extra_fds.size()].fd = m_wakeup; 81 | extra[extra_fds.size()].events = CURL_WAIT_POLLIN; 82 | extra[extra_fds.size()].revents = 0; 83 | std::scoped_lock lck{m_mtx}; 84 | auto res = curl_multi_wait(m_instance, extra, extra_fds.size() + 1, timeout_ms, num_fds); 85 | if (res != CURLM_OK) throw exception{res, true}; 86 | uint64_t temp; 87 | auto unused = read(m_wakeup, &temp, sizeof(temp)); 88 | static_cast(unused); // We dont care about the result 89 | #else 90 | std::scoped_lock lck{m_mtx}; 91 | auto res = curl_multi_poll(m_instance, extra_fds.data(), extra_fds.size(), timeout_ms, num_fds); 92 | if (res != CURLM_OK) throw exception{res, true}; 93 | #endif 94 | } 95 | 96 | void multi::wakeup() { 97 | #if !CURL_AT_LEAST_VERSION(7, 68, 0) 98 | uint64_t t = 1; 99 | auto unused = write(m_wakeup, &t, sizeof(t)); 100 | static_cast(unused); 101 | #else 102 | auto res = curl_multi_wakeup(m_instance); 103 | if (res != CURLM_OK) throw exception{res, true}; 104 | #endif 105 | } 106 | 107 | void multi::fdset(fd_set& read_set, fd_set& write_set, fd_set& exc_set, int& max_fd) { 108 | std::scoped_lock lck{m_mtx}; 109 | auto res = curl_multi_fdset(m_instance, &read_set, &write_set, &exc_set, &max_fd); 110 | if (res != CURLM_OK) throw exception{res, true}; 111 | } 112 | 113 | bool multi::next_event(event& evt) { 114 | std::scoped_lock lck{m_mtx}; 115 | int msgs_in_queue = -1; 116 | auto ptr = curl_multi_info_read(m_instance, &msgs_in_queue); 117 | if (ptr) { 118 | evt.code = static_cast(ptr->msg); 119 | evt.handle = handle::get_handle_from_raw(ptr->easy_handle); 120 | evt.result_code = ptr->data.result; 121 | return true; 122 | } 123 | return false; 124 | } 125 | 126 | void multi::set_option_long(int opt, long val) { 127 | std::scoped_lock lck{m_mtx}; 128 | auto res = curl_multi_setopt(m_instance, static_cast(opt), val); 129 | if (res != CURLM_OK) throw exception{res, true}; 130 | } 131 | 132 | void multi::set_option_ptr(int opt, const void* str) { 133 | std::scoped_lock lck{m_mtx}; 134 | auto res = curl_multi_setopt(m_instance, static_cast(opt), str); 135 | if (res != CURLM_OK) throw exception{res, true}; 136 | } 137 | 138 | void multi::set_option_bool(int opt, bool on) { 139 | std::scoped_lock lck{m_mtx}; 140 | auto res = curl_multi_setopt(m_instance, static_cast(opt), static_cast(on ? 1 : 0)); 141 | if (res != CURLM_OK) throw exception{res, true}; 142 | } 143 | 144 | } // namespace asyncpp::curl 145 | -------------------------------------------------------------------------------- /src/curl/sha1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 5 | 6 | #if BYTE_ORDER == LITTLE_ENDIAN 7 | #define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF)) 8 | #elif BYTE_ORDER == BIG_ENDIAN 9 | #define blk0(i) block->l[i] 10 | #else 11 | #error "Endianness not defined!" 12 | #endif 13 | #define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) 14 | 15 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 16 | #define R0(v, w, x, y, z, i) \ 17 | z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ 18 | w = rol(w, 30); 19 | #define R1(v, w, x, y, z, i) \ 20 | z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ 21 | w = rol(w, 30); 22 | #define R2(v, w, x, y, z, i) \ 23 | z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ 24 | w = rol(w, 30); 25 | #define R3(v, w, x, y, z, i) \ 26 | z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ 27 | w = rol(w, 30); 28 | #define R4(v, w, x, y, z, i) \ 29 | z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ 30 | w = rol(w, 30); 31 | 32 | namespace { 33 | void transform(uint32_t* state, const unsigned char* buffer) { 34 | uint32_t a, b, c, d, e; 35 | 36 | typedef union { 37 | unsigned char c[64]; 38 | uint32_t l[16]; 39 | } CHAR64LONG16; 40 | 41 | CHAR64LONG16 block[1]; 42 | memcpy(block, buffer, 64); 43 | a = state[0]; 44 | b = state[1]; 45 | c = state[2]; 46 | d = state[3]; 47 | e = state[4]; 48 | 49 | R0(a, b, c, d, e, 0); 50 | R0(e, a, b, c, d, 1); 51 | R0(d, e, a, b, c, 2); 52 | R0(c, d, e, a, b, 3); 53 | R0(b, c, d, e, a, 4); 54 | R0(a, b, c, d, e, 5); 55 | R0(e, a, b, c, d, 6); 56 | R0(d, e, a, b, c, 7); 57 | R0(c, d, e, a, b, 8); 58 | R0(b, c, d, e, a, 9); 59 | R0(a, b, c, d, e, 10); 60 | R0(e, a, b, c, d, 11); 61 | R0(d, e, a, b, c, 12); 62 | R0(c, d, e, a, b, 13); 63 | R0(b, c, d, e, a, 14); 64 | R0(a, b, c, d, e, 15); 65 | R1(e, a, b, c, d, 16); 66 | R1(d, e, a, b, c, 17); 67 | R1(c, d, e, a, b, 18); 68 | R1(b, c, d, e, a, 19); 69 | R2(a, b, c, d, e, 20); 70 | R2(e, a, b, c, d, 21); 71 | R2(d, e, a, b, c, 22); 72 | R2(c, d, e, a, b, 23); 73 | R2(b, c, d, e, a, 24); 74 | R2(a, b, c, d, e, 25); 75 | R2(e, a, b, c, d, 26); 76 | R2(d, e, a, b, c, 27); 77 | R2(c, d, e, a, b, 28); 78 | R2(b, c, d, e, a, 29); 79 | R2(a, b, c, d, e, 30); 80 | R2(e, a, b, c, d, 31); 81 | R2(d, e, a, b, c, 32); 82 | R2(c, d, e, a, b, 33); 83 | R2(b, c, d, e, a, 34); 84 | R2(a, b, c, d, e, 35); 85 | R2(e, a, b, c, d, 36); 86 | R2(d, e, a, b, c, 37); 87 | R2(c, d, e, a, b, 38); 88 | R2(b, c, d, e, a, 39); 89 | R3(a, b, c, d, e, 40); 90 | R3(e, a, b, c, d, 41); 91 | R3(d, e, a, b, c, 42); 92 | R3(c, d, e, a, b, 43); 93 | R3(b, c, d, e, a, 44); 94 | R3(a, b, c, d, e, 45); 95 | R3(e, a, b, c, d, 46); 96 | R3(d, e, a, b, c, 47); 97 | R3(c, d, e, a, b, 48); 98 | R3(b, c, d, e, a, 49); 99 | R3(a, b, c, d, e, 50); 100 | R3(e, a, b, c, d, 51); 101 | R3(d, e, a, b, c, 52); 102 | R3(c, d, e, a, b, 53); 103 | R3(b, c, d, e, a, 54); 104 | R3(a, b, c, d, e, 55); 105 | R3(e, a, b, c, d, 56); 106 | R3(d, e, a, b, c, 57); 107 | R3(c, d, e, a, b, 58); 108 | R3(b, c, d, e, a, 59); 109 | R4(a, b, c, d, e, 60); 110 | R4(e, a, b, c, d, 61); 111 | R4(d, e, a, b, c, 62); 112 | R4(c, d, e, a, b, 63); 113 | R4(b, c, d, e, a, 64); 114 | R4(a, b, c, d, e, 65); 115 | R4(e, a, b, c, d, 66); 116 | R4(d, e, a, b, c, 67); 117 | R4(c, d, e, a, b, 68); 118 | R4(b, c, d, e, a, 69); 119 | R4(a, b, c, d, e, 70); 120 | R4(e, a, b, c, d, 71); 121 | R4(d, e, a, b, c, 72); 122 | R4(c, d, e, a, b, 73); 123 | R4(b, c, d, e, a, 74); 124 | R4(a, b, c, d, e, 75); 125 | R4(e, a, b, c, d, 76); 126 | R4(d, e, a, b, c, 77); 127 | R4(c, d, e, a, b, 78); 128 | R4(b, c, d, e, a, 79); 129 | 130 | state[0] += a; 131 | state[1] += b; 132 | state[2] += c; 133 | state[3] += d; 134 | state[4] += e; 135 | } 136 | } // namespace 137 | 138 | namespace asyncpp::curl { 139 | sha1::sha1() noexcept { 140 | /* SHA1 initialization constants */ 141 | m_state[0] = 0x67452301; 142 | m_state[1] = 0xEFCDAB89; 143 | m_state[2] = 0x98BADCFE; 144 | m_state[3] = 0x10325476; 145 | m_state[4] = 0xC3D2E1F0; 146 | } 147 | 148 | void sha1::update(const void* data, size_t len) noexcept { 149 | auto u8ptr = static_cast(data); 150 | uint32_t i = 0; 151 | uint32_t j = m_count[0]; 152 | if ((m_count[0] += len << 3) < j) m_count[1]++; 153 | m_count[1] += (len >> 29); 154 | j = (j >> 3) & 63; 155 | if ((j + len) > 63) { 156 | i = 64 - j; 157 | memcpy(&m_buffer[j], data, i); 158 | transform(m_state, m_buffer); 159 | for (; i + 63 < len; i += 64) { 160 | transform(m_state, &u8ptr[i]); 161 | } 162 | j = 0; 163 | } 164 | memcpy(&m_buffer[j], &u8ptr[i], len - i); 165 | } 166 | 167 | void sha1::finish(void* digest) noexcept { 168 | unsigned char finalcount[8]; 169 | 170 | for (size_t i = 0; i < 8; i++) { 171 | finalcount[i] = (unsigned char)((m_count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); 172 | } 173 | uint8_t c = 128; 174 | update(&c, 1); 175 | while ((m_count[0] & 504) != 448) { 176 | c = 0; 177 | update(&c, 1); 178 | } 179 | update(finalcount, 8); 180 | for (size_t i = 0; i < 20; i++) { 181 | static_cast(digest)[i] = (unsigned char)((m_state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); 182 | } 183 | } 184 | 185 | } // namespace asyncpp::curl 186 | -------------------------------------------------------------------------------- /src/curl/slist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace asyncpp::curl { 6 | 7 | static std::pair copy_slist(const curl_slist* ptr) { 8 | curl_slist* first_node = nullptr; 9 | curl_slist* last_node = nullptr; 10 | while (ptr) { 11 | last_node = curl_slist_append(last_node, ptr->data); 12 | if (!last_node) { 13 | // We failed to allocate the next node, clean up our new nodes and restore the original 14 | curl_slist_free_all(first_node); 15 | throw std::bad_alloc(); 16 | } 17 | if (!first_node) first_node = last_node; 18 | ptr = ptr->next; 19 | } 20 | return {first_node, last_node}; 21 | } 22 | 23 | slist::slist(const curl_slist* raw, ownership_copy_tag) { 24 | auto l = copy_slist(raw); 25 | m_first_node = l.first; 26 | m_last_node = l.second; 27 | } 28 | 29 | slist::slist(const slist& other) { 30 | auto l = copy_slist(other.m_first_node); 31 | m_first_node = l.first; 32 | m_last_node = l.second; 33 | } 34 | 35 | slist& slist::operator=(const slist& other) { 36 | // Copy list 37 | auto l = copy_slist(other.m_first_node); 38 | // We successfully made a copy, clean up the old list 39 | curl_slist_free_all(m_first_node); 40 | m_first_node = l.first; 41 | m_last_node = l.second; 42 | return *this; 43 | } 44 | 45 | slist_iterator slist::prepend(const char* str) { 46 | // We use curl_slist_append to alloc the new node because on windows a dll might use a different heap. 47 | auto node = curl_slist_append(nullptr, str); 48 | if (node == nullptr) throw std::bad_alloc{}; 49 | node->next = m_first_node; 50 | m_first_node = node; 51 | if (m_last_node == nullptr) m_last_node = node; 52 | return slist_iterator{node}; 53 | } 54 | 55 | slist_iterator slist::insert_after(slist_iterator pos, const char* str) { 56 | // We use curl_slist_append to alloc the new node because on windows a dll might use a different heap. 57 | auto node = curl_slist_append(nullptr, str); 58 | if (node == nullptr) throw std::bad_alloc{}; 59 | if (pos.m_current == nullptr) pos.m_current = m_last_node; 60 | if (pos.m_current) { 61 | node->next = pos.m_current->next; 62 | pos.m_current->next = node; 63 | } else { 64 | node->next = nullptr; 65 | m_first_node = node; 66 | } 67 | if (node->next == nullptr) m_last_node = node; 68 | return slist_iterator{node}; 69 | } 70 | 71 | void slist::remove(size_t index) { 72 | if (index == 0) { 73 | auto ptr = m_first_node; 74 | if (m_last_node == ptr) m_last_node = nullptr; 75 | if (ptr != nullptr) { 76 | m_first_node = ptr->next; 77 | ptr->next = nullptr; 78 | curl_slist_free_all(ptr); 79 | } 80 | } else { 81 | auto prev = m_first_node; 82 | if (prev == nullptr) return; 83 | auto ptr = prev->next; 84 | for (size_t i = 1; i < index && ptr != nullptr; i++) { 85 | prev = ptr; 86 | ptr = ptr->next; 87 | } 88 | if (ptr != nullptr) { 89 | if (ptr == m_last_node) m_last_node = prev; 90 | prev->next = ptr->next; 91 | ptr->next = nullptr; 92 | curl_slist_free_all(ptr); 93 | } 94 | } 95 | } 96 | 97 | void slist::remove(slist_iterator pos) { 98 | if (pos.m_current == nullptr) pos.m_current = m_last_node; 99 | if (pos.m_current == nullptr) return; 100 | if (pos.m_current == m_first_node) { 101 | m_first_node = pos.m_current->next; 102 | if (m_first_node == nullptr) m_last_node = nullptr; 103 | } else { 104 | auto prev = m_first_node; 105 | while (prev != nullptr && prev->next != pos.m_current) { 106 | prev = prev->next; 107 | } 108 | if (prev == nullptr) return; 109 | prev->next = pos.m_current->next; 110 | if (pos.m_current->next == nullptr) m_last_node = prev; 111 | } 112 | pos.m_current->next = nullptr; 113 | curl_slist_free_all(pos.m_current); 114 | } 115 | 116 | void slist::clear() { 117 | curl_slist_free_all(m_first_node); 118 | m_first_node = nullptr; 119 | m_last_node = nullptr; 120 | } 121 | 122 | } // namespace asyncpp::curl 123 | -------------------------------------------------------------------------------- /src/curl/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace asyncpp::curl { 11 | tcp_client::tcp_client(curl::executor& e) : m_executor{e}, m_handle{}, m_is_connected{false} {} 12 | 13 | tcp_client::tcp_client() : tcp_client(executor::get_default()) {} 14 | 15 | tcp_client::~tcp_client() noexcept { 16 | if (m_recv_handler) m_executor.push([cb = std::move(m_recv_handler)]() { cb(true); }); 17 | if (m_send_handler) m_executor.push([cb = std::move(m_send_handler)]() { cb(true); }); 18 | } 19 | 20 | bool tcp_client::is_connected() const noexcept { 21 | std::unique_lock lck{m_mtx}; 22 | return m_is_connected; 23 | } 24 | 25 | void tcp_client::set_on_data_available(std::function cb) { 26 | std::unique_lock lck{m_mtx}; 27 | m_recv_handler = cb; 28 | } 29 | 30 | std::ptrdiff_t tcp_client::recv_raw(void* buffer, size_t buflen) { 31 | std::unique_lock lck{m_mtx}; 32 | return m_handle.recv(buffer, buflen); 33 | } 34 | 35 | void tcp_client::pause_receive(bool paused) { 36 | std::unique_lock lck{m_mtx}; 37 | if (paused) 38 | m_handle.pause(CURLPAUSE_RECV); 39 | else 40 | m_handle.unpause(CURLPAUSE_RECV); 41 | } 42 | 43 | void tcp_client::connect(std::string remote, uint16_t port, bool ssl, std::function cb) { 44 | // TODO: Rewrite this to use the async codepath once 45 | // TODO: https://github.com/curl/curl/pull/9342 gets merged and has a version number 46 | std::unique_lock lck{m_mtx}; 47 | m_handle.set_option_bool(CURLOPT_CONNECT_ONLY, true); 48 | m_handle.set_option_bool(CURLOPT_FRESH_CONNECT, true); 49 | m_handle.set_readfunction([this](char* buf, size_t size) -> size_t { 50 | assert(buf == nullptr && size == 0); 51 | std::unique_lock lck{m_mtx}; 52 | auto res = callback_result::clear; 53 | if (m_send_handler) res = m_send_handler(false); 54 | if (!!(res & callback_result::clear)) m_send_handler = {}; 55 | return !res ? 1 : CURL_READFUNC_PAUSE; 56 | }); 57 | m_handle.set_writefunction([this](char* buf, size_t size) -> size_t { 58 | assert(buf == nullptr && size == 0); 59 | std::unique_lock lck{m_mtx}; 60 | auto res = callback_result::clear; 61 | if (m_recv_handler) res = m_recv_handler(false); 62 | if (!!(res & callback_result::clear)) m_recv_handler = {}; 63 | return !res ? 1 : CURL_WRITEFUNC_PAUSE; 64 | }); 65 | m_handle.set_url((ssl ? "https://" : "http://") + remote + ":" + std::to_string(port)); 66 | m_handle.set_donefunction([this, cb = std::move(cb)](int res) mutable { 67 | if (res == CURLE_OK) { 68 | std::unique_lock lck{m_mtx}; 69 | m_is_connected = true; 70 | if (m_handle.is_verbose()) printf("* curl::tcp_client connected\n"); 71 | } 72 | cb(res); 73 | cb = {}; 74 | }); 75 | m_handle.pause(CURLPAUSE_ALL); 76 | if (m_handle.is_verbose()) printf("* curl::tcp_client connecting to %s:%d ssl=%s\n", remote.c_str(), port, (ssl ? "true" : "false")); 77 | m_executor.add_handle(m_handle); 78 | try { 79 | lck.unlock(); 80 | m_handle.perform(); 81 | } catch (const std::exception& e) { 82 | if (m_handle.is_verbose()) printf("* curl::tcp_client failure connecting: %s\n", e.what()); 83 | m_executor.remove_handle(m_handle); 84 | throw; 85 | } 86 | } 87 | 88 | void tcp_client::disconnect(std::function cb) { 89 | std::unique_lock lck{m_mtx}; 90 | if (m_handle.is_verbose()) printf("* curl::tcp_client resetting handle to disconnect\n"); 91 | // Cancel active read and send transactions 92 | auto send_cb = std::exchange(m_send_handler, {}); 93 | auto recv_cb = std::exchange(m_recv_handler, {}); 94 | m_handle.reset(); 95 | m_is_connected = false; 96 | m_executor.push([cb = std::move(cb), recv_cb = std::move(recv_cb), send_cb = std::move(send_cb)]() { 97 | if (recv_cb) recv_cb(true); 98 | if (send_cb) send_cb(true); 99 | if (cb) cb(); 100 | }); 101 | } 102 | 103 | void tcp_client::send(const void* buffer, size_t size, std::function cb) { 104 | std::unique_lock lck{m_mtx}; 105 | if (m_send_handler) throw std::logic_error("Send already in progress"); 106 | if (!m_is_connected) { 107 | return m_executor.push([cb = std::move(cb)]() { cb(0); }); 108 | } 109 | // Try inline send 110 | auto res = m_handle.send(buffer, size); 111 | if (res != -1) { 112 | if (m_handle.is_verbose()) printf("* curl::tcp_client.send finished inline res=%td\n", res); 113 | return m_executor.push([cb = std::move(cb), res]() { cb(res); }); 114 | } 115 | // No data available, set the callback and unpause the transfer (adds the connections to poll). 116 | m_send_handler = [this, buffer, size, cb](bool cancel) { 117 | if (cancel) { 118 | m_executor.push([cb = std::move(cb)]() { cb(0); }); 119 | return callback_result::clear; 120 | } 121 | auto res = m_handle.send(buffer, size); 122 | if (res == -1) return callback_result::none; 123 | // We push the actual callback invocation into the queue to make sure we don't end up erasing ourself 124 | // if the callback invokes send, or deadlock. An alternative would be implementing a function like type that 125 | // is safe to reassign while being executed, but this is way simpler and probably fast enough. 126 | m_executor.push([cb = std::move(cb), res]() { cb(res); }); 127 | return callback_result::clear; 128 | }; 129 | m_handle.unpause(CURLPAUSE_SEND); 130 | } 131 | 132 | void tcp_client::send_all(const void* buffer, size_t size, std::function cb) { 133 | std::unique_lock lck{m_mtx}; 134 | if (m_send_handler) throw std::logic_error("Send already in progress"); 135 | if (!m_is_connected) { 136 | return m_executor.push([cb = std::move(cb)]() { cb(0); }); 137 | } 138 | // Try inline send 139 | auto res = m_handle.send(buffer, size); 140 | if (res == 0 || static_cast(res) == size) { 141 | if (m_handle.is_verbose()) printf("* curl::tcp_client.send_all finished inline res=%td\n", res); 142 | return m_executor.push([cb = std::move(cb), res]() { cb(res); }); 143 | } 144 | auto u8ptr = reinterpret_cast(buffer); 145 | size_t sent{(res < 0) ? 0u : static_cast(res)}; 146 | m_send_handler = [this, u8ptr, sent, size, cb](bool cancel) mutable { 147 | if (cancel) { 148 | m_executor.push([cb = std::move(cb), sent]() { cb(sent); }); 149 | return callback_result::clear; 150 | } 151 | auto res = m_handle.send(u8ptr + sent, size - sent); 152 | if (res == -1) return callback_result::none; 153 | sent += res; 154 | if (res == 0 || size == sent) { 155 | // See comment in send for details 156 | m_executor.push([cb = std::move(cb), sent]() { cb(sent); }); 157 | return callback_result::clear; 158 | } 159 | return callback_result::none; 160 | }; 161 | m_handle.unpause(CURLPAUSE_SEND); 162 | } 163 | 164 | void tcp_client::recv(void* buffer, size_t size, std::function cb) { 165 | std::unique_lock lck{m_mtx}; 166 | if (m_recv_handler) throw std::logic_error("Read already in progress"); 167 | if (!m_is_connected) { 168 | return m_executor.push([cb = std::move(cb)]() { cb(0); }); 169 | } 170 | // Try inline recv 171 | auto res = m_handle.recv(buffer, size); 172 | if (res != -1) { 173 | if (m_handle.is_verbose()) printf("* curl::tcp_client.recv finished inline res=%td\n", res); 174 | return m_executor.push([cb = std::move(cb), res]() { cb(res); }); 175 | } 176 | m_recv_handler = [this, buffer, size, cb](bool cancel) { 177 | if (cancel) { 178 | m_executor.push([cb = std::move(cb)]() { cb(0); }); 179 | return callback_result::clear; 180 | } 181 | auto res = m_handle.recv(buffer, size); 182 | if (res == -1) return callback_result::none; 183 | // See comment in send for details 184 | m_executor.push([cb = std::move(cb), res]() { cb(res); }); 185 | return callback_result::clear; 186 | }; 187 | m_handle.unpause(CURLPAUSE_RECV); 188 | } 189 | 190 | void tcp_client::recv_all(void* buffer, size_t size, std::function cb) { 191 | std::unique_lock lck{m_mtx}; 192 | if (m_recv_handler) throw std::logic_error("Read already in progress"); 193 | if (!m_is_connected) { 194 | return m_executor.push([cb = std::move(cb)]() { cb(0); }); 195 | } 196 | // Try inline send 197 | auto res = m_handle.recv(buffer, size); 198 | if (res == 0 || static_cast(res) == size) { 199 | if (m_handle.is_verbose()) printf("* curl::tcp_client.recv_all finished inline res=%td\n", res); 200 | return m_executor.push([cb = std::move(cb), res]() { cb(res); }); 201 | } 202 | auto u8ptr = reinterpret_cast(buffer); 203 | size_t read{(res < 0) ? 0u : static_cast(res)}; 204 | m_recv_handler = [this, u8ptr, read, size, cb](bool cancel) mutable { 205 | if (cancel) { 206 | m_executor.push([cb = std::move(cb), read]() { cb(read); }); 207 | return callback_result::clear; 208 | } 209 | auto res = m_handle.recv(u8ptr + read, size - read); 210 | if (res == -1) return callback_result::none; 211 | read += res; 212 | if (res == 0 || size == read) { 213 | // See comment in send for details 214 | m_executor.push([cb = std::move(cb), read]() { cb(read); }); 215 | return callback_result::clear; 216 | } 217 | return callback_result::none; 218 | }; 219 | m_handle.unpause(CURLPAUSE_RECV); 220 | } 221 | } // namespace asyncpp::curl 222 | -------------------------------------------------------------------------------- /src/curl/uri.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace asyncpp::curl { 6 | namespace { 7 | bool parse_into(std::string_view str, uri& res) { 8 | // Parse scheme 9 | auto pos = str.find(":/"); 10 | if (pos == std::string::npos) return false; 11 | res.scheme(str.substr(0, pos)); 12 | str.remove_prefix(pos + 2); 13 | if (str.empty()) return false; 14 | // Double shlash means we can parse a host segment 15 | if (str[0] == '/') { 16 | str.remove_prefix(1); 17 | auto end = str.find_first_of("/?#"); 18 | if (end == std::string::npos) end = str.size(); 19 | auto host = str.substr(0, end); 20 | str.remove_prefix(end); 21 | // check if the host has a auth part 22 | pos = host.find('@'); 23 | if (pos != std::string::npos) { 24 | res.auth(host.substr(0, pos)); 25 | host.remove_prefix(pos + 1); 26 | } 27 | if (!host.empty() && host[0] == '[') { 28 | // IPv6 address 29 | pos = host.find(']'); 30 | if (pos == std::string::npos) return false; 31 | res.host(host.substr(0, pos + 1)); 32 | host.remove_prefix(pos + 1); 33 | if (!host.empty() && host[0] == ':') { 34 | host.remove_prefix(1); 35 | res.port(std::stoi(std::string{host})); 36 | } 37 | } else { 38 | pos = host.rfind(':'); 39 | if (pos != std::string::npos) { 40 | res.port(std::stoi(std::string{host.substr(pos + 1)})); 41 | host.remove_suffix(host.size() - pos); 42 | } 43 | res.host(host); 44 | } 45 | } 46 | pos = str.find_first_of("?#"); 47 | res.path(str.substr(0, pos)); 48 | if (pos == std::string::npos) return true; 49 | str.remove_prefix(pos); 50 | if (str[0] == '?') { 51 | pos = str.find_first_of("#"); 52 | res.query(str.substr(1, pos - 1)); 53 | if (pos == std::string::npos) return true; 54 | str.remove_prefix(pos); 55 | } 56 | str.remove_prefix(1); 57 | res.fragment(str); 58 | return true; 59 | } 60 | } // namespace 61 | uri::uri(std::string_view str) { 62 | if (!parse_into(str, *this)) throw std::invalid_argument("invalid uri supplied"); 63 | } 64 | 65 | std::optional uri::parse(std::string_view str) { 66 | uri res{}; 67 | if (!parse_into(str, res)) return std::nullopt; 68 | return res; 69 | } 70 | 71 | std::string uri::to_string() const { 72 | std::string res; 73 | if (!m_scheme.empty()) res += m_scheme + "://"; 74 | if (!m_auth.empty()) res += m_auth + "@"; 75 | if (!m_host.empty()) res += m_host; 76 | if (m_port != -1) res += ":" + std::to_string(m_port); 77 | if (!m_path.empty()) res += m_path; 78 | if (!m_query.empty()) res += "?" + m_query; 79 | if (!m_fragment.empty()) res += "#" + m_fragment; 80 | return res; 81 | } 82 | 83 | std::string uri::encode(const std::string_view in) { 84 | auto ptr = curl_easy_escape(nullptr, in.data(), in.size()); 85 | if (!ptr) throw std::bad_alloc{}; 86 | std::string res{ptr}; 87 | curl_free(ptr); 88 | return res; 89 | } 90 | 91 | std::string uri::decode(const std::string_view in) { 92 | int size = -1; 93 | auto ptr = curl_easy_unescape(nullptr, in.data(), in.size(), &size); 94 | if (!ptr || size < 0) throw std::bad_alloc{}; 95 | std::string res{ptr, static_cast(size)}; 96 | curl_free(ptr); 97 | return res; 98 | } 99 | 100 | std::unordered_multimap uri::parse_formdata(std::string_view data) { 101 | std::unordered_multimap res; 102 | size_t offset = 0; 103 | do { 104 | auto pos = data.find('&', offset); 105 | auto part = data.substr(offset, pos == std::string::npos ? std::string::npos : pos - offset); 106 | if (!part.empty()) { 107 | auto pos_eq = part.find('='); 108 | if (pos_eq != std::string::npos) 109 | res.insert({decode(part.substr(0, pos_eq)), decode(part.substr(pos_eq + 1))}); 110 | else 111 | res.insert({decode(part), ""}); 112 | } 113 | if (pos == std::string::npos) break; 114 | offset = pos + 1; 115 | } while (offset < data.size()); 116 | return res; 117 | } 118 | 119 | } // namespace asyncpp::curl 120 | -------------------------------------------------------------------------------- /src/curl/version.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define info static_cast(m_info) 6 | 7 | namespace asyncpp::curl { 8 | //static_assert(CURL_VERSION_GSASL == (1 << static_cast(version::feature::gsasl))); 9 | 10 | version::version() noexcept { m_info = curl_version_info(CURLVERSION_NOW); } 11 | 12 | std::string_view version::curl_version() const noexcept { return info->version ? info->version : ""; } 13 | 14 | unsigned int version::curl_version_num() const noexcept { return info->version_num; } 15 | 16 | std::string_view version::host() const noexcept { return info->host ? info->host : ""; } 17 | 18 | bool version::has_feature(feature f) const noexcept { return (info->features & (1 << static_cast(f))) != 0; } 19 | 20 | std::string_view version::ssl_version() const noexcept { return info->ssl_version ? info->ssl_version : ""; } 21 | 22 | std::string_view version::libz_version() const noexcept { return info->libz_version ? info->libz_version : ""; } 23 | 24 | cstring_array_iterator::helper version::protocols() const noexcept { return cstring_array_iterator::helper{info->protocols}; } 25 | 26 | std::string_view version::protocol(size_t index) const noexcept { 27 | auto pos = info->protocols; 28 | while (index) { 29 | if (pos == nullptr || *pos == nullptr) break; 30 | pos++; 31 | index--; 32 | } 33 | return (pos != nullptr && *pos != nullptr && index == 0) ? *pos : ""; 34 | } 35 | 36 | size_t version::protocol_count() const noexcept { 37 | auto pos = info->protocols; 38 | while (pos && *pos) 39 | pos++; 40 | return pos - info->protocols; 41 | } 42 | 43 | std::string_view version::ares_version() const noexcept { 44 | if (info->age >= CURLVERSION_SECOND) return info->ares ? info->ares : ""; 45 | return ""; 46 | } 47 | 48 | int version::ares_version_num() const noexcept { 49 | if (info->age >= CURLVERSION_SECOND) return info->ares_num; 50 | return -1; 51 | } 52 | 53 | std::string_view version::libidn_version() const noexcept { 54 | if (info->age >= CURLVERSION_THIRD) return info->libidn ? info->libidn : ""; 55 | return ""; 56 | } 57 | 58 | #if CURL_AT_LEAST_VERSION(7, 16, 1) 59 | std::string_view version::iconv_version() const noexcept { 60 | if (info->age >= CURLVERSION_FOURTH) { 61 | static std::array str = [](int v) { 62 | std::array res{}; 63 | if (v == 0) return res; 64 | snprintf(res.data(), res.size() - 1, "%d.%u", v >> 8, v & 0xff); 65 | return res; 66 | }(iconv_num()); 67 | return std::string_view(str.data()); 68 | } 69 | return ""; 70 | } 71 | 72 | int version::iconv_num() const noexcept { 73 | if (info->age >= CURLVERSION_FOURTH) return info->iconv_ver_num; 74 | return -1; 75 | } 76 | 77 | std::string_view version::libssh_version() const noexcept { 78 | if (info->age >= CURLVERSION_FOURTH) return info->libssh_version ? info->libssh_version : ""; 79 | return ""; 80 | } 81 | 82 | #else 83 | std::string_view version::iconv_version() const noexcept { return ""; } 84 | int version::iconv_num() const noexcept { return -1; } 85 | std::string_view version::libssh_version() const noexcept { return ""; } 86 | #endif 87 | 88 | #if CURL_AT_LEAST_VERSION(7, 57, 0) 89 | unsigned int version::brotli_version_num() const noexcept { 90 | if (info->age >= CURLVERSION_FIFTH) return info->brotli_ver_num; 91 | return -1; 92 | } 93 | 94 | std::string_view version::brotli_version() const noexcept { 95 | if (info->age >= CURLVERSION_FIFTH) return info->brotli_version ? info->brotli_version : ""; 96 | return ""; 97 | } 98 | #else 99 | unsigned int version::brotli_version_num() const noexcept { return -1; } 100 | std::string_view version::brotli_version() const noexcept { return ""; } 101 | #endif 102 | 103 | #if CURL_AT_LEAST_VERSION(7, 66, 0) 104 | unsigned int version::nghttp2_version_num() const noexcept { 105 | if (info->age >= CURLVERSION_SIXTH) return info->nghttp2_ver_num; 106 | return -1; 107 | } 108 | 109 | std::string_view version::nghttp2_version() const noexcept { 110 | if (info->age >= CURLVERSION_SIXTH) return info->nghttp2_version ? info->nghttp2_version : ""; 111 | return ""; 112 | } 113 | 114 | std::string_view version::quic_version() const noexcept { 115 | if (info->age >= CURLVERSION_SIXTH) return info->quic_version ? info->quic_version : ""; 116 | return ""; 117 | } 118 | #else 119 | unsigned int version::nghttp2_version_num() const noexcept { return -1; } 120 | std::string_view version::nghttp2_version() const noexcept { return ""; } 121 | std::string_view version::quic_version() const noexcept { return ""; } 122 | #endif 123 | 124 | #if CURL_AT_LEAST_VERSION(7, 70, 0) 125 | std::string_view version::cainfo() const noexcept { 126 | if (info->age >= CURLVERSION_SEVENTH) return info->cainfo ? info->cainfo : ""; 127 | return ""; 128 | } 129 | 130 | std::string_view version::capath() const noexcept { 131 | if (info->age >= CURLVERSION_SEVENTH) return info->capath ? info->capath : ""; 132 | return ""; 133 | } 134 | #else 135 | std::string_view version::cainfo() const noexcept { return ""; } 136 | std::string_view version::capath() const noexcept { return ""; } 137 | #endif 138 | 139 | #if CURL_AT_LEAST_VERSION(7, 71, 0) 140 | unsigned int version::zstd_version_num() const noexcept { 141 | if (info->age >= CURLVERSION_EIGHTH) return info->zstd_ver_num; 142 | return -1; 143 | } 144 | 145 | std::string_view version::zstd_version() const noexcept { 146 | if (info->age >= CURLVERSION_EIGHTH) return info->zstd_version ? info->zstd_version : ""; 147 | return ""; 148 | } 149 | #else 150 | unsigned int version::zstd_version_num() const noexcept { return -1; } 151 | std::string_view version::zstd_version() const noexcept { return ""; } 152 | #endif 153 | 154 | #if CURL_AT_LEAST_VERSION(7, 75, 0) 155 | std::string_view version::hyper_version() const noexcept { 156 | if (info->age >= CURLVERSION_NINTH) return info->hyper_version ? info->hyper_version : ""; 157 | return ""; 158 | } 159 | #else 160 | std::string_view version::hyper_version() const noexcept { return ""; } 161 | #endif 162 | 163 | #if CURL_AT_LEAST_VERSION(7, 77, 0) 164 | std::string_view version::gsasl_version() const noexcept { 165 | if (info->age >= CURLVERSION_TENTH) return info->gsasl_version ? info->gsasl_version : ""; 166 | return ""; 167 | } 168 | #else 169 | std::string_view version::gsasl_version() const noexcept { return ""; } 170 | #endif 171 | 172 | std::span version::features() noexcept { 173 | static constexpr feature list[]{feature::ipv6, feature::kerberos4, feature::ssl, feature::libz, feature::ntlm, 174 | feature::gssnegotiate, feature::debug, feature::asynchdns, feature::spnego, feature::largefile, 175 | feature::idn, feature::sspi, feature::conv, feature::curldebug, feature::tlsauth_srp, 176 | feature::ntlm_wb, feature::http2, feature::gssapi, feature::kerberos5, feature::unix_sockets, 177 | feature::psl, feature::https_proxy, feature::multi_ssl, feature::brotli, feature::altsvc, 178 | feature::http3, feature::zstd, feature::unicode, feature::hsts, feature::gsasl}; 179 | return std::span{list}; 180 | } 181 | 182 | std::string_view version::feature_name(feature f) noexcept { 183 | switch (f) { 184 | case feature::ipv6: return "ipv6"; 185 | case feature::kerberos4: return "kerberos4"; 186 | case feature::ssl: return "ssl"; 187 | case feature::libz: return "libz"; 188 | case feature::ntlm: return "ntlm"; 189 | case feature::gssnegotiate: return "gssnegotiate"; 190 | case feature::debug: return "debug"; 191 | case feature::asynchdns: return "asynchdns"; 192 | case feature::spnego: return "spnego"; 193 | case feature::largefile: return "largefile"; 194 | case feature::idn: return "idn"; 195 | case feature::sspi: return "sspi"; 196 | case feature::conv: return "conv"; 197 | case feature::curldebug: return "curldebug"; 198 | case feature::tlsauth_srp: return "tlsauth_srp"; 199 | case feature::ntlm_wb: return "ntlm_wb"; 200 | case feature::http2: return "http2"; 201 | case feature::gssapi: return "gssapi"; 202 | case feature::kerberos5: return "kerberos5"; 203 | case feature::unix_sockets: return "unix_sockets"; 204 | case feature::psl: return "psl"; 205 | case feature::https_proxy: return "https_proxy"; 206 | case feature::multi_ssl: return "multi_ssl"; 207 | case feature::brotli: return "brotli"; 208 | case feature::altsvc: return "altsvc"; 209 | case feature::http3: return "http3"; 210 | case feature::zstd: return "zstd"; 211 | case feature::unicode: return "unicode"; 212 | case feature::hsts: return "hsts"; 213 | case feature::gsasl: return "gsasl"; 214 | default: return ""; 215 | }; 216 | } 217 | 218 | version::feature version::next_feature(feature f) noexcept { 219 | switch (f) { 220 | case feature::ipv6: return feature::kerberos4; 221 | case feature::kerberos4: return feature::ssl; 222 | case feature::ssl: return feature::libz; 223 | case feature::libz: return feature::ntlm; 224 | case feature::ntlm: return feature::gssnegotiate; 225 | case feature::gssnegotiate: return feature::debug; 226 | case feature::debug: return feature::asynchdns; 227 | case feature::asynchdns: return feature::spnego; 228 | case feature::spnego: return feature::largefile; 229 | case feature::largefile: return feature::idn; 230 | case feature::idn: return feature::sspi; 231 | case feature::sspi: return feature::conv; 232 | case feature::conv: return feature::curldebug; 233 | case feature::curldebug: return feature::tlsauth_srp; 234 | case feature::tlsauth_srp: return feature::ntlm_wb; 235 | case feature::ntlm_wb: return feature::http2; 236 | case feature::http2: return feature::gssapi; 237 | case feature::gssapi: return feature::kerberos5; 238 | case feature::kerberos5: return feature::unix_sockets; 239 | case feature::unix_sockets: return feature::psl; 240 | case feature::psl: return feature::https_proxy; 241 | case feature::https_proxy: return feature::multi_ssl; 242 | case feature::multi_ssl: return feature::brotli; 243 | case feature::brotli: return feature::altsvc; 244 | case feature::altsvc: return feature::http3; 245 | case feature::http3: return feature::zstd; 246 | case feature::zstd: return feature::unicode; 247 | case feature::unicode: return feature::hsts; 248 | case feature::hsts: return feature::gsasl; 249 | case feature::gsasl: 250 | default: return f; 251 | } 252 | } 253 | 254 | } // namespace asyncpp::curl 255 | -------------------------------------------------------------------------------- /src/curl/webclient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace asyncpp::curl { 13 | 14 | http_request http_request::make_get(uri url) { return http_request{.request_method = "GET", .url = url}; } 15 | 16 | http_request http_request::make_head(uri url) { return http_request{.request_method = "HEAD", .url = url}; } 17 | 18 | http_request http_request::make_post(uri url, body_provider_t body) { 19 | return http_request{.request_method = "POST", .url = url, .body_provider = std::move(body)}; 20 | } 21 | 22 | http_request http_request::make_patch(uri url, body_provider_t body) { 23 | return http_request{.request_method = "PATCH", .url = url, .body_provider = std::move(body)}; 24 | } 25 | 26 | http_request http_request::make_put(uri url, body_provider_t body) { 27 | return http_request{.request_method = "PUT", .url = url, .body_provider = std::move(body)}; 28 | } 29 | 30 | http_request http_request::make_delete(uri url) { return http_request{.request_method = "DELETE", .url = url}; } 31 | 32 | namespace { 33 | std::string_view trim(std::string_view str) { 34 | auto pos = str.find_first_not_of(" \t\n\v\f\r"); 35 | if (pos == std::string::npos) pos = str.size(); 36 | str.remove_prefix(pos); 37 | pos = str.find_last_not_of(" \t\n\v\f\r"); 38 | if (pos == std::string::npos) return ""; 39 | return str.substr(0, pos + 1); 40 | } 41 | 42 | auto make_header_cb(http_response& response) { 43 | return [&response](char* buffer, size_t size) mutable -> size_t { 44 | if (size == 0 || size == 1) return size; 45 | try { 46 | std::string_view line{buffer, size}; 47 | line = trim(line); 48 | if (line.empty()) return size; 49 | // First line 50 | if (line.starts_with("HTTP/")) { 51 | auto pos = line.find(' '); 52 | if (pos == std::string::npos) return size; 53 | pos = line.find(' ', pos + 1); 54 | if (pos == std::string::npos) return size; 55 | response.status_message = trim(line.substr(pos + 1)); 56 | response.headers.clear(); 57 | } else { 58 | auto pos = line.find(':'); 59 | if (pos == std::string::npos) { 60 | response.headers.emplace(line, ""); 61 | } else { 62 | response.headers.emplace(trim(line.substr(0, pos)), trim(line.substr(pos + 1))); 63 | } 64 | } 65 | } catch (...) { 66 | return 0; // Write error 67 | } 68 | return size; 69 | }; 70 | } 71 | 72 | void set_write_cb(handle& hdl, http_response& resp, http_response::body_storage_t body_store_method) { 73 | if (std::holds_alternative(body_store_method)) { 74 | hdl.set_writefunction([](char*, size_t size) -> size_t { return size; }); 75 | } else if (std::holds_alternative(body_store_method)) { 76 | hdl.set_writestring(resp.body); 77 | } else if (std::holds_alternative(body_store_method)) { 78 | hdl.set_writestring(*std::get(body_store_method)); 79 | } else if (std::holds_alternative(body_store_method)) { 80 | hdl.set_writestream(*std::get(body_store_method)); 81 | } else if (std::holds_alternative>(body_store_method)) { 82 | hdl.set_writefunction(std::move(std::get>(body_store_method))); 83 | } else 84 | throw std::logic_error("invalide variant"); 85 | } 86 | 87 | void set_read_cb(handle& hdl, http_request::body_provider_t body_provider) { 88 | if (std::holds_alternative(body_provider)) { 89 | hdl.set_readfunction([](char*, size_t) -> size_t { return 0; }); 90 | } else if (std::holds_alternative(body_provider)) { 91 | hdl.set_option_bool(CURLOPT_UPLOAD, true); 92 | hdl.set_option_offset(CURLOPT_INFILESIZE_LARGE, std::get(body_provider)->size()); 93 | hdl.set_readstring(*std::get(body_provider)); 94 | } else if (std::holds_alternative(body_provider)) { 95 | hdl.set_option_bool(CURLOPT_UPLOAD, true); 96 | hdl.set_readstream(*std::get(body_provider)); 97 | } else if (std::holds_alternative>(body_provider)) { 98 | hdl.set_option_bool(CURLOPT_UPLOAD, true); 99 | hdl.set_readfunction(std::move(std::get>(body_provider))); 100 | } else 101 | throw std::logic_error("invalide variant"); 102 | } 103 | 104 | void prepare_handle(handle& hdl, const http_request& req, http_response& resp, http_response::body_storage_t body_store_method) { 105 | // Prepare response handling 106 | hdl.set_headerfunction(make_header_cb(resp)); 107 | set_write_cb(hdl, resp, std::move(body_store_method)); 108 | 109 | hdl.set_option_string(CURLOPT_CUSTOMREQUEST, req.request_method.c_str()); 110 | auto string_url = req.url.to_string(); 111 | hdl.set_option_string(CURLOPT_URL, string_url.c_str()); 112 | slist out_headers{}; 113 | for (auto& e : req.headers) { 114 | std::string hdr{e.first}; 115 | if (e.second.empty()) 116 | hdr += ";"; 117 | else 118 | hdr += ": " + e.second; 119 | out_headers.append(hdr.c_str()); 120 | } 121 | hdl.set_headers(std::move(out_headers)); 122 | set_read_cb(hdl, req.body_provider); 123 | hdl.set_follow_location(req.follow_redirects); 124 | hdl.set_verbose(req.verbose); 125 | hdl.set_option_long(CURLOPT_TIMEOUT_MS, req.timeout.count()); 126 | hdl.set_option_long(CURLOPT_CONNECTTIMEOUT_MS, req.timeout_connect.count()); 127 | if (auto user = req.url.auth(); !user.empty()) { 128 | auto pos = user.find(":"); 129 | if (pos != std::string::npos) { 130 | hdl.set_option_string(CURLOPT_PASSWORD, user.substr(pos + 1).c_str()); 131 | user.resize(pos); 132 | } 133 | hdl.set_option_string(CURLOPT_USERNAME, user.c_str()); 134 | } 135 | // Wipe the cookie chain of existing cookies 136 | hdl.set_option_string(CURLOPT_COOKIEFILE, ""); 137 | for (auto& e : req.cookies) { 138 | hdl.set_option_string(CURLOPT_COOKIELIST, e.to_string().c_str()); 139 | } 140 | } 141 | } // namespace 142 | 143 | http_response http_request::execute_sync(http_response::body_storage_t body_store_method) { 144 | http_response response{}; 145 | handle hdl{}; 146 | prepare_handle(hdl, *this, response, std::move(body_store_method)); 147 | 148 | if (configure_hook) configure_hook(hdl); 149 | hdl.perform(); 150 | if (result_hook) result_hook(hdl); 151 | 152 | response.status_code = hdl.get_response_code(); 153 | auto cookies = hdl.get_info_slist(CURLINFO_COOKIELIST); 154 | for (auto e : cookies) { 155 | response.cookies.emplace_back(e); 156 | } 157 | return response; 158 | } 159 | 160 | struct http_request::execute_awaiter::data { 161 | data(executor* exec, http_request* req, std::stop_token st) : m_exec(exec, &m_handle, std::move(st)), m_request(req) {} 162 | 163 | executor::exec_awaiter m_exec; 164 | handle m_handle{}; 165 | http_request* m_request{}; 166 | http_response m_response{}; 167 | }; 168 | 169 | http_request::execute_awaiter::execute_awaiter(http_request& req, http_response::body_storage_t storage, executor* executor, std::stop_token st) { 170 | m_impl = new data(executor ? executor : &executor::get_default(), &req, std::move(st)); 171 | prepare_handle(m_impl->m_handle, req, m_impl->m_response, std::move(storage)); 172 | } 173 | 174 | http_request::execute_awaiter::~execute_awaiter() { 175 | if (m_impl) delete m_impl; 176 | } 177 | 178 | void http_request::execute_awaiter::await_suspend(coroutine_handle<> h) noexcept { 179 | if (m_impl->m_request->configure_hook) m_impl->m_request->configure_hook(m_impl->m_handle); 180 | m_impl->m_exec.await_suspend(h); 181 | } 182 | 183 | http_response http_request::execute_awaiter::await_resume() const { 184 | auto res = m_impl->m_exec.await_resume(); 185 | if (m_impl->m_request->result_hook) m_impl->m_request->result_hook(m_impl->m_handle); 186 | if (res != CURLE_OK) throw exception(res, false); 187 | m_impl->m_response.status_code = m_impl->m_handle.get_response_code(); 188 | return std::move(m_impl->m_response); 189 | } 190 | 191 | } // namespace asyncpp::curl 192 | -------------------------------------------------------------------------------- /test/base64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace asyncpp::curl; 5 | 6 | TEST(ASYNCPP_CURL, Base64Encode) { 7 | ASSERT_EQ(base64::encode("Hel"), "SGVs"); 8 | ASSERT_EQ(base64::encode("Hell"), "SGVsbA=="); 9 | ASSERT_EQ(base64::encode("Hello"), "SGVsbG8="); 10 | } 11 | 12 | TEST(ASYNCPP_CURL, Base64UrlEncode) { 13 | ASSERT_EQ(base64url::encode("Hel"), "SGVs"); 14 | ASSERT_EQ(base64url::encode("Hell"), "SGVsbA"); 15 | ASSERT_EQ(base64url::encode("Hello"), "SGVsbG8"); 16 | } 17 | 18 | TEST(ASYNCPP_CURL, Base64Decode) { 19 | ASSERT_EQ(base64::decode("SGVs"), "Hel"); 20 | ASSERT_EQ(base64::decode("SGVsbA=="), "Hell"); 21 | ASSERT_EQ(base64::decode("SGVsbG8="), "Hello"); 22 | } 23 | 24 | TEST(ASYNCPP_CURL, Base64UrlDecode) { 25 | ASSERT_EQ(base64url::decode("SGVs"), "Hel"); 26 | ASSERT_EQ(base64url::decode("SGVsbA"), "Hell"); 27 | ASSERT_EQ(base64url::decode("SGVsbG8"), "Hello"); 28 | } 29 | 30 | TEST(ASYNCPP_CURL, Base64NonPrintable) { 31 | static constexpr std::string_view unprintable{ 32 | "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24" 33 | "\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49" 34 | "\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e" 35 | "\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93" 36 | "\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8" 37 | "\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd" 38 | "\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", 39 | 256}; 40 | static constexpr std::string_view b64{"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+" 41 | "P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+" 42 | "AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/" 43 | "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="}; 44 | static constexpr std::string_view b64url{"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-" 45 | "P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-" 46 | "AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_" 47 | "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w"}; 48 | 49 | ASSERT_EQ(base64::encode(unprintable), b64); 50 | ASSERT_EQ(base64::decode(b64), unprintable); 51 | ASSERT_EQ(base64url::encode(unprintable), b64url); 52 | ASSERT_EQ(base64url::decode(b64url), unprintable); 53 | } 54 | -------------------------------------------------------------------------------- /test/cookie.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace asyncpp::curl; 8 | 9 | TEST(ASYNCPP_CURL, Cookie) { 10 | constexpr std::string_view line = "example.com\tFALSE\t/foobar/\tTRUE\t1462299217\tperson\tdaniel\n"; 11 | 12 | cookie c{line}; 13 | ASSERT_EQ(c.domain(), "example.com"); 14 | ASSERT_EQ(c.include_subdomains(), false); 15 | ASSERT_EQ(c.path(), "/foobar/"); 16 | ASSERT_EQ(c.secure(), true); 17 | ASSERT_EQ(c.expires(), std::chrono::system_clock::from_time_t(1462299217)); 18 | ASSERT_EQ(c.name(), "person"); 19 | ASSERT_EQ(c.value(), "daniel"); 20 | 21 | ASSERT_EQ(c.to_string() + "\n", line); 22 | } 23 | -------------------------------------------------------------------------------- /test/slist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace asyncpp::curl; 6 | 7 | namespace { 8 | struct SListTester { 9 | curl_slist* m_first_node; 10 | curl_slist* m_last_node; 11 | }; 12 | } // namespace 13 | 14 | TEST(ASYNCPP_CURL, SlistCreate) { 15 | slist list; 16 | SListTester* t = reinterpret_cast(&list); 17 | ASSERT_EQ(t->m_first_node, nullptr); 18 | ASSERT_EQ(t->m_last_node, nullptr); 19 | } 20 | 21 | TEST(ASYNCPP_CURL, SlistCreateCopy) { 22 | auto n = curl_slist_append(nullptr, "Test"); 23 | slist list{n, slist::ownership_copy}; 24 | SListTester* t = reinterpret_cast(&list); 25 | ASSERT_NE(t->m_first_node, nullptr); 26 | ASSERT_NE(t->m_last_node, nullptr); 27 | ASSERT_EQ(t->m_first_node, t->m_last_node); 28 | ASSERT_NE(t->m_first_node, n); 29 | ASSERT_EQ(t->m_first_node->data, std::string_view{"Test"}); 30 | curl_slist_free_all(n); 31 | } 32 | 33 | TEST(ASYNCPP_CURL, SlistCreateTake) { 34 | auto n = curl_slist_append(nullptr, "Test"); 35 | slist list{n, slist::ownership_take}; 36 | SListTester* t = reinterpret_cast(&list); 37 | ASSERT_NE(t->m_first_node, nullptr); 38 | ASSERT_NE(t->m_last_node, nullptr); 39 | ASSERT_EQ(t->m_first_node, t->m_last_node); 40 | ASSERT_EQ(t->m_first_node, n); 41 | ASSERT_EQ(t->m_first_node->data, std::string_view{"Test"}); 42 | } 43 | 44 | TEST(ASYNCPP_CURL, SlistAppend) { 45 | slist list; 46 | list.append("Test"); 47 | SListTester* t = reinterpret_cast(&list); 48 | ASSERT_NE(t->m_first_node, nullptr); 49 | ASSERT_NE(t->m_last_node, nullptr); 50 | ASSERT_EQ(t->m_first_node, t->m_last_node); 51 | ASSERT_EQ(t->m_first_node->next, nullptr); 52 | ASSERT_NE(t->m_first_node->data, nullptr); 53 | ASSERT_EQ(t->m_first_node->data, std::string_view{"Test"}); 54 | } 55 | 56 | TEST(ASYNCPP_CURL, SlistAppendRemove) { 57 | slist list; 58 | list.append("Test"); 59 | list.remove(0); 60 | SListTester* t = reinterpret_cast(&list); 61 | ASSERT_EQ(t->m_first_node, nullptr); 62 | ASSERT_EQ(t->m_last_node, nullptr); 63 | } 64 | 65 | TEST(ASYNCPP_CURL, SlistAppendRemoveIterator) { 66 | slist list; 67 | list.append("Test"); 68 | list.remove(list.begin()); 69 | SListTester* t = reinterpret_cast(&list); 70 | ASSERT_EQ(t->m_first_node, nullptr); 71 | ASSERT_EQ(t->m_last_node, nullptr); 72 | } 73 | 74 | TEST(ASYNCPP_CURL, SlistAppend2Remove) { 75 | slist list; 76 | SListTester* t = reinterpret_cast(&list); 77 | list.append("Test"); 78 | list.append("Test2"); 79 | ASSERT_NE(t->m_first_node, nullptr); 80 | ASSERT_NE(t->m_last_node, nullptr); 81 | ASSERT_NE(t->m_first_node, t->m_last_node); 82 | ASSERT_NE(t->m_first_node->next, nullptr); 83 | ASSERT_EQ(t->m_first_node->next, t->m_last_node); 84 | auto n = t->m_last_node; 85 | list.remove(0); 86 | ASSERT_EQ(n, t->m_first_node); 87 | ASSERT_EQ(n, t->m_last_node); 88 | ASSERT_EQ(t->m_last_node->next, nullptr); 89 | list.remove(0); 90 | ASSERT_EQ(t->m_first_node, nullptr); 91 | ASSERT_EQ(t->m_last_node, nullptr); 92 | } 93 | 94 | TEST(ASYNCPP_CURL, SlistAppend2RemoveLast) { 95 | slist list; 96 | SListTester* t = reinterpret_cast(&list); 97 | list.append("Test"); 98 | list.append("Test2"); 99 | ASSERT_NE(t->m_first_node, nullptr); 100 | ASSERT_NE(t->m_last_node, nullptr); 101 | ASSERT_NE(t->m_first_node, t->m_last_node); 102 | ASSERT_NE(t->m_first_node->next, nullptr); 103 | ASSERT_EQ(t->m_first_node->next, t->m_last_node); 104 | auto n = t->m_first_node; 105 | list.remove(1); 106 | ASSERT_EQ(n, t->m_first_node); 107 | ASSERT_EQ(n, t->m_last_node); 108 | ASSERT_EQ(t->m_last_node->next, nullptr); 109 | list.remove(0); 110 | ASSERT_EQ(t->m_first_node, nullptr); 111 | ASSERT_EQ(t->m_last_node, nullptr); 112 | } 113 | 114 | TEST(ASYNCPP_CURL, SlistAppend2RemoveIterator) { 115 | slist list; 116 | SListTester* t = reinterpret_cast(&list); 117 | list.append("Test"); 118 | list.append("Test2"); 119 | ASSERT_NE(t->m_first_node, nullptr); 120 | ASSERT_NE(t->m_last_node, nullptr); 121 | ASSERT_NE(t->m_first_node, t->m_last_node); 122 | ASSERT_NE(t->m_first_node->next, nullptr); 123 | ASSERT_EQ(t->m_first_node->next, t->m_last_node); 124 | auto n = t->m_last_node; 125 | list.remove(list.begin()); 126 | ASSERT_EQ(n, t->m_first_node); 127 | ASSERT_EQ(n, t->m_last_node); 128 | ASSERT_EQ(t->m_last_node->next, nullptr); 129 | list.remove(list.begin()); 130 | ASSERT_EQ(t->m_first_node, nullptr); 131 | ASSERT_EQ(t->m_last_node, nullptr); 132 | } 133 | 134 | TEST(ASYNCPP_CURL, SlistAppend2RemoveLastIterator) { 135 | slist list; 136 | SListTester* t = reinterpret_cast(&list); 137 | list.append("Test"); 138 | list.append("Test2"); 139 | ASSERT_NE(t->m_first_node, nullptr); 140 | ASSERT_NE(t->m_last_node, nullptr); 141 | ASSERT_NE(t->m_first_node, t->m_last_node); 142 | ASSERT_NE(t->m_first_node->next, nullptr); 143 | ASSERT_EQ(t->m_first_node->next, t->m_last_node); 144 | auto n = t->m_first_node; 145 | list.remove(list.end()); 146 | ASSERT_EQ(n, t->m_first_node); 147 | ASSERT_EQ(n, t->m_last_node); 148 | ASSERT_EQ(t->m_last_node->next, nullptr); 149 | list.remove(list.begin()); 150 | ASSERT_EQ(t->m_first_node, nullptr); 151 | ASSERT_EQ(t->m_last_node, nullptr); 152 | } 153 | 154 | TEST(ASYNCPP_CURL, SlistClear) { 155 | slist list; 156 | SListTester* t = reinterpret_cast(&list); 157 | list.append("Test"); 158 | list.append("Test2"); 159 | ASSERT_NE(t->m_first_node, nullptr); 160 | ASSERT_NE(t->m_last_node, nullptr); 161 | list.clear(); 162 | ASSERT_EQ(t->m_first_node, nullptr); 163 | ASSERT_EQ(t->m_last_node, nullptr); 164 | } 165 | 166 | TEST(ASYNCPP_CURL, SlistEmpty) { 167 | slist list; 168 | ASSERT_TRUE(list.empty()); 169 | list.append("Test"); 170 | ASSERT_FALSE(list.empty()); 171 | } 172 | 173 | TEST(ASYNCPP_CURL, SlistInsertFront) { 174 | slist list; 175 | list.insert(0, "Test"); 176 | SListTester* t = reinterpret_cast(&list); 177 | ASSERT_NE(t->m_first_node, nullptr); 178 | ASSERT_NE(t->m_last_node, nullptr); 179 | ASSERT_EQ(t->m_first_node, t->m_last_node); 180 | ASSERT_EQ(t->m_first_node->next, nullptr); 181 | ASSERT_NE(t->m_first_node->data, nullptr); 182 | ASSERT_EQ(t->m_first_node->data, std::string_view{"Test"}); 183 | list.insert(0, "Test2"); 184 | ASSERT_NE(t->m_first_node, t->m_last_node); 185 | ASSERT_NE(t->m_first_node->next, nullptr); 186 | ASSERT_NE(t->m_first_node->data, nullptr); 187 | ASSERT_EQ(t->m_first_node->data, std::string_view{"Test2"}); 188 | } 189 | 190 | TEST(ASYNCPP_CURL, SlistInsertMiddle) { 191 | slist list; 192 | list.append("Test"); 193 | list.append("Test2"); 194 | SListTester* t = reinterpret_cast(&list); 195 | auto first = t->m_first_node; 196 | auto last = t->m_last_node; 197 | list.insert(1, "Test3"); 198 | ASSERT_EQ(t->m_first_node, first); 199 | ASSERT_EQ(t->m_last_node, last); 200 | ASSERT_NE(t->m_first_node->next, last); 201 | ASSERT_NE(t->m_first_node->next, nullptr); 202 | ASSERT_EQ(t->m_first_node->next->next, last); 203 | } 204 | 205 | TEST(ASYNCPP_CURL, SlistInsertEnd) { 206 | slist list; 207 | list.append("Test"); 208 | list.append("Test2"); 209 | SListTester* t = reinterpret_cast(&list); 210 | auto first = t->m_first_node; 211 | auto last = t->m_last_node; 212 | list.insert(2, "Test3"); 213 | ASSERT_EQ(t->m_first_node, first); 214 | ASSERT_NE(t->m_last_node, last); 215 | ASSERT_EQ(t->m_first_node->next, last); 216 | ASSERT_NE(t->m_first_node->next->next, last); 217 | ASSERT_EQ(t->m_first_node->next->next, t->m_last_node); 218 | ASSERT_EQ(t->m_first_node->next->next->next, nullptr); 219 | } 220 | 221 | TEST(ASYNCPP_CURL, SlistInsertMiddleIterator) { 222 | slist list; 223 | list.append("Test"); 224 | list.append("Test2"); 225 | SListTester* t = reinterpret_cast(&list); 226 | auto first = t->m_first_node; 227 | auto last = t->m_last_node; 228 | list.insert_after(list.begin(), "Test3"); 229 | ASSERT_EQ(t->m_first_node, first); 230 | ASSERT_EQ(t->m_last_node, last); 231 | ASSERT_NE(t->m_first_node->next, last); 232 | ASSERT_NE(t->m_first_node->next, nullptr); 233 | ASSERT_EQ(t->m_first_node->next->next, last); 234 | } 235 | 236 | TEST(ASYNCPP_CURL, SlistInsertEndIterator) { 237 | slist list; 238 | list.append("Test"); 239 | list.append("Test2"); 240 | SListTester* t = reinterpret_cast(&list); 241 | auto first = t->m_first_node; 242 | auto last = t->m_last_node; 243 | list.insert_after(++(list.begin()), "Test3"); 244 | ASSERT_EQ(t->m_first_node, first); 245 | ASSERT_NE(t->m_last_node, last); 246 | ASSERT_EQ(t->m_first_node->next, last); 247 | ASSERT_NE(t->m_first_node->next->next, last); 248 | ASSERT_EQ(t->m_first_node->next->next, t->m_last_node); 249 | ASSERT_EQ(t->m_first_node->next->next->next, nullptr); 250 | } 251 | 252 | TEST(ASYNCPP_CURL, SlistInsertEndIterator2) { 253 | slist list; 254 | list.append("Test"); 255 | list.append("Test2"); 256 | SListTester* t = reinterpret_cast(&list); 257 | auto first = t->m_first_node; 258 | auto last = t->m_last_node; 259 | list.insert_after(list.end(), "Test3"); 260 | ASSERT_EQ(t->m_first_node, first); 261 | ASSERT_NE(t->m_last_node, last); 262 | ASSERT_EQ(t->m_first_node->next, last); 263 | ASSERT_NE(t->m_first_node->next->next, last); 264 | ASSERT_EQ(t->m_first_node->next->next, t->m_last_node); 265 | ASSERT_EQ(t->m_first_node->next->next->next, nullptr); 266 | } 267 | -------------------------------------------------------------------------------- /test/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace asyncpp::curl; 11 | using namespace asyncpp; 12 | 13 | #define COASSERT_EQ(a, b) \ 14 | { \ 15 | bool failed{true}; \ 16 | [&]() { \ 17 | ASSERT_EQ(a, b); \ 18 | failed = false; \ 19 | }(); \ 20 | if (failed) co_return; \ 21 | } 22 | 23 | TEST(ASYNCPP_CURL, TcpClient) { 24 | as_promise([]() -> task { 25 | std::string str = "Hello World\n"; 26 | tcp_client client; 27 | co_await client.connect("tcpbin.com", 4242, false); 28 | auto written = co_await client.send_all(str.data(), str.size()); 29 | COASSERT_EQ(written, 12); 30 | char buf[128]{}; 31 | auto read = co_await client.recv(buf, 128); 32 | COASSERT_EQ(read, str.size()); 33 | std::string_view read_str(buf, str.size()); 34 | COASSERT_EQ(str, read_str); 35 | co_await client.disconnect(); 36 | co_return; 37 | }()) 38 | .get(); 39 | } 40 | 41 | TEST(ASYNCPP_CURL, TcpClientAsyncRead) { 42 | as_promise([]() -> task { 43 | async_launch_scope scope; 44 | static const std::string str = "Hello World\n"; 45 | 46 | tcp_client client; 47 | co_await client.connect("tcpbin.com", 4242, false); 48 | 49 | bool did_receive = false; 50 | scope.launch([](tcp_client& client, bool& did_receive) -> task { 51 | char buf[12]{}; 52 | auto read = co_await client.recv(buf, 12); 53 | COASSERT_EQ(read, str.size()); 54 | std::string_view read_str(buf, str.size()); 55 | COASSERT_EQ(str, read_str); 56 | did_receive = true; 57 | }(client, did_receive)); 58 | COASSERT_EQ(did_receive, 0); 59 | 60 | auto written = co_await client.send_all(str.data(), str.size()); 61 | COASSERT_EQ(written, 12); 62 | 63 | co_await scope.join(); 64 | COASSERT_EQ(did_receive, 1); 65 | did_receive = false; 66 | scope.launch([](tcp_client& client, bool& did_receive) -> task { 67 | char buf[12]{}; 68 | auto read = co_await client.recv(buf, 12); 69 | COASSERT_EQ(read, 0); 70 | did_receive = true; 71 | }(client, did_receive)); 72 | COASSERT_EQ(did_receive, 0); 73 | co_await client.disconnect(); 74 | co_await scope.join(); 75 | 76 | char buf[12]{}; 77 | auto read = co_await client.recv(buf, 12); 78 | COASSERT_EQ(read, 0); 79 | 80 | co_return; 81 | }()) 82 | .get(); 83 | } 84 | -------------------------------------------------------------------------------- /test/uri.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace asyncpp::curl; 6 | 7 | TEST(ASYNCPP_CURL, UriParse) { 8 | std::array parts{std::string{"http://"}, std::string{"user:pass@"}, std::string{"example.org"}, std::string{":81"}, 9 | std::string{"/path/random"}, std::string{"?params"}, std::string{"#fragment"}}; 10 | for (size_t i = 0; i < (1 << parts.size()); i++) { 11 | std::string test_url; 12 | for (size_t x = 0; x < parts.size(); x++) { 13 | if (i & (1 << x)) test_url += parts[x]; 14 | } 15 | auto parsed = uri::parse(test_url); 16 | bool should_be_valid = (i & 0x01); 17 | if (!should_be_valid) { 18 | ASSERT_FALSE(parsed.has_value()); 19 | continue; 20 | } 21 | ASSERT_TRUE(parsed.has_value()); 22 | auto& u = *parsed; 23 | if (i & 0x01) 24 | ASSERT_EQ("http", u.scheme()); 25 | else 26 | ASSERT_EQ("", u.scheme()); 27 | if (i & 0x02) 28 | ASSERT_EQ("user:pass", u.auth()); 29 | else 30 | ASSERT_EQ("", u.auth()); 31 | if (i & 0x04) 32 | ASSERT_EQ("example.org", u.host()); 33 | else 34 | ASSERT_EQ("", u.host()); 35 | if (i & 0x08) 36 | ASSERT_EQ(81, u.port()); 37 | else 38 | ASSERT_EQ(-1, u.port()); 39 | if (i & 0x10) 40 | ASSERT_EQ("/path/random", u.path()); 41 | else 42 | ASSERT_EQ("", u.path()); 43 | if (i & 0x20) 44 | ASSERT_EQ("params", u.query()); 45 | else 46 | ASSERT_EQ("", u.query()); 47 | if (i & 0x40) 48 | ASSERT_EQ("fragment", u.fragment()); 49 | else 50 | ASSERT_EQ("", u.fragment()); 51 | } 52 | } 53 | 54 | TEST(ASYNCPP_CURL, UriParseIPv6) { 55 | auto parsed = uri::parse("http://user:pass@[fe88:a8ad:686a:3e31:2b25:eb00:27b2:c87b]:81/path/random?params#fragment"); 56 | ASSERT_TRUE(parsed.has_value()); 57 | auto& u = *parsed; 58 | ASSERT_EQ("http", u.scheme()); 59 | ASSERT_EQ("user:pass", u.auth()); 60 | ASSERT_EQ("[fe88:a8ad:686a:3e31:2b25:eb00:27b2:c87b]", u.host()); 61 | ASSERT_EQ(81, u.port()); 62 | ASSERT_EQ("/path/random", u.path()); 63 | ASSERT_EQ("params", u.query()); 64 | ASSERT_EQ("fragment", u.fragment()); 65 | } 66 | 67 | TEST(ASYNCPP_CURL, URIToString) { 68 | uri u{"http://user:pass@example.org:81/path/random?params#fragment"}; 69 | ASSERT_EQ("http://user:pass@example.org:81/path/random?params#fragment", u.to_string()); 70 | } 71 | 72 | TEST(ASYNCPP_CURL, URIPathSplitMerge) { 73 | constexpr auto path = "/hello/world/1245/"; 74 | auto parts = uri::split_path(path); 75 | ASSERT_EQ(3, parts.size()); 76 | ASSERT_EQ("hello", parts[0]); 77 | ASSERT_EQ("world", parts[1]); 78 | ASSERT_EQ("1245", parts[2]); 79 | ASSERT_EQ(path, uri::merge_path(parts.begin(), parts.end())); 80 | } 81 | 82 | TEST(ASYNCPP_CURL, URIParseQuery) { 83 | uri u{"http://test.com?param1¶m2=value¶m3¶m2"}; 84 | auto& parsed = u.query_parsed(); 85 | ASSERT_EQ(4, parsed.size()); 86 | ASSERT_EQ(1, parsed.count("param1")); 87 | ASSERT_EQ(2, parsed.count("param2")); 88 | ASSERT_EQ(1, parsed.count("param3")); 89 | ASSERT_EQ("", parsed.find("param1")->second); 90 | if (auto it = parsed.find("param2"); it->second == "") { 91 | ASSERT_EQ("value", (++it)->second); 92 | } else { 93 | ASSERT_EQ("value", it->second); 94 | ASSERT_EQ("", (++it)->second); 95 | } 96 | ASSERT_EQ("", parsed.find("param3")->second); 97 | } 98 | -------------------------------------------------------------------------------- /test/util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace asyncpp::curl; 5 | namespace { 6 | 7 | using result = utf8_validator::result; 8 | constexpr const char* result_names[] = {"invalid", "valid", "valid_incomplete"}; 9 | // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt 10 | // Test, normal result, strict result, pedantic result, extreme result 11 | constexpr std::tuple test_cases[]{ 12 | {"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5", result::valid, result::valid, result::valid, result::valid}, 13 | // 2.1 (1) 14 | {{"\x00", 1}, result::valid, result::valid, result::valid, result::valid}, 15 | {"\xC2\x80", result::valid, result::valid, result::valid, result::valid}, 16 | {"\xe0\xa0\x80", result::valid, result::valid, result::valid, result::valid}, 17 | {"\xf0\x90\x80\x80", result::valid, result::valid, result::valid, result::valid}, 18 | {"\xf8\x88\x80\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 19 | {"\xfc\x84\x80\x80\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 20 | // 2.2 (7) 21 | {"\x7f", result::valid, result::valid, result::valid, result::valid}, 22 | {"\xdf\xbf", result::valid, result::valid, result::valid, result::valid}, 23 | {"\xef\xbf\xbf", result::valid, result::valid, result::valid, result::invalid}, 24 | {"\xf7\xbf\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 25 | {"\xfb\xbf\xbf\xbf\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 26 | {"\xfd\xbf\xbf\xbf\xbf\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 27 | // 2.3 (13) 28 | {"\xed\x9f\xbf", result::valid, result::valid, result::valid, result::valid}, 29 | {"\xee\x80\x80", result::valid, result::valid, result::valid, result::valid}, 30 | {"\xef\xbf\xbd", result::valid, result::valid, result::valid, result::valid}, 31 | {"\xf4\x8f\xbf\xbf", result::valid, result::valid, result::valid, result::invalid}, 32 | {"\xf4\x90\x80\x80", result::valid, result::valid, result::invalid, result::invalid}, 33 | // 3.1 (18) 34 | {"\x80", result::invalid, result::invalid, result::invalid, result::invalid}, 35 | {"\xbf", result::invalid, result::invalid, result::invalid, result::invalid}, 36 | {"\x80\xbf", result::invalid, result::invalid, result::invalid, result::invalid}, 37 | {"\x80\xbf\x80", result::invalid, result::invalid, result::invalid, result::invalid}, 38 | {"\x80\xbf\x80\xbf", result::invalid, result::invalid, result::invalid, result::invalid}, 39 | {"\x80\xbf\x80\xbf\x80", result::invalid, result::invalid, result::invalid, result::invalid}, 40 | {"\x80\xbf\x80\xbf\x80\xbf", result::invalid, result::invalid, result::invalid, result::invalid}, 41 | {"\x80\xbf\x80\xbf\x80\xbf\x80", result::invalid, result::invalid, result::invalid, result::invalid}, 42 | {"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4" 43 | "\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", 44 | result::invalid, result::invalid, result::invalid, result::invalid}, 45 | // 3.2 (27) 46 | {"\xc0\x20\xc1\x20\xc2\x20\xc3\x20\xc4\x20\xc5\x20\xc6\x20\xc7\x20\xc8\x20\xc9\x20\xca\x20\xcb\x20\xcc\x20\xcd\x20\xce\x20\xcf\x20\xd0\x20\xd1\x20\xd2" 47 | "\x20\xd3\x20\xd4\x20\xd5\x20\xd6\x20\xd7\x20\xd8\x20\xd9\x20\xda\x20\xdb\x20\xdc\x20\xdd\x20\xde\x20\xdf\x20", 48 | result::invalid, result::invalid, result::invalid, result::invalid}, 49 | {"\xe0\x20\xe1\x20\xe2\x20\xe3\x20\xe4\x20\xe5\x20\xe6\x20\xe7\x20\xe8\x20\xe9\x20\xea\x20\xeb\x20\xec\x20\xed\x20\xee\x20\xef\x20", result::invalid, 50 | result::invalid, result::invalid, result::invalid}, 51 | {"\xf0\x20\xf1\x20\xf2\x20\xf3\x20\xf4\x20\xf5\x20\xf6\x20\xf7\x20", result::invalid, result::invalid, result::invalid, result::invalid}, 52 | {"\xf8\x20\xf9\x20\xfa\x20\xfb\x20", result::invalid, result::invalid, result::invalid, result::invalid}, 53 | {"\xfc\x20\xfd\x20", result::invalid, result::invalid, result::invalid, result::invalid}, 54 | // 3.3 (32) 55 | {"\xc0", result::valid_incomplete, result::valid_incomplete, result::valid_incomplete, result::valid_incomplete}, 56 | {"\xe0\x80", result::valid_incomplete, result::valid_incomplete, result::valid_incomplete, result::valid_incomplete}, 57 | {"\xf0\x80\x80", result::valid_incomplete, result::valid_incomplete, result::valid_incomplete, result::valid_incomplete}, 58 | {"\xf8\x80\x80\x80", result::valid_incomplete, result::invalid, result::invalid, result::invalid}, 59 | {"\xfc\x80\x80\x80\x80", result::valid_incomplete, result::invalid, result::invalid, result::invalid}, 60 | {"\xdf", result::valid_incomplete, result::valid_incomplete, result::valid_incomplete, result::valid_incomplete}, 61 | {"\xef\xbf", result::valid_incomplete, result::valid_incomplete, result::valid_incomplete, result::valid_incomplete}, 62 | {"\xf7\xbf\xbf", result::valid_incomplete, result::valid_incomplete, result::invalid, result::invalid}, 63 | {"\xfb\xbf\xbf\xbf", result::valid_incomplete, result::invalid, result::invalid, result::invalid}, 64 | {"\xfd\xbf\xbf\xbf\xbf", result::valid_incomplete, result::invalid, result::invalid, result::invalid}, 65 | // 3.4 (42) 66 | {"\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80\x80\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf", result::invalid, 67 | result::invalid, result::invalid, result::invalid}, 68 | // 3.5 (43) 69 | {"\xfe", result::invalid, result::invalid, result::invalid, result::invalid}, 70 | {"\xff", result::invalid, result::invalid, result::invalid, result::invalid}, 71 | {"\xfe\xfe\xff\xff", result::invalid, result::invalid, result::invalid, result::invalid}, 72 | // 4.1 (46) 73 | {"\xc0\xaf", result::valid, result::invalid, result::invalid, result::invalid}, 74 | {"\xe0\x80\xaf", result::valid, result::invalid, result::invalid, result::invalid}, 75 | {"\xf0\x80\x80\xaf", result::valid, result::invalid, result::invalid, result::invalid}, 76 | {"\xf8\x80\x80\x80\xaf", result::valid, result::invalid, result::invalid, result::invalid}, 77 | {"\xfc\x80\x80\x80\x80\xaf", result::valid, result::invalid, result::invalid, result::invalid}, 78 | // 4.2 (51) 79 | {"\xc1\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 80 | {"\xe0\x9f\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 81 | {"\xf0\x8f\xbf\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 82 | {"\xf8\x87\xbf\xbf\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 83 | {"\xfc\x83\xbf\xbf\xbf\xbf", result::valid, result::invalid, result::invalid, result::invalid}, 84 | // 4.3 (56) 85 | {"\xc0\x80", result::valid, result::invalid, result::invalid, result::invalid}, 86 | {"\xe0\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 87 | {"\xf0\x80\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 88 | {"\xf8\x80\x80\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 89 | {"\xfc\x80\x80\x80\x80\x80", result::valid, result::invalid, result::invalid, result::invalid}, 90 | // 5.1 (61) 91 | {"\xed\xa0\x80", result::valid, result::valid, result::invalid, result::invalid}, 92 | {"\xed\xad\xbf", result::valid, result::valid, result::invalid, result::invalid}, 93 | {"\xed\xae\x80", result::valid, result::valid, result::invalid, result::invalid}, 94 | {"\xed\xaf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 95 | {"\xed\xb0\x80", result::valid, result::valid, result::invalid, result::invalid}, 96 | {"\xed\xbe\x80", result::valid, result::valid, result::invalid, result::invalid}, 97 | {"\xed\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 98 | // 5.2 (68) 99 | {"\xed\xa0\x80\xed\xb0\x80", result::valid, result::valid, result::invalid, result::invalid}, 100 | {"\xed\xa0\x80\xed\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 101 | {"\xed\xad\xbf\xed\xb0\x80", result::valid, result::valid, result::invalid, result::invalid}, 102 | {"\xed\xad\xbf\xed\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 103 | {"\xed\xae\x80\xed\xb0\x80", result::valid, result::valid, result::invalid, result::invalid}, 104 | {"\xed\xae\x80\xed\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 105 | {"\xed\xaf\xbf\xed\xb0\x80", result::valid, result::valid, result::invalid, result::invalid}, 106 | {"\xed\xaf\xbf\xed\xbf\xbf", result::valid, result::valid, result::invalid, result::invalid}, 107 | // 5.3 (76) 108 | {"\xef\xbf\xbe", result::valid, result::valid, result::valid, result::invalid}, 109 | {"\xef\xbf\xbf", result::valid, result::valid, result::valid, result::invalid}, 110 | {"\xef\xb7\x90\xef\xb7\x91\xef\xb7\x92\xef\xb7\x93\xef\xb7\x94\xef\xb7\x95\xef\xb7\x96\xef\xb7\x97\xef\xb7\x98\xef\xb7\x99\xef\xb7\x9a\xef\xb7\x9b\xef" 111 | "\xb7\x9c\xef\xb7\x9d\xef\xb7\x9e\xef\xb7\x9f\xef\xb7\xa0\xef\xb7\xa1\xef\xb7\xa2\xef\xb7\xa3\xef\xb7\xa4\xef\xb7\xa5\xef\xb7\xa6\xef\xb7\xa7\xef\xb7" 112 | "\xa8\xef\xb7\xa9\xef\xb7\xaa\xef\xb7\xab\xef\xb7\xac\xef\xb7\xad\xef\xb7\xae\xef\xb7\xaf", 113 | result::valid, result::valid, result::invalid, result::invalid}, 114 | {"\xf0\x9f\xbf\xbe\xf0\x9f\xbf\xbf\xf0\xaf\xbf\xbe\xf0\xaf\xbf\xbf\xf0\xbf\xbf\xbe\xf0\xbf\xbf\xbf\xf1\x8f\xbf\xbe\xf1\x8f\xbf\xbf\xf1\x9f\xbf\xbe\xf1" 115 | "\x9f\xbf\xbf\xf1\xaf\xbf\xbe\xf1\xaf\xbf\xbf\xf1\xbf\xbf\xbe\xf1\xbf\xbf\xbf\xf2\x8f\xbf\xbe\xf2\x8f\xbf\xbf\xf2\x9f\xbf\xbe\xf2\x9f\xbf\xbf\xf2\xaf" 116 | "\xbf\xbe\xf2\xaf\xbf\xbf\xf2\xbf\xbf\xbe\xf2\xbf\xbf\xbf\xf3\x8f\xbf\xbe\xf3\x8f\xbf\xbf\xf3\x9f\xbf\xbe\xf3\x9f\xbf\xbf\xf3\xaf\xbf\xbe\xf3\xaf\xbf" 117 | "\xbf\xf3\xbf\xbf\xbe\xf3\xbf\xbf\xbf\xf4\x8f\xbf\xbe\xf4\x8f\xbf\xbf", 118 | result::valid, result::valid, result::valid, result::invalid}, 119 | 120 | // Other 121 | {"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\xf4\x90\x80\x80\x65\x64\x69\x74\x65\x64", result::valid, result::valid, result::invalid, 122 | result::invalid}, 123 | }; 124 | 125 | } // namespace 126 | 127 | TEST(ASYNCPP_CURL, Utf8CheckNormal) { 128 | constexpr size_t size = sizeof(test_cases) / sizeof(test_cases[0]); 129 | for (size_t i = 0; i < size; i++) { 130 | auto& t = test_cases[i]; 131 | auto res = utf8_validator{}(std::get<0>(t), utf8_validator::mode::normal); 132 | auto wanted = std::get<1>(t); 133 | ASSERT_EQ(res, wanted) << "Failed test " << i << " expected=" << result_names[static_cast(wanted)] 134 | << " got=" << result_names[static_cast(res)]; 135 | } 136 | } 137 | 138 | TEST(ASYNCPP_CURL, Utf8CheckStrict) { 139 | constexpr size_t size = sizeof(test_cases) / sizeof(test_cases[0]); 140 | for (size_t i = 0; i < size; i++) { 141 | auto& t = test_cases[i]; 142 | auto res = utf8_validator{}(std::get<0>(t), utf8_validator::mode::strict); 143 | auto wanted = std::get<2>(t); 144 | ASSERT_EQ(res, wanted) << "Failed test " << i << " expected=" << result_names[static_cast(wanted)] 145 | << " got=" << result_names[static_cast(res)]; 146 | } 147 | } 148 | 149 | TEST(ASYNCPP_CURL, Utf8CheckPedantic) { 150 | constexpr size_t size = sizeof(test_cases) / sizeof(test_cases[0]); 151 | for (size_t i = 0; i < size; i++) { 152 | auto& t = test_cases[i]; 153 | auto res = utf8_validator{}(std::get<0>(t), utf8_validator::mode::pedantic); 154 | auto wanted = std::get<3>(t); 155 | ASSERT_EQ(res, wanted) << "Failed test " << i << " expected=" << result_names[static_cast(wanted)] 156 | << " got=" << result_names[static_cast(res)]; 157 | } 158 | } 159 | 160 | TEST(ASYNCPP_CURL, Utf8CheckExtreme) { 161 | constexpr size_t size = sizeof(test_cases) / sizeof(test_cases[0]); 162 | for (size_t i = 0; i < size; i++) { 163 | auto& t = test_cases[i]; 164 | auto res = utf8_validator{}(std::get<0>(t), utf8_validator::mode::extreme); 165 | auto wanted = std::get<4>(t); 166 | ASSERT_EQ(res, wanted) << "Failed test " << i << " expected=" << result_names[static_cast(wanted)] 167 | << " got=" << result_names[static_cast(res)]; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /test/version.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace asyncpp::curl; 5 | 6 | TEST(ASYNCPP_CURL, Version) { 7 | version info; 8 | ASSERT_FALSE(info.curl_version().empty()); 9 | ASSERT_FALSE(info.host().empty()); 10 | std::cout << "version: " << info.curl_version(); 11 | std::cout << "\nhost: " << info.host(); 12 | std::cout << "\ncapath: " << info.capath(); 13 | std::cout << "\ncainfo: " << info.cainfo(); 14 | std::cout << "\nfeatures:"; 15 | for (auto f : version::features()) { 16 | if (info.has_feature(f)) std::cout << " " << version::feature_name(f); 17 | ASSERT_FALSE(version::feature_name(f).empty()); 18 | } 19 | std::cout << "\nprotocols:"; 20 | size_t idx = 0; 21 | for (auto p : info.protocols()) { 22 | std::cout << " " << p; 23 | ASSERT_FALSE(p.empty()); 24 | ASSERT_EQ(p, info.protocol(idx)); 25 | idx++; 26 | } 27 | ASSERT_EQ(idx, info.protocol_count()); 28 | std::cout << "\nlibz: " << info.libz_version(); 29 | std::cout << "\nares: " << info.ares_version(); 30 | std::cout << "\niconv: " << info.iconv_version(); 31 | std::cout << "\nidn: " << info.libidn_version(); 32 | std::cout << "\nlibssh: " << info.libssh_version(); 33 | std::cout << "\nbrotli: " << info.brotli_version(); 34 | std::cout << "\nnghttp2: " << info.nghttp2_version(); 35 | std::cout << "\nquic: " << info.quic_version(); 36 | std::cout << "\nzstd: " << info.zstd_version(); 37 | std::cout << "\nhyper: " << info.hyper_version(); 38 | std::cout << "\ngsasl: " << info.gsasl_version(); 39 | std::cout << std::endl; 40 | } 41 | -------------------------------------------------------------------------------- /test/webclient.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace asyncpp::curl; 10 | 11 | TEST(ASYNCPP_CURL, WebClientSync) { 12 | auto req = http_request::make_get("https://www.google.de"); 13 | auto resp = req.execute_sync(); 14 | ASSERT_EQ(resp.status_code, 200); 15 | ASSERT_FALSE(resp.headers.empty()); 16 | ASSERT_FALSE(resp.body.empty()); 17 | } 18 | 19 | TEST(ASYNCPP_CURL, WebClientAsync) { 20 | auto req = http_request::make_get("https://www.google.de"); 21 | auto resp = asyncpp::as_promise(req.execute_async()).get(); 22 | ASSERT_EQ(resp.status_code, 200); 23 | ASSERT_FALSE(resp.headers.empty()); 24 | ASSERT_FALSE(resp.body.empty()); 25 | } 26 | 27 | TEST(ASYNCPP_CURL, WebClientAsyncStopToken) { 28 | std::stop_source source; 29 | auto req = http_request::make_get("https://www.google.de"); 30 | auto resp_future = asyncpp::as_promise(req.execute_async(source.get_token())); 31 | source.request_stop(); 32 | try { 33 | resp_future.get(); 34 | FAIL() << "Did not throw"; 35 | } catch (const exception& e) { ASSERT_EQ(e.code(), CURLE_ABORTED_BY_CALLBACK); } 36 | } 37 | 38 | TEST(ASYNCPP_CURL, WebClientCookies) { 39 | auto req = http_request::make_get("https://www.google.de"); 40 | auto resp = req.execute_sync(); 41 | ASSERT_EQ(resp.status_code, 200); 42 | ASSERT_FALSE(resp.headers.empty()); 43 | ASSERT_FALSE(resp.body.empty()); 44 | ASSERT_FALSE(resp.cookies.empty()); 45 | } 46 | 47 | TEST(ASYNCPP_CURL, WebClientHeadersInsensitive) { 48 | http_request req; 49 | req.headers.emplace("Hello", "World"); 50 | req.headers.emplace("hello", "Sun"); 51 | req.headers.emplace("goodBye", "Sun"); 52 | 53 | ASSERT_EQ(3, req.headers.size()); 54 | ASSERT_EQ(2, req.headers.count("HELLO")); 55 | ASSERT_EQ(1, req.headers.count("GOODBYE")); 56 | } 57 | -------------------------------------------------------------------------------- /test/ws-autobahn.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace asyncpp; 10 | using namespace asyncpp::curl; 11 | 12 | promise> read_message(websocket* ws) { 13 | dispatcher* const main_dp = dispatcher::current(); 14 | promise> res; 15 | ws->set_on_open([=](int code) mutable { 16 | main_dp->push([code, res]() mutable { 17 | if (code != 0) res.try_reject(code); 18 | }); 19 | }); 20 | ws->set_on_message([=](websocket::buffer data, bool binary) mutable { 21 | std::pair p; 22 | p.first.append(reinterpret_cast(data.data()), data.size()); 23 | p.second = binary; 24 | ws->set_on_message({}); 25 | ws->set_on_close({}); 26 | main_dp->push([p = std::move(p), res]() mutable { res.try_fulfill(std::move(p)); }); 27 | }); 28 | ws->set_on_close([=](int, std::string_view) mutable { 29 | ws->set_on_message({}); 30 | ws->set_on_close({}); 31 | main_dp->push([res]() mutable { res.try_reject("disconnected"); }); 32 | }); 33 | return res; 34 | } 35 | 36 | task async_main(int argc, const char** argv) { 37 | if (argc < 2) { 38 | std::cerr << argv[0] << " [caseID]" << std::endl; 39 | co_return -1; 40 | } 41 | 42 | const std::string base = argv[1]; 43 | const std::string app_name = "asyncpp-curl"; 44 | dispatcher* const main_dp = dispatcher::current(); 45 | 46 | // Get testcase count 47 | size_t ncases = argc > 2 ? std::stoull(argv[2]) : 0; 48 | if (ncases == 0) { 49 | websocket socket; 50 | auto msg = read_message(&socket); 51 | socket.connect(uri(base + "/getCaseCount")); 52 | auto res = co_await msg; 53 | ncases = std::stoull(res.first); 54 | } 55 | std::cout << "Number of cases: " << ncases << std::endl; 56 | for (size_t i = argc > 2 ? ncases : 1; i <= ncases; i++) { 57 | websocket socket; 58 | socket.set_on_open([i, ncases](int code) { std::cout << "Test case " << i << "/" << ncases << " ... " << std::flush; }); 59 | socket.set_on_message([&socket](websocket::buffer data, bool binary) { socket.send(data, binary, [](bool) {}); }); 60 | promise res; 61 | socket.set_on_close([res, main_dp](uint16_t code, std::string_view reason) mutable { 62 | std::cout << "finished (code=" << code << ", reason=\"" << reason << "\")" << std::endl; 63 | main_dp->push([res]() mutable { res.fulfill(); }); 64 | }); 65 | socket.connect(uri(base + "/runCase?case=" + std::to_string(i) + "&agent=" + app_name)); 66 | co_await res; 67 | } 68 | { 69 | websocket socket; 70 | socket.connect(uri(base + "/updateReports?agent=" + app_name)); 71 | } 72 | 73 | co_return 0; 74 | } 75 | --------------------------------------------------------------------------------