/* * Copyright (c) 1997-2007, OpenFWTK Development Group * All rights reserved. See LICENSE. */ /* cookies.c */ /* Copyright 1997 by Eberhard Mattes Donated to the public domain. No warranty. 1997-07-28 Initial version */ #include #include #include #include "libemfw.h" #include "emio.h" #include "squid-gw.h" #define SELECT_CTYPE_HTTP #include "tables.h" /* References: RFC 2068 HTTP/1.1 RFC 2109 Cookies */ /* ============================== CHARACTERS =============================== */ #define HTTP_CHAR_TYPE(c,x) (http_char_type[(unsigned char)c] & (x)) #define TOKEN(c) HTTP_CHAR_TYPE (c, CTYPE_TOKEN) #define WHITE(c) HTTP_CHAR_TYPE (c, CTYPE_WHITE) extern const unsigned char http_char_type[UCHAR_MAX+1]; /* ==================== MANAGING ATTRIBUTE/VALUE PAIRS ===================== */ #define AV_PAIRS 64 struct av_pair { const octet *name, *value; size_t name_length, value_length; octet has_value; octet quote; octet sep; }; static int av_pairs; static size_t av_size; static struct av_pair av[AV_PAIRS]; static octet av_sep; static const octet *av_verbatim; static void av_pairs_init (void) { av_pairs = 0; av_size = 0; av_sep = 0; } static const struct av_pair *parse_av_pair (const octet **sp) { struct av_pair a; const octet *s = *sp; DEBUG_ASSERT (!TOKEN (0)); DEBUG_ASSERT (!WHITE (0)); DEBUG_ASSERT ((av_pairs == 0) == (av_sep == 0)); /* Check if there's space for another attribute/value pair. */ if (av_pairs >= AV_PAIRS) return NULL; /* Initialize the new pair. */ a.name = a.value = s; a.name_length = a.value_length = 0; a.has_value = a.quote = 0; a.sep = av_sep; /* Parse the name. */ while (TOKEN (*s)) ++s; a.name_length = s - a.name; if (a.name_length == 0) return NULL; while (WHITE (*s)) ++s; if (*s == '=') { /* Parse the value. */ ++s; a.has_value = 1; while (WHITE (*s)) ++s; if (*s == '"') { ++s; a.quote = 1; a.value = s; while (*s != 0 && *s != '"') ++s; if (*s != '"') return NULL; a.value_length = s - a.value; ++s; } else { a.value = s; /* First apply RFC 2109 strictly. */ while (TOKEN (*s)) ++s; /* Unfortunately, most HTTP servers don't care about RFC 2109 and don't quote the value of "path=/foobar" and "expires=Wednesday, 15-Sep-99 12:00:00 GMT". */ if (cf_http.parse_cookies == PC_NETSCAPE || cf_http.parse_cookies == PC_NETSCAPE_QUOTE) { /* White space may or may not be part of the value. Note that we'll remove trailing white space later. */ while (WHITE (*s)) ++s; /* If we're not at the end of the value, we'll mark the value for quoting on output and continue scanning. Note that we do not stop at "," (see example above)! */ if (*s != 0 && *s != ';') { /* If PC_NETSCAPE_QUOTE is in effect, at least quote the value. */ if (cf_http.parse_cookies == PC_NETSCAPE_QUOTE) a.quote = 1; while (*s != 0 && *s != ';') ++s; } while (s != a.value && WHITE (s[-1])) --s; } a.value_length = s - a.value; if (a.value_length == 0) return NULL; } while (WHITE (*s)) ++s; } /* Remember the separator for the next attribute/value pair. */ if (*s ==';' || *s == ',') av_sep = *s; else av_sep = 0; *sp = s; /* Update av_size. */ if (a.sep != 0) av_size += 2; /* "; " or ", " */ av_size += a.name_length + a.value_length; if (a.has_value) { av_size += 1; /* "=" */ if (a.quote) av_size += 2; /* Two quotes */ } /* Store the new pair. */ av[av_pairs] = a; return &av[av_pairs++]; } /* =========================== CHECKING COOKIES ============================ */ /* Return 0 if S points to a valid domain (w.r.t. the request URL). Return -1 otherwise. */ static int check_domain (const octet *s, size_t len) { /* Check if the domain matches the request URL's host. */ DEBUG_ASSERT (req_url_str != NULL); if (len == 0 || len > req_url.host_length) return -1; if (*s != '.' && len != req_url.host_length) return -1; if (memcmp (s, req_url_str + req_url.host_start + req_url.host_length - len, len) != 0) return -1; /* Check for dots in the host part. */ if (memchr (req_url_str + req_url.host_start, '.', req_url.host_length - len) != NULL) return -1; /* Check for embedded dots. */ if (*s == '.') { ++s; --len; } if (len != 0 && s[len-1] == '.') --len; if (memchr (s, '.', len) == NULL) return -1; return 0; } /* Return 0 if S points to a valid path (w.r.t. the request URL). Return -1 otherwise. */ static int check_path (const octet *s, size_t len) { const char *upath; size_t ulen; DEBUG_ASSERT (req_url_str != NULL); if (req_url.path_length == 0) { upath = "/"; ulen = 1; } else { upath = req_url_str + req_url.path_start; ulen = req_url.path_length; } if (len == 0 || len > ulen) return -1; if (memcmp (s, upath, len) != 0) return -1; return 0; } /* ============================ PARSING COOKIES ============================ */ /* Parse the value of a "Cookie" HTTP header field. Return -1 if it's invalid, 1 if it is to be rejected, 0 if it is to be kept. */ int cookies_parse_cookie (const octet *s) { const struct av_pair *a; int r; if (cf_http.parse_cookies == PC_NOCHECK) { av_verbatim = s; av_size = strlen ((char*) s); return cf_http.block_cookies.v ? 1 : 0; } else av_verbatim = NULL; av_pairs_init (); DEBUG_ASSERT (!WHITE (0)); while (WHITE (*s)) ++s; while (*s != 0) { a = parse_av_pair (&s); if (a == NULL) return -1; /* Check the attribute/value pair. We check the domain and the path because we don't trust the browser -- it might send cookies to a wrong server. */ if ((a->name_length == 7 && lower_cmpn ((char*)a->name,"$domain", 7) == 0) || (a->name_length == 6 && lower_cmpn ((char*)a->name, "domain", 6) == 0)) { r = check_domain (a->value, a->value_length); if (r != 0) return r; } else if ((a->name_length==5 && lower_cmpn ((char*)a->name,"$path",5) == 0) || (a->name_length == 4 && lower_cmpn ((char*)a->name, "path", 4) == 0)) { r = check_path (a->value, a->value_length); if (r != 0) return r; } if (*s == 0) break; if (*s != ';' && *s != ',') /* Backwards compatibility */ return -1; ++s; while (WHITE (*s)) ++s; if (*s == 0 && cf_http.parse_cookies == PC_RFC2109) return -1; } return cf_http.block_cookies.v ? 1 : 0; } /* Parse the value of a "Set-Cookie" HTTP header field. Return -1 if it's invalid, 1 if it is to be rejected, 0 if it is to be kept. */ int cookies_parse_set_cookie (const octet *s) { const struct av_pair *a; int r; if (cf_http.parse_cookies == PC_NOCHECK) { av_verbatim = s; av_size = strlen ((char*) s); return cf_http.block_cookies.v ? 1 : 0; } else av_verbatim = NULL; av_pairs_init (); DEBUG_ASSERT (!WHITE (0)); while (WHITE (*s)) ++s; while (*s != 0) { a = parse_av_pair (&s); if (a == NULL) return -1; /* Check the attribute/value pair. We check the domain and the path to be able to log attempts to set cookies for other servers. */ if (a->name_length == 6 && lower_cmpn ((char*)a->name, "domain", 6) == 0) { r = check_domain (a->value, a->value_length); if (r != 0) return r; } else if (a->name_length == 4 && lower_cmpn ((char*)a->name, "path", 4) == 0) { r = check_path (a->value, a->value_length); if (r != 0) return r; } if (*s == 0) break; if (*s != ';') return -1; ++s; while (WHITE (*s)) ++s; if (*s == 0 && cf_http.parse_cookies == PC_RFC2109) return -1; } return cf_http.block_cookies.v ? 1 : 0; } /* ========================== REBUILDING COOKIES =========================== */ size_t cookies_rebuild_length (void) { return av_size; } /* Return 0 on success, -1 on failure (buffer overflow). */ int cookies_rebuild (octet *dst, size_t size) { int i; const struct av_pair *a; octet *d = dst; if (cf_http.parse_cookies == PC_NOCHECK) { DEBUG_ASSERT (av_verbatim != NULL); if (av_size > size) return -1; memcpy (dst, av_verbatim, av_size); return 0; } DEBUG_ASSERT (av_verbatim == NULL); for (i = 0; i < av_pairs; ++i) { a = &av[i]; DEBUG_ASSERT ((i == 0) == (a->sep == 0)); if (a->sep != 0) { if (size < 2) return -1; *d++ = a->sep; *d++ = ' '; size -= 2; } if (a->name_length > size) return -1; memcpy (d, a->name, a->name_length); d += a->name_length; size -= a->name_length; if (a->has_value) { if (size < 1) return -1; *d++ = '='; size -= 1; if (a->quote) { if (size < 2) /* Take the closing quote into account */ return -1; *d++ = '"'; size -= 2; } if (size < a->value_length) return -1; memcpy (d, a->value, a->value_length); d += a->value_length; size -= a->value_length; if (a->quote) *d++ = '"'; /* size already checked and adjusted above */ } } DEBUG_ASSERT (d - dst == av_size); return 0; }