python: Remove Python 2.4 support macros
[sfrench/samba-autobuild/.git] / source4 / web_server / wsgi.c
1 /* 
2    Unix SMB/CIFS implementation.
3    Samba utility functions
4    Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
5
6    Implementation of the WSGI interface described in PEP0333 
7    (http://www.python.org/dev/peps/pep-0333)
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <Python.h>
24 #include "includes.h"
25 #include "web_server/web_server.h"
26 #include "../lib/util/dlinklist.h"
27 #include "lib/tls/tls.h"
28 #include "lib/tsocket/tsocket.h"
29 #include "python/modules.h"
30
31 typedef struct {
32         PyObject_HEAD
33         struct websrv_context *web;
34 } web_request_Object;
35
36 static PyObject *start_response(PyObject *self, PyObject *args, PyObject *kwargs)
37 {
38         PyObject *response_header, *exc_info = NULL;
39         char *status;
40         Py_ssize_t i;
41         const char *kwnames[] = {
42                 "status", "response_header", "exc_info", NULL
43         };
44         web_request_Object *py_web = (web_request_Object *)self;
45         struct websrv_context *web = py_web->web;
46         struct http_header *headers = NULL;
47
48         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|O:start_response", discard_const_p(char *, kwnames), &status, &response_header, &exc_info)) {
49                 return NULL;
50         }
51
52         /* FIXME: exc_info */
53
54         if (!PyList_Check(response_header)) {
55                 PyErr_SetString(PyExc_TypeError, "response_header should be list");
56                 return NULL;
57         }
58
59         for (i = 0; i < PyList_Size(response_header); i++) {
60                 struct http_header *hdr = talloc_zero(web, struct http_header);
61                 PyObject *item = PyList_GetItem(response_header, i);
62                 PyObject *py_name, *py_value;
63
64                 if (!PyTuple_Check(item)) {
65                         PyErr_SetString(PyExc_TypeError, "Expected tuple");
66                         return NULL;
67                 }
68
69                 if (PyTuple_Size(item) != 2) {
70                         PyErr_SetString(PyExc_TypeError, "header tuple has invalid size, expected 2");
71                         return NULL;
72                 }
73
74                 py_name = PyTuple_GetItem(item, 0);
75
76                 if (!PyString_Check(py_name)) {
77                         PyErr_SetString(PyExc_TypeError, "header name should be string");
78                         return NULL;
79                 }
80
81                 py_value = PyTuple_GetItem(item, 1);
82                 if (!PyString_Check(py_value)) {
83                         PyErr_SetString(PyExc_TypeError, "header value should be string");
84                         return NULL;
85                 }
86
87                 hdr->name = talloc_strdup(hdr, PyString_AsString(py_name));
88                 hdr->value = talloc_strdup(hdr, PyString_AsString(py_value));
89                 DLIST_ADD(headers, hdr);
90         }
91
92         websrv_output_headers(web, status, headers);
93
94         Py_RETURN_NONE;
95 }
96
97 static PyMethodDef web_request_methods[] = {
98         { "start_response", (PyCFunction)start_response, METH_VARARGS|METH_KEYWORDS, NULL },
99         { NULL }
100 };
101
102
103 PyTypeObject web_request_Type = {
104         PyObject_HEAD_INIT(NULL) 0,
105         .tp_name = "wsgi.Request",
106         .tp_methods = web_request_methods,
107         .tp_basicsize = sizeof(web_request_Object),
108         .tp_flags = Py_TPFLAGS_DEFAULT,
109 };
110
111 typedef struct {
112         PyObject_HEAD
113 } error_Stream_Object;
114
115 static PyObject *py_error_flush(PyObject *self, PyObject *args, PyObject *kwargs)
116 {
117         /* Nothing to do here */
118         Py_RETURN_NONE;
119 }
120
121 static PyObject *py_error_write(PyObject *self, PyObject *args, PyObject *kwargs)
122 {
123         const char *kwnames[] = { "str", NULL };
124         char *str = NULL;
125
126         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s:write", discard_const_p(char *, kwnames), &str)) {
127                 return NULL;
128         }
129
130         DEBUG(0, ("%s", str));
131
132         Py_RETURN_NONE;
133 }
134
135 static PyObject *py_error_writelines(PyObject *self, PyObject *args, PyObject *kwargs)
136 {
137         const char *kwnames[] = { "seq", NULL };
138         PyObject *seq = NULL, *item;
139
140         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:writelines", discard_const_p(char *, kwnames), &seq)) {
141                 return NULL;
142         }
143
144         while ((item = PyIter_Next(seq))) {
145                 char *str = PyString_AsString(item);
146
147                 DEBUG(0, ("%s", str));
148         }
149
150         Py_RETURN_NONE;
151 }
152
153 static PyMethodDef error_Stream_methods[] = {
154         { "flush", (PyCFunction)py_error_flush, METH_VARARGS|METH_KEYWORDS, NULL },
155         { "write", (PyCFunction)py_error_write, METH_VARARGS|METH_KEYWORDS, NULL },
156         { "writelines", (PyCFunction)py_error_writelines, METH_VARARGS|METH_KEYWORDS, NULL },
157         { NULL, NULL, 0, NULL }
158 };
159
160 PyTypeObject error_Stream_Type = {
161         PyObject_HEAD_INIT(NULL) 0,
162         .tp_name = "wsgi.ErrorStream",
163         .tp_basicsize = sizeof(error_Stream_Object),
164         .tp_methods = error_Stream_methods,
165         .tp_flags = Py_TPFLAGS_DEFAULT,
166 };
167
168 typedef struct {
169         PyObject_HEAD
170         struct websrv_context *web;
171         size_t offset;
172 } input_Stream_Object;
173
174 static PyObject *py_input_read(PyObject *_self, PyObject *args, PyObject *kwargs)
175 {
176         const char *kwnames[] = { "size", NULL };
177         PyObject *ret;
178         input_Stream_Object *self = (input_Stream_Object *)_self;
179         int size = -1;
180
181         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", discard_const_p(char *, kwnames), &size))
182                 return NULL;
183         
184         /* Don't read beyond buffer boundaries */
185         if (size == -1)
186                 size = self->web->input.partial.length-self->offset;
187         else
188                 size = MIN(size, self->web->input.partial.length-self->offset);
189
190         ret = PyString_FromStringAndSize((char *)self->web->input.partial.data+self->offset, size);
191         self->offset += size;
192
193         return ret;
194 }
195
196 static PyObject *py_input_readline(PyObject *_self)
197 {
198         /* FIXME */
199         PyErr_SetString(PyExc_NotImplementedError, 
200                         "readline() not yet implemented");
201         return NULL;
202 }
203
204 static PyObject *py_input_readlines(PyObject *_self, PyObject *args, PyObject *kwargs)
205 {
206         const char *kwnames[] = { "hint", NULL };
207         int hint;
208
209         if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", discard_const_p(char *, kwnames), &hint))
210                 return NULL;
211         
212         /* FIXME */
213         PyErr_SetString(PyExc_NotImplementedError, 
214                         "readlines() not yet implemented");
215         return NULL;
216 }
217
218 static PyObject *py_input___iter__(PyObject *_self)
219 {
220         /* FIXME */
221         PyErr_SetString(PyExc_NotImplementedError, 
222                         "__iter__() not yet implemented");
223         return NULL;
224 }
225
226 static PyMethodDef input_Stream_methods[] = {
227         { "read", (PyCFunction)py_input_read, METH_VARARGS|METH_KEYWORDS, NULL },
228         { "readline", (PyCFunction)py_input_readline, METH_NOARGS, NULL },
229         { "readlines", (PyCFunction)py_input_readlines, METH_VARARGS|METH_KEYWORDS, NULL },
230         { "__iter__", (PyCFunction)py_input___iter__, METH_NOARGS, NULL },
231         { NULL, NULL, 0, NULL }
232 };
233
234 PyTypeObject input_Stream_Type = {
235         PyObject_HEAD_INIT(NULL) 0,
236         .tp_name = "wsgi.InputStream",
237         .tp_basicsize = sizeof(input_Stream_Object),
238         .tp_methods = input_Stream_methods,
239         .tp_flags = Py_TPFLAGS_DEFAULT,
240 };
241
242 static PyObject *Py_InputHttpStream(struct websrv_context *web)
243 {
244         input_Stream_Object *ret = PyObject_New(input_Stream_Object, &input_Stream_Type);
245         ret->web = web;
246         ret->offset = 0;
247         return (PyObject *)ret;
248 }
249
250 static PyObject *Py_ErrorHttpStream(void)
251 {
252         error_Stream_Object *ret = PyObject_New(error_Stream_Object, &error_Stream_Type);
253         return (PyObject *)ret;
254 }
255
256 static void DEBUG_Print_PyError(int level, const char *message)
257 {
258         PyObject *old_stderr, *new_stderr;
259         PyObject *sys_module;
260         PyObject *ptype, *pvalue, *ptb;
261
262         PyErr_Fetch(&ptype, &pvalue, &ptb);
263
264         DEBUG(0, ("WSGI: Server exception occurred: %s\n", message));
265
266         sys_module = PyImport_ImportModule("sys");
267         if (sys_module == NULL) {
268                 DEBUG(0, ("Unable to obtain sys module while printing error"));
269                 return;
270         }
271
272         old_stderr = PyObject_GetAttrString(sys_module, "stderr");
273         if (old_stderr == NULL) {
274                 DEBUG(0, ("Unable to obtain old stderr"));
275                 Py_DECREF(sys_module);
276                 return;
277         }
278
279         new_stderr = Py_ErrorHttpStream();
280         if (new_stderr == NULL) {
281                 DEBUG(0, ("Unable to create error stream"));
282                 Py_DECREF(sys_module);
283                 Py_DECREF(old_stderr);
284                 return;
285         }
286
287         PyObject_SetAttrString(sys_module, "stderr", new_stderr);
288         Py_DECREF(new_stderr);
289
290         PyErr_Restore(ptype, pvalue, ptb);
291         PyErr_Print();
292
293         PyObject_SetAttrString(sys_module, "stderr", old_stderr);
294         Py_DECREF(old_stderr);
295
296         Py_DECREF(sys_module);
297 }
298
299 static PyObject *create_environ(bool tls, int content_length, struct http_header *headers, const char *request_method, const char *servername, int serverport, PyObject *inputstream, const char *request_string)
300 {
301         PyObject *env;
302         PyObject *py_scheme;
303         PyObject *py_val;
304         struct http_header *hdr;
305         char *questionmark;
306
307         env = PyDict_New();
308         if (env == NULL) {
309                 return NULL;
310         }
311
312         PyDict_SetItemString(env, "wsgi.input", inputstream);
313
314         py_val = Py_ErrorHttpStream();
315         if (py_val == NULL) goto error;
316         PyDict_SetItemString(env, "wsgi.errors", py_val);
317         Py_DECREF(py_val);
318
319         py_val = Py_BuildValue("(i,i)", 1, 0);
320         if (py_val == NULL) goto error;
321         PyDict_SetItemString(env, "wsgi.version", py_val);
322         Py_DECREF(py_val);
323         PyDict_SetItemString(env, "wsgi.multithread", Py_False);
324         PyDict_SetItemString(env, "wsgi.multiprocess", Py_False);
325         PyDict_SetItemString(env, "wsgi.run_once", Py_False);
326         py_val = PyString_FromString("HTTP/1.0");
327         if (py_val == NULL) goto error;
328         PyDict_SetItemString(env, "SERVER_PROTOCOL", py_val);
329         Py_DECREF(py_val);
330         if (content_length > 0) {
331                 py_val = PyLong_FromLong(content_length);
332                 if (py_val == NULL) goto error;
333                 PyDict_SetItemString(env, "CONTENT_LENGTH", py_val);
334                 Py_DECREF(py_val);
335         }
336         py_val = PyString_FromString(request_method);
337         if (py_val == NULL) goto error;
338         PyDict_SetItemString(env, "REQUEST_METHOD", py_val);
339         Py_DECREF(py_val);
340
341         /* There is always a single wsgi app to which all requests are redirected,
342          * so SCRIPT_NAME will be / */
343         py_val = PyString_FromString("/");
344         if (py_val == NULL) goto error;
345         PyDict_SetItemString(env, "SCRIPT_NAME", py_val);
346         Py_DECREF(py_val);
347         questionmark = strchr(request_string, '?');
348         if (questionmark == NULL) {
349                 py_val = PyString_FromString(request_string);
350                 if (py_val == NULL) goto error;
351                 PyDict_SetItemString(env, "PATH_INFO", py_val);
352                 Py_DECREF(py_val);
353         } else {
354                 py_val = PyString_FromString(questionmark+1);
355                 if (py_val == NULL) goto error;
356                 PyDict_SetItemString(env, "QUERY_STRING", py_val);
357                 Py_DECREF(py_val);
358                 py_val = PyString_FromStringAndSize(request_string, questionmark-request_string);
359                 if (py_val == NULL) goto error;
360                 PyDict_SetItemString(env, "PATH_INFO", py_val);
361                 Py_DECREF(py_val);
362         }
363
364         py_val = PyString_FromString(servername);
365         if (py_val == NULL) goto error;
366         PyDict_SetItemString(env, "SERVER_NAME", py_val);
367         Py_DECREF(py_val);
368         py_val = PyString_FromFormat("%d", serverport);
369         if (py_val == NULL) goto error;
370         PyDict_SetItemString(env, "SERVER_PORT", py_val);
371         Py_DECREF(py_val);
372
373         for (hdr = headers; hdr; hdr = hdr->next) {
374                 char *name;
375                 if (!strcasecmp(hdr->name, "Content-Type")) {
376                         py_val = PyString_FromString(hdr->value);
377                         PyDict_SetItemString(env, "CONTENT_TYPE", py_val);
378                         Py_DECREF(py_val);
379                 } else {
380                         if (asprintf(&name, "HTTP_%s", hdr->name) < 0) {
381                                 PyErr_NoMemory();
382                                 goto error;
383                         }
384                         py_val = PyString_FromString(hdr->value);
385                         PyDict_SetItemString(env, name, py_val);
386                         Py_DECREF(py_val);
387                         free(name);
388                 }
389         }
390
391         if (tls) {
392                 py_scheme = PyString_FromString("https");
393         } else {
394                 py_scheme = PyString_FromString("http");
395         }
396         if (py_scheme == NULL) goto error;
397         PyDict_SetItemString(env, "wsgi.url_scheme", py_scheme);
398         Py_DECREF(py_scheme);
399
400         return env;
401 error:
402         Py_DECREF(env);
403         return NULL;
404 }
405
406 static void wsgi_serve_500(struct websrv_context *web)
407 {
408         struct http_header *headers = NULL;
409         const char *contents[] = {
410                 "An internal server error occurred while handling this request. ",
411                 "Please refer to the server logs for more details. ",
412                 NULL
413         };
414         int i;
415
416         websrv_output_headers(web, "500 Internal Server Error", headers);
417         for (i = 0; contents[i]; i++) {
418                 websrv_output(web, contents[i], strlen(contents[i]));
419         }
420 }
421
422 static void wsgi_process_http_input(struct web_server_data *wdata,
423                                     struct websrv_context *web)
424 {
425         PyObject *py_environ, *result, *item, *iter;
426         PyObject *request_handler = (PyObject *)wdata->private_data;
427         struct tsocket_address *my_address = web->conn->local_address;
428         const char *addr = "0.0.0.0";
429         uint16_t port = 0;
430         web_request_Object *py_web;
431         PyObject *py_input_stream;
432
433         py_web = PyObject_New(web_request_Object, &web_request_Type);
434         if (py_web == NULL) {
435                 DEBUG_Print_PyError(0, "Unable to allocate web request");
436                 return;
437         }
438         py_web->web = web;
439
440         if (tsocket_address_is_inet(my_address, "ip")) {
441                 addr = tsocket_address_inet_addr_string(my_address, wdata);
442                 port = tsocket_address_inet_port(my_address);
443         }
444
445         py_input_stream = Py_InputHttpStream(web);
446         if (py_input_stream == NULL) {
447                 DEBUG_Print_PyError(0, "unable to create python input stream");
448                 return;
449         }
450
451         py_environ = create_environ(tls_enabled(web->conn->socket),
452                                     web->input.content_length, 
453                                     web->input.headers, 
454                                     web->input.post_request?"POST":"GET",
455                                     addr,
456                                     port,
457                                     py_input_stream,
458                                     web->input.url
459                                     );
460
461         Py_DECREF(py_input_stream);
462
463         if (py_environ == NULL) {
464                 DEBUG_Print_PyError(0, "Unable to create WSGI environment object");
465                 wsgi_serve_500(web);
466                 return;
467         }
468
469         result = PyObject_CallMethod(request_handler, discard_const_p(char, "__call__"), discard_const_p(char, "OO"),
470                                        py_environ, PyObject_GetAttrString((PyObject *)py_web, "start_response"));
471
472         if (result == NULL) {
473                 DEBUG_Print_PyError(0, "error while handling request");
474                 wsgi_serve_500(web);
475                 return;
476         }
477
478         iter = PyObject_GetIter(result);
479         Py_DECREF(result);
480
481         if (iter == NULL) {
482                 DEBUG_Print_PyError(0, "application did not return iterable");
483                 wsgi_serve_500(web);
484                 return;
485         }
486
487         /* Now, iter over all the data returned */
488
489         while ((item = PyIter_Next(iter))) {
490                 websrv_output(web, PyString_AsString(item), PyString_Size(item));
491                 Py_DECREF(item);
492         }
493
494         Py_DECREF(iter);
495 }
496
497 bool wsgi_initialize(struct web_server_data *wdata)
498 {
499         PyObject *py_web_server;
500
501         Py_Initialize();
502
503         py_update_path(); /* Ensure that we have the Samba paths at
504                            * the start of the sys.path() */
505
506         if (PyType_Ready(&web_request_Type) < 0)
507                 return false;
508
509         if (PyType_Ready(&input_Stream_Type) < 0)
510                 return false;
511
512         if (PyType_Ready(&error_Stream_Type) < 0)
513                 return false;
514
515         wdata->http_process_input = wsgi_process_http_input;
516         py_web_server = PyImport_ImportModule("samba.web_server");
517         if (py_web_server == NULL) {
518                 DEBUG_Print_PyError(0, "Unable to find web server");
519                 return false;
520         }
521         wdata->private_data = py_web_server;
522         return true;
523 }