/*
 * Copyright (c) 2004
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdarg.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>

#if defined(_WIN32)
#include <winsock2.h>
#else
#include <unistd.h> 
#include <sys/file.h>
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <netdb.h>
#include <sys/socket.h> 
#include <netdb.h>
#endif

#include "httpd.h"
#include "httpd_priv.h"




char *httpdUrlEncode(char *str)
{
	char    *new,
	    *cp;

        new = (char *)_httpd_escape(str);
	if (new == NULL) {
		return(NULL);
	}
        cp = new;
        while(*cp) {
                if (*cp == ' ')
                        *cp = '+';
                cp++;
        }
	return(new);
}



char *httpdRequestMethodName(httpd *server)
{
	static	char	tmpBuf[255];

	switch(server->request.method) {
	case HTTP_GET: return("GET");
	case HTTP_POST: return("POST");
	default: 
		snprintf(tmpBuf,255,"Invalid method '%d'", 
		    server->request.method);
		return(tmpBuf);
	}
}


httpVar *httpdGetVariableByName(httpd *server, char *name)
{
	httpVar	*curVar;

	curVar = server->variables;
	while(curVar) {
		if (strcmp(curVar->name, name) == 0)
			return(curVar);
		curVar = curVar->nextVariable;
	}
	return(NULL);
}



httpVar *httpdGetVariableByPrefix(httpd *server, char *prefix)
{
	httpVar	*curVar;

	if (prefix == NULL)
		return(server->variables);
	curVar = server->variables;
	while(curVar) {
		if (strncmp(curVar->name, prefix, strlen(prefix)) == 0)
			return(curVar);
		curVar = curVar->nextVariable;
	}
	return(NULL);
}


httpVar *httpdGetVariableByPrefixedName(httpd *server, char *prefix, char *name)
{
	httpVar	*curVar;
	int	prefixLen;

	if (prefix == NULL)
		return(server->variables);
	curVar = server->variables;
	prefixLen = strlen(prefix);
	while(curVar) {
		if (strncmp(curVar->name, prefix, prefixLen) == 0 &&
		    strcmp(curVar->name + prefixLen, name) == 0) {
			return(curVar);
		}
		curVar = curVar->nextVariable;
	}
	return(NULL);
}


httpVar *httpdGetNextVariableByPrefix(httpVar *curVar, char *prefix)
{
	if(curVar)
		curVar = curVar->nextVariable;
	while(curVar) {
		if (strncmp(curVar->name, prefix, strlen(prefix)) == 0)
			return(curVar);
		curVar = curVar->nextVariable;
	}
	return(NULL);
}


int httpdAddVariable(httpd *server, const char *name, const char *value)
{
	httpVar *curVar, *lastVar, *newVar;

	while(*name == ' ' || *name == '\t')
		name++;
	newVar = malloc(sizeof(httpVar));
	bzero(newVar, sizeof(httpVar));
	newVar->name = strdup(name);
	newVar->value = strdup(value);
	lastVar = NULL;
	curVar = server->variables;
	while(curVar) {
		if (strcmp(curVar->name, name) != 0) {
			lastVar = curVar;
			curVar = curVar->nextVariable;
			continue;
		}
		while(curVar) {
			lastVar = curVar;
			curVar = curVar->nextValue;
		}
		lastVar->nextValue = newVar;
		return(0);
	}
	if (lastVar)
		lastVar->nextVariable = newVar;
	else
		server->variables = newVar;
	return(0);
}

httpd *httpdCreate(const char *host, int port)
{
	httpd	*new;
	int	sock,
	    opt;
        struct  sockaddr_in     addr;

	/*
	 * Create the handle and setup it's basic config
	 */
	new = malloc(sizeof(httpd));
	if (new == NULL)
		return(NULL);
	bzero(new, sizeof(httpd));
	new->port = port;
	if (host == HTTP_ANY_ADDR)
		new->host = HTTP_ANY_ADDR;
	else
		new->host = strdup(host);
	new->content = (httpDir*)malloc(sizeof(httpDir));
	bzero(new->content,sizeof(httpDir));
	new->content->name = strdup("");

	/*
	 * Setup the socket
	 */
#ifdef _WIN32
	{ 
		WORD 	wVersionRequested;
		WSADATA wsaData;
		int 	err;

		wVersionRequested = MAKEWORD( 2, 2 );

		err = WSAStartup( wVersionRequested, &wsaData );
	
		/* Found a usable winsock dll? */
		if( err != 0 ) 
			return NULL;

		/* 
		 * Confirm that the WinSock DLL supports 2.2.
		 * Note that if the DLL supports versions greater 
		 * than 2.2 in addition to 2.2, it will still return
		 * 2.2 in wVersion since that is the version we
		 * requested.
		 */

		if( LOBYTE( wsaData.wVersion ) != 2 || 
		    HIBYTE( wsaData.wVersion ) != 2 ) {

			/* 
			 * Tell the user that we could not find a usable
			 * WinSock DLL.
			 */
			WSACleanup( );
			return NULL;
		}

		/* The WinSock DLL is acceptable. Proceed. */
 	}
#endif

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock  < 0) {
		free(new);
		return(NULL);
	}
#	ifdef SO_REUSEADDR
	opt = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt,sizeof(int));
#	endif
	new->serverSock = sock;
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	if (new->host == HTTP_ANY_ADDR) {
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else {
		addr.sin_addr.s_addr = inet_addr(new->host);
	}
	addr.sin_port = htons((u_short)new->port);
	if (bind(sock,(struct sockaddr *)&addr,sizeof(addr)) <0) {
		close(sock);
		free(new);
		return(NULL);
	}
	listen(sock, 128);
	new->startTime = time(NULL);
	return(new);
}

