/*  -*- 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 <unistd.h>
#include <ctype.h>
#include <assert.h>
#include <sys/param.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.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 <limits.h>
#include <linux/limits.h>

#include <sys/stat.h>
#include <sys/fcntl.h>
#include <errno.h>

#include "list.h"
#include "pkt.h"
#include "interface.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "seq.h"
#include "fingerprint.h"
#include "pool.h"
#include "if_nprobe.h"
#include "output.h"
#include "interesting.h"
#include "content_t.h"
#include "sundry_records.h"
#include "probe_config.h"

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

/* 
 * For the moment at least we're really concerned with the server's status 
 * code as it affects our knowlege of any returned entity. 
 * For known non-persistant connections any body length is implicit 
 * (or may be stated), for any others we have to assume persistance, and make 
 * sure that we have a notion of any entity length. If we've seen a length 
 * header line we need do no more - otherwise make a guess based on the code.
 */
unsigned int 
do_status_code(short code, unsigned int conn_status, int bodylen, int plen, unsigned short *content_type)
{
  /* Code 304 responses often quote a body length - although mandatory no body*/
  if (code == 304)
    return 0;
  else if (conn_status & HTTP_CLOSE)
    return HTTP_BODY_LEN_UNKNOWN;
  else if (bodylen != HTTP_BODY_LEN_UNKNOWN)
    return bodylen;

  /* 
   * Some codes forbid an entity body, none make it mandatory, while some 
   * recommend there should be an explanatory or informative entity (often 
   * not followed)  . The tactic is to act on the mandatory requirements, and 
   * for the others to assume that if the server thinks the connection is 
   * persistant and has not provided a length header field it can be inferred 
   * that there is no entity.
   */

  switch (code/100)
    {
    case 1:
      /* Informational - mandatory no entity */
      return 0U; 
      break;	

    case 2:	
      /* Success */
      switch (code%100)
	{
	case 4:
	case 5:
	  /* mandatory no entity */
	  return 0U; 
	  break;	
	case 1:
	case 2:
	case 3:
	  /* possible body */
	  *content_type = CT_TEXT_HTML_ASSUMED;
	  if (conn_status & HTTP_SERV_KEEP_ALIVE)
	    {
	      if (bodylen == HTTP_BODY_LEN_UNKNOWN)
	          return plen;
	      else 
		return bodylen;
	    }
	  break;
	case 0:
	case 6:
	  return bodylen;
	  break;
	default:
	  return bodylen;
	  break;
	}
      break;

    case 3:                      /* Redirection */
    case 4:			/* Client error */
    case 5:			/* Server error */
      
      if (code == 304)
	{
	  /* mandatory no entity */
	  return 0U;
	}
      else 
	{
	  /* possible body */
	  *content_type = CT_TEXT_HTML_ASSUMED;
	  if (conn_status & HTTP_SERV_KEEP_ALIVE)
	    {
	      if (bodylen == HTTP_BODY_LEN_UNKNOWN)
		return plen;
	      else 
		return bodylen;
	    }
	}
      break;

    default:
      return bodylen;
      break;
    }

  /* NOT REACHED */
  return bodylen;
} 

/*
 * Write content of HTTP (re)location field into links buffer 
 */
static jmp_buf no_room;
int 
get_location(prec_t *pp, tcp_conn_t *tconnp, int way, char *dummy, unsigned char *anotherdummy)
{
  int copylen, jmp;
  char c;
  int len = pp->len;
  unsigned char *end = pp->buf + len, *start = pp->buf;
  int found = 0;
  struct links_chars *chars = &OB_LINKS_CHARS;

  while (*start == ' ')
    {
      start++;
      len--;
    }
  if (!len)
    return HTTP_SERV_ERR_TRUNCHDR;
    
  end = start;
  while (len)
    {
      c = *end;
      if (c == '\n' || c == '\r' || c == '?')
	{
	  found++;
	  break;
	}
      end++;
      len--;
    }

  if (!found)
    return HTTP_SERV_ERR_TRUNCHDR;

  if (!(copylen = end-start))
    goto out;

  if (chars->chain == NULL)
    chain_first_links_buf(chars);
 
  jmp = setjmp(no_room);
  if (jmp == HTML_LINKS_BUFS_EXHUSTED)
    {
       HTTP_REP_TRANS_STATUS |= TRANS_LINKS_CHARS_EX;
       goto out;
    }
  else if (jmp != 0)
    {
      /* error */
      goto error;
    }
  
  write_tstamp(chars, (int)(TCP_SSOLID_TM - HTTP_FIRSTREP_SEEN));
  OB_PARSE_STATE &=  ~P_NEED_LINK_TIMESTAMP;
 
  *(chars->buf++) = LR_REDIRECT_INLINE; 
   chars->nchars += 1;

   write_url(chars, start, copylen, no_room);
  
 out:

  ADJ_BUF(pp, end);
  CLEAR_LINE_RET(pp, way);

  return 0;

 error:

  return jmp;
  
}

/*
 * Get refresh header info
 * 
 */
