view test/cookies.c @ 2030:382160be8c2f

cookies comments I was going to say something in the comments about simplicity and not implementing every little bit of the rfc when we don't know of cookies that make it necessary, but then I suppose that's all implied with dillo.
author corvid <corvid@lavabit.com>
date Tue, 17 May 2011 22:48:50 +0000
parents 20e683b693c0
children
line wrap: on
line source
/*
 * Dillo cookies test
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * This has a big blob of the current src/IO/dpi.c in it.
 * I hope there's a better way.
 */

#include <stdlib.h> /* malloc, etc. */
#include <unistd.h> /* read, etc. */
#include <stdio.h>
#include <stdarg.h>  /* va_list */
#include <string.h> /* strchr */
#include <errno.h>
#include <ctype.h>
#include <time.h>
/* net */
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>


#define _MSG(...)

#define MSG_INNARDS(prefix, ...)                   \
   D_STMT_START {                                  \
      printf(prefix __VA_ARGS__);               \
      fflush (stdout);                          \
   } D_STMT_END

#define MSG(...) MSG_INNARDS("", __VA_ARGS__)
#define MSG_ERR(...) MSG_INNARDS("** ERROR **: ", __VA_ARGS__)


#include "../dlib/dlib.h"
#include "../dpip/dpip.h"

static uint_t failed = 0;
static uint_t passed = 0;

static char SharedKey[32];

/*
 * Read all the available data from a filedescriptor.
 * This is intended for short answers, i.e. when we know the server
 * will write it all before being preempted. For answers that may come
 * as an stream with delays, non-blocking is better.
 * Return value: read data, or NULL on error and no data.
 */
static char *Dpi_blocking_read(int fd)
{
   int st;
   const int buf_sz = 8*1024;
   char buf[buf_sz], *msg = NULL;
   Dstr *dstr = dStr_sized_new(buf_sz);

   do {
      st = read(fd, buf, buf_sz);
      if (st < 0) {
         if (errno == EINTR) {
            continue;
         } else {
            MSG_ERR("[Dpi_blocking_read] %s\n", dStrerror(errno));
            break;
         }
      } else if (st > 0) {
         dStr_append_l(dstr, buf, st);
      }
   } while (st == buf_sz);

   msg = (dstr->len > 0) ? dstr->str : NULL;
   dStr_free(dstr, (dstr->len > 0) ? FALSE : TRUE);
   return msg;
}

static void Dpi_close_fd(int fd)
{
   int st;

   dReturn_if (fd < 0);
   do
      st = close(fd);
   while (st < 0 && errno == EINTR);
}

static int Dpi_make_socket_fd()
{
   int fd, ret = -1;

   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
      ret = fd;
   }
   return ret;
}

/*
 * Read dpid's communication keys from its saved file.
 * Return value: 1 on success, -1 on error.
 */
static int Dpi_read_comm_keys(int *port)
{
   FILE *In;
   char *fname, *rcline = NULL, *tail;
   int i, ret = -1;

   fname = dStrconcat(dGethomedir(), "/.dillo/dpid_comm_keys", NULL);
   if ((In = fopen(fname, "r")) == NULL) {
      MSG_ERR("[Dpi_read_comm_keys] %s\n", dStrerror(errno));
   } else if ((rcline = dGetline(In)) == NULL) {
      MSG_ERR("[Dpi_read_comm_keys] empty file: %s\n", fname);
   } else {
      *port = strtol(rcline, &tail, 10);
      for (i = 0; *tail && isxdigit(tail[i+1]); ++i)
         SharedKey[i] = tail[i+1];
      SharedKey[i] = 0;
      ret = 1;
   }
   if (In)
      fclose(In);
   dFree(rcline);
   dFree(fname);

   return ret;
}

static int Dpi_check_dpid_ids()
{
   struct sockaddr_in sin;
   const socklen_t sin_sz = sizeof(sin);
   int sock_fd, dpid_port, ret = -1;

   /* socket connection test */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

   if (Dpi_read_comm_keys(&dpid_port) != -1) {
      sin.sin_port = htons(dpid_port);
      if ((sock_fd = Dpi_make_socket_fd()) == -1) {
         MSG("Dpi_check_dpid_ids: sock_fd=%d %s\n", sock_fd, dStrerror(errno));
      } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {
         MSG("Dpi_check_dpid_ids: %s\n", dStrerror(errno));
      } else {
         Dpi_close_fd(sock_fd);
         ret = 1;
      }
   }
   return ret;
}