void httpdDestroy(httpd *server)
{
	if (server == NULL)
		return;
	if (server->host)
		free(server->host);
	free(server);
}



int httpdGetConnection(httpd *server, struct timeval *timeout)
{
	int	result;
	fd_set	fds;
	struct  sockaddr_in     addr;
	size_t  addrLen;
	char	*ipaddr;

	FD_ZERO(&fds);
	FD_SET(server->serverSock, &fds);
	result = 0;
	while(result == 0) {
		result = select(server->serverSock + 1, &fds, 0, 0, timeout);
		if (result < 0) {
			return(-1);
		}
		if (timeout != 0 && result == 0) {
			return(0);
		}
		if (result > 0) {
			break;
		}
	}
	bzero(&addr, sizeof(addr));
	addrLen = sizeof(addr);
	server->clientSock = accept(server->serverSock,(struct sockaddr *)&addr,
	    &addrLen);
	ipaddr = inet_ntoa(addr.sin_addr);
	if (ipaddr)
		strncpy(server->clientAddr, ipaddr, HTTP_IP_ADDR_LEN);
	else
		*server->clientAddr = 0;
	server->readBufRemain = 0;
	server->readBufPtr = NULL;

	/*
	 * Check the default ACL
	 */
	if (server->defaultAcl) {
		if (httpdCheckAcl(server, server->defaultAcl) == HTTP_ACL_DENY) {
			httpdEndRequest(server);
			return(-2);
		}
	}
	return(1);
}



int httpdReadRequest(httpd *server)
{
	static	char	buf[HTTP_MAX_LEN];
	int	count,
	    inHeaders;
	char	*cp, *cp2;
	int	_httpd_decode();


	/*
	 * Setup for a standard response
	 */
	strcpy(server->response.headers,
	    "Server: Hughes Technologies Embedded Server\n"); 
	strcpy(server->response.contentType, "text/html");
	strcpy(server->response.response,"200 Output Follows\n");
	server->response.headersSent = 0;


	/*
	 * Read the request
	 */
	count = 0;
	inHeaders = 1;
	while(_httpd_readLine(server, buf, HTTP_MAX_LEN) > 0) {
		count++;

		/*
		 * Special case for the first line.  Scan the request
		 * method and path etc
		 */
		if (count == 1) {
			/*
			 * First line.  Scan the request info
			 */
			cp = cp2 = buf;
			while(isalpha(*cp2))
				cp2++;
			*cp2 = 0;
			if (strcasecmp(cp,"GET") == 0)
				server->request.method = HTTP_GET;
			if (strcasecmp(cp,"POST") == 0)
				server->request.method = HTTP_POST;
			if (server->request.method == 0) {
				_httpd_net_write( server->clientSock,
				    HTTP_METHOD_ERROR,
				    strlen(HTTP_METHOD_ERROR));
				_httpd_net_write( server->clientSock, cp, 
				    strlen(cp));
				_httpd_writeErrorLog(server,LEVEL_ERROR, 
				    "Invalid method received");
				return(-1);
			}
			cp = cp2+1;
			while(*cp == ' ')
				cp++;
			cp2 = cp;
			while(*cp2 != ' ' && *cp2 != 0)
				cp2++;
			*cp2 = 0;
			strncpy(server->request.path,cp,HTTP_MAX_URL);
			_httpd_sanitiseUrl(server->request.path);
			continue;
		}

		/*
		 * Process the headers
		 */
		if (inHeaders) {
			if (*buf == 0) {
				/*
				 * End of headers.  Continue if there's
				 * data to read
				 */
				if (server->request.contentLength == 0)
					break;
				inHeaders = 0;
				break;
			}
			if (strncasecmp(buf,"Cookie: ", 7) == 0) {
				char	*var,
				    *val,
				    *end;

				var = index(buf,':');
				while(var) {
					var++;
					val = index(var, '=');
					*val = 0;
					val++;
					end = index(val,';');
					if(end)
						*end = 0;
					httpdAddVariable(server, var, val);
					var = end;
				}
			}
			if (strncasecmp(buf,"Authorization: ", 15) == 0) {
				cp = index(buf,':') + 2;
				if (strncmp(cp,"Basic ", 6) != 0) {
					/* Unknown auth method */
				}
				else {
					char 	authBuf[100];

					cp = index(cp,' ') + 1;
					_httpd_decode(cp, authBuf, 100);
					server->request.authLength = 
					    strlen(authBuf);
					cp = index(authBuf,':');
					if (cp) {
						*cp = 0;
						strncpy(
							server->request.authPassword,
							    cp+1, HTTP_MAX_AUTH);
					}
					strncpy(server->request.authUser, 
					    authBuf, HTTP_MAX_AUTH);
				}
			}
			if (strncasecmp(buf,"Referer: ", 9) == 0) {
				cp = index(buf,':') + 2;
				if(cp) {
					strncpy(server->request.referer,cp,
					    HTTP_MAX_URL);
				}
			}
			if (strncasecmp(buf,"If-Modified-Since: ", 19) == 0) {
					cp = index(buf,':') + 2;
					if(cp) {
						strncpy(server->request.ifModified,cp,
						    HTTP_MAX_URL);
						cp = index(server->request.ifModified,
						    ';');
						if (cp)
							*cp = 0;
					}
				}
			if (strncasecmp(buf,"Content-Type: ", 14) == 0) {
				cp = index(buf,':') + 2;
				if(cp) {
					strncpy(server->request.contentType,cp,
					    HTTP_MAX_URL);
				}
			}
			if (strncasecmp(buf,"Content-Length: ",16) == 0) {
				cp = index(buf,':') + 2;
				if(cp)
					server->request.contentLength=atoi(cp);
			}
			continue;
		}
	}

	/*
	 * Process and POST data
	 */
	if (server->request.contentLength > 0) {
		bzero(buf, HTTP_MAX_LEN);
		_httpd_readBuf(server, buf, server->request.contentLength);
		_httpd_storeData(server, buf);
		
	}

	/*
	 * Process any URL data
	 */
	cp = index(server->request.path,'?');
	if (cp != NULL) {
		*cp = 0;
		cp++;
		_httpd_storeData(server, cp);
	}
	return(0);
}


