/*
	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
*/
	
// ZMWS.cpp: implementation of the ZMWS class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ZOSAL.h"
#include <errno.h>
#include <fstream>
#include <time.h>
#include "ZMWS.h"
#include "ZMWSConfig.h"
#include "ZMWSFileUtils.h"
#include "ZMWSReqResp.h"
#include "ZMWSOptParser.h"
#include "ZMWSConfFileParser.h"
#include "ZMWSbase64.h"
#include "ZMWSResolver.h"
#include "ZMWSSAPI.h"
#ifdef WIN32
#include <shellapi.h>
#include <process.h>
#include "ZMWSLogger.h"
#endif

#define WSVERSION MAKEWORD(2,2)

#define ZMWS_TIMEOUT 30 //(secondes)
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
volatile DWORD ZMWS::nbClients = 0;

ZMWS::ZMWS()
{
	std::stringstream verSS;
	verSS << VER_MAJOR << ".";
	verSS << VER_MINOR << ".";
	verSS << VER_MICRO;
	serverVersion = verSS.str();
	if (VER_MINOR%2) 
		verSS << " (unstable) ";
	verSS << "\n";
	print_dbg(("ZazouMiniWebServer "+verSS.str()).c_str());
	std::string tmpS = "\n----------------------------------------\n";
	tmpS += "Copyright (C) 2003-2011 Xavier Garreau <xavier@xgarreau.org>\n";
	tmpS += "ZazouMiniWebServer is free software.\n";
	tmpS += "ZazouMiniWebServer comes with ABSOLUTELY NO WARRANTY.\n";
	tmpS += "You are welcome to redistribute it under certain conditions,\n";
	tmpS += "Read the LICENSE: http://www.fsf.org/licenses/gpl.txt\n";
	tmpS += "----------------------------------------\n";
	print_dbg (tmpS.c_str());

	ZazouModSslHandle = NULL;
	serverSocket = INVALID_SOCKET;
	secureServerSocket = INVALID_SOCKET;
	ServerStop = TRUE;

	int err1 = 0, err2 = 0;
#ifdef WIN32
	ZMWS_SERVERSTOP_MUTEX = CreateMutex(NULL, FALSE, "ZMWS_SERVERSTOP_MUTEX");
	if (!ZMWS_SERVERSTOP_MUTEX) {
		err1=-1;
	}
	ZMWS_NBCLIENTS_MUTEX = CreateMutex(NULL, FALSE, "ZMWS_NBCLIENTS_MUTEX");
	if (!ZMWS_NBCLIENTS_MUTEX) {
		err2=-1;
	}
#else
	err1 = pthread_mutex_init (&ZMWS_SERVERSTOP_MUTEX, NULL);
	err2 = pthread_mutex_init (&ZMWS_NBCLIENTS_MUTEX, NULL);
#endif
	if (err1)
		print_dbg ("ERROR : Could not Create ZMWS_SERVERSTOP_MUTEX Mutex\n");
	if (err2)
		print_dbg ("ERROR : Could not Create ZMWS_NBCLIENTS_MUTEX Mutex\n");

#ifdef WIN32
	// Considérons que ça marche à tous les coups :)
	ZMWSSTOPMESSAGE = RegisterWindowMessage (LPCTSTR("ZazouMiniWebServerStop"));
	// Initialisation winsocks
	WSAStartup(WSVERSION, &wsaData);
#endif
}

ZMWS::~ZMWS()
{
	BOOL l_ServerStop = FALSE;
	DWORD result = WaitForSingleMutex (ZMWS_SERVERSTOP_MUTEX, 5000);
	if (result == WAIT_OBJECT_0) { 
		l_ServerStop = ServerStop;
		ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
		if (l_ServerStop != TRUE)
			StopServer();
	}

#ifdef WIN32
	WSACleanup();
	if (!ZMWS_NBCLIENTS_MUTEX)
		CloseMutex (ZMWS_NBCLIENTS_MUTEX);
	if (!ZMWS_SERVERSTOP_MUTEX)
		CloseMutex (ZMWS_SERVERSTOP_MUTEX);
#else
		CloseMutex (ZMWS_NBCLIENTS_MUTEX);
		CloseMutex (ZMWS_SERVERSTOP_MUTEX);
#endif
}

void ZMWS::HideConsole()
{
		// Le bloc qui suit cache la console...
		// Truc adapté de http://mail.python.org/pipermail/python-list/2002-February/085621.html

		// format a "unique" newWindowTitle
		TCHAR newWindowTitle[256];
		wsprintf(newWindowTitle,"%d/%d",
			GetTickCount(),
			GetCurrentProcessId());
		// change current window title
		SetConsoleTitle(newWindowTitle);
		// ensure window title has been updated
		Sleep(40);
		// look for newWindowTitle
		HWND hwndFound=FindWindow(NULL, newWindowTitle);
		// If found, hide it
		if ( hwndFound != NULL)
			ShowWindow( hwndFound, SW_HIDE);
}

void __cdecl ZMWS::Run(LPVOID param)
{
	ZMWS* obj = static_cast<ZMWS *>(param);
	obj->MainThread();
	_endthread();
}

void __cdecl ZMWS::QueueClient(LPVOID param) {
	QUEUECLIENTPARAM* qcp = static_cast<QUEUECLIENTPARAM*>(param);
	std::string tmpS;
	DWORD local_nbclients;
	DWORD result = WaitForSingleMutex (qcp->object->ZMWS_NBCLIENTS_MUTEX, 5000);
	if (result == WAIT_OBJECT_0) { 
	    local_nbclients = nbClients+1;
		if (nbClients++ > qcp->object->serverConfig.GetMaxClients()) {
			--nbClients;
			ReleaseMutex(qcp->object->ZMWS_NBCLIENTS_MUTEX);
			/* Fermeture de la connexion, brutal */
			closesocket(qcp->clientSocket);
			delete qcp;
			_endthread();
		} else {
			ReleaseMutex(qcp->object->ZMWS_NBCLIENTS_MUTEX);
		}    
	} else {
		tmpS  = "ERROR : QueueClient 1 : Could not acquire NBCLIENTS Mutex\n";
		print_dbg (tmpS.c_str());
    	/* Fermeture de la connexion */
    	closesocket(qcp->clientSocket);
		delete qcp;
		_endthread();
	}

	qcp->modssl_data = NULL;
	if (!qcp->object->serverConfig.GetBeQuiet()) {
		/* Affichage du client */
		std::stringstream tmpSS;
		SOCKADDR_IN clientSockAddr;
		int csaLen = sizeof(SOCKADDR_IN);
		getpeername(qcp->clientSocket, (LPSOCKADDR)&clientSockAddr, &csaLen);

		tmpSS << "Connection accepted (" << local_nbclients << '/' << qcp->object->serverConfig.GetMaxClients();

		tmpSS << ") from ";
		tmpSS << (int)clientSockAddr.sin_addr.S_un.S_un_b.s_b1 << ".";
		tmpSS << (int)clientSockAddr.sin_addr.S_un.S_un_b.s_b2 << ".";
		tmpSS << (int)clientSockAddr.sin_addr.S_un.S_un_b.s_b3 << ".";
		tmpSS << (int)clientSockAddr.sin_addr.S_un.S_un_b.s_b4 << "\n";
		tmpS = tmpSS.str();
		print_dbg (tmpS.c_str());
	}
	qcp->object->HandleClient(qcp);

	/* Fermeture de la connexion */
	closesocket(qcp->clientSocket);

	result = WaitForSingleMutex (qcp->object->ZMWS_NBCLIENTS_MUTEX, 5000);
	if (result == WAIT_OBJECT_0) { 
		--nbClients;
		ReleaseMutex(qcp->object->ZMWS_NBCLIENTS_MUTEX);
	} else {
		tmpS  = "ERROR : QueueClient 2 : Could not acquire NBCLIENTS Mutex\n";
		print_dbg (tmpS.c_str());
		delete qcp;
		_endthread();
	}
	if (!qcp->object->serverConfig.GetBeQuiet()) {
		std::stringstream tmpSS2;
		tmpSS2 << "Duree de la connexion: " << GetTickCount() - qcp->tinit << " msec\n";
		tmpS = tmpSS2.str();
		print_dbg(tmpS.c_str());
	}
	delete qcp;
	_endthread();
}

// End of Internals

void ZMWS::BrowseNow() {
	SOCKET s;
	SOCKADDR_IN sockaddr;
	int i = 0;
	int csalen = sizeof(SOCKADDR);
	unsigned short port, sport;

	s = socket (AF_INET, SOCK_STREAM, 0);
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	port = serverConfig.GetPort();
	if (port) {
		sockaddr.sin_port = htons(port);
	} else {
		sport = serverConfig.GetHTTPSPort();
		sockaddr.sin_port = htons(sport);
	}
	// On attend au maximum 20 secondes
	while ( (connect(s, (LPSOCKADDR)&sockaddr, csalen) == SOCKET_ERROR) && (++i<200) )  {
		Sleep(100);
	}
	closesocket(s);
	std::stringstream URL;
	if (port) {
		URL << "http://127.0.0.1";
		if (port!=80)
			URL << ":" << port;
	} else if (sport) {
		URL << "https://127.0.0.1";
		if (sport!=443)
			URL << ":" << sport;
	} else {
		URL << "http://www.zmws.com/utils/badport.php";
	}
	if (serverConfig.GetBrowserCmd().length()) {
		std::string cmd = "start ";
		cmd += serverConfig.GetBrowserCmd();
		std::string url = URL.str();
		std::string::size_type pos;
		while ((pos = cmd.find("%u")) != std::string::npos) {
			cmd.replace(pos, 2, url);
		}
		system (cmd.c_str());
	} else {
		ShellExecute (NULL, NULL, 
					  URL.str().c_str(),
					  NULL, NULL,
					  SW_SHOWNORMAL);
	}
}    

void ZMWS::WaitForShutDown() {
	DWORD local_nbclients;
	WaitForSingleThread(serverThread, INFINITE);
	if (!serverConfig.GetDropClients()) {
		do {
			DWORD result = WaitForSingleMutex (this->ZMWS_NBCLIENTS_MUTEX, 5000);
			if (result == WAIT_OBJECT_0) { 
				local_nbclients = nbClients;
				ReleaseMutex(this->ZMWS_NBCLIENTS_MUTEX);
			}
			Sleep(100);
		} while (local_nbclients);
	}
	print_dbg("Shutting down !\n");
}

BOOL ZMWS::CreateServerSockets () {
	unsigned int IPaddr = INADDR_ANY;
	SOCKADDR_IN serverSockAddr;
	int rc;

	std::string serverIP = serverConfig.GetBindAddr();
	if (serverIP.length()) {
		IPaddr = inet_addr(serverIP.c_str());
		if (IPaddr == INADDR_NONE) {
			print_dbg (("ERROR: Invalid bind option: " + serverIP + "\n").c_str());
			return FALSE;
		}
	}	

	serverSockAddr.sin_family = AF_INET;
	serverSockAddr.sin_addr.s_addr = IPaddr;

	// HTTP
	WORD port = serverConfig.GetPort();
	if (port) {
		serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (serverSocket == INVALID_SOCKET) return FALSE;

		serverSockAddr.sin_port = htons(port);
		if ((rc = bind(serverSocket, (LPSOCKADDR)&serverSockAddr, sizeof(SOCKADDR_IN))) == SOCKET_ERROR ) {
			if (serverConfig.GetTry808xPorts()) {
				port = 8080;
				serverConfig.SetPort(port);
				serverSockAddr.sin_port = htons(port);
				while ((port<serverConfig.GetPort()+10) && (rc = bind(serverSocket, (LPSOCKADDR)&serverSockAddr, sizeof(SOCKADDR_IN))) == SOCKET_ERROR ) {
		    		serverSockAddr.sin_port = htons(++port);
				}
			}    		
		}
		if (rc == SOCKET_ERROR) {
			std::stringstream outputSS;
			outputSS << "Port " << port << " is not available" << std::endl;
			print_dbg (outputSS.str().c_str());
			if (serverConfig.GetBrowseNow()) {
				print_dbg ("Launching browser ...\n");
				BrowseNow();
			}    
			return FALSE;
		}

		rc = listen(serverSocket, SOMAXCONN);
		if (rc == SOCKET_ERROR) return FALSE;
		serverConfig.SetPort(port);
	} else {
		print_dbg ("HTTP port is null\n");
	}
	// HTTP est OK

	// HTTPS
	WORD sport = serverConfig.GetHTTPSPort();
	if (sport) {
		secureServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (secureServerSocket == INVALID_SOCKET) {
			serverConfig.SetHTTPSPort (0);
			return TRUE;
		}

		serverSockAddr.sin_port = htons(sport);
		if ((rc = bind(secureServerSocket, (LPSOCKADDR)&serverSockAddr, sizeof(SOCKADDR_IN))) == SOCKET_ERROR ) {
			if (serverConfig.GetTry808xPorts()) {
				sport = 8443;
				serverConfig.SetHTTPSPort(sport);
				serverSockAddr.sin_port = htons(sport);
				while ((sport<serverConfig.GetHTTPSPort()+10) && (rc = bind(secureServerSocket, (LPSOCKADDR)&serverSockAddr, sizeof(SOCKADDR_IN))) == SOCKET_ERROR ) {
		    		serverSockAddr.sin_port = htons(++sport);
				}
			}
		}
		if (rc == SOCKET_ERROR ) {
			std::stringstream outputSS;
			outputSS << "HTTPS port is not available." << std::endl;
			print_dbg (outputSS.str().c_str());
			serverConfig.SetHTTPSPort (0);
			closesocket(secureServerSocket);
			secureServerSocket = INVALID_SOCKET;
			return TRUE;
		}
		serverConfig.SetHTTPSPort(sport);
		if ((rc = listen(secureServerSocket, SOMAXCONN)) == SOCKET_ERROR ) {
			serverConfig.SetHTTPSPort (0);
			closesocket(secureServerSocket);
			secureServerSocket = INVALID_SOCKET;
			return TRUE;
		}

		int zmsslerr;
		if (!ZazouModSslHandle) {
			ZazouModSslHandle = LoadLibrary ("ZazouModSsl.dll");
		}
		if (ZazouModSslHandle) {
			ZazouModSslLoad = (ZazouModSslLoad_t)GetProcAddress(ZazouModSslHandle, "ZazouModSsl");
			if (ZazouModSslLoad) {
				modssl_functions = ZazouModSslLoad();
				zmsslerr = modssl_functions.fnInitialize();
			} else {
				zmsslerr = 1;
			}
			if (zmsslerr) {
				FreeLibrary (ZazouModSslHandle);
				ZazouModSslHandle = NULL;
			}
		} else {
			zmsslerr = 1;
		}
		if (zmsslerr) {
			serverConfig.SetHTTPSPort (0);
			closesocket(secureServerSocket);
			secureServerSocket = INVALID_SOCKET;
		}
	}
	// HTTPS est OK

	return TRUE;
}

// A appeler après la lecture de la configuration
// On se sert notamment de L'adresse passée à Main:bind
std::string ZMWS::CreateIPList () {
	std::string iplistS;

	// Réinitialisation de la liste d'IP
	serverIPs = "";

	// Lecture des IPs locales
	std::stringstream iplistSS;
	std::string serverIP = serverConfig.GetBindAddr();
	if (serverIP.length()) {
		iplistS = "\n --> IP: " + serverIP + " (bind option)";
		serverIPs = serverIP;
	} else {
		LPHOSTENT he = gethostbyname(NULL);
		if (!he) {
			iplistS = "\n --> IP: 127.0.0.1 (localhost)";
			serverIPs = "127.0.0.1";
		} else {
			BYTE** ip_ptr = (BYTE**)he->h_addr_list;
			while (*ip_ptr) {
				std::stringstream ipSS;
				if (serverIPs.length()) {
					serverIPs += ", ";
				}
				iplistSS << "\n --> Adresse IP: ";
				ipSS << (unsigned int)(*ip_ptr)[0] << "." << (unsigned int)(*ip_ptr)[1] << "." << (unsigned int)(*ip_ptr)[2] << "." << (unsigned int)(*ip_ptr)[3];
				serverIPs += ipSS.str();
				iplistSS << ipSS.str();
				iplistSS << " (" << he->h_name << ")";
				ip_ptr += 1;
			}
			iplistS = iplistSS.str();
		}
	}

	// Renvoi d'une liste d'ip et noms de machine
	return iplistS;
}

