
/*  -*- Mode: C;  -*- */

/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe 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.                                       *
*                                                                             *
*   Nprobe 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 Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#ifdef __alpha__
#include <sys/mbuf.h>
#endif
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#undef __STDC__
#include <netinet/ip.h>

#ifdef __alpha__
#include <netinet/ip_var.h>
#endif
#include <netinet/tcp.h>
#include <netinet/if_ether.h>

#include <fcntl.h>


#include <assert.h>

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "http.h"
#include "tcp.h"
#include "seq.h"
#include "if_nprobe.h"
#include "content_t.h"
#include "output.h"
#include "interesting.h"

#ifdef PRINT_OUT
#include "print_util.h"
#endif

/* TODO - get rid of strlen()s */
int 
get_req_method(prec_t *pp, tcp_conn_t *tconnp)
{
  int len;
  
  switch (*(pp->buf))
    {
    case 'G':
      len = strlen("GET ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "GET ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_GET;
	}
      break;
    case 'H':
      len = strlen("HEAD ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "HEAD ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_HEAD;
	}
      break;
    case 'P':
      if (*(pp->buf+1) =='O')
	{
	  len = strlen("POST ");
	  if (pp->len < len)
	    return HTTP_CLI_ERR_TRUNCHDR;
	  if(!memcmp(pp->buf, "POST ", len))
	    {
	      ADJUST(pp, len);
	      return HTTP_METHOD_POST;
	    }
	}
      else if (*(pp->buf+1) =='U')
	{
	  len = strlen("PUT ");
	  if (pp->len < len)
	    return HTTP_CLI_ERR_TRUNCHDR;
	  if(!memcmp(pp->buf, "PUT ", len))
	    {
	      ADJUST(pp, len);
	      return HTTP_METHOD_PUT;
	    }
	}
      else if (*(pp->buf+1) =='R')
	{
	  if (*(pp->buf+4) =='F')
	    {
	      len = strlen("PROPFIND ");
	      if (pp->len < len)
		return HTTP_CLI_ERR_TRUNCHDR;
	      if(!memcmp(pp->buf, "PROPFIND ", len))
		{
		  ADJUST(pp, len);
		  return HTTP_METHOD_PROPFIND;
		}
	    }
	  else if (*(pp->buf+4) =='P')
	    {
	      len = strlen("PROPPATCH ");
	      if (pp->len < len)
		return HTTP_CLI_ERR_TRUNCHDR;
	      if(!memcmp(pp->buf, "PROPPATCH ", len))
		{
		  ADJUST(pp, len);
		  return HTTP_METHOD_PROPPATCH;
		}
	    }
	}
      break;
    case 'O':
      len = strlen("OPTIONS ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "OPTIONS ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_OPTIONS;
	}
      break;
    case 'D':
      len = strlen("DELETE ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "DELETE ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_DELETE;
	}
      break;
    case 'T':
      len = strlen("TRACE ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "TRACE ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_TRACE;
	}
      break;
    case 'C':
      len = strlen("CONNECT ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "CONNECT ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_CONNECT;
	}
      break;
    case 'M':
      len = strlen("MOVE ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "MOVE ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_MOVE;
	}
      break;
    case 'B':
      len = strlen("BMOVE ");
      if (pp->len < len)
	return HTTP_CLI_ERR_TRUNCHDR;
      if(!memcmp(pp->buf, "BMOVE ", len))
	{
	  ADJUST(pp, len);
	  return HTTP_METHOD_BMOVE;
	}
      break;
    default:
      return HTTP_CLI_ERR_REQHDR_NOMETHOD;
      break;
    }

  
  return HTTP_CLI_ERR_REQHDR_NOMETHOD;
}