void httpdEndRequest(httpd *server)
{
	_httpd_freeVariables(server->variables);
	server->variables = NULL;
	shutdown(server->clientSock,2);
	close(server->clientSock);
	bzero(&server->request, sizeof(server->request));
}


void httpdFreeVariables(httpd *server)
{
        _httpd_freeVariables(server->variables);
}



void httpdDumpVariables(httpd *server)
{
	httpVar	*curVar,
	    *curVal;

	curVar = server->variables;
	while(curVar) {
		printf("Variable '%s'\n", curVar->name);
		curVal = curVar;
		while(curVal) {
			printf("\t= '%s'\n",curVal->value);
			curVal = curVal->nextValue;
		}
		curVar = curVar->nextVariable;
	}
}

void httpdSetFileBase(httpd *server, char *path)
{
	strncpy(server->fileBasePath, path, HTTP_MAX_URL);
}


int httpdAddFileContent(httpd *server, char *dir, char *name, int indexFlag, int (*preload)(), char *path)
{
	httpDir	*dirPtr;
	httpContent *newEntry;

	dirPtr = _httpd_findContentDir(server, dir, HTTP_TRUE);
	newEntry =  malloc(sizeof(httpContent));
	if (newEntry == NULL)
		return(-1);
	bzero(newEntry,sizeof(httpContent));
	newEntry->name = strdup(name);
	newEntry->type = HTTP_FILE;
	newEntry->indexFlag = indexFlag;
	newEntry->preload = preload;
	newEntry->next = dirPtr->entries;
	dirPtr->entries = newEntry;
	if (*path == '/') {
		/* Absolute path */
		newEntry->path = strdup(path);
	}
	else {
		/* Path relative to base path */
		newEntry->path = malloc(strlen(server->fileBasePath) +
		    strlen(path) + 2);
		snprintf(newEntry->path, HTTP_MAX_URL, "%s/%s",
		    server->fileBasePath, path);
	}
	return(0);
}



int httpdAddWildcardContent(httpd *server, char *dir, int (*preload)(), char *path)
{
	httpDir	*dirPtr;
	httpContent *newEntry;

	dirPtr = _httpd_findContentDir(server, dir, HTTP_TRUE);
	newEntry =  malloc(sizeof(httpContent));
	if (newEntry == NULL)
		return(-1);
	bzero(newEntry,sizeof(httpContent));
	newEntry->name = NULL;
	newEntry->type = HTTP_WILDCARD;
	newEntry->indexFlag = HTTP_FALSE;
	newEntry->preload = preload;
	newEntry->next = dirPtr->entries;
	dirPtr->entries = newEntry;
	if (*path == '/') {
		/* Absolute path */
		newEntry->path = strdup(path);
	}
	else {
		/* Path relative to base path */
		newEntry->path = malloc(strlen(server->fileBasePath) +
		    strlen(path) + 2);
		snprintf(newEntry->path, HTTP_MAX_URL, "%s/%s",
		    server->fileBasePath, path);
	}
	return(0);
}




int httpdAddCContent(httpd *server, char *dir, char *name, int indexFlag, int (*preload)(), void (*function)())
{
	httpDir	*dirPtr;
	httpContent *newEntry;

	dirPtr = _httpd_findContentDir(server, dir, HTTP_TRUE);
	newEntry =  malloc(sizeof(httpContent));
	if (newEntry == NULL)
		return(-1);
	bzero(newEntry,sizeof(httpContent));
	newEntry->name = strdup(name);
	newEntry->type = HTTP_C_FUNCT;
	newEntry->indexFlag = indexFlag;
	newEntry->function = function;
	newEntry->preload = preload;
	newEntry->next = dirPtr->entries;
	dirPtr->entries = newEntry;
	return(0);
}


int httpdAddCWildcardContent(httpd *server, char *dir, int (*preload)(), void (*function)())
{
	httpDir	*dirPtr;
	httpContent *newEntry;

	dirPtr = _httpd_findContentDir(server, dir, HTTP_TRUE);
	newEntry =  malloc(sizeof(httpContent));
	if (newEntry == NULL)
		return(-1);
	bzero(newEntry,sizeof(httpContent));
	newEntry->name = NULL;
	newEntry->type = HTTP_C_WILDCARD;
	newEntry->indexFlag = HTTP_FALSE;
	newEntry->function = function;
	newEntry->preload = preload;
	newEntry->next = dirPtr->entries;
	dirPtr->entries = newEntry;
	return(0);
}

int httpdAddStaticContent(httpd *server, char *dir, char *name, int indexFlag, int (*preload)(), char *data)
{
	httpDir	*dirPtr;
	httpContent *newEntry;

	dirPtr = _httpd_findContentDir(server, dir, HTTP_TRUE);
	newEntry =  malloc(sizeof(httpContent));
	if (newEntry == NULL)
		return(-1);
	bzero(newEntry,sizeof(httpContent));
	newEntry->name = strdup(name);
	newEntry->type = HTTP_STATIC;
	newEntry->indexFlag = indexFlag;
	newEntry->data = data;
	newEntry->preload = preload;
	newEntry->next = dirPtr->entries;
	dirPtr->entries = newEntry;
	return(0);
}

