diff --git a/.gitignore b/.gitignore index 5cf91b6..c8fcc1b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ tags example/server example/client example/hello +example/simplecli example/simplesvr example/benchmark example/redirect diff --git a/example/Makefile b/example/Makefile index 2f294cc..7e18250 100644 --- a/example/Makefile +++ b/example/Makefile @@ -5,7 +5,7 @@ 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 -all: server client hello simplesvr upload redirect sse benchmark +all: server client hello simplecli simplesvr upload redirect sse benchmark server : server.cc ../httplib.h Makefile $(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) @@ -16,6 +16,9 @@ client : client.cc ../httplib.h Makefile hello : hello.cc ../httplib.h Makefile $(CXX) -o hello $(CXXFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) +simplecli : simplecli.cc ../httplib.h Makefile + $(CXX) -o simplecli $(CXXFLAGS) simplecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) + simplesvr : simplesvr.cc ../httplib.h Makefile $(CXX) -o simplesvr $(CXXFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) @@ -36,4 +39,4 @@ pem: openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem clean: - rm server client hello simplesvr upload redirect sse benchmark *.pem + rm server client hello simplecli simplesvr upload redirect sse benchmark *.pem diff --git a/example/hello.cc b/example/hello.cc index 1ef6b98..1590302 100644 --- a/example/hello.cc +++ b/example/hello.cc @@ -15,5 +15,5 @@ int main(void) { res.set_content("Hello World!", "text/plain"); }); - svr.listen("localhost", 1234); + svr.listen("localhost", 8080); } diff --git a/example/simplecli.cc b/example/simplecli.cc new file mode 100644 index 0000000..a9089a3 --- /dev/null +++ b/example/simplecli.cc @@ -0,0 +1,33 @@ +// +// simplecli.cc +// +// Copyright (c) 2019 Yuji Hirose. All rights reserved. +// MIT License +// + +#include +#include + +#define CA_CERT_FILE "./ca-bundle.crt" + +using namespace std; + +int main(void) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + httplib::url::Options options; + options.ca_cert_file_path = CA_CERT_FILE; + // options.server_certificate_verification = true; + + auto res = httplib::url::Get("https://localhost:8080/hi", options); +#else + auto res = httplib::url::Get("http://localhost:8080/hi"); +#endif + + if (res) { + cout << res->status << endl; + cout << res->get_header_value("Content-Type") << endl; + cout << res->body << endl; + } + + return 0; +} diff --git a/httplib.h b/httplib.h index ee2fbca..3ea4529 100644 --- a/httplib.h +++ b/httplib.h @@ -3885,10 +3885,10 @@ inline bool Client::redirect(const Request &req, Response &res) { if (location.empty()) { return false; } const static std::regex re( - R"(^(?:(https?):)?(?://([^/?#]*)(?:(:\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + R"(^(?:(https?):)?(?://([^:/?#]*)(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); std::smatch m; - if (!regex_match(location, m, re)) { return false; } + if (!std::regex_match(location, m, re)) { return false; } auto scheme = is_ssl() ? "https" : "http"; @@ -4967,6 +4967,63 @@ inline bool SSLClient::check_host_name(const char *pattern, } #endif +namespace url { + +struct Options { + // TODO: support more options... + bool follow_location = false; + std::string client_cert_path; + std::string client_key_path; + + std::string ca_cert_file_path; + std::string ca_cert_dir_path; + bool server_certificate_verification = false; +}; + +inline std::shared_ptr Get(const char *url, Options &options) { + const static std::regex re( + R"(^(https?)://([^:/?#]+)(?::(\d+))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + + std::cmatch m; + if (!std::regex_match(url, m, re)) { return nullptr; } + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + auto port_str = m[3].str(); + auto next_path = m[4].str(); + + auto next_port = !port_str.empty() ? std::stoi(port_str) + : (next_scheme == "https" ? 443 : 80); + + if (next_path.empty()) { next_path = "/"; } + + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host.c_str(), next_port, options.client_cert_path, + options.client_key_path); + cli.set_follow_location(options.follow_location); + cli.set_ca_cert_path(options.ca_cert_file_path.c_str(), options.ca_cert_dir_path.c_str()); + cli.enable_server_certificate_verification( + options.server_certificate_verification); + return cli.Get(next_path.c_str()); +#else + return nullptr; +#endif + } else { + Client cli(next_host.c_str(), next_port, options.client_cert_path, + options.client_key_path); + cli.set_follow_location(options.follow_location); + return cli.Get(next_path.c_str()); + } +} + +inline std::shared_ptr Get(const char *url) { + Options options; + return Get(url, options); +} + +} // namespace url + // ---------------------------------------------------------------------------- } // namespace httplib diff --git a/test/test.cc b/test/test.cc index 7f2cae3..4c3e4e7 100644 --- a/test/test.cc +++ b/test/test.cc @@ -654,7 +654,7 @@ TEST(YahooRedirectTestWithURL, Redirect) { ASSERT_TRUE(res != nullptr); EXPECT_EQ(301, res->status); - httplib::url::options options; + httplib::url::Options options; options.follow_location = true; res = httplib::url::Get("http://yahoo.com", options); @@ -669,6 +669,59 @@ TEST(HttpsToHttpRedirectTest, Redirect) { cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302"); ASSERT_TRUE(res != nullptr); } + +TEST(HttpsToHttpRedirectTestWithURL, Redirect) { + httplib::url::Options options; + options.follow_location = true; + + auto res = httplib::url::Get( + "https://httpbin.org/" + "redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302"); + ASSERT_TRUE(res != nullptr); +} + +TEST(RedirectToDifferentPort, Redirect) { + Server svr8080; + Server svr8081; + + svr8080.Get("/1", [&](const Request & /*req*/, Response &res) { + res.set_redirect("http://localhost:8081/2"); + }); + + svr8081.Get("/2", [&](const Request & /*req*/, Response &res) { + res.set_content("Hello World!", "text/plain"); + }); + + auto thread8080 = std::thread([&]() { + svr8080.listen("localhost", 8080); + }); + + auto thread8081 = std::thread([&]() { + svr8081.listen("localhost", 8081); + }); + + while (!svr8080.is_running() || !svr8081.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", 8080); + cli.set_follow_location(true); + + auto res = cli.Get("/1"); + ASSERT_TRUE(res != nullptr); + EXPECT_EQ(200, res->status); + EXPECT_EQ(res->body, "Hello World!"); + + svr8080.stop(); + svr8081.stop(); + thread8080.join(); + thread8081.join(); + ASSERT_FALSE(svr8080.is_running()); + ASSERT_FALSE(svr8081.is_running()); +} #endif TEST(Server, BindAndListenSeparately) {