e1e211872843df1d862aa514e2f71a43fd5e23dc
[metze/samba/wip.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/util/data_blob.h"
28 #include "lib/tls/tls.h"
29 #include "lib/tsocket/tsocket.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         int 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 | Py_TPFLAGS_BASETYPE,
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, ("WSGI App: %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, ("WSGI App: %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 | Py_TPFLAGS_BASETYPE,
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 | Py_TPFLAGS_BASETYPE,
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 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)
257 {
258         PyObject *env;
259         PyObject *errorstream;
260         PyObject *py_scheme;
261         struct http_header *hdr;
262         char *questionmark;
263         
264         env = PyDict_New();
265         if (env == NULL) {
266                 return NULL;
267         }
268
269         errorstream = Py_ErrorHttpStream();
270         if (errorstream == NULL) {
271                 Py_DECREF(env);
272                 Py_DECREF(inputstream);
273                 return NULL;
274         }
275
276         PyDict_SetItemString(env, "wsgi.input", inputstream);
277         PyDict_SetItemString(env, "wsgi.errors", errorstream);
278         PyDict_SetItemString(env, "wsgi.version", Py_BuildValue("(i,i)", 1, 0));
279         PyDict_SetItemString(env, "wsgi.multithread", Py_False);
280         PyDict_SetItemString(env, "wsgi.multiprocess", Py_True);
281         PyDict_SetItemString(env, "wsgi.run_once", Py_False);
282         PyDict_SetItemString(env, "SERVER_PROTOCOL", PyString_FromString("HTTP/1.0"));
283         if (content_length > 0) {
284                 PyDict_SetItemString(env, "CONTENT_LENGTH", PyLong_FromLong(content_length));
285         }
286         PyDict_SetItemString(env, "REQUEST_METHOD", PyString_FromString(request_method));
287
288         questionmark = strchr(request_string, '?');
289         if (questionmark == NULL) {
290                 PyDict_SetItemString(env, "SCRIPT_NAME", PyString_FromString(request_string));
291         } else {
292                 PyDict_SetItemString(env, "QUERY_STRING", PyString_FromString(questionmark+1));
293                 PyDict_SetItemString(env, "SCRIPT_NAME", PyString_FromStringAndSize(request_string, questionmark-request_string));
294         }
295         
296         PyDict_SetItemString(env, "SERVER_NAME", PyString_FromString(servername));
297         PyDict_SetItemString(env, "SERVER_PORT", PyInt_FromLong(serverport));
298         for (hdr = headers; hdr; hdr = hdr->next) {
299                 char *name;
300                 if (!strcasecmp(hdr->name, "Content-Type")) {
301                         PyDict_SetItemString(env, "CONTENT_TYPE", PyString_FromString(hdr->value));
302                 } else { 
303                         if (asprintf(&name, "HTTP_%s", hdr->name) < 0) {
304                                 Py_DECREF(env);
305                                 Py_DECREF(inputstream);
306                                 PyErr_NoMemory();
307                                 return NULL;
308                         }
309                         PyDict_SetItemString(env, name, PyString_FromString(hdr->value));
310                         free(name);
311                 }
312         }
313
314         if (tls) {
315                 py_scheme = PyString_FromString("https");
316         } else {
317                 py_scheme = PyString_FromString("http");
318         }
319         PyDict_SetItemString(env, "wsgi.url_scheme", py_scheme);
320
321         return env;
322 }
323
324 static void wsgi_process_http_input(struct web_server_data *wdata,
325                                     struct websrv_context *web)
326 {
327         PyObject *py_environ, *result, *item, *iter;
328         PyObject *request_handler = (PyObject *)wdata->private_data;
329         struct tsocket_address *my_address = web->conn->local_address;
330         const char *addr = "0.0.0.0";
331         uint16_t port = 0;
332         web_request_Object *py_web = PyObject_New(web_request_Object, &web_request_Type);
333         py_web->web = web;
334
335         if (tsocket_address_is_inet(my_address, "ip")) {
336                 addr = tsocket_address_inet_addr_string(my_address, wdata);
337                 port = tsocket_address_inet_port(my_address);
338         }
339
340         py_environ = create_environ(tls_enabled(web->conn->socket),
341                                     web->input.content_length, 
342                                     web->input.headers, 
343                                     web->input.post_request?"POST":"GET",
344                                     addr,
345                                     port,
346                                     Py_InputHttpStream(web),
347                                     web->input.url
348                                     );
349         if (py_environ == NULL) {
350                 DEBUG(0, ("Unable to create WSGI environment object\n"));
351                 return;
352         }
353
354         result = PyObject_CallMethod(request_handler, discard_const_p(char, "__call__"), discard_const_p(char, "OO"),
355                                        py_environ, PyObject_GetAttrString((PyObject *)py_web, "start_response"));
356
357         if (result == NULL) {
358                 DEBUG(0, ("error while running WSGI code\n"));
359                 return;
360         }
361
362         iter = PyObject_GetIter(result);
363         Py_DECREF(result);
364
365         /* Now, iter over all the data returned */
366
367         while ((item = PyIter_Next(iter))) {
368                 websrv_output(web, PyString_AsString(item), PyString_Size(item));
369                 Py_DECREF(item);
370         }
371
372         Py_DECREF(iter);
373 }
374
375 bool wsgi_initialize(struct web_server_data *wdata)
376 {
377         PyObject *py_swat;
378
379         Py_Initialize();
380
381         if (PyType_Ready(&web_request_Type) < 0)
382                 return false;
383
384         if (PyType_Ready(&input_Stream_Type) < 0)
385                 return false;
386
387         if (PyType_Ready(&error_Stream_Type) < 0)
388                 return false;
389
390         wdata->http_process_input = wsgi_process_http_input;
391         py_swat = PyImport_Import(PyString_FromString("swat"));
392         if (py_swat == NULL) {
393                 DEBUG(0, ("Unable to find SWAT\n"));
394                 return false;
395         }
396         wdata->private_data = py_swat;
397         return true;
398 }