~ubuntu-branches/ubuntu/natty/xmlrpc-c/natty

« back to all changes in this revision

Viewing changes to src/cpp/server_cgi.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2011-01-06 18:56:02 UTC
  • mfrom: (1.1.6 upstream)
  • Revision ID: james.westby@ubuntu.com-20110106185602-09og2x3suqlzbf6s
Tags: 1.16.32-0ubuntu1
* New upstream version (stable release). LP: #659591.
  - No unresolved symbols in the shared libraries. LP: #690779.
  - Builds with --no-add-needed and --as-needed.
* Rename shared library packages.
* Add symbols files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*=============================================================================
 
2
                               server_cgi
 
3
===============================================================================
 
4
 
 
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.
 
9
 
 
10
   By Bryan Henderson 08.09.17.
 
11
 
 
12
   Contributed to the public domain by its author.
 
13
=============================================================================*/
 
14
 
 
15
#include <memory>
 
16
#include <stdio.h>
 
17
#include <cstdlib>
 
18
 
 
19
#include "xmlrpc-c/girerr.hpp"
 
20
using girerr::throwf;
 
21
#include "xmlrpc-c/server_cgi.hpp"
 
22
 
 
23
using namespace std;
 
24
 
 
25
 
 
26
 
 
27
namespace {
 
28
 
 
29
class httpInfo {
 
30
 
 
31
public:
 
32
    string requestMethod;
 
33
    bool contentTypePresent;
 
34
    string contentType;
 
35
    unsigned int contentLength;
 
36
    bool contentLengthPresent;
 
37
    bool authCookiePresent;
 
38
    string authCookie;
 
39
 
 
40
    httpInfo() {
 
41
 
 
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");
 
46
 
 
47
        if (requestMethodC)
 
48
            this->requestMethod = string(requestMethodC);
 
49
        else
 
50
            throwf("Invalid CGI environment; environment variable "
 
51
                   "REQUEST_METHOD is not set");
 
52
 
 
53
        if (contentTypeC) {
 
54
            this->contentTypePresent = true;
 
55
            this->contentType = string(contentTypeC);
 
56
        } else
 
57
            this->contentTypePresent = false;
 
58
 
 
59
        if (contentLengthC) {
 
60
            this->contentLengthPresent = true;
 
61
 
 
62
            int const lengthAtoi(atoi(string(contentLengthC).c_str()));
 
63
 
 
64
            if (lengthAtoi < 0)
 
65
                throwf("Content-length HTTP header value is negative");
 
66
            else if (lengthAtoi == 0)
 
67
                throwf("Content-length HTTP header value is zero");
 
68
            else
 
69
                this->contentLength = lengthAtoi;
 
70
        } else
 
71
            this->contentLengthPresent = false;
 
72
 
 
73
        if (authCookieC) {
 
74
            this->authCookie = string(authCookieC);
 
75
            this->authCookiePresent = true;
 
76
        } else
 
77
            this->authCookiePresent = false;
 
78
    }
 
79
};
 
80
 
 
81
 
 
82
 
 
83
class httpError {
 
84
 
 
85
public:
 
86
 
 
87
    int const code;
 
88
    string const msg;
 
89
    
 
90
    httpError(int    const code,
 
91
              string const& msg) :
 
92
        code(code), msg(msg) {}
 
93
};
 
94
 
 
95
 
 
96
} // namespace
 
97
 
 
98
 
 
99
 
 
100
namespace xmlrpc_c {
 
101
 
 
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.
 
109
 
 
110
    registryPtr registryHolder;
 
111
    const registry * registryP;
 
112
 
 
113
    serverCgi_impl(serverCgi::constrOpt const& opt);
 
114
 
 
115
    void
 
116
    establishRegistry(serverCgi::constrOpt const& opt);
 
117
 
 
118
    void
 
119
    tryToProcessCall();
 
120
};
 
121
 
 
122
 
 
123
 
 
124
 
 
125
void
 
126
serverCgi_impl::establishRegistry(serverCgi::constrOpt const& opt) {
 
127
 
 
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");
 
133
    else {
 
134
        if (opt.present.registryP)
 
135
            this->registryP      = opt.value.registryP;
 
136
        else {
 
137
            this->registryHolder = opt.value.registryPtr;
 
138
            this->registryP      = opt.value.registryPtr.get();
 
139
        }
 
140
    }
 
141
}
 
142
 
 
143
 
 
144
 
 
145
serverCgi_impl::serverCgi_impl(serverCgi::constrOpt const& opt) {
 
146
    this->establishRegistry(opt);
 
147
}
 
148
 
 
149
 
 
150
 
 
151
serverCgi::constrOpt::constrOpt() {
 
152
 
 
153
    present.registryP   = false;
 
154
    present.registryPtr = false;
 
155
}
 
156
 
 
157
 
 
158
 
 
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; \
 
164
    return *this; \
 
165
}
 
166
 
 
167
DEFINE_OPTION_SETTER(registryP,   const registry *);
 
168
DEFINE_OPTION_SETTER(registryPtr, xmlrpc_c::registryPtr);
 