void httpdSendHeaders(httpd *server)
{
	_httpd_sendHeaders(server, 0, 0);
}

void httpdSetResponse(httpd *server, char *msg)
{
	strncpy(server->response.response, msg, HTTP_MAX_URL);
}

void httpdSetContentType(httpd *server, char *type)
{
	strcpy(server->response.contentType, type);
}


void httpdAddHeader(httpd *server, char *msg)
{
	strcat(server->response.headers,msg);
	if (msg[strlen(msg) - 1] != '\n')
		strcat(server->response.headers,"\n");
}

void httpdSetCookie(httpd *server, char *name, char *value)
{
	char	buf[HTTP_MAX_URL];

	snprintf(buf,HTTP_MAX_URL, "Set-Cookie: %s=%s; path=/;", name, value);
	httpdAddHeader(server,buf);
}

void httpdOutput(httpd *server, char *msg)
{
	char	buf[HTTP_MAX_LEN],
	    varName[80],
	    *src,
	    *dest;
	int	count;

	src = msg;
	dest = buf;
	count = 0;
	while(*src && count < HTTP_MAX_LEN) {
		if (*src == '$') {
			char	*cp,
			    *tmp;
			int	count2;
			httpVar	*curVar;

			tmp = src + 1;
			cp = varName;
			count2 = 0;
			while(*tmp&&(isalnum(*tmp)||*tmp == '_')&&count2 < 80) {
				*cp++ = *tmp++;
				count2++;
			}
			*cp = 0;
			curVar = httpdGetVariableByName(server,varName);
			if (curVar) {
				strcpy(dest, curVar->value);
				dest = dest + strlen(dest);
				count += strlen(dest);
			}
			else {
				*dest++ = '$';
				strcpy(dest, varName);
				dest += strlen(varName);
				count += 1 + strlen(varName);
			}
			src = src + strlen(varName) + 1;
			continue;
		}
		*dest++ = *src++;
		count++;
	}	
	*dest = 0;
	server->response.responseLength += strlen(buf);
	if (server->response.headersSent == 0)
		httpdSendHeaders(server);
	_httpd_net_write( server->clientSock, buf, strlen(buf));
}




void httpdPrintf(httpd *server, char *fmt, ...)
{
        va_list         args;
	char		buf[HTTP_MAX_LEN];

        va_start(args, fmt);
	if (server->response.headersSent == 0)
		httpdSendHeaders(server);
	vsnprintf(buf, HTTP_MAX_LEN, fmt, args);
	server->response.responseLength += strlen(buf);
	_httpd_net_write( server->clientSock, buf, strlen(buf));
}




void httpdProcessRequest(httpd *server)
{
	char	dirName[HTTP_MAX_URL],
	    entryName[HTTP_MAX_URL],
	    *cp;
	httpDir	*dir;
	httpContent *entry;

	server->response.responseLength = 0;
	strncpy(dirName, httpdRequestPath(server), HTTP_MAX_URL);
	cp = rindex(dirName, '/');
	if (cp == NULL) {
		printf("Invalid request path '%s'\n",dirName);
		return;
	}
	strncpy(entryName, cp + 1, HTTP_MAX_URL);
	if (cp != dirName)
		*cp = 0;
	else
		*(cp+1) = 0;
	dir = _httpd_findContentDir(server, dirName, HTTP_FALSE);
	if (dir == NULL) {
		_httpd_send404(server);
		_httpd_writeAccessLog(server);
		return;
	}
	entry = _httpd_findContentEntry(server, dir, entryName);
	if (entry == NULL) {
		_httpd_send404(server);
		_httpd_writeAccessLog(server);
		return;
	}
	if (entry->preload) {
		if ((entry->preload)(server) < 0) {
			_httpd_writeAccessLog(server);
			return;
		}
	}
	switch(entry->type) {
	case HTTP_C_FUNCT:
	case HTTP_C_WILDCARD:
		(entry->function)(server);
		break;

	case HTTP_STATIC:
		_httpd_sendStatic(server, entry->data);
		break;

	case HTTP_FILE:
		_httpd_sendFile(server, entry->path);
		break;

	case HTTP_WILDCARD:
		if (_httpd_sendDirectoryEntry(server,entry,entryName) < 0) {
			_httpd_send404(server);
		}
		break;
	}
	_httpd_writeAccessLog(server);
}

void httpdSetAccessLog(httpd *server, FILE *fp)
{
	server->accessLog = fp;
}

void httpdSetErrorLog(httpd *server, FILE *fp)
{
	server->errorLog = fp;
}

int httpdAuthenticate(httpd *server, char *realm)
{
	char	buffer[255];

	if (server->request.authLength == 0) {
		httpdSetResponse(server, "401 Please Authenticate");
		snprintf(buffer,sizeof(buffer), 
		    "WWW-Authenticate: Basic realm=\"%s\"\n", realm);
		httpdAddHeader(server, buffer);
		httpdOutput(server,"\n");
	}
	return 0;
}


int httpdForceAuthenticate(httpd *server, char *realm)
{
	char	buffer[255];

	httpdSetResponse(server, "401 Please Authenticate");
	snprintf(buffer,sizeof(buffer), 
	    "WWW-Authenticate: Basic realm=\"%s\"\n", realm);
	httpdAddHeader(server, buffer);
	httpdOutput(server,"\n");
	return 0;
}




