mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2f4728489 | |||
| d1b616286f | |||
| df74526f91 | |||
| 9f7ae0737a | |||
| 1ebb8412c5 | |||
| 7b69999c37 | |||
| c7e959a948 | |||
| ba5884e779 | |||
| cdaa5c48db | |||
| bab5c0e907 | |||
| 016838fd10 | |||
| 75053bf855 | |||
| ae3a6dd2a9 | |||
| 6d963fbe8d | |||
| 88f6245c84 | |||
| 0e7d2f9f93 | |||
| 4e6ded1f36 | |||
| d663588491 | |||
| 439caf5b79 | |||
| c4ba43ca6f | |||
| 20cba2ecd9 | |||
| 0ff2e16d69 | |||
| 51607ec752 | |||
| 7992b14896 | |||
| 7e420aeed3 | |||
| 227d2c2050 | |||
| 93e53c91f7 | |||
| 58cffd3223 | |||
| 8f32271e8c | |||
| c8c1c3d376 | |||
| 9f512acb42 |
+12
-6
@@ -8,13 +8,14 @@
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
After installation with Cmake, a find_package(httplib) is available.
|
||||
This creates a httplib::httplib target (if found).
|
||||
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available.
|
||||
This creates a httplib::httplib target (if found and if listed components are supported).
|
||||
It can be linked like so:
|
||||
|
||||
target_link_libraries(your_exe httplib::httplib)
|
||||
@@ -78,7 +79,7 @@ set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silenty continue if not found.
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
|
||||
# Make these options so their automatic use can be specifically disabled (as needed)
|
||||
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
@@ -88,7 +89,7 @@ option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a c
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
# Defaults to static library
|
||||
@@ -205,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
$<$<PLATFORM_ID:Windows>:cryptui>
|
||||
# Needed for API from MacOS Security framework
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
|
||||
@@ -232,8 +235,6 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
||||
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
|
||||
# Passes the includedir install path
|
||||
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
|
||||
# There aren't any components, so don't use the macro
|
||||
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||
)
|
||||
|
||||
if(HTTPLIB_COMPILE)
|
||||
@@ -278,3 +279,8 @@ install(EXPORT httplibTargets
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
@@ -54,6 +54,7 @@ SSL Support
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
NOTE for macOS: cpp-httplib now uses system certs. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -493,6 +494,10 @@ auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
or
|
||||
```c++
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
```
|
||||
or
|
||||
```c++
|
||||
cli.set_default_headers({
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
});
|
||||
|
||||
+8
-1
@@ -8,12 +8,19 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
endif
|
||||
endif
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark issue
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public:
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
|
||||
sink->write(message_.data(), message_.size());
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
|
||||
+17
-8
@@ -12,19 +12,19 @@ set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
# We add find_dependency calls here to not make the end-user have to call them.
|
||||
find_dependency(Threads REQUIRED)
|
||||
find_dependency(Threads)
|
||||
if(@HTTPLIB_IS_USING_OPENSSL@)
|
||||
# OpenSSL COMPONENTS were added in Cmake v3.11
|
||||
if(CMAKE_VERSION VERSION_LESS "3.11")
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ REQUIRED)
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@)
|
||||
else()
|
||||
# Once the COMPONENTS were added, they were made optional when not specified.
|
||||
# Since we use both, we need to search for both.
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL REQUIRED)
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
|
||||
endif()
|
||||
endif()
|
||||
if(@HTTPLIB_IS_USING_ZLIB@)
|
||||
find_dependency(ZLIB REQUIRED)
|
||||
find_dependency(ZLIB)
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
@@ -32,7 +32,7 @@ if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder REQUIRED)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder)
|
||||
endif()
|
||||
|
||||
# Mildly useful for end-users
|
||||
@@ -42,10 +42,19 @@ set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
# This is helpful if you're using Cmake's pre-compiled header feature
|
||||
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
|
||||
|
||||
# Brings in the target library
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
|
||||
# Consider each library support as a "component"
|
||||
set(httplib_OpenSSL_FOUND @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(httplib_ZLIB_FOUND @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(httplib_Brotli_FOUND @HTTPLIB_IS_USING_BROTLI@)
|
||||
|
||||
# Ouputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
|
||||
check_required_components(httplib)
|
||||
|
||||
# Brings in the target library, but only if all required components are found
|
||||
if(NOT DEFINED httplib_FOUND OR httplib_FOUND)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
|
||||
endif()
|
||||
|
||||
# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
|
||||
include(FindPackageMessage)
|
||||
if(TARGET httplib::httplib)
|
||||
set(HTTPLIB_FOUND TRUE)
|
||||
|
||||
@@ -34,6 +34,9 @@ openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cp
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
if host_machine.system() == 'darwin'
|
||||
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
|
||||
endif
|
||||
endif
|
||||
|
||||
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
find_package(GTest)
|
||||
|
||||
if(GTest_FOUND)
|
||||
if(NOT TARGET GTest::gtest_main AND TARGET GTest::Main)
|
||||
# CMake <3.20
|
||||
add_library(GTest::gtest_main INTERFACE IMPORTED)
|
||||
target_link_libraries(GTest::gtest_main INTERFACE GTest::Main)
|
||||
endif()
|
||||
else()
|
||||
if(POLICY CMP0135)
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
set(BUILD_GMOCK OFF)
|
||||
set(INSTALL_GTEST OFF)
|
||||
set(gtest_force_shared_crt ON)
|
||||
|
||||
FetchContent_Declare(
|
||||
gtest
|
||||
URL https://github.com/google/googletest/archive/main.tar.gz
|
||||
)
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
endif()
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
|
||||
gtest_discover_tests(httplib-test)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
if(HTTPLIB_IS_USING_OPENSSL)
|
||||
find_program(OPENSSL_COMMAND
|
||||
NAMES openssl
|
||||
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
|
||||
REQUIRED
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key.pem
|
||||
OUTPUT_FILE cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE rootCA.key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
OUTPUT_FILE rootCA.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE client.key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client.key.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
|
||||
OUTPUT_FILE client.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa -passout pass:test123! 2048
|
||||
OUTPUT_FILE key_encrypted.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key_encrypted.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key_encrypted.pem
|
||||
OUTPUT_FILE cert_encrypted.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
endif()
|
||||
@@ -8,6 +8,13 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
endif
|
||||
endif
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
|
||||
@@ -22,8 +22,6 @@ public:
|
||||
|
||||
ssize_t write(const std::string &s) { return write(s.data(), s.size()); }
|
||||
|
||||
std::string get_remote_addr() const { return ""; }
|
||||
|
||||
bool is_readable() const override { return true; }
|
||||
|
||||
bool is_writable() const override { return true; }
|
||||
@@ -33,6 +31,11 @@ public:
|
||||
port = 8080;
|
||||
}
|
||||
|
||||
void get_local_ip_and_port(std::string &ip, int &port) const override {
|
||||
ip = "127.0.0.1";
|
||||
port = 8080;
|
||||
}
|
||||
|
||||
socket_t socket() const override { return 0; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
// Forward declare the "fuzz target" interface.
|
||||
// We deliberately keep this inteface simple and header-free.
|
||||
// We deliberately keep this interface simple and header-free.
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
// It reads all files passed as parameters and feeds their contents
|
||||
|
||||
+501
-25
@@ -1,10 +1,12 @@
|
||||
#include <httplib.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
@@ -44,7 +46,7 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
return *it;
|
||||
#else
|
||||
if (it != files.end()) { return *it; }
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
throw std::runtime_error("invalid multipart form data name error");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -167,6 +169,39 @@ TEST(ParamsToQueryTest, ConvertParamsToQuery) {
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, DefaultValue) {
|
||||
string content_type = "multipart/form-data; boundary=something";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "something");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithQuote) {
|
||||
string content_type = "multipart/form-data; boundary=\"gc0pJq0M:08jU534c0p\"";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "gc0pJq0M:08jU534c0p");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithCharset) {
|
||||
string content_type = "multipart/mixed; boundary=THIS_STRING_SEPARATES;charset=UTF-8";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "THIS_STRING_SEPARATES");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
|
||||
string content_type =
|
||||
"multipart/mixed; boundary=\"cpp-httplib-multipart-data\"; charset=UTF-8";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "cpp-httplib-multipart-data");
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
@@ -594,7 +629,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
|
||||
ASSERT_TRUE(!res);
|
||||
stringstream s;
|
||||
s << "error code: " << res.error();
|
||||
EXPECT_EQ("error code: Connection (2)", s.str());
|
||||
EXPECT_EQ("error code: Could not establish connection (2)", s.str());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
@@ -777,7 +812,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
}
|
||||
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
@@ -942,6 +977,44 @@ TEST(UrlWithSpace, Redirect_Online) {
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
TEST(ReceiveSignals, Signal) {
|
||||
auto setupSignalHandlers = []() {
|
||||
struct sigaction act;
|
||||
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
act.sa_sigaction = [](int sig, siginfo_t *, void *) {
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
default: break;
|
||||
}
|
||||
};
|
||||
::sigaction(SIGINT, &act, nullptr);
|
||||
};
|
||||
|
||||
Server svr;
|
||||
int port = 0;
|
||||
auto thread = std::thread([&]() {
|
||||
setupSignalHandlers();
|
||||
port = svr.bind_to_any_port("localhost");
|
||||
svr.listen_after_bind();
|
||||
});
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
pthread_kill(thread.native_handle(), SIGINT);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
ASSERT_TRUE(svr.is_running());
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(RedirectToDifferentPort, Redirect) {
|
||||
Server svr1;
|
||||
svr1.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -1324,7 +1397,12 @@ TEST(NoContentTest, ContentLength) {
|
||||
}
|
||||
|
||||
TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
#else
|
||||
Server svr;
|
||||
#endif
|
||||
|
||||
svr.set_pre_routing_handler([](const Request &req, Response &res) {
|
||||
if (req.path == "/routing_handler") {
|
||||
@@ -1355,7 +1433,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/routing_handler");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1368,7 +1451,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
}
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1379,7 +1467,12 @@ TEST(RoutingHandlerTest, PreRoutingHandler) {
|
||||
}
|
||||
|
||||
{
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
#else
|
||||
Client cli(HOST, PORT);
|
||||
#endif
|
||||
|
||||
auto res = cli.Get("/aaa");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -1521,6 +1614,17 @@ protected:
|
||||
std::stoi(req.get_header_value("REMOTE_PORT")));
|
||||
res.set_content(remote_addr.c_str(), "text/plain");
|
||||
})
|
||||
.Get("/local_addr",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_TRUE(req.has_header("LOCAL_PORT"));
|
||||
EXPECT_TRUE(req.has_header("LOCAL_ADDR"));
|
||||
auto local_addr = req.get_header_value("LOCAL_ADDR");
|
||||
auto local_port = req.get_header_value("LOCAL_PORT");
|
||||
EXPECT_EQ(req.local_addr, local_addr);
|
||||
EXPECT_EQ(req.local_port, std::stoi(local_port));
|
||||
res.set_content(local_addr.append(":").append(local_port),
|
||||
"text/plain");
|
||||
})
|
||||
.Get("/endwith%",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
@@ -1579,7 +1683,6 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain", [](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "123";
|
||||
sink.os << "456";
|
||||
sink.os << "789";
|
||||
@@ -1593,7 +1696,6 @@ protected:
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
switch (*i) {
|
||||
case 0: sink.os << "123"; break;
|
||||
case 1: sink.os << "456"; break;
|
||||
@@ -1623,7 +1725,6 @@ protected:
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
@@ -1643,8 +1744,6 @@ protected:
|
||||
res.set_content_provider(
|
||||
size_t(-1), "text/plain",
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
if (!sink.is_writable()) return false;
|
||||
|
||||
sink.os << "data_chunk";
|
||||
return true;
|
||||
});
|
||||
@@ -1701,6 +1800,40 @@ protected:
|
||||
EXPECT_EQ("application/json tmp-string", file.content_type);
|
||||
}
|
||||
})
|
||||
.Post("/multipart/multi_file_values",
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_EQ(5u, req.files.size());
|
||||
ASSERT_TRUE(!req.has_file("???"));
|
||||
ASSERT_TRUE(req.body.empty());
|
||||
|
||||
{
|
||||
const auto &text_value = req.get_file_values("text");
|
||||
EXPECT_EQ(text_value.size(), 1);
|
||||
auto &text = text_value[0];
|
||||
EXPECT_TRUE(text.filename.empty());
|
||||
EXPECT_EQ("default text", text.content);
|
||||
}
|
||||
{
|
||||
const auto &text1_values = req.get_file_values("multi_text1");
|
||||
EXPECT_EQ(text1_values.size(), 2);
|
||||
EXPECT_EQ("aaaaa", text1_values[0].content);
|
||||
EXPECT_EQ("bbbbb", text1_values[1].content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file1_values = req.get_file_values("multi_file1");
|
||||
EXPECT_EQ(file1_values.size(), 2);
|
||||
auto file1 = file1_values[0];
|
||||
EXPECT_EQ(file1.filename, "hello.txt");
|
||||
EXPECT_EQ(file1.content_type, "text/plain");
|
||||
EXPECT_EQ("h\ne\n\nl\nl\no\n", file1.content);
|
||||
|
||||
auto file2 = file1_values[1];
|
||||
EXPECT_EQ(file2.filename, "world.json");
|
||||
EXPECT_EQ(file2.content_type, "application/json");
|
||||
EXPECT_EQ("{\n \"world\", true\n}\n", file2.content);
|
||||
}
|
||||
})
|
||||
.Post("/empty",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
@@ -2545,6 +2678,23 @@ TEST_F(ServerTest, MultipartFormData) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, MultipartFormDataMultiFileValues) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text", "default text", "", ""},
|
||||
|
||||
{"multi_text1", "aaaaa", "", ""},
|
||||
{"multi_text1", "bbbbb", "", ""},
|
||||
|
||||
{"multi_file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
|
||||
{"multi_file1", "{\n \"world\", true\n}\n", "world.json", "application/json"},
|
||||
};
|
||||
|
||||
auto res = cli_.Post("/multipart/multi_file_values", items);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, CaseInsensitiveHeaderName) {
|
||||
auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2810,6 +2960,15 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
|
||||
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodLocalAddr) {
|
||||
auto res = cli_.Get("/local_addr");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_TRUE(res->body == std::string("::1:").append(to_string(PORT)) ||
|
||||
res->body == std::string("127.0.0.1:").append(to_string(PORT)));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HTTPResponseSplitting) {
|
||||
auto res = cli_.Get("/http_response_splitting");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2872,7 +3031,6 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
@@ -2899,7 +3057,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
@@ -2926,7 +3083,6 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
@@ -2955,7 +3111,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
@@ -3063,7 +3218,7 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
{
|
||||
httplib::detail::gzip_decompressor decompressor;
|
||||
|
||||
// Chunk size is chosen specificaly to have a decompressed chunk size equal
|
||||
// Chunk size is chosen specifically to have a decompressed chunk size equal
|
||||
// to 16384 bytes 16384 bytes is the size of decompressor output buffer
|
||||
size_t chunk_size = 130;
|
||||
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size();
|
||||
@@ -3212,7 +3367,7 @@ TEST_F(ServerTest, PostContentReceiver) {
|
||||
ASSERT_EQ("content", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
TEST_F(ServerTest, PostMultipartFileContentReceiver) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
{"text2", "aωb", "", ""},
|
||||
@@ -3227,7 +3382,7 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMulitpartPlusBoundary) {
|
||||
TEST_F(ServerTest, PostMultipartPlusBoundary) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
{"text2", "aωb", "", ""},
|
||||
@@ -3643,10 +3798,10 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparsableHeaderLine) {
|
||||
// Make sure this doesn't crash the server.
|
||||
// In a previous version of the header line regex, the "\r" rendered the line
|
||||
// unparseable and the regex engine repeatedly backtracked, trying to look for
|
||||
// unparsable and the regex engine repeatedly backtracked, trying to look for
|
||||
// a new position where the leading white space ended and the field value
|
||||
// began.
|
||||
// The crash occurs with libc++ but not libstdc++.
|
||||
@@ -3749,6 +3904,32 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, ClientAccessAfterServerDown) {
|
||||
httplib::Server svr;
|
||||
svr.Post("/hi", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||
res.status = 200;
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Post("/hi", "data", "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
|
||||
res = cli.Post("/hi", "data", "text/plain");
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
Server svr;
|
||||
|
||||
@@ -3904,7 +4085,7 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
ASSERT_FALSE(resa);
|
||||
EXPECT_EQ(Error::Read, resa.error());
|
||||
|
||||
auto resb = cli.Get("/b");
|
||||
@@ -3918,35 +4099,74 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, Issue1041) {
|
||||
const auto resourcePath = "/hi";
|
||||
|
||||
Server svr;
|
||||
svr.set_keep_alive_timeout(3);
|
||||
|
||||
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Get(resourcePath);
|
||||
auto result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
|
||||
result = cli.Get(resourcePath);
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
a2.wait();
|
||||
f.wait();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, SSLClientReconnection) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.set_keep_alive_timeout(1);
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Recoonect
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
f.wait();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
Server svr;
|
||||
|
||||
@@ -5066,6 +5286,247 @@ TEST(MultipartFormDataTest, LargeData) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, DataProviderItems) {
|
||||
|
||||
std::random_device seed_gen;
|
||||
std::mt19937 random(seed_gen());
|
||||
|
||||
std::string rand1;
|
||||
rand1.resize(1000);
|
||||
std::generate(rand1.begin(), rand1.end(), [&]() { return random(); });
|
||||
|
||||
std::string rand2;
|
||||
rand2.resize(3000);
|
||||
std::generate(rand2.begin(), rand2.end(), [&]() { return random(); });
|
||||
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
|
||||
svr.Post("/post-none", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_FALSE(req.is_multipart_form_data());
|
||||
|
||||
std::string body;
|
||||
content_reader([&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_EQ(body, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-items", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 2);
|
||||
|
||||
EXPECT_EQ(std::string(files[0].name), "name1");
|
||||
EXPECT_EQ(files[0].content, "Testing123");
|
||||
EXPECT_EQ(files[0].filename, "filename1");
|
||||
EXPECT_EQ(files[0].content_type, "application/octet-stream");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name2");
|
||||
EXPECT_EQ(files[1].content, "Testing456");
|
||||
EXPECT_EQ(files[1].filename, "");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-providers", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 2);
|
||||
|
||||
EXPECT_EQ(files[0].name, "name3");
|
||||
EXPECT_EQ(files[0].content, rand1);
|
||||
EXPECT_EQ(files[0].filename, "filename3");
|
||||
EXPECT_EQ(files[0].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name4");
|
||||
EXPECT_EQ(files[1].content, rand2);
|
||||
EXPECT_EQ(files[1].filename, "filename4");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
});
|
||||
|
||||
svr.Post("/post-both", [&](const Request &req, Response & /*res*/,
|
||||
const ContentReader &content_reader) {
|
||||
ASSERT_TRUE(req.is_multipart_form_data());
|
||||
MultipartFormDataItems files;
|
||||
content_reader(
|
||||
[&](const MultipartFormData &file) {
|
||||
files.push_back(file);
|
||||
return true;
|
||||
},
|
||||
[&](const char *data, size_t data_length) {
|
||||
files.back().content.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(files.size() == 4);
|
||||
|
||||
EXPECT_EQ(std::string(files[0].name), "name1");
|
||||
EXPECT_EQ(files[0].content, "Testing123");
|
||||
EXPECT_EQ(files[0].filename, "filename1");
|
||||
EXPECT_EQ(files[0].content_type, "application/octet-stream");
|
||||
|
||||
EXPECT_EQ(files[1].name, "name2");
|
||||
EXPECT_EQ(files[1].content, "Testing456");
|
||||
EXPECT_EQ(files[1].filename, "");
|
||||
EXPECT_EQ(files[1].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[2].name, "name3");
|
||||
EXPECT_EQ(files[2].content, rand1);
|
||||
EXPECT_EQ(files[2].filename, "filename3");
|
||||
EXPECT_EQ(files[2].content_type, "");
|
||||
|
||||
EXPECT_EQ(files[3].name, "name4");
|
||||
EXPECT_EQ(files[3].content, rand2);
|
||||
EXPECT_EQ(files[3].filename, "filename4");
|
||||
EXPECT_EQ(files[3].content_type, "");
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("https://localhost:8080");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
MultipartFormDataItems items{
|
||||
{"name1", "Testing123", "filename1", "application/octet-stream"},
|
||||
{"name2", "Testing456", "", ""}, // not a file
|
||||
};
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-none", {}, {}, {});
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
MultipartFormDataProviderItems providers;
|
||||
|
||||
{
|
||||
auto res =
|
||||
cli.Post("/post-items", {}, items, providers); // empty providers
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
providers.push_back({"name3",
|
||||
[&](size_t offset, httplib::DataSink &sink) -> bool {
|
||||
// test the offset is given correctly at each step
|
||||
if (!offset)
|
||||
sink.os.write(rand1.data(), 30);
|
||||
else if (offset == 30)
|
||||
sink.os.write(rand1.data() + 30, 300);
|
||||
else if (offset == 330)
|
||||
sink.os.write(rand1.data() + 330, 670);
|
||||
else if (offset == rand1.size())
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"filename3",
|
||||
{}});
|
||||
|
||||
providers.push_back({"name4",
|
||||
[&](size_t offset, httplib::DataSink &sink) -> bool {
|
||||
// test the offset is given correctly at each step
|
||||
if (!offset)
|
||||
sink.os.write(rand2.data(), 2000);
|
||||
else if (offset == 2000)
|
||||
sink.os.write(rand2.data() + 2000, 1);
|
||||
else if (offset == 2001)
|
||||
sink.os.write(rand2.data() + 2001, 999);
|
||||
else if (offset == rand2.size())
|
||||
sink.done();
|
||||
return true;
|
||||
},
|
||||
"filename4",
|
||||
{}});
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-providers", {}, {}, providers);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Post("/post-both", {}, items, providers);
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(200, res->status);
|
||||
}
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, BadHeader) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
const std::string body =
|
||||
"This is the preamble. It is to be ignored, though it\r\n"
|
||||
"is a handy place for composition agents to include an\r\n"
|
||||
"explanatory note to non-MIME conformant readers.\r\n"
|
||||
"\r\n"
|
||||
"\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field1\"\r\n"
|
||||
": BAD...\r\n"
|
||||
"\r\n"
|
||||
"value1\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field2\"; "
|
||||
"filename=\"example.txt\"\r\n"
|
||||
"\r\n"
|
||||
"value2\r\n"
|
||||
"--simple boundary--\r\n"
|
||||
"This is the epilogue. It is also to be ignored.\r\n";
|
||||
|
||||
std::string content_type =
|
||||
R"(multipart/form-data; boundary="simple boundary")";
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Post("/post", body, content_type.c_str());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(400, res->status);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, WithPreamble) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -5502,3 +5963,18 @@ TEST(SocketStream, is_writable_INET) {
|
||||
ASSERT_EQ(0, close(disconnected_svr_sock));
|
||||
}
|
||||
#endif // #ifndef _WIN32
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
static constexpr unsigned int number_of_task{1000000};
|
||||
std::atomic_uint count{0};
|
||||
std::unique_ptr<TaskQueue> task_queue{
|
||||
new ThreadPool{CPPHTTPLIB_THREAD_POOL_COUNT}};
|
||||
|
||||
for (unsigned int i = 0; i < number_of_task; ++i) {
|
||||
task_queue->enqueue(
|
||||
[&count] { count.fetch_add(1, std::memory_order_relaxed); });
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_task, count.load());
|
||||
}
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
}
|
||||
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
|
||||
Reference in New Issue
Block a user