/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * 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)) {
    const SOCKET_APP_TYPE sat = get_ioa_socket_app_type(s);
    if ((sat == HTTP_CLIENT_SOCKET) || (sat == HTTPS_CLIENT_SOCKET)) {
      char content_http[1025];
      const char *const title = "TURN Server";
      const int content_len = snprintf(
          content_http, sizeof(content_http),
          "<!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);
      if (content_len < 0) {
        return;
      }

      ioa_network_buffer_handle nbh_http = ioa_network_buffer_allocate(s->e);
      char *data = (char *)ioa_network_buffer_data(nbh_http);
      size_t cap = ioa_network_buffer_get_capacity(nbh_http);
      const int response_len =
          snprintf(data, cap,
                   "HTTP/1.0 200 OK\r\nServer: %s\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: "
                   "%d\r\n\r\n%s",
                   TURN_SOFTWARE, content_len, content_http);
      if (response_len < 0) {
        ioa_network_buffer_delete(s->e, nbh_http);
        return;
      }
      size_t len = (size_t)response_len;
      if (len >= cap) {
        len = cap - 1;
      }
      ioa_network_buffer_set_size(nbh_http, len);
      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"};

  const 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 void free_headers_list(struct headers_list *h);

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(1, sizeof(struct headers_list));
      if (list == NULL) {
        free(post_data);
        return NULL;
      }
      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++;
          }
          char **new_keys = (char **)realloc(list->keys, sizeof(char *) * (list->n + 1));
          char **new_values = (char **)realloc(list->values, sizeof(char *) * (list->n + 1));
          char *new_key = strdup(key);
          if (new_keys == NULL || new_values == NULL || new_key == NULL) {
            free(new_key);
            if (new_keys) {
              list->keys = new_keys;
            }
            if (new_values) {
              list->values = new_values;
            }
            free(value);
            free_headers_list(list);
            free(post_data);
            return NULL;
          }
          list->keys = new_keys;
          list->values = new_values;
          list->keys[list->n] = new_key;
          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(1, sizeof(struct evkeyvalq));
          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(1, sizeof(struct http_headers));
            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(1, sizeof(struct http_headers));
            }
            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(1, sizeof(struct http_request));

    if (ret == NULL) {
      TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: failure in call to calloc \n", __FUNCTION__);
      return NULL;
    }

    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);
  }
}