int _httpd_net_read(int sock, char *buf, int len)
{
#if defined(_WIN32) 
	return( recv(sock, buf, len, 0));
#else
	return( read(sock, buf, len));
#endif
}


int _httpd_net_write(int sock, char *buf, int len)
{
#if defined(_WIN32) 
	return( send(sock, buf, len, 0));
#else
	return( write(sock, buf, len));
#endif
}

int _httpd_readChar(server, cp)
	httpd	*server;
	char	*cp;
{
	if (server->readBufRemain == 0) {
		bzero(server->readBuf, HTTP_READ_BUF_LEN + 1);
		server->readBufRemain = _httpd_net_read(server->clientSock, 
		    server->readBuf, HTTP_READ_BUF_LEN);
		if (server->readBufRemain < 1)
			return(0);
		server->readBuf[server->readBufRemain] = 0;
		server->readBufPtr = server->readBuf;
	}
	*cp = *server->readBufPtr++;
	server->readBufRemain--;
	return(1);
}


int _httpd_readLine(httpd *server, char *destBuf, int len)
{
	char	curChar,
	    *dst;
	int	count;
	

	count = 0;
	dst = destBuf;
	while(count < len) {
		if (_httpd_readChar(server, &curChar) < 1)
			return(0);
		if (curChar == '\n') {
			*dst = 0;
			return(1);
		}
		if (curChar == '\r') {
			continue;
		}
		else {
			*dst++ = curChar;
			count++;
		}
	}
	*dst = 0;
	return(1);
}


int _httpd_readBuf(httpd *server, char *destBuf, int len)
{
	char	curChar,
	    *dst;
	int	count;
	

	count = 0;
	dst = destBuf;
	while(count < len) {
		if (_httpd_readChar(server, &curChar) < 1)
			return(0);
		*dst++ = curChar;
		count++;
	}
	return(1);
}

void _httpd_writeAccessLog(httpd *server)
{
	char	dateBuf[30];
	struct 	tm *timePtr;
	time_t	clock;
	int	responseCode;

	if (server->accessLog == NULL)
		return;
	clock = time(NULL);
	timePtr = localtime(&clock);
	strftime(dateBuf, 30, "%d/%b/%Y:%T %Z",  timePtr);
	responseCode = atoi(server->response.response);
	fprintf(server->accessLog, "%s - - [%s] %s \"%s\" %d %d\n", 
	    server->clientAddr, dateBuf, httpdRequestMethodName(server), 
	    httpdRequestPath(server), responseCode, 
	    server->response.responseLength);
}

void _httpd_writeErrorLog(httpd *server, char *level, char *message)
{
	char	dateBuf[30];
	struct 	tm *timePtr;
	time_t	clock;

	if (server->errorLog == NULL)
		return;
	clock = time(NULL);
	timePtr = localtime(&clock);
	strftime(dateBuf, 30, "%a %b %d %T %Y",  timePtr);
	if (*server->clientAddr != 0) {
		fprintf(server->errorLog, "[%s] [%s] [client %s] %s\n",
		    dateBuf, level, server->clientAddr, message);
	}
	else {
		fprintf(server->errorLog, "[%s] [%s] %s\n",
		    dateBuf, level, message);
	}
}



int _httpd_decode (char *bufcoded, char *bufplain, int outbufsize)
{
	static char six2pr[64] = {
    		'A','B','C','D','E','F','G','H','I','J','K','L','M',
    		'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    		'a','b','c','d','e','f','g','h','i','j','k','l','m',
    		'n','o','p','q','r','s','t','u','v','w','x','y','z',
    		'0','1','2','3','4','5','6','7','8','9','+','/'   
	};
  
	static unsigned char pr2six[256];

	/* single character decode */
#	define DEC(c) pr2six[(int)c]
#	define _DECODE_MAXVAL 63

	static int first = 1;

	int nbytesdecoded, j;
	register char *bufin = bufcoded;
	register unsigned char *bufout = (unsigned char*)bufplain;
	register int nprbytes;

	/*
	 * If this is the first call, initialize the mapping table.
	 * This code should work even on non-ASCII machines.
	 */
	if(first) {
		first = 0;
		for(j=0; j<256; j++) pr2six[j] = _DECODE_MAXVAL+1;
		for(j=0; j<64; j++) pr2six[(int)six2pr[j]] = (unsigned char)j;
	}

   	/* Strip leading whitespace. */

   	while(*bufcoded==' ' || *bufcoded == '\t') bufcoded++;

	/*
	 * Figure out how many characters are in the input buffer.
	 * If this would decode into more bytes than would fit into
	 * the output buffer, adjust the number of input bytes downwards.
	 */
	bufin = bufcoded;
	while(pr2six[(int)*(bufin++)] <= _DECODE_MAXVAL);
	nprbytes = bufin - bufcoded - 1;
	nbytesdecoded = ((nprbytes+3)/4) * 3;
	if(nbytesdecoded > outbufsize) {
		nprbytes = (outbufsize*4)/3;
	}
	bufin = bufcoded;
   
	while (nprbytes > 0) {
		*(bufout++)=(unsigned char)(DEC(*bufin)<<2|DEC(bufin[1])>>4);
		*(bufout++)=(unsigned char)(DEC(bufin[1])<<4|DEC(bufin[2])>>2);
		*(bufout++)=(unsigned char)(DEC(bufin[2])<<6|DEC(bufin[3]));
		bufin += 4;
		nprbytes -= 4;
	}
	if(nprbytes & 03) {
		if(pr2six[(int)bufin[-2]] > _DECODE_MAXVAL) {
			nbytesdecoded -= 2;
		}
		else {
			nbytesdecoded -= 1;
		}
	}
	bufplain[nbytesdecoded] = 0;
	return(nbytesdecoded);
}



