/*
 * Copyright (C) 2011, 2012, 2013, 2014 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ns_ioalib_impl.h"

#include "http_server.h"

#include <event2/http.h>
#include <event2/keyvalq_struct.h>

#include <time.h>

//////////////////////////////////////

struct headers_list {
  size_t n;
  char **keys;
  char **values;
};

struct http_headers {
  struct evkeyvalq *uri_headers;
  struct headers_list *post_headers;
};

//////////////////////////////////////

static void write_http_echo(ioa_socket_handle s) {
  if (s && !ioa_socket_tobeclosed(s)) {
    SOCKET_APP_TYPE sat = get_ioa_socket_app_type(s);
    if ((sat == HTTP_CLIENT_SOCKET) || (sat == HTTPS_CLIENT_SOCKET)) {
      ioa_network_buffer_handle nbh_http = ioa_network_buffer_allocate(s->e);
      size_t len_http = ioa_network_buffer_get_size(nbh_http);
      uint8_t *data = ioa_network_buffer_data(nbh_http);
      char data_http[1025];
      char content_http[1025];
      const char *title = "TURN Server";
      snprintf(content_http, sizeof(content_http) - 1,
               "<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <title>%s</title>\r\n  </head>\r\n  <body>\r\n    "
               "<b>%s</b> <br> <b><i>use https connection for the admin session</i></b>\r\n  </body>\r\n</html>\r\n",
               title, title);
      snprintf(
          data_http, sizeof(data_http) - 1,
          "HTTP/1.0 200 OK\r\nServer: %s\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: %d\r\n\r\n%.906s",
          TURN_SOFTWARE, (int)strlen(content_http), content_http);
      len_http = strlen(data_http);
      memcpy(data, data_http, len_http);
      ioa_network_buffer_set_size(nbh_http, len_http);
      send_data_from_ioa_socket_nbh(s, NULL, nbh_http, TTL_IGNORE, TOS_IGNORE, NULL);
    }
  }
}

void handle_http_echo(ioa_socket_handle s) { write_http_echo(s); }

const char *get_http_date_header(void) {
  static char buffer_date[256];
  static char buffer_header[1025];
  static const char *wds[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  static const char *mons[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

  time_t now = time(NULL);
  struct tm *gmtm = gmtime(&now);

  buffer_header[0] = 0;
  buffer_date[0] = 0;
  if (gmtm) {
    snprintf(buffer_date, sizeof(buffer_date) - 1, "%s, %d %s %d %d:%d:%d GMT", wds[gmtm->tm_wday], gmtm->tm_mday,
             mons[gmtm->tm_mon], gmtm->tm_year + 1900, gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec);
    buffer_date[sizeof(buffer_date) - 1] = 0;
    snprintf(buffer_header, sizeof(buffer_header) - 1, "Date: %s\r\n", buffer_date);
    buffer_header[sizeof(buffer_header) - 1] = 0;
  }

  return buffer_header;
}

///////////////////////////////////////////////

static struct headers_list *post_parse(char *data, size_t data_len) {
  while ((*data == '\r') || (*data == '\n')) {
    ++data;
    --data_len;
  }
  if (data_len) {
    char *post_data = (char *)calloc(data_len + 1, sizeof(char));
    if (post_data != NULL) {
      memcpy(post_data, data, data_len);
      char *fmarker = NULL;
      char *fsplit = strtok_r(post_data, "&", &fmarker);
      struct headers_list *list = (struct headers_list *)calloc(sizeof(struct headers_list), 1);
      while (fsplit != NULL) {
        char *vmarker = NULL;
        char *key = strtok_r(fsplit, "=", &vmarker);
        if (key == NULL) {
          break;
        } else {
          char *value = strtok_r(NULL, "=", &vmarker);
          char empty[1];
          empty[0] = 0;
          value = value ? value : empty;
          value = evhttp_decode_uri(value);
          char *p = value;
          while (*p) {
            if (*p == '+') {
              *p = ' ';
            }
            p++;
          }
          list->keys = (char **)realloc(list->keys, sizeof(char *) * (list->n + 1));
          list->keys[list->n] = strdup(key);
          list->values = (char **)realloc(list->values, sizeof(char *) * (list->n + 1));
          list->values[list->n] = value;
          ++(list->n);
          fsplit = strtok_r(NULL, "&", &fmarker);
        }
      }
      free(post_data);
      return list;
    }
  }
  return NULL;
}

static struct http_request *parse_http_request_1(struct http_request *ret, char *request, int parse_post) {

  if (ret && request) {

    char *s = strstr(request, " HTTP/");
    if (!s) {
      free(ret);
      ret = NULL;
    } else {
      *s = 0;

      struct evhttp_uri *uri = evhttp_uri_parse(request);
      if (!uri) {
        free(ret);
        ret = NULL;
      } else {

        const char *query = evhttp_uri_get_query(uri);
        if (query) {
          struct evkeyvalq *kv = (struct evkeyvalq *)calloc(sizeof(struct evkeyvalq), 1);
          if (evhttp_parse_query_str(query, kv) < 0) {
            free(ret);
            ret = NULL;
            if (kv) {
              // kv no longer assigned on this path
              free(kv);
            }
          } else {
            ret->headers = (struct http_headers *)calloc(sizeof(struct http_headers), 1);
            ret->headers->uri_headers = kv;
          }
        }

        const char *path = evhttp_uri_get_path(uri);
        if (path && ret) {
          ret->path = strdup(path);
        }

        evhttp_uri_free(uri);

        if (parse_post && ret) {
          char *body = strstr(s + 1, "\r\n\r\n");
          if (body && body[0]) {
            if (!ret->headers) {
              ret->headers = (struct http_headers *)calloc(sizeof(struct http_headers), 1);
            }
            ret->headers->post_headers = post_parse(body, strlen(body));
          }
        }
      }

      *s = ' ';
    }
  }

  return ret;
}

struct http_request *parse_http_request(char *request) {

  struct http_request *ret = NULL;

  if (request) {

    ret = (struct http_request *)calloc(sizeof(struct http_request), 1);

    if (strstr(request, "GET ") == request) {
      ret->rtype = HRT_GET;
      ret = parse_http_request_1(ret, request + 4, 0);
    } else if (strstr(request, "HEAD ") == request) {
      ret->rtype = HRT_HEAD;
      ret = parse_http_request_1(ret, request + 5, 0);
    } else if (strstr(request, "POST ") == request) {
      ret->rtype = HRT_POST;
      ret = parse_http_request_1(ret, request + 5, 1);
    } else if (strstr(request, "PUT ") == request) {
      ret->rtype = HRT_PUT;
      ret = parse_http_request_1(ret, request + 4, 1);
    } else if (strstr(request, "DELETE ") == request) {
      ret->rtype = HRT_DELETE;
      ret = parse_http_request_1(ret, request + 7, 1);
    } else {
      free(ret);
      ret = NULL;
    }
  }

  return ret;
}

static const char *get_headers_list_value(struct headers_list *h, const char *key) {
  const char *ret = NULL;
  if (h && h->keys && h->values && key && key[0]) {
    size_t i = 0;
    for (i = 0; i < h->n; ++i) {
      if (h->keys[i] && !strcmp(key, h->keys[i]) && h->values[i]) {
        ret = h->values[i];
        break;
      }
    }
  }
  return ret;
}

static void free_headers_list(struct headers_list *h) {
  if (h) {
    if (h->keys) {
      size_t i = 0;
      for (i = 0; i < h->n; ++i) {
        if (h->keys[i]) {
          free(h->keys[i]);
          h->keys[i] = NULL;
        }
      }
      free(h->keys);
      h->keys = NULL;
    }
    if (h->values) {
      size_t i = 0;
      for (i = 0; i < h->n; ++i) {
        if (h->values[i]) {
          free(h->values[i]);
          h->values[i] = NULL;
        }
      }
      free(h->values);
      h->values = NULL;
    }
    h->n = 0;
    free(h);
  }
}

const char *get_http_header_value(const struct http_request *request, const char *key, const char *default_value) {
  const char *ret = NULL;
  if (key && key[0] && request && request->headers) {
    if (request->headers->uri_headers) {
      ret = evhttp_find_header(request->headers->uri_headers, key);
    }
    if (!ret && request->headers->post_headers) {
      ret = get_headers_list_value(request->headers->post_headers, key);
    }
  }
  if (!ret) {
    ret = default_value;
  }
  return ret;
}

void free_http_request(struct http_request *request) {
  if (request) {
    if (request->path) {
      free(request->path);
      request->path = NULL;
    }
    if (request->headers) {
      if (request->headers->uri_headers) {
        evhttp_clear_headers(request->headers->uri_headers);
        free(request->headers->uri_headers);
        request->headers->uri_headers = NULL;
      }
      if (request->headers->post_headers) {
        free_headers_list(request->headers->post_headers);
        request->headers->post_headers = NULL;
      }
      free(request->headers);
      request->headers = NULL;
    }
    free(request);
  }
}

////////////////////////////////////////////

struct str_buffer {
  size_t capacity;
  size_t sz;
  char *buffer;
};

struct str_buffer *str_buffer_new(void) {
  struct str_buffer *ret = (struct str_buffer *)calloc(sizeof(struct str_buffer), 1);
  if (!ret) {
    return NULL;
  }
  ret->buffer = (char *)malloc(1);
  if (!(ret->buffer)) {
    free(ret);
    return NULL;
  }
  ret->buffer[0] = 0;
  ret->capacity = 1;
  return ret;
}

void str_buffer_append(struct str_buffer *sb, const char *str) {
  if (sb && str && str[0]) {
    size_t len = strlen(str);
    while (sb->sz + len + 1 > sb->capacity) {
      sb->capacity += len + 1024;
      sb->buffer = (char *)realloc(sb->buffer, sb->capacity);
    }
    memcpy(sb->buffer + sb->sz, str, len + 1);
    sb->sz += len;
  }
}

void str_buffer_append_sz(struct str_buffer *sb, size_t sz) {
  char ssz[129];
  snprintf(ssz, sizeof(ssz) - 1, "%lu", (unsigned long)sz);
  str_buffer_append(sb, ssz);
}

void str_buffer_append_sid(struct str_buffer *sb, turnsession_id sid) {
  char ssz[129];
  snprintf(ssz, sizeof(ssz) - 1, "%018llu", (unsigned long long)sid);
  str_buffer_append(sb, ssz);
}

const char *str_buffer_get_str(const struct str_buffer *sb) {
  if (sb) {
    return sb->buffer;
  }
  return NULL;
}

size_t str_buffer_get_str_len(const struct str_buffer *sb) {
  if (sb) {
    return sb->sz;
  }
  return 0;
}

void str_buffer_free(struct str_buffer *sb) {
  if (sb) {
    free(sb->buffer);
    free(sb);
  }
}

///////////////////////////////////////////////
