| #include <httplib.h> |
| #include <signal.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include <atomic> |
| #include <chrono> |
| #include <future> |
| #include <memory> |
| #include <sstream> |
| #include <stdexcept> |
| #include <thread> |
| #include <type_traits> |
| |
| #define SERVER_CERT_FILE "./cert.pem" |
| #define SERVER_CERT2_FILE "./cert2.pem" |
| #define SERVER_PRIVATE_KEY_FILE "./key.pem" |
| #define CA_CERT_FILE "./ca-bundle.crt" |
| #define CLIENT_CA_CERT_FILE "./rootCA.cert.pem" |
| #define CLIENT_CA_CERT_DIR "." |
| #define CLIENT_CERT_FILE "./client.cert.pem" |
| #define CLIENT_PRIVATE_KEY_FILE "./client.key.pem" |
| #define SERVER_ENCRYPTED_CERT_FILE "./cert_encrypted.pem" |
| #define SERVER_ENCRYPTED_PRIVATE_KEY_FILE "./key_encrypted.pem" |
| #define SERVER_ENCRYPTED_PRIVATE_KEY_PASS "test123!" |
| |
| using namespace std; |
| using namespace httplib; |
| |
| const char *HOST = "localhost"; |
| const int PORT = 1234; |
| |
| const string LONG_QUERY_VALUE = string(25000, '@'); |
| const string LONG_QUERY_URL = "/long-query-value?key=" + LONG_QUERY_VALUE; |
| |
| const std::string JSON_DATA = "{\"hello\":\"world\"}"; |
| |
| const string LARGE_DATA = string(1024 * 1024 * 100, '@'); // 100MB |
| |
| MultipartFormData &get_file_value(MultipartFormDataItems &files, |
| const char *key) { |
| auto it = std::find_if( |
| files.begin(), files.end(), |
| [&](const MultipartFormData &file) { return file.name == key; }); |
| #ifdef CPPHTTPLIB_NO_EXCEPTIONS |
| return *it; |
| #else |
| if (it != files.end()) { return *it; } |
| throw std::runtime_error("invalid multipart form data name error"); |
| #endif |
| } |
| |
| TEST(ConstructorTest, MoveConstructible) { |
| EXPECT_FALSE(std::is_copy_constructible<Client>::value); |
| EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value); |
| } |
| |
| #ifdef _WIN32 |
| TEST(StartupTest, WSAStartup) { |
| WSADATA wsaData; |
| int ret = WSAStartup(0x0002, &wsaData); |
| ASSERT_EQ(0, ret); |
| } |
| #endif |
| |
| TEST(DecodeURLTest, PercentCharacter) { |
| EXPECT_EQ( |
| detail::decode_url( |
| R"(descrip=Gastos%20%C3%A1%C3%A9%C3%AD%C3%B3%C3%BA%C3%B1%C3%91%206)", |
| false), |
| R"(descrip=Gastos áéíóúñÑ 6)"); |
| } |
| |
| TEST(EncodeQueryParamTest, ParseUnescapedChararactersTest) { |
| string unescapedCharacters = "-_.!~*'()"; |
| |
| EXPECT_EQ(detail::encode_query_param(unescapedCharacters), "-_.!~*'()"); |
| } |
| |
| TEST(EncodeQueryParamTest, ParseReservedCharactersTest) { |
| string reservedCharacters = ";,/?:@&=+$"; |
| |
| EXPECT_EQ(detail::encode_query_param(reservedCharacters), |
| "%3B%2C%2F%3F%3A%40%26%3D%2B%24"); |
| } |
| |
| TEST(EncodeQueryParamTest, TestUTF8Characters) { |
| string chineseCharacters = "中国語"; |
| string russianCharacters = "дом"; |
| string brazilianCharacters = "óculos"; |
| |
| EXPECT_EQ(detail::encode_query_param(chineseCharacters), |
| "%E4%B8%AD%E5%9B%BD%E8%AA%9E"); |
| |
| EXPECT_EQ(detail::encode_query_param(russianCharacters), |
| "%D0%B4%D0%BE%D0%BC"); |
| |
| EXPECT_EQ(detail::encode_query_param(brazilianCharacters), "%C3%B3culos"); |
| } |
| |
| TEST(TrimTests, TrimStringTests) { |
| EXPECT_EQ("abc", detail::trim_copy("abc")); |
| EXPECT_EQ("abc", detail::trim_copy(" abc ")); |
| EXPECT_TRUE(detail::trim_copy("").empty()); |
| } |
| |
| TEST(SplitTest, ParseQueryString) { |
| string s = "key1=val1&key2=val2&key3=val3"; |
| Params dic; |
| |
| detail::split(s.c_str(), s.c_str() + s.size(), '&', |
| [&](const char *b, const char *e) { |
| string key, val; |
| detail::split(b, e, '=', [&](const char *b2, const char *e2) { |
| if (key.empty()) { |
| key.assign(b2, e2); |
| } else { |
| val.assign(b2, e2); |
| } |
| }); |
| dic.emplace(key, val); |
| }); |
| |
| EXPECT_EQ("val1", dic.find("key1")->second); |
| EXPECT_EQ("val2", dic.find("key2")->second); |
| EXPECT_EQ("val3", dic.find("key3")->second); |
| } |
| |
| TEST(SplitTest, ParseInvalidQueryTests) { |
| |
| { |
| string s = " "; |
| Params dict; |
| detail::parse_query_text(s, dict); |
| EXPECT_TRUE(dict.empty()); |
| } |
| |
| { |
| string s = " = ="; |
| Params dict; |
| detail::parse_query_text(s, dict); |
| EXPECT_TRUE(dict.empty()); |
| } |
| } |
| |
| TEST(ParseQueryTest, ParseQueryString) { |
| string s = "key1=val1&key2=val2&key3=val3"; |
| Params dic; |
| |
| detail::parse_query_text(s, dic); |
| |
| EXPECT_EQ("val1", dic.find("key1")->second); |
| EXPECT_EQ("val2", dic.find("key2")->second); |
| 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(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"); |
| EXPECT_STREQ("text/plain", val); |
| } |
| |
| TEST(GetHeaderValueTest, DefaultValueInt) { |
| Headers headers = {{"Dummy", "Dummy"}}; |
| auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100); |
| EXPECT_EQ(100ull, val); |
| } |
| |
| TEST(GetHeaderValueTest, RegularValue) { |
| Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}}; |
| auto val = detail::get_header_value(headers, "Content-Type", 0, "text/plain"); |
| EXPECT_STREQ("text/html", val); |
| } |
| |
| TEST(GetHeaderValueTest, RegularValueWithDifferentCase) { |
| Headers headers = {{"Content-Type", "text/html"}, {"Dummy", "Dummy"}}; |
| auto val = detail::get_header_value(headers, "content-type", 0, "text/plain"); |
| EXPECT_STREQ("text/html", val); |
| } |
| |
| TEST(GetHeaderValueTest, SetContent) { |
| Response res; |
| |
| res.set_content("html", "text/html"); |
| EXPECT_EQ("text/html", res.get_header_value("Content-Type")); |
| |
| res.set_content("text", "text/plain"); |
| EXPECT_EQ(1U, res.get_header_value_count("Content-Type")); |
| EXPECT_EQ("text/plain", res.get_header_value("Content-Type")); |
| } |
| |
| TEST(GetHeaderValueTest, RegularValueInt) { |
| Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}}; |
| auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0); |
| EXPECT_EQ(100ull, val); |
| } |
| |
| TEST(GetHeaderValueTest, Range) { |
| { |
| Headers headers = {make_range_header({{1, -1}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=1-", val); |
| } |
| |
| { |
| Headers headers = {make_range_header({{-1, 1}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=-1", val); |
| } |
| |
| { |
| Headers headers = {make_range_header({{1, 10}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=1-10", val); |
| } |
| |
| { |
| Headers headers = {make_range_header({{1, 10}, {100, -1}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=1-10, 100-", val); |
| } |
| |
| { |
| Headers headers = {make_range_header({{1, 10}, {100, 200}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=1-10, 100-200", val); |
| } |
| |
| { |
| Headers headers = {make_range_header({{0, 0}, {-1, 1}})}; |
| auto val = detail::get_header_value(headers, "Range", 0, 0); |
| EXPECT_STREQ("bytes=0-0, -1", val); |
| } |
| } |
| |
| TEST(ParseHeaderValueTest, Range) { |
| { |
| Ranges ranges; |
| auto ret = detail::parse_range_header("bytes=1-", ranges); |
| EXPECT_TRUE(ret); |
| EXPECT_EQ(1u, ranges.size()); |
| EXPECT_EQ(1u, ranges[0].first); |
| EXPECT_EQ(-1, ranges[0].second); |
| } |
| |
| { |
| Ranges ranges; |
| auto ret = detail::parse_range_header("bytes=-1", ranges); |
| EXPECT_TRUE(ret); |
| EXPECT_EQ(1u, ranges.size()); |
| EXPECT_EQ(-1, ranges[0].first); |
| EXPECT_EQ(1u, ranges[0].second); |
| } |
| |
| { |
| Ranges ranges; |
| auto ret = detail::parse_range_header("bytes=1-10", ranges); |
| EXPECT_TRUE(ret); |
| EXPECT_EQ(1u, ranges.size()); |
| EXPECT_EQ(1u, ranges[0].first); |
| EXPECT_EQ(10u, ranges[0].second); |
| } |
| |
| { |
| Ranges ranges; |
| auto ret = detail::parse_range_header("bytes=10-1", ranges); |
| EXPECT_FALSE(ret); |
| } |
| |
| { |
| Ranges ranges; |
| auto ret = detail::parse_range_header("bytes=1-10, 100-", ranges); |
| EXPECT_TRUE(ret); |
| EXPECT_EQ(2u, ranges.size()); |
| EXPECT_EQ(1u, ranges[0].first); |
| EXPECT_EQ(10u, ranges[0].second); |
| EXPECT_EQ(100u, ranges[1].first); |
| EXPECT_EQ(-1, ranges[1].second); |
| } |
| |
| { |
| Ranges ranges; |
| auto ret = |
| detail::parse_range_header("bytes=1-10, 100-200, 300-400", ranges); |
| EXPECT_TRUE(ret); |
| EXPECT_EQ(3u, ranges.size()); |
| EXPECT_EQ(1u, ranges[0].first); |
| EXPECT_EQ(10u, ranges[0].second); |
| EXPECT_EQ(100u, ranges[1].first); |
| EXPECT_EQ(200u, ranges[1].second); |
| EXPECT_EQ(300u, ranges[2].first); |
| EXPECT_EQ(400u, ranges[2].second); |
| } |
| } |
| |
| TEST(ParseAcceptEncoding1, AcceptEncoding) { |
| Request req; |
| req.set_header("Accept-Encoding", "gzip"); |
| |
| Response res; |
| res.set_header("Content-Type", "text/plain"); |
| |
| auto ret = detail::encoding_type(req, res); |
| |
| #ifdef CPPHTTPLIB_ZLIB_SUPPORT |
| EXPECT_TRUE(ret == detail::EncodingType::Gzip); |
| #else |
| EXPECT_TRUE(ret == detail::EncodingType::None); |
| #endif |
| } |
| |
| TEST(ParseAcceptEncoding2, AcceptEncoding) { |
| Request req; |
| req.set_header("Accept-Encoding", "gzip, deflate, br"); |
| |
| Response res; |
| res.set_header("Content-Type", "text/plain"); |
| |
| auto ret = detail::encoding_type(req, res); |
| |
| #ifdef CPPHTTPLIB_BROTLI_SUPPORT |
| EXPECT_TRUE(ret == detail::EncodingType::Brotli); |
| #elif CPPHTTPLIB_ZLIB_SUPPORT |
| EXPECT_TRUE(ret == detail::EncodingType::Gzip); |
| #else |
| EXPECT_TRUE(ret == detail::EncodingType::None); |
| #endif |
| } |
| |
| TEST(ParseAcceptEncoding3, AcceptEncoding) { |
| Request req; |
| req.set_header("Accept-Encoding", "br;q=1.0, gzip;q=0.8, *;q=0.1"); |
| |
| Response res; |
| res.set_header("Content-Type", "text/plain"); |
| |
| auto ret = detail::encoding_type(req, res); |
| |
| #ifdef CPPHTTPLIB_BROTLI_SUPPORT |
| EXPECT_TRUE(ret == detail::EncodingType::Brotli); |
| #elif CPPHTTPLIB_ZLIB_SUPPORT |
| EXPECT_TRUE(ret == detail::EncodingType::Gzip); |
| #else |
| EXPECT_TRUE(ret == detail::EncodingType::None); |
| #endif |
| } |
| |
| TEST(BufferStreamTest, read) { |
| detail::BufferStream strm1; |
| Stream &strm = strm1; |
| |
| EXPECT_EQ(5, strm.write("hello")); |
| |
| char buf[512]; |
| EXPECT_EQ(2, strm.read(buf, 2)); |
| EXPECT_EQ('h', buf[0]); |
| EXPECT_EQ('e', buf[1]); |
| |
| EXPECT_EQ(2, strm.read(buf, 2)); |
| EXPECT_EQ('l', buf[0]); |
| EXPECT_EQ('l', buf[1]); |
| |
| EXPECT_EQ(1, strm.read(buf, 1)); |
| EXPECT_EQ('o', buf[0]); |
| |
| EXPECT_EQ(0, strm.read(buf, 1)); |
| } |
| |
| TEST(ChunkedEncodingTest, FromHTTPWatch_Online) { |
| auto host = "www.httpwatch.com"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(2); |
| |
| auto res = |
| cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137"); |
| ASSERT_TRUE(res); |
| |
| std::string out; |
| detail::read_file("./image.jpg", out); |
| |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ(out, res->body); |
| } |
| |
| TEST(HostnameToIPConversionTest, HTTPWatch_Online) { |
| auto host = "www.httpwatch.com"; |
| |
| auto ip = hosted_at(host); |
| EXPECT_EQ("23.96.13.243", ip); |
| |
| std::vector<std::string> addrs; |
| hosted_at(host, addrs); |
| EXPECT_EQ(1u, addrs.size()); |
| } |
| |
| #if 0 // It depends on each test environment... |
| TEST(HostnameToIPConversionTest, YouTube_Online) { |
| auto host = "www.youtube.com"; |
| |
| std::vector<std::string> addrs; |
| hosted_at(host, addrs); |
| |
| EXPECT_EQ(20u, addrs.size()); |
| |
| auto it = std::find(addrs.begin(), addrs.end(), "2607:f8b0:4006:809::200e"); |
| EXPECT_TRUE(it != addrs.end()); |
| } |
| #endif |
| |
| TEST(ChunkedEncodingTest, WithContentReceiver_Online) { |
| auto host = "www.httpwatch.com"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(2); |
| |
| std::string body; |
| auto res = |
| cli.Get("/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137", |
| [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| ASSERT_TRUE(res); |
| |
| std::string out; |
| detail::read_file("./image.jpg", out); |
| |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ(out, body); |
| } |
| |
| TEST(ChunkedEncodingTest, WithResponseHandlerAndContentReceiver_Online) { |
| auto host = "www.httpwatch.com"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(2); |
| |
| std::string body; |
| auto res = cli.Get( |
| "/httpgallery/chunked/chunkedimage.aspx?0.4153841143030137", |
| [&](const Response &response) { |
| EXPECT_EQ(200, response.status); |
| return true; |
| }, |
| [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| ASSERT_TRUE(res); |
| |
| std::string out; |
| detail::read_file("./image.jpg", out); |
| |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ(out, body); |
| } |
| |
| TEST(RangeTest, FromHTTPBin_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto path = std::string{"/range/32"}; |
| #else |
| auto host = "nghttp2.org"; |
| auto path = std::string{"/httpbin/range/32"}; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(5); |
| |
| { |
| auto res = cli.Get(path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| { |
| Headers headers = {make_range_header({{1, -1}})}; |
| auto res = cli.Get(path, headers); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("bcdefghijklmnopqrstuvwxyzabcdef", res->body); |
| EXPECT_EQ(206, res->status); |
| } |
| |
| { |
| Headers headers = {make_range_header({{1, 10}})}; |
| auto res = cli.Get(path, headers); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("bcdefghijk", res->body); |
| EXPECT_EQ(206, res->status); |
| } |
| |
| { |
| Headers headers = {make_range_header({{0, 31}})}; |
| auto res = cli.Get(path, headers); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| { |
| Headers headers = {make_range_header({{0, -1}})}; |
| auto res = cli.Get(path, headers); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| { |
| Headers headers = {make_range_header({{0, 32}})}; |
| auto res = cli.Get(path, headers); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(416, res->status); |
| } |
| } |
| |
| TEST(ConnectionErrorTest, InvalidHost) { |
| auto host = "-abcde.com"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(2)); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Connection, res.error()); |
| } |
| |
| TEST(ConnectionErrorTest, InvalidHost2) { |
| auto host = "httpbin.org/"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(2)); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Connection, res.error()); |
| } |
| |
| TEST(ConnectionErrorTest, InvalidHostCheckResultErrorToString) { |
| auto host = "httpbin.org/"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(2)); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| stringstream s; |
| s << "error code: " << res.error(); |
| EXPECT_EQ("error code: Could not establish connection (2)", s.str()); |
| } |
| |
| TEST(ConnectionErrorTest, InvalidPort) { |
| auto host = "localhost"; |
| auto port = 44380; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host, port); |
| #else |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(2)); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| EXPECT_TRUE(Error::Connection == res.error() || |
| Error::ConnectionTimeout == res.error()); |
| } |
| |
| TEST(ConnectionErrorTest, Timeout_Online) { |
| auto host = "google.com"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 44380; |
| SSLClient cli(host, port); |
| #else |
| auto port = 8080; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(2)); |
| |
| // only probe one address type so that the error reason |
| // correlates to the timed-out IPv4, not the unsupported |
| // IPv6 connection attempt |
| cli.set_address_family(AF_INET); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::ConnectionTimeout, res.error()); |
| } |
| |
| TEST(CancelTest, NoCancel_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto path = std::string{"/range/32"}; |
| #else |
| auto host = "nghttp2.org"; |
| auto path = std::string{"/httpbin/range/32"}; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(5)); |
| |
| auto res = cli.Get(path, [](uint64_t, uint64_t) { return true; }); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyzabcdef", res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(CancelTest, WithCancelSmallPayload_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto path = std::string{"/range/32"}; |
| #else |
| auto host = "nghttp2.org"; |
| auto path = std::string{"/httpbin/range/32"}; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| |
| auto res = cli.Get(path, [](uint64_t, uint64_t) { return false; }); |
| cli.set_connection_timeout(std::chrono::seconds(5)); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Canceled, res.error()); |
| } |
| |
| TEST(CancelTest, WithCancelLargePayload_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto path = std::string{"/range/65536"}; |
| #else |
| auto host = "nghttp2.org"; |
| auto path = std::string{"/httpbin/range/65536"}; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| cli.set_connection_timeout(std::chrono::seconds(5)); |
| |
| uint32_t count = 0; |
| auto res = |
| cli.Get(path, [&count](uint64_t, uint64_t) { return (count++ == 0); }); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Canceled, res.error()); |
| } |
| |
| TEST(BaseAuthTest, FromHTTPWatch_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto path = std::string{"/basic-auth/hello/world"}; |
| #else |
| auto host = "nghttp2.org"; |
| auto path = std::string{"/httpbin/basic-auth/hello/world"}; |
| #endif |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| auto port = 443; |
| SSLClient cli(host, port); |
| #else |
| auto port = 80; |
| Client cli(host, port); |
| #endif |
| |
| { |
| auto res = cli.Get(path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(401, res->status); |
| } |
| |
| { |
| auto res = |
| cli.Get(path, {make_basic_authentication_header("hello", "world")}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", |
| res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| { |
| cli.set_basic_auth("hello", "world"); |
| auto res = cli.Get(path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", |
| res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| { |
| cli.set_basic_auth("hello", "bad"); |
| auto res = cli.Get(path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(401, res->status); |
| } |
| |
| { |
| cli.set_basic_auth("bad", "world"); |
| auto res = cli.Get(path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(401, res->status); |
| } |
| } |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| TEST(DigestAuthTest, FromHTTPWatch_Online) { |
| #ifdef CPPHTTPLIB_DEFAULT_HTTPBIN |
| auto host = "httpbin.org"; |
| auto unauth_path = std::string{"/digest-auth/auth/hello/world"}; |
| auto paths = std::vector<std::string>{ |
| "/digest-auth/auth/hello/world/MD5", |
| "/digest-auth/auth/hello/world/SHA-256", |
| "/digest-auth/auth/hello/world/SHA-512", |
| "/digest-auth/auth-int/hello/world/MD5", |
| }; |
| #else |
| auto host = "nghttp2.org"; |
| auto unauth_path = std::string{"/httpbin/digest-auth/auth/hello/world"}; |
| auto paths = std::vector<std::string>{ |
| "/httpbin/digest-auth/auth/hello/world/MD5", |
| "/httpbin/digest-auth/auth/hello/world/SHA-256", |
| "/httpbin/digest-auth/auth/hello/world/SHA-512", |
| "/httpbin/digest-auth/auth-int/hello/world/MD5", |
| }; |
| #endif |
| |
| auto port = 443; |
| SSLClient cli(host, port); |
| |
| { |
| auto res = cli.Get(unauth_path); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(401, res->status); |
| } |
| |
| { |
| |
| cli.set_digest_auth("hello", "world"); |
| for (const auto &path : paths) { |
| auto res = cli.Get(path.c_str()); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n", |
| res->body); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| cli.set_digest_auth("hello", "bad"); |
| for (const auto &path : paths) { |
| auto res = cli.Get(path.c_str()); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(401, res->status); |
| } |
| |
| // NOTE: Until httpbin.org fixes issue #46, the following test is commented |
| // out. Please see https://httpbin.org/digest-auth/auth/hello/world |
| // cli.set_digest_auth("bad", "world"); |
| // for (const auto& path : paths) { |
| // auto res = cli.Get(path.c_str()); |
| // ASSERT_TRUE(res); |
| // EXPECT_EQ(400, res->status); |
| // } |
| } |
| } |
| #endif |
| |
| TEST(SpecifyServerIPAddressTest, AnotherHostname_Online) { |
| auto host = "google.com"; |
| auto another_host = "example.com"; |
| auto wrong_ip = "0.0.0.0"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_hostname_addr_map({{another_host, wrong_ip}}); |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(301, res->status); |
| } |
| |
| TEST(SpecifyServerIPAddressTest, RealHostname_Online) { |
| auto host = "google.com"; |
| auto wrong_ip = "0.0.0.0"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_hostname_addr_map({{host, wrong_ip}}); |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Connection, res.error()); |
| } |
| |
| TEST(AbsoluteRedirectTest, Redirect_Online) { |
| auto host = "nghttp2.org"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_follow_location(true); |
| auto res = cli.Get("/httpbin/absolute-redirect/3"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(RedirectTest, Redirect_Online) { |
| auto host = "nghttp2.org"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_follow_location(true); |
| auto res = cli.Get("/httpbin/redirect/3"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(RelativeRedirectTest, Redirect_Online) { |
| auto host = "nghttp2.org"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_follow_location(true); |
| auto res = cli.Get("/httpbin/relative-redirect/3"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(TooManyRedirectTest, Redirect_Online) { |
| auto host = "nghttp2.org"; |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(host); |
| #else |
| Client cli(host); |
| #endif |
| |
| cli.set_follow_location(true); |
| auto res = cli.Get("/httpbin/redirect/21"); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::ExceedRedirectCount, res.error()); |
| } |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| TEST(YahooRedirectTest, Redirect_Online) { |
| Client cli("yahoo.com"); |
| |
| auto res = cli.Get("/"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(301, res->status); |
| |
| cli.set_follow_location(true); |
| res = cli.Get("/"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("https://www.yahoo.com/", res->location); |
| } |
| |
| TEST(HttpsToHttpRedirectTest, Redirect_Online) { |
| SSLClient cli("nghttp2.org"); |
| cli.set_follow_location(true); |
| auto res = cli.Get( |
| "/httpbin/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(HttpsToHttpRedirectTest2, Redirect_Online) { |
| SSLClient cli("nghttp2.org"); |
| cli.set_follow_location(true); |
| |
| Params params; |
| params.emplace("url", "http://www.google.com"); |
| params.emplace("status_code", "302"); |
| |
| auto res = cli.Get("/httpbin/redirect-to", params, Headers{}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(HttpsToHttpRedirectTest3, Redirect_Online) { |
| SSLClient cli("nghttp2.org"); |
| cli.set_follow_location(true); |
| |
| Params params; |
| params.emplace("url", "http://www.google.com"); |
| |
| auto res = cli.Get("/httpbin/redirect-to?status_code=302", params, Headers{}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST(UrlWithSpace, Redirect_Online) { |
| SSLClient cli("edge.forgecdn.net"); |
| cli.set_follow_location(true); |
| |
| auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ(18527U, res->get_header_value_u64("Content-Length")); |
| } |
| |
| #endif |
| |
| #if !defined(_WIN32) && !defined(_WIN64) |
| TEST(ReceiveSignals, Signal) { |
| auto setupSignalHandlers = []() { |
| struct sigaction act; |
| |
| sigemptyset(&act.sa_mask); |
| act.sa_flags = SA_SIGINFO; |
| act.sa_sigaction = [](int sig, siginfo_t *, void *) { |
| switch (sig) { |
| case SIGINT: |
| default: break; |
| } |
| }; |
| ::sigaction(SIGINT, &act, nullptr); |
| }; |
| |
| Server svr; |
| int port = 0; |
| auto thread = std::thread([&]() { |
| setupSignalHandlers(); |
| port = svr.bind_to_any_port("localhost"); |
| svr.listen_after_bind(); |
| }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| ASSERT_TRUE(svr.is_running()); |
| pthread_kill(thread.native_handle(), SIGINT); |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| ASSERT_TRUE(svr.is_running()); |
| } |
| #endif |
| |
| TEST(RedirectToDifferentPort, Redirect) { |
| Server svr1; |
| svr1.Get("/1", [&](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!", "text/plain"); |
| }); |
| |
| int svr1_port = 0; |
| auto thread1 = std::thread([&]() { |
| svr1_port = svr1.bind_to_any_port("localhost"); |
| svr1.listen_after_bind(); |
| }); |
| |
| Server svr2; |
| svr2.Get("/2", [&](const Request & /*req*/, Response &res) { |
| res.set_redirect("http://localhost:" + std::to_string(svr1_port) + "/1"); |
| }); |
| |
| int svr2_port = 0; |
| auto thread2 = std::thread([&]() { |
| svr2_port = svr2.bind_to_any_port("localhost"); |
| svr2.listen_after_bind(); |
| }); |
| auto se = detail::scope_exit([&] { |
| svr2.stop(); |
| thread2.join(); |
| svr1.stop(); |
| thread1.join(); |
| ASSERT_FALSE(svr2.is_running()); |
| ASSERT_FALSE(svr1.is_running()); |
| }); |
| |
| svr1.wait_until_ready(); |
| svr2.wait_until_ready(); |
| |
| Client cli("localhost", svr2_port); |
| cli.set_follow_location(true); |
| |
| auto res = cli.Get("/2"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", res->body); |
| } |
| |
| TEST(RedirectFromPageWithContent, Redirect) { |
| Server svr; |
| |
| svr.Get("/1", [&](const Request & /*req*/, Response &res) { |
| res.set_content("___", "text/plain"); |
| res.set_redirect("/2"); |
| }); |
| |
| svr.Get("/2", [&](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!", "text/plain"); |
| }); |
| |
| auto th = std::thread([&]() { svr.listen("localhost", PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| th.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli("localhost", PORT); |
| cli.set_follow_location(true); |
| |
| std::string body; |
| auto res = cli.Get("/1", [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", body); |
| } |
| |
| { |
| Client cli("localhost", PORT); |
| |
| std::string body; |
| auto res = cli.Get("/1", [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(302, res->status); |
| EXPECT_EQ("___", body); |
| } |
| } |
| |
| TEST(RedirectFromPageWithContentIP6, Redirect) { |
| Server svr; |
| |
| svr.Get("/1", [&](const Request & /*req*/, Response &res) { |
| res.set_content("___", "text/plain"); |
| // res.set_redirect("/2"); |
| res.set_redirect("http://[::1]:1234/2"); |
| }); |
| |
| svr.Get("/2", [&](const Request &req, Response &res) { |
| auto host_header = req.headers.find("Host"); |
| ASSERT_TRUE(host_header != req.headers.end()); |
| EXPECT_EQ("[::1]:1234", host_header->second); |
| |
| res.set_content("Hello World!", "text/plain"); |
| }); |
| |
| auto th = std::thread([&]() { svr.listen("::1", 1234); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| th.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| // When IPV6 support isn't available svr.listen("::1", 1234) never |
| // actually starts anything, so the condition !svr.is_running() will |
| // always remain true, and the loop never stops. |
| // This basically counts how many milliseconds have passed since the |
| // call to svr.listen(), and if after 5 seconds nothing started yet |
| // aborts the test. |
| for (unsigned int milliseconds = 0; !svr.is_running(); milliseconds++) { |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| ASSERT_LT(milliseconds, 5000U); |
| } |
| |
| { |
| Client cli("http://[::1]:1234"); |
| cli.set_follow_location(true); |
| |
| std::string body; |
| auto res = cli.Get("/1", [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", body); |
| } |
| |
| { |
| Client cli("http://[::1]:1234"); |
| |
| std::string body; |
| auto res = cli.Get("/1", [&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(302, res->status); |
| EXPECT_EQ("___", body); |
| } |
| } |
| |
| TEST(PathUrlEncodeTest, PathUrlEncode) { |
| Server svr; |
| |
| svr.Get("/foo", [](const Request &req, Response &res) { |
| auto a = req.params.find("a"); |
| if (a != req.params.end()) { |
| res.set_content((*a).second, "text/plain"); |
| res.status = 200; |
| } else { |
| res.status = 400; |
| } |
| }); |
| |
| 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("/foo?a=explicitly+encoded"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| // This expects it back with a space, as the `+` won't have been |
| // url-encoded, and server-side the params get decoded turning `+` |
| // into spaces. |
| EXPECT_EQ("explicitly encoded", res->body); |
| } |
| } |
| |
| TEST(BindServerTest, DISABLED_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); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli("127.0.0.1", PORT); |
| |
| auto res = cli.Get("/1"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", res->body); |
| } |
| { |
| Client cli("::1", PORT); |
| |
| auto res = cli.Get("/1"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", res->body); |
| } |
| } |
| |
| TEST(BindServerTest, BindAndListenSeparately) { |
| Server svr; |
| 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(BindServerTest, BindAndListenSeparatelySSL) { |
| 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 |
| |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| TEST(BindServerTest, BindAndListenSeparatelySSLEncryptedKey) { |
| SSLServer svr(SERVER_ENCRYPTED_CERT_FILE, SERVER_ENCRYPTED_PRIVATE_KEY_FILE, |
| nullptr, nullptr, SERVER_ENCRYPTED_PRIVATE_KEY_PASS); |
| int port = svr.bind_to_any_port("0.0.0.0"); |
| ASSERT_TRUE(svr.is_valid()); |
| ASSERT_TRUE(port > 0); |
| svr.stop(); |
| } |
| #endif |
| |
| TEST(ErrorHandlerTest, ContentLength) { |
| Server svr; |
| |
| svr.set_error_handler([](const Request & /*req*/, Response &res) { |
| res.status = 200; |
| res.set_content("abcdefghijklmnopqrstuvwxyz", |
| "text/html"); // <= Content-Length still 13 |
| }); |
| |
| svr.Get("/hi", [](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!\n", "text/plain"); |
| res.status = 524; |
| }); |
| |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli(HOST, PORT); |
| |
| auto res = cli.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("26", res->get_header_value("Content-Length")); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body); |
| } |
| } |
| |
| #ifndef CPPHTTPLIB_NO_EXCEPTIONS |
| TEST(ExceptionHandlerTest, ContentLength) { |
| Server svr; |
| |
| svr.set_exception_handler([](const Request & /*req*/, Response &res, |
| std::exception_ptr ep) { |
| EXPECT_FALSE(ep == nullptr); |
| try { |
| std::rethrow_exception(ep); |
| } catch (std::exception &e) { EXPECT_EQ("abc", std::string(e.what())); } |
| res.status = 500; |
| res.set_content("abcdefghijklmnopqrstuvwxyz", |
| "text/html"); // <= Content-Length still 13 at this point |
| }); |
| |
| svr.Get("/hi", [](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!\n", "text/plain"); |
| throw std::runtime_error("abc"); |
| }); |
| |
| 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(); |
| |
| for (size_t i = 0; i < 10; i++) { |
| Client cli(HOST, PORT); |
| |
| for (size_t j = 0; j < 100; j++) { |
| auto res = cli.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(500, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("26", res->get_header_value("Content-Length")); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body); |
| } |
| |
| cli.set_keep_alive(true); |
| |
| for (size_t j = 0; j < 100; j++) { |
| auto res = cli.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(500, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("26", res->get_header_value("Content-Length")); |
| EXPECT_EQ("abcdefghijklmnopqrstuvwxyz", res->body); |
| } |
| } |
| } |
| #endif |
| |
| TEST(NoContentTest, ContentLength) { |
| Server svr; |
| |
| svr.Get("/hi", |
| [](const Request & /*req*/, Response &res) { res.status = 204; }); |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli(HOST, PORT); |
| |
| auto res = cli.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(204, res->status); |
| EXPECT_EQ("0", res->get_header_value("Content-Length")); |
| } |
| } |
| |
| TEST(RoutingHandlerTest, PreRoutingHandler) { |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE); |
| ASSERT_TRUE(svr.is_valid()); |
| #else |
| Server svr; |
| #endif |
| |
| svr.set_pre_routing_handler([](const Request &req, Response &res) { |
| if (req.path == "/routing_handler") { |
| res.set_header("PRE_ROUTING", "on"); |
| res.set_content("Routing Handler", "text/plain"); |
| return httplib::Server::HandlerResponse::Handled; |
| } |
| return httplib::Server::HandlerResponse::Unhandled; |
| }); |
| |
| svr.set_error_handler([](const Request & /*req*/, Response &res) { |
| res.set_content("Error", "text/html"); |
| }); |
| |
| svr.set_post_routing_handler([](const Request &req, Response &res) { |
| if (req.path == "/routing_handler") { |
| res.set_header("POST_ROUTING", "on"); |
| } |
| }); |
| |
| svr.Get("/hi", [](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!\n", "text/plain"); |
| }); |
| |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(HOST, PORT); |
| cli.enable_server_certificate_verification(false); |
| #else |
| Client cli(HOST, PORT); |
| #endif |
| |
| auto res = cli.Get("/routing_handler"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Routing Handler", res->body); |
| EXPECT_EQ(1U, res->get_header_value_count("PRE_ROUTING")); |
| EXPECT_EQ("on", res->get_header_value("PRE_ROUTING")); |
| EXPECT_EQ(1U, res->get_header_value_count("POST_ROUTING")); |
| EXPECT_EQ("on", res->get_header_value("POST_ROUTING")); |
| } |
| |
| { |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(HOST, PORT); |
| cli.enable_server_certificate_verification(false); |
| #else |
| Client cli(HOST, PORT); |
| #endif |
| |
| auto res = cli.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!\n", res->body); |
| EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING")); |
| EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING")); |
| } |
| |
| { |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli(HOST, PORT); |
| cli.enable_server_certificate_verification(false); |
| #else |
| Client cli(HOST, PORT); |
| #endif |
| |
| auto res = cli.Get("/aaa"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| EXPECT_EQ("Error", res->body); |
| EXPECT_EQ(0U, res->get_header_value_count("PRE_ROUTING")); |
| EXPECT_EQ(0U, res->get_header_value_count("POST_ROUTING")); |
| } |
| } |
| |
| TEST(InvalidFormatTest, StatusCode) { |
| Server svr; |
| |
| svr.Get("/hi", [](const Request & /*req*/, Response &res) { |
| res.set_content("Hello World!\n", "text/plain"); |
| res.status = 9999; // Status should be a three-digit code... |
| }); |
| |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli(HOST, PORT); |
| |
| auto res = cli.Get("/hi"); |
| ASSERT_FALSE(res); |
| } |
| } |
| |
| TEST(URLFragmentTest, WithFragment) { |
| Server svr; |
| |
| svr.Get("/hi", [](const Request &req, Response & /*res*/) { |
| EXPECT_TRUE(req.target == "/hi"); |
| }); |
| |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli(HOST, PORT); |
| |
| auto res = cli.Get("/hi#key1=val1=key2=val2"); |
| EXPECT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| |
| res = cli.Get("/hi%23key1=val1=key2=val2"); |
| EXPECT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| } |
| |
| TEST(HeaderWriter, SetHeaderWriter) { |
| Server svr; |
| |
| svr.set_header_writer([](Stream &strm, Headers &hdrs) { |
| hdrs.emplace("CustomServerHeader", "CustomServerValue"); |
| return detail::write_headers(strm, hdrs); |
| }); |
| svr.Get("/hi", [](const Request &req, Response &res) { |
| auto it = req.headers.find("CustomClientHeader"); |
| EXPECT_TRUE(it != req.headers.end()); |
| EXPECT_EQ(it->second, "CustomClientValue"); |
| res.set_content("Hello World!\n", "text/plain"); |
| }); |
| |
| auto thread = std::thread([&]() { svr.listen(HOST, PORT); }); |
| auto se = detail::scope_exit([&] { |
| svr.stop(); |
| thread.join(); |
| ASSERT_FALSE(svr.is_running()); |
| }); |
| |
| svr.wait_until_ready(); |
| |
| { |
| Client cli(HOST, PORT); |
| cli.set_header_writer([](Stream &strm, Headers &hdrs) { |
| hdrs.emplace("CustomClientHeader", "CustomClientValue"); |
| return detail::write_headers(strm, hdrs); |
| }); |
| |
| auto res = cli.Get("/hi"); |
| EXPECT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| |
| auto it = res->headers.find("CustomServerHeader"); |
| EXPECT_TRUE(it != res->headers.end()); |
| EXPECT_EQ(it->second, "CustomServerValue"); |
| } |
| } |
| |
| class ServerTest : public ::testing::Test { |
| protected: |
| ServerTest() |
| : cli_(HOST, PORT) |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| , |
| svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE) |
| #endif |
| { |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| cli_.enable_server_certificate_verification(false); |
| #endif |
| } |
| |
| virtual void SetUp() { |
| svr_.set_mount_point("/", "./www"); |
| svr_.set_mount_point("/mount", "./www2"); |
| svr_.set_file_extension_and_mimetype_mapping("abcde", "text/abcde"); |
| |
| svr_.Get("/hi", |
| [&](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(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_header("a", "1\nSet-Cookie: a=1"); |
| EXPECT_EQ(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_header("a", "1\rSet-Cookie: a=1"); |
| EXPECT_EQ(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_header("a\r\nb", "0"); |
| EXPECT_EQ(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_header("a\rb", "0"); |
| EXPECT_EQ(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_header("a\nb", "0"); |
| EXPECT_EQ(0U, res.headers.size()); |
| EXPECT_FALSE(res.has_header("a")); |
| |
| res.set_redirect("1\r\nSet-Cookie: a=1"); |
| EXPECT_EQ(0U, 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)); |
| res.set_content("slow", "text/plain"); |
| }) |
| #if 0 |
| .Post("/slowpost", |
| [&](const Request & /*req*/, Response &res) { |
| std::this_thread::sleep_for(std::chrono::seconds(2)); |
| res.set_content("slow", "text/plain"); |
| }) |
| #endif |
| .Get("/remote_addr", |
| [&](const Request &req, Response &res) { |
| auto remote_addr = req.headers.find("REMOTE_ADDR")->second; |
| EXPECT_TRUE(req.has_header("REMOTE_PORT")); |
| EXPECT_EQ(req.remote_addr, req.get_header_value("REMOTE_ADDR")); |
| EXPECT_EQ(req.remote_port, |
| std::stoi(req.get_header_value("REMOTE_PORT"))); |
| res.set_content(remote_addr.c_str(), "text/plain"); |
| }) |
| .Get("/local_addr", |
| [&](const Request &req, Response &res) { |
| EXPECT_TRUE(req.has_header("LOCAL_PORT")); |
| EXPECT_TRUE(req.has_header("LOCAL_ADDR")); |
| auto local_addr = req.get_header_value("LOCAL_ADDR"); |
| auto local_port = req.get_header_value("LOCAL_PORT"); |
| EXPECT_EQ(req.local_addr, local_addr); |
| EXPECT_EQ(req.local_port, std::stoi(local_port)); |
| res.set_content(local_addr.append(":").append(local_port), |
| "text/plain"); |
| }) |
| .Get("/endwith%", |
| [&](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("/1", [](const Request & /*req*/, |
| Response &res) { res.set_redirect("/2", 303); }) |
| .Get("/2", |
| [](const Request & /*req*/, Response &res) { |
| res.set_content("redirected.", "text/plain"); |
| res.status = 200; |
| }) |
| .Post("/person", |
| [&](const Request &req, Response &res) { |
| if (req.has_param("name") && req.has_param("note")) { |
| persons_[req.get_param_value("name")] = |
| req.get_param_value("note"); |
| } else { |
| res.status = 400; |
| } |
| }) |
| .Put("/person", |
| [&](const Request &req, Response &res) { |
| if (req.has_param("name") && req.has_param("note")) { |
| persons_[req.get_param_value("name")] = |
| req.get_param_value("note"); |
| } else { |
| res.status = 400; |
| } |
| }) |
| .Get("/person/(.*)", |
| [&](const Request &req, Response &res) { |
| string name = req.matches[1]; |
| if (persons_.find(name) != persons_.end()) { |
| auto note = persons_[name]; |
| res.set_content(note, "text/plain"); |
| } else { |
| res.status = 404; |
| } |
| }) |
| .Post("/x-www-form-urlencoded-json", |
| [&](const Request &req, Response &res) { |
| auto json = req.get_param_value("json"); |
| ASSERT_EQ(JSON_DATA, json); |
| res.set_content(json, "appliation/json"); |
| res.status = 200; |
| }) |
| .Get("/streamed-chunked", |
| [&](const Request & /*req*/, Response &res) { |
| res.set_chunked_content_provider( |
| "text/plain", [](size_t /*offset*/, DataSink &sink) { |
| sink.os << "123"; |
| sink.os << "456"; |
| sink.os << "789"; |
| sink.done(); |
| return true; |
| }); |
| }) |
| .Get("/streamed-chunked2", |
| [&](const Request & /*req*/, Response &res) { |
| auto i = new int(0); |
| 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(); break; |
| } |
| (*i)++; |
| return true; |
| }, |
| [i](bool success) { |
| EXPECT_TRUE(success); |
| 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( |
| 6, "text/plain", |
| [](size_t offset, size_t /*length*/, DataSink &sink) { |
| sink.os << (offset < 3 ? "a" : "b"); |
| return true; |
| }); |
| }) |
| .Get("/streamed-with-range", |
| [&](const Request & /*req*/, Response &res) { |
| auto data = new std::string("abcdefg"); |
| res.set_content_provider( |
| data->size(), "text/plain", |
| [data](size_t offset, size_t length, DataSink &sink) { |
| size_t DATA_CHUNK_SIZE = 4; |
| const auto &d = *data; |
| auto out_len = |
| std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE); |
| auto ret = |
| sink.write(&d[static_cast<size_t>(offset)], out_len); |
| EXPECT_TRUE(ret); |
| return true; |
| }, |
| [data](bool success) { |
| EXPECT_TRUE(success); |
| delete data; |
| }); |
| }) |
| .Get("/streamed-cancel", |
| [&](const Request & /*req*/, Response &res) { |
| res.set_content_provider( |
| size_t(-1), "text/plain", |
| [](size_t /*offset*/, size_t /*length*/, DataSink &sink) { |
| sink.os << "data_chunk"; |
| return true; |
| }); |
| }) |
| .Get("/with-range", |
| [&](const Request & /*req*/, Response &res) { |
| res.set_content("abcdefg", "text/plain"); |
| }) |
| .Post("/chunked", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(req.body, "dechunked post body"); |
| }) |
| .Post("/large-chunked", |
| [&](const Request &req, Response & /*res*/) { |
| std::string expected(6 * 30 * 1024u, 'a'); |
| EXPECT_EQ(req.body, expected); |
| }) |
| .Post("/multipart", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(6u, req.files.size()); |
| ASSERT_TRUE(!req.has_file("???")); |
| ASSERT_TRUE(req.body.empty()); |
| |
| { |
| const auto &file = req.get_file_value("text1"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("text default", file.content); |
| } |
| |
| { |
| const auto &file = req.get_file_value("text2"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("aωb", file.content); |
| } |
| |
| { |
| const auto &file = req.get_file_value("file1"); |
| EXPECT_EQ("hello.txt", file.filename); |
| EXPECT_EQ("text/plain", file.content_type); |
| EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content); |
| } |
| |
| { |
| const auto &file = req.get_file_value("file3"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("application/octet-stream", file.content_type); |
| EXPECT_EQ(0u, file.content.size()); |
| } |
| |
| { |
| const auto &file = req.get_file_value("file4"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ(0u, file.content.size()); |
| 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(1u, text_value.size()); |
| 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(2u, text1_values.size()); |
| 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(2u, file1_values.size()); |
| 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, ""); |
| EXPECT_EQ("text/plain", req.get_header_value("Content-Type")); |
| EXPECT_EQ("0", req.get_header_value("Content-Length")); |
| res.set_content("empty", "text/plain"); |
| }) |
| .Post("/empty-no-content-type", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, ""); |
| EXPECT_FALSE(req.has_header("Content-Type")); |
| EXPECT_EQ("0", req.get_header_value("Content-Length")); |
| res.set_content("empty-no-content-type", "text/plain"); |
| }) |
| .Post("/path-only", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, ""); |
| EXPECT_EQ("", req.get_header_value("Content-Type")); |
| EXPECT_EQ("0", req.get_header_value("Content-Length")); |
| res.set_content("path-only", "text/plain"); |
| }) |
| .Post("/path-headers-only", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, ""); |
| EXPECT_EQ("", req.get_header_value("Content-Type")); |
| EXPECT_EQ("0", req.get_header_value("Content-Length")); |
| EXPECT_EQ("world", req.get_header_value("hello")); |
| EXPECT_EQ("world2", req.get_header_value("hello2")); |
| res.set_content("path-headers-only", "text/plain"); |
| }) |
| .Post("/post-large", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, LARGE_DATA); |
| res.set_content(req.body, "text/plain"); |
| }) |
| .Put("/empty-no-content-type", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, ""); |
| EXPECT_FALSE(req.has_header("Content-Type")); |
| EXPECT_EQ("0", req.get_header_value("Content-Length")); |
| res.set_content("empty-no-content-type", "text/plain"); |
| }) |
| .Put("/put", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, "PUT"); |
| res.set_content(req.body, "text/plain"); |
| }) |
| .Put("/put-large", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, LARGE_DATA); |
| res.set_content(req.body, "text/plain"); |
| }) |
| .Patch("/patch", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(req.body, "PATCH"); |
| res.set_content(req.body, "text/plain"); |
| }) |
| .Delete("/delete", |
| [&](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"); |
| }) |
| .Get("/request-target", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ("/request-target?aaa=bbb&ccc=ddd", req.target); |
| EXPECT_EQ("bbb", req.get_param_value("aaa")); |
| EXPECT_EQ("ddd", req.get_param_value("ccc")); |
| }) |
| .Get("/long-query-value", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(LONG_QUERY_URL, req.target); |
| EXPECT_EQ(LONG_QUERY_VALUE, req.get_param_value("key")); |
| }) |
| .Get("/array-param", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(3u, req.get_param_value_count("array")); |
| EXPECT_EQ("value1", req.get_param_value("array", 0)); |
| EXPECT_EQ("value2", req.get_param_value("array", 1)); |
| EXPECT_EQ("value3", req.get_param_value("array", 2)); |
| }) |
| .Post("/validate-no-multiple-headers", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(1u, req.get_header_value_count("Content-Length")); |
| EXPECT_EQ("5", req.get_header_value("Content-Length")); |
| }) |
| .Post("/content_receiver", |
| [&](const Request &req, Response &res, |
| const ContentReader &content_reader) { |
| if (req.is_multipart_form_data()) { |
| MultipartFormDataItems files; |
| content_reader( |
| [&](const MultipartFormData &file) { |
| files.push_back(file); |
| return true; |
| }, |
| [&](const char *data, size_t data_length) { |
| files.back().content.append(data, data_length); |
| return true; |
| }); |
| |
| EXPECT_EQ(5u, files.size()); |
| |
| { |
| const auto &file = get_file_value(files, "text1"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("text default", file.content); |
| } |
| |
| { |
| const auto &file = get_file_value(files, "text2"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("aωb", file.content); |
| } |
| |
| { |
| const auto &file = get_file_value(files, "file1"); |
| EXPECT_EQ("hello.txt", file.filename); |
| EXPECT_EQ("text/plain", file.content_type); |
| EXPECT_EQ("h\ne\n\nl\nl\no\n", file.content); |
| } |
| |
| { |
| const auto &file = get_file_value(files, "file3"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("application/octet-stream", file.content_type); |
| EXPECT_EQ(0u, file.content.size()); |
| } |
| } else { |
| std::string body; |
| content_reader([&](const char *data, size_t data_length) { |
| EXPECT_EQ(7U, data_length); |
| body.append(data, data_length); |
| return true; |
| }); |
| EXPECT_EQ(body, "content"); |
| res.set_content(body, "text/plain"); |
| } |
| }) |
| .Put("/content_receiver", |
| [&](const Request & /*req*/, Response &res, |
| const ContentReader &content_reader) { |
| std::string body; |
| content_reader([&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| EXPECT_EQ(body, "content"); |
| res.set_content(body, "text/plain"); |
| }) |
| .Patch("/content_receiver", |
| [&](const Request & /*req*/, Response &res, |
| const ContentReader &content_reader) { |
| std::string body; |
| content_reader([&](const char *data, size_t data_length) { |
| body.append(data, data_length); |
| return true; |
| }); |
| EXPECT_EQ(body, "content"); |
| res.set_content(body, "text/plain"); |
| }) |
| .Post("/query-string-and-body", |
| [&](const Request &req, Response & /*res*/) { |
| ASSERT_TRUE(req.has_param("key")); |
| EXPECT_EQ(req.get_param_value("key"), "value"); |
| EXPECT_EQ(req.body, "content"); |
| }) |
| .Get("/last-request", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ("close", req.get_header_value("Connection")); |
| }) |
| .Get(R"(/redirect/(\d+))", |
| [&](const Request &req, Response &res) { |
| auto num = std::stoi(req.matches[1]) + 1; |
| std::string url = "/redirect/" + std::to_string(num); |
| res.set_redirect(url); |
| }) |
| .Post("/binary", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(4U, req.body.size()); |
| EXPECT_EQ("application/octet-stream", |
| req.get_header_value("Content-Type")); |
| EXPECT_EQ("4", req.get_header_value("Content-Length")); |
| res.set_content(req.body, "application/octet-stream"); |
| }) |
| .Put("/binary", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(4U, req.body.size()); |
| EXPECT_EQ("application/octet-stream", |
| req.get_header_value("Content-Type")); |
| EXPECT_EQ("4", req.get_header_value("Content-Length")); |
| res.set_content(req.body, "application/octet-stream"); |
| }) |
| .Patch("/binary", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(4U, req.body.size()); |
| EXPECT_EQ("application/octet-stream", |
| req.get_header_value("Content-Type")); |
| EXPECT_EQ("4", req.get_header_value("Content-Length")); |
| res.set_content(req.body, "application/octet-stream"); |
| }) |
| .Delete("/binary", |
| [&](const Request &req, Response &res) { |
| EXPECT_EQ(4U, req.body.size()); |
| EXPECT_EQ("application/octet-stream", |
| req.get_header_value("Content-Type")); |
| EXPECT_EQ("4", req.get_header_value("Content-Length")); |
| res.set_content(req.body, "application/octet-stream"); |
| }) |
| #if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT) |
| .Get("/compress", |
| [&](const Request & /*req*/, Response &res) { |
| res.set_content( |
| "12345678901234567890123456789012345678901234567890123456789" |
| "01234567890123456789012345678901234567890", |
| "text/plain"); |
| }) |
| .Get("/nocompress", |
| [&](const Request & /*req*/, Response &res) { |
| res.set_content( |
| "12345678901234567890123456789012345678901234567890123456789" |
| "01234567890123456789012345678901234567890", |
| "application/octet-stream"); |
| }) |
| .Post("/compress-multipart", |
| [&](const Request &req, Response & /*res*/) { |
| EXPECT_EQ(2u, req.files.size()); |
| ASSERT_TRUE(!req.has_file("???")); |
| |
| { |
| const auto &file = req.get_file_value("key1"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("test", file.content); |
| } |
| |
| { |
| const auto &file = req.get_file_value("key2"); |
| EXPECT_TRUE(file.filename.empty()); |
| EXPECT_EQ("--abcdefg123", file.content); |
| } |
| }) |
| #endif |
| ; |
| |
| persons_["john"] = "programmer"; |
| |
| t_ = thread([&]() { ASSERT_TRUE(svr_.listen(HOST, PORT)); }); |
| |
| svr_.wait_until_ready(); |
| } |
| |
| virtual void TearDown() { |
| svr_.stop(); |
| if (!request_threads_.empty()) { |
| std::this_thread::sleep_for(std::chrono::seconds(1)); |
| for (auto &t : request_threads_) { |
| t.join(); |
| } |
| } |
| t_.join(); |
| } |
| |
| map<string, string> persons_; |
| #ifdef CPPHTTPLIB_OPENSSL_SUPPORT |
| SSLClient cli_; |
| SSLServer svr_; |
| #else |
| Client cli_; |
| Server svr_; |
| #endif |
| thread t_; |
| std::vector<thread> request_threads_; |
| }; |
| |
| TEST_F(ServerTest, GetMethod200) { |
| auto res = cli_.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("HTTP/1.1", res->version); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("OK", res->reason); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ(1U, res->get_header_value_count("Content-Type")); |
| EXPECT_EQ("Hello World!", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethod200withPercentEncoding) { |
| auto res = cli_.Get("/%68%69"); // auto res = cli_.Get("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ("HTTP/1.1", res->version); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ(1U, res->get_header_value_count("Content-Type")); |
| EXPECT_EQ("Hello World!", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethod302) { |
| auto res = cli_.Get("/"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(302, res->status); |
| EXPECT_EQ("/hi", res->get_header_value("Location")); |
| } |
| |
| TEST_F(ServerTest, GetMethod302Redirect) { |
| cli_.set_follow_location(true); |
| auto res = cli_.Get("/"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("Hello World!", res->body); |
| EXPECT_EQ("/hi", res->location); |
| } |
| |
| TEST_F(ServerTest, GetMethod404) { |
| auto res = cli_.Get("/invalid"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, HeadMethod200) { |
| auto res = cli_.Head("/hi"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_TRUE(res->body.empty()); |
| } |
| |
| TEST_F(ServerTest, HeadMethod200Static) { |
| auto res = cli_.Head("/mount/dir/index.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ(104, std::stoi(res->get_header_value("Content-Length"))); |
| EXPECT_TRUE(res->body.empty()); |
| } |
| |
| TEST_F(ServerTest, HeadMethod404) { |
| auto res = cli_.Head("/invalid"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| EXPECT_TRUE(res->body.empty()); |
| } |
| |
| TEST_F(ServerTest, GetMethodPersonJohn) { |
| auto res = cli_.Get("/person/john"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ("programmer", res->body); |
| } |
| |
| TEST_F(ServerTest, PostMethod1) { |
| auto res = cli_.Get("/person/john1"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(404, res->status); |
| |
| res = cli_.Post("/person", "name=john1¬e=coder", |
| "application/x-www-form-urlencoded"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| |
| res = cli_.Get("/person/john1"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("text/plain", res->get_header_value("Content-Type")); |
| ASSERT_EQ("coder", res->body); |
| } |
| |
| TEST_F(ServerTest, PostMethod2) { |
| auto res = cli_.Get("/person/john2"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(404, res->status); |
| |
| Params params; |
| params.emplace("name", "john2"); |
| params.emplace("note", "coder"); |
| |
| res = cli_.Post("/person", params); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| |
| res = cli_.Get("/person/john2"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("text/plain", res->get_header_value("Content-Type")); |
| ASSERT_EQ("coder", res->body); |
| } |
| |
| TEST_F(ServerTest, PutMethod3) { |
| auto res = cli_.Get("/person/john3"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(404, res->status); |
| |
| Params params; |
| params.emplace("name", "john3"); |
| params.emplace("note", "coder"); |
| |
| res = cli_.Put("/person", params); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| |
| res = cli_.Get("/person/john3"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("text/plain", res->get_header_value("Content-Type")); |
| ASSERT_EQ("coder", res->body); |
| } |
| |
| TEST_F(ServerTest, PostWwwFormUrlEncodedJson) { |
| Params params; |
| params.emplace("json", JSON_DATA); |
| |
| auto res = cli_.Post("/x-www-form-urlencoded-json", params); |
| |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(JSON_DATA, res->body); |
| } |
| |
| TEST_F(ServerTest, PostEmptyContent) { |
| auto res = cli_.Post("/empty", "", "text/plain"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("empty", res->body); |
| } |
| |
| TEST_F(ServerTest, PostEmptyContentWithNoContentType) { |
| auto res = cli_.Post("/empty-no-content-type"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("empty-no-content-type", res->body); |
| } |
| |
| TEST_F(ServerTest, PostPathOnly) { |
| auto res = cli_.Post("/path-only"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("path-only", res->body); |
| } |
| |
| TEST_F(ServerTest, PostPathAndHeadersOnly) { |
| auto res = cli_.Post("/path-headers-only", |
| Headers({{"hello", "world"}, {"hello2", "world2"}})); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("path-headers-only", res->body); |
| } |
| |
| TEST_F(ServerTest, PostLarge) { |
| auto res = cli_.Post("/post-large", LARGE_DATA, "text/plain"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| EXPECT_EQ(LARGE_DATA, res->body); |
| } |
| |
| TEST_F(ServerTest, PutEmptyContentWithNoContentType) { |
| auto res = cli_.Put("/empty-no-content-type"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ("empty-no-content-type", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodDir) { |
| auto res = cli_.Get("/dir/"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| |
| auto body = R"(<html> |
| <head> |
| </head> |
| <body> |
| <a href="/dir/test.html">Test</a> |
| <a href="/hi">hi</a> |
| </body> |
| </html> |
| )"; |
| EXPECT_EQ(body, res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodDirTest) { |
| auto res = cli_.Get("/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("test.html", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodDirTestWithDoubleDots) { |
| auto res = cli_.Get("/dir/../dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("test.html", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodInvalidPath) { |
| auto res = cli_.Get("/dir/../test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, GetMethodOutOfBaseDir) { |
| auto res = cli_.Get("/../www/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, GetMethodOutOfBaseDir2) { |
| auto res = cli_.Get("/dir/../../www/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, GetMethodDirMountTest) { |
| auto res = cli_.Get("/mount/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("test.html", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodDirMountTestWithDoubleDots) { |
| auto res = cli_.Get("/mount/dir/../dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/html", res->get_header_value("Content-Type")); |
| EXPECT_EQ("test.html", res->body); |
| } |
| |
| TEST_F(ServerTest, GetMethodInvalidMountPath) { |
| auto res = cli_.Get("/mount/dir/../test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, GetMethodOutOfBaseDirMount) { |
| auto res = cli_.Get("/mount/../www2/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, GetMethodOutOfBaseDirMount2) { |
| auto res = cli_.Get("/mount/dir/../../www2/dir/test.html"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, PostMethod303) { |
| auto res = cli_.Post("/1", "body", "text/plain"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(303, res->status); |
| EXPECT_EQ("/2", res->get_header_value("Location")); |
| } |
| |
| TEST_F(ServerTest, PostMethod303Redirect) { |
| cli_.set_follow_location(true); |
| auto res = cli_.Post("/1", "body", "text/plain"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("redirected.", res->body); |
| EXPECT_EQ("/2", res->location); |
| } |
| |
| TEST_F(ServerTest, UserDefinedMIMETypeMapping) { |
| auto res = cli_.Get("/dir/test.abcde"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); |
| EXPECT_EQ("abcde", res->body); |
| } |
| |
| TEST_F(ServerTest, StaticFileRange) { |
| auto res = cli_.Get("/dir/test.abcde", {{make_range_header({{2, 3}})}}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(206, res->status); |
| EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); |
| EXPECT_EQ("2", res->get_header_value("Content-Length")); |
| EXPECT_EQ(true, res->has_header("Content-Range")); |
| EXPECT_EQ(std::string("cd"), res->body); |
| } |
| |
| TEST_F(ServerTest, StaticFileRanges) { |
| auto res = |
| cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(206, res->status); |
| EXPECT_TRUE( |
| res->get_header_value("Content-Type") |
| .find( |
| "multipart/byteranges; boundary=--cpp-httplib-multipart-data-") == |
| 0); |
| EXPECT_EQ("265", res->get_header_value("Content-Length")); |
| } |
| |
| TEST_F(ServerTest, StaticFileRangeHead) { |
| auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(206, res->status); |
| EXPECT_EQ("text/abcde", res->get_header_value("Content-Type")); |
| EXPECT_EQ("2", res->get_header_value("Content-Length")); |
| EXPECT_EQ(true, res->has_header("Content-Range")); |
| } |
| |
| TEST_F(ServerTest, StaticFileRangeBigFile) { |
| auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(206, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ("5", res->get_header_value("Content-Length")); |
| EXPECT_EQ(true, res->has_header("Content-Range")); |
| EXPECT_EQ("LAST\n", res->body); |
| } |
| |
| TEST_F(ServerTest, StaticFileRangeBigFile2) { |
| auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}}); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(206, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ("4097", res->get_header_value("Content-Length")); |
| EXPECT_EQ(true, res->has_header("Content-Range")); |
| } |
| |
| TEST_F(ServerTest, StaticFileBigFile) { |
| auto res = cli_.Get("/dir/1MB.txt"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("text/plain", res->get_header_value("Content-Type")); |
| EXPECT_EQ("1048576", res->get_header_value("Content-Length")); |
| } |
| |
| TEST_F(ServerTest, InvalidBaseDirMount) { |
| EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3")); |
| } |
| |
| TEST_F(ServerTest, Binary) { |
| std::vector<char> binary{0x00, 0x01, 0x02, 0x03}; |
| |
| auto res = cli_.Post("/binary", binary.data(), binary.size(), |
| "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Put("/binary", binary.data(), binary.size(), |
| "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Patch("/binary", binary.data(), binary.size(), |
| "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Delete("/binary", binary.data(), binary.size(), |
| "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| } |
| |
| TEST_F(ServerTest, BinaryString) { |
| auto binary = std::string("\x00\x01\x02\x03", 4); |
| |
| auto res = cli_.Post("/binary", binary, "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Put("/binary", binary, "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Patch("/binary", binary, "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| |
| res = cli_.Delete("/binary", binary, "application/octet-stream"); |
| ASSERT_TRUE(res); |
| ASSERT_EQ(200, res->status); |
| ASSERT_EQ(4U, res->body.size()); |
| } |
| |
| TEST_F(ServerTest, EmptyRequest) { |
| auto res = cli_.Get(""); |
| ASSERT_TRUE(!res); |
| EXPECT_EQ(Error::Connection, res.error()); |
| } |
| |
| TEST_F(ServerTest, LongRequest) { |
| std::string request; |
| for (size_t i = 0; i < 545; i++) { |
| request += "/TooLongRequest"; |
| } |
| request += "OK"; |
| |
| auto res = cli_.Get(request.c_str()); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, TooLongRequest) { |
| std::string request; |
| for (size_t i = 0; i < 545; i++) { |
| request += "/TooLongRequest"; |
| } |
| request += "_NG"; |
| |
| auto res = cli_.Get(request.c_str()); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(414, res->status); |
| } |
| |
| TEST_F(ServerTest, LongHeader) { |
| Request req; |
| req.method = "GET"; |
| req.path = "/hi"; |
| |
| std::string host_and_port; |
| host_and_port += HOST; |
| host_and_port += ":"; |
| host_and_port += std::to_string(PORT); |
| |
| req.headers.emplace("Host", host_and_port.c_str()); |
| req.headers.emplace("Accept", "*/*"); |
| req.headers.emplace("User-Agent", "cpp-httplib/0.1"); |
| |
| req.headers.emplace( |
| "Header-Name", |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@"); |
| |
| auto res = std::make_shared<Response>(); |
| auto error = Error::Success; |
| auto ret = cli_.send(req, *res, error); |
| |
| ASSERT_TRUE(ret); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST_F(ServerTest, LongQueryValue) { |
| auto res = cli_.Get(LONG_QUERY_URL.c_str()); |
| |
| ASSERT_TRUE(res); |
| EXPECT_EQ(414, res->status); |
| } |
| |
| TEST_F(ServerTest, TooLongHeader) { |
| Request req; |
| req.method = "GET"; |
| req.path = "/hi"; |
| |
| std::string host_and_port; |
| host_and_port += HOST; |
| host_and_port += ":"; |
| host_and_port += std::to_string(PORT); |
| |
| req.headers.emplace("Host", host_and_port.c_str()); |
| req.headers.emplace("Accept", "*/*"); |
| req.headers.emplace("User-Agent", "cpp-httplib/0.1"); |
| |
| req.headers.emplace( |
| "Header-Name", |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" |
| "@@@@@@@@@@@@@@@@@"); |
| |
| auto res = std::make_shared<Response>(); |
| auto error = Error::Success; |
| auto ret = cli_.send(req, *res, error); |
| |
| ASSERT_TRUE(ret); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST_F(ServerTest, PercentEncoding) { |
| auto res = cli_.Get("/e%6edwith%"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST_F(ServerTest, PercentEncodingUnicode) { |
| auto res = cli_.Get("/e%u006edwith%"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| } |
| |
| TEST_F(ServerTest, InvalidPercentEncoding) { |
| auto res = cli_.Get("/%endwith%"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, InvalidPercentEncodingUnicode) { |
| auto res = cli_.Get("/%uendwith%"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, EndWithPercentCharacterInQuery) { |
| auto res = cli_.Get("/hello?aaa=bbb%"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(404, res->status); |
| } |
| |
| TEST_F(ServerTest, PlusSignEncoding) { |
| auto res = cli_.Get("/a+%2Bb?a %2bb=a %2Bb"); |
| ASSERT_TRUE(res); |
| EXPECT_EQ(200, res->status); |
| EXPECT_EQ("a +b", res->body); |
| } |
| |
| TEST_F(ServerTest, MultipartFormData) { |
| 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"}, |
| {"file4", "", "", " application/json tmp-string "}}; |
| |
| auto res = cli_.Post("/multipart", items); |
| |
| |