blob: 0a47c3e5a1ddd2093dd29b0b45220fa57f8e8ab6 [file] [log] [blame]
/*
** Copyright 2003-2004 The Apache Software Foundation
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#include "apreq_cookie.h"
#include "apreq_env.h"
#include "apr_strings.h"
#include "apr_lib.h"
#include "apr_date.h"
#define RFC APREQ_COOKIE_VERSION_RFC
#define NETSCAPE APREQ_COOKIE_VERSION_NETSCAPE
#define DEFAULT APREQ_COOKIE_VERSION_DEFAULT
APREQ_DECLARE(apreq_cookie_t *) apreq_cookie(const apreq_jar_t *jar,
const char *name)
{
const char *val = apr_table_get(jar->cookies, name);
if (val == NULL)
return NULL;
return apreq_value_to_cookie(apreq_char_to_value(val));
}
APREQ_DECLARE(void) apreq_jar_add(apreq_jar_t *jar,
const apreq_cookie_t *c)
{
apr_table_addn(jar->cookies,
c->v.name,c->v.data);
}
APREQ_DECLARE(void) apreq_cookie_expires(apreq_cookie_t *c,
const char *time_str)
{
if (time_str == NULL) {
c->max_age = -1;
return;
}
if (!strcasecmp(time_str, "now"))
c->max_age = 0;
else {
c->max_age = apr_date_parse_rfc(time_str);
if (c->max_age == APR_DATE_BAD)
c->max_age = apr_time_from_sec(apreq_atoi64t(time_str));
else
c->max_age -= apr_time_now();
}
}
static int has_rfc_cookie(void *ctx, const char *key, const char *val)
{
const apreq_cookie_t *c = apreq_value_to_cookie(
apreq_char_to_value(val));
return c->version == NETSCAPE; /* 0 -> non-netscape cookie found, stop.
1 -> not found, keep going. */
}
APREQ_DECLARE(apreq_cookie_version_t) apreq_ua_cookie_version(void *env)
{
if (apreq_env_cookie2(env) == NULL) {
apreq_jar_t *j = apreq_jar(env, NULL);
if (j == NULL || apreq_jar_nelts(j) == 0)
return NETSCAPE;
else if (apr_table_do(has_rfc_cookie, NULL, j->cookies, NULL) == 1)
return NETSCAPE;
else
return RFC;
}
else
return RFC;
}
APREQ_DECLARE(apr_status_t)
apreq_cookie_attr(apr_pool_t *p, apreq_cookie_t *c,
const char *attr, apr_size_t alen,
const char *val, apr_size_t vlen)
{
if (alen < 2)
return APR_EGENERAL;
if ( attr[0] == '-' || attr[0] == '$' ) {
++attr;
--alen;
}
switch (apr_tolower(*attr)) {
case 'n': /* name */
c->v.name = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
case 'v': /* version */
while (!apr_isdigit(*val)) {
if (vlen == 0)
return APR_EGENERAL;
++val;
--vlen;
}
c->version = *val - '0';
return APR_SUCCESS;
case 'e': case 'm': /* expires, max-age */
apreq_cookie_expires(c, val);
return APR_SUCCESS;
case 'd':
c->domain = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
case 'p':
if (alen != 4)
break;
if (!strncasecmp("port", attr, 4)) {
c->port = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
}
else if (!strncasecmp("path", attr, 4)) {
c->path = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
}
break;
case 'c':
if (!strncasecmp("commentURL", attr, 10)) {
c->commentURL = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
}
else if (!strncasecmp("comment", attr, 7)) {
c->comment = apr_pstrmemdup(p,val,vlen);
return APR_SUCCESS;
}
break;
case 's':
c->secure = (vlen > 0 && *val != '0'
&& strncasecmp("off",val,vlen));
return APR_SUCCESS;
};
return APR_ENOTIMPL;
}
APREQ_DECLARE(apreq_cookie_t *) apreq_cookie_make(apr_pool_t *p,
const char *name, const apr_size_t nlen,
const char *value, const apr_size_t vlen)
{
apreq_cookie_t *c = apr_palloc(p, vlen + sizeof *c);
apreq_value_t *v = &c->v;
v->size = vlen;
v->name = apr_pstrmemdup(p, name, nlen);
memcpy(v->data, value, vlen);
v->data[vlen] = 0;
c->version = DEFAULT;
/* session cookie is the default */
c->max_age = -1;
c->path = NULL;
c->domain = NULL;
c->port = NULL;
c->secure = 0;
c->comment = NULL;
c->commentURL = NULL;
return c;
}
APR_INLINE
static apr_status_t get_pair(apr_pool_t *p, const char **data,
const char **n, apr_size_t *nlen,
const char **v, apr_size_t *vlen, unsigned unquote)
{
const char *hdr, *key, *val;
hdr = *data;
while (apr_isspace(*hdr) || *hdr == '=')
++hdr;
key = strchr(hdr, '=');
if (key == NULL)
return APR_NOTFOUND;
val = key + 1;
do --key;
while (key > hdr && apr_isspace(*key));
*n = key;
while (key >= hdr && !apr_isspace(*key))
--key;
*nlen = *n - key;
*n = key + 1;
while (apr_isspace(*val))
++val;
if (*val == '"') {
unsigned saw_backslash = 0;
for (*v = (unquote) ? ++val : val++; *val; ++val) {
switch (*val) {
case '"':
*data = val + 1;
if (!unquote) {
*vlen = (val - *v) + 1;
}
else if (!saw_backslash) {
*vlen = val - *v;
}
else {
char *dest = apr_palloc(p, val - *v), *d = dest;
const char *s = *v;
while (s < val) {
if (*s == '\\')
++s;
*d++ = *s++;
}
*vlen = d - dest;
*v = dest;
}
return APR_SUCCESS;
case '\\':
saw_backslash = 1;
if (val[1] != 0)
++val;
default:
break;
}
}
/* bad attr: no terminating quote found */
return APR_EGENERAL;
}
else {
/* value is not wrapped in quotes */
for (*v = val; *val; ++val) {
switch (*val) {
case ';':
case ',':
case ' ':
case '\t':
case '\r':
case '\n':
*data = val;
*vlen = val - *v;
return APR_SUCCESS;
default:
break;
}
}
}
*data = val;
*vlen = val - *v;
return APR_SUCCESS;
}
APREQ_DECLARE(apreq_jar_t *) apreq_jar(void *env, const char *hdr)
{
apr_pool_t *p = apreq_env_pool(env);
apreq_cookie_version_t version;
apreq_jar_t *j = NULL;
apreq_cookie_t *c;
const char *origin;
const char *name, *value;
apr_size_t nlen, vlen;
/* initialize jar */
if (hdr == NULL) {
/* use the environment's cookie data */
j = apreq_env_jar(env, NULL);
if ( j != NULL )
return j;
j = apr_palloc(p, sizeof *j);
j->env = env;
j->cookies = apr_table_make(p, APREQ_NELTS);
j->status = APR_SUCCESS;
hdr = apreq_env_cookie(env);
/* XXX: potential race condition here
between env_jar fetch and env_jar set. */
apreq_env_jar(env,j);
if (hdr == NULL)
return j;
}
else {
j = apr_palloc(p, sizeof *j);
j->env = env;
j->cookies = apr_table_make(p, APREQ_NELTS);
j->status = APR_SUCCESS;
}
origin = hdr;
apreq_log(APREQ_DEBUG 0, env, "parsing cookie data: %s", hdr);
/* parse data */
parse_cookie_header:
c = NULL;
version = NETSCAPE;
while (apr_isspace(*hdr))
++hdr;
/* XXX cheat: assume "$..." => "$Version" => RFC Cookie header */
if (*hdr == '$') {
version = RFC;
while (*hdr && !apr_isspace(*hdr))
++hdr;
}
for (;;) {
apr_status_t status;
while (*hdr == ';' || apr_isspace(*hdr))
++hdr;
switch (*hdr) {
case 0:
/* this is the normal exit point for apreq_jar */
if (c != NULL) {
apreq_log(APREQ_DEBUG j->status, env,
"adding cookie: %s => %s", c->v.name, c->v.data);
apreq_add_cookie(j, c);
}
return j;
case ',':
++hdr;
if (c != NULL) {
apreq_log(APREQ_DEBUG j->status, env,
"adding cookie: %s => %s", c->v.name, c->v.data);
apreq_add_cookie(j, c);
}
goto parse_cookie_header;
case '$':
if (c == NULL) {
j->status = APR_EGENERAL;
apreq_log(APREQ_ERROR j->status, env,
"Saw RFC attribute, was expecting NAME=VALUE cookie pair: %s",
hdr);
return j;
}
else if (version == NETSCAPE) {
j->status = APR_EGENERAL;
apreq_log(APREQ_ERROR j->status, env,
"Saw RFC attribute in a Netscape Cookie header: %s",
hdr);
return j;
}
++hdr;
status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 1);
if (status != APR_SUCCESS) {
j->status = status;
apreq_log(APREQ_ERROR status, env,
"Bad RFC attribute: %s",
apr_pstrmemdup(p, name, hdr-name));
return j;
}
status = apreq_cookie_attr(p, c, name, nlen, value, vlen);
switch (status) {
case APR_ENOTIMPL:
apreq_log(APREQ_WARN status, env,
"Skipping unrecognized RFC attribute pair: %s",
apr_pstrmemdup(p, name, hdr-name));
/* fall through */
case APR_SUCCESS:
break;
default:
j->status = status;
apreq_log(APREQ_ERROR status, env,
"Bad RFC attribute pair: %s",
apr_pstrmemdup(p, name, hdr-name));
return j;
}
break;
default:
if (c != NULL) {
apreq_log(APREQ_DEBUG j->status, env,
"adding cookie: %s => %s", c->v.name, c->v.data);
apreq_add_cookie(j, c);
}
status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 0);
if (status == APR_SUCCESS) {
c = apreq_make_cookie(p, name, nlen, value, vlen);
c->version = version;
}
else {
if (status == APR_EGENERAL)
j->status = APR_EGENERAL;
return j;
}
}
}
/* NOT REACHED */
return j;
}
APREQ_DECLARE(int) apreq_cookie_serialize(const apreq_cookie_t *c,
char *buf, apr_size_t len)
{
/* The format string must be large enough to accomodate all
* of the cookie attributes. The current attributes sum to
* ~90 characters (w/ 6-8 padding chars per attr), so anything
* over 100 should be fine.
*/
char format[128] = "%s=%s";
char *f = format + strlen(format);
/* XXX protocol enforcement (for debugging, anyway) ??? */
if (c->v.name == NULL)
return -1;
#define NULL2EMPTY(attr) (attr ? attr : "")
if (c->version == NETSCAPE) {
char expires[APR_RFC822_DATE_LEN] = {0};
#define ADD_NS_ATTR(name) do { \
if (c->name != NULL) \
strcpy(f, "; " #name "=%s"); \
else \
strcpy(f, "%0.s"); \
f += strlen(f); \
} while (0)
ADD_NS_ATTR(path);
ADD_NS_ATTR(domain);
if (c->max_age != -1) {
strcpy(f, "; expires=%s");
apr_rfc822_date(expires, c->max_age + apr_time_now());
expires[7] = '-';
expires[11] = '-';
}
else
strcpy(f, "");
f += strlen(f);
if (c->secure)
strcpy(f, "; secure");
return apr_snprintf(buf, len, format, c->v.name, c->v.data,
NULL2EMPTY(c->path), NULL2EMPTY(c->domain), expires);
}
/* c->version == RFC */
strcpy(f,"; Version=%d");
f += strlen(f);
/* ensure RFC attributes are always quoted */
#define ADD_RFC_ATTR(name) do { \
if (c->name != NULL) \
if (*c->name == '"') \
strcpy(f, "; " #name "=%s"); \
else \
strcpy(f, "; " #name "=\"%s\""); \
else \
strcpy(f, "%0.s"); \
f += strlen (f); \
} while (0)
ADD_RFC_ATTR(path);
ADD_RFC_ATTR(domain);
ADD_RFC_ATTR(port);
ADD_RFC_ATTR(comment);
ADD_RFC_ATTR(commentURL);
strcpy(f, c->max_age != -1 ? "; max-age=%" APR_TIME_T_FMT : "");
f += strlen(f);
if (c->secure)
strcpy(f, "; secure");
return apr_snprintf(buf, len, format, c->v.name, c->v.data, c->version,
NULL2EMPTY(c->path), NULL2EMPTY(c->domain),
NULL2EMPTY(c->port), NULL2EMPTY(c->comment),
NULL2EMPTY(c->commentURL), apr_time_sec(c->max_age));
}
APREQ_DECLARE(char*) apreq_cookie_as_string(const apreq_cookie_t *c,
apr_pool_t *p)
{
int n = apreq_cookie_serialize(c, NULL, 0);
char *s = apr_palloc(p, n + 1);
apreq_cookie_serialize(c, s, n + 1);
return s;
}
APREQ_DECLARE(apr_status_t) apreq_cookie_bake(const apreq_cookie_t *c,
void *env)
{
char s[APREQ_COOKIE_MAX_LENGTH];
int len = apreq_cookie_serialize(c, s, APREQ_COOKIE_MAX_LENGTH);
if (len < APREQ_COOKIE_MAX_LENGTH)
return apreq_env_set_cookie(env, s);
apreq_log(APREQ_ERROR APR_INCOMPLETE, env,
"serialized cookie length exceeds limit %d",
APREQ_COOKIE_MAX_LENGTH - 1);
return APR_INCOMPLETE;
}
APREQ_DECLARE(apr_status_t) apreq_cookie_bake2(const apreq_cookie_t *c,
void *env)
{
char s[APREQ_COOKIE_MAX_LENGTH];
if ( c->version != NETSCAPE ) {
int len = apreq_cookie_serialize(c, s, APREQ_COOKIE_MAX_LENGTH);
if (len < APREQ_COOKIE_MAX_LENGTH)
return apreq_env_set_cookie2(env, s);
apreq_log(APREQ_ERROR APR_INCOMPLETE, env,
"serialized cookie length exceeds limit %d",
APREQ_COOKIE_MAX_LENGTH - 1);
return APR_INCOMPLETE;
}
apreq_log(APREQ_ERROR APR_EGENERAL, env,
"Cannot bake2 a Netscape cookie: %s", s);
return APR_EGENERAL;
}