// WMSOAPClientConn.cpp: implementation of the WMSOAPClientConn class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2003 Robert Cunnings. All rights reserved.
//
// You may copy, modify, distribute or publish this code free of charge.
//
// NO WARRANTY: This software is provided AS IS, without warranty of any kind.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPClientConn class - a class for HTTP protocol support.
// Uses Winsock2, supports HTTP Version 1.0.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WMSOAPClientConn.h"
#include "WMSOAPListener.h"
#include "SOAPAutomationStub.h"
#include "WMSOAPProcessor.h"
#include "..\wmwsdlclient\wmwsdlclient.h"
#include "..\wmwsdlclient\wmwsdlclient_i.c"
#include "WMLogMgr.h"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

char *g_pszHTTPVersion = "HTTP/1.0";

WMSOAPClientConn::WMSOAPClientConn()
{
	InitializeCriticalSection(&m_CSWriteQueue);
}

WMSOAPClientConn::WMSOAPClientConn(WMSOAPListener *pServer, ULONG uIndex)
{
	m_pServer = pServer;
	m_uListenerIndex = uIndex;
	m_RcvBuffer.buf = NULL;
	m_RcvBuffer.len = 0;
	m_pszBuffer = NULL;
	InitializeCriticalSection(&m_CSWriteQueue);
}

WMSOAPClientConn::~WMSOAPClientConn()
{
	m_Connection.Shutdown();
	// empty write queue
	dequeWriteQueue::iterator iQ;
	for (iQ = m_WriteQueue.begin(); iQ != m_WriteQueue.end(); iQ++)
	{
		LPWSABUF pBuffer = *iQ;
		if (pBuffer != NULL)
		{
			if (pBuffer->buf != NULL)
			{
				free(pBuffer->buf);
			}
			free(pBuffer);
		}
	}
	m_WriteQueue.clear();
	// free receive buffer
	if (m_RcvBuffer.buf != NULL)
	{
		free(m_RcvBuffer.buf);
	}
	// free request buffer 
	if (m_pszBuffer != NULL)
	{
		free(m_pszBuffer);
	}
	DeleteCriticalSection(&m_CSWriteQueue);
}

BOOL WMSOAPClientConn::Initialize(SOCKET s)
{
	EnterCriticalSection(&m_CSWriteQueue);
	m_dwIndex = 0;
	m_dwEntityBodyLength = 0;
	m_nMethod = METHOD_UNSUPPORTED;
	m_bHTTPVersionFound = FALSE;
	m_nHTTPMajorVersion = 0;
	m_nHTTPMinorVersion = 0;
    m_nSOAPVersion = SOAP_VERSION_1_2;
	m_StateMachine.Initialize(ST_READ_HDR, this);
	m_nLastInternalError = ERR_NO_ERROR;
	m_pszStart = NULL;
	// initialize socket connection
	m_Connection.Initialize(s);
	// initialize Common Log Format Record
	time(&m_LogRec.DateTime);
	m_LogRec.strClient.erase();
	m_LogRec.strLoginId = "-";
	m_LogRec.strUsername = "-";
	m_LogRec.strRequest.erase();
	m_LogRec.lStatus = 200;
	m_LogRec.lBytes = 0;
	if (m_Connection.ResolveName(m_pServer->m_bResolveClientname, m_LogRec.strClient) == FALSE)
	{
		m_LogRec.strClient = "0.0.0.0";
	}
	// post recv
	WMConnection::RCVSTATUS status;
	status = m_Connection.PostAsychRcv();
	LeaveCriticalSection(&m_CSWriteQueue);
	if (status != WMConnection::RCV_IO_PENDING)
	{
		return FALSE;
	}
	return TRUE;
}

void WMSOAPClientConn::Terminate()
{
	EnterCriticalSection(&m_CSWriteQueue);
	m_Connection.Shutdown();
	// empty write queue
	dequeWriteQueue::iterator iQ;
	for (iQ = m_WriteQueue.begin(); iQ != m_WriteQueue.end(); iQ++)
	{
		LPWSABUF pBuffer = *iQ;
		if (pBuffer != NULL)
		{
			if (pBuffer->buf != NULL)
			{
				free(pBuffer->buf);
			}
			free(pBuffer);
			*iQ = NULL;
		}
	}
	m_WriteQueue.clear();
	// free receive buffer
	if (m_RcvBuffer.buf != NULL)
	{
		free(m_RcvBuffer.buf);
		m_RcvBuffer.buf = NULL;
		m_RcvBuffer.len = 0;
	}
	// free request buffer 
	if (m_pszBuffer != NULL)
	{
		free(m_pszBuffer);
		m_pszBuffer = NULL;
	}
	// clear request header line list
	m_listRequestStrings.clear();
	// clear header storage
	m_mapHeaders.clear();
    // clear SOAPAction value
    m_strSOAPAction.erase();
	// clear any DIME attachments
	m_DIMEParser.Clear();
	// make available for another connection
	m_pServer->ReQueueClient(m_uListenerIndex);
    // done
	LeaveCriticalSection(&m_CSWriteQueue);
}

// event processing
//
void WMSOAPClientConn::OnIOCompletion(DWORD dwNumBytes)
{
	EnterCriticalSection(&m_CSWriteQueue);
	// update byte count
	m_Connection.m_dwNumRecvBytes = dwNumBytes;
	// change state
	m_StateMachine.Transition(EV_SOCK_IO_COMPLETION);
	LeaveCriticalSection(&m_CSWriteQueue);
}

BOOL WMSOAPClientConn::ReadSocket()
{
	WMConnection::RCVSTATUS RcvStatus;
	RcvStatus = m_Connection.RcvBlock(&m_RcvBuffer);

	// see what happened
	if (RcvStatus == WMConnection::RCV_SUCCESS)
	{
		if (m_pszBuffer == NULL)
		{
			m_pszBuffer = (char *)malloc(m_RcvBuffer.len + 1);
		}
		else
		{
			m_pszBuffer = (char *)realloc(m_pszBuffer, m_dwIndex + m_RcvBuffer.len + 1);
		}
		if (m_pszBuffer == NULL)
		{
			return FALSE;
		}
		if (m_RcvBuffer.buf != NULL)
		{
			memcpy(&m_pszBuffer[m_dwIndex], m_RcvBuffer.buf, m_RcvBuffer.len);
			free(m_RcvBuffer.buf);
			m_RcvBuffer.buf = NULL;
		}
		// update index
		m_dwIndex += m_RcvBuffer.len;
		m_pszBuffer[m_dwIndex] = 0;
		return TRUE;
	}
	return FALSE;
}

BOOL WMSOAPClientConn::ReadSocketDIME()
{
	WMConnection::RCVSTATUS RcvStatus;
	RcvStatus = m_Connection.RcvBlock(&m_RcvBuffer);
	// see what happened
	if (RcvStatus == WMConnection::RCV_SUCCESS)
	{
		if (m_RcvBuffer.buf != NULL)
		{
			try
			{
				m_DIMEParser.Deserialize(m_RcvBuffer.buf, m_RcvBuffer.len);
				free(m_RcvBuffer.buf);
				m_RcvBuffer.buf = NULL;
			}
			catch (DIMEParserException *e)
			{
				delete e;
				return FALSE;
			}
		}
		// update index
		m_dwIndex += m_RcvBuffer.len;
		return TRUE;
	}
	return FALSE;
}

