/**HEADER********************************************************************
* 
* Copyright (c) 2008 Freescale Semiconductor;
* All Rights Reserved                       
*
*************************************************************************** 
*
* THIS SOFTWARE IS PROVIDED BY FREESCALE "AS IS" AND ANY EXPRESSED 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 FREESCALE OR ITS 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.
*
**************************************************************************
*
* $FileName: httpd.c$
* $Version : 3.5.22.0$
* $Date    : Nov-26-2009$
*
* Comments:
*
*   This file contains the HTTPD implementation.
*
*END************************************************************************/

#include "httpd.h"
#include "httpd_supp.h"

#if HTTPDCFG_POLL_MODE
#include <select.h>
#endif

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

//extern int strcasecmp(const char *str1, const char *str2);

HTTPD_PARAMS_STRUCT* httpd_default_params(HTTPD_PARAMS_STRUCT *params) {
	HTTPD_PARAMS_STRUCT *ptr = NULL;
	
	if (params)
		ptr = params;
	else {
		ptr = HTTPD_MALLOC(sizeof(HTTPD_PARAMS_STRUCT));
      HTTPD_MEMTYPE(ptr,MEM_TYPE_HTTPD_PARAMS);
   }
	if (ptr) {
		HTTPD_MEMZERO(ptr, sizeof(HTTPD_PARAMS_STRUCT));

		ptr->port = HTTPDCFG_DEF_PORT;
		ptr->max_uri = HTTPDCFG_DEF_URL_LEN;
		ptr->max_auth = HTTPDCFG_DEF_AUTH_LEN;

		ptr->cgi_lnk_tbl = 0;
		ptr->fn_lnk_tbl = 0;

		ptr->auth_fn = 0;
		
		ptr->root_dir = NULL;
		ptr->index_page = HTTPDCFG_DEF_INDEX_PAGE;

		ptr->max_ses = HTTPDCFG_DEF_SES_CNT;
		
#if HTTPDCFG_POLL_MODE
        ptr->max_line = HTTPD_MAX_LEN;
#endif		

	}
	
	return ptr;
}

int httpd_set_index_page(HTTPD_STRUCT *server, char *index_page) {
	HTTPD_ASSERT(server && server->params && index_page);
	if (server && server->params && index_page) {
		server->params->index_page = index_page;
		return 0;
	}
	
	return 1;
}

