/*
 * File: cookies.c
 *
 * Copyright 2001 Lars Clausen   <lrclause@cs.uiuc.edu>
 *                Jörgen Viksell <jorgen.viksell@telia.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 */

/**
 * @file
 * Handling of cookies takes place here.
 */

#include "msg.h"

#ifdef DISABLE_COOKIES

/**
 * Initialize the cookies module
 */
void a_Cookies_init(void)
{
   MSG("Cookies: absolutely disabled at compilation time.\n");
}

#else

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>

#include "IO/Url.h"
#include "list.h"
#include "cookies.h"
#include "capi.h"
#include "../dpip/dpip.h"


/** The maximum length of a line in the cookie file */
#define LINE_MAXLEN 4096

typedef enum {
   COOKIE_ACCEPT,
   COOKIE_ACCEPT_SESSION,
   COOKIE_DENY
} CookieControlAction;

typedef struct {
   CookieControlAction action;
   char *domain;
} CookieControl;

/* Variables for access control */
static CookieControl *ccontrol = NULL;
static int num_ccontrol = 0;
static int num_ccontrol_max = 1;
static CookieControlAction default_action = COOKIE_DENY;

static bool_t disabled;

static FILE *Cookies_fopen(const char *file, char *init_str);
static CookieControlAction Cookies_control_check(const DilloUrl *url);
static CookieControlAction Cookies_control_check_domain(const char *domain);
static int Cookie_control_init(void);

/**
 * Return a file pointer. If the file doesn't exist, try to create it,
 * with the optional 'init_str' as its content.
 */
static FILE *Cookies_fopen(const char *filename, char *init_str)
{
   FILE *F_in;
   int fd, rc;

   if ((F_in = fopen(filename, "r")) == NULL) {
      /* Create the file */
      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
      if (fd != -1) {
         if (init_str) {
            rc = write(fd, init_str, strlen(init_str));
            if (rc == -1) {
               MSG("Cookies: Could not write initial string to file %s: %s\n",
                   filename, dStrerror(errno));
            }
         }
         dClose(fd);

         MSG("Cookies: Created file: %s\n", filename);
         F_in = fopen(filename, "r");
      } else {
         MSG("Cookies: Could not create file: %s!\n", filename);
      }
   }

   if (F_in) {
      /* set close on exec */
      fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD));
   }

   return F_in;
}

/**
 * Initialize the cookies module
 * (The 'disabled' variable is writable only within a_Cookies_init)
 */
void a_Cookies_init(void)
{
   /* Default setting */
   disabled = TRUE;

   /* Read and parse the cookie control file (cookiesrc) */
   if (Cookie_control_init() != 0) {
      MSG("Disabling cookies.\n");
      return;
   }

   MSG("Enabling cookies as from cookiesrc...\n");
   disabled = FALSE;
}

/**
 * Flush cookies to disk and free all the memory allocated.
 */
void a_Cookies_freeall(void)
{
}

/**
 * Set the value corresponding to the cookie string
 */
void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url,
                   const char *date)
{
   CookieControlAction action;
   char *cmd, *cookie_string, *dpip_tag;
   const char *path;
   int i;

   if (disabled)
      return;

   action = Cookies_control_check(set_url);
   if (action == COOKIE_DENY) {
      _MSG("Cookies: denied SET for %s\n", URL_HOST_(set_url));
      return;
   }

   for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) {
      path = URL_PATH_(set_url);
      if (date)
         cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s date=%s",
                                "set_cookie", cookie_string,
                                URL_HOST_(set_url), path ? path : "/", date);
      else
         cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s",
                                "set_cookie", cookie_string,
                                URL_HOST_(set_url), path ? path : "/");

      _MSG("Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd );
      /* This call is commented because it doesn't guarantee the order
       * in which cookies are set and got. (It may deadlock too) */
      //a_Capi_dpi_send_cmd(NULL, NULL, cmd, "cookies", 1);

      dpip_tag = a_Dpi_send_blocking_cmd("cookies", cmd);
      _MSG("a_Cookies_set: dpip_tag = {%s}\n", dpip_tag);
      dFree(dpip_tag);
      dFree(cmd);
   }
}

/**
 * Return a string containing cookie data for an HTTP query.
 */
