/*
	ZazouMiniWebServer

	Copyright (C) 2003-2011 Xavier Garreau <xavier@xgarreau.org>

	This file is part of ZazouMiniWebServer.

    ZazouMiniWebServer is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    ZazouMiniWebServer is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ZazouMiniWebServer; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
	
// ZMWSConfig.cpp: implementation of the ZMWSConfig class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ZMWSConfig.h"
#include "ZMWSFileUtils.h"
#include "ZMWSCommon.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

void ZMWSConfig::GetCBaseConfig(CBaseZMWSConfig* conf) const {
	(*conf).bindAddr = strdup(bindAddr.c_str());
	(*conf).documentRoot = strdup(documentRoot.c_str());
	(*conf).serverRoot = strdup(serverRoot.c_str());
	(*conf).logsDir = strdup(logsDir.c_str());
	(*conf).pathToConfigFile = strdup(pathToConfigFile.c_str());
	(*conf).pathToPHP = strdup(pathToPHP.c_str());
	(*conf).browserCmd = strdup(browserCmd.c_str());
	(*conf).defCharSet = strdup(defCharSet.c_str());
	(*conf).startPages = strdup(startPages.c_str());
	(*conf).allowFrom = strdup(allowFrom.c_str());
	(*conf).denyFrom = strdup(denyFrom.c_str());

	(*conf).port = port;
	(*conf).sport = sport;
	(*conf).maxClients = maxClients;
	(*conf).hideConsole = hideConsole;
	(*conf).try808xPorts = try808xPorts;
	(*conf).canIndex = canIndex;
	(*conf).beQuiet = beQuiet;
	(*conf).canStop = canStop;
	(*conf).closeBrowser = closeBrowser;
	(*conf).reverseDNS = reverseDNS;
	(*conf).writeLogs = writeLogs;
	(*conf).browseNow = browseNow; 
	(*conf).dropClients = dropClients;

	(*conf).pbtoken = pbtoken;
}

void ZMWSConfig::FreeCBaseConfig(CBaseZMWSConfig* conf) const {
	free ((*conf).bindAddr);
	free ((*conf).documentRoot);
	free ((*conf).serverRoot);
	free ((*conf).logsDir);
	free ((*conf).pathToConfigFile);
	free ((*conf).pathToPHP);
	free ((*conf).browserCmd);
	free ((*conf).defCharSet);
	free ((*conf).startPages);
	free ((*conf).allowFrom);
	free ((*conf).denyFrom);
}

ZMWSConfig::ZMWSConfig(const int& p,
					   const int& httpsp,
					   const std::string& dr,
					   const std::string& ld,
					   const std::string& ptp,
					   const std::string& bc,
					   const std::string& sp,
					   const std::string& af,
					   const std::string& df,
					   const pbtoken_type pbt,
					   const DWORD& mc,
					   const BOOL& wl,
					   const BOOL& hc,
					   const BOOL& tp,
					   const BOOL& rv,
					   const BOOL& ci, 
					   const BOOL& bq, 
					   const BOOL& cs, 
					   const BOOL& cb, 
					   const BOOL& bn,
					   const BOOL& dc
					   )
{
    char exeFilename[MAX_PATH];
	std::string exePath;
	std::string::size_type lastSlashPos;

	// On se place dans le répertoire de l'exécutable
	GetModuleFileName(NULL,exeFilename,sizeof(exeFilename));
    exePath = exeFilename;
	lastSlashPos = exePath.find_last_of("/\\");			
	exePath.erase(lastSlashPos);
	SetCurrentDirectory(exePath.c_str());

	this->port = p;
	this->sport = httpsp;
	this->documentRoot = dr;
	this->writeLogs = wl;
	this->logsDir = dr+ld;
	this->pathToPHP = ptp;
	this->browserCmd = bc;
	this->startPages = sp;
	this->allowFrom = af;
	this->denyFrom = af;
	this->pbtoken = pbt;
	this->maxClients = mc;
	this->hideConsole = hc;
	this->try808xPorts = tp;
	this->reverseDNS = rv;
	this->canIndex = ci;
	this->beQuiet = bq;
	this->canStop = cs;
	this->closeBrowser = cb;
	this->browseNow = bn;
	this->dropClients = dc;

	ConfFeatures = 0;

	pathToConfigFile = "_config.zmwsc";

	// Reset config parts
	this->DirAliases.clear();
	this->Handlers.clear();
	this->PerUrlConfigs.clear();
	this->VHostsAliases.clear();
	this->VHostsConfig.clear();

	/* Can we load openssl dlls ? */
	/* If no, disable https */
	HMODULE Hlib = LoadLibrary("libeay32.dll");
	if (!Hlib) {
		this->sport = 0;
	} else {
		FreeLibrary (Hlib);
	}
	HMODULE Hssl = LoadLibrary("ssleay32.dll");
	if (!Hssl) {
		this->sport = 0;
	} else {
		FreeLibrary (Hssl);
	}

	/* Get the serverRoot */
	LPTSTR CurDir = NULL;
	DWORD szCurDir = 0;
	if (szCurDir = GetCurrentDirectory(szCurDir, CurDir)) {
		CurDir = (LPTSTR)malloc(szCurDir);
		szCurDir = GetCurrentDirectory(szCurDir, CurDir);
	}
	if (CurDir) {
		int cdtaille = strlen(CurDir);
		if (CurDir[cdtaille-1]== '\\') {
			CurDir[cdtaille-1] = '\0';
		}
		this->serverRoot = CurDir;
		free (CurDir);
	}

	/* set documentRoot to current directory if dr is empty */
	if (!dr.length()) {
		ZMWSFileUtils zfu;
		zfu.getFile(serverRoot + "\\_web.zmwsc");
		if ( zfu.fileExists() && zfu.isDir()) {
			documentRoot = serverRoot + "\\_web.zmwsc";
		} else {
			documentRoot = serverRoot;
		}
	}

	/* Try to find php */
	BYTE phpOK = 0;
	if (!phpOK) {
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "php\\php-cgi";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "c:\\php\\php-cgi";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "c:\\php4\\php-cgi";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "c:\\php5\\php-cgi";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "php5\\php";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "php\\php";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "c:\\php\\php";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}
	if (!phpOK) {
		pathToPHP = "c:\\php4\\php";
		if ( ! ZMWSFileUtils::fileExists((pathToPHP+".exe").c_str())) {
			pathToPHP = "";
		} else {
			phpOK = 1;
		}
	}


	/* Can send directories listing if there's no index.* ? */
	canIndex = ! ZMWSFileUtils::fileExists("_noIndex.zmwsc");

	/* Hide Console ? */
	hideConsole = ZMWSFileUtils::fileExists("_hideConsole.zmwsc");
}