static int Dpi_blocking_write(int fd, const char *msg, int msg_len)
{
   int st, sent = 0;

   while (sent < msg_len) {
      st = write(fd, msg + sent, msg_len - sent);
      if (st < 0) {
         if (errno == EINTR) {
            continue;
         } else {
            MSG_ERR("[Dpi_blocking_write] %s\n", dStrerror(errno));
            break;
         }
      }
      sent += st;
   }

   return (sent == msg_len) ? 1 : -1;
}

/*
 * Start dpid.
 * Return: 0 starting now, 1 Error.
 */
static int Dpi_start_dpid(void)
{
   pid_t pid;
   int st_pipe[2], ret = 1;
   char *answer;

   /* create a pipe to track our child's status */
   if (pipe(st_pipe))
      return 1;

   pid = fork();
   if (pid == 0) {
      /* This is the child process.  Execute the command. */
      char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL);
      Dpi_close_fd(st_pipe[0]);
      if (execl(path1, "dpid", (char*)NULL) == -1) {
         dFree(path1);
         if (execlp("dpid", "dpid", (char*)NULL) == -1) {
            MSG("Dpi_start_dpid (child): %s\n", dStrerror(errno));
            if (Dpi_blocking_write(st_pipe[1], "ERROR", 5) == -1) {
               MSG("Dpi_start_dpid (child): can't write to pipe.\n");
            }
            Dpi_close_fd(st_pipe[1]);
            _exit (EXIT_FAILURE);
         }
      }
   } else if (pid < 0) {
      /* The fork failed.  Report failure.  */
      MSG("Dpi_start_dpid: %s\n", dStrerror(errno));
      /* close the unused pipe */
      Dpi_close_fd(st_pipe[0]);
      Dpi_close_fd(st_pipe[1]);

   } else {
      /* This is the parent process, check our child status... */
      Dpi_close_fd(st_pipe[1]);
      if ((answer = Dpi_blocking_read(st_pipe[0])) != NULL) {
         MSG("Dpi_start_dpid: can't start dpid\n");
         dFree(answer);
      } else {
         ret = 0;
      }
      Dpi_close_fd(st_pipe[0]);
   }

   return ret;
}

/*
 * Confirm that the dpid is running. If not, start it.
 * Return: 0 running OK, 1 starting (EAGAIN), 2 Error.
 */
