Compare commits

...

45 Commits

Author SHA1 Message Date
PabloMK7 a609330e4c Add optional user defined header writer (#1683)
* Add optional user defined header writer

* Fix errors and add test
2023-09-30 22:13:14 -04:00
Jiwoo Park c029597a5a Update the remote address of www.httpwatch.com (#1664) 2023-09-13 10:33:33 -04:00
yhirose 30b7732565 Release v0.14.0 2023-08-22 20:19:07 -04:00
yhirose 6650632e7f Fix #1638 2023-08-22 19:36:10 -04:00
Sven Panne afe627e7af Avoid a -Warray-bounds false positive in GCC 13. (#1639)
The exact circumstances when this false positive is triggered are quite
tricky to reproduce, but it happened reproducibly with g++ 13.1 and 13.2 in
a close-source SW I'm working on.  The fix even improves performance by a
very tiny bit: There is no need to copy the std::smatch, having a const
reference is enough.

Just as a side note: -Warray-bounds seems to cause trouble in other
projects, too, so e.g. the Linux kernel has disabled since June 2022.
2023-08-14 10:26:54 -04:00
Duncan Ogilvie 67f6ff7fa9 Fix CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR (#1634) 2023-08-03 17:01:40 -04:00
yhirose c7ed1796a7 Release v0.13.3 2023-07-31 21:28:33 -04:00
yhirose 44c62d838e Use memory mapped file for static file server (#1632)
* Use memory mapped file for static file server

* Fix build error
2023-07-31 07:43:50 -04:00
yhirose 6bb580cda8 Fix #1559 2023-07-31 00:27:26 -04:00
yhirose 00a8cb8e5d Code cleanup 2023-07-31 00:22:22 -04:00
yhirose 2e34a39673 Added StaticFileRanges test 2023-07-31 00:22:22 -04:00
yhirose 01b90829bc Removed unnecessary CRLF at the end of multipart ranges data 2023-07-31 00:22:22 -04:00
yhirose e699bd0730 Release v0.13.2 2023-07-29 12:26:19 -04:00
Akın ELDEN 961a9379d5 Fix #1590 (#1630)
* ClientImpl: Connection=close header control moved from process_request to send_

* Connection=close header control moved from send_ to handle_request

* SSLClient::connect_with_proxy error handling improved

* to_string definition added for Error::ProxyConnection

* Comment improvement

---------

Co-authored-by: akinelden <akin.elden@gmail.com>
2023-07-29 12:09:25 -04:00
yhirose ec87b04aff Fix #1619 2023-07-29 00:53:57 -04:00
yhirose aabf752a51 Fix #1519 2023-07-28 23:37:45 -04:00
yhirose afb0674ccb Fix #1624 2023-07-21 20:36:01 -04:00
bdenhollander ee625232a4 Fix successful decompress reported as Error::Read (#1612)
* Fix successful decompress reported as Error::Read

Streams less than 4096 bytes are sometimes reported as failed reads because stream_.avail_in is not reduced to 0. The next iteration of the loop finds `prev_avail_in == strm_.avail_in` and return false. `ret = inflate(...)` returns Z_STREAM_END on the first iteration of the loop indicating that inflate is finished. This fix prevents the second iteration of the loop from failing.

* Fix successful decompress reported as Error::Read

- Add unit tests for raw deflate that illustrates the decompression failure when there are extra trailing bytes
2023-07-11 18:35:27 -04:00
Andrea Pappacoda 52d8dd41f1 build(meson): use C++14 with GTest >= 1.13.0 (#1618)
GoogleTest, starting with vesion 1.13.0, requires C++14 to build. This
patch enables C++14 if GoogleTest 1.13.0 or newer is detected when
compiling the tests with Meson, making it possible to use new GTest
versions.

You can find GoogleTest's release notes at
<https://github.com/google/googletest/releases/tag/v1.13.0>.
2023-07-11 18:32:41 -04:00
Alexandre Bouvier be07d2d7a9 cmake: fix comment (#1616) 2023-07-08 13:24:36 -04:00
yhirose 0f1b62c2b3 Release v0.13.1 2023-07-08 07:41:24 -04:00
yhirose c30906a541 Code cleanup 2023-07-07 19:43:37 -04:00
bgs99 3533503323 Fix explicit constructor warning (#1614) 2023-07-07 13:17:19 -04:00
yhirose 82acdca638 Release v0.13.0 2023-07-06 18:32:42 -04:00
vmaffione a1e56a567b Result: allow default constructor (#1609) 2023-07-05 12:05:29 -04:00
yhirose 0c17d172a2 Code cleanup 2023-07-05 09:06:21 -04:00
yhirose 5d8e7c761f Updated README 2023-07-05 08:45:05 -04:00
bgs99 17fc522b75 Add named path parameters parsing (Implements #1587) (#1608)
* Add named path parameters parsing

* Select match mode based on pattern

* Add examples and comments to README

* Add documentation to matchers
2023-07-05 07:44:19 -04:00
yhirose f1daa5b88b Fix #1607 2023-07-04 20:27:11 -04:00
yhirose fe9a1949a6 Fixed an example in README 2023-06-28 11:28:08 -04:00
yhirose 50cba6db9f Fix #1603 2023-06-27 21:23:23 -04:00
Nathan Moinvaziri 18592e7f98 Ability to turn off linking against OpenSSL if already detected. (#1602)
We already detect OpenSSL in our tree for another project, but don't want to
link httplib against OpenSSL or with SSL support.

Allow us to `set(HTTPLIB_IS_USING_OPENSSL FALSE)`.
2023-06-27 20:09:42 -04:00
yhirose bd9612b81e Changed to use auto (#1594) 2023-06-17 02:18:34 -04:00
yhirose 7ab5fb65b2 Code cleanup (#1593) 2023-06-16 18:08:28 -04:00
Andre Eisenbach 8df5fedc35 Fix -Wold-style-cast warnings (#1591)
Removed multiple old-style (C) casts and replaced them with the
appropriate _cast's. This makes httplib a better citizen inside projects
that are compiled with all warnings enabled.

Unfortunately one warning remains as a result of invoking an
OpenSSL macro (at least on Linux), that would have to be fixed
upstream.

Also fixed a few casts for invocations of setsockopt.
setsockopt takes a const void* for the value pointer, not a char*.
Well, except on Windows ...

Co-authored-by: Andre Eisenbach <git@4ae.us>
2023-06-16 17:30:24 -04:00
Jiwoo Park 4a61f68fa4 Don't overwrite the last redirected location (#1589)
* Don't overwrite the last redirected location

* Check the last redirected location
2023-06-16 14:56:16 -04:00
Jiwoo Park 067890133c Use nghttp2-hosted httpbin.org (#1586)
* Use nghttp2-hosted httpbin.org

* Add CPPHTTPLIB_DEFAULT_HTTPBIN macro to choose the default httpbin.org
2023-06-15 11:12:41 -04:00
yhirose d3076f5a70 v0.12.6 2023-06-10 07:02:38 +09:00
yhirose ed129f057f Fixed C++11 warnings and code format 2023-06-09 20:49:46 +09:00
Jiwoo Park eab5ea01d7 Load in-memory CA certificates (#1579)
* Load in-memory CA certs

* Add test cases for in-memory cert loading

* Don't use the IIFE style
2023-06-09 16:34:51 +09:00
Petr Hosek 3e287b3a26 Provide a CMake option to disable C++ exceptions (#1580)
This allows disabling the use of C++ exceptions at CMake configure time.
The value is encoded in the generated httplibTargets.cmake file and will
be used by CMake projects that import it.
2023-06-06 16:56:26 +09:00
v1gnesh 4f33637b43 Add support for zOS (#1581)
Signed-off-by: v1gnesh <v1gnesh@users.noreply.github.com>
2023-06-06 14:14:06 +09:00
db-src 698a1e51ec Move, not copy, Logger and Handler functors (#1576)
* Explicitly #include <utility> for use of std::move

* Move not copy Logger arg from Client to ClientImpl

* Move not copy, set_error_handler Handler to lambda

* Remove null statement in non-empty if/else block

I guess it was a relic from a time before the other statement was added.

---------

Co-authored-by: Daniel Boles <daniel.boles@voltalis.com>
2023-06-02 15:40:00 +09:00
yhirose 27c0e1186c Release v12.0.5 2023-05-30 16:09:42 +09:00
Niccolò Iardella c54c71a3e5 Add HTTPLIB_INSTALL CMake option (#1575)
* Add HTTPLIB_INSTALL CMake option

* Proper formatting of HTTPLIB_INSTALL block

Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>

---------

Co-authored-by: Niccolò Iardella <niccolo.iardella@doriansrl.it>
Co-authored-by: Jiwoo Park <jiwoo_90@naver.com>
2023-05-30 16:08:58 +09:00
6 changed files with 9589 additions and 366 deletions
+30 -20
View File
@@ -9,6 +9,7 @@
* HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN (default on)
* HTTPLIB_REQUIRE_BROTLI (default off)
* HTTPLIB_COMPILE (default off)
* HTTPLIB_INSTALL (default on)
* 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).
@@ -74,6 +75,10 @@ string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
# Lets you disable C++ exception during CMake configure time.
# The value is used in the install CMake config file.
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
# Change as needed to set an OpenSSL minimum version.
# This is used in the installed Cmake config file.
set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
@@ -87,6 +92,8 @@ option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable H
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
# Lets you compile the program as a regular library instead of header-only
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
# Lets you disable the installation (useful when fetched from another CMake project)
option(HTTPLIB_INSTALL "Enables the installation target" ON)
# Just setting this variable here for people building in-tree
if(HTTPLIB_COMPILE)
set(HTTPLIB_IS_COMPILED TRUE)
@@ -113,7 +120,7 @@ elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
endif()
# Just setting this variable here for people building in-tree
if(OPENSSL_FOUND)
if(OPENSSL_FOUND AND NOT DEFINED HTTPLIB_IS_USING_OPENSSL)
set(HTTPLIB_IS_USING_OPENSSL TRUE)
endif()
@@ -226,6 +233,7 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
# Set the definitions to enable optional features
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
@@ -248,13 +256,13 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
if(HTTPLIB_COMPILE)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5 and <= 1.0 is accepted
# then anything >= 0.5.4 and < 0.6 is accepted
COMPATIBILITY SameMinorVersion
)
else()
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
# Example: if you find_package(httplib 0.5.4)
# then anything >= 0.5 and <= 1.0 is accepted
# then anything >= 0.5.4 and < 0.6 is accepted
COMPATIBILITY SameMinorVersion
# Tells Cmake that it's a header-only lib
# Mildly useful for end-users :)
@@ -262,31 +270,33 @@ else()
)
endif()
# Creates the export httplibTargets.cmake
# This is strictly what holds compilation requirements
# and linkage information (doesn't find deps though).
install(TARGETS ${PROJECT_NAME}
EXPORT httplibTargets
)
if(HTTPLIB_INSTALL)
# Creates the export httplibTargets.cmake
# This is strictly what holds compilation requirements
# and linkage information (doesn't find deps though).
install(TARGETS ${PROJECT_NAME}
EXPORT httplibTargets
)
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
install(FILES
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
# Install it so it can be used later by the httplibConfig.cmake file.
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
# NOTE: This path changes depending on if it's on Windows or Linux
install(EXPORT httplibTargets
# Puts the targets into the httplib namespace
# So this makes httplib::httplib linkable after doing find_package(httplib)
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
# NOTE: This path changes depending on if it's on Windows or Linux
install(EXPORT httplibTargets
# Puts the targets into the httplib namespace
# So this makes httplib::httplib linkable after doing find_package(httplib)
NAMESPACE ${PROJECT_NAME}::
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
)
endif()
if(HTTPLIB_TEST)
include(CTest)
+12 -3
View File
@@ -94,11 +94,20 @@ int main(void)
res.set_content("Hello World!", "text/plain");
});
// Match the request path against a regular expression
// and extract its captures
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
// Capture the second segment of the request path as "id" path param
svr.Get("/users/:id", [&](const Request& req, Response& res) {
auto user_id = req.path_params.at("id");
res.set_content(user_id, "text/plain");
});
// Extract values from HTTP headers and URL query params
svr.Get("/body-header-param", [](const Request& req, Response& res) {
if (req.has_header("Content-Length")) {
auto val = req.get_header_value("Content-Length");
@@ -181,6 +190,8 @@ The followings are built-in mappings:
| webm | video/webm | zip | application/zip |
| mp3 | audio/mp3 | wasm | application/wasm |
NOTE: These static file server methods are not thread-safe.
### File request handler
```cpp
@@ -190,8 +201,6 @@ svr.set_file_request_handler([](const Request &req, Response &res) {
});
```
NOTE: These static file server methods are not thread-safe.
### Logging
```cpp
@@ -303,7 +312,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
res.set_content_provider(
data->size(), // Content length
"text/plain", // Content type
[data](size_t offset, size_t length, DataSink &sink) {
[&, data](size_t offset, size_t length, DataSink &sink) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
return true; // return 'false' if you want to cancel the process.
+756 -284
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -86,6 +86,12 @@ subdir(join_paths('www', 'dir'))
subdir(join_paths('www2', 'dir'))
subdir(join_paths('www3', 'dir'))
# GoogleTest 1.13.0 requires C++14
test_options = []
if gtest_dep.version().version_compare('>=1.13.0')
test_options += 'cpp_std=c++14'
endif
test(
'main',
executable(
@@ -94,7 +100,8 @@ test(
dependencies: [
cpp_httplib_dep,
gtest_dep
]
],
override_options: test_options
),
depends: [
key_pem,
+591 -58
View File
@@ -211,8 +211,7 @@ TEST(GetHeaderValueTest, DefaultValue) {
TEST(GetHeaderValueTest, DefaultValueInt) {
Headers headers = {{"Dummy", "Dummy"}};
auto val =
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 100);
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
EXPECT_EQ(100ull, val);
}
@@ -241,8 +240,7 @@ TEST(GetHeaderValueTest, SetContent) {
TEST(GetHeaderValueTest, RegularValueInt) {
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
auto val =
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 0);
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0);
EXPECT_EQ(100ull, val);
}
@@ -444,7 +442,7 @@ TEST(HostnameToIPConversionTest, HTTPWatch_Online) {
auto host = "www.httpwatch.com";
auto ip = hosted_at(host);
EXPECT_EQ("191.236.16.12", ip);
EXPECT_EQ("23.96.13.243", ip);
std::vector<std::string> addrs;
hosted_at(host, addrs);
@@ -526,7 +524,13 @@ TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) {
}
TEST(RangeTest, FromHTTPBin_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/range/32"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/range/32"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
@@ -538,7 +542,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
cli.set_connection_timeout(5);
{
auto res = cli.Get("/range/32");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
@@ -546,7 +550,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
{
Headers headers = {make_range_header({{1, -1}})};
auto res = cli.Get("/range/32", headers);
auto res = cli.Get(path, headers);
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(206, res->status);
@@ -554,7 +558,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
{
Headers headers = {make_range_header({{1, 10}})};
auto res = cli.Get("/range/32", headers);
auto res = cli.Get(path, headers);
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
@@ -562,7 +566,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
{
Headers headers = {make_range_header({{0, 31}})};
auto res = cli.Get("/range/32", headers);
auto res = cli.Get(path, headers);
ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
@@ -570,7 +574,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
{
Headers headers = {make_range_header({{0, -1}})};
auto res = cli.Get("/range/32", headers);
auto res = cli.Get(path, headers);
ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
@@ -578,7 +582,7 @@ TEST(RangeTest, FromHTTPBin_Online) {
{
Headers headers = {make_range_header({{0, 32}})};
auto res = cli.Get("/range/32", headers);
auto res = cli.Get(path, headers);
ASSERT_TRUE(res);
EXPECT_EQ(416, res->status);
}
@@ -673,7 +677,13 @@ TEST(ConnectionErrorTest, Timeout_Online) {
}
TEST(CancelTest, NoCancel_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/range/32"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/range/32"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
@@ -684,14 +694,20 @@ TEST(CancelTest, NoCancel_Online) {
#endif
cli.set_connection_timeout(std::chrono::seconds(5));
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return true; });
auto res = cli.Get(path, [](uint64_t, uint64_t) { return true; });
ASSERT_TRUE(res);
EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body);
EXPECT_EQ(200, res->status);
}
TEST(CancelTest, WithCancelSmallPayload_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/range/32"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/range/32"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
@@ -701,14 +717,20 @@ TEST(CancelTest, WithCancelSmallPayload_Online) {
Client cli(host, port);
#endif
auto res = cli.Get("/range/32", [](uint64_t, uint64_t) { return false; });
auto res = cli.Get(path, [](uint64_t, uint64_t) { return false; });
cli.set_connection_timeout(std::chrono::seconds(5));
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(CancelTest, WithCancelLargePayload_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/range/65536"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/range/65536"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
@@ -720,14 +742,20 @@ TEST(CancelTest, WithCancelLargePayload_Online) {
cli.set_connection_timeout(std::chrono::seconds(5));
uint32_t count = 0;
auto res = cli.Get("/range/65536",
[&count](uint64_t, uint64_t) { return (count++ == 0); });
auto res =
cli.Get(path, [&count](uint64_t, uint64_t) { return (count++ == 0); });
ASSERT_TRUE(!res);
EXPECT_EQ(Error::Canceled, res.error());
}
TEST(BaseAuthTest, FromHTTPWatch_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/basic-auth/hello/world"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/basic-auth/hello/world"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
auto port = 443;
@@ -738,14 +766,14 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
#endif
{
auto res = cli.Get("/basic-auth/hello/world");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ(401, res->status);
}
{
auto res = cli.Get("/basic-auth/hello/world",
{make_basic_authentication_header("hello", "world")});
auto res =
cli.Get(path, {make_basic_authentication_header("hello", "world")});
ASSERT_TRUE(res);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
@@ -754,7 +782,7 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
{
cli.set_basic_auth("hello", "world");
auto res = cli.Get("/basic-auth/hello/world");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
res->body);
@@ -763,14 +791,14 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
{
cli.set_basic_auth("hello", "bad");
auto res = cli.Get("/basic-auth/hello/world");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ(401, res->status);
}
{
cli.set_basic_auth("bad", "world");
auto res = cli.Get("/basic-auth/hello/world");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ(401, res->status);
}
@@ -778,26 +806,39 @@ TEST(BaseAuthTest, FromHTTPWatch_Online) {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(DigestAuthTest, FromHTTPWatch_Online) {
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto unauth_path = std::string{"/digest-auth/auth/hello/world"};
auto paths = std::vector<std::string>{
"/digest-auth/auth/hello/world/MD5",
"/digest-auth/auth/hello/world/SHA-256",
"/digest-auth/auth/hello/world/SHA-512",
"/digest-auth/auth-int/hello/world/MD5",
};
#else
auto host = "nghttp2.org";
auto unauth_path = std::string{"/httpbin/digest-auth/auth/hello/world"};
auto paths = std::vector<std::string>{
"/httpbin/digest-auth/auth/hello/world/MD5",
"/httpbin/digest-auth/auth/hello/world/SHA-256",
"/httpbin/digest-auth/auth/hello/world/SHA-512",
"/httpbin/digest-auth/auth-int/hello/world/MD5",
};
#endif
auto port = 443;
SSLClient cli(host, port);
{
auto res = cli.Get("/digest-auth/auth/hello/world");
auto res = cli.Get(unauth_path);
ASSERT_TRUE(res);
EXPECT_EQ(401, res->status);
}
{
std::vector<std::string> paths = {
"/digest-auth/auth/hello/world/MD5",
"/digest-auth/auth/hello/world/SHA-256",
"/digest-auth/auth/hello/world/SHA-512",
"/digest-auth/auth-int/hello/world/MD5",
};
cli.set_digest_auth("hello", "world");
for (auto path : paths) {
for (const auto &path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res);
EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n",
@@ -806,7 +847,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
}
cli.set_digest_auth("hello", "bad");
for (auto path : paths) {
for (const auto &path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res);
EXPECT_EQ(401, res->status);
@@ -815,7 +856,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// for (const auto& path : paths) {
// auto res = cli.Get(path.c_str());
// ASSERT_TRUE(res);
// EXPECT_EQ(400, res->status);
@@ -929,7 +970,7 @@ TEST(YahooRedirectTest, Redirect_Online) {
res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://yahoo.com/", res->location);
EXPECT_EQ("https://www.yahoo.com/", res->location);
}
TEST(HttpsToHttpRedirectTest, Redirect_Online) {
@@ -973,7 +1014,7 @@ TEST(UrlWithSpace, Redirect_Online) {
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
EXPECT_EQ(18527U, res->get_header_value_u64("Content-Length"));
}
#endif
@@ -1551,6 +1592,46 @@ TEST(URLFragmentTest, WithFragment) {
}
}
TEST(HeaderWriter, SetHeaderWriter) {
Server svr;
svr.set_header_writer([](Stream &strm, Headers &hdrs) {
hdrs.emplace("CustomServerHeader", "CustomServerValue");
return detail::write_headers(strm, hdrs);
});
svr.Get("/hi", [](const Request &req, Response &res) {
auto it = req.headers.find("CustomClientHeader");
EXPECT_TRUE(it != req.headers.end());
EXPECT_EQ(it->second, "CustomClientValue");
res.set_content("Hello World!\n", "text/plain");
});
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
});
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli(HOST, PORT);
cli.set_header_writer([](Stream &strm, Headers &hdrs) {
hdrs.emplace("CustomClientHeader", "CustomClientValue");
return detail::write_headers(strm, hdrs);
});
auto res = cli.Get("/hi");
EXPECT_TRUE(res);
EXPECT_EQ(200, res->status);
auto it = res->headers.find("CustomServerHeader");
EXPECT_TRUE(it != res->headers.end());
EXPECT_EQ(it->second, "CustomServerValue");
}
}
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -2458,6 +2539,55 @@ TEST_F(ServerTest, StaticFileRange) {
EXPECT_EQ(std::string("cd"), res->body);
}
TEST_F(ServerTest, StaticFileRanges) {
auto res =
cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_TRUE(
res->get_header_value("Content-Type")
.find(
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
0);
EXPECT_EQ("265", res->get_header_value("Content-Length"));
}
TEST_F(ServerTest, StaticFileRangeHead) {
auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
EXPECT_EQ("2", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
}
TEST_F(ServerTest, StaticFileRangeBigFile) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("5", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
EXPECT_EQ("LAST\n", res->body);
}
TEST_F(ServerTest, StaticFileRangeBigFile2) {
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("4097", res->get_header_value("Content-Length"));
EXPECT_EQ(true, res->has_header("Content-Range"));
}
TEST_F(ServerTest, StaticFileBigFile) {
auto res = cli_.Get("/dir/1MB.txt");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ("1048576", res->get_header_value("Content-Length"));
}
TEST_F(ServerTest, InvalidBaseDirMount) {
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
}
@@ -2843,9 +2973,9 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ("267", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269U, res->body.size());
EXPECT_EQ(267U, res->body.size());
}
TEST_F(ServerTest, GetStreamedEndless) {
@@ -2935,9 +3065,9 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
ASSERT_TRUE(res);
EXPECT_EQ(206, res->status);
EXPECT_EQ("269", res->get_header_value("Content-Length"));
EXPECT_EQ("267", res->get_header_value("Content-Length"));
EXPECT_EQ(false, res->has_header("Content-Range"));
EXPECT_EQ(269U, res->body.size());
EXPECT_EQ(267U, res->body.size());
}
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
@@ -3204,7 +3334,7 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
EXPECT_EQ(101942u, res.get_request_header_value<uint64_t>("Content-Length"));
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
}
@@ -3287,6 +3417,59 @@ TEST(GzipDecompressor, ChunkedDecompression) {
ASSERT_EQ(data, decompressed_data);
}
TEST(GzipDecompressor, DeflateDecompression) {
std::string original_text = "Raw deflate without gzip";
unsigned char data[32] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E};
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
std::string decompressed_data;
{
httplib::detail::gzip_decompressor decompressor;
bool result = decompressor.decompress(
compressed_data.data(), compressed_data.size(),
[&](const char *decompressed_data_chunk,
size_t decompressed_data_chunk_size) {
decompressed_data.insert(decompressed_data.size(),
decompressed_data_chunk,
decompressed_data_chunk_size);
return true;
});
ASSERT_TRUE(result);
}
ASSERT_EQ(original_text, decompressed_data);
}
TEST(GzipDecompressor, DeflateDecompressionTrailingBytes) {
std::string original_text = "Raw deflate without gzip";
unsigned char data[40] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
std::string decompressed_data;
{
httplib::detail::gzip_decompressor decompressor;
bool result = decompressor.decompress(
compressed_data.data(), compressed_data.size(),
[&](const char *decompressed_data_chunk,
size_t decompressed_data_chunk_size) {
decompressed_data.insert(decompressed_data.size(),
decompressed_data_chunk,
decompressed_data_chunk_size);
return true;
});
ASSERT_TRUE(result);
}
ASSERT_EQ(original_text, decompressed_data);
}
#ifdef _WIN32
TEST(GzipDecompressor, LargeRandomData) {
@@ -3919,16 +4102,16 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
svr.Get("/events", [](const Request & /*req*/, Response &res) {
res.set_header("Cache-Control", "no-cache");
res.set_chunked_content_provider("text/event-stream", [](size_t offset,
DataSink &sink) {
std::string s = "data:";
s += std::to_string(offset);
s += "\n\n";
auto ret = sink.write(s.data(), s.size());
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
res.set_chunked_content_provider(
"text/event-stream", [](size_t offset, DataSink &sink) {
std::string s = "data:";
s += std::to_string(offset);
s += "\n\n";
auto ret = sink.write(s.data(), s.size());
EXPECT_TRUE(ret);
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
@@ -4336,6 +4519,12 @@ TEST(GetWithParametersTest, GetWithParameters) {
EXPECT_EQ("bar", req.get_param_value("param2"));
});
svr.Get("/users/:id", [&](const Request &req, Response &) {
EXPECT_EQ("user-id", req.path_params.at("id"));
EXPECT_EQ("foo", req.get_param_value("param1"));
EXPECT_EQ("bar", req.get_param_value("param2"));
});
auto listen_thread = std::thread([&svr]() { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
@@ -4376,6 +4565,15 @@ TEST(GetWithParametersTest, GetWithParameters) {
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
{
Client cli(HOST, PORT);
auto res = cli.Get("/users/user-id?param1=foo&param2=bar");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
}
}
TEST(GetWithParametersTest, GetWithParameters2) {
@@ -4414,19 +4612,32 @@ TEST(GetWithParametersTest, GetWithParameters2) {
}
TEST(ClientDefaultHeadersTest, DefaultHeaders_Online) {
Client cli("httpbin.org");
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/range/32"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/range/32"};
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
SSLClient cli(host);
#else
Client cli(host);
#endif
cli.set_default_headers({make_range_header({{1, 10}})});
cli.set_connection_timeout(5);
{
auto res = cli.Get("/range/32");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
}
{
auto res = cli.Get("/range/32");
auto res = cli.Get(path);
ASSERT_TRUE(res);
EXPECT_EQ("bcdefghijk", res->body);
EXPECT_EQ(206, res->status);
@@ -4637,6 +4848,26 @@ TEST_F(PayloadMaxLengthTest, ExceedLimit) {
EXPECT_EQ(200, res->status);
}
TEST(HostAndPortPropertiesTest, NoSSL) {
httplib::Client cli("www.google.com", 1234);
ASSERT_EQ("www.google.com", cli.host());
ASSERT_EQ(1234, cli.port());
}
TEST(HostAndPortPropertiesTest, NoSSLWithSimpleAPI) {
httplib::Client cli("www.google.com:1234");
ASSERT_EQ("www.google.com", cli.host());
ASSERT_EQ(1234, cli.port());
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(HostAndPortPropertiesTest, SSL) {
httplib::SSLClient cli("www.google.com");
ASSERT_EQ("www.google.com", cli.host());
ASSERT_EQ(443, cli.port());
}
#endif
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLClientTest, UpdateCAStore) {
httplib::SSLClient httplib_client("www.google.com");
@@ -4652,8 +4883,16 @@ TEST(SSLClientTest, UpdateCAStore) {
}
TEST(SSLClientTest, ServerNameIndication_Online) {
SSLClient cli("httpbin.org", 443);
auto res = cli.Get("/get");
#ifdef CPPHTTPLIB_DEFAULT_HTTPBIN
auto host = "httpbin.org";
auto path = std::string{"/get"};
#else
auto host = "nghttp2.org";
auto path = std::string{"/httpbin/get"};
#endif
SSLClient cli(host, 443);
auto res = cli.Get(path);
ASSERT_TRUE(res);
ASSERT_EQ(200, res->status);
}
@@ -4710,6 +4949,49 @@ TEST(SSLClientTest, ServerCertificateVerification4) {
ASSERT_EQ(200, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification5_Online) {
std::string cert;
detail::read_file(CA_CERT_FILE, cert);
SSLClient cli("google.com");
cli.load_ca_cert_store(cert.data(), cert.size());
const auto res = cli.Get("/");
ASSERT_TRUE(res);
ASSERT_EQ(301, res->status);
}
TEST(SSLClientTest, ServerCertificateVerification6_Online) {
// clang-format off
static constexpr char cert[] =
"GlobalSign Root CA\n"
"==================\n"
"-----BEGIN CERTIFICATE-----\n"
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx\n"
"GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds\n"
"b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV\n"
"BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD\n"
"VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa\n"
"DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc\n"
"THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb\n"
"Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP\n"
"c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX\n"
"gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF\n"
"AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj\n"
"Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG\n"
"j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH\n"
"hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC\n"
"X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"
"-----END CERTIFICATE-----\n";
// clang-format on
SSLClient cli("google.com");
cli.load_ca_cert_store(cert, sizeof(cert));
const auto res = cli.Get("/");
ASSERT_TRUE(res);
ASSERT_EQ(301, res->status);
}
TEST(SSLClientTest, WildcardHostNameMatch_Online) {
SSLClient cli("www.youtube.com");
@@ -5194,7 +5476,7 @@ TEST(YahooRedirectTest2, SimpleInterface_Online) {
res = cli.Get("/");
ASSERT_TRUE(res);
EXPECT_EQ(200, res->status);
EXPECT_EQ("https://yahoo.com/", res->location);
EXPECT_EQ("https://www.yahoo.com/", res->location);
}
TEST(YahooRedirectTest3, SimpleInterface_Online) {
@@ -5910,6 +6192,120 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
}
}
TEST(MultipartFormDataTest, AlternateFilename) {
auto handled = false;
Server svr;
svr.Post("/test", [&](const Request &req, Response &res) {
ASSERT_EQ(3u, req.files.size());
auto it = req.files.begin();
ASSERT_EQ("file1", it->second.name);
ASSERT_EQ("A.txt", it->second.filename);
ASSERT_EQ("text/plain", it->second.content_type);
ASSERT_EQ("Content of a.txt.\r\n", it->second.content);
++it;
ASSERT_EQ("file2", it->second.name);
ASSERT_EQ("a.html", it->second.filename);
ASSERT_EQ("text/html", it->second.content_type);
ASSERT_EQ("<!DOCTYPE html><title>Content of a.html.</title>\r\n",
it->second.content);
++it;
ASSERT_EQ("text", it->second.name);
ASSERT_EQ("", it->second.filename);
ASSERT_EQ("", it->second.content_type);
ASSERT_EQ("text default", it->second.content);
res.set_content("ok", "text/plain");
handled = true;
});
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
ASSERT_TRUE(handled);
});
svr.wait_until_ready();
auto req = "POST /test HTTP/1.1\r\n"
"Content-Type: multipart/form-data;boundary=--------\r\n"
"Content-Length: 399\r\n"
"\r\n"
"----------\r\n"
"Content-Disposition: form-data; name=\"text\"\r\n"
"\r\n"
"text default\r\n"
"----------\r\n"
"Content-Disposition: form-data; filename*=\"UTF-8''\%41.txt\"; "
"filename=\"a.txt\"; name=\"file1\"\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"Content of a.txt.\r\n"
"\r\n"
"----------\r\n"
"Content-Disposition: form-data; name=\"file2\" ;filename = "
"\"a.html\"\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<!DOCTYPE html><title>Content of a.html.</title>\r\n"
"\r\n"
"------------\r\n";
ASSERT_TRUE(send_request(1, req));
}
TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
auto handled = false;
Server svr;
svr.Post("/test", [&](const Request &req, Response &) {
ASSERT_EQ(2u, req.files.size());
auto it = req.files.begin();
ASSERT_EQ("text1", it->second.name);
ASSERT_EQ("text1", it->second.content);
++it;
ASSERT_EQ("text2", it->second.name);
ASSERT_EQ("text2", it->second.content);
handled = true;
});
thread t = thread([&] { svr.listen(HOST, PORT); });
auto se = detail::scope_exit([&] {
svr.stop();
t.join();
ASSERT_FALSE(svr.is_running());
ASSERT_TRUE(handled);
});
svr.wait_until_ready();
auto req = "POST /test HTTP/1.1\r\n"
"Content-Type: multipart/form-data;boundary=--------\r\n"
"Content-Length: 146\r\n"
"\r\n----------\r\n"
"Content-Disposition: form-data; name=\"text1\"\r\n"
"\r\n"
"text1"
"\r\n----------\r\n"
"Content-Disposition: form-data; name=\"text2\"\r\n"
"\r\n"
"text2"
"\r\n------------";
std::string resonse;
ASSERT_TRUE(send_request(1, req, &resonse));
ASSERT_EQ("200", resonse.substr(9, 3));
}
#endif
#ifndef _WIN32
@@ -6121,19 +6517,19 @@ TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
TEST(VulnerabilityTest, CRLFInjection) {
Server svr;
svr.Post("/test1", [](const Request &/*req*/, Response &res) {
svr.Post("/test1", [](const Request & /*req*/, Response &res) {
res.set_content("Hello 1", "text/plain");
});
svr.Delete("/test2", [](const Request &/*req*/, Response &res) {
svr.Delete("/test2", [](const Request & /*req*/, Response &res) {
res.set_content("Hello 2", "text/plain");
});
svr.Put("/test3", [](const Request &/*req*/, Response &res) {
svr.Put("/test3", [](const Request & /*req*/, Response &res) {
res.set_content("Hello 3", "text/plain");
});
svr.Patch("/test4", [](const Request &/*req*/, Response &res) {
svr.Patch("/test4", [](const Request & /*req*/, Response &res) {
res.set_content("Hello 4", "text/plain");
});
@@ -6163,3 +6559,140 @@ TEST(VulnerabilityTest, CRLFInjection) {
cli.Patch("/test4", "content", "text/plain\r\nevil: hello4");
}
}
TEST(PathParamsTest, StaticMatch) {
const auto pattern = "/users/all";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/all";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, StaticMismatch) {
const auto pattern = "/users/all";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/1";
ASSERT_FALSE(matcher.match(request));
}
TEST(PathParamsTest, SingleParamInTheMiddle) {
const auto pattern = "/users/:id/subscriptions";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/42/subscriptions";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, SingleParamInTheEnd) {
const auto pattern = "/users/:id";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/24";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {{"id", "24"}};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, SingleParamInTheEndTrailingSlash) {
const auto pattern = "/users/:id/";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/42/";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {{"id", "42"}};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, EmptyParam) {
const auto pattern = "/users/:id/";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users//";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {{"id", ""}};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, FragmentMismatch) {
const auto pattern = "/users/:id/";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/admins/24/";
ASSERT_FALSE(matcher.match(request));
}
TEST(PathParamsTest, ExtraFragments) {
const auto pattern = "/users/:id";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/42/subscriptions";
ASSERT_FALSE(matcher.match(request));
}
TEST(PathParamsTest, MissingTrailingParam) {
const auto pattern = "/users/:id";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users";
ASSERT_FALSE(matcher.match(request));
}
TEST(PathParamsTest, MissingParamInTheMiddle) {
const auto pattern = "/users/:id/subscriptions";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/subscriptions";
ASSERT_FALSE(matcher.match(request));
}
TEST(PathParamsTest, MultipleParams) {
const auto pattern = "/users/:userid/subscriptions/:subid";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/users/42/subscriptions/2";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {
{"userid", "42"}, {"subid", "2"}};
EXPECT_EQ(request.path_params, expected_params);
}
TEST(PathParamsTest, SequenceOfParams) {
const auto pattern = "/values/:x/:y/:z";
detail::PathParamsMatcher matcher(pattern);
Request request;
request.path = "/values/1/2/3";
ASSERT_TRUE(matcher.match(request));
std::unordered_map<std::string, std::string> expected_params = {
{"x", "1"}, {"y", "2"}, {"z", "3"}};
EXPECT_EQ(request.path_params, expected_params);
}
+8192
View File
File diff suppressed because it is too large Load Diff