// DimeDocClient.cpp
//
//////////////////////////////////////////////////////////////////////
//
// 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.
//
//////////////////////////////////////////////////////////////////////
//
// WMDimeDocClient class - a class implementing an HTTP client
// for sending SOAP messages using DIME packaging per the WS-Attachments spec
// found at http://www.ietf.org/internet-drafts/draft-nielsen-dime-soap-01.txt
// Uses the "document/literal" SOAP messaging convention.
// 
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "dimedocclient.h"
#include "charencode.h"
#include "xmlparser.h"
#include "dimerequestsock.h"
#include "soap.h"
#include "..\wmwsdlclient\wmwsdlclient.h"
#include "..\wmwsdlclient\wmwsdlclient_i.c"

WMDimeDocClient::WMDimeDocClient(void)
{
    m_pIwmwsdlclient = NULL;
    m_nSOAPVersion = SOAP_VERSION_1_1;
    m_bForceDIMEPackaging = false;
    m_ulRecordSize = 4096;
    m_bInitialized = false;
    m_bUseProxy = false;
}

WMDimeDocClient::~WMDimeDocClient(void)
{
    if (m_pIwmwsdlclient != NULL)
    {
        m_pIwmwsdlclient->Release();
    }
}

bool WMDimeDocClient::Initialize(   const wstring& wstrWSDLFileURL,
                                    const wstring& wstrServiceName,
                                    const wstring& wstrPortName,
                                    const wstring& wstrUserAgent,
                                    bool bForceDIMEPackaging,
                                    unsigned long ulRecordSize)
{
    m_bInitialized = false;
    m_ulRecordSize = ulRecordSize;
    if (ReadWSDLDocument(wstrWSDLFileURL, wstrServiceName, wstrPortName) == false)
    {
        m_wstrErrorMsg = L"Initialize: Failed to read WSDL document.";
        return false;
    }
    m_bForceDIMEPackaging = bForceDIMEPackaging;
    WideStringToUTF8(wstrUserAgent, m_strUserAgent);
    m_bInitialized = true;
    return true;
}

bool WMDimeDocClient::SetHttpProxy(   const string& strProxyHost,
                                        int nProxyPort)
{
    if (m_bInitialized == false)
    {
    	m_wstrErrorMsg = L"SetHttpProxy: Client not initialized yet.";
        return false;
    }
    m_bUseProxy = true;
    m_strProxyHost = strProxyHost;
    m_nProxyPort = nProxyPort;
    return true;
}

bool WMDimeDocClient::DoOperation(const wstring& wstrOperation)
{
    return DoOperation(wstrOperation, string("POST"), string(""), string(""));
}

bool WMDimeDocClient::DoOperation(const wstring& wstrOperation,
                                  const string& strHTTPMethod)
{
    return DoOperation(wstrOperation, strHTTPMethod, string(""), string(""));
}

bool WMDimeDocClient::DoOperation(const wstring& wstrOperation,
                                  const string& strHTTPMethod,
                                  const string& strExtraPath)
{
    return DoOperation(wstrOperation, strHTTPMethod, strExtraPath, string(""));
}

