blob: e576f52410c0fc99511aba049c4d3dbb42822f7c [file] [log] [blame]
/** 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;
}