int 
get_referer(prec_t *pp, tcp_conn_t *tconnp, int way, char *dummy, unsigned char *anotherdummy)
{
  unsigned char *cp;
  unsigned char *start;
  unsigned char *top = HTTP_REFSTR;
  int len;

  CLEAR_SPACE(pp, way);
  cp = pp->buf;
  start = cp;
  len = MIN(HTTP_REQSTR_LEN-1, pp->len);

  while (*cp != '\r' && len > 0)
    {
      *(top++) = *(cp++);
      len --;
    }
  // clear any trailing spaces
    len = top - HTTP_REFSTR;
  while (len && *(top -1) == ' ')
    {
	top--;
	len--;
      }
  HTTP_REFSTR[len] = '\0';
  /* Trim any URL query or parameter delimiter */
  len = strcspn(HTTP_REFSTR, "?; ");
  
  HTTP_REFSTRLEN = len;
  if (cp - start >= pp->len)
    {
      /* TODO - this may just be a packet boundary */
      return HTTP_CLI_ERR_TRUNCHDR;
    }
  else
    {
      ADJUST(pp, cp - start);
      CLEAR_LINE_RET(pp, way);
    }
 
  return 0;
}

int 
get_te_perm(prec_t *pp, tcp_conn_t *tconnp, int way, char *dummy, unsigned char *anotherdummy)    
{

  /* XXX TODO - check for te types other than trailers */
  CLEAR_COMMA(pp);
  CLEAR_SPACE(pp, way);

  if (*(pp->buf) == '\r')
    {
      /* empty - just denotes chunking ok - ie default */
      CLEAR_LINE_RET(pp, way);
      return 0;
    }

  /* "trailers" present? */
  if (seqstr_dl(pp->buf, "trailers", '\n', pp->len))
    {
      BUMP_CTR(http_trans_trailer_perm);
      INTERESTING_NL(&tconnp->flow_common.inner, way, pp, "#%d Trailer perms: ", tconnp->hdrs.conn_id);
    }

  CLEAR_LINE_RET(pp, way);
  return 0;

}

int 
parse_req_body(prec_t *pp, tcp_conn_t *tconnp, int len)
{
  ADJUST(pp, len);
  HTTP_REQ_RECD_LEN += len;

  /*
   * Some buggy HTTP1.0 clients send an additional CR/LF after a POST body 
   * - check this out 
   */

  if (HTTP_METH == HTTP_METHOD_POST
      && pp->len >= 2 
      && pp->buf[0] == '\r' 
      && pp->buf[1] == '\n')
    ADJUST(pp, 2*sizeof(char));

  return 0;
}


#if 0
/* TODO - make de-chunk able to span packets */

int 
client_de_chunk(prec_t *pp, tcp_conn_t *tconnp)
{
  unsigned int *chunk_left = &tconnp->su.http.reptrans->inner.c.chunk_left;

  /* any others mandatory no body? */
  if (HTTP_METH == HTTP_METHOD_GET 
      || HTTP_METH == HTTP_METHOD_HEAD
      || HTTP_METH == HTTP_METHOD_OPTIONS
      || HTTP_METH == HTTP_METHOD_DELETE)
    return HTTP_CLI_ILLICIT_BODY;
  
  while (pp->len)
    {
      
      if (*chunk_left)
	{
	  unsigned int chunk_to_do = MIN(pp->len, *chunk_left);
	  parse_req_body(pp, tconnp, chunk_to_do);
	  *chunk_left -= chunk_to_do;
	  if (!*chunk_left)
	    {
	      /* if chunk finished clear delimiter */
	      if (pp->len < 2 || pp->buf[0] != '\r' || pp->buf[1] != '\n')
		return HTTP_CLI_ERR_CHUNK;
	      ADJUST(pp, 2*sizeof(char));
	    }
	  continue;
	}
      else
	{
	  int status;
	  /* get chunk size */
	  if((status = get_chunk_len(pp, chunk_left, HTTP_CLI_ERR_CHUNKLEN)) < 0)
	    return status;
	  
	  if (HTTP_REQ_BODY_LEN == HTTP_BODY_LEN_UNKNOWN)
	    HTTP_REQ_BODY_LEN = 0;
	  HTTP_REQ_BODY_LEN += *chunk_left;
	  
	  if (*chunk_left)
	    {
	      /* a chunk still to do */
	      /* clear any EXT and delimiter */
	      while (*(pp->buf) != '\r' && pp->len)
		ADJUST(pp, sizeof(char));
	      if (pp->len < 2 || pp->buf[0] != '\r' || pp->buf[1] != '\n')
		return HTTP_CLI_ERR_CHUNK;
	      ADJUST(pp, 2*sizeof(char));
	      continue;
	    }
	  else
	    {
	      /* finished - clear delimiters and any footer */ 
	      unsigned char *tmpp;
	      char delim[] = {'\r', '\n', '\r', '\n'};

	      trans_complete(tconnp, CLIENT);

	      tmpp = seqstr_l(pp->buf, delim, pp->len);
	      if (tmpp)
		{
		  int adj = (tmpp - pp->buf) + 4*sizeof(char);
		  ADJUST(pp, adj);
		}
	      else
		{
		  return HTTP_CLI_ERR_CHUNK;
		}
	      break;
	    }
	}
    }
  
  return 0;
}
#endif

