Ver Fonte

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.
yhirose há 2 dias atrás
pai
commit
fb439bda2e
1 ficheiros alterados com 49 adições e 71 exclusões
  1. 49 71
      httplib.h

+ 49 - 71
httplib.h

@@ -10334,41 +10334,6 @@ public:
 static WSInit wsinit_;
 #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) {
   std::string out;
   out.reserve(s.size());
@@ -10395,6 +10360,19 @@ inline std::string escape_quoted_pairs(const std::string &s) {
   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,
                                    std::map<std::string, std::string> &auth,
                                    bool is_proxy) {
@@ -10404,44 +10382,44 @@ inline bool parse_www_authenticate(const Response &res,
 
   auto found_digest = false;
   auto in_digest_challenge = false;
-  for (const auto &raw_segment : split_challenge_segments(combined)) {
-    auto segment = trim_copy(raw_segment);
-    if (segment.empty()) { continue; }
-
-    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.
-        continue;
-      }
-      key_part = key_part.substr(space_pos + 1);
-    }
+  split_unquoted(
+      combined.data(), combined.data() + combined.size(), ',',
+      [&](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);
+        }
 
-    if (!in_digest_challenge) { continue; }
+        if (!in_digest_challenge) { return; }
 
-    auto val = trim_copy(segment.substr(eq_pos + 1));
-    auto unquoted = trim_double_quotes_copy(val);
-    if (unquoted.size() != val.size()) {
-      unquoted = unescape_quoted_pairs(unquoted);
-    }
-    auth[std::move(key_part)] = std::move(unquoted);
-  }
+        auto val = trim_copy(segment.substr(eq_pos + 1));
+        auto unquoted = trim_double_quotes_copy(val);
+        if (unquoted.size() != val.size()) {
+          unquoted = unescape_quoted_pairs(unquoted);
+        }
+        auth[std::move(key_part)] = std::move(unquoted);
+      });
 
   // RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge;
   // make_digest_authentication_header() dereferences both unconditionally, so