Compare commits

...

45 Commits

Author SHA1 Message Date
yhirose 2c0613f211 Fix #472 2020-05-13 21:48:14 -04:00
Saika Fatih be45ff1ff1 A detail about Gzip support (#475)
* Typos fixed

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

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

Therefore clear this option in order to allow
an server socket which can support IPv6 and IPv4 at the same time.
2020-05-04 22:13:12 -04:00
yhirose f5b806d995 Added a test case for #396. 2020-05-04 21:26:14 -04:00
yhirose 3895210f19 Code format 2020-05-04 21:25:59 -04:00
yhirose d45250fd88 Appled HANDLE_EINTR to send and select system calls 2020-05-01 21:38:23 -04:00
yhirose 528cacdc0d Changed CPPHTTPLIB_THREAD_POOL_COUNT back to 8. (#454) 2020-05-01 21:23:02 -04:00
Matthew DeVore ed1b6afa10 Fix crash caused by header field regex complexity (#457) 2020-05-01 12:44:13 -04:00
yhirose 08fc7085e5 Fixed #456 2020-04-30 19:40:23 -04:00
yhirose 8333340e2c Chagned to use inline function instead of macro 2020-04-27 12:36:39 -04:00
yhirose 98a0887571 Merge branch 'je-ik-sketch-eintr-handling' 2020-04-27 12:33:21 -04:00
Jan Lukavsky b0a189e50e Sketch handling EINTR errors 2020-04-27 17:36:44 +02:00
yhirose 776b3ffbf9 Code format 2020-04-25 18:01:48 -04:00
yhirose a061b97677 Adjust appveyor.yml 2020-04-25 18:01:12 -04:00
yhirose d359e3a5f7 Renave queue_adjust to on_idle (#442) 2020-04-25 17:56:55 -04:00
evg82 5928e0af1a TaskQueue method to internal size adjust (#442)
I use a custom TaskQueue, with variable number of workers, adding workers on demand is an easy task when new connection arrive (in enqueue function) however i need another funtion to be called even (or better) went no new connections arrives to reduce workers count. I only added a new virtual method in TaskQueue class to allow custom class to adjust workers size over time. Even if this methods is called frequenlty custom class can keep a "last_update" counter to check if need to adjust worker count or any other internal task. Without this function i need an external thread to make this adjust task.
2020-04-25 17:55:20 -04:00
yhirose a5005789ff Fixed Visual Studio compiler warnings with x64 platform (Resolve #440 and #446) (#448) 2020-04-25 17:13:14 -04:00
yhirose fae30af47d Updated appveyor.yml 2020-04-25 15:48:19 -04:00
Hoa Thiên Vũ 2feea0c9ab Fixed error: ‘ULONG_MAX’ was not declared in this scope on line 1921 (#445)
* Fixed error:
ULONG_MAX is defined in the limits.h header file. Putting #include <climits>
```
httplib.h: In function ‘bool httplib::detail::read_content_chunked(httplib::Stream&, httplib::ContentReceiver)’:
httplib.h:1921:22: error: ‘ULONG_MAX’ was not declared in this scope
     if (chunk_len == ULONG_MAX) { return false; }
                      ^~~~~~~~~
httplib.h:1921:22: note: suggested alternative: ‘_SC_ULONG_MAX’
     if (chunk_len == ULONG_MAX) { return false; }
                      ^~~~~~~~~
                      _SC_ULONG_MAX
```

* Move #include <climits> to after #include <cassert>
2020-04-24 12:02:19 -04:00
yhirose a2e4af54b7 Fix #399 2020-04-23 23:09:04 -04:00
yhirose d0b123be26 Support remote_addr and remote_port REMOTE_PORT header in client Request (#433) 2020-04-23 22:12:12 -04:00
Matthew DeVore df138366e4 Fail to read a chunk if its length is >= ULONG_MAX (#444)
We cannot trivially support such large chunks, and the maximum value
std::strtoul can parse accurately is ULONG_MAX-1. Error out early if the
length is longer than that.
2020-04-23 10:59:15 -04:00
Matthew DeVore c49441ae64 Do not throw exceptions when parsing request chunks (#441)
detail::read_content_chunked was using std::stoul to parse the
hexadecimal chunk lengths for "Transfer-Encoding: chunked" requests.
This throws an exception if the string does not begin with any valid
digits. read_content_chunked is not called in the context of a try block
so this caused the process to terminate.

Rather than use exceptions, I opted for std::stroul, which is similar to
std::stoul but does not throw exceptions. Since malformed user input is
not particularly exceptional, and some projects are compiled without
exception support, this approach seems both more portable and more
correct.
2020-04-23 09:05:45 -04:00
yhirose e1506fa186 Code cleanup 2020-04-22 21:43:16 -04:00
yhirose ad9fd3bd93 Fix #436 2020-04-22 21:42:58 -04:00
yhirose 05e0253195 Fixed test error 2020-04-21 23:07:51 -04:00
yhirose da26b517a3 Added url::Get interface 2020-04-21 23:00:39 -04:00
yhirose 2b7a968468 Added a unit test for URL interface 2020-04-21 21:21:31 -04:00
yhirose 240cc85ccb Fixed regex problem for recirect location 2020-04-21 21:18:29 -04:00
yhirose 129e2f00b8 Removed unnecessary noexcept 2020-04-20 19:42:05 -04:00
10 changed files with 748 additions and 230 deletions
+1
View File
@@ -3,6 +3,7 @@ tags
example/server
example/client
example/hello
example/simplecli
example/simplesvr
example/benchmark
example/redirect
-14
View File
@@ -1,14 +0,0 @@
# Environment
language: cpp
os:
- linux
- osx
# Compiler selection
compiler:
- clang
# Build/test steps
script:
- cd ${TRAVIS_BUILD_DIR}/test
- make all
+49 -10
View File
@@ -2,7 +2,6 @@ cpp-httplib
===========
[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions)
[![Build Status](https://travis-ci.org/yhirose/cpp-httplib.svg?branch=master)](https://travis-ci.org/yhirose/cpp-httplib)
[![Bulid Status](https://ci.appveyor.com/api/projects/status/github/yhirose/cpp-httplib?branch=master&svg=true)](https://ci.appveyor.com/project/yhirose/cpp-httplib)
A C++11 single-file header-only cross platform HTTP/HTTPS library.
@@ -172,16 +171,17 @@ 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));
return true; // return 'false' if you want to cancel the process.
},
[data] { delete data; });
});
@@ -192,11 +192,12 @@ 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) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
sink.done();
[](size_t offset, DataSink &sink) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
sink.done();
return true; // return 'false' if you want to cancel the process.
}
);
});
@@ -291,7 +292,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;
});
@@ -364,6 +365,35 @@ res = cli.Options("/resource/foo");
```c++
cli.set_timeout_sec(5); // timeouts in 5 seconds
```
### Receive content with Content receiver
```cpp
std::string body;
auto res = cli.Get(
"/stream", Headers(),
[&](const Response &response) {
EXPECT_EQ(200, response.status);
return true; // return 'false' if you want to cancel the request.
},
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true; // return 'false' if you want to cancel the request.
});
```
### Send content with Content provider
```cpp
std::string body = ...;
auto res = cli_.Post(
"/stream", body.size(),
[](size_t offset, size_t length, DataSink &sink) {
sink.write(body.data() + offset, length);
return true; // return 'false' if you want to cancel the request.
},
"text/plain");
```
### With Progress Callback
```cpp
@@ -437,6 +467,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) {
@@ -486,7 +525,7 @@ cli.enable_server_certificate_verification(true);
Zlib Support
------------
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`.
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked.
The server applies gzip compression to the following MIME type contents:
+12 -7
View File
@@ -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
View File
@@ -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
View File
@@ -15,5 +15,5 @@ int main(void) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("localhost", 1234);
svr.listen("localhost", 8080);
}
+33
View File
@@ -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;
}
+346 -141
View File
@@ -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
*/
@@ -225,7 +227,10 @@ public:
};
using ContentProvider =
std::function<void(size_t offset, size_t length, DataSink &sink)>;
std::function<bool(size_t offset, size_t length, DataSink &sink)>;
using ChunkedContentProvider =
std::function<bool(size_t offset, DataSink &sink)>;
using ContentReceiver =
std::function<bool(const char *data, size_t data_length)>;
@@ -239,18 +244,18 @@ public:
using MultipartReader = std::function<bool(MultipartContentHeader header,
ContentReceiver receiver)>;
ContentReader(Reader reader, MultipartReader muitlpart_reader)
: reader_(reader), muitlpart_reader_(muitlpart_reader) {}
ContentReader(Reader reader, MultipartReader multipart_reader)
: reader_(reader), multipart_reader_(multipart_reader) {}
bool operator()(MultipartContentHeader header,
ContentReceiver receiver) const {
return muitlpart_reader_(header, receiver);
return multipart_reader_(header, receiver);
}
bool operator()(ContentReceiver receiver) const { return reader_(receiver); }
Reader reader_;
MultipartReader muitlpart_reader_;
MultipartReader multipart_reader_;
};
using Range = std::pair<ssize_t, ssize_t>;
@@ -262,6 +267,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 +280,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;
@@ -317,13 +326,11 @@ struct Response {
void set_content(std::string s, const char *content_type);
void set_content_provider(
size_t length,
std::function<void(size_t offset, size_t length, DataSink &sink)>
provider,
size_t length, ContentProvider provider,
std::function<void()> resource_releaser = [] {});
void set_chunked_content_provider(
std::function<void(size_t offset, DataSink &sink)> provider,
ChunkedContentProvider provider,
std::function<void()> resource_releaser = [] {});
Response() = default;
@@ -352,7 +359,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 +371,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 +614,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 +643,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 +702,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 +736,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 +849,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 +859,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 +917,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 +1217,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 +1238,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 +1248,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 +1261,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 +1271,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 +1284,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 +1305,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 +1328,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 +1347,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 +1366,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 +1489,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 +1557,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 +1574,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 +1584,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 +1608,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 +1889,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 +1958,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 +1978,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) {
@@ -2000,20 +2075,25 @@ inline ssize_t write_content(Stream &strm, ContentProvider content_provider,
size_t offset, size_t length) {
size_t begin_offset = offset;
size_t end_offset = offset + length;
ssize_t written_length = 0;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
offset += l;
written_length = strm.write(d, l);
};
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
};
while (offset < end_offset) {
ssize_t written_length = 0;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
offset += l;
written_length = strm.write(d, l);
};
data_sink.done = [&](void) { written_length = -1; };
data_sink.is_writable = [&](void) { return strm.is_writable(); };
content_provider(offset, end_offset - offset, data_sink);
if (!content_provider(offset, end_offset - offset, data_sink)) {
return -1;
}
if (written_length < 0) { return written_length; }
}
return static_cast<ssize_t>(offset - begin_offset);
}
@@ -2024,29 +2104,32 @@ inline ssize_t write_content_chunked(Stream &strm,
size_t offset = 0;
auto data_available = true;
ssize_t total_written_length = 0;
ssize_t written_length = 0;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
data_available = l > 0;
offset += l;
// Emit chunked response header and footer for each chunk
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
written_length = strm.write(chunk);
};
data_sink.done = [&](void) {
data_available = false;
written_length = strm.write("0\r\n\r\n");
};
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
};
while (data_available && !is_shutting_down()) {
ssize_t written_length = 0;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
data_available = l > 0;
offset += l;
// Emit chunked response header and footer for each chunk
auto chunk = from_i_to_hex(l) + "\r\n" + std::string(d, l) + "\r\n";
written_length = strm.write(chunk);
};
data_sink.done = [&](void) {
data_available = false;
written_length = strm.write("0\r\n\r\n");
};
data_sink.is_writable = [&](void) { return strm.is_writable(); };
content_provider(offset, 0, data_sink);
if (!content_provider(offset, 0, data_sink)) { return -1; }
if (written_length < 0) { return written_length; }
total_written_length += written_length;
}
return total_written_length;
}
@@ -2662,10 +2745,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 +2887,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;
}
@@ -2825,24 +2909,23 @@ inline void Response::set_content(std::string s, const char *content_type) {
set_header("Content-Type", content_type);
}
inline void Response::set_content_provider(
size_t in_length,
std::function<void(size_t offset, size_t length, DataSink &sink)> provider,
std::function<void()> resource_releaser) {
inline void
Response::set_content_provider(size_t in_length, ContentProvider provider,
std::function<void()> resource_releaser) {
assert(in_length > 0);
content_length = in_length;
content_provider = [provider](size_t offset, size_t length, DataSink &sink) {
provider(offset, length, sink);
return provider(offset, length, sink);
};
content_provider_resource_releaser = resource_releaser;
}
inline void Response::set_chunked_content_provider(
std::function<void(size_t offset, DataSink &sink)> provider,
ChunkedContentProvider provider,
std::function<void()> resource_releaser) {
content_length = 0;
content_provider = [provider](size_t offset, size_t, DataSink &sink) {
provider(offset, sink);
return provider(offset, sink);
};
content_provider_resource_releaser = resource_releaser;
}
@@ -2900,25 +2983,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 +3041,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 +3522,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 +3556,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 +3737,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 +3790,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 +3826,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 +3848,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 +3901,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 +3913,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 +3992,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);
}
@@ -3995,15 +4110,22 @@ inline bool Client::write_request(Stream &strm, const Request &req,
size_t offset = 0;
size_t end_offset = req.content_length;
ssize_t written_length = 0;
DataSink data_sink;
data_sink.write = [&](const char *d, size_t l) {
auto written_length = strm.write(d, l);
written_length = strm.write(d, l);
offset += static_cast<size_t>(written_length);
};
data_sink.is_writable = [&](void) { return strm.is_writable(); };
data_sink.is_writable = [&](void) {
return strm.is_writable() && written_length >= 0;
};
while (offset < end_offset) {
req.content_provider(offset, end_offset - offset, data_sink);
if (!req.content_provider(offset, end_offset - offset, data_sink)) {
return false;
}
if (written_length < 0) { return false; }
}
}
} else {
@@ -4022,7 +4144,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_) {
@@ -4037,7 +4159,9 @@ inline std::shared_ptr<Response> Client::send_with_content_provider(
data_sink.is_writable = [&](void) { return true; };
while (offset < content_length) {
content_provider(offset, content_length - offset, data_sink);
if (!content_provider(offset, content_length - offset, data_sink)) {
return nullptr;
}
}
} else {
req.body = body;
@@ -4214,6 +4338,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 +4414,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 +4535,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 +4641,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 +4753,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 +4909,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 +5100,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
+288 -42
View File
@@ -563,15 +563,17 @@ TEST(DigestAuthTest, FromHTTPWatch) {
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
EXPECT_EQ(401, res->status);
}
cli.set_digest_auth("bad", "world");
for (auto path : paths) {
auto res = cli.Get(path.c_str());
ASSERT_TRUE(res != nullptr);
EXPECT_EQ(400, res->status);
}
// NOTE: Until httpbin.org fixes issue #46, the following test is commented
// out. Plese see https://httpbin.org/digest-auth/auth/hello/world
// cli.set_digest_auth("bad", "world");
// for (auto path : paths) {
// auto res = cli.Get(path.c_str());
// ASSERT_TRUE(res != nullptr);
// EXPECT_EQ(400, res->status);
// }
}
}
#endif
@@ -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%",
@@ -794,20 +899,21 @@ protected:
.Get("/streamed-chunked",
[&](const Request & /*req*/, Response &res) {
res.set_chunked_content_provider(
[](uint64_t /*offset*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
[](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
sink.write("123", 3);
sink.write("456", 3);
sink.write("789", 3);
sink.done();
return true;
});
})
.Get("/streamed-chunked2",
[&](const Request & /*req*/, Response &res) {
auto i = new int(0);
res.set_chunked_content_provider(
[i](uint64_t /*offset*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
[i](size_t /*offset*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
switch (*i) {
case 0: sink.write("123", 3); break;
case 1: sink.write("456", 3); break;
@@ -815,14 +921,16 @@ protected:
case 3: sink.done(); break;
}
(*i)++;
return true;
},
[i] { delete i; });
})
.Get("/streamed",
[&](const Request & /*req*/, Response &res) {
res.set_content_provider(
6, [](uint64_t offset, uint64_t /*length*/, DataSink &sink) {
6, [](size_t offset, size_t /*length*/, DataSink &sink) {
sink.write(offset < 3 ? "a" : "b", 1);
return true;
});
})
.Get("/streamed-with-range",
@@ -830,25 +938,27 @@ protected:
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(),
[data](uint64_t offset, uint64_t length, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
[data](size_t offset, size_t length, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
size_t DATA_CHUNK_SIZE = 4;
const auto &d = *data;
auto out_len =
std::min(static_cast<size_t>(length), DATA_CHUNK_SIZE);
sink.write(&d[static_cast<size_t>(offset)], out_len);
return true;
},
[data] { delete data; });
})
.Get("/streamed-cancel",
[&](const Request & /*req*/, Response &res) {
res.set_content_provider(size_t(-1), [](uint64_t /*offset*/,
uint64_t /*length*/,
DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
std::string data = "data_chunk";
sink.write(data.data(), data.size());
});
res.set_content_provider(
size_t(-1),
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
EXPECT_TRUE(sink.is_writable());
std::string data = "data_chunk";
sink.write(data.data(), data.size());
return true;
});
})
.Get("/with-range",
[&](const Request & /*req*/, Response &res) {
@@ -871,13 +981,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 +1000,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 +1008,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 +1096,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 +1115,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 +1180,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 +1268,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 +1277,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 +1370,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 +1787,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);
@@ -1772,8 +1923,9 @@ TEST_F(ServerTest, PutWithContentProvider) {
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
EXPECT_TRUE(sink.is_writable());
sink.write("PUT", 3);
return true;
},
"text/plain");
@@ -1782,14 +1934,26 @@ TEST_F(ServerTest, PutWithContentProvider) {
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PostWithContentProviderAbort) {
auto res = cli_.Post(
"/post", 42,
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
return false;
},
"text/plain");
ASSERT_TRUE(res == nullptr);
}
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
TEST_F(ServerTest, PutWithContentProviderWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put(
"/put", 3,
[](size_t /*offset*/, size_t /*length*/, DataSink &sink) {
ASSERT_TRUE(sink.is_writable());
EXPECT_TRUE(sink.is_writable());
sink.write("PUT", 3);
return true;
},
"text/plain");
@@ -1798,6 +1962,18 @@ TEST_F(ServerTest, PutWithContentProviderWithGzip) {
EXPECT_EQ("PUT", res->body);
}
TEST_F(ServerTest, PostWithContentProviderWithGzipAbort) {
cli_.set_compress(true);
auto res = cli_.Post(
"/post", 42,
[](size_t /*offset*/, size_t /*length*/, DataSink & /*sink*/) {
return false;
},
"text/plain");
ASSERT_TRUE(res == nullptr);
}
TEST_F(ServerTest, PutLargeFileWithGzip) {
cli_.set_compress(true);
auto res = cli_.Put("/put-large", LARGE_DATA, "text/plain");
@@ -1944,6 +2120,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 &) { return true; }, "text/plain");
std::vector<Response> responses;
auto ret = cli_.send(requests, responses);
@@ -1963,8 +2142,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 +2171,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 +2212,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 +2272,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 +2291,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 +2325,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 +2346,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 +2400,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;
@@ -2225,6 +2470,7 @@ TEST(ServerStopTest, StopServerWithChunkedTransmission) {
auto size = static_cast<size_t>(sprintf(buffer, "data:%ld\n\n", offset));
sink.write(buffer, size);
std::this_thread::sleep_for(std::chrono::seconds(1));
return true;
});
});
+13 -13
View File
@@ -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>