/*
 * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
 *               2002, 2003, 2004
 *	Ohio University.
 *
 * ---
 * 
 * Starting with the release of tcptrace version 6 in 2001, tcptrace
 * is licensed under the GNU General Public License (GPL).  We believe
 * that, among the available licenses, the GPL will do the best job of
 * allowing tcptrace to continue to be a valuable, freely-available
 * and well-maintained tool for the networking community.
 *
 * Previous versions of tcptrace were released under a license that
 * was much less restrictive with respect to how tcptrace could be
 * used in commercial products.  Because of this, I am willing to
 * consider alternate license arrangements as allowed in Section 10 of
 * the GNU GPL.  Before I would consider licensing tcptrace under an
 * alternate agreement with a particular individual or company,
 * however, I would have to be convinced that such an alternative
 * would be to the greater benefit of the networking community.
 * 
 * ---
 *
 * This file is part of Tcptrace.
 *
 * Tcptrace was originally written and continues to be maintained by
 * Shawn Ostermann with the help of a group of devoted students and
 * users (see the file 'THANKS').  The work on tcptrace has been made
 * possible over the years through the generous support of NASA GRC,
 * the National Science Foundation, and Sun Microsystems.
 *
 * Tcptrace 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.
 *
 * Tcptrace 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 Tcptrace (in the file 'COPYING'); if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 * 
 * Author:	Shawn Ostermann
 * 		School of Electrical Engineering and Computer Science
 * 		Ohio University
 * 		Athens, OH
 *		ostermann@cs.ohiou.edu
 *		http://www.tcptrace.org/
 */
#include "tcptrace.h"
static char const GCC_UNUSED rcsid[] =
   "$Header: /usr/local/cvs/tcptrace/mod_http.c,v 5.13 2003/11/19 14:38:02 sdo Exp $";


#ifdef LOAD_MODULE_HTTP

#include <sys/mman.h>
#include "mod_http.h"


#define DEFAULT_SERVER_PORT 80


/* Revised HTTP module with a new HTTP parser provided by Bruce Mah */

/* codes for different message types */
typedef enum {
     MethodCodeOptions,
     MethodCodeGet,
     MethodCodeHead,
     MethodCodePost,
     MethodCodePut,
     MethodCodeDelete,
     MethodCodeTrace,
     MethodCodeUnknown
} MethodCode;

char *MethodCodeString[] = {
     "OPTIONS",
     "GET",
     "HEAD",
     "POST",
     "PUT",
     "DELETE",
     "TRACE"
};

/* info gathered for each GET */
struct get_info {
    timeval get_time;		/* when CLIENT sent GET */
    timeval send_time;		/* when SERVER sent CONTENT */
    timeval lastbyte_time;	/* when SERVER sent last byte of CONTENT */
    timeval ack_time;		/* when CLIENT acked CONTENT */
    unsigned request_position;  /* byte offset for this request */
    unsigned reply_position;    /* byte offset for this reply */
    MethodCode method;          /* HTTP method code */
    unsigned response_code;     /* HTTP response code */
    unsigned content_length;	/* as reported by server */
    char *get_string;		/* content of GET string */
    char *content_type;         /* MIME type */  

    struct get_info *next;
};



/* linked list of times */
struct time_stamp {
    timeval 	       thetime;
    u_long 	       position;
    struct time_stamp *next;
    struct time_stamp *prev;
};


/* info kept for each client */
static struct client_info {
    PLOTTER plotter;
    char *clientname;
    struct client_info *next;
} *client_head = NULL;


/* info kept for each connection */
static struct http_info {
    timeval c_syn_time;		/* when CLIENT sent SYN */
    timeval s_syn_time;		/* when SERVER sent SYN */
    timeval c_fin_time;		/* when CLIENT sent FIN */
    timeval s_fin_time;		/* when SERVER sent FIN */

    /* client record */
    struct client_info *pclient;

    /* info about the TCP connection */
    tcp_pair *ptp;
    tcb *tcb_client;
    tcb *tcb_server;

    /* aggregate statistics for HTTP requests on this connection*/
    /* some of this info is available in *tcb_client and *tcb_server */
    /* but we keep a copy of it here to keep all useful info in one place */
    unsigned total_request_length;
    unsigned total_reply_length;
    unsigned total_request_count;
    unsigned total_reply_count;
   
    /* when querries (GETs) were sent by client */
    struct time_stamp get_head;
    struct time_stamp get_tail;

    /* when answers (CONTENT) were sent by server */
    struct time_stamp data_head;
    struct time_stamp data_tail;

    /* when answers (CONTENT) were acked by client */
    struct time_stamp ack_head;
    struct time_stamp ack_tail;

    /* linked list of requests */
    struct get_info *gets_head;
    struct get_info *gets_tail;

    struct http_info *next;
} *httphead = NULL, *httptail = NULL;



/* which port are we monitoring?? */
static unsigned httpd_port;


/* local routines */
static timeval WhenSent(struct time_stamp *phead, struct time_stamp *ptail,
			u_long position);
static timeval WhenAcked(struct time_stamp *phead, struct time_stamp *ptail,
			 u_long position);
static void MFUnMap(MFILE *mf, char *firstbyte);
static void MFMap(MFILE *mf, char **firstbyte, char **lastbyte);
static void FindGets(struct http_info *ph);
static void FindContent(struct http_info *ph);
static void HttpGather(struct http_info *ph);
static struct http_info *MakeHttpRec(void);
static struct get_info *MakeGetRec(struct http_info *ph);
static u_long DataOffset(tcb *tcb, seqnum seq);
static void AddGetTS(struct http_info *ph, u_long position);
static void AddDataTS(struct http_info *ph, u_long position);
static void AddAckTS(struct http_info *ph, u_long position);
static void AddTS(struct time_stamp *phead, struct time_stamp *ptail,
		  u_long position);
static double ts2d(timeval *pt);
static void HttpPrintone(MFILE *pmf, struct http_info *ph);
static void HttpDoPlot(void);
static struct client_info *FindClient(char *clientname);


/* useful macros */
#define IS_CLIENT(ptcp) (ntohs((ptcp)->th_dport) == httpd_port)
#define IS_SERVER(ptcp) (ntohs((ptcp)->th_dport) != httpd_port)


