// SOAPAutomationDIMEProxy.cpp: implementation of the WMSOAPAutomationDIMEProxy 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.
//
//////////////////////////////////////////////////////////////////////
//
// WMSOAPAutomationDIMEProxy class - a proxy for SOAP RPC calls to 
// COM based web services... for use by the 'client'.
// Uses DIME packaging, in accordance with Internet-Draft "WS-Attachments"
// (draft-nielsen-dime-soap-01 June 17, 2002).
// 
//////////////////////////////////////////////////////////////////////

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

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

WMSOAPAutomationDIMEProxy::WMSOAPAutomationDIMEProxy()
{
	m_nHTTPStatus = 0;
    m_bUseProxy = false;
    m_nProxyPort = 0;
}

WMSOAPAutomationDIMEProxy::~WMSOAPAutomationDIMEProxy()
{
}

int WMSOAPAutomationDIMEProxy::GetHTTPStatus()
{
	return m_nHTTPStatus;
}

bool WMSOAPAutomationDIMEProxy::InvokeMethod(   const string& strSOAPAction,
                                                bool bSOAPActionRequired,
										        const string& strHost,
										        int nPortNbr,
                                                const string& strHTTPMethod,
										        const string& strSvcURI,
                                                const string& strQueryString,
										        const string& strUserAgent,
										        Iwmsoapmsg2 *pIwmsoapmsg,
                                                int nSOAPVersion,
										        bool bForceDIMEPackaging)
{
	try
	{
		InvokeSOAPMethod(   strSOAPAction,
                            bSOAPActionRequired,
							strHost,
							nPortNbr,
                            strHTTPMethod,
							strSvcURI,
                            strQueryString,
							strUserAgent,
							pIwmsoapmsg,
                            nSOAPVersion,
							bForceDIMEPackaging);
	}
	catch(WMSOAPDIMEProxyFault *f)
	{
		// consume the exception
		delete f;
		return false;
	}
	return true;
}

