mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba685dbe48 | |||
| 49c4c2f9c1 | |||
| 58909f5917 | |||
| 5982b5c360 | |||
| eb1fe5b191 | |||
| 5e01587ed6 | |||
| 5935d9fa59 | |||
| 5bb4c12c6b | |||
| 85637844c9 | |||
| d043b18097 | |||
| 31bb13abd2 | |||
| 8728db7477 | |||
| 1c50ac3667 | |||
| cf386f97fd | |||
| b2203bb05a | |||
| f5b806d995 | |||
| 3895210f19 | |||
| d45250fd88 | |||
| 528cacdc0d | |||
| ed1b6afa10 | |||
| 08fc7085e5 | |||
| 8333340e2c | |||
| 98a0887571 | |||
| b0a189e50e | |||
| 776b3ffbf9 | |||
| a061b97677 | |||
| d359e3a5f7 | |||
| 5928e0af1a | |||
| a5005789ff | |||
| fae30af47d | |||
| 2feea0c9ab | |||
| a2e4af54b7 | |||
| d0b123be26 | |||
| df138366e4 | |||
| c49441ae64 | |||
| e1506fa186 | |||
| ad9fd3bd93 | |||
| 05e0253195 | |||
| da26b517a3 | |||
| 2b7a968468 | |||
| 240cc85ccb | |||
| 129e2f00b8 |
@@ -3,6 +3,7 @@ tags
|
||||
example/server
|
||||
example/client
|
||||
example/hello
|
||||
example/simplecli
|
||||
example/simplesvr
|
||||
example/benchmark
|
||||
example/redirect
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
# Environment
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
# Compiler selection
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
# Build/test steps
|
||||
script:
|
||||
- cd ${TRAVIS_BUILD_DIR}/test
|
||||
- make all
|
||||
@@ -2,7 +2,6 @@ cpp-httplib
|
||||
===========
|
||||
|
||||
[](https://github.com/yhirose/cpp-httplib/actions)
|
||||
[](https://travis-ci.org/yhirose/cpp-httplib)
|
||||
[](https://ci.appveyor.com/project/yhirose/cpp-httplib)
|
||||
|
||||
A C++11 single-file header-only cross platform HTTP/HTTPS library.
|
||||
@@ -172,14 +171,14 @@ svr.Post("/content_receiver",
|
||||
### Send content with Content provider
|
||||
|
||||
```cpp
|
||||
const uint64_t DATA_CHUNK_SIZE = 4;
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
|
||||
svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
auto data = new std::string("abcdefg");
|
||||
|
||||
res.set_content_provider(
|
||||
data->size(), // Content length
|
||||
[data](uint64_t offset, uint64_t length, DataSink &sink) {
|
||||
[data](size_t offset, size_t length, DataSink &sink) {
|
||||
const auto &d = *data;
|
||||
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
@@ -192,7 +191,7 @@ svr.Get("/stream", [&](const Request &req, Response &res) {
|
||||
```cpp
|
||||
svr.Get("/chunked", [&](const Request& req, Response& res) {
|
||||
res.set_chunked_content_provider(
|
||||
[](uint64_t offset, DataSink &sink) {
|
||||
[](size_t offset, DataSink &sink) {
|
||||
sink.write("123", 3);
|
||||
sink.write("345", 3);
|
||||
sink.write("789", 3);
|
||||
@@ -291,7 +290,7 @@ auto res = cli.Get("/hi", headers);
|
||||
std::string body;
|
||||
|
||||
auto res = cli.Get("/large-data",
|
||||
[&](const char *data, uint64_t data_length) {
|
||||
[&](const char *data, size_t data_length) {
|
||||
body.append(data, data_length);
|
||||
return true;
|
||||
});
|
||||
@@ -437,6 +436,15 @@ Get(requests, "/get-request2");
|
||||
Post(requests, "/post-request1", "text", "text/plain");
|
||||
Post(requests, "/post-request2", "text", "text/plain");
|
||||
|
||||
const size_t DATA_CHUNK_SIZE = 4;
|
||||
std::string data("abcdefg");
|
||||
Post(requests, "/post-request-with-content-provider",
|
||||
data.size(),
|
||||
[&](size_t offset, size_t length, DataSink &sink){
|
||||
sink.write(&data[offset], std::min(length, DATA_CHUNK_SIZE));
|
||||
},
|
||||
"text/plain");
|
||||
|
||||
std::vector<Response> responses;
|
||||
if (cli.send(requests, responses)) {
|
||||
for (const auto& res: responses) {
|
||||
|
||||
+12
-7
@@ -1,9 +1,14 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2017
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
image:
|
||||
- Visual Studio 2019
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
build_script:
|
||||
- cmd: >-
|
||||
cd test
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Release;Platform=%PLATFORM%
|
||||
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build /p:Configuration=Debug;Platform=Win32
|
||||
test_script:
|
||||
- cmd: Debug\test.exe
|
||||
- cmd: x64\Release\test.exe
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -15,5 +15,5 @@ int main(void) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("localhost", 1234);
|
||||
svr.listen("localhost", 8080);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// simplecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -50,7 +50,9 @@
|
||||
|
||||
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
|
||||
#define CPPHTTPLIB_THREAD_POOL_COUNT \
|
||||
((std::max)(1u, std::thread::hardware_concurrency() - 1))
|
||||
((std::max)(8u, std::thread::hardware_concurrency() > 0 \
|
||||
? std::thread::hardware_concurrency() - 1 \
|
||||
: 0))
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -134,6 +136,7 @@ using socket_t = int;
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <condition_variable>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -174,7 +177,6 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Declaration
|
||||
*/
|
||||
@@ -262,6 +264,9 @@ struct Request {
|
||||
Headers headers;
|
||||
std::string body;
|
||||
|
||||
std::string remote_addr;
|
||||
int remote_port = -1;
|
||||
|
||||
// for server
|
||||
std::string version;
|
||||
std::string target;
|
||||
@@ -272,6 +277,7 @@ struct Request {
|
||||
|
||||
// for client
|
||||
size_t redirect_count = CPPHTTPLIB_REDIRECT_MAX_COUNT;
|
||||
size_t authorization_count = 1;
|
||||
ResponseHandler response_handler;
|
||||
ContentReceiver content_receiver;
|
||||
Progress progress;
|
||||
@@ -352,7 +358,7 @@ public:
|
||||
|
||||
virtual ssize_t read(char *ptr, size_t size) = 0;
|
||||
virtual ssize_t write(const char *ptr, size_t size) = 0;
|
||||
virtual std::string get_remote_addr() const = 0;
|
||||
virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
|
||||
|
||||
template <typename... Args>
|
||||
ssize_t write_format(const char *fmt, const Args &... args);
|
||||
@@ -364,8 +370,11 @@ class TaskQueue {
|
||||
public:
|
||||
TaskQueue() = default;
|
||||
virtual ~TaskQueue() = default;
|
||||
|
||||
virtual void enqueue(std::function<void()> fn) = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void on_idle(){};
|
||||
};
|
||||
|
||||
class ThreadPool : public TaskQueue {
|
||||
@@ -604,6 +613,8 @@ public:
|
||||
|
||||
std::shared_ptr<Response> Head(const char *path, const Headers &headers);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Post(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -631,6 +642,8 @@ public:
|
||||
std::shared_ptr<Response> Post(const char *path, const Headers &headers,
|
||||
const MultipartFormDataItems &items);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path);
|
||||
|
||||
std::shared_ptr<Response> Put(const char *path, const std::string &body,
|
||||
const char *content_type);
|
||||
|
||||
@@ -688,6 +701,8 @@ public:
|
||||
bool send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses);
|
||||
|
||||
void stop();
|
||||
|
||||
void set_timeout_sec(time_t timeout_sec);
|
||||
|
||||
void set_read_timeout(time_t sec, time_t usec);
|
||||
@@ -720,6 +735,8 @@ protected:
|
||||
bool process_request(Stream &strm, const Request &req, Response &res,
|
||||
bool last_connection, bool &connection_close);
|
||||
|
||||
std::atomic<socket_t> sock_;
|
||||
|
||||
const std::string host_;
|
||||
const int port_;
|
||||
const std::string host_and_port_;
|
||||
@@ -831,7 +848,7 @@ inline void Post(std::vector<Request> &requests, const char *path,
|
||||
req.method = "POST";
|
||||
req.path = path;
|
||||
req.headers = headers;
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
req.body = body;
|
||||
requests.emplace_back(std::move(req));
|
||||
}
|
||||
@@ -841,6 +858,21 @@ inline void Post(std::vector<Request> &requests, const char *path,
|
||||
Post(requests, path, Headers(), body, content_type);
|
||||
}
|
||||
|
||||
inline void Post(std::vector<Request> &requests, const char *path,
|
||||
size_t content_length, ContentProvider content_provider,
|
||||
const char *content_type) {
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
req.headers = Headers();
|
||||
req.path = path;
|
||||
req.content_length = content_length;
|
||||
req.content_provider = content_provider;
|
||||
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
|
||||
requests.emplace_back(std::move(req));
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
class SSLServer : public Server {
|
||||
public:
|
||||
@@ -884,7 +916,7 @@ public:
|
||||
|
||||
long get_openssl_verify_result() const;
|
||||
|
||||
SSL_CTX *ssl_context() const noexcept;
|
||||
SSL_CTX *ssl_context() const;
|
||||
|
||||
private:
|
||||
bool process_and_close_socket(
|
||||
@@ -1184,7 +1216,20 @@ inline int close_socket(socket_t sock) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
template <typename T> inline ssize_t handle_EINTR(T fn) {
|
||||
ssize_t res = false;
|
||||
while (true) {
|
||||
res = fn();
|
||||
if (res < 0 && errno == EINTR) { continue; }
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#define HANDLE_EINTR(method, ...) \
|
||||
(handle_EINTR([&]() { return method(__VA_ARGS__); }))
|
||||
|
||||
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1192,7 +1237,7 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1202,11 +1247,12 @@ inline int select_read(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), &fds, nullptr, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), &fds, nullptr,
|
||||
nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
#ifdef CPPHTTPLIB_USE_POLL
|
||||
struct pollfd pfd_read;
|
||||
pfd_read.fd = sock;
|
||||
@@ -1214,7 +1260,7 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
return poll(&pfd_read, 1, timeout);
|
||||
return HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
#else
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
@@ -1224,7 +1270,8 @@ inline int select_write(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
return select(static_cast<int>(sock + 1), nullptr, &fds, nullptr, &tv);
|
||||
return HANDLE_EINTR(select, static_cast<int>(sock + 1), nullptr, &fds,
|
||||
nullptr, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1236,13 +1283,13 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
|
||||
auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
|
||||
|
||||
if (poll(&pfd_read, 1, timeout) > 0 &&
|
||||
pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
auto poll_res = HANDLE_EINTR(poll, &pfd_read, 1, timeout);
|
||||
if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
return getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len) >= 0 &&
|
||||
!error;
|
||||
auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
|
||||
reinterpret_cast<char *>(&error), &len);
|
||||
return res >= 0 && !error;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
@@ -1257,7 +1304,8 @@ inline bool wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) {
|
||||
tv.tv_sec = static_cast<long>(sec);
|
||||
tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
|
||||
|
||||
if (select(static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse, &tv) > 0 &&
|
||||
if (HANDLE_EINTR(select, static_cast<int>(sock + 1), &fdsr, &fdsw, &fdse,
|
||||
&tv) > 0 &&
|
||||
(FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
|
||||
int error = 0;
|
||||
socklen_t len = sizeof(error);
|
||||
@@ -1279,7 +1327,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1298,7 +1346,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
private:
|
||||
socket_t sock_;
|
||||
@@ -1317,7 +1365,7 @@ public:
|
||||
bool is_writable() const override;
|
||||
ssize_t read(char *ptr, size_t size) override;
|
||||
ssize_t write(const char *ptr, size_t size) override;
|
||||
std::string get_remote_addr() const override;
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override;
|
||||
|
||||
const std::string &get_buffer() const;
|
||||
|
||||
@@ -1440,11 +1488,18 @@ socket_t create_socket(const char *host, int port, Fn fn,
|
||||
int yes = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
|
||||
#ifdef SO_REUSEPORT
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char *>(&yes),
|
||||
sizeof(yes));
|
||||
#endif
|
||||
|
||||
if (rp->ai_family == AF_INET6) {
|
||||
int no = 0;
|
||||
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char *>(&no),
|
||||
sizeof(no));
|
||||
}
|
||||
|
||||
// bind or connect
|
||||
if (fn(sock, *rp)) {
|
||||
freeaddrinfo(result);
|
||||
@@ -1501,8 +1556,8 @@ inline bool bind_ip_address(socket_t sock, const char *host) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
#ifndef _WIN32
|
||||
inline std::string if2ip(const std::string &ifn) {
|
||||
struct ifaddrs *ifap;
|
||||
getifaddrs(&ifap);
|
||||
for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
@@ -1518,9 +1573,9 @@ inline std::string if2ip(const std::string &ifn) {
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
#endif
|
||||
return std::string();
|
||||
}
|
||||
#endif
|
||||
|
||||
inline socket_t create_client_socket(const char *host, int port,
|
||||
time_t timeout_sec,
|
||||
@@ -1528,9 +1583,11 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
return create_socket(
|
||||
host, port, [&](socket_t sock, struct addrinfo &ai) -> bool {
|
||||
if (!intf.empty()) {
|
||||
#ifndef _WIN32
|
||||
auto ip = if2ip(intf);
|
||||
if (ip.empty()) { ip = intf; }
|
||||
if (!bind_ip_address(sock, ip.c_str())) { return false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
set_nonblocking(sock, true);
|
||||
@@ -1550,21 +1607,32 @@ inline socket_t create_client_socket(const char *host, int port,
|
||||
});
|
||||
}
|
||||
|
||||
inline std::string get_remote_addr(socket_t sock) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr), &len)) {
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
|
||||
if (!getnameinfo(reinterpret_cast<struct sockaddr *>(&addr), len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()),
|
||||
nullptr, 0, NI_NUMERICHOST)) {
|
||||
return ipstr.data();
|
||||
}
|
||||
inline void get_remote_ip_and_port(const struct sockaddr_storage &addr,
|
||||
socklen_t addr_len, std::string &ip,
|
||||
int &port) {
|
||||
if (addr.ss_family == AF_INET) {
|
||||
port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
port =
|
||||
ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
}
|
||||
|
||||
return std::string();
|
||||
std::array<char, NI_MAXHOST> ipstr{};
|
||||
if (!getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
|
||||
ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
|
||||
0, NI_NUMERICHOST)) {
|
||||
ip = ipstr.data();
|
||||
}
|
||||
}
|
||||
|
||||
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
|
||||
if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len)) {
|
||||
get_remote_ip_and_port(addr, addr_len, ip, port);
|
||||
}
|
||||
}
|
||||
|
||||
inline const char *
|
||||
@@ -1820,7 +1888,7 @@ inline bool read_headers(Stream &strm, Headers &headers) {
|
||||
// the left or right side of the header value:
|
||||
// - https://stackoverflow.com/questions/50179659/
|
||||
// - https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
||||
static const std::regex re(R"(([^:]+):[\t ]*(.+))");
|
||||
static const std::regex re(R"(([^:]+):[\t ]*([^\t ].*))");
|
||||
|
||||
std::cmatch m;
|
||||
if (std::regex_match(line_reader.ptr(), end, m, re)) {
|
||||
@@ -1889,9 +1957,17 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
auto chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
unsigned long chunk_len;
|
||||
while (true) {
|
||||
char *end_ptr;
|
||||
|
||||
chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16);
|
||||
|
||||
if (end_ptr == line_reader.ptr()) { return false; }
|
||||
if (chunk_len == ULONG_MAX) { return false; }
|
||||
|
||||
if (chunk_len == 0) { break; }
|
||||
|
||||
while (chunk_len > 0) {
|
||||
if (!read_content_with_length(strm, chunk_len, nullptr, out)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1901,8 +1977,6 @@ inline bool read_content_chunked(Stream &strm, ContentReceiver out) {
|
||||
if (strcmp(line_reader.ptr(), "\r\n")) { break; }
|
||||
|
||||
if (!line_reader.getline()) { return false; }
|
||||
|
||||
chunk_len = std::stoul(line_reader.ptr(), 0, 16);
|
||||
}
|
||||
|
||||
if (chunk_len == 0) {
|
||||
@@ -2009,7 +2083,9 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
|
||||
written_length = strm.write(d, l);
|
||||
};
|
||||
data_sink.done = [&](void) { written_length = -1; };
|
||||
data_sink.is_writable = [&](void) { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
};
|
||||
|
||||
content_provider(offset, end_offset - offset, data_sink);
|
||||
if (written_length < 0) { return written_length; }
|
||||
@@ -2040,7 +2116,9 @@ inline ssize_t write_content_chunked(Stream &strm,
|
||||
data_available = false;
|
||||
written_length = strm.write("0\r\n\r\n");
|
||||
};
|
||||
data_sink.is_writable = [&](void) { return strm.is_writable(); };
|
||||
data_sink.is_writable = [&](void) {
|
||||
return strm.is_writable() && written_length >= 0;
|
||||
};
|
||||
|
||||
content_provider(offset, 0, data_sink);
|
||||
|
||||
@@ -2662,10 +2740,11 @@ inline std::pair<std::string, std::string> make_digest_authentication_header(
|
||||
":" + qop + ":" + H(A2));
|
||||
}
|
||||
|
||||
auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
|
||||
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
|
||||
"\", algorithm=" + algo + ", qop=" + qop + ", nc=\"" + nc +
|
||||
"\", cnonce=\"" + cnonce + "\", response=\"" + response + "\"";
|
||||
auto field = "Digest username=\"" + username + "\", realm=\"" +
|
||||
auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
|
||||
"\", uri=\"" + req.path + "\", algorithm=" + algo +
|
||||
", qop=" + qop + ", nc=\"" + nc + "\", cnonce=\"" + cnonce +
|
||||
"\", response=\"" + response + "\"";
|
||||
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
return std::make_pair(key, field);
|
||||
@@ -2803,11 +2882,11 @@ inline void Response::set_header(const char *key, const std::string &val) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void Response::set_redirect(const char *url, int status) {
|
||||
inline void Response::set_redirect(const char *url, int stat) {
|
||||
if (!detail::has_crlf(url)) {
|
||||
set_header("Location", url);
|
||||
if (300 <= status && status < 400) {
|
||||
this->status = status;
|
||||
if (300 <= stat && stat < 400) {
|
||||
this->status = stat;
|
||||
} else {
|
||||
this->status = 302;
|
||||
}
|
||||
@@ -2900,25 +2979,42 @@ inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec,
|
||||
inline SocketStream::~SocketStream() {}
|
||||
|
||||
inline bool SocketStream::is_readable() const {
|
||||
return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
|
||||
}
|
||||
|
||||
inline bool SocketStream::is_writable() const {
|
||||
return detail::select_write(sock_, 0, 0) > 0;
|
||||
return select_write(sock_, 0, 0) > 0;
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::read(char *ptr, size_t size) {
|
||||
if (is_readable()) { return recv(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_readable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return recv(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(recv, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
|
||||
if (is_writable()) { return send(sock_, ptr, size, 0); }
|
||||
return -1;
|
||||
if (!is_writable()) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (size > static_cast<size_t>(std::numeric_limits<int>::max())) {
|
||||
return -1;
|
||||
}
|
||||
return send(sock_, ptr, static_cast<int>(size), 0);
|
||||
#else
|
||||
return HANDLE_EINTR(send, sock_, ptr, size, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string SocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
return detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
// Buffer stream implementation
|
||||
@@ -2941,7 +3037,8 @@ inline ssize_t BufferStream::write(const char *ptr, size_t size) {
|
||||
return static_cast<ssize_t>(size);
|
||||
}
|
||||
|
||||
inline std::string BufferStream::get_remote_addr() const { return ""; }
|
||||
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
|
||||
int & /*port*/) const {}
|
||||
|
||||
inline const std::string &BufferStream::get_buffer() const { return buffer; }
|
||||
|
||||
@@ -3421,17 +3518,16 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) {
|
||||
if (svr_sock_ == INVALID_SOCKET) { return -1; }
|
||||
|
||||
if (port == 0) {
|
||||
struct sockaddr_storage address;
|
||||
socklen_t len = sizeof(address);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&address),
|
||||
&len) == -1) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
|
||||
&addr_len) == -1) {
|
||||
return -1;
|
||||
}
|
||||
if (address.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&address)->sin_port);
|
||||
} else if (address.ss_family == AF_INET6) {
|
||||
return ntohs(
|
||||
reinterpret_cast<struct sockaddr_in6 *>(&address)->sin6_port);
|
||||
if (addr.ss_family == AF_INET) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
@@ -3456,6 +3552,7 @@ inline bool Server::listen_internal() {
|
||||
auto val = detail::select_read(svr_sock_, 0, 100000);
|
||||
|
||||
if (val == 0) { // Timeout
|
||||
task_queue->on_idle();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3636,7 +3733,9 @@ Server::process_request(Stream &strm, bool last_connection,
|
||||
connection_close = true;
|
||||
}
|
||||
|
||||
req.set_header("REMOTE_ADDR", strm.get_remote_addr());
|
||||
strm.get_remote_ip_and_port(req.remote_addr, req.remote_port);
|
||||
req.set_header("REMOTE_ADDR", req.remote_addr);
|
||||
req.set_header("REMOTE_PORT", std::to_string(req.remote_port));
|
||||
|
||||
if (req.has_header("Range")) {
|
||||
const auto &range_header_value = req.get_header_value("Range");
|
||||
@@ -3687,7 +3786,7 @@ inline bool Server::process_and_close_socket(socket_t sock) {
|
||||
inline Client::Client(const std::string &host, int port,
|
||||
const std::string &client_cert_path,
|
||||
const std::string &client_key_path)
|
||||
: host_(host), port_(port),
|
||||
: sock_(INVALID_SOCKET), host_(host), port_(port),
|
||||
host_and_port_(host_ + ":" + std::to_string(port_)),
|
||||
client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
|
||||
|
||||
@@ -3723,18 +3822,19 @@ inline bool Client::read_response_line(Stream &strm, Response &res) {
|
||||
}
|
||||
|
||||
inline bool Client::send(const Request &req, Response &res) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return error; }
|
||||
if (!connect(sock_, res, error)) { return error; }
|
||||
}
|
||||
#endif
|
||||
|
||||
return process_and_close_socket(
|
||||
sock, 1, [&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
sock_, 1,
|
||||
[&](Stream &strm, bool last_connection, bool &connection_close) {
|
||||
return handle_request(strm, req, res, last_connection,
|
||||
connection_close);
|
||||
});
|
||||
@@ -3744,18 +3844,18 @@ inline bool Client::send(const std::vector<Request> &requests,
|
||||
std::vector<Response> &responses) {
|
||||
size_t i = 0;
|
||||
while (i < requests.size()) {
|
||||
auto sock = create_client_socket();
|
||||
if (sock == INVALID_SOCKET) { return false; }
|
||||
sock_ = create_client_socket();
|
||||
if (sock_ == INVALID_SOCKET) { return false; }
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (is_ssl() && !proxy_host_.empty()) {
|
||||
Response res;
|
||||
bool error;
|
||||
if (!connect(sock, res, error)) { return false; }
|
||||
if (!connect(sock_, res, error)) { return false; }
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!process_and_close_socket(sock, requests.size() - i,
|
||||
if (!process_and_close_socket(sock_, requests.size() - i,
|
||||
[&](Stream &strm, bool last_connection,
|
||||
bool &connection_close) -> bool {
|
||||
auto &req = requests[i++];
|
||||
@@ -3797,7 +3897,8 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
if (res.status == 401 || res.status == 407) {
|
||||
if ((res.status == 401 || res.status == 407) &&
|
||||
req.authorization_count == 1) {
|
||||
auto is_proxy = res.status == 407;
|
||||
const auto &username =
|
||||
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
||||
@@ -3808,10 +3909,12 @@ inline bool Client::handle_request(Stream &strm, const Request &req,
|
||||
std::map<std::string, std::string> auth;
|
||||
if (parse_www_authenticate(res, auth, is_proxy)) {
|
||||
Request new_req = req;
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "WWW-Authorization";
|
||||
new_req.authorization_count += 1;
|
||||
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
|
||||
new_req.headers.erase(key);
|
||||
new_req.headers.insert(make_digest_authentication_header(
|
||||
req, auth, 1, random_string(10), username, password, is_proxy));
|
||||
req, auth, new_req.authorization_count, random_string(10), username,
|
||||
password, is_proxy));
|
||||
|
||||
Response new_res;
|
||||
|
||||
@@ -3885,34 +3988,42 @@ inline bool Client::redirect(const Request &req, Response &res) {
|
||||
if (location.empty()) { return false; }
|
||||
|
||||
const static std::regex re(
|
||||
R"(^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*(?:\?[^#]*)?)(?:#.*)?)");
|
||||
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";
|
||||
|
||||
auto next_scheme = m[1].str();
|
||||
auto next_host = m[2].str();
|
||||
auto next_path = m[3].str();
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
auto port_str = m[3].str();
|
||||
auto next_path = m[4].str();
|
||||
|
||||
auto next_port = port_;
|
||||
if (!port_str.empty()) {
|
||||
next_port = std::stoi(port_str);
|
||||
} else if (!next_scheme.empty()) {
|
||||
next_port = next_scheme == "https" ? 443 : 80;
|
||||
}
|
||||
|
||||
if (next_scheme.empty()) { next_scheme = scheme; }
|
||||
if (next_host.empty()) { next_host = host_; }
|
||||
if (next_path.empty()) { next_path = "/"; }
|
||||
|
||||
if (next_scheme == scheme && next_host == host_) {
|
||||
if (next_scheme == scheme && next_host == host_ && next_port == port_) {
|
||||
return detail::redirect(*this, req, res, next_path);
|
||||
} else {
|
||||
if (next_scheme == "https") {
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
SSLClient cli(next_host.c_str());
|
||||
SSLClient cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
Client cli(next_host.c_str());
|
||||
Client cli(next_host.c_str(), next_port);
|
||||
cli.copy_settings(*this);
|
||||
return detail::redirect(cli, req, res, next_path);
|
||||
}
|
||||
@@ -4022,7 +4133,7 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
|
||||
req.headers = headers;
|
||||
req.path = path;
|
||||
|
||||
req.headers.emplace("Content-Type", content_type);
|
||||
if (content_type) { req.headers.emplace("Content-Type", content_type); }
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
if (compress_) {
|
||||
@@ -4214,6 +4325,10 @@ inline std::shared_ptr<Response> Client::Head(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path) {
|
||||
return Post(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Post(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4286,6 +4401,10 @@ Client::Post(const char *path, const Headers &headers,
|
||||
return Post(path, headers, body, content_type.c_str());
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path) {
|
||||
return Put(path, std::string(), nullptr);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<Response> Client::Put(const char *path,
|
||||
const std::string &body,
|
||||
const char *content_type) {
|
||||
@@ -4403,6 +4522,14 @@ inline std::shared_ptr<Response> Client::Options(const char *path,
|
||||
return send(req, *res) ? res : nullptr;
|
||||
}
|
||||
|
||||
inline void Client::stop() {
|
||||
if (sock_ != INVALID_SOCKET) {
|
||||
std::atomic<socket_t> sock(sock_.exchange(INVALID_SOCKET));
|
||||
detail::shutdown_socket(sock);
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Client::set_timeout_sec(time_t timeout_sec) {
|
||||
timeout_sec_ = timeout_sec;
|
||||
}
|
||||
@@ -4501,8 +4628,8 @@ inline bool process_and_close_socket_ssl(
|
||||
auto count = keep_alive_max_count;
|
||||
while (count > 0 &&
|
||||
(is_client_request ||
|
||||
detail::select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
select_read(sock, CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND,
|
||||
CPPHTTPLIB_KEEPALIVE_TIMEOUT_USECOND) > 0)) {
|
||||
SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec);
|
||||
auto last_connection = count == 1;
|
||||
auto connection_close = false;
|
||||
@@ -4613,8 +4740,9 @@ inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline std::string SSLSocketStream::get_remote_addr() const {
|
||||
return detail::get_remote_addr(sock_);
|
||||
inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
|
||||
int &port) const {
|
||||
detail::get_remote_ip_and_port(sock_, ip, port);
|
||||
}
|
||||
|
||||
static SSLInit sslinit_;
|
||||
@@ -4768,7 +4896,7 @@ inline long SSLClient::get_openssl_verify_result() const {
|
||||
return verify_result_;
|
||||
}
|
||||
|
||||
inline SSL_CTX *SSLClient::ssl_context() const noexcept { return ctx_; }
|
||||
inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
|
||||
|
||||
inline bool SSLClient::process_and_close_socket(
|
||||
socket_t sock, size_t request_count,
|
||||
@@ -4959,6 +5087,70 @@ 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<Response> 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<Response> Get(const char *url) {
|
||||
Options options;
|
||||
return Get(url, options);
|
||||
}
|
||||
|
||||
} // namespace url
|
||||
|
||||
namespace detail {
|
||||
|
||||
#undef HANDLE_EINTR
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
} // namespace httplib
|
||||
|
||||
+241
-26
@@ -563,15 +563,17 @@ TEST(DigestAuthTest, FromHTTPWatch) {
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
EXPECT_EQ(401, res->status);
|
||||
}
|
||||
|
||||
cli.set_digest_auth("bad", "world");
|
||||
for (auto path : paths) {
|
||||
auto res = cli.Get(path.c_str());
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(400, res->status);
|
||||
}
|
||||
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
|
||||
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
|
||||
// cli.set_digest_auth("bad", "world");
|
||||
// for (auto path : paths) {
|
||||
// auto res = cli.Get(path.c_str());
|
||||
// ASSERT_TRUE(res != nullptr);
|
||||
// EXPECT_EQ(400, res->status);
|
||||
// }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -649,15 +651,114 @@ TEST(YahooRedirectTest, Redirect) {
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(YahooRedirectTestWithURL, Redirect) {
|
||||
auto res = httplib::url::Get("http://yahoo.com");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(301, res->status);
|
||||
|
||||
httplib::url::Options options;
|
||||
options.follow_location = true;
|
||||
|
||||
res = httplib::url::Get("http://yahoo.com", options);
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST(HttpsToHttpRedirectTest, Redirect) {
|
||||
httplib::SSLClient cli("httpbin.org");
|
||||
cli.set_follow_location(true);
|
||||
auto res =
|
||||
cli.Get("/redirect-to?url=http%3A%2F%2Fwww.google.com&status_code=302");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
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",
|
||||
options);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
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, BindDualStack) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/1", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
auto thread = std::thread([&]() { svr.listen("::", PORT); });
|
||||
|
||||
// Give GET time to get a few messages.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
|
||||
{
|
||||
Client cli("127.0.0.1", PORT);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
}
|
||||
{
|
||||
Client cli("::1", PORT);
|
||||
|
||||
auto res = cli.Get("/1");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ(res->body, "Hello World!");
|
||||
}
|
||||
svr.stop();
|
||||
thread.join();
|
||||
ASSERT_FALSE(svr.is_running());
|
||||
}
|
||||
|
||||
TEST(Server, BindAndListenSeparately) {
|
||||
Server svr;
|
||||
int port = svr.bind_to_any_port("0.0.0.0");
|
||||
@@ -735,6 +836,10 @@ protected:
|
||||
.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("/endwith%",
|
||||
@@ -871,13 +976,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -890,7 +995,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -898,8 +1003,24 @@ protected:
|
||||
.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");
|
||||
})
|
||||
.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");
|
||||
@@ -970,13 +1091,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("text default", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "text2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("aωb", file.content);
|
||||
}
|
||||
|
||||
@@ -989,7 +1110,7 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = get_file_value(files, "file3");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.content.size());
|
||||
}
|
||||
@@ -1054,13 +1175,13 @@ protected:
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("test", file.content);
|
||||
}
|
||||
|
||||
{
|
||||
const auto &file = req.get_file_value("key2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_TRUE(file.filename.empty());
|
||||
EXPECT_EQ("--abcdefg123", file.content);
|
||||
}
|
||||
})
|
||||
@@ -1142,7 +1263,7 @@ TEST_F(ServerTest, HeadMethod200) {
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(200, res->status);
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod200Static) {
|
||||
@@ -1151,14 +1272,14 @@ TEST_F(ServerTest, HeadMethod200Static) {
|
||||
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_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, HeadMethod404) {
|
||||
auto res = cli_.Head("/invalid");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(404, res->status);
|
||||
EXPECT_EQ("", res->body);
|
||||
EXPECT_TRUE(res->body.empty());
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetMethodPersonJohn) {
|
||||
@@ -1244,6 +1365,20 @@ TEST_F(ServerTest, PostEmptyContent) {
|
||||
ASSERT_EQ("empty", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PostEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Post("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
ASSERT_EQ(200, res->status);
|
||||
ASSERT_EQ("empty-no-content-type", res->body);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, PutEmptyContentWithNoContentType) {
|
||||
auto res = cli_.Put("/empty-no-content-type");
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
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 != nullptr);
|
||||
@@ -1647,6 +1782,17 @@ TEST_F(ServerTest, GetStreamedEndless) {
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, ClientStop) {
|
||||
thread t = thread([&]() {
|
||||
auto res = cli_.Get("/streamed-cancel",
|
||||
[&](const char *, uint64_t) { return true; });
|
||||
ASSERT_TRUE(res == nullptr);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
cli_.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, GetWithRange1) {
|
||||
auto res = cli_.Get("/with-range", {{make_range_header({{3, 5}})}});
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
@@ -1944,6 +2090,9 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
Get(requests, "/hi");
|
||||
Get(requests, "/not-exist");
|
||||
Post(requests, "/empty", "", "text/plain");
|
||||
Post(
|
||||
requests, "/empty", 0, [&](size_t, size_t, httplib::DataSink &) {},
|
||||
"text/plain");
|
||||
|
||||
std::vector<Response> responses;
|
||||
auto ret = cli_.send(requests, responses);
|
||||
@@ -1963,8 +2112,8 @@ TEST_F(ServerTest, KeepAlive) {
|
||||
EXPECT_EQ(404, res.status);
|
||||
}
|
||||
|
||||
{
|
||||
auto &res = responses[4];
|
||||
for (size_t i = 4; i < 6; i++) {
|
||||
auto &res = responses[i];
|
||||
EXPECT_EQ(200, res.status);
|
||||
EXPECT_EQ("text/plain", res.get_header_value("Content-Type"));
|
||||
EXPECT_EQ("empty", res.body);
|
||||
@@ -1992,7 +2141,7 @@ TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
|
||||
auto res = cli_.Get("/gzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -2033,7 +2182,7 @@ TEST_F(ServerTest, GzipWithContentReceiverWithoutAcceptEncoding) {
|
||||
});
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_TRUE(res->get_header_value("Content-Encoding").empty());
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
|
||||
@@ -2093,7 +2242,8 @@ TEST_F(ServerTest, MultipartFormDataGzip) {
|
||||
#endif
|
||||
|
||||
// Sends a raw request to a server listening at HOST:PORT.
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
static bool send_request(time_t read_timeout_sec, const std::string &req,
|
||||
std::string *resp = nullptr) {
|
||||
auto client_sock = detail::create_client_socket(HOST, PORT, /*timeout_sec=*/5,
|
||||
std::string());
|
||||
|
||||
@@ -2111,7 +2261,9 @@ static bool send_request(time_t read_timeout_sec, const std::string &req) {
|
||||
char buf[512];
|
||||
|
||||
detail::stream_line_reader line_reader(strm, buf, sizeof(buf));
|
||||
while (line_reader.getline()) {}
|
||||
while (line_reader.getline()) {
|
||||
if (resp) { *resp += line_reader.ptr(); }
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -2143,11 +2295,15 @@ TEST(ServerRequestParsingTest, TrimWhitespaceFromHeaderValues) {
|
||||
}
|
||||
|
||||
// Sends a raw request and verifies that there isn't a crash or exception.
|
||||
static void test_raw_request(const std::string &req) {
|
||||
static void test_raw_request(const std::string &req,
|
||||
std::string *out = nullptr) {
|
||||
Server svr;
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("ok", "text/plain");
|
||||
});
|
||||
|
||||
// Server read timeout must be longer than the client read timeout for the
|
||||
// bug to reproduce, probably to force the server to process a request
|
||||
@@ -2160,7 +2316,7 @@ static void test_raw_request(const std::string &req) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req));
|
||||
ASSERT_TRUE(send_request(client_read_timeout_sec, req, out));
|
||||
svr.stop();
|
||||
t.join();
|
||||
EXPECT_TRUE(listen_thread_ok);
|
||||
@@ -2214,6 +2370,65 @@ TEST(ServerRequestParsingTest, ReadHeadersRegexComplexity2) {
|
||||
"&&&%%%");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ExcessiveWhitespaceInUnparseableHeaderLine) {
|
||||
// Make sure this doesn't crash the server.
|
||||
// In a previous version of the header line regex, the "\r" rendered the line
|
||||
// unparseable and the regex engine repeatedly backtracked, trying to look for
|
||||
// a new position where the leading white space ended and the field value
|
||||
// began.
|
||||
// The crash occurs with libc++ but not libstdc++.
|
||||
test_raw_request("GET /hi HTTP/1.1\r\n"
|
||||
"a:" +
|
||||
std::string(2000, ' ') + '\r' + std::string(20, 'z') +
|
||||
"\r\n"
|
||||
"\r\n");
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidFirstChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"nothex\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidSecondChunkLengthInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
"3\r\n"
|
||||
"xyz\r\n"
|
||||
"NaN\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, ChunkLengthTooHighInRequest) {
|
||||
std::string out;
|
||||
|
||||
test_raw_request("PUT /put_hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Transfer-Encoding: chunked\r\n"
|
||||
"\r\n"
|
||||
// Length is too large for 64 bits.
|
||||
"1ffffffffffffffff\r\n"
|
||||
"xyz\r\n",
|
||||
&out);
|
||||
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
|
||||
}
|
||||
|
||||
TEST(ServerRequestParsingTest, InvalidHeaderTextWithExtraCR) {
|
||||
test_raw_request("GET /hi HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n\r");
|
||||
}
|
||||
|
||||
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
|
||||
Server svr;
|
||||
|
||||
|
||||
+13
-13
@@ -28,7 +28,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
@@ -40,7 +40,7 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
@@ -69,13 +69,13 @@
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\lib\VC;C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
@@ -84,14 +84,14 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>C:\Program Files\OpenSSL-Win64\include;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>C:\Program Files\OpenSSL-Win64\lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -108,7 +108,7 @@
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>./;../</AdditionalIncludeDirectories>
|
||||
@@ -118,12 +118,12 @@
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -144,7 +144,7 @@
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<WarningLevel>Level4</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
@@ -160,7 +160,7 @@
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>Ws2_32.lib;libssl.lib;libcrypto.lib;libssl.lib;libcrypto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user