static int Dpi_check_dpid(int num_tries)
{
   static int starting = 0;
   int check_st = 1, ret = 2;

   check_st = Dpi_check_dpid_ids();
   _MSG("Dpi_check_dpid: check_st=%d\n", check_st);

   if (check_st == 1) {
      /* connection test with dpi server passed */
      starting = 0;
      ret = 0;
   } else {
      if (!starting) {
         /* start dpid */
         if (Dpi_start_dpid() == 0) {
            starting = 1;
            ret = 1;
         }
      } else if (++starting < num_tries) {
         /* starting */
         ret = 1;
      } else {
         /* we waited too much, report an error... */
         starting = 0;
      }
   }

   _MSG("Dpi_check_dpid:: %s\n",
        (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR"));
   return ret;
}


static int Dpi_blocking_start_dpid(void)
{
   int cst, try = 0,
       n_tries = 12; /* 3 seconds */

   /* test the dpid, and wait a bit for it to start if necessary */
   while ((cst = Dpi_check_dpid(n_tries)) == 1) {
      MSG("Dpi_blocking_start_dpid: try %d\n", ++try);
      usleep(250000); /* 1/4 sec */
   }
   return cst;
}


/*
 * Return the dpi server's port number, or -1 on error.
 * (A query is sent to dpid and then its answer parsed)
 * note: as the available servers and/or the dpi socket directory can
 *       change at any time, we'll ask each time. If someday we find
 *       that connecting each time significantly degrades performance,
 *       an optimized approach can be tried.
 */
static int Dpi_get_server_port(const char *server_name)
{
   int sock_fd = -1, dpi_port = -1;
   int dpid_port, ok = 0;
   struct sockaddr_in sin;
   char *cmd, *request, *rply = NULL, *port_str;
   socklen_t sin_sz;

   dReturn_val_if_fail (server_name != NULL, dpi_port);
   _MSG("Dpi_get_server_port:: server_name = [%s]\n", server_name);

   /* Read dpid's port from saved file */
   if (Dpi_read_comm_keys(&dpid_port) != -1) {
      ok = 1;
   }
   if (ok) {
      /* Connect a socket with dpid */
      ok = 0;
      sin_sz = sizeof(sin);
      memset(&sin, 0, sizeof(sin));
      sin.sin_family = AF_INET;
      sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      sin.sin_port = htons(dpid_port);
      if ((sock_fd = Dpi_make_socket_fd()) == -1 ||
          connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {
         MSG("Dpi_get_server_port: %s\n", dStrerror(errno));
      } else {
         ok = 1;
      }
   }
   if (ok) {
      /* ask dpid to check the dpi and send its port number back */
      ok = 0;
      request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name);
      _MSG("[%s]\n", request);

      if (Dpi_blocking_write(sock_fd, request, strlen(request)) == -1) {
         MSG("Dpi_get_server_port: %s\n", dStrerror(errno));
      } else {
         ok = 1;
      }
      dFree(request);
   }
   if (ok) {
      /* Get the reply */
      ok = 0;
      if ((rply = Dpi_blocking_read(sock_fd)) == NULL) {
         MSG("Dpi_get_server_port: can't read server port from dpid.\n");
      } else {
         ok = 1;
      }
   }
   if (ok) {
      /* Parse reply */
      ok = 0;
      cmd = a_Dpip_get_attr(rply, "cmd");
      if (strcmp(cmd, "send_data") == 0) {
         port_str = a_Dpip_get_attr(rply, "msg");
         _MSG("Dpi_get_server_port: rply=%s\n", rply);
         _MSG("Dpi_get_server_port: port_str=%s\n", port_str);
         dpi_port = strtol(port_str, NULL, 10);
         dFree(port_str);
         ok = 1;
      }
      dFree(cmd);
   }
   dFree(rply);
   Dpi_close_fd(sock_fd);

   return ok ? dpi_port : -1;
}


static int Dpi_connect_socket(const char *server_name, int retry)
{
   struct sockaddr_in sin;
   int sock_fd, err, dpi_port, ret=-1;
   char *cmd = NULL;

   /* Query dpid for the port number for this server */
   if ((dpi_port = Dpi_get_server_port(server_name)) == -1) {
      _MSG("Dpi_connect_socket:: can't get port number for %s\n", server_name);
      return -1;
   }
   _MSG("Dpi_connect_socket: server=%s port=%d\n", server_name, dpi_port);

   /* connect with this server's socket */
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
   sin.sin_port = htons(dpi_port);

   if ((sock_fd = Dpi_make_socket_fd()) == -1) {
      perror("[dpi::socket]");
   } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) {
      err = errno;
      sock_fd = -1;
      MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno));
      if (retry) {
         switch (err) {
            case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL:
               sock_fd = Dpi_connect_socket(server_name, FALSE);
               break;
         }
      }

   /* send authentication Key (the server closes sock_fd on error) */
   } else if (!(cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "auth", SharedKey))) {
      MSG_ERR("[Dpi_connect_socket] Can't make auth message.\n");
   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {
      MSG_ERR("[Dpi_connect_socket] Can't send auth message.\n");
   } else {
      ret = sock_fd;
   }
   dFree(cmd);

   return ret;
}


char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd)
{
   int cst, sock_fd;
   char *ret = NULL;

   /* test the dpid, and wait a bit for it to start if necessary */
   if ((cst = Dpi_blocking_start_dpid()) != 0) {
      return ret;
   }

   if ((sock_fd = Dpi_connect_socket(server_name, TRUE)) == -1) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't connect to server.\n");
   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't send message.\n");
   } if ((ret = Dpi_blocking_read(sock_fd)) == NULL) {
      MSG_ERR("[a_Dpi_send_blocking_cmd] Can't read message.\n");
   }
   Dpi_close_fd(sock_fd);

   return ret;
}



void a_Cookies_set(const char *cookie, const char *host, const char *path,
                   const char *date)
{
   char *cmd, *dpip_tag;

   if (date)
      cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s date=%s",
                             "set_cookie", cookie,
                             host, path, date);
   else
      cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s",
                             "set_cookie", cookie,
                             host, path);

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