ZMWSConfig::~ZMWSConfig() {
}

BOOL ZMWSConfig::SetPathToConfigFile(const std::string& path) {
	std::string tmpPath = path;
	BOOL result = ZMWSFileUtils::normalizePath(tmpPath);

	if (result) {
		pathToConfigFile = tmpPath;
	}

	return result;
}

BOOL ZMWSConfig::SetPathToPHP(const std::string& path) {
	std::string tmpPath = path + "\\php-cgi.exe";
	ZMWSFileUtils::normalizePath(tmpPath);
	BOOL result = ZMWSFileUtils::fileExists(tmpPath.c_str());
	if (result) {
		pathToPHP = tmpPath;
		return result;
	}
	tmpPath = path+"\\php.exe";
	ZMWSFileUtils::normalizePath(tmpPath);
	result = ZMWSFileUtils::fileExists(tmpPath.c_str());
	if (result) {
		pathToPHP = tmpPath;
		return result;
	}
	tmpPath = path + ".exe";
	ZMWSFileUtils::normalizePath(tmpPath);
	result = ZMWSFileUtils::fileExists(tmpPath.c_str());
	if (result) {
		pathToPHP = tmpPath;
		return result;
	}
	tmpPath = path;
	ZMWSFileUtils::normalizePath(tmpPath);
	result = ZMWSFileUtils::fileExists(tmpPath.c_str());
	if (result) {
		pathToPHP = tmpPath;
		return result;
	}
	return result;
}

