/*
    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
*/

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

#include <sys/types.h>

#include <httperf.h>
#include <http.h>
#include <call.h>
#include <event.h>
#include <conn.h>

/* Read a CRLF terminated line of characters into c->reply.line.
   Returns 1 when the line is complete, 0 when the line is incomplete
   and more data is needed.  */
static int
get_line (Call *c, char **bufp, size_t *buf_lenp)
{
  size_t to_copy, buf_len = *buf_lenp;
  Conn *s = c->conn;
  char *buf = *bufp;
  const char *eol;
  int has_lf;

  if (buf_len <= 0)
    return 0;

  /* Note that core.c guarantees that BUF is '\0' terminated. */
  eol = strchr (buf, '\n');
  if (eol)
    ++eol;
  else
    eol = buf + buf_len;

  to_copy = eol - buf;
  buf_len -= to_copy;
  if (s->line.iov_len + to_copy >= sizeof (s->line_buf))
    {
      fprintf (stderr,
	       "%s.get_line: truncating header from %lu to %lu bytes\n",
	       prog_name, (u_long) (s->line.iov_len + to_copy),
	       (u_long) sizeof (s->line_buf));
      to_copy = sizeof (s->line_buf) - 1 - s->line.iov_len;
    }
  memcpy ((char *) s->line.iov_base + s->line.iov_len, buf, to_copy);
  s->line.iov_len += to_copy;

  has_lf = ((char *) s->line.iov_base)[s->line.iov_len - 1] == '\n';

  *bufp = (char *) eol;
  *buf_lenp = buf_len;

  if (has_lf || s->line.iov_len == sizeof (s->line_buf) - 1)
    {
      /* We got a full header line.  Chop off \r\n at the tail if
	 necessary.  */
      if (((char *) s->line.iov_base)[s->line.iov_len - 1] == '\n')
	{
	  --s->line.iov_len;
	  if (((char *) s->line.iov_base)[s->line.iov_len - 1] == '\r')
	    --s->line.iov_len;
	}
      ((char *) s->line.iov_base)[s->line.iov_len] = '\0';
      return 1;
    }
  return 0;
}

static void
parse_status_line (Call *c, char **bufp, size_t *buf_lenp)
{
  char *buf, *buf_start = *bufp;
  u_int major, minor, status;
  Conn *s = c->conn;
  Any_Type arg;

  s->is_chunked = 0;

  /* default to "infinite" content length: */
  s->content_length = ~(size_t) 0;

  if (!get_line (c, bufp, buf_lenp))
    return;

  buf = c->conn->line.iov_base;
  if (sscanf (buf, "HTTP/%u.%u %u ", &major, &minor, &status) == 3)
    {
      c->reply.version = 0x10000*major + minor;
      c->reply.status = status;
    }
  else
    {
      c->reply.version = 0x10000;		/* default to 1.0 */
      c->reply.status = 599;
      if (c->reply.status == 599)
	fprintf (stderr, "%s.parse_status_line: invalid status line `%s'!!\n",
		 prog_name, buf);
    }
  if (DBG > 0)
    fprintf (stderr,
	     "parse_status_line.%lu: reply is HTTP/%u.%u, status = %d\n",
	     c->id, c->reply.version / 0x10000, c->reply.version & 0xffff,
	     c->reply.status);

  /* Determine whether we should be expecting a message body.  HEAD
     never includes an entity.  For other methods, things depend on
     the status code.  */

  if (strcmp ((char *) c->req.iov[IE_METHOD].iov_base, "HEAD") == 0)
    s->has_body = 0;
  else
    {
      s->has_body = 1;
      switch (status / 100)
	{
	case 1: /* informational */
	  s->has_body = 0;
	  if (status == 100)
	    {
	      arg.l = c->reply.status;
	      event_signal (EV_CALL_RECV_START, (Object *) c, arg);
	      s->state = S_REPLY_CONTINUE;
	      goto done;
	    }
	  break;

	case 2: /* success */
	case 3: /* redirection */
	  switch (status)
	    {
	    case 204: /* No Content */
	    case 205: /* Reset Content */
	    case 304: /* Not Modified */
	      s->has_body = 0;
	      break;
	    }
	  break;

	case 4: /* client errors */
	case 5: /* server errors */
	  break;

	default:
	  fprintf (stderr, "%s.parse_status_line: bad status %u\n",
		   prog_name, status);
	  break;
	}
    }
  arg.l = c->reply.status;
  event_signal (EV_CALL_RECV_START, (Object *) c, arg);
  if (s->state >= S_CLOSING)
    return;
  s->state = S_REPLY_HEADER;

 done:
  c->reply.header_bytes += *bufp - buf_start;
  s->line.iov_len = 0;		
}