int WMSOAPClientConn::InitializeStateMachine()
{
	///////////////////////////
	// register transitions  //
	///////////////////////////

	// transitions from read header state
	m_StateMachine.RegisterTransition(ST_READ_HDR, ST_READ_HDR, EV_SOCK_IO_COMPLETION, TR_READ_HDR_TO_READ_HDR);
	m_StateMachine.RegisterTransition(ST_READ_HDR, ST_PROCESS_SIMPLE, EV_SIMPLE_REQUEST, TR_READ_HDR_TO_PROC_SIMPLE);
	m_StateMachine.RegisterTransition(ST_READ_HDR, ST_PROCESS_FULL, EV_FULL_REQUEST, TR_READ_HDR_TO_PROC_FULL);
	m_StateMachine.RegisterTransition(ST_READ_HDR, ST_PROTO_ERROR, EV_REQUEST_ERROR, TR_READ_HDR_TO_REQ_ERROR);

	// request processing transitions
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROCESS_FULL, EV_PROCESS_REQUEST, TR_PROC_FULL_TO_PROC_FULL);

	// protocol error transitions
	m_StateMachine.RegisterTransition(ST_PROCESS_SIMPLE, ST_PROTO_ERROR, EV_REQUEST_ERROR, TR_PROC_SIMPLE_TO_REQ_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_SIMPLE, ST_PROTO_ERROR, EV_HTTP_VERSION_ERROR, TR_PROC_SIMPLE_TO_HTTP_VER_ERROR);

	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_REQUEST_ERROR, TR_PROC_FULL_TO_REQ_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_METHOD_ERROR, TR_PROC_FULL_TO_METHOD_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_NOT_FOUND_ERROR, TR_PROC_FULL_TO_NOTFOUND_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_NOT_AVAILABLE_ERROR, TR_PROC_FULL_TO_NOTAVAILABLE_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_HTTP_VERSION_ERROR, TR_PROC_FULL_TO_HTTP_VER_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_PROTO_ERROR, EV_FORBIDDEN_ERROR, TR_PROC_FULL_TO_FORBIDDEN_ERROR);

	// network error transitions
	m_StateMachine.RegisterTransition(ST_READ_HDR, ST_NETWORK_ERROR, EV_SOCK_CLOSE, TR_READ_HDR_TO_NET_ERROR);
	m_StateMachine.RegisterTransition(ST_READ_BODY, ST_NETWORK_ERROR, EV_SOCK_CLOSE, TR_READ_BODY_TO_NET_ERROR);
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_NETWORK_ERROR, EV_SOCK_CLOSE, TR_PROC_FULL_TO_NET_ERROR);
	m_StateMachine.RegisterTransition(ST_SENDING, ST_NETWORK_ERROR, EV_SOCK_CLOSE, TR_SENDING_TO_NET_ERROR);

	// internal error transitions
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_INTERNAL_ERROR, EV_PROCESSING_ERROR, TR_PROC_FULL_TO_INTERNAL_ERROR);

	// transitions to sending state
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_SENDING, EV_PROCESSING_DONE, TR_PROC_FULL_TO_SENDING);
	m_StateMachine.RegisterTransition(ST_SENDING, ST_SENDING, EV_SOCK_IO_COMPLETION, TR_SENDING_TO_SENDING);
	m_StateMachine.RegisterTransition(ST_PROTO_ERROR, ST_SENDING, EV_ERROR_HANDLED, TR_PROTO_ERROR_TO_SENDING);
	m_StateMachine.RegisterTransition(ST_INTERNAL_ERROR, ST_SENDING, EV_ERROR_HANDLED, TR_INTERNAL_ERROR_TO_SENDING);

	// entity body read transitions
	m_StateMachine.RegisterTransition(ST_PROCESS_FULL, ST_READ_BODY, EV_ENTITYBODY_PENDING, TR_PROC_FULL_TO_READ_BODY);
	m_StateMachine.RegisterTransition(ST_READ_BODY, ST_READ_BODY, EV_SOCK_IO_COMPLETION, TR_READ_BODY_TO_READ_BODY);
	m_StateMachine.RegisterTransition(ST_READ_BODY, ST_PROCESS_FULL, EV_BODY_COMPLETE, TR_READ_BODY_TO_PROC_FULL);

	// normal transition to terminal state
	m_StateMachine.RegisterTransition(ST_SENDING, ST_DATA_SENT, EV_DATA_SENT, TR_SENDING_TO_DATA_SENT);
	m_StateMachine.RegisterTransition(ST_DATA_SENT, ST_TERMINAL, EV_TERMINATE, TR_DATA_SENT_TO_TERM);

	// abnormal transition to terminal state
	m_StateMachine.RegisterTransition(ST_NETWORK_ERROR, ST_TERMINAL, EV_ERROR_HANDLED, TR_NET_ERROR_TO_TERM);

	///////////////////////////
	// load function array   //
	///////////////////////////

	// transitions from read header state
	m_ActionFunctions[TR_READ_HDR_TO_READ_HDR] = (ActFuncPtr)&WMSOAPClientConn::ACT_HdrRead;
	m_ActionFunctions[TR_READ_HDR_TO_PROC_SIMPLE] = (ActFuncPtr)&WMSOAPClientConn::ACT_SimpleRequest;
	m_ActionFunctions[TR_READ_HDR_TO_PROC_FULL] = (ActFuncPtr)&WMSOAPClientConn::ACT_FullRequest;
	m_ActionFunctions[TR_READ_HDR_TO_REQ_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_RequestError;

	// request processing transitions
	m_ActionFunctions[TR_PROC_FULL_TO_PROC_FULL] = (ActFuncPtr)&WMSOAPClientConn::ACT_ProcessRequest;

	// network error transitions
	m_ActionFunctions[TR_READ_HDR_TO_NET_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NetworkError;
	m_ActionFunctions[TR_READ_BODY_TO_NET_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NetworkError;
	m_ActionFunctions[TR_PROC_FULL_TO_NET_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NetworkError;
	m_ActionFunctions[TR_SENDING_TO_NET_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NetworkError;

	// protocol error transitions
	m_ActionFunctions[TR_PROC_SIMPLE_TO_REQ_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_RequestError;
	m_ActionFunctions[TR_PROC_SIMPLE_TO_HTTP_VER_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_HTTPVersionError;

	m_ActionFunctions[TR_PROC_FULL_TO_REQ_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_RequestError;
	m_ActionFunctions[TR_PROC_FULL_TO_METHOD_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_MethodError;
	m_ActionFunctions[TR_PROC_FULL_TO_NOTFOUND_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NotFoundError;
	m_ActionFunctions[TR_PROC_FULL_TO_NOTAVAILABLE_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_NotAvailableError;
	m_ActionFunctions[TR_PROC_FULL_TO_HTTP_VER_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_HTTPVersionError;
	m_ActionFunctions[TR_PROC_FULL_TO_FORBIDDEN_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_ForbiddenError;

	// internal error transitions
	m_ActionFunctions[TR_PROC_FULL_TO_INTERNAL_ERROR] = (ActFuncPtr)&WMSOAPClientConn::ACT_InternalError;

	// transitions to sending state
	m_ActionFunctions[TR_PROC_FULL_TO_SENDING] = (ActFuncPtr)&WMSOAPClientConn::ACT_SendData;
	m_ActionFunctions[TR_PROTO_ERROR_TO_SENDING] = (ActFuncPtr)&WMSOAPClientConn::ACT_SendData;
	m_ActionFunctions[TR_INTERNAL_ERROR_TO_SENDING] = (ActFuncPtr)&WMSOAPClientConn::ACT_SendData;

	// entity body read transitions
	m_ActionFunctions[TR_PROC_FULL_TO_READ_BODY] = (ActFuncPtr)&WMSOAPClientConn::ACT_ReadEntityBody;
	m_ActionFunctions[TR_READ_BODY_TO_READ_BODY] = (ActFuncPtr)&WMSOAPClientConn::ACT_BodyRead;
	m_ActionFunctions[TR_READ_BODY_TO_PROC_FULL] = (ActFuncPtr)&WMSOAPClientConn::ACT_ProcessRequest;

	// data transmission
	m_ActionFunctions[TR_SENDING_TO_SENDING] = (ActFuncPtr)&WMSOAPClientConn::ACT_SendData;
	m_ActionFunctions[TR_SENDING_TO_DATA_SENT] = (ActFuncPtr)&WMSOAPClientConn::ACT_DataSent;

	// normal transition to terminal state
	m_ActionFunctions[TR_DATA_SENT_TO_TERM] = (ActFuncPtr)&WMSOAPClientConn::ACT_Terminated;

	// abnormal transition to terminal state
	m_ActionFunctions[TR_NET_ERROR_TO_TERM] = (ActFuncPtr)&WMSOAPClientConn::ACT_Terminated;

	return EV_NULL_EVENT;
}

// state machine actions
//
int WMSOAPClientConn::ACT_HdrRead()
{
	if (ReadSocket() == FALSE)
	{
		return EV_SOCK_CLOSE;
	}
	// check for EOL (CRLF)
	char *p = m_pszBuffer;
	for (DWORD i = 0; i < m_dwIndex; i++)
	{
		// look for the '\n' (LF)
		if (p[i] == '\n')
			break;
	}
	if (i == m_dwIndex)
	{
		// no EOL here...post another recv
		WMConnection::RCVSTATUS status;
		status = m_Connection.PostAsychRcv();
		if (status != WMConnection::RCV_IO_PENDING && status != WMConnection::RCV_SUCCESS)
		{
			return EV_SOCK_CLOSE;
		}
		return EV_NULL_EVENT;
	}
	// ok, EOL so break up buffer into line(s)
	m_pszBuffer[m_dwIndex] = '\0';
	char *pBOL = m_pszBuffer;
	unsigned long ulIndex = 0;
	for (char *pEOL = strchr(pBOL, '\r');
			pEOL;
			pEOL = strchr(pBOL, '\r'))
	{
		// make into null terminated string
		*pEOL = '\0';
		// add to string list
		string strLine(pBOL);
		m_listRequestStrings.push_back(strLine);
		// skip '\0'
		pEOL++;
		// skip '\n'
		pEOL++;
		// update index
		ulIndex += strLine.length() + 2;
		// point to next text
		pBOL = pEOL;
		// check for empty line signalling EOH
		// in a full request
		if (strLine.length() == 0)
		{
			break;
		}
	}
	// is there any left over?
	int nLen = m_dwIndex - ulIndex;
	if (nLen > 0)
	{
		// yes, so save it
		m_dwIndex = nLen;
		char *p = (char *)malloc(m_dwIndex + 1);
		memcpy(p, pBOL, m_dwIndex);
		free(m_pszBuffer);
		m_pszBuffer = p;
		m_pszBuffer[m_dwIndex] = 0;
	}
	else
	{
		// reset for next chunk from client
		m_dwIndex = 0;
		free(m_pszBuffer);
		m_pszBuffer = NULL;
	}
	// what HTTP protocol version?
	int nReturnValue;
	if (m_bHTTPVersionFound == FALSE)
	{
		nReturnValue = ParseRequestLine();
		if (nReturnValue != EV_FULL_REQUEST)
		{
			// simple request or ill-formed header
			return nReturnValue;
		}
	}
	// this is a full request so
	// look for the null line indicating EOH
	string strLastLine = m_listRequestStrings.back();
	if (strLastLine.length() == 0)
	{
		// get rid of null line
		m_listRequestStrings.pop_back();
		// deal with any continued lines
		for (list<string>::iterator iL = m_listRequestStrings.begin();
				iL != m_listRequestStrings.end();
				iL++)
		{
			// get current string
			string strCurrent = *iL;
			if (strCurrent[0] == ' ' || strCurrent[0] == '\t')
			{
				// folded line
				if (iL != m_listRequestStrings.begin())
				{
					// store position of current string
					list<string>::iterator iCurrent = iL;
					// get previous string position in pos
					iL--;
					// append current string
					*iL += strCurrent;
					// delete current string
					m_listRequestStrings.erase(iCurrent);
				}
			}
		}
		nReturnValue = EV_FULL_REQUEST;
	}
	else
	{
		// wait for the rest of the header
		WMConnection::RCVSTATUS status;
		status = m_Connection.PostAsychRcv();
		if (status != WMConnection::RCV_IO_PENDING && status != WMConnection::RCV_SUCCESS)
		{
			return EV_SOCK_CLOSE;
		}
		nReturnValue = EV_NULL_EVENT;
	}
	return nReturnValue;
}

int WMSOAPClientConn::ACT_BodyRead()
{
	switch (m_ContentType)
	{
		case Content_Type_Application_DIME:
			{
				if (ReadSocketDIME() == FALSE)
				{
					return EV_SOCK_CLOSE;
				}
			}
			break;
		default:
			{
				if (ReadSocket() == FALSE)
				{
					return EV_SOCK_CLOSE;
				}
			}
	}
	// check for end of body
	if (m_dwIndex < m_dwEntityBodyLength)
	{
		// wait for the rest of the body
		WMConnection::RCVSTATUS status;
		status = m_Connection.PostAsychRcv();
		if (status != WMConnection::RCV_IO_PENDING && status != WMConnection::RCV_SUCCESS)
		{
			return EV_SOCK_CLOSE;
		}
		return EV_NULL_EVENT;
	}
	// done
	return EV_BODY_COMPLETE;
}

int WMSOAPClientConn::ACT_RequestError()
{
	// RFC 1945 9.4
	// Client Error 4xx - "400 Bad Request"
	SendCannedMsg(400);
	// inform server
	m_pServer->IncNumHTTPErrors();
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_MethodError()
{
	// RFC 1945 9.5
	// Server Error 4xx - "405 Method Not Allowed"
	SendCannedMsg(405, m_strMethod.c_str());
	// inform server
	m_pServer->IncNumHTTPErrors();
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_NotFoundError()
{
	// RFC 1945 9.4
	// Client Error 4xx - "404 Not Found"
	SendCannedMsg(404, m_strURIRequested.c_str());
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_NotAvailableError()
{
	// RFC 1945 9.5
	// Server Error 5xx - "503 Service Unavailable"
	SendCannedMsg(503);
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_HTTPVersionError()
{
	// RFC 2068 10.5.6
	// Server Error 5xx - "505 HTTP Version Not Supported"
	SendCannedMsg(505, m_strHTTPVersion.c_str());
	// inform server
	m_pServer->IncNumHTTPErrors();
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_ForbiddenError()
{
	// RFC 1945 9.4
	// Client Error 4xx - "403 Forbidden"
	SendCannedMsg(403);
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_InternalError()
{
	_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Internal Error: %d!"), m_nLastInternalError);
	// RFC 1945 9.5
	// Server Error 5xx - "500 Internal Server Error"
	SendCannedMsg(500);
	// inform server
	m_pServer->IncNumInternalErrors();
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_NetworkError()
{
	_AtlModule.LogEvent(/*EVENTLOG_ERROR_TYPE,*/ _T("Unexpected Socket Closure!"));
	// inform server
	m_pServer->IncNumNetworkErrors();
	return EV_ERROR_HANDLED;
}

int WMSOAPClientConn::ACT_SimpleRequest()
{
	// HTTP Version 0.9 not supported
	// send 505 HTTP Version Not Supported status...
	// RFC 2068 10.5.6
	return EV_HTTP_VERSION_ERROR;
}

int WMSOAPClientConn::ACT_FullRequest()
{
	// check to see if HTTP Version is supported
	if (m_nHTTPMajorVersion > 1)
	{
		// send 505 HTTP Version Not Supported status...
		// RFC 2068 10.5.6
		return EV_HTTP_VERSION_ERROR;
	}
	return ParseHeader();
}

int WMSOAPClientConn::ACT_ReadEntityBody()
{
	return EV_NULL_EVENT;
}

int WMSOAPClientConn::ACT_ProcessRequest()
{
	switch (m_nMethod)
	{
		case METHOD_GET:
			return Process_GET();
			break;
		case METHOD_HEAD:
			return Process_HEAD();
			break;
		case METHOD_POST:
			return Process_POST();
			break;
		default:
			return EV_METHOD_ERROR;
	}
	return EV_PROCESSING_DONE;
}

int WMSOAPClientConn::ACT_SendData()
{
	// get next block from queue
	dequeWriteQueue::iterator iQ = m_WriteQueue.begin();
	if (iQ == m_WriteQueue.end())
	{
		// done
		return EV_DATA_SENT;
	}
	else
	{
		WMConnection::SENDSTATUS SendStatus;
		SendStatus = m_Connection.SendBlock(*iQ);
		// see what happened
		switch (SendStatus)
		{
			case WMConnection::SEND_IO_PENDING:
			case WMConnection::SEND_SUCCESS:
				// done so remove from queue
				free((*iQ)->buf);
				free(*iQ);
				m_WriteQueue.pop_front();
				break;
			case WMConnection::SEND_WOULDBLOCK:
				// leave in queue
				break;
			default:
				return EV_SOCK_CLOSE;
		}
	}
	return EV_NULL_EVENT;
}

int WMSOAPClientConn::ACT_DataSent()
{
	WriteLog();
	return EV_TERMINATE;
}

int WMSOAPClientConn::ACT_Terminated()
{
	// all done so bow out
	Terminate();
	return EV_NULL_EVENT;
}

// helper structures
static struct WMSOAPClientConn::tagMethodTable MethodTable[] = {
	{WMSOAPClientConn::METHOD_GET   , "GET"},
	{WMSOAPClientConn::METHOD_POST  , "POST"},
	{WMSOAPClientConn::METHOD_HEAD  , "HEAD"},
	//{WMSOAPClientConn::METHOD_PUT   , "PUT"},
	//{WMSOAPClientConn::METHOD_DELETE, "DELETE"},
	//{WMSOAPClientConn::METHOD_LINK  , "LINK"},
	//{WMSOAPClientConn::METHOD_UNLINK, "UNLINK"},
};
static const int MethodTableLen = sizeof(MethodTable)/
						  sizeof(struct WMSOAPClientConn::tagMethodTable) ;
// helper functions

int WMSOAPClientConn::ParseRequestLine()
{
	int eventReturn;
	list<string> TokenList;

	if (m_listRequestStrings.empty())
	{
		return EV_REQUEST_ERROR;
	}
	string strRequest = m_listRequestStrings.front();
	// update log record
	m_LogRec.strRequest = strRequest;

	// look for HTTP Version token
	string::size_type i;
	if ((i = strRequest.find("HTTP/")) == string::npos)
	{
		// HTTP Version 0.9
		m_nHTTPMajorVersion = 0;
		m_nHTTPMinorVersion = 9;
		m_strHTTPVersion = "HTTP/0.9";
		eventReturn = EV_SIMPLE_REQUEST;
	}
	else
	{
		char *p;
		char szBuffer[128];
		char *pszSeparator =  ".";
		strncpy(szBuffer, strRequest.substr(i + 5).c_str(), 16);
        szBuffer[16] = 0;
		// get major version number
		p = GetToken(szBuffer, pszSeparator);
		if (p == NULL)
		{
			return EV_REQUEST_ERROR;		
		}
		m_nHTTPMajorVersion = atoi(p);
		// get minor version number
		p = GetToken(NULL, pszSeparator);
		if (p == NULL)
		{
			return EV_REQUEST_ERROR;		
		}
		m_nHTTPMinorVersion = atoi(p);
		// store in string form
		_snprintf(szBuffer, 16, "HTTP/%d.%d", m_nHTTPMajorVersion, m_nHTTPMinorVersion);
        {
            szBuffer[16] = 0;
        }
		m_strHTTPVersion = szBuffer;
		eventReturn = EV_FULL_REQUEST;
	}
	// set flag
	m_bHTTPVersionFound = TRUE;
	// parse the request line into a list of tokens
	LPSTR pszBuffer = (char *)malloc(strRequest.length() + 1);
    strncpy(pszBuffer, strRequest.c_str(), strRequest.length());
    pszBuffer[strRequest.length()] = 0;
	char *pBOL = pszBuffer;
	for (char *pEOL = strchr(pBOL, ' ');
		  pEOL;
		  pEOL = strchr(pBOL, ' '))
	{
		*pEOL = '\0';
		string strTempToken(pBOL, strlen(pBOL));
		*pEOL++;	// skip '\0'
		pBOL = pEOL;
		TokenList.push_back(strTempToken);
	}
	// save whatever's left as the last token
	string strTempToken(pBOL, strlen(pBOL));
	TokenList.push_back(strTempToken);
	free(pszBuffer);
	// 1) parse the method
	list<string>::iterator iL = TokenList.begin();
	if (iL == TokenList.end())
	{
		return EV_REQUEST_ERROR;		
	}
	m_strMethod = *iL;
	// see if method is in table
	for (i = 0; i < MethodTableLen; i++)
	{
		if (m_strMethod.compare(MethodTable[i].pszKey) == 0)
		{
			// found it
			m_nMethod = MethodTable[i].nID;
			break;
		}
	}
	// 2) parse the URI
	iL++;
	if (iL == TokenList.end())
	{
		return EV_REQUEST_ERROR;		
	}
	m_strURIRequested = *iL;
	return eventReturn;
}

bool WMSOAPClientConn::ParseURI()
{
	// capture any query string (RFC1945 3.2.1)
	string::size_type i;
	if ((i = m_strURIRequested.find('?')) != string::npos)
	{
		m_strQueryString = m_strURIRequested.substr(i + 1);
		m_strURIRequested = m_strURIRequested.substr(0, i);
	}
	else
	{
		m_strQueryString.erase();
	}
	// now decode URL
	char szBuffer[2048];
	int len = URLDecodeString(m_strURIRequested, szBuffer, 0, 2000);
	szBuffer[len] = 0;
	m_strURIRequested = szBuffer;
	m_strLocalFilename = szBuffer;
	return true;
}

int WMSOAPClientConn::ParseHeader()
{
	// skip request line
	m_listRequestStrings.pop_front();
	// walk list of header lines
	for (list<string>::iterator iL = m_listRequestStrings.begin();
			iL != m_listRequestStrings.end();
			iL++)
	{
		string strTemp = *iL;
		string::size_type i;
		if ((i = strTemp.find(": ")) == string::npos)
		{
			// ill formed
			continue;
		}
		// store in header map
		string strField = strTemp.substr(0, i);
		for (string::size_type pos = 0; pos < strField.length(); pos++)
		{
			strField[pos] = toupper(strField[pos]);
		}
		// check for multiple instance header (RFC1945 4.2)
		if (m_mapHeaders.find(strField) != m_mapHeaders.end())
		{
			// append value to comma delimited string
			m_mapHeaders[strField] += ",";
			m_mapHeaders[strField] += strTemp.substr(i + 2);
		}
		else
		{
			m_mapHeaders.insert(
				map<string, string>::value_type(strField, strTemp.substr(i + 2)));
		}
	}
	// Content-Length header?
	map<string, string>::iterator iM;
	if ((iM = m_mapHeaders.find(string("CONTENT-LENGTH"))) != m_mapHeaders.end())
	{
		m_dwEntityBodyLength = (DWORD)atoi((*iM).second.c_str());
	}
    // SOAPAction header?
	if ((iM = m_mapHeaders.find(string("SOAPACTION"))) != m_mapHeaders.end())
    {
        m_strSOAPAction = (*iM).second;
    }
	if (m_dwEntityBodyLength > 0)
	{
		// then there ought to be a Content-Type header
		if ((iM = m_mapHeaders.find(string("CONTENT-TYPE"))) == m_mapHeaders.end())
		{
			// not found, send 400 msg
			return EV_REQUEST_ERROR;
		}
		// parse any parameters
		string strContentType;
		string strTemp(iM->second);
		map<string, string> mapParams;
		if (ParseContentType(strTemp, strContentType, mapParams) == false)
		{
			// send 400 msg
			return EV_REQUEST_ERROR;
		}
		// determine content-type
		m_ContentType = Content_Type_Empty;
		if (strContentType.find("TEXT/XML") != string::npos)	
		{
			m_ContentType = Content_Type_Text_XML;
		}
		else if (strContentType.find("APPLICATION/SOAP+XML") != string::npos)	
		{
			m_ContentType = Content_Type_Application_SOAP_XML;
            // action parameter present?
		    if ((iM = mapParams.find(string("ACTION"))) != mapParams.end())
		    {
			    // yes, get value
			    m_strSOAPAction = (*iM).second;
		    }
        }
		else if (strContentType.find("APPLICATION/DIME") != string::npos)	
		{
			m_ContentType = Content_Type_Application_DIME;
			// initialize DIME parser
			try
			{
				m_DIMEParser.DeserializeInit();
			}
			catch (DIMEParserException *e)
			{
				delete e;
				return ST_INTERNAL_ERROR;
			}
			// is there entity body data buffered?
			if (m_dwIndex > 0 && m_pszBuffer != NULL)
			{
				// feed data to dime parser
				try
				{
					m_DIMEParser.Deserialize(m_pszBuffer, m_dwIndex);
				}
				catch (DIMEParserException *e)
				{
					delete e;
					return EV_REQUEST_ERROR;
				}
				free(m_pszBuffer);
				m_pszBuffer = NULL;
			}
		}
		if (m_ContentType == Content_Type_Empty)
		{
			// MIME type not found/not supported, send 400 msg
			return EV_REQUEST_ERROR;
		}
		// is charset param found?
		m_strCharsetParam.erase();
		if ((iM = mapParams.find(string("CHARSET"))) != mapParams.end())
		{
			// yes, get value
			m_strCharsetParam = (*iM).second;
			// force to uppercase
			for (string::size_type i = 0; i < m_strCharsetParam.length(); i++)
			{
				// force to uppercase... comparisons are case-insensitive
				m_strCharsetParam[i] = toupper(m_strCharsetParam[i]);
			}
			if (m_ContentType == Content_Type_Text_XML || m_ContentType == Content_Type_Application_SOAP_XML)
			{
				// is this a supported encoding?
				if (!(  m_strCharsetParam == "UTF-8" ||
						m_strCharsetParam == "UTF-16" ||
						m_strCharsetParam == "US-ASCII" ||
						m_strCharsetParam == ""))
				{
					// no, send 400 msg
					return EV_REQUEST_ERROR;
				}
			}
		}
		// must be an entity body present
		if (m_dwEntityBodyLength > m_dwIndex)
		{
			// more to come so keep reading
			WMConnection::RCVSTATUS status;
			status = m_Connection.PostAsychRcv();
			if (status != WMConnection::RCV_IO_PENDING && status != WMConnection::RCV_SUCCESS)
			{
				return EV_SOCK_CLOSE;
			}
			return EV_ENTITYBODY_PENDING;
		}
	}
	// done
	return EV_PROCESS_REQUEST;
}

int WMSOAPClientConn::WriteLog()
{
	// write log records
	m_pServer->m_LogMgr.WriteServerLog(m_LogRec);
	m_pServer->m_LogMgr.WriteUserAgentLog(m_mapHeaders[string("USER-AGENT")]);
	// update bytes sent counter
	m_pServer->UpdateNumBytesSent(m_LogRec.lBytes);
	return TRUE;
}

int WMSOAPClientConn::URLDecodeString(string& str,
						   LPSTR pszBuffer,
						   int nStart, int nStop)
{
	int nLength = str.length();
    string strAllowed("0123456789ABCDEF");
	// walk the query string
	for (int i = 0, j = nStart;
			i < nLength && j < nStop;
			i++)
	{
		char c = str[i];
		// check for space encoded as '+'
		if (c == '+')
		{
			// decode space
			pszBuffer[j++] = ' ';
		}
		// check for char escaped as "%XX" triplet
		else if (c == '%')
		{
			// unescape character
			char d = str[i + 1];
            if (d == '%')
            {
                pszBuffer[j++] = d;
                ++i;
                continue;
            }
            if (strAllowed.find((char)toupper(d)) != string::npos)
            {
			    c = (d >= 'A' ? ((d & 0xdf) - 'A') + 10 : (d - '0'));
			    c *= 16;
			    d = str[i + 2];
                if (strAllowed.find((char)toupper(d)) != string::npos)
                {
			        c += (d >= 'A' ? ((d & 0xdf) - 'A') + 10 : (d - '0'));
			        pszBuffer[j++] = c;
                }
            }
			i += 2;
		}
		else
		{
			pszBuffer[j++] = c;
		}
	}
	return j;
}

IDispatch *WMSOAPClientConn::GetWSDLClient(SOAPSvcDesc *pSvc)
{
	// is the WSDL client we need already in the cache?
	IDispatch *pIWSDLParser = m_pServer->FindWSDLCacheEntry(m_strSvcURI);
	if (pIWSDLParser == NULL)
	{
		// instantiate WSDL client object
		Iwmwsdlclient2 *pIwmwsdlclient;
		HRESULT res = CoCreateInstance(CLSID_wmwsdlclient2,
										NULL, CLSCTX_SERVER,
										IID_Iwmwsdlclient2,
										(void **) &pIwmwsdlclient);
		if (FAILED(res) || pSvc == NULL)
		{
			return NULL;
		}
		BSTR bstrSvcName = SysAllocString(pSvc->m_wstrWsdlSvc.c_str());
		BSTR bstrPortName = SysAllocString(pSvc->m_wstrWsdlPort.c_str());
    	wstring wstrWSDLDocURL;
        UTF8ToWideString(pSvc->m_strWsdlDocURL, wstrWSDLDocURL);
        BSTR bstrWSDLFileURL = SysAllocString(wstrWSDLDocURL.c_str());
		VARIANT_BOOL bResult = VARIANT_FALSE;
		// try to load and parse WSDL file
		pIwmwsdlclient->ReadServiceDesc(bstrSvcName,
										bstrPortName,
										bstrWSDLFileURL,
										&bResult);
		SysFreeString(bstrSvcName);
		SysFreeString(bstrPortName);
		SysFreeString(bstrWSDLFileURL);
		// success?
		if (bResult == VARIANT_FALSE)
		{
			pIwmwsdlclient->Release();
			// send 500 msg
			return NULL;
		}
		// get WSDL client IDispatch ptr to pass in to processor
		res = pIwmwsdlclient->QueryInterface(IID_IDispatch, (void **)&pIWSDLParser);
		// done with WSDL client interface ptr
		pIwmwsdlclient->Release();
		if (FAILED(res))
		{
			// send 500 msg
			return NULL;
		}
		// put the IDispatch ptr in the cache
		if (m_pServer->AddWSDLCacheEntry(m_strSvcURI, pIWSDLParser) == false)
		{
			// trouble... not put in map
			pIWSDLParser->Release();
			// send 500 msg
			return NULL;
		}
	}
    return pIWSDLParser;
}

SOAPSvcDesc *WMSOAPClientConn::FindServiceMapEntry()
{
    // clear out extra path
    m_strExtraPath.erase();
    // can we find request URI in services map?
	map<string, SOAPSvcDesc *>::iterator iM;
    if ((iM = m_pServer->m_mapSOAPSvcDescriptions.find(m_strURIRequested)) !=
            m_pServer->m_mapSOAPSvcDescriptions.end())
    {
        // store service URI
        m_strSvcURI = (*iM).first;
        // is it enabled?
        SOAPSvcDesc *pSvc = (*iM).second;
        if (pSvc->m_bEnabled == false)
        {
            // no, it is treated as nonexistent
            return NULL;
        }
        // done
        return pSvc;
    }
    // no, seek partial match
    string strTemp(m_strURIRequested);
    for (   string::size_type pos = strTemp.rfind("/");
            pos != string::npos;
            pos = strTemp.rfind("/"))
    {
        strTemp = strTemp.substr(0, pos);
        if ((iM = m_pServer->m_mapSOAPSvcDescriptions.find(strTemp)) !=
                m_pServer->m_mapSOAPSvcDescriptions.end())
        {
            // store service URI
            m_strSvcURI = (*iM).first;
            // is it enabled? if so is extra path allowed?
            SOAPSvcDesc *pSvc = (*iM).second;
            if (pSvc->m_bEnabled == false || pSvc->m_bExtraPathEnabled == false)
            {
                // no, it is treated as nonexistent
                return NULL;
            }
            m_strExtraPath = m_strURIRequested.substr(m_strSvcURI.length());
            // done
            return pSvc;
        }
    }
    return NULL;
}

int WMSOAPClientConn::Process_GET()
{
	ParseURI();
    // see if this is directed at a service
    SOAPSvcDesc *pSvc = FindServiceMapEntry();
    if (pSvc != NULL)
    {
        if (DoSOAPGet(pSvc) != EV_PROCESSING_DONE)
        {
            // probably a case of GET not supported by service
            return SendSOAPEndpointGETResponse();
        }
        return EV_PROCESSING_DONE;
    }
    return SendFile();
}

int WMSOAPClientConn::Process_HEAD()
{
	ParseURI();
    // see if this is directed at a service
    SOAPSvcDesc *pSvc = FindServiceMapEntry();
    if (pSvc != NULL)
    {
        // HEAD not allowed on services
        return EV_FORBIDDEN_ERROR;
    }
    return SendFile(TRUE);
}

int WMSOAPClientConn::Process_POST()
{
	// check for valid Content-Length
	if (m_dwEntityBodyLength == 0)
	{
		// RFC 1945 8.3 POST...return "400 Bad Request"
		// if there is no valid Content-Length given
		return EV_REQUEST_ERROR;
	}
	ParseURI();
    SOAPSvcDesc *pSvc = FindServiceMapEntry();
    if (pSvc != NULL)
    {
        return DoSOAPPost(pSvc);
    }
	return EV_NOT_FOUND_ERROR;
}

int WMSOAPClientConn::DoSOAPPost(SOAPSvcDesc *pSvc)
{
    // get WSDL client object IDispatch ptr
    IDispatch *pIWSDLParser = GetWSDLClient(pSvc);
    if (pIWSDLParser == NULL)
    {
		// send 500 Internal Server Error
		return EV_PROCESSING_ERROR;
	}
	// output buffer
	char *pszOutBuffer = NULL;
	unsigned long ulOutBufferSize = 0;
	// log buffer
	string strLogEntry;
	// HTTP status
	int nHTTPStatus = 0;
	// create processor
   	WMSOAPPostProcessor sp;
	switch (m_ContentType)
	{
		case Content_Type_Text_XML:
			{
                sp.SetSOAPVersion(SOAP_VERSION_1_1);
                m_nSOAPVersion = SOAP_VERSION_1_1;
				// SOAP 1.1 - SOAPAction header required
	            map<string, string>::iterator iM;
	            if ((iM = m_mapHeaders.find(string("SOAPACTION"))) == m_mapHeaders.end())
				{
					// not found, send 500 msg
					return EV_PROCESSING_ERROR;
				}
                m_strSOAPAction = (*iM).second;
			}
            // fall through to process request
        case Content_Type_Application_SOAP_XML:
			{
				// run processor
                pIWSDLParser->AddRef();
				nHTTPStatus = sp.Process(
								m_pszBuffer,
								m_dwEntityBodyLength,
					            m_strSvcURI,
					            m_strExtraPath,
								pIWSDLParser,
                                *pSvc,
								m_strSOAPAction,
								&pszOutBuffer,
								ulOutBufferSize,
								strLogEntry,
								m_pServer->m_LogMgr.m_bSOAPTraceLogEnable == TRUE ? true : false,
								false,
								&m_DIMEParser);
                pIWSDLParser->Release();
			}
			break;
		case Content_Type_Application_DIME:
			{
                // DIME packaged request
                vector<DIMEPayload *>::iterator iV;
                iV = m_DIMEParser.GetPayloads().begin();
                DIMEPayload *pPayload = *iV;
                if (pPayload->GetType() == g_pszSoapEnvelopeNS)
                {
                    sp.SetSOAPVersion(SOAP_VERSION_1_1);
                    m_nSOAPVersion = SOAP_VERSION_1_1;
                }
                pIWSDLParser->AddRef();
				nHTTPStatus = sp.Process(
								m_pszBuffer,
								m_dwEntityBodyLength,
					            m_strSvcURI,
					            m_strExtraPath,
								pIWSDLParser,
                                *pSvc,
								m_strSOAPAction,
								&pszOutBuffer,
								ulOutBufferSize,
								strLogEntry,
								m_pServer->m_LogMgr.m_bSOAPTraceLogEnable == TRUE ? true : false,
								true,
								&m_DIMEParser);
                pIWSDLParser->Release();
			}
			break;
		default:
			{
				// unexpected media type... send 400 "Bad Request"
				return EV_REQUEST_ERROR;
			}
	}
	// trouble before processing SOAP envelope?
	if (nHTTPStatus == 0)
	{
		// send 500 Internal Server Error
		return EV_PROCESSING_ERROR;
	}
	// write to log
	if (m_pServer->m_LogMgr.m_bSOAPTraceLogEnable == TRUE)
	{
		m_pServer->m_LogMgr.WriteSOAPTraceLog(strLogEntry);
	}
	// 204 No Content?
	if (nHTTPStatus == 204)
	{
		// send 204 No Content response
		return (Send204Response() == TRUE ? EV_PROCESSING_DONE : EV_PROCESSING_ERROR);
	}
	// send response...may be SOAP:fault
	int nRtn;
	if (pszOutBuffer != NULL)
	{
		// response not DIME packaged
        if (m_ContentType == Content_Type_Application_DIME)
        {
            // however, the request was DIME packaged
            if (sp.GetSOAPVersion() == SOAP_VERSION_1_1)
            {
                // set content type to text/xml
                m_ContentType = Content_Type_Text_XML;
            }
            else
            {
                // set content type to application/soap+xml
                m_ContentType = Content_Type_Application_SOAP_XML;
            }
        }
		nRtn = SendSOAPOutput(pszOutBuffer, ulOutBufferSize, nHTTPStatus);
		free(pszOutBuffer);
	}
	else
	{
		// response is DIME packaged
        if (m_ContentType != Content_Type_Application_DIME)
        {
            // request was not DIME packaged
            m_ContentType = Content_Type_Application_DIME;
        }
		nRtn = SendDIMEOutput(nHTTPStatus);
	}
	return nRtn;
}

int WMSOAPClientConn::DoSOAPGet(SOAPSvcDesc *pSvc)
{
    // get WSDL client object IDispatch ptr
    IDispatch *pIWSDLParser = GetWSDLClient(pSvc);
    if (pIWSDLParser == NULL)
    {
		// send 500 Internal Server Error
		return EV_PROCESSING_ERROR;
	}
	// output buffer
	char *pszOutBuffer = NULL;
	unsigned long ulOutBufferSize = 0;
	// log buffer
	string strLogEntry;
	// HTTP status
	int nHTTPStatus = 0;
	// create processor
	WMSOAPGetProcessor sp;
	// run processor
    pIWSDLParser->AddRef();
	nHTTPStatus = sp.Process(
					m_strSvcURI,
					m_strExtraPath,
                    m_strQueryString,
					pIWSDLParser,
                    *pSvc,
					&pszOutBuffer,
					ulOutBufferSize,
					strLogEntry,
					m_pServer->m_LogMgr.m_bSOAPTraceLogEnable == TRUE ? true : false,
                    &m_DIMEParser);
    pIWSDLParser->Release();
	// big trouble?
	if (nHTTPStatus == 0)
	{
		// send 500 Internal Server Error
		return EV_PROCESSING_ERROR;
	}
	// write to log
	if (m_pServer->m_LogMgr.m_bSOAPTraceLogEnable == TRUE)
	{
		m_pServer->m_LogMgr.WriteSOAPTraceLog(strLogEntry);
	}
	// 204 No Content?
	if (nHTTPStatus == 204)
	{
		// send 204 No Content response
		return (Send204Response() == TRUE ? EV_PROCESSING_DONE : EV_PROCESSING_ERROR);
	}
	// send response...may be SOAP:fault
	int nRtn;
	if (pszOutBuffer != NULL)
	{
        // set content type to application/soap+xml
        m_ContentType = Content_Type_Application_SOAP_XML;
		nRtn = SendSOAPOutput(pszOutBuffer, ulOutBufferSize, nHTTPStatus);
		free(pszOutBuffer);
	}
	else
	{
        // set content type to application/dime
        m_ContentType = Content_Type_Application_DIME;
		nRtn = SendDIMEOutput(nHTTPStatus);
	}
	return nRtn;
}

BOOL WMSOAPClientConn::SendData(const char *pszData, size_t NumBytes)
{
	if (NumBytes < 1)
	{
		return TRUE;
	}
    // allocate buffer
	LPWSABUF p = (LPWSABUF)malloc(sizeof(WSABUF));
	if (p == NULL)
	{
		return FALSE;
	}
	p->buf = (char *)malloc(NumBytes);
	if (p->buf == NULL)
	{
		free(p);
		return FALSE;
	}
    // copy data
	memcpy(p->buf, pszData, NumBytes);
	p->len = (u_long)NumBytes;
    // append to write queue
	m_WriteQueue.push_back(p);
	// update log record
	m_LogRec.lBytes += NumBytes;
	return TRUE;
}

BOOL WMSOAPClientConn::SendData(HANDLE hFile)
{
	char szBuffer[FILE_BUF_SIZE];
	DWORD dwNumBytesRead;

 	for (;;)
	{
		BOOL bResult = ReadFile(hFile, szBuffer, FILE_BUF_SIZE, &dwNumBytesRead, NULL);
		if (bResult == TRUE && dwNumBytesRead > 0)
		{
			SendData(szBuffer, dwNumBytesRead);
		}
		else
		{
			return bResult;
		}
	}
	return TRUE;
}

int WMSOAPClientConn::SendFile(BOOL bHeaderOnly)
{
    // these not permitted
    if (m_strLocalFilename.find("..") != string::npos ||
        m_strLocalFilename.find('%') != string::npos)
    {
        return EV_NOT_FOUND_ERROR;
    }
	string strDocBasePath = m_pServer->m_strDocBasePath;
	string strDocDefault = m_pServer->m_strDocDefault;
	if (m_strLocalFilename == "/")
	{
		// empty request so use default file
		m_strLocalFilename = strDocBasePath + strDocDefault;
	}
	else if (m_strLocalFilename[0] != '/')
	{
		// prepend base path
		m_strLocalFilename = strDocBasePath + m_strLocalFilename;
	}
	else
	{
		// prepend base path...'/' at start of URI
		//  is redundant so don't use it
		m_strLocalFilename = strDocBasePath + m_strLocalFilename.substr(1);
	}
	// look up MIME type
	char szExtension[_MAX_EXT];
	_splitpath(m_strLocalFilename.c_str(), NULL, NULL, NULL, szExtension);
	// force to uppercase for lookup
	_strupr(szExtension);
	string strExt = szExtension;
	string strContentType;
	map<string, string>::iterator iM;
	iM = m_pServer->m_mapMIMETypes.find(strExt);
	if (iM != m_pServer->m_mapMIMETypes.end())
	{
		strContentType = (*iM).second;
	}
	else
	{
		strContentType = "application/octet-stream";
	}
	// try to open the file specified
	HANDLE hFile = CreateFile(	m_strLocalFilename.c_str(),
								GENERIC_READ,
								FILE_SHARE_READ,
								NULL,
								OPEN_EXISTING,
								FILE_FLAG_SEQUENTIAL_SCAN,
								NULL); 
	// any luck?
	if (hFile == INVALID_HANDLE_VALUE)
	{
		// send 404 msg
		return EV_NOT_FOUND_ERROR;
	}
	// get file info
	BY_HANDLE_FILE_INFORMATION  Info;
	if (GetFileInformationByHandle(hFile, &Info) == FALSE)
	{
		{
			// couldn't get file status info
			return EV_PROCESSING_ERROR;
		}
	}
	// check for conditional send (except if method is HEAD)
	string strIfModifiedSince = m_mapHeaders[string("IF-MODIFIED-SINCE")];
	if (bHeaderOnly == FALSE && strIfModifiedSince.length() > 0)
	{
		// get time last modified
		FILETIME IfModifiedTime;
		if ((StringToFileTime(strIfModifiedSince, &IfModifiedTime)) != FALSE)
		{
			// check against If-Modified-Since time
			if (CompareFileTime(&Info.ftLastWriteTime, &IfModifiedTime) == -1)
			{
				// not modified since so send 304 response
				if (Send304Response() == FALSE)
				{
					return EV_PROCESSING_ERROR;
				}
				return EV_PROCESSING_DONE;
			}
		}
	}
	// send header
	BOOL bReturn = SendReplyHeader(strContentType, hFile, Info);
	// send file
	if (bReturn == TRUE && bHeaderOnly == FALSE)
	{
		bReturn = SendData(hFile);
	}
	// close file
	CloseHandle(hFile);
	return bReturn == TRUE ? EV_PROCESSING_DONE : EV_PROCESSING_ERROR;
}

int WMSOAPClientConn::SendSOAPOutput(const char *pszOutput,
								 unsigned long ulLength,
								 int nHTTPStatus,
                                 BOOL bHeaderOnly)
{
	// get status for log record
	m_LogRec.lStatus = nHTTPStatus;

	BOOL bReturn = SendSOAPReplyHeader(nHTTPStatus, ulLength);
	if (bReturn == TRUE && bHeaderOnly == FALSE)
	{
		bReturn = SendData(pszOutput, ulLength);
	}
	return bReturn == TRUE ? EV_PROCESSING_DONE : EV_PROCESSING_ERROR;
}

int WMSOAPClientConn::SendDIMEOutput(int nHTTPStatus, BOOL bHeaderOnly)
{
	// get status for log record
	m_LogRec.lStatus = nHTTPStatus;

	unsigned long ulLength = m_DIMEParser.CalcSerialLength();
	BOOL bReturn = SendDIMEReplyHeader(nHTTPStatus, ulLength);
	if (bReturn == TRUE && bHeaderOnly == FALSE)
	{
		char szBuffer[FILE_BUF_SIZE];
		unsigned long ulBytes;
		try
		{
			for (bool bContinue = true; bContinue == true; )
			{
					bContinue = m_DIMEParser.Serialize(szBuffer, FILE_BUF_SIZE, &ulBytes);
					if (SendData(szBuffer, ulBytes) == false)
					{
						// trouble
						bReturn = FALSE;
                        break;
					}
					Sleep(0);
			}
		}
		catch (DIMEParserException *e)
		{
			delete e;
			bReturn = FALSE;
		}
	}
	return bReturn == TRUE ? EV_PROCESSING_DONE : EV_PROCESSING_ERROR;
}

// table of canned messages
static struct tagMsgTable
{
	int	id;
	int	idStr;
	char *pszReason;
} MsgTable[] =
{
	{304, IDS_304_MESSAGE, "Not Modified"},
	{400, IDS_400_MESSAGE, "Bad Request"},
	{401, IDS_401_MESSAGE, "Unauthorized"},
	{403, IDS_403_MESSAGE, "Forbidden"},
	{404, IDS_404_MESSAGE, "Not Found"},
	{405, IDS_405_MESSAGE, "Method Not Allowed"},
	{501, IDS_501_MESSAGE, "Not Implemented"},
	{503, IDS_503_MESSAGE, "Service Unavailable"},
	{505, IDS_505_MESSAGE, "HTTP Version Not Supported"}
};
static const int MsgTableSize = sizeof(MsgTable)
							  / sizeof(struct tagMsgTable);

BOOL WMSOAPClientConn::SendCannedMsg(int nError, ...)
{
	BOOL bFound = FALSE ;
	for (int i = 0; i < MsgTableSize; i++)
	{
		if (MsgTable[i].id == nError)
		{
			bFound = TRUE;
			break;
		}
	}
	char szHeader[256];
	char szMsg[512];
	char szEntityBody[1024];
	if (bFound == FALSE)  //error 500 or bad error id
	{
        if (m_nSOAPVersion = SOAP_VERSION_1_1)
        {
		    _snprintf(  szHeader,
                        255,
				        "%s 500 Internal Server Error\r\n",
					    g_pszHTTPVersion);
            szHeader[255] = 0;
	        if (m_nMethod != METHOD_HEAD)
            {
                strcat(szHeader, "Content Type: text/xml\r\n");
    		    LoadString(_AtlBaseModule.GetModuleInstance(), IDS_500_MESSAGE, szMsg, 512);
		        _snprintf(szEntityBody, 1023, szMsg, nError);
                szEntityBody[1023] = 0;
            }
        }
        else
        {
		    _snprintf(  szHeader,
                        255,
				        "%s 500 Internal Server Error\r\n",
					    g_pszHTTPVersion);
            szHeader[255] = 0;
	        if (m_nMethod != METHOD_HEAD)
            {
                strcat(szHeader, "Content Type: application/soap+xml\r\n");
		        LoadString(_AtlBaseModule.GetModuleInstance(), IDS_500_MESSAGE12, szMsg, 512);
		        _snprintf(szEntityBody, 1023, szMsg, nError);
                szEntityBody[1023] = 0;
            }
        }
	}
	else
	{
		_snprintf(  szHeader,
                    255,
				    "%s %d %s\r\n",
					g_pszHTTPVersion,
					MsgTable[i].id, MsgTable[i].pszReason);
        szHeader[255] = 0;
	    if (m_nMethod != METHOD_HEAD)
        {
            strcat(szHeader, "Content Type: text/html\r\n");
		    LoadString(_AtlBaseModule.GetModuleInstance(), MsgTable[i].idStr, szMsg, 512);
		    va_list	ptr;
		    va_start(ptr, nError);
		    _vsnprintf(szEntityBody, 1023, szMsg, ptr); 
            szEntityBody[1023] = 0;
        }
	}
	// send "Connection: close" header if HTTP 1.1
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// persistent connections not supported yet
        strcat(szHeader, "Connection: close\r\n");
	}
	// send header
	if (SendData(szHeader, strlen(szHeader)) == FALSE)
    {
		return FALSE;
    }
	// send entity body if Method wasn't HEAD
	if (m_nMethod != METHOD_HEAD)
    {
		SendData(szEntityBody, strlen(szEntityBody));
    }
	// update log record
 	m_LogRec.lStatus = nError;
	return TRUE;
}

static struct tagMonthTable
{
	int	nID;
	char *pszMonth;
} MonthTable[] =
{
	{1, "Jan"},
	{2, "Feb"},
	{3, "Mar"},
	{4, "Apr"},
	{5, "May"},
	{6, "Jun"},
	{7, "Jul"},
	{8, "Aug"},
	{9, "Sep"},
	{10, "Oct"},
	{11, "Nov"},
	{12, "Dec"},
};
static const int MonthTableSize = sizeof(MonthTable)
							  / sizeof(struct tagMonthTable);

BOOL WMSOAPClientConn::StringToFileTime(string& strTime, LPFILETIME pFileTime)
{
	#define RFC850 1
	#define RFC1123 2
	#define ASCTIME 3

	int nYear, nMonth, nDay, nHour, nMinute, nSecond;
	char szBuffer[50];
	int nFormat;
	BOOL bError = FALSE;

	if (strTime.find("GMT") != string::npos)
	{
		if (strTime.find('-') != string::npos)
			nFormat = RFC850;
		else
			nFormat = RFC1123;
	}
	else
	{
		nFormat = ASCTIME;
	}
	memset(szBuffer, 0, 50);
	strncpy(szBuffer, strTime.c_str(), 48);

	switch (nFormat)
	{
		case RFC850:
		case RFC1123:
		{
			char *p;
			char *pszSeparators = " -:";

			// get day
			p = GetToken(szBuffer, pszSeparators);

			// get day of month
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nDay = atoi(p);

			// get month
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
			{
				bError = TRUE;
			}
			else
			{
				nMonth = 1;
				for (int i = 0; i < MonthTableSize; i++)
				{
					if (strcmp(p, MonthTable[i].pszMonth) == 0)
						nMonth = MonthTable[i].nID;
				}
			}

			// get year
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nYear = atoi(p);

			// get hours
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nHour = atoi(p);

			// get minutes
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nMinute = atoi(p);

			// get seconds
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nSecond = atoi(p);
			break;
		}
		case ASCTIME:
		{
			char *p;
			char *pszSeparators = " -:";

			// get day
			p = GetToken(szBuffer, pszSeparators);

			// get month
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
			{
				bError = TRUE;
			}
			else
			{
				nMonth = 0;
				for (int i = 0; i < MonthTableSize; i++)
				{
					if (strcmp(p, MonthTable[i].pszMonth) == 0)
						nMonth = MonthTable[i].nID;
				}
			}

			// get day of month
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nDay = atoi(p);

			// get hours
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nHour = atoi(p);

			// get minutes
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nMinute = atoi(p);

			// get seconds
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nSecond = atoi(p);

			// get year
			p = GetToken(NULL, pszSeparators);
			if (p == NULL)
				bError = TRUE;
			else
				nYear = atoi(p);
			break;
		}
	}
	// check validity of day
	if (nDay < 1 || nDay > 31)
		bError = TRUE;

	// check validity of month
	if (nMonth < 1 || nMonth > 12)
		bError = TRUE;

	// check validity of year
	if (nYear < 100)
		nYear += 1900;

	// create struct SYSTEMTIME
	SYSTEMTIME t;
	memset(&t, 0, sizeof(SYSTEMTIME));
	// fill it in
	if (bError == FALSE)
	{
		t.wYear = (WORD)nYear;
		t.wMonth = (WORD)nMonth;
		t.wDay = (WORD)nDay;
		t.wHour = (WORD)nHour;
		t.wMinute = (WORD)nMinute;
		t.wSecond = (WORD)nSecond;
	}
	else
	{
		t.wYear = 1970;
		t.wMonth = 1;
		t.wDay = 1;
		t.wHour = 0;
		t.wMinute = 0;
		t.wSecond = 0;
	}
	// convert to FILETIME
	return SystemTimeToFileTime(&t, pFileTime);
}

BOOL WMSOAPClientConn::SendReplyHeader(const string& strContentType,
								   HANDLE hFile,
								   BY_HANDLE_FILE_INFORMATION& FileInfo)
{
	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version
	// 4 - MIME version
	// 5 - MIME type
	// 6 - Last-modified time
	// 7 - Content length
	// 8 - Connection header if HTTP 1.1
	// 9 - End-of-header

	char szTemp[128];
	char szOutput[HEADER_LINE_SIZE];
	string strOutput;

	// 1
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s 200 OK\r\n", g_pszHTTPVersion);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput = szOutput;
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Date: %s\r\n", szTemp);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 3
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	// 4
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_MIME_VERSION, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	// 5
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Type: %s\r\n", strContentType.c_str());
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 6
	SYSTEMTIME SysTime;
	if (FileTimeToSystemTime(&FileInfo.ftLastWriteTime, &SysTime) != 0)
	{
		struct tm Time;
		Time.tm_year = (SysTime.wYear < 1900 ? 0 : SysTime.wYear - 1900);
		Time.tm_mon = SysTime.wMonth - 1;
		Time.tm_mday = SysTime.wDay;
		Time.tm_hour = SysTime.wHour;
		Time.tm_min = SysTime.wMinute;
		Time.tm_sec = SysTime.wSecond;
		Time.tm_isdst = 0;
		Time.tm_wday = SysTime.wDayOfWeek;

		strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", &Time);
		_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Last-Modified: %s\r\n", szTemp);
        szOutput[HEADER_LINE_SIZE - 1] = 0;
		strOutput += szOutput;
	}
	// 7
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Length: %u\r\n", FileInfo.nFileSizeLow);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// 8, 9 (persistent connections not supported yet)
		strOutput += "Connection: close\r\n\r\n";
	}
	else
	{
		// 9
		strOutput += "\r\n";
	}
	return SendData(strOutput.c_str(), strOutput.length());
}

BOOL WMSOAPClientConn::SendSOAPReplyHeader(int nHTTPStatus, DWORD dwContentLength)
{
	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version
	// 4 - MIME type
	// 5 - Content length
	// 6 - Connection header if HTTP 1.1
	// 7 - End-of-header

	char szTemp[128];
	char szOutput[HEADER_LINE_SIZE];
	string strOutput;

	// 1
	switch (nHTTPStatus)
	{
		case 200:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d OK\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 400:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Bad Request\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 404:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Not Found\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 500:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Internal Server Error\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		default:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Unexpected\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
	}
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput = szOutput;
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Date: %s\r\n", szTemp);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 3
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	// 4
	if (m_ContentType == Content_Type_Text_XML)
	{
    	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Type: text/xml; charset=\"utf-8\"\r\n");
    }
    else
	{
    	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Type: application/soap+xml; charset=\"utf-8\"\r\n");
    }
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 5
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Length: %u\r\n", dwContentLength);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// 6, 7 (persistent connections not supported yet)
		strOutput += "Connection: close\r\n\r\n";
	}
	else
	{
		// 7
		strOutput += "\r\n";
	}
	return SendData(strOutput.c_str(), strOutput.length());
}

BOOL WMSOAPClientConn::SendDIMEReplyHeader(int nHTTPStatus, unsigned long ulContentLength)
{
	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version
	// 4 - MIME type
	// 5 - Content length
	// 6 - Connection header if HTTP 1.1
	// 7 - End-of-header

	char szTemp[128];
	char szOutput[HEADER_LINE_SIZE];
	string strOutput;

	// 1
	switch (nHTTPStatus)
	{
		case 200:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d OK\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 400:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Bad Request\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 404:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Not Found\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		case 500:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Internal Server Error\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
			break;
		default:
			_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d Unexpected\r\n", m_strHTTPVersion.c_str(), nHTTPStatus);
	}
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput = szOutput;
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Date: %s\r\n", szTemp);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 3
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	// 4
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Type: application/dime\r\n");
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 5
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Length: %u\r\n", ulContentLength);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// 6, 7 (persistent connections not supported yet)
		strOutput += "Connection: close\r\n\r\n";
	}
	else
	{
		// 7
		strOutput += "\r\n";
	}
	return SendData(strOutput.c_str(), strOutput.length());
}

char *WMSOAPClientConn::GetToken(char *szString, char *pszDelimiters)
{
	if (szString != NULL)
	{
		m_pszStart = szString;
		if (m_pszStart[0] == 0)
		{
			return NULL;
		}
		while (strcspn(m_pszStart, pszDelimiters) == 0)
		{
			m_pszStart++;
			if (m_pszStart[0] == 0)
			{
				return NULL;
			}
		}
	}
	if (m_pszStart[0] == 0)
	{
		return NULL;
	}
	char *pszTemp = m_pszStart;
	int nOffset = strcspn(m_pszStart, pszDelimiters);
	if (m_pszStart[nOffset] == 0)
	{
		m_pszStart = pszTemp + nOffset;
	}
	else
	{
		m_pszStart[nOffset] = 0;
		m_pszStart = pszTemp + nOffset + 1;
	}
	return pszTemp;
}

BOOL WMSOAPClientConn::Send204Response()
{
	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version
	// 4 - Connection header if HTTP 1.1
	// 5 - End-of-header

	char szTemp[128];
	char szOutput[HEADER_LINE_SIZE];
	string strOutput;

	// 1
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s 204 No Content\r\n", m_strHTTPVersion.c_str());
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput = szOutput;
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Date: %s\r\n", szTemp);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 3
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// 4, 5 (persistent connections not supported yet)
		strOutput += "Connection: close\r\n\r\n";
	}
	else
	{
		// 5
		strOutput += "\r\n";
	}
	// update status for log record
	m_LogRec.lStatus = 204;
	// send it along
	return SendData(strOutput.c_str(), strOutput.length());
}

BOOL WMSOAPClientConn::Send304Response()
{
	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version

	// 1
	// status line handled internally by SendCannedMsg()
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	char szTimeBuffer[64];
	strftime(szTimeBuffer, 64, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	// 3
	char szServerVersion[128];
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szServerVersion, 128);
	// send it along
	return SendCannedMsg(304, szTimeBuffer, szServerVersion);
}

bool WMSOAPClientConn::ParseParameters(string& strParams, map<string, string>& mapParams)
{
	// strip out any comments
	if (StripComments(strParams) == false)
	{
		// bad comment syntax
		return false;
	}
	while (true)
	{
		// seek delimiting ';' between parameters
		string::size_type pos = strParams.find(';');
		// extract attribute/value pair
		string strTemp = strParams.substr(0, pos);
		// find '=' in attribute/value pair
		string::size_type index = strTemp.find('=');
		// if found then process
		if (index != string::npos)
		{
			string strAttr = strTemp.substr(0, index);
			// trim whitespace
			TrimWhitespace(strAttr);
			// attribute name is not case sensitive
			for (string::size_type i = 0; i < strAttr.length(); i++)
			{
				// force to uppercase... comparisons are case-insensitive
				strAttr[i] = toupper(strAttr[i]);
			}
			string strValue = strTemp.substr(index + 1);
			// trim whitespace
			TrimWhitespace(strValue);
			// unquote value if necessary
			string::size_type start, stop;
			start = strValue.find('"');
			stop = strValue.rfind('"');
			if (start != string::npos && stop != string::npos)
			{
				++start;
				strValue = strValue.substr(start, stop - start);
			}
			// now add to map
			mapParams[strAttr] = strValue;
		}
		// was this the last one?
		if (pos == string::npos)
		{
			// yes, we are done
			break;
		}
		strParams = strParams.substr(pos + 1);
	}
	return true;
}

void WMSOAPClientConn::TrimWhitespace(string& strData)
{
	LeftTrim(strData);
	RightTrim(strData);
}

void WMSOAPClientConn::LeftTrim(string& strData)
{
	// left trim
	string::size_type pos;
	for (pos = 0; pos < strData.length(); pos++)
	{
		if (!isspace(strData[pos]))
		{
			break;
		}
	}
	if (pos > 0)
	{
		strData = strData.substr(pos);
	}
}

void WMSOAPClientConn::RightTrim(string& strData)
{
	// right trim
	string::size_type pos;
	string::size_type upperbound = strData.length() - 1;
	for (pos = upperbound; pos >= 0; pos--)
	{
		if (!isspace(strData[pos]))
		{
			break;
		}
	}
	if (pos < upperbound)
	{
		strData = strData.substr(0, pos + 1);
	}
}

bool WMSOAPClientConn::StripComments(string& strFieldBody)
{
	// level of nesting
	int nCommentsLevel = 0;
	// quoted string flag
	bool bIsQuotedString = false;
	string strTemp;
	string::size_type pos;
	for (pos = 0; pos < strFieldBody.length(); pos++)
	{
		char c = strFieldBody[pos];
		if (c == '\\')
		{
			// quoted character... skip it
			pos++;
			continue;
		}
		else if (bIsQuotedString == false && c == '(')
		{
			// start of a comment
			nCommentsLevel++;
			continue;
		}
		else if (bIsQuotedString == false && c == ')')
		{
			// end of a comment
			nCommentsLevel--;
			if (nCommentsLevel == 0)
			{
				// comment is ended, replace with SPACE
				strTemp += ' ';
			}
			continue;
		}
		if (nCommentsLevel == 0)
		{
			// quoted string?
			if (c == '"')
			{
				// toggle flag
				bIsQuotedString = !bIsQuotedString;
			}
			// ok, save it
			strTemp += c;
		}
	}
	if (nCommentsLevel > 0 || bIsQuotedString == true)
	{
		// ill formed
		return false;
	}
	strFieldBody = strTemp;
	return true;
}

bool WMSOAPClientConn::ParseContentType(string& strFieldBody,
										string& strContentType,
										map<string, string>& mapParams)
{
	// strip out any comments
	if (StripComments(strFieldBody) == false)
	{
		// bad comment syntax
		return false;
	}
	// split content type and parameters
	string::size_type pos;
	pos = strFieldBody.find(';');
	strContentType = strFieldBody.substr(0, pos);
	TrimWhitespace(strContentType);
	// content type is not case sensitive
	for (string::size_type i = 0; i < strContentType.length(); i++)
	{
		// force to uppercase... comparisons are case-insensitive
		strContentType[i] = toupper(strContentType[i]);
	}
	// now parse parameters
	ParseParameters(strFieldBody.substr(pos + 1), mapParams);
	return true;
}

int WMSOAPClientConn::SendSOAPEndpointGETResponse(BOOL bHeaderOnly)
{
	// load page
	char szStrBuffer[1024];
	char szPageBuffer[1024];
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_ENDPOINT_PAGE, szStrBuffer, 1024);
	_snprintf(szPageBuffer, 1023, szStrBuffer, m_strURIRequested.c_str());
    szPageBuffer[1023] = 0;


	// Response header components:
	// 1 - HTTP status line
	// 2 - Server time
	// 3 - Server version
	// 4 - MIME type
	// 5 - Content length
	// 6 - Connection header if HTTP 1.1
	// 7 - End-of-header

	char szTemp[128];
	char szOutput[HEADER_LINE_SIZE];
	string strOutput;

	// 1
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "%s %d OK\r\n", m_strHTTPVersion.c_str(), 200);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput = szOutput;
	// 2
	time_t Time;
	time(&Time);
	struct tm *pGMT = gmtime(&Time);
	strftime(szTemp, 128, "%a, %d %b %Y %H:%M:%S GMT", pGMT);
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Date: %s\r\n", szTemp);
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 3
	LoadString(_AtlBaseModule.GetModuleInstance(), IDS_SERVER_HEADER, szOutput, HEADER_LINE_SIZE);
	strOutput += szOutput;
	// 4
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Type: text/html\r\n");
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	// 5
	_snprintf(szOutput, HEADER_LINE_SIZE - 1, "Content-Length: %u\r\n", strlen(szPageBuffer));
    szOutput[HEADER_LINE_SIZE - 1] = 0;
	strOutput += szOutput;
	if (m_nHTTPMajorVersion > 0 && m_nHTTPMinorVersion > 0)
	{
		// 6, 7 (persistent connections not supported yet)
		strOutput += "Connection: close\r\n\r\n";
	}
	else
	{
		// 7
		strOutput += "\r\n";
	}
	// send header
	if (SendData(strOutput.c_str(), strOutput.length()) == FALSE)
	{
		return EV_PROCESSING_ERROR;
	}
	// send file
	if (bHeaderOnly == FALSE)
	{
		if (SendData(szPageBuffer, strlen(szPageBuffer)) == FALSE)
		{
			return EV_PROCESSING_ERROR;
		}
	}
	return EV_PROCESSING_DONE;
}

