/*

http.c - HTTP service

*/

/*...sincludes:0:*/
#include <stdio.h>
#include <string.h>
#include "types.h"
#include "fiber.h"
#include "cpmio.h"
#include "net.h"
#include "http.h"

/*...vtypes\46\h:0:*/
/*...vfiber\46\h:0:*/
/*...vcpmio\46\h:0:*/
/*...vnet\46\h:0:*/
/*...vhttp\46\h:0:*/
/*...e*/

/*...shttpline_\42\:0:*/
static void httpline_complete(HTTPLINE *httpline, byte state)
	{
	httpline->state = state;
	fiber_enqueue(&(httpline->fiber));
	}

static void httpline_recv_char(HTTPLINE *httpline);

static void httpline_rcvd(HTTPLINE *httpline)
	{
	SOCKET *socket = httpline->socket;
	if ( socket->state == SS_ERROR )
		httpline_complete(httpline, HTTPLINE_STATE_ERROR);
	else
		{
		byte n = ++(httpline->actual);
		if ( n >= 2                     &&
		     httpline->buf[n-2] == '\r' &&
		     httpline->buf[n-1] == '\n' )
			httpline_complete(httpline, HTTPLINE_STATE_OK);
		else if ( n == httpline->length )
			httpline_complete(httpline, HTTPLINE_STATE_TOO_LONG);
		else
			httpline_recv_char(httpline);
		}
	}

static void httpline_recv_char(HTTPLINE *httpline)
	{
	SOCKET *socket = httpline->socket;
	net_recv(socket, httpline->buf+httpline->actual, 1, (NF)httpline_rcvd, httpline);
	}

static void httpline_recv(HTTPLINE *httpline, SOCKET *socket, byte *buf, byte length, NF nf, void *nfp)
	{
	httpline->socket    = socket;
	httpline->buf       = buf;
	httpline->length    = length;
	httpline->fiber.nf  = nf;
	httpline->fiber.nfp = nfp;
	httpline->actual    = 0;
	httpline_recv_char(httpline);
	}
/*...e*/
/*...shttpblock_\42\:0:*/
static void httpblock_complete(HTTPBLOCK *httpblock, byte state)
	{
	httpblock->state = state;
	fiber_enqueue(&(httpblock->fiber));
	}

static void httpblock_recv_more(HTTPBLOCK *httpblock);

static void httpblock_rcvd(HTTPBLOCK *httpblock)
	{
	SOCKET *socket = httpblock->socket;
	if ( socket->state == SS_ERROR )
		httpblock_complete(httpblock, HTTPBLOCK_STATE_ERROR);
	else
		{
		httpblock->actual += socket->actual;
		if ( httpblock->actual == httpblock->length )
			httpblock_complete(httpblock, HTTPBLOCK_STATE_OK);
		else
			httpblock_recv_more(httpblock);
		}
	}

static void httpblock_recv_more(HTTPBLOCK *httpblock)
	{
	SOCKET *socket = httpblock->socket;
	net_recv(socket, httpblock->buf+httpblock->actual, httpblock->length-httpblock->actual, (NF)httpblock_rcvd, httpblock);
	}

static void httpblock_recv(HTTPBLOCK *httpblock, SOCKET *socket, byte *buf, byte length, NF nf, void *nfp)
	{
	httpblock->socket    = socket;
	httpblock->buf       = buf;
	httpblock->length    = length;
	httpblock->fiber.nf  = nf;
	httpblock->fiber.nfp = nfp;
	httpblock->actual    = 0;
	httpblock_recv_more(httpblock);
	}
/*...e*/

#define	CTRL_Z 26

/*...shttp_copy:0:*/
static byte *http_copy(byte *dest, const byte *src)
	{
	while ( *src )
		*dest++ = *src++;
	return dest;
	}
/*...e*/
/*...shttp_disconnect:0:*/
static void http_listen(HTTP *http);

static void http_disconnect(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	bdos_printf("http_disconnect:\r\n$");
	net_disconnect(socket, (NF)http_listen, http);
	}		
/*...e*/
/*...shttp_close_and_disconnect:0:*/
static void http_close_and_disconnect(HTTP *http)
	{
	bdos_close_file(&(http->fcb));
	http_disconnect(http);
	}
/*...e*/
/*...shttp_send_ok_response:0:*/
static void http_send_ok_response(
	HTTP *http,
	const byte *mime,
	NF nf
	)
	{
	SOCKET *socket = http->socket_alloc.socket;
	byte *p = http->buffer;
	bdos_printf("http_send_ok_response:\r\n$");
	p = http_copy(p, "HTTP/1.1 200 OK\r\n");
	if ( mime != NULL )
		{
		p = http_copy(p, "Content-type: ");
		p = http_copy(p, mime);
		p = http_copy(p, "\r\n");
		}
	p = http_copy(p, "\r\n");
	net_send(socket, http->buffer, p-http->buffer, nf, http);
	}