/* Mostly as a module example, here's a plug in that records HTTP info */
int
http_init(
    int argc,
    char *argv[])
{
    int i;
    int enable=0;

    /* look for "-xhttp[N]" */
    for (i=1; i < argc; ++i) {
	if (!argv[i])
	    continue;  /* argument already taken by another module... */
	if (strncmp(argv[i],"-x",2) == 0) {
	    if (strncasecmp(argv[i]+2,"http",4) == 0) {
		/* I want to be called */
		enable = 1;
		if (isdigit((int)(argv[i][6]))) {
		    httpd_port = atoi(argv[i]+6);
		} else {
		    httpd_port = DEFAULT_SERVER_PORT;
		}
		printf("mod_http: Capturing HTTP traffic (port %d)\n", httpd_port);
		argv[i] = NULL;
	    }
	}
    }

    if (!enable)
	return(0);	/* don't call me again */


    /* init stuff */

    /* We need to save the contents for accurate reconstruction of questions */
    /* and answers */
    save_tcp_data = TRUE;


    return(1);	/* TRUE means call http_read and http_done later */
}


/* N.B.  first byte is position _1_ */
static u_long
DataOffset(
    tcb *tcb,
    seqnum seq)
{
    u_long off;
    
    /* we're going to be a little lazy and assume that a http connection */
    /* can't be longer than 2**32  */
    if (seq > tcb->syn)
	off = seq-tcb->syn;
    else
	off = tcb->syn-seq;

    if (debug>1)
	fprintf(stderr,"DataOffset: seq is %lu, syn is %lu, offset is %ld\n",
		seq, tcb->syn, off);

    return(off);
}


static struct get_info *
MakeGetRec(
    struct http_info *ph)
{
    struct get_info *pg;

    pg = MallocZ(sizeof(struct get_info));
   
    /* initialize some fields */
    pg->get_string = "- -";
    pg->content_type = "-/-";  

    /* put at the end of the chain */
    if (ph->gets_head == NULL) {
	ph->gets_head = pg;
	ph->gets_tail = pg;
    } else {
	ph->gets_tail->next = pg;
	ph->gets_tail = pg;
    }

    return(pg);
}


static struct http_info *
MakeHttpRec()
{
    struct http_info *ph;

    ph = MallocZ(sizeof(struct http_info));

    ph->get_head.next = &ph->get_tail;
    ph->get_tail.prev = &ph->get_head;
    ph->get_tail.position = 0xffffffff;

    ph->data_head.next = &ph->data_tail;
    ph->data_tail.prev = &ph->data_head;
    ph->data_tail.position = 0xffffffff;

    ph->ack_head.next = &ph->ack_tail;
    ph->ack_tail.prev = &ph->ack_head;
    ph->ack_tail.position = 0xffffffff;

    /* chain it in (at the tail of the list) */
    if (httphead == NULL) {
	httphead = ph;
	httptail = ph;
    } else {
	httptail->next = ph;
	httptail = ph;
    }

    return(ph);
}


static void
AddGetTS(
    struct http_info *ph,
    u_long position)
{
    AddTS(&ph->get_head,&ph->get_tail,position);
}



static void
AddDataTS(
    struct http_info *ph,
    u_long position)
{
    AddTS(&ph->data_head,&ph->data_tail,position);
}


static void
AddAckTS(
    struct http_info *ph,
    u_long position)
{
    AddTS(&ph->ack_head,&ph->ack_tail,position);
}


/* add a timestamp to the record */
/* HEAD points to the smallest position numbers */
/* TAIL points to the largest position numbers */
static void
AddTS(
    struct time_stamp *phead,
    struct time_stamp *ptail,
    u_long position)
{
    struct time_stamp *pts;
    struct time_stamp *pts_new;

    pts_new = MallocZ(sizeof(struct time_stamp));
    pts_new->thetime = current_time;
    pts_new->position = position;

    for (pts = ptail->prev; pts != NULL; pts = pts->prev) {
	if (position == pts->position)
	    return; /* ignore duplicates */

	if (position > pts->position) {
	    /* it goes AFTER this one (pts) */
	    pts_new->next = pts->next;
	    pts_new->prev = pts;
	    pts->next = pts_new;
	    pts_new->next->prev = pts_new;
	    return;
	}
    }

    /* can't fail, the tail has timestamp 0 */
}


static struct client_info *
FindClient(
    char *clientname)
{
    struct client_info *p;

    for (p=client_head; p; p = p->next) {
	if (strcmp(clientname,p->clientname)==0) {
	    return(p);
	}
    }

    /* else, make one up */
    p = MallocZ(sizeof(struct client_info));
    p->next = client_head;
    client_head = p;
    p->clientname = strdup(clientname);
    p->plotter = NO_PLOTTER;

    return(p);
}



