30#include "gtest/gtest.h"
33#include <boost/algorithm/string/predicate.hpp>
34#include <boost/algorithm/string/join.hpp>
35#include <boost/fusion/adapted/std_pair.hpp>
36#include <boost/range/algorithm/find_if.hpp>
37#include <boost/range/iterator_range_core.hpp>
38#include <boost/spirit/include/karma_char.hpp>
39#include <boost/spirit/include/karma_list.hpp>
40#include <boost/spirit/include/karma_generate.hpp>
41#include <boost/spirit/include/karma_right_alignment.hpp>
42#include <boost/spirit/include/karma_sequence.hpp>
43#include <boost/spirit/include/karma_string.hpp>
44#include <boost/spirit/include/karma_uint.hpp>
45#include <boost/spirit/include/qi_alternative.hpp>
46#include <boost/spirit/include/qi_char.hpp>
47#include <boost/spirit/include/qi_char_class.hpp>
48#include <boost/spirit/include/qi_difference.hpp>
49#include <boost/spirit/include/qi_eoi.hpp>
50#include <boost/spirit/include/qi_list.hpp>
51#include <boost/spirit/include/qi_parse.hpp>
52#include <boost/spirit/include/qi_plus.hpp>
53#include <boost/spirit/include/qi_sequence.hpp>
54#include <boost/spirit/include/qi_string.hpp>
58#include <unordered_map>
68using fields = std::unordered_map<std::string, std::string>;
69using auth_responses = std::vector<fields>;
71void rng(
size_t len,
uint8_t *ptr)
76std::string quoted(std::string str)
78 str.insert(str.begin(),
'"');
83void write_fields(std::string& out,
const fields& args)
85 namespace karma = boost::spirit::karma;
87 std::back_inserter(out),
88 (karma::string <<
" = " << karma::string) %
" , ",
92std::string write_fields(
const fields& args)
95 write_fields(out, args);
99http::http_request_info make_request(
const fields& args)
101 std::string
out{
" DIGEST "};
102 write_fields(out, args);
104 http::http_request_info request{};
105 request.m_http_method_str =
"NOP";
106 request.m_header_info.m_etc_fields.push_back(
107 std::make_pair(
u8"authorization", std::move(out))
112http::http_response_info make_response(
const auth_responses& choices)
114 http::http_response_info
response{};
115 for (
const auto& choice : choices)
117 std::string
out{
" DIGEST "};
118 write_fields(out, choice);
120 response.m_header_info.m_etc_fields.push_back(
121 std::make_pair(
u8"WWW-authenticate", std::move(out))
127bool has_same_fields(
const auth_responses& in)
129 const std::vector<std::string> check{
u8"nonce",
u8"qop",
u8"realm",
u8"stale"};
131 auto current =
in.begin();
132 const auto end =
in.end();
137 for ( ; current != end; ++current )
139 for (
const auto& field : check)
141 const std::string& expected =
in[0].at(field);
142 const std::string& actual = current->at(field);
144 if (expected != actual)
151bool is_unauthorized(
const http::http_response_info& response)
156 return response.m_response_code == 401 &&
157 response.m_response_comment ==
u8"Unauthorized" &&
161fields parse_fields(
const std::string&
value)
163 namespace qi = boost::spirit::qi;
166 const bool rc = qi::parse(
168 qi::lit(
u8"Digest ") >> ((
171 (qi::lit(
'"') >> +(qi::ascii::char_ -
'"') >> qi::lit(
'"')) |
172 +(qi::ascii::graph - qi::ascii::char_(
u8"()<>@,;:\\\"/[]?={}"))
179 throw std::runtime_error{
"Bad field given in HTTP header"};
184auth_responses parse_response(
const http::http_response_info& response)
186 auth_responses result{};
188 const auto end =
response.m_additional_fields.end();
189 for (
auto current =
response.m_additional_fields.begin();; ++current)
191 current = std::find_if(current, end, [] (
const std::pair<std::string, std::string>& field) {
192 return boost::equals(
u8"WWW-authenticate", field.first);
198 result.push_back(parse_fields(current->second));
203std::string md5_hex(
const std::string& in)
206 md5::MD5Init(std::addressof(ctx));
209 reinterpret_cast<const std::uint8_t*
>(
in.data()),
213 std::array<std::uint8_t, 16> digest{{}};
214 md5::MD5Final(digest.data(), std::addressof(ctx));
218std::string get_a1(
const http::login& user,
const fields& src)
220 const std::string& realm = src.at(
u8"realm");
222 std::vector<std::string>{user.username, realm, std::string(user.password.data(), user.password.size())},
u8":"
226std::string get_a1(
const http::login& user,
const auth_responses& responses)
228 return get_a1(user, responses.at(0));
231std::string get_a1_sess(
const http::login& user,
const std::string& cnonce,
const auth_responses& responses)
233 const std::string& nonce = responses.at(0).at(
u8"nonce");
235 std::vector<std::string>{md5_hex(get_a1(user, responses)), nonce, cnonce},
u8":"
239std::string get_a2(
const std::string& uri)
241 return boost::join(std::vector<std::string>{
"NOP", uri},
u8":");
244std::string get_nc(std::uint32_t count)
246 namespace karma = boost::spirit::karma;
249 std::back_inserter(out),
250 karma::right_align(8,
'0')[karma::uint_generator<std::uint32_t, 16>{}],
258TEST(HTTP_Server_Auth, NotRequired)
260 http::http_server_auth auth{};
261 EXPECT_FALSE(auth.get_response(http::http_request_info{}));
264TEST(HTTP_Server_Auth, MissingAuth)
266 http::http_server_auth auth{{
"foo",
"bar"}, rng};
267 EXPECT_TRUE(
bool(auth.get_response(http::http_request_info{})));
269 http::http_request_info request{};
270 request.m_header_info.m_etc_fields.push_back({
"\xFF",
"\xFF"});
275TEST(HTTP_Server_Auth, BadSyntax)
277 http::http_server_auth auth{{
"foo",
"bar"}, rng};
278 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"algorithm",
"fo\xFF"}}))));
279 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"cnonce",
"\"000\xFF\""}}))));
280 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"cnonce \xFF =",
"\"000\xFF\""}}))));
281 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8" \xFF cnonce",
"\"000\xFF\""}}))));
286 http::login user{
"foo",
"bar"};
287 http::http_server_auth auth{user, rng};
289 const auto response = auth.get_response(make_request(fields{}));
293 const auto fields = parse_response(*response);
297 const std::string& nonce = fields[0].at(
u8"nonce");
300 const std::string uri{
"/some_foo_thing"};
302 const std::string a1 = get_a1(user, fields);
303 const std::string a2 = get_a2(uri);
305 const std::string auth_code = md5_hex(
306 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
309 const auto request = make_request({
310 {
u8"nonce", quoted(nonce)},
311 {
u8"realm", quoted(fields[0].at(
u8"realm"))},
312 {
u8"response", quoted(auth_code)},
313 {
u8"uri", quoted(uri)},
314 {
u8"username", quoted(user.username)}
319 const auto response2 = auth.get_response(request);
323 const auto fields2 = parse_response(*response2);
331TEST(HTTP_Server_Auth, MD5_sess)
333 constexpr const char cnonce[] =
"not a good cnonce";
335 http::login user{
"foo",
"bar"};
336 http::http_server_auth auth{user, rng};
338 const auto response = auth.get_response(make_request(fields{}));
342 const auto fields = parse_response(*response);
346 const std::string& nonce = fields[0].at(
u8"nonce");
349 const std::string uri{
"/some_foo_thing"};
351 const std::string a1 = get_a1_sess(user, cnonce, fields);
352 const std::string a2 = get_a2(uri);
354 const std::string auth_code = md5_hex(
355 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
358 const auto request = make_request({
359 {
u8"algorithm",
u8"md5-sess"},
360 {
u8"cnonce", quoted(cnonce)},
361 {
u8"nonce", quoted(nonce)},
362 {
u8"realm", quoted(fields[0].at(
u8"realm"))},
363 {
u8"response", quoted(auth_code)},
364 {
u8"uri", quoted(uri)},
365 {
u8"username", quoted(user.username)}
370 const auto response2 = auth.get_response(request);
374 const auto fields2 = parse_response(*response2);
382TEST(HTTP_Server_Auth, MD5_auth)
384 constexpr const char cnonce[] =
"not a nonce";
385 constexpr const char qop[] =
"auth";
387 http::login user{
"foo",
"bar"};
388 http::http_server_auth auth{user, rng};
390 const auto response = auth.get_response(make_request(fields{}));
394 const auto parsed = parse_response(*response);
398 const std::string& nonce = parsed[0].at(
u8"nonce");
401 const std::string uri{
"/some_foo_thing"};
403 const std::string a1 = get_a1(user, parsed);
404 const std::string a2 = get_a2(uri);
405 std::string nc = get_nc(1);
407 const auto generate_auth = [&] {
410 std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)},
u8":"
416 {
u8"algorithm", quoted(
u8"md5")},
417 {
u8"cnonce", quoted(cnonce)},
419 {
u8"nonce", quoted(nonce)},
420 {
u8"qop", quoted(qop)},
421 {
u8"realm", quoted(parsed[0].at(
u8"realm"))},
422 {
u8"response", quoted(generate_auth())},
423 {
u8"uri", quoted(uri)},
424 {
u8"username", quoted(user.username)}
427 const auto request = make_request(args);
430 for (
unsigned i = 2; i < 20; ++i)
433 args.at(
u8"nc") = nc;
434 args.at(
u8"response") = quoted(generate_auth());
438 const auto replay = auth.get_response(request);
442 const auto parsed_replay = parse_response(*replay);
450TEST(HTTP_Server_Auth, MD5_sess_auth)
452 constexpr const char cnonce[] =
"not a nonce";
453 constexpr const char qop[] =
"auth";
455 http::login user{
"foo",
"bar"};
456 http::http_server_auth auth{user, rng};
458 const auto response = auth.get_response(make_request(fields{}));
462 const auto parsed = parse_response(*response);
466 const std::string& nonce = parsed[0].at(
u8"nonce");
469 const std::string uri{
"/some_foo_thing"};
471 const std::string a1 = get_a1_sess(user, cnonce, parsed);
472 const std::string a2 = get_a2(uri);
473 std::string nc = get_nc(1);
475 const auto generate_auth = [&] {
478 std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)},
u8":"
484 {
u8"algorithm",
u8"md5-sess"},
485 {
u8"cnonce", quoted(cnonce)},
487 {
u8"nonce", quoted(nonce)},
489 {
u8"realm", quoted(parsed[0].at(
u8"realm"))},
490 {
u8"response", quoted(generate_auth())},
491 {
u8"uri", quoted(uri)},
492 {
u8"username", quoted(user.username)}
495 const auto request = make_request(args);
498 for (
unsigned i = 2; i < 20; ++i)
501 args.at(
u8"nc") = nc;
502 args.at(
u8"response") = quoted(generate_auth());
506 const auto replay = auth.get_response(request);
510 const auto parsed_replay = parse_response(*replay);
521 const auto add_auth_field = [] (http::http_request_info& request, http::http_client_auth& client)
523 auto field = client.get_auth_field(request.m_http_method_str, request.m_URI);
527 request.m_header_info.m_etc_fields.push_back(std::move(*field));
531 const http::login user{
"some_user",
"ultimate password"};
533 http::http_server_auth server{user, rng};
534 http::http_client_auth client{user};
536 http::http_request_info request{};
537 request.m_http_method_str =
"GET";
538 request.m_URI =
"/FOO";
540 auto response = server.get_response(request);
543 EXPECT_TRUE(response->m_header_info.m_etc_fields.empty());
544 response->m_header_info.m_etc_fields = response->m_additional_fields;
546 EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
550 for (
unsigned i = 0; i < 1000; ++i)
552 request.m_http_method_str += std::to_string(i);
553 request.m_header_info.m_etc_fields.clear();
559 request.m_header_info.m_etc_fields.clear();
560 client = http::http_client_auth{user};
561 EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response));
564 auto response2 = server.get_response(request);
567 EXPECT_TRUE(response2->m_header_info.m_etc_fields.empty());
568 response2->m_header_info.m_etc_fields = response2->m_additional_fields;
570 const auth_responses parsed1 = parse_response(*response);
571 const auth_responses parsed2 = parse_response(*response2);
574 EXPECT_NE(parsed1[0].at(
u8"nonce"), parsed2[0].at(
u8"nonce"));
577 request.m_header_info.m_etc_fields.clear();
578 EXPECT_EQ(http::http_client_auth::kSuccess, client.handle_401(*response2));
583 EXPECT_EQ(http::http_client_auth::kBadPassword, client.handle_401(*response));
586TEST(HTTP_Client_Auth, Unavailable)
588 http::http_client_auth auth{};
589 EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(http::http_response_info{}));
590 EXPECT_FALSE(
bool(auth.get_auth_field(
"GET",
"/file")));
593TEST(HTTP_Client_Auth, MissingAuthenticate)
595 http::http_client_auth auth{{
"foo",
"bar"}};
596 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(http::http_response_info{}));
597 EXPECT_FALSE(
bool(auth.get_auth_field(
"POST",
"/\xFFname")));
599 http::http_response_info response{};
600 response.m_additional_fields.push_back({
"\xFF",
"\xFF"});
601 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(response));
603 EXPECT_FALSE(
bool(auth.get_auth_field(
"DELETE",
"/file/does/not/exist")));
606TEST(HTTP_Client_Auth, BadSyntax)
608 http::http_client_auth auth{{
"foo",
"bar"}};
609 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{
u8"realm",
"fo\xFF"}}})));
610 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{
u8"domain",
"fo\xFF"}}})));
611 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{
u8"nonce",
"fo\xFF"}}})));
612 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{
u8"nonce \xFF =",
"fo\xFF"}}})));
613 EXPECT_EQ(http::http_client_auth::kParseFailure, auth.handle_401(make_response({{{
u8" \xFF nonce",
"fo\xFF"}}})));
618 constexpr char method[] =
"NOP";
619 constexpr char nonce[] =
"some crazy nonce";
620 constexpr char realm[] =
"the only realm";
621 constexpr char uri[] =
"/some_file";
623 const http::login user{
"foo",
"bar"};
624 http::http_client_auth auth{user};
626 auto response = make_response({
628 {
u8"domain", quoted(
"ignored")},
629 {
u8"nonce", quoted(nonce)},
630 {
u8"REALM", quoted(realm)}
633 {
u8"algorithm",
"null"},
634 {
u8"domain", quoted(
"ignored")},
635 {
u8"nonce", quoted(std::string{
"e"} + nonce)},
636 {
u8"realm", quoted(std::string{
"e"} + realm)}
640 EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
641 const auto auth_field = auth.get_auth_field(method, uri);
644 const auto parsed = parse_fields(auth_field->second);
655 const std::string a1 = get_a1(user, parsed);
656 const std::string a2 = get_a2(uri);
657 const std::string auth_code = md5_hex(
658 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
660 EXPECT_TRUE(boost::iequals(auth_code, parsed.at(
u8"response")));
662 const auto auth_field_dup = auth.get_auth_field(method, uri);
668 EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
669 response.m_header_info.m_etc_fields.front().second.append(
u8"," + write_fields({{
u8"stale",
u8"TRUE"}}));
670 EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
673TEST(HTTP_Client_Auth, MD5_auth)
675 constexpr char cnonce[] =
"";
676 constexpr char method[] =
"NOP";
677 constexpr char nonce[] =
"some crazy nonce";
678 constexpr char opaque[] =
"this is the opaque";
679 constexpr char qop[] =
u8"ignore,auth,ignore";
680 constexpr char realm[] =
"the only realm";
681 constexpr char uri[] =
"/some_file";
683 const http::login user{
"foo",
"bar"};
684 http::http_client_auth auth{user};
686 auto response = make_response({
688 {
u8"algorithm",
u8"MD5"},
689 {
u8"domain", quoted(
"ignored")},
690 {
u8"nonce", quoted(std::string{
"e"} + nonce)},
691 {
u8"realm", quoted(std::string{
"e"} + realm)},
692 {
u8"qop", quoted(
"some,thing,to,ignore")}
695 {
u8"algorIthm", quoted(
u8"md5")},
696 {
u8"domain", quoted(
"ignored")},
697 {
u8"noNce", quoted(nonce)},
698 {
u8"opaque", quoted(opaque)},
699 {
u8"realm", quoted(realm)},
700 {
u8"QoP", quoted(qop)}
704 EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
706 for (
unsigned i = 1; i < 1000; ++i)
708 const std::string nc = get_nc(i);
710 const auto auth_field = auth.get_auth_field(method, uri);
713 const auto parsed = parse_fields(auth_field->second);
724 const std::string a1 = get_a1(user, parsed);
725 const std::string a2 = get_a2(uri);
726 const std::string auth_code = md5_hex(
727 boost::join(std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce,
u8"auth", md5_hex(a2)},
u8":")
729 EXPECT_TRUE(boost::iequals(auth_code, parsed.at(
u8"response")));
732 EXPECT_EQ(http::http_client_auth::kBadPassword, auth.handle_401(response));
733 response.m_header_info.m_etc_fields.back().second.append(
u8"," + write_fields({{
u8"stale",
u8"trUe"}}));
734 EXPECT_EQ(http::http_client_auth::kSuccess, auth.handle_401(response));
740 std::string str{
"leading text"};
741 epee::net_utils::http::add_field(str,
"foo",
"bar");
742 epee::net_utils::http::add_field(str, std::string(
"bar"), std::string(
"foo"));
743 epee::net_utils::http::add_field(str, {
"moarbars",
"moarfoo"});
745 EXPECT_STREQ(
"leading textfoo: bar\r\nbar: foo\r\nmoarbars: moarfoo\r\n", str.c_str());
#define EXPECT_EQ(val1, val2)
#define EXPECT_NE(val1, val2)
#define ASSERT_LE(val1, val2)
#define EXPECT_TRUE(condition)
#define EXPECT_STREQ(s1, s2)
#define TEST(test_case_name, test_name)
#define ASSERT_TRUE(condition)
#define EXPECT_FALSE(condition)
std::enable_if< std::is_pod< T >::value, T >::type rand()
epee::misc_utils::struct_init< response_t > response
mdb_size_t count(MDB_cursor *cur)
const GenericPointer< typename T::ValueType > T2 value