Compare commits

..
14 Commits
Author SHA1 Message Date
yhirose 5814e121df Release v0.18.7 2025-02-08 15:53:35 -05:00
Florian Albrechtskirchinger 7adbccbaf7 Refine when content is expected (#2044)
Consider Content-Length and Transfer-Encoding headers when determining
whether to expect content. Don't handle the HTTP/2 connection preface
pseudo-method PRI.

Fixes #2028.
2025-02-08 15:51:52 -05:00
yhirose eb10c22db1 Add unit test for #609 2025-02-08 10:17:09 -05:00
yhirose 708f860e3a Fix #2042 2025-02-06 05:56:31 -05:00
yhirose eb30f15363 Release v0.18.6 2025-02-05 19:14:20 -05:00
yhirose 4941d5b56b Fix #2033 (#2039) 2025-02-05 12:46:33 -05:00
Florian Albrechtskirchinger 9bbb4741b4 Run clang-format (#2037) 2025-02-02 22:32:33 -05:00
yhirose 282f2feb77 Add a unit test 2025-02-01 22:11:15 -05:00
alex-cornford 60a1f00618 Support building httplib.h on OpenVMS x86 systems (#2031)
Modify for OpenVMS x86 C++. Make tests on OpenVMS currently not supported due to no cmake support.
Changes tested on OpenVMS clang C++ and Fedora & GCC
2025-01-28 18:44:22 -05:00
yhirose 9104054ca5 Fix README example 2025-01-27 13:37:16 -05:00
Baiyies d69f144a99 Update httplib.h (#2030)
fix 'max'
2025-01-26 08:50:10 -05:00
yhirose 929dfbd348 Update copyright year 2025-01-20 00:32:10 -05:00
yhirose 3047183fd9 Update README 2025-01-20 00:02:02 -05:00
yhirose ef5e4044f1 Update README 2025-01-19 23:46:12 -05:00
6 changed files with 86 additions and 36 deletions
+3 -6
View File
@@ -39,10 +39,10 @@ svr.listen("0.0.0.0", 8080);
#include "path/to/httplib.h"
// HTTP
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
httplib::Client cli("http://yhirose.github.io");
// HTTPS
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
httplib::Client cli("https://yhirose.github.io");
auto res = cli.Get("/hi");
res->status;
@@ -931,9 +931,6 @@ From Docker Hub
```bash
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server
...
> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server
Serving HTTP on 0.0.0.0 port 80 ...
192.168.65.1 - - [31/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"
192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET / HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."
@@ -971,7 +968,7 @@ Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32
License
-------
MIT license (© 2024 Yuji Hirose)
MIT license (© 2025 Yuji Hirose)
Special Thanks To
-----------------
+1 -1
View File
@@ -1,7 +1,7 @@
//
// httplib.h
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// main.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
+1 -1
View File
@@ -1,7 +1,7 @@
//
// server_and_client.cc
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
+28 -23
View File
@@ -1,14 +1,14 @@
//
// httplib.h
//
// Copyright (c) 2024 Yuji Hirose. All rights reserved.
// Copyright (c) 2025 Yuji Hirose. All rights reserved.
// MIT License
//
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_VERSION "0.18.5"
#define CPPHTTPLIB_VERSION "0.18.7"
/*
* Configuration
@@ -218,7 +218,9 @@ using socket_t = SOCKET;
#include <csignal>
#include <pthread.h>
#include <sys/mman.h>
#ifndef __VMS
#include <sys/select.h>
#endif
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
@@ -2547,7 +2549,7 @@ 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.empty()) { return true; }
if (s.size() == 1) {
return is_field_vchar(s[0]);
@@ -4212,22 +4214,21 @@ inline bool parse_header(const char *beg, const char *end, T fn) {
if (!key_len) { return false; }
auto key = std::string(beg, key_end);
auto val = case_ignore::equal(key, "Location")
? std::string(p, end)
: decode_url(std::string(p, end), false);
// auto val = (case_ignore::equal(key, "Location") ||
// case_ignore::equal(key, "Referer"))
// ? std::string(p, end)
// : decode_url(std::string(p, end), false);
auto val = std::string(p, end);
// NOTE: From RFC 9110:
// Field values containing CR, LF, or NUL characters are
// invalid and dangerous, due to the varying ways that
// implementations might parse and interpret those
// characters; a recipient of CR, LF, or NUL within a field
// value MUST either reject the message or replace each of
// those characters with SP before further processing or
// forwarding of that message.
static const std::string CR_LF_NUL("\r\n\0", 3);
if (val.find_first_of(CR_LF_NUL) != std::string::npos) { return false; }
if (!detail::fields::is_field_value(val)) { return false; }
if (case_ignore::equal(key, "Location") ||
case_ignore::equal(key, "Referer")) {
fn(key, val);
} else {
fn(key, decode_url(val, false));
}
fn(key, val);
return true;
}
@@ -4263,7 +4264,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
if (!parse_header(line_reader.ptr(), end,
[&](const std::string &key, std::string &val) {
[&](const std::string &key, const std::string &val) {
headers.emplace(key, val);
})) {
return false;
@@ -4456,9 +4457,9 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
ret = read_content_without_length(strm, out);
} else {
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);
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;
@@ -5380,10 +5381,14 @@ write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
inline bool expect_content(const Request &req) {
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
req.method == "PRI" || req.method == "DELETE") {
req.method == "DELETE") {
return true;
}
// TODO: check if Content-Length is set
if (req.has_header("Content-Length") &&
req.get_header_value_u64("Content-Length") > 0) {
return true;
}
if (is_chunked_transfer_encoding(req.headers)) { return true; }
return false;
}
+52 -4
View File
@@ -1863,6 +1863,32 @@ TEST(PathUrlEncodeTest, PathUrlEncode) {
}
}
TEST(PathUrlEncodeTest, IncludePercentEncodingLF) {
Server svr;
svr.Get("/", [](const Request &req, Response &) {
EXPECT_EQ("\x0A", req.get_param_value("something"));
});
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);
cli.set_url_encode(false);
auto res = cli.Get("/?something=%0A");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
}
TEST(BindServerTest, DISABLED_BindDualStack) {
Server svr;
@@ -2951,6 +2977,11 @@ protected:
res.status = 401;
res.set_header("WWW-Authenticate", "Basic realm=123456");
})
.Delete("/issue609",
[](const httplib::Request &, httplib::Response &res,
const httplib::ContentReader &) {
res.set_content("ok", "text/plain");
})
#if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT)
.Get("/compress",
[&](const Request & /*req*/, Response &res) {
@@ -4022,6 +4053,13 @@ TEST_F(ServerTest, Issue1772) {
EXPECT_EQ(StatusCode::Unauthorized_401, res->status);
}
TEST_F(ServerTest, Issue609) {
auto res = cli_.Delete("/issue609");
ASSERT_TRUE(res);
EXPECT_EQ(StatusCode::OK_200, res->status);
EXPECT_EQ(std::string("ok"), res->body);
}
TEST_F(ServerTest, GetStreamedChunked) {
auto res = cli_.Get("/streamed-chunked");
ASSERT_TRUE(res);
@@ -4910,8 +4948,10 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
"Connection: close\r\n"
"\r\n";
ASSERT_TRUE(send_request(5, req));
EXPECT_EQ(header_value, "\v bar \x1B");
std::string res;
ASSERT_TRUE(send_request(5, req, &res));
EXPECT_EQ(header_value, "");
EXPECT_EQ("HTTP/1.1 400 Bad Request", res.substr(0, 24));
}
// Sends a raw request and verifies that there isn't a crash or exception.
@@ -5069,6 +5109,14 @@ TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, InvalidFieldValueContains_LF) {
std::string out;
std::string request(
"GET /header_field_value_check HTTP/1.1\r\nTest: [\n\n\n]\r\n\r\n", 55);
test_raw_request(request, &out);
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, EmptyFieldValue) {
std::string out;
@@ -6153,7 +6201,7 @@ TEST(SSLClientTest, WildcardHostNameMatch_Online) {
ASSERT_EQ(StatusCode::OK_200, res->status);
}
TEST(SSLClientTest, Issue2004) {
TEST(SSLClientTest, Issue2004_Online) {
Client client("https://google.com");
client.set_follow_location(true);
@@ -7958,7 +8006,7 @@ TEST(InvalidHeaderCharsTest, is_field_value) {
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_TRUE(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)));