mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3779800322 | |||
| 986a20fb7d | |||
| 8311e1105f | |||
| ba6845925d | |||
| 343a0fc073 | |||
| 54f8a4d0f3 | |||
| 9c36aae4b7 | |||
| b766025a83 | |||
| 9b5f76f833 | |||
| d647f484a4 | |||
| 8794792baa | |||
| b85768c1f3 | |||
| e6d71bd702 | |||
| 258992a160 | |||
| a7bc00e330 | |||
| 11a40584e9 | |||
| 3e86bdb4d8 | |||
| c817d65695 | |||
| 51dee793fe | |||
| 457fc4306e | |||
| 4f5b003e76 | |||
| 5421e27106 | |||
| fe07660f40 | |||
| da2f9e476e | |||
| 1a7a7ed1c3 | |||
| 413994912d | |||
| 01dcf1d0ad | |||
| 8e378779c2 | |||
| 970b52897c | |||
| 412ba04d19 | |||
| bfef4b3e9b | |||
| 7bd316f3d0 | |||
| 26208363ee | |||
| b1b4bb8850 | |||
| 9dd565b6e3 | |||
| 924f214303 |
+32
-10
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: install libraries
|
||||
run: sudo apt-get update && sudo apt-get install -y libbrotli-dev libcurl4-openssl-dev
|
||||
- name: build and run tests
|
||||
run: cd test && make -j4
|
||||
run: cd test && make
|
||||
- name: run fuzz test target
|
||||
run: cd test && make fuzz_test
|
||||
|
||||
@@ -21,23 +21,45 @@ jobs:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: build and run tests
|
||||
run: |
|
||||
cd test && make -j2
|
||||
run: cd test && make
|
||||
- name: run fuzz test target
|
||||
run: cd test && make fuzz_test
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: prepare git for checkout on windows
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: setup msbuild on windows
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Setup msbuild on windows
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: make-windows
|
||||
- name: Install libraries
|
||||
run: |
|
||||
cd test
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
|
||||
x64\Release\test.exe
|
||||
vcpkg install gtest curl zlib brotli
|
||||
choco install openssl
|
||||
|
||||
- name: Configure CMake with SSL
|
||||
run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build with with SSL
|
||||
run: cmake --build build --config Release
|
||||
- name: Run tests with SSL
|
||||
run: ctest --output-on-failure --test-dir build -C Release
|
||||
|
||||
- name: Configure CMake without SSL
|
||||
run: cmake -B build-no-ssl -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake -DHTTPLIB_TEST=ON -DHTTPLIB_REQUIRE_OPENSSL=ON -DHTTPLIB_REQUIRE_ZLIB=ON -DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
- name: Build without SSL
|
||||
run: cmake --build build-no-ssl --config Release
|
||||
- name: Run tests without SSL
|
||||
run: ctest --output-on-failure --test-dir build-no-ssl -C Release
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
@@ -79,7 +79,7 @@ cli.set_ca_cert_path("./ca-bundle.crt");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
// Disable host verification
|
||||
cli.enable_server_host_verification(false);
|
||||
cli.enable_server_hostname_verification(false);
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
@@ -125,6 +125,21 @@ int main(void)
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
|
||||
// If the handler takes time to finish, you can also poll the connection state
|
||||
svr.Get("/task", [&](const Request& req, Response& res) {
|
||||
const char * result = nullptr;
|
||||
process.run(); // for example, starting an external process
|
||||
while (result == nullptr) {
|
||||
sleep(1);
|
||||
if (req.is_connection_closed()) {
|
||||
process.kill(); // kill the process
|
||||
return;
|
||||
}
|
||||
result = process.stdout(); // != nullptr if the process finishes
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/stop", [&](const Request& req, Response& res) {
|
||||
svr.stop();
|
||||
});
|
||||
@@ -557,18 +572,18 @@ enum Error {
|
||||
|
||||
```c++
|
||||
httplib::Headers headers = {
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
{ "Hello", "World!" }
|
||||
};
|
||||
auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
or
|
||||
```c++
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
auto res = cli.Get("/hi", {{"Hello", "World!"}});
|
||||
```
|
||||
or
|
||||
```c++
|
||||
cli.set_default_headers({
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
{ "Hello", "World!" }
|
||||
});
|
||||
auto res = cli.Get("/hi");
|
||||
```
|
||||
@@ -823,6 +838,21 @@ The server can apply compression to the following MIME type contents:
|
||||
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
|
||||
Please see https://github.com/google/brotli for more detail.
|
||||
|
||||
### Default `Accept-Encoding` value
|
||||
|
||||
The default `Acdcept-Encoding` value contains all possible compression types. So, the following two examples are same.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo");
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
```
|
||||
|
||||
If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl.
|
||||
|
||||
```c++
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", ""}});
|
||||
```
|
||||
|
||||
### Compress request body on client
|
||||
|
||||
```c++
|
||||
@@ -834,8 +864,9 @@ res = cli.Post("/resource/foo", "...", "text/plain");
|
||||
|
||||
```c++
|
||||
cli.set_decompress(false);
|
||||
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
|
||||
res = cli.Get("/resource/foo");
|
||||
res->body; // Compressed data
|
||||
|
||||
```
|
||||
|
||||
Use `poll` instead of `select`
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ 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 issue
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark one_time_request server_and_client
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
@@ -22,34 +22,34 @@
|
||||
<ProjectGuid>{6DB1FC63-B153-4279-92B7-D8A11AF285D6}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>client</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
|
||||
@@ -18,38 +18,41 @@
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="simplesvr.cc" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{864CD288-050A-4C8B-9BEF-3048BD876C5B}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>sample</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
@@ -151,9 +154,6 @@
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="server.cc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.18.1"
|
||||
#define CPPHTTPLIB_VERSION "0.18.5"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -612,6 +612,7 @@ using Ranges = std::vector<Range>;
|
||||
struct Request {
|
||||
std::string method;
|
||||
std::string path;
|
||||
Params params;
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
@@ -623,11 +624,11 @@ struct Request {
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
Params params;
|
||||
MultipartFormDataMap files;
|
||||
Ranges ranges;
|
||||
Match matches;
|
||||
std::unordered_map<std::string, std::string> path_params;
|
||||
std::function<bool()> is_connection_closed = []() { return true; };
|
||||
|
||||
// for client
|
||||
ResponseHandler response_handler;
|
||||
@@ -1582,6 +1583,9 @@ private:
|
||||
bool send_(Request &req, Response &res, Error &error);
|
||||
Result send_(Request &&req);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
bool is_ssl_peer_could_be_closed(SSL *ssl) const;
|
||||
#endif
|
||||
socket_t create_client_socket(Error &error) const;
|
||||
bool read_response_line(Stream &strm, const Request &req,
|
||||
Response &res) const;
|
||||
@@ -2009,18 +2013,34 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
|
||||
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
|
||||
}
|
||||
|
||||
inline bool is_numeric(const std::string &str) {
|
||||
return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit);
|
||||
}
|
||||
|
||||
inline uint64_t get_header_value_u64(const Headers &headers,
|
||||
const std::string &key, uint64_t def,
|
||||
size_t id) {
|
||||
size_t id, bool &is_invalid_value) {
|
||||
is_invalid_value = false;
|
||||
auto rng = headers.equal_range(key);
|
||||
auto it = rng.first;
|
||||
std::advance(it, static_cast<ssize_t>(id));
|
||||
if (it != rng.second) {
|
||||
return std::strtoull(it->second.data(), nullptr, 10);
|
||||
if (is_numeric(it->second)) {
|
||||
return std::strtoull(it->second.data(), nullptr, 10);
|
||||
} else {
|
||||
is_invalid_value = true;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
inline uint64_t get_header_value_u64(const Headers &headers,
|
||||
const std::string &key, uint64_t def,
|
||||
size_t id) {
|
||||
bool dummy = false;
|
||||
return get_header_value_u64(headers, key, def, id, dummy);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline uint64_t Request::get_header_value_u64(const std::string &key,
|
||||
@@ -2038,8 +2058,6 @@ inline void default_socket_options(socket_t sock) {
|
||||
#ifdef _WIN32
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
|
||||
reinterpret_cast<const char *>(&opt), sizeof(opt));
|
||||
#else
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
|
||||
@@ -2258,13 +2276,33 @@ make_basic_authentication_header(const std::string &username,
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if defined(_WIN32)
|
||||
inline std::wstring u8string_to_wstring(const char *s) {
|
||||
std::wstring ws;
|
||||
auto len = static_cast<int>(strlen(s));
|
||||
auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
|
||||
if (wlen > 0) {
|
||||
ws.resize(wlen);
|
||||
wlen = ::MultiByteToWideChar(
|
||||
CP_UTF8, 0, s, len,
|
||||
const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
|
||||
if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
|
||||
}
|
||||
return ws;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct FileStat {
|
||||
FileStat(const std::string &path);
|
||||
bool is_file() const;
|
||||
bool is_dir() const;
|
||||
|
||||
private:
|
||||
#if defined(_WIN32)
|
||||
struct _stat st_;
|
||||
#else
|
||||
struct stat st_;
|
||||
#endif
|
||||
int ret_ = -1;
|
||||
};
|
||||
|
||||
@@ -2483,6 +2521,60 @@ private:
|
||||
bool is_open_empty_file = false;
|
||||
};
|
||||
|
||||
// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
|
||||
namespace fields {
|
||||
|
||||
inline bool is_token_char(char c) {
|
||||
return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
|
||||
c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
|
||||
c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
|
||||
}
|
||||
|
||||
inline bool is_token(const std::string &s) {
|
||||
if (s.empty()) { return false; }
|
||||
for (auto c : s) {
|
||||
if (!is_token_char(c)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool is_field_name(const std::string &s) { return is_token(s); }
|
||||
|
||||
inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
|
||||
|
||||
inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
|
||||
|
||||
inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
|
||||
|
||||
inline bool is_field_content(const std::string &s) {
|
||||
if (s.empty()) { return false; }
|
||||
|
||||
if (s.size() == 1) {
|
||||
return is_field_vchar(s[0]);
|
||||
} else if (s.size() == 2) {
|
||||
return is_field_vchar(s[0]) && is_field_vchar(s[1]);
|
||||
} else {
|
||||
size_t i = 0;
|
||||
|
||||
if (!is_field_vchar(s[i])) { return false; }
|
||||
i++;
|
||||
|
||||
while (i < s.size() - 1) {
|
||||
auto c = s[i++];
|
||||
if (c == ' ' || c == '\t' || is_field_vchar(c)) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return is_field_vchar(s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_field_value(const std::string &s) { return is_field_content(s); }
|
||||
|
||||
} // namespace fields
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -2639,7 +2731,12 @@ inline bool is_valid_path(const std::string &path) {
|
||||
}
|
||||
|
||||
inline FileStat::FileStat(const std::string &path) {
|
||||
#if defined(_WIN32)
|
||||
auto wpath = u8string_to_wstring(path.c_str());
|
||||
ret_ = _wstat(wpath.c_str(), &st_);
|
||||
#else
|
||||
ret_ = stat(path.c_str(), &st_);
|
||||
#endif
|
||||
}
|
||||
inline bool FileStat::is_file() const {
|
||||
return ret_ >= 0 && S_ISREG(st_.st_mode);
|
||||
@@ -2909,10 +3006,8 @@ inline bool mmap::open(const char *path) {
|
||||
close();
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::wstring wpath;
|
||||
for (size_t i = 0; i < strlen(path); i++) {
|
||||
wpath += path[i];
|
||||
}
|
||||
auto wpath = u8string_to_wstring(path);
|
||||
if (wpath.empty()) { return false; }
|
||||
|
||||
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
|
||||
hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
|
||||
@@ -3282,8 +3377,6 @@ inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
|
||||
} else {
|
||||
return true; // Ready for read
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(microseconds{interval_usec});
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -4362,8 +4455,14 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
} else if (!has_header(x.headers, "Content-Length")) {
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0);
|
||||
if (len > payload_max_length) {
|
||||
auto is_invalid_value = false;
|
||||
auto len = get_header_value_u64(x.headers, "Content-Length",
|
||||
std::numeric_limits<uint64_t>::max(),
|
||||
0, is_invalid_value);
|
||||
|
||||
if (is_invalid_value) {
|
||||
ret = false;
|
||||
} else if (len > payload_max_length) {
|
||||
exceed_payload_max_length = true;
|
||||
skip_content_with_length(strm, len);
|
||||
ret = false;
|
||||
@@ -5132,7 +5231,18 @@ inline bool range_error(Request &req, Response &res) {
|
||||
last_pos = contant_len - 1;
|
||||
}
|
||||
|
||||
if (last_pos == -1) { last_pos = contant_len - 1; }
|
||||
// NOTE: RFC-9110 '14.1.2. Byte Ranges':
|
||||
// A client can limit the number of bytes requested without knowing the
|
||||
// size of the selected representation. If the last-pos value is absent,
|
||||
// or if the value is greater than or equal to the current length of the
|
||||
// representation data, the byte range is interpreted as the remainder of
|
||||
// the representation (i.e., the server replaces the value of last-pos
|
||||
// with a value that is one less than the current length of the selected
|
||||
// representation).
|
||||
// https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
|
||||
if (last_pos == -1 || last_pos >= contant_len) {
|
||||
last_pos = contant_len - 1;
|
||||
}
|
||||
|
||||
// Range must be within content length
|
||||
if (!(0 <= first_pos && first_pos <= last_pos &&
|
||||
@@ -5664,7 +5774,8 @@ inline size_t Request::get_header_value_count(const std::string &key) const {
|
||||
|
||||
inline void Request::set_header(const std::string &key,
|
||||
const std::string &val) {
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
|
||||
if (detail::fields::is_field_name(key) &&
|
||||
detail::fields::is_field_value(val)) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
@@ -5730,13 +5841,14 @@ inline size_t Response::get_header_value_count(const std::string &key) const {
|
||||
|
||||
inline void Response::set_header(const std::string &key,
|
||||
const std::string &val) {
|
||||
if (!detail::has_crlf(key) && !detail::has_crlf(val)) {
|
||||
if (detail::fields::is_field_name(key) &&
|
||||
detail::fields::is_field_value(val)) {
|
||||
headers.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_redirect(const std::string &url, int stat) {
|
||||
if (!detail::has_crlf(url)) {
|
||||
if (detail::fields::is_field_value(url)) {
|
||||
set_header("Location", url);
|
||||
if (300 <= stat && stat < 400) {
|
||||
this->status = stat;
|
||||
@@ -7106,6 +7218,11 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup `is_connection_closed` method
|
||||
req.is_connection_closed = [&]() {
|
||||
return !detail::is_socket_alive(strm.socket());
|
||||
};
|
||||
|
||||
// Routing
|
||||
auto routed = false;
|
||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
@@ -7148,14 +7265,6 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
: StatusCode::PartialContent_206;
|
||||
}
|
||||
|
||||
if (detail::range_error(req, res)) {
|
||||
res.body.clear();
|
||||
res.content_length_ = 0;
|
||||
res.content_provider_ = nullptr;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
// Serve file content by using a content provider
|
||||
if (!res.file_content_path_.empty()) {
|
||||
const auto &path = res.file_content_path_;
|
||||
@@ -7182,6 +7291,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
|
||||
});
|
||||
}
|
||||
|
||||
if (detail::range_error(req, res)) {
|
||||
res.body.clear();
|
||||
res.content_length_ = 0;
|
||||
res.content_provider_ = nullptr;
|
||||
res.status = StatusCode::RangeNotSatisfiable_416;
|
||||
return write_response(strm, close_connection, req, res);
|
||||
}
|
||||
|
||||
return write_response_with_content(strm, close_connection, req, res);
|
||||
} else {
|
||||
if (res.status == -1) { res.status = StatusCode::NotFound_404; }
|
||||
@@ -7392,6 +7509,18 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
inline bool ClientImpl::is_ssl_peer_could_be_closed(SSL *ssl) const {
|
||||
detail::set_nonblocking(socket_.sock, true);
|
||||
auto se = detail::scope_exit(
|
||||
[&]() { detail::set_nonblocking(socket_.sock, false); });
|
||||
|
||||
char buf[1];
|
||||
return !SSL_peek(ssl, buf, 1) &&
|
||||
SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
@@ -7403,6 +7532,13 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
auto is_alive = false;
|
||||
if (socket_.is_open()) {
|
||||
is_alive = detail::is_socket_alive(socket_.sock);
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_alive && is_ssl()) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) { is_alive = false; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!is_alive) {
|
||||
// Attempt to avoid sigpipe by shutting down nongracefully if it seems
|
||||
// like the other side has already closed the connection Also, there
|
||||
@@ -7679,12 +7815,26 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
|
||||
if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); }
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.set_header("User-Agent", agent);
|
||||
}
|
||||
if (!req.content_receiver) {
|
||||
if (!req.has_header("Accept-Encoding")) {
|
||||
std::string accept_encoding;
|
||||
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
|
||||
accept_encoding = "br";
|
||||
#endif
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (!accept_encoding.empty()) { accept_encoding += ", "; }
|
||||
accept_encoding += "gzip, deflate";
|
||||
#endif
|
||||
req.set_header("Accept-Encoding", accept_encoding);
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
|
||||
if (!req.has_header("User-Agent")) {
|
||||
auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
|
||||
req.set_header("User-Agent", agent);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
if (req.body.empty()) {
|
||||
if (req.content_provider_) {
|
||||
@@ -7744,7 +7894,13 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
{
|
||||
detail::BufferStream bstrm;
|
||||
|
||||
const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path;
|
||||
const auto &path_with_query =
|
||||
req.params.empty() ? req.path
|
||||
: append_query_params(req.path, req.params);
|
||||
|
||||
const auto &path =
|
||||
url_encode_ ? detail::encode_url(path_with_query) : path_with_query;
|
||||
|
||||
detail::write_request_line(bstrm, req.method, path);
|
||||
|
||||
header_writer_(bstrm, req.headers);
|
||||
@@ -7885,9 +8041,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
if (is_ssl()) {
|
||||
auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
|
||||
if (!is_proxy_enabled) {
|
||||
char buf[1];
|
||||
if (SSL_peek(socket_.ssl, buf, 1) == 0 &&
|
||||
SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) {
|
||||
if (is_ssl_peer_could_be_closed(socket_.ssl)) {
|
||||
error = Error::SSLPeerCouldBeClosed_;
|
||||
return false;
|
||||
}
|
||||
@@ -7905,7 +8059,9 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// Body
|
||||
if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
|
||||
req.method != "CONNECT") {
|
||||
auto redirect = 300 < res.status && res.status < 400 && follow_location_;
|
||||
auto redirect = 300 < res.status && res.status < 400 &&
|
||||
res.status != StatusCode::NotModified_304 &&
|
||||
follow_location_;
|
||||
|
||||
if (req.response_handler && !redirect) {
|
||||
if (!req.response_handler(res)) {
|
||||
@@ -7926,9 +8082,7 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
: static_cast<ContentReceiverWithProgress>(
|
||||
[&](const char *buf, size_t n, uint64_t /*off*/,
|
||||
uint64_t /*len*/) {
|
||||
if (res.body.size() + n > res.body.max_size()) {
|
||||
return false;
|
||||
}
|
||||
assert(res.body.size() + n <= res.body.max_size());
|
||||
res.body.append(buf, n);
|
||||
return true;
|
||||
});
|
||||
@@ -7942,18 +8096,23 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
|
||||
if (res.has_header("Content-Length")) {
|
||||
if (!req.content_receiver) {
|
||||
auto len = std::min<size_t>(res.get_header_value_u64("Content-Length"),
|
||||
res.body.max_size());
|
||||
if (len > 0) { res.body.reserve(len); }
|
||||
auto len = res.get_header_value_u64("Content-Length");
|
||||
if (len > res.body.max_size()) {
|
||||
error = Error::Read;
|
||||
return false;
|
||||
}
|
||||
res.body.reserve(static_cast<size_t>(len));
|
||||
}
|
||||
}
|
||||
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress), std::move(out),
|
||||
decompress_)) {
|
||||
if (error != Error::Canceled) { error = Error::Read; }
|
||||
return false;
|
||||
if (res.status != StatusCode::NotModified_304) {
|
||||
int dummy_status;
|
||||
if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
|
||||
dummy_status, std::move(progress),
|
||||
std::move(out), decompress_)) {
|
||||
if (error != Error::Canceled) { error = Error::Read; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8842,6 +9001,7 @@ inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
|
||||
// best-efforts.
|
||||
if (shutdown_gracefully) {
|
||||
#ifdef _WIN32
|
||||
(void)(sock);
|
||||
SSL_shutdown(ssl);
|
||||
#else
|
||||
timeval tv;
|
||||
|
||||
+341
-60
@@ -1,3 +1,4 @@
|
||||
// NOTE: This file should be saved as UTF-8 w/ BOM
|
||||
#include <httplib.h>
|
||||
#include <signal.h>
|
||||
|
||||
@@ -241,7 +242,7 @@ TEST(DecodeURLTest, PercentCharacter) {
|
||||
detail::decode_url(
|
||||
R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)",
|
||||
false),
|
||||
R"(descrip=Gastos áéíóúñÑ 6)");
|
||||
u8"descrip=Gastos áéíóúñÑ 6");
|
||||
}
|
||||
|
||||
TEST(DecodeURLTest, PercentCharacterNUL) {
|
||||
@@ -267,9 +268,9 @@ TEST(EncodeQueryParamTest, ParseReservedCharactersTest) {
|
||||
}
|
||||
|
||||
TEST(EncodeQueryParamTest, TestUTF8Characters) {
|
||||
string chineseCharacters = "中国語";
|
||||
string russianCharacters = "дом";
|
||||
string brazilianCharacters = "óculos";
|
||||
string chineseCharacters = u8"中国語";
|
||||
string russianCharacters = u8"дом";
|
||||
string brazilianCharacters = u8"óculos";
|
||||
|
||||
EXPECT_EQ(detail::encode_query_param(chineseCharacters),
|
||||
"%E4%B8%AD%E5%9B%BD%E8%AA%9E");
|
||||
@@ -510,6 +511,15 @@ TEST(GetHeaderValueTest, RegularValueInt) {
|
||||
EXPECT_EQ(100ull, val);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, RegularInvalidValueInt) {
|
||||
Headers headers = {{"Content-Length", "x"}};
|
||||
auto is_invalid_value = false;
|
||||
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0,
|
||||
is_invalid_value);
|
||||
EXPECT_EQ(0ull, val);
|
||||
EXPECT_TRUE(is_invalid_value);
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, Range) {
|
||||
{
|
||||
Headers headers = {make_range_header({{1, -1}})};
|
||||
@@ -2003,7 +2013,7 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2013,7 +2023,46 @@ TEST(ErrorHandlerTest, ContentLength) {
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionHandlerTest, ContentLength) {
|
||||
TEST(ExceptionTest, WithoutExceptionHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
listen_thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, WithExceptionHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_exception_handler([](const Request & /*req*/, Response &res,
|
||||
@@ -2021,7 +2070,9 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
EXPECT_FALSE(ep == nullptr);
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) { EXPECT_EQ("abc", std::string(e.what())); }
|
||||
} catch (std::exception &e) {
|
||||
EXPECT_EQ("abc", std::string(e.what()));
|
||||
} catch (...) {}
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
res.set_content("abcdefghijklmnopqrstuvwxyz",
|
||||
"text/html"); // <= Content-Length still 13 at this point
|
||||
@@ -2045,7 +2096,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2056,7 +2107,7 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
for (size_t j = 0; j < 100; j++) {
|
||||
auto res = cli.Get("/hi");
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
@@ -2065,6 +2116,66 @@ TEST(ExceptionHandlerTest, ContentLength) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ExceptionTest, AndErrorHandler) {
|
||||
Server svr;
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
if (res.body.empty()) { res.set_content("NOT_FOUND", "text/html"); }
|
||||
});
|
||||
|
||||
svr.set_exception_handler(
|
||||
[](const Request & /*req*/, Response &res, std::exception_ptr ep) {
|
||||
EXPECT_FALSE(ep == nullptr);
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) {
|
||||
res.set_content(e.what(), "text/html");
|
||||
} catch (...) {}
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
});
|
||||
|
||||
svr.Get("/exception", [](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("EXCEPTION");
|
||||
});
|
||||
|
||||
svr.Get("/error", [](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ERROR", "text/html");
|
||||
res.status = StatusCode::InternalServerError_500;
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("EXCEPTION", res->body);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/error");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
EXPECT_EQ("ERROR", res->body);
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/invalid");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::NotFound_404, res->status);
|
||||
EXPECT_EQ("NOT_FOUND", res->body);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(NoContentTest, ContentLength) {
|
||||
@@ -2934,6 +3045,16 @@ TEST_F(ServerTest, GetFileContent) {
|
||||
EXPECT_EQ("test.html", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetFileContentWithRange) {
|
||||
auto res = cli_.Get("/file_content", {{make_range_header({{1, 3}})}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("text/html", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("bytes 1-3/9", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(3, std::stoi(res->get_header_value("Content-Length")));
|
||||
EXPECT_EQ("est", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetFileContentWithContentType) {
|
||||
auto res = cli_.Get("/file_content_with_content_type");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -3692,16 +3813,21 @@ TEST_F(ServerTest, GetStreamedWithRangeError) {
|
||||
TEST_F(ServerTest, GetRangeWithMaxLongLength) {
|
||||
auto res = cli_.Get(
|
||||
"/with-range",
|
||||
{{"Range",
|
||||
"bytes=0-" + std::to_string(std::numeric_limits<long>::max())}});
|
||||
EXPECT_EQ(StatusCode::RangeNotSatisfiable_416, res->status);
|
||||
EXPECT_EQ("0", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||
EXPECT_EQ(0U, res->body.size());
|
||||
{{"Range", "bytes=0-" + std::to_string(std::numeric_limits<long>::max())},
|
||||
{"Accept-Encoding", ""}});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||
EXPECT_EQ("bytes 0-6/7", res->get_header_value("Content-Range"));
|
||||
EXPECT_EQ(std::string("abcdefg"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetRangeWithZeroToInfinite) {
|
||||
auto res = cli_.Get("/with-range", {{"Range", "bytes=0-"}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
{"Range", "bytes=0-"},
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("7", res->get_header_value("Content-Length"));
|
||||
@@ -3797,7 +3923,10 @@ TEST_F(ServerTest, ClientStop) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange1) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{3, 5}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("3", res->get_header_value("Content-Length"));
|
||||
@@ -3807,7 +3936,10 @@ TEST_F(ServerTest, GetWithRange1) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange2) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{1, -1}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{1, -1}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("6", res->get_header_value("Content-Length"));
|
||||
@@ -3817,7 +3949,10 @@ TEST_F(ServerTest, GetWithRange2) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange3) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{0, 0}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{0, 0}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("1", res->get_header_value("Content-Length"));
|
||||
@@ -3827,7 +3962,10 @@ TEST_F(ServerTest, GetWithRange3) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange4) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{-1, 2}})}});
|
||||
auto res = cli_.Get("/with-range", {
|
||||
make_range_header({{-1, 2}}),
|
||||
{"Accept-Encoding", ""},
|
||||
});
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::PartialContent_206, res->status);
|
||||
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||
@@ -4141,7 +4279,10 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(LARGE_DATA, res->body);
|
||||
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
|
||||
// The compressed size should be less than a 10th of the original. May vary
|
||||
// depending on the zlib library.
|
||||
EXPECT_LT(res.get_request_header_value_u64("Content-Length"),
|
||||
static_cast<uint64_t>(10 * 1024 * 1024));
|
||||
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
|
||||
}
|
||||
|
||||
@@ -4572,7 +4713,9 @@ TEST_F(ServerTest, Gzip) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
auto res = cli_.Get("/compress");
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "");
|
||||
auto res = cli_.Get("/compress", headers);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
@@ -4621,12 +4764,16 @@ TEST_F(ServerTest, GzipWithoutDecompressing) {
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "");
|
||||
|
||||
std::string body;
|
||||
auto res = cli_.Get("/compress", [&](const char *data, uint64_t data_length) {
|
||||
EXPECT_EQ(100U, data_length);
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
auto res = cli_.Get("/compress", headers,
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
EXPECT_EQ(100U, data_length);
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
@@ -5170,18 +5317,9 @@ TEST(MountTest, Redicect) {
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
|
||||
TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
TEST(MountTest, MultibytesPathName) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/exception", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception...");
|
||||
});
|
||||
|
||||
svr.Get("/unknown", [&](const Request & /*req*/, Response & /*res*/) {
|
||||
throw std::runtime_error("exception\r\n...");
|
||||
});
|
||||
|
||||
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
@@ -5189,27 +5327,16 @@ TEST(ExceptionTest, ThrowExceptionInHandler) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.set_mount_point("/", "./www");
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", PORT);
|
||||
|
||||
{
|
||||
auto res = cli.Get("/exception");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
|
||||
{
|
||||
auto res = cli.Get("/unknown");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::InternalServerError_500, res->status);
|
||||
ASSERT_TRUE(res->has_header("EXCEPTION_WHAT"));
|
||||
EXPECT_EQ("exception\\r\\n...", res->get_header_value("EXCEPTION_WHAT"));
|
||||
}
|
||||
auto res = cli.Get(u8"/日本語Dir/日本語File.txt");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(StatusCode::OK_200, res->status);
|
||||
EXPECT_EQ(u8"日本語コンテンツ", res->body);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(KeepAliveTest, ReadTimeout) {
|
||||
Server svr;
|
||||
@@ -5506,6 +5633,7 @@ TEST(LongPollingTest, ClientCloseDetection) {
|
||||
auto count = 10;
|
||||
while (count > 0 && sink.is_writable()) {
|
||||
this_thread::sleep_for(chrono::milliseconds(10));
|
||||
count--;
|
||||
}
|
||||
EXPECT_FALSE(sink.is_writable()); // the socket is closed
|
||||
return true;
|
||||
@@ -6025,6 +6153,18 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
|
||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, Issue2004) {
|
||||
Client client("https://google.com");
|
||||
client.set_follow_location(true);
|
||||
|
||||
auto res = client.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(StatusCode::OK_200, res->status);
|
||||
|
||||
auto body = res->body;
|
||||
EXPECT_EQ(body.substr(0, 15), "<!doctype html>");
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(SSLClientTest, SetInterfaceWithINET6) {
|
||||
auto cli = std::make_shared<httplib::Client>("https://httpbin.org");
|
||||
@@ -6436,6 +6576,40 @@ TEST(SendAPI, SimpleInterface_Online) {
|
||||
EXPECT_EQ(StatusCode::MovedPermanently_301, res->status);
|
||||
}
|
||||
|
||||
TEST(SendAPI, WithParamsInRequest) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_TRUE(req.has_param("test"));
|
||||
EXPECT_EQ("test_value", req.get_param_value("test"));
|
||||
});
|
||||
|
||||
auto t = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
{
|
||||
Request req;
|
||||
req.method = "GET";
|
||||
req.path = "/";
|
||||
req.params.emplace("test", "test_value");
|
||||
auto res = cli.send(req);
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
{
|
||||
auto res = cli.Get("/", {{"test", "test_value"}}, Headers{});
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ClientImplMethods, GetSocketTest) {
|
||||
httplib::Server svr;
|
||||
svr.Get("/", [&](const httplib::Request & /*req*/, httplib::Response &res) {
|
||||
@@ -7331,9 +7505,9 @@ TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
|
||||
"text2"
|
||||
"\r\n------------";
|
||||
|
||||
std::string resonse;
|
||||
ASSERT_TRUE(send_request(1, req, &resonse));
|
||||
ASSERT_EQ("200", resonse.substr(9, 3));
|
||||
std::string response;
|
||||
ASSERT_TRUE(send_request(1, req, &response));
|
||||
ASSERT_EQ("200", response.substr(9, 3));
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, ContentLength) {
|
||||
@@ -7378,11 +7552,10 @@ TEST(MultipartFormDataTest, ContentLength) {
|
||||
"text2"
|
||||
"\r\n------------\r\n";
|
||||
|
||||
std::string resonse;
|
||||
ASSERT_TRUE(send_request(1, req, &resonse));
|
||||
ASSERT_EQ("200", resonse.substr(9, 3));
|
||||
std::string response;
|
||||
ASSERT_TRUE(send_request(1, req, &response));
|
||||
ASSERT_EQ("200", response.substr(9, 3));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
@@ -7760,6 +7933,114 @@ TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
|
||||
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
|
||||
}
|
||||
|
||||
TEST(InvalidHeaderCharsTest, is_field_name) {
|
||||
EXPECT_TRUE(detail::fields::is_field_name("exampleToken"));
|
||||
EXPECT_TRUE(detail::fields::is_field_name("token123"));
|
||||
EXPECT_TRUE(detail::fields::is_field_name("!#$%&'*+-.^_`|~"));
|
||||
|
||||
EXPECT_FALSE(detail::fields::is_field_name("example token"));
|
||||
EXPECT_FALSE(detail::fields::is_field_name(" example_token"));
|
||||
EXPECT_FALSE(detail::fields::is_field_name("example_token "));
|
||||
EXPECT_FALSE(detail::fields::is_field_name("token@123"));
|
||||
EXPECT_FALSE(detail::fields::is_field_name(""));
|
||||
EXPECT_FALSE(detail::fields::is_field_name("example\rtoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_name("example\ntoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_name(std::string("\0", 1)));
|
||||
EXPECT_FALSE(detail::fields::is_field_name("example\ttoken"));
|
||||
}
|
||||
|
||||
TEST(InvalidHeaderCharsTest, is_field_value) {
|
||||
EXPECT_TRUE(detail::fields::is_field_value("exampleToken"));
|
||||
EXPECT_TRUE(detail::fields::is_field_value("token123"));
|
||||
EXPECT_TRUE(detail::fields::is_field_value("!#$%&'*+-.^_`|~"));
|
||||
|
||||
EXPECT_TRUE(detail::fields::is_field_value("example token"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value(" example_token"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example_token "));
|
||||
EXPECT_TRUE(detail::fields::is_field_value("token@123"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value(""));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example\rtoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value("example\ntoken"));
|
||||
EXPECT_FALSE(detail::fields::is_field_value(std::string("\0", 1)));
|
||||
EXPECT_TRUE(detail::fields::is_field_value("example\ttoken"));
|
||||
|
||||
EXPECT_TRUE(detail::fields::is_field_value("0"));
|
||||
}
|
||||
|
||||
TEST(InvalidHeaderCharsTest, OnServer) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/test_name", [&](const Request &req, Response &res) {
|
||||
std::string header = "Not Set";
|
||||
if (req.has_param("header")) { header = req.get_param_value("header"); }
|
||||
|
||||
res.set_header(header, "value");
|
||||
res.set_content("Page Content Page Content", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/test_value", [&](const Request &req, Response &res) {
|
||||
std::string header = "Not Set";
|
||||
if (req.has_param("header")) { header = req.get_param_value("header"); }
|
||||
|
||||
res.set_header("X-Test", header);
|
||||
res.set_content("Page Content Page Content", "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
{
|
||||
auto res = cli.Get(
|
||||
R"(/test_name?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("Page Content Page Content", res->body);
|
||||
EXPECT_FALSE(res->has_header("HEADER_KEY"));
|
||||
}
|
||||
{
|
||||
auto res = cli.Get(
|
||||
R"(/test_value?header=Value%00%0d%0aHEADER_KEY%3aHEADER_VALUE%0d%0a%0d%0aBODY_BODY_BODY)");
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ("Page Content Page Content", res->body);
|
||||
EXPECT_FALSE(res->has_header("HEADER_KEY"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(InvalidHeaderValueTest, InvalidContentLength) {
|
||||
auto handled = false;
|
||||
|
||||
Server svr;
|
||||
svr.Post("/test", [&](const Request &, Response &) { handled = true; });
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
t.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
ASSERT_FALSE(handled);
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
auto req = "POST /test HTTP/1.1\r\n"
|
||||
"Content-Length: x\r\n"
|
||||
"\r\n";
|
||||
|
||||
std::string response;
|
||||
ASSERT_TRUE(send_request(1, req, &response));
|
||||
ASSERT_EQ("HTTP/1.1 400 Bad Request",
|
||||
response.substr(0, response.find("\r\n")));
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST(Expect100ContinueTest, ServerClosesConnection) {
|
||||
static constexpr char reject[] = "Unauthorized";
|
||||
|
||||
+5
-5
@@ -28,26 +28,26 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -177,4 +177,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -0,0 +1 @@
|
||||
日本語コンテンツ
|
||||
Reference in New Issue
Block a user