char _httpd_from_hex (char c)
{
	return  c >= '0' && c <= '9' ?  c - '0'
            : c >= 'A' && c <= 'F'? c - 'A' + 10
            : c - 'a' + 10;     /* accept small letters just in case */
}

char * _httpd_unescape(char *str)
{
	char * p = str;
	char * q = str;
	static char blank[] = "";

	if (!str)
		return(blank);
	while(*p) {
		if (*p == '%') {
			p++;
			if (*p) *q = _httpd_from_hex(*p++) * 16;
			if (*p) *q = (*q + _httpd_from_hex(*p++));
			q++;
		} else {
			if (*p == '+') {
				*q++ = ' ';
				p++;
			} else {
				*q++ = *p++;
			}
		}
	}

	*q++ = 0;
	return str;
} 


void _httpd_freeVariables(httpVar *var)
{
	httpVar	*curVar, *lastVar;

	if (var == NULL)
		return;
	_httpd_freeVariables(var->nextVariable);
	var->nextVariable = NULL;
	curVar = var;
	while(curVar) {
		lastVar = curVar;
		curVar = curVar->nextValue;
		free(lastVar->name);
		free(lastVar->value);
		free(lastVar);
	}
	return;
}

void _httpd_storeData(httpd *server, char *query)
{
        char    *cp,
	    *cp2,
	    var[50],
	    *val,
	    *tmpVal;

        if (!query)
                return;

	cp = query;
	cp2 = var;
        bzero(var,sizeof(var));
	val = NULL;
        while(*cp) {
                if (*cp == '=') {
                        cp++;
			*cp2 = 0;
                        val = cp;
                        continue;
                }
                if (*cp == '&') {
			*cp = 0;
			tmpVal = _httpd_unescape(val);
			httpdAddVariable(server, var, tmpVal);
                        cp++;
                        cp2 = var;
			val = NULL;
                        continue;
                }
		if (val) {
			cp++;
		}
		else {
                	*cp2 = *cp++;
			if (*cp2 == '.') {
				strcpy(cp2,"_dot_");
				cp2 += 5;
			}
			else {
				cp2++;
			}
		}
        }
	*cp = 0;
	tmpVal = _httpd_unescape(val);
	httpdAddVariable(server, var, tmpVal);
}


void _httpd_formatTimeString(httpd *server, char *ptr, int clock)
{
	struct 	tm *timePtr;

	if (clock == 0)
		clock = time(NULL);
	timePtr = gmtime((time_t*)&clock);
	strftime(ptr, HTTP_TIME_STRING_LEN,"%a, %d %b %Y %T GMT",timePtr);
}


void _httpd_sendHeaders(httpd *server, int contentLength, int modTime)
{
	char	tmpBuf[80],
	    timeBuf[HTTP_TIME_STRING_LEN];

	if(server->response.headersSent)
		return;

	server->response.headersSent = 1;
	_httpd_net_write(server->clientSock, "HTTP/1.0 ", 9);
	_httpd_net_write(server->clientSock, server->response.response, 
	    strlen(server->response.response));
	_httpd_net_write(server->clientSock, server->response.headers, 
	    strlen(server->response.headers));

	_httpd_formatTimeString(server, timeBuf, 0);
	_httpd_net_write(server->clientSock,"Date: ", 6);
	_httpd_net_write(server->clientSock, timeBuf, strlen(timeBuf));
	_httpd_net_write(server->clientSock, "\n", 1);

	_httpd_net_write(server->clientSock, "Connection: close\n", 18);
	_httpd_net_write(server->clientSock, "Content-Type: ", 14);
	_httpd_net_write(server->clientSock, server->response.contentType, 
	    strlen(server->response.contentType));
	_httpd_net_write(server->clientSock, "\n", 1);

	if (contentLength > 0) {
		_httpd_net_write(server->clientSock, "Content-Length: ", 16);
		snprintf(tmpBuf, sizeof(tmpBuf), "%d", contentLength);
		_httpd_net_write(server->clientSock, tmpBuf, strlen(tmpBuf));
		_httpd_net_write(server->clientSock, "\n", 1);

		_httpd_formatTimeString(server, timeBuf, modTime);
		_httpd_net_write(server->clientSock, "Last-Modified: ", 15);
		_httpd_net_write(server->clientSock, timeBuf, strlen(timeBuf));
		_httpd_net_write(server->clientSock, "\n", 1);
	}
	_httpd_net_write(server->clientSock, "\n", 1);
}

httpDir *_httpd_findContentDir(httpd *server, char *dir, int createFlag)
{
	char	buffer[HTTP_MAX_URL],
	    *curDir;
	httpDir	*curItem,
	    *curChild;

	strncpy(buffer, dir, HTTP_MAX_URL);
	curItem = server->content;
	curDir = strtok(buffer,"/");
	while(curDir) {
		curChild = curItem->children;
		while(curChild)	{
			if (strcmp(curChild->name, curDir) == 0)
				break;
			curChild = curChild->next;
		}
		if (curChild == NULL) {
			if (createFlag == HTTP_TRUE) {
				curChild = malloc(sizeof(httpDir));
				bzero(curChild, sizeof(httpDir));
				curChild->name = strdup(curDir);
				curChild->next = curItem->children;
				curItem->children = curChild;
			}
			else {
				return(NULL);
			}
		}
		curItem = curChild;
		curDir = strtok(NULL,"/");
	}
	return(curItem);
}