bool WMDimeDocClient::DoOperation(const wstring& wstrOperation,
                                  const string& strHTTPMethod,
                                  const string& strExtraPath,
                                  const string& strQueryString)
{
    if (m_bInitialized == false)
    {
    	m_wstrErrorMsg = L"Request: Client not initialized yet.";
        return false;
    }
    m_strRequestXML.erase();
    m_strResponseXML.erase();
    m_bIsDIMEResponse = false;
    unsigned long ulMsgLength = 0;
    bool bUsingDimePkg = false;
	// create DIME parser
	DIMEParser d;
	// initialize it
	try
	{
		d.SerializeInit(m_ulRecordSize);
	}
	catch (DIMEParserException *e)
	{
    	m_wstrErrorMsg = L"Request: Could not initialize DIME parser.";
		delete e;
		return false;
	}
    // using POST?
    bool bPost = false;
    if (strHTTPMethod == "POST")
    {
        bPost = true;
        // prepare request message
        wstring wstrSOAPEnvNS;
        if (m_nSOAPVersion == SOAP_VERSION_1_1)
        {
            wstrSOAPEnvNS = g_pwszSoapEnvelopeNS;
        }
        else
        {
            wstrSOAPEnvNS = g_pwszSoap12EnvelopeNS;
        }
        // serialize request
        if (m_RequestMsg.Serialize(m_strRequestXML, m_nSOAPVersion) == false)
        {
    	    m_wstrErrorMsg = L"Request: Failed to serialize request message.";
            return false;
        }
        bUsingDimePkg = (m_arrPayloads.empty() == false || m_bForceDIMEPackaging == true);
        if (bUsingDimePkg == true)
        {
            string strType;
            WideStringToUTF8(wstrSOAPEnvNS, strType);
	        // add primary payload
	        if (d.AddPayload(   m_strRequestXML.c_str(),
						        m_strRequestXML.length(),
						        string(""),
						        strType,
						        DIME_TYPENAME_FORMAT_URI) == false)
	        {
   		        m_wstrErrorMsg = L"Request: Could not add primary payload to DIME package.";
		        return false;
	        }
            vector<DIMEPayload *>::iterator iV;
	        for (iV = m_arrPayloads.begin(); iV != m_arrPayloads.end(); )
	        {
                if (d.AddPayload(*iV) == false)
                {
    		        m_wstrErrorMsg = L"Request: Could not add secondary payload to DIME package.";
                    return false;
                }
                iV = m_arrPayloads.erase(iV);
	        }
    	    ulMsgLength = d.CalcSerialLength();
        }
        else
        {
            ulMsgLength = m_strRequestXML.length();
        }
    }
    // get SOAPAction value
    string strSOAPAction;
    bool bSARequired = true;
	BSTR bstrSOAPAction;
    BSTR bstrOpName = SysAllocString(wstrOperation.c_str());
    VARIANT_BOOL bResult;
	m_pIwmwsdlclient->GetMethodSOAPAction(bstrOpName, &bstrSOAPAction, &bResult);
    if (bResult == VARIANT_TRUE)
    {
	    WideStringToUTF8(wstring(bstrSOAPAction), strSOAPAction);
    	SysFreeString(bstrSOAPAction);
	}
    if (m_nSOAPVersion == SOAP_VERSION_1_2)
    {
        // is SA required?
        VARIANT_BOOL bSOAPActionRequired;
        m_pIwmwsdlclient->SOAPActionRequired(bstrOpName, &bSOAPActionRequired, &bResult);
        if (bResult == VARIANT_TRUE)
        {
            bSARequired = (bSOAPActionRequired == VARIANT_TRUE ? true : false);
	    }
    }
    SysFreeString(bstrOpName);

	// build HTTP Header
	char szBuffer[128];
	string strHeaders = "Host: ";
	strHeaders += m_strHost;
	_snprintf(szBuffer, 127, ":%d\r\n", m_nPortNbr);
    szBuffer[127] = 0;
	strHeaders += szBuffer;
	strHeaders += "User-Agent: ";
	strHeaders += m_strUserAgent;
    strHeaders += "\r\n";
    if (bPost == true)
    {
	    if (bUsingDimePkg == true)
	    {
		    strHeaders += "Content-Type: application/dime\r\n";
	    }
	    else
	    {
            if (m_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 (bSARequired == true)
                {
                    strHeaders += "; action=";
                    strHeaders += strSOAPAction;
                }
                strHeaders += "\r\n";
            }
	    }
	    _snprintf(szBuffer, 127, "Content-Length: %d\r\n", ulMsgLength);
        szBuffer[127] = 0;
	    strHeaders += szBuffer;
        if (m_nSOAPVersion == SOAP_VERSION_1_1 && bSARequired == true)
        {
	        strHeaders += "SOAPAction: ";
	        strHeaders += strSOAPAction;
	        strHeaders += "\r\n";
        }
    }
    // instantiate socket
	WMDIMERequestSock sock;
    // make request
    bool bSuccess;
    if (bUsingDimePkg == true)
    {
	    // make DIME request
        if (m_bUseProxy == true)
        {
            // modify request URI
            char szBuffer[16];
	        _snprintf(szBuffer, 15, ":%d", m_nPortNbr);
            szBuffer[15] = 0;
            string strTemp("http://");
            strTemp += m_strHost;
            strTemp += szBuffer;
            strTemp += m_strSvcURI;
            strTemp += strExtraPath;
	        bSuccess = sock.SendHTTPRequest(m_nProxyPort,
                                            strHTTPMethod,
		                                    m_strProxyHost,
                                            strTemp,
                                            strQueryString,
                                            strHeaders,
                                            &d);
        }
        else
        {
            // build request URI
            string strTemp(m_strSvcURI);
            strTemp += strExtraPath;
	        bSuccess = sock.SendHTTPRequest(m_nPortNbr,
                                            strHTTPMethod,
		                                    m_strHost,
                                            strTemp,
                                            strQueryString,
                                            strHeaders,
                                            &d);
        }
    }
    else
    {
        if (m_bUseProxy == true)
        {
            // modify request URI
            char szBuffer[16];
	        _snprintf(szBuffer, 15, ":%d", m_nPortNbr);
            szBuffer[15] = 0;
            string strTemp("http://");
            strTemp += m_strHost;
            strTemp += szBuffer;
            strTemp += m_strSvcURI;
            strTemp += strExtraPath;
	        bSuccess = sock.SendHTTPRequest(m_nProxyPort,
                                            strHTTPMethod,
		                                    m_strProxyHost,
                                            strTemp,
                                            strQueryString,
                                            strHeaders,
                                            m_strRequestXML,
                                            &d);
        }
        else
        {
            // build request URI
            string strTemp(m_strSvcURI);
            strTemp += strExtraPath;
	        bSuccess = sock.SendHTTPRequest(m_nPortNbr,
                                            strHTTPMethod,
		                                    m_strHost,
                                            strTemp,
                                            strQueryString,
                                            strHeaders,
                                            m_strRequestXML,
                                            &d);
        }
    }
    if (bSuccess == false)
	{
		m_wstrErrorMsg = L"Request: Could not connect to server.";
		return false;
	}
	// get HTTP status code
	m_nHTTPStatus = sock.GetStatus();
    // no content?
    if (m_nHTTPStatus == 204)
	{
		return true;
	}
    // reduce status code to generic value
    m_nHTTPStatus = (m_nHTTPStatus / 100) * 100;
	switch(m_nHTTPStatus)
	{
		case 400:
		{
            if (m_nSOAPVersion == SOAP_VERSION_1_1)
            {
			    m_wstrErrorMsg = L"Response: Unexpected HTTP Status received.";
			    break;
            }
		}
		case 200:
		case 500:
		{
			// is this dime??
			if (sock.GetHeader("Content-Type").find("application/dime") != string::npos)
			{
                m_bIsDIMEResponse = true;
				char szTemp[4096];
				unsigned long ulNumBytes = 0;
				int counter = 0;
				vector<DIMEPayload *>::iterator iter;
				for (iter = d.GetPayloads().begin(); iter != d.GetPayloads().end(); )
				{
					DIMEPayload *pPayload = *iter;
                    iter = d.GetPayloads().erase(iter);
                    if (counter == 0)
                    {
                        // primary payload - SOAP envelope
					    pPayload->OpenForRead();
					    do
					    {
						    pPayload->Read(szTemp, 4096, &ulNumBytes);
                            m_strResponseXML.append(szTemp, ulNumBytes);
					    } while (ulNumBytes == 4096);
					    pPayload->Close();
                        delete pPayload;
                        if (m_ResponseMsg.Deserialize(m_strResponseXML) == false)
                        {
			                m_wstrErrorMsg = L"Response: Failed to deserialize message.";
			                return false;
                        }
                        if (m_ResponseMsg.GetSOAPVersion() != m_nSOAPVersion)
                        {
			                m_wstrErrorMsg = L"Response: Unexpected SOAP Version.";
			                return false;
                        }
                    }
                    else
                    {
                        // secondary payload
                        m_arrPayloads.push_back(pPayload);
                    }
                    counter++;
				}
                d.GetPayloads().clear();
			}
			else if (   (m_nSOAPVersion == SOAP_VERSION_1_1 &&
                        sock.GetHeader("Content-Type").find("text/xml") != string::npos) ||
                        sock.GetHeader("Content-Type").find("application/soap+xml") != string::npos)
			{
                // non-DIME packaged SOAP response
                m_strResponseXML.append(sock.GetContent(), sock.GetContentLength());
                if (m_ResponseMsg.Deserialize(m_strResponseXML) == false)
                {
			        m_wstrErrorMsg = L"Response: Failed to deserialize message.";
			        return false;
                }
                if (m_ResponseMsg.GetSOAPVersion() != m_nSOAPVersion)
                {
			        m_wstrErrorMsg = L"Response: Unexpected SOAP Version.";
			        return false;
                }
			}
            else
            {
                // non-SOAP response
			    m_wstrErrorMsg = L"Response: Unexpected response message type.";
                return false;
            }
            return true;
		}
		default:
		{
			m_wstrErrorMsg = L"Response: Unexpected HTTP Status received.";
			break;
		}
	}
    return false;
}

