/*
 * $Id$
 * 
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. 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.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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 <qse/http/http.h>
#include <qse/cmn/str.h>
#include <qse/cmn/chr.h>
#include <qse/cmn/htb.h>
#include "../cmn/mem-prv.h"

int qse_comparehttpversions (
	const qse_http_version_t* v1,
	const qse_http_version_t* v2)
{
	if (v1->major == v2->major) return v1->minor - v2->minor;
	return v1->major - v2->major;
}

const qse_mchar_t* qse_httpstatustombs (int code)
{
	const qse_mchar_t* msg;

	switch (code)
	{
		case 100: msg = QSE_MT("Continue"); break;
		case 101: msg = QSE_MT("Switching Protocols"); break;

		case 200: msg = QSE_MT("OK"); break;
		case 201: msg = QSE_MT("Created"); break;
		case 202: msg = QSE_MT("Accepted"); break;
		case 203: msg = QSE_MT("Non-Authoritative Information"); break;
		case 204: msg = QSE_MT("No Content"); break;
		case 205: msg = QSE_MT("Reset Content"); break;
		case 206: msg = QSE_MT("Partial Content"); break;
		
		case 300: msg = QSE_MT("Multiple Choices"); break;
		case 301: msg = QSE_MT("Moved Permanently"); break;
		case 302: msg = QSE_MT("Found"); break;
		case 303: msg = QSE_MT("See Other"); break;
		case 304: msg = QSE_MT("Not Modified"); break;
		case 305: msg = QSE_MT("Use Proxy"); break;
		case 307: msg = QSE_MT("Temporary Redirect"); break;
		case 308: msg = QSE_MT("Permanent Redirect"); break;

		case 400: msg = QSE_MT("Bad Request"); break;
		case 401: msg = QSE_MT("Unauthorized"); break;
		case 402: msg = QSE_MT("Payment Required"); break;
		case 403: msg = QSE_MT("Forbidden"); break;
		case 404: msg = QSE_MT("Not Found"); break;
		case 405: msg = QSE_MT("Method Not Allowed"); break;
		case 406: msg = QSE_MT("Not Acceptable"); break;
		case 407: msg = QSE_MT("Proxy Authentication Required"); break;
		case 408: msg = QSE_MT("Request Timeout"); break;
		case 409: msg = QSE_MT("Conflict"); break;
		case 410: msg = QSE_MT("Gone"); break;
		case 411: msg = QSE_MT("Length Required"); break;
		case 412: msg = QSE_MT("Precondition Failed"); break;
		case 413: msg = QSE_MT("Request Entity Too Large"); break;
		case 414: msg = QSE_MT("Request-URI Too Long"); break;
		case 415: msg = QSE_MT("Unsupported Media Type"); break;
		case 416: msg = QSE_MT("Requested Range Not Satisfiable"); break;
		case 417: msg = QSE_MT("Expectation Failed"); break;
		case 426: msg = QSE_MT("Upgrade Required"); break;
		case 428: msg = QSE_MT("Precondition Required"); break;
		case 429: msg = QSE_MT("Too Many Requests"); break;
		case 431: msg = QSE_MT("Request Header Fields Too Large"); break;

		case 500: msg = QSE_MT("Internal Server Error"); break;
		case 501: msg = QSE_MT("Not Implemented"); break;
		case 502: msg = QSE_MT("Bad Gateway"); break;
		case 503: msg = QSE_MT("Service Unavailable"); break;
		case 504: msg = QSE_MT("Gateway Timeout"); break;
		case 505: msg = QSE_MT("HTTP Version Not Supported"); break;

		default: msg = QSE_MT("Unknown Error"); break;
	}

	return msg;
}

const qse_mchar_t* qse_httpmethodtombs (qse_http_method_t type)
{
	/* keep this table in the same order as qse_httpd_method_t enumerators */
	static qse_mchar_t* names[]  =
	{
		QSE_MT("OTHER"),

		QSE_MT("HEAD"),
		QSE_MT("GET"),
		QSE_MT("POST"),
		QSE_MT("PUT"),
		QSE_MT("DELETE"),
		QSE_MT("OPTIONS"),
		QSE_MT("TRACE"),
		QSE_MT("CONNECT")
	}; 

	return (type < 0 || type >= QSE_COUNTOF(names))? QSE_NULL: names[type];
}

struct mtab_t
{
	const qse_mchar_t* name;
	qse_http_method_t type;
};

