Compare commits

..

17 Commits

Author SHA1 Message Date
yhirose ba685dbe48 Fixed potential infinite loop with content receiver 2020-05-10 20:45:57 -04:00
yhirose 49c4c2f9c1 Fix #459 2020-05-10 20:39:16 -04:00
yhirose 58909f5917 Fix #466 2020-05-10 15:58:53 -04:00
yhirose 5982b5c360 Fix #471 2020-05-10 14:18:03 -04:00
yhirose eb1fe5b191 Fixed warnings 2020-05-09 15:08:49 -04:00
yhirose 5e01587ed6 Fixed problem created in the previous commit 2020-05-09 13:43:06 -04:00
yhirose 5935d9fa59 Commented out the unit test for digest auth. 2020-05-09 13:32:51 -04:00
PixlRainbow 5bb4c12c6b Fix #465 (#467)
update digest header username to use username parameter instead of "hello" test value
2020-05-09 08:29:08 -04:00
yhirose 85637844c9 Updated README 2020-05-07 21:13:45 -04:00
Daniel Ottiger d043b18097 keepalive: support multiple post using content provider (#461) 2020-05-07 08:31:14 -04:00
yhirose 31bb13abd2 Removed TravisCI badge from README 2020-05-04 22:19:17 -04:00
yhirose 8728db7477 Apply IPV6_V6ONLY only when socket is AF_INET6 2020-05-04 22:16:43 -04:00
yhirose 1c50ac3667 Stop using TravisCI anymore due to IPv6 issue 2020-05-04 22:14:03 -04:00
yhirose cf386f97fd Merge branch 'master' of https://github.com/yhirose/cpp-httplib 2020-05-04 22:13:17 -04:00
Daniel Ottiger b2203bb05a server: support dual-stack server socket (#450)
According to RFC 3493 the socket option IPV6_V6ONLY
should be off by default, see
https://tools.ietf.org/html/rfc3493#page-22 (chapter 5.3).

However this does not seem to be the case on all systems.
For instance on any Windows OS, the option is on by default.

Therefore clear this option in order to allow
an server socket which can support IPv6 and IPv4 at the same time.
2020-05-04 22:13:12 -04:00
yhirose f5b806d995 Added a test case for #396. 2020-05-04 21:26:14 -04:00
yhirose 3895210f19 Code format 2020-05-04 21:25:59 -04:00
4 changed files with 123 additions and 53 deletions
-14
View File
@@ -1,14 +0,0 @@
# Environment
language: cpp
os:
- linux
- osx
# Compiler selection
compiler:
- clang
# Build/test steps
script:
- cd ${TRAVIS_BUILD_DIR}/test
- make all
+13 -5
View File
@@ -2,7 +2,6 @@ cpp-httplib
===========
[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions)
[![Build Status](https://travis-ci.org/yhirose/cpp-httplib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-httplib)
[![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-httplib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-httplib)
A C++11 single-file header-only cross platform HTTP/HTTPS library.
@@ -172,14 +171,14 @@ svr.Post("/content_receiver",
### Send content with Content provider
```cpp
const uint64_t DATA_CHUNK_SIZE = 4;
const size_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), // Content length
[data](uint64_t offset, uint64_t length, DataSink &sink) {
[data](size_t offset, size_t length, DataSink &sink) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
@@ -192,7 +191,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
```cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_chunked_content_provider(
[](uint64_t offset, DataSink &sink) {
[](size_t offset, DataSink &sink) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
@@ -291,7 +290,7 @@ auto res = cli.Get("/hi", headers);
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length) {
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
@@ -437,6 +436,15 @@ Get(requests, "/get-request2");
Post(requests, "/post-request1", "text", "text/plain");
Post(requests, "/post-request2", "text", "text/plain");
const size_t DATA_CHUNK_SIZE = 4;
std::string data("abcdefg");
Post(requests, "/post-request-with-content-provider",
data.size(),
[&](size_t offset, size_t length, DataSink &sink){
sink.write(&data[offset], std::min(length, DATA_CHUNK_SIZE));
},
"text/plain");
std::vector<Response> responses;
if (cli.send(requests, responses)) {
for (const auto& res: responses) {
+54 -21
View File
@@ -50,7 +50,9 @@
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
#define CPPHTTPLIB_THREAD_POOL_COUNT \
((std::max)(8u, std::thread::hardware_concurrency() - 1))
((std::max)(8u, std::thread::hardware_concurrency() > 0 \
? std::thread::hardware_concurrency() - 1 \
: 0))
#endif
/*
@@ -275,6 +277,7 @@ struct Request {
// for client
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
size_t authorization_count = 1;
ResponseHandler response_handler;
ContentReceiver content_receiver;
Progress progress;
@@ -855,6 +858,21 @@ inline void Post(std::vector<Request> &requests, const char *path,
Post(requests, path, Headers(), body, content_type);
}
inline void Post(std::vector<Request> &requests, const char *path,
size_t content_length, ContentProvider content_provider,
const char *content_type) {
Request req;
req.method = "POST";
req.headers = Headers();
req.path = path;
req.content_length = content_length;
req.content_provider = content_provider;
if (content_type) { req.headers.emplace("Content-Type", content_type); }
requests.emplace_back(std::move(req));
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
class SSLServer : public Server {
public:
@@ -1198,20 +1216,18 @@ inline int close_socket(socket_t sock) {
#endif
}
template <typename T>
inline ssize_t handle_EINTR(T fn) {
template <typename T> inline ssize_t handle_EINTR(T fn) {
ssize_t res = false;
while (true) {
res = fn();
if (res < 0 && errno == EINTR) {
continue;
}
if (res < 0 && errno == EINTR) { continue; }
break;
}
return res;
}
#define HANDLE_EINTR(method, ...) (handle_EINTR([&]() { return method(__VA_ARGS__); }))
#define HANDLE_EINTR(method, ...) \
(handle_EINTR([&]() { return method(__VA_ARGS__); }))
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
#ifdef CPPHTTPLIB_USE_POLL
@@ -1231,7 +1247,8 @@ inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr,
nullptr, &tv);
#endif
}
@@ -1253,7 +1270,8 @@ inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds,
nullptr, &tv);
#endif
}
@@ -1270,7 +1288,7 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
int error = 0;
socklen_t len = sizeof(error);
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char *>(&error), &len);
reinterpret_cast<char *>(&error), &len);
return res >= 0 && !error;
}
return false;
@@ -1286,7 +1304,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
tv.tv_sec = static_cast<long>(sec);
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse,
&tv) > 0 &&
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
int error = 0;
socklen_t len = sizeof(error);
@@ -1469,11 +1488,18 @@ socket_t create_socket(const char *host, int port, Fn fn,
int yes = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
sizeof(yes));
#ifdef SO_REUSEPORT
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
sizeof(yes));
#endif
if (rp->ai_family == AF_INET6) {
int no = 0;
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
sizeof(no));
}
// bind or connect
if (fn(sock, *rp)) {
freeaddrinfo(result);
@@ -2057,7 +2083,9 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
written_length = strm.write(d, l);
};
data_sink.done = [&](void) { written_length = -1; };
data_sink.is_writable = [&](void) { return strm.is_writable(); };
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
};
content_provider(offset, end_offset - offset, data_sink);
if (written_length < 0) { return written_length; }
@@ -2088,7 +2116,9 @@ inline ssize_t write_content_chunked(Stream &strm,
data_available = false;
written_length = strm.write("0\r\n\r\n");
};
data_sink.is_writable = [&](void) { return strm.is_writable(); };
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
};
content_provider(offset, 0, data_sink);
@@ -2710,10 +2740,11 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
":" + qop + ":" + H(A2));
}
auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
"\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
"\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
auto field = "Digest username=\"" + username + "\", realm=\"" +
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
"\", uri=\"" + req.path + "\", algorithm=" + algo +
", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
"\", response=\"" + response + "\"";
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
return std::make_pair(key, field);
@@ -3866,7 +3897,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
if (res.status == 401 || res.status == 407) {
if ((res.status == 401 || res.status == 407) &&
req.authorization_count == 1) {
auto is_proxy = res.status == 407;
const auto &username =
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
@@ -3877,10 +3909,12 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
std::map<std::string, std::string> auth;
if (parse_www_authenticate(res, auth, is_proxy)) {
Request new_req = req;
auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
new_req.authorization_count += 1;
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
new_req.headers.erase(key);
new_req.headers.insert(make_digest_authentication_header(
req, auth, 1, random_string(10), username, password, is_proxy));
req, auth, new_req.authorization_count, random_string(10), username,
password, is_proxy));
Response new_res;
@@ -5122,4 +5156,3 @@ namespace detail {
} // namespace httplib
#endif // CPPHTTPLIB_HTTPLIB_H
+56 -13
View File
@@ -563,15 +563,17 @@ TEST(DigestAuthTest, FromHTTPWatch) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
EXPECT_EQ(401, res->status);
}
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese 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());
// ASSERT_TRUE(res != nullptr);
// EXPECT_EQ(400, res->status);
// }
}
}
#endif
@@ -724,6 +726,39 @@ TEST(RedirectToDifferentPort, Redirect) {
}
#endif
TEST(Server, BindDualStack) {
Server svr;
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
});
auto thread = std::thread([&]() { svr.listen("::", PORT); });
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
{
Client cli("127.0.0.1", PORT);
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
}
{
Client cli("::1", PORT);
auto res = cli.Get("/1");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ(res->body, "Hello World!");
}
svr.stop();
thread.join();
ASSERT_FALSE(svr.is_running());
}
TEST(Server, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("0.0.0.0");
@@ -1749,9 +1784,8 @@ TEST_F(ServerTest, GetStreamedEndless) {
TEST_F(ServerTest, ClientStop) {
thread t = thread([&]() {
auto res =
cli_.Get("/streamed-cancel",
[&](const char *, uint64_t) { return true; });
auto res = cli_.Get("/streamed-cancel",
[&](const char *, uint64_t) { return true; });
ASSERT_TRUE(res == nullptr);
});
std::this_thread::sleep_for(std::chrono::seconds(1));
@@ -2056,6 +2090,9 @@ TEST_F(ServerTest, KeepAlive) {
Get(requests, "/hi");
Get(requests, "/not-exist");
Post(requests, "/empty", "", "text/plain");
Post(
requests, "/empty", 0, [&](size_t, size_t, httplib::DataSink &) {},
"text/plain");
std::vector<Response> responses;
auto ret = cli_.send(requests, responses);
@@ -2075,8 +2112,8 @@ TEST_F(ServerTest, KeepAlive) {
EXPECT_EQ(404, res.status);
}
{
auto &res = responses[4];
for (size_t i = 4; i < 6; i++) {
auto &res = responses[i];
EXPECT_EQ(200, res.status);
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
EXPECT_EQ("empty", res.body);
@@ -2341,7 +2378,8 @@ TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
// began.
// The crash occurs with libc++ but not libstdc++.
test_raw_request("GET /hi HTTP/1.1\r\n"
"a:" + std::string(2000, ' ') + '\r' + std::string(20, 'z') +
"a:" +
std::string(2000, ' ') + '\r' + std::string(20, 'z') +
"\r\n"
"\r\n");
}
@@ -2386,6 +2424,11 @@ TEST(ServerRequestParsingTest, ChunkLengthTooHighInRequest) {
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
}
TEST(ServerRequestParsingTest, InvalidHeaderTextWithExtraCR) {
test_raw_request("GET /hi HTTP/1.1\r\n"
"Content-Type: text/plain\r\n\r");
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;