/*
 * @(#)HTTPServer.cpp
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 2002-2003 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * Jörg Haeger, 18.04.2002
 */

#include "BufferedReader.h"
#include "Config.h"
#include "File.h"
#include "HTTPRequest.h"
#include "HTTPResponse.h"
#include "HTTPServer.h"
#include "Log.h"
#include "PrintWriter.h"
#include "ServerPro.h"
#include "Socket.h"
#include "Status.h"
#include "String.h"
#include "StringBuffer.h"
#include "Version.h"

HTTPServer::HTTPServer(Socket &socket): socket(socket) {
}

HTTPServer::~HTTPServer() {
}

HTTPRequest *HTTPServer::nextRequest() {
	return new HTTPRequest(socket.getInputStream());
}

StringBuffer &HTTPServer::replace(StringBuffer &buf,
		const char *key, String &by) {
	log.debug("HTTPServer::replace", S.e + buf.length());
	int pos = 0;
	while (1) {
		pos = buf.indexOf(key, pos);
		if (pos < 0)
			break;
		buf.delete_(pos, pos + strlen(key));
		buf.insert(pos, by.getBytes());
		pos += by.length();
		log.debug("HTTPServer::replace", S.e + buf.length());
	}
	return buf;
}

void HTTPServer::run() {
	log.put(4, S.e + "HTTPServer::run() start "
		+ socket.getOutputStream());
/*	try {
		socket.setKeepAlive(true);
		socket.setSoLinger(false, 10);
	}
	catch (Exception *e) {
		log.put(1, S.e + e->getMessage());
		delete e;
	}
*/
	PrintWriter writer(socket.getOutputStream());

	while (1) {
		HTTPRequest request = nextRequest();

		if (request.method.equalsIgnoreCase("GET")
			|| request.method.equalsIgnoreCase("HEAD")) {
			log.put(4, S.e + socket.getInetAddress()
				+ " <" + request.method
				+ "> <" + request.uri
				+ "> <" + request.protocol + ">");

			if (request.uri.endsWith(".html")) {
				int forNetscape4 = 0;
				if (request.uri.equals("/webCDcreator/4netscape.html")) {
					forNetscape4 = 1;
					File file = new File(config.getHttpDir(),
						"/webCDcreator/4netscape/swing.jar");
					if (!file.exists())
						request.uri = "/webCDcreator/4netscape/swing.html";
				}

				if (request.uri.endsWith("/4MJR.html")
					|| forNetscape4
					|| request.uri.endsWith("/4pluginIE.html")
					|| request.uri.endsWith("/4pluginRSA.html")) {
					File file = new File(config.getHttpDir(),
						"/webCDcreator/4pluginRSA/webCDcreator.jar");
					String str = "/webCDcreator/4pluginRSA/version-";
					str = str + Version.version;
					File version = new File(config.getHttpDir(), str);
					if (!file.exists() || !version.exists())
						request.uri = "/webCDcreator/4pluginRSA/info.html";
				}
			}

			if (request.uri.endsWith("/"))
				request.uri = request.uri + "index.html";
			if (request.uri.equals("/index.html"))
				request.uri = "/status.html";

			if (request.uri.indexOf("..") == -1
					&& request.uri.startsWith("/"))
			try {
				serve(request, writer);
				log.put(4, "HTTPServer::run() OK");
				break;
			} catch (Exception *e) {
				HTTPResponse response = HTTPResponse::notFound();
				response.write(writer);
				delete e;
				break;
			}

			HTTPResponse response = HTTPResponse::notFound();
			response.write(writer);
			break;
		}
		else if (request.method.equalsIgnoreCase("CDWP")) {
			ServerPro server(socket.getOutputStream());
			server.run();
			break;
		}
		else if (request.method.equalsIgnoreCase("POST")
				&& (request.uri.startsWith("/config/"))) {
			log.put(2, S.e + "POST " + request.uri);
			static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
			try {
				pthread_mutex_lock(&mutex);
				HTTPResponse response = new HTTPResponse();
				const char LF = '\n', CR = '\r';
				StringBuffer sb = new StringBuffer(request.contentLength);
				for (int i = 0; i < request.contentLength; i++) {
					char ch = socket.getInputStream().read();
					if (ch == CR) {
						// CRLF -> LF
						if (i < request.contentLength - 1) {
							ch = socket.getInputStream().read();
							i++;
							if (ch != LF)
								sb.append(LF);
						}
						else
							ch = LF;
					}
					else
					if (ch == '&')
						ch = LF;
					else
					if (ch == '+')
						ch = ' ';
					else
					if (ch == '%' && i < request.contentLength - 2) {
						char hex[2];
						hex[0] = socket.getInputStream().read();
						hex[1] = socket.getInputStream().read();
						i += 2;
						ch = Server::hextoint(hex, 2);
						if (ch < 0)
							ch = '_';
					}

					sb.append(ch);
				}

				try {
					String str = new String(sb);
					config.updateEditor(str);
					String passwdKey = PASSWORD;
					String password = config.getEditorValue(passwdKey);
					Server::activateConfig();
					config.setEditor(PASSWORD, password);
				}
				catch (Exception *e) {
					log.put(5, S.e + e->toString());
					delete e;
				}

				serve(request, writer);
				config.resetEditor();
				pthread_mutex_unlock(&mutex);
			}
			catch (Exception *e) {
				config.resetEditor();
				pthread_mutex_unlock(&mutex);
				HTTPResponse response = HTTPResponse::notImplemented();
				response.write(writer);
				delete e;
			}
			break;
		}
		else {
			HTTPResponse response = HTTPResponse::notImplemented();
			response.write(writer);
			break;
		}
	}

	log.put(4, "HTTPServer::run() end");
}