static struct mtab_t mtab[] =
{
	/* keep this table sorted by name for binary search */
	{ QSE_MT("CONNECT"), QSE_HTTP_CONNECT },
	{ QSE_MT("DELETE"),  QSE_HTTP_DELETE },
	{ QSE_MT("GET"),     QSE_HTTP_GET },
	{ QSE_MT("HEAD"),    QSE_HTTP_HEAD },
	{ QSE_MT("OPTIONS"), QSE_HTTP_OPTIONS },
	{ QSE_MT("POST"),    QSE_HTTP_POST },
	{ QSE_MT("PUT"),     QSE_HTTP_PUT },
	{ QSE_MT("TRACE"),   QSE_HTTP_TRACE }
};

qse_http_method_t qse_mbstohttpmethod (const qse_mchar_t* name)
{
	/* perform binary search */

	/* declaring left, right, mid to be of int is ok
	 * because we know mtab is small enough. */
	int left = 0, right = QSE_COUNTOF(mtab) - 1, mid;

	while (left <= right)
	{
		int n;
		struct mtab_t* entry;

		/*mid = (left + right) / 2;*/
		mid = left + (right - left) / 2;
		entry = &mtab[mid];

		n = qse_mbscmp (name, entry->name);
		if (n < 0) 
		{
			/* if left, right, mid were of qse_size_t,
			 * you would need the following line. 
			if (mid == 0) break;
			 */
			right = mid - 1;
		}
		else if (n > 0) left = mid + 1;
		else return entry->type;
	}

	return QSE_HTTP_OTHER;
}

qse_http_method_t qse_mcstrtohttpmethod (const qse_mcstr_t* name)
{
	/* perform binary search */

	/* declaring left, right, mid to be of int is ok
	 * because we know mtab is small enough. */
	int left = 0, right = QSE_COUNTOF(mtab) - 1, mid;

	while (left <= right)
	{
		int n;
		struct mtab_t* entry;

		/*mid = (left + right) / 2;*/
		mid = left + (right - left) / 2;
		entry = &mtab[mid];

		n = qse_mbsxcmp (name->ptr, name->len, entry->name);
		if (n < 0) 
		{
			/* if left, right, mid were of qse_size_t,
			 * you would need the following line. 
			if (mid == 0) break;
			 */
			right = mid - 1;
		}
		else if (n > 0) left = mid + 1;
		else return entry->type;
	}

	return QSE_HTTP_OTHER;
}

int qse_parsehttprange (const qse_mchar_t* str, qse_http_range_t* range)
{
	/* NOTE: this function does not support a range set 
	 *       like bytes=1-20,30-50 */

	qse_http_range_int_t from, to;
	int type = QSE_HTTP_RANGE_PROPER;

	if (str[0] != QSE_MT('b') ||
	    str[1] != QSE_MT('y') ||
	    str[2] != QSE_MT('t') ||
	    str[3] != QSE_MT('e') ||
	    str[4] != QSE_MT('s') ||
	    str[5] != QSE_MT('=')) return -1;
	
	str += 6;

	from = 0;
	if (QSE_ISMDIGIT(*str))
	{
		do
		{
			from = from * 10 + (*str - QSE_MT('0'));
			str++;
		}
		while (QSE_ISMDIGIT(*str));
	}
	else type = QSE_HTTP_RANGE_SUFFIX;

	if (*str != QSE_MT('-')) return -1;
	str++;

	if (QSE_ISMDIGIT(*str))
	{
		to = 0;
		do
		{
			to = to * 10 + (*str - QSE_MT('0'));
			str++;
		}
		while (QSE_ISMDIGIT(*str));
	}
	else to = QSE_TYPE_MAX(qse_http_range_int_t); 

	if (from > to) return -1;

	range->type = type;
	range->from = from;
	range->to = to;
	return 0;
}

typedef struct mname_t mname_t;
struct mname_t
{
	const qse_mchar_t* s;
	const qse_mchar_t* l;
};
	
static mname_t wday_name[] =
{
	{ QSE_MT("Sun"), QSE_MT("Sunday") },
	{ QSE_MT("Mon"), QSE_MT("Monday") },
	{ QSE_MT("Tue"), QSE_MT("Tuesday") },
	{ QSE_MT("Wed"), QSE_MT("Wednesday") },
	{ QSE_MT("Thu"), QSE_MT("Thursday") },
	{ QSE_MT("Fri"), QSE_MT("Friday") },
	{ QSE_MT("Sat"), QSE_MT("Saturday") }
};

