/** Implementation of the ISAPI_Server class 
  * @file ISAPI_Server.cpp
  * @author Christian Aberger 
  * Copyright (C) 2001 WebWare (http://www.webware.at) 
  * Load a gosap server dll dynamically (if not already loaded) and serve the request.
  * See http://www.aberger.at/SOAP for documentation.
  * 
  */

#include <memory>
#include <strstream>
#ifdef __DEBUG_ISAPI
#include <fstream>
#endif
#include <cassert>
using namespace std;

#include "ISAPI_Server.h"
#include "isapistream.h"
#include "ISAPI_HttpContext.h"
#include "ISAPI_SoapServerFactory.h"

static const char *GSOAP_ID = "mod_gsoap isapi extension 0.0.2";
static const char *crlf = "\r\n";

///> helper function to build full path to dll.
static string MakeDllName(EXTENSION_CONTROL_BLOCK *pECB, const char *pszDllName); 

/* --- plugin functions -- */
static int mod_gsoap_plugin_copy(struct soap *soap, struct soap_plugin *dst, struct soap_plugin *src) {
	*dst = *src;
	return SOAP_OK;
}
static void mod_gsoap_delete(struct soap *soap, struct soap_plugin *p) {
}
static int mod_gsoap_plugin(struct soap *soap, struct soap_plugin *p, void *arg) {
	p->id = GSOAP_ID;
	p->data = arg;
	p->fcopy = mod_gsoap_plugin_copy;
	p->fdelete = mod_gsoap_delete;
	return SOAP_OK;
} 

/** bundle the request/response data into an object to be able to attach it to soap. */
class SoapTransaction {
public:
    SoapTransaction(soap *);
    virtual ~SoapTransaction();

    static SoapTransaction *transaction(soap *);
    void SendHeaders(); ///< send output headers of gsoap to client, if not done so already.
    size_t SupplyRequestHeaders(char *pBuf, const size_t len); // send input request headers to gsoap.
    void BuildHeaders(); ///< build the headers that must be sent to gsoap.
public:
    soap *_soap;
    std::string _header; ///< header part returned to client e.g. "200 OK".
	std::string _request_header; ///< the (remaining part of ) the first line of the request.
    DWORD _dwReturnCode; ///< the return code for the HttpExtensionProc.
    ISAPI_HttpRequest *_request;
    HttpResponse *_response;
    isapistream *_istream;
    const mod_gsoap_interface *_interface;
#ifdef _DEBUG_ISAPI
	ofstream *_debug_in; ///< debugging output stream
	ofstream *_debug_out; ///< debugging output stream
#endif
protected:
    bool _headers_sent; ///< true if Http headers have already been sent.
    bool _headers_supplied; ///< true if Http headers have already supplied to gsoap.
};

