mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Split authentication challenges with split_unquoted
split_challenge_segments() did what split_unquoted() does, trimming and dropping empty segments included, so parse_www_authenticate() uses the latter.
This commit is contained in:
1 parent
079f2176af
commit
fb439bda2e
1 file changed
+49
-71
@@ -10334,41 +10334,6 @@ public:
|
|||||||
static WSInit wsinit_;
|
static WSInit wsinit_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// RFC 9110 Section 11.6.1 defines a challenge list as
|
|
||||||
// WWW-Authenticate = #challenge
|
|
||||||
// challenge = auth-scheme [ 1*SP ( token68 / [ #auth-param ] ) ]
|
|
||||||
// auth-param = token BWS "=" BWS ( token / quoted-string )
|
|
||||||
// so a server may offer several schemes, each with its own comma-separated
|
|
||||||
// auth-param list, in either order and either as separate field lines or
|
|
||||||
// packed into one. Splitting on every comma would break apart a challenge's
|
|
||||||
// own param list; splitting only on the first space would miss a Digest
|
|
||||||
// challenge that isn't first. Split on commas that aren't inside a
|
|
||||||
// quoted-string instead, then track which scheme each resulting segment
|
|
||||||
// belongs to: a segment whose text before "=" contains whitespace (or that
|
|
||||||
// has no "=" at all) starts a new challenge named by its leading token.
|
|
||||||
inline std::vector<std::string> split_challenge_segments(const std::string &s) {
|
|
||||||
std::vector<std::string> segments;
|
|
||||||
size_t start = 0;
|
|
||||||
auto in_quotes = false;
|
|
||||||
for (size_t i = 0; i < s.size(); i++) {
|
|
||||||
auto c = s[i];
|
|
||||||
if (in_quotes) {
|
|
||||||
if (c == '\\' && i + 1 < s.size()) {
|
|
||||||
i++;
|
|
||||||
} else if (c == '"') {
|
|
||||||
in_quotes = false;
|
|
||||||
}
|
|
||||||
} else if (c == '"') {
|
|
||||||
in_quotes = true;
|
|
||||||
} else if (c == ',') {
|
|
||||||
segments.push_back(s.substr(start, i - start));
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segments.push_back(s.substr(start));
|
|
||||||
return segments;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string unescape_quoted_pairs(const std::string &s) {
|
inline std::string unescape_quoted_pairs(const std::string &s) {
|
||||||
std::string out;
|
std::string out;
|
||||||
out.reserve(s.size());
|
out.reserve(s.size());
|
||||||
@@ -10395,6 +10360,19 @@ inline std::string escape_quoted_pairs(const std::string &s) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RFC 9110 Section 11.6.1 defines a challenge list as
|
||||||
|
// WWW-Authenticate = #challenge
|
||||||
|
// challenge = auth-scheme [ 1*SP ( token68 / [ #auth-param ] ) ]
|
||||||
|
// auth-param = token BWS "=" BWS ( token / quoted-string )
|
||||||
|
// so a server may offer several schemes, each with its own comma-separated
|
||||||
|
// auth-param list, in either order and either as separate field lines or
|
||||||
|
// packed into one. Splitting on every comma would break apart a challenge's
|
||||||
|
// own param list; splitting only on the first space would miss a Digest
|
||||||
|
// challenge that isn't first. Split on commas that aren't inside a
|
||||||
|
// quoted-string instead (split_unquoted), then track which scheme each
|
||||||
|
// resulting segment belongs to: a segment whose text before "=" contains
|
||||||
|
// whitespace (or that has no "=" at all) starts a new challenge named by its
|
||||||
|
// leading token.
|
||||||
inline bool parse_www_authenticate(const Response &res,
|
inline bool parse_www_authenticate(const Response &res,
|
||||||
std::map<std::string, std::string> &auth,
|
std::map<std::string, std::string> &auth,
|
||||||
bool is_proxy) {
|
bool is_proxy) {
|
||||||
@@ -10404,44 +10382,44 @@ inline bool parse_www_authenticate(const Response &res,
|
|||||||
|
|
||||||
auto found_digest = false;
|
auto found_digest = false;
|
||||||
auto in_digest_challenge = false;
|
auto in_digest_challenge = false;
|
||||||
for (const auto &raw_segment : split_challenge_segments(combined)) {
|
split_unquoted(
|
||||||
auto segment = trim_copy(raw_segment);
|
combined.data(), combined.data() + combined.size(), ',',
|
||||||
if (segment.empty()) { continue; }
|
[&](const char *b, const char *e) {
|
||||||
|
std::string segment(b, e);
|
||||||
|
auto eq_pos = segment.find('=');
|
||||||
|
// BWS is allowed on both sides of "=", so the text naming the key (or,
|
||||||
|
// for the first segment of a challenge, "<scheme> <key>") must be
|
||||||
|
// trimmed before its boundaries are inspected.
|
||||||
|
auto key_part = trim_copy(
|
||||||
|
eq_pos == std::string::npos ? segment : segment.substr(0, eq_pos));
|
||||||
|
auto space_pos = key_part.find_last_of(" \t");
|
||||||
|
if (space_pos != std::string::npos || eq_pos == std::string::npos) {
|
||||||
|
// "<scheme>[ <key>]" starts a new challenge.
|
||||||
|
auto scheme_end =
|
||||||
|
space_pos == std::string::npos ? key_part.size() : space_pos;
|
||||||
|
// RFC 7616 Section 3.7: a server may offer more than one Digest
|
||||||
|
// challenge (e.g. SHA-256 and MD5); keep only the first so a nonce
|
||||||
|
// from one challenge is never paired with another's algorithm.
|
||||||
|
in_digest_challenge =
|
||||||
|
!found_digest &&
|
||||||
|
case_ignore::equal(key_part.substr(0, scheme_end), "Digest");
|
||||||
|
if (in_digest_challenge) { found_digest = true; }
|
||||||
|
if (space_pos == std::string::npos) {
|
||||||
|
// Bare scheme (or a token68), no auth-param on this segment.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
key_part = key_part.substr(space_pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
auto eq_pos = segment.find('=');
|
if (!in_digest_challenge) { return; }
|
||||||
// BWS is allowed on both sides of "=", so the text naming the key (or,
|
|
||||||
// for the first segment of a challenge, "<scheme> <key>") must be
|
|
||||||
// trimmed before its boundaries are inspected.
|
|
||||||
auto key_part = trim_copy(
|
|
||||||
eq_pos == std::string::npos ? segment : segment.substr(0, eq_pos));
|
|
||||||
auto space_pos = key_part.find_last_of(" \t");
|
|
||||||
if (space_pos != std::string::npos || eq_pos == std::string::npos) {
|
|
||||||
// "<scheme>[ <key>]" starts a new challenge.
|
|
||||||
auto scheme_end =
|
|
||||||
space_pos == std::string::npos ? key_part.size() : space_pos;
|
|
||||||
// RFC 7616 Section 3.7: a server may offer more than one Digest
|
|
||||||
// challenge (e.g. SHA-256 and MD5); keep only the first so a nonce
|
|
||||||
// from one challenge is never paired with another's algorithm.
|
|
||||||
in_digest_challenge =
|
|
||||||
!found_digest &&
|
|
||||||
case_ignore::equal(key_part.substr(0, scheme_end), "Digest");
|
|
||||||
if (in_digest_challenge) { found_digest = true; }
|
|
||||||
if (space_pos == std::string::npos) {
|
|
||||||
// Bare scheme (or a token68), no auth-param on this segment.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
key_part = key_part.substr(space_pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!in_digest_challenge) { continue; }
|
auto val = trim_copy(segment.substr(eq_pos + 1));
|
||||||
|
auto unquoted = trim_double_quotes_copy(val);
|
||||||
auto val = trim_copy(segment.substr(eq_pos + 1));
|
if (unquoted.size() != val.size()) {
|
||||||
auto unquoted = trim_double_quotes_copy(val);
|
unquoted = unescape_quoted_pairs(unquoted);
|
||||||
if (unquoted.size() != val.size()) {
|
}
|
||||||
unquoted = unescape_quoted_pairs(unquoted);
|
auth[std::move(key_part)] = std::move(unquoted);
|
||||||
}
|
});
|
||||||
auth[std::move(key_part)] = std::move(unquoted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge;
|
// RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge;
|
||||||
// make_digest_authentication_header() dereferences both unconditionally, so
|
// make_digest_authentication_header() dereferences both unconditionally, so
|
||||||
|
|||||||
Reference in new issue
Block a user