BOOL ZMWS::Configure(const char* config_file_path) {
	BOOL OptsOK;

	// Reset Config
	serverConfig = ZMWSConfig();

	// Command line args
	ZMWSOptParser zmwsop;
	OptsOK = zmwsop.Parse(0, NULL, serverConfig);
	if (!OptsOK) {
		print_dbg ("ERROR : Could not run server. Check your command line options ...\n");
		return FALSE;
	}

	if (config_file_path) {
		std::string cfp(config_file_path);
		serverConfig.SetPathToConfigFile(cfp);
	}

	// Config file args
	ZMWSConfFileParser zmwscfp;
	OptsOK = zmwscfp.Parse(serverConfig, MIME_Manager);
	if (!OptsOK) {
		print_dbg ("ERROR : Could not run server. Check your configuration file ...\n");
		return FALSE;
	}
	
	// Find and configure VHosts
	serverConfig.ConfigureVHosts();

	// Get local IPs list
	CreateIPList();

	return TRUE;
}

BOOL ZMWS::StartServer(int argc, char** argv)
{
	BOOL OptsOK;
	std::string tmpS;
	std::string iplistS;

	serverConfig = ZMWSConfig();

	// Lecture des options de ligne de commande
	ZMWSOptParser zmwsop;
	OptsOK = zmwsop.Parse(argc, argv, serverConfig);

	if (OptsOK) {
		// Lecture des options du fichier de configuration
		ZMWSConfFileParser zmwscfp;
		OptsOK = zmwscfp.Parse(serverConfig, MIME_Manager);
		if (!OptsOK) {
			tmpS  = "ERROR : Could not run server. Check your configuration file ...\n";
			print_dbg (tmpS.c_str());
		}
	} else {
		tmpS  = "ERROR : Could not run server. Check your command line options ...\n";
		print_dbg (tmpS.c_str());
	}
	
	// Configuration des valeurs par défaut des VHosts
	serverConfig.ConfigureVHosts();

	iplistS = CreateIPList();

	if (serverConfig.GetHideConsole()) {
		HideConsole();
	}

	// Set and create logs dir if needed !
	if (serverConfig.GetWriteLogs())
		serverConfig.SetLogsDir(serverConfig.GetLogsDir());

	if (!CreateServerSockets()) {
		return FALSE;
	}

	// zmws.pid
	if (serverConfig.GetLogsDir().length()) {
		std::ofstream pidfile;
		std::stringstream pidfilePathStr;
		pidfilePathStr << serverConfig.GetLogsDir() << "\\zmws_" << GetCurrentProcessId() << ".pid";
		std::string pidfilePath = pidfilePathStr.str();
		ZMWSFileUtils::normalizePath(pidfilePath);
		pidfile.open (pidfilePath.c_str(), std::ios::out | std::ios::trunc);
		if (pidfile.is_open()) {
			pidfile << GetCurrentProcessId();
			pidfile.close();
		}
	}

	tmpS = "\n----------------------------------------";
	std::stringstream tmpSS, tmpSS2;
	tmpS += "\nServer Config:";
	tmpS += "\n --> ServerRoot: ";
	tmpS += serverConfig.GetServerRoot();
	tmpS += "\n --> WebDir: ";
	tmpS += serverConfig.GetDocumentRoot();
	tmpS += "\n --> LogsDir: ";
	if (serverConfig.GetWriteLogs()) 
		tmpS += serverConfig.GetLogsDir();
	else
		tmpS+= "Logging disabled";
	tmpS += "\n --> PhpPath: ";
	std::string phpPath = serverConfig.GetPathToPHP();
	if (phpPath.length())
		tmpS += serverConfig.GetPathToPHP();
	else
		tmpS += "Not found: (use -phppath option)";
	tmpS += "\n --> MaxClientConnections: ";
	tmpSS2 << serverConfig.GetMaxClients(); 
	tmpS += tmpSS2.str();
	tmpS += "\n --> Indexing: ";
	if (serverConfig.GetCanIndex()) 
		tmpS += "Enabled";
	else
		tmpS += "Disabled";
	tmpS += "\n --> On Requests: ";
	if (serverConfig.GetBeQuiet()) 
		tmpS += "Be quiet";
	else
		tmpS += "Be verbose";
	tmpS += "\n --> Reverse DNS queries: ";
	if (serverConfig.GetReverseDNS()) 
		tmpS += "Enabled";
	else
		tmpS += "Disabled";
	tmpS += "\n --> Can stop server: ";
	if (serverConfig.GetCanStop()) 
		tmpS += "Yes, browse fake URL /_stopServer.zmwsc";
	else
		tmpS += "Disabled";
	tmpS += "\n --> Close Browser: ";
	if (serverConfig.GetCanStop() && serverConfig.GetCloseBrowser()) 
		tmpS += "Enabled";
	else
		tmpS += "Disabled";
	tmpS += "\n --> Start Page: " + serverConfig.GetStartPages();
	tmpS += "\n --> Allow from: ";
	std::string af = serverConfig.GetAllowedFrom();
	if (af.length()) 
		tmpS += af;
	else
		tmpS += "All";
	tmpS += "\n----------------------------------------";
	tmpS += iplistS;
	int port = serverConfig.GetPort();
	tmpSS << "\n --> HTTP : ";
	if (port)
		tmpSS << "YES, port " << port;
	else
		tmpSS << "NO";
	int sport = serverConfig.GetHTTPSPort();
	tmpSS << "\n --> HTTPS : ";
	if (sport)
		tmpSS << "YES, port " << sport;
	else
		tmpSS << "NO";
	tmpSS << "\n----------------------------------------\n";
	tmpS += tmpSS.str();;
	print_dbg (tmpS.c_str());

	DWORD result = WaitForSingleMutex (ZMWS_SERVERSTOP_MUTEX, 5000);
	if (result == WAIT_OBJECT_0) { 
		ServerStop = FALSE;
		ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
	} else {
		tmpS  = "ERROR : StartServer : Could not acquire Server Mutex\n";
		print_dbg (tmpS.c_str());
		return FALSE;
	}

	if (OptsOK) {
		serverThread = (HANDLE_THREAD)_beginthread(Run, 0, this);
    	if (serverConfig.GetBrowseNow()) {
    	    print_dbg ("Launching browser ...\n");
    	    BrowseNow();
    	}
	} else {
		tmpS  = "ERROR : Could not run server. Check your options ...\n";
		print_dbg (tmpS.c_str());
	}

	return OptsOK;
}

ZMWS& ZMWS::GetInstance()
{
	static ZMWS theServer;

	return theServer;
}

void ZMWS::GetConfig (CBaseZMWSConfig* zbc) {
	serverConfig.GetCBaseConfig(zbc);
}

void ZMWS::FreeConfig (CBaseZMWSConfig* zbc) {
	serverConfig.FreeCBaseConfig(zbc);
}

void ZMWS::StopServer(BOOL postMessage)
{
	print_dbg("Exiting ...\n");

	std::stringstream pidfilePathStr;
	pidfilePathStr << serverConfig.GetLogsDir() << "\\zmws_" << GetCurrentProcessId() << ".pid";
	std::string pidfilePath = pidfilePathStr.str();
	ZMWSFileUtils::normalizePath (pidfilePath);
	remove (pidfilePath.c_str());

	SOCKET stopSocket;
	SOCKADDR_IN stopSockAddr;

	DWORD result = WaitForSingleMutex (ZMWS_SERVERSTOP_MUTEX, 5000);
	if (result == WAIT_OBJECT_0) {
		if (ServerStop) {
			ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
			return;
		} else {
			ServerStop = TRUE;
			ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
		}
	} else {
		std::string tmpS  = "ERROR : StopServer : Could not acquire Server Mutex\n";
		print_dbg (tmpS.c_str());
	}

	stopSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

	stopSockAddr.sin_family = AF_INET;
	stopSockAddr.sin_port = htons(serverConfig.GetPort());
	stopSockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;

	if (connect(stopSocket, (LPSOCKADDR)&stopSockAddr, sizeof(SOCKADDR)) != SOCKET_ERROR) {
		closesocket(stopSocket);
	}

	stopSockAddr.sin_family = AF_INET;
	stopSockAddr.sin_port = htons(serverConfig.GetHTTPSPort());
	stopSockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;

	if (connect(stopSocket, (LPSOCKADDR)&stopSockAddr, sizeof(SOCKADDR)) != SOCKET_ERROR) {
		closesocket(stopSocket);
	}

	if (serverSocket != INVALID_SOCKET) closesocket(serverSocket);
	serverSocket = INVALID_SOCKET;

	if (secureServerSocket != INVALID_SOCKET) closesocket(secureServerSocket);
	secureServerSocket = INVALID_SOCKET;

	WaitForSingleThread(serverThread, 30000);//INFINITE);

	if (postMessage)
		PostMessage (HWND_BROADCAST, ZMWSSTOPMESSAGE, 0, 0);

}

void ZMWS::ShutdownServer(unsigned short port)
{
	print_dbg("Trying to stop a running instance of ZazouMiniWebServer ...\n");

	SOCKET stopSocket;
	SOCKADDR_IN stopSockAddr;

	stopSocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
	stopSockAddr.sin_family = AF_INET;
	stopSockAddr.sin_port = htons(port);
	stopSockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
	stopSockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;

	if (connect(stopSocket, (LPSOCKADDR)&stopSockAddr, sizeof(SOCKADDR)) != SOCKET_ERROR) {
		send(stopSocket, "GET /_stopServer.zmwsc\n", strlen("GET /_stopServer.zmwsc\n"), 0);
		closesocket(stopSocket);
	}
}

UINT ZMWS::MainThread()
{
	SOCKET clientSocket;
	SOCKET maxSocket = (serverSocket>secureServerSocket) ? serverSocket : secureServerSocket;
	SOCKADDR_IN clientSockAddr;
	int csaLen = sizeof(SOCKADDR);
	BOOL ShouldStop = ((serverSocket == INVALID_SOCKET) && (secureServerSocket == INVALID_SOCKET));

	while ( ShouldStop != TRUE ) {
		DWORD result = WaitForSingleObject (ZMWS_SERVERSTOP_MUTEX, 5000);
		if (result == WAIT_OBJECT_0) { 
			ShouldStop = ServerStop;
			ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
		} else {
			std::string tmpS  = "ERROR : Mainthread : Could not acquire SERVERSTOP Mutex\n";
			print_dbg (tmpS.c_str());
			return FALSE;
		}
		/* Attente d'un client */
		fd_set clientfds;
		FD_ZERO(&clientfds);
		if (serverSocket != INVALID_SOCKET)
			FD_SET(serverSocket, &clientfds);
		if (secureServerSocket != INVALID_SOCKET)
			FD_SET(secureServerSocket, &clientfds);
		if (select(maxSocket+1, &clientfds, NULL, NULL, NULL) <= 0) continue;
		/* Acceptation si ok */
		clientSocket = 0;
		if (FD_ISSET(serverSocket, &clientfds)) {
			if ((clientSocket = accept(serverSocket, (LPSOCKADDR)&clientSockAddr, &csaLen)) == INVALID_SOCKET) {
				print_dbg("WARNING: accept_failed (http)\n");
				continue;
			}
		} else if (FD_ISSET(secureServerSocket, &clientfds)) {
			if ((clientSocket = accept(secureServerSocket, (LPSOCKADDR)&clientSockAddr, &csaLen)) == INVALID_SOCKET) {
				print_dbg("WARNING: accept failed (https)\n");
				continue;
			}
		} else {
			continue;
		}
		
		QUEUECLIENTPARAM* qcp = NULL;
		try {
		    qcp = new QUEUECLIENTPARAM;
        } catch (std::bad_alloc) {
            qcp = NULL;
        }    
        if (!qcp) {
			print_dbg("ERROR : Mainthread : Could not allocate a new qcp object\n");
			if (clientSocket) {
				closesocket (clientSocket);
			}
			continue;
        }     
		qcp->clientSocket = clientSocket;
		qcp->object = this;
		qcp->tinit = GetTickCount();

		_beginthread(QueueClient, 0, qcp);
	}
	if (serverSocket != INVALID_SOCKET)
		closesocket (serverSocket);
	if (secureServerSocket != INVALID_SOCKET)
		closesocket (secureServerSocket);

	return 0;
}

BOOL ZMWS::WaitForInputOnSocket (const SOCKET& clientSocket) const {
		timeval tv;
		tv.tv_sec = ZMWS_TIMEOUT;
		tv.tv_usec = 0;
		fd_set readfds;
		FD_ZERO(&readfds);
		FD_SET(clientSocket, &readfds);
		// Si entrée, timeout ou erreur, on s'en va
		// Si pas d'entrée retourne FALSE sinon TRUE
		return (select(clientSocket+1, &readfds, NULL, NULL, &tv) > 0);
}

ZMWSReqResp* ZMWS::InitializeRR(const SOCKET& clientSocket)
{
	ZMWSReqResp* RR;
	try {
	    RR = new ZMWSReqResp;
	} catch (std::bad_alloc) {
	    RR = NULL;
    }        

	if (RR) {
		RR->setSocket(&clientSocket);
		RR->setReqMethod(UNKNOWN);
		RR->setRetCode(200);
		RR->cnx_policy = CNX_POLICY_CLOSE;
		if (serverConfig.GetPBToken() != PBTOKEN_OFF) {
			std::stringstream serverSW;
			serverSW << "ZazouMiniWebServer";
			if (serverConfig.GetPBToken() != PBTOKEN_NOVERSION) {
				serverSW << " v" << VER_MAJOR << ".";
				serverSW << VER_MINOR << ".";
				serverSW << VER_MICRO;
			}
			RR->setRespVar("Server", serverSW.str());
		}
		RR->setHTTPVer(0.9);
		struct sockaddr_in our_sa, client_sa;
		int sa_len = sizeof(struct sockaddr_in);
		getpeername (*(RR->getSocket()), (struct sockaddr *)&client_sa, &sa_len);
		RR->setReqVar("REMOTE_ADDR", inet_ntoa(client_sa.sin_addr));
		char* txtport = (char*)malloc(16);
		if (txtport) {
			RR->setReqVar("REMOTE_PORT", _ultoa(ntohs(client_sa.sin_port), txtport, 10));
			free(txtport);
		}
		BOOL reverseDNS;
		ZMWSVHostConfig vhc;
		if (serverConfig.GetVHostConfig(RR->RemovePort(RR->getReqVar("HOST")), vhc)) {
			reverseDNS = vhc.reverseDNS;
		} else {
			reverseDNS = serverConfig.GetReverseDNS();
		}
		if (reverseDNS) {
			std::string remote_host = ZMWSResolver::getHostName(client_sa.sin_addr);
			if (remote_host.length())
				RR->setReqVar("REMOTE_HOST", remote_host);
		}

		sa_len = sizeof(struct sockaddr_in);
		getsockname (*(RR->getSocket()), (struct sockaddr *)&our_sa, &sa_len);
		std::stringstream serverport;
		serverport << ntohs(our_sa.sin_port);
		RR->setReqVar("SERVER_PORT", serverport.str());
	}
	return RR;
}

