/* Copyright (c) 2013-2014 the Civetweb developers
 * Copyright (c) 2013 No Face Press, LLC
 *
 * License http://opensource.org/licenses/mit-license.php MIT License
 */

#include "CivetServer.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdexcept>

#ifndef UNUSED_PARAMETER
#define UNUSED_PARAMETER(x) (void)(x)
#endif

bool
CivetHandler::handleGet(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handlePost(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handleHead(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handlePut(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handlePatch(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handleDelete(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetHandler::handleOptions(CivetServer *server, struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return false;
}

bool
CivetWebSocketHandler::handleConnection(CivetServer *server,
                                        const struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return true;
}

void
CivetWebSocketHandler::handleReadyState(CivetServer *server,
                                        struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return;
}

bool
CivetWebSocketHandler::handleData(CivetServer *server,
                                  struct mg_connection *conn,
                                  int bits,
                                  char *data,
                                  size_t data_len)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	UNUSED_PARAMETER(bits);
	UNUSED_PARAMETER(data);
	UNUSED_PARAMETER(data_len);
	return true;
}

void
CivetWebSocketHandler::handleClose(CivetServer *server,
                                   const struct mg_connection *conn)
{
	UNUSED_PARAMETER(server);
	UNUSED_PARAMETER(conn);
	return;
}

int
CivetServer::requestHandler(struct mg_connection *conn, void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return 0;

	mg_lock_context(me->context);
	me->connections[conn] = CivetConnection();
	mg_unlock_context(me->context);

	CivetHandler *handler = (CivetHandler *)cbdata;

	if (handler) {
		if (strcmp(request_info->request_method, "GET") == 0) {
			return handler->handleGet(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "POST") == 0) {
			return handler->handlePost(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "HEAD") == 0) {
			return handler->handleHead(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "PUT") == 0) {
			return handler->handlePut(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "DELETE") == 0) {
			return handler->handleDelete(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "OPTIONS") == 0) {
			return handler->handleOptions(me, conn) ? 1 : 0;
		} else if (strcmp(request_info->request_method, "PATCH") == 0) {
			return handler->handlePatch(me, conn) ? 1 : 0;
		}
	}

	return 0; // No handler found
}

int
CivetServer::authHandler(struct mg_connection *conn, void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return 0;

	mg_lock_context(me->context);
	me->connections[conn] = CivetConnection();
	mg_unlock_context(me->context);

	CivetAuthHandler *handler = (CivetAuthHandler *)cbdata;

	if (handler) {
		return handler->authorize(me, conn) ? 1 : 0;
	}

	return 0; // No handler found
}

int
CivetServer::webSocketConnectionHandler(const struct mg_connection *conn,
                                        void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return 0;

	CivetWebSocketHandler *handler = (CivetWebSocketHandler *)cbdata;

	if (handler) {
		return handler->handleConnection(me, conn) ? 0 : 1;
	}

	return 1; // No handler found, close connection
}

void
CivetServer::webSocketReadyHandler(struct mg_connection *conn, void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return;

	CivetWebSocketHandler *handler = (CivetWebSocketHandler *)cbdata;

	if (handler) {
		handler->handleReadyState(me, conn);
	}
}

int
CivetServer::webSocketDataHandler(struct mg_connection *conn,
                                  int bits,
                                  char *data,
                                  size_t data_len,
                                  void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return 0;

	CivetWebSocketHandler *handler = (CivetWebSocketHandler *)cbdata;

	if (handler) {
		return handler->handleData(me, conn, bits, data, data_len) ? 1 : 0;
	}

	return 1; // No handler found
}

void
CivetServer::webSocketCloseHandler(const struct mg_connection *conn,
                                   void *cbdata)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return;

	CivetWebSocketHandler *handler = (CivetWebSocketHandler *)cbdata;

	if (handler) {
		handler->handleClose(me, conn);
	}
}

CivetCallbacks::CivetCallbacks()
{
	memset(this, 0, sizeof(*this));
}

CivetServer::CivetServer(const char **options,
                         const struct CivetCallbacks *_callbacks)
    : context(0)
{
	struct CivetCallbacks callbacks;

	if (_callbacks) {
		callbacks = *_callbacks;
		userCloseHandler = _callbacks->connection_close;
	} else {
		userCloseHandler = NULL;
	}
	callbacks.connection_close = closeHandler;
	context = mg_start(&callbacks, this, options);
	if (context == NULL)
		throw CivetException("null context when constructing CivetServer. "
		                     "Possible problem binding to port.");
}

CivetServer::CivetServer(std::vector<std::string> options,
                         const struct CivetCallbacks *_callbacks)
    : context(0)
{
	struct CivetCallbacks callbacks;

	if (_callbacks) {
		callbacks = *_callbacks;
		userCloseHandler = _callbacks->connection_close;
	} else {
		userCloseHandler = NULL;
	}
	callbacks.connection_close = closeHandler;

	std::vector<const char *> pointers(options.size());
	for (size_t i = 0; i < options.size(); i++) {
		pointers[i] = (options[i].c_str());
	}
	pointers.push_back(0);

	context = mg_start(&callbacks, this, &pointers[0]);
	if (context == NULL)
		throw CivetException("null context when constructing CivetServer. "
		                     "Possible problem binding to port.");
}

CivetServer::~CivetServer()
{
	close();
}

void
CivetServer::closeHandler(const struct mg_connection *conn)
{
	const struct mg_request_info *request_info = mg_get_request_info(conn);
	assert(request_info != NULL);
	CivetServer *me = (CivetServer *)(request_info->user_data);
	assert(me != NULL);

	// Happens when a request hits the server before the context is saved
	if (me->context == NULL)
		return;

	if (me->userCloseHandler)
		me->userCloseHandler(conn);
	mg_lock_context(me->context);
	me->connections.erase(const_cast<struct mg_connection *>(conn));
	mg_unlock_context(me->context);
}

void
CivetServer::addHandler(const std::string &uri, CivetHandler *handler)
{
	mg_set_request_handler(context, uri.c_str(), requestHandler, handler);
}

void
CivetServer::addWebSocketHandler(const std::string &uri,
                                 CivetWebSocketHandler *handler)
{
	mg_set_websocket_handler(context,
	                         uri.c_str(),
	                         webSocketConnectionHandler,
	                         webSocketReadyHandler,
	                         webSocketDataHandler,
	                         webSocketCloseHandler,
	                         handler);
}

void
CivetServer::addAuthHandler(const std::string &uri, CivetAuthHandler *handler)
{
	mg_set_auth_handler(context, uri.c_str(), authHandler, handler);
}

void
CivetServer::removeHandler(const std::string &uri)
{
	mg_set_request_handler(context, uri.c_str(), NULL, NULL);
}

void
CivetServer::removeWebSocketHandler(const std::string &uri)
{
	mg_set_websocket_handler(
	    context, uri.c_str(), NULL, NULL, NULL, NULL, NULL);
}

void
CivetServer::removeAuthHandler(const std::string &uri)
{
	mg_set_auth_handler(context, uri.c_str(), NULL, NULL);
}

void
CivetServer::close()
{
	if (context) {
		mg_stop(context);
		context = 0;
	}
}

int
CivetServer::getCookie(struct mg_connection *conn,
                       const std::string &cookieName,
                       std::string &cookieValue)
{
	// Maximum cookie length as per microsoft is 4096.
	// http://msdn.microsoft.com/en-us/library/ms178194.aspx
	char _cookieValue[4096];
	const char *cookie = mg_get_header(conn, "Cookie");
	int lRead = mg_get_cookie(cookie,
	                          cookieName.c_str(),
	                          _cookieValue,
	                          sizeof(_cookieValue));
	cookieValue.clear();
	cookieValue.append(_cookieValue);
	return lRead;
}

const char *
CivetServer::getHeader(struct mg_connection *conn,
                       const std::string &headerName)
{
	return mg_get_header(conn, headerName.c_str());
}

void
CivetServer::urlDecode(const char *src,
                       std::string &dst,
                       bool is_form_url_encoded)
{
	urlDecode(src, strlen(src), dst, is_form_url_encoded);
}

void
CivetServer::urlDecode(const char *src,
                       size_t src_len,
                       std::string &dst,
                       bool is_form_url_encoded)
{
	int i, j, a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')

	dst.clear();
	for (i = j = 0; i < (int)src_len; i++, j++) {
		if (i < (int)src_len - 2 && src[i] == '%'
		    && isxdigit(*(const unsigned char *)(src + i + 1))
		    && isxdigit(*(const unsigned char *)(src + i + 2))) {
			a = tolower(*(const unsigned char *)(src + i + 1));
			b = tolower(*(const unsigned char *)(src + i + 2));
			dst.push_back((char)((HEXTOI(a) << 4) | HEXTOI(b)));
			i += 2;
		} else if (is_form_url_encoded && src[i] == '+') {
			dst.push_back(' ');
		} else {
			dst.push_back(src[i]);
		}
	}
}

bool
CivetServer::getParam(struct mg_connection *conn,
                      const char *name,
                      std::string &dst,
                      size_t occurrence)
{
	const char *formParams = NULL;
	const struct mg_request_info *ri = mg_get_request_info(conn);
	assert(ri != NULL);
	CivetServer *me = (CivetServer *)(ri->user_data);
	assert(me != NULL);
	mg_lock_context(me->context);
	CivetConnection &conobj = me->connections[conn];
	mg_lock_connection(conn);
	mg_unlock_context(me->context);

	if (conobj.postData != NULL) {
		formParams = conobj.postData;
	} else {
		const char *con_len_str = mg_get_header(conn, "Content-Length");
		if (con_len_str) {
			unsigned long con_len = atoi(con_len_str);
			if (con_len > 0) {
				// Add one extra character: in case the post-data is a text, it
				// is required as 0-termination.
				// Do not increment con_len, since the 0 terminating is not part
				// of the content (text or binary).
				conobj.postData = (char *)malloc(con_len + 1);
				if (conobj.postData != NULL) {
					// malloc may fail for huge requests
					mg_read(conn, conobj.postData, con_len);
					conobj.postData[con_len] = 0;
					formParams = conobj.postData;
					conobj.postDataLen = con_len;
				}
			}
		}
	}
	if (formParams == NULL) {
		// get requests do store html <form> field values in the http
		// query_string
		formParams = ri->query_string;
	}
	mg_unlock_connection(conn);

	if (formParams != NULL) {
		return getParam(formParams, strlen(formParams), name, dst, occurrence);
	}

	return false;
}

bool
CivetServer::getParam(const char *data,
                      size_t data_len,
                      const char *name,
                      std::string &dst,
                      size_t occurrence)
{
	const char *p, *e, *s;
	size_t name_len;

	dst.clear();
	if (data == NULL || name == NULL || data_len == 0) {
		return false;
	}
	name_len = strlen(name);
	e = data + data_len;

	// data is "var1=val1&var2=val2...". Find variable first
	for (p = data; p + name_len < e; p++) {
		if ((p == data || p[-1] == '&') && p[name_len] == '='
		    && !mg_strncasecmp(name, p, name_len) && 0 == occurrence--) {

			// Point p to variable value
			p += name_len + 1;

			// Point s to the end of the value
			s = (const char *)memchr(p, '&', (size_t)(e - p));
			if (s == NULL) {
				s = e;
			}
			assert(s >= p);

			// Decode variable into destination buffer
			urlDecode(p, (int)(s - p), dst, true);
			return true;
		}
	}
	return false;
}

void
CivetServer::urlEncode(const char *src, std::string &dst, bool append)
{
	urlEncode(src, strlen(src), dst, append);
}

void
CivetServer::urlEncode(const char *src,
                       size_t src_len,
                       std::string &dst,
                       bool append)
{
	static const char *dont_escape = "._-$,;~()";
	static const char *hex = "0123456789abcdef";

	if (!append)
		dst.clear();

	for (; src_len > 0; src++, src_len--) {
		if (isalnum(*(const unsigned char *)src)
		    || strchr(dont_escape, *(const unsigned char *)src) != NULL) {
			dst.push_back(*src);
		} else {
			dst.push_back('%');
			dst.push_back(hex[(*(const unsigned char *)src) >> 4]);
			dst.push_back(hex[(*(const unsigned char *)src) & 0xf]);
		}
	}
}

std::vector<int>
CivetServer::getListeningPorts()
{
	std::vector<int> ports(10);
	std::vector<int> ssl(10);
	size_t size = mg_get_ports(context, ports.size(), &ports[0], &ssl[0]);
	ports.resize(size);
	ssl.resize(size);
	return ports;
}

CivetServer::CivetConnection::CivetConnection()
{
	postData = NULL;
	postDataLen = 0;
}

CivetServer::CivetConnection::~CivetConnection()
{
	free(postData);
}
