/* Copyright (C) 2014 Daniel Dressler and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. */

#define _GNU_SOURCE
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include <limits.h>

#include "http.h"
#include "logging.h"

#define BUFFER_STEP (1 << 12)

struct http_message_t *http_message_new()
{
  struct http_message_t *msg = calloc(1, sizeof(*msg));
  if (msg == NULL) {
    ERR("failed to alloc space for http message");
    return NULL;
  }

  msg->spare_capacity = 0;
  msg->spare_filled = 0;
  msg->spare_buffer = NULL;

  return msg;
}

void message_free(struct http_message_t *msg)
{
  free(msg->spare_buffer);
  free(msg);
}

static void packet_check_completion(struct http_packet_t *pkt)
{
  struct http_message_t *msg = pkt->parent_message;
  /* Msg full */
  if (msg->claimed_size && msg->received_size >= msg->claimed_size) {
    msg->is_completed = 1;
    NOTE("http: Message completed: Received size >= claimed size");

    /* Sanity check */
    if (msg->spare_filled > 0)
      ERR_AND_EXIT("Msg spare not empty upon completion");
  }

  /* Pkt full */
  if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) {
    pkt->is_completed = 1;
    NOTE("http: Packet completed: Packet full");
  }

  /* Pkt over capacity */
  if (pkt->filled_size > pkt->buffer_capacity) {
    /* Santiy check */
    ERR_AND_EXIT("Overflowed packet buffer");
  }
}

static int doesMatch(const char *matcher, size_t matcher_len,
                     const uint8_t *key, size_t key_len)
{
  for (size_t i = 0; i < matcher_len; i++)
    if (i >= key_len || matcher[i] != key[i])
      return 0;
  return 1;
}

static int inspect_header_field(struct http_packet_t *pkt, size_t header_size,
                                char *key, size_t key_size)
{
  /* Find key */
  uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size);
  if (pos == NULL)
    return -1;

  /* Find first digit */
  size_t number_pos = (size_t) (pos - pkt->buffer) + key_size;
  while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos]))
    ++number_pos;

  /* Find next non-digit */
  size_t number_end = number_pos;
  while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end]))
    ++number_end;

  /* Failed to find next non-digit
     header field might be broken */
  if (number_end >= pkt->filled_size)
    return -1;

  /* Temporary stringification of buffer for atoi() */
  uint8_t original_char = pkt->buffer[number_end];
  pkt->buffer[number_end] = '\0';
  int val = atoi((const char *)(pkt->buffer + number_pos));

  /* Restore buffer */
  pkt->buffer[number_end] = original_char;
  return val;
}

static void packet_store_excess(struct http_packet_t *pkt)
{
  struct http_message_t *msg = pkt->parent_message;
  if (msg->spare_buffer != NULL)
    ERR_AND_EXIT("Do not store excess to non-empty packet");

  if (pkt->expected_size >= pkt->filled_size)
    ERR_AND_EXIT("Do not call packet_store_excess() unless needed");

  size_t spare_size = pkt->filled_size - pkt->expected_size;
  size_t non_spare = pkt->expected_size;
  NOTE("HTTP: Storing %d bytes of excess", spare_size);

  /* Align to BUFFER_STEP */
  size_t needed_size = 0;
  needed_size += spare_size / BUFFER_STEP;
  needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0;

  if (msg->spare_buffer == NULL) {
    uint8_t *buffer = calloc(1, needed_size);
    if (buffer == NULL)
      ERR_AND_EXIT("Failed to alloc msg spare buffer");
    msg->spare_buffer = buffer;
  }

  memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size);
  pkt->filled_size = non_spare;

  msg->spare_capacity = needed_size;
  msg->spare_filled = spare_size;
}

static void packet_take_spare(struct http_packet_t *pkt)
{
  struct http_message_t *msg = pkt->parent_message;
  if (msg->spare_filled == 0)
    return;

  if (msg->spare_buffer == NULL)
    return;

  if (pkt->filled_size > 0)
    ERR_AND_EXIT("pkt should be empty when loading msg spare");

  /* Take message's buffer */
  size_t msg_size = msg->spare_capacity;
  size_t msg_filled = msg->spare_filled;
  uint8_t *msg_buffer = msg->spare_buffer;

  pkt->buffer_capacity = msg_size;
  pkt->filled_size = msg_filled;
  pkt->buffer = msg_buffer;

  msg->spare_capacity = 0;
  msg->spare_filled = 0;
  msg->spare_buffer = NULL;
}