void ZMWS::ParseRequestLine(std::string& RequestLine, ZMWSReqResp* RR) const
{
	std::string::size_type pos = RequestLine.find(" ");
	if ((pos!=std::string::npos) && (pos>0)) {
		std::string ReqMethod = RequestLine.substr(0, pos);
		std::transform (ReqMethod.begin(), ReqMethod.end(), ReqMethod.begin(), toupper);
		if (ReqMethod=="GET") {
			RR->setReqMethod(HTTP_GET);
			RR->setReqVar("REQUEST_METHOD", "GET");
		} else if (ReqMethod=="POST") {
			RR->setReqMethod(HTTP_POST);
			RR->setReqVar("REQUEST_METHOD", "POST");
		} else if (ReqMethod=="HEAD") {
			RR->setReqMethod(HTTP_HEAD);
			RR->setReqVar("REQUEST_METHOD", "HEAD");
		} else if (ReqMethod=="PUT") {
			RR->setRetCode (501);
		} else if (ReqMethod=="TRACE") {
			RR->setRetCode (501);
		} else if (ReqMethod=="DELETE") {
			RR->setRetCode (501);
		} else if (ReqMethod=="CONNECT") {
			RR->setRetCode (501);
		} else if (ReqMethod=="OPTIONS") {
			RR->setRetCode (501);
		} else {
			RR->setRetCode (501);
		}
	}  else {
		RR->setRetCode(400);
		RR->setErrorMessage("Invalid request: "+RequestLine);
	}
	RR->setRequestLine(RequestLine);

	// Détermination de la version du protocole
	std::transform (RequestLine.begin(),RequestLine.end(), RequestLine.begin(), toupper);
	std::string::size_type Verpos = RequestLine.find("HTTP/");
	double httpver = 0.9;
	if (Verpos!=std::string::npos) {
		Verpos += 5;
		httpver = strtod(RequestLine.substr(Verpos).c_str(), NULL);
		RR->setReqVar("SERVER_PROTOCOL", "HTTP/"+RequestLine.substr(Verpos));
		if (httpver>=1.1) {
			RR->cnx_policy = CNX_POLICY_KEEP;
		}
	} else {
		RR->setReqVar("SERVER_PROTOCOL", "HTTP/0.9");
	}
	RR->setHTTPVer(httpver);
}

void ZMWS::ParseHeaderLine(std::string& HeaderLine, ZMWSReqResp* RR) const
{
	std::string Key, Value;
	std::string::size_type pos = HeaderLine.find(":");
	if ((pos!=std::string::npos) && (pos>0)) {
		Key = HeaderLine.substr(0, pos);
		while (++pos<HeaderLine.length() && (HeaderLine[pos]==' '));
		if (pos<HeaderLine.length()) {
			Value = HeaderLine.substr(pos);
		} else {
			Value = "";
		}
		std::transform (Key.begin(),Key.end(), Key.begin(), toupper);
		std::replace(Key.begin(), Key.end(), '-','_');
		// Protection des variables définies par ailleurs
		// TODO: Prévoir un ServerVars, comme on a ReqVars et RespVars pour faire plus propre
		if ((Key != "QUERY_STRING") &&
			(Key != "REQUEST_METHOD") &&
			(Key != "PATH_TRANSLATED") &&
			(Key != "REMOTE_ADDR") &&
			(Key != "REMOTE_HOST") &&
			(Key != "REMOTE_PORT") &&
			(Key != "REMOTE_USER") &&
			(Key != "SERVER_PROTOCOL") &&
			(Key != "SERVER_PORT") ) {
				RR->setReqVar(Key, Value);
			}
	} else {
		RR->setRetCode(400);
		RR->setErrorMessage("Unidentifiable header: '"+HeaderLine+"'");
	}
}

ZMWSReqResp* ZMWS::GetRequestLines (ZMWSReqResp* RR)
{
	BOOL foundEmptyLine = FALSE;
	char buff[8192], c;
	std::string Line = "";
	std::string OldLine = "";
	char LastWasEOL = FALSE;
	int bytesRead = 0;

//	print_dbg("In GetRequestLines\n");

	do {
		if (!WaitForInputOnSocket(*(RR->getSocket()))) {
			delete RR;
			RR = NULL;
			break;
		}
		int bytesAvail = recv (*(RR->getSocket()), buff, 8192, MSG_PEEK);
		if (bytesAvail <= 0) {
			delete RR;
			RR = NULL;
			break;
		}
		while (!foundEmptyLine && bytesAvail--) {
			if (RR->getIsSSL()) {
				bytesRead = modssl_functions.fnRead (RR->GetModSSLData(), (LPBYTE)&c, 1);
			} else {
				bytesRead = recv (*(RR->getSocket()), &c, 1, 0);
			}
			if (bytesRead <= 0)  {
				delete RR;
				RR = NULL;
				break;
			}
			if ( (c!='\r') && (c!='\n') ) {
				if (LastWasEOL) {
					LastWasEOL = FALSE;
					if ( (c==' ') || (c=='\t') ) {
						// This is a multiline request continuation
						// Add content to previous line
						Line = OldLine;
						Line += ' ';
						continue;
					} else {
						// Previous line was a complete request line
						if (OldLine.length()) {
							ParseHeaderLine(OldLine, RR);
						}
					}					
				}
				Line += c;
			} else if (c=='\n') {
				LastWasEOL = TRUE;
// Very useful for debugging purposes :)
//				print_dbg((Line+"\n").c_str());
				if (Line.length()) {
					if (RR->getReqMethod() == UNKNOWN) {
						// Parse the first request line
						ParseRequestLine(Line, RR);

						// For HTTP/0.9 break as soon as we've parsed the request
						if (RR->getHTTPVer() < 1) {
							// Fake an empty line to break off the 2 while loops
							foundEmptyLine = TRUE;
						}
					} else {
						// Cache the read line
						OldLine = Line;
					}
					// Empty the consumed line
					Line = "";
				} else {
					foundEmptyLine = TRUE;
					// If we get here, we have to parse the last line
					if (OldLine.length() && (RR->getReqMethod() != UNKNOWN)) {
						ParseHeaderLine(OldLine, RR);
					}
				}
			}
		}
	} while ( (!foundEmptyLine) && (bytesRead > 0) && (RR->getRetCode() < 300) );
	if (RR) {
		if (RR->getReqMethod() == UNKNOWN) {
			RR->setRetCode(400);
			RR->setErrorMessage("Empty request");
		} else if (!RR->getReqVar("HOST").length() && (RR->getRetCode()==200) && (RR->getHTTPVer() >= 1.1)) {
			RR->setRetCode(400);
			RR->setErrorMessage("Missing 'Host:' header");
		}
	} else {
		//print_dbg("GetRequestLines: No Request\n");
	}
	return RR;
}

std::string ZMWS::GetResponseLine(const HANDLE_FILE& h) const
{
	char c = '\n';
	DWORD cnt;

	std::string Line = "";
	do {
		if (ReadFile (h, &c, 1, &cnt, NULL) <= 0) break;
		if (!((c=='\r') || (c=='\n'))) Line += c;
	} while (c!='\n');

	return Line;
}

LPCTSTR ZMWS::SetupCgiEnvironment(ZMWSReqResp& rr) {
	int total_size=0;
	char* ce = NULL;
	std::string new_var;
	std::stringstream tmpSS;

	char** env; 	 
/*	char* var; 
	env = GetEnvironmentStrings();

	if (env != NULL) {
		for (var = (char*)env; *var; var++) 
		{ 
			new_var = var;
			ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
			strcpy (ce+total_size, new_var.c_str());
			total_size += new_var.length()+1;
			var += new_var.length();
		}
		FreeEnvironmentStrings((char*)env);
	}
*/
	env = _environ;
	while (*env != NULL) {
		new_var = *env;
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
		++env;
	}
	if (rr.getAuthRealm().length()) {
		new_var = "AUTH_TYPE=";
		new_var += "Basic";
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
	}

	new_var = "SERVER_ROOT=";
	new_var += serverConfig.GetServerRoot();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	new_var = "DOCUMENT_ROOT=";
	new_var += rr.getReqRootDir();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	new_var = "GATEWAY_INTERFACE=CGI/1.1";
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	if (rr.getIsSSL()) {
		new_var = "HTTPS=on";
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
	}

	RRVARS ReqVars = rr.getReqVars();
	RRVARS::iterator VarsIterator = ReqVars.begin();
	do {
		new_var = (*VarsIterator).first;
		std::transform (new_var.begin(),new_var.end(), new_var.begin(), toupper);
		std::replace(new_var.begin(), new_var.end(), '-','_');
		if (new_var=="HOST") {
			new_var = rr.RemovePort(new_var);
		}
		if ((new_var != "QUERY_STRING") &&
			(new_var != "REQUEST_METHOD") &&
			(new_var != "CONTENT_LENGTH") &&
			(new_var != "CONTENT_TYPE") &&
			(new_var != "PATH_INFO") &&
			(new_var != "PATH_TRANSLATED") &&
			(new_var != "REMOTE_ADDR") &&
			(new_var != "REMOTE_HOST") &&
			(new_var != "REMOTE_PORT") &&
			(new_var != "REMOTE_USER") &&
			(new_var != "SERVER_PROTOCOL") &&
			(new_var != "SERVER_PORT") 
			)
		{
			new_var = "HTTP_" + new_var;
		}
		
		new_var += "=" + (*VarsIterator).second;
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
	} while (++VarsIterator != ReqVars.end());

	new_var = "PHP_SELF=";
	new_var += rr.getURL();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	std::stringstream redirectStatus;
	redirectStatus << rr.getRetCode();
	new_var = "REDIRECT_STATUS=" + redirectStatus.str();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;
  
	new_var = "REQUEST_URI=";
	new_var += rr.getURL();
	std::string QS = rr.getReqVar("QUERY_STRING");
	if (QS.length()) {
		new_var += "?"+QS;
	}
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	new_var = "SCRIPT_NAME=";
	std::string path_info = rr.getReqVar("PATH_INFO");
	std::string script_name = rr.getURL().substr(0, rr.getURL().length()-path_info.length());
	if (script_name[script_name.length()-1]=='/') {
		ZMWSFileUtils zfu;
		if (zfu.getFile(rr.getFilePath())) {
			script_name += zfu.getFileName();
		}
	}
	new_var += script_name;
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	new_var = "SCRIPT_FILENAME=";
	new_var += rr.getFilePath();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	new_var = "PATH_TRANSLATED=";
	new_var += rr.getFilePath();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	if (rr.RemovePort(rr.getReqVar("HOST")).length()) {
		new_var = "SERVER_NAME=";
		new_var += rr.RemovePort(rr.getReqVar("HOST"));
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
	}

	std::stringstream serverSW;
	serverSW << "ZazouMiniWebServer/";
	serverSW << VER_MAJOR << ".";
	serverSW << VER_MINOR << ".";
	serverSW << VER_MICRO;
	new_var = "SERVER_SOFTWARE="+serverSW.str();
	ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
	strcpy (ce+total_size, new_var.c_str());
	total_size += new_var.length()+1;

	if (rr.getRetCode()>=400) {
		new_var = "ZMWS_HTTP_MSG=";
		new_var += HTTP_Utils.getReasonPhrase(rr.getRetCode());
		ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
		strcpy (ce+total_size, new_var.c_str());
		total_size += new_var.length()+1;
		if (rr.getErrorMessage().length()) {
			new_var = "ZMWS_ERR_MSG="+rr.getErrorMessage();
			ce = (LPSTR)realloc(ce, total_size + new_var.length()+1);
			strcpy (ce+total_size, new_var.c_str());
			total_size += new_var.length()+1;
		}
	}

	// Bloc d'environnement terminé par un 0
	ce = (LPSTR)realloc(ce, total_size+1);
	ce[total_size]=0;
	total_size += 1;
	return ce;
}

void ZMWS::Redirect(ZMWSReqResp& rr, const std::string& newUrl)
{
	rr.setRespVar("Location", newUrl.c_str());
	rr.setErrorMessage("This page has moved to <a href=\""+newUrl+"\">"+newUrl+"</a>\n");
	DefaultErrorMessage(rr);
}

void ZMWS::CloseBrowser(ZMWSReqResp& rr) {
	std::stringstream Response;

	rr.setReqVar("CONNECTION", "CLOSE");
	rr.setRespVar("Content-Type", "text/html");
	Response << "<html>\n<body>\n";
	Response << "<script>\n<!--\n";
	Response << "window.close();\n";
	Response << "-->\n</script>\n";
	Response << "</body>\n</html>\n";
	SendHeaders(rr);
	SendChunk(rr, (void*)Response.str().c_str(), Response.str().length());
	SendChunk(rr, NULL, 0);
}


void ZMWS::AddRespVars(ZMWSReqResp& rr, std::stringstream& Response)
{
	// Ajout de la Date
	SYSTEMTIME st;
	GetSystemTime(&st);
	rr.setRespVar ("Date", rr.ComputeDate(st));

	RRVARS RespVars = rr.getRespVars();
	RRVARS::iterator VarsIterator = RespVars.begin();
	do {
		Response << (*VarsIterator).first << ": " << (*VarsIterator).second << "\r\n";
	} while (++VarsIterator != RespVars.end());
}

BOOL ZMWS::SendHeaders(ZMWSReqResp& rr, BOOL EndHeaders) {
	// Pas d'entête en HTTP 0.9
	if (rr.getHTTPVer()<1.0) return TRUE;

	std::stringstream Response;

	// En tête, numéro de version
	std::string httpVer("1.1");
	if (rr.getHTTPVer()<1.1)
		httpVer = "1.0";
	Response << "HTTP/" << httpVer << " " << rr.getRetCode() << " " << HTTP_Utils.getReasonPhrase(rr.getRetCode()) << "\r\n";

	// En tête, encodage chunked pour les clients HTTP/1.1
	// Si le document :
	//	* est du texte 
	//	* qu'on a du contenu
	//	* qu'il n'est pas caché (réparation du bug Safari)
	// sinon, on utilise le Content-Length
	if ((rr.getHTTPVer() >= 1.1) &&
		( (rr.getRespVar("Content-Type").find("text/")!=std::string::npos) ||
		(!(rr.getRespVar("Content-Length").length())))) {
		if (rr.getRetCode()!=304) {
			// Pour Safari, ne pas dire comment on aurait envoyé le contenu
			// s'il ne l'avait pas eu en cache ...
			rr.setRespVar("Transfer-Encoding", "Chunked");
		}
		rr.eraseRespVar("Content-Length");
	}

	// CharSet par défaut si aucun spécifié //
		if ( (rr.getRespVar("Content-Type").find("text/")!=std::string::npos) &&
		 (rr.getRespVar("Content-Type").find("charset=")==std::string::npos) ) {
			std::string new_cs;
			ZMWSVHostConfig vhc;
			if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
				new_cs = vhc.defCharSet;
				if (!new_cs.length())
					new_cs = serverConfig.GetDefaultCharSet();
			} else {
				new_cs = serverConfig.GetDefaultCharSet();
			}
			if (new_cs.length()) {
				new_cs =  rr.getRespVar("Content-Type") + "; charset=" + new_cs;
				rr.setRespVar("Content-Type", new_cs);
			}
	}

	std::string connectionMAJ = rr.getReqVar("CONNECTION");
	std::transform (connectionMAJ.begin(),connectionMAJ.end(), connectionMAJ.begin(), toupper);
	if (connectionMAJ=="CLOSE") {
		// Fermeture de connexion demandée par le client
		rr.cnx_policy = CNX_POLICY_CLOSE;
	} else if (connectionMAJ=="KEEP-ALIVE") {
		// Compatibilité connexions persistantes HTTP/1.0
		if ((rr.getHTTPVer() >= 1.1) || (rr.getRespVar("Content-Length").length())) {
			rr.setRespVar("Connection", "Keep-Alive");
			rr.cnx_policy = CNX_POLICY_KEEP;
		} else {
			rr.cnx_policy = CNX_POLICY_CLOSE;
		}
	}

	// Si fermeture de connexion, envoi de l'entête correspondant
	if (rr.cnx_policy == CNX_POLICY_CLOSE) {
		rr.setRespVar("Connection", "Close");
	}

	// Ajout d'un message si erreur
	AddRespVars(rr, Response);

	// Est ce tout pour les headers ? (dans le cas de php, c'est lui qui finit !)
	if (EndHeaders) {
		Response << "\r\n";
	}

	// On envoie les entêtes
	SendChunk(rr, (void*)Response.str().c_str(), Response.str().length(), /* is_header */ TRUE);

	return TRUE;
}

