/*
 * Copyright (c) 1999-2001 Caucho Technology.  All rights reserved.
 *
 * Caucho Technology permits redistribution, modification and use
 * of this file in source and binary form ("the Software") under the
 * Caucho Developer Source License ("the License").  The following
 * conditions must be met:
 *
 * 1. Each copy or derived work of the Software must preserve the copyright
 *    notice and this notice unmodified.
 *
 * 2. Redistributions of the Software in source or binary form must include 
 *    an unmodified copy of the License, normally in a plain ASCII text
 *
 * 3. The names "Resin" or "Caucho" are trademarks of Caucho Technology and
 *    may not be used to endorse products derived from this software.
 *    "Resin" or "Caucho" may not appear in the names of products derived
 *    from this software.
 *
 * 4. Caucho Technology requests that attribution be given to Resin
 *    in any manner possible.  We suggest using the "Resin Powered"
 *    button or creating a "powered by Resin(tm)" link to
 *    http://www.caucho.com for each page served by Resin.
 *
 * This Software is provided "AS IS," without a warranty of any kind. 
 * ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 *
 * CAUCHO TECHNOLOGY AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE OR ANY THIRD PARTY AS A RESULT OF USING OR
 * DISTRIBUTING SOFTWARE. IN NO EVENT WILL Caucho OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE SOFTWARE, EVEN IF HE HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.      
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>

#include "cse.h"
#include "proxy.h"

#define READ(req) ((req->offset < req->length) ? \
                   req->buffer[req->offset++] :  \
                   request_read_byte(req))

int_map_t *g_header_map;

static void
init_header_map()
{
  g_header_map = int_map_create();

  int_map_set(g_header_map, "content-length", CONTENT_LENGTH);
  int_map_set(g_header_map, "host", HOST);
  int_map_set(g_header_map, "connection", CONNECTION);
}

/**
 * Create a new request.
 */
request_t *
request_create()
{
  request_t *req;

  if (! g_header_map)
    init_header_map();

  req = (request_t *) cse_malloc(sizeof(request_t));
  if (! req)
    return 0;

  memset(req, 0, sizeof(request_t));
  
  req->capacity = 8192;
  req->buffer = cse_malloc(req->capacity);
  if (! req->buffer) {
    cse_free(req);
    return 0;
  }
  
  return req;
}

/**
 * Free a request.
 */
void
request_free(request_t *req)
{
  cse_free(req->buffer);
  cse_free(req);
}

int
request_fill(request_t *req)
{
  connection_t *conn = req->conn;
  fd_set read_set;
  struct timeval timeout;
  int result = -1;
  int i = 3;
  
  if (conn->fd < 0)
    return -1;

  do {
    timeout.tv_sec = 30;
    timeout.tv_usec = 0;
    FD_ZERO(&read_set);
    FD_SET(conn->fd, &read_set);

    result = select(conn->fd + 1, &read_set, 0, 0, &timeout);

    if (result > 0)
      result = conn->ops->read(conn, req->buffer, req->capacity);
  } while (result < 0 && errno == EINTR && conn->fd >= 0 && i-- > 0);

  req->active_time = g_now_seconds;
  req->offset = 0;
  
  if (result > 0)
    req->length = result;
  else {
    req->length = 0;
    conn->ops->close(conn);
    return -1;
  }
  
  return req->length;
}

/**
 * Reads a byte from the request buffer.
 */
static int
request_read_byte(request_t *req)
{
  if (req->offset < req->length)
    return req->buffer[req->offset++];

  if (request_fill(req) <= 0)
    return -1;
  else
    return req->buffer[req->offset++];
}

static int
request_parse_uri(request_t *req)
{
  int ch;
  int i;

  for (ch = READ(req); isspace(ch); ch = READ(req)) {
  }

  if (ch < 0) {
    req->keepalive = 0;
    return -1;
  }

  for (i = 0; ch > 0 && ! isspace(ch); ch = READ(req)) {
    if (i < sizeof(req->method) - 1)
      req->method[i++] = ch;
  }
  req->method[i] = 0;

  for (; isspace(ch); ch = READ(req)) {
  }

  for (i = 0; ch > 0 && ! isspace(ch); ch = READ(req)) {
    if (i < sizeof(req->uri) - 1)
      req->uri[i++] = ch;
  }
  req->uri[i] = 0;

  for (; ch == ' '; ch = READ(req)) {
  }

  for (i = 0; ch > 0 && ch != '\n'; ch = READ(req)) {
    if (i < sizeof(req->protocol) - 1 && ch != '\n')
      req->protocol[i++] = ch;
  }
  req->protocol[i] = 0;
  if (req->protocol[i - 1] == '\r') {
    req->protocol[i - 1] = 0;
    i--;
  }
  
  if (i == 0) {
    req->version = HTTP_0_9;
    req->keepalive = 0;
  }
  else if (req->protocol[i - 1] >= '1') {
    req->version = HTTP_1_1;
    req->keepalive = 1;
  }
  else {
    req->version = HTTP_1_0;
    req->keepalive = 0;
  }

  return 1;
}

static void
request_parse_headers(request_t *req)
{
  int i = 0;
  int ch = READ(req);
  char *key = "";
  char *value = "";

  while (ch != '\n' && ch != '\r') {
    int code;

    /* Continuation */
    if ((ch == ' ' || ch == '\t') && i != 0) {
      ch = ' ';
      req->header_size--;
      i--;
    }
    else {
      key = req->headers + i;
    
      req->header_keys[req->header_size] = i;

      for (; ch > 0 && ch != ' ' && ch != ':'; ch = READ(req)) {
        if (i < sizeof(req->headers) - 1)
          req->headers[i++] = ch;
      }
      
      if (i < sizeof(req->headers) - 1)
        req->headers[i++] = 0;
      else {
        i = sizeof(req->headers) - 1;
        req->headers[i] = 0;
      }

      value = req->headers + i;
    
      req->header_values[req->header_size] = i;
      for (; ch == ' ' || ch == ':'; ch = READ(req)) {
      }
    }

    for (; ch > 0 && ch != '\r' && ch != '\n'; ch = READ(req)) {
      if (i < sizeof(req->headers) - 1)
        req->headers[i++] = ch;
    }
    
    if (i < sizeof(req->headers) - 1)
      req->headers[i++] = 0;
    else {
      i = sizeof(req->headers) - 1;
      req->headers[i] = 0;
    }

    if (req->header_size < 250)
      req->header_size++;

    code = int_map_get(g_header_map, key);
    if (code >= 0) {
      switch (code) {
      case CONTENT_LENGTH:
        req->content_length = atoi(value);
        break;
        
      case CONNECTION:
        if (! strcasecmp(value, "Close"))
          req->keepalive = 0;
        break;
        
      case HOST:
        if (! req->host)
          req->host = value;
        break;
      }
    }
    
    if (ch == '\r')
      ch = READ(req);
    
    if (ch == '\n')
      ch = READ(req);
  }

  req->header_keys[req->header_size] = i;
  req->header_values[req->header_size] = i;
  req->header_length = i;

  if (ch == '\r')
    ch = READ(req);
}

int
request_parse(request_t *req)
{
  req->keepalive = 1;
  
  if (request_parse_uri(req) < 0)
    return -1;
  
  req->header_size = 0;
  req->content_length = 0;
  req->content_offset = 0;
  req->host = 0;

  if (req->version >= HTTP_1_0)
    request_parse_headers(req);

  return 1;
}