static mname_t mon_name[] =
{
	{ QSE_MT("Jan"), QSE_MT("January") },
	{ QSE_MT("Feb"), QSE_MT("February") },
	{ QSE_MT("Mar"), QSE_MT("March") },
	{ QSE_MT("Apr"), QSE_MT("April") },
	{ QSE_MT("May"), QSE_MT("May") },
	{ QSE_MT("Jun"), QSE_MT("June") },
	{ QSE_MT("Jul"), QSE_MT("July") },
	{ QSE_MT("Aug"), QSE_MT("August") },
	{ QSE_MT("Sep"), QSE_MT("September") },
	{ QSE_MT("Oct"), QSE_MT("October") },
	{ QSE_MT("Nov"), QSE_MT("November") },
	{ QSE_MT("Dec"), QSE_MT("December") }
};

int qse_parsehttptime (const qse_mchar_t* str, qse_ntime_t* nt)
{
	qse_btime_t bt;
	const qse_mchar_t* word;
	qse_size_t wlen, i;

	/* TODO: support more formats */

	QSE_MEMSET (&bt, 0, QSE_SIZEOF(bt));

	/* weekday */
	while (QSE_ISMSPACE(*str)) str++;
	for (word = str; QSE_ISMALPHA(*str); str++);
	wlen = str - word;
	for (i = 0; i < QSE_COUNTOF(wday_name); i++)
	{
		if (qse_mbsxcmp (word, wlen, wday_name[i].s) == 0)
		{
			bt.wday = i;
			break;
		}
	}
	if (i >= QSE_COUNTOF(wday_name)) return -1;

	/* comma - i'm just loose as i don't care if it doesn't exist */
	while (QSE_ISMSPACE(*str)) str++;
	if (*str == QSE_MT(',')) str++;

	/* day */
	while (QSE_ISMSPACE(*str)) str++;
	if (!QSE_ISMDIGIT(*str)) return -1;
	do bt.mday = bt.mday * 10 + *str++ - QSE_MT('0'); while (QSE_ISMDIGIT(*str));

	/* month */
	while (QSE_ISMSPACE(*str)) str++;
	for (word = str; QSE_ISMALPHA(*str); str++);
	wlen = str - word;
	for (i = 0; i < QSE_COUNTOF(mon_name); i++)
	{
		if (qse_mbsxcmp (word, wlen, mon_name[i].s) == 0)
		{
			bt.mon = i;
			break;
		}
	}
	if (i >= QSE_COUNTOF(mon_name)) return -1;

	/* year */
	while (QSE_ISMSPACE(*str)) str++;
	if (!QSE_ISMDIGIT(*str)) return -1;
	do bt.year = bt.year * 10 + *str++ - QSE_MT('0'); while (QSE_ISMDIGIT(*str));
	bt.year -= QSE_BTIME_YEAR_BASE;

	/* hour */
	while (QSE_ISMSPACE(*str)) str++;
	if (!QSE_ISMDIGIT(*str)) return -1;
	do bt.hour = bt.hour * 10 + *str++ - QSE_MT('0'); while (QSE_ISMDIGIT(*str));
	if (*str != QSE_MT(':'))  return -1;
	str++;

	/* min */
	while (QSE_ISMSPACE(*str)) str++;
	if (!QSE_ISMDIGIT(*str)) return -1;
	do bt.min = bt.min * 10 + *str++ - QSE_MT('0'); while (QSE_ISMDIGIT(*str));
	if (*str != QSE_MT(':'))  return -1;
	str++;

	/* sec */
	while (QSE_ISMSPACE(*str)) str++;
	if (!QSE_ISMDIGIT(*str)) return -1;
	do bt.sec = bt.sec * 10 + *str++ - QSE_MT('0'); while (QSE_ISMDIGIT(*str));

	/* GMT */
	while (QSE_ISMSPACE(*str)) str++;
	for (word = str; QSE_ISMALPHA(*str); str++);
	wlen = str - word;
	if (qse_mbsxcmp (word, wlen, QSE_MT("GMT")) != 0) return -1;

	while (QSE_ISMSPACE(*str)) str++;
	if (*str != QSE_MT('\0')) return -1;

	return qse_timegm (&bt, nt);
}

qse_mchar_t* qse_fmthttptime (
	const qse_ntime_t* nt, qse_mchar_t* buf, qse_size_t bufsz)
{
	qse_btime_t bt;

	qse_gmtime (nt, &bt);

	qse_mbsxfmt (
		buf, bufsz,
		QSE_MT("%s, %d %s %d %02d:%02d:%02d GMT"),
		wday_name[bt.wday].s,
		bt.mday,
		mon_name[bt.mon].s,
		bt.year + QSE_BTIME_YEAR_BASE,
		bt.hour, bt.min, bt.sec
	);

	return buf;
}

