/*
    http-exchange.c - HTTP request/response exchange
    Copyright (C) 2008, 2009, 2010 siliconforks.com

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <config.h>

#include "http-server.h"

#include <assert.h>
#include <ctype.h>
#include <string.h>

#include "util.h"

struct HTTPExchange {
  HTTPConnection * connection;

  HTTPMessage * request_message;

  char * method;
  char * request_uri;
  char * request_http_version;

  char * host;
  uint16_t port;
  char * abs_path;
  char * query;

  HTTPMessage * response_message;

  uint16_t status_code;
  char * response_http_version;
};

static const struct {
  const int status_code;
  const char * const reason_phrase;
} reason_phrases[] = {
  {100, "Continue"},
  {101, "Switching Protocols"},
  {200, "OK"},
  {201, "Created"},
  {202, "Accepted"},
  {203, "Non-Authoritative Information"},
  {204, "No Content"},
  {205, "Reset Content"},
  {206, "Partial Content"},
  {301, "Moved Permanently"},
  {302, "Found"},
  {303, "See Other"},
  {304, "Not Modified"},
  {305, "Use Proxy"},
  {307, "Temporary Redirect"},
  {400, "Bad Request"},
  {401, "Unauthorized"},
  {402, "Payment Required"},
  {403, "Forbidden"},
  {404, "Not Found"},
  {405, "Method Not Allowed"},
  {406, "Not Acceptable"},
  {407, "Proxy Authentication Required"},
  {408, "Request Time-out"},
  {409, "Conflict"},
  {410, "Gone"},
  {411, "Length Required"},
  {412, "Precondition Failed"},
  {413, "Request Entity Too Large"},
  {414, "Request-URI Too Large"},
  {415, "Unsupported Media Type"},
  {416, "Requested range not satisfiable"},
  {417, "Expectation Failed"},
  {500, "Internal Server Error"},
  {501, "Not Implemented"},
  {502, "Bad Gateway"},
  {503, "Service Unavailable"},
  {504, "Gateway Time-out"},
  {505, "HTTP Version not supported"},
};

HTTPExchange * HTTPExchange_new(HTTPConnection * connection) {
  HTTPExchange * exchange = xmalloc(sizeof(HTTPExchange));

  exchange->connection = connection;

  exchange->request_message = HTTPMessage_new(connection);
  exchange->method = NULL;
  exchange->request_uri = NULL;
  exchange->request_http_version = NULL;
  exchange->host = NULL;
  exchange->port = 0;
  exchange->abs_path = NULL;
  exchange->query = NULL;

  exchange->response_message = HTTPMessage_new(connection);
  exchange->response_http_version = NULL;
  exchange->status_code = 0;

  return exchange;
}

void HTTPExchange_delete(HTTPExchange * exchange) {
  HTTPMessage_delete(exchange->response_message);
  free(exchange->response_http_version);

  HTTPMessage_delete(exchange->request_message);
  free(exchange->method);
  free(exchange->request_uri);
  free(exchange->request_http_version);
  free(exchange->host);
  free(exchange->abs_path);
  free(exchange->query);

  free(exchange);
}

int HTTPExchange_get_peer(const HTTPExchange * exchange, struct sockaddr_in * peer) {
  return HTTPConnection_get_peer(exchange->connection, peer);
}

HTTPMessage * HTTPExchange_get_request_message(const HTTPExchange * exchange) {
  return exchange->request_message;
}

const char * HTTPExchange_get_request_line(const HTTPExchange * exchange) {
  return HTTPMessage_get_start_line(exchange->request_message);
}

const char * HTTPExchange_get_method(const HTTPExchange * exchange) {
  return exchange->method;
}

const char * HTTPExchange_get_request_uri(const HTTPExchange * exchange) {
  return exchange->request_uri;
}

const char * HTTPExchange_get_request_http_version(const HTTPExchange * exchange) {
  return exchange->request_http_version;
}

const char * HTTPExchange_get_host(const HTTPExchange * exchange) {
  return exchange->host;
}

uint16_t HTTPExchange_get_port(const HTTPExchange * exchange) {
  return exchange->port;
}

const char * HTTPExchange_get_abs_path(const HTTPExchange * exchange) {
  return exchange->abs_path;
}

const char * HTTPExchange_get_query(const HTTPExchange * exchange) {
  return exchange->query;
}

void HTTPExchange_set_method(HTTPExchange * exchange, const char * method) {
  free(exchange->method);
  exchange->method = xstrdup(method);
}

void HTTPExchange_set_request_uri(HTTPExchange * exchange, const char * request_uri) {
  free(exchange->request_uri);
  exchange->request_uri = xstrdup(request_uri);
}

const HTTPHeader * HTTPExchange_get_request_headers(const HTTPExchange * exchange) {
  return HTTPMessage_get_headers(exchange->request_message);
}

const char * HTTPExchange_find_request_header(const HTTPExchange * exchange, const char * name) {
  return HTTPMessage_find_header(exchange->request_message, name);
}

void HTTPExchange_add_request_header(HTTPExchange * exchange, const char * name, const char * value) {
  HTTPMessage_add_header(exchange->request_message, name, value);
}

void HTTPExchange_set_request_header(HTTPExchange * exchange, const char * name, const char * value) {
  HTTPMessage_set_header(exchange->request_message, name, value);
}

void HTTPExchange_set_request_content_length(HTTPExchange * exchange, size_t value) {
  HTTPMessage_set_content_length(exchange->request_message, value);
}

int HTTPExchange_read_request_headers(HTTPExchange * exchange) {
  int result = 0;

  result = HTTPMessage_read_start_line_and_headers(exchange->request_message);
  if (result != 0) {
    return result;
  }

  /* parse the Request-Line */
  const char * request_line = HTTPMessage_get_start_line(exchange->request_message);
  const char * p = request_line;

  /* parse the Method */
  while (*p != ' ') {
    if (*p == '\0') {
      return -1;
    }
    p++;
  }
  if (p == request_line) {
    return -1;
  }
  exchange->method = xstrndup(request_line, p - request_line);

  /* skip over space */
  p++;

  /* parse the Request-URI */
  const char * start = p;
  while (*p != ' ') {
    if (*p == '\0') {
      return -1;
    }
    p++;
  }
  if (p == start) {
    return -1;
  }
  exchange->request_uri = xstrndup(start, p - start);

  /* skip over space */
  p++;

  /* parse the HTTP-Version */
  start = p;
  while (*p != '\r' && *p != '\n') {
    if (*p == '\0') {
      return -1;
    }
    p++;
  }
  if (p == start) {
    return -1;
  }
  exchange->request_http_version = xstrndup(start, p - start);

  /* uri elements */
  /* RFC 2616 5.1.2: the Request-URI can be an `absoluteURI' or an `abs_path' */
  result = URL_parse(exchange->request_uri, &(exchange->host), &(exchange->port),
                                            &(exchange->abs_path), &(exchange->query));
  if (result != 0) {
    return result;
  }

  if (exchange->host == NULL) {
    /* abs_path */
    const char * h = HTTPMessage_find_header(exchange->request_message, HTTP_HOST);
    if (h == NULL) {
      /* this must be an HTTP/1.0 client */
    }
    else {
      result = URL_parse_host_and_port(h, &(exchange->host), &(exchange->port));
      if (result != 0) {
        return result;
      }
    }
  }

  return 0;
}

