Main Page | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

cookies.c

Go to the documentation of this file.
00001 /* ==================================================================== 
00002  * The Kannel Software License, Version 1.0 
00003  * 
00004  * Copyright (c) 2001-2008 Kannel Group  
00005  * Copyright (c) 1998-2001 WapIT Ltd.   
00006  * All rights reserved. 
00007  * 
00008  * Redistribution and use in source and binary forms, with or without 
00009  * modification, are permitted provided that the following conditions 
00010  * are met: 
00011  * 
00012  * 1. Redistributions of source code must retain the above copyright 
00013  *    notice, this list of conditions and the following disclaimer. 
00014  * 
00015  * 2. Redistributions in binary form must reproduce the above copyright 
00016  *    notice, this list of conditions and the following disclaimer in 
00017  *    the documentation and/or other materials provided with the 
00018  *    distribution. 
00019  * 
00020  * 3. The end-user documentation included with the redistribution, 
00021  *    if any, must include the following acknowledgment: 
00022  *       "This product includes software developed by the 
00023  *        Kannel Group (http://www.kannel.org/)." 
00024  *    Alternately, this acknowledgment may appear in the software itself, 
00025  *    if and wherever such third-party acknowledgments normally appear. 
00026  * 
00027  * 4. The names "Kannel" and "Kannel Group" must not be used to 
00028  *    endorse or promote products derived from this software without 
00029  *    prior written permission. For written permission, please  
00030  *    contact org@kannel.org. 
00031  * 
00032  * 5. Products derived from this software may not be called "Kannel", 
00033  *    nor may "Kannel" appear in their name, without prior written 
00034  *    permission of the Kannel Group. 
00035  * 
00036  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
00037  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
00038  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
00039  * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
00040  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
00041  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT  
00042  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
00043  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
00044  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
00045  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
00046  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
00047  * ==================================================================== 
00048  * 
00049  * This software consists of voluntary contributions made by many 
00050  * individuals on behalf of the Kannel Group.  For more information on  
00051  * the Kannel Group, please see <http://www.kannel.org/>. 
00052  * 
00053  * Portions of this software are based upon software originally written at  
00054  * WapIT Ltd., Helsinki, Finland for the Kannel project.  
00055  */ 
00056 
00057 /*
00058  * Module: cookies.c
00059  *
00060  * Description: Implements a minimal cookie handler for session persistence.
00061  *
00062  * References: RFC 2109
00063  *
00064  * Author: Paul Keogh, ANAM Wireless Internet Solutions
00065  *
00066  * Date: May 2000
00067  */
00068 
00069 #include <string.h>
00070 #include <ctype.h>
00071 
00072 #include "gwlib/gwlib.h"
00073 #include "wsp.h"
00074 #include "cookies.h"
00075 
00076 /* Statics */
00077 
00078 static Octstr *get_header_value(Octstr*);
00079 static Cookie *parse_cookie(Octstr*);
00080 static void add_cookie_to_cache(const WSPMachine*, Cookie*);
00081 static void expire_cookies(List*);
00082 static void cookie_destroy(void*);
00083 static int have_cookie(List*, Cookie*);
00084 static int parse_http_date(const char*);
00085 static Cookie emptyCookie;
00086 
00087 
00088 Cookie *cookie_create(void)
00089 {
00090     Cookie *p;
00091 
00092     p = gw_malloc(sizeof(Cookie));  /* Never returns NULL */
00093 
00094     *p = emptyCookie;
00095     p -> max_age = -1;
00096     time (&p -> birth);
00097     return p;
00098 }
00099 
00100 void cookies_destroy(List *cookies) 
00101 {
00102     gwlib_assert_init();
00103 
00104     if (cookies == NULL)
00105         return;
00106 
00107     gwlist_destroy(cookies, cookie_destroy);
00108 }
00109 
00110 
00111 int get_cookies(List *headers, const WSPMachine *sm)
00112 {
00113     Octstr *header = NULL;
00114     Octstr *value = NULL;
00115     Cookie *cookie = NULL;
00116     long pos = 0;
00117 
00118     /* 
00119      * This can happen if the user aborts while the HTTP request is pending from the server.
00120      * In that case, the session machine is destroyed and is not available to this function
00121      * for cookie caching.
00122      */
00123 
00124     if (sm == NULL) {
00125         info (0, "No session machine for cookie retrieval");
00126         return 0;
00127     }
00128 
00129     for (pos = 0; pos < gwlist_len(headers); pos++) {
00130         header = gwlist_get(headers, pos);
00131         /* debug ("wap.wsp.http", 0, "get_cookies: Examining header (%s)", octstr_get_cstr (header)); */
00132         if (strncasecmp ("set-cookie", octstr_get_cstr (header),10) == 0) {     
00133             debug ("wap.wsp.http", 0, "Caching cookie (%s)", octstr_get_cstr (header));
00134 
00135             if ((value = get_header_value (header)) == NULL) {
00136                 error (0, "get_cookies: No value in (%s)", octstr_get_cstr(header));
00137                 continue;
00138             }
00139 
00140             /* Parse the received cookie */
00141             if ((cookie = parse_cookie(value)) != NULL) {
00142 
00143                 /* Check to see if this cookie is already present */
00144                 if (have_cookie(sm->cookies, cookie) == 1) {
00145                     debug("wap.wsp.http", 0, "parse_cookie: Cookie present");
00146                           cookie_destroy(cookie);
00147                     continue;
00148                 } else {
00149                     add_cookie_to_cache(sm, cookie);
00150                     debug("wap.wsp.http", 0, "get_cookies: Added (%s)", 
00151                           octstr_get_cstr(cookie -> name));
00152                 }
00153             }
00154         }
00155     }
00156 
00157     debug("wap.wsp.http", 0, "get_cookies: End");
00158     return 0;
00159 }
00160 
00161 
00162 int set_cookies(List *headers, WSPMachine *sm)
00163 {
00164     Cookie *value = NULL;
00165     Octstr *cookie = NULL;
00166     long pos = 0;
00167 
00168     if (headers == NULL || sm == NULL) {
00169         error (0, "set_cookies: Null argument(s) - no headers, WSPMachine or both");
00170         return -1;
00171     }
00172 
00173     /* Expire cookies that have timed out */
00174     expire_cookies(sm->cookies);
00175 
00176     /* Walk through the cookie cache, adding the cookie to the request headers */
00177     if (gwlist_len(sm->cookies) > 0) {
00178         debug("wap.wsp.http", 0, "set_cookies: Cookies in cache");
00179 
00180         for (pos = 0; pos < gwlist_len(sm->cookies); pos++) {
00181             value = gwlist_get(sm->cookies, pos);
00182 
00183             cookie = octstr_create("Cookie: ");
00184             if (value->version) 
00185                 octstr_append(cookie, value->version);
00186             octstr_append(cookie, value->name);
00187             octstr_append_char(cookie, '=');
00188             octstr_append(cookie, value->value);
00189 
00190             if (value->path) {
00191                 octstr_append_char(cookie, ';');
00192                 octstr_append(cookie, value->path);
00193             }
00194             if (value->domain) {
00195                 octstr_append_char(cookie, ';');
00196                 octstr_append(cookie, value->domain);
00197             }
00198 
00199             gwlist_append(headers, cookie);
00200             debug("wap.wsp.http", 0, "set_cookies: Added (%s)", octstr_get_cstr (cookie));
00201         }
00202     } else
00203         debug("wap.wsp.http", 0, "set_cookies: No cookies in cache");
00204 
00205     return 0;
00206 }
00207 
00208 /*
00209  * Private interface functions
00210  */
00211 
00212 /*
00213  * Function: get_header_value
00214  *
00215  * Description: Gets the header value as an Octstr.
00216  */
00217 
00218 static Octstr *get_header_value(Octstr *header) 
00219 {
00220     Octstr *h = NULL;
00221     long colon = -1;
00222     
00223     if (header == NULL) {
00224         error(0, "get_header_value: NULL argument");
00225         return NULL;
00226     }
00227 
00228     octstr_strip_blanks(header);
00229     colon = octstr_search_char(header, ':', 0);
00230     if (colon == -1) {
00231         error(0, "get_header_value: Malformed header (%s)", octstr_get_cstr (header));
00232         return NULL;
00233     } else {
00234         h = octstr_copy(header, colon + 1, octstr_len(header));
00235         octstr_strip_blanks(h);
00236     }
00237 
00238     debug("wap.wsp.http", 0, "get_header_value: Value (%s)", octstr_get_cstr (h));
00239     return h;
00240 }
00241 
00242 /*
00243  * Function: parse_cookie
00244  *
00245  * Description: Parses the received cookie and rewrites it for sending.
00246  */
00247 
00248 static Cookie *parse_cookie(Octstr *cookiestr)
00249 {
00250     char *v = NULL;
00251     char *p = NULL;
00252     int delta = 0;
00253     Cookie *c = NULL;
00254     Octstr **f = NULL;
00255 
00256     if (cookiestr == NULL) {
00257         error(0, "parse_cookie: NULL argument");
00258         return NULL;
00259     }
00260 
00261     v = gw_strdup(octstr_get_cstr (cookiestr));
00262     p = strtok(v, ";");
00263     
00264     c = cookie_create();    /* Never returns NULL */
00265 
00266     while (p != NULL) {
00267         while (isspace((int)*p)) p++;       /* Skip leading whitespace */
00268 
00269         if (strncasecmp("version", p, 7) == 0)
00270             f = &c -> version;
00271         else if (strncasecmp("path", p, 4) == 0)
00272             f = &c -> path;
00273         else if (strncasecmp("domain", p, 6) == 0)
00274             f = &c -> domain;   /* XXX DAVI: Shouldn't we check if domain is similar 
00275                          *           to real domain, and to set domain to
00276                          *           real domain if not set by header ??? */
00277         else if (strncasecmp("max-age", p, 7) == 0) {
00278             c -> max_age = atol(strrchr (p, '=') + 1);
00279             p = strtok(NULL, ";");
00280             continue;
00281         } 
00282         else if (strncasecmp("expires", p, 7) == 0) {
00283             delta = parse_http_date(p);
00284             if (delta != -1) 
00285                 c->max_age = delta;
00286             p = strtok(NULL, ";");
00287             continue;
00288         }
00289         else if (strncasecmp("comment", p, 7) == 0 ) { /* Ignore comments */
00290             p = strtok(NULL, ";");
00291             continue;
00292         }
00293         else if (strncasecmp("secure", p, 6) == 0 ) { /* XXX DAVI: this should processed */
00294             p = strtok(NULL, ";");
00295             continue;
00296         }
00297         else {      /* Name value pair - this should be first */
00298             char *equals = NULL;
00299 
00300             if ((equals = strrchr(p, '=')) != NULL) {
00301                 *equals = '\0';
00302 
00303                 c->name = octstr_create(p);
00304                 c->value = octstr_create(equals + 1);
00305             } else {
00306                 error(0, "parse_cookie: Bad name=value cookie component (%s)", p);
00307                 cookie_destroy(c);
00308                 return NULL;
00309             }
00310             p = strtok(NULL, ";");
00311             continue;
00312         }
00313 
00314         if (*f != NULL) {   /* Undefined behaviour - 4.2.2 */
00315             error(0, "parse_cookie: Duplicate cookie field (%s), discarding", p);
00316             p = strtok(NULL, ";");
00317             continue;
00318         }
00319 
00320         *f = octstr_create("$");
00321         octstr_append_cstr(*f, p);
00322         p = strtok(NULL, ";");
00323     }
00324 
00325     /* Process version - 4.3.4 
00326          * XXX DAVI: Altough it seems to be "MUST" in RFC, no one sends a Version 
00327          * tag when it's value is "0" 
00328     if (c->version == NULL) {
00329         c->version = octstr_create("");
00330         octstr_append_cstr(c->version, "$Version=\"0\";");
00331     }
00332     */
00333 
00334     gw_free (v);
00335     return c;
00336 }
00337 
00338 /*
00339  * Function: add_cookie_to_cache
00340  *
00341  * Description: Adds the cookie to the WSPMachine cookie cache.
00342  */
00343 
00344 static void add_cookie_to_cache(const WSPMachine *sm, Cookie *value)
00345 {
00346     gw_assert(sm != NULL);
00347     gw_assert(sm->cookies != NULL);
00348     gw_assert(value != NULL);
00349 
00350     gwlist_append(sm->cookies, value);
00351 
00352     return;
00353 }
00354 
00355 /*
00356  * Function: have_cookie
00357  *
00358  * Description: Checks to see if the cookie is present in the list.
00359  */
00360 
00361 static int have_cookie(List *cookies, Cookie *cookie)
00362 {
00363     Cookie *value = NULL;
00364     long pos = 0;
00365 
00366     if (cookies == NULL || cookie == NULL) {
00367         error(0, "have_cookie: Null argument(s) - no Cookie list, Cookie or both");
00368         return 0;
00369     }
00370 
00371     /* Walk through the cookie cache, comparing cookie */
00372     while (pos < gwlist_len(cookies)) {
00373         value = gwlist_get(cookies, pos);
00374 
00375         /* octstr_compare() now only returns 0 on an exact match or if both args are 0 */
00376         debug ("wap.wsp.http", 0, "have_cookie: Comparing name (%s:%s), path (%s:%s), domain (%s:%s)",
00377                octstr_get_cstr(cookie->name), octstr_get_cstr(value->name),
00378                octstr_get_cstr(cookie->path), octstr_get_cstr(value->path),
00379                octstr_get_cstr(cookie->domain), octstr_get_cstr(value->domain));
00380 
00381         /* Match on no value or value and value equality for name, path and domain */
00382         if ( 
00383             (value->name == NULL || 
00384                 ((value->name != NULL && cookie->name != NULL) && octstr_compare(value->name, cookie->name) == 0)) &&
00385             (value->path == NULL || 
00386                 ((value->path != NULL && cookie->path != NULL) && octstr_compare(value->path, cookie->path) == 0)) &&
00387             (value->domain == NULL || 
00388                 ((value->domain != NULL && cookie->domain != NULL) && octstr_compare(value->domain, cookie->domain) == 0))
00389            ) {
00390             
00391             /* We have a match according to 4.3.3 - discard the old one */
00392             cookie_destroy(value);
00393             gwlist_delete(cookies, pos, 1);
00394 
00395             /* Discard the new cookie also if max-age is 0 - set if expiry date is up */
00396             if (cookie->max_age == 0) {
00397                 debug("wap.wsp.http", 0, "have_cookie: Discarding expired cookie (%s)",
00398                       octstr_get_cstr(cookie->name));
00399                 return 1;
00400             }
00401 
00402             debug("wap.wsp.http", 0, "have_cookie: Updating cached cookie (%s)", 
00403                   octstr_get_cstr (cookie->name));
00404             break;
00405         } else {
00406             pos++;
00407         }
00408     }
00409 
00410     return 0;
00411 }
00412 
00413 /*
00414  * Function: expire_cookies
00415  *
00416  * Description: Walks thru the cookie list and checking for expired cookies.
00417  */
00418 
00419 static void expire_cookies(List *cookies)
00420 {
00421     Cookie *value = NULL;
00422     time_t now = 0;
00423     long pos = 0;
00424 
00425     if (cookies == NULL) {
00426         error(0, "expire_cookies: Null argument(s) - no Cookie list");
00427         return;
00428     }
00429 
00430     /* Walk through the cookie cache */
00431 
00432     time(&now);
00433 
00434     if (gwlist_len(cookies) > 0) {
00435         debug("wap.wsp.http", 0, "expire_cookies: Cookies in cache");
00436         for (pos = 0; pos < gwlist_len(cookies); pos++) {
00437             value = gwlist_get(cookies, pos);
00438             gw_assert(value != NULL);
00439 
00440             if (value->max_age != -1) {     /* Interesting value */
00441                 if (value->max_age + value->birth < now) {
00442                     debug("wap.wsp.http", 0, "expire_cookies: Expired cookie (%s)",
00443                           octstr_get_cstr(value->name));
00444                     cookie_destroy(value);
00445                     gwlist_delete(cookies, pos, 1);
00446                 }
00447             }
00448         }
00449     } else
00450         debug("wap.wsp.http", 0, "expire_cookies: No cookies in cache");
00451 
00452     return;
00453 }
00454 
00455 static void cookie_destroy(void *p)
00456 {
00457     Cookie *cookie;
00458     
00459     if (p == NULL) 
00460         return;
00461     cookie = p;
00462 
00463     octstr_destroy(cookie->name);
00464     octstr_destroy(cookie->value);
00465     octstr_destroy(cookie->version);
00466     octstr_destroy(cookie->domain);
00467     octstr_destroy(cookie->path);
00468 
00469     gw_free(cookie);
00470     debug("wap.wsp.http", 0, "cookie_destroy: Destroyed cookie");
00471     return;
00472 }
00473 
00474 /*
00475  * Function: parse_http_date
00476  *
00477  * Description: Parses an HTTP date format as used by the Expires: header. See RFC 2616
00478  * section 3.3.1 for more information.
00479  * HTTP applications have historically allowed three different formats
00480  * for the representation of date/time stamps:
00481  *
00482  *    Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
00483  *    Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
00484  *    Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
00485  *
00486  * The first format is preferred as an Internet standard and represents
00487  * a fixed-length subset of that defined by RFC 1123 [8] (an update to
00488  * RFC 822 [9]). The second format is in common use, but is based on the
00489  * obsolete RFC 850 [12] date format and lacks a four-digit year.
00490  * HTTP/1.1 clients and servers that parse the date value MUST accept
00491  * all three formats (for compatibility with HTTP/1.0), though they MUST
00492  * only generate the RFC 1123 format for representing HTTP-date values
00493  * in header fields.
00494  *
00495  * Returns: -1 on success, max-age sematic value on success.
00496  */
00497 
00498 /*
00499  * TODO: This is obviously the same as gwlib/date.c:date_http_parse().
00500  * Need to unify this to one pulic function.
00501  */
00502  
00503 static const char* months[] = {
00504     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00505     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL
00506 };
00507 
00508 static int month_index(const char *s)
00509 {
00510     const char **p = &months[0];
00511     int i = 0;
00512 
00513     while (*p != NULL) {
00514         if (strcmp(s, *p) == 0)
00515             return i;
00516         p++, i++;
00517     }
00518 
00519     return -1;
00520 }
00521 
00522 static int parse_http_date(const char *expires)
00523 {
00524     struct tm ti;
00525     char *p = NULL;
00526     char *date = NULL;
00527     char month[MAX_HTTP_DATE_LENGTH];
00528     time_t rv;
00529     time_t now;
00530 
00531     memset(&ti, 0, sizeof(struct tm));
00532 
00533     /* Break up the Expires: header */
00534     if (!(date = strchr(expires, '='))) {
00535         error(0, "parse_http_date: Bogus expires type=value header (%s)", expires);
00536         return -1;
00537     } else {
00538         date++;
00539         while (isspace((int)*date))
00540             ++date;
00541     }
00542 
00543     /* Onto the date value */
00544     if (!(p = strchr (date,' '))) {
00545         error(0, "parse_http_date: Bogus date string (%s)", date);
00546         return -1;
00547     } else
00548         while (isspace((int)*p))
00549             ++p;
00550 
00551     if (MAX_HTTP_DATE_LENGTH < strlen(p)) {
00552         error(0, "parse_http_date: %s blows length limit (%d)", date, MAX_HTTP_DATE_LENGTH);
00553         return -1;
00554     }
00555 
00556     if (isalpha((int)*p)) {
00557         /* ctime */
00558         sscanf(p, (strstr(p, "DST") ? "%s %d %d:%d:%d %*s %d" : "%s %d %d:%d:%d %d"),
00559                month, &ti.tm_mday, &ti.tm_hour, &ti.tm_min,
00560                &ti.tm_sec, &ti.tm_year);
00561         ti.tm_year -= 1900;
00562 
00563     } else if (p[2] == '-') {
00564         /* RFC 850 (normal HTTP) */
00565         char  buf[MAX_HTTP_DATE_LENGTH];
00566 
00567         sscanf(p, "%s %d:%d:%d", buf, &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
00568         buf[2] = '\0';
00569         ti.tm_mday = atoi(buf);
00570         buf[6] = '\0';
00571 
00572         strcpy(month, &buf[3]);
00573         ti.tm_year = atoi(&buf[7]);
00574 
00575         /* Prevent wraparound from ambiguity */
00576 
00577         if (ti.tm_year < 70) {
00578             ti.tm_year += 100;
00579         } else if (ti.tm_year > 1900) {
00580             ti.tm_year -= 1900;
00581         }
00582     } else {
00583         /* RFC 822 */
00584         sscanf(p,"%d %s %d %d:%d:%d",&ti.tm_mday, month, &ti.tm_year,
00585                &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
00586 
00587         /* 
00588          * since tm_year is years since 1900 and the year we parsed
00589          * is absolute, we need to subtract 1900 years from it
00590          */
00591         ti.tm_year -= 1900;
00592     }
00593 
00594     ti.tm_mon = month_index(month);
00595     if (ti.tm_mon == -1) {
00596         error(0, "parse_http_date () failed on bad month value (%s)", month);
00597         return -1;
00598     }
00599 
00600     ti.tm_isdst = -1;
00601 
00602     rv = gw_mktime(&ti);
00603     if (ti.tm_isdst)
00604         rv -= 3600;
00605 
00606     if (rv == -1) {
00607         error(0, "parse_http_date(): mktime() was unable to resolve date/time: %s", 
00608               asctime(&ti));
00609         return -1;
00610     }
00611 
00612     debug("parse_http_date", 0, "Parsed date (%s) as: %s", date, asctime(&ti));
00613 
00614     /* 
00615      * If rv is valid, it should be some time in the (near) future. Normalise this to
00616      * a max-age semantic so we can use the same expiry mechanism 
00617      */
00618     now = time(NULL);
00619 
00620     if (rv - now < 0) {
00621         /* This is bad - set the delta to 0 so we expire next time around */
00622         error(0, "parse_http_date () Expiry time (%s) (delta=%ld) is in the past !", 
00623               asctime(&ti), rv-now);
00624         return 0;
00625     }
00626 
00627     return rv - now;
00628 }
00629  
See file LICENSE for details about the license agreement for using, modifying, copying or deriving work from this software.