/*============================================================================= server_cgi =============================================================================== This is the definition of the xmlrpc_c::server_cgi class. An object of this class is the guts of a CGI-based XML-RPC server. It runs inside a CGI script and gets the XML-RPC call from and delivers the XML-RPC response to the CGI environment. By Bryan Henderson 08.09.17. Contributed to the public domain by its author. =============================================================================*/ #include "xmlrpc_config.h" #if MSVCRT #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include #include #endif #include // for getenv #include #include #include "xmlrpc-c/girerr.hpp" using girerr::throwf; #include "xmlrpc-c/server_cgi.hpp" using namespace std; namespace { class httpInfo { public: string requestMethod; bool contentTypePresent; string contentType; unsigned int contentLength; bool contentLengthPresent; bool authCookiePresent; string authCookie; httpInfo() { const char * const requestMethodC = getenv("REQUEST_METHOD"); const char * const contentTypeC = getenv("CONTENT_TYPE"); const char * const contentLengthC = getenv("CONTENT_LENGTH"); const char * const authCookieC = getenv("HTTP_COOKIE_AUTH"); if (requestMethodC) this->requestMethod = string(requestMethodC); else throwf("Invalid CGI environment; environment variable " "REQUEST_METHOD is not set"); if (contentTypeC) { this->contentTypePresent = true; this->contentType = string(contentTypeC); } else this->contentTypePresent = false; if (contentLengthC) { this->contentLengthPresent = true; int const lengthAtoi(atoi(string(contentLengthC).c_str())); if (lengthAtoi < 0) throwf("Content-length HTTP header value is negative"); else if (lengthAtoi == 0) throwf("Content-length HTTP header value is zero"); else this->contentLength = lengthAtoi; } else this->contentLengthPresent = false; if (authCookieC) { this->authCookie = string(authCookieC); this->authCookiePresent = true; } else this->authCookiePresent = false; } }; class httpError { public: int const code; string const msg; httpError(int const code, string const& msg) : code(code), msg(msg) {} }; } // namespace namespace xmlrpc_c { struct serverCgi_impl { // 'registryP' is what we actually use; 'registryHolder' just holds a // reference to 'registryP' so the registry doesn't disappear while // this server exists. But note that if the creator doesn't supply // a registryPtr, 'registryHolder' is just a placeholder variable and // the creator is responsible for making sure the registry doesn't // go anywhere while the server exists. registryPtr registryHolder; const registry * registryP; serverCgi_impl(serverCgi::constrOpt const& opt); void establishRegistry(serverCgi::constrOpt const& opt); void tryToProcessCall(); }; void serverCgi_impl::establishRegistry(serverCgi::constrOpt const& opt) { if (!opt.present.registryP && !opt.present.registryPtr) throwf("You must specify the 'registryP' or 'registryPtr' option"); else if (opt.present.registryP && opt.present.registryPtr) throwf("You may not specify both the 'registryP' and " "the 'registryPtr' options"); else { if (opt.present.registryP) this->registryP = opt.value.registryP; else { this->registryHolder = opt.value.registryPtr; this->registryP = opt.value.registryPtr.get(); } } } serverCgi_impl::serverCgi_impl(serverCgi::constrOpt const& opt) { this->establishRegistry(opt); } serverCgi::constrOpt::constrOpt() { present.registryP = false; present.registryPtr = false; } #define DEFINE_OPTION_SETTER(OPTION_NAME, TYPE) \ serverCgi::constrOpt & \ serverCgi::constrOpt::OPTION_NAME(TYPE const& arg) { \ this->value.OPTION_NAME = arg; \ this->present.OPTION_NAME = true; \ return *this; \ } DEFINE_OPTION_SETTER(registryP, const registry *); DEFINE_OPTION_SETTER(registryPtr, xmlrpc_c::registryPtr); #undef DEFINE_OPTION_SETTER serverCgi::serverCgi(constrOpt const& opt) { this->implP = new serverCgi_impl(opt); } serverCgi::~serverCgi() { delete(this->implP); } #if MSVCRT #define FILEVAR fileP #else #define FILEVAR #endif static void setModeBinary(FILE * const FILEVAR) { #if MSVCRT /* Fix from Jeff Stewart: NT opens stdin and stdout in text mode by default, badly confusing our length calculations. So we need to set the file handle to binary. */ _setmode(_fileno(FILEVAR), _O_BINARY); #endif } static string getHttpBody(FILE * const fileP, size_t const length) { setModeBinary(fileP); char * const buffer(new char[length]); auto_ptr p(buffer); // To make it go away when we leave size_t count; count = fread(buffer, sizeof(buffer[0]), length, fileP); if (count < length) throwf("Expected %lu bytes, received %lu", (unsigned long) length, (unsigned long) count); return string(buffer, length); } static void writeNormalHttpResp(FILE * const fileP, bool const sendCookie, string const& authCookie, string const& httpBody) { setModeBinary(fileP); // HTTP headers fprintf(fileP, "Status: 200 OK\n"); if (sendCookie) fprintf(fileP, "Set-Cookie: auth=%s\n", authCookie.c_str()); fprintf(fileP, "Content-type: text/xml; charset=\"utf-8\"\n"); fprintf(fileP, "Content-length: %u\n", (unsigned)httpBody.size()); fprintf(fileP, "\n"); // HTTP body fwrite(httpBody.c_str(), sizeof(char), httpBody.size(), fileP); } void processCall2(const registry * const registryP, FILE * const callFileP, unsigned int const callSize, bool const sendCookie, string const& authCookie, FILE * const respFileP) { if (callSize > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID)) throw(xmlrpc_c::fault(string("XML-RPC call is too large"), fault::CODE_LIMIT_EXCEEDED)); else { string const callXml(getHttpBody(callFileP, callSize)); string responseXml; try { registryP->processCall(callXml, &responseXml); } catch (exception const& e) { throw(httpError(500, e.what())); } writeNormalHttpResp(respFileP, sendCookie, authCookie, responseXml); } } static void sendHttpErrorResp(FILE * const fileP, httpError const& e) { setModeBinary(fileP); // HTTP headers fprintf(fileP, "Status: %d %s\n", e.code, e.msg.c_str()); fprintf(fileP, "Content-type: text/html\n"); fprintf(fileP, "\n"); // HTTP body: HTML error message fprintf(fileP, "%d %s\n", e.code, e.msg.c_str()); fprintf(fileP, "

%d %s

\n", e.code, e.msg.c_str()); fprintf(fileP, "

The Xmlrpc-c CGI server was unable to process " "your request. It could not process it even enough to generate " "an XML-RPC fault response.

\n"); } void serverCgi_impl::tryToProcessCall() { httpInfo httpInfo; if (httpInfo.requestMethod != string("POST")) throw(httpError(405, "Method must be POST")); if (!httpInfo.contentTypePresent) throw(httpError(400, "Must have content-type header")); if (httpInfo.contentType != string("text/xml")) throw(httpError(400, string("ContentType must be 'text/xml', not '") + httpInfo.contentType + string("'"))); if (!httpInfo.contentLengthPresent) throw(httpError(411, "Content-length required")); processCall2(this->registryP, stdin, httpInfo.contentLength, httpInfo.authCookiePresent, httpInfo.authCookie, stdout); } void serverCgi::processCall() { /*---------------------------------------------------------------------------- Get the XML-RPC call from Standard Input and environment variables, parse it, find the right method, call it, prepare an XML-RPC response with the result, and write it to Standard Output. -----------------------------------------------------------------------------*/ try { this->implP->tryToProcessCall(); } catch (httpError const e) { sendHttpErrorResp(stdout, e); } } } // namespace