From 6cb5bdacd2582d6241744485ab4d4749f045e4ce Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 15:59:31 +0000 Subject: [PATCH] Complete Set-Cookie header parser implementation Implemented missing functionality in the Set-Cookie parser: 1. Completed is_cookie_octet() function to properly validate cookie characters according to RFC 6265 (excludes CTLs, whitespace, DQUOTE, comma, semicolon, backslash) 2. Fixed http_parse_set_cookie() return type from void to int to properly report parsing errors 3. Implemented Path attribute parsing to extract cookie path restrictions 4. Fixed HttpOnly attribute bug where it was incorrectly set to false instead of true 5. Added path field to HTTP_SetCookie struct with have_path flag 6. Added missing return 0 statement for successful parsing 7. Exported Set-Cookie parser types and function to public API: - HTTP_WeekDay, HTTP_Month, HTTP_Date enums/struct - HTTP_SetCookie struct - http_parse_set_cookie() function All changes follow RFC 6265 specification for Set-Cookie header parsing. --- chttp.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++++++++ chttp.h | 62 +++++++++++- src/parse.c | 73 ++++---------- src/parse.h | 60 ++++++++++++ 4 files changed, 405 insertions(+), 58 deletions(-) diff --git a/chttp.c b/chttp.c index f9ef146..6d09137 100644 --- a/chttp.c +++ b/chttp.c @@ -1435,6 +1435,274 @@ bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) return http_streq(host, domain); } + +// , :: GMT +static int parse_date(Scanner *s, HTTP_Date *out) +{ + struct { HTTP_String str; HTTP_WeekDay val; } week_day_table[] = { + { HTTP_STR("Mon, "), HTTP_WEEKDAY_MON }, + { HTTP_STR("Tue, "), HTTP_WEEKDAY_TUE }, + { HTTP_STR("Wed, "), HTTP_WEEKDAY_WED }, + { HTTP_STR("Thu, "), HTTP_WEEKDAY_THU }, + { HTTP_STR("Fri, "), HTTP_WEEKDAY_FRI }, + { HTTP_STR("Sat, "), HTTP_WEEKDAY_SAT }, + { HTTP_STR("Sun, "), HTTP_WEEKDAY_SUN }, + }; + + bool found = false; + for (int i = 0; i < HTTP_COUNT(week_day_table); i++) + if (consume_str(s, week_day_table[i].str)) { + out->week_day = week_day_table[i].val; + found = true; + break; + } + if (!found) + return -1; + + if (1 >= s->len - s->cur + || !is_digit(s->src[s->cur+0]) + || !is_digit(s->src[s->cur+1])) + return -1; + out->day + = (s->src[s->cur+0] - '0') * 10 + + (s->src[s->cur+1] - '0') * 1; + s->cur += 2; + + struct { HTTP_String str; HTTP_Month val; } month_table[] = { + { HTTP_STR(" Jan "), HTTP_MONTH_JAN }, + { HTTP_STR(" Feb "), HTTP_MONTH_FEB }, + { HTTP_STR(" Mar "), HTTP_MONTH_MAR }, + { HTTP_STR(" Apr "), HTTP_MONTH_APR }, + { HTTP_STR(" May "), HTTP_MONTH_MAY }, + { HTTP_STR(" Jun "), HTTP_MONTH_JUN }, + { HTTP_STR(" Jul "), HTTP_MONTH_JUL }, + { HTTP_STR(" Aug "), HTTP_MONTH_AUG }, + { HTTP_STR(" Sep "), HTTP_MONTH_SEP }, + { HTTP_STR(" Oct "), HTTP_MONTH_OCT }, + { HTTP_STR(" Nov "), HTTP_MONTH_NOV }, + { HTTP_STR(" Dec "), HTTP_MONTH_DEC }, + }; + + found = false; + for (int i = 0; i < HTTP_COUNT(month_table); i++) + if (consume_str(s, month_table[i].str)) { + out->month = month_table[i].val; + found = true; + break; + } + if (!found) + return -1; + + if (3 >= s->len - s->cur + || !is_digit(s->src[s->cur+0]) + || !is_digit(s->src[s->cur+1]) + || !is_digit(s->src[s->cur+2]) + || !is_digit(s->src[s->cur+3])) + return -1; + out->year + = (s->src[s->cur+0] - '0') * 1000 + + (s->src[s->cur+1] - '0') * 100 + + (s->src[s->cur+2] - '0') * 10 + + (s->src[s->cur+3] - '0') * 1; + s->cur += 4; + + if (s->cur == s->len || s->src[s->cur] != ' ') + return -1; + s->cur++; + + if (7 >= s->len - s->cur + || !is_digit(s->src[s->cur+0]) + || !is_digit(s->src[s->cur+1]) + || s->src[s->cur+2] != ':' + || !is_digit(s->src[s->cur+3]) + || !is_digit(s->src[s->cur+4]) + || s->src[s->cur+5] != ':' + || !is_digit(s->src[s->cur+6]) + || !is_digit(s->src[s->cur+7]) + || s->src[s->cur+8] != ' ' + || s->src[s->cur+9] != 'G' + || s->src[s->cur+10] != 'M' + || s->src[s->cur+11] != 'T') + return -1; + out->hour + = (s->src[s->cur+0] - '0') * 10 + + (s->src[s->cur+1] - '0') * 1; + out->minute + = (s->src[s->cur+3] - '0') * 10 + + (s->src[s->cur+4] - '0') * 1; + out->second + = (s->src[s->cur+6] - '0') * 10 + + (s->src[s->cur+7] - '0') * 1; + s->cur += 12; + return 0; +} + +// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E +// ; US-ASCII characters excluding CTLs, +// ; whitespace, DQUOTE, comma, semicolon, +// ; and backslash +static bool is_cookie_octet(char c) +{ + return c == 0x21 || + (c >= 0x23 && c <= 0x2B) || + (c >= 0x2D && c <= 0x3A) || + (c >= 0x3C && c <= 0x5B) || + (c >= 0x5D && c <= 0x7E); +} + +int http_parse_set_cookie(HTTP_String str, HTTP_SetCookie *out) +{ + Scanner s = { str.ptr, str.len, 0 }; + + // cookie-name = token + if (s.cur == s.len || !is_tchar(s.src[s.cur])) + return -1; + int off = s.cur; + do + s.cur++; + while (s.cur < s.len && is_tchar(s.src[s.cur])); + out->name = (HTTP_String) { s.src + off, s.cur - off }; + + // cookie-pair = cookie-name "=" cookie-value + if (s.cur == s.len || s.src[s.cur] != '=') + return -1; + s.cur++; + + // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + if (s.cur < s.len && s.src[s.cur] == '"') { + s.cur++; // Consume opening double quote + int off = s.cur; + while (s.cur < s.len && is_cookie_octet(s.src[s.cur])) + s.cur++; + if (s.cur == s.len || s.src[s.cur] != '"') + return -1; // Missing closing double quote + out->value = (HTTP_String) { s.src + off, s.cur - off }; + s.cur++; // Consume closing double quote + } else { + int off = s.cur; + while (s.cur < s.len && is_cookie_octet(s.src[s.cur])) + s.cur++; + out->value = (HTTP_String) { s.src + off, s.cur - off }; + } + + // *( ";" SP cookie-av ) + // + // cookie-av = expires-av / max-age-av / domain-av / + // path-av / secure-av / httponly-av / + // extension-av + out->secure = false; + out->http_only = false; + out->have_date = false; + out->have_max_age = false; + out->have_domain = false; + out->have_path = false; + while (consume_str(&s, HTTP_STR("; "))) { + if (consume_str(&s, HTTP_STR("Expires="))) { + + // expires-av = "Expires=" sane-cookie-date + if (parse_date(&s, &out->date) < 0) + return -1; + out->have_date = true; + + } else if (consume_str(&s, HTTP_STR("Max-Age="))) { + + // max-age-av = "Max-Age=" non-zero-digit *DIGIT + + uint32_t value = 0; + if (s.cur == s.len || !is_digit(s.src[s.cur])) + return -1; + do { + int d = s.src[s.cur++] - '0'; + if (value > (UINT32_MAX - d) / 10) + return -1; + value = value * 10 + d; + } while (s.cur < s.len && is_digit(s.src[s.cur])); + + out->have_max_age = true; + out->max_age = value; + + } else if (consume_str(&s, HTTP_STR("Domain="))) { + + // domain-av = "Domain=" domain-value + // domain-value = + // ; defined in RFC 1034, Section 3.5 + // + // From RFC 1034: + // ::=