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"
31 /* There's no Py_ssize_t in 2.4, apparently */
32 #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 5
33 typedef int Py_ssize_t
;
34 typedef inquiry lenfunc
;
35 typedef intargfunc ssizeargfunc
;
40 struct websrv_context
*web
;
43 static PyObject
*start_response(PyObject
*self
, PyObject
*args
, PyObject
*kwargs
)
45 PyObject
*response_header
, *exc_info
= NULL
;
48 const char *kwnames
[] = {
49 "status", "response_header", "exc_info", NULL
51 web_request_Object
*py_web
= (web_request_Object
*)self
;
52 struct websrv_context
*web
= py_web
->web
;
53 struct http_header
*headers
= NULL
;
55 if (!PyArg_ParseTupleAndKeywords(args
, kwargs
, "sO|O:start_response", discard_const_p(char *, kwnames
), &status
, &response_header
, &exc_info
)) {
61 if (!PyList_Check(response_header
)) {
62 PyErr_SetString(PyExc_TypeError
, "response_header should be list");
66 for (i
= 0; i
< PyList_Size(response_header
); i
++) {
67 struct http_header
*hdr
= talloc_zero(web
, struct http_header
);
68 PyObject
*item
= PyList_GetItem(response_header
, i
);
69 PyObject
*py_name
, *py_value
;
71 if (!PyTuple_Check(item
)) {
72 PyErr_SetString(PyExc_TypeError
, "Expected tuple");
76 if (PyTuple_Size(item
) != 2) {
77 PyErr_SetString(PyExc_TypeError
, "header tuple has invalid size, expected 2");
81 py_name
= PyTuple_GetItem(item
, 0);
83 if (!PyString_Check(py_name
)) {
84 PyErr_SetString(PyExc_TypeError
, "header name should be string");
88 py_value
= PyTuple_GetItem(item
, 1);
89 if (!PyString_Check(py_value
)) {
90 PyErr_SetString(PyExc_TypeError
, "header value should be string");
94 hdr
->name
= talloc_strdup(hdr
, PyString_AsString(py_name
));
95 hdr
->value
= talloc_strdup(hdr
, PyString_AsString(py_value
));
96 DLIST_ADD(headers
, hdr
);
99 websrv_output_headers(web
, status
, headers
);
104 static PyMethodDef web_request_methods
[] = {
105 { "start_response", (PyCFunction
)start_response
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
110 PyTypeObject web_request_Type
= {
111 PyObject_HEAD_INIT(NULL
) 0,
112 .tp_name
= "wsgi.Request",
113 .tp_methods
= web_request_methods
,
114 .tp_basicsize
= sizeof(web_request_Object
),
115 .tp_flags
= Py_TPFLAGS_DEFAULT
,
120 } error_Stream_Object
;
122 static PyObject
*py_error_flush(PyObject
*self
, PyObject
*args
, PyObject
*kwargs
)
124 /* Nothing to do here */
128 static PyObject
*py_error_write(PyObject
*self
, PyObject
*args
, PyObject
*kwargs
)
130 const char *kwnames
[] = { "str", NULL
};
133 if (!PyArg_ParseTupleAndKeywords(args
, kwargs
, "s:write", discard_const_p(char *, kwnames
), &str
)) {
137 DEBUG(0, ("%s", str
));
142 static PyObject
*py_error_writelines(PyObject
*self
, PyObject
*args
, PyObject
*kwargs
)
144 const char *kwnames
[] = { "seq", NULL
};
145 PyObject
*seq
= NULL
, *item
;
147 if (!PyArg_ParseTupleAndKeywords(args
, kwargs
, "O:writelines", discard_const_p(char *, kwnames
), &seq
)) {
151 while ((item
= PyIter_Next(seq
))) {
152 char *str
= PyString_AsString(item
);
154 DEBUG(0, ("%s", str
));
160 static PyMethodDef error_Stream_methods
[] = {
161 { "flush", (PyCFunction
)py_error_flush
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
162 { "write", (PyCFunction
)py_error_write
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
163 { "writelines", (PyCFunction
)py_error_writelines
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
164 { NULL
, NULL
, 0, NULL
}
167 PyTypeObject error_Stream_Type
= {
168 PyObject_HEAD_INIT(NULL
) 0,
169 .tp_name
= "wsgi.ErrorStream",
170 .tp_basicsize
= sizeof(error_Stream_Object
),
171 .tp_methods
= error_Stream_methods
,
172 .tp_flags
= Py_TPFLAGS_DEFAULT
,
177 struct websrv_context
*web
;
179 } input_Stream_Object
;
181 static PyObject
*py_input_read(PyObject
*_self
, PyObject
*args
, PyObject
*kwargs
)
183 const char *kwnames
[] = { "size", NULL
};
185 input_Stream_Object
*self
= (input_Stream_Object
*)_self
;
188 if (!PyArg_ParseTupleAndKeywords(args
, kwargs
, "|i", discard_const_p(char *, kwnames
), &size
))
191 /* Don't read beyond buffer boundaries */
193 size
= self
->web
->input
.partial
.length
-self
->offset
;
195 size
= MIN(size
, self
->web
->input
.partial
.length
-self
->offset
);
197 ret
= PyString_FromStringAndSize((char *)self
->web
->input
.partial
.data
+self
->offset
, size
);
198 self
->offset
+= size
;
203 static PyObject
*py_input_readline(PyObject
*_self
)
206 PyErr_SetString(PyExc_NotImplementedError
,
207 "readline() not yet implemented");
211 static PyObject
*py_input_readlines(PyObject
*_self
, PyObject
*args
, PyObject
*kwargs
)
213 const char *kwnames
[] = { "hint", NULL
};
216 if (!PyArg_ParseTupleAndKeywords(args
, kwargs
, "|i", discard_const_p(char *, kwnames
), &hint
))
220 PyErr_SetString(PyExc_NotImplementedError
,
221 "readlines() not yet implemented");
225 static PyObject
*py_input___iter__(PyObject
*_self
)
228 PyErr_SetString(PyExc_NotImplementedError
,
229 "__iter__() not yet implemented");
233 static PyMethodDef input_Stream_methods
[] = {
234 { "read", (PyCFunction
)py_input_read
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
235 { "readline", (PyCFunction
)py_input_readline
, METH_NOARGS
, NULL
},
236 { "readlines", (PyCFunction
)py_input_readlines
, METH_VARARGS
|METH_KEYWORDS
, NULL
},
237 { "__iter__", (PyCFunction
)py_input___iter__
, METH_NOARGS
, NULL
},
238 { NULL
, NULL
, 0, NULL
}
241 PyTypeObject input_Stream_Type
= {
242 PyObject_HEAD_INIT(NULL
) 0,
243 .tp_name
= "wsgi.InputStream",
244 .tp_basicsize
= sizeof(input_Stream_Object
),
245 .tp_methods
= input_Stream_methods
,
246 .tp_flags
= Py_TPFLAGS_DEFAULT
,
249 static PyObject
*Py_InputHttpStream(struct websrv_context
*web
)
251 input_Stream_Object
*ret
= PyObject_New(input_Stream_Object
, &input_Stream_Type
);
254 return (PyObject
*)ret
;
257 static PyObject
*Py_ErrorHttpStream(void)
259 error_Stream_Object
*ret
= PyObject_New(error_Stream_Object
, &error_Stream_Type
);
260 return (PyObject
*)ret
;
263 static void DEBUG_Print_PyError(int level
, const char *message
)
265 PyObject
*old_stderr
, *new_stderr
;
266 PyObject
*sys_module
;
267 PyObject
*ptype
, *pvalue
, *ptb
;
269 PyErr_Fetch(&ptype
, &pvalue
, &ptb
);
271 DEBUG(0, ("WSGI: Server exception occurred: %s\n", message
));
273 sys_module
= PyImport_ImportModule("sys");
274 if (sys_module
== NULL
) {
275 DEBUG(0, ("Unable to obtain sys module while printing error"));
279 old_stderr
= PyObject_GetAttrString(sys_module
, "stderr");
280 if (old_stderr
== NULL
) {
281 DEBUG(0, ("Unable to obtain old stderr"));
282 Py_DECREF(sys_module
);
286 new_stderr
= Py_ErrorHttpStream();
287 if (new_stderr
== NULL
) {
288 DEBUG(0, ("Unable to create error stream"));
289 Py_DECREF(sys_module
);
290 Py_DECREF(old_stderr
);
294 PyObject_SetAttrString(sys_module
, "stderr", new_stderr
);
295 Py_DECREF(new_stderr
);
297 PyErr_Restore(ptype
, pvalue
, ptb
);
300 PyObject_SetAttrString(sys_module
, "stderr", old_stderr
);
301 Py_DECREF(old_stderr
);
303 Py_DECREF(sys_module
);
306 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
)
311 struct http_header
*hdr
;
319 PyDict_SetItemString(env
, "wsgi.input", inputstream
);
321 py_val
= Py_ErrorHttpStream();
322 if (py_val
== NULL
) goto error
;
323 PyDict_SetItemString(env
, "wsgi.errors", py_val
);
326 py_val
= Py_BuildValue("(i,i)", 1, 0);
327 if (py_val
== NULL
) goto error
;
328 PyDict_SetItemString(env
, "wsgi.version", py_val
);
330 PyDict_SetItemString(env
, "wsgi.multithread", Py_False
);
331 PyDict_SetItemString(env
, "wsgi.multiprocess", Py_False
);
332 PyDict_SetItemString(env
, "wsgi.run_once", Py_False
);
333 py_val
= PyString_FromString("HTTP/1.0");
334 if (py_val
== NULL
) goto error
;
335 PyDict_SetItemString(env
, "SERVER_PROTOCOL", py_val
);
337 if (content_length
> 0) {
338 py_val
= PyLong_FromLong(content_length
);
339 if (py_val
== NULL
) goto error
;
340 PyDict_SetItemString(env
, "CONTENT_LENGTH", py_val
);
343 py_val
= PyString_FromString(request_method
);
344 if (py_val
== NULL
) goto error
;
345 PyDict_SetItemString(env
, "REQUEST_METHOD", py_val
);
348 /* There is always a single wsgi app to which all requests are redirected,
349 * so SCRIPT_NAME will be / */
350 py_val
= PyString_FromString("/");
351 if (py_val
== NULL
) goto error
;
352 PyDict_SetItemString(env
, "SCRIPT_NAME", py_val
);
354 questionmark
= strchr(request_string
, '?');
355 if (questionmark
== NULL
) {
356 py_val
= PyString_FromString(request_string
);
357 if (py_val
== NULL
) goto error
;
358 PyDict_SetItemString(env
, "PATH_INFO", py_val
);
361 py_val
= PyString_FromString(questionmark
+1);
362 if (py_val
== NULL
) goto error
;
363 PyDict_SetItemString(env
, "QUERY_STRING", py_val
);
365 py_val
= PyString_FromStringAndSize(request_string
, questionmark
-request_string
);
366 if (py_val
== NULL
) goto error
;
367 PyDict_SetItemString(env
, "PATH_INFO", py_val
);
371 py_val
= PyString_FromString(servername
);
372 if (py_val
== NULL
) goto error
;
373 PyDict_SetItemString(env
, "SERVER_NAME", py_val
);
375 py_val
= PyString_FromFormat("%d", serverport
);
376 if (py_val
== NULL
) goto error
;
377 PyDict_SetItemString(env
, "SERVER_PORT", py_val
);
380 for (hdr
= headers
; hdr
; hdr
= hdr
->next
) {
382 if (!strcasecmp(hdr
->name
, "Content-Type")) {
383 py_val
= PyString_FromString(hdr
->value
);
384 PyDict_SetItemString(env
, "CONTENT_TYPE", py_val
);
387 if (asprintf(&name
, "HTTP_%s", hdr
->name
) < 0) {
391 py_val
= PyString_FromString(hdr
->value
);
392 PyDict_SetItemString(env
, name
, py_val
);
399 py_scheme
= PyString_FromString("https");
401 py_scheme
= PyString_FromString("http");
403 if (py_scheme
== NULL
) goto error
;
404 PyDict_SetItemString(env
, "wsgi.url_scheme", py_scheme
);
405 Py_DECREF(py_scheme
);
413 static void wsgi_serve_500(struct websrv_context
*web
)
415 struct http_header
*headers
= NULL
;
416 const char *contents
[] = {
417 "An internal server error occurred while handling this request. ",
418 "Please refer to the server logs for more details. ",
423 websrv_output_headers(web
, "500 Internal Server Error", headers
);
424 for (i
= 0; contents
[i
]; i
++) {
425 websrv_output(web
, contents
[i
], strlen(contents
[i
]));
429 static void wsgi_process_http_input(struct web_server_data
*wdata
,
430 struct websrv_context
*web
)
432 PyObject
*py_environ
, *result
, *item
, *iter
;
433 PyObject
*request_handler
= (PyObject
*)wdata
->private_data
;
434 struct tsocket_address
*my_address
= web
->conn
->local_address
;
435 const char *addr
= "0.0.0.0";
437 web_request_Object
*py_web
;
438 PyObject
*py_input_stream
;
440 py_web
= PyObject_New(web_request_Object
, &web_request_Type
);
441 if (py_web
== NULL
) {
442 DEBUG_Print_PyError(0, "Unable to allocate web request");
447 if (tsocket_address_is_inet(my_address
, "ip")) {
448 addr
= tsocket_address_inet_addr_string(my_address
, wdata
);
449 port
= tsocket_address_inet_port(my_address
);
452 py_input_stream
= Py_InputHttpStream(web
);
453 if (py_input_stream
== NULL
) {
454 DEBUG_Print_PyError(0, "unable to create python input stream");
458 py_environ
= create_environ(tls_enabled(web
->conn
->socket
),
459 web
->input
.content_length
,
461 web
->input
.post_request
?"POST":"GET",
468 Py_DECREF(py_input_stream
);
470 if (py_environ
== NULL
) {
471 DEBUG_Print_PyError(0, "Unable to create WSGI environment object");
476 result
= PyObject_CallMethod(request_handler
, discard_const_p(char, "__call__"), discard_const_p(char, "OO"),
477 py_environ
, PyObject_GetAttrString((PyObject
*)py_web
, "start_response"));
479 if (result
== NULL
) {
480 DEBUG_Print_PyError(0, "error while handling request");
485 iter
= PyObject_GetIter(result
);
489 DEBUG_Print_PyError(0, "application did not return iterable");
494 /* Now, iter over all the data returned */
496 while ((item
= PyIter_Next(iter
))) {
497 websrv_output(web
, PyString_AsString(item
), PyString_Size(item
));
504 bool wsgi_initialize(struct web_server_data
*wdata
)
506 PyObject
*py_web_server
;
510 py_update_path(); /* Ensure that we have the Samba paths at
511 * the start of the sys.path() */
513 if (PyType_Ready(&web_request_Type
) < 0)
516 if (PyType_Ready(&input_Stream_Type
) < 0)
519 if (PyType_Ready(&error_Stream_Type
) < 0)
522 wdata
->http_process_input
= wsgi_process_http_input
;
523 py_web_server
= PyImport_ImportModule("samba.web_server");
524 if (py_web_server
== NULL
) {
525 DEBUG_Print_PyError(0, "Unable to find web server");
528 wdata
->private_data
= py_web_server
;