Log request header count in GFE. We will use this for potential log analysis in the context of Sonic Marmot (omg/98169).

Note: a new field in HttpRequestInfo is needed to track this because by the time we get to session_logger, the request headers in there already include all of the headers added by the GFE, and it would not be right to count these. So we propagate the count of what was actually received from the client.

Protected by logging change.

PiperOrigin-RevId: 927836409
diff --git a/quiche/balsa/balsa_headers.cc b/quiche/balsa/balsa_headers.cc
index fae19d3..ee946a0 100644
--- a/quiche/balsa/balsa_headers.cc
+++ b/quiche/balsa/balsa_headers.cc
@@ -199,7 +199,7 @@
     // found. marked.
     const char* nws = start;
 
-    // search for next whitspace or separator char.
+    // search for next whitespace or separator char.
     while (*start != ',' && static_cast<unsigned char>(*start) > ' ') {
       ++start;
       if (start == end) {
@@ -231,6 +231,7 @@
   whitespace_4_idx_ = 0;
   header_lines_.clear();
   header_lines_.shrink_to_fit();
+  header_lines_removed_ = 0;
 }
 
 void BalsaHeaders::CopyFrom(const BalsaHeaders& other) {
@@ -253,6 +254,7 @@
   non_whitespace_3_idx_ = other.non_whitespace_3_idx_;
   whitespace_4_idx_ = other.whitespace_4_idx_;
   header_lines_ = other.header_lines_;
+  header_lines_removed_ = other.header_lines_removed_;
 }
 
 void BalsaHeaders::AddAndMakeDescription(absl::string_view key,
@@ -341,6 +343,7 @@
   MaybeClearSpecialHeaderValues(key);
   while (start != header_lines_.end()) {
     start->skip = true;
+    ++header_lines_removed_;
     ++start;
     start = GetHeaderLinesIterator(key, start);
   }
@@ -386,6 +389,7 @@
 
   // Invalidate the old header line and add the new one.
   i->skip = true;
+  ++header_lines_removed_;
   header_lines_.push_back(hld);
 }
 
@@ -407,6 +411,7 @@
 
   // Invalidate the old header line and add the new one.
   i->skip = true;
+  ++header_lines_removed_;
   header_lines_.push_back(hld);
 }
 
@@ -644,6 +649,7 @@
     std::string lowercase_key = absl::AsciiStrToLower(key);
     if (lowercase_keys.count(lowercase_key) != 0) {
       line.skip = true;
+      ++header_lines_removed_;
     }
   }
 }
@@ -672,6 +678,7 @@
           GetPtr(line.buffer_base_idx) + line.first_char_idx, key_len);
       MaybeClearSpecialHeaderValues(current_key);
       line.skip = true;
+      ++header_lines_removed_;
     }
   }
 }
@@ -691,6 +698,7 @@
     const absl::string_view value = GetValueFromHeaderLineDescription(line);
     if (predicate(key, value)) {
       line.skip = true;
+      ++header_lines_removed_;
     }
   }
 }
@@ -792,6 +800,7 @@
     if (values.size() == needle.size()) {
       if (values == needle) {
         line->skip = true;
+        ++header_lines_removed_;
         removals++;
       }
       continue;
@@ -854,6 +863,7 @@
     if (insertion <= value_begin) {
       // All values removed.
       line->skip = true;
+      ++header_lines_removed_;
     } else {
       line->last_char_idx = insertion - buf;
     }
@@ -1184,8 +1194,10 @@
         << "Attempting to remove a token from an empty header value "
         << "for header \"" << key << "\"";
     header_line->skip = true;  // remove the whole line
+    ++header_lines_removed_;
   } else if (tokens.size() == 1) {
     header_line->skip = true;  // remove the whole line
+    ++header_lines_removed_;
   } else {
     // Shrink the line size and leave the extra data in the buffer.
     absl::string_view new_last_token = tokens[tokens.size() - 2];
diff --git a/quiche/balsa/balsa_headers.h b/quiche/balsa/balsa_headers.h
index 373de8f..d7e90cf 100644
--- a/quiche/balsa/balsa_headers.h
+++ b/quiche/balsa/balsa_headers.h
@@ -464,7 +464,8 @@
         non_whitespace_3_idx_(0),
         whitespace_4_idx_(0),
         content_length_status_(BalsaHeadersEnums::NO_CONTENT_LENGTH),
-        transfer_encoding_is_chunked_(false) {}
+        transfer_encoding_is_chunked_(false),
+        header_lines_removed_(0) {}
 
   explicit BalsaHeaders(size_t bufsize)
       : balsa_buffer_(bufsize),
@@ -1014,6 +1015,13 @@
 
   bool IsEmpty() const override;
 
+  size_t size() const {
+    if (header_lines_removed_ > header_lines_.size()) {
+      return 0;
+    }
+    return header_lines_.size() - header_lines_removed_;
+  }
+
   // From HeaderApi and ConstHeaderApi.
   absl::string_view Authority() const override;
   void ReplaceOrAppendAuthority(absl::string_view value) override;
@@ -1185,6 +1193,13 @@
   // If true, QUICHE_BUG if a header that starts with an invalid prefix is
   // explicitly set.
   bool enforce_header_policy_ = true;
+
+  // Track number of header lines removed for correctness of the size() method.
+  // We cannot just return header_lines_.size() because that would include the
+  // removed lines (which are marked as "skip"). We also cannot easily just
+  // track the total number of lines as they are added/removed, because those
+  // are originally added by the framer.
+  size_t header_lines_removed_;
 };
 
 // Base class for iterating the headers in a BalsaHeaders object, returning a
diff --git a/quiche/balsa/balsa_headers_test.cc b/quiche/balsa/balsa_headers_test.cc
index 75e43f1..bc394eb 100644
--- a/quiche/balsa/balsa_headers_test.cc
+++ b/quiche/balsa/balsa_headers_test.cc
@@ -421,6 +421,23 @@
   }
 }
 
+TEST(BalsaHeaders, Size) {
+  BalsaHeaders header;
+  EXPECT_EQ(header.size(), 0);
+
+  header.AppendHeader("key_1", "value_1");
+  EXPECT_EQ(header.size(), 1);
+
+  header.AppendHeader("key_2", "value_2");
+  EXPECT_EQ(header.size(), 2);
+
+  header.RemoveAllOfHeader("key_1");
+  EXPECT_EQ(header.size(), 1);
+
+  header.Clear();
+  EXPECT_EQ(header.size(), 0);
+}
+
 TEST(BalsaHeaders, ReplaceOrAppendHeaderTestAppending) {
   BalsaHeaders header;
   std::string key_1 = "key_1";