/* ----------------------------------------------------------------------------- 
 * parse.c
 *
 *     This file contains code to parse HTTP requests into various subcomponents
 * 
 * Author(s) : David Beazley (beazley@cs.uchicago.edu)
 *             Mike Sliczniak (mzsliczn@midway.uchicago.edu)
 *
 * Copyright (C) 1999-2000.  The University of Chicago
 * See the file LICENSE for information on usage and redistribution.	
 * ----------------------------------------------------------------------------- */

static char cvsroot[] = "$Header: /home/pastacvs/cvs-rep/vermicelli/tot/src/SWILL-0.1/Source/SWILL/parse.c,v 1.1 2005/01/27 08:46:25 dillema Exp $";

#include "swillint.h"

/* -----------------------------------------------------------------------------
 * swill_read_rawrequest()
 *
 * Reads a raw HTTP request from a file descriptor.  This function is responsible
 * for reading directly from the underlying socket.  It is very picky and only 
 * allows headers smaller than SWILL_MAX_HEADER.
 *
 * Returns 0 on error.  Otherwise, it returns 1 and places the request in the
 * passed request string.  excess is a string containing excess data that was
 * supplied on the connection, but which is not part of the request header.
 * ----------------------------------------------------------------------------- */

int 
swill_read_rawrequest(int fd, String **request, String **excess)
{
  char   buffer[SWILL_MAX_HEADER];
  char   requestbuf[SWILL_MAX_HEADER];
  int    buf_in = 0;
  int    request_in = 0;
  int    state = 0;
  int    len;
  fd_set reading;
  struct timeval  tv;
  char   c;
  int    retval;

  FD_ZERO(&reading);

  while (buf_in < SWILL_MAX_HEADER) {
    FD_SET(fd,&reading);
    tv.tv_sec = SwillTimeout;
    tv.tv_usec = 0;

    retval = select(fd+1,&reading,NULL,NULL,&tv);
    if (retval <= 0) {
      /* Timeout.  We're gone */
      swill_logprintf("Request read timeout! ");
      return 0;
    }
    len = read(fd, buffer+buf_in, SWILL_MAX_HEADER-buf_in);
    if (len <= 0) {
      if (errno == EINTR) continue;
      else return 0;       /* Read error. Failure!! */
    }
    while (len > 0) {
      c = buffer[buf_in];
      if (c == '\r') {         /* Nuke carriage returns (A Windows bogosity) */
	buf_in++;
	len--;
	continue;
      }
      switch (state) {
      case 0:	/* Request line */
	requestbuf[request_in++] = c;
	if (c == '\n') {
	  state = 1;
	} 
	break;
      case 1:  /* End of headers? */
	if (c == '\n') {
	  *request = NewString("");
	  Write(*request,requestbuf,request_in);    /* Save request                          */
	  *excess = NewString("");
	  Write(*excess,buffer+buf_in, len);        /* Write remaining data in excess string */
	  return 1;
	} else {
	  requestbuf[request_in++] = c;
	  state = 0;
	}
	break;
      }
      len--;
      buf_in++;
    }
  }
  /* Header is too large. Bail out with an error */
  return 0;
}

/* -----------------------------------------------------------------------------
 * swill_read_post()
 *
 * Reads data supplied with a POST operation.   Returns as a string on success.
 * Returns NULL on failure.
 * ----------------------------------------------------------------------------- */

String *
swill_read_post(int fd, int length, String *excess)
{
  char buffer[8192];
  int  rlen;
  int  elen;
  String *post;
  fd_set reading;
  struct timeval  tv;
  char   c;
  int    retval;

  FD_ZERO(&reading);

  if (length > SWILL_MAX_QUERY) {
    return 0;
  }

  post = NewString("");
  /* First copy data in the excess string over */
  elen = Len(excess);
  if (length < elen) elen = length;
  Write(post,Char(excess),elen);
  length -= elen;
  
  while (length > 0) {
    FD_SET(fd,&reading);
    tv.tv_sec = SwillTimeout;
    tv.tv_usec = 0;
    retval = select(fd+1,&reading,NULL,NULL,&tv);
    if (retval <= 0) {
      /* Timeout.  We're gone */
      Delete(post);
      return 0;
    }
    rlen = read(fd,buffer,8192);
    if (rlen <= 0) {
      if (errno == EINTR) continue;
      Delete(post);
      return 0;
    }
    Write(post,buffer,rlen);
    length -= rlen;
  }
  return post;
}