/*...e*/
/*...shttp_send_error_response:0:*/
static void http_send_error_response(
	HTTP *http,
	const byte *status_code,
	const byte *status_text,
	NF nf
	)
	{
	SOCKET *socket = http->socket_alloc.socket;
	byte *p = http->buffer;
	byte s[4];
	bdos_printf("http_send_error_response: $");
	s[0] = status_code[0];
	s[1] = status_code[1];
	s[2] = status_code[2];
	s[3] = '$';
	bdos_printf(s);
	bdos_printf("\r\n$");
	p = http_copy(p, "HTTP/1.1 ");
	p = http_copy(p, status_code);
	p = http_copy(p, " ");
	p = http_copy(p, status_text);
	p = http_copy(p, "\r\n\r\n");
	p = http_copy(p, "<H1>Error ");
	p = http_copy(p, status_code);
	p = http_copy(p, "</H1>\r\n<P>");
	p = http_copy(p, status_text);
	p = http_copy(p, "\r\n");
	net_send(socket, http->buffer, p-http->buffer, nf, http);
	}
/*...e*/
/*...shttp_send_block:0:*/
static void http_send_block(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	if ( socket->state == SS_ERROR )
		http_close_and_disconnect(http);
	else
		{
		bdos_set_dma_address(http->buffer);
		if ( bdos_read_file(&(http->fcb)) != 0 )
			/* EOF or error */
			http_close_and_disconnect(http);
		else
			/* read OK */
			{
			byte len;
			if ( http->textual )
				for ( len = 0; len < 128 && http->buffer[len] != CTRL_Z; len++ )
					;
			else
				len = 128;
			if ( len == 128 )
				net_send(socket, http->buffer, len, (NF)http_send_block, http);
			else
				net_send(socket, http->buffer, len, (NF)http_close_and_disconnect, http);
			}
		}
	}
/*...e*/
/*...shttp_do_get:0:*/
/*...shttp_mime_of:0:*/
typedef struct
	{
	const byte *type;
	const byte *mime;
	} MIME_OF;

static MIME_OF mime_ofs[] =
	{
	{"HTM","text/html"},
	{"CSS","text/css"},
	{"JS ","text/javascript"},
	{"TXT","text/plain"},
	{"SUB","text/plain"},
	{"DOC","text/plain"},
	{"XML","text/xml"},
	{"GIF","image/gif"},
	{"PNG","image/png"},
	{"JPG","image/jpeg"},
	{"TIF","image/tiff"},
	{"PDF","application/pdf"},
	{"COM","application/octet-stream"},
	{"OVR","application/octet-stream"},
	{"MTX","application/octet-stream"},
	{"RUN","application/octet-stream"},
	{"BAS","application/octet-stream"},
	{"ZIP","application/x-zip-compressed"},
	};

static byte *http_mime_of(byte *type)
	{
	byte i;
	for ( i = 0; i < sizeof(mime_ofs)/sizeof(mime_ofs[0]); i++ )
		{
		MIME_OF *mo = &(mime_ofs[i]);
		if ( type[0] == mo->type[0] &&
		     type[1] == mo->type[1] &&
		     type[2] == mo->type[2] )
			return mo->mime;
		}
	return "text/plain";
	}
/*...e*/
/*...shttp_mime_is_textual:0:*/
static BOOLEAN http_mime_is_textual(byte *mime)
	{
	return mime[0] == 't';
	}
/*...e*/

static void http_do_get(HTTP *http)
	{
	bdos_printf("http_do_get:\r\n$");
	http->fcb.ex = 0;
	http->fcb.s1 = 0;
	http->fcb.s2 = 0;
	http->fcb.rc = 0;
	if ( bdos_open_file(&(http->fcb)) == 255 )
		http_send_error_response(http, "404", "File Not Found", (NF)http_disconnect);
	else
		{
		byte *mime = http_mime_of(http->fcb.type);
		http->textual = http_mime_is_textual(mime);
		http->fcb.ex = 0;
		http->fcb.cr = 0;
		http_send_ok_response(http, mime, (NF)http_send_block);
		}
	}
/*...e*/
/*...shttp_rcvd_block:0:*/
static void http_recv_block(HTTP *http);