void
http_read(
    struct ip *pip,		/* the packet */
    tcp_pair *ptp,		/* info I have about this connection */
    void *plast,		/* past byte in the packet */
    void *mod_data)		/* module specific info for this connection */
{
    struct tcphdr *ptcp;
    unsigned tcp_length;
    unsigned tcp_data_length;
    char *pdata;
    struct http_info *ph = mod_data;

    /* find the start of the TCP header */
    ptcp = (struct tcphdr *) ((char *)pip + 4*IP_HL(pip));
    tcp_length = ntohs(pip->ip_len) - (4 * IP_HL(pip));
    tcp_data_length = tcp_length - (4 * TH_OFF(ptcp));

    /* verify port */
    if ((ntohs(ptcp->th_sport) != httpd_port) && (ntohs(ptcp->th_dport) != httpd_port))
	return;

    /* find the data */
    pdata = (char *)ptcp + (unsigned)TH_OFF(ptcp)*4;

    /* for client, record both ACKs and DATA time stamps */
    if (ph && IS_CLIENT(ptcp)) {
	if (tcp_data_length > 0) {
	    AddGetTS(ph,DataOffset(ph->tcb_client,ntohl(ptcp->th_seq)));
	}
	if (ACK_SET(ptcp)) {
	    if (debug > 4)
		printf("Client acks %ld\n", DataOffset(ph->tcb_server,ntohl(ptcp->th_ack)));	    
	    AddAckTS(ph,DataOffset(ph->tcb_server,ntohl(ptcp->th_ack)));
	}
    }

    /* for server, record DATA time stamps */
    if (ph && IS_SERVER(ptcp)) {
	if (tcp_data_length > 0) {
	    AddDataTS(ph,DataOffset(ph->tcb_server,ntohl(ptcp->th_seq)));
	    if (debug > 5) {
		printf("Server sends %ld thru %ld\n",
		       DataOffset(ph->tcb_server,ntohl(ptcp->th_seq)),
		       DataOffset(ph->tcb_server,ntohl(ptcp->th_seq))+tcp_data_length-1);
	    }
	}
    }

    
    /* we also want the time that the FINs were sent */
    if (ph && FIN_SET(ptcp)) {
	if (IS_SERVER(ptcp)) {
	    /* server */
	    if (ZERO_TIME(&(ph->s_fin_time)))
		ph->s_fin_time = current_time;
	} else {
	    /* client */
	    if (ZERO_TIME(&ph->c_fin_time))
		ph->c_fin_time = current_time;
	}
    }

    /* we also want the time that the SYNs were sent */
    if (ph && SYN_SET(ptcp)) {
	if (IS_SERVER(ptcp)) {
	    /* server */
	    if (ZERO_TIME(&ph->s_syn_time))
		ph->s_syn_time = current_time;
	} else {
	    /* client */
	    if (ZERO_TIME(&ph->c_syn_time))
		ph->c_syn_time = current_time;
	}
    }
}


static double
ts2d(timeval *pt)
{
    double d;
    d = pt->tv_sec;
    d += (double)pt->tv_usec/1000000;
    return(d);
}


static void
MFMap(
    MFILE *mf,
    char **firstbyte,
    char **lastbyte)
{
    int fd;
    char *vaddr;
    int len;
    
    /* find length of file */
    if (Mfseek(mf,0,SEEK_END) != 0) {
	perror("fseek");
	exit(-1);
    }
    len = Mftell(mf);

    if (len == 0) {
      *firstbyte = NULL;
      *lastbyte = NULL;
      return;
    }
  
    /* Memory map the entire file */
    fd = Mfileno(mf);
    vaddr = mmap((caddr_t) 0,	/* put it anywhere	*/
		 len,		/* fixed size		*/
		 PROT_READ,	/* read only		*/
		 MAP_PRIVATE,	/* won't be sharing...	*/
		 fd,		/* attach to 'fd'	*/
		 (off_t) 0);	/* ... offset 0 in 'fd'	*/
    if (vaddr == (char *) -1) {
	perror("mmap");
	exit(-1);
    }

    *firstbyte = vaddr;
    *lastbyte = vaddr+len-1;

    return;
}



static void
MFUnMap(
    MFILE *mf,
    char *firstbyte)
{
    int fd;
    int len;
    
    /* find length of file */
    if (Mfseek(mf,0,SEEK_END) != 0) {
	perror("fseek");
	exit(-1);
    }
    len = Mftell(mf);

    /* unmap it */
    fd = Mfileno(mf);
    if (munmap(firstbyte,len) != 0) {
	perror("munmap");
	exit(-1);
    }

    return;
}


static void
HttpGather(
    struct http_info *ph)
{
    while (ph) {
	if (ph->tcb_client->extr_contents_file &&
	    ph->tcb_server->extr_contents_file)
	{
	    FindGets(ph);
	    FindContent(ph);
	}

	ph = ph->next;
    }
}


static void
PrintTSChain(
    struct time_stamp *phead,
    struct time_stamp *ptail)
{
    struct time_stamp *pts;

    for (pts = phead->next; pts != ptail; pts = pts->next) {
	printf("Pos: %ld  time: %s\n",
	       pts->position, ts2ascii(&pts->thetime));
    }
}


/* when was the byte at offset "position" acked?? */
/* return the timeval for the record of the smallest position >= "position" */
static timeval
WhenAcked(
    struct time_stamp *phead,
    struct time_stamp *ptail,
    u_long position)
{
    struct time_stamp *pts;
    timeval epoch = {0,0};

    if (debug > 10) {
	printf("pos:%ld, Chain:\n", position);
	PrintTSChain(phead,ptail);
    }

    for (pts = phead->next; pts != NULL; pts = pts->next) {
/* 	fprintf(stderr,"Checking pos %ld against %ld\n", */
/* 		position, pts->position); */
	if (pts->position >= position) {
	    /* sent after this one */
	    return(pts->thetime);
	}
    }

    /* fails if we can't find it */
    return(epoch);
}



/* when was the byte at offset "position" sent?? */
/* return the timeval for the record of the largest position <= "position" */
static timeval
WhenSent(
    struct time_stamp *phead,
    struct time_stamp *ptail,
    u_long position)
{
    struct time_stamp *pts;
    timeval epoch = {0,0};

    if (debug > 10) {
	printf("pos:%ld, Chain:\n", position);
	PrintTSChain(phead,ptail);
    }

    for (pts = ptail->prev; pts != phead; pts = pts->prev) {
/* 	fprintf(stderr,"Checking pos %ld against %ld\n", */
/* 		position, pts->position); */
	if (pts->position <= position) {
	    /* sent after this one */
	    return(pts->thetime);
	}
    }

    /* fails if we can't find it */
    return(epoch);
}




static void
FindContent(
    struct http_info *ph)
{
    tcb *tcb = ph->tcb_server;
    MFILE *mf = tcb->extr_contents_file;
    char *pdata;
    char *plast;
    char *pch;
    char *pch2;
    struct get_info *pget;
    char getbuf[1024];
    u_long position = 0;
    unsigned last_position = 0;
    int done;
    int i;
    typedef enum {
       ContentStateStartHttp,
       ContentStateFinishHttp,
       ContentStateFindResponse,
       ContentStateFindContentLength,
       ContentStateFinishHeader} StateType;
    StateType state;