/* -----------------------------------------------------------------------------
 * swill_parse_url()
 *
 * Parses the request line of a HTTP header, splitting it into an operation,
 * URI, and query string.  Returns 1 on success, 0 otherwise.
 * ----------------------------------------------------------------------------- */

int
swill_parse_url(String *request, String **op_out, String **url_out, String **query_out) {
  List *fields;
  String *rawurl;
  String *url;
  List   *urlparts;

  fields = Split(request," ", -1);
  if (Len(fields) != 3) {
    Delete(fields);
    return 0;
  }
  *op_out = Copy(Getitem(fields,0));
  rawurl = Getitem(fields,1);
  Delitem(rawurl,0);            /* Nuke first character */
  urlparts = Split(rawurl,"?",1);
  
  *url_out = Copy(Getitem(urlparts,0));
  if (Len(urlparts) > 1) {
    *query_out = Copy(Getitem(urlparts,1));
  } else {
    *query_out = 0;
  }
  
  Delete(urlparts);
  Delete(fields);
  return 1;
}

/* -----------------------------------------------------------------------------
 * swill_parse_query()
 *
 * Parse a query string into a hash-table of attribute-value pairs
 * ----------------------------------------------------------------------------- */

Hash *
swill_parse_query(String *qs) {
  List *list;
  Hash *map;
  int i,j;
  map = NewHash();

  if (!qs) return map;
  list = Split(qs,"&",-1);             /* Split into fields */
  for (i = 0; i < Len(list); i++) {
    String *item, *name, *value, *decoded;
    List   *pair;
    item = Getitem(list,i);
    pair = Split(item,"=",1);
    if (Len(pair) != 2) {
      Delete(pair);
      Delete(list);
      Delete(map);
      return 0;
    }
    name = Getitem(pair,0);
    value = Getitem(pair,1);
    if (name && value) {
      decoded = NewString("");
      Seek(value,0,SEEK_SET);
      swill_url_decode(value,decoded);
      Setattr(map,name,decoded);
      Delete(decoded);
    }
    Delete(pair);
  }
  Delete(list);
  return map;
}


/* -----------------------------------------------------------------------------
 * convert_tolower()
 *
 * Convert a string to all lower-case.  Used in HTTP header parsing.
 * ----------------------------------------------------------------------------- */

static String *
convert_tolower(String *in) {
  String *str;
  int  ch;
  str = NewString("");
  Seek(in,0,SEEK_SET);
  while (1) {
    ch = Getc(in);
    if (ch != EOF) {
      Putc(tolower(ch),str);
    } else {
      break;
    }
  }
  return str;
}

/* -----------------------------------------------------------------------------
 * swill_parse_headers()
 *
 * Parse a list of RFC822 lines into a hash table
 * ----------------------------------------------------------------------------- */

Hash *
swill_parse_headers(List *lines) {
  /* Grab all of the HTTP headers and put into a hash object */
  Hash *headers;
  String *header;
  List   *pair;
  String *name, *value = 0;
  int i;
  headers = NewHash();
  for (i = 0; i < Len(lines); i++) {
    header = Getitem(lines,i);
    if (!Len(header)) return headers;     /* Blank line.  End of headers */
    if (isspace(*(Char(header)))) {
      /* Must be a continuation */
      if (value)
	Append(value,header);
      continue;
    }
    pair = Split(header,":",1);       /* Split into components */
    if (Len(pair) == 2) {
      String *nlower;
      name = Getitem(pair,0);
      value = Getitem(pair,1);
      Delitem(value,0);              /* Get rid of leading space */
      nlower = convert_tolower(name);
      Setattr(headers,nlower,value);
      Delete(nlower);
    }
    Delete(pair);
  }
  return headers;
}

