httpd: match header field names case-insensitively

scan_header() found Content-Type and Cookie with is_word(), which compares
bytes exactly and requires a separator after the pattern. Field names are
case-insensitive (RFC 7230 section 3.2), and the whitespace after the colon
is optional, so "content-type: multipart/..." and "Content-Type:multipart/..."
were both treated as absent. The value pointers were then fixed offsets that
assumed exactly one space.

Add header_value(), which matches a lower-case name anchored at the line
start, folds the request bytes to lower case as it compares, and returns
the start of the value past any blanks, so a call site no longer adds the
name length by hand. Cookie scanning reuses the returned pointer, and the
end-of-header test becomes the strstart() the file already has.
This commit is contained in:
bloqaudio
2026-09-01 14:52:09 -05:00
parent 74a1c6831a
commit 2499d116a3
+28 -9
View File
@@ -138,6 +138,24 @@ bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p)
} }
/* name must be lower-case, starting with the '\n' of the previous line's end */
__xdata uint8_t *header_value(__xdata uint8_t *p, __code uint8_t *name)
{
uint8_t u, c;
while ((c = *name++)) {
u = *p++;
if (u >= 'A' && u <= 'Z')
u += 'a' - 'A';
if (u != c)
return 0;
}
while (*p == ' ' || *p == '\t')
p++;
return p;
}
bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p) bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p)
{ {
uint8_t u, s; uint8_t u, s;
@@ -248,28 +266,29 @@ void send_unauthorized(void)
__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
{ {
__xdata uint8_t *v;
content_type = 0; content_type = 0;
session = 0; session = 0;
authenticated = 0; authenticated = 0;
while (*p != '\r' || *(p + 1) != '\n' || *(p + 2) != '\r' || *(p + 3) != '\n') { while (!strstart(p, "\r\n\r\n")) {
dbg_char(*p); dbg_char(*p);
if (!*p++) if (!*p++)
break; break;
if (is_word(p, "\nContent-Type:")) if ((v = header_value(p, "\ncontent-type:")))
content_type = p + 15; content_type = v;
else if (is_word(p, "\nCookie:")) { else if ((v = header_value(p, "\ncookie:"))) {
/* Scan for the "session" key: the header may hold several /* Scan for the "session" key: the header may hold several
* cookies in any order. Match "session" not "session=" - * cookies in any order. Match "session" not "session=" -
* is_word() requires a separator after the match and '=' is * is_word() requires a separator after the match and '=' is
* one, so this also rejects a longer key like "sessionx". */ * one, so this also rejects a longer key like "sessionx". */
__xdata uint8_t *c = p + 8; /* past "\nCookie:" */ while (*v && *v != '\r' && *v != '\n') {
while (*c && *c != '\r' && *c != '\n') { if (is_word(v, "session")) {
if (is_word(c, "session")) { session = v + 8; /* past "session=" */
session = c + 8; /* past "session=" */
break; break;
} }
c++; v++;
} }
} }
} }