int 
get_refresh(prec_t *pp, tcp_conn_t *tconnp, int way, char *dummy, unsigned char *anotherdummy) 
{

  char *msg;
  
  if (way != SERVER)
    msg = "Refresh";
  else
    msg = "Client refresh";
  

  /* XXX TODO - get actual fields */
  CLEAR_SPACE(pp, way);
  INTERESTING_NL(&tconnp->flow_common.inner, way, pp, "#%u %s: ", tconnp->hdrs.conn_id, msg);

  CLEAR_LINE_RET(pp, way);

  return 0;

}

short 
get_status_code(prec_t *pp)
{
  int i;
  short code = 0;

  /* Expect three ASCII decimal digits of status code */
  if (pp->len < 3 * sizeof(char))
    return HTTP_SERV_ERR_TRUNCHDR;
  for (i = 0; i < 3; i++)
    {
      if (!isdigit(pp->buf[i]))
	return HTTP_SERV_ERR_SCODE;
      code *= 10;
      code += (pp->buf[i] - '0');
    }

  ADJUST(pp, strlen("xxx"));

  if (code < 100 || code > 600)
    return HTTP_SERV_ERR_SCODE;
  return code;
}


/*
 * Dump object 
 */
void 
dump_object(prec_t *pp, tcp_conn_t *tconnp, int len)
{

  http_trans_inner_t *inner = &tconnp->su.http.reptrans->inner;

  if (inner->dump_fd == 0)
    {
      /* this must be the first segment of the body - open file */

      char path[512];
      int pathlen = strlen(obj_dirnm);
      int rc;
      
      strcpy(path, obj_dirnm);

      
      sprintf(&path[pathlen], "%u.%u", TCP_CONN_ID, tconnp->su.http.curr_rep_trans_no++);
      

      rc = open(path, O_WRONLY | O_CREAT | O_EXCL, 00666 );
      if (rc < 0)
	{
	  if (errno == EMFILE || errno == ENFILE)
	    {
	      BUMP_CTR(http_dump_objects_dropped);
	    }
	  else
	    {
	      char errbuf[256];
	      sprintf(errbuf, "write_object %s", path);
	      error(errbuf,  "creat:");
	    }
	}
      else
	{
	  inner->dump_fd = rc;
	}
    }
  
  
  if (inner->dump_fd > 0 )
    {
      int retval;
      int write_len;

      if (http_ob_dump_len > 0)
	write_len = MIN(http_ob_dump_len - inner->len_written, len);
      else
	write_len = len;

      if (write_len > 0)
	{
	  if (write(inner->dump_fd, pp->buf, write_len) != write_len )
	    {
	      
	      error("write_object", "write:");
	    }
	  else
	    {
	      inner->len_written += write_len;
	      if (inner->len_written == http_ob_dump_len)
		{
		  /*
                   * never if http_ob_dump_len = -1 (dump whole object)
                   *  - free_trans() will close it on completion
                   */
		  if (close(inner->dump_fd) != 0)
		    error("dump_object", "close");
		  inner->dump_fd = -1;
		}
	    }  
	}
    }
  
  return;
}



/*
 * We only get here if there is some body to do
 */

int 
do_rep_body(prec_t *pp, tcp_conn_t *tconnp, int len, short code)
{
  int status = 0;

  assert(len <= pp->len);

  /* Pick up arrival time of first reply body packet */
  if (!(HTTP_STATUS & HTTP_IN_REP_BODY))
    {
      HTTP_STATUS |= HTTP_IN_REP_BODY;
      HTTP_FIRSTDATA_SEEN = TCP_SSOLID_TM;
    }
  
  /* If we're dumping object bodies take care of it here */

  if (http_ob_dump_len != 0) 
    dump_object(pp, tconnp, len);


#ifdef __alpha__

  body_in_cksm(pp, tconnp);

#else

  if (code == 200)
    fingerprint_add(&tconnp->su.http.reptrans->inner.sinf.finger, pp->buf, len);

#endif

  /* Now parse the body for stuff of interest */

  if (IS_TEXT)
    {
      BUMP_INFO(html_parsed);
      TIME_NP_CALL_RET("DO rep body", &call_times.html, 
		       parse_rep_body(pp, tconnp, len, code));
      if (status)
	return status;
    }
  
  ADJUST(pp, len);
  HTTP_REP_RECD_LEN += len;
  return 0;
}