    pget = ph->gets_head;
    if ((mf) && (pget)) {
 
       state = ContentStateStartHttp;
       done = 0;

       /* Memory map the entire file (I hope it's short!) */
       MFMap(mf,&pdata,&plast);
      
       if (pdata == NULL) {
	 return;
       }
      
       pch = pdata;
       
       ph->total_reply_length = (unsigned) (plast - pdata);
       ph->total_reply_count= 0;
       
       while ((!done) && (pch <= (char *) plast)) {
           switch (state) {

	    /* Start state: Find "HTTP/" that begins a response */
	    case (ContentStateStartHttp): {
	       if (strncasecmp(pch, "HTTP/", 5) == 0) {

		  /* Found start of a response */
		  state = ContentStateFinishHttp;
		  position = pch - pdata + 1;
		  pget->reply_position = position;
		  pch += 5;
	       }
	       else {
		  pch++;
	       }
	    }
	      break;

	    /* Finish off HTTP string (version number) by looking for whitespace */
	    case (ContentStateFinishHttp): {
	       if (*(pch) == ' ') {
		  state = ContentStateFindResponse;
	       }
	       else {
		  pch++;
	       }
	    }
	      break;
	      
	    /* Look for response code by finding non-whitespace. */
	    case (ContentStateFindResponse): {
	       if (*(pch) != ' ') {
		  pget->response_code = atoi(pch);
		  pch += 3;
		  state = ContentStateFindContentLength;
	       }
	       else {
		  pch++;
	       }
	    }
	      break;
	      
	    /* this state is now misnamed since we pull out other */
	    /* headers than just content-length now. */
	    case (ContentStateFindContentLength): {
	       if (strncasecmp(pch, "\r\nContent-Length:", 17) == 0) {
		  /* Got content-length field, ignore rest of header */
		  pget->content_length = atoi(&(pch[17]));
		  pch += 18;
	       }
	       else if (strncasecmp(pch, "\r\nContent-Type:", 15) == 0) {
		  /* Get content-type field, skipping leading spaces */
		  pch += 15;
		  while (*pch == ' ') {
		     pch++;
		  }
		  for (i=0,pch2 = pch; ; ++i, ++pch2) {
		     if ((*pch2 == '\n') || (*pch2 == '\r') ||
			 (i >= sizeof(getbuf)-1)) {
			getbuf[i] = '\00';
			pch = pch2;  /* skip forward */
			break;
		     }
		     getbuf[i] = *pch2;
		  }
		  
		  /* If there are any spaces in the Content-Type */
		  /* field, we need to truncate at that point */
		    {
		       char *sp;
		       sp = (char *)index(getbuf, ' ');
		       if (sp) {
			  *sp = '\00';
		       }
		    }
		  pget->content_type = strdup(getbuf);
		  
	       }
	       else if (strncmp(pch, "\r\n\r\n", 4) == 0) {
		  /* No content-length header detected */
		  /* No increment for pch here, effectively fall through */
		  /* pget->content_length = 0; */
		  state = ContentStateFinishHeader;
	       }
	       else {
		  pch++;
	       }
	    }
	      break;
	      
	    /* Skip over the rest of the header */
	    case (ContentStateFinishHeader): {
	       if (strncmp(pch, "\r\n\r\n", 4) == 0) {
		  
		  /* Found end of header */
		  pch += 4;
		  
		  /*
		   * At this point, we need to find the end of the
		   * response body.  There's a variety of ways to
		   * do this, but in any case, we need to make sure
		   * that pget->content_length, pch, and last_postiion
		   * are all set appropriately.
		   *
		   * See if we can ignore the body.  We can do this
		   * for the reply to HEAD, for a 204 (no content),
		   * 205 (reset content), or 304 (not modified).
		   */
		  if ((pget->method == MethodCodeHead) ||
		      (pget->response_code == 204) ||
		      (pget->response_code == 205) ||
		      (pget->response_code == 304)) {
		     pget->content_length = 0;
		  }
		  
		  /*
		   * Use content-length header if one was present.
		   * XXX is content_length > 0 the right test?
		   */
		  else if (pget->content_length > 0) {
		     pch += pget->content_length;
		  }
		  
		  /*
		   * No content-length header, so delimit response
		   * by end of file.
		   * 
		   * But, make sure we do not have a "\r\n\r\n" string
		   * in the response, because that might indicate the 
		   * beginning of a following response.
		   * (Patch from Yufei Wang)
		   */
		  else {
		   char *start = pch;
		   while (pch <= (char *)plast) {
		     if (strncmp(pch, "\r\n\r\n", 4) == 0) {
		       pch += 4;
		       state = ContentStateStartHttp;
		       break;
		     } else {
		       pch++;
		     }
		   } 
		   
		   if (state == ContentStateStartHttp) {
		     pget->content_length = pch - start;
		   } else {
		     /* calculate the content length */
		     pget->content_length = plast - start + 1;
		     pch = plast + 1;
		   }
		  }
		 
		  /* Set next state and do original tcptrace
		   * processing based on what we learned above.
		   */
		  state = ContentStateStartHttp;
		  last_position = pch - pdata + 1;
		  
		  /* when was the first byte sent? */
		  pget->send_time = WhenSent(&ph->data_head,&ph->data_tail,position);
		  
		  /* when was the LAST byte sent? */
		  pget->lastbyte_time = WhenSent(&ph->data_head,&ph->data_tail,last_position);
		  
		  /* when was the last byte ACKed? */
		  if (debug > 4)
		    printf("Content length: %d\n", pget->content_length);
		  pget->ack_time = WhenAcked(&ph->ack_head,&ph->ack_tail,last_position);

		  /* increment our counts */
		  ph->total_reply_count++;

		  /* skip to the next request */
		  pget = pget->next;

		  if (!pget) {
		     /* no more questions, quit */
		     done = 1;
		     break;
		  }
	       }
	       else {
		  pch++;
	       }
	    }
	      break;
	      
	   }
       }
       
       MFUnMap(mf,pdata);
    }
   else {
      if (debug > 4) {
	 printf("FindContent() with null server contents");
      }
   }
   
}