char *a_Cookies_get_query(const char *scheme, const char *host,
                          const char *path)
{
   char *cmd, *dpip_tag, *query;

   cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s",
                          "get_cookie", scheme,
                          host, 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;
}

static void expect(int lineno, const char *exp_reply,
                      const char *scheme, const char *host, const char *path)
{
   char *reply = a_Cookies_get_query(scheme, host, path);

   if (strcmp(reply, exp_reply)) {
      MSG("line %d: EXPECTED: %s GOT: %s\n", lineno, exp_reply, reply);
      failed++;
   } else {
      passed++;
   }
}

static void toomany()
{
   a_Cookies_set("1=1", "toomany.com", "/", NULL);
   a_Cookies_set("2=1", "toomany.com", "/", NULL);
   a_Cookies_set("3=1", "toomany.com", "/", NULL);
   a_Cookies_set("4=1", "toomany.com", "/", NULL);
   a_Cookies_set("5=1", "toomany.com", "/", NULL);
   a_Cookies_set("6=1", "toomany.com", "/", NULL);
   a_Cookies_set("7=1", "toomany.com", "/path/", NULL);
   a_Cookies_set("8=1", "toomany.com", "/", NULL);
   a_Cookies_set("9=1", "toomany.com", "/", NULL);
   a_Cookies_set("10=1", "toomany.com", "/", NULL);
   a_Cookies_set("11=1", "toomany.com", "/", NULL);
   a_Cookies_set("12=1", "toomany.com", "/", NULL);
   a_Cookies_set("13=1", "toomany.com", "/", NULL);
   a_Cookies_set("14=1", "toomany.com", "/", NULL);
   a_Cookies_set("15=1", "toomany.com", "/", NULL);
   a_Cookies_set("16=1", "toomany.com", "/", NULL);
   a_Cookies_set("17=1", "toomany.com", "/", NULL);
   a_Cookies_set("18=1", "toomany.com", "/", NULL);
   a_Cookies_set("19=1", "toomany.com", "/", NULL);
   a_Cookies_set("20=1", "toomany.com", "/", NULL);
   a_Cookies_set("21=1", "toomany.com", "/", NULL);
   /* 1 was oldest and discarded */
   expect(__LINE__, "Cookie: 7=1; 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; "
                    "11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; "
                    "20=1; 21=1\r\n", "http", "toomany.com", "/path/");
   sleep(1);
   /* touch all of them except #7 (path matching) */
   expect(__LINE__, "Cookie: 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; "
                    "11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; "
                    "20=1; 21=1\r\n", "http", "toomany.com", "/");
   a_Cookies_set("22=1", "toomany.com", "/", NULL);
   /* 7 was oldest and discarded */
   expect(__LINE__, "Cookie: 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; "
                    "11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; "
                    "20=1; 21=1; 22=1\r\n", "http", "toomany.com", "/path/");
}

