Skip to content

Commit fd4e1b4

Browse files
committed
1 parent f6a2365 commit fd4e1b4

File tree

4 files changed

+234
-6
lines changed

4 files changed

+234
-6
lines changed

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,16 +324,21 @@ std::shared_ptr<httplib::Response> res =
324324

325325
This feature was contributed by [underscorediscovery](https://github.com/yhirose/cpp-httplib/pull/23).
326326

327-
### Basic Authentication
327+
### Authentication
328328

329329
```cpp
330330
httplib::Client cli("httplib.org");
331+
cli.set_auth("user", "pass");
331332

332-
auto res = cli.Get("/basic-auth/hello/world", {
333-
httplib::make_basic_authentication_header("hello", "world")
334-
});
333+
// Basic
334+
auto res = cli.Get("/basic-auth/user/pass");
335+
// res->status should be 200
336+
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
337+
338+
// Digest
339+
res = cli.Get("/digest-auth/auth/user/pass/SHA-256");
335340
// res->status should be 200
336-
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n".
341+
// res->body should be "{\n \"authenticated\": true, \n \"user\": \"user\"\n}\n".
337342
```
338343
339344
### Range

example/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ pem:
3333
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
3434

3535
clean:
36-
rm server client hello simplesvr upload redirect *.pem
36+
rm server client hello simplesvr upload redirect benchmark *.pem

httplib.h

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,13 @@ using socket_t = int;
149149

150150
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
151151
#include <openssl/err.h>
152+
#include <openssl/md5.h>
152153
#include <openssl/ssl.h>
153154
#include <openssl/x509v3.h>
154155

156+
#include <iomanip>
157+
#include <sstream>
158+
155159
// #if OPENSSL_VERSION_NUMBER < 0x1010100fL
156160
// #error Sorry, OpenSSL versions prior to 1.1.1 are not supported
157161
// #endif
@@ -756,10 +760,13 @@ class Client {
756760
std::vector<Response> &responses);
757761

758762
void set_keep_alive_max_count(size_t count);
763+
759764
void set_read_timeout(time_t sec, time_t usec);
760765

761766
void follow_location(bool on);
762767

768+
void set_auth(const char *username, const char *password);
769+
763770
protected:
764771
bool process_request(Stream &strm, const Request &req, Response &res,
765772
bool last_connection, bool &connection_close);
@@ -772,6 +779,8 @@ class Client {
772779
time_t read_timeout_sec_;
773780
time_t read_timeout_usec_;
774781
size_t follow_location_;
782+
std::string username_;
783+
std::string password_;
775784

776785
private:
777786
socket_t create_client_socket() const;
@@ -1439,6 +1448,7 @@ inline const char *status_message(int status) {
14391448
case 303: return "See Other";
14401449
case 304: return "Not Modified";
14411450
case 400: return "Bad Request";
1451+
case 401: return "Unauthorized";
14421452
case 403: return "Forbidden";
14431453
case 404: return "Not Found";
14441454
case 413: return "Payload Too Large";
@@ -2287,6 +2297,43 @@ inline bool expect_content(const Request &req) {
22872297
return false;
22882298
}
22892299

2300+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2301+
template <typename CTX, typename Init, typename Update, typename Final>
2302+
inline std::string message_digest(const std::string &s, Init init,
2303+
Update update, Final final,
2304+
size_t digest_length) {
2305+
using namespace std;
2306+
2307+
unsigned char md[digest_length];
2308+
CTX ctx;
2309+
init(&ctx);
2310+
update(&ctx, s.data(), s.size());
2311+
final(md, &ctx);
2312+
2313+
stringstream ss;
2314+
for (auto c : md) {
2315+
ss << setfill('0') << setw(2) << hex << (unsigned int)c;
2316+
}
2317+
return ss.str();
2318+
}
2319+
2320+
inline std::string MD5(const std::string &s) {
2321+
using namespace detail;
2322+
return message_digest<MD5_CTX>(s, MD5_Init, MD5_Update, MD5_Final,
2323+
MD5_DIGEST_LENGTH);
2324+
}
2325+
2326+
inline std::string SHA_256(const std::string &s) {
2327+
return message_digest<SHA256_CTX>(s, SHA256_Init, SHA256_Update, SHA256_Final,
2328+
SHA256_DIGEST_LENGTH);
2329+
}
2330+
2331+
inline std::string SHA_512(const std::string &s) {
2332+
return message_digest<SHA512_CTX>(s, SHA512_Init, SHA512_Update, SHA512_Final,
2333+
SHA512_DIGEST_LENGTH);
2334+
}
2335+
#endif
2336+
22902337
#ifdef _WIN32
22912338
class WSInit {
22922339
public:
@@ -2324,6 +2371,98 @@ make_basic_authentication_header(const std::string &username,
23242371
return std::make_pair("Authorization", field);
23252372
}
23262373

2374+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
2375+
inline std::pair<std::string, std::string> make_digest_authentication_header(
2376+
const Request &req,
2377+
const std::map<std::string, std::string> &auth,
2378+
size_t cnonce_count, const std::string &cnonce,
2379+
const std::string &username, const std::string &password) {
2380+
using namespace std;
2381+
2382+
string nc;
2383+
{
2384+
stringstream ss;
2385+
ss << setfill('0') << setw(8) << hex << cnonce_count;
2386+
nc = ss.str();
2387+
}
2388+
2389+
auto qop = auth.at("qop");
2390+
if (qop.find("auth-int") != std::string::npos) {
2391+
qop = "auth-int";
2392+
} else {
2393+
qop = "auth";
2394+
}
2395+
2396+
string response;
2397+
{
2398+
auto algo = auth.at("algorithm");
2399+
2400+
auto H = algo == "SHA-256"
2401+
? detail::SHA_256
2402+
: algo == "SHA-512" ? detail::SHA_512 : detail::MD5;
2403+
2404+
auto A1 = username + ":" + auth.at("realm") + ":" + password;
2405+
2406+
auto A2 = req.method + ":" + req.path;
2407+
if (qop == "auth-int") {
2408+
A2 += ":" + H(req.body);
2409+
}
2410+
2411+
response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
2412+
":" + qop + ":" + H(A2));
2413+
}
2414+
2415+
auto field = "Digest username=\"hello\", realm=\"" + auth.at("realm") +
2416+
"\", nonce=\"" + auth.at("nonce") + "\", uri=\"" + req.path +
2417+
"\", algorithm=" + auth.at("algorithm") + ", qop=" + qop + ", nc=\"" +
2418+
nc + "\", cnonce=\"" + cnonce + "\", response=\"" + response +
2419+
"\"";
2420+
2421+
return make_pair("Authorization", field);
2422+
}
2423+
#endif
2424+
2425+
inline int parse_www_authenticate(const httplib::Response &res,
2426+
std::map<std::string, std::string> &digest_auth) {
2427+
if (res.has_header("WWW-Authenticate")) {
2428+
static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*)))))~");
2429+
auto s = res.get_header_value("WWW-Authenticate");
2430+
auto pos = s.find(' ');
2431+
if (pos != std::string::npos) {
2432+
auto type = s.substr(0, pos);
2433+
if (type == "Basic") {
2434+
return 1;
2435+
} else if (type == "Digest") {
2436+
s = s.substr(pos + 1);
2437+
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
2438+
for (auto i = beg; i != std::sregex_iterator(); ++i) {
2439+
auto m = *i;
2440+
auto key = s.substr(m.position(1), m.length(1));
2441+
auto val = m.length(2) > 0 ? s.substr(m.position(2), m.length(2))
2442+
: s.substr(m.position(3), m.length(3));
2443+
digest_auth[key] = val;
2444+
}
2445+
return 2;
2446+
}
2447+
}
2448+
}
2449+
return 0;
2450+
}
2451+
2452+
// https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c/440240#answer-440240
2453+
inline std::string random_string(size_t length) {
2454+
auto randchar = []() -> char {
2455+
const char charset[] = "0123456789"
2456+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2457+
"abcdefghijklmnopqrstuvwxyz";
2458+
const size_t max_index = (sizeof(charset) - 1);
2459+
return charset[rand() % max_index];
2460+
};
2461+
std::string str(length, 0);
2462+
std::generate_n(str.begin(), length, randchar);
2463+
return str;
2464+
}
2465+
23272466
// Request implementation
23282467
inline bool Request::has_header(const char *key) const {
23292468
return detail::has_header(headers, key);
@@ -3244,6 +3383,43 @@ inline bool Client::send(const Request &req, Response &res) {
32443383
ret = redirect(req, res);
32453384
}
32463385

3386+
if (ret && !username_.empty() && !password_.empty() && res.status == 401) {
3387+
int type;
3388+
std::map<std::string, std::string> digest_auth;
3389+
3390+
if ((type = parse_www_authenticate(res, digest_auth)) > 0) {
3391+
std::pair<std::string, std::string> header;
3392+
3393+
if (type == 1) {
3394+
header = make_basic_authentication_header(username_, password_);
3395+
} else if (type == 2) {
3396+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3397+
size_t cnonce_count = 1;
3398+
auto cnonce = random_string(10);
3399+
3400+
header = make_digest_authentication_header(
3401+
req, digest_auth, cnonce_count, cnonce, username_, password_);
3402+
#endif
3403+
}
3404+
3405+
Request new_req;
3406+
new_req.method = req.method;
3407+
new_req.path = req.path;
3408+
new_req.headers = req.headers;
3409+
new_req.body = req.body;
3410+
new_req.response_handler = req.response_handler;
3411+
new_req.content_receiver = req.content_receiver;
3412+
new_req.progress = req.progress;
3413+
3414+
new_req.headers.insert(header);
3415+
3416+
Response new_res;
3417+
auto ret = send(new_req, new_res);
3418+
if (ret) { res = new_res; }
3419+
return ret;
3420+
}
3421+
}
3422+
32473423
return ret;
32483424
}
32493425