bool WMDimeDocClient::ReadWSDLDocument( const wstring& wstrWSDLFileURL,
                                        const wstring& wstrServiceName,
                                        const wstring& wstrPortName)
{
    if (m_pIwmwsdlclient != NULL)
    {
        m_pIwmwsdlclient->Release();
    }
	// instantiate WSDL client object
	Iwmwsdlclient2 *pIwmwsdlclient;
	HRESULT res = CoCreateInstance(CLSID_wmwsdlclient2,
									NULL, CLSCTX_SERVER,
									IID_Iwmwsdlclient2,
									(void **) &pIwmwsdlclient);
	if (FAILED(res))
	{
		return false;
	}
	BSTR bstrSvcName = SysAllocString(wstrServiceName.c_str());
	BSTR bstrPortName = SysAllocString(wstrPortName.c_str());
	BSTR bstrWSDLFileURL = SysAllocString(wstrWSDLFileURL.c_str());
	VARIANT_BOOL bResult;
	// 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();
		return false;
	}
	// get WSDL service info items
	BSTR bstrHost, bstrURI;
	pIwmwsdlclient->GetSOAPAddress(&bstrHost, &bstrURI, &m_nPortNbr, &bResult);
    if (bResult == VARIANT_FALSE)
    {
        pIwmwsdlclient->Release();
        return false;
    }

	WideStringToUTF8(wstring(bstrHost), m_strHost);
	WideStringToUTF8(wstring(bstrURI), m_strSvcURI);
	SysFreeString(bstrHost);
	SysFreeString(bstrURI);
    // cache pointer
    m_pIwmwsdlclient = pIwmwsdlclient;
    // get SOAP version
    m_nSOAPVersion = SOAP_VERSION_1_1;
    BSTR bstrBindingVersion = NULL;
    pIwmwsdlclient->GetSOAPBindingVersion(&bstrBindingVersion, &bResult);
    if (bResult == VARIANT_TRUE)
    {
        wstring wstrVersionURI(bstrBindingVersion);
        SysFreeString(bstrBindingVersion);
        if (wstrVersionURI == L"http://schemas.xmlsoap.org/wsdl/soap12/")
        {
            m_nSOAPVersion = SOAP_VERSION_1_2;
        }
    }
    return true;
}

DIMEPayload *WMDimeDocClient::GetPayload(const wstring& wstrId)
{
    string strId;
    WideStringToUTF8(wstrId, strId);
	vector<DIMEPayload *>::iterator iter;
	for (iter = m_arrPayloads.begin();
				iter != m_arrPayloads.end(); iter++)
	{
		DIMEPayload *pPayload = *iter;
		if (strId == pPayload->GetId())
		{
			// Id matches
            return pPayload;
        }
    }
    return NULL;
}
