From: Willy Tarreau Date: Wed, 4 Nov 2026 16:29:02 +0000 (+0100) Subject: MINOR: ist: add ist_find_http_ctl() to match http CTL chars X-Git-Tag: v3.5-dev11~40 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=cb1fa1e09dc6b683fc309b7949337bbdbfc9ffaa;p=haproxy.git MINOR: ist: add ist_find_http_ctl() to match http CTL chars 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. --- diff --git a/doc/internals/api/ist.txt b/doc/internals/api/ist.txt index 0f118d6e6..128347d20 100644 --- a/doc/internals/api/ist.txt +++ b/doc/internals/api/ist.txt @@ -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 in ist_find_ctl(i1):char* return pointer to first CTL char in 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 in istadv(i1,n):ist advance the string by characters istalloc(n):ist return allocated string of zero initial length diff --git a/include/import/ist.h b/include/import/ist.h index a3c3c888a..d127785c6 100644 --- a/include/import/ist.h +++ b/include/import/ist.h @@ -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 , 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 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)