// SOAPDIMEHTTPClient.cpp: implementation of the WMSOAPDIMEHTTPClient class.
//
//////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2002 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.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPDIMEHTTPClient class - an HTTP client for SOAP messaging.
// uses DIME packaging of SOAP message and any attachments,
// in accordance with Internet-Draft "WS-Attachments"
// (draft-nielsen-dime-soap-01 June 17, 2002).
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SOAPDIMEHTTPClient.h"
#include "WMXMLMessage.h"
#include "..\wmsoapmsg\wmsoapmsg.h"

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

WMSOAPDIMEHTTPClient::WMSOAPDIMEHTTPClient()
{
	m_nHTTPStatus = 0;
    m_bIsSoapResponse = false;
	m_wstrErrorMsg = L"Unexpected error processing HTTP request/response.";
}

WMSOAPDIMEHTTPClient::~WMSOAPDIMEHTTPClient()
{
}

bool WMSOAPDIMEHTTPClient::SendSOAPMessage(  const string& strMethod,
                                             const string& strSOAPAction,
											 const string& strHost,
											 int nPortNbr,
											 const string& strSvcURI,
											 const string& strUserAgent,
											 Iwmsoapmsg2 *pIwmsoapmsg,
											 bool bIsDIMERequest)
{
    if (strMethod == "POST")
    {
        return SendRequestWithEntity(   strMethod,
                                        strSOAPAction,
									    strHost,
									    nPortNbr,
									    strSvcURI,
									    strUserAgent,
									    pIwmsoapmsg,
									    bIsDIMERequest);
    }
    else if (strMethod == "GET")
    {
        return SendRequestWithoutEntity(strMethod,
                                        strSOAPAction,
									    strHost,
									    nPortNbr,
									    strSvcURI,
									    strUserAgent,
									    pIwmsoapmsg);
    }
	m_wstrErrorMsg = L"Unsupported HTTP request method.";
    return false;
}