static ssize_t packet_find_chunked_size(struct http_packet_t *pkt)
{
  /* NOTE:
     chunks can have trailers which are
     tacked on http header fields.
     NOTE:
     chunks may also have extensions.
     No one uses or supports them. */

  /* Find end of size string */
  if (pkt->filled_size >= SSIZE_MAX)
    ERR_AND_EXIT("Buffer beyond sane size");

  ssize_t max = (ssize_t) pkt->filled_size;
  ssize_t size_end = -1;
  ssize_t miniheader_end = -1;
  ssize_t delimiter_start = -1;
  for (ssize_t i = 0; i < max; i++) {

    uint8_t *buf = pkt->buffer;
    if (size_end < 0) {
      /* No extension */
      if (i + 1 < max &&
	  (buf[i] == '\r' &&      /* CR */
	   buf[i + 1] == '\n')) { /* LF */
	size_end = i + 1;
	miniheader_end = size_end;
	delimiter_start = i;
	break;
      }

      /* No extension */
      if (buf[i] == '\n') { /* LF */
	size_end = i;
	miniheader_end = size_end;
	delimiter_start = i;
	break;
      }

      /* Has extensions */
      if (buf[i] == ';') {
	size_end = i;
	continue;
      }
    }
    if (miniheader_end < 0) {
      if (i + 1 < max &&
	  (buf[i] == '\r' &&      /* CR */
	   buf[i + 1] == '\n')) { /* LF */
	miniheader_end = i + 1;
	delimiter_start = i;
	break;
      }

      if (buf[i] == '\n') { /* LF */
	miniheader_end = i;
	delimiter_start = i;
	break;
      }
    }
  }

  if (miniheader_end < 0) {
    /* NOTE: knowing just the size field
       is not enough since the extensions
       are not included in the size */
    NOTE("failed to find chunk mini-header so far");
    return -1;
  }

  /* Temporary stringification for strtol() */
  uint8_t original_char = *(pkt->buffer + size_end);
  *(pkt->buffer + size_end) = '\0';
  size_t size = strtoul((char *)pkt->buffer, NULL, 16);
  NOTE("Chunk size raw: %s", pkt->buffer);
  *(pkt->buffer + size_end) = original_char;
  if (size > SSIZE_MAX)
    ERR_AND_EXIT("chunk size is insane");

  if (size > 0) {
    /* Regular chunk */
    ssize_t chunk_size = (ssize_t) size; /* Chunk body */
    chunk_size += miniheader_end + 1; /* Mini-header */
    chunk_size += 2; /* Trailing CRLF */
    NOTE("HTTP: Chunk size: %lu", chunk_size);
    return (ssize_t) chunk_size;
  }

  /* Terminator chunk
     May have trailers in body */
  ssize_t full_size = -1;
  for (ssize_t i = delimiter_start; i < max; i++) {
    uint8_t *buf = pkt->buffer;
    if (i + 3 < max &&
	(buf[i] == '\r' &&      /* CR */
	 buf[i + 1] == '\n' &&  /* LF */
	 buf[i + 2] == '\r' &&  /* CR */
	 buf[i + 3] == '\n')) { /* LF */
      full_size = i + 4;
      break;
    }

    if (i + 1 < max &&
	buf[i] == '\n' &&     /* LF */
	buf[i + 1] == '\n') { /* LF */
      full_size = i + 2;
      break;
    }
  }

  if (full_size < 0) {
    NOTE("Chunk miniheader present but body incomplete");
    return -1;
  }

  NOTE("Found end chunked packet");
  pkt->parent_message->is_completed = 1;
  pkt->is_completed = 1;
  return full_size;
}