BOOL ZMWS::FindAuthFile(ZMWSReqResp& rr) {
	// look for an auth file, sets AuthFilePath and return TRUE if found
	ZMWSFileUtils zfu;
	std::string rootDir = rr.getAliasedRootDir().length() ? rr.getAliasedRootDir() : rr.getReqRootDir();
	std::string tmpPath = rr.getFilePath();
	if (rr.getIsDirectory()) tmpPath += '\\';
    int next_path_pos = tmpPath.find_last_of("/\\");
	while (next_path_pos != std::string::npos) {
		tmpPath = tmpPath.substr(0, next_path_pos);
		if (zfu.getFile(tmpPath+"/_accesspath.zmwsc")) {
			std::ifstream ifs((tmpPath+"/_accesspath.zmwsc").c_str());
			std::string accesspath;
			std::string authRealm;
			char tmp_char;
			while ((tmp_char = ifs.get()) != -1) {
				if (tmp_char != '\n') {
					authRealm += tmp_char;
				} else {
					break;
				}
			}
			if (authRealm.length()) {
				rr.setAuthRealm(authRealm);
			} else {
				rr.setRetCode(500);
				rr.setErrorMessage("Erreur dans la procédure d'authentification");
				return TRUE;
			}
			while ((tmp_char = ifs.get()) != -1) {
				if (tmp_char != '\n') {
					accesspath += tmp_char;
				} else {
					break;
				}
			}
			if (accesspath.length()>2) {
				ZMWSFileUtils::normalizePath(accesspath);
				if (accesspath[1]!=':') {
					if (accesspath[0]=='\\') {
						accesspath = rootDir + accesspath;
					} else {
						accesspath = tmpPath + '\\' + accesspath;
					}
				}
				rr.setAuthFilePath(accesspath);
				return TRUE;
			} else {
				rr.setRetCode(500);
				rr.setErrorMessage("Erreur dans la procédure d'authentification.");
				return TRUE;
			}
		}
		if (zfu.getFile(tmpPath+"/_access.zmwsc")) {
			std::ifstream ifs((tmpPath+"/_access.zmwsc").c_str());
			std::string authRealm;
			char tmp_char;
			while ((tmp_char = ifs.get()) != -1) {
				if (tmp_char != '\n') {
					authRealm += tmp_char;
				} else {
					break;
				}
			}
			if (authRealm.length()) {
				rr.setAuthRealm(authRealm);
				rr.setAuthFilePath(tmpPath+"/_access.zmwsc");
				return TRUE;
			} else {
				rr.setRetCode(500);
				rr.setErrorMessage("Erreur dans la procédure d'authentification");
				return TRUE;
			}
		}
		next_path_pos = tmpPath.find_last_of("/\\", tmpPath.length()-2);
		if (tmpPath.length() <= rootDir.length())
			break;
	}
	return FALSE;
}

BOOL ZMWS::AuthenticateUser(ZMWSReqResp& rr) {
	// authenticate user, 
	std::ifstream ifs(rr.getAuthFilePath().c_str());
	if (!ifs) {
		rr.setRetCode(500);
		rr.setErrorMessage("Erreur dans la procédure d'authentification.\nImpossible d'ouvrir le fichier.");
		return TRUE;
	}

	char tmp_char;
	// Supprimé pour le cas des _accesspath.zmwsc
	// ifs.ignore(rr.getAuthRealm().length()+1);
	// Remplacé par
	while (((tmp_char = ifs.get()) != -1) && (tmp_char != '\n'));

	while (!ifs.eof()) {
		std::string chalenge_response;
		while ((tmp_char = ifs.get()) != -1) {
			if (tmp_char != '\n') {
				chalenge_response += tmp_char;
			} else {
				break;
			}
		}
		std::string credentials = rr.getReqVar("AUTHORIZATION");
		std::string b64_userpass;
		std::string::size_type b64_userpass_pos;
		b64_userpass_pos = credentials.find_last_of(' ');
		b64_userpass = credentials.substr(b64_userpass_pos+1);
		if (base64::encode(chalenge_response) == b64_userpass) {
		    std::string::size_type username_end = chalenge_response.find(':');
		    if (username_end != std::string::npos) {
		        rr.setReqVar("REMOTE_USER", chalenge_response.substr(0, username_end));
		    }    
			return TRUE;
		}
	}
	return FALSE;
}

void ZMWS::SendAuthChallenge(ZMWSReqResp& rr) {
	rr.setRespVar("WWW-Authenticate", "Basic realm=\""+rr.getAuthRealm()+"\"");
}

BOOL ZMWS::LocalFileIsMoreRecentThanClientCache(ZMWSReqResp& rr) {
	BOOL result = TRUE;
	std::string clientVersionDate = rr.getReqVar("IF_MODIFIED_SINCE");
	std::istringstream clientVersionDateSS;
	clientVersionDateSS.str(clientVersionDate);
	std::string tmp, SortTime;
	// day of week
	clientVersionDateSS >> tmp;
    //Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
    //Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
    //Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
	std::string fullDate, day, month, year, fullTime, hour, minute, second;
	switch (tmp.length()) {
		case 4: // RFC822/1123 !
			if (!(clientVersionDateSS >> day)) return result;
			if (!(clientVersionDateSS >> month)) return result;
			if (!(clientVersionDateSS >> year)) return result;
			if (!(clientVersionDateSS >> fullTime)) return result;
			break;
		case 3: // asctime C format !
			if (!(clientVersionDateSS >> month)) return result;
			if (!(clientVersionDateSS >> day)) return result;
			if (day.length()<2) day = "0" + day;
			if (!(clientVersionDateSS >> fullTime)) return result;
			if (!(clientVersionDateSS >> year)) return result;
			break;
		default: // RFC850/1036 !
			if (!(clientVersionDateSS >> fullDate)) return result;
			if (!(clientVersionDateSS >> fullTime)) return result;
			if (fullDate.length()!=9) return result;
		    day = fullDate.substr(0, 2);
		    month = fullDate.substr(3, 3);
		    year = fullDate.substr(7, 2);
			if (year.length()!=2) return result;
			if (year[0]>='8') 
				year = "19"+year;
			else
				year = "20"+year;
			break;
	}
	std::transform (month.begin(),month.end(), month.begin(), toupper);
	if (!month.compare("JAN")) month="01";
	if (!month.compare("FEB")) month="02";
	if (!month.compare("MAR")) month="03";
	if (!month.compare("APR")) month="04";
	if (!month.compare("MAY")) month="05";
	if (!month.compare("JUN")) month="06";
	if (!month.compare("JUL")) month="07";
	if (!month.compare("AUG")) month="08";
	if (!month.compare("SEP")) month="09";
	if (!month.compare("OCT")) month="10";
	if (!month.compare("NOV")) month="11";
	if (!month.compare("DEC")) month="12";
	if (month.length()!=2) return result;
	if (fullTime.length()!=8) return result;
    hour = fullTime.substr(0, 2);
    minute = fullTime.substr(3, 2);
    second = fullTime.substr(6, 2);
	SortTime = year + month + day + hour + minute + second;
	ZMWSFileUtils zfu;
	zfu.getFile(rr.getFilePath());
	result = (zfu.getWriteDateSort() > SortTime);
	return result;
}