/* -----------------------------------------------------------------------------
 * swill_parse_request_headers()
 *
 * Takes a request string and parses it into a full request object.  Returns a 
 * hash table that contains the following fields:
 *
 *       uri         - HTTP URI
 *       method      - HTTP Method
 *       query       - Hash table of query variables
 *       headers     - Hash table of HTTP headers
 *       request     - Raw request string that can be used to regenerate the request
 *       querystring - Raw query string
 *
 * Returns 0 on failure.
 * ----------------------------------------------------------------------------- */

Hash *
swill_parse_request_headers(String *request) {
  List *lines;
  String *urlline;
  String *method = 0, *uri = 0, *querystring = 0;
  Hash   *headers = 0;

  Hash *reqh = NewHash();
  
  /* Split request into lines */
  lines = Split(request,"\n",-1);
  if (Len(lines) < 1) {
    Delete(reqh);
    Delete(lines);
    return 0;
  }
  urlline = Getitem(lines,0);

  if (!swill_parse_url(urlline, &method, &uri, &querystring)) {
    /* Bad HTTP request */
    Delete(reqh);
    Delete(lines);
    return 0;
  }
  Delitem(lines,0);
  headers = swill_parse_headers(lines);
  if (!headers) {
    Delete(reqh);
    Delete(lines);
    Delete(method);
    Delete(uri);
    Delete(querystring);
    return 0;
  }
  
  /* If the uri has zero length, then we use the SWILL default document.
     Usually this is "index.html" */
  
  if ((Len(uri) == 0)) {
    Append(uri, SWILL_DEFAULT);
  }

  /* Request looks okay for now */
  Setattr(reqh,"uri", uri);
  Setattr(reqh,"method",method);
  Setattr(reqh,"headers", headers);
  Setattr(reqh,"request", request);
  Setattr(reqh,"querystring", querystring);

  Delete(lines);
  Delete(headers);
  Delete(uri);
  Delete(method);
  Delete(querystring);
  return reqh;
} 

/* -----------------------------------------------------------------------------
 * swill_parse_request_data()
 *
 * Takes a hash returned by swill_parse_request_headers and parses form variables
 * ----------------------------------------------------------------------------- */

int
swill_parse_request_data(Hash *reqh) {

  String *method;
  String *qs = 0;
  Hash   *headers;
  Hash   *query;
  int     post = 0;

  method = Getattr(reqh,"method");
  if (!method) return 0;

  headers = Getattr(reqh,"headers");
  if (!headers) return 0;

  if (Strcmp(method,"GET") == 0) {
    qs = Getattr(reqh,"querystring");
  } else if (Strcmp(method,"POST") == 0) {
    /* Data follows the headers */
    String *req = Getattr(reqh,"request");
    int length = GetInt(headers,"content-length");
    if (length > 0) {
      /* Find start of the headers */
      char *qsc = Strstr(req,"\n\n");
      if (qsc) qs = NewString(qsc+2);
      Chop(qs);
    }
    post = 1;
  }
  if (qs) {
    query = swill_parse_query(qs);
    if (post) {
      Setattr(reqh,"querystring",qs);
      Delete(qs);
    }
    if (!query) {
      return 0;
    }
    Setattr(reqh,"query",query);
    Delete(query);
  } else {
    query = NewHash();
    Setattr(reqh,"query", query);
    Delete(query);
  }
  /* Set up some additional query variables */
  Setattr(query,"__uri__", Getattr(reqh,"uri"));
  Setattr(query,"__method__",method);
  return 1;
}

/* -----------------------------------------------------------------------------
 * swill_parse_request()
 *
 * Parses a complete request in a string
 * ----------------------------------------------------------------------------- */

Hash *
swill_parse_request(String *request) {
  Hash *h = swill_parse_request_headers(request);
  if (!h) return 0;

  if (!(swill_parse_request_data(h))) {
    Delete(h);
    return 0;
  }
  return h;
}




syntax highlighted by Code2HTML, v. 0.9.1