bool WMSOAPDIMEHTTPClient::SendRequestWithEntity(   const string& strMethod,
                                                    const string& strSOAPAction,
											        const string& strHost,
											        int nPortNbr,
											        const string& strSvcURI,
											        const string& strUserAgent,
											        Iwmsoapmsg2 *pIwmsoapmsg,
											        bool bIsDIMERequest)
{
	// get serialized message
	VARIANT_BOOL bResult;
	_variant_t varBuffer;
	pIwmsoapmsg->GetBuffer(&varBuffer, &bResult);
	SAFEARRAY *pArray = varBuffer.parray;
	void *pData;
	if (	bResult == VARIANT_FALSE ||
			!(varBuffer.vt & (VT_ARRAY | VT_UI1)) ||
			pArray == NULL ||
			SafeArrayGetDim(pArray) != 1 ||
			FAILED(SafeArrayAccessData(pArray, &pData)))
	{
		m_wstrErrorMsg = L"Request: Failed to access buffer for SOAP message.";
		return false;
	}
    // get soap version
    long lSOAPVersion;
    pIwmsoapmsg->GetSOAPVersion(&lSOAPVersion);
	unsigned long ulMsgLength;
	if (bIsDIMERequest == true)
	{
		// try to initialize parser for serialization
		// accept default record size of 4096 octets
		try
		{
			m_DIMEParser.SerializeInit();
		}
		catch (DIMEParserException *e)
		{
			m_wstrErrorMsg = L"Request: Failed to initialize DIME parser for serialization.";
			delete e;
			SafeArrayUnaccessData(pArray);
			return false;
		}
        string strTypeURI;
        if (lSOAPVersion == SOAP_VERSION_1_1)
        {
            strTypeURI = g_pszSoapEnvelopeNS;
        }
        else
        {
            strTypeURI = g_pszSoap12EnvelopeNS;
        }
		// add primary DIME payload
		if (m_DIMEParser.AddPayload((const char *)pData,
									pArray->rgsabound[0].cElements,
									string(""),
									strTypeURI,
									DIME_TYPENAME_FORMAT_URI) == false)
		{
			m_wstrErrorMsg = L"Request: Failed to create primary DIME payload.";
			SafeArrayUnaccessData(pArray);
			return false;
		}
		SafeArrayUnaccessData(pArray);
		// now get secondary payloads
		long lPosition;
		BSTR bstrId, bstrType;
		long lTNF;
		IUnknown *pIUnknown;
		_variant_t varOptions;
		for (pIwmsoapmsg->GetFirstDIMEAttachmentReq(&bstrId,
													&bstrType,
													&lTNF,
													&pIUnknown,
													&varOptions,
													&lPosition,
													&bResult);
			 bResult == VARIANT_TRUE;
			 pIwmsoapmsg->GetNextDIMEAttachmentReq( &bstrId,
													&bstrType,
													&lTNF,
													&pIUnknown,
													&varOptions,
													&lPosition,
													&bResult)
		)
		{
			string strId, strType;
			WideStringToUTF8(wstring(bstrId), strId);
			WideStringToUTF8(wstring(bstrType), strType);
			SysFreeString(bstrId);
			SysFreeString(bstrType);
			IStorage *pIStorage;
			HRESULT res = pIUnknown->QueryInterface(IID_IStorage, (void **)&pIStorage);
			if (FAILED(res))
			{
				m_wstrErrorMsg = L"Request: Failed to obtain IStorage interface pointer.";
				return false;
			}
			SAFEARRAY *pArray = NULL;
			if ((varOptions.vt & VT_TYPEMASK) & (VT_ARRAY | VT_UI1))
			{
				pArray = varOptions.parray;
			}
			if (m_DIMEParser.AddPayload(pIStorage,
										strId,
										strType,
										lTNF,
										pArray) == false)
			{
				// this will result in 500 "Internal Server Error"
				pIStorage->Release();
				m_wstrErrorMsg = L"Request: Failed to create secondary DIME payload.";
				return false;
			}
			pIStorage->Release();
			varOptions.Clear();
		}
		ulMsgLength = m_DIMEParser.CalcSerialLength();
	}
	else
	{
		// not a DIME packaged request... get SOAP envelope
		m_strRequestXML.append((char *)pData, pArray->rgsabound[0].cElements);
		ulMsgLength = pArray->rgsabound[0].cElements;
		SafeArrayUnaccessData(pArray);
	}
	// build HTTP Header
	char szBuffer[128];
	string strHeaders = "Host: ";
	strHeaders += strHost;
	_snprintf(szBuffer, 127, ":%d\r\n", nPortNbr);
    szBuffer[127] = 0;
	strHeaders += szBuffer;
	strHeaders += "User-Agent: ";
	strHeaders += strUserAgent;
	if (bIsDIMERequest == true)
	{
		strHeaders += "\r\nContent-Type: application/dime\r\n";
	}
	else
	{
        if (lSOAPVersion == SOAP_VERSION_1_1)
        {
	        strHeaders += "\r\nContent-Type: text/xml; charset=\"utf-8\"\r\n";
        }
        else
        {
	        strHeaders += "\r\nContent-Type: application/soap+xml; charset=\"utf-8\"\r\n";
        }
	}
	_snprintf(szBuffer, 127, "Content-Length: %d\r\n", ulMsgLength);
    szBuffer[127] = 0;
	strHeaders += szBuffer;
	strHeaders += "SOAPAction: ";
	strHeaders += strSOAPAction;
	strHeaders += "\r\n";
	// make HTTP request
	bool bRtn;
	if (bIsDIMERequest == true)
	{
		bRtn = m_Socket.SendHTTPRequest(nPortNbr,
										strMethod,
										strHost,
										strSvcURI,
										string(""),
										strHeaders,
										&m_DIMEParser);
	}
	else
	{
		bRtn = m_Socket.SendHTTPRequest(nPortNbr,
										strMethod,
										strHost,
										strSvcURI,
										string(""),
										strHeaders,
										m_strRequestXML,
										&m_DIMEParser);
	}
	if (bRtn == false)
	{
		m_wstrErrorMsg = L"Request: Failed to connect to server.";
		return false;
	}
	// get HTTP status code
	m_nHTTPStatus = m_Socket.GetStatus();
    // success?
    if (m_nHTTPStatus == 204)
	{
		// set "no content" response
		pIwmsoapmsg->SetNoContentResponse(VARIANT_TRUE);
		return true;
	}
    // reduce status code to generic value
    m_nHTTPStatus = (m_nHTTPStatus / 100) * 100;
	switch(m_nHTTPStatus)
	{
		case 400:
		{
            if (lSOAPVersion == SOAP_VERSION_1_1)
            {
			    m_wstrErrorMsg = L"Response: Unexpected HTTP Status Code.";
			    return false;
            }
		}
		case 200:
		case 500:
		{
			// is this DIME?
			if (m_Socket.GetHeader("Content-Type").find("application/dime") == string::npos)
			{
				// no, is this SOAP 1.1 or 1.2?
                if (    (lSOAPVersion == SOAP_VERSION_1_1 &&
                        m_Socket.GetHeader("Content-Type").find("text/xml") != string::npos) ||
                        (lSOAPVersion == SOAP_VERSION_1_2 &&
                        m_Socket.GetHeader("Content-Type").find("application/soap+xml") != string::npos)
                )
				{
					// SOAP 1.1 or 1.2 envelope... store in message object
					void *pData;
					_variant_t varBuffer;
					SAFEARRAY *pArray = SafeArrayCreateVector(VT_UI1, 0, m_Socket.GetContentLength());
					if (pArray == NULL || FAILED(SafeArrayAccessData(pArray, &pData)))
					{
						if (pArray != NULL)
						{
							SafeArrayDestroy(pArray);
						}
						m_wstrErrorMsg = L"Response: Unexpected memory allocation error.";
						return false;
					}
					else
					{
						memcpy(pData, m_Socket.GetContent(), m_Socket.GetContentLength());
						SafeArrayUnaccessData(pArray);
						varBuffer.vt = (VT_ARRAY | VT_UI1);
						varBuffer.parray = pArray;
					}
					VARIANT_BOOL bResult;
					pIwmsoapmsg->SetBuffer(varBuffer, &bResult);
					if (bResult == VARIANT_FALSE)
					{
						m_wstrErrorMsg = L"Response: Message buffer initialization error.";
						return false;
					}
                    m_bIsSoapResponse = true;
                    return true;
				}
				else
				{
                    // not a SOAP media type
                    if (m_nHTTPStatus == 200)
                    {
                        // we are expecting a SOAP message for status 200
					    m_wstrErrorMsg = L"Response: Unexpected media type for response.";
                        return false;
                    }
                    return true;
				}
			}
			else
			{
				// DIME packaged response
                if (ProcessDIMEResponse(pIwmsoapmsg) == true)
                {
                    m_bIsSoapResponse = true;
                    return true;
                }
                return false;
			}
			break;
		}
		default:
		{
			m_wstrErrorMsg = L"Response: Unexpected HTTP Status Code.";
			break;
		}
	}
	return false;
}

