Skip to content

Commit 38adeaf

Browse files
committed
Fixed problem with proxy support and added unit tests
1 parent a444b61 commit 38adeaf

File tree

11 files changed

+439
-29
lines changed

11 files changed

+439
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ example/redirect
99
example/upload
1010
example/*.pem
1111
test/test
12+
test/test_proxy
1213
test/test.xcodeproj/xcuser*
1314
test/test.xcodeproj/*/xcuser*
1415
test/*.pem

httplib.h

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2476,7 +2476,8 @@ inline std::pair<std::string, std::string> make_range_header(Ranges ranges) {
24762476

24772477
inline std::pair<std::string, std::string>
24782478
make_basic_authentication_header(const std::string &username,
2479-
const std::string &password, bool is_proxy = false) {
2479+
const std::string &password,
2480+
bool is_proxy = false) {
24802481
auto field = "Basic " + detail::base64_encode(username + ":" + password);
24812482
auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
24822483
return std::make_pair(key, field);
@@ -3500,31 +3501,36 @@ inline bool Client::send(const Request &req, Response &res) {
35003501
return false;
35013502
}
35023503

3503-
if (res2.status == 407 && !proxy_digest_auth_username_.empty() &&
3504-
!proxy_digest_auth_password_.empty()) {
3505-
std::map<std::string, std::string> auth;
3506-
if (parse_www_authenticate(res2, auth, true)) {
3507-
detail::close_socket(sock);
3508-
sock = create_client_socket();
3509-
if (sock == INVALID_SOCKET) { return false; }
3510-
3511-
Response res2;
3512-
if (!detail::process_socket(
3513-
true, sock, 1, read_timeout_sec_, read_timeout_usec_,
3514-
[&](Stream &strm, bool /*last_connection*/,
3515-
bool &connection_close) {
3516-
Request req2;
3517-
req2.method = "CONNECT";
3518-
req2.path = host_and_port_;
3519-
req2.headers.insert(make_digest_authentication_header(
3520-
req2, auth, 1, random_string(10),
3521-
proxy_digest_auth_username_, proxy_digest_auth_password_,
3522-
true));
3523-
return process_request(strm, req2, res2, false,
3524-
connection_close);
3525-
})) {
3526-
return false;
3504+
if (res2.status == 407) {
3505+
if (!proxy_digest_auth_username_.empty() &&
3506+
!proxy_digest_auth_password_.empty()) {
3507+
std::map<std::string, std::string> auth;
3508+
if (parse_www_authenticate(res2, auth, true)) {
3509+
detail::close_socket(sock);
3510+
sock = create_client_socket();
3511+
if (sock == INVALID_SOCKET) { return false; }
3512+
3513+
Response res2;
3514+
if (!detail::process_socket(
3515+
true, sock, 1, read_timeout_sec_, read_timeout_usec_,
3516+
[&](Stream &strm, bool /*last_connection*/,
3517+
bool &connection_close) {
3518+
Request req2;
3519+
req2.method = "CONNECT";
3520+
req2.path = host_and_port_;
3521+
req2.headers.insert(make_digest_authentication_header(
3522+
req2, auth, 1, random_string(10),
3523+
proxy_digest_auth_username_,
3524+
proxy_digest_auth_password_, true));
3525+
return process_request(strm, req2, res2, false,
3526+
connection_close);
3527+
})) {
3528+
return false;
3529+
}
35273530
}
3531+
} else {
3532+
res = res2;
3533+
return true;
35283534
}
35293535
}
35303536
}
@@ -3890,7 +3896,8 @@ inline std::shared_ptr<Response> Client::Get(const char *path,
38903896
inline std::shared_ptr<Response> Client::Get(const char *path,
38913897
ContentReceiver content_receiver,
38923898
Progress progress) {
3893-
return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress));
3899+
return Get(path, Headers(), nullptr, std::move(content_receiver),
3900+
std::move(progress));
38943901
}
38953902

38963903
inline std::shared_ptr<Response> Client::Get(const char *path,
@@ -3903,14 +3910,16 @@ inline std::shared_ptr<Response> Client::Get(const char *path,
39033910
const Headers &headers,
39043911
ContentReceiver content_receiver,
39053912
Progress progress) {
3906-
return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress));
3913+
return Get(path, headers, nullptr, std::move(content_receiver),
3914+
std::move(progress));
39073915
}
39083916

39093917
inline std::shared_ptr<Response> Client::Get(const char *path,
39103918
const Headers &headers,
39113919
ResponseHandler response_handler,
39123920
ContentReceiver content_receiver) {
3913-
return Get(path, headers, std::move(response_handler), content_receiver, Progress());
3921+
return Get(path, headers, std::move(response_handler), content_receiver,
3922+
Progress());
39143923
}
39153924