httpContent *_httpd_findContentEntry(httpd *server, httpDir *dir, char *entryName)
{
	httpContent *curEntry;

	curEntry = dir->entries;
	while(curEntry)	{
		if (curEntry->type == HTTP_WILDCARD || 
		    curEntry->type ==HTTP_C_WILDCARD)
			break;
		if (*entryName == 0 && curEntry->indexFlag)
			break;
		if (strcmp(curEntry->name, entryName) == 0)
			break;
		curEntry = curEntry->next;
	}
	if (curEntry)
		server->response.content = curEntry;
	return(curEntry);
}


void _httpd_send304(httpd *server)
{
	httpdSetResponse(server, "304 Not Modified\n");
	_httpd_sendHeaders(server,0,0);
}


void _httpd_send403(httpd *server)
{
	httpdSetResponse(server, "403 Permission Denied\n");
	_httpd_sendHeaders(server,0,0);
	_httpd_sendText(server,
	    "<HTML><HEAD><TITLE>403 Permission Denied</TITLE></HEAD>\n");
	_httpd_sendText(server,
	    "<BODY><H1>Access to the request URL was denied!</H1>\n");
}


void _httpd_send404(httpd *server)
{
	char	msg[HTTP_MAX_URL];

	snprintf(msg, HTTP_MAX_URL,
	    "File does not exist: %s", server->request.path);
	_httpd_writeErrorLog(server,LEVEL_ERROR, msg);
	httpdSetResponse(server, "404 Not Found\n");
	_httpd_sendHeaders(server,0,0);
	_httpd_sendText(server,
	    "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>\n");
	_httpd_sendText(server,
	    "<BODY><H1>The request URL was not found!</H1>\n");
	_httpd_sendText(server, "</BODY></HTML>\n");
}


void _httpd_catFile(httpd *server, char *path)
{
	int	fd,
	    len;
	char	buf[HTTP_MAX_LEN];

	fd = open(path,O_RDONLY);
	if (fd < 0)
		return;
	len = read(fd, buf, HTTP_MAX_LEN);
	while(len > 0)
		{
			server->response.responseLength += len;
			_httpd_net_write(server->clientSock, buf, len);
			len = read(fd, buf, HTTP_MAX_LEN);
		}
	close(fd);
}


void _httpd_sendStatic(httpd *server, char *data)
{
	if (_httpd_checkLastModified(server,server->startTime) == 0) {
		_httpd_send304(server);
	}
	_httpd_sendHeaders(server, server->startTime, strlen(data));
	httpdOutput(server, data);
}



void _httpd_sendFile(httpd *server, char *path)
{
	char	*suffix;
	struct 	stat sbuf;

	suffix = rindex(path, '.');
	if (suffix != NULL) {
		if (strcasecmp(suffix,".gif") == 0) 
			strcpy(server->response.contentType,"image/gif");
		if (strcasecmp(suffix,".jpg") == 0) 
			strcpy(server->response.contentType,"image/jpeg");
		if (strcasecmp(suffix,".xbm") == 0) 
			strcpy(server->response.contentType,"image/xbm");
		if (strcasecmp(suffix,".png") == 0) 
			strcpy(server->response.contentType,"image/png");
	}
	if (stat(path, &sbuf) < 0) {
		_httpd_send404(server);
		return;
	}
	if (_httpd_checkLastModified(server,sbuf.st_mtime) == 0) {
		_httpd_send304(server);
	}
	else {
		_httpd_sendHeaders(server, sbuf.st_size, sbuf.st_mtime);
		_httpd_catFile(server, path);
	}
}


int _httpd_sendDirectoryEntry(httpd *server, httpContent *entry, char *entryName)
{
	char		path[HTTP_MAX_URL];

	snprintf(path, HTTP_MAX_URL, "%s/%s", entry->path, entryName);
	_httpd_sendFile(server,path);
	return(0);
}


void _httpd_sendText(httpd *server, char *msg)
{
	server->response.responseLength += strlen(msg);
	_httpd_net_write(server->clientSock,msg,strlen(msg));
}


int _httpd_checkLastModified(httpd *server, int modTime)
{
	char 	timeBuf[HTTP_TIME_STRING_LEN];

	_httpd_formatTimeString(server, timeBuf, modTime);
	if (strcmp(timeBuf, server->request.ifModified) == 0)
		return(0);
	return(1);
}


static unsigned char isAcceptable[96] =

/* Overencodes */
#define URL_XALPHAS     (unsigned char) 1
#define URL_XPALPHAS    (unsigned char) 2

/*      Bit 0           xalpha          -- see HTFile.h
 *      Bit 1           xpalpha         -- as xalpha but with plus.
 *      Bit 2 ...       path            -- as xpalpha but with /
 */
    /*   0 1 2 3 4 5 6 7 8 9 A B C D E F */
{    7,0,0,0,0,0,0,0,0,0,7,0,0,7,7,7,       /* 2x   !"#$%&'()*+,-./ */
     7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,       /* 3x  0123456789:;<=>?  */
     7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 4x  @ABCDEFGHIJKLMNO */
     7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7,       /* 5X  PQRSTUVWXYZ[\]^_ */
     0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,       /* 6x  `abcdefghijklmno */
     7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0 };     /* 7X  pqrstuvwxyz{\}~ DEL */
 
#define ACCEPTABLE(a)   ( a>=32 && a<128 && ((isAcceptable[a-32]) & mask))

static char *hex = "0123456789ABCDEF";