int HTTPExchange_write_request_headers(HTTPExchange * exchange) {
  if (HTTPMessage_has_sent_headers(exchange->request_message)) {
    return 0;
  }

  /* set the Request-Line */
  if (HTTPMessage_get_start_line(exchange->request_message) == NULL) {
    if (exchange->method == NULL) {
      exchange->method = xstrdup("GET");
    }
    assert(exchange->request_uri != NULL);
    char * request_line;
    xasprintf(&request_line, "%s %s HTTP/1.1\r\n", exchange->method, exchange->request_uri);
    HTTPMessage_set_start_line(exchange->request_message, request_line);
    free(request_line);
  }

  /* set the Host, if necessary */
  if (! str_starts_with(exchange->request_uri, "http://")) {
    const char * host = HTTPMessage_find_header(exchange->request_message, HTTP_HOST);
    if (host == NULL) {
      struct sockaddr_in peer;
      int result = HTTPConnection_get_peer(exchange->connection, &peer);
      if (result != 0) {
        return result;
      }
      const char * a = inet_ntoa(peer.sin_addr);
      char * value;
      xasprintf(&value, "%s:%u", a, ntohs(peer.sin_port));
      HTTPMessage_add_header(exchange->request_message, HTTP_HOST, value);
      free(value);
    }
  }

  return HTTPMessage_write_start_line_and_headers(exchange->request_message);
}

bool HTTPExchange_request_has_body(const HTTPExchange * exchange) {
  /*
  RFC 2616 4.3: a request has a body iff the request has a Content-Length or Transfer-Encoding header
  */
  return HTTPMessage_find_header(exchange->request_message, HTTP_CONTENT_LENGTH) != NULL || 
         HTTPMessage_find_header(exchange->request_message, HTTP_TRANSFER_ENCODING) != NULL;
}

int HTTPExchange_read_entire_request_entity_body(HTTPExchange * exchange, Stream * stream) {
  return HTTPMessage_read_entire_entity_body(exchange->request_message, stream);
}

int HTTPExchange_flush_request(HTTPExchange * exchange) {
  return HTTPMessage_flush(exchange->request_message);
}

/* response methods */

HTTPMessage * HTTPExchange_get_response_message(const HTTPExchange * exchange) {
  return exchange->response_message;
}

const char * HTTPExchange_get_response_http_version(const HTTPExchange * exchange) {
  return exchange->response_http_version;
}

uint16_t HTTPExchange_get_status_code(const HTTPExchange * exchange) {
  return exchange->status_code;
}

