Cast to unsigned char for comparisons.

No behavioral change if char is unsigned (for example, internal code).
Fixes incorrect behavior if char is signed (for example, Envoy).

PiperOrigin-RevId: 447703949
diff --git a/quiche/common/balsa/balsa_frame.cc b/quiche/common/balsa/balsa_frame.cc
index cb7db65..167632a 100644
--- a/quiche/common/balsa/balsa_frame.cc
+++ b/quiche/common/balsa/balsa_frame.cc
@@ -23,6 +23,21 @@
 #include "quiche/common/balsa/header_properties.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 
+// When comparing characters (other than == and !=), cast to unsigned char
+// to make sure values above 127 rank as expected, even on platforms where char
+// is signed and thus such values are represented as negative numbers before the
+// cast.
+#define CHAR_LT(a, b) \
+  (static_cast<unsigned char>(a) < static_cast<unsigned char>(b))
+#define CHAR_LE(a, b) \
+  (static_cast<unsigned char>(a) <= static_cast<unsigned char>(b))
+#define CHAR_GT(a, b) \
+  (static_cast<unsigned char>(a) > static_cast<unsigned char>(b))
+#define CHAR_GE(a, b) \
+  (static_cast<unsigned char>(a) >= static_cast<unsigned char>(b))
+#define QUICHE_DCHECK_CHAR_GE(a, b) \
+  QUICHE_DCHECK_GE(static_cast<unsigned char>(a), static_cast<unsigned char>(b))
+
 namespace quiche {
 
 namespace {
@@ -88,11 +103,11 @@
                                   const char* end, size_t* first_whitespace,
                                   size_t* first_nonwhite) {
   *first_whitespace = current - begin;
-  while (current < end && *current <= ' ') {
+  while (current < end && CHAR_LE(*current, ' ')) {
     ++current;
   }
   *first_nonwhite = current - begin;
-  while (current<end&& * current> ' ') {
+  while (current < end && CHAR_GT(*current, ' ')) {
     ++current;
   }
   return current;
@@ -151,14 +166,14 @@
 
   // Clean up any trailing whitespace that comes after the third island
   const char* last = end;
-  while (current <= last && *last <= ' ') {
+  while (current <= last && CHAR_LE(*last, ' ')) {
     --last;
   }
   headers->whitespace_4_idx_ = last - begin + 1;
 
   // Either the passed-in line is empty, or it starts with a non-whitespace
   // character.
-  QUICHE_DCHECK(begin == end || *begin > ' ');
+  QUICHE_DCHECK(begin == end || static_cast<unsigned char>(*begin) > ' ');
 
   QUICHE_DCHECK_EQ(0u, headers->whitespace_1_idx_);
   QUICHE_DCHECK_EQ(0u, headers->non_whitespace_1_idx_);
@@ -268,13 +283,13 @@
   QUICHE_DCHECK_LT(colon_loc, line_end);
   QUICHE_DCHECK_EQ(':', *colon_loc);
   QUICHE_DCHECK_EQ(':', *current);
-  QUICHE_DCHECK_GE(' ', *line_end)
+  QUICHE_DCHECK_CHAR_GE(' ', *line_end)
       << "\"" << std::string(line_begin, line_end) << "\"";
 
   // TODO(fenix): Investigate whether or not the bounds tests in the
   // while loops here are redundant, and if so, remove them.
   --current;
-  while (current > line_begin && *current <= ' ') {
+  while (current > line_begin && CHAR_LE(*current, ' ')) {
     --current;
   }
   current += static_cast<int>(current != colon_loc);
@@ -283,7 +298,7 @@
   current = colon_loc;
   QUICHE_DCHECK_EQ(':', *current);
   ++current;
-  while (current < line_end && *current <= ' ') {
+  while (current < line_end && CHAR_LE(*current, ' ')) {
     ++current;
   }
   current_header_line->value_begin_idx = current - stream_begin;
@@ -323,7 +338,7 @@
     // "\r\n", etc -within- the line (and not just at the end of it).
     for (++i; i < lines_size_m1; ++i) {
       const char c = *(stream_begin + lines[i].first);
-      if (c > ' ') {
+      if (CHAR_GT(c, ' ')) {
         // Not a continuation, so stop.  Note that if the 'original' i = 1,
         // and the next line is not a continuation, we'll end up with i = 2
         // when we break. This handles the incrementing of i for the outer
@@ -355,11 +370,11 @@
     --line_end;
     QUICHE_DCHECK_EQ('\n', *line_end)
         << "\"" << std::string(line_begin, line_end) << "\"";
-    while (*line_end <= ' ' && line_end > line_begin) {
+    while (CHAR_LE(*line_end, ' ') && line_end > line_begin) {
       --line_end;
     }
     ++line_end;
-    QUICHE_DCHECK_GE(' ', *line_end);
+    QUICHE_DCHECK_CHAR_GE(' ', *line_end);
     QUICHE_DCHECK_LT(line_begin, line_end);
 
     // We use '0' for the block idx, because we're always writing to the first
@@ -731,7 +746,7 @@
       do {
         const char c = *message_current;
         if (c != '\r' && c != '\n') {
-          if (c <= ' ') {
+          if (CHAR_LE(c, ' ')) {
             HandleError(BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST);
             return message_current - original_message_start;
           }
@@ -1294,3 +1309,9 @@
 const int32_t BalsaFrame::kValidTerm2Mask;
 
 }  // namespace quiche
+
+#undef CHAR_LT
+#undef CHAR_LE
+#undef CHAR_GT
+#undef CHAR_GE
+#undef QUICHE_DCHECK_CHAR_GE
diff --git a/quiche/common/balsa/balsa_headers.cc b/quiche/common/balsa/balsa_headers.cc
index e024e1a..16c223b 100644
--- a/quiche/common/balsa/balsa_headers.cc
+++ b/quiche/common/balsa/balsa_headers.cc
@@ -182,8 +182,12 @@
   const char* start = header_value.begin();
   const char* end = header_value.end();
   while (true) {
+    // Cast `*start` to unsigned char to make values above 127 rank as expected
+    // on platforms with signed char, where such values are represented as
+    // negative numbers before the cast.
+
     // search for first nonwhitespace, non separator char.
-    while (*start == ',' || *start <= ' ') {
+    while (*start == ',' || static_cast<unsigned char>(*start) <= ' ') {
       ++start;
       if (start == end) {
         return;
@@ -193,7 +197,7 @@
     const char* nws = start;
 
     // search for next whitspace or separator char.
-    while (*start != ',' && *start > ' ') {
+    while (*start != ',' && static_cast<unsigned char>(*start) > ' ') {
       ++start;
       if (start == end) {
         if (nws != start) {