static char * formatGetString(char * s) 
{
  int len = strlen(s);
  int i = 0;
  int j = 0;
  char *buf = (char *)malloc(len);
  char ascii[2];
  while (i < len) {
    if (s[i] == '%') {
      ascii[0] = s[i+1];
      ascii[1] = s[i+2];
      buf[j++] = atoi(ascii);
      i = i+3;
    } else {
      buf[j++] = s[i];
      i++;
    }
  }
  buf[j] = 0;
  return buf;
}

static void
FindGets(
    struct http_info *ph)
{
    tcb *tcb = ph->tcb_client;
    MFILE *mf = tcb->extr_contents_file;
    char *pdata;
    char *plast;
    char *pch;
    char *pch2;
    struct get_info *pget = NULL;
    char getbuf[1024];
    u_long position = 0;
    int j;
    int methodlen;
    unsigned long long contentLength = 0;

     typedef enum {
       GetStateStartMethod,
       GetStateFinishMethod,
       GetStateFindContentLength,
       GetStateFinishHeader
     } StateType;
     StateType state;

   if (mf) {

      /* Memory map the entire file (I hope it's short!) */
      MFMap(mf,&pdata,&plast);

      if (pdata == NULL) {
	return;
      }
	
      ph->total_request_length = (unsigned) (plast - pdata);
      ph->total_request_count = 0;
      
      state = GetStateStartMethod;
      
      /* search for method string*/
      pch = pdata;
      while (pch <= (char *)plast) {
	  switch (state) {
	     
	  /* Start state: Find access method keyword */
	  case (GetStateStartMethod): {
	     
	  /* Try to find a word describing a method.  These
	   * are all the methods defined in
           * draft-ietf-http-v11-spec-rev-06
	   */
	     MethodCode method = MethodCodeUnknown;
	     methodlen = 0;
	     if (strncasecmp(pch, "options ", 8) == 0) {
		methodlen = 8;
		method = MethodCodeOptions;
	     }
	     else if (strncasecmp(pch, "get ", 4) == 0) {
		methodlen = 4;
		method = MethodCodeGet;
	     }
	     else if (strncasecmp(pch, "head ", 5) == 0) {
		methodlen = 5;
		method = MethodCodeHead;
	     }
	     else if (strncasecmp(pch, "post ", 5) == 0) {
		methodlen = 5;
		method = MethodCodePost;
	     }
	     else if (strncasecmp(pch, "put ", 4) == 0) {
		methodlen = 4;
		method = MethodCodePut;
	     }
	     else if (strncasecmp(pch, "delete ", 7) == 0) {
		methodlen = 7;
		method = MethodCodeDelete;
	     }
	     else if (strncasecmp(pch, "trace ", 6) == 0) {
		methodlen = 6;
		method = MethodCodeTrace;
	     }
	     
	     if (methodlen > 0) {
		/* make a new record for this entry */
		pget = MakeGetRec(ph);
		
		/* remember where it started */
		position = pch - pdata + 1;
		pget->request_position = position;
		pget->reply_position = 0;
		pget->method = method;
		
		contentLength = 0;
		pch += methodlen;
		state = GetStateFinishMethod;
	     }
	     else {
		/* Couldn't find a valid method, so increment */
		/* and attempt to resynchronize.  This shouldn't */
		/* happen often. */
		pch++;
	     }
	     
	  };
	    break;
	    
	  case (GetStateFinishMethod): {
	     /* grab the GET string */
	     for (j=0,pch2 = pch; ; ++j,++pch2) {
		if ((*pch2 == '\n') || (*pch2 == '\r') ||
		    (j >= sizeof(getbuf)-1)) {
		   getbuf[j] = '\00';
		   pch = pch2;  /* skip forward */
		   state = GetStateFindContentLength;
		   break;
		}
		getbuf[j] = *pch2;
	     }
	     pget->get_string = formatGetString(getbuf);
	     
	     /* grab the time stamps */
	     pget->get_time =
	       WhenSent(&ph->get_head,&ph->get_tail,position);
	     ph->total_request_count++;
	     
	  }
	     break;
	     
	  /* Locate content-length field, if any */
	   case (GetStateFindContentLength): {
	      
	      if (strncasecmp(pch, "\r\nContent-Length:", 17) == 0) {
		 /* Get content-length field */
		 contentLength = atoi(&pch[17]);
		 pch += 17;
	      }
	      else if (strncmp(pch, "\r\n\r\n", 4) == 0) {
		 /* No content-length header detected, assume */
		 /* zero.  Fall through (effective). */
		 /* contentLength = 0; */
		 state = GetStateFinishHeader;
	      }
	      else {
		 pch++;
	      }
	   }
	     break;
	     
	  case (GetStateFinishHeader): {
	     if (strncmp(pch, "\r\n\r\n", 4) == 0) {
		
		/* Found end of header */
		pch += 4;
		
		/* Find end of response body. */
		if (contentLength > 0) {
		   pch += contentLength;
		}
		else {
		   /* XXX What if a POST with no content-length? */
		}
		
		state = GetStateStartMethod;
	     }
	     else {
		pch++;
	     }
	  }
	     break;
	  }
      }
            
      MFUnMap(mf,pdata);
   }
   
   else {
      if (debug > 4) {
	 printf("FindGets() with null client contents");
      }
   }
}


