| /** 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; |
| } |