static void http_rcvd_block(HTTP *http)
	{
	byte i;
	HTTPBLOCK *httpblock = &(http->httpblock);
	for ( i = httpblock->actual; i < 128; i++ )
		http->buffer[i] = CTRL_Z;
	bdos_set_dma_address(http->buffer);
	if ( bdos_write_file(&(http->fcb)) != 0 )
		http_send_error_response(http, "500", "Internal Server Error - unable to write file", (NF)http_close_and_disconnect);
	else if ( httpblock->state != HTTPBLOCK_STATE_OK )
		http_close_and_disconnect(http);
	else
		{
		http->content_length -= httpblock->actual;
		http_recv_block(http);
		}
	}
/*...e*/
/*...shttp_recv_block:0:*/
static void http_recv_block(HTTP *http)
	{
	if ( http->content_length == 0 )
		http_send_ok_response(http, NULL, (NF)http_close_and_disconnect);
	else
		{
		SOCKET *socket = http->socket_alloc.socket;
		HTTPBLOCK *httpblock = &(http->httpblock);
		dword length = http->content_length;
		if ( length > 128 )
			length = 128;
		httpblock_recv(httpblock, socket, http->buffer, (byte) length, (NF)http_rcvd_block, http);
		}
	}
/*...e*/
/*...shttp_do_post:0:*/
static void http_do_post(HTTP *http)
	{
	bdos_printf("http_do_post:\r\n$");
	if ( http->content_length == ((dword)0xffffffffUL) )
		http_send_error_response(http, "411", "Length Required", (NF)http_disconnect);
	else
		{
		SOCKET *socket = http->socket_alloc.socket;
		bdos_delete_files(&(http->fcb));
		http->fcb.ex = 0;
		http->fcb.s1 = 0;
		http->fcb.s2 = 0;
		http->fcb.rc = 0;
		if ( bdos_create_file(&(http->fcb)) == 255 )
			http_send_error_response(http, "500", "Internal Server Error - can't create file", (NF)http_disconnect);
		else
			{
			http->fcb.ex = 0;
			http->fcb.cr = 0;
			http_recv_block(http);
			}
		}
	}
/*...e*/
/*...shttp_do_delete:0:*/
static void http_do_delete(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	bdos_printf("http_do_delete:\r\n$");
	bdos_delete_files(&(http->fcb));
	http_send_ok_response(http, NULL, (NF)http_disconnect);
	}
/*...e*/
/*...shttp_rcvd_request_header:0:*/
/*...shttp_parse_number:0:*/
static dword http_parse_number(const byte *p)
	{
	dword n = 0;
	while ( *p == ' ' )
		p++;
	while ( *p >= '0' && *p <= '9' )
		{
		n *= 10;
		n += ( *p++ - '0' );
		}
	return n;
	}
/*...e*/

static void http_recv_request_header(HTTP *http);

static void http_rcvd_request_header(HTTP *http)
	{
	HTTPLINE *httpline = &(http->httpline);
	if ( httpline->state != HTTPLINE_STATE_OK )
		http_disconnect(http);
	else if ( httpline->actual != 2 )
		{
		if ( !memcmp(http->buffer, "Content-Length:", 15) )
			http->content_length = http_parse_number(http->buffer+15);
		http_recv_request_header(http);
		}
	else
		switch ( http->verb )
			{
			case HTTP_VERB_GET:
				http_do_get(http);
				break;
			case HTTP_VERB_POST:
				http_do_post(http);
				break;
			case HTTP_VERB_DELETE:
				http_do_delete(http);
				break;
			}
	}
/*...e*/
/*...shttp_recv_request_header:0:*/
static void http_recv_request_header(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	HTTPLINE *httpline = &(http->httpline);
	httpline_recv(httpline, socket, http->buffer, HTTP_MAX_REQUEST_HEADER, (NF)http_rcvd_request_header, http);
	}
/*...e*/
/*...shttp_rcvd_request:0:*/
/* Request will hopefully look like this
   GET /
   GET /filename.typ
   GET /filename.typ?someflags
   GET /filename.typ HTTP/v.v
   POST /filename.typ
   DELETE /filename.typ
*/

/*...shttp_verb_is_get:0:*/
static BOOLEAN http_verb_is_get(const byte *line)
	{
	return line[0] == 'G' &&
	       line[1] == 'E' &&
	       line[2] == 'T' &&
	       line[3] == ' ' &&
	       line[4] == '/' ;
	}
/*...e*/
/*...shttp_verb_is_post:0:*/
static BOOLEAN http_verb_is_post(const byte *line)
	{
	return line[0] == 'P' &&
	       line[1] == 'O' &&
	       line[2] == 'S' &&
	       line[3] == 'T' &&
	       line[4] == ' ' &&
	       line[5] == '/' ;
	}