static void
HttpDoPlot()
{
    struct http_info *ph;
    struct get_info *pget;
    int y_axis = 1000;
    int ix_color = 0;
    char buf[100];
    struct time_stamp *pts;

    /* sort by increasing order of TCP connection startup */
    /* (makes the graphs look better) */

    for (ph=httphead; ph; ph=ph->next) {
	PLOTTER p = ph->pclient->plotter;
	tcp_pair *ptp = ph->ptp;
	tcb a2b, b2a;

	if (ptp == NULL)
	    continue;

	a2b = ptp->a2b;
	b2a = ptp->b2a;

	ix_color = (ix_color + 1) % NCOLORS;

	/* find the plotter for this client */
	if (p==NO_PLOTTER) {
	    char title[256];
	    snprintf(title, sizeof(title), "Client %s HTTP trace\n", ph->pclient->clientname); 
	    p = ph->pclient->plotter =
		new_plotter(&ptp->a2b,
			    ph->pclient->clientname,	/* file name prefix */
			    title,			/* plot title */
			    "time", 			/* X axis */
			    "URL",			/* Y axis */
			    "_http.xpl");		/* file suffix */
	}

	y_axis += 2;

	/* plot the TCP connection lifetime */
	plotter_perm_color(p,ColorNames[ix_color]);
	plotter_larrow(p, ph->ptp->first_time, y_axis);
	plotter_rarrow(p, ph->ptp->last_time, y_axis);
	plotter_line(p,
		     ph->ptp->first_time, y_axis,
		     ph->ptp->last_time, y_axis);

	/* label the connection */
	plotter_text(p,ph->ptp->first_time,y_axis,"b",
		     (snprintf(buf, sizeof(buf), "%s ==> %s",
			      ph->ptp->a_endpoint, ph->ptp->b_endpoint), buf));

	/* mark the data packets */
	for (pts=ph->data_head.next; pts->next; pts=pts->next) {
	    plotter_tick(p,pts->thetime,y_axis,'d');
	}
		     

	/* plot the SYN's */
	if (!ZERO_TIME(&ph->c_syn_time)) {
	    plotter_tick(p,ph->c_syn_time,y_axis,'u');
	    plotter_text(p,ph->c_syn_time,y_axis,"a","Clnt SYN");
	}
	if (!ZERO_TIME(&ph->s_syn_time)) {
	    plotter_tick(p,ph->s_syn_time,y_axis,'u');
	    plotter_text(p,ph->s_syn_time,y_axis,"a","Serv SYN");
	}

	/* plot the FINs */
	if (!ZERO_TIME(&ph->c_fin_time)) {
	    plotter_tick(p,ph->c_fin_time,y_axis,'u');
	    plotter_text(p,ph->c_fin_time,y_axis,"a","Clnt FIN");
	}
	if (!ZERO_TIME(&ph->s_fin_time)) {
	    plotter_tick(p,ph->s_fin_time,y_axis,'u');
	    plotter_text(p,ph->s_fin_time,y_axis,"a","Serv FIN");
	}

	y_axis += 4;

	for (pget = ph->gets_head; pget; pget = pget->next) {

	    if (ZERO_TIME(&pget->send_time) ||
		ZERO_TIME(&pget->get_time) ||
		ZERO_TIME(&pget->ack_time))
		continue;
	    
	    plotter_temp_color(p,"white");
	    plotter_text(p, pget->get_time, y_axis, "l", pget->get_string);

	    plotter_diamond(p, pget->get_time, y_axis);
	    plotter_larrow(p, pget->send_time, y_axis);
	    plotter_rarrow(p, pget->lastbyte_time, y_axis);
	    plotter_line(p,
			 pget->send_time, y_axis,
			 pget->lastbyte_time, y_axis);
	    plotter_temp_color(p,"white");
	    plotter_text(p, pget->lastbyte_time, y_axis, "r",
			 (snprintf(buf, sizeof(buf), "%d",pget->content_length),buf));
	    plotter_diamond(p, pget->ack_time, y_axis);
#ifdef CLUTTERED
	    plotter_temp_color(p,"white");
	    plotter_text(p, pget->ack_time, y_axis, "b", "ACK");
#endif  /* CLUTTERED */

	    y_axis += 2;

	}

    }
}


static void
HttpPrintone(
    MFILE *pmf,
    struct http_info *ph)
{
    tcp_pair *ptp = ph->ptp;
    tcb *pab = &ptp->a2b;
    tcb *pba = &ptp->b2a;
    struct get_info *pget = NULL;
    u_long missing;
    double etime;

    if (!ptp)
	return;
	
    printf("%s ==> %s (%s2%s)\n",
	   ptp->a_endpoint, ptp->b_endpoint,
	   ptp->a2b.host_letter, ptp->b2a.host_letter);

    printf("  Server Syn Time:      %s (%.3f)\n",
	   ts2ascii(&ph->s_syn_time),
	   ts2d(&ph->s_syn_time));
    printf("  Client Syn Time:      %s (%.3f)\n",
	   ts2ascii(&ph->c_syn_time),
	   ts2d(&ph->c_syn_time));
  
   /* From the patch by Yufei Wang
    * Print "Server Rst Time" if the last segment we saw was a RST
    *       "Server Fin Time" if we saw a FIN
    *       "Server Last Time" if neither FIN/RST was received to indicate
    *                          the time the last segment was seen from the
    *                          server. */

   if ((pba->fin_count == 0) && (pba->reset_count > 0)) {
	printf("  Server Rst Time:      %s (%.3f)\n",
		ts2ascii(&pba->last_time),
		ts2d(&pba->last_time));
    } else if (pba->fin_count == 0) {
	printf("  Server Last Time:      %s (%.3f)\n",
	       ts2ascii(&pba->last_time),
	       ts2d(&pba->last_time));
    } else {
    	printf("  Server Fin Time:      %s (%.3f)\n",
	   	ts2ascii(&ph->s_fin_time),
	   	ts2d(&ph->s_fin_time));
    }
    /* Similarly information is printed out for the Client end-point.*/	
    if ((pab->fin_count == 0) && (pab->reset_count > 0)) {
	printf("  Client Rst Time:      %s (%.3f)\n",
		ts2ascii(&pab->last_time),
		ts2d(&pab->last_time));
    } else if (pab->fin_count == 0) {
	printf("  Client Last Time:      %s (%.3f)\n",
		ts2ascii(&pab->last_time),
		ts2d(&pab->last_time));
    } else {
    	printf("  Client Fin Time:      %s (%.3f)\n",
	   	ts2ascii(&ph->c_fin_time),
	   	ts2d(&ph->c_fin_time));
    }

#ifdef HTTP_SAFE
    /* check the SYNs */
    if ((pab->syn_count == 0) || (pba->syn_count == 0)) {
	printf("\
No additional information available, beginning of \
connection (SYNs) were not found in trace file.\n");
	return;      
    }