BOOL ZMWSConfig::SetLogsDir(const std::string& ld, const std::string& vhost) {
	if (!GetWriteLogs()) return TRUE;
	BOOL result = TRUE;
	std::string tmpLogsDir, tmpLogFileName;

	if (ld.find(':') != std::string::npos || ld.at(0) == '\\' || ld.at(0) == '/') {
		// Absolute path
		tmpLogsDir = ld;
	} else {
		// Relative path
		tmpLogsDir = serverRoot + "\\" + ld;
	}

	ZMWSFileUtils::normalizePath(tmpLogsDir);

	/* Try to Create logs dir */
	if (!CreateDirectory(tmpLogsDir.c_str(), NULL)) {
		DWORD ErrCode = GetLastError();
		if (ErrCode != ERROR_ALREADY_EXISTS) {
			tmpLogFileName = tmpLogsDir+"\\access_log";
			if (vhost.length()) {
				tmpLogFileName += "_" + vhost;
			}
			ZMWSFileUtils::normalizePath(tmpLogFileName);
			std::ofstream logFile (tmpLogFileName.c_str(), std::ios::ate);
			if (!logFile) {
				SetWriteLogs(false);
				result = FALSE;
				logsDir = "";
			} else { 
				logFile.close();
			}
		}
	}

	if (!vhost.length()) {
		logsDir = tmpLogsDir;
	} else {
		VHostsConfig[vhost].logsDir = tmpLogsDir;
	}

	return result;
}

BOOL ZMWSConfig::SetDocumentRoot(const std::string& dr, const std::string& vhost) {
	BOOL result = TRUE;
	std::string tmpDocRoot;

	tmpDocRoot = dr;

	while ((tmpDocRoot.length() > 1) &&
		   (tmpDocRoot[tmpDocRoot.length()-1]=='/' || tmpDocRoot[tmpDocRoot.length()-1]=='\\')
		  ) {
		tmpDocRoot = tmpDocRoot.substr(0, tmpDocRoot.length()-1);
	}

	if (tmpDocRoot.find(':') == std::string::npos && tmpDocRoot.at(0) != '\\' && tmpDocRoot.at(0) != '/') {
		// Relative path
		tmpDocRoot = serverRoot + "\\" + tmpDocRoot;
	}

	ZMWSFileUtils::normalizePath(tmpDocRoot);

	if (!vhost.length())
		documentRoot = tmpDocRoot;
	else
		VHostsConfig[vhost].documentRoot = tmpDocRoot;

	return result;
}


BOOL ZMWSConfig::AddDirAlias(const std::string& alias,
							 const std::string& real,
							 const std::string& vhost) {
	BOOL isVHost = FALSE;
	std::string realpath;
	ZMWSVHostConfig vhc;
	if (vhost.length()) {
		isVHost = this->GetVHostConfig(vhost, vhc);
		if (!isVHost) {
			return FALSE;
		}
	}

	// Si c'est un chemin absolu on le stocke tel quel
	if (real.at(1)==':' || real.at(0) == '\\') {
		realpath = real;
	} else {
		if (real.at(0) == '/') {
			// Si le chemin commence par un /
			// il est relatif au docRoot
			if (isVHost) {
				realpath = vhc.documentRoot;
			} else {
				realpath = this->documentRoot;
			}
		} else {
			// Si le chemin ne commence pas par un /
			// il est relatif au serverRoot
			realpath = this->serverRoot + '\\';
		}
		realpath += real;
	}

	ZMWSFileUtils::normalizePath(realpath);
	if (isVHost) {
		vhc.DirAliases.push_back(DirAliasType(alias, realpath));
		SetVHostConfig(vhc.domain, vhc);
	} else {
		DirAliases.push_back(DirAliasType(alias, realpath));
	}
	return TRUE;
}

