2 Unix SMB/CIFS implementation.
3 Samba utility functions
4 Copyright © Jelmer Vernooij <jelmer@samba.org> 2008
6 Implementation of the WSGI interface described in PEP0333
7 (http://www.python.org/dev/peps/pep-0333)
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.
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.
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/>.
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"
34 struct websrv_context *web;
37 static PyObject *start_response(PyObject *self, PyObject *args, PyObject *kwargs)
39 PyObject *response_header, *exc_info = NULL;
42 const char *kwnames[] = {
43 "status", "response_header", "exc_info", NULL
45 web_request_Object *py_web = (web_request_Object *)self;
46 struct websrv_context *web = py_web->web;
47 struct http_header *headers = NULL;
49 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|O:start_response", discard_const_p(char *, kwnames), &status, &response_header, &exc_info)) {
55 if (!PyList_Check(response_header)) {
56 PyErr_SetString(PyExc_TypeError, "response_header should be list");
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;
65 if (!PyTuple_Check(item)) {
66 PyErr_SetString(PyExc_TypeError, "Expected tuple");
70 if (PyTuple_Size(item) != 2) {
71 PyErr_SetString(PyExc_TypeError, "header tuple has invalid size, expected 2");
75 py_name = PyTuple_GetItem(item, 0);
77 if (!PyStr_Check(py_name)) {
78 PyErr_SetString(PyExc_TypeError, "header name should be string");
82 py_value = PyTuple_GetItem(item, 1);
83 if (!PyStr_Check(py_value)) {
84 PyErr_SetString(PyExc_TypeError, "header value should be string");
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);
93 websrv_output_headers(web, status, headers);
98 static PyMethodDef web_request_methods[] = {
99 { "start_response", (PyCFunction)start_response, METH_VARARGS|METH_KEYWORDS, NULL },
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,
114 } error_Stream_Object;
116 static PyObject *py_error_flush(PyObject *self, PyObject *args, PyObject *kwargs)
118 /* Nothing to do here */
122 static PyObject *py_error_write(PyObject *self, PyObject *args, PyObject *kwargs)
124 const char *kwnames[] = { "str", NULL };
127 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s:write", discard_const_p(char *, kwnames), &str)) {
131 DEBUG(0, ("%s", str));
136 static PyObject *py_error_writelines(PyObject *self, PyObject *args, PyObject *kwargs)
138 const char *kwnames[] = { "seq", NULL };
139 PyObject *seq = NULL, *item;
141 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:writelines", discard_const_p(char *, kwnames), &seq)) {
145 while ((item = PyIter_Next(seq))) {
146 const char *str = NULL;
148 if (!IsPy3Bytes(item)) {
149 str = PyStr_AsUTF8AndSize(item, &size);
151 str = PyBytes_AsString(item);
152 size = PyBytes_Size(item);
155 DEBUG(0, ("%s", str));
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 }
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,
179 struct websrv_context *web;
181 } input_Stream_Object;
183 static PyObject *py_input_read(PyObject *_self, PyObject *args, PyObject *kwargs)
185 const char *kwnames[] = { "size", NULL };
187 input_Stream_Object *self = (input_Stream_Object *)_self;
190 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", discard_const_p(char *, kwnames), &size))
193 /* Don't read beyond buffer boundaries */
195 size = self->web->input.partial.length-self->offset;
197 size = MIN(size, self->web->input.partial.length-self->offset);
199 ret = PyBytes_FromStringAndSize((char *)self->web->input.partial.data+self->offset, size);
200 self->offset += size;
205 static PyObject *py_input_readline(PyObject *_self)
208 PyErr_SetString(PyExc_NotImplementedError,
209 "readline() not yet implemented");
213 static PyObject *py_input_readlines(PyObject *_self, PyObject *args, PyObject *kwargs)
215 const char *kwnames[] = { "hint", NULL };
218 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", discard_const_p(char *, kwnames), &hint))
222 PyErr_SetString(PyExc_NotImplementedError,
223 "readlines() not yet implemented");
227 static PyObject *py_input___iter__(PyObject *_self)
230 PyErr_SetString(PyExc_NotImplementedError,
231 "__iter__() not yet implemented");
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 }
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,
251 static PyObject *Py_InputHttpStream(struct websrv_context *web)
253 input_Stream_Object *ret = PyObject_New(input_Stream_Object, &input_Stream_Type);
256 return (PyObject *)ret;
259 static PyObject *Py_ErrorHttpStream(void)
261 error_Stream_Object *ret = PyObject_New(error_Stream_Object, &error_Stream_Type);
262 return (PyObject *)ret;
265 static void DEBUG_Print_PyError(int level, const char *message)
267 PyObject *old_stderr, *new_stderr;
268 PyObject *sys_module;
269 PyObject *ptype, *pvalue, *ptb;
271 PyErr_Fetch(&ptype, &pvalue, &ptb);
273 DEBUG(0, ("WSGI: Server exception occurred: %s\n", message));
275 sys_module = PyImport_ImportModule("sys");
276 if (sys_module == NULL) {
277 DEBUG(0, ("Unable to obtain sys module while printing error"));
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);
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);
296 PyObject_SetAttrString(sys_module, "stderr", new_stderr);
297 Py_DECREF(new_stderr);
299 PyErr_Restore(ptype, pvalue, ptb);
302 PyObject_SetAttrString(sys_module, "stderr", old_stderr);
303 Py_DECREF(old_stderr);
305 Py_DECREF(sys_module);
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)
313 struct http_header *hdr;
321 PyDict_SetItemString(env, "wsgi.input", inputstream);
323 py_val = Py_ErrorHttpStream();
324 if (py_val == NULL) goto error;
325 PyDict_SetItemString(env, "wsgi.errors", py_val);
328 py_val = Py_BuildValue("(i,i)", 1, 0);
329 if (py_val == NULL) goto error;
330 PyDict_SetItemString(env, "wsgi.version", 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);
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);
345 py_val = PyStr_FromString(request_method);
346 if (py_val == NULL) goto error;
347 PyDict_SetItemString(env, "REQUEST_METHOD", py_val);
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);
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);
363 py_val = PyStr_FromString(questionmark+1);
364 if (py_val == NULL) goto error;
365 PyDict_SetItemString(env, "QUERY_STRING", 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);
373 py_val = PyStr_FromString(servername);
374 if (py_val == NULL) goto error;
375 PyDict_SetItemString(env, "SERVER_NAME", py_val);
377 py_val = PyStr_FromFormat("%d", serverport);
378 if (py_val == NULL) goto error;
379 PyDict_SetItemString(env, "SERVER_PORT", py_val);
382 for (hdr = headers; hdr; hdr = hdr->next) {
384 if (!strcasecmp(hdr->name, "Content-Type")) {
385 py_val = PyStr_FromString(hdr->value);
386 PyDict_SetItemString(env, "CONTENT_TYPE", py_val);
389 if (asprintf(&name, "HTTP_%s", hdr->name) < 0) {
393 py_val = PyStr_FromString(hdr->value);
394 PyDict_SetItemString(env, name, py_val);
401 py_scheme = PyStr_FromString("https");
403 py_scheme = PyStr_FromString("http");
405 if (py_scheme == NULL) goto error;
406 PyDict_SetItemString(env, "wsgi.url_scheme", py_scheme);
407 Py_DECREF(py_scheme);
415 static void wsgi_serve_500(struct websrv_context *web)
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. ",
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]));
431 static void wsgi_process_http_input(struct web_server_data *wdata,
432 struct websrv_context *web)
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";
439 web_request_Object *py_web;
440 PyObject *py_input_stream;
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");
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);
454 py_input_stream = Py_InputHttpStream(web);
455 if (py_input_stream == NULL) {
456 DEBUG_Print_PyError(0, "unable to create python input stream");
460 py_environ = create_environ(tls_enabled(web->conn->socket),
461 web->input.content_length,
463 web->input.post_request?"POST":"GET",
470 Py_DECREF(py_input_stream);
472 if (py_environ == NULL) {
473 DEBUG_Print_PyError(0, "Unable to create WSGI environment object");
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"));
481 if (result == NULL) {
482 DEBUG_Print_PyError(0, "error while handling request");
487 iter = PyObject_GetIter(result);
491 DEBUG_Print_PyError(0, "application did not return iterable");
496 /* Now, iter over all the data returned */
498 while ((item = PyIter_Next(iter))) {
501 int ret = PyBytes_AsStringAndSize(item, &value, &size);
503 DEBUG_Print_PyError(0, "application failed to "
504 "extract string from iterable");
508 websrv_output(web, value, size);
515 bool wsgi_initialize(struct web_server_data *wdata)
517 PyObject *py_web_server;
521 py_update_path(); /* Ensure that we have the Samba paths at
522 * the start of the sys.path() */
524 if (PyType_Ready(&web_request_Type) < 0)
527 if (PyType_Ready(&input_Stream_Type) < 0)
530 if (PyType_Ready(&error_Stream_Type) < 0)
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");
539 wdata->private_data = py_web_server;