39163925
inline std::shared_ptr<Response> Client::Get(const char *path,

test/Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ all : test
1111
test : test.cc ../httplib.h Makefile cert.pem
1212
$(CXX) -o test $(CXXFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
1313

14+
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
15+
$(CXX) -o test_proxy $(CXXFLAGS) test_proxy.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) -pthread
16+
1417
cert.pem:
1518
openssl genrsa 2048 > key.pem
1619
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
@@ -21,4 +24,4 @@ cert.pem:
2124
#c_rehash .
2225

2326
clean:
24-
rm -f test *.pem *.0 *.1 *.srl
27+
rm -f test test_proxy pem *.0 *.1 *.srl

test/test_proxy.cc

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#include <future>
2+
#include <gtest/gtest.h>
3+
#include <httplib.h>
4+
5+
using namespace std;
6+
using namespace httplib;
7+
8+
void ProxyTest(Client& cli, bool basic) {
9+
cli.set_proxy("localhost", basic ? 3128 : 3129);
10+
auto res = cli.Get("/get");
11+
ASSERT_TRUE(res != nullptr);
12+
EXPECT_EQ(407, res->status);
13+
}
14+
15+
TEST(ProxyTest, NoSSLBasic) {
16+
Client cli("httpbin.org");
17+
ProxyTest(cli, true);
18+
}
19+
20+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
21+
TEST(ProxyTest, SSLBasic) {
22+
SSLClient cli("httpbin.org");
23+
ProxyTest(cli, true);
24+
}
25+
26+
TEST(ProxyTest, NoSSLDigest) {
27+
Client cli("httpbin.org");
28+
ProxyTest(cli, false);
29+
}
30+
31+
TEST(ProxyTest, SSLDigest) {
32+
SSLClient cli("httpbin.org");
33+
ProxyTest(cli, false);
34+
}
35+
#endif
36+
37+
// ----------------------------------------------------------------------------
38+
39+
void RedirectTestHTTPBin(Client& cli, const char *path, bool basic) {
40+
cli.set_proxy("localhost", basic ? 3128 : 3129);
41+
if (basic) {
42+
cli.set_proxy_basic_auth("hello", "world");
43+
} else {
44+
cli.set_proxy_digest_auth("hello", "world");
45+
}
46+
cli.set_follow_location(true);
47+
48+
auto res = cli.Get(path);
49+
ASSERT_TRUE(res != nullptr);
50+
EXPECT_EQ(200, res->status);
51+
}
52+
53+
TEST(RedirectTest, HTTPBinNoSSLBasic) {
54+
Client cli("httpbin.org");
55+
RedirectTestHTTPBin(cli, "/redirect/2", true);
56+
}
57+
58+
TEST(RedirectTest, HTTPBinNoSSLDigest) {
59+
Client cli("httpbin.org");
60+
RedirectTestHTTPBin(cli, "/redirect/2", false);
61+
}
62+
63+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
64+
TEST(RedirectTest, HTTPBinSSLBasic) {
65+
SSLClient cli("httpbin.org");
66+
RedirectTestHTTPBin(cli, "/redirect/2", true);
67+
}
68+
69+
TEST(RedirectTest, HTTPBinSSLDigest) {
70+
SSLClient cli("httpbin.org");
71+
RedirectTestHTTPBin(cli, "/redirect/2", false);
72+
}
73+
#endif
74+
75+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
76+
TEST(RedirectTest, YouTubeNoSSLBasic) {
77+
Client cli("youtube.com");
78+
RedirectTestHTTPBin(cli, "/", true);
79+
}
80+
81+
TEST(RedirectTest, YouTubeNoSSLDigest) {
82+
Client cli("youtube.com");
83+
RedirectTestHTTPBin(cli, "/", false);
84+
}
85+
86+
TEST(RedirectTest, YouTubeSSLBasic) {
87+
SSLClient cli("youtube.com");
88+
RedirectTestHTTPBin(cli, "/", true);
89+
}
90+
91+
TEST(RedirectTest, YouTubeSSLDigest) {
92+
SSLClient cli("youtube.com");
93+
RedirectTestHTTPBin(cli, "/", false);
94+
}
95+
#endif
96+
97+
// ----------------------------------------------------------------------------
98+
99+
void BaseAuthTestFromHTTPWatch(Client& cli) {
100+
cli.set_proxy("localhost", 3128);
101+
cli.set_proxy_basic_auth("hello", "world");
102+
103+
{
104+
auto res = cli.Get("/basic-auth/hello/world");
105+
ASSERT_TRUE(res != nullptr);
106+
EXPECT_EQ(401, res->status);
107+
}
108+
109+
{
110+
auto res =
111+
cli.Get("/basic-auth/hello/world",
112+
{make_basic_authentication_header("hello", "world")});
113+
ASSERT_TRUE(res != nullptr);
114+
EXPECT_EQ(res->body,
115+
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
116+
EXPECT_EQ(200, res->status);
117+
}
118+
119+
{
120+
cli.set_basic_auth("hello", "world");
121+
auto res = cli.Get("/basic-auth/hello/world");
122+
ASSERT_TRUE(res != nullptr);
123+
EXPECT_EQ(res->body,
124+
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
125+
EXPECT_EQ(200, res->status);
126+
}
127+
128+
{
129+
cli.set_basic_auth("hello", "bad");
130+
auto res = cli.Get("/basic-auth/hello/world");
131+
ASSERT_TRUE(res != nullptr);
132+
EXPECT_EQ(401, res->status);
133+
}
134+
135+
{
136+
cli.set_basic_auth("bad", "world");
137+
auto res = cli.Get("/basic-auth/hello/world");
138+
ASSERT_TRUE(res != nullptr);
139+
EXPECT_EQ(401, res->status);
140+
}
141+
}
142+
143+
TEST(BaseAuthTest, NoSSL) {
144+
Client cli("httpbin.org");
145+
BaseAuthTestFromHTTPWatch(cli);
146+
}
147+
148+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
149+
TEST(BaseAuthTest, SSL) {
150+
SSLClient cli("httpbin.org");
151+
BaseAuthTestFromHTTPWatch(cli);
152+
}
153+
154+
// ----------------------------------------------------------------------------
155+
156+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
157+
void DigestAuthTestFromHTTPWatch(Client& cli) {
158+
cli.set_proxy("localhost", 3129);
159+
cli.set_proxy_digest_auth("hello", "world");
160+
161+
{
162+
auto res = cli.Get("/digest-auth/auth/hello/world");
163+
ASSERT_TRUE(res != nullptr);
164+
EXPECT_EQ(401, res->status);
165+
}
166+
167+
{
168+
std::vector<std::string> paths = {
169+
"/digest-auth/auth/hello/world/MD5",
170+
"/digest-auth/auth/hello/world/SHA-256",
171+
"/digest-auth/auth/hello/world/SHA-512",
172+
"/digest-auth/auth-init/hello/world/MD5",
173+
"/digest-auth/auth-int/hello/world/MD5",
174+
};
175+
176+
cli.set_digest_auth("hello", "world");
177+
for (auto path : paths) {
178+
auto res = cli.Get(path.c_str());
179+
ASSERT_TRUE(res != nullptr);
180+
EXPECT_EQ(res->body,
181+
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
182+
EXPECT_EQ(200, res->status);
183+
}
184+
185+
cli.set_digest_auth("hello", "bad");
186+
for (auto path : paths) {
187+
auto res = cli.Get(path.c_str());
188+
ASSERT_TRUE(res != nullptr);
189+
EXPECT_EQ(400, res->status);
190+
}
191+
192+
cli.set_digest_auth("bad", "world");
193+
for (auto path : paths) {
194+
auto res = cli.Get(path.c_str());
195+
ASSERT_TRUE(res != nullptr);
196+
EXPECT_EQ(400, res->status);
197+
}
198+
}
199+
}
200+
#endif
201+
202+
TEST(DigestAuthTest, SSL) {
203+
SSLClient cli("httpbin.org");
204+
DigestAuthTestFromHTTPWatch(cli);
205+
}
206+
207+
TEST(DigestAuthTest, NoSSL) {
208+
Client cli("httpbin.org");
209+
DigestAuthTestFromHTTPWatch(cli);
210+
}
211+
#endif

test/test_proxy_docker/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM centos
2+
LABEL maintainer="yuji.hirose.bug@gmail.com"
3+
ARG auth="basic"
4+
ARG port="3128"
5+
6+
RUN yum install -y squid
7+
8+
COPY ./${auth}_squid.conf /etc/squid/squid.conf
9+
COPY ./${auth}_passwd /etc/squid/passwd
10+
11+
EXPOSE ${port}
12+
13+
CMD ["/usr/sbin/squid", "-N"]

test/test_proxy_docker/basic_passwd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello:$apr1$O6S28OBL$8dr3ixl4Mohf97hgsYvLy/

0 commit comments

Comments
 (0)