int qse_isperencedhttpstr (const qse_mchar_t* str)
{
	const qse_mchar_t* p = str;

	while (*p != QSE_MT('\0'))
	{
		if (*p == QSE_MT('%') && *(p + 1) != QSE_MT('\0') && *(p + 2) != QSE_MT('\0'))
		{
			int q = QSE_MXDIGITTONUM (*(p + 1));
			if (q >= 0)
			{
				/* return true if the first valid percent-encoded sequence is found */
				int w = QSE_MXDIGITTONUM (*(p + 2));
				if (w >= 0) return 1; 
			}
		}

		p++;
	}

	return 1;
}

qse_size_t qse_perdechttpstr (const qse_mchar_t* str, qse_mchar_t* buf, qse_size_t* ndecs)
{
	const qse_mchar_t* p = str;
	qse_mchar_t* out = buf;
	qse_size_t dec_count = 0;

	while (*p != QSE_MT('\0'))
	{
		if (*p == QSE_MT('%') && *(p + 1) != QSE_MT('\0') && *(p + 2) != QSE_MT('\0'))
		{
			int q = QSE_MXDIGITTONUM (*(p + 1));
			if (q >= 0)
			{
				int w = QSE_MXDIGITTONUM (*(p + 2));
				if (w >= 0)
				{
					/* we don't care if it contains a null character */
					*out++ = ((q << 4) + w);
					p += 3;
					dec_count++;
					continue;
				}
			}
		}

		*out++ = *p++;
	}

	*out = QSE_MT('\0');
	if (ndecs) *ndecs = dec_count;
	return out - buf;
}

#define IS_UNRESERVED(c) \
	(((c) >= QSE_MT('A') && (c) <= QSE_MT('Z')) || \
	 ((c) >= QSE_MT('a') && (c) <= QSE_MT('z')) || \
	 ((c) >= QSE_MT('0') && (c) <= QSE_MT('9')) || \
	 (c) == QSE_MT('-') || (c) == QSE_MT('_') || \
	 (c) == QSE_MT('.') || (c) == QSE_MT('~'))

#define TO_HEX(v) (QSE_MT("0123456789ABCDEF")[(v) & 15])

qse_size_t qse_perenchttpstr (int opt, const qse_mchar_t* str, qse_mchar_t* buf, qse_size_t* nencs)
{
	const qse_mchar_t* p = str;
	qse_mchar_t* out = buf;
	qse_size_t enc_count = 0;

	/* this function doesn't accept the size of the buffer. the caller must 
	 * ensure that the buffer is large enough */

	if (opt & QSE_PERENCHTTPSTR_KEEP_SLASH)
	{
		while (*p != QSE_MT('\0'))
		{
			if (IS_UNRESERVED(*p) || *p == QSE_MT('/')) *out++ = *p;
			else
			{
				*out++ = QSE_MT('%');
				*out++ = TO_HEX (*p >> 4);
				*out++ = TO_HEX (*p & 15);
				enc_count++;
			}
			p++;
		}
	}
	else
	{
		while (*p != QSE_MT('\0'))
		{
			if (IS_UNRESERVED(*p)) *out++ = *p;
			else
			{
				*out++ = QSE_MT('%');
				*out++ = TO_HEX (*p >> 4);
				*out++ = TO_HEX (*p & 15);
				enc_count++;
			}
			p++;
		}
	}
	*out = QSE_MT('\0');
	if (nencs) *nencs = enc_count;
	return out - buf;
}

qse_mchar_t* qse_perenchttpstrdup (int opt, const qse_mchar_t* str, qse_mmgr_t* mmgr)
{
	qse_mchar_t* buf;
	qse_size_t len = 0;
	qse_size_t count = 0;
	
	/* count the number of characters that should be encoded */
	if (opt & QSE_PERENCHTTPSTR_KEEP_SLASH)
	{
		for (len = 0; str[len] != QSE_MT('\0'); len++)
		{
			if (!IS_UNRESERVED(str[len]) && str[len] != QSE_MT('/')) count++;
		}
	}
	else
	{
		for (len = 0; str[len] != QSE_MT('\0'); len++)
		{
			if (!IS_UNRESERVED(str[len])) count++;
		}
	}

	/* if there are no characters to escape, just return the original string */
	if (count <= 0) return (qse_mchar_t*)str;

	/* allocate a buffer of an optimal size for escaping, otherwise */
	buf = QSE_MMGR_ALLOC (mmgr, (len  + (count * 2) + 1)  * QSE_SIZEOF(*buf));
	if (buf == QSE_NULL) return QSE_NULL;

	/* perform actual escaping */
	qse_perenchttpstr (opt, str, buf, QSE_NULL);

	return buf;
}
