1
/*=============================================================================
3
===============================================================================
5
This is the definition of the xmlrpc_c::server_cgi class. An object of
6
this class is the guts of a CGI-based XML-RPC server. It runs inside
7
a CGI script and gets the XML-RPC call from and delivers the XML-RPC
8
response to the CGI environment.
10
By Bryan Henderson 08.09.17.
12
Contributed to the public domain by its author.
13
=============================================================================*/
19
#include "xmlrpc-c/girerr.hpp"
21
#include "xmlrpc-c/server_cgi.hpp"
33
bool contentTypePresent;
35
unsigned int contentLength;
36
bool contentLengthPresent;
37
bool authCookiePresent;
42
const char * const requestMethodC = getenv("REQUEST_METHOD");
43
const char * const contentTypeC = getenv("CONTENT_TYPE");
44
const char * const contentLengthC = getenv("CONTENT_LENGTH");
45
const char * const authCookieC = getenv("HTTP_COOKIE_AUTH");
48
this->requestMethod = string(requestMethodC);
50
throwf("Invalid CGI environment; environment variable "
51
"REQUEST_METHOD is not set");
54
this->contentTypePresent = true;
55
this->contentType = string(contentTypeC);
57
this->contentTypePresent = false;
60
this->contentLengthPresent = true;
62
int const lengthAtoi(atoi(string(contentLengthC).c_str()));
65
throwf("Content-length HTTP header value is negative");
66
else if (lengthAtoi == 0)
67
throwf("Content-length HTTP header value is zero");
69
this->contentLength = lengthAtoi;
71
this->contentLengthPresent = false;
74
this->authCookie = string(authCookieC);
75
this->authCookiePresent = true;
77
this->authCookiePresent = false;
90
httpError(int const code,
92
code(code), msg(msg) {}
102
struct serverCgi_impl {
103
// 'registryP' is what we actually use; 'registryHolder' just holds a
104
// reference to 'registryP' so the registry doesn't disappear while
105
// this server exists. But note that if the creator doesn't supply
106
// a registryPtr, 'registryHolder' is just a placeholder variable and
107
// the creator is responsible for making sure the registry doesn't
108
// go anywhere while the server exists.
110
registryPtr registryHolder;
111
const registry * registryP;
113
serverCgi_impl(serverCgi::constrOpt const& opt);
116
establishRegistry(serverCgi::constrOpt const& opt);
126
serverCgi_impl::establishRegistry(serverCgi::constrOpt const& opt) {
128
if (!opt.present.registryP && !opt.present.registryPtr)
129
throwf("You must specify the 'registryP' or 'registryPtr' option");
130
else if (opt.present.registryP && opt.present.registryPtr)
131
throwf("You may not specify both the 'registryP' and "
132
"the 'registryPtr' options");
134
if (opt.present.registryP)
135
this->registryP = opt.value.registryP;
137
this->registryHolder = opt.value.registryPtr;
138
this->registryP = opt.value.registryPtr.get();
145
serverCgi_impl::serverCgi_impl(serverCgi::constrOpt const& opt) {
146
this->establishRegistry(opt);
151
serverCgi::constrOpt::constrOpt() {
153
present.registryP = false;
154
present.registryPtr = false;
159
#define DEFINE_OPTION_SETTER(OPTION_NAME, TYPE) \
160
serverCgi::constrOpt & \
161
serverCgi::constrOpt::OPTION_NAME(TYPE const& arg) { \
162
this->value.OPTION_NAME = arg; \
163
this->present.OPTION_NAME = true; \
167
DEFINE_OPTION_SETTER(registryP, const registry *);
168
DEFINE_OPTION_SETTER(registryPtr, xmlrpc_c::registryPtr);
170
#undef DEFINE_OPTION_SETTER
174
serverCgi::serverCgi(constrOpt const& opt) {
176
this->implP = new serverCgi_impl(opt);
181
serverCgi::~serverCgi() {
189
#define FILEVAR fileP
195
setModeBinary(FILE * const FILEVAR) {
198
/* Fix from Jeff Stewart: NT opens stdin and stdout in text mode
199
by default, badly confusing our length calculations. So we need
200
to set the file handle to binary.
202
_setmode(_fileno(FILEVAR), _O_BINARY);
209
getHttpBody(FILE * const fileP,
210
size_t const length) {
212
setModeBinary(fileP);
213
char * const buffer(new char[length]);
214
auto_ptr<char> p(buffer); // To make it go away when we leave
218
count = fread(buffer, sizeof(buffer[0]), length, fileP);
220
throwf("Expected %lu bytes, received %lu",
221
(unsigned long) length, (unsigned long) count);
223
return string(buffer, length);
229
writeNormalHttpResp(FILE * const fileP,
230
bool const sendCookie,
231
string const& authCookie,
232
string const& httpBody) {
234
setModeBinary(fileP);
238
fprintf(fileP, "Status: 200 OK\n");
241
fprintf(fileP, "Set-Cookie: auth=%s\n", authCookie.c_str());
243
fprintf(fileP, "Content-type: text/xml; charset=\"utf-8\"\n");
244
fprintf(fileP, "Content-length: %u\n", httpBody.size());
245
fprintf(fileP, "\n");
249
fwrite(httpBody.c_str(), sizeof(char), httpBody.size(), fileP);
255
processCall2(const registry * const registryP,
256
FILE * const callFileP,
257
unsigned int const callSize,
258
bool const sendCookie,
259
string const& authCookie,
260
FILE * const respFileP) {
262
if (callSize > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
263
throw(xmlrpc_c::fault(string("XML-RPC call is too large"),
264
fault::CODE_LIMIT_EXCEEDED));
266
string const callXml(getHttpBody(callFileP, callSize));
271
registryP->processCall(callXml, &responseXml);
272
} catch (exception const& e) {
273
throw(httpError(500, e.what()));
276
writeNormalHttpResp(respFileP, sendCookie, authCookie, responseXml);
284
sendHttpErrorResp(FILE * const fileP,
285
httpError const& e) {
287
setModeBinary(fileP);
291
fprintf(fileP, "Status: %d %s\n", e.code, e.msg.c_str());
292
fprintf(fileP, "Content-type: text/html\n");
293
fprintf(fileP, "\n");
295
// HTTP body: HTML error message
297
fprintf(fileP, "<title>%d %s</title>\n", e.code, e.msg.c_str());
298
fprintf(fileP, "<h1>%d %s</h1>\n", e.code, e.msg.c_str());
299
fprintf(fileP, "<p>The Xmlrpc-c CGI server was unable to process "
300
"your request. It could not process it even enough to generate "
301
"an XML-RPC fault response.</p>\n");
307
serverCgi_impl::tryToProcessCall() {
311
if (httpInfo.requestMethod != string("POST"))
312
throw(httpError(405, "Method must be POST"));
314
if (!httpInfo.contentTypePresent)
315
throw(httpError(400, "Must have content-type header"));
317
if (httpInfo.contentType != string("text/xml"))
318
throw(httpError(400, string("ContentType must be 'text/xml', not '") +
319
httpInfo.contentType + string("'")));
321
if (!httpInfo.contentLengthPresent)
322
throw(httpError(411, "Content-length required"));
324
processCall2(this->registryP, stdin, httpInfo.contentLength,
325
httpInfo.authCookiePresent, httpInfo.authCookie, stdout);
331
serverCgi::processCall() {
332
/*----------------------------------------------------------------------------
333
Get the XML-RPC call from Standard Input and environment variables,
334
parse it, find the right method, call it, prepare an XML-RPC
335
response with the result, and write it to Standard Output.
336
-----------------------------------------------------------------------------*/
338
this->implP->tryToProcessCall();
339
} catch (httpError const e) {
340
sendHttpErrorResp(stdout, e);