]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: ist: add ist_find_http_ctl() to match http CTL chars
authorWilly Tarreau <w@1wt.eu>
Wed, 4 Nov 2026 16:29:02 +0000 (17:29 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 4 Nov 2026 21:45:32 +0000 (22:45 +0100)
Some tests in the HTTP code involve ist_find_ctl() hoping to find any
HTTP CTL char, but CTL for ist is 0x00..0x1F only (and documented as
such so we won't change it). For HTTP, this also includes 0x7F which
these tests are missing.

It's not realistic to do a second full scan on those data for something
that never happens. Better introduce a new function which scans for
0x00..0x1F,0x7F, that's much more efficient. It's constructed exactly
like the existing one with a extra check for 0x7F by adding 1 to
generate a carry in this case as well. It's expected to cost roughly
2 extra cycles per word, which is OK.

doc/internals/api/ist.txt
include/import/ist.h

index 0f118d6e63745f96c968e5bcfb6463db6d6125ea..128347d20d87385393491261438aad62cf2f767b 100644 (file)
@@ -96,6 +96,7 @@ Arguments "c" are of type "char", and "n" are of type size_t.
   ist2str_uc(s,i1):ist   like ist2str() but turning turning to upper case
   ist_find(i1,c):ist     return first occurrence of char <c> in <i1>
   ist_find_ctl(i1):char* return pointer to first CTL char in <i1> or NULL
+  ist_find_http_ctl(i1):char* like ist_find_ctl() but also matches 0x7F (DEL)
   ist_skip(i1,c):ist     return first occurrence of char not <c> in <i1>
   istadv(i1,n):ist       advance the string by <n> characters
   istalloc(n):ist        return allocated string of zero initial length
index a3c3c888aaadb5aac78b33ceaaf7e148f44c20b0..d127785c630e2c4aaea0b8ee88e45517c2ef002a 100644 (file)
@@ -766,6 +766,51 @@ static inline const char *ist_find_ctl(const struct ist ist)
        return NULL;
 }
 
+/* Returns a pointer to the first HTTP control character found in <ist>, or
+ * NULL if none is present. An HTTP control character is defined as a byte
+ * whose value is between 0x00 and 0x1F included, or equals 0x7F (DEL). Bytes
+ * 0x80 and above are not considered (obs-text). It works exactly like
+ * ist_find_ctl() and only costs two extra operations per word. A byte 0xFF
+ * immediately followed by a byte 0x7E causes a false positive in the fast
+ * path (carry), which is only resolved by the byte-per-byte loop, so the
+ * result remains exact, it's just slower in this case.
+ */
+static inline const char *ist_find_http_ctl(const struct ist ist)
+{
+       const union { unsigned long v; } __attribute__((packed)) *u;
+       const char *curr = (void *)ist.ptr - sizeof(long);
+       const char *last = curr + ist.len;
+       unsigned long l1, l2, l3;
+
+       do {
+               curr += sizeof(long);
+               if (curr > last)
+                       break;
+               u = (void *)curr;
+               /* subtract 0x202020...20 to the value to generate a carry in
+                * the lower byte if the byte contains a lower value. If we
+                * generate a bit 7 that was not there, it means the byte was
+                * within 0x00..0x1F. l3 is used to add 1 to 0x7F and trigger
+                * a carry if it is met. 0x80..0xFE do not (masked by l1) but
+                * 0xFF does due to that +1.
+                */
+               l3  = l2 = u->v;
+               l1  = ~l2 & ((~0UL / 255) * 0x80); /* 0x808080...80 */
+               l2 -= (~0UL / 255) * 0x20;         /* 0x202020...20 */
+               l3 += (~0UL / 255) * 0x01;         /* 0x010101...01 */
+       } while ((l1 & (l2 | l3)) == 0);
+
+       last += sizeof(long);
+       if (__builtin_expect(curr < last, 0)) {
+               do {
+                       if ((unsigned char)*curr < 0x20 || (unsigned char)*curr == 0x7f)
+                               return curr;
+                       curr++;
+               } while (curr < last);
+       }
+       return NULL;
+}
+
 /* Returns a pointer to the first character from <ist> that belongs to the
  * range [min:max] inclusive, or NULL if none is present. The function is
  * optimized for strings having no such chars by processing up to sizeof(long)