@@ -3810,6 +3986,11 @@ inline void Client::set_read_timeout(time_t sec, time_t usec) {
38103986

38113987
inline void Client::follow_location(bool on) { follow_location_ = on; }
38123988

3989+
inline void Client::set_auth(const char *username, const char *password) {
3990+
username_ = username;
3991+
password_ = password;
3992+
}
3993+
38133994
/*
38143995
* SSL Implementation
38153996
*/

test/test.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,49 @@ TEST(BaseAuthTest, FromHTTPWatch) {
469469
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
470470
EXPECT_EQ(200, res->status);
471471
}
472+
473+
{
474+
cli.set_auth("hello", "world");
475+
auto res = cli.Get("/basic-auth/hello/world");
476+
ASSERT_TRUE(res != nullptr);
477+
EXPECT_EQ(res->body,
478+
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
479+
EXPECT_EQ(200, res->status);
480+
}
481+
}
482+
483+
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
484+
TEST(DigestAuthTest, FromHTTPWatch) {
485+
auto host = "httpbin.org";
486+
auto port = 443;
487+
httplib::SSLClient cli(host, port);
488+
489+
{
490+
auto res = cli.Get("/digest-auth/auth/hello/world");
491+
ASSERT_TRUE(res != nullptr);
492+
EXPECT_EQ(401, res->status);
493+
}
494+
495+
{
496+
std::vector<std::string> paths = {
497+
"/digest-auth/auth/hello/world/MD5",
498+
"/digest-auth/auth/hello/world/SHA-256",
499+
"/digest-auth/auth/hello/world/SHA-512",
500+
"/digest-auth/auth-init/hello/world/MD5",
501+
"/digest-auth/auth-int/hello/world/MD5",
502+
};
503+
504+
cli.set_auth("hello", "world");
505+
for (auto path: paths) {
506+
auto res = cli.Get(path.c_str());
507+
ASSERT_TRUE(res != nullptr);
508+
EXPECT_EQ(res->body,
509+
"{\n \"authenticated\": true, \n \"user\": \"hello\"\n}\n");
510+
EXPECT_EQ(200, res->status);
511+
}
512+
}
472513
}
514+
#endif
473515

474516
TEST(AbsoluteRedirectTest, Redirect) {
475517
auto host = "httpbin.org";

0 commit comments

Comments
 (0)