void HTTPExchange_set_status_code(HTTPExchange * exchange, uint16_t status_code) {
  exchange->status_code = status_code;
}

const HTTPHeader * HTTPExchange_get_response_headers(const HTTPExchange * exchange) {
  return HTTPMessage_get_headers(exchange->response_message);
}

const char * HTTPExchange_find_response_header(const HTTPExchange * exchange, const char * name) {
  return HTTPMessage_find_header(exchange->response_message, name);
}

void HTTPExchange_add_response_header(HTTPExchange * exchange, const char * name, const char * value) {
  HTTPMessage_add_header(exchange->response_message, name, value);
}

void HTTPExchange_set_response_header(HTTPExchange * exchange, const char * name, const char * value) {
  HTTPMessage_set_header(exchange->response_message, name, value);
}

void HTTPExchange_set_response_content_length(HTTPExchange * exchange, size_t value) {
  HTTPMessage_set_content_length(exchange->response_message, value);
}

static void skip_digits(const char ** p) {
  while (**p != '\0' && isdigit(**p)) {
    (*p)++;
  }
}

int HTTPExchange_read_response_headers(HTTPExchange * exchange) {
  /* make sure the request went through before we try to read stuff */
  int result = HTTPExchange_flush_request(exchange);
  if (result != 0) {
    return result;
  }

  result = HTTPMessage_read_start_line_and_headers(exchange->response_message);
  if (result != 0) {
    return result;
  }

  /* parse the Status-Line (RFC 2616 6.1) */
  const char * status_line = HTTPMessage_get_start_line(exchange->response_message);
  const char * p = status_line;

  /* read the HTTP-Version */
  if (! str_starts_with(p, "HTTP/")) {
    return -1;
  }
  p += 5;
  const char * start = p;
  skip_digits(&p);
  if (start == p) {
    return -1;
  }
  if (*p != '.') {
    return -1;
  }
  p++;
  start = p;
  skip_digits(&p);
  if (start == p) {
    return -1;
  }
  if (*p != ' ') {
    return -1;
  }
  exchange->response_http_version = xstrndup(status_line, p - status_line);

  /* skip over the space */
  p++;

  /* read the Status-Code */
  start = p;
  skip_digits(&p);
  if (p - start != 3) {
    return -1;
  }
  if (*p != ' ') {
    return -1;
  }
  exchange->status_code = strtoul(start, NULL, 10);

  return 0;
}

int HTTPExchange_write_response_headers(HTTPExchange * exchange) {
  if (HTTPMessage_has_sent_headers(exchange->response_message)) {
    return 0;
  }

  /* set the Status-Line (RFC 2616 6.1) */
  if (exchange->status_code == 0) {
    exchange->status_code = 200;
  }
  const char * reason_phrase = NULL;
  size_t num_reason_phrases = sizeof(reason_phrases) / sizeof(reason_phrases[0]);
  for (size_t i = 0; i < num_reason_phrases; i++) {
    if (reason_phrases[i].status_code == exchange->status_code) {
      reason_phrase = reason_phrases[i].reason_phrase;
      break;
    }
  }
  assert(reason_phrase != NULL);
  char * status_line;
  xasprintf(&status_line, "HTTP/1.1 %u %s\r\n", exchange->status_code, reason_phrase);
  HTTPMessage_set_start_line(exchange->response_message, status_line);
  free(status_line);

  /* set the Content-Type, if necessary */
  const char * content_type = HTTPMessage_find_header(exchange->response_message, HTTP_CONTENT_TYPE);
  if (content_type == NULL) {
    HTTPMessage_set_header(exchange->response_message, HTTP_CONTENT_TYPE, "text/html");
  }

  return HTTPMessage_write_start_line_and_headers(exchange->response_message);
}

bool HTTPExchange_response_has_body(const HTTPExchange * exchange) {
  /*
  RFC 2616 4.3: a response has a body iff the request is not HEAD and the response is not 1xx, 204, 304
  */
  const char * request_method = HTTPExchange_get_method(exchange);
  assert(request_method != NULL);
  return strcmp(request_method, "HEAD") != 0 &&
         exchange->status_code != 304 &&
         exchange->status_code != 204 &&
         exchange->status_code / 100 != 1;
}

int HTTPExchange_read_entire_response_entity_body(HTTPExchange * exchange, Stream * stream) {
  return HTTPMessage_read_entire_entity_body(exchange->response_message, stream);
}

int HTTPExchange_write_response(HTTPExchange * exchange, const void * p, size_t size) {
  int result = HTTPExchange_write_response_headers(exchange);
  if (result != 0) {
    return result;
  }
  return HTTPMessage_write(exchange->response_message, p, size);
}

int HTTPExchange_flush_response(HTTPExchange * exchange) {
  int result = HTTPExchange_write_response_headers(exchange);
  if (result != 0) {
    return result;
  }
  return HTTPMessage_flush(exchange->response_message);
}