int ZMWS::StartPHP(ZMWSReqResp& rr) {
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	SECURITY_ATTRIBUTES sa; 
	sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
	sa.bInheritHandle = TRUE; 
	sa.lpSecurityDescriptor = NULL; 

	HANDLE_FILE hChildStdoutRd0, hChildStdoutRd, hChildStdoutWr;
	if (!CreatePipe(&hChildStdoutRd0, &hChildStdoutWr, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe1");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	if (!DuplicateHandle(
			GetCurrentProcess(), hChildStdoutRd0,
			GetCurrentProcess(), &hChildStdoutRd, 
			0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
		print_dbg("ERREUR: DuplicateHandle1");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	HANDLE_FILE hChildStdinRd, hChildStdinWr0, hChildStdinWr;
	if (!CreatePipe(&hChildStdinRd, &hChildStdinWr0, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe2");
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	if (!DuplicateHandle(
			GetCurrentProcess(), hChildStdinWr0,
			GetCurrentProcess(), &hChildStdinWr, 
			0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
		print_dbg("ERREUR: DuplicateHandle2");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	memset(&si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	si.hStdInput = hChildStdinRd;
	si.hStdOutput = hChildStdoutWr;
	si.wShowWindow = SW_HIDE;

	LPVOID MODULE_ENVIRONMENT = (LPVOID)SetupCgiEnvironment(rr);
	BOOL retVal = ::CreateProcess(NULL,
					(char*)(serverConfig.GetPathToPHP().c_str()),
					NULL,
					NULL,
					TRUE,
					0,
					MODULE_ENVIRONMENT,
					NULL,
					&si,
					&pi);
	free (MODULE_ENVIRONMENT);
	if (!retVal) {
		print_dbg("ERREUR: Je n'ai pas lancé le module\n");
		CloseFile(hChildStdinRd);
		CloseFile(hChildStdinWr);
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	CloseThread(pi.hThread);
	CloseProcess(pi.hProcess);
	CloseFile(hChildStdinRd);
	CloseFile(hChildStdoutWr);

	rr.setHandle(&hChildStdoutRd);

	DWORD ContentLength = 0;
	std::string tmp = rr.getReqVar("CONTENT_LENGTH");
	if (tmp.length()) ContentLength = strtoul(tmp.c_str(), NULL, 10);

	char buff[8192];
	DWORD nbOfCharRead, nbOfCharWritten, nbOfCharWritten_total = 0;
	BOOL ret;
	if (ContentLength) {
		do {
			if (rr.getIsSSL()) {
				nbOfCharRead = modssl_functions.fnRead (rr.GetModSSLData(), (LPBYTE)buff, 8192);
			} else {
				// 14.05.2006: Déplacé ici pour ne pas faire le test en https
				if (!WaitForInputOnSocket(*(rr.getSocket()))) break;
				nbOfCharRead = recv(*(rr.getSocket()), buff, 8192, 0);
			}
			if ( nbOfCharRead > 0 ) {
				DWORD delta = 0;
				do {
					ret = WriteFile(hChildStdinWr,
								buff + delta,
								nbOfCharRead - delta,
								&nbOfCharWritten, NULL);
					if (!ret) {
						DWORD err = GetLastError();
						print_dbg ("Erreur lors du passage des variables POST !\n");
					}
					delta+=nbOfCharWritten;
					nbOfCharWritten_total += nbOfCharWritten;
				} while (ret && (delta < nbOfCharRead));
			}
		} while ( ret && nbOfCharRead && ( nbOfCharWritten_total < ContentLength ) );
	}
	CloseFile(hChildStdinWr);

	// Récupération d'un code HTTP différent de 200
	std::string respL, respL_MAJ;
	std::stringstream respLs;
	do {
		respL = GetResponseLine(*(rr.getHandle()));
		respL_MAJ = respL;
		std::transform (respL_MAJ.begin(),respL_MAJ.end(), respL_MAJ.begin(), toupper);
		if (respL_MAJ.substr(0, 7) == "STATUS:") {
			int deb = 7;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				HTTP_RETCODE rc = strtoul(respL.substr(deb, 3).c_str(), NULL, 10);
				rr.setRetCode(rc);
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 13) == "CONTENT-TYPE:") {
			int deb = 13;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Type", respL.substr(deb));
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 15) == "CONTENT-LENGTH:") {
			int deb = 15;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Length", respL.substr(deb)); 
			} else {
				respLs << respL << "\r\n";
			}
		} else {
			respLs << respL << "\r\n";
		}
	} while (respL.length()); 

	// Envoi des entêtes
	SendHeaders(rr, FALSE);
	SendChunk(rr, (void*)(respLs.str().c_str()), respLs.str().length(), /* is_header */TRUE);

	if ((rr.getReqMethod()!=HTTP_HEAD) &&
		(!((rr.getRetCode() >= 100 && rr.getRetCode() < 200) ||	// Information
		(rr.getRetCode() == 204) ||				// No Content
		(rr.getRetCode() == 304))				// Not Modified
		))
	{
		// Envoi du reste
		SendFile(rr);
		// Fermeture 
		SendChunk(rr, NULL, 0);
	}

	CloseFile(hChildStdoutRd);

	return rr.getRetCode();
}

int ZMWS::StartZMWSSAPIHandler(ZMWSReqResp& rr) {
	SECURITY_ATTRIBUTES sa; 
	sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
	sa.bInheritHandle = TRUE; 
	sa.lpSecurityDescriptor = NULL;

	HANDLE_FILE hChildStdoutRd, hChildStdoutWr;
	if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe1");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	HANDLE_FILE hChildStdinRd, hChildStdinWr;
	if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe2");
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	// Lancement du handler
	ZMWSHandler handler = rr.getHandler();
	LPVOID MODULE_ENVIRONMENT = (LPVOID)SetupCgiEnvironment(rr);
	ZMWSSAPI_DATA* zmwssapi_args = (ZMWSSAPI_DATA*)malloc(sizeof(ZMWSSAPI_DATA));
	if (!zmwssapi_args) {
		print_dbg("ERREUR: Create zmwssapi_args");
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		CloseFile(hChildStdinWr);
		CloseFile(hChildStdinRd);
		rr.setRetCode(500);
		rr.setErrorMessage("No more memory ...");
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}
	zmwssapi_args->hIN = hChildStdinRd;
	zmwssapi_args->hOUT = hChildStdoutWr;
	zmwssapi_args->ENV = MODULE_ENVIRONMENT;
	HANDLE_THREAD zsapi_handler = ZMWSSAPI::Run (handler.handlerPath.c_str(), zmwssapi_args);

	// Lecture des retours
	rr.setHandle(&hChildStdoutRd);

	DWORD ContentLength = 0;
	std::string tmp = rr.getReqVar("CONTENT_LENGTH");
	if (tmp.length()) ContentLength = strtoul(tmp.c_str(), NULL, 10);

	char buff[8192];
	DWORD nbOfCharRead, nbOfCharWritten, nbOfCharWritten_total = 0;
	BOOL ret;
	if (ContentLength) {
		do {
			if (rr.getIsSSL()) {
				nbOfCharRead = modssl_functions.fnRead (rr.GetModSSLData(), (LPBYTE)buff, 8192);
			} else {
				// 14.05.2006: Déplacé ici pour ne pas faire le test en https
				if (!WaitForInputOnSocket(*(rr.getSocket()))) break;
				nbOfCharRead = recv(*(rr.getSocket()), buff, 8192, 0);
			}
			if ( nbOfCharRead > 0 ) {
				DWORD delta = 0;
				do {
					ret = WriteFile(hChildStdinWr,
								buff + delta,
								nbOfCharRead - delta,
								&nbOfCharWritten, NULL);
					if (!ret) {
						DWORD err = GetLastError();
						print_dbg ("Erreur lors du passage des variables POST !\n");
					}
					delta+=nbOfCharWritten;
					nbOfCharWritten_total += nbOfCharWritten;
				} while (ret && (delta < nbOfCharRead));
			}
		} while ( ret && nbOfCharRead && ( nbOfCharWritten_total < ContentLength ) );
	}

	// Récupération d'un code HTTP différent de 200
	std::string respL, respL_MAJ;
	std::stringstream respLs;
	do {
		respL = GetResponseLine(*(rr.getHandle()));
		respL_MAJ = respL;
		std::transform (respL_MAJ.begin(),respL_MAJ.end(), respL_MAJ.begin(), toupper);
		if (respL_MAJ.substr(0, 7) == "STATUS:") {
			int deb = 7;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				HTTP_RETCODE rc = strtoul(respL.substr(deb, 3).c_str(), NULL, 10);
				rr.setRetCode(rc);
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 13) == "CONTENT-TYPE:") {
			int deb = 13;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Type", respL.substr(deb));
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 15) == "CONTENT-LENGTH:") {
			int deb = 15;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Length", respL.substr(deb)); 
			} else {
				respLs << respL << "\r\n";
			}
		} else {
			respLs << respL << "\r\n";
		}
	} while (respL.length()); 

	// Envoi des entêtes
	SendHeaders(rr, FALSE);
	SendChunk(rr, (void*)(respLs.str().c_str()), respLs.str().length(), /* is_header */TRUE);

	if ((rr.getReqMethod() != HTTP_HEAD) &&
		(!((rr.getRetCode() >= 100 && rr.getRetCode() < 200) ||	// Information
		(rr.getRetCode() == 204) ||				// No Content
		(rr.getRetCode() == 304))				// Not Modified
		))
	{
		// Envoi du reste
		SendFile(rr);
		// Fermeture 
		SendChunk(rr, NULL, 0);
	}

	// Fermeture des handles
	CloseFile(hChildStdinWr);
	CloseFile(hChildStdoutRd);

// ZMWSSAPI Cleanup

	return rr.getRetCode();
}

int ZMWS::StartHandler(ZMWSReqResp& rr) {
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	SECURITY_ATTRIBUTES sa; 
	sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
	sa.bInheritHandle = TRUE; 
	sa.lpSecurityDescriptor = NULL; 

	HANDLE_FILE hChildStdoutRd0, hChildStdoutRd, hChildStdoutWr;
	if (!CreatePipe(&hChildStdoutRd0, &hChildStdoutWr, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe1");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	if (!DuplicateHandle(
			GetCurrentProcess(), hChildStdoutRd0,
			GetCurrentProcess(), &hChildStdoutRd, 
			0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
		print_dbg("ERREUR: DuplicateHandle1");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	HANDLE_FILE hChildStdinRd, hChildStdinWr0, hChildStdinWr;
	if (!CreatePipe(&hChildStdinRd, &hChildStdinWr0, &sa, 0)) {
		print_dbg("ERREUR: CreatePipe2");
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	if (!DuplicateHandle(
			GetCurrentProcess(), hChildStdinWr0,
			GetCurrentProcess(), &hChildStdinWr, 
			0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
		print_dbg("ERREUR: DuplicateHandle2");
		rr.setRetCode(500);
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	memset(&si, 0, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	si.hStdInput = hChildStdinRd;
	si.hStdOutput = hChildStdoutWr;
	si.wShowWindow = SW_HIDE;
	
	ZMWSHandler handler = rr.getHandler();
	std::string commandLine = handler.handlerPath;
	if (handler.handlerType == handler.DEFAULT) {
		if (commandLine.length()) {
			commandLine += " \""+rr.getFilePath()+"\"";
		} else {
			commandLine = "\""+rr.getFilePath()+"\"";
		}
	}    
	LPVOID MODULE_ENVIRONMENT = (LPVOID)SetupCgiEnvironment(rr);
	BOOL retVal = ::CreateProcess(NULL,
					(char*)((commandLine).c_str()),
					NULL,
					NULL,
					TRUE,
					0,
					MODULE_ENVIRONMENT,
					NULL,
					&si,
					&pi);
	free (MODULE_ENVIRONMENT);
	if (!retVal) {
		print_dbg(("ERREUR: "+commandLine+"\n").c_str());
		CloseFile(hChildStdinRd);
		CloseFile(hChildStdinWr);
		CloseFile(hChildStdoutWr);
		CloseFile(hChildStdoutRd);
		rr.setRetCode(500);
		rr.setErrorMessage("Le handler n'a pas pu être exécuté\n");
		DefaultErrorMessage (rr);
		return rr.getRetCode();
	}

	// WaitForInputIdle(pi.hProcess, INFINITE);
	CloseThread(pi.hThread);
	CloseProcess(pi.hProcess);
	CloseFile(hChildStdinRd);
	CloseFile(hChildStdoutWr);

	rr.setHandle(&hChildStdoutRd);

	DWORD ContentLength = 0;
	std::string tmp = rr.getReqVar("CONTENT_LENGTH");
	if (tmp.length()) ContentLength = strtoul(tmp.c_str(), NULL, 10);

	char buff[8192];
	DWORD nbOfCharRead, nbOfCharWritten, nbOfCharWritten_total = 0;
	BOOL ret;
	if (ContentLength) {
		do {
			if (rr.getIsSSL()) {
				nbOfCharRead = modssl_functions.fnRead (rr.GetModSSLData(), (LPBYTE)buff, 8192);
			} else {
				// 14.05.2006: Déplacé ici pour ne pas faire le test en https
				if (!WaitForInputOnSocket(*(rr.getSocket()))) break;
				nbOfCharRead = recv(*(rr.getSocket()), buff, 8192, 0);
			}
			if ( nbOfCharRead > 0 ) {
				DWORD delta = 0;
				do {
					ret = WriteFile(hChildStdinWr,
								buff + delta,
								nbOfCharRead - delta,
								&nbOfCharWritten, NULL);
					if (!ret) {
						DWORD err = GetLastError();
						print_dbg ("Erreur lors du passage des variables POST !\n");
					}
					delta+=nbOfCharWritten;
					nbOfCharWritten_total += nbOfCharWritten;
				} while (ret && (delta < nbOfCharRead));
			}
		} while ( ret && nbOfCharRead && ( nbOfCharWritten_total < ContentLength ) );
	}
	CloseFile(hChildStdinWr);

	// Récupération d'un code HTTP différent de 200
	std::string respL, respL_MAJ;
	std::stringstream respLs;
	do {
		respL = GetResponseLine(*(rr.getHandle()));
		respL_MAJ = respL;
		std::transform (respL_MAJ.begin(),respL_MAJ.end(), respL_MAJ.begin(), toupper);
		if (respL_MAJ.substr(0, 7) == "STATUS:") {
			int deb = 7;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				HTTP_RETCODE rc = strtoul(respL.substr(deb, 3).c_str(), NULL, 10);
				rr.setRetCode(rc);
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 13) == "CONTENT-TYPE:") {
			int deb = 13;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Type", respL.substr(deb));
			} else {
				respLs << respL << "\r\n";
			}
		} else if (respL_MAJ.substr(0, 15) == "CONTENT-LENGTH:") {
			int deb = 15;
			while (deb<respL.length() && respL.at(deb)==' ') ++deb;
			if (deb<respL.length()) {
				rr.setRespVar("Content-Length", respL.substr(deb)); 
			} else {
				respLs << respL << "\r\n";
			}
		} else {
			respLs << respL << "\r\n";
		}
	} while (respL.length()); 

	// Envoi des entêtes
	SendHeaders(rr, FALSE);
	SendChunk(rr, (void*)(respLs.str().c_str()), respLs.str().length(), /* is_header */TRUE);
 
	if ((rr.getReqMethod()!=HTTP_HEAD) &&
		(!((rr.getRetCode() >= 100 && rr.getRetCode() < 200) ||	// Information
		(rr.getRetCode() == 204) ||				// No Content
		(rr.getRetCode() == 304))				// Not Modified
		))
	{
		// Envoi du reste
		SendFile(rr);
		// Fermeture
		SendChunk(rr, NULL, 0);
	}

	CloseFile(hChildStdoutRd);

	return rr.getRetCode();
}

int ZMWS::SendRSSDirListing(ZMWSReqResp& rr) {
	std::string tmpPath;
	ZMWSFileUtils zfu;
	std::string urlPrefix = rr.getIsSSL() ? "https://" : "http://";
	if (rr.getReqVar("HOST").length()) {
		urlPrefix += rr.getReqVar("HOST"); // Contient déjà le port
	} else {
		rr.setRetCode (400);
		rr.setErrorMessage ("Missing 'Host' header, RSS listing is only available to HTTP/1.1 clients");
		return rr.getRetCode();
	}
	tmpPath = rr.getFilePath() + "/*.*";
	if (zfu.getFile(tmpPath)) {
		rr.setRespVar("Content-Type", "application/xml");
		rr.setRetCode(200);
		SendHeaders(rr);
		if (rr.getReqMethod() == HTTP_HEAD)
			return rr.getRetCode();
		std::string thisDirS = rr.getURL();
		std::string thisDirLink = thisDirS;
		std::string Header = "<?xml version=\"1.0\"?>\n";
		
		ZMWSFileUtils::normalizeUrl(thisDirLink);
		

		std::ifstream here_headers ((rr.getFilePath() + "/_rssIndex.zmwsc").c_str());
		if (here_headers) {
			std::string header_string;
			while (getline(here_headers, header_string)) {
				Header += header_string + "\n";
			}
			here_headers.close();
		}
		Header += "<rss version=\"2.0\">\n";
		Header += "<channel>\n";
		Header += "<title>"+thisDirS+" sur "+rr.getReqVar("HOST")+"</title>\n";
		Header += "<description>Listing des fichiers de "+thisDirS+" sur "+rr.getReqVar("HOST")+"</description>\n";
		Header += "<link>"+urlPrefix+thisDirLink+"</link>\n";
		SendChunk(rr, (void*)Header.c_str(), Header.length());
		BOOL IndexContinue = TRUE;
		do {
			std::string NameStr = zfu.getFileName();
			std::string NameLink = NameStr;
			std::stringstream ResponseSS;
			std::string SaveTimeStrS;

			ZMWSFileUtils::normalizeUrl(NameLink);

			if (NameStr == ".") continue;
			if (NameStr == "..") continue;
			if (NameStr.find(".zmwsc")!=std::string::npos) continue;

			SaveTimeStrS = zfu.getWriteDateRFC1123();
			ResponseSS << "<item>\n" ;
			ResponseSS << "<title>" << NameStr << "</title>\n";
			ResponseSS << "<pubDate>" << SaveTimeStrS << "</pubDate>\n";
			ResponseSS << "<link>" << urlPrefix << thisDirLink << NameLink << "?" << zfu.getWriteDateSort() << "</link>\n";
			ResponseSS << "</item>\n" ;
			SendChunk(rr, (void*)ResponseSS.str().c_str(), ResponseSS.str().length());
		} while (zfu.getNextFile());
		std::string Footer = "</channel>\n";
		Footer += "</rss>\n";
		SendChunk(rr, (void*)Footer.c_str(), Footer.length());
		SendChunk(rr, NULL, 0);
	} else {
		rr.setRetCode(404);
	}
	return rr.getRetCode();
}

int ZMWS::SendXMLDirListing(ZMWSReqResp& rr) {
	std::string tmpPath;
	ZMWSFileUtils zfu;
	tmpPath = rr.getFilePath() + "/*.*";
	if (zfu.getFile(tmpPath)) {
		rr.setRespVar("Content-Type", "application/xml");
		rr.setRetCode(200);
		SendHeaders(rr);
		if (rr.getReqMethod() == HTTP_HEAD)
			return rr.getRetCode();
		std::string thisDirS = rr.getURL();
		std::string Header = "<?xml version=\"1.0\"?>\n";
		std::ifstream here_headers ((rr.getFilePath() + "/_xmlIndex.zmwsc").c_str());
		if (here_headers) {
			std::string header_string;
			while (getline(here_headers, header_string)) {
				Header += header_string + "\n";
			}
			here_headers.close();
		}
		Header += "<dirlisting>\n";
		Header += "<url>"+thisDirS+"</url>\n";
		Header += "<dir>\n";
		SendChunk(rr, (void*)Header.c_str(), Header.length());
		BOOL IndexContinue = TRUE;
		do {
			std::string NameStr = zfu.getFileName();
			std::stringstream ResponseSS;
			std::string SaveTimeStrS;

			if (NameStr == ".") continue;
			if (NameStr == "..") continue;
			if (NameStr.find(".zmwsc")!=std::string::npos) continue;

			ZMWSFileUtils::normalizeUrl(NameStr);

			SaveTimeStrS = zfu.getWriteDate();
			if ( zfu.isDir() ) {
				ResponseSS << "<directory>\n" ;
				ResponseSS << "<link>" << NameStr << "</link>\n";
				ResponseSS << "<name>" << zfu.getFileName() << "</name>\n";
				ResponseSS << "<mtime>" << SaveTimeStrS << "</mtime>\n";
				ResponseSS << "</directory>\n" ;
			} else {
				ResponseSS << "<file>\n" ;
				ResponseSS << "<link>" << NameStr << "</link>\n";
				ResponseSS << "<name>" << zfu.getFileName() << "</name>\n";
				ResponseSS << "<mtime>" << SaveTimeStrS << "</mtime>\n";
				ResponseSS << "<sizeh>" << zfu.getFileSizeH() << "</sizeh>\n";
				ResponseSS << "<size>" << zfu.getFileSize() << "</size>\n";
				ResponseSS << "</file>\n" ;
			}
			SendChunk(rr, (void*)ResponseSS.str().c_str(), ResponseSS.str().length());
		} while (zfu.getNextFile());
		std::string Footer = "</dir>\n";
		if (serverConfig.GetPBToken() != PBTOKEN_OFF) {
			Footer += "<server>";
			Footer += rr.getRespVar("Server");
			Footer += "</server>\n";
		}
		Footer += "</dirlisting>\n";
		SendChunk(rr, (void*)Footer.c_str(), Footer.length());
		SendChunk(rr, NULL, 0);
	} else {
		rr.setRetCode(404);
	}
	return rr.getRetCode();
}

int ZMWS::SendDirListing(ZMWSReqResp& rr) {
	std::string tmpPath;
	typedef std::pair<DWORD, std::string> DWSTRPair;
	typedef std::pair<std::string, std::string> STRSTRPair;
	typedef std::multimap<DWORD, std::string>::reverse_iterator DWSTRMapRIterator;
	typedef std::multimap<std::string, std::string>::reverse_iterator STRSTRMapRIterator;
	typedef std::multimap<std::string, std::string>::iterator STRSTRMapIterator;
	std::multimap<std::string, std::string> StringSortedMapD;
	std::multimap<DWORD, std::string> DWORDSortedMapF;
	std::multimap<std::string, std::string> StringSortedMapF;

	BOOL canIndex;
	ZMWSVHostConfig vhc;
	if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
		canIndex = vhc.canIndex;
	} else {
		canIndex = serverConfig.GetCanIndex();
	}

	if (!canIndex) {
		if (!ZMWSFileUtils::fileExists(rr.getFilePath() + "/_canIndex.zmwsc")) {
			rr.setRetCode(403);
			rr.setErrorMessage("Directory listing is disabled ...");
			return rr.getRetCode();
		}
	}

	if (ZMWSFileUtils::fileExists(rr.getFilePath() + "/_rssIndex.zmwsc")) {
		SendRSSDirListing (rr);
		return rr.getRetCode();
	}

	if (ZMWSFileUtils::fileExists(rr.getFilePath() + "/_xmlIndex.zmwsc")) {
		SendXMLDirListing (rr);
		return rr.getRetCode();
	}

	std::string dirico, ficico, upico;
	ZMWSFileUtils zfu;
	tmpPath = rr.getReqRootDir()+"/icons/dir.*";
	ZMWSFileUtils::normalizePath(tmpPath);
	if (zfu.getFile(tmpPath)) {
		dirico = "/icons/";
		dirico += zfu.getFileName();
	}
	tmpPath = rr.getReqRootDir()+"/icons/fic.*";
	ZMWSFileUtils::normalizePath(tmpPath);
	if (zfu.getFile(tmpPath)) {
		ficico = "/icons/";
		ficico += zfu.getFileName();
	}
	tmpPath = rr.getReqRootDir()+"/icons/up.*";
	ZMWSFileUtils::normalizePath(tmpPath);
	if (zfu.getFile(tmpPath)) {
		upico = "/icons/";
		upico += zfu.getFileName();
	}

	tmpPath = rr.getFilePath() + "/*.*";
	ZMWSFileUtils::normalizePath(tmpPath);
	if (zfu.getFile(tmpPath)) {
		rr.setRespVar("Content-Type", "text/html; charset=iso-8859-1");
		rr.setRetCode(200);
		SendHeaders(rr);
		if (rr.getReqMethod() == HTTP_HEAD)
			return rr.getRetCode();
		std::string thisDirS = rr.getURL();
		std::string Header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
		Header += "<html xml:lang=\"fr\">\n";
		Header += "<head>\n";
		Header += "<title>Index : "+thisDirS+"</title>\n";
		Header += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\" />\n";
		Header += "</head>\n";
		Header += "<body>\n";
		Header += "<h1>Index de ";
		Header += thisDirS;
		Header += "</h1>\n<hr />\n<table border=\"0\">\n";
		Header += "<tr><th><a href=\""+thisDirS+"?S=N\">Nom</a></th>";
		Header += "<th width=\"150\" align=\"right\"><a href=\""+thisDirS+"?S=S\">Taille</a></th>";
		Header += "<th width=\"250\"><a href=\""+thisDirS+"?S=D\">Modification (GMT)</a></th></tr>\n";
		SendChunk(rr, (void*)Header.c_str(), Header.length());
		BOOL IndexContinue = TRUE;
		do {
			std::string NameStr = zfu.getFileName();
			if (NameStr == ".") continue;
			if (NameStr.find(".zmwsc")!=std::string::npos) continue;
			std::stringstream ImgStrSS;
			std::stringstream ResponseSS;
			std::string SizeStrS;
			std::string SaveTimeStrS;
			if (NameStr == "..") {
				NameStr = "Remonter d'un niveau";
				if (upico.length()) ImgStrSS << "<img alt=\"UP\" src=\"" << upico << "\" />";
				ResponseSS << "<tr><td>" << ImgStrSS.str() << " <a href=\"..\">" << NameStr
					<< "</a></td><td align=\"right\">" << SizeStrS
					<< "</td><td align=\"center\">" << SaveTimeStrS << "</td></tr>\n";
				SendChunk(rr, (void*)ResponseSS.str().c_str(), ResponseSS.str().length());
				continue;
			}
			ZMWSFileUtils::normalizeUrl(NameStr);
			SaveTimeStrS = zfu.getWriteDate();
			if ( zfu.isDir() ) {
				SizeStrS = "DIR";
				if (dirico.length()) ImgStrSS << "<img alt=\"DIR\" src=\"" << dirico << "\" />";
			} else {
				SizeStrS = zfu.getFileSizeH();
				if (ficico.length()) ImgStrSS << "<img alt=\"FIC\" src=\"" << ficico << "\" />";
			}
			ResponseSS << "<tr><td>" << ImgStrSS.str() << " <a href=\"" << NameStr
				<< "\">" << zfu.getFileName() << "</a></td><td align=\"right\">" << SizeStrS
				<< "</td><td align=\"center\">" << SaveTimeStrS << "</td></tr>\n";
			if (rr.getReqVar("QUERY_STRING").find("S=S")!=std::string::npos) {
				if (zfu.isDir())
					StringSortedMapD.insert(STRSTRPair(zfu.getFileName(), ResponseSS.str()));
				else
					DWORDSortedMapF.insert(DWSTRPair(zfu.getFileSizeN(), ResponseSS.str()));
			} else if (rr.getReqVar("QUERY_STRING").find("S=D")!=std::string::npos) {
				if (zfu.isDir())
					StringSortedMapD.insert(STRSTRPair(zfu.getWriteDateSort(), ResponseSS.str()));
				else
					StringSortedMapF.insert(STRSTRPair(zfu.getWriteDateSort(), ResponseSS.str()));
			} else {
				if (zfu.isDir())
					StringSortedMapD.insert(STRSTRPair(zfu.getFileName(), ResponseSS.str()));
				else
					StringSortedMapF.insert(STRSTRPair(zfu.getFileName(), ResponseSS.str()));
			}
		} while (zfu.getNextFile());
		if (rr.getReqVar("QUERY_STRING").find("S=S")!=std::string::npos) {
			STRSTRMapIterator itS;
			DWSTRMapRIterator ritD;
			for (itS = StringSortedMapD.begin(); itS != StringSortedMapD.end(); ++itS) {
				SendChunk(rr, (void*)itS->second.c_str(), itS->second.length());
			}
			for (ritD = DWORDSortedMapF.rbegin(); ritD != DWORDSortedMapF.rend(); ++ritD) {
				SendChunk(rr, (void*)ritD->second.c_str(), ritD->second.length());
			}
		} else if (rr.getReqVar("QUERY_STRING").find("S=D")!=std::string::npos) {
			STRSTRMapRIterator rit;
			for (rit = StringSortedMapD.rbegin(); rit != StringSortedMapD.rend(); ++rit) {
				SendChunk(rr, (void*)rit->second.c_str(), rit->second.length());
			}
			for (rit = StringSortedMapF.rbegin(); rit != StringSortedMapF.rend(); ++rit) {
				SendChunk(rr, (void*)rit->second.c_str(), rit->second.length());
			}
		} else {
			STRSTRMapIterator it;
			for (it = StringSortedMapD.begin(); it != StringSortedMapD.end(); ++it) {
				SendChunk(rr, (void*)it->second.c_str(), it->second.length());
			}
			for (it = StringSortedMapF.begin(); it != StringSortedMapF.end(); ++it) {
				SendChunk(rr, (void*)it->second.c_str(), it->second.length());
			}
		}
		std::string Footer = "</table>\n<hr />\n";
		if (serverConfig.GetPBToken() != PBTOKEN_OFF) {
			Footer += "<div align=\"right\">Powered by ";
			Footer += rr.getRespVar("Server");
			Footer += "</div>\n";
		}
		Footer += "</body>\n</html>\n";
		SendChunk(rr, (void*)Footer.c_str(), Footer.length());
		SendChunk(rr, NULL, 0);
	} else {
		rr.setRetCode(404);
	}
	return rr.getRetCode();
}


int ZMWS::GetStartPage(ZMWSReqResp& rr, ZMWSFileUtils& indexZFU) {
	std::string startPages = serverConfig.GetStartPages();
	std::string::size_type pos1 = 0, pos2 = 0;
	std::string pattern;
	std::string::size_type pattern_length;
	do {
		// On cherche le motif 
		pos2 = startPages.find(';', pos1);
		if ((pos2 != std::string::npos)) {
			pattern_length = pos2-pos1;
		} else {
			pattern_length = startPages.length()-pos1;
		}
		pattern = startPages.substr(pos1, pattern_length);
		// On avance pos1
		pos1 += pattern_length + 1;
		// On cherche un fichier correpondant
		if (!indexZFU.getFile(rr.getFilePath() + '\\' + pattern)) {
			continue;
		}
		// Tant que c'est un répertoire ça ne nous intéresse pas
		while (indexZFU.isDir()) {
			if (!indexZFU.getNextFile()) break;
		}
		// Si c'est un fichier, on a gagné //
	} while ((!indexZFU.isFile()) && (pos2 != std::string::npos));
	return rr.getRetCode();
}

BOOL ZMWS::FindWithLangNegotiation (ZMWSReqResp& rr, std::string FilePath) {
	// On tente les .lang
	ZMWSFileUtils zfu;
	langs_map_type langs = rr.getLangsMap();
	langs_map_type::reverse_iterator lang_it;

	for (lang_it = langs.rbegin() ; lang_it != langs.rend() ; ++lang_it) {
		if (lang_it->first == 0) continue;
		std::string searchPath = FilePath + "." + lang_it->second;
		std::string::size_type extensionPos, typeExtensionPos, lastSlashPos;
		std::string extension;
		if (zfu.getFile(searchPath) && zfu.isFile()) {
			extensionPos = zfu.getFileName().rfind('.');
			if (extensionPos != std::string::npos) {
				extension = zfu.getFileName().substr(extensionPos+1);
			} else {
				extension ="";
			}
			typeExtensionPos = zfu.getFileName().rfind('.', extensionPos-1);
			if (typeExtensionPos != std::string::npos) {
				extension = zfu.getFileName().substr(typeExtensionPos+1, extensionPos-typeExtensionPos-1);
			} else {
				extension ="";
			}
			lastSlashPos = FilePath.find_last_of("/\\");			
			rr.setFilePath(FilePath.substr(0, lastSlashPos+1)+zfu.getFileName());
			rr.setFileExtension (extension);
			return TRUE;
		}
	}
	return FALSE;
}

BOOL ZMWS::FindWithMultiview (ZMWSReqResp& rr, std::string FilePath) {
/*	if (FindWithLangNegotiation(rr, FilePath+".*")) {
		return TRUE;
	}
*/
	// Si on n'a rien trouvé encore, on essaie un .*
	ZMWSFileUtils zfu;
	std::string searchPath = FilePath + ".*";
	std::string::size_type extensionPos;
	std::string extension;
	if (zfu.getFile(searchPath) && zfu.isFile()) {
		extensionPos = zfu.getFileName().rfind('.');
		if (extensionPos != std::string::npos) {
			extension = zfu.getFileName().substr(extensionPos+1);
		} else {
			extension ="";
		}    
		rr.setFilePath(FilePath+"."+extension);
		rr.setFileExtension (extension);
		return TRUE;
	}
	return FALSE;
}

int ZMWS::FindLocalFile (ZMWSReqResp& rr, std::string FilePath) {
   	ZMWSFileUtils zfu;
    int next_path_pos = FilePath.find_last_of("/\\");
	std::string OrigFilePath = FilePath;
	std::string rootDir = rr.getAliasedRootDir().length() ?
		rr.getAliasedRootDir() : rr.getReqRootDir();
	// Add support for multiviews here
	while (next_path_pos != std::string::npos) {
		if (FindWithMultiview(rr, FilePath)) {
			std::string path_info = OrigFilePath.substr(FilePath.length());
			rr.setReqVar("PATH_INFO", path_info);
			rr.setRetCode(200);
			return rr.getRetCode();
		}
		FilePath = FilePath.substr(0, next_path_pos);
		if (zfu.getFile(FilePath) && zfu.isFile()) {
			std::string path_info = OrigFilePath.substr(FilePath.length());
			rr.setReqVar("PATH_INFO", path_info);
// Non, php s'en sert pour savoir de quel fichier on parle
//			rr.setReqVar("PATH_TRANSLATED", rr.getReqRootDir()+path_info);
			rr.setFilePath(FilePath);
			rr.setRetCode(200);
			return rr.getRetCode();
		}
		next_path_pos = FilePath.find_last_of("/\\", FilePath.length()-2);
		if (FilePath.length() <= rootDir.length()) {
			rr.setRetCode(404);
			break;
		}
	}
	return rr.getRetCode();
}

int ZMWS::GetLocalFile(ZMWSReqResp& rr) {
	// disallow ".." sequences in the urls
	if (rr.getURL().find("..")!=std::string::npos) {
	    rr.setRetCode(403);
		return rr.getRetCode();
	}

	if (rr.getURL().find(".zmwsc")!=std::string::npos) {
		// zmwsc dirs and files do NOT exist !
		rr.setRetCode(403);
		return rr.getRetCode();
	}

	// Get the VHostConfig if any
	ZMWSVHostConfig vhc;
	serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc);

	// Alias rules
	std::string FilePath;
	std::string AliasedReqRootDir;
	std::string UrlToFile = serverConfig.GetAliasedDir(rr.getURL(), AliasedReqRootDir, vhc.domain);
	if (UrlToFile.length()) {
		ZMWSFileUtils::normalizePath(AliasedReqRootDir);
		rr.setAliasedRootDir(AliasedReqRootDir);
		FilePath = UrlToFile;
	} else {
		FilePath = rr.getReqRootDir() + rr.getURL();
	}
	rr.setFilePath(FilePath);
	
	std::string rootDir = AliasedReqRootDir.length() ? AliasedReqRootDir : rr.getReqRootDir();

	ZMWSFileUtils zfu;
	ZMWSFileUtils indexZFU;

	// Directory index ou redirection pour / manquant à la fin
	if (rr.getFilePath() == rootDir)  {
		// Permet de passer outre le fait que findFirstFile sur des racines de lecteurs
		// ne fonctionne pas comme avec les autres chemins
		if ( rr.getURL().at(rr.getURL().length()-1) != '/') {
			rr.setURL(rr.getURL()+'/');
			rr.setRetCode(301);
			Redirect(rr, rr.getURL());
		} else {
			GetStartPage(rr, indexZFU);
			if (indexZFU.isFile()) {
				rr.setFilePath(rr.getFilePath() + '\\' + indexZFU.getFileName());
	//			rr.setURL(rr.getURL()+indexZFU.getFileName());
			} else {
				rr.setIsDirectory();
			}
		}
	} else if (zfu.getFile(rr.getFilePath()) && zfu.isDir()) {
		if ( rr.getURL().at(rr.getURL().length()-1) != '/') {
			rr.setURL(rr.getURL()+'/');
			rr.setRetCode(301);
			Redirect(rr, rr.getURL());
		} else {
			GetStartPage(rr, indexZFU);
			if (indexZFU.isFile()) {
				rr.setFilePath(rr.getFilePath() + '\\' + indexZFU.getFileName());
//				rr.setURL(rr.getURL()+indexZFU.getFileName());
			} else {
				rr.setIsDirectory();
			}
		}
	} else {
	    // Regular file
		if (!ZMWSFileUtils::fileExists(rr.getFilePath())) {				
				rr.setRetCode(404);
				// Try to find a path info (with multiviews)
				FindLocalFile(rr, FilePath);
		}/* else {
			FindWithLangNegotiation(rr, FilePath);
		}*/
	}
	return rr.getRetCode();
}

void ZMWS::DefaultErrorMessage ( ZMWSReqResp& rr ) {
	HTTP_RETCODE rc = rr.getRetCode();
	if ((rc >= 100 && rc < 200) ||	// Information
		(rc == 204) ||				// No Content
		(rc == 304)) {				// Not Modified
		// No Message Body Here !
		SendHeaders(rr);
	} else {
		rr.setRespVar("Content-Type", "text/html");
		
		std::stringstream ResponseBody;
		ResponseBody << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
		ResponseBody << "<html xml:lang=\"fr\">\n";
		ResponseBody << "<head>\n";
		ResponseBody << "<title>Erreur :" << rr.getRetCode() << "</title>\n";
//		ResponseBody << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-15\" />\n";
		ResponseBody << "</head>\n";
		ResponseBody << "<body>\n<h1>Erreur " << rr.getRetCode() << "</h1>\n<hr />\n";
		ResponseBody << "<h2>Il y a eu une erreur</h2>\n<p>Le code est ";
		ResponseBody << rr.getRetCode() << ".</p>\n";
		if (rr.getErrorMessage().length())
			ResponseBody << "<hr />\nMessage Serveur: </p><p><i>" << rr.getErrorMessage() << "</i></p>\n";
		std::string Footer = "<hr />\n";
		if (serverConfig.GetPBToken() != PBTOKEN_OFF) {
			Footer += "<div align=\"right\">Powered by ";
			Footer += rr.getRespVar("Server");
			Footer += "</div>\n";
		}
		Footer += "</body>\n</html>\n";
		ResponseBody << Footer;

		std::stringstream taille;
		taille << ResponseBody.str().length();
		rr.setRespVar("Content-Length", taille.str());
		SendHeaders(rr);
		SendChunk(rr, (void*)ResponseBody.str().c_str(), ResponseBody.str().length());
		SendChunk(rr, NULL, 0);
	}
}

int ZMWS::Reply( ZMWSReqResp& rr ) {
	// vhost check :-)
	ZMWSVHostConfig vhc;
	if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
		// map url to vhost path
		rr.setReqRootDir(vhc.documentRoot);
	} else {
		// map url to local path
		rr.setReqRootDir(serverConfig.GetDocumentRoot());
	}

	if (!CheckClientIPOK(rr) || CheckClientIPDenied(rr)) {
		// Allow from or Denied From
		rr.cnx_policy = CNX_POLICY_CLOSE;
	} else {
		// Arrêt du serveur ?
		// Uniquement depuis la machine locale
		if ((serverConfig.GetCanStop()) &&
			(rr.getURL()=="/_stopServer.zmwsc") &&
			(rr.getReqVar("REMOTE_ADDR")=="127.0.0.1")) {
			rr.cnx_policy = CNX_POLICY_CLOSE;
			if (serverConfig.GetCloseBrowser()) CloseBrowser(rr);
			StopServer(TRUE); // TRUE == Post a Quit Message
			return rr.getRetCode();
		}

		// Limitation de traffic ?
		if (CheckSentBytesOK(rr)) {
			// Lien URI --- Fichier Local
			if (rr.getRetCode() == 200) {
				int retcode = GetLocalFile(rr);
				if (retcode != 200) {
					// les redirections sont prises en charge avant ...
					// le cache est pris en charge ensuite ...
					if (retcode<400) return retcode;
				}
			}

			// Authentification
			// Après avoir cherché le fichier local
			if (FindAuthFile(rr)) {
				if (rr.getRetCode()!=500) {
					if (!rr.getReqVar("AUTHORIZATION").length() || !AuthenticateUser(rr)) {
						rr.setRetCode(401);
						SendAuthChallenge(rr);
					}
				}
			}

			// C'est un indexage de répertoires ?	
			if (rr.getIsDirectory() && (rr.getRetCode() == 200)) {
				rr.setRetCode(SendDirListing(rr));
				if (rr.getRetCode() == 200) {
					return rr.getRetCode();
				}
			}
		}
	}

	// Erreurs
	if (rr.getRetCode() >= 400) {
		// On cherche une page d'erreur personnalisée
		ZMWSFileUtils zfu;
		std::stringstream pageName;
		std::string pathprefix;

		// On la cherche dans l'arborescence du VHost
		pathprefix = rr.getReqRootDir();
		pageName << pathprefix << "/_errorpages.zmwsc/" << rr.getRetCode() << ".*";
		zfu.getFile(pageName.str());
		if (!zfu.fileExists()) {
			std::stringstream pageName;
			pageName << pathprefix << "/_errorpages.zmwsc/default.*";
			zfu.getFile(pageName.str());
		} 

		// Si on n'a rien, on recherche dans l'arborescence principale
		if (!zfu.fileExists()) {
			pathprefix = serverConfig.GetDocumentRoot();
			std::stringstream pageName;
			pageName << pathprefix << "/_errorpages.zmwsc/" << rr.getRetCode() << ".*";
			zfu.getFile(pageName.str());
		} 
		if (!zfu.fileExists()) {
			std::stringstream pageName;
			pageName << pathprefix << "/_errorpages.zmwsc/default.*";
			zfu.getFile(pageName.str());
		} 

		// Si on a trouvé une page d'erreur on l'utilise !
		if (zfu.fileExists()) {
			std::string errPagePath(pathprefix);
			errPagePath += "/_errorpages.zmwsc/";
			errPagePath += zfu.getFileName();
			rr.setFilePath(errPagePath);
		} else {
			// Body par défaut des erreurs
			DefaultErrorMessage (rr);
			return rr.getRetCode();
		}
	}

	HANDLE_FILE FileHandle;
	if ((FileHandle = CreateFile(rr.getFilePath().c_str(),
			 					 GENERIC_READ,
								 FILE_SHARE_READ, 
								 NULL,
								 OPEN_EXISTING,
								 FILE_ATTRIBUTE_NORMAL,
								 NULL)) == INVALID_HANDLE_VALUE) {
		rr.setRetCode(404); // Limite 500 là ...
		rr.setErrorMessage("Could not open: "+rr.getFilePath());
		DefaultErrorMessage (rr);
	} else {
		ZMWSVHostConfig vhc;
		serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc);

		// Lancements des modules s'il y a besoin
		ZMWSFileUtils zfu;
		if (zfu.getFile(rr.getFilePath())) {
			std::string extension = rr.getFileExtension();
			if (!extension.length()) {
				std::string::size_type extensionPos = zfu.getFileName().rfind('.');
				if (extensionPos != std::string::npos) {
					rr.setFileExtension (zfu.getFileName().substr(extensionPos+1));
					extension = rr.getFileExtension();
				}
			} 
			
			if ( extension.length() ) {
				ZMWSPerUrlConfig puc;
				if (serverConfig.GetPerUrlConfig(rr.getURL(), puc)) {
					ZMWSHandler handler = puc.GetHandler(extension);
					if (handler.handlerType != handler.NONE) {
						CloseFile(FileHandle);
						rr.setHandler(handler);
						StartHandler(rr);
						return rr.getRetCode();
					}
				}
				ZMWSHandler handler = serverConfig.GetHandler(extension, vhc.domain);
				switch (handler.handlerType) {
					case handler.DEFAULT:
					case handler.TRUECGI:
						CloseFile(FileHandle);
						rr.setHandler(handler);
						StartHandler(rr);
						return rr.getRetCode();
					case handler.ZMWSSAPI:
						CloseFile(FileHandle);
						rr.setHandler(handler);
						StartZMWSSAPIHandler(rr);
						return rr.getRetCode();
					default:
						break;
				}
				if (serverConfig.GetPathToPHP().length() && extension.substr(0,3) == "php") {
						CloseFile(FileHandle);
						StartPHP(rr);
						return rr.getRetCode();
				}
			}

			// Vérification du champ if Modified Since
			// On n'envoie pas un fichier que le client a en cache !
			if (rr.getReqVar("IF_MODIFIED_SINCE").length()) {
				if (!LocalFileIsMoreRecentThanClientCache(rr)) {
					CloseFile(FileHandle);
					rr.setRetCode(304);
					DefaultErrorMessage (rr);
					return rr.getRetCode();
				}
			}

			// Traitement normal pour les fichiers statiques
			// On accepte les ranges et on spécifie la date de modification
			rr.setRespVar("Accept-Ranges", "bytes");
			rr.setRespVar("Last-Modified", zfu.getWriteDateRFC1123()); 
			rr.setRespVar("Content-Type", MIME_Manager.getMimeType(extension));
			rr.setRespVar("Content-Length", zfu.getFileSize());

			// si range dans rr alors :
			//    - retcode à 206
			//	  - entêtes Content-Range correspondantes
			//	  - mise à jour du Content-Length pour avoir le nombre d'octets envoyés
			// TODO: pour plusieurs plages, envoyer en Content-Type: multipart/byteranges 
			ZMWSRange rangeReq = rr.popRangeReq();
			if (rangeReq.range_spec_type != ZMWSRange::INVALID_RANGE_SPEC) {
				unsigned long fbyte, lbyte;
				switch (rangeReq.range_spec_type) {
					case ZMWSRange::BYTE_RANGE_SPEC:
						fbyte = rangeReq.begin;
						lbyte = rangeReq.end;
						break;
					case ZMWSRange::PREFIX_BYTE_RANGE_SPEC:
						fbyte = rangeReq.begin;
						lbyte = zfu.getFileSizeN()-1;
						break;
					case ZMWSRange::SUFFIX_BYTE_RANGE_SPEC:
						fbyte = zfu.getFileSizeN()- 1 - rangeReq.end;
						lbyte = zfu.getFileSizeN()- 1;
						break;
				}
				if	(lbyte >= zfu.getFileSizeN()){
					rr.setRetCode(416);
					rr.setRespVar("Content-Range", "bytes */" + zfu.getFileSize());
				} else if (lbyte >= fbyte) {
					rr.setRetCode(206);
					std::stringstream content_range, content_length;
					content_range << "bytes " << fbyte << "-" << lbyte;
					content_range << '/' << zfu.getFileSize();
					content_length << lbyte - fbyte + 1;
					rr.setRespVar("Content-Range", content_range.str());
					rr.setRespVar("Content-Length", content_length.str());
					rr.pushContentRangeResp(ZMWSContentRange(fbyte, lbyte, zfu.getFileSizeN()));
				}
			}
		}

		SendHeaders(rr);
		rr.setHandle(&FileHandle);
		if (rr.getReqMethod()!=HTTP_HEAD) {
			SendFile (rr);
		}
		CloseFile(FileHandle);
	}
	return rr.getRetCode();
}

int ZMWS::SendFile ( ZMWSReqResp& rr ) {
		char buffer[4096];
		DWORD NbBytes, BytesRead, RemainingBytes = 0;
		BOOL RetCode, Ranged = FALSE;
		ZMWSContentRange content_range;
		if (rr.getRetCode()==206) {
			content_range = rr.popContentRangeResp();
			if (content_range.isValid()) {
				SetFilePointer(*(rr.getHandle()), content_range.begin, 0, FILE_BEGIN);
				RemainingBytes = content_range.end - content_range.begin + 1;
				Ranged = TRUE;
			}
		}
		do {
			NbBytes = 4096;
			if (Ranged && (RemainingBytes < 4096)) {
				NbBytes = RemainingBytes;
			}
			if (RetCode = ReadFile(*(rr.getHandle()), buffer, NbBytes, &BytesRead, NULL)) {
				if (SendChunk(rr, buffer, BytesRead) == SOCKET_ERROR) {
					// Supprimé pour le cas des Handlers ...
					// TODO: Voir s'il faut distinguer fichier statique/handler
					// RetCode = 0;

					// On finit quand même la lecture ...
					// TODO: Arranger ça
					//		 Normalement avec le ménage fait dans les CloseHandle
					//		 ça doit être bon maintenant, même sans lire la fin de
					//		 ce que génère le Handler
					//		 A VERIFIER !
					//
					// J'ai supprimé la lecture du reste, sinon, c'est sans fin
					// while (ReadFile(*(rr.getHandle()), buffer, NbBytes, &BytesRead, NULL) && BytesRead);
					// Remplacé par :
					break;
				}
			}
			if (Ranged) {
				RemainingBytes -= BytesRead;
				if (!RemainingBytes) break;
			}
		} while ( RetCode && BytesRead );
		return rr.getRetCode();
}

int ZMWS::SendChunk(ZMWSReqResp& rr, void* chunk, const DWORD& chunksize, const BOOL is_header) {
		DWORD nBytes;
		DWORD TotBytesSent = 0;
		DWORD BytesSent = 0;
		BOOL RetCode = rr.getRetCode();
		BOOL chunked = (rr.getRespVar("Transfer-Encoding")=="Chunked");

		if (!chunk) {
			if (chunked) {
				std::string endchunk = "0\r\n\r\n";
				if (rr.getIsSSL()) {
					if (BytesSent = modssl_functions.fnWrite(rr.GetModSSLData(), (LPBYTE)endchunk.c_str(), endchunk.length()) <= 0) {
						RetCode = SOCKET_ERROR;
					} else { 
	//					print_dbg((const char *)endchunk.c_str());
						rr.addSentBytes (BytesSent);
					}
				} else {
					if (BytesSent = send(*(rr.getSocket()), (const char *)endchunk.c_str(), endchunk.length(), 0) == SOCKET_ERROR) {
						RetCode = SOCKET_ERROR;
					} else { 
	//					print_dbg((const char *)endchunk.c_str());
						rr.addSentBytes (BytesSent);
					}
				}
			}
			return RetCode;
		}
		char buffer[8192];
		do {
			memset(buffer, 0, 8192);
			nBytes = (chunksize - TotBytesSent > 4096) ? 4096 : chunksize - TotBytesSent;
			if (!chunked || is_header) {
				if (rr.getIsSSL()) {
					if (BytesSent = modssl_functions.fnWrite(rr.GetModSSLData(), (LPBYTE)chunk + TotBytesSent, nBytes) <= 0) {
						RetCode = SOCKET_ERROR;
						break;
					} else { 
	//					print_dbg((const char *)endchunk.c_str());
						rr.addSentBytes (BytesSent);
					}
				} else {
					if ((BytesSent = send(*(rr.getSocket()), (const char *)chunk + TotBytesSent, nBytes, 0)) == SOCKET_ERROR) {
						RetCode = SOCKET_ERROR;
						break;
					} else {
	//					print_dbg((const char *)chunk);
						rr.addSentBytes (BytesSent);
					}
				}
			} else {
				std::stringstream chunkheader;
				chunkheader.setf(std::ios::hex, std::ios::basefield);
				chunkheader << nBytes << "\r\n";
				std::stringstream chunkfooter;
				chunkfooter << "\r\n";
				int bufferpos = 0;
				memcpy (buffer + bufferpos, chunkheader.str().c_str(), chunkheader.str().length());
				bufferpos += chunkheader.str().length();
				memcpy (buffer + bufferpos, (char*)chunk + TotBytesSent, nBytes);
				bufferpos += nBytes;
				memcpy (buffer + bufferpos, chunkfooter.str().c_str(), chunkfooter.str().length());
				bufferpos += chunkfooter.str().length();
				if (rr.getIsSSL()) {
					if (BytesSent = modssl_functions.fnWrite(rr.GetModSSLData(), (LPBYTE)buffer, bufferpos) <= 0) {
						RetCode = SOCKET_ERROR;
						break;
					} else { 
	//					print_dbg((const char *)endchunk.c_str());
						rr.addSentBytes (BytesSent);
					}
				} else {
					if ((BytesSent = send(*(rr.getSocket()), (const char *)buffer, bufferpos, 0)) == SOCKET_ERROR) {
						RetCode = SOCKET_ERROR;
						break;
					} else {
	//					print_dbg((const char *)buffer);
						rr.addSentBytes (BytesSent);
					}
				}
			}
			TotBytesSent += nBytes;
		} while ( TotBytesSent < chunksize );
		return RetCode;
}

int ZMWS::LogRequest(ZMWSReqResp& rr) {
	if (!serverConfig.GetWriteLogs()) return 1;

	std::string FilePath;
	ZMWSVHostConfig vhc;
	if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
		FilePath = vhc.logsDir+"/access_log_"+vhc.domain;
	} else {
		FilePath = serverConfig.GetLogsDir()+"/access_log";
	}

	std::stringstream LogLine;
	if (rr.getReqVar("REMOTE_HOST").length())
		LogLine << rr.getReqVar("REMOTE_HOST");
	else
		LogLine << rr.getReqVar("REMOTE_ADDR");
	LogLine << " - - [";
	SYSTEMTIME lt, st;
	GetSystemTime(&st);
	GetLocalTime(&lt);
	if (lt.wDay<10) LogLine << '0';
	LogLine << lt.wDay << '/';
	switch (lt.wMonth) {
		case 1:
			LogLine << "Jan/";
			break;
		case 2:
			LogLine << "Feb/";
			break;
		case 3:
			LogLine << "Mar/";
			break;
		case 4:
			LogLine << "Apr/";
			break;
		case 5:
			LogLine << "May/";
			break;
		case 6:
			LogLine << "Jun/";
			break;
		case 7:
			LogLine << "Jul/";
			break;
		case 8:
			LogLine << "Aug/";
			break;
		case 9:
			LogLine << "Sep/";
			break;
		case 10:
			LogLine << "Oct/";
			break;
		case 11:
			LogLine << "Nov/";
			break;
		case 12:
			LogLine << "Dec/";
			break;
	}
	if (lt.wYear<10) LogLine << "0";
	LogLine << lt.wYear << ":";
	if (lt.wHour<10) LogLine << "0";
	LogLine << lt.wHour << ":";
	if (lt.wMinute<10) LogLine << "0";
	LogLine << lt.wMinute << ":";
	if (lt.wSecond<10) LogLine << "0";
	LogLine << lt.wSecond << " ";
	int deltaH, deltaM;
	deltaH = lt.wHour - st.wHour;
	deltaM = lt.wMinute - st.wMinute;
	if (deltaM < 0) {
		deltaM+=60;
		deltaH-=1;
	}
	if (deltaH>12) deltaH = 24 - deltaH;
	if (deltaH<-12) deltaH = 24 + deltaH;
	if (deltaH<0) {
		LogLine << "-";
		deltaH -= 2*deltaH;
	} else {
		LogLine << "+";
	}
	if (deltaH<10) LogLine << "0";
	LogLine << deltaH;
	if (deltaM<10) LogLine << "0";
	LogLine << deltaM << "] ";

	LogLine << "\"" << rr.getRequestLine() << "\" ";
	
	LogLine << rr.getRetCode() << " " << rr.getSentBytes_LO() << " ";
	
	std::string tmp = rr.getReqVar("REFERER");
	if (!tmp.length()) tmp = "-";
	LogLine << "\"" << tmp << "\" ";

	tmp = rr.getReqVar("USER_AGENT");
	if (!tmp.length()) tmp = "-";
	LogLine << "\"" << tmp << "\"" << std::endl;

#ifdef WIN32
	ZMWSLogger::LogIt(FilePath, LogLine.str());
#else
	std::ofstream ofs;
	do {
		ofs.open(FilePath.c_str(), std::ios::app);
	} while (!ofs.is_open());

	ofs << LogLine.str();
	ofs.close();
#endif

	return 0;
}

int ZMWS::LogSentBytes(ZMWSReqResp& rr) {
	if (!serverConfig.GetWriteLogs())
		return 1;

	ZMWSVHostConfig vhc;
	if (!serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc))
		return 1;

	if (!vhc.maxKBytesDTime) return 1;

	std::string FilePath = vhc.logsDir+"/bytes_log_"+vhc.domain;

	DWORD BytesWritten;
	DWORD BytesRead1, BytesRead2;
	DWORD SentBytes;
	HANDLE_FILE FileHandle;
	while ((FileHandle = CreateFile(FilePath.c_str(),
			 					 GENERIC_WRITE | GENERIC_READ,
								 0, 
								 NULL,
								 OPEN_ALWAYS,
								 FILE_ATTRIBUTE_NORMAL,
								 NULL)) == INVALID_HANDLE_VALUE) {
		Sleep(1);
	}
	time_t tpsNow, tpsLast;
	time (&tpsNow);
	if (! ReadFile(FileHandle, &tpsLast, 4, &BytesRead1, NULL)) {
		print_dbg("Bytes Log Error (R)\n");
	}
	if (! ReadFile(FileHandle, &SentBytes, 4, &BytesRead2, NULL)) {
		print_dbg("Bytes Log Error (R)\n");
	}
	if ((tpsNow > (tpsLast + vhc.maxKBytesDTime)) || !BytesRead1 || !BytesRead2) {
		SetFilePointer(FileHandle, (LONG)0, NULL, FILE_BEGIN);
		if (! WriteFile(FileHandle, &tpsNow, 4, &BytesWritten, NULL)) {
			print_dbg("Bytes Log Error (W)\n");
		}
		SentBytes = rr.getSentBytes_LO()/1024;
	} else {
		SentBytes += rr.getSentBytes_LO()/1024;
	}
	SetFilePointer(FileHandle, 4, NULL, FILE_BEGIN);
	if (! WriteFile(FileHandle, &SentBytes, 4, &BytesWritten, NULL)) {
		print_dbg("Bytes Log Error (W)\n");
	}
	CloseFile(FileHandle);
	return 0;
}

