mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a66a013ed7 | |||
| f4b02dfdc1 | |||
| 4cf218643e | |||
| 8f96b69a25 | |||
| d262033ded | |||
| 5745eabe69 | |||
| 5f18642271 | |||
| 88a9278872 | |||
| 9bb3ca8169 | |||
| f2f4728489 | |||
| d1b616286f | |||
| df74526f91 | |||
| 9f7ae0737a | |||
| 1ebb8412c5 | |||
| 7b69999c37 | |||
| c7e959a948 | |||
| ba5884e779 | |||
| cdaa5c48db | |||
| bab5c0e907 | |||
| 016838fd10 | |||
| 75053bf855 | |||
| ae3a6dd2a9 | |||
| 6d963fbe8d | |||
| 88f6245c84 | |||
| 0e7d2f9f93 | |||
| 4e6ded1f36 | |||
| d663588491 | |||
| 439caf5b79 | |||
| c4ba43ca6f | |||
| 20cba2ecd9 | |||
| 0ff2e16d69 | |||
| 51607ec752 |
@@ -17,7 +17,7 @@ jobs:
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: install brotli library on ubuntu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt update && sudo apt-get install -y libbrotli-dev
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: cd test && make fuzz_test
|
||||
- name: setup msbuild on windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
- name: make-windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
|
||||
+12
-6
@@ -8,13 +8,14 @@
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
After installation with Cmake, a find_package(httplib) is available.
|
||||
This creates a httplib::httplib target (if found).
|
||||
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available.
|
||||
This creates a httplib::httplib target (if found and if listed components are supported).
|
||||
It can be linked like so:
|
||||
|
||||
target_link_libraries(your_exe httplib::httplib)
|
||||
@@ -78,7 +79,7 @@ set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silenty continue if not found.
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
|
||||
# Make these options so their automatic use can be specifically disabled (as needed)
|
||||
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
@@ -88,7 +89,7 @@ option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a c
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
# Defaults to static library
|
||||
@@ -205,6 +206,8 @@ target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
$<$<PLATFORM_ID:Windows>:cryptui>
|
||||
# Needed for API from MacOS Security framework
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
|
||||
@@ -232,8 +235,6 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
||||
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
|
||||
# Passes the includedir install path
|
||||
PATH_VARS CMAKE_INSTALL_FULL_INCLUDEDIR
|
||||
# There aren't any components, so don't use the macro
|
||||
NO_CHECK_REQUIRED_COMPONENTS_MACRO
|
||||
)
|
||||
|
||||
if(HTTPLIB_COMPILE)
|
||||
@@ -278,3 +279,8 @@ install(EXPORT httplibTargets
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
@@ -54,6 +54,7 @@ SSL Support
|
||||
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
|
||||
|
||||
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
|
||||
NOTE for macOS: cpp-httplib now uses system certs. `CoreFoundation` and `Security` should be linked with `-framework`.
|
||||
|
||||
```c++
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
@@ -346,6 +347,27 @@ svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
});
|
||||
```
|
||||
|
||||
With trailer:
|
||||
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_header("Trailer", "Dummy1, Dummy2");
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
sink.done_with_trailer({
|
||||
{"Dummy1", "DummyVal1"},
|
||||
{"Dummy2", "DummyVal2"}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
### 'Expect: 100-continue' handler
|
||||
|
||||
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
|
||||
@@ -493,6 +515,10 @@ auto res = cli.Get("/hi", headers);
|
||||
```
|
||||
or
|
||||
```c++
|
||||
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
|
||||
```
|
||||
or
|
||||
```c++
|
||||
cli.set_default_headers({
|
||||
{ "Accept-Encoding", "gzip, deflate" }
|
||||
});
|
||||
@@ -815,7 +841,7 @@ Note: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are
|
||||
License
|
||||
-------
|
||||
|
||||
MIT license (© 2022 Yuji Hirose)
|
||||
MIT license (© 2023 Yuji Hirose)
|
||||
|
||||
Special Thanks To
|
||||
-----------------
|
||||
|
||||
+8
-1
@@ -8,12 +8,19 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
endif
|
||||
endif
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark issue
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public:
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
if (sink->is_writable()) { sink->write(message_.data(), message_.size()); }
|
||||
sink->write(message_.data(), message_.size());
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//
|
||||
// httplib.h
|
||||
//
|
||||
// Copyright (c) 2022 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2023 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||
#define CPPHTTPLIB_HTTPLIB_H
|
||||
|
||||
#define CPPHTTPLIB_VERSION "0.11.4"
|
||||
#define CPPHTTPLIB_VERSION "0.12.2"
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
@@ -239,7 +239,10 @@ using socket_t = int;
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#pragma comment(lib, "cryptui.lib")
|
||||
#endif
|
||||
#endif //_WIN32
|
||||
#elif defined(__APPLE__) // _WIN32
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
@@ -308,6 +311,34 @@ struct ci {
|
||||
}
|
||||
};
|
||||
|
||||
// This is based on
|
||||
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
|
||||
|
||||
template <typename EF> struct scope_exit {
|
||||
explicit scope_exit(EF &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
|
||||
scope_exit(scope_exit &&rhs)
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) { this->exit_function(); }
|
||||
}
|
||||
|
||||
void release() { this->execute_on_destruction = false; }
|
||||
|
||||
private:
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
|
||||
EF exit_function;
|
||||
bool execute_on_destruction;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
using Headers = std::multimap<std::string, std::string, detail::ci>;
|
||||
@@ -340,7 +371,7 @@ public:
|
||||
|
||||
std::function<bool(const char *data, size_t data_len)> write;
|
||||
std::function<void()> done;
|
||||
std::function<bool()> is_writable;
|
||||
std::function<void(const Headers &trailer)> done_with_trailer;
|
||||
std::ostream os;
|
||||
|
||||
private:
|
||||
@@ -455,6 +486,7 @@ struct Request {
|
||||
|
||||
bool has_file(const std::string &key) const;
|
||||
MultipartFormData get_file_value(const std::string &key) const;
|
||||
std::vector<MultipartFormData> get_file_values(const std::string &key) const;
|
||||
|
||||
// private members...
|
||||
size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
|
||||
@@ -769,7 +801,7 @@ private:
|
||||
ContentReceiver multipart_receiver);
|
||||
bool read_content_core(Stream &strm, Request &req, Response &res,
|
||||
ContentReceiver receiver,
|
||||
MultipartContentHeader mulitpart_header,
|
||||
MultipartContentHeader multipart_header,
|
||||
ContentReceiver multipart_receiver);
|
||||
|
||||
virtual bool process_and_close_socket(socket_t sock);
|
||||
@@ -823,6 +855,9 @@ enum class Error {
|
||||
UnsupportedMultipartBoundaryChars,
|
||||
Compression,
|
||||
ConnectionTimeout,
|
||||
|
||||
// For internal use only
|
||||
SSLPeerCouldBeClosed_,
|
||||
};
|
||||
|
||||
std::string to_string(const Error error);
|
||||
@@ -1095,8 +1130,6 @@ protected:
|
||||
bool is_open() const { return sock != INVALID_SOCKET; }
|
||||
};
|
||||
|
||||
Result send_(Request &&req);
|
||||
|
||||
virtual bool create_and_connect_socket(Socket &socket, Error &error);
|
||||
|
||||
// All of:
|
||||
@@ -1118,7 +1151,7 @@ protected:
|
||||
|
||||
void copy_settings(const ClientImpl &rhs);
|
||||
|
||||
// Socket endoint information
|
||||
// Socket endpoint information
|
||||
const std::string host_;
|
||||
const int port_;
|
||||
const std::string host_and_port_;
|
||||
@@ -1197,6 +1230,9 @@ protected:
|
||||
Logger logger_;
|
||||
|
||||
private:
|
||||
bool send_(Request &req, Response &res, Error &error);
|
||||
Result send_(Request &&req);
|
||||
|
||||
socket_t create_client_socket(Error &error) const;
|
||||
bool read_response_line(Stream &strm, const Request &req, Response &res);
|
||||
bool write_request(Stream &strm, Request &req, bool close_connection,
|
||||
@@ -1665,20 +1701,20 @@ Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
|
||||
|
||||
inline std::string to_string(const Error error) {
|
||||
switch (error) {
|
||||
case Error::Success: return "Success";
|
||||
case Error::Connection: return "Connection";
|
||||
case Error::BindIPAddress: return "BindIPAddress";
|
||||
case Error::Read: return "Read";
|
||||
case Error::Write: return "Write";
|
||||
case Error::ExceedRedirectCount: return "ExceedRedirectCount";
|
||||
case Error::Canceled: return "Canceled";
|
||||
case Error::SSLConnection: return "SSLConnection";
|
||||
case Error::SSLLoadingCerts: return "SSLLoadingCerts";
|
||||
case Error::SSLServerVerification: return "SSLServerVerification";
|
||||
case Error::Success: return "Success (no error)";
|
||||
case Error::Connection: return "Could not establish connection";
|
||||
case Error::BindIPAddress: return "Failed to bind IP address";
|
||||
case Error::Read: return "Failed to read connection";
|
||||
case Error::Write: return "Failed to write connection";
|
||||
case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
|
||||
case Error::Canceled: return "Connection handling canceled";
|
||||
case Error::SSLConnection: return "SSL connection failed";
|
||||
case Error::SSLLoadingCerts: return "SSL certificate loading failed";
|
||||
case Error::SSLServerVerification: return "SSL server verification failed";
|
||||
case Error::UnsupportedMultipartBoundaryChars:
|
||||
return "UnsupportedMultipartBoundaryChars";
|
||||
case Error::Compression: return "Compression";
|
||||
case Error::ConnectionTimeout: return "ConnectionTimeout";
|
||||
return "Unsupported HTTP multipart boundary characters";
|
||||
case Error::Compression: return "Compression failed";
|
||||
case Error::ConnectionTimeout: return "Connection timed out";
|
||||
case Error::Unknown: return "Unknown";
|
||||
default: break;
|
||||
}
|
||||
@@ -1788,6 +1824,9 @@ std::string params_to_query_str(const Params ¶ms);
|
||||
|
||||
void parse_query_text(const std::string &s, Params ¶ms);
|
||||
|
||||
bool parse_multipart_boundary(const std::string &content_type,
|
||||
std::string &boundary);
|
||||
|
||||
bool parse_range_header(const std::string &s, Ranges &ranges);
|
||||
|
||||
int close_socket(socket_t sock);
|
||||
@@ -2939,9 +2978,14 @@ inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
|
||||
inline constexpr unsigned int str2tag_core(const char *s, size_t l,
|
||||
unsigned int h) {
|
||||
return (l == 0) ? h
|
||||
: str2tag_core(s + 1, l - 1,
|
||||
(h * 33) ^ static_cast<unsigned char>(*s));
|
||||
return (l == 0)
|
||||
? h
|
||||
: str2tag_core(
|
||||
s + 1, l - 1,
|
||||
// Unsets the 6 high bits of h, therefore no overflow happens
|
||||
(((std::numeric_limits<unsigned int>::max)() >> 6) &
|
||||
h * 33) ^
|
||||
static_cast<unsigned char>(*s));
|
||||
}
|
||||
|
||||
inline unsigned int str2tag(const std::string &s) {
|
||||
@@ -3354,6 +3398,14 @@ inline const char *get_header_value(const Headers &headers,
|
||||
return def;
|
||||
}
|
||||
|
||||
inline bool compare_case_ignore(const std::string &a, const std::string &b) {
|
||||
if (a.size() != b.size()) { return false; }
|
||||
for (size_t i = 0; i < b.size(); i++) {
|
||||
if (::tolower(a[i]) != ::tolower(b[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
// Skip trailing spaces and tabs.
|
||||
@@ -3377,7 +3429,11 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
|
||||
}
|
||||
|
||||
if (p < end) {
|
||||
fn(std::string(beg, key_end), decode_url(std::string(p, end), false));
|
||||
auto key = std::string(beg, key_end);
|
||||
auto val = compare_case_ignore(key, "Location")
|
||||
? std::string(p, end)
|
||||
: decode_url(std::string(p, end), false);
|
||||
fn(std::move(key), std::move(val));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3475,7 +3531,8 @@ inline bool read_content_without_length(Stream &strm,
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool read_content_chunked(Stream &strm,
|
||||
template <typename T>
|
||||
inline bool read_content_chunked(Stream &strm, T &x,
|
||||
ContentReceiverWithProgress out) {
|
||||
const auto bufsiz = 16;
|
||||
char buf[bufsiz];
|
||||
@@ -3501,15 +3558,29 @@ inline bool read_content_chunked(Stream &strm,
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { return false; }
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
}
|
||||
|
||||
if (chunk_len == 0) {
|
||||
// Reader terminator after chunks
|
||||
if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n"))
|
||||
return false;
|
||||
assert(chunk_len == 0);
|
||||
|
||||
// Trailer
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
while (strcmp(line_reader.ptr(), "\r\n")) {
|
||||
if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
|
||||
|
||||
// Exclude line terminator
|
||||
constexpr auto line_terminator_len = 2;
|
||||
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
|
||||
|
||||
parse_header(line_reader.ptr(), end,
|
||||
[&](std::string &&key, std::string &&val) {
|
||||
x.headers.emplace(std::move(key), std::move(val));
|
||||
});
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -3579,7 +3650,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
||||
auto exceed_payload_max_length = false;
|
||||
|
||||
if (is_chunked_transfer_encoding(x.headers)) {
|
||||
ret = read_content_chunked(strm, out);
|
||||
ret = read_content_chunked(strm, x, out);
|
||||
} else if (!has_header(x.headers, "Content-Length")) {
|
||||
ret = read_content_without_length(strm, out);
|
||||
} else {
|
||||
@@ -3632,7 +3703,7 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
if (write_data(strm, d, l)) {
|
||||
if (strm.is_writable() && write_data(strm, d, l)) {
|
||||
offset += l;
|
||||
} else {
|
||||
ok = false;
|
||||
@@ -3641,14 +3712,14 @@ inline bool write_content(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
|
||||
while (offset < end_offset && !is_shutting_down()) {
|
||||
if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
if (!strm.is_writable()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, end_offset - offset, data_sink)) {
|
||||
error = Error::Canceled;
|
||||
return false;
|
||||
}
|
||||
if (!ok) {
|
||||
} else if (!ok) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
@@ -3680,18 +3751,21 @@ write_content_without_length(Stream &strm,
|
||||
data_sink.write = [&](const char *d, size_t l) -> bool {
|
||||
if (ok) {
|
||||
offset += l;
|
||||
if (!write_data(strm, d, l)) { ok = false; }
|
||||
if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; }
|
||||
}
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) { data_available = false; };
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!content_provider(offset, 0, data_sink)) { return false; }
|
||||
if (!ok) { return false; }
|
||||
if (!strm.is_writable()) {
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
return false;
|
||||
} else if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -3720,7 +3794,10 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk =
|
||||
from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
|
||||
if (!strm.is_writable() ||
|
||||
!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
@@ -3729,7 +3806,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.done = [&](void) {
|
||||
auto done_with_trailer = [&](const Headers *trailer) {
|
||||
if (!ok) { return; }
|
||||
|
||||
data_available = false;
|
||||
@@ -3747,26 +3824,46 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider,
|
||||
if (!payload.empty()) {
|
||||
// Emit chunked response header and footer for each chunk
|
||||
auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
|
||||
if (!write_data(strm, chunk.data(), chunk.size())) {
|
||||
if (!strm.is_writable() ||
|
||||
!write_data(strm, chunk.data(), chunk.size())) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string done_marker("0\r\n\r\n");
|
||||
static const std::string done_marker("0\r\n");
|
||||
if (!write_data(strm, done_marker.data(), done_marker.size())) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
// Trailer
|
||||
if (trailer) {
|
||||
for (const auto &kv : *trailer) {
|
||||
std::string field_line = kv.first + ": " + kv.second + "\r\n";
|
||||
if (!write_data(strm, field_line.data(), field_line.size())) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string crlf("\r\n");
|
||||
if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; }
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && strm.is_writable(); };
|
||||
data_sink.done = [&](void) { done_with_trailer(nullptr); };
|
||||
|
||||
data_sink.done_with_trailer = [&](const Headers &trailer) {
|
||||
done_with_trailer(&trailer);
|
||||
};
|
||||
|
||||
while (data_available && !is_shutting_down()) {
|
||||
if (!content_provider(offset, 0, data_sink)) {
|
||||
if (!strm.is_writable()) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
} else if (!content_provider(offset, 0, data_sink)) {
|
||||
error = Error::Canceled;
|
||||
return false;
|
||||
}
|
||||
if (!ok) {
|
||||
} else if (!ok) {
|
||||
error = Error::Write;
|
||||
return false;
|
||||
}
|
||||
@@ -3847,9 +3944,12 @@ inline void parse_query_text(const std::string &s, Params ¶ms) {
|
||||
|
||||
inline bool parse_multipart_boundary(const std::string &content_type,
|
||||
std::string &boundary) {
|
||||
auto pos = content_type.find("boundary=");
|
||||
auto boundary_keyword = "boundary=";
|
||||
auto pos = content_type.find(boundary_keyword);
|
||||
if (pos == std::string::npos) { return false; }
|
||||
boundary = content_type.substr(pos + 9);
|
||||
auto end = content_type.find(';', pos);
|
||||
auto beg = pos + strlen(boundary_keyword);
|
||||
boundary = content_type.substr(beg, end - beg);
|
||||
if (boundary.length() >= 2 && boundary.front() == '"' &&
|
||||
boundary.back() == '"') {
|
||||
boundary = boundary.substr(1, boundary.size() - 2);
|
||||
@@ -3960,6 +4060,9 @@ public:
|
||||
if (std::regex_match(header, m, re_content_disposition)) {
|
||||
file_.name = m[1];
|
||||
file_.filename = m[2];
|
||||
} else {
|
||||
is_valid_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
buf_erase(pos + crlf_.size());
|
||||
@@ -4379,15 +4482,15 @@ inline std::string SHA_512(const std::string &s) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#ifdef _WIN32
|
||||
// NOTE: This code came up with the following stackoverflow post:
|
||||
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
|
||||
inline bool load_system_certs_on_windows(X509_STORE *store) {
|
||||
auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
|
||||
|
||||
if (!hStore) { return false; }
|
||||
|
||||
auto result = false;
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
|
||||
nullptr) {
|
||||
@@ -4398,16 +4501,107 @@ inline bool load_system_certs_on_windows(X509_STORE *store) {
|
||||
if (x509) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
X509_free(x509);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
template <typename T>
|
||||
using CFObjectPtr =
|
||||
std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
|
||||
|
||||
inline void cf_object_ptr_deleter(CFTypeRef obj) {
|
||||
if (obj) { CFRelease(obj); }
|
||||
}
|
||||
|
||||
inline bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
|
||||
CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
|
||||
CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
|
||||
kCFBooleanTrue};
|
||||
|
||||
CFObjectPtr<CFDictionaryRef> query(
|
||||
CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
|
||||
sizeof(keys) / sizeof(keys[0]),
|
||||
&kCFTypeDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks),
|
||||
cf_object_ptr_deleter);
|
||||
|
||||
if (!query) { return false; }
|
||||
|
||||
CFTypeRef security_items = nullptr;
|
||||
if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
|
||||
CFArrayGetTypeID() != CFGetTypeID(security_items)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
certs.reset(reinterpret_cast<CFArrayRef>(security_items));
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
|
||||
CFArrayRef root_security_items = nullptr;
|
||||
if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
certs.reset(root_security_items);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
|
||||
auto result = false;
|
||||
for (int i = 0; i < CFArrayGetCount(certs); ++i) {
|
||||
const auto cert = reinterpret_cast<const __SecCertificate *>(
|
||||
CFArrayGetValueAtIndex(certs, i));
|
||||
|
||||
if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
|
||||
|
||||
CFDataRef cert_data = nullptr;
|
||||
if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
|
||||
errSecSuccess) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
|
||||
|
||||
auto encoded_cert = static_cast<const unsigned char *>(
|
||||
CFDataGetBytePtr(cert_data_ptr.get()));
|
||||
|
||||
auto x509 =
|
||||
d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
|
||||
|
||||
if (x509) {
|
||||
X509_STORE_add_cert(store, x509);
|
||||
X509_free(x509);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool load_system_certs_on_apple(X509_STORE *store) {
|
||||
auto result = false;
|
||||
CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
|
||||
if (retrieve_certs_from_keychain(certs) && certs) {
|
||||
result = add_certs_to_x509_store(certs.get(), store);
|
||||
}
|
||||
|
||||
if (retrieve_root_certs_from_keychain(certs) && certs) {
|
||||
result = add_certs_to_x509_store(certs.get(), store) || result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
class WSInit {
|
||||
public:
|
||||
WSInit() {
|
||||
@@ -4682,6 +4876,16 @@ inline MultipartFormData Request::get_file_value(const std::string &key) const {
|
||||
return MultipartFormData();
|
||||
}
|
||||
|
||||
inline std::vector<MultipartFormData>
|
||||
Request::get_file_values(const std::string &key) const {
|
||||
std::vector<MultipartFormData> values;
|
||||
auto rng = files.equal_range(key);
|
||||
for (auto it = rng.first; it != rng.second; it++) {
|
||||
values.push_back(it->second);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
// Response implementation
|
||||
inline bool Response::has_header(const std::string &key) const {
|
||||
return headers.find(key) != headers.end();
|
||||
@@ -5397,7 +5601,7 @@ inline bool Server::read_content_with_content_receiver(
|
||||
|
||||
inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
ContentReceiver receiver,
|
||||
MultipartContentHeader mulitpart_header,
|
||||
MultipartContentHeader multipart_header,
|
||||
ContentReceiver multipart_receiver) {
|
||||
detail::MultipartFormDataParser multipart_form_data_parser;
|
||||
ContentReceiverWithProgress out;
|
||||
@@ -5417,14 +5621,14 @@ inline bool Server::read_content_core(Stream &strm, Request &req, Response &res,
|
||||
while (pos < n) {
|
||||
auto read_size = (std::min)<size_t>(1, n - pos);
|
||||
auto ret = multipart_form_data_parser.parse(
|
||||
buf + pos, read_size, multipart_receiver, mulitpart_header);
|
||||
buf + pos, read_size, multipart_receiver, multipart_header);
|
||||
if (!ret) { return false; }
|
||||
pos += read_size;
|
||||
}
|
||||
return true;
|
||||
*/
|
||||
return multipart_form_data_parser.parse(buf, n, multipart_receiver,
|
||||
mulitpart_header);
|
||||
multipart_header);
|
||||
};
|
||||
} else {
|
||||
out = [receiver](const char *buf, size_t n, uint64_t /*off*/,
|
||||
@@ -5590,11 +5794,7 @@ inline bool Server::listen_internal() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __cplusplus > 201703L
|
||||
task_queue->enqueue([=, this]() { process_and_close_socket(sock); });
|
||||
#else
|
||||
task_queue->enqueue([=]() { process_and_close_socket(sock); });
|
||||
#endif
|
||||
task_queue->enqueue([this, sock]() { process_and_close_socket(sock); });
|
||||
}
|
||||
|
||||
task_queue->shutdown();
|
||||
@@ -6141,7 +6341,15 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
||||
|
||||
inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
|
||||
auto ret = send_(req, res, error);
|
||||
if (error == Error::SSLPeerCouldBeClosed_) {
|
||||
assert(!ret);
|
||||
ret = send_(req, res, error);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
|
||||
@@ -6172,7 +6380,7 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
if (is_ssl()) {
|
||||
auto &scli = static_cast<SSLClient &>(*this);
|
||||
if (!proxy_host_.empty() && proxy_port_ != -1) {
|
||||
bool success = false;
|
||||
auto success = false;
|
||||
if (!scli.connect_with_proxy(socket_, res, success, error)) {
|
||||
return success;
|
||||
}
|
||||
@@ -6199,13 +6407,11 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = false;
|
||||
auto close_connection = !keep_alive_;
|
||||
auto ret = process_socket(socket_, [&](Stream &strm) {
|
||||
return handle_request(strm, req, res, close_connection, error);
|
||||
});
|
||||
|
||||
// Briefly lock mutex in order to mark that a request is no longer ongoing
|
||||
{
|
||||
auto se = detail::scope_exit<std::function<void(void)>>([&]() {
|
||||
// Briefly lock mutex in order to mark that a request is no longer ongoing
|
||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||
socket_requests_in_flight_ -= 1;
|
||||
if (socket_requests_in_flight_ <= 0) {
|
||||
@@ -6219,7 +6425,11 @@ inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ret = process_socket(socket_, [&](Stream &strm) {
|
||||
return handle_request(strm, req, res, close_connection, error);
|
||||
});
|
||||
|
||||
if (!ret) {
|
||||
if (error == Error::Success) { error = Error::Unknown; }
|
||||
@@ -6307,11 +6517,11 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto location = detail::decode_url(res.get_header_value("location"), true);
|
||||
auto location = res.get_header_value("location");
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
|
||||
|
||||
std::smatch m;
|
||||
if (!std::regex_match(location, m, re)) { return false; }
|
||||
@@ -6323,6 +6533,7 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (next_host.empty()) { next_host = m[3].str(); }
|
||||
auto port_str = m[4].str();
|
||||
auto next_path = m[5].str();
|
||||
auto next_query = m[6].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
@@ -6335,22 +6546,24 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
auto path = detail::decode_url(next_path, true) + next_query;
|
||||
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
return detail::redirect(*this, req, res, next_path, location, error);
|
||||
return detail::redirect(*this, req, res, path, location, error);
|
||||
} else {
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); }
|
||||
return detail::redirect(cli, req, res, next_path, location, error);
|
||||
return detail::redirect(cli, req, res, path, location, error);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
ClientImpl cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path, location, error);
|
||||
return detail::redirect(cli, req, res, path, location, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6361,7 +6574,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
|
||||
auto is_shutting_down = []() { return false; };
|
||||
|
||||
if (req.is_chunked_content_provider_) {
|
||||
// TODO: Brotli suport
|
||||
// TODO: Brotli support
|
||||
std::unique_ptr<detail::compressor> compressor;
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) {
|
||||
@@ -6378,7 +6591,7 @@ inline bool ClientImpl::write_content_with_provider(Stream &strm,
|
||||
return detail::write_content(strm, req.content_provider_, 0,
|
||||
req.content_length_, is_shutting_down, error);
|
||||
}
|
||||
} // namespace httplib
|
||||
}
|
||||
|
||||
inline bool ClientImpl::write_request(Stream &strm, Request &req,
|
||||
bool close_connection, Error &error) {
|
||||
@@ -6541,8 +6754,6 @@ inline std::unique_ptr<Response> ClientImpl::send_with_content_provider(
|
||||
return ok;
|
||||
};
|
||||
|
||||
data_sink.is_writable = [&](void) { return ok && true; };
|
||||
|
||||
while (ok && offset < content_length) {
|
||||
if (!content_provider(offset, content_length - offset, data_sink)) {
|
||||
error = Error::Canceled;
|
||||
@@ -6613,6 +6824,20 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
||||
// Send request
|
||||
if (!write_request(strm, req, close_connection, error)) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
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) {
|
||||
error = Error::SSLPeerCouldBeClosed_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Receive response and headers
|
||||
if (!read_response_line(strm, req, res) ||
|
||||
!detail::read_headers(strm, res.headers)) {
|
||||
@@ -6714,7 +6939,6 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
|
||||
bool has_data = true;
|
||||
cur_sink.write = sink.write;
|
||||
cur_sink.done = [&]() { has_data = false; };
|
||||
cur_sink.is_writable = sink.is_writable;
|
||||
|
||||
if (!provider_items[cur_item].provider(offset - cur_start, cur_sink))
|
||||
return false;
|
||||
@@ -7240,7 +7464,7 @@ inline void ClientImpl::stop() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, sitll holding the mutex, we can shut everything down ourselves
|
||||
// Otherwise, still holding the mutex, we can shut everything down ourselves
|
||||
shutdown_ssl(socket_, true);
|
||||
shutdown_socket(socket_);
|
||||
close_socket(socket_);
|
||||
@@ -7653,7 +7877,7 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) {
|
||||
},
|
||||
[](SSL * /*ssl2*/) { return true; });
|
||||
|
||||
bool ret = false;
|
||||
auto ret = false;
|
||||
if (ssl) {
|
||||
ret = detail::process_server_socket_ssl(
|
||||
svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
|
||||
@@ -7831,11 +8055,14 @@ inline bool SSLClient::load_certs() {
|
||||
ret = false;
|
||||
}
|
||||
} else {
|
||||
auto loaded = false;
|
||||
#ifdef _WIN32
|
||||
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
|
||||
#else
|
||||
SSL_CTX_set_default_verify_paths(ctx_);
|
||||
loaded =
|
||||
detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
|
||||
#elif defined(__APPLE__)
|
||||
loaded = detail::load_system_certs_on_apple(SSL_CTX_get_cert_store(ctx_));
|
||||
#endif
|
||||
if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7980,7 +8207,7 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
|
||||
if (alt_names) {
|
||||
auto dsn_matched = false;
|
||||
auto ip_mached = false;
|
||||
auto ip_matched = false;
|
||||
|
||||
auto count = sk_GENERAL_NAME_num(alt_names);
|
||||
|
||||
@@ -7996,14 +8223,14 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
|
||||
case GEN_IPADD:
|
||||
if (!memcmp(&addr6, name, addr_len) ||
|
||||
!memcmp(&addr, name, addr_len)) {
|
||||
ip_mached = true;
|
||||
ip_matched = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dsn_matched || ip_mached) { ret = true; }
|
||||
if (dsn_matched || ip_matched) { ret = true; }
|
||||
}
|
||||
|
||||
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names);
|
||||
|
||||
+17
-8
@@ -12,19 +12,19 @@ set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
# We add find_dependency calls here to not make the end-user have to call them.
|
||||
find_dependency(Threads REQUIRED)
|
||||
find_dependency(Threads)
|
||||
if(@HTTPLIB_IS_USING_OPENSSL@)
|
||||
# OpenSSL COMPONENTS were added in Cmake v3.11
|
||||
if(CMAKE_VERSION VERSION_LESS "3.11")
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ REQUIRED)
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@)
|
||||
else()
|
||||
# Once the COMPONENTS were added, they were made optional when not specified.
|
||||
# Since we use both, we need to search for both.
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL REQUIRED)
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
|
||||
endif()
|
||||
endif()
|
||||
if(@HTTPLIB_IS_USING_ZLIB@)
|
||||
find_dependency(ZLIB REQUIRED)
|
||||
find_dependency(ZLIB)
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
@@ -32,7 +32,7 @@ if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
# Note that the FindBrotli.cmake file is installed in the same dir as this file.
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder REQUIRED)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder)
|
||||
endif()
|
||||
|
||||
# Mildly useful for end-users
|
||||
@@ -42,10 +42,19 @@ set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
# This is helpful if you're using Cmake's pre-compiled header feature
|
||||
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
|
||||
|
||||
# Brings in the target library
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
|
||||
# Consider each library support as a "component"
|
||||
set(httplib_OpenSSL_FOUND @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(httplib_ZLIB_FOUND @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(httplib_Brotli_FOUND @HTTPLIB_IS_USING_BROTLI@)
|
||||
|
||||
# Ouputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
|
||||
check_required_components(httplib)
|
||||
|
||||
# Brings in the target library, but only if all required components are found
|
||||
if(NOT DEFINED httplib_FOUND OR httplib_FOUND)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/httplibTargets.cmake")
|
||||
endif()
|
||||
|
||||
# Outputs a "found httplib /usr/include/httplib.h" message when using find_package(httplib)
|
||||
include(FindPackageMessage)
|
||||
if(TARGET httplib::httplib)
|
||||
set(HTTPLIB_FOUND TRUE)
|
||||
|
||||
@@ -34,6 +34,9 @@ openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cp
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
if host_machine.system() == 'darwin'
|
||||
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
|
||||
endif
|
||||
endif
|
||||
|
||||
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
find_package(GTest)
|
||||
|
||||
if(GTest_FOUND)
|
||||
if(NOT TARGET GTest::gtest_main AND TARGET GTest::Main)
|
||||
# CMake <3.20
|
||||
add_library(GTest::gtest_main INTERFACE IMPORTED)
|
||||
target_link_libraries(GTest::gtest_main INTERFACE GTest::Main)
|
||||
endif()
|
||||
else()
|
||||
if(POLICY CMP0135)
|
||||
cmake_policy(SET CMP0135 NEW)
|
||||
endif()
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
set(BUILD_GMOCK OFF)
|
||||
set(INSTALL_GTEST OFF)
|
||||
set(gtest_force_shared_crt ON)
|
||||
|
||||
FetchContent_Declare(
|
||||
gtest
|
||||
URL https://github.com/google/googletest/archive/main.tar.gz
|
||||
)
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
endif()
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
|
||||
gtest_discover_tests(httplib-test)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
if(HTTPLIB_IS_USING_OPENSSL)
|
||||
find_program(OPENSSL_COMMAND
|
||||
NAMES openssl
|
||||
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
|
||||
REQUIRED
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key.pem
|
||||
OUTPUT_FILE cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE rootCA.key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
OUTPUT_FILE rootCA.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa 2048
|
||||
OUTPUT_FILE client.key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client.key.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
|
||||
OUTPUT_FILE client.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa -passout pass:test123! 2048
|
||||
OUTPUT_FILE key_encrypted.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key_encrypted.pem
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 3650 -req -signkey key_encrypted.pem
|
||||
OUTPUT_FILE cert_encrypted.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
endif()
|
||||
@@ -8,6 +8,13 @@ OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
endif
|
||||
endif
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
// Forward declare the "fuzz target" interface.
|
||||
// We deliberately keep this inteface simple and header-free.
|
||||
// We deliberately keep this interface simple and header-free.
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||
|
||||
// It reads all files passed as parameters and feeds their contents
|
||||
|
||||
+274
-26
@@ -46,7 +46,7 @@ MultipartFormData &get_file_value(MultipartFormDataItems &files,
|
||||
return *it;
|
||||
#else
|
||||
if (it != files.end()) { return *it; }
|
||||
throw std::runtime_error("invalid mulitpart form data name error");
|
||||
throw std::runtime_error("invalid multipart form data name error");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -169,6 +169,40 @@ TEST(ParamsToQueryTest, ConvertParamsToQuery) {
|
||||
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, DefaultValue) {
|
||||
string content_type = "multipart/form-data; boundary=something";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "something");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithQuote) {
|
||||
string content_type = "multipart/form-data; boundary=\"gc0pJq0M:08jU534c0p\"";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "gc0pJq0M:08jU534c0p");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithCharset) {
|
||||
string content_type =
|
||||
"multipart/mixed; boundary=THIS_STRING_SEPARATES;charset=UTF-8";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "THIS_STRING_SEPARATES");
|
||||
}
|
||||
|
||||
TEST(ParseMultipartBoundaryTest, ValueWithQuotesAndCharset) {
|
||||
string content_type =
|
||||
"multipart/mixed; boundary=\"cpp-httplib-multipart-data\"; charset=UTF-8";
|
||||
string boundary;
|
||||
auto ret = detail::parse_multipart_boundary(content_type, boundary);
|
||||
EXPECT_TRUE(ret);
|
||||
EXPECT_EQ(boundary, "cpp-httplib-multipart-data");
|
||||
}
|
||||
|
||||
TEST(GetHeaderValueTest, DefaultValue) {
|
||||
Headers headers = {{"Dummy", "Dummy"}};
|
||||
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
|
||||
@@ -596,7 +630,7 @@ TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) {
|
||||
ASSERT_TRUE(!res);
|
||||
stringstream s;
|
||||
s << "error code: " << res.error();
|
||||
EXPECT_EQ("error code: Connection (2)", s.str());
|
||||
EXPECT_EQ("error code: Could not establish connection (2)", s.str());
|
||||
}
|
||||
|
||||
TEST(ConnectionErrorTest, InvalidPort) {
|
||||
@@ -779,7 +813,7 @@ TEST(DigestAuthTest, FromHTTPWatch_Online) {
|
||||
}
|
||||
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
@@ -1650,7 +1684,6 @@ protected:
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain", [](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "123";
|
||||
sink.os << "456";
|
||||
sink.os << "789";
|
||||
@@ -1664,7 +1697,6 @@ protected:
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
switch (*i) {
|
||||
case 0: sink.os << "123"; break;
|
||||
case 1: sink.os << "456"; break;
|
||||
@@ -1679,6 +1711,30 @@ protected:
|
||||
delete i;
|
||||
});
|
||||
})
|
||||
.Get("/streamed-chunked-with-trailer",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
auto i = new int(0);
|
||||
res.set_header("Trailer", "Dummy1, Dummy2");
|
||||
res.set_chunked_content_provider(
|
||||
"text/plain",
|
||||
[i](size_t /*offset*/, DataSink &sink) {
|
||||
switch (*i) {
|
||||
case 0: sink.os << "123"; break;
|
||||
case 1: sink.os << "456"; break;
|
||||
case 2: sink.os << "789"; break;
|
||||
case 3: {
|
||||
sink.done_with_trailer(
|
||||
{{"Dummy1", "DummyVal1"}, {"Dummy2", "DummyVal2"}});
|
||||
} break;
|
||||
}
|
||||
(*i)++;
|
||||
return true;
|
||||
},
|
||||
[i](bool success) {
|
||||
EXPECT_TRUE(success);
|
||||
delete i;
|
||||
});
|
||||
})
|
||||
.Get("/streamed",
|
||||
[&](const Request & /*req*/, Response &res) {
|
||||
res.set_content_provider(
|
||||
@@ -1694,7 +1750,6 @@ protected:
|
||||
res.set_content_provider(
|
||||
data->size(), "text/plain",
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
size_t DATA_CHUNK_SIZE = 4;
|
||||
const auto &d = *data;
|
||||
auto out_len =
|
||||
@@ -1714,8 +1769,6 @@ protected:
|
||||
res.set_content_provider(
|
||||
size_t(-1), "text/plain",
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
if (!sink.is_writable()) return false;
|
||||
|
||||
sink.os << "data_chunk";
|
||||
return true;
|
||||
});
|
||||
@@ -1772,6 +1825,40 @@ protected:
|
||||
EXPECT_EQ("application/json tmp-string", file.content_type);
|
||||
}
|
||||
})
|
||||
.Post("/multipart/multi_file_values",
|
||||
[&](const Request &req, Response & /*res*/) {
|
||||
EXPECT_EQ(5u, req.files.size());
|
||||
ASSERT_TRUE(!req.has_file("???"));
|
||||
ASSERT_TRUE(req.body.empty());
|
||||
|
||||
{
|
||||
const auto &text_value = req.get_file_values("text");
|
||||
EXPECT_EQ(text_value.size(), 1);
|
||||
auto &text = text_value[0];
|
||||
EXPECT_TRUE(text.filename.empty());
|
||||
EXPECT_EQ("default text", text.content);
|
||||
}
|
||||
{
|
||||
const auto &text1_values = req.get_file_values("multi_text1");
|
||||
EXPECT_EQ(text1_values.size(), 2);
|
||||
EXPECT_EQ("aaaaa", text1_values[0].content);
|
||||
EXPECT_EQ("bbbbb", text1_values[1].content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file1_values = req.get_file_values("multi_file1");
|
||||
EXPECT_EQ(file1_values.size(), 2);
|
||||
auto file1 = file1_values[0];
|
||||
EXPECT_EQ(file1.filename, "hello.txt");
|
||||
EXPECT_EQ(file1.content_type, "text/plain");
|
||||
EXPECT_EQ("h\ne\n\nl\nl\no\n", file1.content);
|
||||
|
||||
auto file2 = file1_values[1];
|
||||
EXPECT_EQ(file2.filename, "world.json");
|
||||
EXPECT_EQ(file2.content_type, "application/json");
|
||||
EXPECT_EQ("{\n \"world\", true\n}\n", file2.content);
|
||||
}
|
||||
})
|
||||
.Post("/empty",
|
||||
[&](const Request &req, Response &res) {
|
||||
EXPECT_EQ(req.body, "");
|
||||
@@ -2616,6 +2703,24 @@ TEST_F(ServerTest, MultipartFormData) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, MultipartFormDataMultiFileValues) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text", "default text", "", ""},
|
||||
|
||||
{"multi_text1", "aaaaa", "", ""},
|
||||
{"multi_text1", "bbbbb", "", ""},
|
||||
|
||||
{"multi_file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain"},
|
||||
{"multi_file1", "{\n \"world\", true\n}\n", "world.json",
|
||||
"application/json"},
|
||||
};
|
||||
|
||||
auto res = cli_.Post("/multipart/multi_file_values", items);
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, CaseInsensitiveHeaderName) {
|
||||
auto res = cli_.Get("/hi");
|
||||
ASSERT_TRUE(res);
|
||||
@@ -2841,6 +2946,15 @@ TEST_F(ServerTest, GetStreamedChunked2) {
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetStreamedChunkedWithTrailer) {
|
||||
auto res = cli_.Get("/streamed-chunked-with-trailer");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(std::string("123456789"), res->body);
|
||||
EXPECT_EQ(std::string("DummyVal1"), res->get_header_value("Dummy1"));
|
||||
EXPECT_EQ(std::string("DummyVal2"), res->get_header_value("Dummy2"));
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, LargeChunkedPost) {
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
@@ -2952,7 +3066,6 @@ TEST_F(ServerTest, PutWithContentProvider) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
@@ -2979,7 +3092,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLength) {
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
@@ -3006,7 +3118,6 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
|
||||
auto res = cli_.Put(
|
||||
"/put", 3,
|
||||
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
return true;
|
||||
},
|
||||
@@ -3035,7 +3146,6 @@ TEST_F(ServerTest, PutWithContentProviderWithoutLengthWithGzip) {
|
||||
auto res = cli_.Put(
|
||||
"/put",
|
||||
[](size_t /*offset*/, DataSink &sink) {
|
||||
EXPECT_TRUE(sink.is_writable());
|
||||
sink.os << "PUT";
|
||||
sink.done();
|
||||
return true;
|
||||
@@ -3143,7 +3253,7 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
||||
{
|
||||
httplib::detail::gzip_decompressor decompressor;
|
||||
|
||||
// Chunk size is chosen specificaly to have a decompressed chunk size equal
|
||||
// Chunk size is chosen specifically to have a decompressed chunk size equal
|
||||
// to 16384 bytes 16384 bytes is the size of decompressor output buffer
|
||||
size_t chunk_size = 130;
|
||||
for (size_t chunk_begin = 0; chunk_begin < compressed_data.size();
|
||||
@@ -3292,7 +3402,7 @@ TEST_F(ServerTest, PostContentReceiver) {
|
||||
ASSERT_EQ("content", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
TEST_F(ServerTest, PostMultipartFileContentReceiver) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
{"text2", "aωb", "", ""},
|
||||
@@ -3307,7 +3417,7 @@ TEST_F(ServerTest, PostMulitpartFilsContentReceiver) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostMulitpartPlusBoundary) {
|
||||
TEST_F(ServerTest, PostMultipartPlusBoundary) {
|
||||
MultipartFormDataItems items = {
|
||||
{"text1", "text default", "", ""},
|
||||
{"text2", "aωb", "", ""},
|
||||
@@ -3723,10 +3833,10 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparsableHeaderLine) {
|
||||
// Make sure this doesn't crash the server.
|
||||
// In a previous version of the header line regex, the "\r" rendered the line
|
||||
// unparseable and the regex engine repeatedly backtracked, trying to look for
|
||||
// unparsable and the regex engine repeatedly backtracked, trying to look for
|
||||
// a new position where the leading white space ended and the field value
|
||||
// began.
|
||||
// The crash occurs with libc++ but not libstdc++.
|
||||
@@ -3829,6 +3939,31 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, ClientAccessAfterServerDown) {
|
||||
httplib::Server svr;
|
||||
svr.Post("/hi", [&](const httplib::Request & /*req*/,
|
||||
httplib::Response &res) { res.status = 200; });
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
}
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
auto res = cli.Post("/hi", "data", "text/plain");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
|
||||
res = cli.Post("/hi", "data", "text/plain");
|
||||
ASSERT_FALSE(res);
|
||||
}
|
||||
|
||||
TEST(StreamingTest, NoContentLengthStreaming) {
|
||||
Server svr;
|
||||
|
||||
@@ -3984,7 +4119,7 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
cli.set_read_timeout(std::chrono::seconds(1));
|
||||
|
||||
auto resa = cli.Get("/a");
|
||||
ASSERT_TRUE(!resa);
|
||||
ASSERT_FALSE(resa);
|
||||
EXPECT_EQ(Error::Read, resa.error());
|
||||
|
||||
auto resb = cli.Get("/b");
|
||||
@@ -3998,35 +4133,74 @@ TEST(KeepAliveTest, ReadTimeout) {
|
||||
}
|
||||
|
||||
TEST(KeepAliveTest, Issue1041) {
|
||||
const auto resourcePath = "/hi";
|
||||
|
||||
Server svr;
|
||||
svr.set_keep_alive_timeout(3);
|
||||
|
||||
svr.Get(resourcePath, [](const httplib::Request &, httplib::Response &res) {
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto a2 = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Get(resourcePath);
|
||||
auto result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
|
||||
result = cli.Get(resourcePath);
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
a2.wait();
|
||||
f.wait();
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
TEST(KeepAliveTest, SSLClientReconnection) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
ASSERT_TRUE(svr.is_valid());
|
||||
svr.set_keep_alive_timeout(1);
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto f = std::async(std::launch::async, [&svr] { svr.listen(HOST, PORT); });
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
|
||||
SSLClient cli(HOST, PORT);
|
||||
cli.enable_server_certificate_verification(false);
|
||||
cli.set_keep_alive(true);
|
||||
|
||||
auto result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Recoonect
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
result = cli.Get("/hi");
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(200, result->status);
|
||||
|
||||
svr.stop();
|
||||
f.wait();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ClientProblemDetectionTest, ContentProvider) {
|
||||
Server svr;
|
||||
|
||||
@@ -4442,7 +4616,7 @@ TEST(SSLClientTest, ServerNameIndication_Online) {
|
||||
}
|
||||
|
||||
TEST(SSLClientTest, ServerCertificateVerification1_Online) {
|
||||
SSLClient cli("google.com");
|
||||
Client cli("https://google.com");
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
ASSERT_EQ(301, res->status);
|
||||
@@ -5344,6 +5518,49 @@ TEST(MultipartFormDataTest, DataProviderItems) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, BadHeader) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||
while (!svr.is_running()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
const std::string body =
|
||||
"This is the preamble. It is to be ignored, though it\r\n"
|
||||
"is a handy place for composition agents to include an\r\n"
|
||||
"explanatory note to non-MIME conformant readers.\r\n"
|
||||
"\r\n"
|
||||
"\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field1\"\r\n"
|
||||
": BAD...\r\n"
|
||||
"\r\n"
|
||||
"value1\r\n"
|
||||
"--simple boundary\r\n"
|
||||
"Content-Disposition: form-data; name=\"field2\"; "
|
||||
"filename=\"example.txt\"\r\n"
|
||||
"\r\n"
|
||||
"value2\r\n"
|
||||
"--simple boundary--\r\n"
|
||||
"This is the epilogue. It is also to be ignored.\r\n";
|
||||
|
||||
std::string content_type =
|
||||
R"(multipart/form-data; boundary="simple boundary")";
|
||||
|
||||
Client cli(HOST, PORT);
|
||||
auto res = cli.Post("/post", body, content_type.c_str());
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(400, res->status);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(MultipartFormDataTest, WithPreamble) {
|
||||
Server svr;
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
@@ -5795,3 +6012,34 @@ TEST(TaskQueueTest, IncreaseAtomicInteger) {
|
||||
EXPECT_NO_THROW(task_queue->shutdown());
|
||||
EXPECT_EQ(number_of_task, count.load());
|
||||
}
|
||||
|
||||
TEST(RedirectTest, RedirectToUrlWithQueryParameters) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request & /*req*/, Response &res) {
|
||||
res.set_redirect(R"(/hello?key=val%26key2%3Dval2)");
|
||||
});
|
||||
|
||||
svr.Get("/hello", [](const Request &req, Response &res) {
|
||||
res.set_content(req.get_param_value("key"), "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli(HOST, PORT);
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
ASSERT_TRUE(res);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("val&key2=val2", res->body);
|
||||
}
|
||||
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -193,7 +193,7 @@ void DigestAuthTestFromHTTPWatch(T& cli) {
|
||||
}
|
||||
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// out. Please see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
|
||||
Reference in New Issue
Block a user