char *a_Cookies_get_query(const DilloUrl *query_url, const DilloUrl *requester)
{
   char *cmd, *dpip_tag, *query;
   const char *path;
   CookieControlAction action;

   if (disabled)
      return dStrdup("");

   action = Cookies_control_check(query_url);
   if (action == COOKIE_DENY) {
      _MSG("Cookies: denied GET for %s\n", URL_HOST_(query_url));
      return dStrdup("");
   }

   if (requester == NULL) {
      /* request made by user */
   } else if (!a_Url_same_organization(query_url, requester)) {
      MSG("Cookies: not sent for request by '%s' for '%s'\n",
          URL_HOST(requester), URL_HOST(query_url));
      return dStrdup("");
   }

   path = URL_PATH_(query_url);

   cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s",
                          "get_cookie", URL_SCHEME(query_url),
                         URL_HOST(query_url), path ? path : "/");

   /* Get the answer from cookies.dpi */
   _MSG("cookies.c: a_Dpi_send_blocking_cmd cmd = {%s}\n", cmd);
   dpip_tag = a_Dpi_send_blocking_cmd("cookies", cmd);
   _MSG("cookies.c: after a_Dpi_send_blocking_cmd resp={%s}\n", dpip_tag);
   dFree(cmd);

   if (dpip_tag != NULL) {
      query = a_Dpip_get_attr(dpip_tag, "cookie");
      dFree(dpip_tag);
   } else {
      query = dStrdup("");
   }
   return query;
}

/* -------------------------------------------------------------
 *                    Access control routines
 * ------------------------------------------------------------- */


/**
 * Get the cookie control rules (from cookiesrc).
 * @return
 *   - 0 = Parsed OK, with cookies enabled
 *   - 1 = Parsed OK, with cookies disabled
 *   - 2 = Can't open the control file
 */
static int Cookie_control_init(void)
{
   CookieControl cc;
   FILE *stream;
   char *filename, *rc;
   char line[LINE_MAXLEN];
   char domain[LINE_MAXLEN];
   char rule[LINE_MAXLEN];
   bool_t enabled = FALSE;

   /* Get a file pointer */
   filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL);
   stream = Cookies_fopen(filename, "DEFAULT DENY\n");
   dFree(filename);

   if (!stream)
      return 2;

   /* Get all lines in the file */
   while (!feof(stream)) {
      line[0] = '\0';
      rc = fgets(line, LINE_MAXLEN, stream);
      if (!rc && ferror(stream)) {
         MSG("Cookies1: Error while reading rule from cookiesrc: %s\n",
             dStrerror(errno));
         fclose(stream);
         return 2; /* bail out */
      }

      /* Remove leading and trailing whitespaces */
      dStrstrip(line);

      if (line[0] != '\0' && line[0] != '#') {
         int i = 0, j = 0;

         /* Get the domain */
         while (line[i] != '\0' && !dIsspace(line[i]))
            domain[j++] = line[i++];
         domain[j] = '\0';

         /* Skip past whitespaces */
         while (dIsspace(line[i]))
            i++;

         /* Get the rule */
         j = 0;
         while (line[i] != '\0' && !dIsspace(line[i]))
            rule[j++] = line[i++];
         rule[j] = '\0';

         if (dStrAsciiCasecmp(rule, "ACCEPT") == 0)
            cc.action = COOKIE_ACCEPT;
         else if (dStrAsciiCasecmp(rule, "ACCEPT_SESSION") == 0)
            cc.action = COOKIE_ACCEPT_SESSION;
         else if (dStrAsciiCasecmp(rule, "DENY") == 0)
            cc.action = COOKIE_DENY;
         else {
            MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n",
                rule, domain);
            continue;
         }

         cc.domain = dStrdup(domain);
         if (dStrAsciiCasecmp(cc.domain, "DEFAULT") == 0) {
            /* Set the default action */
            default_action = cc.action;
            dFree(cc.domain);
         } else {
            int i;
            uint_t len = strlen(cc.domain);

            /* Insert into list such that longest rules come first. */
            a_List_add(ccontrol, num_ccontrol, num_ccontrol_max);
            for (i = num_ccontrol++;
                 i > 0 && (len > strlen(ccontrol[i-1].domain));
                 i--) {
               ccontrol[i] = ccontrol[i-1];
            }
            ccontrol[i] = cc;
         }

         if (cc.action != COOKIE_DENY)
            enabled = TRUE;
      }
   }

   fclose(stream);

   return (enabled ? 0 : 1);
}

/**
 * Check the rules for an appropriate action for this domain.
 * The rules are ordered by domain length, with longest first, so the
 * first match is the most specific.
 */
static CookieControlAction Cookies_control_check_domain(const char *domain)
{
   int i, diff;

   for (i = 0; i < num_ccontrol; i++) {
      if (ccontrol[i].domain[0] == '.') {
         diff = strlen(domain) - strlen(ccontrol[i].domain);
         if (diff >= 0) {
            if (dStrAsciiCasecmp(domain + diff, ccontrol[i].domain) != 0)
               continue;
         } else {
            continue;
         }
      } else {
         if (dStrAsciiCasecmp(domain, ccontrol[i].domain) != 0)
            continue;
      }

      /* If we got here we have a match */
      return( ccontrol[i].action );
   }

   return default_action;
}

/**
 * Same as @ref Cookies_control_check_domain except it takes an URL
 */
static CookieControlAction Cookies_control_check(const DilloUrl *url)
{
   return Cookies_control_check_domain(URL_HOST(url));
}

#endif /* !DISABLE_COOKIES */