bool WMSOAPAutomationDIMEProxy::InvokeSOAPMethod(const string& strSOAPAction,
                                                 bool bSOAPActionRequired,
												 const string& strHost,
												 int nPortNbr,
                                                 const string& strHTTPMethod,
												 const string& strSvcURI,
                                                 const string& strQueryString,
												 const string& strUserAgent,
												 Iwmsoapmsg2 *pIwmsoapmsg,
                                                 int nSOAPVersion,
												 bool bForceDIMEPackaging)
{
	_variant_t varBuffer;
    unsigned long ulMsgLength = 0;
    long lNumAttachments = 0;
    // using POST?
    bool bPost = false;
    if (strHTTPMethod == "POST")
    {
        bPost = true;
	    // get serialized message
	    VARIANT_BOOL bResult;
	    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)))
	    {
		    throw new WMSOAPDIMEProxyFault(L"Request Message", L"Failed to obtain request message.");
	    }
        // fill request message buffer
        m_strXMLOut.erase();
	    m_strXMLOut.append((char *)pData, pArray->rgsabound[0].cElements);
	    pIwmsoapmsg->GetNumReqAttachments(&lNumAttachments);
	    if (lNumAttachments > 0 || bForceDIMEPackaging == true)
	    {
		    // try to initialize parser for serialization
		    // accept default record size of 4096 octets
		    try
		    {
			    m_DIMEParser.SerializeInit();
		    }
		    catch (DIMEParserException *e)
		    {
			    delete e;
			    SafeArrayUnaccessData(pArray);
			    throw new WMSOAPDIMEProxyFault(L"Request Message", L"Failed to initialize DIME parser for serialization.");
		    }
		    // add primary DIME payload
            string strSOAPEnvNS;
            if (nSOAPVersion == SOAP_VERSION_1_1)
            {
                strSOAPEnvNS = g_pszSoapEnvelopeNS;
            }
            else
            {
                strSOAPEnvNS = g_pszSoap12EnvelopeNS;
            }
		    if (m_DIMEParser.AddPayload((const char *)pData,
									    pArray->rgsabound[0].cElements,
									    string(""),
									    strSOAPEnvNS,
									    DIME_TYPENAME_FORMAT_URI) == false)
		    {
			    SafeArrayUnaccessData(pArray);
			    throw new WMSOAPDIMEProxyFault(L"Request Message", L"Failed to create primary DIME payload.");
		    }
		    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))
			    {
				    throw new WMSOAPDIMEProxyFault(L"Request Message", L"Failed to obtain IStorage interface pointer.");
			    }
			    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();
				    throw new WMSOAPDIMEProxyFault(L"Request Message", L"Failed to create secondary DIME payload.");
			    }
			    pIStorage->Release();
			    varOptions.Clear();
		    }
		    ulMsgLength = m_DIMEParser.CalcSerialLength();
	    }
	    else
	    {
		    // DIME packaging not used if there are no attachments, unless forced
		    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;
    strHeaders += "\r\n";
    if (bPost == true)
    {
	    if (lNumAttachments > 0 || bForceDIMEPackaging == true)
	    {
		    strHeaders += "Content-Type: application/dime\r\n";
	    }
	    else
	    {
            if (nSOAPVersion == SOAP_VERSION_1_1)
            {
	            strHeaders += "Content-Type: text/xml; charset=\"utf-8\"\r\n";
            }
            else
            {
	            strHeaders += "Content-Type: application/soap+xml; charset=\"utf-8\"";
                if (bSOAPActionRequired == true)
                {
                    strHeaders += "; action=";
                    strHeaders += strSOAPAction;
                }
                strHeaders += "\r\n";
            }
	    }
	    _snprintf(szBuffer, 127, "Content-Length: %d\r\n", ulMsgLength);
        szBuffer[127] = 0;
	    strHeaders += szBuffer;
        if (nSOAPVersion == SOAP_VERSION_1_1 && bSOAPActionRequired == true)
        {
	        strHeaders += "SOAPAction: ";
	        strHeaders += strSOAPAction;
	        strHeaders += "\r\n";
        }
    }
	// make HTTP request
	bool bSuccess;
	if (lNumAttachments > 0 || bForceDIMEPackaging == true)
	{
	    // make DIME request
        if (m_bUseProxy == true)
        {
            // modify request URI
            char szBuffer[16];
	        _snprintf(szBuffer, 15, ":%d", nPortNbr);
            szBuffer[15] = 0;
            string strTemp("http://");
            strTemp += strHost;
            strTemp += szBuffer;
            strTemp += strSvcURI;
	        bSuccess = m_Socket.SendHTTPRequest(m_nProxyPort,
                                                strHTTPMethod,
		                                        m_strProxyHost,
                                                strTemp,
                                                strQueryString,
                                                strHeaders,
                                                &m_DIMEParser);
        }
        else
        {
		    bSuccess = m_Socket.SendHTTPRequest(nPortNbr,
											    strHTTPMethod,
											    strHost,
											    strSvcURI,
											    strQueryString,
											    strHeaders,
											    &m_DIMEParser);
        }
	}
	else
	{
        // make non-DIME request
        if (m_bUseProxy == true)
        {
            // modify request URI
            char szBuffer[16];
	        _snprintf(szBuffer, 15, ":%d", nPortNbr);
            szBuffer[15] = 0;
            string strTemp("http://");
            strTemp += strHost;
            strTemp += szBuffer;
            strTemp += strSvcURI;
	        bSuccess = m_Socket.SendHTTPRequest(m_nProxyPort,
                                                strHTTPMethod,
		                                        m_strProxyHost,
                                                strTemp,
                                                strQueryString,
                                                strHeaders,
                                                m_strXMLOut,
                                                &m_DIMEParser);
        }
        else
        {
		    bSuccess = m_Socket.SendHTTPRequest(nPortNbr,
											    strHTTPMethod,
											    strHost,
											    strSvcURI,
											    strQueryString,
											    strHeaders,
											    m_strXMLOut,
											    &m_DIMEParser);
        }
	}
	if (bSuccess == false)
	{
		throw new WMSOAPDIMEProxyFault(L"Request Message", L"Request: Could not connect to server.");
	}
	m_strXMLIn.erase();
	// get HTTP status code
	m_nHTTPStatus = m_Socket.GetStatus();
    // no content?
    if (m_nHTTPStatus == 204)
	{
		// set "no content" response
		pIwmsoapmsg->SetNoContentResponse(VARIANT_TRUE);
		return false;
	}
    // reduce status code to generic value
    m_nHTTPStatus = (m_nHTTPStatus / 100) * 100;
	switch(m_nHTTPStatus)
	{
		case 400:
		{
            if (nSOAPVersion == SOAP_VERSION_1_1)
            {
    			throw new WMSOAPDIMEProxyFault(L"Response Message", L"Response: Unexpected HTTP Status Code.");
            }
		}
		case 200:
		case 500:
		{
			// is this DIME?
			if (m_Socket.GetHeader("Content-Type").find("application/dime") == string::npos)
			{
			    // is this xml??
                string strContentType;
                if (nSOAPVersion == SOAP_VERSION_1_1)
                {
                    strContentType = "text/xml";
                }
                else
                {
                    strContentType = "application/soap+xml";
                }
			    if (m_Socket.GetHeader("Content-Type").find(strContentType) == string::npos)
			    {
				    throw new WMSOAPDIMEProxyFault(L"Response Message", L"Unexpected MIME type in non-DIME Response.");
			    }
				// store response 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);
					}
					throw new WMSOAPDIMEProxyFault(L"Response Message", L"Unexpected memory allocation error.");
				}
				else
				{
                    // fill response XML buffer
                    m_strXMLIn.append(m_Socket.GetContent(), m_Socket.GetContentLength());
                    // copy response to SAFEARRAY
					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)
				{
					throw new WMSOAPDIMEProxyFault(L"Response Message", L"Message buffer initialization error.");
				}
			}
			else
			{
				// DIME packaged
				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)
						{
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Memory allocation error.");
						}
						unsigned long ulNumBytes = 0;
						pPayload->Read(pszResponseXML, ulLength, &ulNumBytes);
						if (ulNumBytes != ulLength)
						{
							free(pszResponseXML);
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to read primary DIME payload.");
						}
						pPayload->Close();
                        // fill response XML buffer
                        m_strXMLIn.append(pszResponseXML, ulNumBytes);
						// 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);
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to create SAFEARRAY for primary DIME payload.");
						}
						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)
						{
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to set message primary payload buffer.");
						}
					}
					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)
						{
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to create options buffer.");
						}
						if (ulLength > 0)
						{
							void *pData = NULL;
							if (FAILED(SafeArrayAccessData(pOptions, &pData)))
							{
								SafeArrayDestroy(pOptions);
								throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to access options buffer.");
							}
							// 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)
						{
							throw new WMSOAPDIMEProxyFault(L"Response Message", L"Failed to append secondary DIME payload to message.");
						}
					}
				}
			}
			break;
		}
		default:
		{
			throw new WMSOAPDIMEProxyFault(L"Response Message", L"Unexpected HTTP Status Code.");
		}
	}
	return true;
}

void WMSOAPAutomationDIMEProxy::SetHttpProxy(const string& strHost, int nPort)
{
    m_bUseProxy = true;
    m_strProxyHost = strHost;
    m_nProxyPort = nPort;
}