static ssize_t packet_get_header_size(struct http_packet_t *pkt)
{
  if (pkt->header_size != 0)
    goto found;

 /*
  * RFC2616 recomends we match newline on \n despite full
  * complience requires the message to use only \r\n
  * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3
  */

  /* Find header */
  for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) {
    /* two \r\n pairs */
    if ((i + 3) < pkt->filled_size &&
	'\r' == pkt->buffer[i] &&
	'\n' == pkt->buffer[i + 1] &&
	'\r' == pkt->buffer[i + 2] &&
	'\n' == pkt->buffer[i + 3]) {
      pkt->header_size = i + 4;
      goto found;
    }

    /* two \n pairs */
    if ((i + 1) < pkt->filled_size &&
	'\n' == pkt->buffer[i] &&
	'\n' == pkt->buffer[i + 1]) {
      pkt->header_size = i + 2;
      goto found;
    }
  }

  return -1;

 found:
  return (ssize_t) pkt->header_size;
}

enum http_request_t packet_find_type(struct http_packet_t *pkt)
{
  enum http_request_t type = HTTP_UNSET;
  size_t size = 0;
 /*
  * Valid methods for determining http request
  * size are defined by W3 in RFC2616 section 4.4
  * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
  */

 /*
  * This function attempts to find what method this
  * packet would use. This is only possible in specific case:
  * 1. if the request uses method 1 we can check the http 
  *    request type. We must be called on a packet which
  *    has the full header.
  * 2. if the request uses method 2 we need the full header
  *    but a simple network-byte-order-aware string search
  *    works. This function does not work if called with
  *    a chunked transport's sub-packet.
  * 3. if the request uses method 3 we again perform the
  *    string search.
  *
  * All cases require the packat to contain the full header.
  */

  ssize_t header_size_raw = packet_get_header_size(pkt);
  if (header_size_raw < 0) {
    /* We don't have the header yet */
    goto do_ret;
  }
  size_t header_size = (size_t) header_size_raw;

  /* Try Transfer-Encoding Chunked */
  char xfer_encode_str[] = "Transfer-Encoding: chunked";
  size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1;
  uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size,
				    xfer_encode_str,
				    xfer_encode_str_size);
  if (xfer_encode_pos != NULL) {
    size = 0;
    type = HTTP_CHUNKED;
    goto do_ret;
  }

  /* Try Content-Length */
  char content_length_str[] = "Content-Length: ";
  ssize_t contlen_size =
    inspect_header_field(pkt, header_size,
			 content_length_str, sizeof(content_length_str) - 1);
  if (contlen_size >= 0) {
    size = (size_t) contlen_size + header_size;
    type = HTTP_CONTENT_LENGTH;
    goto do_ret;
  }

  /* GET requests (start with GET) or answers from the server (start
     with HTTP) */
  if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) ||
      doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) {
    size = header_size;
    type = HTTP_HEADER_ONLY;
    goto do_ret;
  }

  /* No size was detectable yet header was found */
  type = HTTP_UNKNOWN;
  size = 0;

 do_ret:
  pkt->parent_message->claimed_size = size;
  pkt->parent_message->type = type;
  return type;
}