static void
parse_headers (Call *c, char **bufp, size_t *buf_lenp)
{
  char *hdr, *buf_start = *bufp;
  Conn *s = c->conn;
  size_t hdr_len;
  Any_Type arg;

  while (get_line (c, bufp, buf_lenp) > 0)
    {
      hdr = s->line.iov_base;
      hdr_len = s->line.iov_len;

      if (!hdr_len)
	{
	  /* empty header implies end of headers */
	  if (s->has_body)
	    if (s->is_chunked)
	      {
		s->content_length = 0;
		s->state = S_REPLY_CHUNKED;
	      }
	    else
	      s->state = S_REPLY_DATA;
	  else if (s->state == S_REPLY_CONTINUE)
	    s->state = S_REPLY_HEADER;
	  else
	    s->state = S_REPLY_DONE;
	  break;
	}

      /* process line as a regular header: */
      switch (tolower (*hdr))
	{
	case 'c':
	  if (strncasecmp (hdr, "content-length:", 15) == 0)
	    {
	      hdr += 15;
	      s->content_length = strtoul (hdr, 0, 10);
	    }
	  break;

	case 't':
	  if (strncasecmp (hdr, "transfer-encoding:", 18) == 0)
	    {
	      hdr += 18;
	      while (isspace (*hdr))
		++hdr;
	      if (strcasecmp (hdr, "chunked") == 0)
		s->is_chunked = 1;
	      else
		fprintf (stderr, "%s.parse_headers: unknown transfer "
			 "encoding `%s'\n", prog_name, hdr);
	    }
	  break;
	}
      arg.vp = &s->line;
      event_signal (EV_CALL_RECV_HDR, (Object *) c, arg);
      if (s->state >= S_CLOSING)
	return;
      s->line.iov_len = 0;		
    }
  c->reply.header_bytes += *bufp - buf_start;
}

static void
parse_footers (Call *c, char **bufp, size_t *buf_lenp)
{
  char *hdr, *buf_start = *bufp;
  Conn *s = c->conn;
  size_t hdr_len;
  Any_Type arg;

  while (get_line (c, bufp, buf_lenp) > 0)
    {
      hdr = s->line.iov_base;
      hdr_len = s->line.iov_len;

      if (!hdr_len)
	{
	  /* empty footer implies end of footers */
	  s->state = S_REPLY_DONE;
	  break;
	}
      /* process line as a regular footer: */
      arg.vp = &s->line;
      event_signal (EV_CALL_RECV_FOOTER, (Object *) c, arg);
      if (s->state >= S_CLOSING)
	return;
      s->line.iov_len = 0;		
    }
  c->reply.footer_bytes += *bufp - buf_start;
}

static int
parse_data (Call *c, char **bufp, size_t *buf_lenp)
{
  size_t bytes_needed, buf_len = *buf_lenp;
  Conn *s = c->conn;
  char *buf = *bufp;
  struct iovec iov;
  Any_Type arg;

  bytes_needed = (s->content_length - c->reply.content_bytes);

  if (buf_len > bytes_needed)
    buf_len = bytes_needed;

  iov.iov_base = (caddr_t) buf;
  iov.iov_len = buf_len;
  arg.vp = &iov;
  event_signal (EV_CALL_RECV_DATA, (Object *) c, arg);

  c->reply.content_bytes += buf_len;
  *bufp = buf + buf_len;
  *buf_lenp -= buf_len;

  return (buf_len == bytes_needed);
}

static void
xfer_chunked  (Call *c, char **bufp, size_t *buf_lenp)
{
  Conn *s = c->conn;
  size_t chunk_length;
  char *end;

  while (*buf_lenp > 0 && s->state < S_CLOSING)
    {
      if (c->reply.content_bytes >= s->content_length)
	{
	  /* need to parse next chunk length line: */
	  if (!get_line (c, bufp, buf_lenp))
	    return;				/* need more data */
	  if (s->line.iov_len == 0)
	    continue;				/* skip over empty line */

	  errno = 0;
	  chunk_length = strtoul (s->line.iov_base, &end, 16);
	  s->line.iov_len = 0;
	  if (errno == ERANGE || end == s->line.iov_base)
	    {
	      fprintf (stderr, "%s.xfer_chunked: bad chunk line `%s'\n",
		       prog_name, (char *) s->line.iov_base);
	      continue;
	    }

	  if (chunk_length == 0)
	    {
	      /* a final chunk of zero bytes indicates the end of the reply */
	      s->state = S_REPLY_FOOTER;
	      return;
	    }
	  s->content_length += chunk_length;
	}
      parse_data (c, bufp, buf_lenp);
    }
}

void
http_process_reply_bytes (Call *c, char **bufp, size_t *buf_lenp)
{
  Conn *s = c->conn;
  struct iovec iov;
  Any_Type arg;

  iov.iov_base = *bufp;
  iov.iov_len = *buf_lenp;
  arg.vp = &iov;
  event_signal (EV_CALL_RECV_RAW_DATA, (Object *) c, arg);

  do
    {
      switch (s->state)
	{
	case S_REPLY_STATUS:
	  parse_status_line (c, bufp, buf_lenp);
	  break;

	case S_REPLY_HEADER:
	  parse_headers (c, bufp, buf_lenp);
	  break;

	case S_REPLY_FOOTER:
	  parse_footers (c, bufp, buf_lenp);
	  break;

	case S_REPLY_DATA:
	  if (parse_data (c, bufp, buf_lenp) && s->state < S_CLOSING)
	    s->state = S_REPLY_DONE;
	  break;

	case S_REPLY_CONTINUE:
	  parse_headers (c, bufp, buf_lenp);
	  break;

	case S_REPLY_CHUNKED:
	  xfer_chunked (c, bufp, buf_lenp);
	  break;

	case S_REPLY_DONE:
	  return;

	default:
	  fprintf (stderr, "%s.http_process_reply_bytes: bad state %d\n",
		   prog_name, s->state);
	  exit (1);
	}
    }
  while (*buf_lenp > 0 && s->state < S_CLOSING);
}


syntax highlighted by Code2HTML, v. 0.9.1