
/***


RECEIVING COOKIE INFORMATION
============================

struct CookieInfo *cookie_init(char *file);
	
	Inits a cookie struct to store data in a local file. This is always
	called before any cookies are set.

int cookies_set(struct CookieInfo *cookie, char *cookie_line);

	The 'cookie_line' parameter is a full "Set-cookie:" line as
	received from a server.

	The function need to replace previously stored lines that this new
	line superceeds.

	It may remove lines that are expired.

	It should return an indication of success/error.


SENDING COOKIE INFORMATION
==========================

struct Cookies *cookie_getlist(struct CookieInfo *cookie,
                               char *host, char *path, bool secure);

	For a given host and path, return a linked list of cookies that
	the client should send to the server if used now. The secure
	boolean informs the cookie if a secure connection is achieved or
	not.

	It shall only return cookies that haven't expired.

    
Example set of cookies:
    
    Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure
    Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
    domain=.fidelity.com; path=/ftgw; secure
    Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
    domain=.fidelity.com; path=/; secure
    Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
    domain=.fidelity.com; path=/; secure
    Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
    domain=.fidelity.com; path=/; secure
    Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;
    domain=.fidelity.com; path=/; secure
    Set-cookie:
    Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,
    13-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure
****/

#include <string.h>
#include <ctype.h>

#include "cookie.h"
#include "getdate.h"

/****************************************************************************
 *
 * cookie_add()
 *
 * Add a single cookie line to the cookie keeping object.
 *
 ***************************************************************************/

struct Cookie *cookie_add(struct CookieInfo *c,
                          char *lineptr) /* first non-space after the
                                            Set-Cookie: prefix */
{
  char what[MAX_COOKIE_LINE];
  char name[MAX_NAME];
  char *ptr;
  struct Cookie *co;
  time_t now = time(NULL);

  /* First, alloc and init a new struct for it */
  co = (struct Cookie *)malloc(sizeof(struct Cookie));
  if(!co)
    return NULL; /* bail out if we're this low on memory */

  /* clear the whole struct first */
  memset(co, 0, sizeof(struct Cookie));
	    
  /* first, point to our "next" */
  co->next = c->cookies;
  /* then make ourselves first in the list */
  c->cookies = co;
  
  ptr=strtok(lineptr, ";"); /* first tokenize it on the semicolon */
  while(ptr) {
    /* we have a <what>=<this> pair or a 'secure' word here */
    if(strchr(ptr, '=')) {
      if(2 == sscanf(ptr, "%" MAX_NAME_TXT "[^=]=%"
                     MAX_COOKIE_LINE_TXT "[^\n]",
                     name, what)) {
        /* this is a legal <what>=<this> pair */
        if(!strcasecmp("path", name)) {
          co->path=strdup(what);
        }
        else if(!strcasecmp("domain", name)) {
          co->domain=strdup(what);
        }
        else if(!strcasecmp("expires", name)) {
          co->expirestr=strdup(what);
          co->expires = get_date(what, &now);
        }
        else if(!co->name) {
          co->name = strdup(name);
          co->value = strdup(what);
        }
        else
          ;/* this is the second (or more) name we don't know
              about! */
      }
      else {
        /* this is an "illegal" <what>=<this> pair */
      }
    }
    else {
      if(sscanf(ptr, "%" MAX_COOKIE_LINE_TXT "[^\n]",
                what)) {
        if(!strcasecmp("secure", what))
          co->secure = TRUE;
        else
          ; /* unsupported keyword without assign! */
      }
    }
    ptr=strtok(NULL, ";");
    while(ptr && *ptr && isspace(*ptr))
      ptr++;
  }
  return co;
}

/*****************************************************************************
 *
 * cookie_init()
 *
 * Inits a cookie struct to read data from a local file. This is always
 * called before any cookies are set. File may be NULL.
 *
 ****************************************************************************/
struct CookieInfo *cookie_init(char *file)
{
  char line[MAX_COOKIE_LINE];
  struct CookieInfo *c;
  struct Cookie *co;
  FILE *fp;