static void maxage()
{
   time_t t = time(NULL)+1000;
   char *server_date = dStrdup(ctime(&t));

   a_Cookies_set("name=val; max-age=0", "maxage0.com", "/", NULL);
   expect(__LINE__, "", "http", "maxage0.com", "/");

   a_Cookies_set("name=val; max-age=100", "maxage100.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage100.com", "/");

   a_Cookies_set("name=val; max-age=-100", "maxage-100.com", "/", NULL);
   expect(__LINE__, "", "http", "maxage-100.com", "/");

   a_Cookies_set("name=val; max-age=2000000000", "maxage-huge.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage-huge.com", "/");
   /* just having a server date shouldn't matter */

   a_Cookies_set("name=val; max-age=0", "maxage0s.com", "/", server_date);
   expect(__LINE__, "", "http", "maxage0s.com", "/");

   a_Cookies_set("name=val; max-age=100", "maxage100s.com", "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "maxage100s.com", "/");

   a_Cookies_set("name=val; max-age=-100", "maxage-100s.com", "/",server_date);
   expect(__LINE__, "", "http", "maxage-100s.com", "/");

   /* MAX-AGE and EXPIRES */
   a_Cookies_set("name=val; max-age=90; expires=Wed Jan 20 01:26:32 2010",
                 "maxagelater.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "maxagelater.com", "/");

   a_Cookies_set("name=val; max-age=90; expires=Wed Jan 20 01:26:32 2010",
                 "maxagelaters.com", "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "maxagelaters.com", "/");

   dFree(server_date);
}

static void expires_server_ahead()
{
   char *string;
   time_t t = time(NULL)+1000;
   char *server_date = dStrdup(ctime(&t));
   time_t expt = t + 1000;
   char *exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e2000s1000.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e2000s1000.com", "/");

   a_Cookies_set(string, "e2000s1000s.com", "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e2000s1000s.com", "/");

   expt = t - 500; /* past for the server, future for us */
   dFree(exp_date);
   exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e500s1000.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e500s1000.com", "/");

   a_Cookies_set(string, "e500s1000s.com", "/", server_date);
   expect(__LINE__, "", "http", "e500s1000s.com", "/");

   expt = t; /* expire at future-for-us server date */
   dFree(exp_date);
   exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e1000s1000.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e1000s1000.com", "/");

   a_Cookies_set(string, "e1000s1000s.com", "/", server_date);
   expect(__LINE__, "", "http", "e1000s1000s.com", "/");

   expt = time(NULL); /* now */
   dFree(exp_date);
   exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e0s1000.com", "/", NULL);
   expect(__LINE__, "", "http", "e0s1000.com", "/");

   a_Cookies_set(string, "e0s1000s.com", "/", server_date);
   expect(__LINE__, "", "http", "e0s1000s.com", "/");

   dFree(exp_date);
   dFree(server_date);
}

static void expires_server_behind()
{
   char *string;
   time_t t = time(NULL)-1000;
   char *server_date = dStrdup(ctime(&t));

   time_t expt = t + 1000;
   char *exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e0s-1000.com", "/", NULL);
   expect(__LINE__, "", "http", "e0s-1000.com", "/");

   a_Cookies_set(string, "e0s-1000s.com", "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e0s-1000s.com","/");

   expt = t + 500; /* future for the server, past for us */
   dFree(exp_date);
   exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e-500s-1000.com", "/", NULL);
   expect(__LINE__, "", "http", "e-500s-1000.com", "/");

   a_Cookies_set(string, "e-500s-1000s.com", "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "e-500s-1000s.com", "/");

   expt = t; /* expire at past-for-us server date */
   dFree(exp_date);
   exp_date = dStrdup(ctime(&expt));

   string = dStrconcat("name=val; expires=", exp_date, NULL);
   a_Cookies_set(string, "e-1000s-1000.com", "/", NULL);
   expect(__LINE__, "", "http", "e-1000s-1000.com", "/");

   a_Cookies_set(string, "e-1000s-1000s.com", "/", server_date);
   expect(__LINE__, "", "http", "e-1000s-1000s.com", "/");

   dFree(server_date);
   dFree(exp_date);
}

static void expires_extremes()
{
   time_t t;
   char *server_date;

   a_Cookies_set("name=val; expires=Fri Dec 13 20:45:52 1901", "expmin.com",
                 "/", NULL);
   expect(__LINE__, "", "http", "expmin.com", "/");

   a_Cookies_set("name=val; expires=Wed Dec 31 23:59:59 1969", "expneg.com",
                 "/", NULL);
   expect(__LINE__, "", "http", "expneg.com", "/");

   a_Cookies_set("name=val; expires=Thu Jan  1 00:00:00 1970", "expepoch.com",
                 "/", NULL);
   expect(__LINE__, "", "http", "expepoch.com", "/");

   /* TODO: revisit these tests in a few decades */
   a_Cookies_set("name=val; expires=Tue Jan 19 03:14:07 2038", "expmax.com",
                 "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "expmax.com", "/");

   a_Cookies_set("name=val; expires=Sun Jan  1 00:00:00 2040", "pastmax.com",
                 "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "pastmax.com", "/");

   t = time(NULL)+1000;
   server_date = dStrdup(ctime(&t));

   a_Cookies_set("name=val; expires=Fri Dec 13 20:45:52 1901", "expmina.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expmina.com", "/");

   a_Cookies_set("name=val; expires=Wed Dec 31 23:59:59 1969", "expnega.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expnega.com", "/");

   a_Cookies_set("name=val; expires=Thu Jan  1 00:00:00 1970", "expepocha.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expepocha.com", "/");

   a_Cookies_set("name=val; expires=Tue Jan 19 03:14:07 2038", "expmaxa.com",
                 "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "expmaxa.com", "/");

   a_Cookies_set("name=val; expires=Sun Jan  1 00:00:00 2040", "pastmaxa.com",
                 "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "pastmaxa.com", "/");

   t = time(NULL)-1000;
   dFree(server_date);
   server_date = dStrdup(ctime(&t));

   a_Cookies_set("name=val; expires=Fri Dec 13 20:45:52 1901", "expminb.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expminb.com", "/");

   a_Cookies_set("name=val; expires=Wed Dec 31 23:59:59 1969", "expnegb.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expnegb.com", "/");

   a_Cookies_set("name=val; expires=Thu Jan  1 00:00:00 1970", "expepochb.com",
                 "/", server_date);
   expect(__LINE__, "", "http", "expepochb.com", "/");

   a_Cookies_set("name=val; expires=Tue Jan 19 03:14:07 2038", "expmaxb.com",
                 "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "expmaxb.com", "/");

   a_Cookies_set("name=val; expires=Sun Jan  1 00:00:00 2040", "pastmaxb.com",
                 "/", server_date);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "pastmaxb.com", "/");

   dFree(server_date);
}

static void path()
{
   a_Cookies_set("name=val; path=/", "p1.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p1.com", "/");

   a_Cookies_set("name=val; path=/dir1", "p2.com", "/dir2", NULL);
   expect(__LINE__, "", "http", "p2.com", "/");
   expect(__LINE__, "", "http", "p2.com", "/d");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p2.com", "/dir1");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p2.com", "/dir1/");
   expect(__LINE__, "", "http", "p2.com", "/dir2");
   expect(__LINE__, "", "http", "p2.com", "/dir11");

   a_Cookies_set("name=val; path=dir1", "p3.com", "/dir2", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p3.com", "/");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p3.com", "/dir1");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p3.com", "/dir2");

   a_Cookies_set("name=val; path=/dir1/", "p4.com", "/dir2", NULL);
   expect(__LINE__, "", "http", "p4.com", "/");
   /* this next one strikes me as a bit odd, personally, but I suppose it's not
    * a big deal */
   expect(__LINE__, "", "http", "p4.com", "/dir1");
   expect(__LINE__, "", "http", "p4.com", "/dir11");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p4.com", "/dir1/");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p4.com", "/dir1/sub");

   a_Cookies_set("name=val", "p5.com", "/dir/subdir", NULL);
   expect(__LINE__, "", "http", "p5.com", "/");
   expect(__LINE__, "", "http", "p5.com", "/bir");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p5.com", "/dir");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p5.com", "/dir/");

   a_Cookies_set("name=val", "p6.com", "/dir/subdir/", NULL);
   expect(__LINE__, "", "http", "p6.com", "/dir/");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p6.com", "/dir/subdir");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "p6.com", "/dir/subdir/s");
}

int main()
{
   a_Cookies_set("name=val", "ordinary.com", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "ordinary.com", "/");

   toomany();
   maxage();
   expires_server_ahead();
   expires_server_behind();
   expires_extremes();

   a_Cookies_set("name=val; expires=\"Sun Jan 10 00:00:00 2038\"",
                 "quoted-date.org", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "quoted-date.org", "/");

   a_Cookies_set("name=val; expires=\"Sun Jan 11 00:00:00 1970\"",
                 "quoted-pastdate.org", "/", NULL);
   expect(__LINE__, "", "http", "quoted-pastdate.org", "/");

   path();

   /* LEADING/TRAILING DOTS AND A LITTLE PUBLIC SUFFIX */
   a_Cookies_set("name=val; domain=co.uk", "www.co.uk", "/", NULL);
   expect(__LINE__, "", "http", "www.co.uk", "/");

   a_Cookies_set("name=val; domain=.co.uk", "www.co.uk", "/", NULL);
   expect(__LINE__, "", "http", "www.co.uk", "/");

   a_Cookies_set("name=val; domain=co.uk.", "www.co.uk.", "/", NULL);
   expect(__LINE__, "", "http", "www.co.uk.", "/");

   a_Cookies_set("name=val; domain=.co.uk.", "www.co.uk.", "/", NULL);
   expect(__LINE__, "", "http", ".www.co.uk.", "/");

   a_Cookies_set("name=val; domain=co.org", "www.co.org", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "www.co.org", "/");

   a_Cookies_set("name=val; domain=.cp.org", "www.cp.org", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "www.cp.org", "/");


   /* DOTDOMAIN */
   a_Cookies_set("name=val; domain=.dotdomain.org", "dotdomain.org", "/",
                 NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "dotdomain.org", "/");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "www.dotdomain.org", "/");

   /* HOST_ONLY */
   a_Cookies_set("name=val; domain=.hostonly.org", "hostonly.org", "/", NULL);
   a_Cookies_set("name2=val2", "hostonly.org", "/", NULL);
   a_Cookies_set("name3=val3; domain=hostonly.org", "hostonly.org", "/", NULL);
   expect(__LINE__, "Cookie: name=val; name2=val2; name3=val3\r\n", "http",
          "hostonly.org", "/");
   a_Cookies_set("name=new; domain=.hostonly.org", "hostonly.org", "/", NULL);
   expect(__LINE__, "Cookie: name=new; name2=val2; name3=val3\r\n", "http",
          "hostonly.org", "/");
   a_Cookies_set("name2=new2", "hostonly.org", "/", NULL);
   expect(__LINE__, "Cookie: name=new; name2=new2; name3=val3\r\n", "http",
          "hostonly.org", "/");
   a_Cookies_set("name3=new3; domain=hostonly.org", "hostonly.org", "/", NULL);
   expect(__LINE__, "Cookie: name=new; name2=new2; name3=new3\r\n", "http",
          "hostonly.org", "/");

   /* SUBDOMAIN */
   a_Cookies_set("name=val; domain=www.subdomain.com", "subdomain.com", "/",
                 NULL);
   a_Cookies_set("name=val; domain=.www.subdomain.com", "subdomain.com", "/",
                 NULL);
   expect(__LINE__, "", "http", "subdomain.com", "/");
   expect(__LINE__, "", "http", "www.subdomain.com", "/");

   /* SUPERDOMAIN(?) */
   a_Cookies_set("name=val; domain=.supdomain.com", "www.supdomain.com", "/",
                 NULL);
   a_Cookies_set("name2=val2; domain=supdomain.com", "www.supdomain.com", "/",
                 NULL);
   expect(__LINE__, "Cookie: name=val; name2=val2\r\n", "http",
          "sub2.sub.supdomain.com", "/");
   expect(__LINE__, "Cookie: name=val; name2=val2\r\n", "http",
          "www.supdomain.com", "/");
   expect(__LINE__, "Cookie: name=val; name2=val2\r\n", "http",
          "supdomain.com", "/");

   /* UNRELATED */
   a_Cookies_set("name=val; domain=another.com", "unrelated.com", "/", NULL);
   expect(__LINE__, "", "http", "another.com", "/");
   a_Cookies_set("name=val; domain=another.com", "a.org", "/", NULL);
   expect(__LINE__, "", "http", "another.com", "/");
   a_Cookies_set("name=val; domain=another.com", "badguys.com", "/", NULL);
   expect(__LINE__, "", "http", "another.com", "/");
   a_Cookies_set("name=val; domain=another.com", "more.badguys.com", "/",
                 NULL);
   expect(__LINE__, "", "http", "another.com", "/");
   a_Cookies_set("name=val; domain=another.com", "verybadguys.com", "/", NULL);
   expect(__LINE__, "", "http", "another.com", "/");

   a_Cookies_set("name=val; domain=similar.com", "imilar.com", "/", NULL);
   a_Cookies_set("name2=val2; domain=similar.com", "ssimilar.com", "/", NULL);
   a_Cookies_set("name3=val3; domain=.similar.com", "imilar.com", "/", NULL);
   a_Cookies_set("name4=val4; domain=.similar.com", "timilar.com", "/", NULL);
   a_Cookies_set("name4=val4; domain=.similar.com", "tiimilar.com", "/", NULL);
   expect(__LINE__, "", "http", "similar.com", "/");

   /* SECURE */
   a_Cookies_set("name=val; secure", "secure.com", "/", NULL);
   expect(__LINE__, "", "http", "secure.com", "/");
   expect(__LINE__, "Cookie: name=val\r\n", "https", "secure.com", "/");

   /* HTTPONLY */
   a_Cookies_set("name=val; HttpOnly", "httponly.net", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "httponly.net", "/");

   /* GIBBERISH ATTR IGNORED */
   a_Cookies_set("name=val; ldkfals", "gibberish.net", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "gibberish.net", "/");

   /* WHITESPACE/DELIMITERS */
   a_Cookies_set(" name=val ", "whitespace.net", "/", NULL);
   a_Cookies_set("name2=val2;", "whitespace.net", "/", NULL);
   expect(__LINE__, "Cookie: name=val; name2=val2\r\n", "http",
          "whitespace.net", "/");

   /* NAMELESS/VALUELESS */
   a_Cookies_set("value", "nonameval.org", "/", NULL);
   a_Cookies_set("name=", "nonameval.org", "/", NULL);
   a_Cookies_set("name2= ", "nonameval.org", "/", NULL);
   expect(__LINE__, "Cookie: name=; name2=\r\n", "http", "nonameval.org", "/");
   a_Cookies_set("=val2", "nonameval.org", "/", NULL);
   expect(__LINE__, "Cookie: name=; name2=\r\n", "http", "nonameval.org", "/");


   /* SOME IP ADDRS */

   a_Cookies_set("name=val", "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]",
                 "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http",
          "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]", "/");

   a_Cookies_set("name=val", "[::FFFF:129.144.52.38]", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "[::FFFF:129.144.52.38]",
          "/");

   a_Cookies_set("name=val", "127.0.0.1", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "127.0.0.1", "/");

   a_Cookies_set("name=val; domain=128.0.0.1", "128.0.0.1", "/", NULL);
   expect(__LINE__, "Cookie: name=val\r\n", "http", "128.0.0.1", "/");

   a_Cookies_set("name=val; domain=130.0.0.1", "129.0.0.1", "/", NULL);
   expect(__LINE__, "", "http", "129.0.0.1", "/");
   expect(__LINE__, "", "http", "130.0.0.1", "/");

   a_Cookies_set("name=val", "2.0.0.1", "/", NULL);
   a_Cookies_set("name=bad; domain=22.0.0.1", "2.0.0.1", "/", NULL);
   a_Cookies_set("name=bad; domain=.0.0.1", "2.0.0.1", "/", NULL);
   a_Cookies_set("name=bad; domain=not-ip.org", "2.0.0.1", "/", NULL);
   expect(__LINE__, "", "http", "22.0.0.1", "/");
   expect(__LINE__, "", "http", "not-ip.org", "/");
   expect(__LINE__, "Cookie: name=val\r\n", "http", "2.0.0.1", "/");

#if 0
HAD BEEN PLAYING AROUND WITH REAL PUBLIC SUFFIX
a_Cookies_set("name=val;domain=sub.sub.yokohama.jp", "sub.sub.yokohama.jp", "/", NULL);
MSG("sub sub yokohama should work: %s\n",
    a_Cookies_get_query("http", "sub.sub.yokohama.jp", "/"));
a_Cookies_set("name=val; domain=sub.tokyo.jp", "sub.sub.tokyo.jp", "/", NULL);
MSG("sub tokyo jp should fail: %s\n",
    a_Cookies_get_query("http", "sub.sub.tokyo.jp", "/"));
a_Cookies_set("name=val; domain=pref.chiba.jp", "sub.pref.chiba.jp", "/", NULL);
MSG("pref chiba jp should succeed: %s\n",
    a_Cookies_get_query("http", "sub.pref.chiba.jp", "/"));
a_Cookies_set("name=val; domain=org", "www.dillo.org", "/", NULL);
a_Cookies_set("name=val; domain=org", "dillo.org", "/", NULL);
a_Cookies_set("name=val; domain=org", ".dillo.org", "/", NULL);
a_Cookies_set("name=val; domain=org.", ".dillo.org", "/", NULL);
a_Cookies_set("name=val; domain=org.", ".dillo.org.", "/", NULL);
MSG("org should fail: %s\n",
    a_Cookies_get_query("http", "www.dillo.org", "/"));
#endif

   MSG("TESTS: passed: %u failed: %u\n", passed, failed);
   return 0;
}