| /* |
| ** 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 = 0, 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; |
| } |
| |