std::string ZMWSConfig::GetAliasedDir(const std::string& alias,
									  std::string& AliasedReqRootDir,
									  const std::string& vhost) {
	DirAliasesTypeIterator it;
	std::string result = "";
	int score = 0; // nombre de caract�es en commun
	if (vhost.length()) {
		ZMWSVHostConfig vhc;
		if (GetVHostConfig(vhost, vhc)) {
			for (it = vhc.DirAliases.begin(); it != vhc.DirAliases.end(); ++it) {
				if (0 == alias.compare(0, it->first.length(), it->first)) {
					if (score < it->first.length()) {
						score = it->first.length();
						result = it->second+alias.substr(score);
						AliasedReqRootDir = it->second;
					}
				}
			}
		}
	}
	for (it = DirAliases.begin(); it != DirAliases.end(); ++it) {
		if (0 == alias.compare(0, it->first.length(), it->first)) {
			if (score < it->first.length()) {
				score = it->first.length();
				result = it->second+alias.substr(score);
				AliasedReqRootDir = it->second;
			}
		}
	}
	int next_path_pos = result.find_first_of("/\\", AliasedReqRootDir.length()-2);
	if (next_path_pos != std::string::npos) {
		AliasedReqRootDir = result.substr(0, next_path_pos+1);
	}
	return result;
}

BOOL ZMWSConfig::GetVHostConfig(const std::string& VHostName,
								ZMWSVHostConfig& vhc) {
	BOOL VHostExists = false;
	std::string tmpHostName;
	if (VHostsAliases.find(VHostName) != VHostsAliases.end())
		tmpHostName = VHostsAliases[VHostName];
	else
		tmpHostName = VHostName;
	if (VHostsConfig.find(tmpHostName) != VHostsConfig.end()) {
		vhc = VHostsConfig[tmpHostName];
		VHostExists = true;
	}    
	return VHostExists;
}

BOOL ZMWSConfig::SetVHostConfig(const std::string& VHostName, const ZMWSVHostConfig& vhc) {
	BOOL VHostExists = false;
	std::string tmpHostName;
	if (VHostsAliases.find(VHostName) != VHostsAliases.end())
		tmpHostName = VHostsAliases[VHostName];
	else
		tmpHostName = VHostName;
	if (VHostsConfig.find(tmpHostName) != VHostsConfig.end()) {
		VHostsConfig[tmpHostName] = vhc;
		VHostExists = true;
	}
	return VHostExists;
}

void ZMWSConfig::ConfigureVHosts() {
	ZMWSFileUtils zfu;

	if (!zfu.getFile(documentRoot + "\\_vhosts.zmwsc\\*")) return;

	do {
		if (zfu.isDir() && (zfu.getFileName()!=".") && (zfu.getFileName()!="..")) {
			CreateDefaultVHost(zfu.getFileName());
		}
	} while (zfu.getNextFile());
}

BOOL ZMWSConfig::CreateDefaultVHost (const std::string& domainName) {
	ZMWSVHostConfig vhc, vhc2;
	vhc.canIndex = this->canIndex;
	vhc.reverseDNS = true;
	vhc.allowFrom = "";
	vhc.denyFrom = "";
	vhc.domain = domainName;
	vhc.documentRoot = documentRoot + "\\_vhosts.zmwsc\\" + vhc.domain;
	vhc.logsDir = logsDir;
	vhc.maxKBytes = 0;
	vhc.maxKBytesDTime = 0;
	if (!GetVHostConfig(vhc.domain, vhc2)) {
		VHostsConfig[vhc.domain] = vhc;
		print_dbg (("Created VHost : "+vhc.domain+"\n").c_str());
	}
	return TRUE;
}

std::string ZMWSConfig::GetDomainForVHostAlias(const std::string& alias) {
	std::string tmpHostName = "";
	if (VHostsAliases.find(alias) != VHostsAliases.end()) {
		tmpHostName = VHostsAliases[alias];
	} else {
		if (VHostsConfig.find(alias) != VHostsConfig.end()) {
			tmpHostName = alias;
		}
	}
	return tmpHostName;
}

void ZMWSConfig::AddVHostAlias(const std::string& alias, const std::string& domain) {
	VHostsAliases[alias] = domain;
}

ZMWSHandler ZMWSConfig::GetHandler(const std::string& extension, const std::string& vhost) {
	ZMWSHandler tmpHandler;

	if (vhost.length()) {
		ZMWSVHostConfig vhc;
		if (GetVHostConfig(vhost, vhc)) {
			HandlersType::const_iterator it = vhc.Handlers.find(extension);
			if (it != vhc.Handlers.end()) {
				tmpHandler = it->second;
				return tmpHandler;
			}
		}
	}

	HandlersType::const_iterator it = Handlers.find(extension);
	if (it != Handlers.end()) {
		tmpHandler = it->second;
	}
	return tmpHandler;
}
    
