diff options
Diffstat (limited to 'srclib/libapreq/library/cookie.c')
-rw-r--r-- | srclib/libapreq/library/cookie.c | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/srclib/libapreq/library/cookie.c b/srclib/libapreq/library/cookie.c new file mode 100644 index 0000000000..417df9e715 --- /dev/null +++ b/srclib/libapreq/library/cookie.c @@ -0,0 +1,537 @@ +/* +** Licensed to the Apache Software Foundation (ASF) under one or more +** contributor license agreements. See the NOTICE file distributed with +** this work for additional information regarding copyright ownership. +** The ASF licenses this file to You 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_error.h" +#include "apreq_util.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_date.h" + + +#define RFC 1 +#define NETSCAPE 0 + +#define ADD_COOKIE(j,c) apreq_value_table_add(&c->v, j) + +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 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_EBADARG; + + if ( attr[0] == '-' || attr[0] == '$' ) { + ++attr; + --alen; + } + + switch (apr_tolower(*attr)) { + + case 'n': /* name is not an attr */ + return APR_ENOTIMPL; + + case 'v': /* version; value is not an attr */ + if (alen == 5 && strncasecmp(attr,"value", 5) == 0) + return APR_ENOTIMPL; + + while (!apr_isdigit(*val)) { + if (vlen == 0) + return APREQ_ERROR_BADSEQ; + ++val; + --vlen; + } + apreq_cookie_version_set(c, *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': + if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen)) + apreq_cookie_secure_on(c); + else + apreq_cookie_secure_off(c); + return APR_SUCCESS; + + case 'h': /* httponly */ + if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen)) + apreq_cookie_httponly_on(c); + else + apreq_cookie_httponly_off(c); + 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; + apreq_value_t *v; + + c = apr_palloc(p, nlen + vlen + 1 + sizeof *c); + + if (c == NULL) + return NULL; + + *(const apreq_value_t **)&v = &c->v; + + if (vlen > 0 && value != NULL) + memcpy(v->data, value, vlen); + v->data[vlen] = 0; + v->dlen = vlen; + v->name = v->data + vlen + 1; + if (nlen && name != NULL) + memcpy(v->name, name, nlen); + v->name[nlen] = 0; + v->nlen = nlen; + + c->path = NULL; + c->domain = NULL; + c->port = NULL; + c->comment = NULL; + c->commentURL = NULL; + c->max_age = -1; /* session cookie is the default */ + c->flags = 0; + + + return c; +} + +static APR_INLINE +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; + int nlen_set = 0; + hdr = *data; + + while (apr_isspace(*hdr) || *hdr == '=') + ++hdr; + + key = hdr; + *n = hdr; + + scan_name: + + switch (*hdr) { + + case 0: + case ';': + case ',': + if (!nlen_set) + *nlen = hdr - key; + *v = hdr; + *vlen = 0; + *data = hdr; + return *nlen ? APREQ_ERROR_NOTOKEN : APREQ_ERROR_BADCHAR; + + case '=': + if (!nlen_set) { + *nlen = hdr - key; + nlen_set = 1; + } + break; + + case ' ': + case '\t': + case '\r': + case '\n': + if (!nlen_set) { + *nlen = hdr - key; + nlen_set = 1; + } + /* fall thru */ + + default: + ++hdr; + goto scan_name; + } + + val = hdr + 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 sequence: no terminating quote found */ + *data = val; + return APREQ_ERROR_BADSEQ; + } + 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(apr_status_t)apreq_parse_cookie_header(apr_pool_t *p, + apr_table_t *j, + const char *hdr) +{ + apreq_cookie_t *c; + unsigned version; + apr_status_t rv = APR_SUCCESS; + + parse_cookie_header: + + c = NULL; + version = NETSCAPE; + + while (apr_isspace(*hdr)) + ++hdr; + + + if (*hdr == '$' && strncasecmp(hdr, "$Version", 8) == 0) { + /* XXX cheat: assume "$Version" => RFC Cookie header */ + version = RFC; + skip_version_string: + switch (*hdr++) { + case 0: + return rv; + case ',': + goto parse_cookie_header; + case ';': + break; + default: + goto skip_version_string; + } + } + + for (;;) { + apr_status_t status; + const char *name, *value; + apr_size_t nlen, vlen; + + while (*hdr == ';' || apr_isspace(*hdr)) + ++hdr; + + switch (*hdr) { + + case 0: + /* this is the normal exit point */ + if (c != NULL) { + ADD_COOKIE(j, c); + } + return rv; + + case ',': + ++hdr; + if (c != NULL) { + ADD_COOKIE(j, c); + } + goto parse_cookie_header; + + case '$': + ++hdr; + if (c == NULL) { + rv = APREQ_ERROR_BADCHAR; + goto parse_cookie_error; + } + else if (version == NETSCAPE) { + rv = APREQ_ERROR_MISMATCH; + } + + status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 1); + if (status != APR_SUCCESS) { + rv = status; + goto parse_cookie_error; + } + + status = apreq_cookie_attr(p, c, name, nlen, value, vlen); + + switch (status) { + + case APR_ENOTIMPL: + rv = APREQ_ERROR_BADATTR; + /* fall thru */ + + case APR_SUCCESS: + break; + + default: + rv = status; + goto parse_cookie_error; + } + + break; + + default: + if (c != NULL) { + ADD_COOKIE(j, c); + } + + status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 0); + + if (status != APR_SUCCESS) { + c = NULL; + rv = status; + goto parse_cookie_error; + } + + c = apreq_cookie_make(p, name, nlen, value, vlen); + apreq_cookie_tainted_on(c); + if (version != NETSCAPE) + apreq_cookie_version_set(c, version); + } + } + + parse_cookie_error: + + switch (*hdr) { + + case 0: + return rv; + + case ',': + case ';': + if (c != NULL) + ADD_COOKIE(j, c); + ++hdr; + goto parse_cookie_header; + + default: + ++hdr; + goto parse_cookie_error; + } + + /* not reached */ + return rv; +} + + +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. + */ + + unsigned version = apreq_cookie_version(c); + 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 (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 (apreq_cookie_is_secure(c)) + strcpy(f, "; secure"); + + f += strlen(f); + + if (apreq_cookie_is_httponly(c)) + strcpy(f, "; HttpOnly"); + + 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=%u"); + 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 (apreq_cookie_is_secure(c)) + strcpy(f, "; secure"); + + f += strlen(f); + + if (apreq_cookie_is_httponly(c)) + strcpy(f, "; HttpOnly"); + + return apr_snprintf(buf, len, format, c->v.name, c->v.data, 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; +} + |