/*
    httperf -- a tool for measuring web server performance
    Copyright (C) 2000  Hewlett-Packard Company
    Contributed by David Mosberger-Tang <davidm@hpl.hp.com>

    This file is part of httperf, a web server performance measurment
    tool.

    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 2 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, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA
*/

/* This module intercepts `Set-Cookie:' headers on a per-session basis
   and includes set cookies in future calls of the session.

   Missing features:
	- intercepted cookies are always sent, independent of any constraints
	   that may be present in the set-cookie header
*/

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <httperf.h>
#include <call.h>
#include <conn.h>
#include <core.h>
#include <event.h>
#include <session.h>

#define MAX_COOKIE_LEN	256

#define SESS_PRIVATE_DATA(c) \
  ((Sess_Private_Data *) ((char *)(c) + sess_private_data_offset))

#define CALL_PRIVATE_DATA(c) \
  ((Call_Private_Data *) ((char *)(c) + call_private_data_offset))

typedef struct Sess_Private_Data
  {
    /* For now, we support just one cookie per session.  If we get
       more than one cookie, we'll print a warning message when
       --debug is turned on.  */
    size_t cookie_len;
    /* We can't malloc the cookie string because we may get a
       ``Set-Cookie:'' while there are calls pending that refer to an
       existing cookie.  So if we were to malloc & free cookies, we
       would have to use reference counting to avoid the risk of
       dangling pointers.  */
    char cookie[MAX_COOKIE_LEN];
  }
Sess_Private_Data;

/* We need the call private data to ensure that the cookie gets set
   only once.  EV_CALL_ISSUE gets signalled each time a call is sent
   on a connection.  Since a connection may fail, the same call may be
   issued multiple times, hence we need to make sure that the cookie
   gets set only once per call.  */
typedef struct Call_Private_Data
  {
    u_int cookie_present;	/* non-zero if cookie has been set already */
    char cookie[MAX_COOKIE_LEN];
  }
Call_Private_Data;

static size_t sess_private_data_offset = -1;
static size_t call_private_data_offset = -1;

/* A simple module that collects cookies from the server responses and
   includes them in future calls to the server.  */

static void
call_issue (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  Call_Private_Data *cpriv;
  Sess_Private_Data *priv;
  Sess *sess;
  Call *call;

  assert (et == EV_CALL_ISSUE && object_is_call (obj));
  call = (Call *) obj;
  cpriv = CALL_PRIVATE_DATA (call);

  if (cpriv->cookie_present)
    /* don't do anything if cookie has been set already */
    return;

  sess = session_get_sess_from_call (call);
  priv = SESS_PRIVATE_DATA (sess);

  if (priv->cookie_len > 0)
    {
      if (DBG > 1)
	fprintf (stderr, "call_issue.%ld: inserting `%s'\n",
		 call->id, priv->cookie);
      cpriv->cookie_present = 1;
      memcpy (cpriv->cookie, priv->cookie, priv->cookie_len + 1);
      call_append_request_header (call, cpriv->cookie, priv->cookie_len);
    }
}

static void
call_recv_hdr (Event_Type et, Object *obj, Any_Type regarg, Any_Type callarg)
{
  char *hdr, *start, *end;
  Sess_Private_Data *priv;
  size_t len;
  struct iovec *line;
  Sess *sess;
  Call *call;

  assert (et == EV_CALL_RECV_HDR && object_is_call (obj));
  call = (Call *) obj;
  sess = session_get_sess_from_call (call);
  priv = SESS_PRIVATE_DATA (sess);

  line = callarg.vp;
  hdr = line->iov_base;
  if (tolower (hdr[0]) == 's' && line->iov_len > 12
      && strncasecmp (hdr + 1, "et-cookie: ", 11) == 0)
    {
      /* munch time! */
      start = hdr + 12;
      end = strchr (start, ';');
      if (!end)
	end = hdr + line->iov_len;
      len = end - start;
      if (DBG > 0 && priv->cookie_len > 0)
	fprintf (stderr, "%s: can't handle more than one "
		 "cookie at a time, replacing existing one\n", prog_name);
      if (len + 10 >= MAX_COOKIE_LEN)
	{
	  fprintf (stderr, "%s.sess_cookie: truncating cookie to %d bytes\n",
		   prog_name, MAX_COOKIE_LEN - 11);
	  len = MAX_COOKIE_LEN - 11;
	}
      memcpy (priv->cookie, "Cookie: ", 8);
      memcpy (priv->cookie + 8, start, len);
      memcpy (priv->cookie + 8 + len, "\r\n", 2);
      priv->cookie[10 + len] = '\0';
      priv->cookie_len = len + 10;

      if (DBG > 0)
	fprintf (stderr, "%s: got cookie `%s'\n", prog_name, start);
    }
}

static void
init (void)
{
  Any_Type arg;

  sess_private_data_offset = object_expand (OBJ_SESS,
					    sizeof (Sess_Private_Data));
  call_private_data_offset = object_expand (OBJ_CALL,
					    sizeof (Call_Private_Data));
  arg.l = 0;
  event_register_handler (EV_CALL_ISSUE, call_issue, arg);
  event_register_handler (EV_CALL_RECV_HDR, call_recv_hdr, arg);
}

Load_Generator sess_cookie =
  {
    "per-session cookie manager",
    init,
    no_op,
    no_op
  };


syntax highlighted by Code2HTML, v. 0.9.1