int 
parse_req_hdr(prec_t *pp, tcp_conn_t *tconnp, int recursing)
{
  int method, version;
  int status;
  unsigned char *tmpp;

  char *start = pp->buf;
  int len = pp->len;

  if (!recursing)
    {
      if (HTTP_STATUS & HTTP_IN_REQ_HDR)
	{
	  /* try appending this packet and re-parsing */
	  http_hdr_parse_state_t *ps = 
	    &tconnp->su.http.reqtrans->inner.c.http_p_state;
	  if ((status = save_interrupted_hdr(start, len, ps, CLIENT)))
	    return status;
	  /* parse starts again from scratch */
	  HTTP_REQSTR[0] = '\0';
	  HTTP_REQSTRLEN = 0;
	  HTTP_REFSTR[0] = '\0';
	  HTTP_REFSTRLEN = 0;
	  status = parse_req_hdr(&ps->buf, tconnp, RECURSING);
	  if (status)
	    {
	      if (status == HTTP_CLI_ERR_TRUNCHDR)
		{
		  pp->len = 0;
		  return 0;
		}
	      else
		{
		  return status;
		}
	    }
	  else
	    {
	      /* parsed OK - adjust pkt buffer for any remaining body */
	      pp->buf = (pp->buf + pp->len) - ps->buf.len;
	      pp->len = ps->buf.len;
	      BUMP_CTR(frag_hdr_trans_ok);
	      return 0;
	    }
	      
	}
	  
      
      HTTP_STATUS |= HTTP_IN_REQ_HDR;
      if (HTTP_STATUS & HTTP_SEEN_REQ_HDR)
	{
	  /* another request - confirm is persistant connection */
	  if (IS_NON_PERSISTENT_CONN)
	    return HTTP_CLI_ERR_PERSISTANCE;
	  HTTP_STATUS |= (HTTP_KEEP_ALIVE | HTTP_WAS_PERSISTENT);
	}
    }

 

  /* first deal with request line - same start for 1.0/1.1/simple requests */
  method = get_req_method(pp, tconnp);
  if (method < 0)
    {
      if (method == HTTP_CLI_ERR_TRUNCHDR)
	goto interrupted;
      else
	return method;
    }
  else
    {
      HTTP_METH = method;
    }

  CLEAR_SPACE(pp, CLIENT);

  /* get URI and HTTP version */
  /* tmpp = seqchr(pp->buf, 'H', pp->len); */
  tmpp = seqstr_l(pp->buf, "HTTP", pp->len);
  if (tmpp)			/* full request */
    {
      unsigned char *cp = tmpp;  /* XXX IAP +1 fix */
      int n;
      
      /* step back over any spaces */
      while (*(cp-1) == ' ')
	cp--;
      
      n = MIN(cp - pp->buf, HTTP_REQSTR_LEN); 
      if(n<0)n=0;
      memcpy(HTTP_REQSTR, pp->buf, n);
      HTTP_REQSTR[n] = '\0';
#if 0
      /* trim URL of any query or parameter delimiter */
      n = strcspn(HTTP_REQSTR, "?; ");
      HTTP_REQSTR[n] = '\0';
#endif
      HTTP_REQSTRLEN = n;
      cp =  pp->buf;
      ADJUST(pp, tmpp - cp);
      version = get_http_version(pp, CLIENT);
      if (version < 0)
	{
	  if (version == HTTP_CLI_ERR_TRUNCHDR)
	    goto interrupted;
	  else
	    return version;
	}
      else
	{
	  HTTP_VERSIONS |= version;
	}

      if (version == HTTP_VERS_1_1)
	HTTP_STATUS |= HTTP_CLI_KEEP_ALIVE;
      else
	HTTP_STATUS &= (~HTTP_CLI_KEEP_ALIVE);

      /* TMP - look for non-standard line delims */
      if (*(pp->buf) != '\r' || *(pp->buf + 1) != '\n')
	INTERESTING(&tconnp->flow_common.inner, CLIENT, "#%d HTTP hdr line delim %d %d", tconnp->hdrs.conn_id, (int)*(pp->buf), (int)*(pp->buf+1));

      /* step over CRLF */
      CLEAR_LINE(pp);

      if ((status = parse_http_fields(pp, tconnp, CLIENT)) < 0)
	{
	  if (status == HTTP_CLI_ERR_TRUNCHDR)
	    goto interrupted;
	  else
	    return status;
	}
    }
  else				/* simple request 0.9/1.0 or run off end */
    {
      char delim[] = {'\r', '\n', '\0'};
      tmpp = seqstr_l(pp->buf, delim, pp->len);
      if (tmpp)
	{
	  /* 0.9 request */
	  unsigned char *cp;
	  int n = MIN(tmpp - pp->buf, HTTP_REQSTR_LEN - 1);
	  memcpy(HTTP_REQSTR, pp->buf, n);
	  HTTP_REQSTR[n] = '\0';
	  HTTP_REQSTRLEN = n;
	  HTTP_STATUS |= HTTP_SIMPLE_REQUEST;
	  HTTP_VERSIONS |= HTTP_CLIENT_VERS0_9;
	  HTTP_STATUS |= HTTP_CLOSE;
	  cp = pp->buf;
	  ADJUST(pp, tmpp - cp);
	  CLEAR_LINE(pp);
	}
      else
	{
	  /* probably run off end */
	  goto interrupted;
	}
    }

  /* Should now have parsed header and stepped through packet to delimiter */

  if (pp->len < 1)
    goto interrupted;

  if (pp->buf[0] != '\n')
    return HTTP_CLI_ERR_NO_HDRDELIM;

  ADJUST(pp, sizeof(char));	/* step over final delimiter */

  /* 
   * Some clients add aditional CR or NL - clear them 
   */
  while (pp->len && (*pp->buf == '\r' || *pp->buf == '\n'))
    ADJUST(pp, sizeof(char));

  HTTP_STATUS |= (HTTP_GOT_REQ_HDR | HTTP_SEEN_REQ_HDR);
  HTTP_STATUS &= ~HTTP_IN_REQ_HDR;
  
  if (HTTP_METH == HTTP_METHOD_GET || HTTP_METH == HTTP_METHOD_HEAD)
    {
      /* Mandatory - no body length */
      HTTP_REQ_BODY_LEN = 0;
      HTTP_REQ_TRANS_STATUS &= ~TRANS_INCOMPLETE;
      if ((HTTP_STATUS & HTTP_CLI_CLOSE) && pp->len != 0)
	return HTTP_CLI_ERR_EXBYTES;
    }
  else if ((HTTP_METH == HTTP_METHOD_POST) 
	   && HTTP_REQ_BODY_LEN == HTTP_BODY_LEN_UNKNOWN
	   && ~(HTTP_REQ_TRANS_STATUS & TRANS_CHUNKED))
    {
      return HTTP_CLI_ERR_NO_POST_BODYLEN;
    }

  return 0;

 interrupted:

  if (!recursing)
    {
      HTTP_CLI_STATUS |= TRANS_HDR_FRAG;
      if ((status = 
	   save_interrupted_hdr(start, len, 
				&tconnp->su.http.reqtrans->inner.c.http_p_state, CLIENT)))
	return status;
      pp->len = 0;
      return 0;
    }
  return HTTP_CLI_ERR_TRUNCHDR;


}


/*
 * end http_req.c 
 */
