s4:web_server: use tsocket_address functions to get the local ip and port
[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                         asprintf(&name, "HTTP_%s", hdr->name);
304                         PyDict_SetItemString(env, name, PyString_FromString(hdr->value));
305                         free(name);
306                 }
307         }
308
309         if (tls) {
310                 py_scheme = PyString_FromString("https");
311         } else {
312                 py_scheme = PyString_FromString("http");
313         }
314         PyDict_SetItemString(env, "wsgi.url_scheme", py_scheme);
315
316         return env;
317 }
318
319 static void wsgi_process_http_input(struct web_server_data *wdata,
320                                     struct websrv_context *web)
321 {
322         PyObject *py_environ, *result, *item, *iter;
323         PyObject *request_handler = (PyObject *)wdata->private_data;
324         struct tsocket_address *my_address = web->conn->local_address;
325         const char *addr = "0.0.0.0";
326         uint16_t port = 0;
327         web_request_Object *py_web = PyObject_New(web_request_Object, &web_request_Type);
328         py_web->web = web;
329
330         if (tsocket_address_is_inet(my_address, "ip")) {
331                 addr = tsocket_address_inet_addr_string(my_address, wdata);
332                 port = tsocket_address_inet_port(my_address);
333         }
334
335         py_environ = create_environ(tls_enabled(web->conn->socket),
336                                     web->input.content_length, 
337                                     web->input.headers, 
338                                     web->input.post_request?"POST":"GET",
339                                     addr,
340                                     port,
341                                     Py_InputHttpStream(web),
342                                     web->input.url
343                                     );
344         if (py_environ == NULL) {
345                 DEBUG(0, ("Unable to create WSGI environment object\n"));
346                 return;
347         }
348
349         result = PyObject_CallMethod(request_handler, discard_const_p(char, "__call__"), discard_const_p(char, "OO"),
350                                        py_environ, PyObject_GetAttrString((PyObject *)py_web, "start_response"));
351
352         if (result == NULL) {
353                 DEBUG(0, ("error while running WSGI code\n"));
354                 return;
355         }
356
357         iter = PyObject_GetIter(result);
358         Py_DECREF(result);
359
360         /* Now, iter over all the data returned */
361
362         while ((item = PyIter_Next(iter))) {
363                 websrv_output(web, PyString_AsString(item), PyString_Size(item));
364                 Py_DECREF(item);
365         }
366
367         Py_DECREF(iter);
368 }
369
370 bool wsgi_initialize(struct web_server_data *wdata)
371 {
372         PyObject *py_swat;
373
374         Py_Initialize();
375
376         if (PyType_Ready(&web_request_Type) < 0)
377                 return false;
378
379         if (PyType_Ready(&input_Stream_Type) < 0)
380                 return false;
381
382         if (PyType_Ready(&error_Stream_Type) < 0)
383                 return false;
384
385         wdata->http_process_input = wsgi_process_http_input;
386         py_swat = PyImport_Import(PyString_FromString("swat"));
387         if (py_swat == NULL) {
388                 DEBUG(0, ("Unable to find SWAT\n"));
389                 return false;
390         }
391         wdata->private_data = py_swat;
392         return true;
393 }