bool WMSOAPDIMEHTTPClient::SendRequestWithoutEntity(const string& strMethod,
                                                    const string& strSOAPAction,
											        const string& strHost,
											        int nPortNbr,
											        const string& strSvcURI,
											        const string& strUserAgent,
											        Iwmsoapmsg2 *pIwmsoapmsg)
{
    // get soap version
    long lSOAPVersion;
    pIwmsoapmsg->GetSOAPVersion(&lSOAPVersion);
	// build HTTP Header
	char szBuffer[128];
	string strHeaders = "Host: ";
	strHeaders += strHost;
	_snprintf(szBuffer, 127, ":%d\r\n", nPortNbr);
    szBuffer[127] = 0;
	strHeaders += szBuffer;
	strHeaders += "User-Agent: ";
	strHeaders += strUserAgent;
	strHeaders += "\r\n";
    m_strRequestXML.erase();
	// make HTTP request
	bool bRtn = m_Socket.SendHTTPRequest(nPortNbr,
										strMethod,
										strHost,
										strSvcURI,
										string(""),
										strHeaders,
										m_strRequestXML,
										&m_DIMEParser);
	if (bRtn == false)
	{
		m_wstrErrorMsg = L"Request: Failed to connect to server.";
		return false;
	}
	// get HTTP status code
	m_nHTTPStatus = m_Socket.GetStatus();
    // no content?
    if (m_nHTTPStatus == 204)
	{
		// set "no content" response
		pIwmsoapmsg->SetNoContentResponse(VARIANT_TRUE);
		return true;
	}
    // reduce status code to generic value
    m_nHTTPStatus = (m_nHTTPStatus / 100) * 100;
	switch(m_nHTTPStatus)
	{
		case 400:
		{
            if (lSOAPVersion == SOAP_VERSION_1_1)
            {
			    m_wstrErrorMsg = L"Response: Unexpected HTTP Status Code.";
			    return false;
            }
		}
		case 200:
		case 500:
		{
			// is this DIME?
			if (m_Socket.GetHeader("Content-Type").find("application/dime") == string::npos)
			{
				// no, is this SOAP 1.1 or 1.2?
                if (    (lSOAPVersion == SOAP_VERSION_1_1 &&
                        m_Socket.GetHeader("Content-Type").find("text/xml") != string::npos) ||
                        (lSOAPVersion == SOAP_VERSION_1_2 &&
                        m_Socket.GetHeader("Content-Type").find("application/soap+xml") != string::npos)
                )
				{
					// SOAP 1.1 or 1.2 envelope... store in message object
					void *pData;
					_variant_t varBuffer;
					SAFEARRAY *pArray = SafeArrayCreateVector(VT_UI1, 0, m_Socket.GetContentLength());
					if (pArray == NULL || FAILED(SafeArrayAccessData(pArray, &pData)))
					{
						if (pArray != NULL)
						{
							SafeArrayDestroy(pArray);
						}
						m_wstrErrorMsg = L"Response: Unexpected memory allocation error.";
						return false;
					}
					else
					{
						memcpy(pData, m_Socket.GetContent(), m_Socket.GetContentLength());
						SafeArrayUnaccessData(pArray);
						varBuffer.vt = (VT_ARRAY | VT_UI1);
						varBuffer.parray = pArray;
					}
					VARIANT_BOOL bResult;
					pIwmsoapmsg->SetBuffer(varBuffer, &bResult);
					if (bResult == VARIANT_FALSE)
					{
						m_wstrErrorMsg = L"Response: Message buffer initialization error.";
						return false;
					}
                    m_bIsSoapResponse = true;
                    return true;
				}
				else
				{
                    // not a SOAP media type
                    if (m_nHTTPStatus == 200)
                    {
                        // we are expecting a SOAP message for status 200
					    m_wstrErrorMsg = L"Response: Unexpected media type for response.";
                        return false;
                    }
                    return true;
				}
			}
			else
			{
				// DIME packaged response
                if (ProcessDIMEResponse(pIwmsoapmsg) == true)
                {
                    m_bIsSoapResponse = true;
                    return true;
                }
                return false;
			}
			break;
		}
		default:
		{
			m_wstrErrorMsg = L"Response: Unexpected HTTP Status Code.";
			break;
		}
	}
	return false;
}