    /* check if we had RSTs (Patch from Yufei Wang)*/
    if ((pab->reset_count > 0) || (pba->reset_count > 0)) {
      /* Do nothing */
    }
      /* check the FINs */
      /* Note from Yufei Wang : If we see a FIN from only one direction,
       * we shall not panic, but print out information that we have under the
       * assumption that either a RST was sent in the missing direction or the
       * FIN segment was lost from packet capture. The information we have
       * is still worth printing out. Hence we have a "&&" instead of a "||"
       * in the following condition. */
      else if ((pab->fin_count == 0) && (pba->fin_count == 0)) {
	printf("\
No additional information available, end of \
connection (FINs) were not found in trace file.\n");
	return;
    }
#endif /* HTTP_SAFE */

    /* see if we got all the bytes */
    missing = pab->trunc_bytes + pba->trunc_bytes;

    /* Patch from Yufei Wang :
     * Adding in a check to see if the connection were closed with RST/FIN 
     * and calculating the "missing" field appropriately
     */
  
    if (pab->fin_count > 0)
      missing += ( (pab->fin - pab->syn -1)- pab->unique_bytes);
    else if (pab->reset_count > 0) {
      /* Check to make sure if no segments were observed between SYN and RST
       * The following check does not work if file is huge and seq space rolled
       * over - To be fixed - Mani */
      if (pab->latest_seq != pab->syn)
	missing += ( (pab->latest_seq - pab->syn -1) - pab->unique_bytes);
    }
  
    if (pba->fin_count > 0)
      missing += ( (pba->fin - pba->syn -1)- pba->unique_bytes);
    else if (pba->reset_count > 0) {
      /* Check to make sure if no segments were observed between SYN and RST
       * The following check does not work if file is huge and seq space rolled
       * over - To be fixed - Mani */
      if (pba->latest_seq != pba->syn)
	missing += ( (pba->latest_seq - pba->syn -1) - pba->unique_bytes);
    }
  
    if (missing != 0)
	printf("WARNING!!!!  Information may be invalid, %ld bytes were not captured\n",
	       missing);

#ifdef HTTP_DUMP_TIMES

     Mfprintf(pmf, "conn %s %s %s2%s %u %u %u %u\n",
            ptp->a_endpoint,
            ptp->b_endpoint,
            ptp->a2b.host_letter, ptp->b2a.host_letter,
            ph->total_request_length,
            ph->total_request_count,
            ph->total_reply_length,
            ph->total_reply_count);

#endif /* HTTP_DUMP_TIMES */
   