SoapTransaction::SoapTransaction(soap *soap) 
: _soap(soap)
, _dwReturnCode(HSE_STATUS_SUCCESS)
, _header("200 OK")
, _request(NULL)
, _response(NULL) 
, _interface(NULL) 
, _istream(NULL)
, _headers_sent(false)
, _headers_supplied(false)
#ifdef _DEBUG_ISAPI
, _debug_in(NULL)
, _debug_out(NULL)
#endif
{
    assert(soap);
#ifdef _DEBUG_ISAPI
	TCHAR szPath[_MAX_PATH];
	::GetTempPath(sizeof szPath, szPath);
	string strIn = szPath;
	string strOut = szPath;
	strIn += "mod_gsoap_debug_in.log";
	strOut += "mod_gsoap_debug_out.log";
	strOut += "mod_gsoap_debug.log";
	_debug_in = new ofstream(strIn.c_str(), ios::out | ios::app);
	_debug_out = new ofstream(strOut.c_str(), ios::out | ios::app);
	*_debug_in << "--------------------------------START--------------------" << endl;
	*_debug_out << "--------------------------------START--------------------" << endl;
#endif
}
SoapTransaction::~SoapTransaction() {
#ifdef _DEBUG_ISAPI
	*_debug_in << "--------------------------------STOP---------------------" << endl;
	*_debug_out << "--------------------------------STOP---------------------" << endl;
	delete _debug_in;
	delete _debug_out;
#endif
}
inline SoapTransaction *SoapTransaction::transaction(soap *soap) {
    assert(NULL != soap);
	return reinterpret_cast<SoapTransaction *>(soap->fplugin(soap, GSOAP_ID));
}
void SoapTransaction::SendHeaders() {
    if (_headers_sent) {
        return;
    }
    ostrstream headers;
    for (HttpMessage::ContentHeaders::const_iterator it = _response->getContentHeaders().begin(); it != _response->getContentHeaders().end(); ++it) {
		if (0 != strcasecmp(it->first.c_str(), "Connection")) {
	        headers << it->first << ": " << it->second << crlf;
		} 
    }
    headers << crlf;
	string strOut(headers.str(), headers.pcount());
#ifdef _DEBUG_ISAPI
	*_debug_out << strOut;
#endif
    BOOL bHeaders = _request->ECB()->ServerSupportFunction(_request->ECB()->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
        (LPVOID)"200 OK", NULL, (LPDWORD)strOut.c_str());
    headers.freeze(false);
    _headers_sent = true;
}
/** Emits HTTP key: val header entries. 
    @return SOAP_OK, or a gSOAP error code. 
    Built-in gSOAP function: http_post_header. 

    IIS handles most of the headers (Connection etc.) itself. 
    The main thing we need to extract is SOAPAction.
*/
static int http_post_header(soap *soap, const char *key, const char *value) {
    if (NULL != key) {
        SoapTransaction *pTrans = SoapTransaction::transaction(soap);
        if (value) {
            pTrans->_response->getContentHeaders()[key] = value;
        }
    }
	return SOAP_OK;
}
/** set the http - response code from the soap error message. */
static int http_response(soap *soap, int soap_error, size_t count) {
    SoapTransaction *pTrans = SoapTransaction::transaction(soap);
    if (SOAP_OK == soap_error) {
        pTrans->_dwReturnCode = HSE_STATUS_SUCCESS;
    } else {
        pTrans->_dwReturnCode = HSE_STATUS_ERROR;
        pTrans->_header = "500 ERR";
    }
    return SOAP_OK;
}
/** gsoap function to append the data to send to our output buffer */
static int fsend(soap *soap, const char *pBuf, size_t len) {
    SoapTransaction *pTrans = SoapTransaction::transaction(soap);
    pTrans->SendHeaders();
#ifdef _DEBUG_ISAPI
	pTrans->_debug_out->write(pBuf, len);
#endif
    pTrans->_istream->write(pBuf, len);
    return SOAP_OK;
}
size_t SoapTransaction::SupplyRequestHeaders(char *pBuf, const size_t len) {
    int nLen = 0;
    if (!_headers_supplied) {
		if (!_request_header.empty()) { // we must send 1st line so that gsoap parses everything correctly.
			nLen = _request_header.length();
			if (nLen > len) {
				nLen = len;
				memcpy(pBuf, _request_header.c_str(), len);
				_request_header = _request_header.substr(len);
			} else {
				memcpy(pBuf, _request_header.c_str(), _request_header.length());
				_request_header.erase();                
				_headers_supplied = true;
			}
#ifdef _DEBUG_ISAPI
			_debug_in->write(pBuf, nLen);
#endif
		}
	}
    return nLen;
}
/** gsoap function that requests the next piece of data from us */
static size_t frecv(soap *soap, char *pBuf, size_t len) {
    SoapTransaction *pTrans = SoapTransaction::transaction(soap);
    size_t nLen = pTrans->SupplyRequestHeaders(pBuf, len); 
    if (0 == nLen) { // query string and headers have been sent already.
        assert(pTrans->_istream->good());
		if (!pTrans->_istream->eof()) {
			pTrans->_istream->readsome(pBuf, len);
			nLen = pTrans->_istream->gcount();
#ifdef _DEBUG_ISAPI
			pTrans->_debug_in->write(pBuf, nLen);
#endif
			assert(pTrans->_istream->good());
		}
    }
    return nLen;
}