/*...e*/
/*...shttp_verb_is_delete:0:*/
static BOOLEAN http_verb_is_delete(const byte *line)
	{
	return line[0] == 'D' &&
	       line[1] == 'E' &&
	       line[2] == 'L' &&
	       line[3] == 'E' &&
	       line[4] == 'T' &&
	       line[5] == 'E' &&
	       line[6] == ' ' &&
	       line[7] == '/' ;
	}
/*...e*/
/*...shttp_parse_filename:0:*/
/*...shttp_valid_filename_char:0:*/
static BOOLEAN http_valid_filename_char(byte b)
	{
	return ( b >= '0' && b <= '9' )
	    || ( b >= 'A' && b <= 'Z' )
	    || ( b >= 'a' && b <= 'z' )
	    ;
	}
/*...e*/
/*...shttp_canonical_filename_char:0:*/
static BOOLEAN http_canonical_filename_char(byte b)
	{
	return ( b >= 'a' && b <= 'z' ) ? ( b & ~0x20 ) : b;
	}
/*...e*/

static BOOLEAN http_parse_filename(const byte *line, FCB *fcb)
	{
	byte i;
	fcb->drive = 0;
	if ( *line == ' ' || *line == '?' || *line == '\r' )
		line = "index.htm ";
	i = 0;
	for ( ; i < 8 && http_valid_filename_char(*line); i++ )
		fcb->filename[i] = http_canonical_filename_char(*line++);
	for ( ; i < 8; i++ )
		fcb->filename[i] = ' ';
	i = 0;
	if ( *line == '.' )
		{
		line++;
		for ( ; i < 3 && http_valid_filename_char(*line); i++ )
			fcb->type[i] = http_canonical_filename_char(*line++);
		}
	for ( ; i < 3; i++ )
		fcb->type[i] = ' ';
	if ( *line != ' ' && *line != '?' && *line != '\r' )
		return FALSE;
	return TRUE;
	}	
/*...e*/

static void http_rcvd_request(HTTP *http)
	{
	HTTPLINE *httpline = &(http->httpline);
	FCB *fcb = &(http->fcb);
	if ( httpline->state != HTTPLINE_STATE_OK )
		http_disconnect(http);
	else if ( http_verb_is_get(http->request) )
		{
		if ( ! http_parse_filename(http->request+5, fcb) )
			http_send_error_response(http, "400", "Bad Request - bad filename", (NF)http_disconnect);
		else
			{
			http->verb = HTTP_VERB_GET;
			http_recv_request_header(http);
			}
		}
	else if ( http_verb_is_post(http->request) )
		{
		if ( ! http->allow_post )
			http_send_error_response(http, "405", "Method Not Allowed", (NF)http_disconnect);
		else if ( ! http_parse_filename(http->request+6, fcb) )
			http_send_error_response(http, "400", "Bad Request - bad filename", (NF)http_disconnect);
		else
			{
			http->verb = HTTP_VERB_POST;
			http->content_length = ((dword)0xffffffffUL);
			http_recv_request_header(http);
			}
		}
	else if ( http_verb_is_delete(http->request) )
		{
		if ( ! http->allow_delete )
			http_send_error_response(http, "405", "Method Not Allowed", (NF)http_disconnect);
		else if ( ! http_parse_filename(http->request+8, fcb) )
			http_send_error_response(http, "400", "Bad Request - bad filename", (NF)http_disconnect);
		else
			{
			http->verb = HTTP_VERB_DELETE;
			http_recv_request_header(http);			
			}
		}
	else
		http_send_error_response(http, "500", "Not Implemented", (NF)http_disconnect);
	}
/*...e*/
/*...shttp_connected:0:*/
static void http_connected(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	HTTPLINE *httpline = &(http->httpline);
	bdos_printf("http_connected:\r\n$");
	httpline_recv(httpline, socket, http->request, HTTP_MAX_REQUEST, (NF)http_rcvd_request, http);
	}
/*...e*/
/*...shttp_listen:0:*/
static void http_listen(HTTP *http)
	{
	SOCKET *socket = http->socket_alloc.socket;
	bdos_printf("http_listen:\r\n$");
	net_listen(socket, HTTP_PORT, (NF)http_connected, http);
	}
/*...e*/
/*...shttp_start:0:*/
void http_start(HTTP *http)
	{
	net_alloc(&(http->socket_alloc), (NF)http_listen, http);
	}
/*...e*/
/*...shttp_init:0:*/
void http_init(void)
	{
	}
/*...e*/