   for (pget = ph->gets_head; pget; pget = pget->next) {
      
      unsigned request_length = 0;
      unsigned reply_length = 0;
      
      /* Compute request lengths */
      if (pget->next) {
	 
	 /* Retrieval following ours, use its position to compute our length */
	 request_length = pget->next->request_position - pget->request_position;
      }
      else {
	 
	 /* Last one in this file, so use the EOF as a delimiter */
	 request_length = ph->total_request_length - pget->request_position;
      }
      
      /* Compute reply lengths */
      if (pget->reply_position == 0) {
	 /* No reply, so length is 0 by definition */
	 request_length = 0;
      }
      else {
	 if ((pget->next) && (pget->next->reply_position > 0)) {
	    /* Retrieval following ours with valid position, so use that to compute length */
	    reply_length = pget->next->reply_position - pget->reply_position;
	 }
	 else {
	    /* No record following ours, or it didn't have a valid position, so use EOF as delimiter */
	    reply_length = ph->total_reply_length - pget->reply_position;
	 }
      }
      
      printf("    %s %s\n", MethodCodeString[pget->method],
	     pget->get_string);
      
      /* Interpretation of response codes as per RFC 2616 - HTTP/1.1 */
      switch (pget->response_code) {
	 /* Informational 1xx */
       case 100 :
	 printf("\tResponse Code:       %d (Continue)\n", pget->response_code);
	 break;
       case 101 :
	 printf("\tResponse Code:       %d (Switching Protocols)\n", pget->response_code);
	 break;	 
	 
	 /* Successful 2xx */
       case 200 :
	 printf("\tResponse Code:       %d (OK)\n", pget->response_code);
	 break;	 
       case 201 :
	 printf("\tResponse Code:       %d (Created)\n", pget->response_code);
	 break;	 
       case 202 :
	 printf("\tResponse Code:       %d (Accepted)\n", pget->response_code);
	 break;	 
       case 203 :
	 printf("\tResponse Code:       %d (Non-Authoritative Information)\n", pget->response_code);
	 break;	 
       case 204 :
	 printf("\tResponse Code:       %d (No Content)\n", pget->response_code);
	 break;	 
       case 205 :
	 printf("\tResponse Code:       %d (Reset Content)\n", pget->response_code);
	 break;	 
       case 206 :
	 printf("\tResponse Code:       %d (Partial Content)\n", pget->response_code);
	 break;
	 
	 /* Redirection 3xx */
       case 300 :
	 printf("\tResponse Code:       %d (Multiple Choices)\n", pget->response_code);
	 break;	 
       case 301 :
	 printf("\tResponse Code:       %d (Moved Permanently)\n", pget->response_code);
	 break;	 
       case 302 :
	 printf("\tResponse Code:       %d (Found)\n", pget->response_code);
	 break;	 
       case 303 :
	 printf("\tResponse Code:       %d (See Other)\n", pget->response_code);
	 break;	 
       case 304 :
	 printf("\tResponse Code:       %d (Not Modified)\n", pget->response_code);
	 break;	 
       case 305 :
	 printf("\tResponse Code:       %d (Use Proxy)\n", pget->response_code);
	 break;	 
       case 306 :
	 printf("\tResponse Code:       %d (Unused)\n", pget->response_code);
	 break;
       case 307 :
	 printf("\tResponse Code:       %d (Temporary Redirect)\n", pget->response_code);
	 break;	 

	 /* Client Error 4xx */
       case 400 :
	 printf("\tResponse Code:       %d (Bad Request)\n", pget->response_code);
	 break;	 
       case 401 :
	 printf("\tResponse Code:       %d (Unauthorized)\n", pget->response_code);
	 break;	 
       case 402 :
	 printf("\tResponse Code:       %d (Payment Required)\n", pget->response_code);
	 break;	 
       case 403 :
	 printf("\tResponse Code:       %d (Forbidden)\n", pget->response_code);
	 break;	 
       case 404 :
	 printf("\tResponse Code:       %d (Not Found)\n", pget->response_code);
	 break;	 
       case 405 :
	 printf("\tResponse Code:       %d (Method Not Allowed)\n", pget->response_code);
	 break;	 
       case 406 :
	 printf("\tResponse Code:       %d (Not Acceptable)\n", pget->response_code);
	 break;	 
       case 407 :
	 printf("\tResponse Code:       %d (Proxy Authentication Required)\n", pget->response_code);
	 break;	 
       case 408 :
	 printf("\tResponse Code:       %d (Request Timeout)\n", pget->response_code);
	 break;	 
       case 409 :
	 printf("\tResponse Code:       %d (Conflict)\n", pget->response_code);
	 break;	 
       case 410 :
	 printf("\tResponse Code:       %d (Gone)\n", pget->response_code);
	 break;	 
       case 411 :
	 printf("\tResponse Code:       %d (Length Required)\n", pget->response_code);
	 break;	 
       case 412 :
	 printf("\tResponse Code:       %d (Precondition Failed)\n", pget->response_code);
	 break;	 
       case 413 :
	 printf("\tResponse Code:       %d (Request Entity Too Large)\n", pget->response_code);
	 break;	 
       case 414 :
	 printf("\tResponse Code:       %d (Request-URI Too Long)\n", pget->response_code);
	 break;	 
       case 415 :
	 printf("\tResponse Code:       %d (Unsupported Media Type)\n", pget->response_code);
	 break;	 
       case 416 :
	 printf("\tResponse Code:       %d (Requested Range Not Satisfiable)\n", pget->response_code);
	 break;	 
       case 417:
	 printf("\tResponse Code:       %d (Expectation Failed)\n", pget->response_code);
	 break;
	 
	 /* Server Error 5xx */
       case 500 :
	 printf("\tResponse Code:       %d (Internal Server Error)\n", pget->response_code);
	 break;	 
       case 501 :
	 printf("\tResponse Code:       %d (Not Implemented)\n", pget->response_code);
	 break;	 
       case 502 :
	 printf("\tResponse Code:       %d (Bad Gateway)\n", pget->response_code);
	 break;	 
       case 503 :
	 printf("\tResponse Code:       %d (Service Unavailable)\n", pget->response_code);
	 break;	 
       case 504 :
	 printf("\tResponse Code:       %d (Gateway Timeout)\n", pget->response_code);
	 break;	 
       case 505 :
	 printf("\tResponse Code:       %d (HTTP Version Not Supported)\n", pget->response_code);
	 break;	 
	   
       default :
	 printf("\tResponse Code:       %d (unknown response code)\n", pget->response_code);

      }
      
      printf("\tRequest Length:      %u\n", request_length);
      printf("\tReply Length:        %u\n", reply_length);
      printf("\tContent Length:      %d\n", pget->content_length);
      printf("\tContent Type  :      %s\n", pget->content_type);
      printf("\tTime request sent:   %s (%.3f)\n",
	     ts2ascii(&pget->get_time), ts2d(&pget->get_time));
      printf("\tTime reply started:  %s (%.3f)\n",
	     ts2ascii(&pget->send_time), ts2d(&pget->send_time));
      printf("\tTime reply ACKed:    %s (%.3f)\n",
	     ts2ascii(&pget->ack_time), ts2d(&pget->ack_time));
      
      /* elapsed time, request started to answer started */
      etime = elapsed(pget->get_time,pget->send_time);
      etime /= 1000;  /* us to msecs */
      printf("\tElapsed time:  %.0f ms (request to first byte sent)\n", etime);
      
      /* elapsed time, request started to answer ACKed */
      etime = elapsed(pget->get_time,pget->ack_time);
      etime /= 1000;  /* us to msecs */
      printf("\tElapsed time:  %.0f ms (request to content ACKed)\n", etime);
      
#ifdef HTTP_DUMP_TIMES
      Mfprintf(pmf,"reqrep %s %s %s2%s %.3f %.3f %.3f %u %u %3d %s %s %s\n",
	       ptp->a_endpoint,
	       ptp->b_endpoint,
	       ptp->a2b.host_letter, ptp->b2a.host_letter,
	       ts2d(&pget->get_time),
	       ts2d(&pget->send_time),
	       ts2d(&pget->ack_time),
	       request_length,
	       reply_length,
	       pget->response_code,
	       MethodCodeString[pget->method],
	       pget->get_string,
	       pget->content_type);
#endif /* HTTP_DUMP_TIMES */
      
   }
   
}

void
http_done(void)
{
    MFILE *pmf = NULL;
    struct http_info *ph;

    /* just return if we didn't grab anything */
    if (!httphead)
	return;

    /* gather up the information */
    HttpGather(httphead);

#ifdef HTTP_DUMP_TIMES
    pmf = Mfopen("http.times","w");
#endif /* HTTP_DUMP_TIMES */

    printf("Http module output:\n");

    for (ph=httphead; ph; ph=ph->next) {
	HttpPrintone(pmf,ph);
    }

    HttpDoPlot();

#ifdef HTTP_DUMP_TIMES
    Mfclose(pmf);
#endif /* HTTP_DUMP_TIMES */
}


void
http_usage(void)
{
    printf("\t-xHTTP[P]\tprint info about http traffic (on port P, default %d)\n",
	   DEFAULT_SERVER_PORT);
}



void
http_newfile(
    char *newfile,
    u_long filesize,
    Bool fcompressed)
{
    /* just an example, really */
}



void *
http_newconn(
    tcp_pair *ptp)
{
    struct http_info *ph;

    ph = MakeHttpRec();

    /* attach tcptrace's info */
    ph->ptp = ptp;
 
    /* determine the server and client tcb's */
    if (ptp->addr_pair.a_port == httpd_port) {
	ph->tcb_client = &ptp->b2a;
	ph->tcb_server = &ptp->a2b;
    } else {
	ph->tcb_client = &ptp->a2b;
	ph->tcb_server = &ptp->b2a;
    }
 
    /* attach the client info */
    ph->pclient = FindClient(HostName(ptp->addr_pair.a_address));

    return(ph);
}
#endif /* LOAD_MODULE_HTTP */


syntax highlighted by Code2HTML, v. 0.9.1