void HTTPServer::serve(HTTPRequest &request, PrintWriter &writer) {
	String &method = request.method;
	String &uri = request.uri;
	String &host = request.host;

	int refresh = 0;
	if (uri.startsWith("/status.html?interval=")) {
		for (int i = uri.indexOf("=") + 1; i < uri.length(); i++)
			refresh = 10 * refresh + (uri.charAt(i) - '0');
		uri = "/status.html";
	}

	File file = new File(config.getHttpDir(), uri.getBytes());
	if (file.isDirectory())
		file = new File(file, "index.html");

	HTTPResponse response = new HTTPResponse();
	if (file.getPath().endsWith(".html")
			|| file.getPath().endsWith(".jnlp")) {
		StringBuffer workBuf = request.languages.load(file);

		if (uri.equals("/about.html")) {
			String license = Server::getLicense();
			if (!license.startsWith("GNU")) {
				ServerPro server(-1);
			}
			workBuf.delete_(0, workBuf.length());
			String str = status.getAbout();
			workBuf.append(str.getBytes());
		}

		if (uri.equals("/status.html")) {
			if (config.getFirstErrorPriority() >= 100)
				Server::activateConfig();

			String license = Server::getLicense();
			if (license.length() == 0) {
				ServerPro server(-1);
			}

			StringBuffer b;
			String no = new String(b + status.getStatusNo());
			replace(workBuf, "@STATUSNO@", no);

			String str = status.getStatus();
//			if (str.length() > 0)
//				replace(workBuf, "<!-- status -->", str);

			str = status.getCDROMs();
			replace(workBuf, "<!-- CDROMs -->", str);

			str = status.getConnects();
			replace(workBuf, "<!-- connects -->", str);

			str = status.getSessions();
			replace(workBuf, "<!-- sessions -->", str);

			str = status.getStatistics();
			replace(workBuf, "<!-- statistics -->", str);

			str = status.getStore();
			replace(workBuf, "<!-- store -->", str);

			str = status.getWriters();
			replace(workBuf, "<!-- writers -->", str);
		}

		if (uri.equals("/config.html")) {
			String str = config.toHTML();
			replace(workBuf, "<!-- config -->", str);
		}

		if (uri.startsWith("/config/")) {
			if (workBuf.indexOf("<!-- firstErrorHelp -->") >= 0) {
				StringBuffer sb = config.getFirstErrorHelp();
				String str = "\n<table bgcolor=#E0E0E0 "
					"cellpadding=10 width=\"100%\"><tr><td>\n";
				replace(sb, "[", str);
				str = "\n</tr></td></table>\n";
				replace(sb, "]", str);
				if (sb.length() > 0) {
					String str = new String(sb);
					replace(workBuf, "<!-- firstErrorHelp -->", str);
				}
			}

			if (workBuf.indexOf("<!-- menu -->") >= 0) {
				File f1 = new File(config.getHttpDir(), "config");
				File f2 = new File(f1, "menu");
				StringBuffer sb = request.languages.load(f2);
				if (sb.length() > 0) {
					String str = new String(sb);
					replace(workBuf, "<!-- menu -->", str);
				}
			}

			if (workBuf.indexOf("<!-- writeBox -->") >= 0) {
				File f1 = new File(config.getHttpDir(), "config");
				File f2 = new File(f1, "writeBox");
				StringBuffer sb = request.languages.load(f2);
				if (sb.length() > 0) {
					String str = new String(sb);
					replace(workBuf, "<!-- writeBox -->", str);
				}
			}

			String str = config.getWritersForm();
			replace(workBuf, "<!-- writers -->", str);

			int p0 = 0;
			while (true) {
				p0 = workBuf.indexOf("@", p0);
				if (p0 < 0)
					break;
				int p = workBuf.indexOf("@", p0 + 1);
				if (p < 0)
					break;
				if (p - p0 < 24) {
					String str0 = workBuf;
					String str = str0.substring(p0 + 1, p);
					try {
						String value = config.getEditorValue(str);
						String str = str0.substring(p0, p + 1);
						replace(workBuf, str.getBytes(), value);
						p = p + value.length() - str.length();
					}
					catch (Exception *e) {
						log.debug("HTTPServer::serve",
							S.e + "replace error: " + e->toString());
						delete e;
					}
				}
				p0 = p + 1;
			}

			p0 = 0;
			while (true) {
				p0 = workBuf.indexOf("<!-- ", p0);
				if (p0 < 0)
					break;
				int p = workBuf.indexOf(" -->", p0 + 1);
				if (p < 0)
					break;
				if (p - p0 < 30) {
					String str0 = workBuf;
					String str = str0.substring(p0 + 5, p);
					try {
						String checked = "";
						if (config.getEditorBoolean(str))
							checked = " checked=\"checked\"";
						String input = "";
						input = input
							+ "<input type=\"hidden\""
								+ " name=\"" + str + "\""
								+ " value=\"off\">"
							+ "<input type=\"checkbox\""
								+ " name=\"" + str + "\""
								+ " value=\"on\""
								+ checked + ">"
								+ " " + str;
						String str = str0.substring(p0, p + 4);
						replace(workBuf, str.getBytes(), input);
					}
					catch (Exception *e) {
						delete e;
						if (str.endsWith("Error"))
						try {
							String str2 = str.substring(0, str.length() - 5);
							String error = config.getError(str2.getBytes());
							String str = str0.substring(p0, p + 4);
							replace(workBuf, str.getBytes(), error);
						}
						catch (Exception *e2) {
							delete e2;
						}
					}
				}
				p0 = p + 1;
			}
		}

		if (workBuf.indexOf("<head>") >= 0) {
			File f = new File(config.getHttpDir(), "head");
			StringBuffer strBuf = request.languages.load(f);
			if (strBuf.length() > 0) {
				String str = new String(strBuf);
				replace(workBuf, "<head>", str);
			}

			if (refresh > 0) {
				String str;
				str = str
					+ "<head>\n"
					+ "<meta http-equiv=\"Refresh\""
					+ " content=\"" + refresh
					+ "; url=http://@SERVER@:@PORT@"
					+ "/status.html?interval="
					+ refresh + "\">";
				replace(workBuf, "<head>", str);
			}
		}

		if (workBuf.indexOf("<body>") >= 0) {
			File f = new File(config.getHttpDir(), "header");
			StringBuffer strBuf = request.languages.load(f);
			if (strBuf.length() > 0) {
				String str = new String(strBuf);
				if (config.getFirstError().length() > 0) {
					String solveLink = "";
					if (!uri.startsWith("/config/error"))
						solveLink = solveLink
							+ "&nbsp;&nbsp;<a href=\""
								+ config.getFirstErrorURL()
							+ "\"><i18n>Solve</i18n>...</a>";
					String bgcolor = "\"#FF8080\"";
					String type = "Error";
					if (config.getFirstErrorPriority() < 100) {
						bgcolor = "\"#FFFF80\"";
						type = "Warning";
					}
					str = str
						+ "<table bgcolor=" + bgcolor
							+ " cellspacing=5 width=\"100%\">\n"
						+ "<tr><td><b>Server Configuration " + type
							+ "</b></td></tr>\n"
						+ "<tr><td>" + config.getFirstError()
							+ solveLink + "</td></tr>\n"
						+ "</table>\n<br>\n";
				}
				replace(workBuf, "<body>", str);
			}

			f = new File(config.getHttpDir(), "footer");
			strBuf = request.languages.load(f);
			if (strBuf.length() > 0) {
				String str = new String(strBuf);
				replace(workBuf, "</body>", str);
			}
		}

		String author = Version.author;
		replace(workBuf, "@AUTHOR@", author);

		String authorHome = Version.authorHome;
		replace(workBuf, "@AUTHORHOME@", authorHome);

		if (workBuf.indexOf("@CONFIGDIR@") >= 0) {
			String configDir = config.getConfigDir();
			if (!configDir.endsWith("/"))
				configDir = configDir + '/';
			replace(workBuf, "@CONFIGDIR@/", configDir);
		}

		if (workBuf.indexOf("@HTTPDIR@") >= 0) {
			String httpDir = config.getHttpDir();
			if (!httpDir.endsWith("/"))
				httpDir = httpDir + '/';
			replace(workBuf, "@HTTPDIR@/", httpDir);
		}

		if (config.getLogoURL().length() > 0) {
			String logo = "";
			logo = logo
				+ "<img align=\"right\" src="
				+ "\"" + config.getLogoURL() + "\" alt=\"Logo\">";
			replace(workBuf, "<!-- logo -->", logo);
		}

		if (workBuf.indexOf("@LANGUAGE@") >= 0) {
			replace(workBuf, "@LANGUAGE@",
				request.languages.getWebCDcreatorLanguage());
			replace(workBuf, "@COUNTRY@",
				request.languages.getWebCDcreatorCountry());
		}

		if (workBuf.indexOf("@LOCALES@") >= 0) {
			String str = request.languages.getLocales();
			replace(workBuf, "@LOCALES@", str);
		}

		if (host.length() > 0)
			replace(workBuf, "@SERVER@", host);

		if (workBuf.indexOf("@PORT@") >= 0) {
			StringBuffer b;
			String port = new String(b + config.getPortNo());
			replace(workBuf, "@PORT@", port);
		}

		String product = Version.product;
		replace(workBuf, "@PRODUCT@", product);

		String projectHome = Version.projectHome;
		replace(workBuf, "@PROJECTHOME@", projectHome);

		if (workBuf.indexOf("<!-- StringBufferExisting -->") >= 0) {
			StringBuffer b;
			String n = new String(b + b.getExisting());
			replace(workBuf, "<!-- StringBufferExisting -->", n);
		}

		if (workBuf.indexOf("<!-- StringBufferNo -->") >= 0) {
			StringBuffer b;
			String no = new String(b + b.getNo());
			replace(workBuf, "<!-- StringBufferNo -->", no);
		}

		String version = Version.version;
		replace(workBuf, "@VERSION@", version);

		if (workBuf.indexOf("<i18n>") >= 0)
			request.languages.i18n(workBuf);

		if (workBuf.indexOf("<dd/>") >= 0) {
			time_t t;
			time(&t);
			struct tm *tm = localtime(&t);
			String year = toString(1900 + tm->tm_year, 4);
			String month = toString(tm->tm_mon + 1, 2);
			String day = toString(tm->tm_mday, 2);
			replace(workBuf, "<yyyy/>", year);
			replace(workBuf, "<mm/>", month);
			replace(workBuf, "<dd/>", day);
		}

		if (workBuf.indexOf("<time/>") >= 0) {
			time_t t;
			time(&t);
			struct tm *tm = localtime(&t);
			String hour = toString(tm->tm_hour, 2);
			String min = toString(tm->tm_min, 2);
			String sec = toString(tm->tm_sec, 2);
			String time = "";
			time = time + hour + ":" + min + ":" + sec;
			replace(workBuf, "<time/>", time);
		}

		if (workBuf.indexOf("utf-8") >= 0) {
			String str = "Ã¶";
			replace(workBuf, "ö", str);
		}

		String entity = new String(workBuf);
		String type;
		if (file.getPath().endsWith(".html"))
			type = "text/html";
		else
			type = "application/x-java-jnlp-file";
		response.setEntity(entity, type);
	} else
		response.setEntity(file);

	try {
		if (method.equalsIgnoreCase("HEAD"))
			response.writeHead(writer);
		else
			response.write(writer);
	}
	catch (Exception *e) {
		log.put(2, S.e + e->getMessage());
		delete e;
	}

	time_t t;
	time(&t);
	struct tm *tm = localtime(&t);
	log.put(1, S.e + socket.getInetAddress() + " - - ["
		+ (1900 + tm->tm_year)
		+ "-" + (tm->tm_mon < 9? "0": "") + (tm->tm_mon + 1)
		+ "-" + (tm->tm_mday < 10? "0": "") + tm->tm_mday
		+ " " + (tm->tm_hour < 10? "0": "") + tm->tm_hour
		+ ":" + (tm->tm_min < 10? "0": "") + tm->tm_min
		+ ":" + (tm->tm_sec < 10? "0": "") + tm->tm_sec
		+ "] \"" + request.method + " " + request.uri
		+ " " + request.protocol
		+ "\" " + response.getStatusCode()
		+ " " + response.getEntityBytesTransferred());
}

String *HTTPServer::toString(int n, int digits) {
	StringBuffer str;
	str.append(n);
	while (str.length() < digits)
		str.insert(0, '0');

	return new String(str);
}
