Compare commits

..

39 Commits

Author SHA1 Message Date
yhirose 85327e19ae Fix #425 2020-04-12 15:33:08 -04:00
Alexandre Taillefer ed8efea98b Added support for DELETE request body (#418)
* Added support for DELETE request body

* Fixed DELETE request body test case typo

Co-authored-by: Alexandre Taillefer <alexandre.taillefer@pwc.ca>
2020-04-07 15:51:52 -04:00
Max 1ccddd1b0b SSL_shutdown() only if not already closed by remote (#413) 2020-04-06 10:30:21 -04:00
yhirose 992f3dc690 Code cleanup 2020-04-03 09:33:29 -04:00
yhirose 402d47e2cd Fix #407 2020-03-31 19:42:53 -04:00
yhirose 171fc2e353 Fix #403. Added more status codes based on MDN document 2020-03-26 20:50:40 -04:00
SoenkeHeeren ced4160d05 add http status code 201 to show the right status message in return headers (#402) 2020-03-26 12:20:32 -04:00
yhirose 5b51aa6851 Revert "Add 1000-concurrency-result report"
This reverts commit b0af78e340.
2020-03-24 17:30:32 -04:00
Igor [hyperxor] dc13cde820 Minor improvements in httplib classes (#395) 2020-03-23 06:54:13 -04:00
yhirose b0af78e340 Add 1000-concurrency-result report 2020-03-21 15:49:26 +00:00
Andrew Gasparovic 914c8860e8 Accept content by value to allow moving
Previously, calling set_content always resulted in 's' being copied. With this change, there will still be the same number of copies made (1) when doing `set_content(my_value, ...)`, but there will be no copies if a caller elects to do `set_content(std::move(value), ...)` or `set_content(some_function_that_returns_a_temporary(), ...)` instead.
2020-03-21 00:39:07 -04:00
yhirose dc6a72a0fd Fix #387 2020-03-17 18:03:52 -04:00
yhirose 685533ba50 Fixed warnings on Windows due to max/min macro 2020-03-16 13:58:09 -04:00
yhirose 6e46ccb37c Updated README 2020-03-15 12:05:12 -04:00
Aristo Chen ac18b70a0f Update calculation formula for progress percentage (#386) 2020-03-15 08:29:27 -04:00
yhirose e1acb949e7 Fix #382 2020-03-13 18:43:29 -04:00
yhirose ab96f49766 Fixed problem that line end char is missing on start messagein simple
server example
2020-03-13 16:36:33 -04:00
Oleg Vorobiov 7b3cea5317 Prevent an implicit capture of 'this' via '[=]' (#381) 2020-03-12 12:31:22 -04:00
yhirose 26deffe0c6 Not to send 'EXCEPTION_WHAT' header to client 2020-03-10 17:44:36 -04:00
Rafael Leira e07c5fec01 simplest way to catch handler exceptions 2020-03-10 17:44:36 -04:00
miketsts 6e473a7c5c Fix conversion to ‘int’ from ‘long int’ warning (#377)
Co-authored-by: Michael Tseitlin <michael.tseitlin@concertio.com>
2020-03-10 14:48:14 -04:00
yhirose c74129a1c2 Fix #372 (#374) 2020-03-09 23:59:00 -04:00
yhirose 18e750b4e7 Code cleanup 2020-03-09 19:47:28 -04:00
Matthew DeVore bf7700d192 Fix exception that occurs with libc++ regex engine (#368)
The regex that parses header lines potentially causes an unlimited
amount of backtracking, which can cause an exception in the libc++ regex
engine.

The exception that occurs looks like this and is identical to the
message of the exception fixed in
https://github.com/yhirose/cpp-httplib/pull/280:

	libc++abi.dylib: terminating with uncaught exception of type
	std::__1::regex_error: The complexity of an attempted match
	against a regular expression exceeded a pre-set level.

This commit eliminates the problematic backtracking.
2020-02-28 06:31:39 -05:00
yhirose 3da925d6fe Adjusted a unit test. 2020-02-25 08:29:29 -05:00
yhirose 319417f26d Updated README 2020-02-24 21:50:07 -05:00
yhirose 4c3b119dde Code format 2020-02-24 21:17:02 -05:00
yhirose 6de8684328 Merge branch 'hyperxor-remove_code_duplication_in_getting_query' 2020-02-24 21:15:01 -05:00
hyperxor ccc9a9b3f4 Remove code duplication in converting params to query 2020-02-25 02:30:34 +03:00
rymis f2bb9c45d6 Support for deflate compression (#360) 2020-02-24 11:48:00 -05:00
yhirose 9a663aa94e Added a unit test. 2020-02-21 11:48:47 -05:00
yhirose d0d744d520 Fixed compiler warning with Visual C++. Close #358 2020-02-20 17:30:04 -05:00
yhirose fce8e6fefd Merge pull request #356 from samhocevar/fix-shadow-declarations
Fix a few shadowed variable compilation warnings.
2020-02-17 09:01:47 -05:00
Sam Hocevar 180aa32ebf Fix a few shadowed variable compilation warnings. 2020-02-17 06:58:30 +01:00
yhirose fe01fa760b Update README 2020-02-14 22:07:39 -05:00
yhirose d61d63dd97 Added unit tests for SSLServer::bind_to_any_port 2020-02-14 21:59:37 -05:00
yhirose 3fe13ecc91 Updated README 2020-02-14 21:49:09 -05:00
yhirose 064cc6810e Fix #352 2020-02-13 17:40:06 -05:00
yhirose 464cc89b77 Fix #351 2020-02-12 22:35:52 -05:00
7 changed files with 691 additions and 381 deletions
+104 -120
View File
@@ -9,6 +9,8 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include **httplib.h** file in your code!
For Windows users: Please read [this note](https://github.com/yhirose/cpp-httplib#windows).
Server Example
--------------
@@ -17,24 +19,34 @@ Server Example
int main(void)
{
using namespace httplib;
using namespace httplib;
Server svr;
Server svr;
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.Get("/body-header-param", [](const Request& req, Response& res) {
if (req.has_header("Content-Length")) {
auto val = req.get_header_value("Content-Length");
}
if (req.has_param("key")) {
auto val = req.get_param_value("key");
}
res.set_content(req.body, "text/plain");
});
svr.listen("localhost", 1234);
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.listen("localhost", 1234);
}
```
@@ -102,7 +114,7 @@ NOTE: These the static file server methods are not thread safe.
```cpp
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
your_logger(req, res);
});
```
@@ -110,10 +122,10 @@ svr.set_logger([](const auto& req, const auto& res) {
```cpp
svr.set_error_handler([](const auto& req, auto& res) {
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
```
@@ -121,31 +133,12 @@ svr.set_error_handler([](const auto& req, auto& res) {
```cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
});
```
### Send content with Content provider
```cpp
const uint64_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) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
[data] { delete data; });
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
});
```
@@ -176,6 +169,24 @@ svr.Post("/content_receiver",
});
```
### Send content with Content provider
```cpp
const uint64_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) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
},
[data] { delete data; });
});
```
### Chunked transfer encoding
```cpp
@@ -200,11 +211,7 @@ Please check [here](https://github.com/yhirose/cpp-httplib/blob/master/example/s
`ThreadPool` is used as a default task queue, and the default thread count is set to value from `std::thread::hardware_concurrency()`.
Set thread count to 8:
```cpp
#define CPPHTTPLIB_THREAD_POOL_COUNT 8
```
You can change the thread count by setting `CPPHTTPLIB_THREAD_POOL_COUNT`.
### Override the default thread pool with yours
@@ -253,44 +260,43 @@ svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
Client Example
--------------
### GET
```c++
#include <httplib.h>
#include <iostream>
int main(void)
{
httplib::Client cli("localhost", 1234);
// IMPORTANT: 1st parameter must be a hostname or an IP adress string.
httplib::Client cli("localhost", 1234);
auto res = cli.Get("/hi");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
auto res = cli.Get("/hi");
if (res && res->status == 200) {
std::cout << res->body << std::endl;
}
}
```
### GET with HTTP headers
```c++
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
```
### GET with Content Receiver
```c++
std::string body;
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length) {
body.append(data, data_length);
return true;
});
auto res = cli.Get("/large-data",
[&](const char *data, uint64_t data_length) {
body.append(data, data_length);
return true;
});
assert(res->body.empty());
assert(res->body.empty());
```
### POST
@@ -323,15 +329,15 @@ auto res = cli.Post("/post", params);
### POST with Multipart Form Data
```c++
httplib::MultipartFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};
httplib::MultipartFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};
auto res = cli.Post("/multipart", items);
auto res = cli.Post("/multipart", items);
```
### PUT
@@ -365,12 +371,12 @@ httplib::Client client(url, port);
// prints: 0 / 000 bytes => 50% complete
std::shared_ptr<httplib::Response> res =
cli.Get("/", [](uint64_t len, uint64_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)((len/total)*100));
return true; // return 'false' if you want to cancel the request.
}
cli.Get("/", [](uint64_t len, uint64_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)(len*100/total));
return true; // return 'false' if you want to cancel the request.
}
);
```
@@ -510,8 +516,25 @@ httplib.h httplib.cc
NOTE
----
### g++
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
### Windows
Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand.
```cpp
#include <httplib.h>
#include <Windows.h>
```
```cpp
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <httplib.h>
```
License
-------
@@ -520,43 +543,4 @@ MIT license (© 2020 Yuji Hirose)
Special Thanks To
-----------------
The following folks made great contributions to polish this library to totally another level from a simple toy!
* [Zefz](https://github.com/Zefz)
* [PixlRainbow](https://github.com/PixlRainbow)
* [sgraham](https://github.com/sgraham)
* [mrexodia](https://github.com/mrexodia)
* [hyperxor](https://github.com/hyperxor)
* [omaralvarez](https://github.com/omaralvarez)
* [vvanelslande](https://github.com/vvanelslande)
* [underscorediscovery](https://github.com/underscorediscovery)
* [sux2mfgj](https://github.com/sux2mfgj)
* [matvore](https://github.com/matvore)
* [intmain-io](https://github.com/intmain)
* [davidgfnet](https://github.com/davidgfnet)
* [crtxcr](https://github.com/crtxcr)
* [const-volatile](https://github.com/const)
* [aguadoenzo](https://github.com/aguadoenzo)
* [TheMaverickProgrammer](https://github.com/TheMaverickProgrammer)
* [vdudouyt](https://github.com/vdudouyt)
* [stupedama](https://github.com/stupedama)
* [rockwotj](https://github.com/rockwotj)
* [marknelson](https://github.com/marknelson)
* [jaspervandeven](https://github.com/jaspervandeven)
* [hans-erickson](https://github.com/hans)
* [ha11owed](https://github.com/ha11owed)
* [gulrak](https://github.com/gulrak)
* [dolphineye](https://github.com/dolphineye)
* [danielzehe](https://github.com/danielzehe)
* [batist73](https://github.com/batist73)
* [barryam3](https://github.com/barryam3)
* [adikabintang](https://github.com/adikabintang)
* [aaronalbers](https://github.com/aaronalbers)
* [Whitetigerswt](https://github.com/Whitetigerswt)
* [TangHuaiZhe](https://github.com/TangHuaiZhe)
* [Sil3ntStorm](https://github.com/Sil3ntStorm)
* [MannyClicks](https://github.com/MannyClicks)
* [DraTeots](https://github.com/DraTeots)
* [BastienDurel](https://github.com/BastienDurel)
* [vitalyster](https://github.com/vitalyster)
* [trollixx](https://github.com/trollixx)
[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy!
+1 -1
View File
@@ -127,7 +127,7 @@ int main(int argc, const char **argv) {
return 1;
}
cout << "The server started at port " << port << "...";
cout << "The server started at port " << port << "..." << endl;
svr.listen("localhost", port);
+361 -245
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
#CXX = clang++
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits
CXXFLAGS = -ggdb -O0 -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -Wtype-limits -Wconversion
OPENSSL_DIR = /usr/local/opt/openssl
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
+14 -1
View File
@@ -38,6 +38,14 @@
// when it's fused.
#include "gtest/gtest.h"
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#elif __GNUC__
#pragma gcc diagnostic push
#pragma gcc diagnostic ignored "-Wsign-conversion"
#endif
// The following lines pull in the real gtest *.cc files.
// Copyright 2005, Google Inc.
// All rights reserved.
@@ -9039,7 +9047,6 @@ void HasNewFatalFailureHelper::ReportTestPartResult(
//
// Author: wan@google.com (Zhanyong Wan)
namespace testing {
namespace internal {
@@ -9116,3 +9123,9 @@ const char* TypedTestCasePState::VerifyRegisteredTestNames(
} // namespace internal
} // namespace testing
#if __clang__
#pragma clang diagnostic pop
#elif __GNUC__
#pragma gcc diagnostic pop
#endif
+15 -1
View File
@@ -59,6 +59,14 @@
#pragma warning(disable : 4996)
#endif
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-compare"
#elif __GNUC__
#pragma gcc diagnostic push
#pragma gcc diagnostic ignored "-Wsign-compare"
#endif
// Copyright 2005, Google Inc.
// All rights reserved.
//
@@ -7311,7 +7319,7 @@ inline const char* SkipComma(const char* str) {
// the entire string if it contains no comma.
inline String GetPrefixUntilComma(const char* str) {
const char* comma = strchr(str, ',');
return comma == NULL ? String(str) : String(str, comma - str);
return comma == NULL ? String(str) : String(str, static_cast<size_t>(comma - str));
}
// TypeParameterizedTest<Fixture, TestSel, Types>::Register()
@@ -19553,4 +19561,10 @@ bool StaticAssertTypeEq() {
#pragma warning( pop )
#endif
#if __clang__
#pragma clang diagnostic pop
#elif __GNUC__
#pragma gcc diagnostic pop
#endif
#endif // GTEST_INCLUDE_GTEST_GTEST_H_
+195 -12
View File
@@ -77,6 +77,21 @@ TEST(ParseQueryTest, ParseQueryString) {
EXPECT_EQ("val3", dic.find("key3")->second);
}
TEST(ParamsToQueryTest, ConvertParamsToQuery) {
Params dic;
EXPECT_EQ(detail::params_to_query_str(dic), "");
dic.emplace("key1", "val1");
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1");
dic.emplace("key2", "val2");
dic.emplace("key3", "val3");
EXPECT_EQ(detail::params_to_query_str(dic), "key1=val1&key2=val2&key3=val3");
}
TEST(GetHeaderValueTest, DefaultValue) {
Headers headers = {{"Dummy", "Dummy"}};
auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain");
@@ -645,11 +660,23 @@ TEST(HttpsToHttpRedirectTest, Redirect) {
TEST(Server, BindAndListenSeparately) {
Server svr;
int port = svr.bind_to_any_port("localhost");
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
svr.stop();
}
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
TEST(SSLServer, BindAndListenSeparately) {
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE, CLIENT_CA_CERT_FILE,
CLIENT_CA_CERT_DIR);
int port = svr.bind_to_any_port("0.0.0.0");
ASSERT_TRUE(svr.is_valid());
ASSERT_TRUE(port > 0);
svr.stop();
}
#endif
class ServerTest : public ::testing::Test {
protected:
ServerTest()
@@ -670,6 +697,36 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
})
.Get("/http_response_splitting",
[&](const Request & /*req*/, Response &res) {
res.set_header("a", "1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a", "1\rSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\r\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\rb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_header("a\nb", "0");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("a"));
res.set_redirect("1\r\nSet-Cookie: a=1");
EXPECT_EQ(0, res.headers.size());
EXPECT_FALSE(res.has_header("Location"));
})
.Get("/slow",
[&](const Request & /*req*/, Response &res) {
std::this_thread::sleep_for(std::chrono::seconds(2));
@@ -684,6 +741,12 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content("Hello World!", "text/plain");
})
.Get("/a\\+\\+b",
[&](const Request &req, Response &res) {
ASSERT_TRUE(req.has_param("a +b"));
auto val = req.get_param_value("a +b");
res.set_content(val, "text/plain");
})
.Get("/", [&](const Request & /*req*/,
Response &res) { res.set_redirect("/hi"); })
.Post("/person",
@@ -797,6 +860,7 @@ protected:
[&](const Request &req, Response & /*res*/) {
EXPECT_EQ(5u, req.files.size());
ASSERT_TRUE(!req.has_file("???"));
ASSERT_TRUE(req.body.empty());
{
const auto &file = req.get_file_value("text1");
@@ -848,6 +912,11 @@ protected:
[&](const Request & /*req*/, Response &res) {
res.set_content("DELETE", "text/plain");
})
.Delete("/delete-body",
[&](const Request &req, Response &res) {
EXPECT_EQ(req.body, "content");
res.set_content(req.body, "text/plain");
})
.Options(R"(\*)",
[&](const Request & /*req*/, Response &res) {
res.set_header("Allow", "GET, POST, HEAD, OPTIONS");
@@ -1030,6 +1099,16 @@ TEST_F(ServerTest, GetMethod200) {
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetMethod200withPercentEncoding) {
auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ("HTTP/1.1", res->version);
EXPECT_EQ(200, res->status);
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
EXPECT_EQ(1, res->get_header_value_count("Content-Type"));
EXPECT_EQ("Hello World!", res->body);
}
TEST_F(ServerTest, GetMethod302) {
auto res = cli_.Get("/");
ASSERT_TRUE(res != nullptr);
@@ -1422,6 +1501,13 @@ TEST_F(ServerTest, EndWithPercentCharacterInQuery) {
EXPECT_EQ(404, res->status);
}
TEST_F(ServerTest, PlusSignEncoding) {
auto res = cli_.Get("/a+%2Bb?a %2bb=a %2Bb");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("a +b", res->body);
}
TEST_F(ServerTest, MultipartFormData) {
MultipartFormDataItems items = {
{"text1", "text default", "", ""},
@@ -1629,6 +1715,12 @@ TEST_F(ServerTest, GetMethodRemoteAddr) {
EXPECT_TRUE(res->body == "::1" || res->body == "127.0.0.1");
}
TEST_F(ServerTest, HTTPResponseSplitting) {
auto res = cli_.Get("/http_response_splitting");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
}
TEST_F(ServerTest, SlowRequest) {
request_threads_.push_back(
std::thread([=]() { auto res = cli_.Get("/slow"); }));
@@ -1684,6 +1776,20 @@ TEST_F(ServerTest, PutLargeFileWithGzip) {
EXPECT_EQ(200, res->status);
EXPECT_EQ(LARGE_DATA, res->body);
}
TEST_F(ServerTest, PutContentWithDeflate) {
cli_.set_compress(false);
httplib::Headers headers;
headers.emplace("Content-Encoding", "deflate");
// PUT in deflate format:
auto res = cli_.Put("/put", headers,
"\170\234\013\010\015\001\0\001\361\0\372", "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("PUT", res->body);
}
#endif
TEST_F(ServerTest, Patch) {
@@ -1700,6 +1806,13 @@ TEST_F(ServerTest, Delete) {
EXPECT_EQ("DELETE", res->body);
}
TEST_F(ServerTest, DeleteContentReceiver) {
auto res = cli_.Delete("/delete-body", "content", "text/plain");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(200, res->status);
EXPECT_EQ("content", res->body);
}
TEST_F(ServerTest, Options) {
auto res = cli_.Options("*");
ASSERT_TRUE(res != nullptr);
@@ -1808,7 +1921,7 @@ TEST_F(ServerTest, KeepAlive) {
ASSERT_TRUE(ret == true);
ASSERT_TRUE(requests.size() == responses.size());
for (int i = 0; i < 3; i++) {
for (size_t i = 0; i < 3; i++) {
auto &res = responses[i];
EXPECT_EQ(200, res.status);
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
@@ -1999,7 +2112,8 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
EXPECT_EQ(header_value, "\v bar \e");
}
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
// Sends a raw request and verifies that there isn't a crash or exception.
static void test_raw_request(const std::string &req) {
Server svr;
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
res.set_content("ok", "text/plain");
@@ -2016,19 +2130,60 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// A certain header line causes an exception if the header property is parsed
// naively with a single regex. This occurs with libc++ but not libstdc++.
const std::string req =
"GET /hi HTTP/1.1\r\n"
" : "
" ";
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
svr.stop();
t.join();
EXPECT_TRUE(listen_thread_ok);
}
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity) {
// A certain header line causes an exception if the header property is parsed
// naively with a single regex. This occurs with libc++ but not libstdc++.
test_raw_request(
"GET /hi HTTP/1.1\r\n"
" : "
" "
);
}
TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
// A certain header line causes an exception if the header property *name* is
// parsed with a regular expression starting with "(.+?):" - this is a non-
// greedy matcher and requires backtracking when there are a lot of ":"
// characters.
// This occurs with libc++ but not libstdc++.
test_raw_request(
"GET /hi HTTP/1.1\r\n"
":-:::::::::::::::::::::::::::-::::::::::::::::::::::::@-&&&&&&&&&&&"
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-:::::"
"::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::::::::::::::::::::"
":::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::"
"::::::::-:::::::::::::::::@-&&&&&&&--:::::::-::::::::::::::::::::::"
":::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::"
"::::::::::-:::::::::::::::::@-&&&&&::::::::::::-:::::::::::::::::@-"
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
"::::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::@-&&"
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
"::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&"
"--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&"
"&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&"
"&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@-&&"
"&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::::@"
"-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::::"
"::@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::"
":::::@-&&&&&&&&&&&::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-::::::"
":::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-:::"
"::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&--:::::::-"
":::::::::::::::::::::::::::::-:::::::::::::::::@-&&&&&&&&&&&---&&:&"
"&&.0------------:-:::::::::::::::::::::::::::::-:::::::::::::::::@-"
"&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-::::::::::::::::"
":@-&&&&&&&&&&&--:::::::-:::::::::::::::::::::::::::::-:::::::::::::"
"::::@-&&&&&&&&&&&---&&:&&&.0------------O--------\rH PUTHTTP/1.1\r\n"
"&&&%%%");
}
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
Server svr;
@@ -2037,7 +2192,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
res.set_header("Cache-Control", "no-cache");
res.set_chunked_content_provider([](size_t offset, const DataSink &sink) {
char buffer[27];
int size = sprintf(buffer, "data:%ld\n\n", offset);
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
sink.write(buffer, size);
std::this_thread::sleep_for(std::chrono::seconds(1));
});
@@ -2113,6 +2268,34 @@ TEST(MountTest, Unmount) {
ASSERT_FALSE(svr.is_running());
}
TEST(ExceptionTest, ThrowExceptionInHandler) {
Server svr;
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
throw std::runtime_error("exception...");
res.set_content("Hello World!", "text/plain");
});
auto listen_thread = std::thread([&svr]() { svr.listen("localhost", PORT); });
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Give GET time to get a few messages.
std::this_thread::sleep_for(std::chrono::seconds(1));
Client cli("localhost", PORT);
auto res = cli.Get("/hi");
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(500, res->status);
ASSERT_FALSE(res->has_header("EXCEPTION_WHAT"));
svr.stop();
listen_thread.join();
ASSERT_FALSE(svr.is_running());
}
class ServerTestWithAI_PASSIVE : public ::testing::Test {
protected:
ServerTestWithAI_PASSIVE()
@@ -2297,7 +2480,7 @@ TEST(SSLClientServerTest, ClientCertPresent) {
char name[BUFSIZ];
auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
name, sizeof(name));
common_name.assign(name, name_len);
common_name.assign(name, static_cast<size_t>(name_len));
}
EXPECT_EQ("Common Name", common_name);