aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcorvid <corvid@lavabit.com>2010-01-09 20:15:18 +0000
committercorvid <corvid@lavabit.com>2010-01-09 20:15:18 +0000
commitc9e62a3b4b93a444617c14b42251e5fe617cc94d (patch)
tree97a3c0bd0ecc765793d948cdfa5461003261c88e
parent03550b0d12399d416e03b380f9780fa87cd7c76e (diff)
cookies follow draft spec's simple parsing
-rw-r--r--dpi/cookies.c335
1 files changed, 104 insertions, 231 deletions
diff --git a/dpi/cookies.c b/dpi/cookies.c
index f96fac40..936d594f 100644
--- a/dpi/cookies.c
+++ b/dpi/cookies.c
@@ -567,272 +567,151 @@ static void Cookies_remove_cookie(CookieData_t *cookie)
}
/*
- * Return the attribute that is present at *cookie_str. This function
- * will also attempt to advance cookie_str past any equal-sign.
+ * Return the attribute that is present at *cookie_str.
*/
static char *Cookies_parse_attr(char **cookie_str)
{
- char *str = *cookie_str;
- uint_t i, end = 0;
- bool_t got_attr = FALSE;
-
- for (i = 0; ; i++) {
- switch (str[i]) {
- case ' ':
- case '\t':
- case '=':
- case ';':
- got_attr = TRUE;
- if (end == 0)
- end = i;
- break;
- case ',':
- *cookie_str = str + i;
- return dStrndup(str, i);
- break;
- case '\0':
- if (!got_attr) {
- end = i;
- got_attr = TRUE;
- }
- /* fall through! */
- default:
- if (got_attr) {
- *cookie_str = str + i;
- return dStrndup(str, end);
- }
- break;
- }
- }
+ char *str;
+ uint_t len;
+
+ while (dIsspace(**cookie_str))
+ (*cookie_str)++;
- return NULL;
+ str = *cookie_str;
+ /* find '=' at end of attr, ';' after attr/val pair, '\0' end of string */
+ len = strcspn(str, "=;");
+ *cookie_str += len;
+
+ while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
+ len--;
+ return dStrndup(str, len);
}
/*
- * Get the value starting at *cookie_str.
- * broken_syntax: watch out for stupid syntax (comma in unquoted string...)
+ * Get the value in *cookie_str.
*/
-static char *Cookies_parse_value(char **cookie_str,
- bool_t broken_syntax,
- bool_t keep_quotes)
+static char *Cookies_parse_value(char **cookie_str)
{
- uint_t i, end;
- char *str = *cookie_str;
-
- for (i = end = 0; !end; ++i) {
- switch (str[i]) {
- case ' ':
- case '\t':
- if (!broken_syntax && str[0] != '\'' && str[0] != '"') {
- end = 1;
- *cookie_str = str + i + 1;
- while (**cookie_str == ' ' || **cookie_str == '\t')
- *cookie_str += 1;
- if (**cookie_str == ';')
- *cookie_str += 1;
- }
- break;
- case '\'':
- case '"':
- if (i != 0 && str[i] == str[0]) {
- char *tmp = str + i;
+ uint_t len;
+ char *str;
- while (*tmp != '\0' && *tmp != ';' && *tmp != ',')
- tmp++;
+ if (**cookie_str == '=') {
+ (*cookie_str)++;
+ while (dIsspace(**cookie_str))
+ (*cookie_str)++;
- *cookie_str = (*tmp == ';') ? tmp + 1 : tmp;
+ str = *cookie_str;
+ /* finds ';' after attr/val pair or '\0' at end of string */
+ len = strcspn(str, ";");
+ *cookie_str += len;
- if (keep_quotes)
- i++;
- end = 1;
- }
- break;
- case '\0':
- *cookie_str = str + i;
- end = 1;
- break;
- case ',':
- if (str[0] != '\'' && str[0] != '"' && !broken_syntax) {
- /* A new cookie starts here! */
- *cookie_str = str + i;
- end = 1;
- }
- break;
- case ';':
- if (str[0] != '\'' && str[0] != '"') {
- *cookie_str = str + i + 1;
- end = 1;
- }
- break;
- default:
- break;
- }
- }
- /* keep i as an index to the last char */
- --i;
-
- if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) {
- return i > 1 ? dStrndup(str + 1, i - 1) : NULL;
+ while (len && (str[len - 1] == ' ' || str[len - 1] == '\t'))
+ len--;
} else {
- return dStrndup(str, i);
+ str = *cookie_str;
+ len = 0;
}
+ return dStrndup(str, len);
}
/*
- * Parse one cookie...
+ * Advance past any value
*/
-static CookieData_t *Cookies_parse_one(char **cookie_str)
+static void Cookies_eat_value(char **cookie_str)
{
- CookieData_t *cookie;
- char *str = *cookie_str;
- char *attr;
- char *value;
- int num_attr = 0;
+ if (**cookie_str == '=')
+ *cookie_str += strcspn(*cookie_str, ";");
+}
+
+/*
+ * Parse cookie. A cookie might look something like:
+ * "Name=Val; Domain=example.com; Max-Age=3600; HttpOnly"
+ */
+static CookieData_t *Cookies_parse(char *cookie_str)
+{
+ CookieData_t *cookie = NULL;
+ char *str = cookie_str;
+ bool_t first_attr = TRUE;
bool_t max_age = FALSE;
bool_t expires = FALSE;
- bool_t discard = FALSE;
- bool_t error = FALSE;
-
- cookie = dNew0(CookieData_t, 1);
-
- /* let's arbitrarily choose a year for now */
- cookie->expires_at = time(NULL) + 60 * 60 * 24 * 365;
- /* Iterate until there is nothing left of the string OR we come
- * across a comma representing the start of another cookie */
- while (*str != '\0' && *str != ',') {
- if (error) {
- str++;
- continue;
- }
- /* Skip whitespace */
- while (dIsspace(*str))
- str++;
+ /* Iterate until there is nothing left of the string */
+ while (*str) {
+ char *attr;
+ char *value;
/* Get attribute */
attr = Cookies_parse_attr(&str);
- if (!attr) {
- MSG("Cannot parse cookie attribute!\n");
- error = TRUE;
- continue;
- }
/* Get the value for the attribute and store it */
- if (num_attr == 0) {
- /* The first attr, which always is the user supplied attr, may
- * have the same name as an ordinary attr. Hence this workaround. */
- cookie->name = dStrdup(attr);
- cookie->value = Cookies_parse_value(&str, FALSE, TRUE);
+ if (first_attr) {
+ if (!*str && !*attr) {
+ dFree(attr);
+ return NULL;
+ }
+ cookie = dNew0(CookieData_t, 1);
+ /* let's arbitrarily choose a year for now */
+ cookie->expires_at = time(NULL) + 60 * 60 * 24 * 365;
+
+ if (*str != '=') {
+ /* NOTE it seems possible that the Working Group will decide
+ * against allowing nameless cookies.
+ */
+ cookie->name = dStrdup("");
+ cookie->value = attr;
+ } else {
+ cookie->name = dStrdup(attr);
+ cookie->value = Cookies_parse_value(&str);
+ }
} else if (dStrcasecmp(attr, "Path") == 0) {
- value = Cookies_parse_value(&str, FALSE, FALSE);
+ value = Cookies_parse_value(&str);
dFree(cookie->path);
cookie->path = value;
} else if (dStrcasecmp(attr, "Domain") == 0) {
- value = Cookies_parse_value(&str, FALSE, FALSE);
+ value = Cookies_parse_value(&str);
dFree(cookie->domain);
cookie->domain = value;
- } else if (dStrcasecmp(attr, "Discard") == 0) {
- discard = TRUE;
} else if (dStrcasecmp(attr, "Max-Age") == 0) {
- value = Cookies_parse_value(&str, FALSE, FALSE);
- if (value) {
+ value = Cookies_parse_value(&str);
+ if (isdigit(*value) || *value == '-') {
cookie->expires_at = time(NULL) + strtol(value, NULL, 10);
expires = max_age = TRUE;
- dFree(value);
- } else {
- MSG("Cannot parse cookie Max-Age value!\n");
- dFree(attr);
- error = TRUE;
- continue;
}
+ dFree(value);
} else if (dStrcasecmp(attr, "Expires") == 0) {
if (!max_age) {
- _MSG("Old Netscape-style cookie...\n");
- value = Cookies_parse_value(&str, TRUE, FALSE);
- if (value) {
- cookie->expires_at = Cookies_create_timestamp(value);
- expires = TRUE;
- dFree(value);
- } else {
- MSG("Cannot parse cookie Expires value!\n");
- dFree(attr);
- error = TRUE;
- continue;
- }
- } else {
- MSG("Cookie cannot contain Max-Age and Expires.\n");
- dFree(attr);
- error = TRUE;
- continue;
+ value = Cookies_parse_value(&str);
+ cookie->expires_at = Cookies_create_timestamp(value);
+ expires = TRUE;
+ dFree(value);
+ MSG("Expires in %ld seconds, at %s",
+ (long)cookie->expires_at - time(NULL),
+ ctime(&cookie->expires_at));
+
}
} else if (dStrcasecmp(attr, "Secure") == 0) {
cookie->secure = TRUE;
+ Cookies_eat_value(&str);
} else if (dStrcasecmp(attr, "HttpOnly") == 0) {
- // this case is intentionally left blank, because we do not
- // do client-side scripting (yet).
+ Cookies_eat_value(&str);
} else {
- /* Oops! this can't be good... */
MSG("Cookie contains unknown attribute: '%s'\n", attr);
- dFree(attr);
- error = TRUE;
- continue;
+ Cookies_eat_value(&str);
}
- dFree(attr);
- num_attr++;
- }
-
- /*
- * Netscape cookie spec: "expires is an optional attribute. If not
- * specified, the cookie will expire when the user's session ends."
- * rfc 2965: (in the absence of) "Max-Age The default behavior is to
- * discard the cookie when the user agent exits."
- * "The Discard attribute instructs the user agent to discard the
- * cookie unconditionally when the user agent terminates."
- */
- cookie->session_only = discard == TRUE || expires == FALSE;
-
- *cookie_str = (*str == ',') ? str + 1 : str;
+ if (first_attr)
+ first_attr = FALSE;
+ else
+ dFree(attr);
- if (!error && (!cookie->name || !cookie->value)) {
- MSG("Cookie missing name and/or value!\n");
- error = TRUE;
- }
- if (error) {
- Cookies_free_cookie(cookie);
- cookie = NULL;
+ if (*str == ';')
+ str++;
}
+ cookie->session_only = expires == FALSE;
return cookie;
}
/*
- * Iterate the cookie string until we catch all cookies.
- * Return Value: a list with all the cookies! (or NULL upon error)
- */
-static Dlist *Cookies_parse_string(char *cookie_string)
-{
- CookieData_t *cookie;
- Dlist *ret = NULL;
- char *str = cookie_string;
-
- /* The string may contain several cookies separated by comma.
- * We'll iterate until we've caught them all */
- while (*str) {
- cookie = Cookies_parse_one(&str);
-
- if (cookie) {
- if (!ret)
- ret = dList_new(4);
- dList_append(ret, cookie);
- } else {
- MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string);
- }
- }
-
- return ret;
-}
-
-/*
* Compare cookies by name and path (return 0 if equal)
*/
static int Cookies_cmp(const void *a, const void *b)
@@ -1038,8 +917,6 @@ static void Cookies_set(char *cookie_string, char *url_host,
{
CookieControlAction action;
CookieData_t *cookie;
- Dlist *list;
- int i;
if (disabled)
return;
@@ -1052,20 +929,17 @@ static void Cookies_set(char *cookie_string, char *url_host,
_MSG("%s setting: %s\n", url_host, cookie_string);
- if ((list = Cookies_parse_string(cookie_string))) {
- for (i = 0; (cookie = dList_nth_data(list, i)); ++i) {
- if (Cookies_validate_domain(cookie, url_host)) {
- Cookies_validate_path(cookie, url_path);
- if (action == COOKIE_ACCEPT_SESSION)
- cookie->session_only = TRUE;
- Cookies_add_cookie(cookie);
- } else {
- MSG("Rejecting cookie for %s from host %s path %s\n",
- cookie->domain, url_host, url_path);
- Cookies_free_cookie(cookie);
- }
+ if ((cookie = Cookies_parse(cookie_string))) {
+ if (Cookies_validate_domain(cookie, url_host)) {
+ Cookies_validate_path(cookie, url_path);
+ if (action == COOKIE_ACCEPT_SESSION)
+ cookie->session_only = TRUE;
+ Cookies_add_cookie(cookie);
+ } else {
+ MSG("Rejecting cookie for %s from host %s path %s\n",
+ cookie->domain, url_host, url_path);
+ Cookies_free_cookie(cookie);
}
- dList_free(list);
}
}
@@ -1166,9 +1040,8 @@ static char *Cookies_get(char *url_host, char *url_path,
for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) {
dStr_sprintfa(cookie_dstring,
- "%s=%s; $Path=%s; $Domain=%s",
- cookie->name, cookie->value,
- cookie->path, cookie->domain);
+ "%s%s%s",
+ cookie->name, *cookie->name ? "=" : "", cookie->value);
dStr_append(cookie_dstring,
dList_length(matching_cookies) > i + 1 ? "; " : "\r\n");
}