void SoapTransaction::BuildHeaders() {
	ostrstream s;
	/* we must rebuild the 1st request line, cause IIS has no API to get it :( */
	s << "POST /" + _request->_querystring + " HTTP/1.0\r\n";
    for (ISAPI_HttpRequest::ContentHeaders::const_iterator it = _request->getContentHeaders().begin(); it != _request->getContentHeaders().end(); ++it) {
		s << it->first << ": " << it->second << crlf;
    }
	s << crlf;
	_request_header.assign(s.str(), s.pcount());
}
BOOL ISAPI_Server::GetExtensionVersion(HSE_VERSION_INFO *pVer) {
    lstrcpyn((LPSTR) pVer->lpszExtensionDesc, "WebWare SOAP ISAPI extension", HSE_MAX_EXT_DLL_NAME_LEN);
    return TRUE;
}
BOOL ISAPI_Server::TerminateExtension(DWORD) {
	ISAPI_SoapServerFactory::instance()->shutdown();
    return TRUE;
}
/* forwards the real work to soap_serve */
static DWORD serve(
	const mod_gsoap_interface *pInterface, 
	ISAPI_HttpRequest& req,
    HttpResponse res,
	isapistream& is) 
{
	assert(NULL != pInterface && pInterface->linked());

	soap soap;
	(*pInterface->fsoap_init)(&soap);
    SoapTransaction trans(&soap);
    if (NULL != pInterface->fsoap_register_plugin_arg) {
		(*pInterface->fsoap_register_plugin_arg)(&soap, mod_gsoap_plugin, (void *)&trans);
	}
    //soap.user = &trans;
    trans._interface = pInterface;
    trans._istream = &is;
    trans._request = &req;
    trans._response = &res;

	trans.BuildHeaders();

#ifdef WITH_ZLIB
	//	always allow gzip in -- but only allow it out if the client can handle it
	soap_set_imode(&soap, SOAP_ENC_ZLIB); 	

	string	str = req.getContentHeaders()["Accept-Encoding"];
	if (strstr(str.c_str(), "gzip"))
	{	
		soap_set_omode(&soap, SOAP_ENC_ZLIB); 		
		http_post_header( &soap, "Content-Encoding", "gzip" );	
	}

#endif

    // set callback functions:
    soap.frecv = frecv;
    soap.fsend = fsend;
    soap.fresponse = http_response;
	soap.fposthdr = http_post_header;

	(*pInterface->fsoap_serve)(&soap);
	if (NULL != pInterface->fsoap_delete) {
		(*pInterface->fsoap_delete)(&soap, NULL);
	}
	(*pInterface->fsoap_done)(&soap);
	(*pInterface->fsoap_end)(&soap);

    return trans._dwReturnCode;
}

static void SendErrorMessage(isapistream& is, const char *pszError) {
    is << "<html><title>gsoap error message</title><body>" << pszError << "<p>";
    is << "see <a href=\"http://www.aberger.at/SOAP\">WebWare gsoap ISAPI module</a> documentation.";
    is << "</body><html>";
}

/** Parse the request, load dll if not already loaded, let it create the response and return the result.
  */
DWORD ISAPI_Server::HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) {
    DWORD dwRes = HSE_STATUS_SUCCESS;
    ISAPI_HttpRequest req(pECB);
    HttpResponse res;

    isapistream is(pECB);
    
    string strQuery = req.getQueryString();
    string strDll = strQuery;
    string::size_type pos = strQuery.find('&');
    if (string::npos != pos) {
        strDll = strQuery.substr(0, pos);
        strQuery = strQuery.substr(pos + 1);
    }
	if (!strDll.empty()) {
		strDll += ".dll";
		strDll = MakeDllName(pECB, strDll.c_str()); // due to security reasons we only allow loading of dlls from the local directory (which must not be writable!).
		const mod_gsoap_interface *pInterface = ISAPI_SoapServerFactory::instance()->getInterface(strDll.c_str());
		if (NULL != pInterface) {
			if (HttpRequest::POST == req.getMethod()) {
				dwRes = serve(pInterface, req, res, is);
			} else {
				SendErrorMessage(is, "You must use a POST request to get answer from gsoap !");
			}
		} else {
			is << "could not create server '" << strDll << "'. Check your request if it is correct. "
				<< ISAPI_SoapServerFactory::instance()->getLastError();
		}
	} else {
		SendErrorMessage(is, "No gsoap server dll specified in request. Please add the name of your soap server dll, e.g. http://localhost/gsoap/mod_gsoap?calc");
	}
    is.flush();
    return dwRes;
}

/** Given a dllname make a fully qualified path to the dll in the current isapi directory 
    @param pECB the Extension Control Block of the Request
    @param pszDllName the unqualified dllname
*/
static string MakeDllName(EXTENSION_CONTROL_BLOCK *pECB, const char *pszDllName) {
    string strPath;
    char szPath[_MAX_PATH + 1];
    ZeroMemory(szPath, sizeof szPath);
    DWORD dwBufferSize = sizeof szPath;
    pECB->GetServerVariable(pECB->ConnID, "APPL_PHYSICAL_PATH", szPath, &dwBufferSize);
    strPath = szPath;
    strPath += pszDllName;
    return strPath;
}