BOOL ZMWS::CheckClientIPOK(ZMWSReqResp& rr) {
	std::string allowedFrom;
	ZMWSVHostConfig vhc;
	if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
		allowedFrom = vhc.allowFrom;
		if (!allowedFrom.length()) {
			allowedFrom = serverConfig.GetAllowedFrom();
		} else if ("all" == allowedFrom) {
			allowedFrom = "";
		}
	} else {
		allowedFrom = serverConfig.GetAllowedFrom();
	}

	if (0 == allowedFrom.length()) return TRUE;

	std::string clientIP = rr.getReqVar("REMOTE_ADDR");
	std::string::size_type pos1 = 0, pos2 = 0;
	std::string pattern;
	std::string::size_type pattern_length;
	std::string::size_type client_length = clientIP.length();
	BOOL allow = FALSE;
	do {
		// On cherche le motif 
		pos2 = allowedFrom.find(';', pos1);
		if ((pos2 != std::string::npos)) {
			pattern_length = pos2-pos1;
		} else {
			pattern_length = allowedFrom.length()-pos1;
		}
		// On regarde si le pattern correspond à l'IP cliente
		// Si le dernier n'est pas un point
		// On a une IP complète donc on matche sur la longueur de l'ip du client
		// Si non, on matche sur la longueur du pattern
		if ('.' != allowedFrom.at(pos1+pattern_length-1)) {
			if (0 == allowedFrom.compare(pos1, client_length, clientIP, 0, client_length)) {
				allow = TRUE;
			}
		} else {
			if (0 == allowedFrom.compare(pos1, pattern_length, clientIP, 0, pattern_length)) {
				allow = TRUE;
			}
		}
		// On avance pos1
		pos1 += pattern_length + 1;
	} while ((!allow) && (pos2 != std::string::npos));

	if (!allow) {
		rr.setRetCode(403);
		rr.setErrorMessage(clientIP+" is not allowed on this server.");	
	}

	return allow;
}