HTTPD_STRUCT* httpd_init(HTTPD_PARAMS_STRUCT *params) {
	struct sockaddr_in sin;
	HTTPD_STRUCT *server = NULL;
	int option, error, si;

	HTTPD_ASSERT(params);

	if (params == NULL)
		return NULL;

	if ((server = HTTPD_MALLOC(sizeof(HTTPD_STRUCT))) == NULL) {
        HTTPD_DEBUG(1, "server: main structure allocation error\n");	        
		return NULL;
	}
	
    HTTPD_MEMTYPE(server,MEM_TYPE_HTTPD_STRUCT);
	server->params = params;

#if HTTPDCFG_POLL_MODE
    // session table allocation
    if ((server->session = HTTPD_MALLOC(sizeof(HTTPD_SESSION_STRUCT*) * server->params->max_ses)) == NULL) {
        HTTPD_DEBUG(1, "server: session table allocation error\n");
        goto ERROR_ALLOC;
    }
    
    HTTPD_MEMTYPE(server->session, MEM_TYPE_HTTPD_SESSION_STRUCT);
     
    // sessions allocation and sessions table initialization 
	for (si = 0; si < server->params->max_ses; si++) {
	    server->session[si] = httpd_ses_alloc(server);
	    
	    if (server->session[si] == NULL) {
	        while (--si) {
	            HTTPD_FREE(server->session[si]);
	        }

            HTTPD_DEBUG(1, "server: session allocation error\n");	        
	        goto ERROR_ALLOC;
	    }
    }
#endif

	// init socket
	if ((server->sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		HTTPD_DEBUG(1, "socket: creation error\n");
		goto ERROR_ALLOC;
	}

#ifdef __MQX__
	option = 500;
	error = setsockopt(server->sock, SOL_TCP, OPT_RECEIVE_TIMEOUT, &option, sizeof(option));

	option = 500;
	error = setsockopt(server->sock, SOL_TCP, OPT_SEND_TIMEOUT, &option, sizeof(option));

	option = 5000;
	error = setsockopt(server->sock, SOL_TCP, OPT_CONNECT_TIMEOUT, &option, sizeof(option));

	option = 1000;
	//error = setsockopt(server->sock, SOL_TCP, OPT_RETRANSMISSION_TIMEOUT, &option, sizeof(option));
	
	option = 1000;
	//error = setsockopt(server->sock, SOL_TCP, OPT_MAXRTO, &option, sizeof(option));

	option = HTTPCFG_TIMEWAIT_TIMEOUT;
	error = setsockopt(server->sock, SOL_TCP, OPT_TIMEWAIT_TIMEOUT, &option, sizeof(option));

	option = TRUE;
	error = setsockopt(server->sock, SOL_TCP, OPT_RECEIVE_NOWAIT, &option, sizeof(option));

	option = TRUE;
	//error = setsockopt(server->sock, SOL_TCP, OPT_SEND_PUSH, &option, sizeof(option));
	
	option = TRUE;
	//error = setsockopt(server->sock, SOL_TCP, OPT_RECEIVE_PUSH, &option, sizeof(option));

	option = TRUE;
	error = setsockopt(server->sock, SOL_TCP, OPT_NO_NAGLE_ALGORITHM, &option, sizeof(option));

	option = HTTPCFG_TX_WINDOW_SIZE;
	error = setsockopt(server->sock, SOL_TCP, OPT_TBSIZE, &option, sizeof(option));

	option = HTTPCFG_RX_WINDOW_SIZE;
	error = setsockopt(server->sock, SOL_TCP, OPT_RBSIZE, &option, sizeof(option));
#endif

#ifndef __MQX__
	sin.sin_port = htons(server->params->port);
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
#else
	sin.sin_port = server->params->port;
	sin.sin_addr.s_addr = INADDR_ANY;
#endif

	sin.sin_family = AF_INET;

	// bind address
	if ((int_32)bind(server->sock, &sin, sizeof(sin)) < 0) {
		HTTPD_DEBUG(1, "bind error\n");
		goto ERROR;
	}

	// listen
	if ((int_32)listen(server->sock, 5) < 0) {
		HTTPD_DEBUG(1, "socket: listen error\n");
		goto ERROR;
	}

	HTTPD_DEBUG(1, "------------------------------------------\n");
	HTTPD_DEBUG(1, " HTTP 1.1 server has been started.\n");
//	HTTPD_DEBUG(1, " Use: http://%s.\n",ip_str);
	HTTPD_DEBUG(1, "------------------------------------------\n\n");

	return server;

	ERROR:
	printf("error %d\n", errno);

#ifdef __MQX__
    shutdown(server->sock, FLAG_ABORT_CONNECTION);
#else
	if (close(server->sock) < 0) {
		//return;
	}
#endif

    ERROR_ALLOC:
    
    HTTPD_FREE(server);

	return NULL;
}

int httpd_release(HTTPD_STRUCT *server) {
	int res = 1;
	
	if (server) {
#ifdef __MQX__
    	shutdown(server->sock, FLAG_ABORT_CONNECTION);
#else
		if (close(server->sock) < 0) {
			//return;
		}
#endif	
		//server->socket = SOCKET_INVALID;

		memset(server->params, 0, sizeof(HTTPD_PARAMS_STRUCT));
	}

	return res;
}

/** Close session
 * \param session < pointer to session
 */
static void httpd_ses_close(HTTPD_SESSION_STRUCT *session) {
	HTTPD_ASSERT(session);
    HTTPD_DEBUG(1, "session closed (%p)\n", session);

    if (session->response.file) {
    	fclose(session->response.file);
    }

	session->valid = HTTPD_SESSION_INVALID;

#ifdef __MQX__
    shutdown(session->sock, FLAG_CLOSE_TX);
#else
	close(session->sock);
#endif

}

/** Read http request.
 * \param session < pointer to session structure
 * \return
 */
static int httpd_readreq(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session) {
#if HTTPDCFG_POLL_MODE
    char *buf;
#else
	char buf[HTTPD_MAX_LEN + 1];
#endif

	char *cp, *cp2;
	int len;

	HTTPD_ASSERT(server && session);

	// standard response
	strcpy(session->response.header, "Server: FNET HTTP - Freescale Embedded Web Server\n");
	session->response.contenttype = CONTENT_TYPE_HTML;
	session->response.statuscode = 200;
	session->response.hdrsent = 0;
	session->response.file = NULL;
	session->response.data = 0;
	session->response.script_token = 0;
	
	HTTPD_DEBUG(4, "request BEGIN\n");
	

#if HTTPDCFG_POLL_MODE
    buf = &session->line[session->line_used];
    len = server->params->max_line - session->line_used;
#else
    len = HTTPD_MAX_LEN;
#endif
    

	// read the request
	while (httpd_readln(session, buf, &len)) {
		HTTPD_DEBUG(4, "request: %s\n", buf);
		session->req_lines++;
		
#if HTTPDCFG_POLL_MODE
        buf = session->line;
        len = server->params->max_line;
#endif

		if (1 == session->req_lines) {	// first line = start-line
			if ((cp = strstr(buf, "GET"))!=0)
			{
				session->request.method = HTTPD_REQ_GET;
			}
			else if ((cp = strstr(buf, "POST"))!=0)
			{
				session->request.method = HTTPD_REQ_POST;
			}
			else {	// unknown method
				session->request.method = HTTPD_REQ_UNKNOWN;
				return -1;
			}

			// parse remain part of 1. line
			cp = strchr(cp, ' ') + 1;
			cp2 = strchr(cp, ' ');
			*cp2 = 0;

			strncpy(session->request.path, cp, server->params->max_uri);
			httpd_sanitiseurl(session->request.path);
			continue;
		}

		if (session->header) {
			if (*buf == 0) {		// header end - \r\n\r\n
				session->header = 0;
			
			    len = 0;
				break;
			}
			else if (strncmp(buf, "Connection: ", 12) == 0) {
				cp = strchr(buf, ':') + 2;
			    
			    if (strncmp(cp, "keep-alive", 10) == 0) {
					session->keep_alive = 0;
					HTTPD_DEBUG(4, "Connection: keep-alive\n");
				}
			}
			else if (strncmp(buf, "Content-Length: ", 16) == 0) {
				cp = strchr(buf, ':') + 2;
				if (cp)
				{
					session->request.content_len = _io_atoi(cp);
					HTTPD_DEBUG(4, "Content-Length: %u\n", session->request.content_len);
				}
			}
			else if (strncmp(buf, "Authorization: ", 15) == 0) {
				cp = strchr(buf, ':') + 2;

				if (strncmp(cp, "Basic ", 6) == 0) {
					cp = strchr(cp, ' ') + 1;

					httpd_decode_base64(session->request.auth, cp, (int)server->params->max_auth);

					cp = strchr(session->request.auth, ':');
					if (cp)
					{
						*cp = 0;
						session->request.authPasswd = cp + 1;
					}

					HTTPD_DEBUG(4, "Authorization - Basic: %s:%s\n", session->request.auth, session->request.authPasswd);
				}
				else
				{
					HTTPD_DEBUG(4, "Authorization - unknown method\n");
				}
			}

			continue;
		}
	}
	
#if HTTPDCFG_POLL_MODE
    session->line_used += len;
#endif

	if (!session->header)
	{
		// process any URL data
		cp = strchr(session->request.path, '?');
		if (0 != cp)
		{
			*cp = 0;
			cp++;
			session->request.urldata = cp;
		}
	}
	
	HTTPD_DEBUG(4, "request END\n");

	return 0;
}

/** Http request process.
 * \param server < pointer to server
 * \param session < pointer to session data structure
 */
static void httpd_processreq(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session)
{
	char buf[HTTPD_MAX_LEN + 1];
	char *cp, *cp1;
	int cgi = 0, res = 0, auth = 0;	// authorization required flag
	HTTPD_ROOT_DIR_STRUCT *root_dir;

	HTTPD_ASSERT(server && session);

	// check request path
	cp = strrchr(session->request.path, '/');
	if (0 == cp)
	{
		HTTPD_DEBUG(1, "Invalid request path '%s'\n", session->request.path);
		return;
	}
	
	if (session->request.path[0] == '/' && session->request.path[1] == 0)
		strcpy(session->request.path, server->params->index_page);
	
	// check if request is script request

	// if request contain data
//	if (session->request.content_len)
//	{
		res = -1;

		if ((cp1 = strrchr(cp, '.')) != 0)
		{
			if (0 == strcasecmp(cp1, ".cgi"))	// file extension is cgi, maybe cgi script
			{
			    cgi = 1;

				// search cgi script
				*cp1 = 0;
				res = httpd_cgi(server, session, cp + 1);	// cp + 1 = cgi script name begin
			}
		}

	if (cgi || session->request.content_len)
	{
		if (res < 0)
		{
			// cgi script not found - read data from socket - discard it
			while (session->request.content_len)
			{
				res = httpd_read(session, buf, (int)((res > sizeof(buf))? sizeof(buf) : session->request.content_len));

				session->request.content_len -= res;

				// TODO add timeout - can be deathlock

				// temporary solution
				if (res == 0)
				    break;
			}

			cgi = 0;
		}
		else {
			// cgi script found - decrease content len
			session->request.content_len -= res;

			if (session->request.content_len == 0)	// whole content procesed
				session->state = HTTPD_SES_END_REQ;
		}
	}

	// send regular file(document) - if is not cgi script
	if (!cgi) {
		// get correct root dir (by alias)
		
		if (server->params->root_dir[1].alias) {
			// more root dir pressents, find alias
			root_dir = server->params->root_dir;
			
			cp = strchr(session->request.path, '/');
			
			if (cp) {
				cp++;
				cp1 = strchr(cp, '/');
			
				if (cp1) {
					while (root_dir->alias) {
						if (strncmp(root_dir->alias, cp, cp1 - cp) == 0) {
							strcpy(buf, root_dir->path);
							strcat(buf, cp1);
							break;
						}
				
						root_dir++;
					}
				}
			}
			
			if (!cp || !cp1 || !root_dir->alias) {
				strcpy(buf, server->params->root_dir->path);
				strcat(buf, session->request.path);
			}
		}
		else {
			strcpy(buf, server->params->root_dir[0].path);
			strcat(buf, session->request.path);
		}
	
		// correct slash
		cp = buf;
		while (*cp) {
		    if ('/' == *cp)
		        *cp = '\\';

		    cp++;
		}

#ifdef __MQX__
		session->response.file = fopen(buf, "rb");
#else
		session->response.file = open(buf, 0, "r");
#endif

		session->response.len = 0;

		if (session->response.file) {
		}
		else {
			session->response.statuscode = 404;
		}

		session->state = HTTPD_SES_RESP;
	}

	if (auth && server->params->auth_fn)
	{
		if (*session->request.auth)
		{
			// check authorization
			if (server->params->auth_fn(session))
			{
				session->response.statuscode = 401;
				strncat(session->response.header, "WWW-Authenticate: Basic realm=\"test\"\n", HTTPDCFG_MAX_HEADER_LEN);
			}
		}
		else
		{
			session->response.statuscode = 401;
			strncat(session->response.header, "WWW-Authenticate: Basic realm=\"test\"\n", HTTPDCFG_MAX_HEADER_LEN);
		}
	}
}

/** http response
 * \param server < pointer to server data structure
 * \param session < pointer to session data structure
 */
static void httpd_response(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session) {
	HTTPD_ASSERT(server && session);

	switch (session->response.statuscode) {
	case 200:
		httpd_sendfile(server, session);
		break;
	case 401:
		httpd_send401(server, session);
		session->state = HTTPD_SES_END_REQ;
		break;
	default:
		httpd_send404(server, session);
		session->state = HTTPD_SES_END_REQ;
		break;
	}
}

/** HTTPD session state machine
 * \param server < pointer to server
 * \param session < session number
 */
void httpd_ses_process(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session) {
	HTTPD_TIME_STRUCT time;
	HTTPD_ASSERT(server && session);

	if (HTTPD_SESSION_VALID == session->valid)
	{
		// check timeout
		HTTPD_GET_TIME(&time);
		if (HTTPD_DIFF_TIME_MS(session->time, time) > HTTPDCFG_SES_TO) {
			HTTPD_DEBUG(1, "session %p timeout\n", session);
			session->state = HTTPD_SES_CLOSE;
		}
		
		switch (session->state) {
		case HTTPD_SES_WAIT_REQ:

			if (0 > httpd_readreq(server, session))
			{
				session->state = HTTPD_SES_END_REQ;
			}
			else \
			{
				if (session->header == 0)
				{
					session->state = HTTPD_SES_PROCESS_REQ;
					//HTTPD_GET_TIME(&session->time);
				}
			}

			break;
		case HTTPD_SES_PROCESS_REQ:
			httpd_processreq(server, session);
			break;

		case HTTPD_SES_RESP:
			httpd_response(server, session);
			break;

		case HTTPD_SES_END_REQ:
		    if (!session->keep_alive)
		        session->state = HTTPD_SES_CLOSE;
		    else {
		        // init session
		        session->state = HTTPD_SES_WAIT_REQ;
		        //session->sock = sock;
		        //session->valid = HTTPD_SESSION_VALID;

		        session->header = 1;
		        session->req_lines = 0;
		        session->remain = 0;
		        session->recv_used = 0;
		
		        HTTPD_GET_TIME(&session->time);		    
		    
		        session->keep_alive--;
		        break;
		    }
		    
		case HTTPD_SES_CLOSE:
			httpd_ses_close(session);
			break;
			
		default:
			// This cannot be good
			session->valid = HTTPD_SESSION_INVALID;
			break;
		}
	}
}

/** Allocate session.
 * \param server < pointer to server
 * \return pointer to allocated session
 */
HTTPD_SESSION_STRUCT* httpd_ses_alloc(HTTPD_STRUCT *server) {
	HTTPD_SESSION_STRUCT *session = NULL;

	HTTPD_ASSERT(server);

	if (server) {
	    session = HTTPD_MALLOC(sizeof(HTTPD_SESSION_STRUCT));
	    if (session) {
            HTTPD_MEMTYPE(session,MEM_TYPE_HTTPD_SESSION_STRUCT);

			HTTPD_MEMZERO(session, sizeof(HTTPD_SESSION_STRUCT));

			session->request.path = HTTPD_MALLOC(server->params->max_uri + 1);
			if (NULL == session->request.path)
				goto ERROR;
			
            HTTPD_MEMTYPE(session->request.path,MEM_TYPE_HTTPD_URI);

			session->request.auth = HTTPD_MALLOC(server->params->max_auth + 1);
			if (NULL == session->request.auth)
				goto ERROR;
			
            HTTPD_MEMTYPE(session->request.auth,MEM_TYPE_HTTPD_AUTH);

   		    session->recv_used = 0;

#if HTTPDCFG_POLL_MODE
            session->line = HTTPD_MALLOC(server->params->max_line + 1);;
            HTTPD_MEMTYPE(session->line, MEM_TYPE_HTTPD_LINE);
            session->line_used = 0;
#endif   		    
   		}
   	}

    return session;

ERROR:
	if (session->request.path)
		HTTPD_FREE(session->request.path);

	HTTPD_FREE(session);

	return NULL;
}

void httpd_ses_free(HTTPD_SESSION_STRUCT *session) {
	HTTPD_ASSERT(session);

	if (session) {
		HTTPD_FREE(session->request.path);
		HTTPD_FREE(session->request.auth);
		HTTPD_FREE(session);
	}
}

void httpd_ses_init(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session, const int sock) {
	HTTPD_ASSERT(server && session && sock >= 0);

	if (server && session) {
		// init session
		session->state = HTTPD_SES_WAIT_REQ;
		session->sock = sock;
		session->keep_alive = 0;
		session->valid = HTTPD_SESSION_VALID;

		session->header = 1;
		session->req_lines = 0;
		session->remain = 0;
		session->recv_used = 0;

#if HTTPDCFG_POLL_MODE		
        session->line_used = 0;
#endif
		
		HTTPD_GET_TIME(&session->time);
	}
}

void httpd_ses_poll(HTTPD_STRUCT *server, HTTPD_SESSION_STRUCT *session) {
	int s;
	unsigned short len;
	struct sockaddr_in sin;

	HTTPD_ASSERT(server && session);

	do {
		if (HTTPD_SESSION_VALID != session->valid) {
			len = sizeof(sin);

			s = accept(server->sock, &sin, &len);
			if (s && s != RTCS_SOCKET_ERROR) {
				HTTPD_DEBUG(1, "request from (%p): %d.%d.%d.%d; port: %d.\n", session, (sin.sin_addr.s_addr >> 24) & 0xff, (sin.sin_addr.s_addr >> 16) & 0xff, (sin.sin_addr.s_addr >> 8) & 0xff, sin.sin_addr.s_addr & 0xff, sin.sin_port);

    	    	httpd_ses_init(server, session, s);
			}
		}

		while (HTTPD_SESSION_VALID == session->valid) {
			httpd_ses_process(server, session);
			
			if (HTTPDCFG_POLL_MODE)
			    break;
		}
	} while (!HTTPDCFG_POLL_MODE);
}