169
 
 
170
#undef DEFINE_OPTION_SETTER
 
171
 
 
172
 
 
173
 
 
174
serverCgi::serverCgi(constrOpt const& opt) {
 
175
 
 
176
    this->implP = new serverCgi_impl(opt);
 
177
}
 
178
 
 
179
 
 
180
 
 
181
serverCgi::~serverCgi() {
 
182
 
 
183
    delete(this->implP);
 
184
}
 
185
 
 
186
 
 
187
 
 
188
#ifdef _WIN32
 
189
#define FILEVAR fileP
 
190
#else
 
191
#define FILEVAR
 
192
#endif
 
193
 
 
194
static void
 
195
setModeBinary(FILE * const FILEVAR) {
 
196
 
 
197
#ifdef _WIN32 
 
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. 
 
201
    */
 
202
    _setmode(_fileno(FILEVAR), _O_BINARY); 
 
203
#endif 
 
204
}
 
205
 
 
206
 
 
207
 
 
208
static string
 
209
getHttpBody(FILE * const fileP,
 
210
            size_t const length) {
 
211
 
 
212
    setModeBinary(fileP);
 
213
    char * const buffer(new char[length]);
 
214
    auto_ptr<char> p(buffer);  // To make it go away when we leave
 
215
 
 
216
    size_t count;
 
217
 
 
218
    count = fread(buffer, sizeof(buffer[0]), length, fileP);
 
219
    if (count < length)
 
220
        throwf("Expected %lu bytes, received %lu",
 
221
               (unsigned long) length, (unsigned long) count);
 
222
 
 
223
    return string(buffer, length);
 
224
}
 
225
 
 
226
 
 
227
 
 
228
static void 
 
229
writeNormalHttpResp(FILE * const  fileP,
 
230
                    bool   const  sendCookie,
 
231
                    string const& authCookie,
 
232
                    string const& httpBody) {
 
233
 
 
234
    setModeBinary(fileP);
 
235
 
 
236
    // HTTP headers
 
237
 
 
238
    fprintf(fileP, "Status: 200 OK\n");
 
239
 
 
240
    if (sendCookie)
 
241
        fprintf(fileP, "Set-Cookie: auth=%s\n", authCookie.c_str());
 
242
 
 
243
    fprintf(fileP, "Content-type: text/xml; charset=\"utf-8\"\n");
 
244
    fprintf(fileP, "Content-length: %u\n", httpBody.size());
 
245
    fprintf(fileP, "\n");
 
246
 
 
247
    // HTTP body
 
248
 
 
249
    fwrite(httpBody.c_str(), sizeof(char), httpBody.size(), fileP);
 
250
}
 
251
 
 
252
 
 
253
 
 
254
void
 
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) {
 
261
 
 
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));
 
265
    else {
 
266
        string const callXml(getHttpBody(callFileP, callSize));
 
267
 
 
268
        string responseXml;
 
269
 
 
270
        try {
 
271
            registryP->processCall(callXml, &responseXml);
 
272
        } catch (exception const& e) {
 
273
            throw(httpError(500, e.what()));
 
274
        }
 
275
        
 
276
        writeNormalHttpResp(respFileP, sendCookie, authCookie, responseXml);
 
277
    }
 
278
}
 
279
 
 
280
 
 
281
 
 
282
 
 
283
static void
 
284
sendHttpErrorResp(FILE *    const  fileP,
 
285
                  httpError const& e) {
 
286
 
 
287
    setModeBinary(fileP);
 
288
 
 
289
    // HTTP headers
 
290
 
 
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");
 
294
    
 
295
    // HTTP body: HTML error message
 
296
 
 
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");
 
302
}
 
303
 
 
304
 
 
305
 
 
306
void
 
307
serverCgi_impl::tryToProcessCall() {
 
308
 
 
309
    httpInfo httpInfo;
 
310
 
 
311
    if (httpInfo.requestMethod != string("POST"))
 
312
        throw(httpError(405, "Method must be POST"));
 
313
 
 
314
    if (!httpInfo.contentTypePresent)
 
315
        throw(httpError(400, "Must have content-type header"));
 
316
 
 
317
    if (httpInfo.contentType != string("text/xml"))
 
318
        throw(httpError(400, string("ContentType must be 'text/xml', not '") +
 
319
                        httpInfo.contentType + string("'")));
 
320
    
 
321
    if (!httpInfo.contentLengthPresent)
 
322
        throw(httpError(411, "Content-length required"));
 
323
              
 
324
    processCall2(this->registryP, stdin, httpInfo.contentLength,
 
325
                 httpInfo.authCookiePresent, httpInfo.authCookie, stdout);
 
326
}
 
327
 
 
328
 
 
329
 
 
330
void
 
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
-----------------------------------------------------------------------------*/
 
337
    try {
 
338
        this->implP->tryToProcessCall();
 
339
    } catch (httpError const e) {
 
340
        sendHttpErrorResp(stdout, e);
 
341
    }
 
342
}
 
343
 
 
344
 
 
345
 
 
346
} // namespace