1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "util_cookies.h" 18#include "apr_lib.h" 19#include "apr_strings.h" 20#include "http_config.h" 21#include "http_core.h" 22#include "http_log.h" 23 24#define LOG_PREFIX "ap_cookie: " 25 26/* we know core's module_index is 0 */ 27#undef APLOG_MODULE_INDEX 28#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX 29 30/** 31 * Write an RFC2109 compliant cookie. 32 * 33 * @param r The request 34 * @param name The name of the cookie. 35 * @param val The value to place in the cookie. 36 * @param attrs The string containing additional cookie attributes. If NULL, the 37 * DEFAULT_ATTRS will be used. 38 * @param maxage If non zero, a Max-Age header will be added to the cookie. 39 */ 40AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val, 41 const char *attrs, long maxage, ...) 42{ 43 44 const char *buffer; 45 const char *rfc2109; 46 apr_table_t *t; 47 va_list vp; 48 49 /* handle expiry */ 50 buffer = ""; 51 if (maxage) { 52 buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); 53 } 54 55 /* create RFC2109 compliant cookie */ 56 rfc2109 = apr_pstrcat(r->pool, name, "=", val, ";", buffer, 57 attrs && *attrs ? attrs : DEFAULT_ATTRS, NULL); 58 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00007) LOG_PREFIX 59 "user '%s' set cookie: '%s'", r->user, rfc2109); 60 61 /* write the cookie to the header table(s) provided */ 62 va_start(vp, maxage); 63 while ((t = va_arg(vp, apr_table_t *))) { 64 apr_table_addn(t, SET_COOKIE, rfc2109); 65 } 66 va_end(vp); 67 68 return APR_SUCCESS; 69 70} 71 72/** 73 * Write an RFC2965 compliant cookie. 74 * 75 * @param r The request 76 * @param name2 The name of the cookie. 77 * @param val The value to place in the cookie. 78 * @param attrs2 The string containing additional cookie attributes. If NULL, the 79 * DEFAULT_ATTRS will be used. 80 * @param maxage If non zero, a Max-Age header will be added to the cookie. 81 */ 82AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val, 83 const char *attrs2, long maxage, ...) 84{ 85 86 const char *buffer; 87 const char *rfc2965; 88 apr_table_t *t; 89 va_list vp; 90 91 /* handle expiry */ 92 buffer = ""; 93 if (maxage) { 94 buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); 95 } 96 97 /* create RFC2965 compliant cookie */ 98 rfc2965 = apr_pstrcat(r->pool, name2, "=", val, ";", buffer, 99 attrs2 && *attrs2 ? attrs2 : DEFAULT_ATTRS, NULL); 100 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00008) LOG_PREFIX 101 "user '%s' set cookie2: '%s'", r->user, rfc2965); 102 103 /* write the cookie to the header table(s) provided */ 104 va_start(vp, maxage); 105 while ((t = va_arg(vp, apr_table_t *))) { 106 apr_table_addn(t, SET_COOKIE2, rfc2965); 107 } 108 va_end(vp); 109 110 return APR_SUCCESS; 111 112} 113 114/** 115 * Remove an RFC2109 compliant cookie. 116 * 117 * @param r The request 118 * @param name The name of the cookie. 119 */ 120AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name, const char *attrs, ...) 121{ 122 apr_table_t *t; 123 va_list vp; 124 125 /* create RFC2109 compliant cookie */ 126 const char *rfc2109 = apr_pstrcat(r->pool, name, "=;Max-Age=0;", 127 attrs ? attrs : CLEAR_ATTRS, NULL); 128 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00009) LOG_PREFIX 129 "user '%s' removed cookie: '%s'", r->user, rfc2109); 130 131 /* write the cookie to the header table(s) provided */ 132 va_start(vp, attrs); 133 while ((t = va_arg(vp, apr_table_t *))) { 134 apr_table_addn(t, SET_COOKIE, rfc2109); 135 } 136 va_end(vp); 137 138 return APR_SUCCESS; 139 140} 141 142/** 143 * Remove an RFC2965 compliant cookie. 144 * 145 * @param r The request 146 * @param name2 The name of the cookie. 147 */ 148AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2, const char *attrs2, ...) 149{ 150 apr_table_t *t; 151 va_list vp; 152 153 /* create RFC2965 compliant cookie */ 154 const char *rfc2965 = apr_pstrcat(r->pool, name2, "=;Max-Age=0;", 155 attrs2 ? attrs2 : CLEAR_ATTRS, NULL); 156 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00010) LOG_PREFIX 157 "user '%s' removed cookie2: '%s'", r->user, rfc2965); 158 159 /* write the cookie to the header table(s) provided */ 160 va_start(vp, attrs2); 161 while ((t = va_arg(vp, apr_table_t *))) { 162 apr_table_addn(t, SET_COOKIE2, rfc2965); 163 } 164 va_end(vp); 165 166 return APR_SUCCESS; 167 168} 169 170/* Iterate through the cookies, isolate our cookie and then remove it. 171 * 172 * If our cookie appears two or more times, but with different values, 173 * remove it twice and set the duplicated flag to true. Remove any 174 * $path or other attributes following our cookie if present. If we end 175 * up with an empty cookie, remove the whole header. 176 */ 177static int extract_cookie_line(ap_cookie_do * v, const char *key, const char *val) 178{ 179 char *last1, *last2; 180 char *cookie = apr_pstrdup(v->r->pool, val); 181 const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL); 182 apr_size_t len = strlen(name); 183 const char *new_cookie = ""; 184 const char *comma = ","; 185 char *next1; 186 const char *semi = ";"; 187 char *next2; 188 const char *sep = ""; 189 int cookies = 0; 190 191 /* find the cookie called name */ 192 int eat = 0; 193 next1 = apr_strtok(cookie, comma, &last1); 194 while (next1) { 195 next2 = apr_strtok(next1, semi, &last2); 196 while (next2) { 197 char *trim = next2; 198 while (apr_isspace(*trim)) { 199 trim++; 200 } 201 if (!strncmp(trim, name, len)) { 202 if (v->encoded) { 203 if (strcmp(v->encoded, trim + len)) { 204 v->duplicated = 1; 205 } 206 } 207 v->encoded = apr_pstrdup(v->r->pool, trim + len); 208 eat = 1; 209 } 210 else { 211 if (*trim != '$') { 212 cookies++; 213 eat = 0; 214 } 215 if (!eat) { 216 new_cookie = apr_pstrcat(v->r->pool, new_cookie, sep, next2, NULL); 217 } 218 } 219 next2 = apr_strtok(NULL, semi, &last2); 220 sep = semi; 221 } 222 223 next1 = apr_strtok(NULL, comma, &last1); 224 sep = comma; 225 } 226 227 /* any cookies left over? */ 228 if (cookies) { 229 apr_table_addn(v->new_cookies, key, new_cookie); 230 } 231 232 return 1; 233} 234 235/** 236 * Read a cookie called name, placing its value in val. 237 * 238 * Both the Cookie and Cookie2 headers are scanned for the cookie. 239 * 240 * If the cookie is duplicated, this function returns APR_EGENERAL. If found, 241 * and if remove is non zero, the cookie will be removed from the headers, and 242 * thus kept private from the backend. 243 */ 244AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val, 245 int remove) 246{ 247 248 ap_cookie_do v; 249 v.r = r; 250 v.encoded = NULL; 251 v.new_cookies = apr_table_make(r->pool, 10); 252 v.duplicated = 0; 253 v.name = name; 254 255 apr_table_do((int (*) (void *, const char *, const char *)) 256 extract_cookie_line, (void *) &v, r->headers_in, 257 "Cookie", "Cookie2", NULL); 258 if (v.duplicated) { 259 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00011) LOG_PREFIX 260 "client submitted cookie '%s' more than once: %s", v.name, r->uri); 261 return APR_EGENERAL; 262 } 263 264 /* remove our cookie(s), and replace them */ 265 if (remove) { 266 apr_table_unset(r->headers_in, "Cookie"); 267 apr_table_unset(r->headers_in, "Cookie2"); 268 r->headers_in = apr_table_overlay(r->pool, r->headers_in, v.new_cookies); 269 } 270 271 *val = v.encoded; 272 273 return APR_SUCCESS; 274 275} 276 277/** 278 * Sanity check a given string that it exists, is not empty, 279 * and does not contain the special characters '=', ';' and '&'. 280 * 281 * It is used to sanity check the cookie names. 282 */ 283AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string) 284{ 285 if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&') || 286 ap_strchr_c(string, ';')) { 287 return APR_EGENERAL; 288 } 289 return APR_SUCCESS; 290} 291