char *_httpd_escape(char *str)
{
	unsigned char mask = URL_XPALPHAS;
	char * p;
	char * q;
	char * result;
	int unacceptable = 0;
	for(p=str; *p; p++)
		if (!ACCEPTABLE((unsigned char)*p))
			unacceptable +=2;
	result = (char *) malloc(p-str + unacceptable + 1);
	bzero(result,(p-str + unacceptable + 1));

	if (result == NULL) {
		return(NULL);
	}
	for(q=result, p=str; *p; p++) {
		unsigned char a = *p;
		if (!ACCEPTABLE(a)) {
			*q++ = '%';  /* Means hex commming */
			*q++ = hex[a >> 4];
			*q++ = hex[a & 15];
		}
		else *q++ = *p;
	}
	*q++ = 0;                   /* Terminate */
	return result;
}



void _httpd_sanitiseUrl(char *url)
{
	char	*from,
	    *to,
	    *last;

	/*
	 * Remove multiple slashes
	 */
	from = to = url;
	while(*from) {
		if (*from == '/' && *(from+1) == '/') {
			from++;
			continue;
		}
		*to = *from;
		to++;
		from++;
	}
	*to = 0;


	/*
	 * Get rid of ./ sequences
	 */
	from = to = url;
	while(*from) {
		if (*from == '/' && *(from+1) == '.' && *(from+2) == '/') {
			from += 2;
			continue;
		}
		*to = *from;
		to++;
		from++;
	}
	*to = 0;


	/*
	 * Catch use of /../ sequences and remove them.  Must track the
	 * path structure and remove the previous path element.
	 */
	from = to = last = url;
	while(*from) {
		if (*from == '/' && *(from+1) == '.' && 
		    *(from+2)=='.' && *(from+3) == '/') {
			to = last;
			from += 3;
			continue;
		}
		if (*from == '/') {
			last = to;
		}
		*to = *from;
		to++;
		from++;
	}
	*to = 0;
}



/**************************************************************************
** PRIVATE ROUTINES
**************************************************************************/

static int scanCidr(char *val, int *result, int *length)
{
	u_int	res, res1, res2, res3, res4, res5;
	char	*cp;

	cp = val;
	res1 = atoi(cp);
	cp = index(cp,'.');
	if (!cp)
		return(-1);
	cp++;
	res2 = atoi(cp);
	cp = index(cp,'.');
	if (!cp)
		return(-1);
	cp++;
	res3 = atoi(cp);
	cp = index(cp,'.');
	if (!cp)
		return(-1);
	cp++;
	res4 = atoi(cp);
	cp = index(cp,'/');
	if (!cp) {
		res5 = 32;
	}
	else {
		cp++;
		res5 = atoi(cp);
	}

	if (res1>255 || res2>255 || res3>255 || res4>255 || res5>32) {
		return(-1);
	}
	res = (res1 << 24) + (res2 << 16) + (res3 << 8) + res4;
	*result = res;
	*length = res5;
	return(0);
}


static int _isInCidrBlock(httpd *server, int addr1, int len1, int addr2, int len2)
{
	int	count,
	    mask;

	/* if (addr1 == 0 && len1 == 0) {
	   return(1);
	   }*/

	if(len2 < len1) {
		_httpd_writeErrorLog(server,LEVEL_ERROR,
		    "IP Address must be more specific than network block");
		return(0);
	}

	mask = count = 0;
	while(count < len1) {
		mask = (mask << 1) + 1;
		count++;
	}
	mask = mask << (32 - len1);
	if ( (addr1 & mask) == (addr2 & mask)) {
		return(1);
	}
	else {
		return(0);
	}
}


/**************************************************************************
** PUBLIC ROUTINES
**************************************************************************/

httpAcl *httpdAddAcl(httpd *server, httpAcl *acl, char *cidr, int action)
{
	httpAcl	*cur;
	int	addr,
	    len;

	/*
	 * Check the ACL info is reasonable
	 */
	if(scanCidr(cidr, &addr, &len) < 0) {
		_httpd_writeErrorLog(server,LEVEL_ERROR,
		    "Invalid IP address format");
		return(NULL);
	}
	if (action != HTTP_ACL_PERMIT && action != HTTP_ACL_DENY) {
		_httpd_writeErrorLog(server,LEVEL_ERROR,
		    "Invalid acl action");
		return(NULL);
	}

	/*
	 * Find a spot to put this ACE
	 */	
	if (acl) {
		cur = acl;
		while(cur->next) {
			cur = cur->next;
		}
		cur->next = (httpAcl*)malloc(sizeof(httpAcl));
		cur = cur->next;
	}
	else {
		cur = (httpAcl*)malloc(sizeof(httpAcl));
		acl = cur;
	}

	/*
	 * Add the details and return
	 */
	cur->addr = addr;
	cur->len = len;
	cur->action = action;
	cur->next = NULL;
	return(acl);
}


int httpdCheckAcl(httpd *server, httpAcl *acl)
{
	httpAcl	*cur;
	int	addr, len,
	    res,
	    action;


	action = HTTP_ACL_DENY;
	scanCidr(server->clientAddr, &addr, &len);
	cur = acl;
	while(cur) {
		res = _isInCidrBlock(server, cur->addr, cur->len, addr, len);
		if (res == 1) {
			action = cur->action;
			break;
		}
		cur = cur->next;
	}
	if (action == HTTP_ACL_DENY) {
		_httpd_send403(server);
		_httpd_writeErrorLog(server,LEVEL_ERROR,
		    "Access denied by ACL");
	}
	return(action);
}


void httpdSetDefaultAcl(httpd *server, httpAcl *acl)
{
	server->defaultAcl = acl;
}