BOOL ZMWS::CheckClientIPDenied(ZMWSReqResp& rr) {
	std::string deniedFrom;
	ZMWSVHostConfig vhc;
	if (serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc)) {
		deniedFrom = vhc.denyFrom;
		if (!deniedFrom.length()) {
			deniedFrom = serverConfig.GetDeniedFrom();
		} else if ("none" == deniedFrom) {
			deniedFrom = "";
		}
	} else {
		deniedFrom = serverConfig.GetDeniedFrom();
	}

	if (0 == deniedFrom.length()) return FALSE;

	std::string clientIP = rr.getReqVar("REMOTE_ADDR");
	std::string::size_type pos1 = 0, pos2 = 0;
	std::string pattern;
	std::string::size_type pattern_length;
	std::string::size_type client_length = clientIP.length();
	BOOL deny = FALSE;
	do {
		// On cherche le motif 
		pos2 = deniedFrom.find(';', pos1);
		if ((pos2 != std::string::npos)) {
			pattern_length = pos2-pos1;
		} else {
			pattern_length = deniedFrom.length()-pos1;
		}
		// On regarde si le pattern correspond à l'IP cliente
		// Si le dernier n'est pas un point
		// On a une IP complète donc on matche sur la longueur de l'ip du client
		// Si non, on matche sur la longueur du pattern
		if ('.' != deniedFrom.at(pos1+pattern_length-1)) {
			if (0 == deniedFrom.compare(pos1, client_length, clientIP, 0, client_length)) {
				deny = TRUE;
			}
		} else {
			if (0 == deniedFrom.compare(pos1, pattern_length, clientIP, 0, pattern_length)) {
				deny = TRUE;
			}
		}
		// On avance pos1
		pos1 += pattern_length + 1;
	} while ((!deny) && (pos2 != std::string::npos));

	if (deny) {
		rr.setRetCode(403);
		rr.setErrorMessage(clientIP+" is not allowed on this server.");
	}
	
	return deny;
}