int 
parse_rep_hdr(prec_t *pp, tcp_conn_t *tconnp, int recursing)
{
  short status_code;
  int version;
  int status;

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

  if (!recursing)
    {
      if (HTTP_STATUS & HTTP_IN_REP_HDR)
	{
	  /* try appending this packet and re-parsing */
	  http_hdr_parse_state_t *ps = 
	    &tconnp->su.http.reptrans->inner.s.http_p_state;
	  if ((status = save_interrupted_hdr(start, len, ps, SERVER)))
	    return status;
	  status = parse_rep_hdr(&ps->buf, tconnp, RECURSING);
	  if (status)
	    {
	      if (status == HTTP_SERV_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_REP_HDR;
      if (HTTP_STATUS & HTTP_SEEN_REP_HDR)
	{
	  /* another request - confirm is persistant connection */
	  if (IS_NON_PERSISTENT_CONN)
	    return HTTP_SERV_ERR_PERSISTANCE;
	  HTTP_STATUS |= (HTTP_KEEP_ALIVE | HTTP_WAS_PERSISTENT);
	}
    }
  
  /* 
   * Determine if status line and deal with it 
   */
  version = get_http_version(pp, SERVER);
  if (version > 0)
    /* full response */
    {
      HTTP_VERSIONS |= (version << HTTP_VERS_SERVER_SHIFT);
      if (version == HTTP_VERS_1_1)
	HTTP_STATUS |= HTTP_SERV_KEEP_ALIVE;
      else
	HTTP_STATUS &= (~HTTP_SERV_KEEP_ALIVE);
      ADJUST(pp, sizeof(char));	/* step over SP */
      status_code = get_status_code(pp);
      /* TODO handle interpretation of status code */
  
      if (status_code > 0)
	{
	  /* Is 1.1 or 1.0 full reply */
	  /* next line */
	  CLEAR_LINE(pp);
	  
	  HTTP_SERV_STATUS = status_code;	

	  if ((status = parse_http_fields(pp, tconnp, SERVER)) < 0)
	    {
	      if (status == HTTP_SERV_ERR_TRUNCHDR)
		goto interrupted;
	      else
		return status;
	    }

	  /* Should now have parsed header and stepped through packet to delimiter */
	  HTTP_REP_BODY_LEN = do_status_code(status_code, HTTP_STATUS, HTTP_REP_BODY_LEN, pp->len, &HTTP_REP_CONTENT_T);

	  /* TMP - look for non-standard line delims */
	  if (*(pp->buf-1) != '\r' || *(pp->buf) != '\n')
	    INTERESTING(&tconnp->flow_common.inner, CLIENT, "#%d HTTP hdr line delim %d %d", tconnp->hdrs.conn_id, (int)*(pp->buf-1), (int)*(pp->buf));
	      
	      
	  
	  /* 
	   * Should at least be a newline - any preceeding cr already cleared
	   * - if no body some servers omit these (the bastards) 
	   * so in this case ignore it - might cause errors later if a lone 
	   * nl or cr turn up! 
	   */
	  if (pp->len < sizeof(char))
	    {
	      if (HTTP_REP_BODY_LEN != 0)
		goto interrupted;
	    }
	  else 
	    {
	      if (pp->buf[0] != '\n')
		return HTTP_SERV_ERR_NO_HDRDELIM;
	      else
		ADJUST(pp, sizeof(char));
	    }
#if 0
	  if (pp->len)
	    {
	      if (pp->buf[0] != '\n')
		return HTTP_SERV_ERR_NO_HDRDELIM;
	  
	      ADJUST(pp, sizeof(char));
	    }

	  if (pp->len && pp->buf[0] == '\r')
	    ADJUST(pp, sizeof(char));
#endif
	  
	  if (status_code != 100)
	    {
	      HTTP_STATUS |= (HTTP_GOT_REP_HDR | HTTP_SEEN_REP_HDR);
	      HTTP_STATUS &= ~HTTP_IN_REP_HDR;
	      HTTP_REP_HDR_LEN = pp->buf - start;
	    }
	  else
	    {
	      /* It was a 'continue' - pretend it's not happened */
	      HTTP_REP_TRANS_STATUS |= TRANS_SERVER_CONT;
	    }
  
	  return 0;
	}
      else
	{
	  if (status_code == HTTP_SERV_ERR_TRUNCHDR)
	    goto interrupted;
	  else 
	    return status_code;
	}
    }
  else if(HTTP_STATUS & HTTP_GOT_REQ_HDR)
    { 
      if (HTTP_STATUS & HTTP_SIMPLE_REQUEST)
	{
	  /* know it's 0.9/1.0 simple response */
	  HTTP_STATUS |= HTTP_GOT_REP_HDR;
	  HTTP_STATUS &= ~HTTP_IN_REP_HDR;
	  HTTP_REP_HDR_LEN = pp->buf - start;
	  return 0;
	}
      else
	{
	  /* must be an error */
	    return version;
	}
    }
  else
    {
      /* don't know if simple request or error */
#if 0
      /* assume simple request */
      HTTP_STATUS |= HTTP_SIMPLE_REQUEST;
      HTTP_STATUS |= HTTP_GOT_REP_HDR;
      HTTP_STATUS &= ~HTTP_IN_REP_HDR;
      HTTP_REP_HDR_LEN = pp->buf - start;
      return 0;
#endif
      /* assume error */
      return HTTP_SERV_ERR_REPHDR_PARSE;
    }
  

 interrupted:

  if (!recursing)
    {
      HTTP_SERV_STATUS |= TRANS_HDR_FRAG;
      if ((status = 
	   save_interrupted_hdr(start, len, 
				&tconnp->su.http.reptrans->inner.s.http_p_state, SERVER)))
	return status;
      pp->len = 0;
      return 0;
    }
  return HTTP_SERV_ERR_TRUNCHDR;
}


/*
 * end http_rep.c 
 */