void ZMWSConfig::AddHandler(const std::string& extension, const ZMWSHandler& handler, const std::string& vhost) {
    ZMWSHandler theHandler = handler;
//	if (handler.handlerPath.length()) {
//		if (handler.handlerPath.find(":") != 1 && handler.handlerPath.at(0) != '\\' && handler.handlerPath.at(0) != '/') {
// 			theHandler.handlerPath = serverRoot + '\\' + handler.handlerPath;
//		}
//	}
//  Don't normalize HandlerPaths because, these are command lines not paths
//  eg for .bat cmd /c, we don't want it to become cmd \c do we ? :p
//	ZMWSFileUtils::normalizePath(theHandler.handlerPath);

	if (vhost.length()) {
		ZMWSVHostConfig vhc;
		if (GetVHostConfig(vhost, vhc)) {
			vhc.Handlers[extension] = theHandler;
			SetVHostConfig(vhc.domain, vhc);
		}
	} else {
		Handlers[extension] = theHandler;
	}
}    

BOOL ZMWSConfig::GetPerUrlConfig(const std::string& url, ZMWSPerUrlConfig& puc, const BOOL& exact) {
	PerUrlConfigsTypeIterator it;
	int score = 0;
	for (it = PerUrlConfigs.begin(); it != PerUrlConfigs.end(); ++it) {
		if (exact) {
			if (url == it->first) {
				puc = it->second;
				return TRUE;
			}
		} else {
			if (0 == url.compare(0, it->first.length(), it->first)) {
				if (score < it->first.length()) {
					score = it->first.length();
					puc = it->second;
				}
			}
		}
	}
	return score;
}

BOOL ZMWSConfig::SetPerUrlConfig(const std::string& url, const ZMWSPerUrlConfig& puc) {
	PerUrlConfigsTypeIterator it;
	for (it = PerUrlConfigs.begin(); it != PerUrlConfigs.end(); ++it) {
		if (0 == url.compare(0, it->first.length(), it->first)) {
			it->second = puc;
			return TRUE;
		}
	}
	PerUrlConfigs.push_back(PerUrlConfigType(url, puc));
	return FALSE;
}


BOOL ZMWSConfig::SetDefaultCharset(const std::string& cs, const std::string& vhost) 
{
	if (!vhost.length())
		defCharSet = cs;
	else
		VHostsConfig[vhost].defCharSet = cs;

	return TRUE;
}

BOOL ZMWSConfig::SetAllowFrom(const std::string& af, const std::string& vhost) 
{
	if (!vhost.length())
		allowFrom = af;
	else
		VHostsConfig[vhost].allowFrom = af;

	return TRUE;
}

BOOL ZMWSConfig::SetDenyFrom(const std::string& df, const std::string& vhost) 
{
	if (!vhost.length())
		denyFrom = df;
	else
		VHostsConfig[vhost].denyFrom = df;

	return TRUE;
}

BOOL ZMWSPerUrlConfig::AddHandler (const std::string& extension, const ZMWSHandler& handler) {
    ZMWSHandler theHandler = handler;
//	if (handler.handlerPath.length()) {
//		if (handler.handlerPath.find(":")==1 && handler.handlerPath.at(0) != '\\' && handler.handlerPath.at(0) != '/' ) {
//			theHandler.handlerPath = serverRoot + '\\' + handler.handlerPath;
//		}    
//	}
	// Don't normalize handler paths
	// ZMWSFileUtils::normalizePath(theHandler.handlerPath);
    Handlers[extension] = theHandler;
	return true;
}

ZMWSHandler ZMWSPerUrlConfig::GetHandler(const std::string& extension) const {
	ZMWSHandler tmpHandler;
	HandlersType::const_iterator it = Handlers.find(extension);
	if (it != Handlers.end()) {
		tmpHandler = it->second;
	}
	return tmpHandler;
}