int ZMWS::CheckSentBytesOK(ZMWSReqResp& rr) {
	if (!serverConfig.GetWriteLogs())
		return 1;

	ZMWSVHostConfig vhc;
	if (!serverConfig.GetVHostConfig(rr.RemovePort(rr.getReqVar("HOST")), vhc))
		return 1;

	if (!vhc.maxKBytesDTime) return 1;

	if (!vhc.maxKBytes) {
		rr.setRetCode(413);
		rr.setErrorMessage("This website is temporarily disabled<br />\nPlease, try again later<br />\n");
		return 0;
	}

	std::string FilePath = vhc.logsDir+"/bytes_log_"+vhc.domain;

	// Si le fichier n'existe pas
	// On considère que c'est bon ...
	if (!ZMWSFileUtils::fileExists(FilePath)) return 1;

	DWORD BytesRead;
	DWORD SentBytes;
	HANDLE_FILE FileHandle;
	while ((FileHandle = CreateFile(FilePath.c_str(),
			 					 GENERIC_READ,
								 0, 
								 NULL,
								 OPEN_ALWAYS,
								 FILE_ATTRIBUTE_NORMAL,
								 NULL)) == INVALID_HANDLE_VALUE) {
		Sleep(1);
	}
	time_t tpsNow, tpsLast;
	time (&tpsNow);
	if (! ReadFile(FileHandle, &tpsLast, 4, &BytesRead, NULL)) {
		print_dbg("Bytes Log Error (R)\n");
	}
	if (!BytesRead) {
		CloseFile(FileHandle);
		return 1;
	}
	if (! ReadFile(FileHandle, &SentBytes, 4, &BytesRead, NULL)) {
		print_dbg("Bytes Log Error (R)\n");
	}
	CloseFile(FileHandle);

	if (!BytesRead) {
		return 1;
	}

	if (tpsNow > (tpsLast + vhc.maxKBytesDTime)) return 1;
	if (SentBytes >= vhc.maxKBytes) {
		rr.setRetCode(413);
		std::string tmpS;
		tmpS = "This website is temporarily disabled because it has reached its traffic limit.<br />\n";
		tmpS += "Please, try again later<br />\n";
		rr.setErrorMessage(tmpS);
		return 0;
	}


	return 1;
}

int ZMWS::HandleRequest(ZMWSReqResp* RR, const SOCKET& clientSocket)
{
	std::string Current;

	if (!serverConfig.GetBeQuiet()) {
		std::string tmpS = RR->getRequestLine();
		tmpS += "\n";
		if (RR->getReqVar("REFERER").length()) {
    		tmpS += "          Referer: " + RR->getReqVar("REFERER");
    		tmpS += "\n";
		}	
		print_dbg(tmpS.c_str());
	}
	DWORD SizeSent = 0;

	Current = RR->getRequestLine();
	std::string::size_type firstSpace = Current.find(" ");
	if ((firstSpace==std::string::npos)) {
		RR->setRetCode(400);
		RR->setErrorMessage("Invalid request: "+Current);
	} else {
		std::string::size_type secondSpace = Current.find(' ', ++firstSpace);
		if ((secondSpace==std::string::npos))
			secondSpace = Current.length();

		std::string::size_type QSpos = Current.find("?");
		if (QSpos!=std::string::npos) {
			if (secondSpace==Current.length())
				RR->setReqVar("QUERY_STRING", Current.substr(QSpos+1));
			else
				RR->setReqVar("QUERY_STRING", Current.substr(QSpos+1, secondSpace-1-QSpos));
		}

		if ((QSpos>firstSpace) && (QSpos<secondSpace)) {
			RR->setURL (Current.substr(firstSpace, QSpos-firstSpace));
		} else {
			RR->setURL (Current.substr(firstSpace, secondSpace-firstSpace));
		}
	}

	// Prise en charge du codage %xy ...
	// TODO: ajouter la prise en charge utf-8 ? (je ne suis pas pour)
	std::string::size_type ind = 0;
	while ((ind = RR->getURL().find("%", ind)) < std::string::npos) {
		char c = (char)strtoul(RR->getURL().substr(ind+1, 2).c_str(), NULL, 16);
		if (c) {
			RR->getURL().replace(ind, 3, std::string(1, c));
		}
		++ind;
	}

	// Fetch preferred languages from the Accept-Language header
	std::string langs = RR->getReqVar("ACCEPT_LANGUAGE");
	std::string::iterator new_end = std::remove(langs.begin(), langs.end(), ' ');
	langs.erase(new_end, langs.end());
	std::string::size_type lang = 0, nextlang, qual;
	std::string alang;
	std::string aqual_str;
	double aqual;
	char* dummy;
	do {
		nextlang = langs.find(",", lang);
		qual = langs.find(";", lang);
		if (nextlang == std::string::npos) {
			if (qual == std::string::npos) {
				alang = langs.substr(lang);
				aqual_str = "1";
			} else {
				alang = langs.substr(lang, qual-lang);
				aqual_str = langs.substr(qual+1+2, -1-2); // +2 et -2 pour q=
			}
		} else {
			if (qual == std::string::npos) {
				alang = langs.substr(lang, nextlang-lang); 
				aqual_str = "1";
			} else {
				if (qual < nextlang) {
					alang = langs.substr(lang, qual-lang); 
					aqual_str = langs.substr(qual+1+2, nextlang-qual-1-2); // +2 et -2 pour q=
				} else {
					alang = langs.substr(lang, nextlang-lang); 
					aqual_str = "1";
				}
			}
		}
		aqual = strtod(aqual_str.c_str(), &dummy);
		RR->addLang(alang, aqual);
		lang = nextlang+1;
	} while (nextlang != std::string::npos);


	switch (RR->getReqMethod()) {
		case HTTP_GET: 
		case HTTP_POST:
		case HTTP_HEAD:
			Reply(*RR);
			break;
		default:
			Reply(*RR);
			break;
	}
	LogRequest(*RR);
	LogSentBytes(*RR);  

	int rc = RR->cnx_policy;
	if (!serverConfig.GetBeQuiet()) {
		std::stringstream tmpSS2;
		tmpSS2 << "Code de retour: " << RR->getRetCode() << "\n";
		print_dbg(tmpSS2.str().c_str());
	}
	delete RR;
	return rc;
}

void ZMWS::HandleClient(QUEUECLIENTPARAM* qcp) {
	std::string tmpS;
	BOOL ShouldStop;
	BOOL isSSL = FALSE;
	int rc;
	ZMWSReqResp* RR = NULL;
	struct sockaddr_in sa;
	int sa_len = sizeof(struct sockaddr_in);
	isSSL = FALSE;
	if (SOCKET_ERROR != getsockname (qcp->clientSocket, (struct sockaddr *)&sa, &sa_len)) {
		short sport = ntohs(sa.sin_port);
		if (sport==serverConfig.GetHTTPSPort()) {
			isSSL = TRUE;
			qcp->modssl_data = modssl_functions.fnAccept(qcp->clientSocket);
		}
	}

	do {
		ShouldStop = FALSE;
		rc = CNX_POLICY_KEEP;
		/* Lecture de la requête */
		ZMWSReqResp* RR = this->InitializeRR(qcp->clientSocket);
		if (!RR) {
			print_dbg("WARNING : HandleClient : Failed to allocate a new ZMWSReqResp\n");
			rc = CNX_POLICY_CLOSE;
		} else {
			RR->SetIsSSL(isSSL);
			RR->SetModSSLData(qcp->modssl_data);
			RR = GetRequestLines(RR);
			if (!RR) {
				// Could not read request lines
				// Time out or connexion closed
				rc = CNX_POLICY_CLOSE;
			}
		}
		
		DWORD result = WaitForSingleMutex (ZMWS_SERVERSTOP_MUTEX, 5000);
		if (result == WAIT_OBJECT_0) { 
			ShouldStop = ServerStop;
			ReleaseMutex(ZMWS_SERVERSTOP_MUTEX);
		} else {
			tmpS  = "ERROR : HandleClient : Could not acquire Server Mutex\n";
			print_dbg (tmpS.c_str());
		}

		if (!ShouldStop) {
			/* Traitement de la requête */
			if (RR) rc = HandleRequest(RR, qcp->clientSocket);
		}
	} while(rc == CNX_POLICY_KEEP);
	if (isSSL && qcp->modssl_data) {
		modssl_functions.fnClose(qcp->modssl_data);
		qcp->modssl_data = NULL;
	}
}
