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