  c = (struct CookieInfo *)malloc(sizeof(struct CookieInfo));
  if(!c)
    return NULL; /* failed to get memory */
  memset(c, 0, sizeof(struct CookieInfo));
  c->filename = strdup(file?file:"none"); /* copy the name just in case */

  fp = file?fopen(file, "r"):NULL;
  if(fp) {
    while(fgets(line, MAX_COOKIE_LINE, fp)) {
      if(!strncasecmp("Set-Cookie:", line, 11)) {
        /* This is a cookie line, get it! */
        char *lineptr=&line[11];
        while(*lineptr && isspace(*lineptr))
          lineptr++;

        co = cookie_add(c, lineptr);
        if(!co)
          break;
      }
    }
    fclose(fp);
  }

  return c;
}

/*****************************************************************************
 *
 * cookie_getlist()
 *
 * For a given host and path, return a linked list of cookies that the
 * client should send to the server if used now. The secure boolean informs
 * the cookie if a secure connection is achieved or not.
 *
 * It shall only return cookies that haven't expired.
 *
 ****************************************************************************/

struct Cookie *cookie_getlist(struct CookieInfo *c,
			      char *host, char *path, bool secure)
{
   struct Cookie *newco;
   struct Cookie *co;
   time_t now = time(NULL);
   int hostlen=strlen(host);
   int domlen;

   struct Cookie *mainco=NULL;

   if(!c || !c->cookies)
      return NULL; /* no cookie struct or no cookies in the struct */

   co = c->cookies;

   while(co) {
      /* only process this cookie if it is not expired or had no expire
	 date AND that if the cookie requires we're secure we must only
	 continue if we are! */
     if( (co->expires<=0 || (co->expires> now)) &&
         (co->secure?secure:TRUE) ) {

	 /* now check if the domain is correct */
	 domlen=strlen(co->domain);
	 if(!co->domain ||
	    ((domlen<hostlen) &&
	     !strcasecmp(host+(hostlen-domlen), co->domain)) ) {
	    /* the right part of the host matches the domain stuff in the
	       cookie data */

	    /* now check the left part of the path with the cookies path
	       requirement */
	    if(!co->path ||
	       !strncasecmp(path, co->path, strlen(co->path))) {

	       /* and now, we know this is a match and we should create an
		  entry for the return-linked-list */

	       newco = (struct Cookie *)malloc(sizeof(struct Cookie));
	       if(newco) {
		  /* first, copy the whole source cookie: */
		  memcpy(newco, co, sizeof(struct Cookie));

		  /* then modify our next */
		  newco->next = mainco;

		  /* point the main to us */
		  mainco = newco;
	       }
	    }
	 }
      }
      co = co->next;
   }

   return mainco; /* return the new list */
}


/*****************************************************************************
 *
 * cookie_freelist()
 *
 * Free a list previously returned by cookie_getlist();
 *
 ****************************************************************************/

void cookie_freelist(struct Cookie *co)
{
   struct Cookie *next;
   if(co) {
      while(co) {
	 next = co->next;
	 free(co); /* we only free the struct since the "members" are all
		      just copied! */
	 co = next;
      }
   }
}

/*****************************************************************************
 *
 * cookie_cleanup()
 *
 * Free a "cookie object" previous created with cookie_init().
 *
 ****************************************************************************/
void cookie_cleanup(struct CookieInfo *c)
{
   struct Cookie *co;
   struct Cookie *next;
   if(c) {
      if(c->filename)
	 free(c->filename);
      co = c->cookies;

      while(co) {
	 if(co->name)
	    free(co->name);
	 if(co->value)
	    free(co->value);
	 if(co->domain)
	    free(co->domain);
	 if(co->path)
	    free(co->path);
	 if(co->expirestr)
	    free(co->expirestr);

	 next = co->next;
	 free(co);
	 co = next;
      }
   }
}