bool WMSOAPDIMEHTTPClient::ProcessDIMEResponse(Iwmsoapmsg2 *pIwmsoapmsg)
{
	// DIME packaged response
	char *pszResponseXML;
	unsigned long ulLength = 0;
	SAFEARRAY *pArray = NULL;
	int counter = 0;
	vector<DIMEPayload *>::iterator iter;
	for (iter = m_DIMEParser.GetPayloads().begin(); iter != m_DIMEParser.GetPayloads().end(); iter++)
	{
		DIMEPayload *pPayload = *iter;
		if (counter++ == 0)
		{
			// primary SOAP payload
			pPayload->OpenForRead();
			ulLength = pPayload->GetDataLength();
			pszResponseXML = (char *)malloc(ulLength);
			if (pszResponseXML == NULL)
			{
				m_wstrErrorMsg = L"Response: Memory allocation error.";
				return false;
			}
			unsigned long ulNumBytes = 0;
			pPayload->Read(pszResponseXML, ulLength, &ulNumBytes);
			if (ulNumBytes != ulLength)
			{
				free(pszResponseXML);
				m_wstrErrorMsg = L"Response: Failed to read primary DIME payload.";
				return false;
			}
			pPayload->Close();
			// make SAFEARRAY containing input XML
			void *pData;
			pArray = SafeArrayCreateVector(VT_UI1, 0, ulLength);
			if (pArray == NULL || FAILED(SafeArrayAccessData(pArray, &pData)))
			{
				// this will result in 500 "Internal Server Error"
				if (pArray != NULL)
				{
					SafeArrayDestroy(pArray);
				}
				free(pszResponseXML);
				free(pszResponseXML);
				m_wstrErrorMsg = L"Response: Failed to create SAFEARRAY for primary DIME payload.";
				return false;
			}
			memcpy(pData, pszResponseXML, ulLength);
			SafeArrayUnaccessData(pArray);
			free(pszResponseXML);
			// now place in message object
			_variant_t varBuffer;
			varBuffer.vt |= (VT_ARRAY | VT_UI1);
			varBuffer.parray = pArray;
			VARIANT_BOOL bResult;
			pIwmsoapmsg->SetBuffer(varBuffer, &bResult);
			if (bResult == VARIANT_FALSE)
			{
				m_wstrErrorMsg = L"Response: Failed to store primary DIME payload.";
				return false;
			}
		}
		else
		{
			// add attachment to message
			wstring wstrId, wstrType;
			UTF8ToWideString(pPayload->GetId(), wstrId);
			UTF8ToWideString(pPayload->GetType(), wstrType);
			BSTR bstrId = SysAllocString(wstrId.c_str());
			BSTR bstrType = SysAllocString(wstrType.c_str());
			long lTNF = pPayload->GetTNF();
			// dime options
			_variant_t varOptions;
			unsigned long ulLength = pPayload->GetOptionsLength();
			SAFEARRAY *pOptions = SafeArrayCreateVector(VT_UI1, 0L, ulLength);
			if (pArray == NULL)
			{
				m_wstrErrorMsg = L"Response: Failed to create options buffer.";
				return false;
			}
			if (ulLength > 0)
			{
				void *pData = NULL;
				if (FAILED(SafeArrayAccessData(pOptions, &pData)))
				{
					SafeArrayDestroy(pOptions);
					m_wstrErrorMsg = L"Response: Failed to access options buffer.";
					return false;
				}
				// copy data into vector
				pPayload->GetOptions((unsigned char *)pData, ulLength);
				SafeArrayUnaccessData(pOptions);
				varOptions.parray = pOptions;
				varOptions.vt = VT_ARRAY | VT_UI1;
			}
			VARIANT_BOOL bResult;
			pIwmsoapmsg->AddDIMEAttachmentResp( bstrId,
												bstrType,
												lTNF,
												pPayload->GetStorage(),
												varOptions,
												&bResult);
			SysFreeString(bstrId);
			SysFreeString(bstrType);
			varOptions.Clear();
			if (bResult == VARIANT_FALSE)
			{
				m_wstrErrorMsg = L"Response: Failed to store secondary DIME payload.";
				return false;
			}
		}
	}
    return true;
}