diff options
Diffstat (limited to 'Src/external_dependencies/cpr')
154 files changed, 17555 insertions, 0 deletions
diff --git a/Src/external_dependencies/cpr/.clang-format b/Src/external_dependencies/cpr/.clang-format new file mode 100644 index 00000000..784d9b49 --- /dev/null +++ b/Src/external_dependencies/cpr/.clang-format @@ -0,0 +1,59 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -2 +AlignAfterOpenBracket: true +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AlwaysBreakAfterDefinitionReturnType: false +AlwaysBreakTemplateDeclarations: true +AlwaysBreakBeforeMultilineStrings: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BinPackParameters: true +BinPackArguments: true +ColumnLimit: 500 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +DerivePointerAlignment: false +ExperimentalAutoDetectBinPacking: false +IndentCaseLabels: true +IndentWrappedFunctionNames: false +IndentFunctionDeclarationAfterType: false +MaxEmptyLinesToKeep: 2 +KeepEmptyLinesAtTheStartOfBlocks: false +NamespaceIndentation: None +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +SpacesBeforeTrailingComments: 1 +Cpp11BracedListStyle: true +Standard: Auto +IndentWidth: 4 +TabWidth: 8 +UseTab: Never +BreakBeforeBraces: Attach +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpacesInAngles: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterCStyleCast: true +SpacesInContainerLiterals: true +SpaceBeforeAssignmentOperators: true +ContinuationIndentWidth: 8 +CommentPragmas: '^ IWYU pragma:' +SpaceBeforeParens: ControlStatements +... diff --git a/Src/external_dependencies/cpr/.clang-tidy b/Src/external_dependencies/cpr/.clang-tidy new file mode 100644 index 00000000..d18b254d --- /dev/null +++ b/Src/external_dependencies/cpr/.clang-tidy @@ -0,0 +1,40 @@ +--- +Checks: '*, +-cppcoreguidelines-pro-type-static-cast-downcast, +-fuchsia-default-arguments-calls, +-fuchsia-default-arguments, +-fuchsia-default-arguments-declarations, +-fuchsia-overloaded-operator, +-fuchsia-statically-constructed-objects, +-hicpp-use-auto, +-modernize-use-auto, +-modernize-use-trailing-return-type, +-readability-implicit-bool-conversion, +-readability-const-return-type, +-google-runtime-references, +-misc-non-private-member-variables-in-classes, +-llvm-include-order, +-cppcoreguidelines-non-private-member-variables-in-classes, +-cppcoreguidelines-pro-type-vararg, +-hicpp-vararg, +-cppcoreguidelines-owning-memory, +-llvmlibc-callee-namespace, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, +-hicpp-no-array-decay, +-modernize-pass-by-value, +-cppcoreguidelines-pro-bounds-constant-array-index, +-hicpp-signed-bitwise, +-llvmlibc-implementation-in-namespace, +-llvmlibc-restrict-system-libc-headers, +-readability-function-cognitive-complexity, +-readability-identifier-length, +-altera-unroll-loops, +-altera-id-dependent-backward-branch, +-bugprone-easily-swappable-parameters, +-modernize-return-braced-init-list, +-cppcoreguidelines-avoid-magic-numbers, +-readability-magic-numbers +' +WarningsAsErrors: '*' +HeaderFilterRegex: 'src/*.hpp' +FormatStyle: file diff --git a/Src/external_dependencies/cpr/.gitignore b/Src/external_dependencies/cpr/.gitignore new file mode 100644 index 00000000..5894bf1d --- /dev/null +++ b/Src/external_dependencies/cpr/.gitignore @@ -0,0 +1,56 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# CMake +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake +install_manifest.txt + +# Custom +build/ +!nuget/build + +# Jekyll stuff +_includes/ +_site/ + +# Vim +.ycm_extra_conf.py* + +# VSCode +.vscode/ +.vs/ + +# clangd +.cache/ + +# macOS +.DS_Store diff --git a/Src/external_dependencies/cpr/CMakeLists.txt b/Src/external_dependencies/cpr/CMakeLists.txt new file mode 100644 index 00000000..886275dd --- /dev/null +++ b/Src/external_dependencies/cpr/CMakeLists.txt @@ -0,0 +1,386 @@ +cmake_minimum_required(VERSION 3.15)
+project(cpr VERSION 1.9.0 LANGUAGES CXX)
+
+math(EXPR cpr_VERSION_NUM "${cpr_VERSION_MAJOR} * 0x10000 + ${cpr_VERSION_MINOR} * 0x100 + ${cpr_VERSION_PATCH}" OUTPUT_FORMAT HEXADECIMAL)
+configure_file("${cpr_SOURCE_DIR}/cmake/cprver.h.in" "${cpr_BINARY_DIR}/cpr_generated_includes/cpr/cprver.h")
+
+# Only change the folder behaviour if cpr is not a subproject
+if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
+ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake")
+ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
+ set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)
+else()
+ # Check required c++ standard of parent project
+ if(CMAKE_CXX_STANDARD)
+ set(PARENT_CXX_STANDARD ${CMAKE_CXX_STANDARD})
+ message(STATUS "CXX standard of parent project: ${PARENT_CXX_STANDARD}")
+ endif()
+endif()
+
+# Avoid the dll boilerplate code for windows
+set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
+
+if (PARENT_CXX_STANDARD)
+ # Don't set CMAKE_CXX_STANDARD if it is already set by parent project
+ if (PARENT_CXX_STANDARD LESS 17)
+ message(FATAL_ERROR "cpr ${cpr_VERSION} does not support ${PARENT_CXX_STANDARD}. Please use cpr <= 1.9.x")
+ endif()
+else()
+ # Set standard version if not already set by potential parent project
+ set(CMAKE_CXX_STANDARD 17)
+endif()
+
+message(STATUS "CXX standard: ${CMAKE_CXX_STANDARD}")
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CPR_LIBRARIES cpr CACHE INTERNAL "")
+
+macro(cpr_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT)
+ option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT})
+ if(DEFINED ENV{${OPTION_NAME}})
+ # Allow overriding the option through an environment variable
+ set(${OPTION_NAME} $ENV{${OPTION_NAME}})
+ endif()
+ if(${OPTION_NAME})
+ add_definitions(-D${OPTION_NAME})
+ endif()
+ message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}")
+endmacro()
+
+option(BUILD_SHARED_LIBS "Build libraries as shared libraries" ON)
+message(STATUS "C++ Requests CMake Options")
+message(STATUS "=======================================================")
+cpr_option(CPR_GENERATE_COVERAGE "Set to ON to generate coverage reports." OFF)
+cpr_option(CPR_CURL_NOSIGNAL "Set to ON to disable use of signals in libcurl." OFF)
+cpr_option(CURL_VERBOSE_LOGGING "Curl verbose logging during building curl" OFF)
+cpr_option(CPR_USE_SYSTEM_GTEST "If ON, this project will look in the system paths for an installed gtest library. If none is found it will use the build in one." OFF)
+cpr_option(CPR_USE_SYSTEM_CURL "If enabled we will use the curl lib already installed on this system." OFF)
+cpr_option(CPR_ENABLE_SSL "Enables or disables the SSL backend. Required to perform HTTPS requests." ON)
+cpr_option(CPR_FORCE_OPENSSL_BACKEND "Force to use the OpenSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF)
+cpr_option(CPR_FORCE_WINSSL_BACKEND "Force to use the WinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF)
+cpr_option(CPR_FORCE_DARWINSSL_BACKEND "Force to use the DarwinSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF)
+cpr_option(CPR_FORCE_MBEDTLS_BACKEND "Force to use the Mbed TLS backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF)
+cpr_option(CPR_ENABLE_LINTING "Set to ON to enable clang linting." OFF)
+cpr_option(CPR_ENABLE_CPPCHECK "Set to ON to enable Cppcheck static analysis. Requires CPR_BUILD_TESTS and CPR_BUILD_TESTS_SSL to be OFF to prevent checking google tests source code." OFF)
+cpr_option(CPR_BUILD_TESTS "Set to ON to build cpr tests." OFF)
+cpr_option(CPR_BUILD_TESTS_SSL "Set to ON to build cpr ssl tests" ${CPR_BUILD_TESTS})
+cpr_option(CPR_BUILD_TESTS_PROXY "Set to ON to build proxy tests. They fail in case there is no valid proxy server available in proxy_tests.cpp" OFF)
+cpr_option(CPR_SKIP_CA_BUNDLE_SEARCH "Skip searching for Certificate Authority certs. Turn ON systems like iOS where file access is restricted and prevents https from working." OFF)
+cpr_option(CPR_USE_BOOST_FILESYSTEM "Set to ON to use the Boost.Filesystem library on OSX." OFF)
+message(STATUS "=======================================================")
+
+if (CPR_FORCE_USE_SYSTEM_CURL)
+ message(WARNING "The variable CPR_FORCE_USE_SYSTEM_CURL is deprecated, please use CPR_USE_SYSTEM_CURL instead")
+ set(CPR_USE_SYSTEM_CURL ${CPR_FORCE_USE_SYSTEM_CURL})
+endif()
+
+include(GNUInstallDirs)
+include(FetchContent)
+include(cmake/code_coverage.cmake)
+include(cmake/sanitizer.cmake)
+include(cmake/clear_variable.cmake)
+
+# So CMake can find FindMbedTLS.cmake
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
+
+# Linting
+if(CPR_ENABLE_LINTING)
+ include(cmake/clang-tidy.cmake)
+endif()
+
+# Cppcheck
+if(CPR_ENABLE_CPPCHECK)
+ if(CPR_BUILD_TESTS OR CPR_BUILD_TESTS_SSL)
+ message(FATAL_ERROR "Cppcheck is incompatible with building tests. Make sure to disable CPR_ENABLE_CPPCHECK or disable tests by setting CPR_BUILD_TESTS and CPR_BUILD_TESTS_SSL to OFF. This is because Cppcheck would try to check the google tests source code and then fail. ")
+ endif()
+ include(cmake/cppcheck.cmake)
+endif()
+
+if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
+else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror")
+endif()
+
+# SSL
+if(CPR_ENABLE_SSL)
+ if(CPR_FORCE_OPENSSL_BACKEND OR CPR_FORCE_WINSSL_BACKEND OR CPR_FORCE_DARWINSSL_BACKEND OR CPR_FORCE_MBEDTLS_BACKEND)
+ message(STATUS "Disabled SSL backend auto detect since either CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, or CPR_FORCE_WINSSL_BACKEND is enabled.")
+ set(DETECT_SSL_BACKEND OFF CACHE INTERNAL "" FORCE)
+ else()
+ message(STATUS "Automatically detecting SSL backend.")
+ set(DETECT_SSL_BACKEND ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ if(CPR_FORCE_WINSSL_BACKEND AND (NOT WIN32))
+ message(FATAL_ERROR "WinSSL is only available on Windows! Use either OpenSSL (CPR_FORCE_OPENSSL_BACKEND) or DarwinSSL (CPR_FORCE_DARWINSSL_BACKEND) instead.")
+ endif()
+
+ if(DETECT_SSL_BACKEND)
+ message(STATUS "Detecting SSL backend...")
+ if(WIN32)
+ message(STATUS "SSL auto detect: Using WinSSL.")
+ set(SSL_BACKEND_USED "WinSSL")
+ elseif(APPLE)
+ message(STATUS "SSL auto detect: Using DarwinSSL.")
+ set(CPR_BUILD_TESTS_SSL OFF)
+ set(SSL_BACKEND_USED "DarwinSSL")
+ else()
+ find_package(OpenSSL)
+ if(OPENSSL_FOUND)
+ message(STATUS "SSL auto detect: Using OpenSSL.")
+ set(SSL_BACKEND_USED "OpenSSL")
+ else()
+ find_package(MbedTLS)
+ if(MBEDTLS_FOUND)
+ set(SSL_BACKEND_USED "MbedTLS")
+ else()
+ message(FATAL_ERROR "No valid SSL backend found! Please install OpenSSL, Mbed TLS or disable SSL by setting CPR_ENABLE_SSL to OFF.")
+ endif()
+ endif()
+ endif()
+ else()
+ if(CPR_FORCE_OPENSSL_BACKEND)
+ find_package(OpenSSL)
+ if(OPENSSL_FOUND)
+ message(STATUS "Using OpenSSL.")
+ set(SSL_BACKEND_USED "OpenSSL")
+ else()
+ message(FATAL_ERROR "CPR_FORCE_OPENSSL_BACKEND enabled but we were not able to find OpenSSL!")
+ endif()
+ elseif(CPR_FORCE_WINSSL_BACKEND)
+ message(STATUS "Using WinSSL.")
+ set(SSL_BACKEND_USED "WinSSL")
+ elseif(CPR_FORCE_DARWINSSL_BACKEND)
+ message(STATUS "Using DarwinSSL.")
+ set(CPR_BUILD_TESTS_SSL OFF)
+ set(SSL_BACKEND_USED "DarwinSSL")
+ elseif(CPR_FORCE_MBEDTLS_BACKEND)
+ message(STATUS "Using Mbed TLS.")
+ set(CPR_BUILD_TESTS_SSL OFF)
+ set(SSL_BACKEND_USED "MbedTLS")
+ endif()
+ endif()
+endif()
+
+if(SSL_BACKEND_USED STREQUAL "OpenSSL")
+# Fix missing OpenSSL includes for Windows since in 'ssl_ctx.cpp' we include OpenSSL directly
+find_package(OpenSSL REQUIRED)
+ add_compile_definitions(OPENSSL_BACKEND_USED)
+endif()
+
+get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if (NOT isMultiConfig)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${ALLOWED_BUILD_TYPES}")
+ if (NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE)
+ elseif(NOT CMAKE_BUILD_TYPE IN_LIST ALLOWED_BUILD_TYPES)
+ message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}")
+ endif()
+else ()
+ unset(CMAKE_BUILD_TYPE)
+ foreach(TYPE ${ALLOWED_BUILD_TYPES})
+ if (NOT ${TYPE} IN_LIST CMAKE_CONFIGURATION_TYPES)
+ list(APPEND CMAKE_CONFIGURATION_TYPES ${TYPE})
+ endif()
+ endforeach()
+endif()
+
+# Curl configuration
+if(CPR_USE_SYSTEM_CURL)
+ if(CPR_ENABLE_SSL)
+ find_package(CURL COMPONENTS HTTP HTTPS)
+ if(CURL_FOUND)
+ message(STATUS "Curl ${CURL_VERSION_STRING} found on this system.")
+ # To be able to load certificates under Windows when using OpenSSL:
+ if(CMAKE_USE_OPENSSL AND WIN32 AND (NOT (CURL_VERSION_STRING VERSION_GREATER_EQUAL "7.71.0")))
+ message(FATAL_ERROR "Your system curl version (${CURL_VERSION_STRING}) is too old to support OpenSSL on Windows which requires curl >= 7.71.0. Update your curl version, use WinSSL, disable SSL or use the build in version of curl.")
+ endif()
+ else()
+ find_package(CURL COMPONENTS HTTP)
+ if(CURL_FOUND)
+ message(FATAL_ERROR "Curl found on this system but WITHOUT HTTPS/SSL support. Either disable SSL by setting CPR_ENABLE_SSL to OFF or use the build in version of curl by setting CPR_USE_SYSTEM_CURL to OFF.")
+ else()
+ message(FATAL_ERROR "Curl not found on this system. To use the build in version set CPR_USE_SYSTEM_CURL to OFF.")
+ endif()
+ endif()
+ else()
+ find_package(CURL COMPONENTS HTTP)
+ if(CURL_FOUND)
+ message(STATUS "Curl found on this system.")
+ else()
+ message(FATAL_ERROR "Curl not found on this system. To use the build in version set CPR_USE_SYSTEM_CURL to OFF.")
+ endif()
+ endif()
+else()
+ message(STATUS "Configuring build in curl...")
+
+ # ZLIB is optional for curl
+ # to disable it:
+ # * from command line:
+ # -DCURL_ZLIB=OFF
+ # * from CMake script:
+ # SET(CURL_ZLIB OFF CACHE STRING "" FORCE)
+ if (CURL_ZLIB OR CURL_ZLIB STREQUAL AUTO OR NOT DEFINED CACHE{CURL_ZLIB})
+ include(cmake/zlib_external.cmake)
+ endif()
+
+ # We only need HTTP (and HTTPS) support:
+ set(HTTP_ONLY ON CACHE INTERNAL "" FORCE)
+ set(BUILD_CURL_EXE OFF CACHE INTERNAL "" FORCE)
+ set(BUILD_TESTING OFF)
+
+ if (CURL_VERBOSE_LOGGING)
+ message(STATUS "Enabled curl debug features")
+ set(ENABLE_DEBUG ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ if (CPR_ENABLE_SSL)
+ set(SSL_ENABLED ON CACHE INTERNAL "" FORCE)
+ if(ANDROID)
+ set(CURL_CA_PATH "/system/etc/security/cacerts" CACHE INTERNAL "")
+ elseif(CPR_SKIP_CA_BUNDLE_SEARCH)
+ set(CURL_CA_PATH "none" CACHE INTERNAL "")
+ else()
+ set(CURL_CA_PATH "auto" CACHE INTERNAL "")
+ endif()
+
+ if(CPR_SKIP_CA_BUNDLE_SEARCH)
+ set(CURL_CA_BUNDLE "none" CACHE INTERNAL "")
+ elseif(NOT DEFINED CURL_CA_BUNDLE)
+ set(CURL_CA_BUNDLE "auto" CACHE INTERNAL "")
+ endif()
+
+ if(SSL_BACKEND_USED STREQUAL "WinSSL")
+ set(CMAKE_USE_SCHANNEL ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ if(SSL_BACKEND_USED STREQUAL "OpenSSL")
+ set(CMAKE_USE_OPENSSL ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ if(SSL_BACKEND_USED STREQUAL "DarwinSSL")
+ set(CMAKE_USE_SECTRANSP ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ if(SSL_BACKEND_USED STREQUAL "MbedTLS")
+ set(CMAKE_USE_MBEDTLS ON CACHE INTERNAL "" FORCE)
+ endif()
+
+ message(STATUS "Enabled curl SSL")
+ else()
+ set(SSL_ENABLED OFF CACHE INTERNAL "" FORCE)
+ set(CURL_CA_PATH "none" CACHE INTERNAL "" FORCE)
+ set(CMAKE_USE_SCHANNEL OFF CACHE INTERNAL "" FORCE)
+ set(CMAKE_USE_OPENSSL OFF CACHE INTERNAL "" FORCE)
+ set(CMAKE_USE_SECTRANSP OFF CACHE INTERNAL "" FORCE)
+ set(CMAKE_USE_MBEDTLS OFF CACHE INTERNAL "" FORCE)
+ message(STATUS "Disabled curl SSL")
+ endif()
+ # Disable linting for curl
+ clear_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+
+ FetchContent_Declare(curl
+ URL https://github.com/curl/curl/releases/download/curl-7_80_0/curl-7.80.0.tar.xz
+ URL_HASH SHA256=a132bd93188b938771135ac7c1f3ac1d3ce507c1fcbef8c471397639214ae2ab # the file hash for curl-7.80.0.tar.xz
+ USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress
+ FetchContent_MakeAvailable(curl)
+
+ restore_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+
+ # Group under the "external" project folder in IDEs such as Visual Studio.
+ if(BUILD_CURL_EXE)
+ set_property(TARGET curl PROPERTY FOLDER "external")
+ endif()
+
+ set_property(TARGET libcurl PROPERTY FOLDER "external")
+endif()
+
+# GTest configuration
+if(CPR_BUILD_TESTS)
+ if(CPR_USE_SYSTEM_GTEST)
+ find_package(GTest)
+ endif()
+ if(NOT CPR_USE_SYSTEM_GTEST OR NOT GTEST_FOUND)
+ message(STATUS "Not using system gtest, using built-in googletest project instead.")
+ if(MSVC)
+ # By default, GTest compiles on Windows in CRT static linkage mode. We use this
+ # variable to force it into using the CRT in dynamic linkage (DLL), just as CPR
+ # does.
+ set(gtest_force_shared_crt ON CACHE BOOL "Force gtest to use the shared c runtime")
+ endif()
+
+ # Disable linting for google test
+ clear_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+
+ FetchContent_Declare(googletest
+ URL https://github.com/google/googletest/archive/release-1.11.0.tar.gz
+ URL_HASH SHA256=b4870bf121ff7795ba20d20bcdd8627b8e088f2d1dab299a031c1034eddc93d5 # the file hash for release-1.11.0.tar.gz
+ USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress
+ FetchContent_MakeAvailable(googletest)
+
+ restore_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+
+ add_library(gtest_int INTERFACE)
+ target_link_libraries(gtest_int INTERFACE gtest)
+ target_include_directories(gtest_int INTERFACE ${googletest_SOURCE_DIR}/include)
+
+ add_library(GTest::GTest ALIAS gtest_int)
+
+ # Group under the "tests/gtest" project folder in IDEs such as Visual Studio.
+ set_property(TARGET gtest PROPERTY FOLDER "tests/gtest")
+ set_property(TARGET gtest_main PROPERTY FOLDER "tests/gtest")
+ endif()
+endif()
+
+
+# Mongoose configuration
+if(CPR_BUILD_TESTS)
+ message(STATUS "Building mongoose project for test support.")
+
+ if(CPR_BUILD_TESTS_SSL)
+ if(NOT CPR_ENABLE_SSL)
+ message(FATAL_ERROR "OpenSSL is required to build SSL test but CPR_ENABLE_SSL is disabled. Either set CPR_ENABLE_SSL to ON or disable CPR_BUILD_TESTS_SSL.")
+ endif()
+
+ if(NOT(SSL_BACKEND_USED STREQUAL "OpenSSL"))
+ message(FATAL_ERROR "OpenSSL is required for SSL test, but it seams like OpenSSL is not being used as SSL backend. Either set CPR_BUILD_TESTS_SSL to OFF or set CPR_FORCE_OPENSSL_BACKEND to ON and try again.")
+ endif()
+
+ set(ENABLE_SSL_TESTS ON CACHE INTERNAL "")
+ else()
+ set(ENABLE_SSL_TESTS OFF CACHE INTERNAL "")
+ endif()
+
+ # Disable linting for mongoose
+ clear_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+
+ FetchContent_Declare(mongoose
+ URL https://github.com/cesanta/mongoose/archive/7.7.tar.gz
+ URL_HASH SHA256=4e5733dae31c3a81156af63ca9aa3a6b9b736547f21f23c3ab2f8e3f1ecc16c0 # the hash for 7.7.tar.gz
+ USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress
+ # We can not use FetchContent_MakeAvailable, since we need to patch mongoose to use CMake
+ if (NOT mongoose_POPULATED)
+ FetchContent_POPULATE(mongoose)
+
+ file(INSTALL cmake/mongoose.CMakeLists.txt DESTINATION ${mongoose_SOURCE_DIR})
+ file(RENAME ${mongoose_SOURCE_DIR}/mongoose.CMakeLists.txt ${mongoose_SOURCE_DIR}/CMakeLists.txt)
+ add_subdirectory(${mongoose_SOURCE_DIR} ${mongoose_BINARY_DIR})
+
+ endif()
+ # Group under the "external" project folder in IDEs such as Visual Studio.
+ set_property(TARGET mongoose PROPERTY FOLDER "external")
+ restore_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+endif()
+
+
+add_subdirectory(cpr)
+add_subdirectory(include)
+
+if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND CPR_BUILD_TESTS)
+ # Disable linting for tests since they are currently not up to the standard
+ clear_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+ enable_testing()
+ add_subdirectory(test)
+ restore_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP)
+endif()
diff --git a/Src/external_dependencies/cpr/CODE_OF_CONDUCT.md b/Src/external_dependencies/cpr/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..1c9ee05c --- /dev/null +++ b/Src/external_dependencies/cpr/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +cc@libcpr.org. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Src/external_dependencies/cpr/CONTRIBUTING.md b/Src/external_dependencies/cpr/CONTRIBUTING.md new file mode 100644 index 00000000..abfb97c3 --- /dev/null +++ b/Src/external_dependencies/cpr/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing to C++ Requests + +Please fork this repository and contribute back using [pull requests](https://github.com/whoshuu/cpr/pulls). Features can be requested using [issues](https://github.com/whoshuu/cpr/issues). All code, comments, and critiques are greatly appreciated. + +## Formatting + +To avoid unproductive debates on formatting, this project uses `clang-format` to ensure a consistent style across all source files. Currently, `clang-format` 3.8 is the version of `clang-format` we use. The format file can be found [here](https://github.com/whoshuu/cpr/blob/master/.clang-format). To install `clang-format` on Ubuntu, run this: + +``` +apt-get install clang-format-3.8 +``` + +To install `clang-format` on OS X, run this: + +``` +brew install clang-format +``` + +Note that `brew` might install a later version of `clang-format`, but it should be mostly compatible with what's run on the Travis servers. + +To run `clang-format` on every source file, run this in the root directory: + +``` +./format-check.sh +``` + +This should indicate which files need formatting and also show a diff of the requested changes. More specific usage instructions can be found on the official [LLVM website](http://releases.llvm.org/3.8.0/tools/clang/docs/ClangFormat.html). diff --git a/Src/external_dependencies/cpr/CppCheckSuppressions.txt b/Src/external_dependencies/cpr/CppCheckSuppressions.txt new file mode 100644 index 00000000..7b811b07 --- /dev/null +++ b/Src/external_dependencies/cpr/CppCheckSuppressions.txt @@ -0,0 +1,2 @@ +noExplicitConstructor
+ConfigurationNotChecked
diff --git a/Src/external_dependencies/cpr/LICENSE b/Src/external_dependencies/cpr/LICENSE new file mode 100644 index 00000000..c63edeb3 --- /dev/null +++ b/Src/external_dependencies/cpr/LICENSE @@ -0,0 +1,25 @@ +This license applies to everything except the contents of the "test" +directory and its subdirectories. + +MIT License + +Copyright (c) 2017-2021 Huu Nguyen +Copyright (c) 2022 libcpr and many other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/README.md b/Src/external_dependencies/cpr/README.md new file mode 100644 index 00000000..3e65f321 --- /dev/null +++ b/Src/external_dependencies/cpr/README.md @@ -0,0 +1,162 @@ +# C++ Requests: Curl for People <img align="right" height="40" src="http://i.imgur.com/d9Xtyts.png"> + +[](https://docs.libcpr.org/) + +[](https://gitter.im/libcpr/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +## Announcements + +* Like you probably have noticed, `cpr` moved to a new home from https://github.com/whoshuu/cpr to https://github.com/libcpr/cpr. Read more [here](https://github.com/libcpr/cpr/issues/636). +* This project is being maintained by [Fabian Sauter](https://github.com/com8) and [Kilian Traub](https://github.com/KingKili). +* For quick help, and discussion libcpr also offer a [gitter](https://gitter.im/libcpr/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) chat. + +## TLDR + +C++ Requests is a simple wrapper around [libcurl](http://curl.haxx.se/libcurl) inspired by the excellent [Python Requests](https://github.com/kennethreitz/requests) project. + +Despite its name, libcurl's easy interface is anything but, and making mistakes, misusing it is a common source of error and frustration. Using the more expressive language facilities of `C++17` (or `C++11` in case you use cpr < 1.10.0), this library captures the essence of making network calls into a few concise idioms. + +Here's a quick GET request: + +```c++ +#include <cpr/cpr.h> + +int main(int argc, char** argv) { + cpr::Response r = cpr::Get(cpr::Url{"https://api.github.com/repos/whoshuu/cpr/contributors"}, + cpr::Authentication{"user", "pass", cpr::AuthMode::BASIC}, + cpr::Parameters{{"anon", "true"}, {"key", "value"}}); + r.status_code; // 200 + r.header["content-type"]; // application/json; charset=utf-8 + r.text; // JSON text string + return 0; +} +``` + +And here's [less functional, more complicated code, without cpr](https://gist.github.com/whoshuu/2dc858b8730079602044). + +## Documentation + +[](https://docs.libcpr.org/) +You can find the latest documentation [here](https://docs.libcpr.org/). It's a work in progress, but it should give you a better idea of how to use the library than the [tests](https://github.com/libcpr/cpr/tree/master/test) currently do. + +## Features + +C++ Requests currently supports: + +* Custom headers +* Url encoded parameters +* Url encoded POST values +* Multipart form POST upload +* File POST upload +* Basic authentication +* Bearer authentication +* Digest authentication +* NTLM authentication +* Connection and request timeout specification +* Timeout for low speed connection +* Asynchronous requests +* :cookie: support! +* Proxy support +* Callback interfaces +* PUT methods +* DELETE methods +* HEAD methods +* OPTIONS methods +* PATCH methods +* Thread Safe access to [libCurl](https://curl.haxx.se/libcurl/c/threadsafe.html) +* OpenSSL and WinSSL support for HTTPS requests + +## Planned + +For a quick overview about the planed features, have a look at the next [Milestones](https://github.com/libcpr/cpr/milestones). + +## Usage + +### CMake + +#### fetch_content: +If you already have a CMake project you need to integrate C++ Requests with, the primary way is to use `fetch_content`. +Add the following to your `CMakeLists.txt`. + + +```cmake +include(FetchContent) +FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git + GIT_TAG 871ed52d350214a034f6ef8a3b8f51c5ce1bd400) # The commit hash for 1.9.0. Replace with the latest from: https://github.com/libcpr/cpr/releases +FetchContent_MakeAvailable(cpr) +``` + +This will produce the target `cpr::cpr` which you can link against the typical way: + +```cmake +target_link_libraries(your_target_name PRIVATE cpr::cpr) +``` + +That should do it! +There's no need to handle `libcurl` yourself. All dependencies are taken care of for you. +All of this can be found in an example [**here**](https://github.com/libcpr/example-cmake-fetch-content). + +#### find_package(): +If you prefer not to use `fetch_content`, you can download, build, and install the library and then use CMake `find_package()` function to integrate it into a project. + +**Note:** this feature is feasible only if CPR_USE_SYSTEM_CURL is set. (see [#645](https://github.com/libcpr/cpr/pull/645)) +```Bash +$ git clone https://github.com/libcpr/cpr.git +$ cd cpr && mkdir build && cd build +$ cmake .. -DCPR_USE_SYSTEM_CURL=ON +$ cmake --build . +$ sudo cmake --install . +``` +In your `CMakeLists.txt`: +```cmake +find_package(cpr REQUIRED) +add_executable(your_target_name your_target_name.cpp) +target_link_libraries(your_target_name PRIVATE cpr::cpr) +``` + +### Bazel + +Please refer to [hedronvision/bazel-make-cc-https-easy](https://github.com/hedronvision/bazel-make-cc-https-easy). + +### Packages for Linux Distributions + +Alternatively, you may install a package specific to your Linux distribution. Since so few distributions currently have a package for cpr, most users will not be able to run your program with this approach. + +Currently, we are aware of packages for the following distributions: + +* [Arch Linux (AUR)](https://aur.archlinux.org/packages/cpr) + +If there's no package for your distribution, try making one! If you do, and it is added to your distribution's repositories, please submit a pull request to add it to the list above. However, please only do this if you plan to actively maintain the package. + +### NuGet Package + +For Windows, there is also a libcpr NuGet package available. Currently, x86 and x64 builds are supported with release and debug configuration. + +The package can be found here: [NuGet.org](https://www.nuget.org/packages/libcpr/) + +## Requirements + +The only explicit requirements are: + +* a `C++17` compatible compiler such as Clang or GCC. The minimum required version of GCC is unknown, so if anyone has trouble building this library with a specific version of GCC, do let me know +* in case you only have a `C++11` compatible compiler available, all versions below cpr 1.9.x are for you. With the upcoming release of cpr 1.10.0, we are switching to `C++17` as a requirement. +* If you would like to perform https requests `OpenSSL` and its development libraries are required. + +## Building cpr - Using vcpkg + +You can download and install cpr using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: +```Bash +git clone https://github.com/Microsoft/vcpkg.git +cd vcpkg +./bootstrap-vcpkg.sh +./vcpkg integrate install +./vcpkg install cpr +``` +The `cpr` port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +## Building cpr - Using Conan + +You can download and install `cpr` using the [Conan](https://conan.io/) package manager. Setup your CMakeLists.txt (see [Conan documentation](https://docs.conan.io/en/latest/integrations/build_system.html) on how to use MSBuild, Meson and others). +An example can be found [**here**](https://github.com/libcpr/example-cmake-conan). + +The `cpr` package in Conan is kept up to date by Conan contributors. If the version is out of date, please [create an issue or pull request](https://github.com/conan-io/conan-center-index) on the `conan-center-index` repository. diff --git a/Src/external_dependencies/cpr/cmake/FindMbedTLS.cmake b/Src/external_dependencies/cpr/cmake/FindMbedTLS.cmake new file mode 100644 index 00000000..61ec4641 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/FindMbedTLS.cmake @@ -0,0 +1,14 @@ +# Source: https://github.com/curl/curl/blob/curl-7_82_0/CMake/FindMbedTLS.cmake +find_path(MBEDTLS_INCLUDE_DIRS mbedtls/ssl.h) + +find_library(MBEDTLS_LIBRARY mbedtls) +find_library(MBEDX509_LIBRARY mbedx509) +find_library(MBEDCRYPTO_LIBRARY mbedcrypto) + +set(MBEDTLS_LIBRARIES "${MBEDTLS_LIBRARY}" "${MBEDX509_LIBRARY}" "${MBEDCRYPTO_LIBRARY}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(MbedTLS DEFAULT_MSG + MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) + +mark_as_advanced(MBEDTLS_INCLUDE_DIRS MBEDTLS_LIBRARY MBEDX509_LIBRARY MBEDCRYPTO_LIBRARY) diff --git a/Src/external_dependencies/cpr/cmake/clang-tidy.cmake b/Src/external_dependencies/cpr/cmake/clang-tidy.cmake new file mode 100644 index 00000000..26defad2 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/clang-tidy.cmake @@ -0,0 +1,13 @@ +if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy) + mark_as_advanced(CLANG_TIDY_EXECUTABLE) + + if (${CLANG_TIDY_EXECUTABLE}) + message(FATAL_ERROR "Clang-tidy not found") + else() + message(STATUS "Enabling clang-tidy") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE};-warnings-as-errors=*") + endif() +else() + message(FATAL_ERROR "Clang-tidy is not supported when building for windows") +endif() diff --git a/Src/external_dependencies/cpr/cmake/clear_variable.cmake b/Src/external_dependencies/cpr/cmake/clear_variable.cmake new file mode 100644 index 00000000..469ac827 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/clear_variable.cmake @@ -0,0 +1,11 @@ +macro(clear_variable) + cmake_parse_arguments(CLEAR_VAR "" "DESTINATION;BACKUP;REPLACE" "" ${ARGN}) + set(${CLEAR_VAR_BACKUP} ${${CLEAR_VAR_DESTINATION}}) + set(${CLEAR_VAR_DESTINATION} ${CLEAR_VAR_REPLACE}) +endmacro() + +macro(restore_variable) + cmake_parse_arguments(CLEAR_VAR "" "DESTINATION;BACKUP" "" ${ARGN}) + set(${CLEAR_VAR_DESTINATION} ${${CLEAR_VAR_BACKUP}}) + unset(${CLEAR_VAR_BACKUP}) +endmacro() diff --git a/Src/external_dependencies/cpr/cmake/code_coverage.cmake b/Src/external_dependencies/cpr/cmake/code_coverage.cmake new file mode 100644 index 00000000..eefc4fb3 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/code_coverage.cmake @@ -0,0 +1,29 @@ +# Code coverage +if(CPR_BUILD_TESTS AND CPR_GENERATE_COVERAGE) + set(CMAKE_BUILD_TYPE COVERAGE CACHE INTERNAL "Coverage enabled build") + message(STATUS "Enabling gcov support") + if(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(COVERAGE_FLAG "--coverage") + endif() + set(CMAKE_CXX_FLAGS_COVERAGE + "-g -O0 ${COVERAGE_FLAG} -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE) + set(CMAKE_C_FLAGS_COVERAGE + "-g -O0 ${COVERAGE_FLAG} -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE) + set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE) + mark_as_advanced( + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE) +endif() diff --git a/Src/external_dependencies/cpr/cmake/cppcheck.cmake b/Src/external_dependencies/cpr/cmake/cppcheck.cmake new file mode 100644 index 00000000..8ef46880 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/cppcheck.cmake @@ -0,0 +1,10 @@ +find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) +if(CMAKE_CXX_CPPCHECK) + list(APPEND CMAKE_CXX_CPPCHECK + "--error-exitcode=1" + "--enable=warning,style" + "--force" + "--inline-suppr" + "--std=c++${CMAKE_CXX_STANDARD}" + "--suppressions-list=${CMAKE_SOURCE_DIR}/CppCheckSuppressions.txt") +endif() diff --git a/Src/external_dependencies/cpr/cmake/cprConfig.cmake.in b/Src/external_dependencies/cpr/cmake/cprConfig.cmake.in new file mode 100644 index 00000000..9c0bda5f --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/cprConfig.cmake.in @@ -0,0 +1,8 @@ +include(CMakeFindDependencyMacro) +@PACKAGE_INIT@ + +find_dependency(CURL REQUIRED) + +include(${CMAKE_CURRENT_LIST_DIR}/cprTargets.cmake) + +check_required_components(cpr)
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/cmake/cprver.h.in b/Src/external_dependencies/cpr/cmake/cprver.h.in new file mode 100644 index 00000000..e3533249 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/cprver.h.in @@ -0,0 +1,30 @@ +#ifndef CPR_CPRVER_H +#define CPR_CPRVER_H + +/** + * CPR version as a string. + **/ +#define CPR_VERSION "${cpr_VERSION}" + +/** + * CPR version split up into parts. + **/ +#define CPR_VERSION_MAJOR ${cpr_VERSION_MAJOR} +#define CPR_VERSION_MINOR ${cpr_VERSION_MINOR} +#define CPR_VERSION_PATCH ${cpr_VERSION_PATCH} + +/** + * CPR version as a single hex digit. + * it can be split up into three parts: + * 0xAABBCC + * AA: The current CPR major version number in a hex format. + * BB: The current CPR minor version number in a hex format. + * CC: The current CPR patch version number in a hex format. + * + * Examples: + * '0x010702' -> 01.07.02 -> CPR_VERSION: 1.7.2 + * '0xA13722' -> A1.37.22 -> CPR_VERSION: 161.55.34 + **/ +#define CPR_VERSION_NUM ${cpr_VERSION_NUM} + +#endif diff --git a/Src/external_dependencies/cpr/cmake/mongoose.CMakeLists.txt b/Src/external_dependencies/cpr/cmake/mongoose.CMakeLists.txt new file mode 100644 index 00000000..f46a3d49 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/mongoose.CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.15)
+project(mongoose C)
+
+
+add_library(mongoose STATIC mongoose.c)
+target_include_directories(mongoose PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+
+if(ENABLE_SSL_TESTS)
+ # Enable mongoose SSL
+ target_compile_definitions(mongoose PUBLIC MG_ENABLE_OPENSSL)
+ target_link_libraries(mongoose PRIVATE OpenSSL::SSL)
+endif()
diff --git a/Src/external_dependencies/cpr/cmake/sanitizer.cmake b/Src/external_dependencies/cpr/cmake/sanitizer.cmake new file mode 100644 index 00000000..09095980 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/sanitizer.cmake @@ -0,0 +1,73 @@ +include(CheckCXXCompilerFlag) +include(CheckCXXSourceRuns) + +set(ALLOWED_BUILD_TYPES Debug Release RelWithDebInfo MinSizeRel) +set(ALL_SAN_FLAGS "") + + # No sanitizers when cross compiling to prevent stuff like this: https://github.com/whoshuu/cpr/issues/582 +if(NOT CMAKE_CROSSCOMPILING) + # Thread sanitizer + set(THREAD_SAN_FLAGS "-fsanitize=thread") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${THREAD_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" THREAD_SANITIZER_AVAILABLE) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(THREAD_SANITIZER_AVAILABLE) + list(APPEND ALLOWED_BUILD_TYPES ThreadSan) + # Do not add Thread sanitizer to all sanitizer because it is incompatible with other sanitizer + endif() + set(CMAKE_C_FLAGS_THREADSAN "${CMAKE_C_FLAGS_DEBUG} ${THREAD_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C compiler during thread sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_THREADSAN "${CMAKE_CXX_FLAGS_DEBUG} ${THREAD_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C++ compiler during thread sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_THREADSAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during thread sanitizer builds" FORCE) + + # Address sanitizer + set(ADDR_SAN_FLAGS "-fsanitize=address") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${ADDR_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" ADDRESS_SANITIZER_AVAILABLE) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(ADDRESS_SANITIZER_AVAILABLE) + list(APPEND ALLOWED_BUILD_TYPES AddrSan) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${ADDR_SAN_FLAGS}") + endif() + set(CMAKE_C_FLAGS_ADDRSAN "${CMAKE_C_FLAGS_DEBUG} ${ADDR_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C compiler during address sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_ADDRSAN "${CMAKE_CXX_FLAGS_DEBUG} ${ADDR_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C++ compiler during address sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_ADDRSAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during address sanitizer builds" FORCE) + + # Leak sanitizer + set(LEAK_SAN_FLAGS "-fsanitize=leak") + check_cxx_compiler_flag(${LEAK_SAN_FLAGS} LEAK_SANITIZER_AVAILABLE) + if(LEAK_SANITIZER_AVAILABLE) + list(APPEND ALLOWED_BUILD_TYPES LeakSan) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${LEAK_SAN_FLAGS}") + endif() + set(CMAKE_C_FLAGS_LEAKSAN "${CMAKE_C_FLAGS_DEBUG} ${LEAK_SAN_FLAGS} -fno-omit-frame-pointer" CACHE INTERNAL "Flags used by the C compiler during leak sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_LEAKSAN "${CMAKE_CXX_FLAGS_DEBUG} ${LEAK_SAN_FLAGS} -fno-omit-frame-pointer" CACHE INTERNAL "Flags used by the C++ compiler during leak sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_LEAKSAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during leak sanitizer builds" FORCE) + + # Undefined behavior sanitizer + set(UDEF_SAN_FLAGS "-fsanitize=undefined") + check_cxx_compiler_flag(${UDEF_SAN_FLAGS} UNDEFINED_BEHAVIOUR_SANITIZER_AVAILABLE) + if(UNDEFINED_BEHAVIOUR_SANITIZER_AVAILABLE) + list(APPEND ALLOWED_BUILD_TYPES UdefSan) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${UDEF_SAN_FLAGS}") + endif() + set(CMAKE_C_FLAGS_UDEFSAN "${CMAKE_C_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C compiler during undefined behaviour sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_UDEFSAN "${CMAKE_CXX_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C++ compiler during undefined behaviour sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_UDEFSAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during undefined behaviour sanitizer builds" FORCE) + + # All sanitizer (without thread sanitizer) + if(NOT ALL_SAN_FLAGS STREQUAL "") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${ALL_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" ALL_SANITIZERS_AVAILABLE) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(ALL_SANITIZERS_AVAILABLE) + list(APPEND ALLOWED_BUILD_TYPES AllSan) + endif() + endif() + + set(CMAKE_C_FLAGS_ALLSAN "${CMAKE_C_FLAGS_DEBUG} ${ALL_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C compiler during most possible sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_ALLSAN "${CMAKE_CXX_FLAGS_DEBUG} ${ALL_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C++ compiler during most possible sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_ALLSAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during most possible sanitizer builds" FORCE) +endif() diff --git a/Src/external_dependencies/cpr/cmake/zlib_external.cmake b/Src/external_dependencies/cpr/cmake/zlib_external.cmake new file mode 100644 index 00000000..0b494f14 --- /dev/null +++ b/Src/external_dependencies/cpr/cmake/zlib_external.cmake @@ -0,0 +1,22 @@ +# ZLIB + +# Fix Windows missing "zlib.dll": +if(WIN32 AND (${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/$<CONFIG> CACHE INTERNAL "" FORCE) +endif() + +set(ZLIB_COMPAT ON CACHE INTERNAL "" FORCE) +set(ZLIB_ENABLE_TESTS OFF CACHE INTERNAL "" FORCE) + +FetchContent_Declare(zlib + GIT_REPOSITORY https://github.com/zlib-ng/zlib-ng + GIT_TAG 2.0.6 + USES_TERMINAL_DOWNLOAD TRUE) +FetchContent_MakeAvailable(zlib) + +# Fix Windows zlib dll names from "zlibd1.dll" to "zlib.dll": +if(WIN32) + set_target_properties(zlib PROPERTIES OUTPUT_NAME "zlib") + set_target_properties(zlib PROPERTIES DEBUG_POSTFIX "") + set_target_properties(zlib PROPERTIES SUFFIX ".dll") +endif() diff --git a/Src/external_dependencies/cpr/cpr-config.cmake b/Src/external_dependencies/cpr/cpr-config.cmake new file mode 100644 index 00000000..58ab4832 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr-config.cmake @@ -0,0 +1,26 @@ +# - C++ Requests, Curl for People +# This module is a libcurl wrapper written in modern C++. +# It provides an easy, intuitive, and efficient interface to +# a host of networking methods. +# +# Finding this module will define the following variables: +# CPR_FOUND - True if the core library has been found +# CPR_LIBRARIES - Path to the core library archive +# CPR_INCLUDE_DIRS - Path to the include directories. Gives access +# to cpr.h, which must be included in every +# file that uses this interface + +find_path(CPR_INCLUDE_DIR + NAMES cpr.h) + +find_library(CPR_LIBRARY + NAMES cpr + HINTS ${CPR_LIBRARY_ROOT}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CPR REQUIRED_VARS CPR_LIBRARY CPR_INCLUDE_DIR) + +if(CPR_FOUND) + set(CPR_LIBRARIES ${CPR_LIBRARY}) + set(CPR_INCLUDE_DIRS ${CPR_INCLUDE_DIR}) +endif() diff --git a/Src/external_dependencies/cpr/cpr/CMakeLists.txt b/Src/external_dependencies/cpr/cpr/CMakeLists.txt new file mode 100644 index 00000000..9d05ad4a --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.15)
+
+add_library(cpr
+ accept_encoding.cpp
+ async.cpp
+ auth.cpp
+ bearer.cpp
+ cert_info.cpp
+ cookies.cpp
+ cprtypes.cpp
+ curl_container.cpp
+ curlholder.cpp
+ error.cpp
+ file.cpp
+ multipart.cpp
+ parameters.cpp
+ payload.cpp
+ proxies.cpp
+ proxyauth.cpp
+ session.cpp
+ threadpool.cpp
+ timeout.cpp
+ unix_socket.cpp
+ util.cpp
+ response.cpp
+ redirect.cpp
+ interceptor.cpp
+ ssl_ctx.cpp
+ curlmultiholder.cpp
+ multiperform.cpp)
+
+add_library(cpr::cpr ALIAS cpr)
+
+target_link_libraries(cpr PUBLIC CURL::libcurl) # todo should be private, but first dependencys in ssl_options need to be removed
+
+# Fix missing OpenSSL includes for Windows since in 'ssl_ctx.cpp' we include OpenSSL directly
+if(SSL_BACKEND_USED STREQUAL "OpenSSL")
+ target_link_libraries(cpr PRIVATE OpenSSL::SSL)
+ target_include_directories(cpr PRIVATE ${OPENSSL_INCLUDE_DIR})
+endif()
+
+# Set version for shared libraries.
+set_target_properties(cpr
+ PROPERTIES
+ VERSION ${${PROJECT_NAME}_VERSION}
+ SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR})
+
+# Import GNU common install directory variables
+include(GNUInstallDirs)
+
+if(CPR_USE_SYSTEM_CURL)
+ install(TARGETS cpr
+ EXPORT cprTargets
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+ # Include CMake helpers for package config files
+ # Follow this installation guideline: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html
+ include(CMakePackageConfigHelpers)
+
+ write_basic_package_version_file(
+ "${PROJECT_BINARY_DIR}/cpr/cprConfigVersion.cmake"
+ VERSION ${${PROJECT_NAME}_VERSION}
+ COMPATIBILITY ExactVersion)
+
+ configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/cprConfig.cmake.in
+ "${PROJECT_BINARY_DIR}/cpr/cprConfig.cmake"
+ INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr)
+
+ install(EXPORT cprTargets
+ FILE cprTargets.cmake
+ NAMESPACE cpr::
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr)
+
+ install(FILES ${PROJECT_BINARY_DIR}/cpr/cprConfig.cmake
+ ${PROJECT_BINARY_DIR}/cpr/cprConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr)
+
+else()
+ install(TARGETS cpr
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif()
diff --git a/Src/external_dependencies/cpr/cpr/accept_encoding.cpp b/Src/external_dependencies/cpr/cpr/accept_encoding.cpp new file mode 100644 index 00000000..f6449d5f --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/accept_encoding.cpp @@ -0,0 +1,25 @@ +#include "cpr/accept_encoding.h" + +#include <algorithm> +#include <iterator> +#include <numeric> + +namespace cpr { + +AcceptEncoding::AcceptEncoding(const std::initializer_list<AcceptEncodingMethods>& methods) { + methods_.clear(); + std::transform(methods.begin(), methods.end(), std::back_inserter(methods_), [&](cpr::AcceptEncodingMethods method) { return cpr::AcceptEncodingMethodsStringMap.at(method); }); +} + +AcceptEncoding::AcceptEncoding(const std::initializer_list<std::string>& string_methods) : methods_{string_methods} {} + +bool AcceptEncoding::empty() const noexcept { + return methods_.empty(); +} + +const std::string AcceptEncoding::getString() const { + std::string methodsString = std::accumulate(std::next(methods_.begin()), methods_.end(), methods_[0], [](std::string a, std::string b) { return std::move(a) + ", " + std::move(b); }); + return methodsString; +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/async.cpp b/Src/external_dependencies/cpr/cpr/async.cpp new file mode 100644 index 00000000..e10d09e1 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/async.cpp @@ -0,0 +1,8 @@ +#include "cpr/async.h" + +namespace cpr { + +// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) +CPR_SINGLETON_IMPL(GlobalThreadPool) + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/auth.cpp b/Src/external_dependencies/cpr/cpr/auth.cpp new file mode 100644 index 00000000..b3576f5c --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/auth.cpp @@ -0,0 +1,16 @@ +#include "cpr/auth.h" +#include "cpr/util.h" + +namespace cpr { +Authentication::~Authentication() noexcept { + util::secureStringClear(auth_string_); +} + +const char* Authentication::GetAuthString() const noexcept { + return auth_string_.c_str(); +} + +AuthMode Authentication::GetAuthMode() const noexcept { + return auth_mode_; +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/bearer.cpp b/Src/external_dependencies/cpr/cpr/bearer.cpp new file mode 100644 index 00000000..02bd728b --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/bearer.cpp @@ -0,0 +1,16 @@ +#include "cpr/bearer.h" +#include "cpr/util.h" + +namespace cpr { +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 +Bearer::~Bearer() noexcept { + util::secureStringClear(token_string_); +} + +const char* Bearer::GetToken() const noexcept { + return token_string_.c_str(); +} +#endif +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/cert_info.cpp b/Src/external_dependencies/cpr/cpr/cert_info.cpp new file mode 100644 index 00000000..a77a0277 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/cert_info.cpp @@ -0,0 +1,43 @@ +#include "cpr/cert_info.h" + +namespace cpr { + +std::string& CertInfo::operator[](const size_t& pos) { + return cert_info_[pos]; +} + +CertInfo::iterator CertInfo::begin() { + return cert_info_.begin(); +} +CertInfo::iterator CertInfo::end() { + return cert_info_.end(); +} + +CertInfo::const_iterator CertInfo::begin() const { + return cert_info_.begin(); +} + +CertInfo::const_iterator CertInfo::end() const { + return cert_info_.end(); +} + +CertInfo::const_iterator CertInfo::cbegin() const { + return cert_info_.cbegin(); +} + +CertInfo::const_iterator CertInfo::cend() const { + return cert_info_.cend(); +} + +void CertInfo::emplace_back(const std::string& str) { + cert_info_.emplace_back(str); +} + +void CertInfo::push_back(const std::string& str) { + cert_info_.push_back(str); +} + +void CertInfo::pop_back() { + cert_info_.pop_back(); +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/cookies.cpp b/Src/external_dependencies/cpr/cpr/cookies.cpp new file mode 100644 index 00000000..9d351fd7 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/cookies.cpp @@ -0,0 +1,106 @@ +#include "cpr/cookies.h" +#include <ctime> +#include <iomanip> + +namespace cpr { +const std::string Cookie::GetDomain() const { + return domain_; +} + +bool Cookie::IsIncludingSubdomains() const { + return includeSubdomains_; +} + +const std::string Cookie::GetPath() const { + return path_; +} + +bool Cookie::IsHttpsOnly() const { + return httpsOnly_; +} + +const std::chrono::system_clock::time_point Cookie::GetExpires() const { + return expires_; +} + +const std::string Cookie::GetExpiresString() const { + std::stringstream ss; + std::tm tm{}; + std::time_t tt = std::chrono::system_clock::to_time_t(expires_); +#ifdef _WIN32 + gmtime_s(&tm, &tt); +#else + gmtime_r(&tt, &tm); +#endif + ss << std::put_time(&tm, "%a, %d %b %Y %H:%M:%S GMT"); + return ss.str(); +} + +const std::string Cookie::GetName() const { + return name_; +} + +const std::string Cookie::GetValue() const { + return value_; +} + +const std::string Cookies::GetEncoded(const CurlHolder& holder) const { + std::stringstream stream; + for (const cpr::Cookie& item : cookies_) { + // Depending on if encoding is set to "true", we will URL-encode cookies + stream << (encode ? holder.urlEncode(item.GetName()) : item.GetName()) << "="; + + // special case version 1 cookies, which can be distinguished by + // beginning and trailing quotes + if (!item.GetValue().empty() && item.GetValue().front() == '"' && item.GetValue().back() == '"') { + stream << item.GetValue(); + } else { + // Depending on if encoding is set to "true", we will URL-encode cookies + stream << (encode ? holder.urlEncode(item.GetValue()) : item.GetValue()); + } + stream << "; "; + } + return stream.str(); +} + +cpr::Cookie& Cookies::operator[](size_t pos) { + return cookies_[pos]; +} + +Cookies::iterator Cookies::begin() { + return cookies_.begin(); +} + +Cookies::iterator Cookies::end() { + return cookies_.end(); +} + +Cookies::const_iterator Cookies::begin() const { + return cookies_.begin(); +} + +Cookies::const_iterator Cookies::end() const { + return cookies_.end(); +} + +Cookies::const_iterator Cookies::cbegin() const { + return cookies_.cbegin(); +} + +Cookies::const_iterator Cookies::cend() const { + return cookies_.cend(); +} + +void Cookies::emplace_back(const Cookie& str) { + cookies_.emplace_back(str); +} + +void Cookies::push_back(const Cookie& str) { + cookies_.push_back(str); +} + +void Cookies::pop_back() { + cookies_.pop_back(); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/cprtypes.cpp b/Src/external_dependencies/cpr/cpr/cprtypes.cpp new file mode 100644 index 00000000..7927b03b --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/cprtypes.cpp @@ -0,0 +1,10 @@ +#include "cpr/cprtypes.h" + +#include <algorithm> +#include <cctype> + +namespace cpr { +bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string& b) const noexcept { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), [](unsigned char ac, unsigned char bc) { return std::tolower(ac) < std::tolower(bc); }); +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/curl_container.cpp b/Src/external_dependencies/cpr/cpr/curl_container.cpp new file mode 100644 index 00000000..81f7cfb5 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/curl_container.cpp @@ -0,0 +1,58 @@ +#include "cpr/curl_container.h" +#include <algorithm> +#include <iterator> + + +namespace cpr { +template <class T> +CurlContainer<T>::CurlContainer(const std::initializer_list<T>& containerList) : containerList_(containerList) {} + +template <class T> +void CurlContainer<T>::Add(const std::initializer_list<T>& containerList) { + std::transform(containerList.begin(), containerList.end(), std::back_inserter(containerList_), [](const T& elem) { return std::move(elem); }); +} + +template <class T> +void CurlContainer<T>::Add(const T& element) { + containerList_.push_back(std::move(element)); +} + +template <> +const std::string CurlContainer<Parameter>::GetContent(const CurlHolder& holder) const { + std::string content{}; + for (const Parameter& parameter : containerList_) { + if (!content.empty()) { + content += "&"; + } + + std::string escapedKey = encode ? holder.urlEncode(parameter.key) : parameter.key; + if (parameter.value.empty()) { + content += escapedKey; + } else { + std::string escapedValue = encode ? holder.urlEncode(parameter.value) : parameter.value; + content += escapedKey + "="; + content += escapedValue; + } + }; + + return content; +} + +template <> +const std::string CurlContainer<Pair>::GetContent(const CurlHolder& holder) const { + std::string content{}; + for (const cpr::Pair& element : containerList_) { + if (!content.empty()) { + content += "&"; + } + std::string escaped = encode ? holder.urlEncode(element.value) : element.value; + content += element.key + "=" + escaped; + } + + return content; +} + +template class CurlContainer<Pair>; +template class CurlContainer<Parameter>; + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/curlholder.cpp b/Src/external_dependencies/cpr/cpr/curlholder.cpp new file mode 100644 index 00000000..acbc838f --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/curlholder.cpp @@ -0,0 +1,49 @@ +#include "cpr/curlholder.h" +#include <cassert> + +namespace cpr { +CurlHolder::CurlHolder() { + /** + * Allow multithreaded access to CPR by locking curl_easy_init(). + * curl_easy_init() is not thread safe. + * References: + * https://curl.haxx.se/libcurl/c/curl_easy_init.html + * https://curl.haxx.se/libcurl/c/threadsafe.html + **/ + curl_easy_init_mutex_().lock(); + // NOLINTNEXTLINE (cppcoreguidelines-prefer-member-initializer) since we need it to happen inside the lock + handle = curl_easy_init(); + curl_easy_init_mutex_().unlock(); + + assert(handle); +} // namespace cpr + +CurlHolder::~CurlHolder() { + curl_slist_free_all(chunk); + curl_slist_free_all(resolveCurlList); + curl_formfree(formpost); + curl_easy_cleanup(handle); +} + +std::string CurlHolder::urlEncode(const std::string& s) const { + assert(handle); + char* output = curl_easy_escape(handle, s.c_str(), static_cast<int>(s.length())); + if (output) { + std::string result = output; + curl_free(output); + return result; + } + return ""; +} + +std::string CurlHolder::urlDecode(const std::string& s) const { + assert(handle); + char* output = curl_easy_unescape(handle, s.c_str(), static_cast<int>(s.length()), nullptr); + if (output) { + std::string result = output; + curl_free(output); + return result; + } + return ""; +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/curlmultiholder.cpp b/Src/external_dependencies/cpr/cpr/curlmultiholder.cpp new file mode 100644 index 00000000..76079370 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/curlmultiholder.cpp @@ -0,0 +1,15 @@ +#include "cpr/curlmultiholder.h" + +#include <cassert> + +namespace cpr { + +CurlMultiHolder::CurlMultiHolder() : handle{curl_multi_init()} { + assert(handle); +} + +CurlMultiHolder::~CurlMultiHolder() { + curl_multi_cleanup(handle); +} + +} // namespace cpr
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/cpr/error.cpp b/Src/external_dependencies/cpr/cpr/error.cpp new file mode 100644 index 00000000..f085051f --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/error.cpp @@ -0,0 +1,68 @@ +#include "cpr/error.h" + +#include <curl/curl.h> + +namespace cpr { +ErrorCode Error::getErrorCodeForCurlError(std::int32_t curl_code) { + switch (curl_code) { + case CURLE_OK: + return ErrorCode::OK; + case CURLE_UNSUPPORTED_PROTOCOL: + return ErrorCode::UNSUPPORTED_PROTOCOL; + case CURLE_URL_MALFORMAT: + return ErrorCode::INVALID_URL_FORMAT; + case CURLE_COULDNT_RESOLVE_PROXY: + return ErrorCode::PROXY_RESOLUTION_FAILURE; + case CURLE_COULDNT_RESOLVE_HOST: + return ErrorCode::HOST_RESOLUTION_FAILURE; + case CURLE_COULDNT_CONNECT: + return ErrorCode::CONNECTION_FAILURE; + case CURLE_OPERATION_TIMEDOUT: + return ErrorCode::OPERATION_TIMEDOUT; + case CURLE_SSL_CONNECT_ERROR: + return ErrorCode::SSL_CONNECT_ERROR; +#if LIBCURL_VERSION_NUM < 0x073e00 + case CURLE_PEER_FAILED_VERIFICATION: + return ErrorCode::SSL_REMOTE_CERTIFICATE_ERROR; +#endif + case CURLE_ABORTED_BY_CALLBACK: + case CURLE_WRITE_ERROR: + return ErrorCode::REQUEST_CANCELLED; + case CURLE_GOT_NOTHING: + return ErrorCode::EMPTY_RESPONSE; + case CURLE_SSL_ENGINE_NOTFOUND: + case CURLE_SSL_ENGINE_SETFAILED: + return ErrorCode::GENERIC_SSL_ERROR; + case CURLE_SEND_ERROR: + return ErrorCode::NETWORK_SEND_FAILURE; + case CURLE_RECV_ERROR: + return ErrorCode::NETWORK_RECEIVE_ERROR; + case CURLE_SSL_CERTPROBLEM: + return ErrorCode::SSL_LOCAL_CERTIFICATE_ERROR; + case CURLE_SSL_CIPHER: + return ErrorCode::GENERIC_SSL_ERROR; +#if LIBCURL_VERSION_NUM >= 0x073e00 + case CURLE_PEER_FAILED_VERIFICATION: + return ErrorCode::SSL_REMOTE_CERTIFICATE_ERROR; +#else + case CURLE_SSL_CACERT: + return ErrorCode::SSL_CACERT_ERROR; +#endif + case CURLE_USE_SSL_FAILED: + case CURLE_SSL_ENGINE_INITFAILED: + return ErrorCode::GENERIC_SSL_ERROR; + case CURLE_SSL_CACERT_BADFILE: + return ErrorCode::SSL_CACERT_ERROR; + case CURLE_SSL_SHUTDOWN_FAILED: + return ErrorCode::GENERIC_SSL_ERROR; + case CURLE_SSL_CRL_BADFILE: + case CURLE_SSL_ISSUER_ERROR: + return ErrorCode::SSL_CACERT_ERROR; + case CURLE_TOO_MANY_REDIRECTS: + return ErrorCode::TOO_MANY_REDIRECTS; + default: + return ErrorCode::INTERNAL_ERROR; + } +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/file.cpp b/Src/external_dependencies/cpr/cpr/file.cpp new file mode 100644 index 00000000..de64109e --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/file.cpp @@ -0,0 +1,40 @@ +#include "cpr/file.h" + +namespace cpr { + +Files::iterator Files::begin() { + return files.begin(); +} + +Files::iterator Files::end() { + return files.end(); +} + +Files::const_iterator Files::begin() const { + return files.begin(); +} + +Files::const_iterator Files::end() const { + return files.end(); +} + +Files::const_iterator Files::cbegin() const { + return files.cbegin(); +} + +Files::const_iterator Files::cend() const { + return files.cend(); +} + +void Files::emplace_back(const File& file) { + files.emplace_back(file); +} + +void Files::push_back(const File& file) { + files.push_back(file); +} + +void Files::pop_back() { + files.pop_back(); +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/interceptor.cpp b/Src/external_dependencies/cpr/cpr/interceptor.cpp new file mode 100644 index 00000000..91dd8425 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/interceptor.cpp @@ -0,0 +1,46 @@ +#include "cpr/interceptor.h" + +#include <exception> + +namespace cpr { + +Response Interceptor::proceed(Session& session) { + return session.proceed(); +} + +Response Interceptor::proceed(Session& session, ProceedHttpMethod httpMethod) { + switch (httpMethod) { + case ProceedHttpMethod::DELETE_REQUEST: + return session.Delete(); + case ProceedHttpMethod::GET_REQUEST: + return session.Get(); + case ProceedHttpMethod::HEAD_REQUEST: + return session.Head(); + case ProceedHttpMethod::OPTIONS_REQUEST: + return session.Options(); + case ProceedHttpMethod::PATCH_REQUEST: + return session.Patch(); + case ProceedHttpMethod::POST_REQUEST: + return session.Post(); + case ProceedHttpMethod::PUT_REQUEST: + return session.Put(); + default: + throw std::invalid_argument{"Can't procceed the session with the provided http method!"}; + } +} + +Response Interceptor::proceed(Session& session, ProceedHttpMethod httpMethod, std::ofstream& file) { + if (httpMethod == ProceedHttpMethod::DOWNLOAD_FILE_REQUEST) { + return session.Download(file); + } + throw std::invalid_argument{"std::ofstream argument is only valid for ProceedHttpMethod::DOWNLOAD_FILE!"}; +} + +Response Interceptor::proceed(Session& session, ProceedHttpMethod httpMethod, const WriteCallback& write) { + if (httpMethod == ProceedHttpMethod::DOWNLOAD_CALLBACK_REQUEST) { + return session.Download(write); + } + throw std::invalid_argument{"WriteCallback argument is only valid for ProceedHttpMethod::DOWNLOAD_CALLBACK!"}; +} + +} // namespace cpr
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/cpr/multipart.cpp b/Src/external_dependencies/cpr/cpr/multipart.cpp new file mode 100644 index 00000000..d82d9a41 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/multipart.cpp @@ -0,0 +1,5 @@ +#include "cpr/multipart.h" + +namespace cpr { +Multipart::Multipart(const std::initializer_list<Part>& p_parts) : parts{p_parts} {} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/multiperform.cpp b/Src/external_dependencies/cpr/cpr/multiperform.cpp new file mode 100644 index 00000000..393185c0 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/multiperform.cpp @@ -0,0 +1,273 @@ +#include "cpr/multiperform.h" + +#include <algorithm> +#include <iostream> + +namespace cpr { + +MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) {} + +MultiPerform::~MultiPerform() { + // Unock all sessions + for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) { + pair.first->isUsedInMultiPerform = false; + } +} + +void MultiPerform::AddSession(std::shared_ptr<Session>& session, HttpMethod method) { + // Check if this multiperform is download only + if (((method != HttpMethod::DOWNLOAD_REQUEST && is_download_multi_perform) && method != HttpMethod::UNDEFINED) || (method == HttpMethod::DOWNLOAD_REQUEST && !is_download_multi_perform && !sessions_.empty())) { + // Currently it is not possible to mix download and non-download methods, as download needs additional parameters + throw std::invalid_argument("Failed to add session: Cannot mix download and non-download methods!"); + } + + // Set download only if neccessary + if (method == HttpMethod::DOWNLOAD_REQUEST) { + is_download_multi_perform = true; + } + + // Add easy handle to multi handle + CURLMcode error_code = curl_multi_add_handle(multicurl_->handle, session->curl_->handle); + if (error_code) { + std::cerr << "curl_multi_add_handle() failed, code " << static_cast<int>(error_code) << std::endl; + return; + } + + // Lock session to the multihandle + session->isUsedInMultiPerform = true; + + // Add session to sessions_ + sessions_.emplace_back(session, method); +} + +void MultiPerform::RemoveSession(const std::shared_ptr<Session>& session) { + // Remove easy handle from multihandle + CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, session->curl_->handle); + if (error_code) { + std::cerr << "curl_multi_remove_handle() failed, code " << static_cast<int>(error_code) << std::endl; + return; + } + + // Unock session + session->isUsedInMultiPerform = false; + + // Remove session from sessions_ + auto it = std::find_if(sessions_.begin(), sessions_.end(), [&session](const std::pair<std::shared_ptr<Session>, HttpMethod>& pair) { return session->curl_->handle == pair.first->curl_->handle; }); + if (it == sessions_.end()) { + throw std::invalid_argument("Failed to find session!"); + } + sessions_.erase(it); + + // Reset download only if empty + if (sessions_.empty()) { + is_download_multi_perform = false; + } +} + +void MultiPerform::DoMultiPerform() { + // Do multi perform until every handle has finished + int still_running{0}; + do { + CURLMcode error_code = curl_multi_perform(multicurl_->handle, &still_running); + if (error_code) { + std::cerr << "curl_multi_perform() failed, code " << static_cast<int>(error_code) << std::endl; + break; + } + + if (still_running) { + const int timeout_ms{250}; + error_code = curl_multi_poll(multicurl_->handle, nullptr, 0, timeout_ms, nullptr); + if (error_code) { + std::cerr << "curl_multi_poll() failed, code " << static_cast<int>(error_code) << std::endl; + break; + } + } + } while (still_running); +} + +std::vector<Response> MultiPerform::ReadMultiInfo(std::function<Response(Session&, CURLcode)>&& complete_function) { + // Get infos and create Response objects + std::vector<Response> responses; + struct CURLMsg* info{nullptr}; + do { + int msgq = 0; + + // Read info from multihandle + info = curl_multi_info_read(multicurl_->handle, &msgq); + + if (info) { + // Find current session + auto it = std::find_if(sessions_.begin(), sessions_.end(), [&info](const std::pair<std::shared_ptr<Session>, HttpMethod>& pair) { return pair.first->curl_->handle == info->easy_handle; }); + if (it == sessions_.end()) { + std::cerr << "Failed to find current session!" << std::endl; + break; + } + std::shared_ptr<Session> current_session = (*it).first; + + // Add response object + // NOLINTNEXTLINE (cppcoreguidelines-pro-type-union-access) + responses.push_back(complete_function(*current_session, info->data.result)); + } + } while (info); + + // Sort response objects to match order of added sessions + std::vector<Response> sorted_responses; + for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) { + Session& current_session = *(pair.first); + auto it = std::find_if(responses.begin(), responses.end(), [¤t_session](const Response& response) { return current_session.curl_->handle == response.curl_->handle; }); + Response current_response = *it; + // Erase response from original vector to increase future search speed + responses.erase(it); + sorted_responses.push_back(current_response); + } + + return sorted_responses; +} + +std::vector<Response> MultiPerform::MakeRequest() { + DoMultiPerform(); + return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.Complete(curl_error); }); +} + +std::vector<Response> MultiPerform::MakeDownloadRequest() { + DoMultiPerform(); + return ReadMultiInfo([](Session& session, CURLcode curl_error) -> Response { return session.CompleteDownload(curl_error); }); +} + +void MultiPerform::PrepareSessions() { + for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) { + switch (pair.second) { + case HttpMethod::GET_REQUEST: + pair.first->PrepareGet(); + break; + case HttpMethod::POST_REQUEST: + pair.first->PreparePost(); + break; + case HttpMethod::PUT_REQUEST: + pair.first->PreparePut(); + break; + case HttpMethod::DELETE_REQUEST: + pair.first->PrepareDelete(); + break; + case HttpMethod::PATCH_REQUEST: + pair.first->PreparePatch(); + break; + case HttpMethod::HEAD_REQUEST: + pair.first->PrepareHead(); + break; + case HttpMethod::OPTIONS_REQUEST: + pair.first->PrepareOptions(); + break; + default: + std::cerr << "PrepareSessions failed: Undefined HttpMethod or download without arguments!" << std::endl; + return; + } + } +} + +void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCallback& write) { + std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index]; + switch (pair.second) { + case HttpMethod::DOWNLOAD_REQUEST: + pair.first->PrepareDownload(write); + break; + default: + std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl; + return; + } +} + +void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& file) { + std::pair<std::shared_ptr<Session>, HttpMethod>& pair = sessions_[sessions_index]; + switch (pair.second) { + case HttpMethod::DOWNLOAD_REQUEST: + pair.first->PrepareDownload(file); + break; + default: + std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << std::endl; + return; + } +} + +void MultiPerform::SetHttpMethod(HttpMethod method) { + for (std::pair<std::shared_ptr<Session>, HttpMethod>& pair : sessions_) { + pair.second = method; + } +} + +void MultiPerform::PrepareGet() { + SetHttpMethod(HttpMethod::GET_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PrepareDelete() { + SetHttpMethod(HttpMethod::DELETE_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PreparePut() { + SetHttpMethod(HttpMethod::PUT_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PreparePatch() { + SetHttpMethod(HttpMethod::PATCH_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PrepareHead() { + SetHttpMethod(HttpMethod::HEAD_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PrepareOptions() { + SetHttpMethod(HttpMethod::OPTIONS_REQUEST); + PrepareSessions(); +} + +void MultiPerform::PreparePost() { + SetHttpMethod(HttpMethod::POST_REQUEST); + PrepareSessions(); +} + +std::vector<Response> MultiPerform::Get() { + PrepareGet(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Delete() { + PrepareDelete(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Put() { + PreparePut(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Head() { + PrepareHead(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Options() { + PrepareOptions(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Patch() { + PreparePatch(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Post() { + PreparePost(); + return MakeRequest(); +} + +std::vector<Response> MultiPerform::Perform() { + PrepareSessions(); + return MakeRequest(); +} + +} // namespace cpr
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/cpr/parameters.cpp b/Src/external_dependencies/cpr/cpr/parameters.cpp new file mode 100644 index 00000000..a24c3936 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/parameters.cpp @@ -0,0 +1,10 @@ +#include "cpr/parameters.h" + +#include <initializer_list> +#include <string> + +#include "cpr/util.h" + +namespace cpr { +Parameters::Parameters(const std::initializer_list<Parameter>& parameters) : CurlContainer<Parameter>(parameters) {} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/payload.cpp b/Src/external_dependencies/cpr/cpr/payload.cpp new file mode 100644 index 00000000..78373fa3 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/payload.cpp @@ -0,0 +1,10 @@ +#include "cpr/payload.h" + +#include <initializer_list> +#include <string> + +#include "cpr/util.h" + +namespace cpr { +Payload::Payload(const std::initializer_list<Pair>& pairs) : CurlContainer<Pair>(pairs) {} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/proxies.cpp b/Src/external_dependencies/cpr/cpr/proxies.cpp new file mode 100644 index 00000000..0d3fe989 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/proxies.cpp @@ -0,0 +1,21 @@ +#include "cpr/proxies.h" + +#include <initializer_list> +#include <map> +#include <string> +#include <utility> + +namespace cpr { + +Proxies::Proxies(const std::initializer_list<std::pair<const std::string, std::string>>& hosts) : hosts_{hosts} {} +Proxies::Proxies(const std::map<std::string, std::string>& hosts) : hosts_{hosts} {} + +bool Proxies::has(const std::string& protocol) const { + return hosts_.count(protocol) > 0; +} + +const std::string& Proxies::operator[](const std::string& protocol) { + return hosts_[protocol]; +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/proxyauth.cpp b/Src/external_dependencies/cpr/cpr/proxyauth.cpp new file mode 100644 index 00000000..01e63033 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/proxyauth.cpp @@ -0,0 +1,21 @@ +#include "cpr/proxyauth.h" +#include "cpr/util.h" + +namespace cpr { +EncodedAuthentication::~EncodedAuthentication() noexcept { + util::secureStringClear(auth_string_); +} + +const char* EncodedAuthentication::GetAuthString() const noexcept { + return auth_string_.c_str(); +} + +bool ProxyAuthentication::has(const std::string& protocol) const { + return proxyAuth_.count(protocol) > 0; +} + +const char* ProxyAuthentication::operator[](const std::string& protocol) { + return proxyAuth_[protocol].GetAuthString(); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/redirect.cpp b/Src/external_dependencies/cpr/cpr/redirect.cpp new file mode 100644 index 00000000..fade1304 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/redirect.cpp @@ -0,0 +1,40 @@ +#include "cpr/redirect.h" + +namespace cpr { +PostRedirectFlags operator|(PostRedirectFlags lhs, PostRedirectFlags rhs) { + return static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)); +} + +PostRedirectFlags operator&(PostRedirectFlags lhs, PostRedirectFlags rhs) { + return static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)); +} + +PostRedirectFlags operator^(PostRedirectFlags lhs, PostRedirectFlags rhs) { + return static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) ^ static_cast<uint8_t>(rhs)); +} + +PostRedirectFlags operator~(PostRedirectFlags flag) { + return static_cast<PostRedirectFlags>(~static_cast<uint8_t>(flag)); +} + +PostRedirectFlags& operator|=(PostRedirectFlags& lhs, PostRedirectFlags rhs) { + lhs = static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs)); + uint8_t tmp = static_cast<uint8_t>(lhs); + lhs = static_cast<PostRedirectFlags>(tmp); + return lhs; +} + +PostRedirectFlags& operator&=(PostRedirectFlags& lhs, PostRedirectFlags rhs) { + lhs = static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs)); + return lhs; +} + +PostRedirectFlags& operator^=(PostRedirectFlags& lhs, PostRedirectFlags rhs) { + lhs = static_cast<PostRedirectFlags>(static_cast<uint8_t>(lhs) ^ static_cast<uint8_t>(rhs)); + return lhs; +} + +bool any(PostRedirectFlags flag) { + return flag != PostRedirectFlags::NONE; +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/response.cpp b/Src/external_dependencies/cpr/cpr/response.cpp new file mode 100644 index 00000000..effe23d9 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/response.cpp @@ -0,0 +1,44 @@ +#include "cpr/response.h" + +namespace cpr { + +Response::Response(std::shared_ptr<CurlHolder> curl, std::string&& p_text, std::string&& p_header_string, Cookies&& p_cookies = Cookies{}, Error&& p_error = Error{}) : curl_(std::move(curl)), text(std::move(p_text)), cookies(std::move(p_cookies)), error(std::move(p_error)), raw_header(std::move(p_header_string)) { + header = cpr::util::parseHeader(raw_header, &status_line, &reason); + assert(curl_); + assert(curl_->handle); + curl_easy_getinfo(curl_->handle, CURLINFO_RESPONSE_CODE, &status_code); + curl_easy_getinfo(curl_->handle, CURLINFO_TOTAL_TIME, &elapsed); + char* url_string{nullptr}; + curl_easy_getinfo(curl_->handle, CURLINFO_EFFECTIVE_URL, &url_string); + url = Url(url_string); +#if LIBCURL_VERSION_NUM >= 0x073700 + curl_easy_getinfo(curl_->handle, CURLINFO_SIZE_DOWNLOAD_T, &downloaded_bytes); + curl_easy_getinfo(curl_->handle, CURLINFO_SIZE_UPLOAD_T, &uploaded_bytes); +#else + double downloaded_bytes_double, uploaded_bytes_double; + curl_easy_getinfo(curl_->handle, CURLINFO_SIZE_DOWNLOAD, &downloaded_bytes_double); + curl_easy_getinfo(curl_->handle, CURLINFO_SIZE_UPLOAD, &uploaded_bytes_double); + downloaded_bytes = downloaded_bytes_double; + uploaded_bytes = uploaded_bytes_double; +#endif + curl_easy_getinfo(curl_->handle, CURLINFO_REDIRECT_COUNT, &redirect_count); +} + +std::vector<CertInfo> Response::GetCertInfos() { + assert(curl_); + assert(curl_->handle); + curl_certinfo* ci{nullptr}; + curl_easy_getinfo(curl_->handle, CURLINFO_CERTINFO, &ci); + + std::vector<CertInfo> cert_infos; + for (int i = 0; i < ci->num_of_certs; i++) { + CertInfo cert_info; + // NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) + for (curl_slist* slist = ci->certinfo[i]; slist; slist = slist->next) { + cert_info.emplace_back(std::string{slist->data}); + } + cert_infos.emplace_back(cert_info); + } + return cert_infos; +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/session.cpp b/Src/external_dependencies/cpr/cpr/session.cpp new file mode 100644 index 00000000..659cc710 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/session.cpp @@ -0,0 +1,964 @@ +#include "cpr/session.h" + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <fstream> +#include <functional> +#include <iostream> +#include <stdexcept> +#include <string> + +#include <curl/curl.h> + +#include "cpr/async.h" +#include "cpr/cprtypes.h" +#include "cpr/interceptor.h" +#include "cpr/util.h" + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#include "cpr/ssl_ctx.h" +#endif + + +namespace cpr { +// Ignored here since libcurl reqires a long: +// NOLINTNEXTLINE(google-runtime-int) +constexpr long ON = 1L; +// Ignored here since libcurl reqires a long: +// NOLINTNEXTLINE(google-runtime-int) +constexpr long OFF = 0L; + +CURLcode Session::DoEasyPerform() { + if (isUsedInMultiPerform) { + std::cerr << "curl_easy_perform cannot be executed if the CURL handle is used in a MultiPerform." << std::endl; + return CURLcode::CURLE_FAILED_INIT; + } + return curl_easy_perform(curl_->handle); +} + +void Session::SetHeaderInternal() { + curl_slist* chunk = nullptr; + for (const std::pair<const std::string, std::string>& item : header_) { + std::string header_string = item.first; + if (item.second.empty()) { + header_string += ";"; + } else { + header_string += ": " + item.second; + } + + curl_slist* temp = curl_slist_append(chunk, header_string.c_str()); + if (temp) { + chunk = temp; + } + } + + // Set the chunked transfer encoding in case it does not already exist: + if (chunkedTransferEncoding_ && header_.find("Transfer-Encoding") == header_.end()) { + curl_slist* temp = curl_slist_append(chunk, "Transfer-Encoding:chunked"); + if (temp) { + chunk = temp; + } + } + + // libcurl would prepare the header "Expect: 100-continue" by default when uploading files larger than 1 MB. + // Here we would like to disable this feature: + curl_slist* temp = curl_slist_append(chunk, "Expect:"); + if (temp) { + chunk = temp; + } + + curl_easy_setopt(curl_->handle, CURLOPT_HTTPHEADER, chunk); + + curl_slist_free_all(curl_->chunk); + curl_->chunk = chunk; +} + +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 +void Session::SetBearer(const Bearer& token) { + // Ignore here since this has been defined by libcurl. + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_BEARER); + curl_easy_setopt(curl_->handle, CURLOPT_XOAUTH2_BEARER, token.GetToken()); +} +#endif + +Session::Session() : curl_(new CurlHolder()) { + // Set up some sensible defaults + curl_version_info_data* version_info = curl_version_info(CURLVERSION_NOW); + std::string version = "curl/" + std::string{version_info->version}; + curl_easy_setopt(curl_->handle, CURLOPT_USERAGENT, version.c_str()); + SetRedirect(Redirect()); + curl_easy_setopt(curl_->handle, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl_->handle, CURLOPT_ERRORBUFFER, curl_->error.data()); + curl_easy_setopt(curl_->handle, CURLOPT_COOKIEFILE, ""); +#ifdef CPR_CURL_NOSIGNAL + curl_easy_setopt(curl_->handle, CURLOPT_NOSIGNAL, 1L); +#endif + +#if LIBCURL_VERSION_NUM >= 0x071900 + curl_easy_setopt(curl_->handle, CURLOPT_TCP_KEEPALIVE, 1L); +#endif +} + +Response Session::makeDownloadRequest() { + if (!interceptors_.empty()) { + std::shared_ptr<Interceptor> interceptor = interceptors_.front(); + interceptors_.pop(); + return interceptor->intercept(*this); + } + + CURLcode curl_error = DoEasyPerform(); + + return CompleteDownload(curl_error); +} + +void Session::prepareCommon() { + assert(curl_->handle); + + // Set Header: + SetHeaderInternal(); + + const std::string parametersContent = parameters_.GetContent(*curl_); + if (!parametersContent.empty()) { + Url new_url{url_ + "?" + parametersContent}; + curl_easy_setopt(curl_->handle, CURLOPT_URL, new_url.c_str()); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str()); + } + + // Proxy: + std::string protocol = url_.str().substr(0, url_.str().find(':')); + if (proxies_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str()); + if (proxyAuth_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]); + } + } + +#if LIBCURL_VERSION_MAJOR >= 7 +#if LIBCURL_VERSION_MINOR >= 21 + if (acceptEncoding_.empty()) { + /* enable all supported built-in compressions */ + curl_easy_setopt(curl_->handle, CURLOPT_ACCEPT_ENCODING, ""); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_ACCEPT_ENCODING, acceptEncoding_.getString().c_str()); + } +#endif +#endif + +#if LIBCURL_VERSION_MAJOR >= 7 +#if LIBCURL_VERSION_MINOR >= 71 + // Fix loading certs from Windows cert store when using OpenSSL: + curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); +#endif +#endif + + curl_->error[0] = '\0'; + + response_string_.clear(); + if (response_string_reserve_size_ > 0) { + response_string_.reserve(response_string_reserve_size_); + } + header_string_.clear(); + if (!this->writecb_.callback) { + curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeFunction); + curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &response_string_); + } + if (!this->headercb_.callback) { + curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::writeFunction); + curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &header_string_); + } + + // Enable so we are able to retrive certificate information: + curl_easy_setopt(curl_->handle, CURLOPT_CERTINFO, 1L); +} + +void Session::prepareCommonDownload() { + assert(curl_->handle); + + // Set Header: + SetHeaderInternal(); + + const std::string parametersContent = parameters_.GetContent(*curl_); + if (!parametersContent.empty()) { + Url new_url{url_ + "?" + parametersContent}; + curl_easy_setopt(curl_->handle, CURLOPT_URL, new_url.c_str()); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str()); + } + + std::string protocol = url_.str().substr(0, url_.str().find(':')); + if (proxies_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str()); + if (proxyAuth_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]); + } + } + + curl_->error[0] = '\0'; + + header_string_.clear(); + if (headercb_.callback) { + curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::headerUserFunction); + curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &headercb_); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::writeFunction); + curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &header_string_); + } +} + +Response Session::makeRequest() { + if (!interceptors_.empty()) { + // At least one interceptor exists -> Execute its intercept function + std::shared_ptr<Interceptor> interceptor = interceptors_.front(); + interceptors_.pop(); + return interceptor->intercept(*this); + } + + CURLcode curl_error = DoEasyPerform(); + return Complete(curl_error); +} + +void Session::SetLimitRate(const LimitRate& limit_rate) { + curl_easy_setopt(curl_->handle, CURLOPT_MAX_RECV_SPEED_LARGE, limit_rate.downrate); + curl_easy_setopt(curl_->handle, CURLOPT_MAX_SEND_SPEED_LARGE, limit_rate.uprate); +} + +void Session::SetReadCallback(const ReadCallback& read) { + readcb_ = read; + curl_easy_setopt(curl_->handle, CURLOPT_INFILESIZE_LARGE, read.size); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, read.size); + curl_easy_setopt(curl_->handle, CURLOPT_READFUNCTION, cpr::util::readUserFunction); + curl_easy_setopt(curl_->handle, CURLOPT_READDATA, &readcb_); + chunkedTransferEncoding_ = read.size == -1; +} + +void Session::SetHeaderCallback(const HeaderCallback& header) { + curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::headerUserFunction); + headercb_ = header; + curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &headercb_); +} + +void Session::SetWriteCallback(const WriteCallback& write) { + curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeUserFunction); + writecb_ = write; + curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &writecb_); +} + +void Session::SetProgressCallback(const ProgressCallback& progress) { + progresscb_ = progress; +#if LIBCURL_VERSION_NUM < 0x072000 + curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSFUNCTION, cpr::util::progressUserFunction); + curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSDATA, &progresscb_); +#else + curl_easy_setopt(curl_->handle, CURLOPT_XFERINFOFUNCTION, cpr::util::progressUserFunction); + curl_easy_setopt(curl_->handle, CURLOPT_XFERINFODATA, &progresscb_); +#endif + curl_easy_setopt(curl_->handle, CURLOPT_NOPROGRESS, 0L); +} + +void Session::SetDebugCallback(const DebugCallback& debug) { + curl_easy_setopt(curl_->handle, CURLOPT_DEBUGFUNCTION, cpr::util::debugUserFunction); + debugcb_ = debug; + curl_easy_setopt(curl_->handle, CURLOPT_DEBUGDATA, &debugcb_); + curl_easy_setopt(curl_->handle, CURLOPT_VERBOSE, 1L); +} + +void Session::SetUrl(const Url& url) { + url_ = url; +} + +void Session::SetResolve(const Resolve& resolve) { + SetResolves({resolve}); +} + +void Session::SetResolves(const std::vector<Resolve>& resolves) { + curl_slist_free_all(curl_->resolveCurlList); + curl_->resolveCurlList = nullptr; + for (const Resolve& resolve : resolves) { + for (const uint16_t port : resolve.ports) { + curl_->resolveCurlList = curl_slist_append(curl_->resolveCurlList, (resolve.host + ":" + std::to_string(port) + ":" + resolve.addr).c_str()); + } + } + curl_easy_setopt(curl_->handle, CURLOPT_RESOLVE, curl_->resolveCurlList); +} + +void Session::SetParameters(const Parameters& parameters) { + parameters_ = parameters; +} + +void Session::SetParameters(Parameters&& parameters) { + parameters_ = std::move(parameters); +} + +void Session::SetHeader(const Header& header) { + header_ = header; +} + +void Session::UpdateHeader(const Header& header) { + for (const std::pair<const std::string, std::string>& item : header) { + header_[item.first] = item.second; + } +} + +void Session::SetTimeout(const Timeout& timeout) { + curl_easy_setopt(curl_->handle, CURLOPT_TIMEOUT_MS, timeout.Milliseconds()); +} + +void Session::SetConnectTimeout(const ConnectTimeout& timeout) { + curl_easy_setopt(curl_->handle, CURLOPT_CONNECTTIMEOUT_MS, timeout.Milliseconds()); +} + +void Session::SetAuth(const Authentication& auth) { + // Ignore here since this has been defined by libcurl. + switch (auth.GetAuthMode()) { + case AuthMode::BASIC: + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); + break; + case AuthMode::DIGEST: + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); + break; + case AuthMode::NTLM: + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); + curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); + break; + } +} + +void Session::SetUserAgent(const UserAgent& ua) { + curl_easy_setopt(curl_->handle, CURLOPT_USERAGENT, ua.c_str()); +} + +void Session::SetPayload(const Payload& payload) { + hasBodyOrPayload_ = true; + const std::string content = payload.GetContent(*curl_); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length())); + curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str()); +} + +void Session::SetPayload(Payload&& payload) { + hasBodyOrPayload_ = true; + const std::string content = payload.GetContent(*curl_); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length())); + curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str()); +} + +void Session::SetProxies(const Proxies& proxies) { + proxies_ = proxies; +} + +void Session::SetProxies(Proxies&& proxies) { + proxies_ = std::move(proxies); +} + +void Session::SetProxyAuth(ProxyAuthentication&& proxy_auth) { + proxyAuth_ = std::move(proxy_auth); +} + +void Session::SetProxyAuth(const ProxyAuthentication& proxy_auth) { + proxyAuth_ = proxy_auth; +} + +void Session::SetMultipart(const Multipart& multipart) { + curl_httppost* formpost = nullptr; + curl_httppost* lastptr = nullptr; + + for (const Part& part : multipart.parts) { + std::vector<curl_forms> formdata; + if (!part.content_type.empty()) { + formdata.push_back({CURLFORM_CONTENTTYPE, part.content_type.c_str()}); + } + if (part.is_file) { + for (const File& file : part.files) { + formdata.push_back({CURLFORM_COPYNAME, part.name.c_str()}); + formdata.push_back({CURLFORM_FILE, file.filepath.c_str()}); + if (file.hasOverridedFilename()) { + formdata.push_back({CURLFORM_FILENAME, file.overrided_filename.c_str()}); + } + formdata.push_back({CURLFORM_END, nullptr}); + curl_formadd(&formpost, &lastptr, CURLFORM_ARRAY, formdata.data(), CURLFORM_END); + formdata.clear(); + } + } else if (part.is_buffer) { + // Do not use formdata, to prevent having to use reinterpreter_cast: + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, part.name.c_str(), CURLFORM_BUFFER, part.value.c_str(), CURLFORM_BUFFERPTR, part.data, CURLFORM_BUFFERLENGTH, part.datalen, CURLFORM_END); + } else { + formdata.push_back({CURLFORM_COPYNAME, part.name.c_str()}); + formdata.push_back({CURLFORM_COPYCONTENTS, part.value.c_str()}); + formdata.push_back({CURLFORM_END, nullptr}); + curl_formadd(&formpost, &lastptr, CURLFORM_ARRAY, formdata.data(), CURLFORM_END); + } + } + curl_easy_setopt(curl_->handle, CURLOPT_HTTPPOST, formpost); + hasBodyOrPayload_ = true; + + curl_formfree(curl_->formpost); + curl_->formpost = formpost; +} + +void Session::SetMultipart(Multipart&& multipart) { + curl_httppost* formpost = nullptr; + curl_httppost* lastptr = nullptr; + + for (const Part& part : multipart.parts) { + std::vector<curl_forms> formdata; + if (!part.content_type.empty()) { + formdata.push_back({CURLFORM_CONTENTTYPE, part.content_type.c_str()}); + } + if (part.is_file) { + for (const File& file : part.files) { + formdata.push_back({CURLFORM_COPYNAME, part.name.c_str()}); + formdata.push_back({CURLFORM_FILE, file.filepath.c_str()}); + if (file.hasOverridedFilename()) { + formdata.push_back({CURLFORM_FILENAME, file.overrided_filename.c_str()}); + } + formdata.push_back({CURLFORM_END, nullptr}); + curl_formadd(&formpost, &lastptr, CURLFORM_ARRAY, formdata.data(), CURLFORM_END); + formdata.clear(); + } + } else if (part.is_buffer) { + // Do not use formdata, to prevent having to use reinterpreter_cast: + curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, part.name.c_str(), CURLFORM_BUFFER, part.value.c_str(), CURLFORM_BUFFERPTR, part.data, CURLFORM_BUFFERLENGTH, part.datalen, CURLFORM_END); + } else { + formdata.push_back({CURLFORM_COPYNAME, part.name.c_str()}); + formdata.push_back({CURLFORM_COPYCONTENTS, part.value.c_str()}); + formdata.push_back({CURLFORM_END, nullptr}); + curl_formadd(&formpost, &lastptr, CURLFORM_ARRAY, formdata.data(), CURLFORM_END); + } + } + curl_easy_setopt(curl_->handle, CURLOPT_HTTPPOST, formpost); + hasBodyOrPayload_ = true; + + curl_formfree(curl_->formpost); + curl_->formpost = formpost; +} + +void Session::SetRedirect(const Redirect& redirect) { + curl_easy_setopt(curl_->handle, CURLOPT_FOLLOWLOCATION, redirect.follow ? 1L : 0L); + curl_easy_setopt(curl_->handle, CURLOPT_MAXREDIRS, redirect.maximum); + curl_easy_setopt(curl_->handle, CURLOPT_UNRESTRICTED_AUTH, redirect.cont_send_cred ? 1L : 0L); + + // NOLINTNEXTLINE (google-runtime-int) + long mask = 0; + if (any(redirect.post_flags & PostRedirectFlags::POST_301)) { + mask |= CURL_REDIR_POST_301; + } + if (any(redirect.post_flags & PostRedirectFlags::POST_302)) { + mask |= CURL_REDIR_POST_302; + } + if (any(redirect.post_flags & PostRedirectFlags::POST_303)) { + mask |= CURL_REDIR_POST_303; + } + curl_easy_setopt(curl_->handle, CURLOPT_POSTREDIR, mask); +} + +void Session::SetCookies(const Cookies& cookies) { + curl_easy_setopt(curl_->handle, CURLOPT_COOKIELIST, "ALL"); + curl_easy_setopt(curl_->handle, CURLOPT_COOKIE, cookies.GetEncoded(*curl_).c_str()); +} + +void Session::SetBody(const Body& body) { + hasBodyOrPayload_ = true; + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length())); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, body.c_str()); +} + +void Session::SetBody(Body&& body) { + hasBodyOrPayload_ = true; + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length())); + curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body.c_str()); +} + +void Session::SetLowSpeed(const LowSpeed& low_speed) { + curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_LIMIT, low_speed.limit); + curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_TIME, low_speed.time); +} + +void Session::SetVerifySsl(const VerifySsl& verify) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYPEER, verify ? ON : OFF); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYHOST, verify ? 2L : 0L); +} + +void Session::SetUnixSocket(const UnixSocket& unix_socket) { + curl_easy_setopt(curl_->handle, CURLOPT_UNIX_SOCKET_PATH, unix_socket.GetUnixSocketString()); +} + +void Session::SetSslOptions(const SslOptions& options) { + if (!options.cert_file.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERT, options.cert_file.c_str()); + if (!options.cert_type.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str()); + } + } + if (!options.key_file.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY, options.key_file.c_str()); + if (!options.key_type.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLKEYTYPE, options.key_type.c_str()); + } + if (!options.key_pass.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_KEYPASSWD, options.key_pass.c_str()); + } +#if SUPPORT_CURLOPT_SSLKEY_BLOB + } else if (!options.key_blob.empty()) { + std::string key_blob(options.key_blob); + curl_blob blob{}; + // NOLINTNEXTLINE (readability-container-data-pointer) + blob.data = &key_blob[0]; + blob.len = key_blob.length(); + curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY_BLOB, &blob); + if (!options.key_type.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLKEYTYPE, options.key_type.c_str()); + } + if (!options.key_pass.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_KEYPASSWD, options.key_pass.c_str()); + } +#endif + } + if (!options.pinned_public_key.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_PINNEDPUBLICKEY, options.pinned_public_key.c_str()); + } +#if SUPPORT_ALPN + curl_easy_setopt(curl_->handle, CURLOPT_SSL_ENABLE_ALPN, options.enable_alpn ? ON : OFF); +#endif +#if SUPPORT_NPN + curl_easy_setopt(curl_->handle, CURLOPT_SSL_ENABLE_NPN, options.enable_npn ? ON : OFF); +#endif + curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYPEER, options.verify_peer ? ON : OFF); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYHOST, options.verify_host ? 2L : 0L); +#if LIBCURL_VERSION_NUM >= 0x072900 + curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYSTATUS, options.verify_status ? ON : OFF); +#endif + + int maxTlsVersion = options.ssl_version; +#if SUPPORT_MAX_TLS_VERSION + maxTlsVersion |= options.max_version; +#endif + + curl_easy_setopt(curl_->handle, CURLOPT_SSLVERSION, + // Ignore here since this has been defined by libcurl. + maxTlsVersion); +#if SUPPORT_SSL_NO_REVOKE + if (options.ssl_no_revoke) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); + } +#endif + if (!options.ca_info.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_CAINFO, options.ca_info.c_str()); + } + if (!options.ca_path.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_CAPATH, options.ca_path.c_str()); + } +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#ifdef OPENSSL_BACKEND_USED + if (!options.ca_buffer.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function_load_ca_cert_from_buffer); + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, options.ca_buffer.c_str()); + } +#endif +#endif + if (!options.crl_file.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_CRLFILE, options.crl_file.c_str()); + } + if (!options.ciphers.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSL_CIPHER_LIST, options.ciphers.c_str()); + } +#if SUPPORT_TLSv13_CIPHERS + if (!options.tls13_ciphers.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_TLS13_CIPHERS, options.ciphers.c_str()); + } +#endif +#if SUPPORT_SESSIONID_CACHE + curl_easy_setopt(curl_->handle, CURLOPT_SSL_SESSIONID_CACHE, options.session_id_cache ? ON : OFF); +#endif +} + +void Session::SetVerbose(const Verbose& verbose) { + curl_easy_setopt(curl_->handle, CURLOPT_VERBOSE, verbose.verbose ? ON : OFF); +} + +void Session::SetInterface(const Interface& iface) { + if (iface.str().empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_INTERFACE, nullptr); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_INTERFACE, iface.c_str()); + } +} + +void Session::SetLocalPort(const LocalPort& local_port) { + curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORT, local_port); +} + +void Session::SetLocalPortRange(const LocalPortRange& local_port_range) { + curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORTRANGE, local_port_range); +} + +void Session::SetHttpVersion(const HttpVersion& version) { + switch (version.code) { + case HttpVersionCode::VERSION_NONE: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE); + break; + + case HttpVersionCode::VERSION_1_0: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + break; + + case HttpVersionCode::VERSION_1_1: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + break; + +#if LIBCURL_VERSION_NUM >= 0x072100 // 7.33.0 + case HttpVersionCode::VERSION_2_0: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + break; +#endif + +#if LIBCURL_VERSION_NUM >= 0x072F00 // 7.47.0 + case HttpVersionCode::VERSION_2_0_TLS: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); + break; +#endif + +#if LIBCURL_VERSION_NUM >= 0x073100 // 7.49.0 + case HttpVersionCode::VERSION_2_0_PRIOR_KNOWLEDGE: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE); + break; +#endif + +#if LIBCURL_VERSION_NUM >= 0x074200 // 7.66.0 + case HttpVersionCode::VERSION_3_0: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3); + break; +#endif + + default: // Should not happen + throw std::invalid_argument("Invalid/Unknown HTTP version type."); + break; + } +} + +void Session::SetRange(const Range& range) { + std::string range_str = range.str(); + curl_easy_setopt(curl_->handle, CURLOPT_RANGE, range_str.c_str()); +} + +void Session::SetMultiRange(const MultiRange& multi_range) { + std::string multi_range_str = multi_range.str(); + curl_easy_setopt(curl_->handle, CURLOPT_RANGE, multi_range_str.c_str()); +} + +void Session::SetReserveSize(const ReserveSize& reserve_size) { + ResponseStringReserve(reserve_size.size); +} + +void Session::SetAcceptEncoding(const AcceptEncoding& accept_encoding) { + acceptEncoding_ = accept_encoding; +} + +void Session::SetAcceptEncoding(AcceptEncoding&& accept_encoding) { + acceptEncoding_ = std::move(accept_encoding); +} + +cpr_off_t Session::GetDownloadFileLength() { + cpr_off_t downloadFileLenth = -1; + curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str()); + + std::string protocol = url_.str().substr(0, url_.str().find(':')); + if (proxies_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str()); + if (proxyAuth_.has(protocol)) { + curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]); + } + } + + curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 1); + if (DoEasyPerform() == CURLE_OK) { + curl_easy_getinfo(curl_->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &downloadFileLenth); + } + return downloadFileLenth; +} + +void Session::ResponseStringReserve(size_t size) { + response_string_reserve_size_ = size; +} + +Response Session::Delete() { + PrepareDelete(); + return makeRequest(); +} + +Response Session::Download(const WriteCallback& write) { + PrepareDownload(write); + return makeDownloadRequest(); +} + +Response Session::Download(std::ofstream& file) { + PrepareDownload(file); + return makeDownloadRequest(); +} + +Response Session::Get() { + PrepareGet(); + return makeRequest(); +} + +Response Session::Head() { + PrepareHead(); + return makeRequest(); +} + +Response Session::Options() { + PrepareOptions(); + return makeRequest(); +} + +Response Session::Patch() { + PreparePatch(); + return makeRequest(); +} + +Response Session::Post() { + PreparePost(); + return makeRequest(); +} + +Response Session::Put() { + PreparePut(); + return makeRequest(); +} + +std::shared_ptr<Session> Session::GetSharedPtrFromThis() { + try { + return shared_from_this(); + } catch (std::bad_weak_ptr&) { + throw std::runtime_error("Failed to get a shared pointer from this. The reason is probably that the session object is not managed by a shared pointer, which is required to use this functionality."); + } +} + +AsyncResponse Session::GetAsync() { + auto shared_this = shared_from_this(); + return async([shared_this]() { return shared_this->Get(); }); +} + +AsyncResponse Session::DeleteAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Delete(); }); +} + +AsyncResponse Session::DownloadAsync(const WriteCallback& write) { + return async([shared_this = GetSharedPtrFromThis(), write]() { return shared_this->Download(write); }); +} + +AsyncResponse Session::DownloadAsync(std::ofstream& file) { + return async([shared_this = GetSharedPtrFromThis(), &file]() { return shared_this->Download(file); }); +} + +AsyncResponse Session::HeadAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Head(); }); +} + +AsyncResponse Session::OptionsAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Options(); }); +} + +AsyncResponse Session::PatchAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Patch(); }); +} + +AsyncResponse Session::PostAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Post(); }); +} + +AsyncResponse Session::PutAsync() { + return async([shared_this = GetSharedPtrFromThis()]() { return shared_this->Put(); }); +} + +std::shared_ptr<CurlHolder> Session::GetCurlHolder() { + return curl_; +} + +std::string Session::GetFullRequestUrl() { + const std::string parametersContent = parameters_.GetContent(*curl_); + return url_.str() + (parametersContent.empty() ? "" : "?") + parametersContent; +} + +void Session::PrepareDelete() { + curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "DELETE"); + prepareCommon(); +} + +void Session::PrepareGet() { + // In case there is a body or payload for this request, we create a custom GET-Request since a + // GET-Request with body is based on the HTTP RFC **not** a leagal request. + if (hasBodyOrPayload_) { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "GET"); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr); + curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1L); + } + prepareCommon(); +} + +void Session::PrepareHead() { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 1L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr); + prepareCommon(); +} + +void Session::PrepareOptions() { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + prepareCommon(); +} + +void Session::PreparePatch() { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "PATCH"); + prepareCommon(); +} + +void Session::PreparePost() { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + + // In case there is no body or payload set it to an empty post: + if (hasBodyOrPayload_) { + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr); + } else { + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, readcb_.callback ? nullptr : ""); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "POST"); + } + prepareCommon(); +} + +void Session::PreparePut() { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + if (!hasBodyOrPayload_ && readcb_.callback) { + /** + * Yes, this one has to be CURLOPT_POSTFIELDS even if we are performing a PUT request. + * In case we don't set this one, performing a POST-request with PUT won't work. + * It in theory this only enforces the usage of the readcallback for POST requests, but works here as well. + **/ + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, nullptr); + } + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_easy_setopt(curl_->handle, CURLOPT_RANGE, nullptr); + prepareCommon(); +} + +void Session::PrepareDownload(std::ofstream& file) { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeFileFunction); + curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &file); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr); + + prepareCommonDownload(); +} + +void Session::PrepareDownload(const WriteCallback& write) { + curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L); + curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1); + curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr); + + SetWriteCallback(write); + + prepareCommonDownload(); +} + +Response Session::Complete(CURLcode curl_error) { + curl_slist* raw_cookies{nullptr}; + curl_easy_getinfo(curl_->handle, CURLINFO_COOKIELIST, &raw_cookies); + Cookies cookies = util::parseCookies(raw_cookies); + curl_slist_free_all(raw_cookies); + + // Reset the has no body property: + hasBodyOrPayload_ = false; + + std::string errorMsg = curl_->error.data(); + return Response(curl_, std::move(response_string_), std::move(header_string_), std::move(cookies), Error(curl_error, std::move(errorMsg))); +} + +Response Session::CompleteDownload(CURLcode curl_error) { + if (!headercb_.callback) { + curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, nullptr); + curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, 0); + } + + curl_slist* raw_cookies{nullptr}; + curl_easy_getinfo(curl_->handle, CURLINFO_COOKIELIST, &raw_cookies); + Cookies cookies = util::parseCookies(raw_cookies); + curl_slist_free_all(raw_cookies); + std::string errorMsg = curl_->error.data(); + + return Response(curl_, "", std::move(header_string_), std::move(cookies), Error(curl_error, std::move(errorMsg))); +} + +void Session::AddInterceptor(const std::shared_ptr<Interceptor>& pinterceptor) { + interceptors_.push(pinterceptor); +} + +Response Session::proceed() { + prepareCommon(); + return makeRequest(); +} + +// clang-format off +void Session::SetOption(const Resolve& resolve) { SetResolve(resolve); } +void Session::SetOption(const std::vector<Resolve>& resolves) { SetResolves(resolves); } +void Session::SetOption(const ReadCallback& read) { SetReadCallback(read); } +void Session::SetOption(const HeaderCallback& header) { SetHeaderCallback(header); } +void Session::SetOption(const WriteCallback& write) { SetWriteCallback(write); } +void Session::SetOption(const ProgressCallback& progress) { SetProgressCallback(progress); } +void Session::SetOption(const DebugCallback& debug) { SetDebugCallback(debug); } +void Session::SetOption(const Url& url) { SetUrl(url); } +void Session::SetOption(const Parameters& parameters) { SetParameters(parameters); } +void Session::SetOption(Parameters&& parameters) { SetParameters(std::move(parameters)); } +void Session::SetOption(const Header& header) { SetHeader(header); } +void Session::SetOption(const Timeout& timeout) { SetTimeout(timeout); } +void Session::SetOption(const ConnectTimeout& timeout) { SetConnectTimeout(timeout); } +void Session::SetOption(const Authentication& auth) { SetAuth(auth); } +void Session::SetOption(const LimitRate& limit_rate) { SetLimitRate(limit_rate); } +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 +void Session::SetOption(const Bearer& auth) { SetBearer(auth); } +#endif +void Session::SetOption(const UserAgent& ua) { SetUserAgent(ua); } +void Session::SetOption(const Payload& payload) { SetPayload(payload); } +void Session::SetOption(Payload&& payload) { SetPayload(std::move(payload)); } +void Session::SetOption(const Proxies& proxies) { SetProxies(proxies); } +void Session::SetOption(Proxies&& proxies) { SetProxies(std::move(proxies)); } +void Session::SetOption(ProxyAuthentication&& proxy_auth) { SetProxyAuth(std::move(proxy_auth)); } +void Session::SetOption(const ProxyAuthentication& proxy_auth) { SetProxyAuth(proxy_auth); } +void Session::SetOption(const Multipart& multipart) { SetMultipart(multipart); } +void Session::SetOption(Multipart&& multipart) { SetMultipart(std::move(multipart)); } +void Session::SetOption(const Redirect& redirect) { SetRedirect(redirect); } +void Session::SetOption(const Cookies& cookies) { SetCookies(cookies); } +void Session::SetOption(const Body& body) { SetBody(body); } +void Session::SetOption(Body&& body) { SetBody(std::move(body)); } +void Session::SetOption(const LowSpeed& low_speed) { SetLowSpeed(low_speed); } +void Session::SetOption(const VerifySsl& verify) { SetVerifySsl(verify); } +void Session::SetOption(const Verbose& verbose) { SetVerbose(verbose); } +void Session::SetOption(const UnixSocket& unix_socket) { SetUnixSocket(unix_socket); } +void Session::SetOption(const SslOptions& options) { SetSslOptions(options); } +void Session::SetOption(const Interface& iface) { SetInterface(iface); } +void Session::SetOption(const LocalPort& local_port) { SetLocalPort(local_port); } +void Session::SetOption(const LocalPortRange& local_port_range) { SetLocalPortRange(local_port_range); } +void Session::SetOption(const HttpVersion& version) { SetHttpVersion(version); } +void Session::SetOption(const Range& range) { SetRange(range); } +void Session::SetOption(const MultiRange& multi_range) { SetMultiRange(multi_range); } +void Session::SetOption(const ReserveSize& reserve_size) { SetReserveSize(reserve_size.size); } +void Session::SetOption(const AcceptEncoding& accept_encoding) { SetAcceptEncoding(accept_encoding); } +void Session::SetOption(AcceptEncoding&& accept_encoding) { SetAcceptEncoding(accept_encoding); } +// clang-format on +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/ssl_ctx.cpp b/Src/external_dependencies/cpr/cpr/ssl_ctx.cpp new file mode 100644 index 00000000..f3ad9e72 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/ssl_ctx.cpp @@ -0,0 +1,70 @@ + +#include "cpr/ssl_ctx.h" + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + +#ifdef OPENSSL_BACKEND_USED + +#include <openssl/err.h> +#include <openssl/safestack.h> +#include <openssl/ssl.h> + +namespace cpr { + +/** + * The ssl_ctx parameter is actually a pointer to the SSL library's SSL_CTX for OpenSSL. + * If an error is returned from the callback no attempt to establish a connection is made and + * the perform operation will return the callback's error code. + * + * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html + */ +CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* /*curl*/, void* sslctx, void* raw_cert_buf) { + // Check arguments + if (raw_cert_buf == nullptr || sslctx == nullptr) { + printf("Invalid callback arguments\n"); + return CURLE_ABORTED_BY_CALLBACK; + } + + // Setup pointer + X509_STORE* store = nullptr; + X509* cert = nullptr; + BIO* bio = nullptr; + char* cert_buf = static_cast<char*>(raw_cert_buf); + + // Create a memory BIO using the data of cert_buf. + // Note: It is assumed, that cert_buf is nul terminated and its length is determined by strlen. + bio = BIO_new_mem_buf(cert_buf, -1); + + // Load the PEM formatted certicifate into an X509 structure which OpenSSL can use. + PEM_read_bio_X509(bio, &cert, nullptr, nullptr); + if (cert == nullptr) { + printf("PEM_read_bio_X509 failed\n"); + return CURLE_ABORTED_BY_CALLBACK; + } + + // Get a pointer to the current certificate verification storage + store = SSL_CTX_get_cert_store(static_cast<SSL_CTX*>(sslctx)); + + // Add the loaded certificate to the verification storage + int status = X509_STORE_add_cert(store, cert); + if (status == 0) { + printf("Error adding certificate\n"); + return CURLE_ABORTED_BY_CALLBACK; + } + + // Decrement the reference count of the X509 structure cert and frees it up + X509_free(cert); + + // Free the entire bio chain + BIO_free(bio); + + // The CA certificate was loaded successfully into the verification storage + return CURLE_OK; +} + +} // namespace cpr + +#endif // OPENSSL_BACKEND_USED + +#endif // SUPPORT_CURLOPT_SSL_CTX_FUNCTION
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/cpr/threadpool.cpp b/Src/external_dependencies/cpr/cpr/threadpool.cpp new file mode 100644 index 00000000..638b0932 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/threadpool.cpp @@ -0,0 +1,148 @@ +#include "cpr/threadpool.h" + +namespace cpr { + +ThreadPool::ThreadPool(size_t min_threads, size_t max_threads, std::chrono::milliseconds max_idle_ms) : min_thread_num(min_threads), max_thread_num(max_threads), max_idle_time(max_idle_ms), status(STOP), cur_thread_num(0), idle_thread_num(0) {} + +ThreadPool::~ThreadPool() { + Stop(); +} + +int ThreadPool::Start(size_t start_threads) { + if (status != STOP) { + return -1; + } + status = RUNNING; + if (start_threads < min_thread_num) { + start_threads = min_thread_num; + } + if (start_threads > max_thread_num) { + start_threads = max_thread_num; + } + for (size_t i = 0; i < start_threads; ++i) { + CreateThread(); + } + return 0; +} + +int ThreadPool::Stop() { + if (status == STOP) { + return -1; + } + status = STOP; + task_cond.notify_all(); + for (auto& i : threads) { + if (i.thread->joinable()) { + i.thread->join(); + } + } + threads.clear(); + cur_thread_num = 0; + idle_thread_num = 0; + return 0; +} + +int ThreadPool::Pause() { + if (status == RUNNING) { + status = PAUSE; + } + return 0; +} + +int ThreadPool::Resume() { + if (status == PAUSE) { + status = RUNNING; + } + return 0; +} + +int ThreadPool::Wait() { + while (true) { + if (status == STOP || (tasks.empty() && idle_thread_num == cur_thread_num)) { + break; + } + std::this_thread::yield(); + } + return 0; +} + +bool ThreadPool::CreateThread() { + if (cur_thread_num >= max_thread_num) { + return false; + } + std::thread* thread = new std::thread([this] { + bool initialRun = true; + while (status != STOP) { + while (status == PAUSE) { + std::this_thread::yield(); + } + + Task task; + { + std::unique_lock<std::mutex> locker(task_mutex); + task_cond.wait_for(locker, std::chrono::milliseconds(max_idle_time), [this]() { return status == STOP || !tasks.empty(); }); + if (status == STOP) { + return; + } + if (tasks.empty()) { + if (cur_thread_num > min_thread_num) { + DelThread(std::this_thread::get_id()); + return; + } + continue; + } + if (!initialRun) { + --idle_thread_num; + } + task = std::move(tasks.front()); + tasks.pop(); + } + if (task) { + task(); + ++idle_thread_num; + } else if (initialRun) { + ++idle_thread_num; + initialRun = false; + } + } + }); + AddThread(thread); + return true; +} + +void ThreadPool::AddThread(std::thread* thread) { + thread_mutex.lock(); + ++cur_thread_num; + ThreadData data; + data.thread = std::shared_ptr<std::thread>(thread); + data.id = thread->get_id(); + data.status = RUNNING; + data.start_time = time(nullptr); + data.stop_time = 0; + threads.emplace_back(data); + thread_mutex.unlock(); +} + +void ThreadPool::DelThread(std::thread::id id) { + time_t now = time(nullptr); + thread_mutex.lock(); + --cur_thread_num; + --idle_thread_num; + auto iter = threads.begin(); + while (iter != threads.end()) { + if (iter->status == STOP && now > iter->stop_time) { + if (iter->thread->joinable()) { + iter->thread->join(); + iter = threads.erase(iter); + continue; + } + } else if (iter->id == id) { + iter->status = STOP; + iter->stop_time = time(nullptr); + } + ++iter; + } + thread_mutex.unlock(); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/timeout.cpp b/Src/external_dependencies/cpr/cpr/timeout.cpp new file mode 100644 index 00000000..5bcd73b2 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/timeout.cpp @@ -0,0 +1,31 @@ +#include "cpr/timeout.h" + +#include <limits> +#include <stdexcept> +#include <string> +#include <type_traits> + +namespace cpr { + +// No way around since curl uses a long here. +// NOLINTNEXTLINE(google-runtime-int) +long Timeout::Milliseconds() const { + static_assert(std::is_same<std::chrono::milliseconds, decltype(ms)>::value, "Following casting expects milliseconds."); + + // No way around since curl uses a long here. + // NOLINTNEXTLINE(google-runtime-int) + if (ms.count() > static_cast<std::chrono::milliseconds::rep>(std::numeric_limits<long>::max())) { + throw std::overflow_error("cpr::Timeout: timeout value overflow: " + std::to_string(ms.count()) + " ms."); + } + // No way around since curl uses a long here. + // NOLINTNEXTLINE(google-runtime-int) + if (ms.count() < static_cast<std::chrono::milliseconds::rep>(std::numeric_limits<long>::min())) { + throw std::underflow_error("cpr::Timeout: timeout value underflow: " + std::to_string(ms.count()) + " ms."); + } + + // No way around since curl uses a long here. + // NOLINTNEXTLINE(google-runtime-int) + return static_cast<long>(ms.count()); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/unix_socket.cpp b/Src/external_dependencies/cpr/cpr/unix_socket.cpp new file mode 100644 index 00000000..3171dbc8 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/unix_socket.cpp @@ -0,0 +1,8 @@ + +#include "cpr/unix_socket.h" + +namespace cpr { +const char* UnixSocket::GetUnixSocketString() const noexcept { + return unix_socket_.data(); +} +} // namespace cpr diff --git a/Src/external_dependencies/cpr/cpr/util.cpp b/Src/external_dependencies/cpr/cpr/util.cpp new file mode 100644 index 00000000..81fd3a28 --- /dev/null +++ b/Src/external_dependencies/cpr/cpr/util.cpp @@ -0,0 +1,236 @@ +#include "cpr/util.h" + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <chrono> +#include <cstdint> +#include <fstream> +#include <iomanip> +#include <ios> +#include <sstream> +#include <string> +#include <vector> + +#if defined(_Win32) +#include <Windows.h> +#else +// https://en.cppreference.com/w/c/string/byte/memset +// NOLINTNEXTLINE(bugprone-reserved-identifier, cert-dcl37-c, cert-dcl51-cpp, cppcoreguidelines-macro-usage) +#define __STDC_WANT_LIB_EXT1__ 1 +#include <cstring> +#endif + +namespace cpr::util { + +enum class CurlHTTPCookieField : size_t { + Domain = 0, + IncludeSubdomains, + Path, + HttpsOnly, + Expires, + Name, + Value, +}; + +Cookies parseCookies(curl_slist* raw_cookies) { + const int CURL_HTTP_COOKIE_SIZE = static_cast<int>(CurlHTTPCookieField::Value) + 1; + Cookies cookies; + for (curl_slist* nc = raw_cookies; nc; nc = nc->next) { + std::vector<std::string> tokens = cpr::util::split(nc->data, '\t'); + while (tokens.size() < CURL_HTTP_COOKIE_SIZE) { + tokens.emplace_back(""); + } + std::time_t expires = static_cast<time_t>(std::stoul(tokens.at(static_cast<size_t>(CurlHTTPCookieField::Expires)))); + cookies.emplace_back(Cookie{ + tokens.at(static_cast<size_t>(CurlHTTPCookieField::Name)), + tokens.at(static_cast<size_t>(CurlHTTPCookieField::Value)), + tokens.at(static_cast<size_t>(CurlHTTPCookieField::Domain)), + isTrue(tokens.at(static_cast<size_t>(CurlHTTPCookieField::IncludeSubdomains))), + tokens.at(static_cast<size_t>(CurlHTTPCookieField::Path)), + isTrue(tokens.at(static_cast<size_t>(CurlHTTPCookieField::HttpsOnly))), + std::chrono::system_clock::from_time_t(expires), + }); + } + return cookies; +} + +Header parseHeader(const std::string& headers, std::string* status_line, std::string* reason) { + Header header; + std::vector<std::string> lines; + std::istringstream stream(headers); + { + std::string line; + while (std::getline(stream, line, '\n')) { + lines.push_back(line); + } + } + + for (std::string& line : lines) { + if (line.substr(0, 5) == "HTTP/") { + // set the status_line if it was given + if ((status_line != nullptr) || (reason != nullptr)) { + line.resize(std::min<size_t>(line.size(), line.find_last_not_of("\t\n\r ") + 1)); + if (status_line != nullptr) { + *status_line = line; + } + + // set the reason if it was given + if (reason != nullptr) { + size_t pos1 = line.find_first_of("\t "); + size_t pos2 = std::string::npos; + if (pos1 != std::string::npos) { + pos2 = line.find_first_of("\t ", pos1 + 1); + } + if (pos2 != std::string::npos) { + line.erase(0, pos2 + 1); + *reason = line; + } + } + } + header.clear(); + } + + if (line.length() > 0) { + size_t found = line.find(':'); + if (found != std::string::npos) { + std::string value = line.substr(found + 1); + value.erase(0, value.find_first_not_of("\t ")); + value.resize(std::min<size_t>(value.size(), value.find_last_not_of("\t\n\r ") + 1)); + header[line.substr(0, found)] = value; + } + } + } + + return header; +} + +std::vector<std::string> split(const std::string& to_split, char delimiter) { + std::vector<std::string> tokens; + + std::stringstream stream(to_split); + std::string item; + while (std::getline(stream, item, delimiter)) { + tokens.push_back(item); + } + + return tokens; +} + +size_t readUserFunction(char* ptr, size_t size, size_t nitems, const ReadCallback* read) { + size *= nitems; + return (*read)(ptr, size) ? size : CURL_READFUNC_ABORT; +} + +size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCallback* header) { + size *= nmemb; + return (*header)({ptr, size}) ? size : 0; +} + +size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data) { + size *= nmemb; + data->append(ptr, size); + return size; +} + +size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file) { + size *= nmemb; + file->write(ptr, static_cast<std::streamsize>(size)); + return size; +} + +size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write) { + size *= nmemb; + return (*write)({ptr, size}) ? size : 0; +} + +#if LIBCURL_VERSION_NUM < 0x072000 +int progressUserFunction(const ProgressCallback* progress, double dltotal, double dlnow, double ultotal, double ulnow) { +#else +int progressUserFunction(const ProgressCallback* progress, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { +#endif + return (*progress)(dltotal, dlnow, ultotal, ulnow) ? 0 : 1; +} // namespace cpr::util + +int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t size, const DebugCallback* debug) { + (*debug)(static_cast<DebugCallback::InfoType>(type), std::string(data, size)); + return 0; +} + +/** + * Creates a temporary CurlHolder object and uses it to escape the given string. + * If you plan to use this methode on a regular basis think about creating a CurlHolder + * object and calling urlEncode(std::string) on it. + * + * Example: + * CurlHolder holder; + * std::string input = "Hello World!"; + * std::string result = holder.urlEncode(input); + **/ +std::string urlEncode(const std::string& s) { + CurlHolder holder; // Create a temporary new holder for URL encoding + return holder.urlEncode(s); +} + +/** + * Creates a temporary CurlHolder object and uses it to unescape the given string. + * If you plan to use this methode on a regular basis think about creating a CurlHolder + * object and calling urlDecode(std::string) on it. + * + * Example: + * CurlHolder holder; + * std::string input = "Hello%20World%21"; + * std::string result = holder.urlDecode(input); + **/ +std::string urlDecode(const std::string& s) { + CurlHolder holder; // Create a temporary new holder for URL decoding + return holder.urlDecode(s); +} + +#if defined(__STDC_LIB_EXT1__) +void secureStringClear(std::string& s) { + if (s.empty()) { + return; + } + memset_s(&s.front(), s.length(), 0, s.length()); + s.clear(); +} +#elif defined(_WIN32) +void secureStringClear(std::string& s) { + if (s.empty()) { + return; + } + SecureZeroMemory(&s.front(), s.length()); + s.clear(); +} +#else +#if defined(__clang__) +#pragma clang optimize off // clang +#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__) +#pragma GCC push_options // g++ +#pragma GCC optimize("O0") // g++ +#endif +void secureStringClear(std::string& s) { + if (s.empty()) { + return; + } + // NOLINTNEXTLINE (readability-container-data-pointer) + char* ptr = &(s[0]); + memset(ptr, '\0', s.length()); + s.clear(); +} + +#if defined(__clang__) +#pragma clang optimize on // clang +#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__) +#pragma GCC pop_options // g++ +#endif +#endif + +bool isTrue(const std::string& s) { + std::string temp_string{s}; + std::transform(temp_string.begin(), temp_string.end(), temp_string.begin(), [](unsigned char c) { return std::tolower(c); }); + return temp_string == "true"; +} + +} // namespace cpr::util diff --git a/Src/external_dependencies/cpr/include/CMakeLists.txt b/Src/external_dependencies/cpr/include/CMakeLists.txt new file mode 100644 index 00000000..fbc657a2 --- /dev/null +++ b/Src/external_dependencies/cpr/include/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.15)
+
+target_include_directories(cpr PUBLIC
+ $<INSTALL_INTERFACE:include>
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+ $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/cpr_generated_includes/>)
+
+target_sources(cpr PRIVATE
+ # Header files (useful in IDEs)
+ cpr/accept_encoding.h
+ cpr/api.h
+ cpr/async.h
+ cpr/auth.h
+ cpr/bearer.h
+ cpr/body.h
+ cpr/buffer.h
+ cpr/cert_info.h
+ cpr/cookies.h
+ cpr/cpr.h
+ cpr/cprtypes.h
+ cpr/curlholder.h
+ cpr/curlholder.h
+ cpr/error.h
+ cpr/file.h
+ cpr/limit_rate.h
+ cpr/local_port.h
+ cpr/local_port_range.h
+ cpr/multipart.h
+ cpr/parameters.h
+ cpr/payload.h
+ cpr/proxies.h
+ cpr/proxyauth.h
+ cpr/response.h
+ cpr/session.h
+ cpr/singleton.h
+ cpr/ssl_ctx.h
+ cpr/ssl_options.h
+ cpr/threadpool.h
+ cpr/timeout.h
+ cpr/unix_socket.h
+ cpr/util.h
+ cpr/verbose.h
+ cpr/interface.h
+ cpr/redirect.h
+ cpr/http_version.h
+ cpr/interceptor.h
+ cpr/filesystem.h
+ cpr/curlmultiholder.h
+ cpr/multiperform.h
+ cpr/resolve.h
+ ${PROJECT_BINARY_DIR}/cpr_generated_includes/cpr/cprver.h
+)
+
+# Filesystem
+if(CPR_USE_BOOST_FILESYSTEM)
+ find_package(Boost 1.44 REQUIRED COMPONENTS filesystem)
+ if(Boost_FOUND)
+ target_link_libraries(cpr PUBLIC Boost::filesystem)
+ endif()
+endif()
+
+if (((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.1) OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND NOT CPR_USE_BOOST_FILESYSTEM)
+ target_link_libraries(cpr PUBLIC stdc++fs)
+endif()
+
+install(DIRECTORY cpr DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(DIRECTORY ${PROJECT_BINARY_DIR}/cpr_generated_includes/cpr DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
diff --git a/Src/external_dependencies/cpr/include/cpr/accept_encoding.h b/Src/external_dependencies/cpr/include/cpr/accept_encoding.h new file mode 100644 index 00000000..e09ad06b --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/accept_encoding.h @@ -0,0 +1,36 @@ +#ifndef CPR_ACCEPT_ENCODING_H +#define CPR_ACCEPT_ENCODING_H + +#include <curl/curlver.h> +#include <initializer_list> +#include <map> +#include <string> +#include <vector> + +namespace cpr { + +enum class AcceptEncodingMethods { + identity, + deflate, + zlib, + gzip, +}; + +static const std::map<AcceptEncodingMethods, std::string> AcceptEncodingMethodsStringMap{{AcceptEncodingMethods::identity, "identity"}, {AcceptEncodingMethods::deflate, "deflate"}, {AcceptEncodingMethods::zlib, "zlib"}, {AcceptEncodingMethods::gzip, "gzip"}}; + +class AcceptEncoding { + public: + AcceptEncoding() = default; + AcceptEncoding(const std::initializer_list<AcceptEncodingMethods>& methods); + AcceptEncoding(const std::initializer_list<std::string>& methods); + + bool empty() const noexcept; + const std::string getString() const; + + private: + std::vector<std::string> methods_; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/api.h b/Src/external_dependencies/cpr/include/cpr/api.h new file mode 100644 index 00000000..7d335237 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/api.h @@ -0,0 +1,321 @@ +#ifndef CPR_API_H +#define CPR_API_H + +#include <fstream> +#include <functional> +#include <future> +#include <string> +#include <utility> + +#include "cpr/async.h" +#include "cpr/auth.h" +#include "cpr/bearer.h" +#include "cpr/cprtypes.h" +#include "cpr/multipart.h" +#include "cpr/multiperform.h" +#include "cpr/payload.h" +#include "cpr/response.h" +#include "cpr/session.h" +#include <cpr/filesystem.h> +#include <utility> + +namespace cpr { + +using AsyncResponse = std::future<Response>; + +namespace priv { + +template <bool processed_header, typename CurrentType> +void set_option_internal(Session& session, CurrentType&& current_option) { + session.SetOption(std::forward<CurrentType>(current_option)); +} + +template <> +inline void set_option_internal<true, Header>(Session& session, Header&& current_option) { + // Header option was already provided -> Update previous header + session.UpdateHeader(std::forward<Header>(current_option)); +} + +template <bool processed_header, typename CurrentType, typename... Ts> +void set_option_internal(Session& session, CurrentType&& current_option, Ts&&... ts) { + set_option_internal<processed_header, CurrentType>(session, std::forward<CurrentType>(current_option)); + + if (std::is_same<CurrentType, Header>::value) { + set_option_internal<true, Ts...>(session, std::forward<Ts>(ts)...); + } else { + set_option_internal<processed_header, Ts...>(session, std::forward<Ts>(ts)...); + } +} + +template <typename... Ts> +void set_option(Session& session, Ts&&... ts) { + set_option_internal<false, Ts...>(session, std::forward<Ts>(ts)...); +} + +// Idea: https://stackoverflow.com/a/19060157 +template <typename Tuple, std::size_t... I> +void apply_set_option_internal(Session& session, Tuple&& t, std::index_sequence<I...>) { + set_option(session, std::get<I>(std::forward<Tuple>(t))...); +} + +// Idea: https://stackoverflow.com/a/19060157 +template <typename Tuple> +void apply_set_option(Session& session, Tuple&& t) { + using Indices = std::make_index_sequence<std::tuple_size<std::decay_t<Tuple>>::value>; + apply_set_option_internal(session, std::forward<Tuple>(t), Indices()); +} + +template <typename T> +void setup_multiperform_internal(MultiPerform& multiperform, T&& t) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + apply_set_option(*session, t); + multiperform.AddSession(session); +} + +template <typename T, typename... Ts> +void setup_multiperform_internal(MultiPerform& multiperform, T&& t, Ts&&... ts) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + apply_set_option(*session, t); + multiperform.AddSession(session); + setup_multiperform_internal<Ts...>(multiperform, std::forward<Ts>(ts)...); +} + +template <typename... Ts> +void setup_multiperform(MultiPerform& multiperform, Ts&&... ts) { + setup_multiperform_internal<Ts...>(multiperform, std::forward<Ts>(ts)...); +} + +} // namespace priv + +// Get methods +template <typename... Ts> +Response Get(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Get(); +} + +// Get async methods +template <typename... Ts> +AsyncResponse GetAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Get(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Get callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto GetCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Get(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Post methods +template <typename... Ts> +Response Post(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Post(); +} + +// Post async methods +template <typename... Ts> +AsyncResponse PostAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Post(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Post callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto PostCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Post(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Put methods +template <typename... Ts> +Response Put(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Put(); +} + +// Put async methods +template <typename... Ts> +AsyncResponse PutAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Put(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Put callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto PutCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Put(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Head methods +template <typename... Ts> +Response Head(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Head(); +} + +// Head async methods +template <typename... Ts> +AsyncResponse HeadAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Head(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Head callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto HeadCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Head(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Delete methods +template <typename... Ts> +Response Delete(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Delete(); +} + +// Delete async methods +template <typename... Ts> +AsyncResponse DeleteAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Delete(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Delete callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto DeleteCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Delete(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Options methods +template <typename... Ts> +Response Options(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Options(); +} + +// Options async methods +template <typename... Ts> +AsyncResponse OptionsAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Options(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Options callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto OptionsCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Options(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Patch methods +template <typename... Ts> +Response Patch(Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Patch(); +} + +// Patch async methods +template <typename... Ts> +AsyncResponse PatchAsync(Ts... ts) { + return cpr::async([](Ts... ts_inner) { return Patch(std::move(ts_inner)...); }, std::move(ts)...); +} + +// Patch callback methods +template <typename Then, typename... Ts> +// NOLINTNEXTLINE(fuchsia-trailing-return) +auto PatchCallback(Then then, Ts... ts) { + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Patch(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); +} + +// Download methods +template <typename... Ts> +Response Download(std::ofstream& file, Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Download(file); +} + +// Download async method +template <typename... Ts> +AsyncResponse DownloadAsync(fs::path local_path, Ts... ts) { + return std::async( + std::launch::async, + [](fs::path local_path_, Ts... ts_) { +#ifdef CPR_USE_BOOST_FILESYSTEM + std::ofstream f(local_path_.string()); +#else + std::ofstream f(local_path_); +#endif + return Download(f, std::move(ts_)...); + }, + std::move(local_path), std::move(ts)...); +} + +// Download with user callback +template <typename... Ts> +Response Download(const WriteCallback& write, Ts&&... ts) { + Session session; + priv::set_option(session, std::forward<Ts>(ts)...); + return session.Download(write); +} + +// Multi requests +template <typename... Ts> +std::vector<Response> MultiGet(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Get(); +} + +template <typename... Ts> +std::vector<Response> MultiDelete(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Delete(); +} + +template <typename... Ts> +std::vector<Response> MultiPut(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Put(); +} + +template <typename... Ts> +std::vector<Response> MultiHead(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Head(); +} + +template <typename... Ts> +std::vector<Response> MultiOptions(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Options(); +} + +template <typename... Ts> +std::vector<Response> MultiPatch(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Patch(); +} + +template <typename... Ts> +std::vector<Response> MultiPost(Ts&&... ts) { + MultiPerform multiperform; + priv::setup_multiperform<Ts...>(multiperform, std::forward<Ts>(ts)...); + return multiperform.Post(); +} + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/async.h b/Src/external_dependencies/cpr/include/cpr/async.h new file mode 100644 index 00000000..6e7db4c1 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/async.h @@ -0,0 +1,49 @@ +#ifndef CPR_ASYNC_H +#define CPR_ASYNC_H + +#include "singleton.h" +#include "threadpool.h" + +namespace cpr { + +class GlobalThreadPool : public ThreadPool { + CPR_SINGLETON_DECL(GlobalThreadPool) + protected: + GlobalThreadPool() = default; + + public: + ~GlobalThreadPool() override = default; +}; + +/** + * Return a future, calling future.get() will wait task done and return RetType. + * async(fn, args...) + * async(std::bind(&Class::mem_fn, &obj)) + * async(std::mem_fn(&Class::mem_fn, &obj)) + **/ +template <class Fn, class... Args> +auto async(Fn&& fn, Args&&... args) { + return GlobalThreadPool::GetInstance()->Submit(std::forward<Fn>(fn), std::forward<Args>(args)...); +} + +class async { + public: + static void startup(size_t min_threads = CPR_DEFAULT_THREAD_POOL_MIN_THREAD_NUM, size_t max_threads = CPR_DEFAULT_THREAD_POOL_MAX_THREAD_NUM, std::chrono::milliseconds max_idle_ms = CPR_DEFAULT_THREAD_POOL_MAX_IDLE_TIME) { + GlobalThreadPool* gtp = GlobalThreadPool::GetInstance(); + if (gtp->IsStarted()) { + return; + } + gtp->SetMinThreadNum(min_threads); + gtp->SetMaxThreadNum(max_threads); + gtp->SetMaxIdleTime(max_idle_ms); + gtp->Start(); + } + + static void cleanup() { + GlobalThreadPool::ExitInstance(); + } +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/auth.h b/Src/external_dependencies/cpr/include/cpr/auth.h new file mode 100644 index 00000000..3354565f --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/auth.h @@ -0,0 +1,33 @@ +#ifndef CPR_AUTH_H +#define CPR_AUTH_H + +#include <string> + +#include <utility> + +namespace cpr { + +enum class AuthMode { BASIC, DIGEST, NTLM }; + +class Authentication { + public: + Authentication(const std::string& username, const std::string& password, const AuthMode& auth_mode) : auth_string_{username + ":" + password}, auth_mode_{auth_mode} {} + Authentication(std::string&& username, std::string&& password, AuthMode&& auth_mode) : auth_string_{std::move(username) + ":" + std::move(password)}, auth_mode_{std::move(auth_mode)} {} + Authentication(const Authentication& other) = default; + Authentication(Authentication&& old) noexcept = default; + ~Authentication() noexcept; + + Authentication& operator=(Authentication&& old) noexcept = default; + Authentication& operator=(const Authentication& other) = default; + + const char* GetAuthString() const noexcept; + AuthMode GetAuthMode() const noexcept; + + private: + std::string auth_string_; + AuthMode auth_mode_; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/bearer.h b/Src/external_dependencies/cpr/include/cpr/bearer.h new file mode 100644 index 00000000..3d254e2d --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/bearer.h @@ -0,0 +1,36 @@ +#ifndef CPR_BEARER_H +#define CPR_BEARER_H + +#include <curl/curlver.h> +#include <string> + +#include <utility> + +namespace cpr { + +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 +class Bearer { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Bearer(const std::string& token) : token_string_{token} {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Bearer(std::string&& token) : token_string_{std::move(token)} {} + Bearer(const Bearer& other) = default; + Bearer(Bearer&& old) noexcept = default; + virtual ~Bearer() noexcept; + + Bearer& operator=(Bearer&& old) noexcept = default; + Bearer& operator=(const Bearer& other) = default; + + virtual const char* GetToken() const noexcept; + + protected: + std::string token_string_; +}; +#endif + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/body.h b/Src/external_dependencies/cpr/include/cpr/body.h new file mode 100644 index 00000000..0c191c54 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/body.h @@ -0,0 +1,54 @@ +#ifndef CPR_BODY_H +#define CPR_BODY_H + +#include <exception> +#include <initializer_list> +#include <string> +#include <vector> +#include <fstream> + +#include "cpr/buffer.h" +#include "cpr/cprtypes.h" +#include "cpr/file.h" + +namespace cpr { + +class Body : public StringHolder<Body> { + public: + Body() : StringHolder<Body>() {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Body(const std::string& body) : StringHolder<Body>(body) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Body(std::string&& body) : StringHolder<Body>(std::move(body)) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Body(std::string_view body) : StringHolder<Body>(body) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Body(const char* body) : StringHolder<Body>(body) {} + Body(const char* str, size_t len) : StringHolder<Body>(str, len) {} + Body(const std::initializer_list<std::string> args) : StringHolder<Body>(args) {} + Body(const Buffer& buffer) : StringHolder<Body>(reinterpret_cast<const char*>(buffer.data), static_cast<size_t>(buffer.datalen)) {} + Body(const File& file) { + std::ifstream is(file.filepath, std::ifstream::binary); + if (!is) { + throw std::invalid_argument("Can't open the file for HTTP request body!"); + } + + is.seekg(0, std::ios::end); + const std::streampos length = is.tellg(); + is.seekg(0, std::ios::beg); + std::string buffer; + buffer.resize(static_cast<size_t>(length)); + is.read(&buffer[0], length); + str_ = std::move(buffer); + } + Body(const Body& other) = default; + Body(Body&& old) noexcept = default; + ~Body() override = default; + + Body& operator=(Body&& old) noexcept = default; + Body& operator=(const Body& other) = default; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/buffer.h b/Src/external_dependencies/cpr/include/cpr/buffer.h new file mode 100644 index 00000000..17065eba --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/buffer.h @@ -0,0 +1,35 @@ +#ifndef CPR_BUFFER_H +#define CPR_BUFFER_H + +#include <string> + +#include <cpr/filesystem.h> + +namespace cpr { + +struct Buffer { + using data_t = const unsigned char*; + + template <typename Iterator> + Buffer(Iterator begin, Iterator end, fs::path&& p_filename) + // Ignored here since libcurl reqires a long. + // There is also no way around the reinterpret_cast. + // NOLINTNEXTLINE(google-runtime-int, cppcoreguidelines-pro-type-reinterpret-cast) + : data{reinterpret_cast<data_t>(&(*begin))}, datalen{static_cast<long>(std::distance(begin, end))}, filename(std::move(p_filename)) { + is_random_access_iterator(begin, end); + static_assert(sizeof(*begin) == 1, "Only byte buffers can be used"); + } + + template <typename Iterator> + typename std::enable_if<std::is_same<typename std::iterator_traits<Iterator>::iterator_category, std::random_access_iterator_tag>::value>::type is_random_access_iterator(Iterator /* begin */, Iterator /* end */) {} + + data_t data; + // Ignored here since libcurl reqires a long: + // NOLINTNEXTLINE(google-runtime-int) + long datalen; + const fs::path filename; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/callback.h b/Src/external_dependencies/cpr/include/cpr/callback.h new file mode 100644 index 00000000..e54c4fb1 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/callback.h @@ -0,0 +1,89 @@ +#ifndef CPR_CALLBACK_H +#define CPR_CALLBACK_H + +#include "cprtypes.h" + +#include <functional> +#include <utility> + +namespace cpr { + +class ReadCallback { + public: + ReadCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ReadCallback(std::function<bool(char* buffer, size_t& size, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), size{-1}, callback{std::move(p_callback)} {} + ReadCallback(cpr_off_t p_size, std::function<bool(char* buffer, size_t& size, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), size{p_size}, callback{std::move(p_callback)} {} + bool operator()(char* buffer, size_t& buffer_size) const { + return callback(buffer, buffer_size, userdata); + } + + intptr_t userdata; + cpr_off_t size{}; + std::function<bool(char* buffer, size_t& size, intptr_t userdata)> callback; +}; + +class HeaderCallback { + public: + HeaderCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + HeaderCallback(std::function<bool(std::string header, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + bool operator()(std::string header) const { + return callback(std::move(header), userdata); + } + + intptr_t userdata; + std::function<bool(std::string header, intptr_t userdata)> callback; +}; + +class WriteCallback { + public: + WriteCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + WriteCallback(std::function<bool(std::string data, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + bool operator()(std::string data) const { + return callback(std::move(data), userdata); + } + + intptr_t userdata; + std::function<bool(std::string data, intptr_t userdata)> callback; +}; + +class ProgressCallback { + public: + ProgressCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ProgressCallback(std::function<bool(cpr_off_t downloadTotal, cpr_off_t downloadNow, cpr_off_t uploadTotal, cpr_off_t uploadNow, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + bool operator()(cpr_off_t downloadTotal, cpr_off_t downloadNow, cpr_off_t uploadTotal, cpr_off_t uploadNow) const { + return callback(downloadTotal, downloadNow, uploadTotal, uploadNow, userdata); + } + + intptr_t userdata; + std::function<bool(cpr_off_t downloadTotal, cpr_off_t downloadNow, cpr_off_t uploadTotal, cpr_off_t uploadNow, intptr_t userdata)> callback; +}; + +class DebugCallback { + public: + enum class InfoType { + TEXT = 0, + HEADER_IN = 1, + HEADER_OUT = 2, + DATA_IN = 3, + DATA_OUT = 4, + SSL_DATA_IN = 5, + SSL_DATA_OUT = 6, + }; + DebugCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + DebugCallback(std::function<void(InfoType type, std::string data, intptr_t userdata)> p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + void operator()(InfoType type, std::string data) const { + callback(type, std::move(data), userdata); + } + + intptr_t userdata; + std::function<void(InfoType type, std::string data, intptr_t userdata)> callback; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/cert_info.h b/Src/external_dependencies/cpr/include/cpr/cert_info.h new file mode 100644 index 00000000..6c328ee7 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/cert_info.h @@ -0,0 +1,35 @@ +#ifndef CPR_CERT_INFO_H +#define CPR_CERT_INFO_H + +#include <initializer_list> +#include <string> +#include <vector> + +namespace cpr { + +class CertInfo { + private: + std::vector<std::string> cert_info_; + + public: + CertInfo() = default; + CertInfo(const std::initializer_list<std::string>& entry) : cert_info_{entry} {}; + ~CertInfo() noexcept = default; + + using iterator = std::vector<std::string>::iterator; + using const_iterator = std::vector<std::string>::const_iterator; + + std::string& operator[](const size_t& pos); + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + void emplace_back(const std::string& str); + void push_back(const std::string& str); + void pop_back(); +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/connect_timeout.h b/Src/external_dependencies/cpr/include/cpr/connect_timeout.h new file mode 100644 index 00000000..546e8a58 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/connect_timeout.h @@ -0,0 +1,18 @@ +#ifndef CPR_CONNECT_TIMEOUT_H +#define CPR_CONNECT_TIMEOUT_H + +#include "cpr/timeout.h" + +namespace cpr { + +class ConnectTimeout : public Timeout { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ConnectTimeout(const std::chrono::milliseconds& duration) : Timeout{duration} {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ConnectTimeout(const std::int32_t& milliseconds) : Timeout{milliseconds} {} +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/cookies.h b/Src/external_dependencies/cpr/include/cpr/cookies.h new file mode 100644 index 00000000..c018ea4a --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/cookies.h @@ -0,0 +1,92 @@ +#ifndef CPR_COOKIES_H +#define CPR_COOKIES_H + +#include "cpr/curlholder.h" +#include <chrono> +#include <initializer_list> +#include <sstream> +#include <string> +#include <vector> + +namespace cpr { +/** + * EXPIRES_STRING_SIZE is an explicitly static and const variable that could be only accessed within the same namespace and is immutable. + * To be used for "std::array", the expression must have a constant value, so EXPIRES_STRING_SIZE must be a const value. + **/ +static const std::size_t EXPIRES_STRING_SIZE = 100; + +class Cookie { + public: + Cookie() = default; + /** + * Some notes for the default value used by expires: + * std::chrono::system_clock::time_point::min() won't work on Windows due to the min, max clash there. + * So we fall back to std::chrono::system_clock::from_time_t(0) for the minimum value here. + **/ + Cookie(const std::string& name, const std::string& value, const std::string& domain = "", bool p_isIncludingSubdomains = false, const std::string& path = "/", bool p_isHttpsOnly = false, std::chrono::system_clock::time_point expires = std::chrono::system_clock::from_time_t(0)) : name_{name}, value_{value}, domain_{domain}, includeSubdomains_{p_isIncludingSubdomains}, path_{path}, httpsOnly_{p_isHttpsOnly}, expires_{expires} {}; + const std::string GetDomain() const; + bool IsIncludingSubdomains() const; + const std::string GetPath() const; + bool IsHttpsOnly() const; + const std::chrono::system_clock::time_point GetExpires() const; + const std::string GetExpiresString() const; + const std::string GetName() const; + const std::string GetValue() const; + + private: + std::string name_; + std::string value_; + std::string domain_; + bool includeSubdomains_{}; + std::string path_; + bool httpsOnly_{}; + /** + * TODO: Update the implementation using `std::chrono::utc_clock` of C++20 + **/ + std::chrono::system_clock::time_point expires_{}; +}; + +class Cookies { + public: + /** + * Should we URL-encode cookies when making a request. + * Based on RFC6265, it is recommended but not mandatory to encode cookies. + * + * ------- + * To maximize compatibility with user agents, servers that wish to + * store arbitrary data in a cookie-value SHOULD encode that data, for + * example, using Base64 [RFC4648]. + * ------- + * Source: RFC6265 (https://www.ietf.org/rfc/rfc6265.txt) + **/ + bool encode{true}; + + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Cookies(bool p_encode = true) : encode{p_encode} {}; + Cookies(const std::initializer_list<cpr::Cookie>& cookies, bool p_encode = true) : encode{p_encode}, cookies_{cookies} {}; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Cookies(const cpr::Cookie& cookie, bool p_encode = true) : encode{p_encode}, cookies_{cookie} {}; + + cpr::Cookie& operator[](size_t pos); + const std::string GetEncoded(const CurlHolder& holder) const; + + using iterator = std::vector<cpr::Cookie>::iterator; + using const_iterator = std::vector<cpr::Cookie>::const_iterator; + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + void emplace_back(const Cookie& str); + void push_back(const Cookie& str); + void pop_back(); + + private: + std::vector<cpr::Cookie> cookies_; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/cpr.h b/Src/external_dependencies/cpr/include/cpr/cpr.h new file mode 100644 index 00000000..fbad1726 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/cpr.h @@ -0,0 +1,46 @@ +#ifndef CPR_CPR_H +#define CPR_CPR_H + +#include "cpr/api.h" +#include "cpr/auth.h" +#include "cpr/bearer.h" +#include "cpr/callback.h" +#include "cpr/cert_info.h" +#include "cpr/connect_timeout.h" +#include "cpr/cookies.h" +#include "cpr/cprtypes.h" +#include "cpr/cprver.h" +#include "cpr/curl_container.h" +#include "cpr/curlholder.h" +#include "cpr/error.h" +#include "cpr/http_version.h" +#include "cpr/interceptor.h" +#include "cpr/interface.h" +#include "cpr/limit_rate.h" +#include "cpr/local_port.h" +#include "cpr/local_port_range.h" +#include "cpr/low_speed.h" +#include "cpr/multipart.h" +#include "cpr/multiperform.h" +#include "cpr/parameters.h" +#include "cpr/payload.h" +#include "cpr/proxies.h" +#include "cpr/proxyauth.h" +#include "cpr/range.h" +#include "cpr/redirect.h" +#include "cpr/reserve_size.h" +#include "cpr/resolve.h" +#include "cpr/response.h" +#include "cpr/session.h" +#include "cpr/ssl_ctx.h" +#include "cpr/ssl_options.h" +#include "cpr/status_codes.h" +#include "cpr/timeout.h" +#include "cpr/unix_socket.h" +#include "cpr/user_agent.h" +#include "cpr/util.h" +#include "cpr/verbose.h" + +#define CPR_LIBCURL_VERSION_NUM LIBCURL_VERSION_NUM + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/cprtypes.h b/Src/external_dependencies/cpr/include/cpr/cprtypes.h new file mode 100644 index 00000000..4ad9e29b --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/cprtypes.h @@ -0,0 +1,137 @@ +#ifndef CPR_CPR_TYPES_H +#define CPR_CPR_TYPES_H + +#include <curl/system.h> +#include <initializer_list> +#include <map> +#include <memory> +#include <numeric> +#include <string> +#include <string_view> + +namespace cpr { + +/** + * Wrapper around "curl_off_t" to prevent applications from having to link against libcurl. + **/ +using cpr_off_t = curl_off_t; + +template <class T> +class StringHolder { + public: + StringHolder() = default; + explicit StringHolder(const std::string& str) : str_(str) {} + explicit StringHolder(std::string&& str) : str_(std::move(str)) {} + explicit StringHolder(std::string_view str) : str_(str) {} + explicit StringHolder(const char* str) : str_(str) {} + StringHolder(const char* str, size_t len) : str_(str, len) {} + StringHolder(const std::initializer_list<std::string> args) { + str_ = std::accumulate(args.begin(), args.end(), str_); + } + StringHolder(const StringHolder& other) = default; + StringHolder(StringHolder&& old) noexcept = default; + virtual ~StringHolder() = default; + + StringHolder& operator=(StringHolder&& old) noexcept = default; + + StringHolder& operator=(const StringHolder& other) = default; + + explicit operator std::string() const { + return str_; + } + + T operator+(const char* rhs) const { + return T(str_ + rhs); + } + + T operator+(const std::string& rhs) const { + return T(str_ + rhs); + } + + T operator+(const StringHolder<T>& rhs) const { + return T(str_ + rhs.str_); + } + + void operator+=(const char* rhs) { + str_ += rhs; + } + void operator+=(const std::string& rhs) { + str_ += rhs; + } + void operator+=(const StringHolder<T>& rhs) { + str_ += rhs; + } + + bool operator==(const char* rhs) const { + return str_ == rhs; + } + bool operator==(const std::string& rhs) const { + return str_ == rhs; + } + bool operator==(const StringHolder<T>& rhs) const { + return str_ == rhs.str_; + } + + bool operator!=(const char* rhs) const { + return str_.c_str() != rhs; + } + bool operator!=(const std::string& rhs) const { + return str_ != rhs; + } + bool operator!=(const StringHolder<T>& rhs) const { + return str_ != rhs.str_; + } + + const std::string& str() { + return str_; + } + const std::string& str() const { + return str_; + } + const char* c_str() const { + return str_.c_str(); + } + const char* data() const { + return str_.data(); + } + + protected: + std::string str_{}; +}; + +template <class T> +std::ostream& operator<<(std::ostream& os, const StringHolder<T>& s) { + os << s.str(); + return os; +} + +class Url : public StringHolder<Url> { + public: + Url() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Url(const std::string& url) : StringHolder<Url>(url) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Url(std::string&& url) : StringHolder<Url>(std::move(url)) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Url(std::string_view url) : StringHolder<Url>(url) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Url(const char* url) : StringHolder<Url>(url) {} + Url(const char* str, size_t len) : StringHolder<Url>(std::string(str, len)) {} + Url(const std::initializer_list<std::string> args) : StringHolder<Url>(args) {} + Url(const Url& other) = default; + Url(Url&& old) noexcept = default; + ~Url() override = default; + + Url& operator=(Url&& old) noexcept = default; + Url& operator=(const Url& other) = default; +}; + +struct CaseInsensitiveCompare { + bool operator()(const std::string& a, const std::string& b) const noexcept; +}; + +using Header = std::map<std::string, std::string, CaseInsensitiveCompare>; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/curl_container.h b/Src/external_dependencies/cpr/include/cpr/curl_container.h new file mode 100644 index 00000000..91a013b3 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/curl_container.h @@ -0,0 +1,53 @@ +#ifndef CURL_CONTAINER_H +#define CURL_CONTAINER_H + +#include <initializer_list> +#include <memory> +#include <string> +#include <vector> + +#include "cpr/curlholder.h" + + +namespace cpr { + +struct Parameter { + Parameter(const std::string& p_key, const std::string& p_value) : key{p_key}, value{p_value} {} + Parameter(std::string&& p_key, std::string&& p_value) : key{std::move(p_key)}, value{std::move(p_value)} {} + + std::string key; + std::string value; +}; + +struct Pair { + Pair(const std::string& p_key, const std::string& p_value) : key(p_key), value(p_value) {} + Pair(std::string&& p_key, std::string&& p_value) : key(std::move(p_key)), value(std::move(p_value)) {} + + std::string key; + std::string value; +}; + + +template <class T> +class CurlContainer { + public: + /** + * Enables or disables URL encoding for keys and values when calling GetContent(...). + **/ + bool encode = true; + + CurlContainer() = default; + CurlContainer(const std::initializer_list<T>&); + + void Add(const std::initializer_list<T>&); + void Add(const T&); + + const std::string GetContent(const CurlHolder&) const; + + protected: + std::vector<T> containerList_; +}; + +} // namespace cpr + +#endif // diff --git a/Src/external_dependencies/cpr/include/cpr/curlholder.h b/Src/external_dependencies/cpr/include/cpr/curlholder.h new file mode 100644 index 00000000..e6913b81 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/curlholder.h @@ -0,0 +1,54 @@ +#ifndef CPR_CURL_HOLDER_H +#define CPR_CURL_HOLDER_H + +#include <array> +#include <mutex> +#include <string> + +#include <curl/curl.h> + +namespace cpr { +struct CurlHolder { + private: + /** + * Mutex for curl_easy_init(). + * curl_easy_init() is not thread save. + * References: + * https://curl.haxx.se/libcurl/c/curl_easy_init.html + * https://curl.haxx.se/libcurl/c/threadsafe.html + **/ + + // Avoids initalization order problems in a static build + static std::mutex& curl_easy_init_mutex_() { + static std::mutex curl_easy_init_mutex_; + return curl_easy_init_mutex_; + } + + public: + CURL* handle{nullptr}; + struct curl_slist* chunk{nullptr}; + struct curl_slist* resolveCurlList{nullptr}; + struct curl_httppost* formpost{nullptr}; + std::array<char, CURL_ERROR_SIZE> error{}; + + CurlHolder(); + CurlHolder(const CurlHolder& other) = default; + CurlHolder(CurlHolder&& old) noexcept = default; + ~CurlHolder(); + + CurlHolder& operator=(CurlHolder&& old) noexcept = default; + CurlHolder& operator=(const CurlHolder& other) = default; + + /** + * Uses curl_easy_escape(...) for escaping the given string. + **/ + std::string urlEncode(const std::string& s) const; + + /** + * Uses curl_easy_unescape(...) for unescaping the given string. + **/ + std::string urlDecode(const std::string& s) const; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/curlmultiholder.h b/Src/external_dependencies/cpr/include/cpr/curlmultiholder.h new file mode 100644 index 00000000..ccd504b6 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/curlmultiholder.h @@ -0,0 +1,18 @@ +#ifndef CPR_CURLMULTIHOLDER_H +#define CPR_CURLMULTIHOLDER_H + +#include <curl/curl.h> + +namespace cpr { + +class CurlMultiHolder { + public: + CurlMultiHolder(); + ~CurlMultiHolder(); + + CURLM* handle{nullptr}; +}; + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/error.h b/Src/external_dependencies/cpr/include/cpr/error.h new file mode 100644 index 00000000..bb59a4cc --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/error.h @@ -0,0 +1,53 @@ +#ifndef CPR_ERROR_H +#define CPR_ERROR_H + +#include <cstdint> +#include <string> + +#include "cpr/cprtypes.h" +#include <utility> + +namespace cpr { + +enum class ErrorCode { + OK = 0, + CONNECTION_FAILURE, + EMPTY_RESPONSE, + HOST_RESOLUTION_FAILURE, + INTERNAL_ERROR, + INVALID_URL_FORMAT, + NETWORK_RECEIVE_ERROR, + NETWORK_SEND_FAILURE, + OPERATION_TIMEDOUT, + PROXY_RESOLUTION_FAILURE, + SSL_CONNECT_ERROR, + SSL_LOCAL_CERTIFICATE_ERROR, + SSL_REMOTE_CERTIFICATE_ERROR, + SSL_CACERT_ERROR, + GENERIC_SSL_ERROR, + UNSUPPORTED_PROTOCOL, + REQUEST_CANCELLED, + TOO_MANY_REDIRECTS, + UNKNOWN_ERROR = 1000, +}; + +class Error { + public: + ErrorCode code = ErrorCode::OK; + std::string message{}; + + Error() = default; + + Error(const std::int32_t& curl_code, std::string&& p_error_message) : code{getErrorCodeForCurlError(curl_code)}, message(std::move(p_error_message)) {} + + explicit operator bool() const { + return code != ErrorCode::OK; + } + + private: + static ErrorCode getErrorCodeForCurlError(std::int32_t curl_code); +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/file.h b/Src/external_dependencies/cpr/include/cpr/file.h new file mode 100644 index 00000000..5c47b638 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/file.h @@ -0,0 +1,55 @@ +#ifndef CPR_FILE_H +#define CPR_FILE_H + +#include <initializer_list> +#include <string> +#include <vector> + +#include <cpr/filesystem.h> + +namespace cpr { + +struct File { + explicit File(std::string&& p_filepath, const std::string& p_overrided_filename = {}) : filepath(std::move(p_filepath)), overrided_filename(p_overrided_filename) {} + explicit File(const std::string& p_filepath, const std::string& p_overrided_filename = {}) : filepath(p_filepath), overrided_filename(p_overrided_filename) {} + + const std::string filepath; + const std::string overrided_filename; + + bool hasOverridedFilename() const noexcept { + return !overrided_filename.empty(); + }; +}; + +class Files { + public: + Files() = default; + Files(const File& p_file) : files{p_file} {}; + Files(const std::initializer_list<File>& p_files) : files{p_files} {}; + Files(const std::initializer_list<std::string>& p_filepaths) { + for (const std::string& filepath : p_filepaths) { + files.emplace_back(File(filepath)); + } + }; + ~Files() noexcept = default; + + using iterator = std::vector<File>::iterator; + using const_iterator = std::vector<File>::const_iterator; + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + void emplace_back(const File& file); + void push_back(const File& file); + void pop_back(); + + private: + std::vector<File> files; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/filesystem.h b/Src/external_dependencies/cpr/include/cpr/filesystem.h new file mode 100644 index 00000000..f498f80f --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/filesystem.h @@ -0,0 +1,19 @@ +#ifndef CPR_FILESYSTEM_H +#define CPR_FILESYSTEM_H + +// Include filesystem into the namespace "fs" from either "filesystem" or "experimental/filesystem" or "boost/filesystem" +#if __has_include(<filesystem>) +#include <filesystem> +namespace fs = std::filesystem; +#elif __has_include("experimental/filesystem") +#include <experimental/filesystem> +namespace fs = std::experimental::filesystem; +//cppcheck-suppress preprocessorErrorDirective +#elif defined(CPR_USE_BOOST_FILESYSTEM) && __has_include(<boost/filesystem.hpp>) +#include <boost/filesystem.hpp> +namespace fs = boost::filesystem; +#else +#error "Failed to include <filesystem> header!" +#endif + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/http_version.h b/Src/external_dependencies/cpr/include/cpr/http_version.h new file mode 100644 index 00000000..45b50287 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/http_version.h @@ -0,0 +1,67 @@ +#ifndef CPR_HTTP_VERSION_H +#define CPR_HTTP_VERSION_H + +#include <curl/curlver.h> + +namespace cpr { +enum class HttpVersionCode { + /** + * Let libcurl decide which version is the best. + **/ + VERSION_NONE, + /** + * Enforce HTTP 1.0 requests. + **/ + VERSION_1_0, + /** + * Enforce HTTP 1.1 requests. + **/ + VERSION_1_1, +#if LIBCURL_VERSION_NUM >= 0x072100 // 7.33.0 + /** + * Attempt HTTP 2.0 requests. + * Fallback to HTTP 1.1 if negotiation fails. + **/ + VERSION_2_0, +#endif +#if LIBCURL_VERSION_NUM >= 0x072F00 // 7.47.0 + /** + * Attempt HTTP 2.0 for HTTPS requests only. + * Fallback to HTTP 1.1 if negotiation fails. + * HTTP 1.1 will be used for HTTP connections. + **/ + VERSION_2_0_TLS, +#endif +#if LIBCURL_VERSION_NUM >= 0x073100 // 7.49.0 + /** + * Start HTTP 2.0 for HTTP requests. + * Requires prior knowledge that the server supports HTTP 2.0. + * For HTTPS requests we will negotiate the protocol version in the TLS handshake. + **/ + VERSION_2_0_PRIOR_KNOWLEDGE, +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 // 7.66.0 + /** + * Attempt HTTP 3.0 requests. + * Requires prior knowledge that the server supports HTTP 3.0 since there is no gracefully downgrade. + * Fallback to HTTP 1.1 if negotiation fails. + **/ + VERSION_3_0 +#endif +}; + +class HttpVersion { + public: + /** + * The HTTP version that should be used by libcurl when initiating a HTTP(S) connection. + * Default: HttpVersionCode::VERSION_NONE + **/ + HttpVersionCode code = HttpVersionCode::VERSION_NONE; + + HttpVersion() = default; + explicit HttpVersion(HttpVersionCode _code) : code(_code) {} +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/interceptor.h b/Src/external_dependencies/cpr/include/cpr/interceptor.h new file mode 100644 index 00000000..fdfe69c2 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/interceptor.h @@ -0,0 +1,36 @@ +#ifndef CPR_INTERCEPTOR_H +#define CPR_INTERCEPTOR_H + +#include "cpr/response.h" +#include "cpr/session.h" + +namespace cpr { + +class Interceptor { + public: + enum class ProceedHttpMethod { + GET_REQUEST = 0, + POST_REQUEST, + PUT_REQUEST, + DELETE_REQUEST, + PATCH_REQUEST, + HEAD_REQUEST, + OPTIONS_REQUEST, + DOWNLOAD_CALLBACK_REQUEST, + DOWNLOAD_FILE_REQUEST, + }; + + virtual ~Interceptor() = default; + virtual Response intercept(Session& session) = 0; + + protected: + static Response proceed(Session& session); + static Response proceed(Session& session, ProceedHttpMethod httpMethod); + static Response proceed(Session& session, ProceedHttpMethod httpMethod, std::ofstream& file); + static Response proceed(Session& session, ProceedHttpMethod httpMethod, const WriteCallback& write); +}; + +} // namespace cpr + + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/interface.h b/Src/external_dependencies/cpr/include/cpr/interface.h new file mode 100644 index 00000000..ded2fda1 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/interface.h @@ -0,0 +1,34 @@ +#ifndef CPR_INTERFACE_H +#define CPR_INTERFACE_H + +#include <initializer_list> +#include <string> + +#include "cpr/cprtypes.h" + +namespace cpr { + +class Interface : public StringHolder<Interface> { + public: + Interface() : StringHolder<Interface>() {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Interface(const std::string& iface) : StringHolder<Interface>(iface) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Interface(std::string&& iface) : StringHolder<Interface>(std::move(iface)) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Interface(std::string_view iface) : StringHolder<Interface>(iface) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Interface(const char* iface) : StringHolder<Interface>(iface) {} + Interface(const char* str, size_t len) : StringHolder<Interface>(str, len) {} + Interface(const std::initializer_list<std::string> args) : StringHolder<Interface>(args) {} + Interface(const Interface& other) = default; + Interface(Interface&& old) noexcept = default; + ~Interface() override = default; + + Interface& operator=(Interface&& old) noexcept = default; + Interface& operator=(const Interface& other) = default; +}; + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/limit_rate.h b/Src/external_dependencies/cpr/include/cpr/limit_rate.h new file mode 100644 index 00000000..2b0e8a24 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/limit_rate.h @@ -0,0 +1,18 @@ +#ifndef CPR_SPEED_LIMIT_H +#define CPR_SPEED_LIMIT_H + +#include <cstdint> + +namespace cpr { + +class LimitRate { + public: + LimitRate(const std::int64_t p_downrate, const std::int64_t p_uprate) : downrate(p_downrate), uprate(p_uprate) {} + + std::int64_t downrate = 0; + std::int64_t uprate = 0; +}; + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/local_port.h b/Src/external_dependencies/cpr/include/cpr/local_port.h new file mode 100644 index 00000000..e853425a --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/local_port.h @@ -0,0 +1,23 @@ +#ifndef CPR_LOCAL_PORT_H +#define CPR_LOCAL_PORT_H + +#include <cstdint> + +namespace cpr { + +class LocalPort { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + LocalPort(const std::uint16_t p_localport) : localport_(p_localport) {} + + operator std::uint16_t() const { + return localport_; + } + + private: + std::uint16_t localport_ = 0; +}; + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/local_port_range.h b/Src/external_dependencies/cpr/include/cpr/local_port_range.h new file mode 100644 index 00000000..cc2d7e25 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/local_port_range.h @@ -0,0 +1,23 @@ +#ifndef CPR_LOCAL_PORT_RANGE_H +#define CPR_LOCAL_PORT_RANGE_H + +#include <cstdint> + +namespace cpr { + +class LocalPortRange { + public: + // NOLINTNEXTLINE(google-explicit-constructor) + LocalPortRange(const std::uint16_t p_localportrange) : localportrange_(p_localportrange) {} + + operator std::uint16_t() const { + return localportrange_; + } + + private: + std::uint16_t localportrange_ = 0; +}; + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/low_speed.h b/Src/external_dependencies/cpr/include/cpr/low_speed.h new file mode 100644 index 00000000..ff77fd2e --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/low_speed.h @@ -0,0 +1,18 @@ +#ifndef CPR_LOW_SPEED_H +#define CPR_LOW_SPEED_H + +#include <cstdint> + +namespace cpr { + +class LowSpeed { + public: + LowSpeed(const std::int32_t p_limit, const std::int32_t p_time) : limit(p_limit), time(p_time) {} + + std::int32_t limit; + std::int32_t time; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/multipart.h b/Src/external_dependencies/cpr/include/cpr/multipart.h new file mode 100644 index 00000000..1c4c9dbf --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/multipart.h @@ -0,0 +1,45 @@ +#ifndef CPR_MULTIPART_H +#define CPR_MULTIPART_H + +#include <cstdint> +#include <initializer_list> +#include <string> +#include <type_traits> +#include <vector> + +#include "buffer.h" +#include "file.h" + +namespace cpr { + +struct Part { + Part(const std::string& p_name, const std::string& p_value, const std::string& p_content_type = {}) : name{p_name}, value{p_value}, content_type{p_content_type}, is_file{false}, is_buffer{false} {} + Part(const std::string& p_name, const std::int32_t& p_value, const std::string& p_content_type = {}) : name{p_name}, value{std::to_string(p_value)}, content_type{p_content_type}, is_file{false}, is_buffer{false} {} + Part(const std::string& p_name, const Files& p_files, const std::string& p_content_type = {}) : name{p_name}, value{}, content_type{p_content_type}, is_file{true}, is_buffer{false}, files{p_files} {} + Part(const std::string& p_name, Files&& p_files, const std::string& p_content_type = {}) : name{p_name}, value{}, content_type{p_content_type}, is_file{true}, is_buffer{false}, files{std::move(p_files)} {} + Part(const std::string& p_name, const Buffer& buffer, const std::string& p_content_type = {}) : name{p_name}, value{buffer.filename.string()}, content_type{p_content_type}, data{buffer.data}, datalen{buffer.datalen}, is_file{false}, is_buffer{true} {} + + std::string name; + // We don't use fs::path here, as this leads to problems using windows + std::string value; + std::string content_type; + Buffer::data_t data{nullptr}; + // Ignored here since libcurl reqires a long: + // NOLINTNEXTLINE(google-runtime-int) + long datalen{0}; + bool is_file; + bool is_buffer; + + Files files; +}; + +class Multipart { + public: + Multipart(const std::initializer_list<Part>& parts); + + std::vector<Part> parts; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/multiperform.h b/Src/external_dependencies/cpr/include/cpr/multiperform.h new file mode 100644 index 00000000..67f1637c --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/multiperform.h @@ -0,0 +1,119 @@ +#ifndef CPR_MULTIPERFORM_H +#define CPR_MULTIPERFORM_H + +#include <functional> +#include <memory> +#include <stdexcept> +#include <vector> + +#include "cpr/curlmultiholder.h" +#include "cpr/response.h" +#include "cpr/session.h" + +namespace cpr { + +class MultiPerform { + public: + enum class HttpMethod { + UNDEFINED = 0, + GET_REQUEST, + POST_REQUEST, + PUT_REQUEST, + DELETE_REQUEST, + PATCH_REQUEST, + HEAD_REQUEST, + OPTIONS_REQUEST, + DOWNLOAD_REQUEST, + }; + + MultiPerform(); + ~MultiPerform(); + + std::vector<Response> Get(); + std::vector<Response> Delete(); + template <typename... DownloadArgTypes> + std::vector<Response> Download(DownloadArgTypes... args); + std::vector<Response> Put(); + std::vector<Response> Head(); + std::vector<Response> Options(); + std::vector<Response> Patch(); + std::vector<Response> Post(); + + std::vector<Response> Perform(); + template <typename... DownloadArgTypes> + std::vector<Response> PerformDownload(DownloadArgTypes... args); + + void AddSession(std::shared_ptr<Session>& session, HttpMethod method = HttpMethod::UNDEFINED); + void RemoveSession(const std::shared_ptr<Session>& session); + + private: + void SetHttpMethod(HttpMethod method); + + void PrepareSessions(); + template <typename CurrentDownloadArgType, typename... DownloadArgTypes> + void PrepareDownloadSessions(size_t sessions_index, CurrentDownloadArgType current_arg, DownloadArgTypes... args); + template <typename CurrentDownloadArgType> + void PrepareDownloadSessions(size_t sessions_index, CurrentDownloadArgType current_arg); + void PrepareDownloadSession(size_t sessions_index, std::ofstream& file); + void PrepareDownloadSession(size_t sessions_index, const WriteCallback& write); + + void PrepareGet(); + void PrepareDelete(); + void PreparePut(); + void PreparePatch(); + void PrepareHead(); + void PrepareOptions(); + void PreparePost(); + template <typename... DownloadArgTypes> + void PrepareDownload(DownloadArgTypes... args); + + std::vector<Response> MakeRequest(); + std::vector<Response> MakeDownloadRequest(); + + void DoMultiPerform(); + std::vector<Response> ReadMultiInfo(std::function<Response(Session&, CURLcode)>&& complete_function); + + std::vector<std::pair<std::shared_ptr<Session>, HttpMethod>> sessions_; + std::unique_ptr<CurlMultiHolder> multicurl_; + bool is_download_multi_perform{false}; +}; + +template <typename CurrentDownloadArgType> +void MultiPerform::PrepareDownloadSessions(size_t sessions_index, CurrentDownloadArgType current_arg) { + PrepareDownloadSession(sessions_index, current_arg); +} + +template <typename CurrentDownloadArgType, typename... DownloadArgTypes> +void MultiPerform::PrepareDownloadSessions(size_t sessions_index, CurrentDownloadArgType current_arg, DownloadArgTypes... args) { + PrepareDownloadSession(sessions_index, current_arg); + PrepareDownloadSessions<DownloadArgTypes...>(sessions_index + 1, args...); +} + + +template <typename... DownloadArgTypes> +void MultiPerform::PrepareDownload(DownloadArgTypes... args) { + SetHttpMethod(HttpMethod::DOWNLOAD_REQUEST); + PrepareDownloadSessions<DownloadArgTypes...>(0, args...); +} + +template <typename... DownloadArgTypes> +std::vector<Response> MultiPerform::Download(DownloadArgTypes... args) { + if (sizeof...(args) != sessions_.size()) { + throw std::invalid_argument("Number of download arguments has to match the number of sessions added to the multiperform!"); + } + PrepareDownload(args...); + return MakeDownloadRequest(); +} + +template <typename... DownloadArgTypes> +std::vector<Response> MultiPerform::PerformDownload(DownloadArgTypes... args) { + if (sizeof...(args) != sessions_.size()) { + throw std::invalid_argument("Number of download arguments has to match the number of sessions added to the multiperform!"); + } + PrepareDownloadSessions<DownloadArgTypes...>(0, args...); + return MakeDownloadRequest(); +} + +} // namespace cpr + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/parameters.h b/Src/external_dependencies/cpr/include/cpr/parameters.h new file mode 100644 index 00000000..0e34d4d7 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/parameters.h @@ -0,0 +1,18 @@ +#ifndef CPR_PARAMETERS_H +#define CPR_PARAMETERS_H + +#include <initializer_list> + +#include "cpr/curl_container.h" + +namespace cpr { + +class Parameters : public CurlContainer<Parameter> { + public: + Parameters() = default; + Parameters(const std::initializer_list<Parameter>& parameters); +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/payload.h b/Src/external_dependencies/cpr/include/cpr/payload.h new file mode 100644 index 00000000..686b540e --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/payload.h @@ -0,0 +1,23 @@ +#ifndef CPR_PAYLOAD_H +#define CPR_PAYLOAD_H + +#include <initializer_list> + +#include "cpr/curl_container.h" + + +namespace cpr { +class Payload : public CurlContainer<Pair> { + public: + template <class It> + Payload(const It begin, const It end) { + for (It pair = begin; pair != end; ++pair) { + Add(*pair); + } + } + Payload(const std::initializer_list<Pair>& pairs); +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/proxies.h b/Src/external_dependencies/cpr/include/cpr/proxies.h new file mode 100644 index 00000000..6970442d --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/proxies.h @@ -0,0 +1,23 @@ +#ifndef CPR_PROXIES_H +#define CPR_PROXIES_H + +#include <initializer_list> +#include <map> +#include <string> + +namespace cpr { +class Proxies { + public: + Proxies() = default; + Proxies(const std::initializer_list<std::pair<const std::string, std::string>>& hosts); + Proxies(const std::map<std::string, std::string>& hosts); + + bool has(const std::string& protocol) const; + const std::string& operator[](const std::string& protocol); + + private: + std::map<std::string, std::string> hosts_; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/proxyauth.h b/Src/external_dependencies/cpr/include/cpr/proxyauth.h new file mode 100644 index 00000000..16369ff1 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/proxyauth.h @@ -0,0 +1,44 @@ +#ifndef CPR_PROXYAUTH_H +#define CPR_PROXYAUTH_H + +#include <initializer_list> +#include <map> + +#include "cpr/auth.h" +#include "cpr/util.h" + +namespace cpr { +class EncodedAuthentication { + public: + EncodedAuthentication() : auth_string_{""} {} + EncodedAuthentication(const std::string& username, const std::string& password) : auth_string_{cpr::util::urlEncode(username) + ":" + cpr::util::urlEncode(password)} {} + EncodedAuthentication(std::string&& username, std::string&& password) : auth_string_{cpr::util::urlEncode(std::move(username)) + ":" + cpr::util::urlEncode(std::move(password))} {} + EncodedAuthentication(const EncodedAuthentication& other) = default; + EncodedAuthentication(EncodedAuthentication&& old) noexcept = default; + virtual ~EncodedAuthentication() noexcept; + + EncodedAuthentication& operator=(EncodedAuthentication&& old) noexcept = default; + EncodedAuthentication& operator=(const EncodedAuthentication& other) = default; + + const char* GetAuthString() const noexcept; + + protected: + std::string auth_string_; +}; + +class ProxyAuthentication { + public: + ProxyAuthentication() = default; + ProxyAuthentication(const std::initializer_list<std::pair<const std::string, EncodedAuthentication>>& auths) : proxyAuth_{auths} {} + ProxyAuthentication(const std::map<std::string, EncodedAuthentication>& auths) : proxyAuth_{auths} {} + + bool has(const std::string& protocol) const; + const char* operator[](const std::string& protocol); + + private: + std::map<std::string, EncodedAuthentication> proxyAuth_; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/range.h b/Src/external_dependencies/cpr/include/cpr/range.h new file mode 100644 index 00000000..2c5a145d --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/range.h @@ -0,0 +1,44 @@ +#ifndef CPR_RANGE_H +#define CPR_RANGE_H + +#include <cstdint> +#include <optional> + +namespace cpr { + +class Range { + public: + Range(const std::optional<std::int64_t> p_resume_from = std::nullopt, const std::optional<std::int64_t> p_finish_at = std::nullopt) { + resume_from = p_resume_from.value_or(0); + finish_at = p_finish_at.value_or(-1); + } + + std::int64_t resume_from; + std::int64_t finish_at; + + const std::string str() const { + std::string from_str = (resume_from < 0U) ? "" : std::to_string(resume_from); + std::string to_str = (finish_at < 0U) ? "" : std::to_string(finish_at); + return from_str + "-" + to_str; + } +}; + +class MultiRange { + public: + MultiRange(std::initializer_list<Range> rs) : ranges{rs} {} + + const std::string str() const { + std::string multi_range_string{}; + for (Range range : ranges) { + multi_range_string += ((multi_range_string.empty()) ? "" : ", ") + range.str(); + } + return multi_range_string; + } + + private: + std::vector<Range> ranges; +}; // namespace cpr + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/redirect.h b/Src/external_dependencies/cpr/include/cpr/redirect.h new file mode 100644 index 00000000..32b4372c --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/redirect.h @@ -0,0 +1,84 @@ +#ifndef CPR_REDIRECT_H +#define CPR_REDIRECT_H + +#include <cstdint> + +namespace cpr { +enum class PostRedirectFlags : uint8_t { + /** + * Respect RFC 7231 (section 6.4.2 to 6.4.4). + * Same as CURL_REDIR_POST_301 (https://curl.se/libcurl/c/CURLOPT_POSTREDIR.html). + **/ + POST_301 = 0x1 << 0, + /** + * Maintain the request method after a 302 redirect. + * Same as CURL_REDIR_POST_302 (https://curl.se/libcurl/c/CURLOPT_POSTREDIR.html). + **/ + POST_302 = 0x1 << 1, + /** + * Maintain the request method after a 303 redirect. + * Same as CURL_REDIR_POST_303 (https://curl.se/libcurl/c/CURLOPT_POSTREDIR.html). + **/ + POST_303 = 0x1 << 2, + /** + * Default value. + * Convenience option to enable all flags. + * Same as CURL_REDIR_POST_ALL (https://curl.se/libcurl/c/CURLOPT_POSTREDIR.html). + **/ + POST_ALL = POST_301 | POST_302 | POST_303, + /** + * * Convenience option to disable all flags. + **/ + NONE = 0x0 +}; + +PostRedirectFlags operator|(PostRedirectFlags lhs, PostRedirectFlags rhs); +PostRedirectFlags operator&(PostRedirectFlags lhs, PostRedirectFlags rhs); +PostRedirectFlags operator^(PostRedirectFlags lhs, PostRedirectFlags rhs); +PostRedirectFlags operator~(PostRedirectFlags flag); +PostRedirectFlags& operator|=(PostRedirectFlags& lhs, PostRedirectFlags rhs); +PostRedirectFlags& operator&=(PostRedirectFlags& lhs, PostRedirectFlags rhs); +PostRedirectFlags& operator^=(PostRedirectFlags& lhs, PostRedirectFlags rhs); +bool any(PostRedirectFlags flag); + +class Redirect { + public: + /** + * The maximum number of redirects to follow. + * 0: Refuse any redirects. + * -1: Infinite number of redirects. + * Default: 50 + * https://curl.se/libcurl/c/CURLOPT_MAXREDIRS.html + **/ + // NOLINTNEXTLINE (google-runtime-int) + long maximum{50L}; + /** + * Follow 3xx redirects. + * Default: true + * https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html + **/ + bool follow{true}; + /** + * Continue to send authentication (user+password) credentials when following locations, even when hostname changed. + * Default: false + * https://curl.se/libcurl/c/CURLOPT_UNRESTRICTED_AUTH.html + **/ + bool cont_send_cred{false}; + /** + * Flags to control how to act after a redirect for a post request. + * Default: POST_ALL + **/ + PostRedirectFlags post_flags{PostRedirectFlags::POST_ALL}; + + Redirect() = default; + // NOLINTNEXTLINE (google-runtime-int) + Redirect(long p_maximum, bool p_follow, bool p_cont_send_cred, PostRedirectFlags p_post_flags) : maximum(p_maximum), follow(p_follow), cont_send_cred(p_cont_send_cred), post_flags(p_post_flags){}; + // NOLINTNEXTLINE (google-runtime-int) + explicit Redirect(long p_maximum) : maximum(p_maximum){}; + explicit Redirect(bool p_follow) : follow(p_follow){}; + Redirect(bool p_follow, bool p_cont_send_cred) : follow(p_follow), cont_send_cred(p_cont_send_cred){}; + explicit Redirect(PostRedirectFlags p_post_flags) : post_flags(p_post_flags){}; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/reserve_size.h b/Src/external_dependencies/cpr/include/cpr/reserve_size.h new file mode 100644 index 00000000..5eae4c80 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/reserve_size.h @@ -0,0 +1,17 @@ +#ifndef CPR_RESERVE_SIZE_H +#define CPR_RESERVE_SIZE_H + +#include <cstdint> + +namespace cpr { + +class ReserveSize { + public: + ReserveSize(const size_t _size) : size(_size) {} + + size_t size = 0; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/resolve.h b/Src/external_dependencies/cpr/include/cpr/resolve.h new file mode 100644 index 00000000..86a7c892 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/resolve.h @@ -0,0 +1,23 @@ +#ifndef CPR_RESOLVE_H +#define CPR_RESOLVE_H + +#include <string> +#include <set> + +namespace cpr { + class Resolve { + public: + std::string host; + std::string addr; + std::set<uint16_t> ports; + + Resolve(const std::string& host_param, const std::string& addr_param, const std::set<uint16_t>& ports_param = std::set<uint16_t>{80U, 443U}): host(host_param), addr(addr_param), ports(ports_param) { + if (this->ports.empty()) { + this->ports.insert(80U); + this->ports.insert(443U); + } + } + }; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/response.h b/Src/external_dependencies/cpr/include/cpr/response.h new file mode 100644 index 00000000..5c296daa --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/response.h @@ -0,0 +1,58 @@ +#ifndef CPR_RESPONSE_H +#define CPR_RESPONSE_H + +#include <cassert> +#include <cstdint> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "cpr/cert_info.h" +#include "cpr/cookies.h" +#include "cpr/cprtypes.h" +#include "cpr/error.h" +#include "cpr/ssl_options.h" +#include "cpr/util.h" + +namespace cpr { + +class MultiPerform; + +class Response { + private: + friend MultiPerform; + std::shared_ptr<CurlHolder> curl_{nullptr}; + + public: + // Ignored here since libcurl uses a long for this. + // NOLINTNEXTLINE(google-runtime-int) + long status_code{}; + std::string text{}; + Header header{}; + Url url{}; + double elapsed{}; + Cookies cookies{}; + Error error{}; + std::string raw_header{}; + std::string status_line{}; + std::string reason{}; + cpr_off_t uploaded_bytes{}; + cpr_off_t downloaded_bytes{}; + // Ignored here since libcurl uses a long for this. + // NOLINTNEXTLINE(google-runtime-int) + long redirect_count{}; + + Response() = default; + Response(std::shared_ptr<CurlHolder> curl, std::string&& p_text, std::string&& p_header_string, Cookies&& p_cookies, Error&& p_error); + std::vector<CertInfo> GetCertInfos(); + Response(const Response& other) = default; + Response(Response&& old) noexcept = default; + ~Response() noexcept = default; + + Response& operator=(Response&& old) noexcept = default; + Response& operator=(const Response& other) = default; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/session.h b/Src/external_dependencies/cpr/include/cpr/session.h new file mode 100644 index 00000000..7b500926 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/session.h @@ -0,0 +1,297 @@ +#ifndef CPR_SESSION_H +#define CPR_SESSION_H + +#include <cstdint> +#include <fstream> +#include <future> +#include <memory> +#include <queue> + +#include "cpr/accept_encoding.h" +#include "cpr/auth.h" +#include "cpr/bearer.h" +#include "cpr/body.h" +#include "cpr/callback.h" +#include "cpr/connect_timeout.h" +#include "cpr/cookies.h" +#include "cpr/cprtypes.h" +#include "cpr/curlholder.h" +#include "cpr/http_version.h" +#include "cpr/interface.h" +#include "cpr/limit_rate.h" +#include "cpr/local_port.h" +#include "cpr/local_port_range.h" +#include "cpr/low_speed.h" +#include "cpr/multipart.h" +#include "cpr/parameters.h" +#include "cpr/payload.h" +#include "cpr/proxies.h" +#include "cpr/proxyauth.h" +#include "cpr/range.h" +#include "cpr/redirect.h" +#include "cpr/reserve_size.h" +#include "cpr/resolve.h" +#include "cpr/response.h" +#include "cpr/ssl_options.h" +#include "cpr/timeout.h" +#include "cpr/unix_socket.h" +#include "cpr/user_agent.h" +#include "cpr/verbose.h" + +namespace cpr { + +using AsyncResponse = std::future<Response>; + +class Interceptor; +class MultiPerform; + +class Session : public std::enable_shared_from_this<Session> { + public: + Session(); + Session(const Session& other) = delete; + Session(Session&& old) = default; + + ~Session() = default; + + Session& operator=(Session&& old) noexcept = default; + Session& operator=(const Session& other) = delete; + + void SetUrl(const Url& url); + void SetParameters(const Parameters& parameters); + void SetParameters(Parameters&& parameters); + void SetHeader(const Header& header); + void UpdateHeader(const Header& header); + void SetTimeout(const Timeout& timeout); + void SetConnectTimeout(const ConnectTimeout& timeout); + void SetAuth(const Authentication& auth); +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 + void SetBearer(const Bearer& token); +#endif + void SetUserAgent(const UserAgent& ua); + void SetPayload(Payload&& payload); + void SetPayload(const Payload& payload); + void SetProxies(Proxies&& proxies); + void SetProxies(const Proxies& proxies); + void SetProxyAuth(ProxyAuthentication&& proxy_auth); + void SetProxyAuth(const ProxyAuthentication& proxy_auth); + void SetMultipart(Multipart&& multipart); + void SetMultipart(const Multipart& multipart); + void SetRedirect(const Redirect& redirect); + void SetCookies(const Cookies& cookies); + void SetBody(Body&& body); + void SetBody(const Body& body); + void SetLowSpeed(const LowSpeed& low_speed); + void SetVerifySsl(const VerifySsl& verify); + void SetUnixSocket(const UnixSocket& unix_socket); + void SetSslOptions(const SslOptions& options); + void SetReadCallback(const ReadCallback& read); + void SetHeaderCallback(const HeaderCallback& header); + void SetWriteCallback(const WriteCallback& write); + void SetProgressCallback(const ProgressCallback& progress); + void SetDebugCallback(const DebugCallback& debug); + void SetVerbose(const Verbose& verbose); + void SetInterface(const Interface& iface); + void SetLocalPort(const LocalPort& local_port); + void SetLocalPortRange(const LocalPortRange& local_port_range); + void SetHttpVersion(const HttpVersion& version); + void SetRange(const Range& range); + void SetResolve(const Resolve& resolve); + void SetResolves(const std::vector<Resolve>& resolves); + void SetMultiRange(const MultiRange& multi_range); + void SetReserveSize(const ReserveSize& reserve_size); + void SetAcceptEncoding(const AcceptEncoding& accept_encoding); + void SetAcceptEncoding(AcceptEncoding&& accept_encoding); + void SetLimitRate(const LimitRate& limit_rate); + + // Used in templated functions + void SetOption(const Url& url); + void SetOption(const Parameters& parameters); + void SetOption(Parameters&& parameters); + void SetOption(const Header& header); + void SetOption(const Timeout& timeout); + void SetOption(const ConnectTimeout& timeout); + void SetOption(const Authentication& auth); +// Only supported with libcurl >= 7.61.0. +// As an alternative use SetHeader and add the token manually. +#if LIBCURL_VERSION_NUM >= 0x073D00 + void SetOption(const Bearer& auth); +#endif + void SetOption(const UserAgent& ua); + void SetOption(Payload&& payload); + void SetOption(const Payload& payload); + void SetOption(const LimitRate& limit_rate); + void SetOption(Proxies&& proxies); + void SetOption(const Proxies& proxies); + void SetOption(ProxyAuthentication&& proxy_auth); + void SetOption(const ProxyAuthentication& proxy_auth); + void SetOption(Multipart&& multipart); + void SetOption(const Multipart& multipart); + void SetOption(const Redirect& redirect); + void SetOption(const Cookies& cookies); + void SetOption(Body&& body); + void SetOption(const Body& body); + void SetOption(const ReadCallback& read); + void SetOption(const HeaderCallback& header); + void SetOption(const WriteCallback& write); + void SetOption(const ProgressCallback& progress); + void SetOption(const DebugCallback& debug); + void SetOption(const LowSpeed& low_speed); + void SetOption(const VerifySsl& verify); + void SetOption(const Verbose& verbose); + void SetOption(const UnixSocket& unix_socket); + void SetOption(const SslOptions& options); + void SetOption(const Interface& iface); + void SetOption(const LocalPort& local_port); + void SetOption(const LocalPortRange& local_port_range); + void SetOption(const HttpVersion& version); + void SetOption(const Range& range); + void SetOption(const MultiRange& multi_range); + void SetOption(const ReserveSize& reserve_size); + void SetOption(const AcceptEncoding& accept_encoding); + void SetOption(AcceptEncoding&& accept_encoding); + void SetOption(const Resolve& resolve); + void SetOption(const std::vector<Resolve>& resolves); + + cpr_off_t GetDownloadFileLength(); + /** + * Attempt to preallocate enough memory for specified number of characters in the response string. + * Pass 0 to disable this behavior and let the response string be allocated dynamically on demand. + * + * Example: + * cpr::Session session; + * session.SetUrl(cpr::Url{"http://xxx/file"}); + * session.ResponseStringReserve(1024 * 512); // Reserve space for at least 1024 * 512 characters + * cpr::Response r = session.Get(); + **/ + void ResponseStringReserve(size_t size); + Response Delete(); + Response Download(const WriteCallback& write); + Response Download(std::ofstream& file); + Response Get(); + Response Head(); + Response Options(); + Response Patch(); + Response Post(); + Response Put(); + + AsyncResponse GetAsync(); + AsyncResponse DeleteAsync(); + AsyncResponse DownloadAsync(const WriteCallback& write); + AsyncResponse DownloadAsync(std::ofstream& file); + AsyncResponse HeadAsync(); + AsyncResponse OptionsAsync(); + AsyncResponse PatchAsync(); + AsyncResponse PostAsync(); + AsyncResponse PutAsync(); + + template <typename Then> + auto GetCallback(Then then); + template <typename Then> + auto PostCallback(Then then); + template <typename Then> + auto PutCallback(Then then); + template <typename Then> + auto HeadCallback(Then then); + template <typename Then> + auto DeleteCallback(Then then); + template <typename Then> + auto OptionsCallback(Then then); + template <typename Then> + auto PatchCallback(Then then); + + std::shared_ptr<CurlHolder> GetCurlHolder(); + std::string GetFullRequestUrl(); + + void PrepareDelete(); + void PrepareGet(); + void PrepareHead(); + void PrepareOptions(); + void PreparePatch(); + void PreparePost(); + void PreparePut(); + void PrepareDownload(const WriteCallback& write); + void PrepareDownload(std::ofstream& file); + Response Complete(CURLcode curl_error); + Response CompleteDownload(CURLcode curl_error); + + void AddInterceptor(const std::shared_ptr<Interceptor>& pinterceptor); + + private: + // Interceptors should be able to call the private procceed() function + friend Interceptor; + friend MultiPerform; + + bool hasBodyOrPayload_{false}; + bool chunkedTransferEncoding_{false}; + std::shared_ptr<CurlHolder> curl_; + Url url_; + Parameters parameters_; + Proxies proxies_; + ProxyAuthentication proxyAuth_; + Header header_; + AcceptEncoding acceptEncoding_; + /** + * Will be set by the read callback. + * Ensures that the "Transfer-Encoding" is set to "chunked", if not overriden in header_. + **/ + ReadCallback readcb_; + HeaderCallback headercb_; + WriteCallback writecb_; + ProgressCallback progresscb_; + DebugCallback debugcb_; + size_t response_string_reserve_size_{0}; + std::string response_string_; + std::string header_string_; + std::queue<std::shared_ptr<Interceptor>> interceptors_; + bool isUsedInMultiPerform{false}; + + Response makeDownloadRequest(); + Response makeRequest(); + Response proceed(); + void prepareCommon(); + void prepareCommonDownload(); + void SetHeaderInternal(); + std::shared_ptr<Session> GetSharedPtrFromThis(); + CURLcode DoEasyPerform(); +}; + +template <typename Then> +auto Session::GetCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Get()); }, std::move(then)); +} + +template <typename Then> +auto Session::PostCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Post()); }, std::move(then)); +} + +template <typename Then> +auto Session::PutCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Put()); }, std::move(then)); +} + +template <typename Then> +auto Session::HeadCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Head()); }, std::move(then)); +} + +template <typename Then> +auto Session::DeleteCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Delete()); }, std::move(then)); +} + +template <typename Then> +auto Session::OptionsCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Options()); }, std::move(then)); +} + +template <typename Then> +auto Session::PatchCallback(Then then) { + return async([shared_this = GetSharedPtrFromThis()](Then then_inner) { return then_inner(shared_this->Patch()); }, std::move(then)); +} + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/singleton.h b/Src/external_dependencies/cpr/include/cpr/singleton.h new file mode 100644 index 00000000..e2ea13bb --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/singleton.h @@ -0,0 +1,47 @@ +#ifndef CPR_SINGLETON_H +#define CPR_SINGLETON_H + +#include <mutex> + +#ifndef CPR_DISABLE_COPY +#define CPR_DISABLE_COPY(Class) \ + Class(const Class&) = delete; \ + Class& operator=(const Class&) = delete; +#endif + +#ifndef CPR_SINGLETON_DECL +#define CPR_SINGLETON_DECL(Class) \ + public: \ + static Class* GetInstance(); \ + static void ExitInstance(); \ + private: \ + CPR_DISABLE_COPY(Class) \ + static Class* s_pInstance; \ + static std::mutex s_mutex; +#endif + +#ifndef CPR_SINGLETON_IMPL +#define CPR_SINGLETON_IMPL(Class) \ + Class* Class::s_pInstance = nullptr; \ + std::mutex Class::s_mutex; \ + Class* Class::GetInstance() { \ + if (s_pInstance == nullptr) { \ + s_mutex.lock(); \ + if (s_pInstance == nullptr) { \ + s_pInstance = new Class; \ + } \ + s_mutex.unlock(); \ + } \ + return s_pInstance; \ + } \ + void Class::ExitInstance() { \ + s_mutex.lock(); \ + if (s_pInstance) { \ + delete s_pInstance; \ + s_pInstance = nullptr; \ + } \ + s_mutex.unlock(); \ + } +#endif + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/ssl_ctx.h b/Src/external_dependencies/cpr/include/cpr/ssl_ctx.h new file mode 100644 index 00000000..d495fb2b --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/ssl_ctx.h @@ -0,0 +1,26 @@ +#ifndef CPR_SSL_CTX_H +#define CPR_SSL_CTX_H + +#include "cpr/ssl_options.h" +#include <curl/curl.h> + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + +namespace cpr { + +/** + * This callback function loads a CA certificate from raw_cert_buf and gets called by libcurl + * just before the initialization of an SSL connection. + * The raw_cert_buf argument is set with the CURLOPT_SSL_CTX_DATA option and has to be a nul + * terminated buffer. + * + * Sources: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html + * https://curl.se/libcurl/c/CURLOPT_SSL_CTX_DATA.html + */ +CURLcode sslctx_function_load_ca_cert_from_buffer(CURL* curl, void* sslctx, void* raw_cert_buf); + +} // Namespace cpr + +#endif + +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/ssl_options.h b/Src/external_dependencies/cpr/include/cpr/ssl_options.h new file mode 100644 index 00000000..7a1784ef --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/ssl_options.h @@ -0,0 +1,622 @@ +#ifndef CPR_SSLOPTIONS_H +#define CPR_SSLOPTIONS_H + +#include <memory> +#include <string> +#include <vector> + +#include <cpr/filesystem.h> +#include <curl/curl.h> + +#include "cpr/util.h" +#include <utility> + +#define __LIBCURL_VERSION_GTE(major, minor) ((LIBCURL_VERSION_MAJOR > (major)) || ((LIBCURL_VERSION_MAJOR == (major)) && (LIBCURL_VERSION_MINOR >= (minor)))) +#define __LIBCURL_VERSION_LT(major, minor) ((LIBCURL_VERSION_MAJOR < (major)) || ((LIBCURL_VERSION_MAJOR == (major)) && (LIBCURL_VERSION_MINOR < (minor)))) + +#ifndef SUPPORT_ALPN +#define SUPPORT_ALPN __LIBCURL_VERSION_GTE(7, 36) +#endif +#ifndef SUPPORT_NPN +#define SUPPORT_NPN __LIBCURL_VERSION_GTE(7, 36) +#endif + +#ifndef SUPPORT_SSLv2 +#define SUPPORT_SSLv2 __LIBCURL_VERSION_LT(7, 19) +#endif +#ifndef SUPPORT_SSLv3 +#define SUPPORT_SSLv3 __LIBCURL_VERSION_LT(7, 39) +#endif +#ifndef SUPPORT_TLSv1_0 +#define SUPPORT_TLSv1_0 __LIBCURL_VERSION_GTE(7, 34) +#endif +#ifndef SUPPORT_TLSv1_1 +#define SUPPORT_TLSv1_1 __LIBCURL_VERSION_GTE(7, 34) +#endif +#ifndef SUPPORT_TLSv1_2 +#define SUPPORT_TLSv1_2 __LIBCURL_VERSION_GTE(7, 34) +#endif +#ifndef SUPPORT_TLSv1_3 +#define SUPPORT_TLSv1_3 __LIBCURL_VERSION_GTE(7, 52) +#endif +#ifndef SUPPORT_MAX_TLS_VERSION +#define SUPPORT_MAX_TLS_VERSION __LIBCURL_VERSION_GTE(7, 54) +#endif +#ifndef SUPPORT_MAX_TLSv1_1 +#define SUPPORT_MAX_TLSv1_1 __LIBCURL_VERSION_GTE(7, 54) +#endif +#ifndef SUPPORT_MAX_TLSv1_2 +#define SUPPORT_MAX_TLSv1_2 __LIBCURL_VERSION_GTE(7, 54) +#endif +#ifndef SUPPORT_MAX_TLSv1_3 +#define SUPPORT_MAX_TLSv1_3 __LIBCURL_VERSION_GTE(7, 54) +#endif +#ifndef SUPPORT_TLSv13_CIPHERS +#define SUPPORT_TLSv13_CIPHERS __LIBCURL_VERSION_GTE(7, 61) +#endif +#ifndef SUPPORT_SESSIONID_CACHE +#define SUPPORT_SESSIONID_CACHE __LIBCURL_VERSION_GTE(7, 16) +#endif +#ifndef SUPPORT_SSL_FALSESTART +#define SUPPORT_SSL_FALSESTART __LIBCURL_VERSION_GTE(7, 42) +#endif +#ifndef SUPPORT_SSL_NO_REVOKE +#define SUPPORT_SSL_NO_REVOKE __LIBCURL_VERSION_GTE(7, 44) +#endif +#ifndef SUPPORT_CURLOPT_SSLKEY_BLOB +#define SUPPORT_CURLOPT_SSLKEY_BLOB __LIBCURL_VERSION_GTE(7, 71) +#endif +#ifndef SUPPORT_CURLOPT_SSL_CTX_FUNCTION +#define SUPPORT_CURLOPT_SSL_CTX_FUNCTION __LIBCURL_VERSION_GTE(7, 11) +#endif + +namespace cpr { + +class VerifySsl { + public: + VerifySsl() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + VerifySsl(bool p_verify) : verify(p_verify) {} + + explicit operator bool() const { + return verify; + } + + bool verify = true; +}; + +namespace ssl { + +// set SSL client certificate +class CertFile { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CertFile(fs::path&& p_filename) : filename(std::move(p_filename)) {} + + virtual ~CertFile() = default; + + const fs::path filename; + + virtual const char* GetCertType() const { + return "PEM"; + } +}; + +using PemCert = CertFile; + +class DerCert : public CertFile { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + DerCert(fs::path&& p_filename) : CertFile(std::move(p_filename)) {} + + virtual ~DerCert() = default; + + const char* GetCertType() const override { + return "DER"; + } +}; + +// specify private keyfile for TLS and SSL client cert +class KeyFile { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + KeyFile(fs::path&& p_filename) : filename(std::move(p_filename)) {} + + template <typename FileType, typename PassType> + KeyFile(FileType&& p_filename, PassType p_password) : filename(std::forward<FileType>(p_filename)), password(std::move(p_password)) {} + + virtual ~KeyFile() { + util::secureStringClear(password); + } + + fs::path filename; + std::string password; + + virtual const char* GetKeyType() const { + return "PEM"; + } +}; + +#if SUPPORT_CURLOPT_SSLKEY_BLOB +class KeyBlob { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + KeyBlob(std::string&& p_blob) : blob(std::move(p_blob)) {} + + template <typename BlobType, typename PassType> + KeyBlob(BlobType&& p_blob, PassType p_password) : blob(std::forward<BlobType>(p_blob)), password(std::move(p_password)) {} + + virtual ~KeyBlob() { + util::secureStringClear(password); + } + + std::string blob; + std::string password; + + virtual const char* GetKeyType() const { + return "PEM"; + } +}; +#endif + +using PemKey = KeyFile; + +class DerKey : public KeyFile { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + DerKey(fs::path&& p_filename) : KeyFile(std::move(p_filename)) {} + + template <typename FileType, typename PassType> + DerKey(FileType&& p_filename, PassType p_password) : KeyFile(std::forward<FileType>(p_filename), std::move(p_password)) {} + + virtual ~DerKey() = default; + + const char* GetKeyType() const override { + return "DER"; + } +}; + +class PinnedPublicKey { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + PinnedPublicKey(std::string&& p_pinned_public_key) : pinned_public_key(std::move(p_pinned_public_key)) {} + + const std::string pinned_public_key; +}; + +#if SUPPORT_ALPN +// This option enables/disables ALPN in the SSL handshake (if the SSL backend libcurl is built to +// use supports it), which can be used to negotiate http2. +class ALPN { + public: + ALPN() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ALPN(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = true; +}; +#endif // SUPPORT_ALPN + +#if SUPPORT_NPN +// This option enables/disables NPN in the SSL handshake (if the SSL backend libcurl is built to +// use supports it), which can be used to negotiate http2. +class NPN { + public: + NPN() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + NPN(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = true; +}; +#endif // SUPPORT_NPN + +// This option determines whether libcurl verifies that the server cert is for the server it is +// known as. +class VerifyHost { + public: + VerifyHost() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + VerifyHost(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = true; +}; + +// This option determines whether libcurl verifies the authenticity of the peer's certificate. +class VerifyPeer { + public: + VerifyPeer() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + VerifyPeer(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = true; +}; + +// This option determines whether libcurl verifies the status of the server cert using the +// "Certificate Status Request" TLS extension (aka. OCSP stapling). +class VerifyStatus { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + VerifyStatus(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = false; +}; + +// TLS v1.0 or later +struct TLSv1 {}; +#if SUPPORT_SSLv2 +// SSL v2 (but not SSLv3) +struct SSLv2 {}; +#endif +#if SUPPORT_SSLv3 +// SSL v3 (but not SSLv2) +struct SSLv3 {}; +#endif +#if SUPPORT_TLSv1_0 +// TLS v1.0 or later (Added in 7.34.0) +struct TLSv1_0 {}; +#endif +#if SUPPORT_TLSv1_1 +// TLS v1.1 or later (Added in 7.34.0) +struct TLSv1_1 {}; +#endif +#if SUPPORT_TLSv1_2 +// TLS v1.2 or later (Added in 7.34.0) +struct TLSv1_2 {}; +#endif +#if SUPPORT_TLSv1_3 +// TLS v1.3 or later (Added in 7.52.0) +struct TLSv1_3 {}; +#endif +#if SUPPORT_MAX_TLS_VERSION +// The flag defines the maximum supported TLS version by libcurl, or the default value from the SSL +// library is used. +struct MaxTLSVersion {}; +#endif +#if SUPPORT_MAX_TLSv1_0 +// The flag defines maximum supported TLS version as TLSv1.0. (Added in 7.54.0) +struct MaxTLSv1_0 {}; +#endif +#if SUPPORT_MAX_TLSv1_1 +// The flag defines maximum supported TLS version as TLSv1.1. (Added in 7.54.0) +struct MaxTLSv1_1 {}; +#endif +#if SUPPORT_MAX_TLSv1_2 +// The flag defines maximum supported TLS version as TLSv1.2. (Added in 7.54.0) +struct MaxTLSv1_2 {}; +#endif +#if SUPPORT_MAX_TLSv1_3 +// The flag defines maximum supported TLS version as TLSv1.3. (Added in 7.54.0) +struct MaxTLSv1_3 {}; +#endif + +// path to Certificate Authority (CA) bundle +class CaInfo { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CaInfo(fs::path&& p_filename) : filename(std::move(p_filename)) {} + + fs::path filename; +}; + +// specify directory holding CA certificates +class CaPath { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CaPath(fs::path&& p_filename) : filename(std::move(p_filename)) {} + + fs::path filename; +}; + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +class CaBuffer { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CaBuffer(std::string&& p_buffer) : buffer(std::move(p_buffer)) {} + + const std::string buffer; +}; +#endif + +// specify a Certificate Revocation List file +class Crl { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Crl(fs::path&& p_filename) : filename(std::move(p_filename)) {} + + fs::path filename; +}; + +// specify ciphers to use for TLS +class Ciphers { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Ciphers(std::string&& p_ciphers) : ciphers(std::move(p_ciphers)) {} + + std::string ciphers; +}; + +#if SUPPORT_TLSv13_CIPHERS +// specify ciphers suites to use for TLS 1.3 +class TLS13_Ciphers { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + TLS13_Ciphers(std::string&& p_ciphers) : ciphers(std::move(p_ciphers)) {} + + std::string ciphers; +}; +#endif + +#if SUPPORT_SESSIONID_CACHE +// enable/disable use of the SSL session-ID cache +class SessionIdCache { + public: + SessionIdCache() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + SessionIdCache(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = true; +}; +#endif + +#if SUPPORT_SSL_FALSESTART +class SslFastStart { + public: + SslFastStart() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + SslFastStart(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = false; +}; +#endif + +class NoRevoke { + public: + NoRevoke() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + NoRevoke(bool p_enabled) : enabled(p_enabled) {} + + explicit operator bool() const { + return enabled; + } + + bool enabled = false; +}; + +} // namespace ssl + +struct SslOptions { + // We don't use fs::path here, as this leads to problems using windows + std::string cert_file; + std::string cert_type; + // We don't use fs::path here, as this leads to problems using windows + std::string key_file; +#if SUPPORT_CURLOPT_SSLKEY_BLOB + std::string key_blob; +#endif + std::string key_type; + std::string key_pass; + std::string pinned_public_key; +#if SUPPORT_ALPN + bool enable_alpn = true; +#endif // SUPPORT_ALPN +#if SUPPORT_NPN + bool enable_npn = true; +#endif // SUPPORT_ALPN + bool verify_host = true; + bool verify_peer = true; + bool verify_status = false; + int ssl_version = CURL_SSLVERSION_DEFAULT; +#if SUPPORT_SSL_NO_REVOKE + bool ssl_no_revoke = false; +#endif +#if SUPPORT_MAX_TLS_VERSION + int max_version = CURL_SSLVERSION_MAX_DEFAULT; +#endif + // We don't use fs::path here, as this leads to problems using windows + std::string ca_info; + // We don't use fs::path here, as this leads to problems using windows + std::string ca_path; +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + std::string ca_buffer; +#endif + // We don't use fs::path here, as this leads to problems using windows + std::string crl_file; + std::string ciphers; +#if SUPPORT_TLSv13_CIPHERS + std::string tls13_ciphers; +#endif +#if SUPPORT_SESSIONID_CACHE + bool session_id_cache = true; +#endif + + ~SslOptions() noexcept { +#if SUPPORT_CURLOPT_SSLKEY_BLOB + util::secureStringClear(key_blob); +#endif + util::secureStringClear(key_pass); + } + + void SetOption(const ssl::CertFile& opt) { + cert_file = opt.filename.string(); + cert_type = opt.GetCertType(); + } + void SetOption(const ssl::KeyFile& opt) { + key_file = opt.filename.string(); + key_type = opt.GetKeyType(); + key_pass = opt.password; + } +#if SUPPORT_CURLOPT_SSLKEY_BLOB + void SetOption(const ssl::KeyBlob& opt) { + key_blob = opt.blob; + key_type = opt.GetKeyType(); + key_pass = opt.password; + } +#endif + void SetOption(const ssl::PinnedPublicKey& opt) { + pinned_public_key = opt.pinned_public_key; + } + +#if SUPPORT_ALPN + void SetOption(const ssl::ALPN& opt) { + enable_alpn = opt.enabled; + } +#endif // SUPPORT_ALPN +#if SUPPORT_NPN + void SetOption(const ssl::NPN& opt) { + enable_npn = opt.enabled; + } +#endif // SUPPORT_NPN + void SetOption(const ssl::VerifyHost& opt) { + verify_host = opt.enabled; + } + void SetOption(const ssl::VerifyPeer& opt) { + verify_peer = opt.enabled; + } + void SetOption(const ssl::VerifyStatus& opt) { + verify_status = opt.enabled; + } + void SetOption(const ssl::TLSv1& /*opt*/) { + ssl_version = CURL_SSLVERSION_TLSv1; + } +#if SUPPORT_SSL_NO_REVOKE + void SetOption(const ssl::NoRevoke& opt) { + ssl_no_revoke = opt.enabled; + } +#endif +#if SUPPORT_SSLv2 + void SetOption(const ssl::SSLv2& /*opt*/) { + ssl_version = CURL_SSLVERSION_SSLv2; + } +#endif +#if SUPPORT_SSLv3 + void SetOption(const ssl::SSLv3& /*opt*/) { + ssl_version = CURL_SSLVERSION_SSLv3; + } +#endif +#if SUPPORT_TLSv1_0 + void SetOption(const ssl::TLSv1_0& /*opt*/) { + ssl_version = CURL_SSLVERSION_TLSv1_0; + } +#endif +#if SUPPORT_TLSv1_1 + void SetOption(const ssl::TLSv1_1& /*opt*/) { + ssl_version = CURL_SSLVERSION_TLSv1_1; + } +#endif +#if SUPPORT_TLSv1_2 + void SetOption(const ssl::TLSv1_2& /*opt*/) { + ssl_version = CURL_SSLVERSION_TLSv1_2; + } +#endif +#if SUPPORT_TLSv1_3 + void SetOption(const ssl::TLSv1_3& /*opt*/) { + ssl_version = CURL_SSLVERSION_TLSv1_3; + } +#endif +#if SUPPORT_MAX_TLS_VERSION + void SetOption(const ssl::MaxTLSVersion& /*opt*/) { + max_version = CURL_SSLVERSION_DEFAULT; + } +#endif +#if SUPPORT_MAX_TLSv1_0 + void SetOption(const ssl::MaxTLSv1_0& opt) { + max_version = CURL_SSLVERSION_MAX_TLSv1_0; + } +#endif +#if SUPPORT_MAX_TLSv1_1 + void SetOption(const ssl::MaxTLSv1_1& /*opt*/) { + max_version = CURL_SSLVERSION_MAX_TLSv1_1; + } +#endif +#if SUPPORT_MAX_TLSv1_2 + void SetOption(const ssl::MaxTLSv1_2& /*opt*/) { + max_version = CURL_SSLVERSION_MAX_TLSv1_2; + } +#endif +#if SUPPORT_MAX_TLSv1_3 + void SetOption(const ssl::MaxTLSv1_3& /*opt*/) { + max_version = CURL_SSLVERSION_MAX_TLSv1_3; + } +#endif + void SetOption(const ssl::CaInfo& opt) { + ca_info = opt.filename.string(); + } + void SetOption(const ssl::CaPath& opt) { + ca_path = opt.filename.string(); + } +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION + void SetOption(const ssl::CaBuffer& opt) { + ca_buffer = opt.buffer; + } +#endif + void SetOption(const ssl::Crl& opt) { + crl_file = opt.filename.string(); + } + void SetOption(const ssl::Ciphers& opt) { + ciphers = opt.ciphers; + } +#if SUPPORT_TLSv13_CIPHERS + void SetOption(const ssl::TLS13_Ciphers& opt) { + tls13_ciphers = opt.ciphers; + } +#endif +#if SUPPORT_SESSIONID_CACHE + void SetOption(const ssl::SessionIdCache& opt) { + session_id_cache = opt.enabled; + } +#endif +}; + +namespace priv { + +template <typename T> +void set_ssl_option(SslOptions& opts, T&& t) { + opts.SetOption(std::forward<T>(t)); +} + +template <typename T, typename... Ts> +void set_ssl_option(SslOptions& opts, T&& t, Ts&&... ts) { + set_ssl_option(opts, std::forward<T>(t)); + set_ssl_option(opts, std::move(ts)...); +} + +} // namespace priv + +template <typename... Ts> +SslOptions Ssl(Ts&&... ts) { + SslOptions opts; + priv::set_ssl_option(opts, std::move(ts)...); + return opts; +} + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/status_codes.h b/Src/external_dependencies/cpr/include/cpr/status_codes.h new file mode 100644 index 00000000..6c7acd6b --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/status_codes.h @@ -0,0 +1,100 @@ +#ifndef _CPR_STATUS_CODES +#define _CPR_STATUS_CODES +#include <cstdint> +namespace cpr { +namespace status { +// Information responses +constexpr std::int32_t HTTP_CONTINUE = 100; +constexpr std::int32_t HTTP_SWITCHING_PROTOCOL = 101; +constexpr std::int32_t HTTP_PROCESSING = 102; +constexpr std::int32_t HTTP_EARLY_HINTS = 103; +// Successful responses +constexpr std::int32_t HTTP_OK = 200; +constexpr std::int32_t HTTP_CREATED = 201; +constexpr std::int32_t HTTP_ACCEPTED = 202; +constexpr std::int32_t HTTP_NON_AUTHORITATIVE_INFORMATION = 203; +constexpr std::int32_t HTTP_NO_CONTENT = 204; +constexpr std::int32_t HTTP_RESET_CONTENT = 205; +constexpr std::int32_t HTTP_PARTIAL_CONTENT = 206; +constexpr std::int32_t HTTP_MULTI_STATUS = 207; +constexpr std::int32_t HTTP_ALREADY_REPORTED = 208; +constexpr std::int32_t HTTP_IM_USED = 226; +// Redirection messages +constexpr std::int32_t HTTP_MULTIPLE_CHOICE = 300; +constexpr std::int32_t HTTP_MOVED_PERMANENTLY = 301; +constexpr std::int32_t HTTP_FOUND = 302; +constexpr std::int32_t HTTP_SEE_OTHER = 303; +constexpr std::int32_t HTTP_NOT_MODIFIED = 304; +constexpr std::int32_t HTTP_USE_PROXY = 305; +constexpr std::int32_t HTTP_UNUSED = 306; +constexpr std::int32_t HTTP_TEMPORARY_REDIRECT = 307; +constexpr std::int32_t HTTP_PERMANENT_REDIRECT = 308; +// Client error responses +constexpr std::int32_t HTTP_BAD_REQUEST = 400; +constexpr std::int32_t HTTP_UNAUTHORIZED = 401; +constexpr std::int32_t HTTP_PAYMENT_REQUIRED = 402; +constexpr std::int32_t HTTP_FORBIDDEN = 403; +constexpr std::int32_t HTTP_NOT_FOUND = 404; +constexpr std::int32_t HTTP_METHOD_NOT_ALLOWED = 405; +constexpr std::int32_t HTTP_NOT_ACCEPTABLE = 406; +constexpr std::int32_t HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; +constexpr std::int32_t HTTP_REQUEST_TIMEOUT = 408; +constexpr std::int32_t HTTP_CONFLICT = 409; +constexpr std::int32_t HTTP_GONE = 410; +constexpr std::int32_t HTTP_LENGTH_REQUIRED = 411; +constexpr std::int32_t HTTP_PRECONDITION_FAILED = 412; +constexpr std::int32_t HTTP_PAYLOAD_TOO_LARGE = 413; +constexpr std::int32_t HTTP_URI_TOO_LONG = 414; +constexpr std::int32_t HTTP_UNSUPPORTED_MEDIA_TYPE = 415; +constexpr std::int32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; +constexpr std::int32_t HTTP_EXPECTATION_FAILED = 417; +constexpr std::int32_t HTTP_IM_A_TEAPOT = 418; +constexpr std::int32_t HTTP_MISDIRECTED_REQUEST = 421; +constexpr std::int32_t HTTP_UNPROCESSABLE_ENTITY = 422; +constexpr std::int32_t HTTP_LOCKED = 423; +constexpr std::int32_t HTTP_FAILED_DEPENDENCY = 424; +constexpr std::int32_t HTTP_TOO_EARLY = 425; +constexpr std::int32_t HTTP_UPGRADE_REQUIRED = 426; +constexpr std::int32_t HTTP_PRECONDITION_REQUIRED = 428; +constexpr std::int32_t HTTP_TOO_MANY_REQUESTS = 429; +constexpr std::int32_t HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; +constexpr std::int32_t HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; +// Server response errors +constexpr std::int32_t HTTP_INTERNAL_SERVER_ERROR = 500; +constexpr std::int32_t HTTP_NOT_IMPLEMENTED = 501; +constexpr std::int32_t HTTP_BAD_GATEWAY = 502; +constexpr std::int32_t HTTP_SERVICE_UNAVAILABLE = 503; +constexpr std::int32_t HTTP_GATEWAY_TIMEOUT = 504; +constexpr std::int32_t HTTP_HTTP_VERSION_NOT_SUPPORTED = 505; +constexpr std::int32_t HTTP_VARIANT_ALSO_NEGOTIATES = 506; +constexpr std::int32_t HTTP_INSUFFICIENT_STORAGE = 507; +constexpr std::int32_t HTTP_LOOP_DETECTED = 508; +constexpr std::int32_t HTTP_NOT_EXTENDED = 510; +constexpr std::int32_t HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; + +constexpr std::int32_t INFO_CODE_OFFSET = 100; +constexpr std::int32_t SUCCESS_CODE_OFFSET = 200; +constexpr std::int32_t REDIRECT_CODE_OFFSET = 300; +constexpr std::int32_t CLIENT_ERROR_CODE_OFFSET = 400; +constexpr std::int32_t SERVER_ERROR_CODE_OFFSET = 500; +constexpr std::int32_t MISC_CODE_OFFSET = 600; + +constexpr bool is_informational(const std::int32_t code) { + return (code >= INFO_CODE_OFFSET && code < SUCCESS_CODE_OFFSET); +} +constexpr bool is_success(const std::int32_t code) { + return (code >= SUCCESS_CODE_OFFSET && code < REDIRECT_CODE_OFFSET); +} +constexpr bool is_redirect(const std::int32_t code) { + return (code >= REDIRECT_CODE_OFFSET && code < CLIENT_ERROR_CODE_OFFSET); +} +constexpr bool is_client_error(const std::int32_t code) { + return (code >= CLIENT_ERROR_CODE_OFFSET && code < SERVER_ERROR_CODE_OFFSET); +} +constexpr bool is_server_error(const std::int32_t code) { + return (code >= SERVER_ERROR_CODE_OFFSET && code < MISC_CODE_OFFSET); +} + +} // namespace status +} // namespace cpr +#endif
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/include/cpr/threadpool.h b/Src/external_dependencies/cpr/include/cpr/threadpool.h new file mode 100644 index 00000000..bb7e7f21 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/threadpool.h @@ -0,0 +1,122 @@ +#ifndef CPR_THREAD_POOL_H +#define CPR_THREAD_POOL_H + +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <functional> +#include <future> +#include <list> +#include <memory> +#include <mutex> +#include <queue> +#include <thread> +#include <utility> + +#define CPR_DEFAULT_THREAD_POOL_MAX_THREAD_NUM std::thread::hardware_concurrency() + +constexpr size_t CPR_DEFAULT_THREAD_POOL_MIN_THREAD_NUM = 1; +constexpr std::chrono::milliseconds CPR_DEFAULT_THREAD_POOL_MAX_IDLE_TIME{60000}; + +namespace cpr { + +class ThreadPool { + public: + using Task = std::function<void()>; + + explicit ThreadPool(size_t min_threads = CPR_DEFAULT_THREAD_POOL_MIN_THREAD_NUM, size_t max_threads = CPR_DEFAULT_THREAD_POOL_MAX_THREAD_NUM, std::chrono::milliseconds max_idle_ms = CPR_DEFAULT_THREAD_POOL_MAX_IDLE_TIME); + + virtual ~ThreadPool(); + + void SetMinThreadNum(size_t min_threads) { + min_thread_num = min_threads; + } + void SetMaxThreadNum(size_t max_threads) { + max_thread_num = max_threads; + } + void SetMaxIdleTime(std::chrono::milliseconds ms) { + max_idle_time = ms; + } + size_t GetCurrentThreadNum() { + return cur_thread_num; + } + size_t GetIdleThreadNum() { + return idle_thread_num; + } + bool IsStarted() { + return status != STOP; + } + bool IsStopped() { + return status == STOP; + } + + int Start(size_t start_threads = 0); + int Stop(); + int Pause(); + int Resume(); + int Wait(); + + /** + * Return a future, calling future.get() will wait task done and return RetType. + * Submit(fn, args...) + * Submit(std::bind(&Class::mem_fn, &obj)) + * Submit(std::mem_fn(&Class::mem_fn, &obj)) + **/ + template <class Fn, class... Args> + auto Submit(Fn&& fn, Args&&... args) { + if (status == STOP) { + Start(); + } + if (idle_thread_num <= 0 && cur_thread_num < max_thread_num) { + CreateThread(); + } + using RetType = decltype(fn(args...)); + auto task = std::make_shared<std::packaged_task<RetType()> >(std::bind(std::forward<Fn>(fn), std::forward<Args>(args)...)); + std::future<RetType> future = task->get_future(); + { + std::lock_guard<std::mutex> locker(task_mutex); + tasks.emplace([task] { (*task)(); }); + } + + task_cond.notify_one(); + return future; + } + + private: + bool CreateThread(); + void AddThread(std::thread* thread); + void DelThread(std::thread::id id); + + public: + size_t min_thread_num; + size_t max_thread_num; + std::chrono::milliseconds max_idle_time; + + private: + enum Status { + STOP, + RUNNING, + PAUSE, + }; + + struct ThreadData { + std::shared_ptr<std::thread> thread; + std::thread::id id; + Status status; + time_t start_time; + time_t stop_time; + }; + + std::atomic<Status> status; + std::atomic<size_t> cur_thread_num; + std::atomic<size_t> idle_thread_num; + std::list<ThreadData> threads; + std::mutex thread_mutex; + std::queue<Task> tasks; + std::mutex task_mutex; + std::condition_variable task_cond; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/timeout.h b/Src/external_dependencies/cpr/include/cpr/timeout.h new file mode 100644 index 00000000..492470ec --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/timeout.h @@ -0,0 +1,27 @@ +#ifndef CPR_TIMEOUT_H +#define CPR_TIMEOUT_H + +#include <chrono> +#include <cstdint> + +namespace cpr { + +class Timeout { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Timeout(const std::chrono::milliseconds& duration) : ms{duration} {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Timeout(const std::int32_t& milliseconds) : Timeout{std::chrono::milliseconds(milliseconds)} {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Timeout(const std::chrono::seconds& duration) : ms{1000 * duration.count()} {} + + // No way around since curl uses a long here. + // NOLINTNEXTLINE(google-runtime-int) + long Milliseconds() const; + + std::chrono::milliseconds ms; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/unix_socket.h b/Src/external_dependencies/cpr/include/cpr/unix_socket.h new file mode 100644 index 00000000..9d4d77c0 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/unix_socket.h @@ -0,0 +1,21 @@ +#ifndef CPR_UNIX_SOCKET_H +#define CPR_UNIX_SOCKET_H + +#include <string> + +namespace cpr { + +class UnixSocket { + public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + UnixSocket(std::string&& unix_socket) : unix_socket_(std::move(unix_socket)) {} + + const char* GetUnixSocketString() const noexcept; + + private: + const std::string unix_socket_; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/user_agent.h b/Src/external_dependencies/cpr/include/cpr/user_agent.h new file mode 100644 index 00000000..369a80d9 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/user_agent.h @@ -0,0 +1,33 @@ +#ifndef CPR_USERAGENT_H +#define CPR_USERAGENT_H + +#include <initializer_list> +#include <string> + +#include "cpr/cprtypes.h" + +namespace cpr { +class UserAgent : public StringHolder<UserAgent> { + public: + UserAgent() : StringHolder<UserAgent>() {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + UserAgent(const std::string& useragent) : StringHolder<UserAgent>(useragent) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + UserAgent(std::string&& useragent) : StringHolder<UserAgent>(std::move(useragent)) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + UserAgent(std::string_view useragent) : StringHolder<UserAgent>(useragent) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + UserAgent(const char* useragent) : StringHolder<UserAgent>(useragent) {} + UserAgent(const char* str, size_t len) : StringHolder<UserAgent>(str, len) {} + UserAgent(const std::initializer_list<std::string> args) : StringHolder<UserAgent>(args) {} + UserAgent(const UserAgent& other) = default; + UserAgent(UserAgent&& old) noexcept = default; + ~UserAgent() override = default; + + UserAgent& operator=(UserAgent&& old) noexcept = default; + UserAgent& operator=(const UserAgent& other) = default; +}; + +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/util.h b/Src/external_dependencies/cpr/include/cpr/util.h new file mode 100644 index 00000000..f35ad473 --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/util.h @@ -0,0 +1,45 @@ +#ifndef CPR_UTIL_H +#define CPR_UTIL_H + +#include <fstream> +#include <string> +#include <vector> + +#include "cpr/callback.h" +#include "cpr/cookies.h" +#include "cpr/cprtypes.h" +#include "cpr/curlholder.h" + +namespace cpr { +namespace util { + +Header parseHeader(const std::string& headers, std::string* status_line = nullptr, std::string* reason = nullptr); +Cookies parseCookies(curl_slist* raw_cookies); +size_t readUserFunction(char* ptr, size_t size, size_t nitems, const ReadCallback* read); +size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCallback* header); +size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data); +size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file); +size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write); +#if LIBCURL_VERSION_NUM < 0x072000 +int progressUserFunction(const ProgressCallback* progress, double dltotal, double dlnow, double ultotal, double ulnow); +#else +int progressUserFunction(const ProgressCallback* progress, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); +#endif +int debugUserFunction(CURL* handle, curl_infotype type, char* data, size_t size, const DebugCallback* debug); +std::vector<std::string> split(const std::string& to_split, char delimiter); +std::string urlEncode(const std::string& s); +std::string urlDecode(const std::string& s); + +/** + * Override the content of the provided string to hide sensitive data. The + * string content after invocation is undefined. The string size is reset to zero. + * impl. based on: + * https://github.com/ojeda/secure_clear/blob/master/example-implementation/secure_clear.h + **/ +void secureStringClear(std::string& s); +bool isTrue(const std::string& s); + +} // namespace util +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/include/cpr/verbose.h b/Src/external_dependencies/cpr/include/cpr/verbose.h new file mode 100644 index 00000000..2bf0df8f --- /dev/null +++ b/Src/external_dependencies/cpr/include/cpr/verbose.h @@ -0,0 +1,18 @@ +#ifndef CPR_VERBOSE_H_ +#define CPR_VERBOSE_H_ + +namespace cpr { + +class Verbose { + public: + Verbose() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + Verbose(const bool p_verbose) : verbose{p_verbose} {} + + bool verbose = true; +}; + +} // namespace cpr + + +#endif /* CPR_VERBOSE_H_ */ diff --git a/Src/external_dependencies/cpr/nuget/build/native/libcpr.props b/Src/external_dependencies/cpr/nuget/build/native/libcpr.props new file mode 100644 index 00000000..ec62c503 --- /dev/null +++ b/Src/external_dependencies/cpr/nuget/build/native/libcpr.props @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" + ToolsVersion="15.0"> + <PropertyGroup> + <LibraryType Condition="'$(Configuration)'=='Debug'">mdd</LibraryType> + <LibraryType Condition="'$(Configuration)'=='Release'">md</LibraryType> + </PropertyGroup> + <ItemGroup> + <CprLibs Include="$(MSBuildThisFileDirectory)\$(Platform)\$(Configuration)\lib\*.lib" /> + </ItemGroup> + <ItemGroup> + <CprDlls Include="$(MSBuildThisFileDirectory)\$(Platform)\$(Configuration)\bin\*.dll" /> + <None Include="@(CprDlls)"> + <Link>%(RecursiveDir)%(FileName)%(Extension)</Link> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> + <PropertyGroup> + <CprLibraries>@(CprLibs)</CprLibraries> + </PropertyGroup> + <ItemDefinitionGroup> + <ClCompile> + <AdditionalIncludeDirectories>$(MSBuildThisFileDirectory)\$(Platform)\$(Configuration)\include</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <AdditionalDependencies>$(CprLibraries);%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> +</Project>
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/nuget/build/native/libcpr.targets b/Src/external_dependencies/cpr/nuget/build/native/libcpr.targets new file mode 100644 index 00000000..700232bf --- /dev/null +++ b/Src/external_dependencies/cpr/nuget/build/native/libcpr.targets @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Target Name="PlatformCheck" BeforeTargets="InjectReference" Condition="(('$(Platform)' != 'x86') AND ('$(Platform)' != 'x64')) AND ('$(Platform)' != 'Win32'))"> + <Error Text="$(MSBuildThisFileName) does not work correctly on this platform: '$(Platform)'. You need to specify platform x86, x64, or Win32." /> + </Target> +</Project>
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/nuget/libcpr.nuspec b/Src/external_dependencies/cpr/nuget/libcpr.nuspec new file mode 100644 index 00000000..e47a8c08 --- /dev/null +++ b/Src/external_dependencies/cpr/nuget/libcpr.nuspec @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> + <metadata> + <id>libcpr</id> + <version>$VERSION$</version> + <title>C++ Requests: Curl for People</title> + <authors>Simon Berger</authors> + <owners>Fabian Sauter, Kilian Traub, many other libcpr contributors</owners> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <license type="expression">MIT</license> + <icon>resources/cpr.png</icon> + <readme>README.md</readme> + <projectUrl>https://github.com/libcpr</projectUrl> + <description>C++ Requests: Curl for People, a spiritual port of Python Requests.</description> + <tags>Native, native</tags> + <language>english</language> + <repository type="git" url="https://github.com/libcpr/cpr" branch="master" commit="$COMMIT_HASH$" /> + </metadata> +</package>
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/nuget/resources/cpr.png b/Src/external_dependencies/cpr/nuget/resources/cpr.png Binary files differnew file mode 100644 index 00000000..61ad7901 --- /dev/null +++ b/Src/external_dependencies/cpr/nuget/resources/cpr.png diff --git a/Src/external_dependencies/cpr/package-build/build-package.sh b/Src/external_dependencies/cpr/package-build/build-package.sh new file mode 100644 index 00000000..eef22da9 --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/build-package.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SRC_DIR=$1 + +LIB='libcpr' + +LIB_DIR="${LIB}_${VERSION}" +DEBIAN_DIR="${LIB_DIR}/debian" + +ARCHIVE_NAME="$LIB_DIR.orig.tar.gz" + +echo -e "Preparing tar archive and directory\n" +cp -r $SRC_DIR $LIB_DIR + +tar --exclude-vcs -czf $ARCHIVE_NAME $LIB_DIR +tar -xzf $ARCHIVE_NAME + +cd $LIB_DIR + +echo -e "\n\nCopying prepared debian files to directory\n" +mkdir debian +cp -r package-build/debian-libcpr/* debian/ +sed -i "s/\%VERSION/$VERSION/g" debian/changelog +sed -i "s/\%DATE/$(date -R)/g" debian/changelog + +echo -e "\n\nCalling debuild\n" +debuild diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/README.Debian b/Src/external_dependencies/cpr/package-build/debian-libcpr/README.Debian new file mode 100644 index 00000000..e159461a --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/README.Debian @@ -0,0 +1,5 @@ +libcpr for Debian + +A package of the libcpr library. + + -- Philip Saendig <philip.saendig@gmail.com> Tue, 24 May 2022 10:37:24 +0200 diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/changelog b/Src/external_dependencies/cpr/package-build/debian-libcpr/changelog new file mode 100644 index 00000000..dbe84a94 --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/changelog @@ -0,0 +1,6 @@ +libcpr (%VERSION-1) UNRELEASED; urgency=low + + [ Philip Saendig ] + * First package of libcpr %VERSION for debian. + + -- Philip Saendig <deb@libcpr.org> %DATE diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/control b/Src/external_dependencies/cpr/package-build/debian-libcpr/control new file mode 100644 index 00000000..e4526b1c --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/control @@ -0,0 +1,39 @@ +Source: libcpr +Section: libs +Priority: optional +Maintainer: Philip Saendig <philip.saendig@gmail.com> +Build-Depends: + debhelper-compat (= 12), + cmake, + libcurl4-openssl-dev, + libssl-dev, +Standards-Version: 4.5.0 +Homepage: https://github.com/libcpr/cpr + +Package: libcpr-dev +Architecture: any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: ${misc:Depends}, ${shlibs:Depends}, libcpr1 +Description: C++ wrapper around the libcurl library - development kit + This package contains the header files and development + libraries of cpr, Curl for People. + . + The project is inspried by the Python Request project. + Using the more expressive language facilities of C++11, + it captures the essence of making network calls into a + few concise idioms. + +Package: libcpr1 +Architecture: any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: ${misc:Depends}, ${shlibs:Depends}, +Description: C++ wrapper around the libcurl library - runtime library + This package contains the runtime, shared library of cpr, + Curl for People. + . + The project is inspried by the Python Request project. + Using the more expressive language facilities of C++11, + it captures the essence of making network calls into a + few concise idioms. diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/copyright b/Src/external_dependencies/cpr/package-build/debian-libcpr/copyright new file mode 100644 index 00000000..33b7ffc2 --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/copyright @@ -0,0 +1,52 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: libcpr +Source: https://github.com/libcpr/cpr + +Files: .clang-format + .clang-tidy + .github/* + CMakeLists.txt + CODE_OF_CONDUCT.md + CONTRIBUTING.md + CppCheckSuppressions.txt + README.md + cmake/* + cpr-config.cmake + cpr/* + include/* + nuget/* + package-build/* + debian/* +Copyright: 2017-2021 Huu Nguyen + 2022 libcpr and many other contributors +License: Expat + MIT License + . + Copyright (c) 2017-2021 Huu Nguyen + Copyright (c) 2022 libcpr and many other contributors + . + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Files: test/* +Copyright: 2022 libcpr and many other contributors +License: GPL-3 + On Debian systems, the full text of the GNU General Public + License version 3 can be found in the file + `/usr/share/common-licenses/GPL-3'. + diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr-dev.install b/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr-dev.install new file mode 100644 index 00000000..bb722673 --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr-dev.install @@ -0,0 +1,3 @@ +usr/include +usr/lib/*/*.so +usr/lib/*/cmake diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr1.install b/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr1.install new file mode 100644 index 00000000..3de3b10a --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/libcpr1.install @@ -0,0 +1 @@ +usr/lib/*/*.so.* diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/rules b/Src/external_dependencies/cpr/package-build/debian-libcpr/rules new file mode 100644 index 00000000..11b4eb1c --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/rules @@ -0,0 +1,14 @@ +#!/usr/bin/make -f +export DH_VERBOSE = 1 +export DEB_BUILD_MAINT_OPTIONS = hardening=+all +export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + +%: + dh $@ + +override_dh_auto_configure: + dh_auto_configure -- \ + -DCMAKE_LIBRARY_ARCHITECTURE="$(DEB_TARGET_MULTIARCH)" -DCMAKE_BUILD_TYPE=Release \ + -DCPR_USE_SYSTEM_CURL=ON -DCURL_ZLIB=OFF -DBUILD_SHARED_LIBS=ON + diff --git a/Src/external_dependencies/cpr/package-build/debian-libcpr/source/format b/Src/external_dependencies/cpr/package-build/debian-libcpr/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/Src/external_dependencies/cpr/package-build/debian-libcpr/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/Src/external_dependencies/cpr/test/CMakeLists.txt b/Src/external_dependencies/cpr/test/CMakeLists.txt new file mode 100644 index 00000000..d84044ff --- /dev/null +++ b/Src/external_dependencies/cpr/test/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 3.15)
+
+find_package(Threads REQUIRED)
+
+if (ENABLE_SSL_TESTS)
+ add_library(test_server STATIC
+ abstractServer.cpp
+ httpServer.cpp
+ httpsServer.cpp)
+else ()
+ add_library(test_server STATIC
+ abstractServer.cpp
+ httpServer.cpp)
+endif()
+if(WIN32)
+ target_link_libraries(test_server PRIVATE Threads::Threads cpr::cpr GTest::GTest
+ PUBLIC mongoose ws2_32 wsock32)
+else()
+ target_link_libraries(test_server PRIVATE Threads::Threads cpr::cpr GTest::GTest
+ PUBLIC mongoose)
+endif()
+
+macro(add_cpr_test _TEST_NAME)
+ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp)
+ target_link_libraries(${_TEST_NAME}_tests PRIVATE
+ test_server
+ GTest::GTest
+ cpr::cpr
+ CURL::libcurl)
+ add_test(NAME cpr_${_TEST_NAME}_tests COMMAND ${_TEST_NAME}_tests)
+ # Group under the "tests" project folder in IDEs such as Visual Studio.
+ set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
+ if(WIN32 AND BUILD_SHARED_LIBS)
+ add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:libcurl> $<TARGET_FILE_DIR:${_TEST_NAME}_tests>)
+ add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:cpr> $<TARGET_FILE_DIR:${_TEST_NAME}_tests>)
+ endif()
+endmacro()
+
+add_cpr_test(get)
+add_cpr_test(post)
+add_cpr_test(session)
+add_cpr_test(prepare)
+add_cpr_test(async)
+if(CPR_BUILD_TESTS_PROXY)
+ add_cpr_test(proxy)
+ add_cpr_test(proxy_auth)
+endif()
+add_cpr_test(head)
+add_cpr_test(delete)
+add_cpr_test(put)
+add_cpr_test(callback)
+add_cpr_test(raw_body)
+add_cpr_test(options)
+add_cpr_test(patch)
+add_cpr_test(error)
+add_cpr_test(alternating)
+add_cpr_test(util)
+add_cpr_test(structures)
+add_cpr_test(encoded_auth)
+add_cpr_test(version)
+add_cpr_test(download)
+add_cpr_test(interceptor)
+add_cpr_test(multiperform)
+add_cpr_test(resolve)
+
+if (ENABLE_SSL_TESTS)
+ add_cpr_test(ssl)
+
+ # Install all ssl keys and certs. Explicit copy for each file to prevent issues when copying on Windows.
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:ssl_tests>/data/certificates $<TARGET_FILE_DIR:ssl_tests>/data/keys)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/certificates/client.crt $<TARGET_FILE_DIR:ssl_tests>/data/certificates/client.crt)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/certificates/root-ca.crt $<TARGET_FILE_DIR:ssl_tests>/data/certificates/root-ca.crt)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/certificates/server.crt $<TARGET_FILE_DIR:ssl_tests>/data/certificates/server.crt)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/keys/client.key $<TARGET_FILE_DIR:ssl_tests>/data/keys/client.key)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/keys/root-ca.key $<TARGET_FILE_DIR:ssl_tests>/data/keys/root-ca.key)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/keys/server.key $<TARGET_FILE_DIR:ssl_tests>/data/keys/server.key)
+ add_custom_command(TARGET ssl_tests POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/data/keys/server.pub $<TARGET_FILE_DIR:ssl_tests>/data/keys/server.pub)
+endif()
+
+file(INSTALL data DESTINATION data)
diff --git a/Src/external_dependencies/cpr/test/LICENSE b/Src/external_dependencies/cpr/test/LICENSE new file mode 100644 index 00000000..4d188aa7 --- /dev/null +++ b/Src/external_dependencies/cpr/test/LICENSE @@ -0,0 +1,677 @@ +This license applies to everything inside this directory and all +subdirectories. + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/test/abstractServer.cpp b/Src/external_dependencies/cpr/test/abstractServer.cpp new file mode 100644 index 00000000..bb8eaeb0 --- /dev/null +++ b/Src/external_dependencies/cpr/test/abstractServer.cpp @@ -0,0 +1,143 @@ +#include "abstractServer.hpp" + +namespace cpr { +void AbstractServer::SetUp() { + Start(); +} + +void AbstractServer::TearDown() { + Stop(); +} + +void AbstractServer::Start() { + should_run = true; + serverThread = std::make_shared<std::thread>(&AbstractServer::Run, this); + serverThread->detach(); + std::unique_lock<std::mutex> server_lock(server_mutex); + server_start_cv.wait(server_lock); +} + +void AbstractServer::Stop() { + should_run = false; + std::unique_lock<std::mutex> server_lock(server_mutex); + server_stop_cv.wait(server_lock); +} + +static void EventHandler(mg_connection* conn, int event, void* event_data, void* context) { + switch (event) { + case MG_EV_READ: + case MG_EV_WRITE: + /** Do nothing. Just for housekeeping. **/ + break; + case MG_EV_POLL: + /** Do nothing. Just for housekeeping. **/ + break; + case MG_EV_CLOSE: + /** Do nothing. Just for housekeeping. **/ + break; + case MG_EV_ACCEPT: + /* Initialize HTTPS connection if Server is an HTTPS Server */ + static_cast<AbstractServer*>(context)->acceptConnection(conn); + break; + case MG_EV_CONNECT: + /** Do nothing. Just for housekeeping. **/ + break; + + case MG_EV_HTTP_CHUNK: { + /** Do nothing. Just for housekeeping. **/ + } break; + + case MG_EV_HTTP_MSG: { + AbstractServer* server = static_cast<AbstractServer*>(context); + server->OnRequest(conn, static_cast<mg_http_message*>(event_data)); + } break; + + default: + break; + } +} + +void AbstractServer::Run() { + // Setup a new mongoose http server. + memset(&mgr, 0, sizeof(mg_mgr)); + initServer(&mgr, EventHandler); + + // Notify the main thread that the server is up and runing: + server_start_cv.notify_all(); + + // Main server loop: + while (should_run) { + // NOLINTNEXTLINE (cppcoreguidelines-avoid-magic-numbers) + mg_mgr_poll(&mgr, 100); + } + + // Shutdown and cleanup: + timer_args.clear(); + mg_mgr_free(&mgr); + + // Notify the main thread that we have shut down everything: + server_stop_cv.notify_all(); +} + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +/** + * Decodes the given BASE64 string to a normal string. + * Source: https://gist.github.com/williamdes/308b95ac9ef1ee89ae0143529c361d37 + **/ +std::string AbstractServer::Base64Decode(const std::string& in) { + std::string out; + + std::vector<int> T(256, -1); + for (size_t i = 0; i < 64; i++) + T[base64_chars[i]] = static_cast<int>(i); + + int val = 0; + int valb = -8; + for (unsigned char c : in) { + if (T[c] == -1) { + break; + } + val = (val << 6) + T[c]; + valb += 6; + if (valb >= 0) { + out.push_back(char((val >> valb) & 0xFF)); + valb -= 8; + } + } + return out; +} + +// Sends error similar like in mongoose 6 method mg_http_send_error +// https://github.com/cesanta/mongoose/blob/6.18/mongoose.c#L7081-L7089 +void AbstractServer::SendError(mg_connection* conn, int code, std::string& reason) { + std::string headers{"Content-Type: text/plain\r\nConnection: close\r\n"}; + mg_http_reply(conn, code, headers.c_str(), reason.c_str()); +} + +// Checks whether a pointer to a connection is still managed by a mg_mgr. +// This check tells whether it is still possible to send a message via the given connection +// Note that it is still possible that the pointer of an old connection object may be reused by mongoose. +// In this case, the active connection might refer to a different connection than the one the caller refers to +bool AbstractServer::IsConnectionActive(mg_mgr* mgr, mg_connection* conn) { + mg_connection* c{mgr->conns}; + while (c) { + if (c == conn) { + return true; + } + c = c->next; + } + return false; +} + +uint16_t AbstractServer::GetRemotePort(const mg_connection* conn) { + return (conn->rem.port >> 8) | (conn->rem.port << 8); +} + +uint16_t AbstractServer::GetLocalPort(const mg_connection* conn) { + return (conn->loc.port >> 8) | (conn->loc.port << 8); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/test/abstractServer.hpp b/Src/external_dependencies/cpr/test/abstractServer.hpp new file mode 100644 index 00000000..d2daec26 --- /dev/null +++ b/Src/external_dependencies/cpr/test/abstractServer.hpp @@ -0,0 +1,70 @@ +#ifndef CPR_TEST_ABSTRACT_SERVER_SERVER_H +#define CPR_TEST_ABSTRACT_SERVER_SERVER_H + +#include <atomic> +#include <condition_variable> +#include <gtest/gtest.h> +#include <memory> +#include <mutex> +#include <string> + +#include "cpr/cpr.h" +#include "mongoose.h" + +namespace cpr { + +// Helper struct for functions using timers to simulate slow connections +struct TimerArg { + mg_mgr* mgr; + mg_connection* connection; + unsigned long connection_id; + mg_timer timer; + unsigned counter; + + explicit TimerArg(mg_mgr* m, mg_connection* c, mg_timer&& t) : mgr{m}, connection{c}, connection_id{0}, timer{t}, counter{0} {} + + ~TimerArg() { + mg_timer_free(&mgr->timers, &timer); + } +}; + +class AbstractServer : public testing::Environment { + public: + ~AbstractServer() override = default; + + void SetUp() override; + void TearDown() override; + + void Start(); + void Stop(); + + virtual std::string GetBaseUrl() = 0; + virtual uint16_t GetPort() = 0; + + virtual void acceptConnection(mg_connection* conn) = 0; + virtual void OnRequest(mg_connection* conn, mg_http_message* msg) = 0; + + private: + std::shared_ptr<std::thread> serverThread{nullptr}; + std::mutex server_mutex; + std::condition_variable server_start_cv; + std::condition_variable server_stop_cv; + std::atomic<bool> should_run{false}; + + void Run(); + + protected: + mg_mgr mgr{}; + std::vector<std::unique_ptr<TimerArg>> timer_args{}; + virtual mg_connection* initServer(mg_mgr* mgr, mg_event_handler_t event_handler) = 0; + + static std::string Base64Decode(const std::string& in); + static void SendError(mg_connection* conn, int code, std::string& reason); + static bool IsConnectionActive(mg_mgr* mgr, mg_connection* conn); + + static uint16_t GetRemotePort(const mg_connection* conn); + static uint16_t GetLocalPort(const mg_connection* conn); +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/test/alternating_tests.cpp b/Src/external_dependencies/cpr/test/alternating_tests.cpp new file mode 100644 index 00000000..d0466f67 --- /dev/null +++ b/Src/external_dependencies/cpr/test/alternating_tests.cpp @@ -0,0 +1,163 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(AlternatingTests, PutGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Header reflect PUT"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Response response = cpr::Get(url); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(AlternatingTests, PutGetPutGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Header reflect PUT"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Response response = cpr::Get(url); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Header reflect PUT"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Response response = cpr::Get(url); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(AlternatingTests, HeadGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + + { + // Head shouldn't return a body + Response response = cpr::Head(url); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Response response = cpr::Get(url); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(AlternatingTests, PutHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Header reflect PUT"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + // Head shouldn't return a body + Response response = cpr::Head(url); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(AlternatingTests, PutPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Header reflect PUT"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + + { + Payload payload{{"x", "5"}}; + Response response = cpr::Post(url, payload); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/async_tests.cpp b/Src/external_dependencies/cpr/test/async_tests.cpp new file mode 100644 index 00000000..c4f15830 --- /dev/null +++ b/Src/external_dependencies/cpr/test/async_tests.cpp @@ -0,0 +1,79 @@ +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +#include <cpr/cpr.h> +#include <cpr/filesystem.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +bool write_data(std::string /*data*/, intptr_t /*userdata*/) { + return true; +} + +TEST(UrlEncodedPostTests, AsyncGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + cpr::AsyncResponse future = cpr::GetAsync(url); + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); +} + +TEST(UrlEncodedPostTests, AsyncGetMultipleTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::GetAsync(url)); + } + for (cpr::AsyncResponse& future : responses) { + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + } +} + +TEST(UrlEncodedPostTests, AsyncGetMultipleReflectTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 100; ++i) { + Parameters p{{"key", std::to_string(i)}}; + responses.emplace_back(cpr::GetAsync(url, p)); + } + int i = 0; + for (cpr::AsyncResponse& future : responses) { + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + Url expected_url{url + "?key=" + std::to_string(i)}; + EXPECT_EQ(expected_url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + ++i; + } +} + +TEST(UrlEncodedPostTests, AsyncDownloadTest) { + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::AsyncResponse future = cpr::DownloadAsync(fs::path{"/tmp/aync_download"}, url, cpr::Header{{"Accept-Encoding", "gzip"}}, cpr::WriteCallback{write_data, 0}); + cpr::Response response = future.get(); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/callback_tests.cpp b/Src/external_dependencies/cpr/test/callback_tests.cpp new file mode 100644 index 00000000..834f960a --- /dev/null +++ b/Src/external_dependencies/cpr/test/callback_tests.cpp @@ -0,0 +1,931 @@ +#include <cstddef> +#include <gtest/gtest.h> + +#include <chrono> +#include <string> +#include <thread> +#include <vector> + +#include <cpr/cpr.h> + +#include "cpr/cprtypes.h" +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); +std::chrono::milliseconds sleep_time{50}; +std::chrono::seconds zero{0}; + +int status_callback(int& status_code, Response r) { + status_code = r.status_code; + return r.status_code; +} + +int status_callback_ref(int& status_code, const Response& r) { + status_code = r.status_code; + return r.status_code; +} + +std::string text_callback(std::string& expected_text, Response r) { + expected_text = r.text; + return r.text; +} + +std::string text_callback_ref(std::string& expected_text, const Response& r) { + expected_text = r.text; + return r.text; +} + +TEST(CallbackGetTests, CallbackGetLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::GetCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackGetTests, CallbackGetLambdaTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::GetCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackGetTests, CallbackGetLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::GetCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackGetTests, CallbackGetLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::GetCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackGetTests, CallbackGetFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::GetCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackGetTests, CallbackGetFunctionTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::GetCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackGetTests, CallbackGetFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::GetCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackGetTests, CallbackGetFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::GetCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + int status_code = 0; + auto future = cpr::DeleteCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteLambdaTextTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::string expected_text{}; + auto future = cpr::DeleteCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + int status_code = 0; + auto future = cpr::DeleteCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::string expected_text{}; + auto future = cpr::DeleteCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::DeleteCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteFunctionTextTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::DeleteCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::DeleteCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackDeleteTests, CallbackDeleteFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::DeleteCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::HeadCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadLambdaTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::HeadCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::HeadCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::HeadCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::HeadCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadFunctionTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::HeadCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::HeadCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackHeadTests, CallbackHeadFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::HeadCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPostTests, CallbackPostLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PostCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPostTests, CallbackPostLambdaTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PostCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPostTests, CallbackPostLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PostCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPostTests, CallbackPostLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PostCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPostTests, CallbackPostFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PostCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPostTests, CallbackPostFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PostCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPostTests, CallbackPostFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PostCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPostTests, CallbackPostFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PostCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPutTests, CallbackPutLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PutCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPutTests, CallbackPutLambdaTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PutCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPutTests, CallbackPutLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PutCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPutTests, CallbackPutLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PutCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPutTests, CallbackPutFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PutCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPutTests, CallbackPutFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PutCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPutTests, CallbackPutFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PutCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPutTests, CallbackPutFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PutCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::OptionsCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsLambdaTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::OptionsCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto future = cpr::OptionsCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto future = cpr::OptionsCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::OptionsCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsFunctionTextTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::OptionsCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::OptionsCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackOptionsTests, CallbackOptionsFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::OptionsCallback(callback, url); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchLambdaStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PatchCallback( + [&status_code](Response r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchLambdaTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PatchCallback( + [&expected_text](Response r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchLambdaStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto future = cpr::PatchCallback( + [&status_code](const Response& r) { + status_code = r.status_code; + return r.status_code; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchLambdaTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto future = cpr::PatchCallback( + [&expected_text](const Response& r) { + expected_text = r.text; + return r.text; + }, + url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchFunctionStatusTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PatchCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PatchCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchFunctionStatusReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + int status_code = 0; + auto callback = std::function<int(Response)>(std::bind(status_callback_ref, std::ref(status_code), std::placeholders::_1)); + auto future = cpr::PatchCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(status_code, future.get()); +} + +TEST(CallbackPatchTests, CallbackPatchFunctionTextReferenceTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::string expected_text{}; + auto callback = std::function<std::string(Response)>(std::bind(text_callback_ref, std::ref(expected_text), std::placeholders::_1)); + auto future = cpr::PatchCallback(callback, url, payload); + std::this_thread::sleep_for(sleep_time); + EXPECT_EQ(future.wait_for(zero), std::future_status::ready); + EXPECT_EQ(expected_text, future.get()); +} + +TEST(CallbackDataTests, CallbackReadFunctionCancelTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, cpr::ReadCallback([](char* /*buffer*/, size_t& /*size*/, intptr_t /*userdata*/) -> size_t { return false; })); + EXPECT_EQ(response.error.code, ErrorCode::REQUEST_CANCELLED); +} + +TEST(CallbackDataTests, CallbackReadFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + unsigned count = 0; + Response response = cpr::Post(url, cpr::ReadCallback{3, [&](char* buffer, size_t& size, intptr_t /*userdata*/) -> size_t { + std::string data; + ++count; + switch (count) { + case 1: + data = "x="; + break; + case 2: + data = "5"; + break; + default: + return false; + } + std::copy(data.begin(), data.end(), buffer); + size = data.size(); + return true; + }}); + EXPECT_EQ(2, count); + EXPECT_EQ(expected_text, response.text); +} + +TEST(CallbackDataTests, CallbackReadFunctionTextTestPut) { + Url url{server->GetBaseUrl() + "/put.html"}; + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + unsigned count = 0; + Response response = cpr::Put(url, cpr::ReadCallback{3, [&](char* buffer, size_t& size, intptr_t /*userdata*/) -> size_t { + std::string data; + ++count; + switch (count) { + case 1: + data = "x="; + break; + case 2: + data = "5"; + break; + default: + return false; + } + std::copy(data.begin(), data.end(), buffer); + size = data.size(); + return true; + }}); + EXPECT_EQ(2, count); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +/** + * Checks if the "Transfer-Encoding" header will be kept when using headers and a read callback. + * Issue: https://github.com/whoshuu/cpr/issues/517 + **/ +TEST(CallbackDataTests, CallbackReadFunctionHeaderTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::string data = "Test"; + Response response = cpr::Post(url, + cpr::ReadCallback{-1, + [&](char* /*buffer*/, size_t& size, intptr_t /*userdata*/) -> size_t { + size = 0; + return true; + }}, + Header{{"TestHeader", "42"}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + + // Check Header: + EXPECT_EQ(std::string{"42"}, response.header["TestHeader"]); // Set by us + EXPECT_TRUE(response.header.find("TestHeader") != response.header.end()); + EXPECT_EQ(std::string{"chunked"}, response.header["Transfer-Encoding"]); // Set by the read callback + EXPECT_TRUE(response.header.find("Transfer-Encoding") != response.header.end()); +} + +/* cesanta mongoose doesn't support chunked requests yet +TEST(CallbackDataTests, CallbackReadFunctionChunkedTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + unsigned count = 0; + Response response = cpr::Post(url, cpr::ReadCallback{[&count](char* buffer, size_t & size) -> size_t { + std::string data; + ++ count; + switch (count) { + case 1: + data = "x="; + break; + case 2: + data = "5"; + break; + default: + data = ""; + break; + } + std::copy(data.begin(), data.end(), buffer); + size = data.size(); + return true; + }}); + EXPECT_EQ(3, count); + EXPECT_EQ(expected_text, response.text); +} +*/ + +TEST(CallbackDataTests, CallbackHeaderFunctionCancelTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = Post(url, HeaderCallback{[](std::string /*header*/, intptr_t /*userdata*/) -> bool { return false; }}); + EXPECT_EQ(response.error.code, ErrorCode::REQUEST_CANCELLED); +} + +TEST(CallbackDataTests, CallbackHeaderFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::vector<std::string> expected_headers{"HTTP/1.1 201 Created\r\n", "Content-Type: application/json\r\n", "\r\n"}; + std::set<std::string> response_headers; + Post(url, HeaderCallback{[&response_headers](std::string header, intptr_t /*userdata*/) -> bool { + response_headers.insert(header); + return true; + }}); + for (std::string& header : expected_headers) { + std::cout << header << std::endl; + EXPECT_TRUE(response_headers.count(header)); + } +} + +TEST(CallbackDataTests, CallbackWriteFunctionCancelTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = Post(url, WriteCallback{[](std::string /*header*/, intptr_t /*userdata*/) -> bool { return false; }}); + EXPECT_EQ(response.error.code, ErrorCode::REQUEST_CANCELLED); +} + +TEST(CallbackDataTests, CallbackWriteFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + std::string response_text; + Post(url, Payload{{"x", "5"}}, WriteCallback{[&response_text](std::string header, intptr_t /*userdata*/) -> bool { + response_text.append(header); + return true; + }}); + EXPECT_EQ(expected_text, response_text); +} + +TEST(CallbackDataTests, CallbackProgressFunctionCancelTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = Post(url, ProgressCallback{[](size_t /*downloadTotal*/, size_t /*downloadNow*/, size_t /*uploadTotal*/, size_t /*uploadNow*/, intptr_t /*userdata*/) -> bool { return false; }}); + EXPECT_EQ(response.error.code, ErrorCode::REQUEST_CANCELLED); +} + +TEST(CallbackDataTests, CallbackProgressFunctionTotalTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Body body{"x=5"}; + size_t response_upload = 0, response_download = 0; + Response response = Post(url, body, ProgressCallback{[&](size_t downloadTotal, size_t /*downloadNow*/, size_t uploadTotal, size_t /*uploadNow*/, intptr_t /*userdata*/) -> bool { + response_upload = uploadTotal; + response_download = downloadTotal; + return true; + }}); + EXPECT_EQ(body.str().length(), response_upload); + EXPECT_EQ(response.text.length(), response_download); +} + +TEST(CallbackDataTests, CallbackDebugFunctionTextTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Body body{"x=5"}; + std::string debug_body; + Response response = Post(url, body, DebugCallback{[&](DebugCallback::InfoType type, std::string data, intptr_t /*userdata*/) { + if (type == DebugCallback::InfoType::DATA_OUT) { + debug_body = data; + } + }}); + EXPECT_EQ(body.str(), debug_body); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/data/certificates/client.crt b/Src/external_dependencies/cpr/test/data/certificates/client.crt new file mode 100644 index 00000000..0583f543 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/certificates/client.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBejCCASygAwIBAgIQKMJShx7GKmJqmABrC/KIkDAFBgMrZXAwMTELMAkGA1UE +BhMCR0IxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIw +NjI5MTEzMzA3WhcNMjcwNjI4MTEzMzA3WjAWMRQwEgYDVQQDDAt0ZXN0LWNsaWVu +dDAqMAUGAytlcAMhAOGArRN1SIicY6uB/2CRB668fBEDTQb1oLcCoTsYQetho3Uw +czAfBgNVHSMEGDAWgBTk8vOFDreFdYR240PRtp0UuOKktzAMBgNVHRMBAf8EAjAA +MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQU +a5RqAAt7DpJN8iHcLvTjH2TIKtowBQYDK2VwA0EApzcNlIuTMToyqyWZ0FhxikP/ +c2TS6u5qkP+YHgcJJkvJ0rRTXs164k4LpvlMG0gNxle4zfoAJQ8mAAMZcQKyAg== +-----END CERTIFICATE----- diff --git a/Src/external_dependencies/cpr/test/data/certificates/root-ca.crt b/Src/external_dependencies/cpr/test/data/certificates/root-ca.crt new file mode 100644 index 00000000..32d7ba97 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/certificates/root-ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBrjCCAWCgAwIBAgIQKMJShx7GKmJqmABrC/KIjjAFBgMrZXAwMTELMAkGA1UE +BhMCR0IxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIw +NjI5MTEzMzA3WhcNMzIwNjI2MTEzMzA3WjAxMQswCQYDVQQGEwJHQjEQMA4GA1UE +CgwHRXhhbXBsZTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAJqzaumMKuMm +htBGbS+UCrCmXbGb+lRcuO71mPRey7HXo4GNMIGKMA8GA1UdEwEB/wQFMAMBAf8w +DgYDVR0PAQH/BAQDAgIEMB0GA1UdDgQWBBTk8vOFDreFdYR240PRtp0UuOKktzBI +BgNVHR4EQTA/oD0wC4IJbG9jYWxob3N0MAqHCH8AAAH/AAAAMCKHIAAAAAAAAAAA +AAAAAAAAAAH/////////////////////MAUGAytlcANBAESQBu1/oyaeYouu3q+h +VbIDkQiyZT4sPRYautZZ+xrN4MkNWDtwLeVJ+a9N0YU9vDpOviJpvXN4H/EEBwBF +3AA= +-----END CERTIFICATE----- diff --git a/Src/external_dependencies/cpr/test/data/certificates/server.crt b/Src/external_dependencies/cpr/test/data/certificates/server.crt new file mode 100644 index 00000000..da572028 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/certificates/server.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBdTCCASegAwIBAgIQKMJShx7GKmJqmABrC/KIjzAFBgMrZXAwMTELMAkGA1UE +BhMCR0IxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIw +NjI5MTEzMzA3WhcNMjcwNjI4MTEzMzA3WjAWMRQwEgYDVQQDDAt0ZXN0LXNlcnZl +cjAqMAUGAytlcAMhAI64JU5RjfdEG1KQMxS5DQWkiGlKIQO7ye4mNFq9QleTo3Aw +bjAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEw +HQYDVR0OBBYEFDnBgTgB3FU45S9OetBMhHu3J9OvMB8GA1UdIwQYMBaAFOTy84UO +t4V1hHbjQ9G2nRS44qS3MAUGAytlcANBAC4NoQ31kHfp64R9gGNjTYrr2SNXHyEq +7YG0qFi5ABvLXJAbM2v27EIgY1TWYO43FBsclQsz6mcp1MzZfjT9RwQ= +-----END CERTIFICATE----- diff --git a/Src/external_dependencies/cpr/test/data/client.cnf b/Src/external_dependencies/cpr/test/data/client.cnf new file mode 100644 index 00000000..d387d39d --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/client.cnf @@ -0,0 +1,8 @@ +# Based on https://www.feistyduck.com/library/openssl-cookbook/online/openssl-command-line/private-ca-create-subordinate.html +[req] +prompt = no +distinguished_name = dn + +[dn] +CN = test-client + diff --git a/Src/external_dependencies/cpr/test/data/generate-certificates.sh b/Src/external_dependencies/cpr/test/data/generate-certificates.sh new file mode 100644 index 00000000..f20d7729 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/generate-certificates.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +# Generate a CA with a self-signed root certificate that then signs the server certificate +# Based on the OpenSSL Cookbook by Ivan Ristic: +# https://www.feistyduck.com/library/openssl-cookbook/online/ +# +# Especially, see chapter 1.5. Creating a private Certification Authority: +# https://www.feistyduck.com/library/openssl-cookbook/online/openssl-command-line/private-ca.html + +export KEY_PATH=keys +export CRT_PATH=certificates +export CA_PATH=ca + +# Create environment. +# $CA_PATH is deleted in the end. +# If new certificates need to be issued, this needs to be done before the cleanup in the end. +mkdir -p $KEY_PATH $CRT_PATH $CA_PATH/db $CA_PATH/private $CA_PATH/certificates +touch $CA_PATH/db/index +openssl rand -hex 16 > $CA_PATH/db/serial + + +# Generate all private keys +openssl genpkey -algorithm ed25519 -out $KEY_PATH/root-ca.key +openssl genpkey -algorithm ed25519 -out $KEY_PATH/server.key +openssl genpkey -algorithm ed25519 -out $KEY_PATH/client.key + +# For the server, we also need the public key +openssl pkey -in $KEY_PATH/server.key -pubout -out $KEY_PATH/server.pub + + +# Generate a Certificate Signing Request for the Root CA based on a config file +openssl req -new \ + -config root-ca.cnf -out root-ca.csr \ + -key $KEY_PATH/root-ca.key + +# Self-sign the root certificate +openssl ca -batch \ + -selfsign -config root-ca.cnf \ + -extensions ca_ext \ + -in root-ca.csr -out $CRT_PATH/root-ca.crt -notext + + +# Create a Certificate Signing request for the server certificate +openssl req -new \ + -config server.cnf -out server.csr \ + -key $KEY_PATH/server.key +openssl req -text -in server.csr -noout + +# Issue the server certificate +openssl ca -batch \ + -config root-ca.cnf \ + -extensions server_ext \ + -extfile server.cnf -extensions ext \ + -in server.csr -out $CRT_PATH/server.crt -notext \ + -days 1825 + + +# Create a Certificate Signing request for the client certificate +openssl req -new \ + -config client.cnf -out client.csr \ + -key $KEY_PATH/client.key + +# Issue the client certificate +openssl ca -batch \ + -config root-ca.cnf \ + -extensions client_ext \ + -in client.csr -out $CRT_PATH/client.crt -notext \ + -days 1825 + + + +# Clean up +# IMPORTANT: If new certificates should be issued, $CA_PATH and its files MUST NOT be deleted! +# New certificates can be created in this script before cleaning up. +rm -rf *.csr $CA_PATH + diff --git a/Src/external_dependencies/cpr/test/data/keys/client.key b/Src/external_dependencies/cpr/test/data/keys/client.key new file mode 100644 index 00000000..120f9e05 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/keys/client.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIPTCPxm8reXOE2aIrafTcibvg4f6Rg1/F2LVk12EILzJ +-----END PRIVATE KEY----- diff --git a/Src/external_dependencies/cpr/test/data/keys/root-ca.key b/Src/external_dependencies/cpr/test/data/keys/root-ca.key new file mode 100644 index 00000000..a574c0be --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/keys/root-ca.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIHbCvDGMRz5Ky+7gJvQYZ5t+5sZyHI+UcAKWvS20CoLU +-----END PRIVATE KEY----- diff --git a/Src/external_dependencies/cpr/test/data/keys/server.key b/Src/external_dependencies/cpr/test/data/keys/server.key new file mode 100644 index 00000000..bfdefcb1 --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/keys/server.key @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIGVXwKYyi/u52mmDVC56TSorC/GGNqgyiW4+jsDno81i +-----END PRIVATE KEY----- diff --git a/Src/external_dependencies/cpr/test/data/keys/server.pub b/Src/external_dependencies/cpr/test/data/keys/server.pub new file mode 100644 index 00000000..715576ad --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/keys/server.pub @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAjrglTlGN90QbUpAzFLkNBaSIaUohA7vJ7iY0Wr1CV5M= +-----END PUBLIC KEY----- diff --git a/Src/external_dependencies/cpr/test/data/root-ca.cnf b/Src/external_dependencies/cpr/test/data/root-ca.cnf new file mode 100644 index 00000000..9a1fd65d --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/root-ca.cnf @@ -0,0 +1,69 @@ +# Based on: https://www.feistyduck.com/library/openssl-cookbook/online/openssl-command-line/private-ca-creating-root.html +[default] +name = root-ca +default_ca = ca_default +name_opt = utf8,esc_ctrl,multiline,lname,align + +[ca_dn] +countryName = "GB" +organizationName = "Example" +commonName = "Root CA" + +[ca_default] +home = ./${ENV::CA_PATH} +database = $home/db/index +serial = $home/db/serial +certificate = ./${ENV::CRT_PATH}/$name.crt +private_key = ./${ENV::KEY_PATH}/$name.key +RANDFILE = $home/private/random +new_certs_dir = $home/certificates +unique_subject = no +copy_extensions = none +default_days = 3650 +default_md = sha256 +policy = policy_cn_supplied + +[policy_cn_supplied] +countryName = optional +stateOrProvinceName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[req] +default_bits = 4096 +encrypt_key = yes +default_md = sha256 +utf8 = yes +string_mask = utf8only +prompt = no +distinguished_name = ca_dn +req_extensions = ca_ext + +[ca_ext] +basicConstraints = critical,CA:true +keyUsage = critical,keyCertSign +subjectKeyIdentifier = hash +nameConstraints = @name_constraints + + +[server_ext] +authorityKeyIdentifier = keyid:always +basicConstraints = critical,CA:false +extendedKeyUsage = clientAuth,serverAuth +keyUsage = critical,digitalSignature,keyEncipherment +subjectKeyIdentifier = hash + +[client_ext] +authorityKeyIdentifier = keyid:always +basicConstraints = critical,CA:false +extendedKeyUsage = clientAuth +keyUsage = critical,digitalSignature +subjectKeyIdentifier = hash + +[name_constraints] +permitted;DNS.0=localhost +permitted;IP.0=127.0.0.1/255.0.0.0 +permitted;IP.1=::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + diff --git a/Src/external_dependencies/cpr/test/data/server.cnf b/Src/external_dependencies/cpr/test/data/server.cnf new file mode 100644 index 00000000..a67fe34a --- /dev/null +++ b/Src/external_dependencies/cpr/test/data/server.cnf @@ -0,0 +1,12 @@ +# Based on https://www.feistyduck.com/library/openssl-cookbook/online/openssl-command-line/private-ca-create-subordinate.html +[req] +prompt = no +distinguished_name = dn +req_extensions = ext + +[dn] +CN = test-server + +[ext] +subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1 + diff --git a/Src/external_dependencies/cpr/test/delete_tests.cpp b/Src/external_dependencies/cpr/test/delete_tests.cpp new file mode 100644 index 00000000..50856dfa --- /dev/null +++ b/Src/external_dependencies/cpr/test/delete_tests.cpp @@ -0,0 +1,259 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(DeleteTests, DeleteTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + Response response = cpr::Delete(url); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, DeleteUnallowedTest) { + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + Response response = cpr::Delete(url); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, DeleteJsonBodyTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + Response response = cpr::Delete(url, cpr::Body{"'foo': 'bar'"}, cpr::Header{{"Content-Type", "application/json"}}); + std::string expected_text{"'foo': 'bar'"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + Session session; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteUnallowedTest) { + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + Session session; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteJsonBodyTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Content-Type", "application/json"}}); + session.SetBody(cpr::Body{"{'foo': 'bar'}"}); + Response response = session.Delete(); + std::string expected_text{"{'foo': 'bar'}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/delete.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteUnallowedAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/delete.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteUnallowedAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, SessionDeleteUnallowedAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, AsyncDeleteTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + cpr::AsyncResponse future_response = cpr::DeleteAsync(url); + cpr::Response response = future_response.get(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, AsyncDeleteUnallowedTest) { + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + cpr::AsyncResponse future_response = cpr::DeleteAsync(url); + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DeleteTests, AsyncMultipleDeleteTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::DeleteAsync(url)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(DeleteTests, AsyncMultipleDeleteUnallowedTest) { + Url url{server->GetBaseUrl() + "/delete_unallowed.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::DeleteAsync(url)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/download_tests.cpp b/Src/external_dependencies/cpr/test/download_tests.cpp new file mode 100644 index 00000000..e9fa7206 --- /dev/null +++ b/Src/external_dependencies/cpr/test/download_tests.cpp @@ -0,0 +1,118 @@ +#include <cstddef> +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "cpr/api.h" +#include "cpr/callback.h" +#include "cpr/cprtypes.h" +#include "cpr/session.h" +#include "httpServer.hpp" + + +static cpr::HttpServer* server = new cpr::HttpServer(); + +bool write_data(std::string /*data*/, intptr_t /*userdata*/) { + return true; +} + +TEST(DownloadTests, DownloadGzip) { + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetUrl(url); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); +} + +TEST(DownloadTests, RangeTestWholeFile) { + const int64_t download_size = 9; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetRange(cpr::Range{std::nullopt, std::nullopt}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +TEST(DownloadTests, RangeTestLowerLimit) { + const int64_t download_size = 8; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetRange(cpr::Range{1, std::nullopt}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(206, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +TEST(DownloadTests, RangeTestUpperLimit) { + const int64_t download_size = 6; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetRange(cpr::Range{std::nullopt, download_size - 1}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(206, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +TEST(DownloadTests, RangeTestLowerAndUpperLimit) { + const int64_t download_size = 2; + const int64_t start_from = 2; + const int64_t finish_at = start_from + download_size - 1; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetRange(cpr::Range{start_from, finish_at}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(206, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +TEST(DownloadTests, RangeTestMultipleRangesSet) { + const int64_t num_parts = 2; + const int64_t download_size = num_parts * (26 /*content range*/ + 4 /*\n*/) + ((num_parts + 1) * 15 /*boundary*/) + 2 /*--*/ + 6 /*data*/; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetMultiRange(cpr::MultiRange{cpr::Range{std::nullopt, 3}, cpr::Range{5, 6}}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(206, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +TEST(DownloadTests, RangeTestMultipleRangesOption) { + const int64_t num_parts = 3; + const int64_t download_size = num_parts * (26 /*content range*/ + 4 /*\n*/) + ((num_parts + 1) * 15 /*boundary*/) + 2 /*--*/ + 7 /*data*/; + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetOption(cpr::MultiRange{cpr::Range{std::nullopt, 2}, cpr::Range{4, 5}, cpr::Range{7, 8}}); + cpr::Response response = session.Download(cpr::WriteCallback{write_data, 0}); + EXPECT_EQ(206, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); + EXPECT_EQ(download_size, response.downloaded_bytes); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/encoded_auth_tests.cpp b/Src/external_dependencies/cpr/test/encoded_auth_tests.cpp new file mode 100644 index 00000000..c8b89ce0 --- /dev/null +++ b/Src/external_dependencies/cpr/test/encoded_auth_tests.cpp @@ -0,0 +1,20 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +using namespace cpr; + +TEST(EncodedAuthenticationTests, UnicodeEncoderTest) { + std::string user = "一二三"; + std::string pass = "Hello World!"; + EncodedAuthentication pa{user, pass}; + std::string expected = "%E4%B8%80%E4%BA%8C%E4%B8%89:Hello%20World%21"; + EXPECT_EQ(pa.GetAuthString(), expected); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/error_tests.cpp b/Src/external_dependencies/cpr/test/error_tests.cpp new file mode 100644 index 00000000..13831ef5 --- /dev/null +++ b/Src/external_dependencies/cpr/test/error_tests.cpp @@ -0,0 +1,97 @@ +#include <gtest/gtest.h> + +#include <chrono> +#include <string> + +#include <cpr/cpr.h> +#include <curl/curl.h> + +#include "httpServer.hpp" +#include "httpsServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(ErrorTests, UnsupportedProtocolFailure) { + Url url{"urk://wat.is.this"}; + Response response = cpr::Get(url); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::UNSUPPORTED_PROTOCOL, response.error.code); +} + +TEST(ErrorTests, InvalidURLFailure) { + Url url{"???"}; + Response response = cpr::Get(url); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::INVALID_URL_FORMAT, response.error.code); +} + +TEST(ErrorTests, TimeoutFailure) { + Url url{server->GetBaseUrl() + "/timeout.html"}; + Response response = cpr::Get(url, cpr::Timeout{1}); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(ErrorTests, ChronoTimeoutFailure) { + Url url{server->GetBaseUrl() + "/timeout.html"}; + Response response = cpr::Get(url, cpr::Timeout{std::chrono::milliseconds{1}}); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(ErrorTests, ConnectTimeoutFailure) { + Url url{"http://localhost:67"}; + Response response = cpr::Get(url, cpr::ConnectTimeout{1}); + EXPECT_EQ(0, response.status_code); + // Sometimes a CONNECTION_FAILURE happens before the OPERATION_TIMEDOUT: + EXPECT_TRUE(response.error.code == ErrorCode::OPERATION_TIMEDOUT || response.error.code == ErrorCode::CONNECTION_FAILURE); +} + +TEST(ErrorTests, ChronoConnectTimeoutFailure) { + Url url{"http://localhost:67"}; + Response response = cpr::Get(url, cpr::ConnectTimeout{std::chrono::milliseconds{1}}); + EXPECT_EQ(0, response.status_code); + // Sometimes a CONNECTION_FAILURE happens before the OPERATION_TIMEDOUT: + EXPECT_TRUE(response.error.code == ErrorCode::OPERATION_TIMEDOUT || response.error.code == ErrorCode::CONNECTION_FAILURE); +} + +TEST(ErrorTests, LowSpeedTimeFailure) { + Url url{server->GetBaseUrl() + "/low_speed.html"}; + Response response = cpr::Get(url, cpr::LowSpeed{1000, 1}); + // Do not check for the HTTP status code, since libcurl always provides the status code of the header if it was received + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(ErrorTests, LowSpeedBytesFailure) { + Url url{server->GetBaseUrl() + "/low_speed_bytes.html"}; + Response response = cpr::Get(url, cpr::LowSpeed{1000, 1}); + // Do not check for the HTTP status code, since libcurl always provides the status code of the header if it was received + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(ErrorTests, ProxyFailure) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, cpr::Proxies{{"http", "http://bad_host/"}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::PROXY_RESOLUTION_FAILURE, response.error.code); +} + +TEST(ErrorTests, BoolFalseTest) { + Error error; + EXPECT_FALSE(error); +} + +TEST(ErrorTests, BoolTrueTest) { + Error error; + error.code = ErrorCode::UNSUPPORTED_PROTOCOL; + EXPECT_TRUE(error); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/get_tests.cpp b/Src/external_dependencies/cpr/test/get_tests.cpp new file mode 100644 index 00000000..d93bd173 --- /dev/null +++ b/Src/external_dependencies/cpr/test/get_tests.cpp @@ -0,0 +1,1305 @@ +#include <gtest/gtest.h> + +#include <memory> +#include <string> + +#include "cpr/cpr.h" +#include "cpr/cprtypes.h" +#include "cpr/redirect.h" +#include "cpr/session.h" +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(BasicTests, HelloWorldTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, HelloWorldStringViewUrlTest) { + Url url{static_cast<std::string_view>(server->GetBaseUrl() + "/hello.html")}; + Response response = cpr::Get(url); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, HelloWorldNoInterfaceTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Interface iface{""}; // Do not specify any specific interface + Response response = cpr::Get(url, iface); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, HelloWorldNoInterfaceStringViewTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Interface iface{std::string_view{}}; // Do not specify any specific interface + Response response = cpr::Get(url, iface); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, TimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, Timeout{0L}); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, BasicJsonTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Response response = cpr::Get(url); + std::string expected_text{ + "[\n" + " {\n" + " \"first_key\": \"first_value\",\n" + " \"second_key\": \"second_value\"\n" + " }\n" + "]"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, ResourceNotFoundTest) { + Url url{server->GetBaseUrl() + "/error.html"}; + Response response = cpr::Get(url); + std::string expected_text{"Not Found"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(404, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, BadHostTest) { + Url url{"http://bad_host/"}; + Response response = cpr::Get(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::HOST_RESOLUTION_FAILURE, response.error.code); +} + +TEST(CookiesTests, BasicCookiesTest) { + Url url{server->GetBaseUrl() + "/basic_cookies.html"}; + Response response = cpr::Get(url); + cpr::Cookies res_cookies{response.cookies}; + std::string expected_text{"Basic Cookies"}; + cpr::Cookies expectedCookies{ + {"SID", "31d4d96e407aad42", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + {"lang", "en-US", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + }; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + for (auto cookie = res_cookies.begin(), expectedCookie = expectedCookies.begin(); cookie != res_cookies.end() && expectedCookie != expectedCookies.end(); cookie++, expectedCookie++) { + EXPECT_EQ(expectedCookie->GetName(), cookie->GetName()); + EXPECT_EQ(expectedCookie->GetValue(), cookie->GetValue()); + EXPECT_EQ(expectedCookie->GetDomain(), cookie->GetDomain()); + EXPECT_EQ(expectedCookie->IsIncludingSubdomains(), cookie->IsIncludingSubdomains()); + EXPECT_EQ(expectedCookie->GetPath(), cookie->GetPath()); + EXPECT_EQ(expectedCookie->IsHttpsOnly(), cookie->IsHttpsOnly()); + EXPECT_EQ(expectedCookie->GetExpires(), cookie->GetExpires()); + } +} + +TEST(CookiesTests, EmptyCookieTest) { + Url url{server->GetBaseUrl() + "/empty_cookies.html"}; + Response response = cpr::Get(url); + cpr::Cookies res_cookies{response.cookies}; + std::string expected_text{"Empty Cookies"}; + cpr::Cookies expectedCookies{ + {"SID", "", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + {"lang", "", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + }; + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + for (auto cookie = res_cookies.begin(), expectedCookie = expectedCookies.begin(); cookie != res_cookies.end() && expectedCookie != expectedCookies.end(); cookie++, expectedCookie++) { + EXPECT_EQ(expectedCookie->GetName(), cookie->GetName()); + EXPECT_EQ(expectedCookie->GetValue(), cookie->GetValue()); + EXPECT_EQ(expectedCookie->GetDomain(), cookie->GetDomain()); + EXPECT_EQ(expectedCookie->IsIncludingSubdomains(), cookie->IsIncludingSubdomains()); + EXPECT_EQ(expectedCookie->GetPath(), cookie->GetPath()); + EXPECT_EQ(expectedCookie->IsHttpsOnly(), cookie->IsHttpsOnly()); + EXPECT_EQ(expectedCookie->GetExpires(), cookie->GetExpires()); + } +} + +TEST(CookiesTests, ClientSetCookiesTest) { + Url url{server->GetBaseUrl() + "/cookies_reflect.html"}; + Cookies cookies{ + {"SID", "31d4d96e407aad42", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + {"lang", "en-US", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + }; + Response response = cpr::Get(url, cookies); + std::string expected_text{"SID=31d4d96e407aad42; lang=en-US;"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterTests, SingleParameterTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Parameters parameters{{"key", "value"}}; + Response response = cpr::Get(url, parameters); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterTests, SingleParameterOnlyKeyTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Parameters parameters{{"key", ""}}; + Response response = cpr::Get(url, parameters); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); +} + +TEST(ParameterTests, MultipleParametersTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, Parameters{{"key", "value"}, {"hello", "world"}, {"test", "case"}}); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value&hello=world&test=case"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterTests, MultipleDynamicParametersTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Parameters parameters{{"key", "value"}}; + parameters.Add({"hello", "world"}); + parameters.Add({"test", "case"}); + Response response = cpr::Get(url, parameters); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value&hello=world&test=case"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationTests, BasicAuthenticationSuccessTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationTests, BasicBearerSuccessTest) { + Url url{server->GetBaseUrl() + "/bearer_token.html"}; +#if CPR_LIBCURL_VERSION_NUM >= 0x073D00 + Response response = cpr::Get(url, Bearer{"the_token"}); +#else + Response response = cpr::Get(url, Header{{"Authorization", "Bearer the_token"}}); +#endif + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationTests, BasicDigestSuccessTest) { + Url url{server->GetBaseUrl() + "/digest_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::DIGEST}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAthenticationParameterTests, BasicAuthenticationSuccessSingleParameterTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterTests, BasicAuthenticationSuccessMultipleParametersTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"key", "value"}, {"hello", "world"}, {"test", "case"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value&hello=world&test=case"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterTests, BasicAuthenticationSuccessSingleParameterReverseTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterTests, BasicAuthenticationSuccessMultipleParametersReverseTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"key", "value"}, {"hello", "world"}, {"test", "case"}}, Authentication{"user", "password", AuthMode::BASIC}); + + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value&hello=world&test=case"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationHeaderTests, BasicAuthenticationSuccessSingleHeaderTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationHeaderTests, BasicAuthenticationSuccessMultipleHeadersTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{{"key", "value"}, {"hello", "world"}, {"test", "case"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(std::string{"case"}, response.header["test"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationHeaderTests, BasicAuthenticationSuccessSingleHeaderReverseTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationHeaderTests, BasicAuthenticationSuccessMultipleHeadersReverseTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"key", "value"}, {"hello", "world"}, {"test", "case"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(std::string{"case"}, response.header["test"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationTests, BasicAuthenticationNullFailureTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationTests, BasicAuthenticationFailureTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterTests, BasicAuthenticationFailureSingleParameterTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"hello", "world"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterTests, BasicAuthenticationFailureMultipleParametersTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"key", "value"}, {"hello", "world"}, {"test", "case"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?key=value&hello=world&test=case"}, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderJsonTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Response response = cpr::Get(url, Header{{"content-type", "application/json"}}); + std::string expected_text{ + "[\n" + " {\n" + " \"first_key\": \"first_value\",\n" + " \"second_key\": \"second_value\"\n" + " }\n" + "]"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectNoneTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectUpdateHeaderAddSessionTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetHeader(Header{{"Header1", "Value1"}}); + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"Value1"}, response.header["Header1"]); + EXPECT_EQ(std::string{}, response.header["Header2"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + + session.UpdateHeader(Header{{"Header2", "Value2"}}); + response = session.Get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"Value1"}, response.header["Header1"]); + EXPECT_EQ(std::string{"Value2"}, response.header["Header2"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +/** + * Test case for #532 + * https://github.com/whoshuu/cpr/issues/532 + **/ +TEST(HeaderTests, SessionHeaderReflectTest) { + std::unique_ptr<cpr::Session> session(new cpr::Session()); + session->SetUrl({server->GetBaseUrl() + "/header_reflect.html"}); + session->SetBody("Some Body to post"); + session->SetHeader({{"Content-Type", "application/json"}}); + cpr::Response response = session->Post(); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(std::string{"Header reflect POST"}, response.text); + EXPECT_EQ(std::string{"application/json"}, response.header["Content-Type"]); +} + +TEST(HeaderTests, HeaderReflectUpdateHeaderUpdateSessionTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetHeader(Header{{"Header1", "Value1"}}); + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"Value1"}, response.header["Header1"]); + EXPECT_EQ(std::string{}, response.header["Header2"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + + session.UpdateHeader(Header{{"Header1", "Value2"}}); + response = session.Get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"Value2"}, response.header["Header1"]); + EXPECT_EQ(std::string{}, response.header["Header2"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectEmptyTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectSingleTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectMultipleTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}, {"key", "value"}, {"test", "case"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(std::string{"case"}, response.header["test"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, HeaderReflectCaseInsensitiveTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"HeLlO", "wOrLd"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hello"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["HELLO"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hElLo"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeaderTests, SetEmptyHeaderTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"hello", ""}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectNoneParametersTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectEmptyParametersTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{}, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectSingleParametersTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectMultipleParametersTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}, {"key", "value"}, {"test", "case"}}, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(std::string{"case"}, response.header["test"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectCaseInsensitiveParametersTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Header{{"HeLlO", "wOrLd"}}, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hello"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["HELLO"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hElLo"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectEmptyParametersReverseTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectSingleParametersReverseTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectMultipleParametersReverseTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}, Header{{"hello", "world"}, {"key", "value"}, {"test", "case"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(std::string{"case"}, response.header["test"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterHeaderTests, HeaderReflectCaseInsensitiveParametersReverseTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}, {"three", "four"}, {"five", "six"}}, Header{{"HeLlO", "wOrLd"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two&three=four&five=six"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hello"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["HELLO"]); + EXPECT_EQ(std::string{"wOrLd"}, response.header["hElLo"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderAATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{}, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderABTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{}, Header{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderACTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"one", "two"}}, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderADTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"one", "two"}}, Header{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderAETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderAFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{}, Header{{"hello", "world"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderAGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"one", "two"}}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderAHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"one", "two"}}, Header{{"hello", "world"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Header{}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBBTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Header{}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBCTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Header{}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBDTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Header{}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Header{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Header{{"hello", "world"}}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Header{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderBHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Header{{"hello", "world"}}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Authentication{"user", "password", AuthMode::BASIC}, Parameters{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCBTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCCTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"one", "two"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCDTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"one", "two"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}, Parameters{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Authentication{"user", "password", AuthMode::BASIC}, Parameters{{"one", "two"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderCHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Authentication{"user", "bad_password", AuthMode::BASIC}, Parameters{{"one", "two"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{}, Parameters{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDBTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{}, Parameters{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDCTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{}, Parameters{{"one", "two"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDDTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{}, Parameters{{"one", "two"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{{"hello", "world"}}, Parameters{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{{"hello", "world"}}, Parameters{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{{"hello", "world"}}, Parameters{{"one", "two"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderDHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{{"hello", "world"}}, Parameters{{"one", "two"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Parameters{}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEBTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Parameters{}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderECTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Parameters{{"one", "two"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEDTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{}, Parameters{{"one", "two"}}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Parameters{}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Parameters{}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Parameters{{"one", "two"}}, Authentication{"user", "password", AuthMode::BASIC}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderEHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Header{{"hello", "world"}}, Parameters{{"one", "two"}}, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFATest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Authentication{"user", "password", AuthMode::BASIC}, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFBTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFCTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Authentication{"user", "password", AuthMode::BASIC}, Header{}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFDTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFETest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Authentication{"user", "password", AuthMode::BASIC}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFFTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{}, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{{"hello", "world"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFGTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Authentication{"user", "password", AuthMode::BASIC}, Header{{"hello", "world"}}); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicAuthenticationParameterHeaderTests, BasicAuthenticationParameterHeaderFHTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Get(url, Parameters{{"one", "two"}}, Authentication{"user", "bad_password", AuthMode::BASIC}, Header{{"hello", "world"}}); + EXPECT_EQ("Unauthorized", response.text); + EXPECT_EQ(Url{url + "?one=two"}, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(GetRedirectTests, RedirectTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Response response = cpr::Get(url, Redirect(false)); + std::string expected_text{"Moved Temporarily"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(302, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(GetRedirectTests, ZeroMaxRedirectsSuccessTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, Redirect(0L)); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(GetRedirectTests, ZeroMaxRedirectsFailureTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Response response = cpr::Get(url, Redirect(0L)); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(301, response.status_code); + EXPECT_EQ(ErrorCode::TOO_MANY_REDIRECTS, response.error.code); +} + +TEST(GetRedirectTests, BasicAuthenticationRedirectSuccessTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Response response = cpr::Get(url, Authentication{"user", "password", AuthMode::BASIC}, Header{{"RedirectLocation", "basic_auth.html"}}, Redirect(true, true)); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + std::string resultUrl = "http://user:password@127.0.0.1:" + std::to_string(server->GetPort()) + "/basic_auth.html"; + EXPECT_EQ(response.url, resultUrl); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, RequestBodyTest) { + Url url{server->GetBaseUrl() + "/body_get.html"}; + Body body{"message=abc123"}; + Response response = cpr::Get(url, body); + std::string expected_text{"abc123"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, RequestBodyStringViewTest) { + Url url{server->GetBaseUrl() + "/body_get.html"}; + Body body{static_cast<std::string_view>("message=abc123")}; + Response response = cpr::Get(url, body); + std::string expected_text{"abc123"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(LimitRateTests, HelloWorldTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, LimitRate(1024, 1024)); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/head_tests.cpp b/Src/external_dependencies/cpr/test/head_tests.cpp new file mode 100644 index 00000000..43c18314 --- /dev/null +++ b/Src/external_dependencies/cpr/test/head_tests.cpp @@ -0,0 +1,226 @@ +#include <chrono> +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(HeadTests, BasicHeadTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, ComplexHeadTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, ResourceNotFoundHeadTest) { + Url url{server->GetBaseUrl() + "/error.html"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(404, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, BadHostHeadTest) { + Url url{"http://bad_host/"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::HOST_RESOLUTION_FAILURE, response.error.code); +} + +TEST(HeadTests, CookieHeadTest) { + Url url{server->GetBaseUrl() + "/basic_cookies.html"}; + Response response = cpr::Head(url); + cpr::Cookies expectedCookies{ + {"SID", "31d4d96e407aad42", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + {"lang", "en-US", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + }; + cpr::Cookies res_cookies{response.cookies}; + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + for (auto cookie = res_cookies.begin(), expectedCookie = expectedCookies.begin(); cookie != res_cookies.end() && expectedCookie != expectedCookies.end(); cookie++, expectedCookie++) { + EXPECT_EQ(expectedCookie->GetName(), cookie->GetName()); + EXPECT_EQ(expectedCookie->GetValue(), cookie->GetValue()); + EXPECT_EQ(expectedCookie->GetDomain(), cookie->GetDomain()); + EXPECT_EQ(expectedCookie->IsIncludingSubdomains(), cookie->IsIncludingSubdomains()); + EXPECT_EQ(expectedCookie->GetPath(), cookie->GetPath()); + EXPECT_EQ(expectedCookie->IsHttpsOnly(), cookie->IsHttpsOnly()); + EXPECT_EQ(expectedCookie->GetExpires(), cookie->GetExpires()); + } +} + +TEST(HeadTests, ParameterHeadTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Parameters parameters{{"key", "value"}}; + Response response = cpr::Head(url, parameters); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(Url{url + "?key=value"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, AuthenticationSuccessHeadTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Head(url, Authentication{"user", "password", AuthMode::BASIC}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, AuthenticationNullFailureHeadTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, AuthenticationFailureHeadTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Response response = cpr::Head(url, Authentication{"user", "bad_password", AuthMode::BASIC}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ("text/plain", response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, BearerSuccessHeadTest) { + Url url{server->GetBaseUrl() + "/bearer_token.html"}; +#if CPR_LIBCURL_VERSION_NUM >= 0x073D00 + Response response = cpr::Get(url, Bearer{"the_token"}); +#else + Response response = cpr::Get(url, Header{{"Authorization", "Bearer the_token"}}); +#endif + EXPECT_EQ(std::string{"Header reflect GET"}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, DigestSuccessHeadTest) { + Url url{server->GetBaseUrl() + "/digest_auth.html"}; + Response response = cpr::Head(url, Authentication{"user", "password", AuthMode::DIGEST}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, HeaderReflectNoneHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Head(url); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, HeaderReflectEmptyHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Head(url, Header{}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, HeaderReflectHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Head(url, Header{{"hello", "world"}}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, SetEmptyHeaderHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Head(url, Header{{"hello", ""}}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, RedirectHeadTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Response response = cpr::Head(url, Redirect(false)); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(302, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, ZeroMaxRedirectsHeadTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Head(url, Redirect(0L)); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(HeadTests, BasicHeadAsyncTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::HeadAsync(url)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/httpServer.cpp b/Src/external_dependencies/cpr/test/httpServer.cpp new file mode 100644 index 00000000..edf3b099 --- /dev/null +++ b/Src/external_dependencies/cpr/test/httpServer.cpp @@ -0,0 +1,914 @@ +#include "httpServer.hpp" +#include <chrono> +#include <string> +#include <system_error> +#include <thread> + +namespace cpr { + +std::string HttpServer::GetBaseUrl() { + return "http://127.0.0.1:" + std::to_string(GetPort()); +} + +uint16_t HttpServer::GetPort() { + // Unassigned port number in the ephemeral range + return 61936; +} + +mg_connection* HttpServer::initServer(mg_mgr* mgr, mg_event_handler_t event_handler) { + // Based on: https://mongoose.ws/tutorials/http-server/ + mg_mgr_init(mgr); + std::string port = std::to_string(GetPort()); + mg_connection* c = mg_http_listen(mgr, GetBaseUrl().c_str(), event_handler, this); + if (!c) { + throw std::system_error(errno, std::system_category(), "Failed to listen at port " + port); + } + return c; +} + +void HttpServer::acceptConnection(mg_connection* /* conn */) {} + +void HttpServer::OnRequestHello(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"OPTIONS"}) { + OnRequestOptions(conn, msg); + } else { + std::string response{"Hello world!"}; + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } +} + +void HttpServer::OnRequestRoot(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"OPTIONS"}) { + OnRequestOptions(conn, msg); + } else { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } +} + +void HttpServer::OnRequestNotFound(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"OPTIONS"}) { + OnRequestOptions(conn, msg); + } else { + std::string errorMessage{"Not Found"}; + SendError(conn, 404, errorMessage); + } +} + +void HttpServer::OnRequestOptions(mg_connection* conn, mg_http_message* /*msg*/) { + std::string headers = + "Content-Type: text/plain\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS\r\n" + "Access-Control-Max-Age: 3600\r\n"; + + std::string response; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestTimeout(mg_connection* conn, mg_http_message* msg) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + OnRequestHello(conn, msg); +} + +void HttpServer::OnRequestLongTimeout(mg_connection* conn, mg_http_message* msg) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + OnRequestHello(conn, msg); +} + +// Send the header, then send "Hello world!" every 100ms +// For this, we use a mongoose timer +void HttpServer::OnRequestLowSpeedTimeout(mg_connection* conn, mg_http_message* /* msg */, TimerArg* timer_arg) { + std::string response{"Hello world!"}; + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n", response.length() * 20); + timer_arg->connection_id = conn->id; + mg_timer_init( + &timer_arg->mgr->timers, &timer_arg->timer, 100, MG_TIMER_REPEAT, + // The following lambda function gets executed each time the timer is called. + // It sends "Hello world!" to the client each 100ms at most 20 times. + [](void* arg) { + TimerArg* timer_arg = static_cast<TimerArg*>(arg); + if (timer_arg->counter < 20 && IsConnectionActive(timer_arg->mgr, timer_arg->connection) && timer_arg->connection->id == timer_arg->connection_id) { + std::string response{"Hello world!"}; + mg_send(timer_arg->connection, response.c_str(), response.length()); + ++timer_arg->counter; + } else { + timer_arg->counter = 20; // Make sure that this timer is never called again + } + }, + timer_arg); +} + +// Before and after calling an endpoint that calls this method, the test needs to wait until all previous connections are closed +// The nested call to mg_mgr_poll can lead to problems otherwise +void HttpServer::OnRequestLowSpeed(mg_connection* conn, mg_http_message* /*msg*/, mg_mgr* mgr) { + std::string response{"Hello world!"}; + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n", response.length()); + mg_timer_add( + mgr, 2000, MG_TIMER_ONCE, + [](void* connection) { + std::string response{"Hello world!"}; + mg_send(static_cast<mg_connection*>(connection), response.c_str(), response.length()); + }, + conn); +} + +// Before and after calling an endpoint that calls this method, the test needs to wait until all previous connections are closed +// The nested call to mg_mgr_poll can lead to problems otherwise +void HttpServer::OnRequestLowSpeedBytes(mg_connection* conn, mg_http_message* /*msg*/, TimerArg* timer_arg) { + std::string response{'a'}; + mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n", response.length() * 20); + + mg_timer_init( + &timer_arg->mgr->timers, &timer_arg->timer, 100, MG_TIMER_REPEAT, + // The following lambda function gets executed each time the timer is called. + // It first waits for 2 seconds, then sends "a" to the client each 100ms at most 20 times. + [](void* arg) { + TimerArg* timer_arg = static_cast<TimerArg*>(arg); + if (timer_arg->counter == 0) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + } + if (timer_arg->counter < 20 && IsConnectionActive(timer_arg->mgr, timer_arg->connection) && timer_arg->connection->id == timer_arg->connection_id) { + std::string response{'a'}; + mg_send(timer_arg->connection, response.c_str(), response.length()); + ++timer_arg->counter; + } else { + timer_arg->counter = 20; // Make sure that this timer is never called again + } + }, + timer_arg); +} + +void HttpServer::OnRequestBasicCookies(mg_connection* conn, mg_http_message* /*msg*/) { + time_t expires_time = 3905119080; // Expires=Wed, 30 Sep 2093 03:18:00 GMT + std::array<char, EXPIRES_STRING_SIZE> expires_string; + std::strftime(expires_string.data(), expires_string.size(), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&expires_time)); + + std::string cookie1{"SID=31d4d96e407aad42; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string cookie2{"lang=en-US; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string headers = + "Content-Type: text/html\r\n" + "Set-Cookie: " + + cookie1 + + "\r\n" + "Set-Cookie: " + + cookie2 + "\r\n"; + std::string response{"Basic Cookies"}; + + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestEmptyCookies(mg_connection* conn, mg_http_message* /*msg*/) { + time_t expires_time = 3905119080; // Expires=Wed, 30 Sep 2093 03:18:00 GMT + std::array<char, EXPIRES_STRING_SIZE> expires_string; + std::strftime(expires_string.data(), sizeof(expires_string), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&expires_time)); + + std::string cookie1{"SID=; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string cookie2{"lang=; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string headers = + "Content-Type: text/html\r\n" + "Set-Cookie: " + + cookie1 + + "\r\n" + "Set-Cookie: " + + cookie2 + "\r\n"; + std::string response{"Empty Cookies"}; + + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestCookiesReflect(mg_connection* conn, mg_http_message* msg) { + mg_str* request_cookies; + if ((request_cookies = mg_http_get_header(msg, "Cookie")) == nullptr) { + std::string errorMessage{"Cookie not found"}; + SendError(conn, 400, errorMessage); + return; + } + std::string cookie_str{request_cookies->ptr, request_cookies->len}; + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), cookie_str.c_str()); +} + +void HttpServer::OnRequestRedirectionWithChangingCookies(mg_connection* conn, mg_http_message* msg) { + time_t expires_time = 3905119080; // Expires=Wed, 30 Sep 2093 03:18:00 GMT + std::array<char, EXPIRES_STRING_SIZE> expires_string; + std::strftime(expires_string.data(), sizeof(expires_string), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&expires_time)); + + mg_str* request_cookies; + std::string cookie_str; + if ((request_cookies = mg_http_get_header(msg, "Cookie")) != nullptr) { + cookie_str = std::string{request_cookies->ptr, request_cookies->len}; + } + + if (cookie_str.find("SID=31d4d96e407aad42") == std::string::npos) { + std::string cookie1{"SID=31d4d96e407aad42; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string cookie2{"lang=en-US; Expires=" + std::string(expires_string.data()) + "; Secure"}; + std::string headers = + "Content-Type: text/html\r\n" + "Location: http://127.0.0.1:61936/redirection_with_changing_cookies.html\r\n" + "Set-Cookie: " + + cookie1 + + "\r\n" + "Set-Cookie: " + + cookie2 + "\r\n"; + + mg_http_reply(conn, 302, headers.c_str(), ""); + } else { + cookie_str = "Received cookies are: " + cookie_str; + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), cookie_str.c_str()); + } +} + +void HttpServer::OnRequestBasicAuth(mg_connection* conn, mg_http_message* msg) { + mg_str* requested_auth; + std::string auth{"Basic"}; + if ((requested_auth = mg_http_get_header(msg, "Authorization")) == nullptr || mg_ncasecmp(requested_auth->ptr, auth.c_str(), auth.length()) != 0) { + std::string errorMessage{"Unauthorized"}; + SendError(conn, 401, errorMessage); + return; + } + std::string auth_string{requested_auth->ptr, requested_auth->len}; + size_t basic_token = auth_string.find(' ') + 1; + auth_string = auth_string.substr(basic_token, auth_string.length() - basic_token); + auth_string = Base64Decode(auth_string); + size_t colon = auth_string.find(':'); + std::string username = auth_string.substr(0, colon); + std::string password = auth_string.substr(colon + 1, auth_string.length() - colon - 1); + if (username == "user" && password == "password") { + OnRequestHeaderReflect(conn, msg); + } else { + std::string errorMessage{"Unauthorized"}; + SendError(conn, 401, errorMessage); + } +} + +void HttpServer::OnRequestBearerAuth(mg_connection* conn, mg_http_message* msg) { + mg_str* requested_auth; + std::string auth{"Bearer"}; + if ((requested_auth = mg_http_get_header(msg, "Authorization")) == nullptr || mg_ncasecmp(requested_auth->ptr, auth.c_str(), auth.length()) != 0) { + std::string errorMessage{"Unauthorized"}; + SendError(conn, 401, errorMessage); + return; + } + std::string auth_string{requested_auth->ptr, requested_auth->len}; + size_t basic_token = auth_string.find(' ') + 1; + auth_string = auth_string.substr(basic_token, auth_string.length() - basic_token); + if (auth_string == "the_token") { + OnRequestHeaderReflect(conn, msg); + } else { + std::string errorMessage{"Unauthorized"}; + SendError(conn, 401, errorMessage); + } +} + +void HttpServer::OnRequestBasicJson(mg_connection* conn, mg_http_message* /*msg*/) { + std::string response = + "[\n" + " {\n" + " \"first_key\": \"first_value\",\n" + " \"second_key\": \"second_value\"\n" + " }\n" + "]"; + std::string headers = "Content-Type: application/json\r\n"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestHeaderReflect(mg_connection* conn, mg_http_message* msg) { + std::string response = "Header reflect " + std::string{msg->method.ptr, msg->method.len}; + std::string headers; + bool hasContentTypeHeader = false; + for (const mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"Content-Type"} == name) { + hasContentTypeHeader = true; + } + + if (std::string{"Host"} != name && std::string{"Accept"} != name) { + if (header.value.ptr) { + headers.append(name + ": " + std::string(header.value.ptr, header.value.len) + "\r\n"); + } + } + } + + if (!hasContentTypeHeader) { + headers.append("Content-Type: text/html\r\n"); + } + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestTempRedirect(mg_connection* conn, mg_http_message* msg) { + // Get the requested target location: + std::string location; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"RedirectLocation"} == name) { + location = std::string(header.value.ptr, header.value.len); + break; + } + } + + // Check if the request contains a valid location, else default to 'hello.html': + if (location.empty()) { + location = "hello.html"; + } + std::string headers = "Location: " + location + "\r\n"; + std::string response = "Moved Temporarily"; + mg_http_reply(conn, 302, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestPermRedirect(mg_connection* conn, mg_http_message* msg) { + // Get the requested target location: + std::string location; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"RedirectLocation"} == name) { + location = std::string(header.value.ptr, header.value.len); + break; + } + } + + // Check if the request contains a valid location, else default to 'hello.html': + if (location.empty()) { + location = "hello.html"; + } + std::string headers = "Location: " + location + "\r\n"; + std::string response = "Moved Permanently"; + + mg_http_reply(conn, 301, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestResolvePermRedirect(mg_connection* conn, mg_http_message* msg) { + // Get the requested target location: + std::string location; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"RedirectLocation"} == name) { + location = std::string(header.value.ptr, header.value.len); + break; + } + } + + if(location.empty()) { + std::string errorMessage{"Redirect location missing"}; + SendError(conn, 405, errorMessage); + return; + } + + std::string headers = "Location: " + location + "\r\n"; + std::string response = "Moved Permanently"; + + mg_http_reply(conn, 301, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestTwoRedirects(mg_connection* conn, mg_http_message* /*msg*/) { + std::string response = "Moved Permanently"; + std::string headers = "Location: permanent_redirect.html\r\n"; + mg_http_reply(conn, 301, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestUrlPost(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} != std::string{"POST"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + return; + } + + std::string headers = "Content-Type: application/json\r\n"; + + char x[100]; + char y[100]; + mg_http_get_var(&(msg->body), "x", x, sizeof(x)); + mg_http_get_var(&(msg->body), "y", y, sizeof(y)); + std::string x_string{x}; + std::string y_string{y}; + std::string response; + if (y_string.empty()) { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + "\n" + "}"}; + } else { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + ",\n" + " \"y\": " + + y_string + + ",\n" + " \"sum\": " + + std::to_string(atoi(x) + atoi(y)) + + "\n" + "}"}; + } + mg_http_reply(conn, 201, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestBodyGet(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} != std::string{"GET"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + return; + } + std::array<char, 100> message{}; + mg_http_get_var(&(msg->body), "message", message.data(), message.size()); + if (msg->body.len <= 0) { + std::string errorMessage{"No Content"}; + SendError(conn, 405, errorMessage); + return; + } + std::string response = message.data(); + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestJsonPost(mg_connection* conn, mg_http_message* msg) { + mg_str* content_type{nullptr}; + if ((content_type = mg_http_get_header(msg, "Content-Type")) == nullptr || std::string{content_type->ptr, content_type->len} != "application/json") { + std::string errorMessage{"Unsupported Media Type"}; + SendError(conn, 415, errorMessage); + return; + } + + std::string headers = "Content-Type: application/json\r\n"; + mg_http_reply(conn, 201, headers.c_str(), msg->body.ptr); +} + +void HttpServer::OnRequestFormPost(mg_connection* conn, mg_http_message* msg) { + size_t pos{0}; + mg_http_part part{}; + + std::string headers = "Content-Type: application/json\r\n"; + std::string response{}; + response += "{\n"; + while ((pos = mg_http_next_multipart(msg->body, pos, &part)) > 0) { + response += " \"" + std::string(part.name.ptr, part.name.len) + "\": \""; + if (!std::string(part.filename.ptr, part.filename.len).empty()) { + response += std::string(part.filename.ptr, part.filename.len) + "="; + } + response += std::string(part.body.ptr, part.body.len) + "\",\n"; + } + response.erase(response.find_last_not_of(",\n") + 1); + response += "\n}"; + + mg_http_reply(conn, 201, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestDelete(mg_connection* conn, mg_http_message* msg) { + bool has_json_header = false; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + std::string value = std::string(header.value.ptr, header.value.len); + if (std::string{"Content-Type"} == name && std::string{"application/json"} == value) { + has_json_header = true; + break; + } + } + if (std::string{msg->method.ptr, msg->method.len} == std::string{"DELETE"}) { + std::string headers; + std::string response = "Patch success"; + if (!has_json_header) { + headers = "Content-Type: text/html\r\n"; + response = "Delete success"; + } else { + headers = "Content-Type: application/json\r\n"; + response = std::string{msg->body.ptr, msg->body.len}; + } + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } else { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } +} + +void HttpServer::OnRequestDeleteNotAllowed(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"DELETE"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } else { + std::string headers = "Content-Type: text/html\r\n"; + std::string response = "Delete success"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } +} + +void HttpServer::OnRequestPut(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"PUT"}) { + char x[100]; + char y[100]; + mg_http_get_var(&(msg->body), "x", x, sizeof(x)); + mg_http_get_var(&(msg->body), "y", y, sizeof(y)); + std::string x_string = std::string{x}; + std::string y_string = std::string{y}; + std::string headers = "Content-Type: application/json\r\n"; + std::string response; + if (y_string.empty()) { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + "\n" + "}"}; + } else { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + ",\n" + " \"y\": " + + y_string + + ",\n" + " \"sum\": " + + std::to_string(atoi(x) + atoi(y)) + + "\n" + "}"}; + } + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } else { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } +} + +void HttpServer::OnRequestPostReflect(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} != std::string{"POST"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } + + std::string response = std::string{msg->body.ptr, msg->body.len}; + std::string headers; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name{header.name.ptr, header.name.len}; + if (std::string{"Host"} != name && std::string{"Accept"} != name) { + if (header.value.ptr) { + headers.append(name + ": " + std::string(header.value.ptr, header.value.len) + "\r\n"); + } + } + } + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestPutNotAllowed(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"PUT"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } else { + std::string headers = "Content-Type: text/html\r\n"; + std::string response = "Delete success"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } +} + +void HttpServer::OnRequestPatch(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"PATCH"}) { + char x[100]; + char y[100]; + mg_http_get_var(&(msg->body), "x", x, sizeof(x)); + mg_http_get_var(&(msg->body), "y", y, sizeof(y)); + std::string x_string = std::string{x}; + std::string y_string = std::string{y}; + std::string headers = "Content-Type: application/json\r\n"; + std::string response; + if (y_string.empty()) { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + "\n" + "}"}; + } else { + response = std::string{ + "{\n" + " \"x\": " + + x_string + + ",\n" + " \"y\": " + + y_string + + ",\n" + " \"sum\": " + + std::to_string(atoi(x) + atoi(y)) + + "\n" + "}"}; + } + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } else { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } +} + +void HttpServer::OnRequestPatchNotAllowed(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"PATCH"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } else { + std::string headers = "Content-Type: text/html\r\n"; + std::string response = "Delete success"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); + } +} + +void HttpServer::OnRequestDownloadGzip(mg_connection* conn, mg_http_message* msg) { + if (std::string{msg->method.ptr, msg->method.len} == std::string{"DOWNLOAD"}) { + std::string errorMessage{"Method Not Allowed"}; + SendError(conn, 405, errorMessage); + } else { + std::string encoding; + std::string range; + std::vector<std::pair<int64_t, int64_t>> ranges; + + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"Accept-Encoding"} == name) { + encoding = std::string(header.value.ptr, header.value.len); + } else if (std::string{"Range"} == name) { + range = std::string(header.value.ptr, header.value.len); + } + } + if (encoding.find("gzip") == std::string::npos) { + std::string errorMessage{"Invalid encoding: " + encoding}; + SendError(conn, 405, errorMessage); + return; + } + if (!range.empty()) { + std::string::size_type eq_pos = range.find('='); + if (eq_pos == std::string::npos) { + std::string errorMessage{"Invalid range header: " + range}; + SendError(conn, 405, errorMessage); + return; + } + + int64_t current_start_index = eq_pos + 1; + int64_t current_end_index; + std::string::size_type range_len = range.length(); + std::string::size_type com_pos; + std::string::size_type sep_pos; + bool more_ranges_exists; + + do { + com_pos = range.find(',', current_start_index); + if (com_pos < range_len) { + current_end_index = com_pos - 1; + } else { + current_end_index = range_len - 1; + } + + std::pair<int64_t, int64_t> current_range{0, -1}; + + sep_pos = range.find('-', current_start_index); + if (sep_pos == std::string::npos) { + std::string errorMessage{"Invalid range format " + range.substr(current_start_index, current_end_index)}; + SendError(conn, 405, errorMessage); + return; + } + if (sep_pos == eq_pos + 1) { + std::string errorMessage{"Suffix ranage not supported: " + range.substr(current_start_index, current_end_index)}; + SendError(conn, 405, errorMessage); + return; + } + + current_range.first = std::strtoll(range.substr(current_start_index, sep_pos - 1).c_str(), nullptr, 10); + if (current_range.first == LLONG_MAX || current_range.first == LLONG_MIN) { + std::string errorMessage{"Start range is invalid number: " + range.substr(current_start_index, current_end_index)}; + SendError(conn, 405, errorMessage); + return; + } + + std::string er_str = range.substr(sep_pos + 1, current_end_index); + if (!er_str.empty()) { + current_range.second = std::strtoll(er_str.c_str(), nullptr, 10); + if (current_range.second == 0 || current_range.second == LLONG_MAX || current_range.second == LLONG_MIN) { + std::string errorMessage{"End range is invalid number: " + range.substr(current_start_index, current_end_index)}; + SendError(conn, 405, errorMessage); + return; + } + } + + ranges.push_back(current_range); + + if (current_end_index >= static_cast<int64_t>(range.length() - 1)) { + more_ranges_exists = false; + } else { + // Multiple ranges are separated by ', ' + more_ranges_exists = true; + current_start_index = current_end_index + 3; + } + } while (more_ranges_exists); + } + + std::string response = "Download!"; + int status_code = 200; + std::string headers; + + if (!ranges.empty()) { + // Create response parts + std::vector<std::string> responses; + for (std::pair<int64_t, int64_t> local_range : ranges) { + if (local_range.first >= 0) { + if (local_range.first >= (int64_t) response.length()) { + responses.push_back(""); + } else if (local_range.second == -1 || local_range.second >= (int64_t) response.length()) { + responses.push_back(response.substr(local_range.first)); + } else { + responses.push_back(response.substr(local_range.first, local_range.second - local_range.first + 1)); + } + } + } + + if (responses.size() > 1) { + // Create mime multipart response + std::string boundary = "3d6b6a416f9b5"; + status_code = 206; + response.clear(); + + for (size_t i{0}; i < responses.size(); ++i) { + response += "--" + boundary + "\n"; + response += "Content-Range: bytes " + std::to_string(ranges.at(i).first) + "-"; + if (ranges.at(i).second > 0) { + response += std::to_string(ranges.at(i).second); + } else { + response += std::to_string(responses.at(i).length()); + } + response += "/" + std::to_string(responses.at(i).length()) + "\n\n"; + response += responses.at(i) + "\n"; + } + response += "--" + boundary + "--"; + } else { + if (ranges.at(0).second == -1 || ranges.at(0).second >= (int64_t) response.length()) { + status_code = ranges.at(0).first > 0 ? 206 : 200; + } else { + status_code = 206; + } + response = responses.at(0); + + if (status_code == 206) { + headers = "Content-Range: bytes " + std::to_string(ranges.at(0).first) + "-"; + if (ranges.at(0).second > 0) { + headers += std::to_string(ranges.at(0).second); + } else { + headers += std::to_string(response.length()); + } + headers += "/" + std::to_string(response.length()); + } + } + } + if (!headers.empty()) { + headers += "\r\n"; + } + + mg_http_reply(conn, status_code, headers.c_str(), response.c_str()); + } +} + +void HttpServer::OnRequestCheckAcceptEncoding(mg_connection* conn, mg_http_message* msg) { + std::string response; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"Accept-Encoding"} == name) { + response = std::string(header.value.ptr, header.value.len); + } + } + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequestCheckExpect100Continue(mg_connection* conn, mg_http_message* msg) { + std::string response; + for (mg_http_header& header : msg->headers) { + if (!header.name.ptr) { + continue; + } + std::string name = std::string(header.name.ptr, header.name.len); + if (std::string{"Expect"} == name) { + response = std::string(header.value.ptr, header.value.len); + } + } + std::string headers = "Content-Type: text/html\r\n"; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +void HttpServer::OnRequest(mg_connection* conn, mg_http_message* msg) { + std::string uri = std::string(msg->uri.ptr, msg->uri.len); + if (uri == "/") { + OnRequestRoot(conn, msg); + } else if (uri == "/hello.html") { + OnRequestHello(conn, msg); + } else if (uri == "/timeout.html") { + OnRequestTimeout(conn, msg); + } else if (uri == "/long_timeout.html") { + OnRequestLongTimeout(conn, msg); + } else if (uri == "/low_speed_timeout.html") { + timer_args.emplace_back(std::make_unique<TimerArg>(&mgr, conn, mg_timer{})); + OnRequestLowSpeedTimeout(conn, msg, timer_args.back().get()); + } else if (uri == "/low_speed.html") { + OnRequestLowSpeed(conn, msg, &mgr); + } else if (uri == "/low_speed_bytes.html") { + timer_args.emplace_back(std::make_unique<TimerArg>(&mgr, conn, mg_timer{})); + OnRequestLowSpeedBytes(conn, msg, timer_args.back().get()); + } else if (uri == "/basic_cookies.html") { + OnRequestBasicCookies(conn, msg); + } else if (uri == "/empty_cookies.html") { + OnRequestEmptyCookies(conn, msg); + } else if (uri == "/cookies_reflect.html") { + OnRequestCookiesReflect(conn, msg); + } else if (uri == "/redirection_with_changing_cookies.html") { + OnRequestRedirectionWithChangingCookies(conn, msg); + } else if (uri == "/basic_auth.html") { + OnRequestBasicAuth(conn, msg); + } else if (uri == "/bearer_token.html") { + OnRequestBearerAuth(conn, msg); + } else if (uri == "/digest_auth.html") { + OnRequestHeaderReflect(conn, msg); + } else if (uri == "/basic.json") { + OnRequestBasicJson(conn, msg); + } else if (uri == "/header_reflect.html") { + OnRequestHeaderReflect(conn, msg); + } else if (uri == "/temporary_redirect.html") { + OnRequestTempRedirect(conn, msg); + } else if (uri == "/permanent_redirect.html") { + OnRequestPermRedirect(conn, msg); + } else if (uri == "/resolve_permanent_redirect.html") { + OnRequestResolvePermRedirect(conn, msg); + } else if (uri == "/two_redirects.html") { + OnRequestTwoRedirects(conn, msg); + } else if (uri == "/url_post.html") { + OnRequestUrlPost(conn, msg); + } else if (uri == "/body_get.html") { + OnRequestBodyGet(conn, msg); + } else if (uri == "/json_post.html") { + OnRequestJsonPost(conn, msg); + } else if (uri == "/form_post.html") { + OnRequestFormPost(conn, msg); + } else if (uri == "/post_reflect.html") { + OnRequestPostReflect(conn, msg); + } else if (uri == "/delete.html") { + OnRequestDelete(conn, msg); + } else if (uri == "/delete_unallowed.html") { + OnRequestDeleteNotAllowed(conn, msg); + } else if (uri == "/put.html") { + OnRequestPut(conn, msg); + } else if (uri == "/put_unallowed.html") { + OnRequestPutNotAllowed(conn, msg); + } else if (uri == "/patch.html") { + OnRequestPatch(conn, msg); + } else if (uri == "/patch_unallowed.html") { + OnRequestPatchNotAllowed(conn, msg); + } else if (uri == "/download_gzip.html") { + OnRequestDownloadGzip(conn, msg); + } else if (uri == "/local_port.html") { + OnRequestLocalPort(conn, msg); + } else if (uri == "/check_accept_encoding.html") { + OnRequestCheckAcceptEncoding(conn, msg); + } else if (uri == "/check_expect_100_continue.html") { + OnRequestCheckExpect100Continue(conn, msg); + } else { + OnRequestNotFound(conn, msg); + } +} + +void HttpServer::OnRequestLocalPort(mg_connection* conn, mg_http_message* /*msg*/) { + // send source port number as response for checking SetLocalPort/SetLocalPortRange + std::string headers = "Content-Type: text/plain\r\n"; + // Convert from big endian to little endian + std::string response = std::to_string(AbstractServer::GetRemotePort(conn)); + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/test/httpServer.hpp b/Src/external_dependencies/cpr/test/httpServer.hpp new file mode 100644 index 00000000..386dace4 --- /dev/null +++ b/Src/external_dependencies/cpr/test/httpServer.hpp @@ -0,0 +1,65 @@ +#ifndef CPR_TEST_HTTP_SERVER_H +#define CPR_TEST_HTTP_SERVER_H + +#include <memory> +#include <string> + +#include "abstractServer.hpp" +#include "cpr/cpr.h" +#include "mongoose.h" + +namespace cpr { +class HttpServer : public AbstractServer { + public: + ~HttpServer() override = default; + + std::string GetBaseUrl() override; + uint16_t GetPort() override; + + void OnRequest(mg_connection* conn, mg_http_message* msg) override; + + private: + static void OnRequestHello(mg_connection* conn, mg_http_message* msg); + static void OnRequestRoot(mg_connection* conn, mg_http_message* msg); + static void OnRequestOptions(mg_connection* conn, mg_http_message* msg); + static void OnRequestNotFound(mg_connection* conn, mg_http_message* msg); + static void OnRequestTimeout(mg_connection* conn, mg_http_message* msg); + static void OnRequestLongTimeout(mg_connection* conn, mg_http_message* msg); + static void OnRequestLowSpeedTimeout(mg_connection* conn, mg_http_message* msg, TimerArg* arg); + static void OnRequestLowSpeed(mg_connection* conn, mg_http_message* msg, mg_mgr* mgr); + static void OnRequestLowSpeedBytes(mg_connection* conn, mg_http_message* msg, TimerArg* arg); + static void OnRequestBasicCookies(mg_connection* conn, mg_http_message* msg); + static void OnRequestEmptyCookies(mg_connection* conn, mg_http_message* msg); + static void OnRequestCookiesReflect(mg_connection* conn, mg_http_message* msg); + static void OnRequestRedirectionWithChangingCookies(mg_connection* conn, mg_http_message* msg); + static void OnRequestBasicAuth(mg_connection* conn, mg_http_message* msg); + static void OnRequestBearerAuth(mg_connection* conn, mg_http_message* msg); + static void OnRequestBasicJson(mg_connection* conn, mg_http_message* msg); + static void OnRequestHeaderReflect(mg_connection* conn, mg_http_message* msg); + static void OnRequestTempRedirect(mg_connection* conn, mg_http_message* msg); + static void OnRequestPermRedirect(mg_connection* conn, mg_http_message* msg); + static void OnRequestResolvePermRedirect(mg_connection* conn, mg_http_message* msg); + static void OnRequestTwoRedirects(mg_connection* conn, mg_http_message* msg); + static void OnRequestUrlPost(mg_connection* conn, mg_http_message* msg); + static void OnRequestPostReflect(mg_connection* conn, mg_http_message* msg); + static void OnRequestBodyGet(mg_connection* conn, mg_http_message* msg); + static void OnRequestJsonPost(mg_connection* conn, mg_http_message* msg); + static void OnRequestFormPost(mg_connection* conn, mg_http_message* msg); + static void OnRequestDelete(mg_connection* conn, mg_http_message* msg); + static void OnRequestDeleteNotAllowed(mg_connection* conn, mg_http_message* msg); + static void OnRequestPut(mg_connection* conn, mg_http_message* msg); + static void OnRequestPutNotAllowed(mg_connection* conn, mg_http_message* msg); + static void OnRequestPatch(mg_connection* conn, mg_http_message* msg); + static void OnRequestPatchNotAllowed(mg_connection* conn, mg_http_message* msg); + static void OnRequestDownloadGzip(mg_connection* conn, mg_http_message* msg); + static void OnRequestLocalPort(mg_connection* conn, mg_http_message* msg); + static void OnRequestCheckAcceptEncoding(mg_connection* conn, mg_http_message* msg); + static void OnRequestCheckExpect100Continue(mg_connection* conn, mg_http_message* msg); + + protected: + mg_connection* initServer(mg_mgr* mgr, mg_event_handler_t event_handler) override; + void acceptConnection(mg_connection* conn) override; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/test/httpsServer.cpp b/Src/external_dependencies/cpr/test/httpsServer.cpp new file mode 100644 index 00000000..401a4703 --- /dev/null +++ b/Src/external_dependencies/cpr/test/httpsServer.cpp @@ -0,0 +1,65 @@ +#include "httpsServer.hpp" +#include <system_error> + +namespace cpr { +HttpsServer::HttpsServer(fs::path&& baseDirPath, fs::path&& sslCertFileName, fs::path&& sslKeyFileName) : baseDirPath(baseDirPath.make_preferred().string()), sslCertFileName(sslCertFileName.make_preferred().string()), sslKeyFileName(sslKeyFileName.make_preferred().string()) { + // See https://mongoose.ws/tutorials/tls/ + memset(static_cast<void*>(&tlsOpts), 0, sizeof(tlsOpts)); + tlsOpts.cert = this->sslCertFileName.c_str(); + tlsOpts.certkey = this->sslKeyFileName.c_str(); +} + +std::string HttpsServer::GetBaseUrl() { + return "https://127.0.0.1:" + std::to_string(GetPort()); +} + +uint16_t HttpsServer::GetPort() { + // Unassigned port in the ephemeral range + return 61937; +} + +mg_connection* HttpsServer::initServer(mg_mgr* mgr, mg_event_handler_t event_handler) { + mg_mgr_init(mgr); + + std::string port = std::to_string(GetPort()); + mg_connection* c = mg_http_listen(mgr, GetBaseUrl().c_str(), event_handler, this); + return c; +} + +void HttpsServer::acceptConnection(mg_connection* conn) { + // See https://mongoose.ws/tutorials/tls/ + mg_tls_init(conn, &tlsOpts); +} + +void HttpsServer::OnRequest(mg_connection* conn, mg_http_message* msg) { + std::string uri = std::string(msg->uri.ptr, msg->uri.len); + if (uri == "/hello.html") { + OnRequestHello(conn, msg); + } else { + OnRequestNotFound(conn, msg); + } +} + +void HttpsServer::OnRequestNotFound(mg_connection* conn, mg_http_message* /*msg*/) { + mg_http_reply(conn, 404, nullptr, "Not Found"); +} + +void HttpsServer::OnRequestHello(mg_connection* conn, mg_http_message* /*msg*/) { + std::string response{"Hello world!"}; + std::string headers{"Content-Type: text/html\r\n"}; + mg_http_reply(conn, 200, headers.c_str(), response.c_str()); +} + +const std::string& HttpsServer::getBaseDirPath() const { + return baseDirPath; +} + +const std::string& HttpsServer::getSslCertFileName() const { + return sslCertFileName; +} + +const std::string& HttpsServer::getSslKeyFileName() const { + return sslKeyFileName; +} + +} // namespace cpr diff --git a/Src/external_dependencies/cpr/test/httpsServer.hpp b/Src/external_dependencies/cpr/test/httpsServer.hpp new file mode 100644 index 00000000..cea4d343 --- /dev/null +++ b/Src/external_dependencies/cpr/test/httpsServer.hpp @@ -0,0 +1,42 @@ +#ifndef CPR_TEST_HTTPS_SERVER_H +#define CPR_TEST_HTTPS_SERVER_H + +#include <memory> +#include <string> + +#include "abstractServer.hpp" +#include "cpr/cpr.h" +#include "mongoose.h" +#include <cpr/filesystem.h> + +namespace cpr { +class HttpsServer : public AbstractServer { + private: + // We don't use fs::path here, as this leads to problems using windows + const std::string baseDirPath; + const std::string sslCertFileName; + const std::string sslKeyFileName; + struct mg_tls_opts tlsOpts; + + public: + explicit HttpsServer(fs::path&& baseDirPath, fs::path&& sslCertFileName, fs::path&& sslKeyFileName); + ~HttpsServer() override = default; + + std::string GetBaseUrl() override; + uint16_t GetPort() override; + + void OnRequest(mg_connection* conn, mg_http_message* msg) override; + static void OnRequestHello(mg_connection* conn, mg_http_message* msg); + static void OnRequestNotFound(mg_connection* conn, mg_http_message* msg); + + const std::string& getBaseDirPath() const; + const std::string& getSslCertFileName() const; + const std::string& getSslKeyFileName() const; + + protected: + mg_connection* initServer(mg_mgr* mgr, mg_event_handler_t event_handler) override; + void acceptConnection(mg_connection* conn) override; +}; +} // namespace cpr + +#endif diff --git a/Src/external_dependencies/cpr/test/interceptor_tests.cpp b/Src/external_dependencies/cpr/test/interceptor_tests.cpp new file mode 100644 index 00000000..2aaf2d62 --- /dev/null +++ b/Src/external_dependencies/cpr/test/interceptor_tests.cpp @@ -0,0 +1,369 @@ +#include <gtest/gtest.h> +#include <iostream> + +#include "cpr/cpr.h" +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +class HiddenHelloWorldRedirectInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + // Save original url + Url old_url = session.GetFullRequestUrl(); + + // Rewrite the url + Url url{server->GetBaseUrl() + "/hello.html"}; + session.SetUrl(url); + + // Procceed the chain + Response response = proceed(session); + + // Restore the url again + response.url = old_url; + return response; + } +}; + +class ChangeStatusCodeInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + // Procceed the chain + Response response = proceed(session); + + // Change the status code + response.status_code = 12345; + return response; + } +}; + +class SetBasicAuthInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + // Set authentication + session.SetAuth(Authentication{"user", "password", AuthMode::BASIC}); + + // Procceed the chain + return proceed(session); + } +}; + +class SetUnsupportedProtocolErrorInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + // Procceed the chain + Response response = proceed(session); + + // Set unsupported protocol error + response.error = Error{CURLE_UNSUPPORTED_PROTOCOL, "SetErrorInterceptor"}; + + // Return response + return response; + } +}; + +class ChangeRequestMethodToGetInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + return proceed(session, Interceptor::ProceedHttpMethod::GET_REQUEST); + } +}; + +class ChangeRequestMethodToPostInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + session.SetOption(Payload{{"x", "5"}}); + return proceed(session, Interceptor::ProceedHttpMethod::POST_REQUEST); + } +}; + +class ChangeRequestMethodToPutInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + session.SetOption(Payload{{"x", "5"}}); + return proceed(session, Interceptor::ProceedHttpMethod::PUT_REQUEST); + } +}; + +class ChangeRequestMethodToDeleteInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + return proceed(session, Interceptor::ProceedHttpMethod::DELETE_REQUEST); + } +}; + +bool write_data(std::string /*data*/, intptr_t /*userdata*/) { + return true; +} + +class ChangeRequestMethodToDownloadCallbackInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + return proceed(session, Interceptor::ProceedHttpMethod::DOWNLOAD_CALLBACK_REQUEST, WriteCallback{write_data, 0}); + } +}; + +class ChangeRequestMethodToHeadInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + return proceed(session, Interceptor::ProceedHttpMethod::HEAD_REQUEST); + } +}; + +class ChangeRequestMethodToOptionsInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + return proceed(session, Interceptor::ProceedHttpMethod::OPTIONS_REQUEST); + } +}; + +class ChangeRequestMethodToPatchInterceptor : public Interceptor { + public: + Response intercept(Session& session) override { + session.SetOption(Payload{{"x", "5"}}); + return proceed(session, Interceptor::ProceedHttpMethod::PATCH_REQUEST); + } +}; + +TEST(InterceptorTest, HiddenUrlRewriteInterceptorTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<HiddenHelloWorldRedirectInterceptor>()); + Response response = session.Get(); + + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeStatusCodeInterceptorTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeStatusCodeInterceptor>()); + Response response = session.Get(); + + long expected_status_code{12345}; + EXPECT_EQ(url, response.url); + EXPECT_EQ(expected_status_code, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, DownloadChangeStatusCodeInterceptorTest) { + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + cpr::Session session; + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeStatusCodeInterceptor>()); + Response response = session.Download(cpr::WriteCallback{write_data, 0}); + + long expected_status_code{12345}; + EXPECT_EQ(url, response.url); + EXPECT_EQ(expected_status_code, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, SetBasicAuthInterceptorTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<SetBasicAuthInterceptor>()); + + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, SetUnsupportedProtocolErrorInterceptorTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<SetUnsupportedProtocolErrorInterceptor>()); + + Response response = session.Get(); + std::string expected_error_message{"SetErrorInterceptor"}; + ErrorCode expected_error_code{ErrorCode::UNSUPPORTED_PROTOCOL}; + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(expected_error_message, response.error.message); + EXPECT_EQ(expected_error_code, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToGetInterceptorTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToGetInterceptor>()); + Response response = session.Head(); + + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToPostInterceptorTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToPostInterceptor>()); + Response response = session.Head(); + + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToPutInterceptorTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToPutInterceptor>()); + Response response = session.Get(); + + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToPatchInterceptorTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToPatchInterceptor>()); + Response response = session.Get(); + + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToOptionsInterceptorTest) { + Url url{server->GetBaseUrl() + "/"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToOptionsInterceptor>()); + Response response = session.Get(); + + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToHeadInterceptorTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToHeadInterceptor>()); + Response response = session.Get(); + + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToDownloadCallbackInterceptorTest) { + Url url{server->GetBaseUrl() + "/download_gzip.html"}; + Session session; + session.SetUrl(url); + session.SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + session.SetTimeout(Timeout{2000}); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToDownloadCallbackInterceptor>()); + Response response = session.Put(); + + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ChangeRequestMethodToDeleteInterceptorTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<ChangeRequestMethodToDeleteInterceptor>()); + Response response = session.Get(); + + + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, TwoInterceptorsTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<HiddenHelloWorldRedirectInterceptor>()); + session.AddInterceptor(std::make_shared<ChangeStatusCodeInterceptor>()); + Response response = session.Get(); + + std::string expected_text{"Hello world!"}; + long expected_status_code{12345}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(expected_status_code, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(InterceptorTest, ThreeInterceptorsTest) { + Url url{server->GetBaseUrl() + "/basic.json"}; + Session session; + session.SetUrl(url); + session.AddInterceptor(std::make_shared<HiddenHelloWorldRedirectInterceptor>()); + session.AddInterceptor(std::make_shared<ChangeStatusCodeInterceptor>()); + session.AddInterceptor(std::make_shared<SetUnsupportedProtocolErrorInterceptor>()); + Response response = session.Get(); + + std::string expected_text{"Hello world!"}; + long expected_status_code{12345}; + std::string expected_error_message{"SetErrorInterceptor"}; + ErrorCode expected_error_code{ErrorCode::UNSUPPORTED_PROTOCOL}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(expected_status_code, response.status_code); + EXPECT_EQ(expected_error_message, response.error.message); + EXPECT_EQ(expected_error_code, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +}
\ No newline at end of file diff --git a/Src/external_dependencies/cpr/test/multiperform_tests.cpp b/Src/external_dependencies/cpr/test/multiperform_tests.cpp new file mode 100644 index 00000000..6d6dcdf0 --- /dev/null +++ b/Src/external_dependencies/cpr/test/multiperform_tests.cpp @@ -0,0 +1,673 @@ +#include <cstdint> +#include <gtest/gtest.h> + +#include <memory> +#include <string> + +#include <cpr/api.h> +#include <cpr/cpr.h> +#include <curl/curl.h> + +#include "httpServer.hpp" +#include "httpsServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +bool write_data(std::string /*data*/, intptr_t /*userdata*/) { + return true; +} + +TEST(MultiperformAddSessionTests, MultiperformAddSingleSessionTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session); + + EXPECT_EQ(2, session.use_count()); +} + +TEST(MultiperformAddSessionTests, MultiperformAddMultipleSessionsTest) { + MultiPerform multiperform; + for (int i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + multiperform.AddSession(session); + EXPECT_EQ(2, session.use_count()); + } +} + +TEST(MultiperformAddSessionTests, MultiperformAddMultipleNonDownloadSessionsTest) { + MultiPerform multiperform; + for (int i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + multiperform.AddSession(session, MultiPerform::HttpMethod::GET_REQUEST); + EXPECT_EQ(2, session.use_count()); + } +} + +TEST(MultiperformAddSessionTests, MultiperformAddMultipleDownloadSessionsTest) { + MultiPerform multiperform; + for (int i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + multiperform.AddSession(session, MultiPerform::HttpMethod::DOWNLOAD_REQUEST); + EXPECT_EQ(2, session.use_count()); + } +} + +TEST(MultiperformAddSessionTests, MultiperformAddTwoMixedTypeSessionsTest) { + std::shared_ptr<Session> session_1 = std::make_shared<Session>(); + std::shared_ptr<Session> session_2 = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session_1, MultiPerform::HttpMethod::GET_REQUEST); + EXPECT_EQ(2, session_1.use_count()); + EXPECT_THROW(multiperform.AddSession(session_2, MultiPerform::HttpMethod::DOWNLOAD_REQUEST), std::invalid_argument); +} + +TEST(MultiperformAddSessionTests, MultiperformAddTwoMixedTypeSessionsReversTest) { + std::shared_ptr<Session> session_1 = std::make_shared<Session>(); + std::shared_ptr<Session> session_2 = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session_1, MultiPerform::HttpMethod::DOWNLOAD_REQUEST); + EXPECT_EQ(2, session_1.use_count()); + EXPECT_THROW(multiperform.AddSession(session_2, MultiPerform::HttpMethod::GET_REQUEST), std::invalid_argument); +} + +TEST(MultiperformRemoveSessionTests, MultiperformRemoveSingleSessionTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session); + EXPECT_EQ(2, session.use_count()); + multiperform.RemoveSession(session); + EXPECT_EQ(1, session.use_count()); +} + +TEST(MultiperformRemoveSessionTests, MultiperformRemoveMultipleSessionsTest) { + MultiPerform multiperform; + for (int i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + multiperform.AddSession(session); + EXPECT_EQ(2, session.use_count()); + multiperform.RemoveSession(session); + EXPECT_EQ(1, session.use_count()); + } +} + +TEST(MultiperformRemoveSessionTests, MultiperformRemoveNonExistingSessionTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + MultiPerform multiperform; + EXPECT_THROW(multiperform.RemoveSession(session), std::invalid_argument); +} + +TEST(MultiperformGetTests, MultiperformSingleSessionGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Get(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformGetTests, MultiperformTwoSessionsGetTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/hello.html"}); + urls.push_back({server->GetBaseUrl() + "/error.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + multiperform.AddSession(sessions.at(i)); + } + + std::vector<Response> responses = multiperform.Get(); + + EXPECT_EQ(responses.size(), sessions.size()); + std::vector<std::string> expected_texts; + expected_texts.emplace_back("Hello world!"); + expected_texts.emplace_back("Not Found"); + + std::vector<std::string> expected_content_types; + expected_content_types.emplace_back("text/html"); + expected_content_types.emplace_back("text/plain"); + + std::vector<uint16_t> expected_status_codes; + expected_status_codes.push_back(200); + expected_status_codes.push_back(404); + + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(expected_texts.at(i), responses.at(i).text); + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(expected_content_types.at(i), responses.at(i).header["content-type"]); + EXPECT_EQ(expected_status_codes.at(i), responses.at(i).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(i).error.code); + } +} + +TEST(MultiperformGetTests, MultiperformRemoveSessionGetTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/hello.html"}); + urls.push_back({server->GetBaseUrl() + "/hello.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + multiperform.AddSession(sessions.at(i)); + } + + multiperform.RemoveSession(sessions.at(0)); + + std::vector<Response> responses = multiperform.Get(); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(urls.at(0), responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +#ifndef __APPLE__ +/** + * This test case is currently disabled for macOS/Apple systems since it fails in an nondeterministic manner. + * It is probably caused by a bug inside curl_multi_perform on macOS. + * Needs further investigation. + * Issue: https://github.com/libcpr/cpr/issues/841 + **/ +TEST(MultiperformGetTests, MultiperformTenSessionsGetTest) { + const size_t sessionCount = 10; + + MultiPerform multiperform; + Url url{server->GetBaseUrl() + "/hello.html"}; + for (size_t i = 0; i < sessionCount; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + multiperform.AddSession(session); + } + + std::vector<Response> responses = multiperform.Get(); + + EXPECT_EQ(responses.size(), sessionCount); + for (Response& response : responses) { + EXPECT_EQ(std::string{"Hello world!"}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} +#endif + +TEST(MultiperformDeleteTests, MultiperformSingleSessionDeleteTest) { + Url url{server->GetBaseUrl() + "/delete.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Delete(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformDownloadTests, MultiperformSingleSessionDownloadTest) { + Url url{server->GetBaseUrl() + "/download_gzip.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Download(WriteCallback{write_data, 0}); + + EXPECT_EQ(responses.size(), 1); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(cpr::ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformDownloadTests, MultiperformSingleSessionDownloadNonMatchingArgumentsNumberTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session); + EXPECT_THROW(std::vector<Response> responses = multiperform.Download(WriteCallback{write_data, 0}, WriteCallback{write_data, 0}), std::invalid_argument); +} + +TEST(MultiperformDownloadTests, MultiperformTwoSessionsDownloadTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/download_gzip.html"}); + urls.push_back({server->GetBaseUrl() + "/download_gzip.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + sessions.at(i)->SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + + multiperform.AddSession(sessions.at(i)); + } + + std::vector<Response> responses = multiperform.Download(WriteCallback{write_data, 0}, WriteCallback{write_data, 0}); + + EXPECT_EQ(responses.size(), sessions.size()); + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(200, responses.at(i).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(i).error.code); + } +} + +TEST(MultiperformPutTests, MultiperformSingleSessionPutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload(Payload{{"x", "5"}}); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Put(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformHeadTests, MultiperformSingleSessionHeadTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Head(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformOptionsTests, MultiperformSingleSessionOptionsTest) { + Url url{server->GetBaseUrl() + "/"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Options(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, responses.at(0).header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformPatchTests, MultiperformSingleSessionPatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload(Payload{{"x", "5"}}); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Patch(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformPostTests, MultiperformSingleSessionPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload(Payload{{"x", "5"}}); + MultiPerform multiperform; + multiperform.AddSession(session); + std::vector<Response> responses = multiperform.Post(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(201, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformPerformTests, MultiperformSingleGetPerformTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + MultiPerform multiperform; + multiperform.AddSession(session, MultiPerform::HttpMethod::GET_REQUEST); + std::vector<Response> responses = multiperform.Perform(); + + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformPerformTests, MultiperformTwoGetPerformTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/hello.html"}); + urls.push_back({server->GetBaseUrl() + "/error.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + multiperform.AddSession(sessions.at(i), MultiPerform::HttpMethod::GET_REQUEST); + } + + std::vector<Response> responses = multiperform.Perform(); + + EXPECT_EQ(responses.size(), sessions.size()); + std::vector<std::string> expected_texts; + expected_texts.emplace_back("Hello world!"); + expected_texts.emplace_back("Not Found"); + + std::vector<std::string> expected_content_types; + expected_content_types.emplace_back("text/html"); + expected_content_types.emplace_back("text/plain"); + + std::vector<uint16_t> expected_status_codes; + expected_status_codes.push_back(200); + expected_status_codes.push_back(404); + + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(expected_texts.at(i), responses.at(i).text); + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(expected_content_types.at(i), responses.at(i).header["content-type"]); + EXPECT_EQ(expected_status_codes.at(i), responses.at(i).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(i).error.code); + } +} + +TEST(MultiperformPerformTests, MultiperformMixedMethodsPerformTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/hello.html"}); + urls.push_back({server->GetBaseUrl() + "/delete.html"}); + urls.push_back({server->GetBaseUrl() + "/error.html"}); + urls.push_back({server->GetBaseUrl() + "/url_post.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + std::vector<MultiPerform::HttpMethod> methods; + methods.push_back(MultiPerform::HttpMethod::GET_REQUEST); + methods.push_back(MultiPerform::HttpMethod::DELETE_REQUEST); + methods.push_back(MultiPerform::HttpMethod::GET_REQUEST); + methods.push_back(MultiPerform::HttpMethod::POST_REQUEST); + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + if (methods.at(i) == MultiPerform::HttpMethod::POST_REQUEST) { + sessions.at(i)->SetPayload(Payload{{"x", "5"}}); + } + multiperform.AddSession(sessions.at(i), methods.at(i)); + } + + std::vector<Response> responses = multiperform.Perform(); + + EXPECT_EQ(responses.size(), sessions.size()); + + std::vector<std::string> expected_texts; + expected_texts.emplace_back("Hello world!"); + expected_texts.emplace_back("Delete success"); + expected_texts.emplace_back("Not Found"); + expected_texts.emplace_back( + "{\n" + " \"x\": 5\n" + "}"); + + std::vector<std::string> expected_content_types; + expected_content_types.emplace_back("text/html"); + expected_content_types.emplace_back("text/html"); + expected_content_types.emplace_back("text/plain"); + expected_content_types.emplace_back("application/json"); + + std::vector<uint16_t> expected_status_codes; + expected_status_codes.push_back(200); + expected_status_codes.push_back(200); + expected_status_codes.push_back(404); + expected_status_codes.push_back(201); + + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(expected_texts.at(i), responses.at(i).text); + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(expected_content_types.at(i), responses.at(i).header["content-type"]); + EXPECT_EQ(expected_status_codes.at(i), responses.at(i).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(i).error.code); + } +} + +TEST(MultiperformPerformDownloadTests, MultiperformSinglePerformDownloadTest) { + Url url{server->GetBaseUrl() + "/download_gzip.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + MultiPerform multiperform; + multiperform.AddSession(session, MultiPerform::HttpMethod::DOWNLOAD_REQUEST); + std::vector<Response> responses = multiperform.PerformDownload(WriteCallback{write_data, 0}); + + EXPECT_EQ(responses.size(), 1); + EXPECT_EQ(url, responses.at(0).url); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(cpr::ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformDownloadTests, MultiperformSinglePerformDownloadNonMatchingArgumentsNumberTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + MultiPerform multiperform; + multiperform.AddSession(session, MultiPerform::HttpMethod::DOWNLOAD_REQUEST); + EXPECT_THROW(std::vector<Response> responses = multiperform.PerformDownload(WriteCallback{write_data, 0}, WriteCallback{write_data, 0}), std::invalid_argument); +} + +TEST(MultiperformPerformDownloadTests, MultiperformTwoPerformDownloadTest) { + MultiPerform multiperform; + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/download_gzip.html"}); + urls.push_back({server->GetBaseUrl() + "/download_gzip.html"}); + + std::vector<std::shared_ptr<Session>> sessions; + sessions.push_back(std::make_shared<Session>()); + sessions.push_back(std::make_shared<Session>()); + + + for (size_t i = 0; i < sessions.size(); ++i) { + sessions.at(i)->SetUrl(urls.at(i)); + sessions.at(i)->SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + + multiperform.AddSession(sessions.at(i), MultiPerform::HttpMethod::DOWNLOAD_REQUEST); + } + + std::vector<Response> responses = multiperform.PerformDownload(WriteCallback{write_data, 0}, WriteCallback{write_data, 0}); + + EXPECT_EQ(responses.size(), sessions.size()); + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(200, responses.at(i).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(i).error.code); + } +} + +TEST(MultiperformAPITests, MultiperformApiSingleGetTest) { + std::vector<Response> responses = MultiGet(std::tuple<Url>{Url{server->GetBaseUrl() + "/hello.html"}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiTwoGetsTest) { + std::vector<Response> responses = MultiGet(std::tuple<Url, Timeout>{Url{server->GetBaseUrl() + "/long_timeout.html"}, Timeout{1000}}, std::tuple<Url>{Url{server->GetBaseUrl() + "/error.html"}}); + + EXPECT_EQ(responses.size(), 2); + std::vector<Url> urls; + urls.push_back({server->GetBaseUrl() + "/long_timeout.html"}); + urls.push_back({server->GetBaseUrl() + "/error.html"}); + + std::vector<std::string> expected_texts; + expected_texts.emplace_back(""); + expected_texts.emplace_back("Not Found"); + + std::vector<std::string> expected_content_types; + expected_content_types.emplace_back(""); + expected_content_types.emplace_back("text/plain"); + + std::vector<uint16_t> expected_status_codes; + expected_status_codes.push_back(0); + expected_status_codes.push_back(404); + + std::vector<ErrorCode> expected_error_codes; + expected_error_codes.push_back(ErrorCode::OPERATION_TIMEDOUT); + expected_error_codes.push_back(ErrorCode::OK); + + for (size_t i = 0; i < responses.size(); ++i) { + EXPECT_EQ(expected_texts.at(i), responses.at(i).text); + EXPECT_EQ(urls.at(i), responses.at(i).url); + EXPECT_EQ(expected_content_types.at(i), responses.at(i).header["content-type"]); + EXPECT_EQ(expected_status_codes.at(i), responses.at(i).status_code); + EXPECT_EQ(expected_error_codes.at(i), responses.at(i).error.code); + } +} + +TEST(MultiperformAPITests, MultiperformApiSingleDeleteTest) { + std::vector<Response> responses = MultiDelete(std::tuple<Url>{Url{server->GetBaseUrl() + "/delete.html"}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{"Delete success"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/delete.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiSinglePutTest) { + std::vector<Response> responses = MultiPut(std::tuple<Url, Payload>{Url{server->GetBaseUrl() + "/put.html"}, Payload{{"x", "5"}}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/put.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiSingleHeadTest) { + std::vector<Response> responses = MultiHead(std::tuple<Url>{Url{server->GetBaseUrl() + "/hello.html"}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"text/html"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiSingleOptionsTest) { + std::vector<Response> responses = MultiOptions(std::tuple<Url>{Url{server->GetBaseUrl() + "/"}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/"}, responses.at(0).url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, responses.at(0).header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiSinglePatchTest) { + std::vector<Response> responses = MultiPatch(std::tuple<Url, Payload>{Url{server->GetBaseUrl() + "/patch.html"}, Payload{{"x", "5"}}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/patch.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(200, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +TEST(MultiperformAPITests, MultiperformApiSinglePostTest) { + std::vector<Response> responses = MultiPost(std::tuple<Url, Payload>{Url{server->GetBaseUrl() + "/url_post.html"}, Payload{{"x", "5"}}}); + EXPECT_EQ(responses.size(), 1); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, responses.at(0).text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/url_post.html"}, responses.at(0).url); + EXPECT_EQ(std::string{"application/json"}, responses.at(0).header["content-type"]); + EXPECT_EQ(201, responses.at(0).status_code); + EXPECT_EQ(ErrorCode::OK, responses.at(0).error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/options_tests.cpp b/Src/external_dependencies/cpr/test/options_tests.cpp new file mode 100644 index 00000000..ee3176f7 --- /dev/null +++ b/Src/external_dependencies/cpr/test/options_tests.cpp @@ -0,0 +1,73 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(OptionsTests, BaseUrlTest) { + Url url{server->GetBaseUrl() + "/"}; + Response response = cpr::Options(url); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(OptionsTests, SpecificUrlTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Options(url); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(OptionsTests, AsyncBaseUrlTest) { + Url url{server->GetBaseUrl() + "/"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::OptionsAsync(url)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(OptionsTests, AsyncSpecificUrlTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::OptionsAsync(url)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/patch_tests.cpp b/Src/external_dependencies/cpr/test/patch_tests.cpp new file mode 100644 index 00000000..22d0b4f4 --- /dev/null +++ b/Src/external_dependencies/cpr/test/patch_tests.cpp @@ -0,0 +1,276 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(PatchTests, PatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + Response response = cpr::Patch(url, payload); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, PatchUnallowedTest) { + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + Response response = cpr::Patch(url, payload); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + Session session; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchUnallowedTest) { + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + Session session; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchUnallowedAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchUnallowedAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, SessionPatchUnallowedAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Patch(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, AsyncPatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + cpr::AsyncResponse future_response = cpr::PatchAsync(url, payload); + cpr::Response response = future_response.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, AsyncPatchUnallowedTest) { + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + cpr::AsyncResponse future_response = cpr::PatchAsync(url, payload); + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PatchTests, AsyncMultiplePatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::PatchAsync(url, payload)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(PatchTests, AsyncMultiplePatchUnallowedTest) { + Url url{server->GetBaseUrl() + "/patch_unallowed.html"}; + Payload payload{{"x", "5"}}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::PatchAsync(url, payload)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/post_tests.cpp b/Src/external_dependencies/cpr/test/post_tests.cpp new file mode 100644 index 00000000..a4d08e5a --- /dev/null +++ b/Src/external_dependencies/cpr/test/post_tests.cpp @@ -0,0 +1,756 @@ +#include <gtest/gtest.h> + +#include <array> +#include <cstdio> +#include <fstream> +#include <string> + +#include <cpr/cpr.h> +#include <cpr/multipart.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(UrlEncodedPostTests, UrlPostSingleTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, UrlPostAddPayloadPair) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "1"}}; + payload.Add({"y", "2"}); + Response response = cpr::Post(url, payload); + std::string expected_text{ + "{\n" + " \"x\": 1,\n" + " \"y\": 2,\n" + " \"sum\": 3\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); +} + +TEST(UrlEncodedPostTests, UrlPostPayloadIteratorTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::vector<Pair> payloadData; + payloadData.emplace_back("x", "1"); + payloadData.emplace_back("y", "2"); + Response response = cpr::Post(url, Payload(payloadData.begin(), payloadData.end())); + std::string expected_text{ + "{\n" + " \"x\": 1,\n" + " \"y\": 2,\n" + " \"sum\": 3\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); +} + +TEST(UrlEncodedPostTests, UrlPostEncodeTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Payload{{"x", "hello world!!~"}}); + std::string expected_text{ + "{\n" + " \"x\": hello world!!~\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, UrlPostEncodeNoCopyTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "hello world!!~"}}; + // payload lives through the lifetime of Post, so it doesn't need to be copied + Response response = cpr::Post(url, payload); + std::string expected_text{ + "{\n" + " \"x\": hello world!!~\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, UrlPostManyTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}, {"y", "13"}}); + std::string expected_text{ + "{\n" + " \"x\": 5,\n" + " \"y\": 13,\n" + " \"sum\": 18\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, UrlPostBadHostTest) { + Url url{"http://bad_host/"}; + Response response = cpr::Post(url, Payload{{"hello", "world"}}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::HOST_RESOLUTION_FAILURE, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostSingleTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", 5}}); + std::string expected_text{ + "{\n" + " \"x\": \"5\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileTest) { + std::string filename{"test_file"}; + std::string content{"hello world"}; + std::ofstream test_file; + test_file.open(filename); + test_file << content; + test_file.close(); + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", File{filename}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + content + + "\"\n" + "}"}; + std::remove(filename.c_str()); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostMultipleFilesTestLvalue) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + std::string filename1{"file1"}; + std::string content1{"apple"}; + std::ofstream file1; + file1.open(filename1); + file1 << content1; + file1.close(); + std::string filename2{"file2"}; + std::string content2{"banana"}; + std::ofstream file2; + file2.open(filename2); + file2 << content2; + file2.close(); + File singleFile{"file1"}; + File singleFileWithOverridedFilename{"file1", "applefile"}; + Files multipleFiles{"file1", "file2"}; + Files multipleFilesWithOverridedFilename{ + File{"file1", "applefile"}, + File{"file2", "bananafile"}, + }; + { + Response response = cpr::Post(url, Multipart{{"files", singleFile}}); + std::string expected_text{ + "{\n" + " \"files\": \"file1=" + + content1 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", singleFileWithOverridedFilename}}); + std::string expected_text{ + "{\n" + " \"files\": \"applefile=" + + content1 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", multipleFiles}}); + std::string expected_text{ + "{\n" + " \"files\": \"file1=" + + content1 + + "\",\n" + " \"files\": \"file2=" + + content2 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", multipleFilesWithOverridedFilename}}); + std::string expected_text{ + "{\n" + " \"files\": \"applefile=" + + content1 + + "\",\n" + " \"files\": \"bananafile=" + + content2 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + std::remove(filename1.c_str()); + std::remove(filename2.c_str()); +} + +TEST(UrlEncodedPostTests, FormPostMultipleFilesTestRvalue) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + std::string filename1{"file1"}; + std::string content1{"apple"}; + std::ofstream file1; + file1.open(filename1); + file1 << content1; + file1.close(); + std::string filename2{"file2"}; + std::string content2{"banana"}; + std::ofstream file2; + file2.open(filename2); + file2 << content2; + file2.close(); + { + Response response = cpr::Post(url, Multipart{{"files", File{"file1"}}}); + std::string expected_text{ + "{\n" + " \"files\": \"file1=" + + content1 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", File{"file1", "applefile"}}}); + std::string expected_text{ + "{\n" + " \"files\": \"applefile=" + + content1 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", Files{"file1", "file2"}}}); + std::string expected_text{ + "{\n" + " \"files\": \"file1=" + + content1 + + "\",\n" + " \"files\": \"file2=" + + content2 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = cpr::Post(url, Multipart{{"files", Files{ + File{"file1", "applefile"}, + File{"file2", "bananafile"}, + }}}); + std::string expected_text{ + "{\n" + " \"files\": \"applefile=" + + content1 + + "\",\n" + " \"files\": \"bananafile=" + + content2 + "\"\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + std::remove(filename1.c_str()); + std::remove(filename2.c_str()); +} + +TEST(UrlEncodedPostTests, FormPostFileTestWithOverridedFilename) { + std::string filename{"test_file"}; + std::string overided_filename{"overided_filename"}; + std::string content{"hello world"}; + std::ofstream test_file; + test_file.open(filename); + test_file << content; + test_file.close(); + Url url{server->GetBaseUrl() + "/form_post.html"}; + + Response response = cpr::Post(url, Multipart{{"x", File{filename, overided_filename}}}); + std::string expected_text{ + "{\n" + " \"x\": \"" + + overided_filename + "=" + content + + "\"\n" + "}"}; + std::remove(filename.c_str()); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileNoCopyTest) { + std::string filename{"./test_file"}; + std::string content{"hello world"}; + std::ofstream test_file; + test_file.open(filename); + test_file << content; + test_file.close(); + Url url{server->GetBaseUrl() + "/form_post.html"}; + Multipart multipart{{"x", File{filename}}}; + Response response = cpr::Post(url, multipart); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + content + + "\"\n" + "}"}; + std::remove(filename.c_str()); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileNoCopyTestWithOverridedFilename) { + std::string filename{"test_file"}; + std::string overrided_filename{"overided_filename"}; + std::string content{"hello world"}; + std::ofstream test_file; + test_file.open(filename); + test_file << content; + test_file.close(); + Url url{server->GetBaseUrl() + "/form_post.html"}; + Multipart multipart{{"x", File{filename, overrided_filename}}}; + Response response = cpr::Post(url, multipart); + std::string expected_text{ + "{\n" + " \"x\": \"" + + overrided_filename + "=" + content + + "\"\n" + "}"}; + std::remove(filename.c_str()); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, TimeoutPostTest) { + Url url{server->GetBaseUrl() + "/json_post.html"}; + std::string body{R"({"RegisterObject": {"DeviceID": "65010000005030000001"}})"}; + cpr::Response response = cpr::Post(url, cpr::Header{{"Content-Type", "application/json"}}, cpr::Body{body}, cpr::ConnectTimeout{3000}, cpr::Timeout{3000}); + std::string expected_text{R"({"RegisterObject": {"DeviceID": "65010000005030000001"}})"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferTest) { + std::string content{"hello world"}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", Buffer{content.begin(), content.end(), "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + content + + "\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferNoCopyTest) { + std::string content{"hello world"}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Multipart multipart{{"x", Buffer{content.begin(), content.end(), "test_file"}}}; + Response response = cpr::Post(url, multipart); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + content + + "\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferPointerTest) { + const char* content = "hello world"; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", Buffer{content, 11 + content, "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + std::string(content) + + "\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferArrayTest) { + const char content[] = "hello world"; + Url url{server->GetBaseUrl() + "/form_post.html"}; + // We subtract 1 from std::end() because we don't want to include the terminating null + Response response = cpr::Post(url, Multipart{{"x", Buffer{std::begin(content), std::end(content) - 1, "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=" + + std::string(content) + + "\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferVectorTest) { + std::vector<unsigned char> content{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", Buffer{content.begin(), content.end(), "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=hello world\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostFileBufferStdArrayTest) { + std::array<unsigned char, 11> content{{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", Buffer{content.begin(), content.end(), "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=hello world\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostBufferRvalueTest) { + std::vector<unsigned char> content{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", Buffer{content.begin(), content.end(), "test_file"}}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=hello world\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, ReflectPostBufferLvalueTest) { + std::vector<unsigned char> content{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; + Url url{server->GetBaseUrl() + "/form_post.html"}; + Buffer buff{content.begin(), content.end(), "test_file"}; + Response response = cpr::Post(url, Multipart{{"x", buff}}); + std::string expected_text{ + "{\n" + " \"x\": \"test_file=hello world\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostManyTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", 5}, {"y", 13}}); + std::string expected_text{ + "{\n" + " \"x\": \"5\",\n" + " \"y\": \"13\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostManyNoCopyTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Multipart multipart{{"x", 5}, {"y", 13}}; + Response response = cpr::Post(url, multipart); + std::string expected_text{ + "{\n" + " \"x\": \"5\",\n" + " \"y\": \"13\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostContentTypeTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url, Multipart{{"x", 5, "application/number"}}); + std::string expected_text{ + "{\n" + " \"x\": \"5\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, FormPostContentTypeLValueTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Multipart multipart{{"x", 5, "application/number"}}; + Response response = cpr::Post(url, multipart); + std::string expected_text{ + "{\n" + " \"x\": \"5\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, UrlPostAsyncSingleTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::PostAsync(url, payload)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(UrlEncodedPostTests, UrlReflectTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UrlEncodedPostTests, PostWithNoBodyTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Response response = cpr::Post(url); + std::string expected_text{"{\n}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +static std::string getTimestamp() { + char buf[1000]; + time_t now = time(0); + struct tm* tm = gmtime(&now); + strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tm); + return buf; +} + +TEST(UrlEncodedPostTests, PostReflectTest) { + std::string uri = server->GetBaseUrl() + "/post_reflect.html"; + std::string body = R"({"property1": "value1"})"; + std::string contentType = "application/json"; + std::string signature = "x-ms-date: something"; + std::string logType = "LoggingTest"; + std::string date = getTimestamp(); + Response response = cpr::Post(cpr::Url(uri), cpr::Header{{"content-type", contentType}, {"Authorization", signature}, {"log-type", logType}, {"x-ms-date", date}, {"content-length", std::to_string(body.length())}}, cpr::Body(body)); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(body, response.text); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(signature, response.header["Authorization"]); + EXPECT_EQ(logType, response.header["log-type"]); + EXPECT_EQ(date, response.header["x-ms-date"]); + EXPECT_EQ(std::to_string(body.length()), response.header["content-length"]); +} + +TEST(UrlEncodedPostTests, PostReflectPayloadTest) { + std::string uri = server->GetBaseUrl() + "/header_reflect.html"; + cpr::Payload payload = cpr::Payload{{"email", ""}, {"password", ""}, {"devicetoken", ""}}; + cpr::Response response = cpr::Post(cpr::Url(uri), cpr::Timeout{10000}, payload); + + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(200, response.status_code); +} + +TEST(UrlEncodedPostTests, InjectMultipleHeadersTest) { + std::string uri = server->GetBaseUrl() + "/post_reflect.html"; + std::string key_1 = "key_1"; + std::string val_1 = "value_1"; + std::string key_2 = "key_2"; + std::string val_2 = "value_2"; + cpr::Response response = cpr::Post(cpr::Url{uri}, cpr::Header{{key_1, val_1}}, cpr::Header{{key_2, val_2}}); + + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(val_1, response.header[key_1]); + EXPECT_EQ(val_2, response.header[key_2]); +} + +TEST(UrlEncodedPostTests, PostBodyWithFile) { + std::string filename{"test_file"}; + std::string expected_text(R"({"property1": "value1"})"); + std::ofstream test_file; + test_file.open(filename); + test_file << expected_text; + test_file.close(); + Url url{server->GetBaseUrl() + "/post_reflect.html"}; + cpr::Response response = Post(url, cpr::Header({{"Content-Type", "application/octet-stream"}}), cpr::Body(File("test_file"))); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(std::string{"application/octet-stream"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); +} + +TEST(UrlEncodedPostTests, PostBodyWithBuffer) { + Url url{server->GetBaseUrl() + "/post_reflect.html"}; + std::string expected_text(R"({"property1": "value1"})"); + cpr::Response response = Post(url, cpr::Header({{"Content-Type", "application/octet-stream"}}), cpr::Body(Buffer{expected_text.begin(), expected_text.end(), "test_file"})); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/octet-stream"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PostRedirectTests, TempRedirectTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}, Header{{"RedirectLocation", "url_post.html"}}); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(response.url, server->GetBaseUrl() + "/url_post.html"); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PostRedirectTests, TempRedirectNoneTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}, Header{{"RedirectLocation", "url_post.html"}}, Redirect(PostRedirectFlags::NONE)); + EXPECT_EQ(response.url, server->GetBaseUrl() + "/url_post.html"); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PostRedirectTests, PermRedirectTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}, Header{{"RedirectLocation", "url_post.html"}}); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(response.url, server->GetBaseUrl() + "/url_post.html"); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PostRedirectTests, PermRedirectNoneTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Response response = cpr::Post(url, Payload{{"x", "5"}}, Header{{"RedirectLocation", "url_post.html"}}, Redirect(PostRedirectFlags::NONE)); + EXPECT_EQ(response.url, server->GetBaseUrl() + "/url_post.html"); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/prepare_tests.cpp b/Src/external_dependencies/cpr/test/prepare_tests.cpp new file mode 100644 index 00000000..d7332c9b --- /dev/null +++ b/Src/external_dependencies/cpr/test/prepare_tests.cpp @@ -0,0 +1,138 @@ +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(PrepareTests, GetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.PrepareGet(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PrepareTests, OptionsTests) { + Url url{server->GetBaseUrl() + "/"}; + Session session; + session.SetUrl(url); + session.PrepareOptions(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"GET, POST, PUT, DELETE, PATCH, OPTIONS"}, response.header["Access-Control-Allow-Methods"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PrepareTests, PatchTest) { + Url url{server->GetBaseUrl() + "/patch.html"}; + Payload payload{{"x", "5"}}; + Session session; + session.SetUrl(url); + session.SetPayload(payload); + session.PreparePatch(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PrepareTests, MultipleDeleteHeadPutGetPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Url urlPost{server->GetBaseUrl() + "/post_reflect.html"}; + Url urlPut{server->GetBaseUrl() + "/put.html"}; + Session session; + for (size_t i = 0; i < 3; ++i) { + { + session.SetUrl(url); + session.PrepareDelete(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{"Header reflect DELETE"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(urlPost); + std::string expectedBody = "a1b2c3Post"; + session.SetBody(expectedBody); + session.PreparePost(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + EXPECT_EQ(expectedBody, response.text); + EXPECT_EQ(urlPost, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(url); + session.PrepareGet(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(urlPut); + session.SetPayload({{"x", "5"}}); + session.PreparePut(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(urlPut, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(url); + session.PrepareHead(); + CURLcode curl_result = curl_easy_perform(session.GetCurlHolder()->handle); + Response response = session.Complete(curl_result); + std::string expected_text{"Header reflect HEAD"}; + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + } +} + + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/proxy_auth_tests.cpp b/Src/external_dependencies/cpr/test/proxy_auth_tests.cpp new file mode 100644 index 00000000..f618ba2f --- /dev/null +++ b/Src/external_dependencies/cpr/test/proxy_auth_tests.cpp @@ -0,0 +1,94 @@ +#include <gtest/gtest.h> + +#include <string> + +#include "cpr/cpr.h" +#include "httpServer.hpp" + +// TODO: This requires a local proxy server (squid). This should be replaced with a source +// code implementation. + +#define HTTP_PROXY "127.0.0.1:3128" +#define HTTPS_PROXY "127.0.0.1:3128" +#define PROXY_USER "u$er" +#define PROXY_PASS "p@ss" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +// TODO: These should be fixed after a source code implementation of a proxy +#if defined(false) +TEST(ProxyAuthTests, SingleProxyTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, Proxies{{"http", HTTP_PROXY}}, ProxyAuthentication{{"http", EncodedAuthentication{PROXY_USER, PROXY_PASS}}}); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyAuthTests, MultipleProxyHttpTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Response response = cpr::Get(url, Proxies{{"https", HTTPS_PROXY}, {"http", HTTP_PROXY}}, ProxyAuthentication{{"http", EncodedAuthentication{PROXY_USER, PROXY_PASS}}, {"https", EncodedAuthentication{PROXY_USER, PROXY_PASS}}}); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyAuthTests, CopyProxyTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Proxies proxies{{"http", HTTP_PROXY}}; + ProxyAuthentication proxy_auth{{"http", EncodedAuthentication{PROXY_USER, PROXY_PASS}}}; + Response response = cpr::Get(url, proxies, proxy_auth); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyAuthTests, ProxySessionTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetProxies(Proxies{{"http", HTTP_PROXY}}); + session.SetProxyAuth(ProxyAuthentication{{"http", EncodedAuthentication{PROXY_USER, PROXY_PASS}}}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyAuthTests, ReferenceProxySessionTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Proxies proxies{{"http", HTTP_PROXY}}; + ProxyAuthentication proxy_auth{{"http", EncodedAuthentication{PROXY_USER, PROXY_PASS}}}; + Session session; + session.SetUrl(url); + session.SetProxies(proxies); + session.SetProxyAuth(proxy_auth); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} +#endif + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/proxy_tests.cpp b/Src/external_dependencies/cpr/test/proxy_tests.cpp new file mode 100644 index 00000000..f43c4c23 --- /dev/null +++ b/Src/external_dependencies/cpr/test/proxy_tests.cpp @@ -0,0 +1,92 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +// TODO: This uses public servers for proxies and endpoints. This should be replaced with a source +// code implementation inside server.cpp + +#define HTTP_PROXY "51.159.4.98:80" +#define HTTPS_PROXY "51.104.53.182:8000" + +using namespace cpr; + +TEST(ProxyTests, SingleProxyTest) { + Url url{"http://www.httpbin.org/get"}; + Response response = cpr::Get(url, Proxies{{"http", HTTP_PROXY}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyTests, MultipleProxyHttpTest) { + Url url{"http://www.httpbin.org/get"}; + Response response = cpr::Get(url, Proxies{{"http", HTTP_PROXY}, {"https", HTTPS_PROXY}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +// TODO: These should be fixed after a source code implementation of an HTTPS proxy +#if defined(false) +TEST(ProxyTests, ProxyHttpsTest) { + Url url{"https://www.httpbin.org/get"}; + Response response = cpr::Get(url, Proxies{{"https", HTTPS_PROXY}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyTests, MultipleProxyHttpsTest) { + Url url{"https://www.httpbin.org/get"}; + Response response = cpr::Get(url, Proxies{{"http", HTTP_PROXY}, {"https", HTTPS_PROXY}}); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} +#endif + +TEST(ProxyTests, CopyProxyTest) { + Url url{"http://www.httpbin.org/get"}; + Proxies proxies{{"http", HTTP_PROXY}}; + Response response = cpr::Get(url, proxies); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyTests, ProxySessionTest) { + Url url{"http://www.httpbin.org/get"}; + Session session; + session.SetUrl(url); + session.SetProxies(Proxies{{"http", HTTP_PROXY}}); + Response response = session.Get(); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ProxyTests, ReferenceProxySessionTest) { + Url url{"http://www.httpbin.org/get"}; + Proxies proxies{{"http", HTTP_PROXY}}; + Session session; + session.SetUrl(url); + session.SetProxies(proxies); + Response response = session.Get(); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/put_tests.cpp b/Src/external_dependencies/cpr/test/put_tests.cpp new file mode 100644 index 00000000..34e083ff --- /dev/null +++ b/Src/external_dependencies/cpr/test/put_tests.cpp @@ -0,0 +1,276 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cpr.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(PutTests, PutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, PutUnallowedTest) { + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + Response response = cpr::Put(url, payload); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + Session session; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutUnallowedTest) { + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + Session session; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutUnallowedAfterGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Get(); + } + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutUnallowedAfterHeadTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/get.html"}; + session.SetUrl(url); + Response response = session.Head(); + } + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, SessionPutUnallowedAfterPostTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + Response response = session.Post(); + } + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + session.SetUrl(url); + session.SetPayload(payload); + Response response = session.Put(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, AsyncPutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + cpr::AsyncResponse future_response = cpr::PutAsync(url, payload); + cpr::Response response = future_response.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, AsyncPutUnallowedTest) { + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + cpr::AsyncResponse future_response = cpr::PutAsync(url, payload); + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PutTests, AsyncMultiplePutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + Payload payload{{"x", "5"}}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::PutAsync(url, payload)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(PutTests, AsyncMultiplePutUnallowedTest) { + Url url{server->GetBaseUrl() + "/put_unallowed.html"}; + Payload payload{{"x", "5"}}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + responses.emplace_back(cpr::PutAsync(url, payload)); + } + for (cpr::AsyncResponse& future_response : responses) { + cpr::Response response = future_response.get(); + std::string expected_text{"Method Not Allowed"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(405, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/raw_body_tests.cpp b/Src/external_dependencies/cpr/test/raw_body_tests.cpp new file mode 100644 index 00000000..2416e4eb --- /dev/null +++ b/Src/external_dependencies/cpr/test/raw_body_tests.cpp @@ -0,0 +1,134 @@ +#include <gtest/gtest.h> + +#include <cstdio> +#include <fstream> +#include <string> + +#include <cpr/cpr.h> +#include <cpr/multipart.h> + +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(BodyPostTests, DefaultUrlEncodedPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Body{"x=5"}); + std::string expected_text = "{\n \"x\": 5\n}"; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, TextUrlEncodedPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Body{"x=hello world!!~"}); + std::string expected_text{ + "{\n" + " \"x\": hello world!!~\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, TextUrlEncodedNoCopyPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Body body{"x=hello world!!~"}; + // body lives through the lifetime of Post, so it doesn't need to be copied + Response response = cpr::Post(url, body); + std::string expected_text{ + "{\n" + " \"x\": hello world!!~\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, UrlEncodedManyPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Body{"x=5&y=13"}); + std::string expected_text{ + "{\n" + " \"x\": 5,\n" + " \"y\": 13,\n" + " \"sum\": 18\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, CustomHeaderNumberPostTest) { + Url url{server->GetBaseUrl() + "/json_post.html"}; + Response response = cpr::Post(url, Body{"{\"x\":5}"}, Header{{"Content-Type", "application/json"}}); + std::string expected_text{"{\"x\":5}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, CustomHeaderTextPostTest) { + Url url{server->GetBaseUrl() + "/json_post.html"}; + Response response = cpr::Post(url, Body{"{\"x\":\"hello world!!~\"}"}, Header{{"Content-Type", "application/json"}}); + std::string expected_text{"{\"x\":\"hello world!!~\"}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, CustomWrongHeaderPostTest) { + Url url{server->GetBaseUrl() + "/json_post.html"}; + Response response = cpr::Post(url, Body{"{\"x\":5}"}, Header{{"Content-Type", "text/plain"}}); + std::string expected_text{"Unsupported Media Type"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(415, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyPostTests, UrlPostBadHostTest) { + Url url{"http://bad_host/"}; + Response response = cpr::Post(url, Body{"hello=world"}); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(0, response.status_code); + EXPECT_EQ(ErrorCode::HOST_RESOLUTION_FAILURE, response.error.code); +} + +TEST(BodyPostTests, StringMoveBodyTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Response response = cpr::Post(url, Body{std::string{"x=5"}}); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/resolve_tests.cpp b/Src/external_dependencies/cpr/test/resolve_tests.cpp new file mode 100644 index 00000000..bf675414 --- /dev/null +++ b/Src/external_dependencies/cpr/test/resolve_tests.cpp @@ -0,0 +1,44 @@ +#include <gtest/gtest.h> + +#include <string> + +#include "cpr/cpr.h" +#include "httpServer.hpp" + +using namespace cpr; + +static HttpServer* server = new HttpServer(); + +TEST(ResolveTests, HelloWorldTest) { + Url url{"http://www.example.com:" + std::to_string(server->GetPort()) + "/hello.html"}; + Resolve resolve{"www.example.com", "127.0.0.1", {server->GetPort()}}; + Response response = cpr::Get(url, resolve); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ResolveTests, RedirectMultiple) { + Url url1{"http://www.example0.com:" + std::to_string(server->GetPort()) + "/resolve_permanent_redirect.html"}; + Url url2{"http://www.example1.com:" + std::to_string(server->GetPort()) + "/hello.html"}; + Resolve resolve1{"www.example0.com", "127.0.0.1", {server->GetPort()}}; + Resolve resolve2{"www.example1.com", "127.0.0.1", {server->GetPort()}}; + + Response response = cpr::Get(url1, std::vector<Resolve>{resolve1, resolve2}, Header{{"RedirectLocation", url2.str()}}); + + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url2, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/session_tests.cpp b/Src/external_dependencies/cpr/test/session_tests.cpp new file mode 100644 index 00000000..81c86130 --- /dev/null +++ b/Src/external_dependencies/cpr/test/session_tests.cpp @@ -0,0 +1,1462 @@ +#include <cstdint> +#include <cstdlib> +#include <gtest/gtest.h> + +#include <chrono> +#include <string> + +#include <cpr/cpr.h> +#include <curl/curl.h> + +#include "httpServer.hpp" + +using namespace cpr; +using namespace std::chrono_literals; + +static HttpServer* server = new HttpServer(); +std::chrono::milliseconds sleep_time{50}; +std::chrono::seconds zero{0}; + +bool write_data(std::string /*data*/, intptr_t /*userdata*/) { + return true; +} + +TEST(RedirectTests, TemporaryDefaultRedirectTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Session session; + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(RedirectTests, NoTemporaryRedirectTest) { + Url url{server->GetBaseUrl() + "/temporary_redirect.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(false)); + Response response = session.Get(); + std::string expected_text{"Moved Temporarily"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(302, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(RedirectTests, PermanentDefaultRedirectTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Session session; + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(RedirectTests, NoPermanentRedirectTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(false)); + Response response = session.Get(); + std::string expected_text{"Moved Permanently"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(301, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MaxRedirectsTests, ZeroMaxRedirectsSuccessTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(0L)); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MaxRedirectsTests, ZeroMaxRedirectsFailureTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(0L)); + Response response = session.Get(); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(301, response.status_code); + EXPECT_EQ(ErrorCode::TOO_MANY_REDIRECTS, response.error.code); +} + +TEST(MaxRedirectsTests, OneMaxRedirectsSuccessTest) { + Url url{server->GetBaseUrl() + "/permanent_redirect.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(1L)); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MaxRedirectsTests, OneMaxRedirectsFailureTest) { + Url url{server->GetBaseUrl() + "/two_redirects.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(1L)); + Response response = session.Get(); + EXPECT_EQ(std::string{}, response.text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/permanent_redirect.html"}, response.url); + EXPECT_EQ(std::string{}, response.header["content-type"]); + EXPECT_EQ(301, response.status_code); + EXPECT_EQ(ErrorCode::TOO_MANY_REDIRECTS, response.error.code); +} + +TEST(MaxRedirectsTests, TwoMaxRedirectsSuccessTest) { + Url url{server->GetBaseUrl() + "/two_redirects.html"}; + Session session; + session.SetUrl(url); + session.SetRedirect(Redirect(2L)); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{server->GetBaseUrl() + "/hello.html"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MultipleGetTests, BasicMultipleGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + for (size_t i = 0; i < 100; ++i) { + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, UrlChangeMultipleGetTest) { + Session session; + { + Url url{server->GetBaseUrl() + "/hello.html"}; + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Url url{server->GetBaseUrl() + "/basic.json"}; + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{ + "[\n" + " {\n" + " \"first_key\": \"first_value\",\n" + " \"second_key\": \"second_value\"\n" + " }\n" + "]"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, HeaderMultipleGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + session.SetHeader(Header{{"hello", "world"}}); + for (size_t i = 0; i < 100; ++i) { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, HeaderChangeMultipleGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + session.SetHeader(Header{{"hello", "world"}}); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"world"}, response.header["hello"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + session.SetHeader(Header{{"key", "value"}}); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(std::string{"value"}, response.header["key"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, ParameterMultipleGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetParameters({{"hello", "world"}}); + for (size_t i = 0; i < 100; ++i) { + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, ParameterChangeMultipleGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetParameters({{"hello", "world"}}); + { + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + session.SetUrl(url); + session.SetParameters({{"key", "value"}}); + { + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?key=value"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, BasicAuthenticationMultipleGetTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Session session; + session.SetUrl(url); + session.SetAuth(Authentication{"user", "password", AuthMode::BASIC}); + for (size_t i = 0; i < 100; ++i) { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(MultipleGetTests, BasicAuthenticationChangeMultipleGetTest) { + Url url{server->GetBaseUrl() + "/basic_auth.html"}; + Session session; + session.SetUrl(url); + session.SetAuth(Authentication{"user", "password", AuthMode::BASIC}); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + session.SetAuth(Authentication{"user", "bad_password", AuthMode::BASIC}); + { + Response response = session.Get(); + EXPECT_EQ(std::string{"Unauthorized"}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + session.SetAuth(Authentication{"bad_user", "password", AuthMode::BASIC}); + { + Response response = session.Get(); + EXPECT_EQ(std::string{"Unauthorized"}, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/plain"}, response.header["content-type"]); + EXPECT_EQ(401, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(ParameterTests, ParameterSingleTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + Parameters parameters{{"hello", "world"}}; + session.SetParameters(parameters); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ParameterTests, ParameterMultipleTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + Parameters parameters{{"hello", "world"}, {"key", "value"}}; + session.SetParameters(parameters); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(Url{url + "?hello=world&key=value"}, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(FullRequestUrlTest, GetFullRequestUrlNoParametersTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + std::string expected_text{server->GetBaseUrl() + "/hello.html"}; + EXPECT_EQ(expected_text, session.GetFullRequestUrl()); +} + +TEST(FullRequestUrlTest, GetFullRequestUrlOneParameterTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + Parameters parameters{{"hello", "world"}}; + session.SetParameters(parameters); + std::string expected_text{server->GetBaseUrl() + "/hello.html" + "?hello=world"}; + EXPECT_EQ(expected_text, session.GetFullRequestUrl()); +} + +TEST(FullRequestUrlTest, GetFullRequestUrlMultipleParametersTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + Parameters parameters{{"hello", "world"}, {"key", "value"}}; + session.SetParameters(parameters); + std::string expected_text{server->GetBaseUrl() + "/hello.html" + "?hello=world&key=value"}; + EXPECT_EQ(expected_text, session.GetFullRequestUrl()); +} + +TEST(TimeoutTests, SetTimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(0L); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(TimeoutTests, SetTimeoutLongTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(10000L); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(TimeoutTests, SetTimeoutLowSpeed) { + Url url{server->GetBaseUrl() + "/low_speed_timeout.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(1000); + Response response = session.Get(); + EXPECT_EQ(url, response.url); + // Do not check for the HTTP status code, since libcurl always provides the status code of the header if it was received + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(TimeoutTests, SetChronoTimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(std::chrono::milliseconds{0}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(TimeoutTests, SetChronoTimeoutLongTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(std::chrono::milliseconds{10000}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + // Do not check for the HTTP status code, since libcurl always provides the status code of the header if it was received + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(TimeoutTests, SetChronoLiteralTimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(2s); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(TimeoutTests, SetChronoLiteralTimeoutLowSpeed) { + Url url{server->GetBaseUrl() + "/low_speed_timeout.html"}; + Session session; + session.SetUrl(url); + session.SetTimeout(1000ms); + Response response = session.Get(); + EXPECT_EQ(url, response.url); + // Do not check for the HTTP status code, since libcurl always provides the status code of the header if it was received + EXPECT_EQ(ErrorCode::OPERATION_TIMEDOUT, response.error.code); +} + +TEST(ConnectTimeoutTests, SetConnectTimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetConnectTimeout(0L); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ConnectTimeoutTests, SetConnectTimeoutLongTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetConnectTimeout(10000L); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ConnectTimeoutTests, SetChronoConnectTimeoutTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetConnectTimeout(std::chrono::milliseconds{0}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(ConnectTimeoutTests, SetChronoConnectTimeoutLongTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetConnectTimeout(std::chrono::milliseconds{10000}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(LowSpeedTests, SetLowSpeedTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetLowSpeed({1, 1}); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PayloadTests, SetPayloadTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Session session; + session.SetUrl(url); + session.SetPayload({{"x", "5"}}); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(PayloadTests, SetPayloadLValueTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Session session; + session.SetUrl(url); + Payload payload{{"x", "5"}}; + session.SetPayload(payload); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MultipartTests, SetMultipartTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Session session; + session.SetUrl(url); + session.SetMultipart({{"x", "5"}}); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": \"5\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(MultipartTests, SetMultipartValueTest) { + Url url{server->GetBaseUrl() + "/form_post.html"}; + Session session; + session.SetUrl(url); + Multipart multipart{{"x", "5"}}; + session.SetMultipart(multipart); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": \"5\"\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyTests, SetBodyTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Session session; + session.SetUrl(url); + session.SetBody(Body{"x=5"}); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BodyTests, SetBodyValueTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + Session session; + session.SetUrl(url); + Body body{"x=5"}; + session.SetBody(body); + Response response = session.Post(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(DigestTests, SetDigestTest) { + Url url{server->GetBaseUrl() + "/digest_auth.html"}; + Session session; + session.SetUrl(url); + session.SetAuth({"user", "password", AuthMode::DIGEST}); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UserAgentTests, SetUserAgentTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + UserAgent userAgent{"Test User Agent"}; + Session session; + session.SetUrl(url); + session.SetUserAgent(userAgent); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(userAgent, response.header["User-Agent"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(UserAgentTests, SetUserAgentStringViewTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + UserAgent userAgent{std::string_view{"Test User Agent"}}; + Session session; + session.SetUrl(url); + session.SetUserAgent(userAgent); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(userAgent, response.header["User-Agent"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CookiesTests, BasicCookiesTest) { + Url url{server->GetBaseUrl() + "/basic_cookies.html"}; + Session session{}; + session.SetUrl(url); + Response response = session.Get(); + Cookies res_cookies{response.cookies}; + std::string expected_text{"Basic Cookies"}; + cpr::Cookies expectedCookies{ + {"SID", "31d4d96e407aad42", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + {"lang", "en-US", "127.0.0.1", false, "/", true, std::chrono::system_clock::from_time_t(3905119080)}, + }; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + for (auto cookie = res_cookies.begin(), expectedCookie = expectedCookies.begin(); cookie != res_cookies.end() && expectedCookie != expectedCookies.end(); cookie++, expectedCookie++) { + EXPECT_EQ(expectedCookie->GetName(), cookie->GetName()); + EXPECT_EQ(expectedCookie->GetValue(), cookie->GetValue()); + EXPECT_EQ(expectedCookie->GetDomain(), cookie->GetDomain()); + EXPECT_EQ(expectedCookie->IsIncludingSubdomains(), cookie->IsIncludingSubdomains()); + EXPECT_EQ(expectedCookie->GetPath(), cookie->GetPath()); + EXPECT_EQ(expectedCookie->IsHttpsOnly(), cookie->IsHttpsOnly()); + EXPECT_EQ(expectedCookie->GetExpires(), cookie->GetExpires()); + } +} + +TEST(CookiesTests, ClientSetCookiesTest) { + Url url{server->GetBaseUrl() + "/cookies_reflect.html"}; + { + Session session{}; + session.SetUrl(url); + session.SetCookies(Cookies{ + {"SID", "31d4d96e407aad42"}, + {"lang", "en-US"}, + }); + Response response = session.Get(); + std::string expected_text{"SID=31d4d96e407aad42; lang=en-US;"}; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + } + { + Session session{}; + session.SetUrl(url); + Cookies cookie{ + {"SID", "31d4d96e407aad42"}, + {"lang", "en-US"}, + }; + session.SetCookies(cookie); + Response response = session.Get(); + std::string expected_text{"SID=31d4d96e407aad42; lang=en-US;"}; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + } +} + +TEST(CookiesTests, RedirectionWithChangingCookiesTest) { + Url url{server->GetBaseUrl() + "/redirection_with_changing_cookies.html"}; + { + Session session{}; + session.SetUrl(url); + session.SetCookies(Cookies{ + {"SID", "31d4d96e407aad42"}, + {"lang", "en-US"}, + }); + session.SetRedirect(Redirect(0L)); + Response response = session.Get(); + std::string expected_text{"Received cookies are: SID=31d4d96e407aad42; lang=en-US;"}; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + } + { + Session session{}; + session.SetUrl(url); + session.SetRedirect(Redirect(1L)); + Response response = session.Get(); + std::string expected_text{"Received cookies are: lang=en-US; SID=31d4d96e407aad42"}; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + } + { + Session session{}; + session.SetUrl(url); + session.SetCookies(Cookies{ + {"SID", "empty_sid"}, + }); + session.SetRedirect(Redirect(1L)); + Response response = session.Get(); + std::string expected_text{"Received cookies are: lang=en-US; SID=31d4d96e407aad42; SID=empty_sid;"}; + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + EXPECT_EQ(expected_text, response.text); + } +} + +TEST(DifferentMethodTests, GetPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(DifferentMethodTests, PostGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(DifferentMethodTests, GetPostGetTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(DifferentMethodTests, PostGetPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(DifferentMethodTests, MultipleGetPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + for (size_t i = 0; i < 100; ++i) { + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + } +} + +TEST(DifferentMethodTests, MultipleDeleteHeadPutGetPostTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Url urlPost{server->GetBaseUrl() + "/post_reflect.html"}; + Url urlPut{server->GetBaseUrl() + "/put.html"}; + Session session; + for (size_t i = 0; i < 10; ++i) { + { + session.SetUrl(url); + Response response = session.Delete(); + std::string expected_text{"Header reflect DELETE"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(urlPost); + std::string expectedBody = "a1b2c3Post"; + session.SetBody(expectedBody); + Response response = session.Post(); + EXPECT_EQ(expectedBody, response.text); + EXPECT_EQ(urlPost, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(url); + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(urlPut); + session.SetPayload({{"x", "5"}}); + Response response = session.Put(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(urlPut, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + session.SetUrl(url); + Response response = session.Head(); + std::string expected_text{"Header reflect HEAD"}; + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + } +} + +TEST(CurlHolderManipulateTests, CustomOptionTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + Session session; + session.SetUrl(url); + curl_easy_setopt(session.GetCurlHolder()->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST | CURLSSLOPT_NO_REVOKE); + { + Response response = session.Get(); + std::string expected_text{"Header reflect GET"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } + { + Response response = session.Post(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + } +} + +TEST(LocalPortTests, SetLocalPortTest) { + Url url{server->GetBaseUrl() + "/local_port.html"}; + Session session; + session.SetUrl(url); + std::uint16_t const local_port = 60252; // beware of HttpServer::GetPort when changing + std::uint16_t const local_port_range = 5000; + session.SetLocalPort(local_port); + session.SetLocalPortRange(local_port_range); + // expected response: body contains port number in specified range + // NOTE: even when trying up to 5000 ports there is the chance that all of them are occupied. + // It would be possible to also check here for ErrorCode::INTERNAL_ERROR but that somehow seems + // wrong as then this test would pass in case SetLocalPort does not work at all + // or in other words: we have to assume that at least one port in the specified range is free. + Response response = session.Get(); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + std::uint16_t port_from_response = std::strtoul(response.text.c_str(), nullptr, 10); + EXPECT_EQ(errno, 0); + EXPECT_GE(port_from_response, local_port); + EXPECT_LE(port_from_response, local_port + local_port_range); +} + +TEST(LocalPortTests, SetOptionTest) { + Url url{server->GetBaseUrl() + "/local_port.html"}; + Session session; + session.SetUrl(url); + std::uint16_t const local_port = 60551; // beware of HttpServer::GetPort when changing + std::uint16_t const local_port_range = 5000; + session.SetOption(LocalPort(local_port)); + session.SetOption(LocalPortRange(local_port_range)); + // expected response: body contains port number in specified range + // NOTE: even when trying up to 5000 ports there is the chance that all of them are occupied. + // It would be possible to also check here for ErrorCode::INTERNAL_ERROR but that somehow seems + // wrong as then this test would pass in case SetOption(LocalPort) does not work at all + // or in other words: we have to assume that at least one port in the specified range is free. + Response response = session.Get(); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); + unsigned long port_from_response = std::strtoul(response.text.c_str(), nullptr, 10); + EXPECT_EQ(errno, 0); + EXPECT_GE(port_from_response, local_port); + EXPECT_LE(port_from_response, local_port + local_port_range); +} + +// The tests using the port of the server as a source port for curl fail for windows. +// The reason probably is that Windows allows two sockets to bind to the same port if the full hostname is different. +// In these tests, mongoose binds to http://127.0.0.1:61936, while libcurl binds to a different hostname, but still port 61936. +// This seems to be okay for Windows, however, these tests expect an error like on Linux and MacOS +// We therefore, simply skip the tests if Windows is used +#ifndef _WIN32 +TEST(LocalPortTests, SetLocalPortTestOccupied) { + Url url{server->GetBaseUrl() + "/local_port.html"}; + Session session; + session.SetUrl(url); + session.SetLocalPort(server->GetPort()); + // expected response: request cannot be made as port is already occupied + Response response = session.Get(); + EXPECT_EQ(ErrorCode::INTERNAL_ERROR, response.error.code); +} + +TEST(LocalPortTests, SetOptionTestOccupied) { + Url url{server->GetBaseUrl() + "/local_port.html"}; + Session session; + session.SetUrl(url); + session.SetOption(LocalPort(server->GetPort())); + // expected response: request cannot be made as port is already occupied + Response response = session.Get(); + EXPECT_EQ(ErrorCode::INTERNAL_ERROR, response.error.code); +} +#endif // _WIN32 + +TEST(BasicTests, ReserveResponseString) { + Url url{server->GetBaseUrl() + "/hello.html"}; + Session session; + session.SetUrl(url); + session.SetReserveSize(4096); + Response response = session.Get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_GE(response.text.capacity(), 4096); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, AcceptEncodingTestWithMethodsStringMap) { + Url url{server->GetBaseUrl() + "/check_accept_encoding.html"}; + Session session; + session.SetUrl(url); + session.SetAcceptEncoding({{AcceptEncodingMethods::deflate, AcceptEncodingMethods::gzip, AcceptEncodingMethods::zlib}}); + Response response = session.Get(); + std::string expected_text{"deflate, gzip, zlib"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, AcceptEncodingTestWithMethodsStringMapLValue) { + Url url{server->GetBaseUrl() + "/check_accept_encoding.html"}; + Session session; + session.SetUrl(url); + AcceptEncoding accept_encoding{{AcceptEncodingMethods::deflate, AcceptEncodingMethods::gzip, AcceptEncodingMethods::zlib}}; + session.SetAcceptEncoding(accept_encoding); + Response response = session.Get(); + std::string expected_text{"deflate, gzip, zlib"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, AcceptEncodingTestWithCostomizedString) { + Url url{server->GetBaseUrl() + "/check_accept_encoding.html"}; + Session session; + session.SetUrl(url); + session.SetAcceptEncoding({{"deflate", "gzip", "zlib"}}); + Response response = session.Get(); + std::string expected_text{"deflate, gzip, zlib"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, AcceptEncodingTestWithCostomizedStringLValue) { + Url url{server->GetBaseUrl() + "/check_accept_encoding.html"}; + Session session; + session.SetUrl(url); + AcceptEncoding accept_encoding{{"deflate", "gzip", "zlib"}}; + session.SetAcceptEncoding(accept_encoding); + Response response = session.Get(); + std::string expected_text{"deflate, gzip, zlib"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(BasicTests, DisableHeaderExpect100ContinueTest) { + Url url{server->GetBaseUrl() + "/check_expect_100_continue.html"}; + std::string filename{"test_file"}; + std::string content{std::string(1024 * 1024, 'a')}; + std::ofstream test_file; + test_file.open(filename); + test_file << content; + test_file.close(); + Session session{}; + session.SetUrl(url); + session.SetMultipart({{"file", File{"test_file"}}}); + Response response = session.Post(); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncGetTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + cpr::AsyncResponse future = session->GetAsync(); + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); +} + +TEST(AsyncRequestsTests, AsyncGetMultipleTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + + std::vector<AsyncResponse> responses; + std::vector<std::shared_ptr<Session>> sessions; + for (size_t i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + sessions.emplace_back(session); + responses.emplace_back(session->GetAsync()); + } + + for (cpr::AsyncResponse& future : responses) { + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + } +} + +TEST(AsyncRequestsTests, AsyncGetMultipleTemporarySessionTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 10; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + responses.emplace_back(session->GetAsync()); + } + + for (cpr::AsyncResponse& future : responses) { + std::string expected_text{"Hello world!"}; + cpr::Response response = future.get(); + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + } +} + +TEST(AsyncRequestsTests, AsyncGetMultipleReflectTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::vector<AsyncResponse> responses; + for (size_t i = 0; i < 100; ++i) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetParameters({{"key", std::to_string(i)}}); + responses.emplace_back(session->GetAsync()); + } + int i = 0; + for (cpr::AsyncResponse& future : responses) { + cpr::Response response = future.get(); + std::string expected_text{"Hello world!"}; + Url expected_url{url + "?key=" + std::to_string(i)}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(expected_url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + ++i; + } +} + +TEST(AsyncRequestsTests, AsyncWritebackDownloadTest) { + std::shared_ptr<Session> session = std::make_shared<Session>(); + cpr::Url url{server->GetBaseUrl() + "/download_gzip.html"}; + session->SetUrl(url); + session->SetHeader(cpr::Header{{"Accept-Encoding", "gzip"}}); + cpr::AsyncResponse future = session->DownloadAsync(cpr::WriteCallback{write_data, 0}); + cpr::Response response = future.get(); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(cpr::ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncPostTest) { + Url url{server->GetBaseUrl() + "/url_post.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload({{"x", "5"}}); + cpr::AsyncResponse future = session->PostAsync(); + cpr::Response response = future.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(201, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncPutTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload({{"x", "5"}}); + cpr::AsyncResponse future = session->PutAsync(); + cpr::Response response = future.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncHeadTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + cpr::AsyncResponse future = session->HeadAsync(); + cpr::Response response = future.get(); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncDeleteTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + cpr::AsyncResponse future = session->DeleteAsync(); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect DELETE"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncOptionsTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + cpr::AsyncResponse future = session->OptionsAsync(); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect OPTIONS"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(AsyncRequestsTests, AsyncPatchTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + cpr::AsyncResponse future = session->PatchAsync(); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect PATCH"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, GetCallbackTest) { + Url url{server->GetBaseUrl() + "/hello.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->GetCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{"Hello world!"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, PostCallbackTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->PostCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect POST"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, PutCallbackTest) { + Url url{server->GetBaseUrl() + "/put.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + session->SetPayload({{"x", "5"}}); + auto future = session->PutCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{ + "{\n" + " \"x\": 5\n" + "}"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, HeadCallbackTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->HeadCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{""}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, DeleteCallbackTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->DeleteCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect DELETE"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, OptionsCallbackTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->OptionsCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect OPTIONS"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +TEST(CallbackTests, PatchCallbackTest) { + Url url{server->GetBaseUrl() + "/header_reflect.html"}; + std::shared_ptr<Session> session = std::make_shared<Session>(); + session->SetUrl(url); + auto future = session->PatchCallback([](Response r) { return r; }); + std::this_thread::sleep_for(sleep_time); + cpr::Response response = future.get(); + std::string expected_text{"Header reflect PATCH"}; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ::testing::AddGlobalTestEnvironment(server); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/ssl_tests.cpp b/Src/external_dependencies/cpr/test/ssl_tests.cpp new file mode 100644 index 00000000..41c73162 --- /dev/null +++ b/Src/external_dependencies/cpr/test/ssl_tests.cpp @@ -0,0 +1,166 @@ +#include <gtest/gtest.h> + +#include <chrono> +#include <string> +#include <thread> +#include <vector> + +#include <cpr/cprtypes.h> +#include <cpr/filesystem.h> +#include <cpr/ssl_options.h> + +#include "httpsServer.hpp" + + +using namespace cpr; + +static HttpsServer* server; + +static std::string caCertPath; +static std::string serverPubKeyPath; +static std::string clientKeyPath; +static std::string clientCertPath; + +std::string loadCertificateFromFile(const std::string certPath) { + std::ifstream certFile(certPath); + std::stringstream buffer; + buffer << certFile.rdbuf(); + return buffer.str(); +} + +TEST(SslTests, HelloWorldTestSimpel) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string baseDirPath{server->getBaseDirPath()}; + std::string crtPath{baseDirPath + "certificates/"}; + std::string keyPath{baseDirPath + "keys/"}; + + SslOptions sslOpts = Ssl(ssl::CaPath{crtPath + "root-ca.crt"}, ssl::CertFile{crtPath + "client.crt"}, ssl::KeyFile{keyPath + "client.key"}, ssl::VerifyPeer{false}, ssl::PinnedPublicKey{keyPath + "server.pub"}, ssl::VerifyHost{false}, ssl::VerifyStatus{false}); + Response response = cpr::Get(url, sslOpts, Timeout{5000}, Verbose{}); + std::string expected_text = "Hello world!"; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; +} + +TEST(SslTests, HelloWorldTestFull) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string baseDirPath{server->getBaseDirPath()}; + std::string crtPath{baseDirPath + "certificates/"}; + std::string keyPath{baseDirPath + "keys/"}; + + SslOptions sslOpts = Ssl(ssl::TLSv1{}, ssl::ALPN{false}, ssl::NPN{false}, ssl::CaPath{crtPath + "root-ca.crt"}, ssl::CertFile{crtPath + "client.crt"}, ssl::KeyFile{keyPath + "client.key"}, ssl::PinnedPublicKey{keyPath + "server.pub"}, ssl::VerifyPeer{false}, ssl::VerifyHost{false}, ssl::VerifyStatus{false}); + Response response = cpr::Get(url, sslOpts, Timeout{5000}, Verbose{}); + std::string expected_text = "Hello world!"; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; +} + +TEST(SslTests, GetCertInfos) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + std::string baseDirPath{server->getBaseDirPath()}; + std::string crtPath{baseDirPath + "certificates/"}; + std::string keyPath{baseDirPath + "keys/"}; + + SslOptions sslOpts = Ssl(ssl::CaPath{crtPath + "root-ca.crt"}, ssl::CertFile{crtPath + "client.crt"}, ssl::KeyFile{keyPath + "client.key"}, ssl::VerifyPeer{false}, ssl::VerifyHost{false}, ssl::VerifyStatus{false}); + + Response response = cpr::Get(url, sslOpts, Timeout{5000}, Verbose{}); + std::vector<CertInfo> certInfos = response.GetCertInfos(); + + std::string expected_text = "Hello world!"; + std::vector<CertInfo> expectedCertInfos{ + CertInfo{ + "Subject:CN = test-server", + "Issuer:C = GB, O = Example, CN = Root CA", + "Version:2", + "Serial Number:28c252871ec62a626a98006b0bf2888f", + "Signature Algorithm:ED25519", + "Public Key Algorithm:ED25519", + "X509v3 Subject Alternative Name:DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1", + "X509v3 Subject Key Identifier:39:C1:81:38:01:DC:55:38:E5:2F:4E:7A:D0:4C:84:7B:B7:27:D3:AF", + "X509v3 Authority Key Identifier:E4:F2:F3:85:0E:B7:85:75:84:76:E3:43:D1:B6:9D:14:B8:E2:A4:B7", + "Start date:Jun 29 11:33:07 2022 GMT", + "Expire date:Jun 28 11:33:07 2027 GMT", + "Signature:2e:0d:a1:0d:f5:90:77:e9:eb:84:7d:80:63:63:4d:8a:eb:d9:23:57:1f:21:2a:ed:81:b4:a8:58:b9:00:1b:cb:5c:90:1b:33:6b:f6:ec:42:20:63:54:d6:60:ee:37:14:1b:1c:95:0b:33:ea:67:29:d4:cc:d9:7e:34:fd:47:04:", + R"(Cert:-----BEGIN CERTIFICATE----- +MIIBdTCCASegAwIBAgIQKMJShx7GKmJqmABrC/KIjzAFBgMrZXAwMTELMAkGA1UE +BhMCR0IxEDAOBgNVBAoMB0V4YW1wbGUxEDAOBgNVBAMMB1Jvb3QgQ0EwHhcNMjIw +NjI5MTEzMzA3WhcNMjcwNjI4MTEzMzA3WjAWMRQwEgYDVQQDDAt0ZXN0LXNlcnZl +cjAqMAUGAytlcAMhAI64JU5RjfdEG1KQMxS5DQWkiGlKIQO7ye4mNFq9QleTo3Aw +bjAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEw +HQYDVR0OBBYEFDnBgTgB3FU45S9OetBMhHu3J9OvMB8GA1UdIwQYMBaAFOTy84UO +t4V1hHbjQ9G2nRS44qS3MAUGAytlcANBAC4NoQ31kHfp64R9gGNjTYrr2SNXHyEq +7YG0qFi5ABvLXJAbM2v27EIgY1TWYO43FBsclQsz6mcp1MzZfjT9RwQ= +-----END CERTIFICATE----- +)", + }, + }; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; + EXPECT_EQ(1, certInfos.size()); + for (auto certInfo_it = certInfos.begin(), expectedCertInfo_it = expectedCertInfos.begin(); certInfo_it != certInfos.end() && expectedCertInfo_it != expectedCertInfos.end(); certInfo_it++, expectedCertInfo_it++) { + for (auto entry_it = (*certInfo_it).begin(), expectedEntry_it = (*expectedCertInfo_it).begin(); entry_it != (*certInfo_it).end() && expectedEntry_it != (*expectedCertInfo_it).end(); entry_it++, expectedEntry_it++) { + std::string search_string = "Identifier:keyid:"; + std::size_t search_index = (*entry_it).find(search_string); + if (search_index != std::string::npos) { + (*entry_it).replace(search_index, search_string.length(), "Identifier:"); + search_string = "\n"; + search_index = (*entry_it).find(search_string); + if (search_index != std::string::npos) { + (*entry_it).replace(search_index, search_string.length(), ""); + } + } + EXPECT_EQ(*expectedEntry_it, *entry_it); + } + std::cout << std::endl; + } +} + +#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION +TEST(SslTests, LoadCertFromBufferTestSimpel) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + Url url{server->GetBaseUrl() + "/hello.html"}; + + std::string baseDirPath{server->getBaseDirPath()}; + std::string crtPath{baseDirPath + "certificates/"}; + std::string keyPath{baseDirPath + "keys/"}; + std::string certBuffer = loadCertificateFromFile(crtPath + "root-ca.crt"); + SslOptions sslOpts = Ssl(ssl::CaBuffer{std::move(certBuffer)}, ssl::CertFile{crtPath + "client.crt"}, ssl::KeyFile{keyPath + "client.key"}, ssl::VerifyPeer{false}, ssl::VerifyHost{false}, ssl::VerifyStatus{false}); + Response response = cpr::Get(url, sslOpts, Timeout{5000}, Verbose{}); + std::string expected_text = "Hello world!"; + EXPECT_EQ(expected_text, response.text); + EXPECT_EQ(url, response.url); + EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]); + EXPECT_EQ(200, response.status_code); + EXPECT_EQ(ErrorCode::OK, response.error.code) << response.error.message; +} +#endif + +fs::path getBasePath(const std::string& execPath) { + return fs::path(fs::path{execPath}.parent_path().string() + "/").make_preferred(); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + fs::path baseDirPath = fs::path{getBasePath(argv[0]).string() + "data/"}; + fs::path serverCertPath = fs::path{baseDirPath}.append("certificates/server.crt"); + fs::path serverKeyPath = fs::path{baseDirPath}.append("keys/server.key"); + server = new HttpsServer(std::move(baseDirPath), std::move(serverCertPath), std::move(serverKeyPath)); + ::testing::AddGlobalTestEnvironment(server); + + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/structures_tests.cpp b/Src/external_dependencies/cpr/test/structures_tests.cpp new file mode 100644 index 00000000..026362e4 --- /dev/null +++ b/Src/external_dependencies/cpr/test/structures_tests.cpp @@ -0,0 +1,62 @@ +#include "cpr/cprtypes.h" +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/parameters.h> +#include <cpr/payload.h> + +using namespace cpr; + +TEST(PayloadTests, UseStringVariableTest) { + std::string value1 = "hello"; + std::string key2 = "key2"; + Payload payload{{"key1", value1}, {key2, "world"}}; + + std::string expected = "key1=hello&key2=world"; + EXPECT_EQ(payload.GetContent(CurlHolder()), expected); +} + +TEST(PayloadTests, DisableEncodingTest) { + std::string key1 = "key1"; + std::string key2 = "key2§$%&/"; + std::string value1 = "hello.,.,"; + std::string value2 = "hello"; + Payload payload{{key1, value1}, {key2, value2}}; + payload.encode = false; + + std::string expected = key1 + '=' + value1 + '&' + key2 + '=' + value2; + EXPECT_EQ(payload.GetContent(CurlHolder()), expected); +} + +TEST(ParametersTests, UseStringVariableTest) { + std::string value1 = "hello"; + std::string key2 = "key2"; + Parameters parameters{{"key1", value1}, {key2, "world"}}; + + std::string expected = "key1=hello&key2=world"; + EXPECT_EQ(parameters.GetContent(CurlHolder()), expected); +} + +TEST(ParametersTests, DisableEncodingTest) { + std::string key1 = "key1"; + std::string key2 = "key2§$%&/"; + std::string value1 = "hello.,.,"; + std::string value2 = "hello"; + Parameters parameters{{key1, value1}, {key2, value2}}; + parameters.encode = false; + + std::string expected = key1 + '=' + value1 + '&' + key2 + '=' + value2; + EXPECT_EQ(parameters.GetContent(CurlHolder()), expected); +} + +TEST(UrlToAndFromString, UrlTests) { + std::string s{"https://github.com/whoshuu/cpr"}; + cpr::Url url = s; + EXPECT_EQ(s, url.str()); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/util_tests.cpp b/Src/external_dependencies/cpr/test/util_tests.cpp new file mode 100644 index 00000000..33977530 --- /dev/null +++ b/Src/external_dependencies/cpr/test/util_tests.cpp @@ -0,0 +1,237 @@ +#include <gtest/gtest.h> + +#include <string> + +#include <cpr/cprtypes.h> +#include <cpr/util.h> + +using namespace cpr; + +TEST(UtilParseCookiesTests, BasicParseTest) { + Cookies expectedCookies{{Cookie("status", "on", "127.0.0.1", false, "/", false, std::chrono::system_clock::from_time_t(1656908640)), Cookie("name", "debug", "127.0.0.1", false, "/", false, std::chrono::system_clock::from_time_t(0))}}; + curl_slist* raw_cookies = new curl_slist{ + (char*) "127.0.0.1\tFALSE\t/\tFALSE\t1656908640\tstatus\ton", + new curl_slist{ + (char*) "127.0.0.1\tFALSE\t/\tFALSE\t0\tname\tdebug", + nullptr, + }, + }; + Cookies cookies = util::parseCookies(raw_cookies); + for (auto cookie = cookies.begin(), expectedCookie = expectedCookies.begin(); cookie != cookies.end() && expectedCookie != expectedCookies.end(); cookie++, expectedCookie++) { + EXPECT_EQ(expectedCookie->GetName(), cookie->GetName()); + EXPECT_EQ(expectedCookie->GetValue(), cookie->GetValue()); + EXPECT_EQ(expectedCookie->GetDomain(), cookie->GetDomain()); + EXPECT_EQ(expectedCookie->IsIncludingSubdomains(), cookie->IsIncludingSubdomains()); + EXPECT_EQ(expectedCookie->GetPath(), cookie->GetPath()); + EXPECT_EQ(expectedCookie->IsHttpsOnly(), cookie->IsHttpsOnly()); + EXPECT_EQ(expectedCookie->GetExpires(), cookie->GetExpires()); + } + delete raw_cookies->next; + delete raw_cookies; +} + +TEST(UtilParseHeaderTests, BasicParseTest) { + std::string header_string{ + "HTTP/1.1 200 OK\r\n" + "Server: nginx\r\n" + "Date: Sun, 05 Mar 2017 00:34:54 GMT\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 351\r\n" + "Connection: keep-alive\r\n" + "Access-Control-Allow-Origin: *\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "\r\n"}; + Header header = util::parseHeader(header_string); + EXPECT_EQ(std::string{"nginx"}, header["Server"]); + EXPECT_EQ(std::string{"Sun, 05 Mar 2017 00:34:54 GMT"}, header["Date"]); + EXPECT_EQ(std::string{"application/json"}, header["Content-Type"]); + EXPECT_EQ(std::string{"351"}, header["Content-Length"]); + EXPECT_EQ(std::string{"keep-alive"}, header["Connection"]); + EXPECT_EQ(std::string{"*"}, header["Access-Control-Allow-Origin"]); + EXPECT_EQ(std::string{"true"}, header["Access-Control-Allow-Credentials"]); +} + +TEST(UtilParseHeaderTests, NewlineTest) { + std::string header_string{ + "HTTP/1.1 200 OK\r\n" + "Auth:\n" + "Access-Control-Allow-Credentials: true\r\n" + "\r\n"}; + Header header = util::parseHeader(header_string); + EXPECT_EQ(std::string{""}, header["Server"]); + EXPECT_EQ(std::string{"true"}, header["Access-Control-Allow-Credentials"]); +} + +TEST(UtilParseHeaderTests, SpaceNewlineTest) { + std::string header_string{ + "HTTP/1.1 200 OK\r\n" + "Auth: \n" + "Access-Control-Allow-Credentials: true\r\n" + "\r\n"}; + Header header = util::parseHeader(header_string); + EXPECT_EQ(std::string{""}, header["Server"]); + EXPECT_EQ(std::string{"true"}, header["Access-Control-Allow-Credentials"]); +} + +TEST(UtilParseHeaderTests, CarriageReturnNewlineTest) { + std::string header_string{ + "HTTP/1.1 200 OK\n" + "Auth:\r\n" + "Access-Control-Allow-Credentials: true\r\n" + "\r\n"}; + Header header = util::parseHeader(header_string); + EXPECT_EQ(std::string{""}, header["Server"]); + EXPECT_EQ(std::string{"true"}, header["Access-Control-Allow-Credentials"]); +} + +TEST(UtilParseHeaderTests, SpaceCarriageReturnNewlineTest) { + std::string header_string{ + "HTTP/1.1 200 OK\n" + "Auth: \r\n" + "Access-Control-Allow-Credentials: true\r\n" + "\r\n"}; + Header header = util::parseHeader(header_string); + EXPECT_EQ(std::string{""}, header["Server"]); + EXPECT_EQ(std::string{"true"}, header["Access-Control-Allow-Credentials"]); +} + +TEST(UtilParseHeaderTests, BasicStatusLineTest) { + std::string header_string{ + "HTTP/1.1 200 OK\r\n" + "Server: nginx\r\n" + "Content-Type: application/json\r\n" + "\r\n"}; + std::string status_line; + std::string reason; + Header header = util::parseHeader(header_string, &status_line, &reason); + EXPECT_EQ(std::string{"HTTP/1.1 200 OK"}, status_line); + EXPECT_EQ(std::string{"OK"}, reason); + EXPECT_EQ(std::string{"nginx"}, header["Server"]); + EXPECT_EQ(std::string{"application/json"}, header["Content-Type"]); +} + +TEST(UtilParseHeaderTests, NewlineStatusLineTest) { + std::string header_string{ + "HTTP/1.1 407 Proxy Authentication Required\n" + "Server: nginx\r\n" + "Content-Type: application/json\r\n" + "\r\n"}; + std::string status_line; + std::string reason; + Header header = util::parseHeader(header_string, &status_line, &reason); + EXPECT_EQ(std::string{"HTTP/1.1 407 Proxy Authentication Required"}, status_line); + EXPECT_EQ(std::string{"Proxy Authentication Required"}, reason); + EXPECT_EQ(std::string{"nginx"}, header["Server"]); + EXPECT_EQ(std::string{"application/json"}, header["Content-Type"]); +} + +TEST(UtilParseHeaderTests, NoReasonSpaceTest) { + std::string header_string{ + "HTTP/1.1 200 \n" + "Server: nginx\r\n" + "Content-Type: application/json\r\n" + "\r\n"}; + std::string status_line; + std::string reason; + Header header = util::parseHeader(header_string, &status_line, &reason); + EXPECT_EQ(std::string{"HTTP/1.1 200"}, status_line); + EXPECT_EQ(std::string{""}, reason); + EXPECT_EQ(std::string{"nginx"}, header["Server"]); + EXPECT_EQ(std::string{"application/json"}, header["Content-Type"]); +} + +TEST(UtilParseHeaderTests, NoReasonTest) { + std::string header_string{ + "HTTP/1.1 200\n" + "Server: nginx\r\n" + "Content-Type: application/json\r\n" + "\r\n"}; + std::string status_line; + std::string reason; + Header header = util::parseHeader(header_string, &status_line, &reason); + EXPECT_EQ(std::string{"HTTP/1.1 200"}, status_line); + EXPECT_EQ(std::string{""}, reason); + EXPECT_EQ(std::string{"nginx"}, header["Server"]); + EXPECT_EQ(std::string{"application/json"}, header["Content-Type"]); +} + +TEST(UtilUrlEncodeTests, UnicodeEncoderTest) { + std::string input = "一二三"; + std::string result = util::urlEncode(input); + std::string expected = "%E4%B8%80%E4%BA%8C%E4%B8%89"; + EXPECT_EQ(result, expected); +} + +TEST(UtilUrlEncodeTests, AsciiEncoderTest) { + std::string input = "Hello World!"; + std::string result = util::urlEncode(input); + std::string expected = "Hello%20World%21"; + EXPECT_EQ(result, expected); +} + +TEST(UtilUrlDecodeTests, UnicodeDecoderTest) { + std::string input = "%E4%B8%80%E4%BA%8C%E4%B8%89"; + std::string result = util::urlDecode(input); + std::string expected = "一二三"; + EXPECT_EQ(result, expected); +} + +TEST(UtilUrlDecodeTests, AsciiDecoderTest) { + std::string input = "Hello%20World%21"; + std::string result = util::urlDecode(input); + std::string expected = "Hello World!"; + EXPECT_EQ(result, expected); +} + +TEST(UtilSecureStringClearTests, EmptyStringTest) { + std::string input; + util::secureStringClear(input); + EXPECT_TRUE(input.empty()); +} + +TEST(UtilSecureStringClearTests, NotEmptyStringTest) { + std::string input = "Hello World!"; + util::secureStringClear(input); + EXPECT_TRUE(input.empty()); +} + +TEST(UtilIsTrueTests, TrueTest) { + { + std::string input = "TRUE"; + bool output = util::isTrue(input); + EXPECT_TRUE(output); + } + { + std::string input = "True"; + bool output = util::isTrue(input); + EXPECT_TRUE(output); + } + { + std::string input = "true"; + bool output = util::isTrue(input); + EXPECT_TRUE(output); + } +} + +TEST(UtilIsTrueTests, FalseTest) { + { + std::string input = "FALSE"; + bool output = util::isTrue(input); + EXPECT_FALSE(output); + } + { + std::string input = "False"; + bool output = util::isTrue(input); + EXPECT_FALSE(output); + } + { + std::string input = "false"; + bool output = util::isTrue(input); + EXPECT_FALSE(output); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Src/external_dependencies/cpr/test/version_tests.cpp b/Src/external_dependencies/cpr/test/version_tests.cpp new file mode 100644 index 00000000..4c589d29 --- /dev/null +++ b/Src/external_dependencies/cpr/test/version_tests.cpp @@ -0,0 +1,65 @@ +#include <cctype> +#include <cpr/cpr.h> +#include <cstddef> +#include <gtest/gtest.h> +#include <string> + + +TEST(VersionTests, StringVersionExists) { +#ifndef CPR_VERSION + EXPECT_TRUE(false); +#endif // CPR_VERSION +} + +TEST(VersionTests, StringVersionValid) { + EXPECT_TRUE(CPR_VERSION != nullptr); + std::string version = CPR_VERSION; + + // Check if the version string is: '\d+\.\d+\.\d+' + bool digit = true; + size_t dotCount = 0; + for (size_t i = 0; i < version.size(); i++) { + if (i == 0) { + EXPECT_TRUE(std::isdigit(version[i])); + } else if (digit) { + if (version[i] == '.') { + digit = false; + dotCount++; + continue; + } + } + EXPECT_TRUE(std::isdigit(version[i])); + digit = true; + } + EXPECT_EQ(dotCount, 2); +} + +TEST(VersionTests, VersionMajorExists) { +#ifndef CPR_VERSION_MAJOR + EXPECT_TRUE(false); +#endif // CPR_VERSION_MAJOR +} + +TEST(VersionTests, VersionMinorExists) { +#ifndef CPR_VERSION_MINOR + EXPECT_TRUE(false); +#endif // CPR_VERSION_MINOR +} + +TEST(VersionTests, VersionPatchExists) { +#ifndef CPR_VERSION_PATCH + EXPECT_TRUE(false); +#endif // CPR_VERSION_PATCH +} + +TEST(VersionTests, VersionNumExists) { +#ifndef CPR_VERSION_NUM + EXPECT_TRUE(false); +#endif // CPR_VERSION_NUM +} + + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |