/******************************************************************************
 *
 * Copyright (c) 1999	TOSHIYUKI ARAI. 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. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * 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.
 *
 *
 *	HTTPCommunicator.cpp
 *
 *****************************************************************************/

// SOL++2000 Sample
// 1999.09.03 Modified to use the class Date in SOL.

#include <sol\StringTokenizer.h>
#include <sol\String.h>
#include <time.h>
#include <sol\SocketSelector.h>
#include "HTTPCommunicator.h"
#include <sol\Date.h>


HTTPCommunicator::HTTPCommunicator(SocketStream* sock, SolWebServer* viewer)
	:Thread(),
	socket(sock),
	webServer(viewer)
{
	Printf("\r\n");
	Printf("[%d] HTTPCommunicator::HTTPCommunicator()\r\n", this);
	docRoot = viewer ->getDocumentRoot();
	cgiRoot = viewer -> getCGIRoot();
}


HTTPCommunicator::~HTTPCommunicator()
{
	Printf("[%d] HTTPCommunicator::~HTTPCommunicator()\r\n", this);

	delete socket;
}


void HTTPCommunicator::run()
{
	if (socket == null) {
		Printf("HTTPCommunicator::run() :Error socket is null\r\n");
		delete this;
		return;
	}
	
	String method;
	String path;
    int lineNumber = 0;

	socket -> nonBlocking();
	
	buffer.clear();
	while (socket -> readLine(buffer) >0) {

        if (lineNumber == 0) {
            const char* ptr = buffer.getBuffer();
            StringTokenizer tokenizer(buffer);
			if (ptr && strlen(ptr) >0) {
				ptr = tokenizer.getToken(method);
				if (ptr) {
					tokenizer.getToken(path);
				}
			}
        }
		const char* request = buffer.getBuffer();
		if (request && strstr(request, "GET")) {
			InetAddress addr;
			socket -> getPeerName(addr);
			Date date;
			String from = (const char*)date;
			from = from + " [";
			from = from + addr.getDottedAddress();
			from = from + "]  ";
			webServer -> append((const char*)from);
			webServer -> append(request);
		}

        if (buffer.getContentSize() == 2) {
            // If the line were empty, break this loop
            break;
        }
        lineNumber++;
		buffer.clear();
	}	

	Printf("Method [%s]\r\n", (const char*)method);
	Printf("Path   [%s]\r\n", (const char*)path);

	if (method == "GET") {

		if (path == "/") {
			path = path + "index.html";
		} else {
			path.replace('/', '\\');
		}

		if (path.find("cgi-bin") ) {
			char fullpath[256];
			String program = (const char*)path;
			const char* pa = program.find("?");
			String param;
			if (pa) {
				pa++;
				param = pa; 
			}
			program.replace('?', '\0');
			sprintf(fullpath, "%s\\%s",(const char*)cgiRoot, (const char*)program);
			File file;
			// Allow file sharing.
			if (file.isExistent(fullpath)) {

				Printf("CGI program %s\r\n", fullpath);
				Printf("PARAM       %s\r\n", (const char*)param);
			}
			webServer -> append("    Sorry, CGI is not supported!\r\n");
			// For future extension.
			sendResponse((const char*)path);
		} else {
			sendResponse((const char*)path);
		}

	}  
	else if (method == "HEAD") {
		sendHeader((const char*)path);	
	} 
	else {
		socket -> printf("HTTP/1.0 400 Unsupported method\r\n");
	}
	socket -> close();

	// This is ugly, but simplifies our implementation of
	// deleting this thread.

	delete this;
}


typedef struct {
	const char* key;
	const char* type;
} ContentType;


const char* HTTPCommunicator::getContentType(const char* path)
{
	const char* content = "*/*";
	
	static ContentType contentType[] = {
		".html", "text/html",
		".txt",  "text/plain",
		".jpg",	 "image/jpeg",
		".gif",  "image/gif",
		".png",	 "image/png",
		".bmp",	 "image/bmp",
		".zip",  "application/zip",
		".tar",  "application/tar",
	
	};

	int size = XtNumber(contentType);

	for (int i = 0; i<size; i++) {
		if (strstr(path, contentType[i].key) ) {
			content = contentType[i].type;
			break;
		}
	}
	return content;
}


Boolean HTTPCommunicator::sendResponse(const char* path)
{
	Boolean rc = False;
	String fullpath = docRoot + path;

	File file;
	// Allow shared-opening.
    if (file.openReadOnly((char*)fullpath, 1)) {
	
		socket -> printf("HTTP/1.0 200 OK\r\n");
		socket -> printf("MIME-Version: 1.0\r\n");
		socket -> printf("Server: %s \r\n", webServer ->getVersion());

		Date date(Date::GMT); 
		socket -> printf("Date: %s\r\n", date.getDate());

		long fileLen = 0;
 
		String content = "text/html";

        Printf("fullpath %s\r\n", (char*)fullpath);
        fileLen = file.fileLength();
		content = getContentType(path);

		FILETIME ft;
		file.getLastWriteTime(ft);
		Date lastModified(ft, Date::GMT);

		socket -> printf("Content-Type: %s\r\n", (const char*)content);	
		socket -> printf("Last-Modified: %s\r\n", lastModified.getDate());
		socket -> printf("Content-Length: %d\r\n", fileLen);
		socket -> printf("\r\n");

		// Send a file to a WebBrowser.
		int len = 0;
		long t = 0;
		SocketSelector selector;
		socket -> nonBlocking();

		selector.setTimeout(0, 100);
		fileSendLoop = True;
			
		while (fileSendLoop) {
			if (fileSendLoop == False) {
				break;
			}
			selector.clearAll();
			selector.setReadable(socket);
			selector.setWritable(socket);
	
			if (selector.select() != SOCKET_ERROR) {
				if (selector.isReadable(socket)) {
					char b[256];
					int l = socket -> recv(b, sizeof(b), 0);
					if (l <= 0) {
						socket -> shutdown(2);
						socket -> close();
						Printf("Peer closed.\r\n");
						break;
					}
				}
					
				if (selector.isWritable(socket)) {
					char buff[512];
				    len = file.read(buff, sizeof(buff)-1);
					if (len <=0) {
						Printf("File end or read error.\r\n");
						break;
					}
					// timeout is 120 seconds
					int l = socket -> sendAll(buff, len, 0,  120);
					if (l == -2) {
						Printf("Timeout \r\n");
						break;
					}

					if (l <=0) {
						Printf("Connection closed.\r\n");
						break;
					}
					if (l > 0) t += l;
				
				}					
			} else {
				Printf("Socket error\r\n");
				break;
			}
		}

		if (fileLen == t) {
			webServer -> printf("    Transfer completed %s  %d bytes\r\n", (char*)fullpath, t);
		} else {
			webServer -> printf("    Transfer aborted %s\r\n", (char*)fullpath);
		}
		
		file.close();
		rc = True;
	} else {
		socket -> printf("HTTP/1.0 404 Not Found\r\n");
		char* notFound = "Sorry, File Not Found";
		socket -> printf("Content-Length: %d\r\n\r\n", strlen(notFound)); 
		socket -> printf(notFound);
		webServer -> printf("    Failed to open file: %s\r\n", 
				(const char*)fullpath);
	}

	Printf("HTTPCommunicator::sendResponse terminated\r\n");
    return rc;
}


// Handling HEAD method
Boolean HTTPCommunicator::sendHeader(const char* path)
{
	Boolean rc = False;
	char fullpath[256];
	sprintf(fullpath, "%s%s", (const char*)docRoot, path);
	File file;

	// Allow shared-opening.
    if (file.openReadOnly(fullpath, 1)) {
	
		socket -> printf("HTTP/1.0 200 OK\r\n");
		socket -> printf("MIME-Version: 1.0\r\n");
		socket -> printf("Server: %s \r\n", webServer ->getVersion());

		Date date(Date::GMT);
		socket -> printf("Date: %s\r\n", date.getDate());

		long fileLen = 0;
 
		String content = "text/html";

        Printf("fullpath %s\r\n", fullpath);
        fileLen = file.fileLength();
		content = getContentType(path);

		FILETIME ft;
		file.getLastWriteTime(ft);
		Date lastModified(ft, Date::GMT);

		socket -> printf("Content-Type: %s\r\n", (const char*)content);		
		socket -> printf("Last-Modified: %s\r\n", lastModified.getDate());
		socket -> printf("Content-Length: %d\r\n\r\n", fileLen);

		file.close();
		rc = True;
	} else {
		socket -> printf("HTTP/1.0 404 Not Found\r\n");
		char* notFound = "Sorry, File Not Found";
		socket -> printf("Content-Length: %d\r\n\r\n", strlen(notFound)); 
		socket -> printf(notFound);

		webServer -> printf("    Failed to open file: %s\r\n", fullpath);
	}
	Printf("HTTPCommunicator::sendHeader terminated\r\n");

	return rc;
}


void HTTPCommunicator::shutdown()
{
	if (socket) {
		socket -> shutdown(2);
		socket -> close();
	}
	fileSendLoop = False;
}