size_t packet_pending_bytes(struct http_packet_t *pkt)
{
  struct http_message_t *msg = pkt->parent_message;

  /* Check Cache */
  if (pkt->expected_size > 0)
    goto pending_known;

  if (HTTP_UNSET == msg->type) {
    msg->type = packet_find_type(pkt);

    if (HTTP_CHUNKED == msg->type) {
      /* Note: this was the packet with the
	 header of our chunked message. */

      /* Save any non-header data we got */
      ssize_t header_size = packet_get_header_size(pkt);

      /* Sanity check */
      if (header_size < 0 ||
	  (size_t)header_size > pkt->filled_size)
	ERR_AND_EXIT("HTTP: Could not find header twice");

      NOTE("HTTP: Chunked header size is %ld bytes",
	   header_size);
      pkt->expected_size = (size_t) header_size;
      msg->claimed_size = 0;
      goto pending_known;
    }
  }

  if (HTTP_CHUNKED == msg->type) {
    if (pkt->filled_size == 0) {
      /* Grab chunk's mini-header */
      goto pending_known;
    }

    if (pkt->expected_size == 0) {
      /* Check chunk's mini-header */
      ssize_t size = packet_find_chunked_size(pkt);
      if (size <= 0) {
	ERR("=============================================");
	ERR("Malformed chunk-transport http header receivd");
	ERR("Missing chunk's mini-headers in first data");
	ERR("Have %d bytes", pkt->filled_size);
	printf("%.*s\n", (int)pkt->filled_size, pkt->buffer);
	ERR("Malformed chunk-transport http header receivd");
	ERR("=============================================");
	goto pending_known;
      }

      pkt->expected_size = (size_t) size;
      msg->claimed_size = 0;
    }

    goto pending_known;
  }
  if (HTTP_HEADER_ONLY == msg->type) {
    /* Note: we can only know it is header only
       when the buffer already contains the header.
       So this next call cannot fail. */
    pkt->expected_size = (size_t) packet_get_header_size(pkt);
    msg->claimed_size = pkt->expected_size;
    goto pending_known;
  }
  if (HTTP_CONTENT_LENGTH == msg->type) {
    /* Note: find_header() has
       filled msg's claimed_size */
    msg->claimed_size = msg->claimed_size;
    pkt->expected_size = msg->claimed_size;
    goto pending_known;
  }

 pending_known:

  /* Save excess data */
  if (pkt->expected_size && pkt->filled_size > pkt->expected_size)
    packet_store_excess(pkt);

  size_t expected = pkt->expected_size;
  if (expected == 0)
    expected = msg->claimed_size;
  if (expected == 0)
    expected = pkt->buffer_capacity;

  /* Sanity check */
  if (expected < pkt->filled_size)
    ERR_AND_EXIT("Expected cannot be larger than filled");

  size_t pending = expected - pkt->filled_size;

  /* Expand buffer as needed */
  while (pending + pkt->filled_size > pkt->buffer_capacity) {
    ssize_t size_added = packet_expand(pkt);
    if (size_added < 0) {
      WARN("packet at max allowed size");
      return 0;
    }
    if (size_added == 0) {
      ERR("Failed to expand packet");
      return 0;
    }
  }

  packet_check_completion(pkt);

  return pending;
}

void packet_mark_received(struct http_packet_t *pkt, size_t received)
{
  struct http_message_t *msg = pkt->parent_message;
  msg->received_size += received;

  pkt->filled_size += received;
  if (received) {
    NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, "
	 "msg has %lu bytes",
	 received, pkt->filled_size, msg->received_size);
  }

  packet_check_completion(pkt);

  if (pkt->filled_size > pkt->buffer_capacity)
    ERR_AND_EXIT("Overflowed packet's buffer");

  if (pkt->expected_size && pkt->filled_size > pkt->expected_size) {
    /* Store excess data */
    packet_store_excess(pkt);
  }
}

struct http_packet_t *packet_new(struct http_message_t *parent_msg)
{
  struct http_packet_t *pkt = NULL;
  uint8_t              *buf = NULL;
  size_t const capacity = BUFFER_STEP;

  assert(parent_msg != NULL);
  pkt = calloc(1, sizeof(*pkt));
  if (pkt == NULL) {
    ERR("failed to alloc packet");
    return NULL;
  }
  pkt->parent_message = parent_msg;
  pkt->expected_size = 0;

  /* Claim any spare data from prior packets */
  packet_take_spare(pkt);

  if (pkt->buffer == NULL) {
    buf = calloc(capacity, sizeof(*buf));
    if (buf == NULL) {
      ERR("failed to alloc space for packet's buffer or space for packet");
      free(pkt);
      return NULL;
    }

    /* Assemble packet */
    pkt->buffer = buf;
    pkt->buffer_capacity = capacity;
    pkt->filled_size = 0;
  }

  return pkt;
}

void packet_free(struct http_packet_t *pkt)
{
  free(pkt->buffer);
  free(pkt);
}

#define MAX_PACKET_SIZE (1 << 26) /* 64MiB */
ssize_t packet_expand(struct http_packet_t *pkt)
{
  size_t cur_size = pkt->buffer_capacity;
  size_t new_size = cur_size * 2;
  if (new_size > MAX_PACKET_SIZE) {
    WARN("HTTP: cannot expand packet beyond limit");
    return -1;
  }
  NOTE("HTTP: doubling packet buffer to %lu", new_size);

  uint8_t *new_buf = realloc(pkt->buffer, new_size);
  if (new_buf == NULL) {
    /* If realloc fails the original buffer is still valid */
    WARN("Failed to expand packet");
    return 0;
  }
  pkt->buffer = new_buf;
  pkt->buffer_capacity = new_size;

  size_t diff = new_size - cur_size;
  if (diff > SSIZE_MAX)
    ERR_AND_EXIT("Buffer expanded beyond sane limit");
  return (ssize_t) diff;
}
