/* vim: set sw=4 expandtab : */ /* * Copyright 2007-2008 GRAHAM DUMPLETON * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Enabled access to Apache private API and data structures. Need to do * this to access the following: * * In Apache 1.3 it is not possible to access ap_check_cmd_context() * where as this was made public in Apache 2.0. * * In Apache 2.X need access to ap_create_request_config(). * * In Apache 2.X need access to core_module and core_request_config. * */ #define CORE_PRIVATE 1 #include "httpd.h" #if !defined(AP_SERVER_MAJORVERSION_NUMBER) #if AP_MODULE_MAGIC_AT_LEAST(20010224,0) #define AP_SERVER_MAJORVERSION_NUMBER 2 #define AP_SERVER_MINORVERSION_NUMBER 0 #else #define AP_SERVER_MAJORVERSION_NUMBER 1 #define AP_SERVER_MINORVERSION_NUMBER 3 #endif #endif #if !defined(AP_SERVER_BASEVERSION) #define AP_SERVER_BASEVERSION SERVER_BASEVERSION #endif #if AP_SERVER_MAJORVERSION_NUMBER < 2 typedef int apr_status_t; #define APR_SUCCESS 0 typedef pool apr_pool_t; typedef unsigned int apr_port_t; #include "ap_ctype.h" #include "ap_alloc.h" #define apr_isspace ap_isspace #define apr_table_make ap_make_table #define apr_table_get ap_table_get #define apr_table_set ap_table_set #define apr_table_setn ap_table_setn #define apr_table_add ap_table_add #define apr_table_elts ap_table_elts #define apr_array_make ap_make_array #define apr_array_push ap_push_array #define apr_array_cat ap_array_cat #define apr_array_append ap_append_arrays typedef array_header apr_array_header_t; typedef table apr_table_t; typedef table_entry apr_table_entry_t; typedef int apr_size_t; typedef unsigned long apr_off_t; #define apr_psprintf ap_psprintf #define apr_pstrndup ap_pstrndup #define apr_pstrdup ap_pstrdup #define apr_pstrcat ap_pstrcat #define apr_pcalloc ap_pcalloc #define apr_palloc ap_palloc typedef time_t apr_time_t; #include "http_config.h" typedef int apr_lockmech_e; #else #include "apr_lib.h" #include "ap_mpm.h" #include "ap_compat.h" #include "apr_tables.h" #include "apr_strings.h" #include "http_config.h" #include "ap_listen.h" #include "apr_version.h" #endif #include "ap_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include "http_protocol.h" #include "http_request.h" #include "util_script.h" #include "util_md5.h" #if !AP_MODULE_MAGIC_AT_LEAST(20050127,0) /* Debian backported ap_regex_t to Apache 2.0 and * thus made official version checking break. */ #ifndef AP_REG_EXTENDED typedef regex_t ap_regex_t; typedef regmatch_t ap_regmatch_t; #define AP_REG_EXTENDED REG_EXTENDED #endif #endif #ifndef WIN32 #include #endif #include "Python.h" #include "compile.h" #include "node.h" #include "osdefs.h" #if !defined(PY_VERSION_HEX) || PY_VERSION_HEX <= 0x02030000 #error Sorry, mod_wsgi requires at least Python 2.3.0. #endif #if !defined(WITH_THREAD) #error Sorry, mod_wsgi requires that Python supporting thread. #endif #ifndef PyVarObject_HEAD_INIT #define PyVarObject_HEAD_INIT(type, size) \ PyObject_HEAD_INIT(type) size, #endif #ifndef WIN32 #if AP_SERVER_MAJORVERSION_NUMBER >= 2 #if APR_HAS_OTHER_CHILD && APR_HAS_THREADS && APR_HAS_FORK #define MOD_WSGI_WITH_DAEMONS 1 #endif #endif #endif #if AP_SERVER_MAJORVERSION_NUMBER >= 2 #define MOD_WSGI_WITH_BUCKETS 1 #define MOD_WSGI_WITH_AAA_HANDLERS 1 #endif #if defined(MOD_WSGI_WITH_AAA_HANDLERS) static PyTypeObject Auth_Type; #if AP_SERVER_MAJORVERSION_NUMBER >= 2 #if AP_SERVER_MINORVERSION_NUMBER >= 2 #define MOD_WSGI_WITH_AUTH_PROVIDER 1 #endif #endif #endif #if defined(MOD_WSGI_WITH_AUTH_PROVIDER) #include "mod_auth.h" #include "ap_provider.h" #endif #if defined(MOD_WSGI_WITH_DAEMONS) #if !AP_MODULE_MAGIC_AT_LEAST(20051115,0) static void ap_close_listeners(void) { ap_listen_rec *lr; for (lr = ap_listeners; lr; lr = lr->next) { apr_socket_close(lr->sd); lr->active = 0; } } #endif #if (APR_MAJOR_VERSION == 0) && \ (APR_MINOR_VERSION == 9) && \ (APR_PATCH_VERSION < 5) static apr_status_t apr_unix_file_cleanup(void *thefile) { apr_file_t *file = thefile; return apr_file_close(file); } static apr_status_t apr_os_pipe_put_ex(apr_file_t **file, apr_os_file_t *thefile, int register_cleanup, apr_pool_t *pool) { apr_status_t rv; rv = apr_os_pipe_put(file, thefile, pool); if (register_cleanup) { apr_pool_cleanup_register(pool, (void *)(*file), apr_unix_file_cleanup, apr_pool_cleanup_null); } return rv; } #endif #endif #if AP_SERVER_MAJORVERSION_NUMBER < 2 static char *apr_off_t_toa(apr_pool_t *p, apr_off_t n) { const int BUFFER_SIZE = sizeof(apr_off_t) * 3 + 2; char *buf = apr_palloc(p, BUFFER_SIZE); char *start = buf + BUFFER_SIZE - 1; int negative; if (n < 0) { negative = 1; n = -n; } else { negative = 0; } *start = 0; do { *--start = '0' + (char)(n % 10); n /= 10; } while (n); if (negative) { *--start = '-'; } return start; } #endif /* Compatibility macros for log level and status. */ #if AP_SERVER_MAJORVERSION_NUMBER < 2 #define WSGI_LOG_LEVEL(l) l #define WSGI_LOG_LEVEL_AND_STATUS(l, e) l | (!e ? APLOG_NOERRNO : 0) #else #define WSGI_LOG_LEVEL(l) l, 0 #define WSGI_LOG_LEVEL_AND_STATUS(l, e) l, e #endif #define WSGI_LOG_EMERG(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_EMERG, e) #define WSGI_LOG_ALERT(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_ALERT, e) #define WSGI_LOG_CRIT(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_CRIT, e) #define WSGI_LOG_ERR(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_ERR, e) #define WSGI_LOG_WARNING(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_WARNING, e) #define WSGI_LOG_NOTICE(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_NOTICE, e) #define WSGI_LOG_INFO(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_INFO, e) #define WSGI_LOG_DEBUG(e) WSGI_LOG_LEVEL_AND_STATUS(APLOG_DEBUG, e) /* Version and module information. */ #define MOD_WSGI_MAJORVERSION_NUMBER 3 #define MOD_WSGI_MINORVERSION_NUMBER 0 #define MOD_WSGI_VERSION_STRING "3.0-TRUNK" #if AP_SERVER_MAJORVERSION_NUMBER < 2 module MODULE_VAR_EXPORT wsgi_module; #else module AP_MODULE_DECLARE_DATA wsgi_module; #endif /* Constants. */ #define WSGI_RELOAD_MODULE 0 #define WSGI_RELOAD_PROCESS 1 /* Base server object. */ static server_rec *wsgi_server = NULL; /* Process information. */ static pid_t wsgi_parent_pid = 0; static int wsgi_multiprocess = 1; static int wsgi_multithread = 1; /* Daemon information. */ static const char *wsgi_daemon_group = ""; static apr_array_header_t *wsgi_daemon_list = NULL; static apr_pool_t *wsgi_parent_pool = NULL; static apr_pool_t *wsgi_daemon_pool = NULL; static int volatile wsgi_daemon_shutdown = 0; #if defined(MOD_WSGI_WITH_DAEMONS) static apr_interval_time_t wsgi_deadlock_timeout = 0; static apr_interval_time_t wsgi_inactivity_timeout = 0; static apr_time_t volatile wsgi_deadlock_shutdown_time = 0; static apr_time_t volatile wsgi_inactivity_shutdown_time = 0; static apr_thread_mutex_t* wsgi_shutdown_lock = NULL; #endif /* Configuration objects. */ typedef struct { const char *location; const char *application; ap_regex_t *regexp; const char *process_group; const char *application_group; const char *callable_object; int pass_authorization; } WSGIAliasEntry; typedef struct { const char *handler_script; const char *process_group; const char *application_group; } WSGIScriptFile; typedef struct { apr_pool_t *pool; apr_array_header_t *alias_list; const char *socket_prefix; apr_lockmech_e lock_mechanism; int verbose_debugging; int python_optimize; const char *python_home; const char *python_path; const char *python_eggs; int restrict_embedded; int restrict_stdin; int restrict_stdout; int restrict_signal; int case_sensitivity; apr_table_t *restrict_process; const char *process_group; const char *application_group; const char *callable_object; apr_array_header_t *import_list; WSGIScriptFile *dispatch_script; int apache_extensions; int pass_authorization; int script_reloading; int reload_mechanism; } WSGIServerConfig; static WSGIServerConfig *wsgi_server_config = NULL; static WSGIScriptFile *newWSGIScriptFile(apr_pool_t *p) { WSGIScriptFile *object = NULL; object = (WSGIScriptFile *)apr_pcalloc(p, sizeof(WSGIScriptFile)); object->handler_script = NULL; object->application_group = NULL; object->process_group = NULL; return object; } static WSGIServerConfig *newWSGIServerConfig(apr_pool_t *p) { WSGIServerConfig *object = NULL; object = (WSGIServerConfig *)apr_pcalloc(p, sizeof(WSGIServerConfig)); object->pool = p; object->alias_list = NULL; object->socket_prefix = NULL; #if defined(MOD_WSGI_WITH_DAEMONS) object->socket_prefix = DEFAULT_REL_RUNTIMEDIR "/wsgi"; object->socket_prefix = ap_server_root_relative(p, object->socket_prefix); #endif object->verbose_debugging = 0; object->python_optimize = -1; object->python_home = NULL; object->python_path = NULL; object->python_eggs = NULL; object->restrict_embedded = -1; object->restrict_stdin = -1; object->restrict_stdout = -1; object->restrict_signal = -1; #if defined(WIN32) || defined(DARWIN) object->case_sensitivity = 0; #else object->case_sensitivity = 1; #endif object->restrict_process = NULL; object->process_group = NULL; object->application_group = NULL; object->callable_object = NULL; object->import_list = NULL; object->dispatch_script = NULL; object->apache_extensions = -1; object->pass_authorization = -1; object->script_reloading = -1; object->reload_mechanism = -1; return object; } static void *wsgi_create_server_config(apr_pool_t *p, server_rec *s) { WSGIServerConfig *config = NULL; config = newWSGIServerConfig(p); return config; } static void *wsgi_merge_server_config(apr_pool_t *p, void *base_conf, void *new_conf) { WSGIServerConfig *config = NULL; WSGIServerConfig *parent = NULL; WSGIServerConfig *child = NULL; config = newWSGIServerConfig(p); parent = (WSGIServerConfig *)base_conf; child = (WSGIServerConfig *)new_conf; if (child->alias_list && parent->alias_list) { config->alias_list = apr_array_append(p, child->alias_list, parent->alias_list); } else if (child->alias_list) { config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry)); apr_array_cat(config->alias_list, child->alias_list); } else if (parent->alias_list) { config->alias_list = apr_array_make(p, 20, sizeof(WSGIAliasEntry)); apr_array_cat(config->alias_list, parent->alias_list); } if (child->restrict_process) config->restrict_process = child->restrict_process; else config->restrict_process = parent->restrict_process; if (child->process_group) config->process_group = child->process_group; else config->process_group = parent->process_group; if (child->application_group) config->application_group = child->application_group; else config->application_group = parent->application_group; if (child->callable_object) config->callable_object = child->callable_object; else config->callable_object = parent->callable_object; if (child->dispatch_script) config->dispatch_script = child->dispatch_script; else config->dispatch_script = parent->dispatch_script; if (child->apache_extensions != -1) config->apache_extensions = child->apache_extensions; else config->apache_extensions = parent->apache_extensions; if (child->pass_authorization != -1) config->pass_authorization = child->pass_authorization; else config->pass_authorization = parent->pass_authorization; if (child->script_reloading != -1) config->script_reloading = child->script_reloading; else config->script_reloading = parent->script_reloading; if (child->reload_mechanism != -1) config->reload_mechanism = child->reload_mechanism; else config->reload_mechanism = parent->reload_mechanism; return config; } typedef struct { apr_pool_t *pool; apr_table_t *restrict_process; const char *process_group; const char *application_group; const char *callable_object; WSGIScriptFile *dispatch_script; int apache_extensions; int pass_authorization; int script_reloading; int reload_mechanism; WSGIScriptFile *access_script; WSGIScriptFile *auth_user_script; WSGIScriptFile *auth_group_script; int user_authoritative; int group_authoritative; } WSGIDirectoryConfig; static WSGIDirectoryConfig *newWSGIDirectoryConfig(apr_pool_t *p) { WSGIDirectoryConfig *object = NULL; object = (WSGIDirectoryConfig *)apr_pcalloc(p, sizeof(WSGIDirectoryConfig)); object->pool = p; object->process_group = NULL; object->application_group = NULL; object->callable_object = NULL; object->dispatch_script = NULL; object->apache_extensions = -1; object->pass_authorization = -1; object->script_reloading = -1; object->reload_mechanism = -1; object->access_script = NULL; object->auth_user_script = NULL; object->auth_group_script = NULL; object->user_authoritative = -1; object->group_authoritative = -1; return object; } static void *wsgi_create_dir_config(apr_pool_t *p, char *dir) { WSGIDirectoryConfig *config = NULL; config = newWSGIDirectoryConfig(p); return config; } static void *wsgi_merge_dir_config(apr_pool_t *p, void *base_conf, void *new_conf) { WSGIDirectoryConfig *config = NULL; WSGIDirectoryConfig *parent = NULL; WSGIDirectoryConfig *child = NULL; config = newWSGIDirectoryConfig(p); parent = (WSGIDirectoryConfig *)base_conf; child = (WSGIDirectoryConfig *)new_conf; if (child->restrict_process) config->restrict_process = child->restrict_process; else config->restrict_process = parent->restrict_process; if (child->process_group) config->process_group = child->process_group; else config->process_group = parent->process_group; if (child->application_group) config->application_group = child->application_group; else config->application_group = parent->application_group; if (child->callable_object) config->callable_object = child->callable_object; else config->callable_object = parent->callable_object; if (child->dispatch_script) config->dispatch_script = child->dispatch_script; else config->dispatch_script = parent->dispatch_script; if (child->apache_extensions != -1) config->apache_extensions = child->apache_extensions; else config->apache_extensions = parent->apache_extensions; if (child->pass_authorization != -1) config->pass_authorization = child->pass_authorization; else config->pass_authorization = parent->pass_authorization; if (child->script_reloading != -1) config->script_reloading = child->script_reloading; else config->script_reloading = parent->script_reloading; if (child->reload_mechanism != -1) config->reload_mechanism = child->reload_mechanism; else config->reload_mechanism = parent->reload_mechanism; if (child->access_script) config->access_script = child->access_script; else config->access_script = parent->access_script; if (child->auth_user_script) config->auth_user_script = child->auth_user_script; else config->auth_user_script = parent->auth_user_script; if (child->auth_group_script) config->auth_group_script = child->auth_group_script; else config->auth_group_script = parent->auth_group_script; if (child->user_authoritative != -1) config->user_authoritative = child->user_authoritative; else config->user_authoritative = parent->user_authoritative; if (child->group_authoritative != -1) config->group_authoritative = child->group_authoritative; else config->group_authoritative = parent->group_authoritative; return config; } typedef struct { apr_pool_t *pool; apr_table_t *restrict_process; const char *process_group; const char *application_group; const char *callable_object; WSGIScriptFile *dispatch_script; int apache_extensions; int pass_authorization; int script_reloading; int reload_mechanism; WSGIScriptFile *access_script; WSGIScriptFile *auth_user_script; WSGIScriptFile *auth_group_script; int user_authoritative; int group_authoritative; } WSGIRequestConfig; static int wsgi_find_path_info(const char *uri, const char *path_info) { int lu = strlen(uri); int lp = strlen(path_info); while (lu-- && lp-- && uri[lu] == path_info[lp]) { if (path_info[lp] == '/') { while (lu && uri[lu-1] == '/') lu--; } } if (lu == -1) { lu = 0; } while (uri[lu] != '\0' && uri[lu] != '/') { lu++; } return lu; } static const char *wsgi_script_name(request_rec *r) { char *script_name = NULL; int path_info_start = 0; if (!r->path_info || !*r->path_info) { script_name = apr_pstrdup(r->pool, r->uri); } else { path_info_start = wsgi_find_path_info(r->uri, r->path_info); script_name = apr_pstrndup(r->pool, r->uri, path_info_start); } if (*script_name) { while (*script_name && (*(script_name+1) == '/')) script_name++; script_name = apr_pstrdup(r->pool, script_name); ap_no2slash((char*)script_name); } ap_str_tolower(script_name); return script_name; } static const char *wsgi_process_group(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; if (!s) return ""; if (*s != '%') return s; name = s + 1; if (*name) { if (!strcmp(name, "{GLOBAL}")) return ""; if (strstr(name, "{ENV:") == name) { int len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == '}') { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) { if (*value == '%' && strstr(value, "%{ENV:") != value) return wsgi_process_group(r, value); return value; } } } } return s; } static const char *wsgi_server_group(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; const char *h = NULL; apr_port_t p = 0; if (!s) return ""; if (*s != '%') return s; name = s + 1; if (*name) { if (!strcmp(name, "{SERVER}")) { h = r->server->server_hostname; p = ap_get_server_port(r); if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (!strcmp(name, "{GLOBAL}")) return ""; } return s; } static const char *wsgi_application_group(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; const char *h = NULL; apr_port_t p = 0; const char *n = NULL; if (!s) { h = r->server->server_hostname; p = ap_get_server_port(r); n = wsgi_script_name(r); if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u|%s", h, p, n); else return apr_psprintf(r->pool, "%s|%s", h, n); } if (*s != '%') return s; name = s + 1; if (*name) { if (!strcmp(name, "{RESOURCE}")) { h = r->server->server_hostname; p = ap_get_server_port(r); n = wsgi_script_name(r); if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u|%s", h, p, n); else return apr_psprintf(r->pool, "%s|%s", h, n); } if (!strcmp(name, "{SERVER}")) { h = r->server->server_hostname; p = ap_get_server_port(r); if (p != DEFAULT_HTTP_PORT && p != DEFAULT_HTTPS_PORT) return apr_psprintf(r->pool, "%s:%u", h, p); else return h; } if (!strcmp(name, "{GLOBAL}")) return ""; if (strstr(name, "{ENV:") == name) { int len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == '}') { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) { if (*value == '%' && strstr(value, "%{ENV:") != value) return wsgi_application_group(r, value); return value; } } } } return s; } static const char *wsgi_callable_object(request_rec *r, const char *s) { const char *name = NULL; const char *value = NULL; if (!s) return "application"; if (*s != '%') return s; name = s + 1; if (!*name) return "application"; if (strstr(name, "{ENV:") == name) { int len = 0; name = name + 5; len = strlen(name); if (len && name[len-1] == '}') { name = apr_pstrndup(r->pool, name, len-1); value = apr_table_get(r->notes, name); if (!value) value = apr_table_get(r->subprocess_env, name); if (!value) value = getenv(name); if (value) return value; } } return "application"; } static WSGIRequestConfig *wsgi_create_req_config(apr_pool_t *p, request_rec *r) { WSGIRequestConfig *config = NULL; WSGIServerConfig *sconfig = NULL; WSGIDirectoryConfig *dconfig = NULL; config = (WSGIRequestConfig *)apr_pcalloc(p, sizeof(WSGIRequestConfig)); dconfig = ap_get_module_config(r->per_dir_config, &wsgi_module); sconfig = ap_get_module_config(r->server->module_config, &wsgi_module); config->pool = p; config->restrict_process = dconfig->restrict_process; if (!config->restrict_process) config->restrict_process = sconfig->restrict_process; config->process_group = dconfig->process_group; if (!config->process_group) config->process_group = sconfig->process_group; config->process_group = wsgi_process_group(r, config->process_group); config->application_group = dconfig->application_group; if (!config->application_group) config->application_group = sconfig->application_group; config->application_group = wsgi_application_group(r, config->application_group); config->callable_object = dconfig->callable_object; if (!config->callable_object) config->callable_object = sconfig->callable_object; config->callable_object = wsgi_callable_object(r, config->callable_object); config->dispatch_script = dconfig->dispatch_script; if (!config->dispatch_script) config->dispatch_script = sconfig->dispatch_script; config->apache_extensions = dconfig->apache_extensions; if (config->apache_extensions < 0) { config->apache_extensions = sconfig->apache_extensions; if (config->apache_extensions < 0) config->apache_extensions = 0; } config->pass_authorization = dconfig->pass_authorization; if (config->pass_authorization < 0) { config->pass_authorization = sconfig->pass_authorization; if (config->pass_authorization < 0) config->pass_authorization = 0; } config->script_reloading = dconfig->script_reloading; if (config->script_reloading < 0) { config->script_reloading = sconfig->script_reloading; if (config->script_reloading < 0) config->script_reloading = 1; } config->reload_mechanism = dconfig->reload_mechanism; if (config->reload_mechanism == -1) { config->reload_mechanism = sconfig->reload_mechanism; if (config->reload_mechanism == -1) { if (*config->process_group) config->reload_mechanism = WSGI_RELOAD_PROCESS; else config->reload_mechanism = WSGI_RELOAD_MODULE; } } config->access_script = dconfig->access_script; config->auth_user_script = dconfig->auth_user_script; config->auth_group_script = dconfig->auth_group_script; config->user_authoritative = dconfig->user_authoritative; if (config->user_authoritative == -1) config->user_authoritative = 1; config->group_authoritative = dconfig->group_authoritative; if (config->group_authoritative == -1) config->group_authoritative = 1; return config; } /* Class objects used by response handler. */ static PyTypeObject Dispatch_Type; typedef struct { PyObject_HEAD request_rec *r; int level; char *s; int expired; } LogObject; static PyTypeObject Log_Type; static LogObject *newLogObject(request_rec *r, int level) { LogObject *self; self = PyObject_New(LogObject, &Log_Type); if (self == NULL) return NULL; self->r = r; self->level = APLOG_NOERRNO|level; self->s = NULL; self->expired = 0; return self; } static void Log_dealloc(LogObject *self) { if (self->s) { if (!self->expired) { if (self->r) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, WSGI_LOG_LEVEL(self->level), self->r, "%s", self->s); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_LEVEL(self->level), wsgi_server, "%s", self->s); Py_END_ALLOW_THREADS } } free(self->s); } PyObject_Del(self); } static PyObject *Log_flush(LogObject *self, PyObject *args) { if (self->expired) { PyErr_SetString(PyExc_RuntimeError, "log object has expired"); return NULL; } if (!PyArg_ParseTuple(args, ":flush")) return NULL; if (self->s) { if (self->r) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, WSGI_LOG_LEVEL(self->level), self->r, "%s", self->s); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_LEVEL(self->level), wsgi_server, "%s", self->s); Py_END_ALLOW_THREADS } free(self->s); self->s = NULL; } Py_INCREF(Py_None); return Py_None; } static void Log_output(LogObject *self, const char *msg) { const char *p = NULL; const char *q = NULL; p = msg; q = strchr(p, '\n'); while (q) { /* Output each complete line. */ if (self->s) { /* Need to join with buffered value. */ int m = 0; int n = 0; char *s = NULL; m = strlen(self->s); n = m+q-p+1; s = (char *)malloc(n); strncpy(s, self->s, m); strncpy(s+m, p, q-p); s[n-1] = '\0'; free(self->s); self->s = NULL; if (self->r) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, WSGI_LOG_LEVEL(self->level), self->r, "%s", s); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_LEVEL(self->level), wsgi_server, "%s", s); Py_END_ALLOW_THREADS } free(s); } else { int n = 0; char *s = NULL; n = q-p+1; s = (char *)malloc(n); strncpy(s, p, q-p); s[n-1] = '\0'; if (self->r) { Py_BEGIN_ALLOW_THREADS ap_log_rerror(APLOG_MARK, WSGI_LOG_LEVEL(self->level), self->r, "%s", s); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_LEVEL(self->level), wsgi_server, "%s", s); Py_END_ALLOW_THREADS } free(s); } p = q+1; q = strchr(p, '\n'); } if (*p) { /* Save away incomplete line. */ if (self->s) { /* Need to join with buffered value. */ int m = 0; int n = 0; m = strlen(self->s); n = strlen(p); self->s = (char *)realloc(self->s, m+n+1); strncpy(self->s+m, p, n); self->s[m+n] = '\0'; } else { self->s = (char *)malloc(strlen(p)+1); strcpy(self->s, p); } } } static PyObject *Log_write(LogObject *self, PyObject *args) { const char *msg = NULL; if (self->expired) { PyErr_SetString(PyExc_RuntimeError, "log object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "s:write", &msg)) return NULL; Log_output(self, msg); Py_INCREF(Py_None); return Py_None; } static PyObject *Log_writelines(LogObject *self, PyObject *args) { PyObject *sequence = NULL; PyObject *iterator = NULL; PyObject *item = NULL; const char *msg = NULL; if (self->expired) { PyErr_SetString(PyExc_RuntimeError, "log object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "O:writelines", &sequence)) return NULL; iterator = PyObject_GetIter(sequence); if (iterator == NULL) return NULL; while ((item = PyIter_Next(iterator))) { msg = PyString_AsString(item); if (msg) { Log_output(self, msg); Py_DECREF(item); } else { Py_DECREF(item); break; } } Py_DECREF(iterator); if (item && !msg) return NULL; Py_INCREF(Py_None); return Py_None; } static PyMethodDef Log_methods[] = { { "flush", (PyCFunction)Log_flush, METH_VARARGS, 0}, { "write", (PyCFunction)Log_write, METH_VARARGS, 0}, { "writelines", (PyCFunction)Log_writelines, METH_VARARGS, 0}, { NULL, NULL} }; static PyTypeObject Log_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Log", /*tp_name*/ sizeof(LogObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Log_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Log_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; static void wsgi_log_python_error(request_rec *r, LogObject *log, const char *filename) { PyObject *m = NULL; PyObject *result = NULL; PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; if (!PyErr_Occurred()) return; if (PyErr_ExceptionMatches(PyExc_SystemExit)) { Py_BEGIN_ALLOW_THREADS if (r) { ap_log_rerror(APLOG_MARK, WSGI_LOG_ERR(0), r, "mod_wsgi (pid=%d): SystemExit exception raised by " "WSGI script '%s' ignored.", getpid(), filename); } else { ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): SystemExit exception raised by " "WSGI script '%s' ignored.", getpid(), filename); } Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS if (r) { ap_log_rerror(APLOG_MARK, WSGI_LOG_ERR(0), r, "mod_wsgi (pid=%d): Exception occurred processing " "WSGI script '%s'.", getpid(), filename); } else { ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Exception occurred processing " "WSGI script '%s'.", getpid(), filename); } Py_END_ALLOW_THREADS } PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); if (!value) { value = Py_None; Py_INCREF(value); } if (!traceback) { traceback = Py_None; Py_INCREF(traceback); } m = PyImport_ImportModule("traceback"); if (m) { PyObject *d = NULL; PyObject *o = NULL; d = PyModule_GetDict(m); o = PyDict_GetItemString(d, "print_exception"); if (o) { PyObject *args = NULL; Py_INCREF(o); args = Py_BuildValue("(OOOOO)", type, value, traceback, Py_None, log); result = PyEval_CallObject(o, args); Py_DECREF(args); Py_DECREF(o); } } if (!result) { /* * If can't output exception and traceback then * use PyErr_Print to dump out details of the * exception. For SystemExit though if we do * that the process will actually be terminated * so can only clear the exception information * and keep going. */ PyErr_Restore(type, value, traceback); if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { PyErr_Print(); PyErr_Clear(); } else { PyErr_Clear(); } } else { Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); } Py_XDECREF(result); Py_XDECREF(m); } typedef struct { PyObject_HEAD request_rec *r; int init; int done; char *buffer; apr_size_t size; apr_size_t offset; apr_size_t length; } InputObject; static PyTypeObject Input_Type; static InputObject *newInputObject(request_rec *r) { InputObject *self; self = PyObject_New(InputObject, &Input_Type); if (self == NULL) return NULL; self->r = r; self->init = 0; self->done = 0; self->buffer = NULL; self->size = 0; self->offset = 0; self->length = 0; return self; } static void Input_dealloc(InputObject *self) { if (self->buffer) free(self->buffer); PyObject_Del(self); } static PyObject *Input_close(InputObject *self, PyObject *args) { if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, ":close")) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject *Input_read(InputObject *self, PyObject *args) { long size = -1; PyObject *result = NULL; char *buffer = NULL; apr_size_t length = 0; apr_size_t n; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "|l:read", &size)) return NULL; #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_inactivity_timeout) { apr_thread_mutex_lock(wsgi_shutdown_lock); wsgi_inactivity_shutdown_time = apr_time_now(); wsgi_inactivity_shutdown_time += wsgi_inactivity_timeout; apr_thread_mutex_unlock(wsgi_shutdown_lock); } #endif if (!self->init) { if (!ap_should_client_block(self->r)) self->done = 1; self->init = 1; } /* No point continuing if no more data to be consumed. */ if (self->done && self->length == 0) return PyString_FromString(""); /* * If requested size if zero bytes, then still need to pass * this through to Apache input filters so that any * 100-continue response is triggered. */ if (size == 0) { char dummy[1]; Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, dummy, 0); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } return PyString_FromString(""); } /* * First deal with case where size has been specified. After * that deal with case where expected that all remaining * data is to be read in and returned as one string. */ if (size > 0) { /* Allocate string of the exact size required. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { if (size >= self->length) { length = self->length; memcpy(buffer, self->buffer + self->offset, length); self->offset = 0; self->length = 0; } else { length = size; memcpy(buffer, self->buffer + self->offset, length); self->offset += length; self->length -= length; } } /* If all data residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } /* Read in remaining data required to achieve size. */ if (length < size) { while (length != size) { Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, buffer + length, size - length); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; break; } length += n; } /* * Resize the final string. If the size reduction is * by more than 25% of the string size, then Python * will allocate a new block of memory and copy the * data into it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } } else { /* * Here we are going to try and read in all the * remaining data. First we have to allocate a suitably * large string, but we can't fully trust the amount * that the request structure says is remaining based on * the original content length though, as an input * filter can insert/remove data from the input stream * thereby invalidating the original content length. * What we do is allow for an extra 25% above what we * have already buffered and what the request structure * says is remaining. A value of 25% has been chosen so * as to match best how Python handles resizing of * strings. Note that even though we do this and allow * all available content, strictly speaking the WSGI * specification says we should only read up until content * length. This though is because the WSGI specification * is deficient in dealing with the concept of mutating * input filters. Since read() with no argument is also * not allowed by WSGI specification implement it in the * way which is most logical and ensure that input data * is not truncated. */ size = self->length; if (self->r->remaining > 0) size += self->r->remaining; size = size + (size >> 2); if (size < 256) size = 256; /* Allocate string of the estimated size. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* * Copy any residual data from use of readline(). The * residual should always be less in size than the * string we have allocated to hold it, so can consume * all of it. */ if (self->buffer && self->length) { length = self->length; memcpy(buffer, self->buffer + self->offset, length); self->offset = 0; self->length = 0; free(self->buffer); self->buffer = NULL; } /* Now make first attempt at reading remaining data. */ Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, buffer + length, size - length); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } length += n; /* * Don't just assume that all data has been read if * amount read was less than that requested. Still must * perform a read which returns that no more data found. */ while (!self->done) { /* Increase the size of the string by 25%. */ size = size + (size >> 2); if (_PyString_Resize(&result, size)) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Now make succesive attempt at reading data. */ Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, buffer + length, size - length); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } length += n; } /* * Resize the final string. If the size reduction is by * more than 25% of the string size, then Python will * allocate a new block of memory and copy the data into * it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } return result; } static PyObject *Input_readline(InputObject *self, PyObject *args) { long size = -1; PyObject *result = NULL; char *buffer = NULL; apr_size_t length = 0; apr_size_t n; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "|l:readline", &size)) return NULL; if (!self->init) { if (!ap_should_client_block(self->r)) self->done = 1; self->init = 1; } /* * No point continuing if requested size is zero or if no * more data to read and no buffered data. */ if ((self->done && self->length == 0) || size == 0) return PyString_FromString(""); /* * First deal with case where size has been specified. After * that deal with case where expected that a complete line * is returned regardless of the size. */ if (size > 0) { /* Allocate string of the exact size required. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { char *p = NULL; const char *q = NULL; p = buffer; q = self->buffer + self->offset; while (self->length && length < size) { self->offset++; self->length--; length++; if ((*p++ = *q++) == '\n') break; } /* If all data in residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } } /* * Read in remaining data required to achieve size. Note * that can't just return whatever the first read might * have returned if no EOL encountered as must return * exactly the required size if no EOL unless that would * have exhausted all input. */ while ((!length || buffer[length-1] != '\n') && !self->done && length < size) { char *p = NULL; char *q = NULL; Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, buffer + length, size - length); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } else { /* * Search for embedded EOL in what was read and if * found copy any residual into a buffer for use * next time the read functions are called. */ p = buffer + length; q = p + n; while (p != q) { length++; if (*p++ == '\n') break; } if (p != q) { self->size = q - p; self->buffer = (char *)malloc(self->size); self->offset = 0; self->length = self->size; memcpy(self->buffer, p, self->size); } } } /* * Resize the final string. If the size reduction is * by more than 25% of the string size, then Python * will allocate a new block of memory and copy the * data into it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } else { /* * Here we have to read in a line but where we have no * idea how long it may be. What we can do first is if * we have any residual data from a previous read * operation, see if it contains an EOL. This means we * have to do a search, but this is likely going to be * better than having to resize and copy memory later on. */ if (self->buffer && self->length) { const char *p = NULL; const char *q = NULL; p = self->buffer + self->offset; q = memchr(p, '\n', self->length); if (q) size = q - p; } /* * If residual data buffer didn't contain an EOL, all we * can do is allocate a reasonably sized string and if * that isn't big enough keep increasing it in size. For * this we will start out with a buffer 25% greater in * size than what is stored in the residual data buffer * or one the same size as Apache string size, whichever * is greater. */ if (self->buffer && size < 0) { size = self->length; size = size + (size >> 2); } if (size < HUGE_STRING_LEN) size = HUGE_STRING_LEN; /* Allocate string of the initial size. */ result = PyString_FromStringAndSize(NULL, size); if (!result) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); /* Copy any residual data from use of readline(). */ if (self->buffer && self->length) { char *p = NULL; const char *q = NULL; p = buffer; q = self->buffer + self->offset; while (self->length && length < size) { self->offset++; self->length--; length++; if ((*p++ = *q++) == '\n') break; } /* If all data in residual buffer consumed then free it. */ if (!self->length) { free(self->buffer); self->buffer = NULL; } } /* * Read in remaining data until find an EOL, or until all * data has been consumed. */ while ((!length || buffer[length-1] != '\n') && !self->done) { char *p = NULL; char *q = NULL; Py_BEGIN_ALLOW_THREADS n = ap_get_client_block(self->r, buffer + length, size - length); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "request data read error"); Py_DECREF(result); return NULL; } else if (n == 0) { /* Have exhausted all the available input data. */ self->done = 1; } else { /* * Search for embedded EOL in what was read and if * found copy any residual into a buffer for use * next time the read functions are called. */ p = buffer + length; q = p + n; while (p != q) { length++; if (*p++ == '\n') break; } if (p != q) { self->size = q - p; self->buffer = (char *)malloc(self->size); self->offset = 0; self->length = self->size; memcpy(self->buffer, p, self->size); } if (buffer[length-1] != '\n') { /* Increase size of string and keep going. */ size = size + (size >> 2); if (_PyString_Resize(&result, size)) return NULL; buffer = PyString_AS_STRING((PyStringObject *)result); } } } /* * Resize the final string. If the size reduction is by * more than 25% of the string size, then Python will * allocate a new block of memory and copy the data into * it. */ if (length != size) { if (_PyString_Resize(&result, length)) return NULL; } } return result; } static PyObject *Input_readlines(InputObject *self, PyObject *args) { long hint = 0; long length = 0; PyObject *result = NULL; PyObject *line = NULL; PyObject *rlargs = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "|l:readlines", &hint)) return NULL; result = PyList_New(0); if (!result) return NULL; rlargs = PyTuple_New(0); if (!rlargs) { Py_DECREF(result); return NULL; } while (1) { int n; if (!(line = Input_readline(self, rlargs))) { Py_DECREF(result); result = NULL; break; } if ((n = PyString_Size(line)) == 0) { Py_DECREF(line); break; } if (PyList_Append(result, line) == -1) { Py_DECREF(line); Py_DECREF(result); result = NULL; break; } Py_DECREF(line); length += n; if (hint > 0 && length >= hint) break; } Py_DECREF(rlargs); return result; } static PyMethodDef Input_methods[] = { { "close", (PyCFunction)Input_close, METH_VARARGS, 0}, { "read", (PyCFunction)Input_read, METH_VARARGS, 0}, { "readline", (PyCFunction)Input_readline, METH_VARARGS, 0}, { "readlines", (PyCFunction)Input_readlines, METH_VARARGS, 0}, { NULL, NULL} }; static PyObject *Input_iter(InputObject *self) { if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } Py_INCREF(self); return (PyObject *)self; } static PyObject *Input_iternext(InputObject *self) { PyObject *line = NULL; PyObject *rlargs = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } rlargs = PyTuple_New(0); if (!rlargs) return NULL; line = Input_readline(self, rlargs); Py_DECREF(rlargs); if (!line) return NULL; if (PyString_GET_SIZE(line) == 0) { PyErr_SetObject(PyExc_StopIteration, Py_None); Py_DECREF(line); return NULL; } return line; } static PyTypeObject Input_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Input", /*tp_name*/ sizeof(InputObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Input_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ #if defined(Py_TPFLAGS_HAVE_ITER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ #else Py_TPFLAGS_DEFAULT, /*tp_flags*/ #endif 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ (getiterfunc)Input_iter, /*tp_iter*/ (iternextfunc)Input_iternext, /*tp_iternext*/ Input_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; typedef struct { PyObject_HEAD int result; request_rec *r; #if defined(MOD_WSGI_WITH_BUCKETS) apr_bucket_brigade *bb; #endif WSGIRequestConfig *config; InputObject *input; LogObject *log; int status; const char *status_line; PyObject *headers; PyObject *sequence; int content_length_set; apr_off_t content_length; apr_off_t output_length; } AdapterObject; static PyTypeObject Adapter_Type; typedef struct { PyObject_HEAD AdapterObject *adapter; PyObject *filelike; apr_size_t blksize; } StreamObject; static PyTypeObject Stream_Type; static AdapterObject *newAdapterObject(request_rec *r) { AdapterObject *self; self = PyObject_New(AdapterObject, &Adapter_Type); if (self == NULL) return NULL; self->result = HTTP_INTERNAL_SERVER_ERROR; self->r = r; #if defined(MOD_WSGI_WITH_BUCKETS) self->bb = NULL; #endif self->config = (WSGIRequestConfig *)ap_get_module_config(r->request_config, &wsgi_module); self->status = HTTP_INTERNAL_SERVER_ERROR; self->status_line = NULL; self->headers = NULL; self->sequence = NULL; self->content_length_set = 0; self->content_length = 0; self->output_length = 0; self->input = newInputObject(r); self->log = newLogObject(r, APLOG_ERR); return self; } static void Adapter_dealloc(AdapterObject *self) { Py_XDECREF(self->headers); Py_XDECREF(self->sequence); Py_DECREF(self->input); Py_DECREF(self->log); PyObject_Del(self); } static PyObject *Adapter_start_response(AdapterObject *self, PyObject *args) { const char *status = NULL; PyObject *headers = NULL; PyObject *exc_info = NULL; char* value = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "sO|O:start_response", &status, &headers, &exc_info)) { return NULL; } if (!PyList_Check(headers)) { PyErr_SetString(PyExc_TypeError, "response headers must be a list"); return NULL; } if (exc_info && exc_info != Py_None) { if (self->status_line && !self->headers) { PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; if (!PyArg_ParseTuple(exc_info, "OOO", &type, &value, &traceback)) return NULL; Py_INCREF(type); Py_INCREF(value); Py_INCREF(traceback); PyErr_Restore(type, value, traceback); return NULL; } } else if (self->status_line && !self->headers) { PyErr_SetString(PyExc_RuntimeError, "headers have already been sent"); return NULL; } self->status_line = apr_pstrdup(self->r->pool, status); value = ap_getword(self->r->pool, &status, ' '); errno = 0; self->status = strtol(value, &value, 10); if (*value || errno == ERANGE) { PyErr_SetString(PyExc_TypeError, "status value is not an integer"); return NULL; } if (!*status) { PyErr_SetString(PyExc_ValueError, "status message was not supplied"); return NULL; } Py_XDECREF(self->headers); self->headers = headers; Py_INCREF(self->headers); return PyObject_GetAttrString((PyObject *)self, "write"); } static int Adapter_output(AdapterObject *self, const char *data, int length) { int i = 0; int n = 0; apr_status_t rv; request_rec *r; #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_inactivity_timeout) { apr_thread_mutex_lock(wsgi_shutdown_lock); wsgi_inactivity_shutdown_time = apr_time_now(); wsgi_inactivity_shutdown_time += wsgi_inactivity_timeout; apr_thread_mutex_unlock(wsgi_shutdown_lock); } #endif if (!self->status_line) { PyErr_SetString(PyExc_RuntimeError, "response has not been started"); return 0; } r = self->r; /* Have response headers yet been sent. */ if (self->headers) { /* * Force a zero length read before sending the * headers. This will ensure that if no request * content has been read that any '100 Continue' * response will be flushed and sent back to the * client if client was expecting one. Only * want to do this for 2xx and 3xx status values. */ if (self->status >= 200 && self->status < 400) { PyObject *args = NULL; PyObject *result = NULL; args = Py_BuildValue("(i)", 0); result = Input_read(self->input, args); if (PyErr_Occurred()) PyErr_Clear(); Py_DECREF(args); Py_XDECREF(result); } /* Now setup response headers in request object. */ r->status = self->status; r->status_line = self->status_line; for (i = 0; i < PyList_Size(self->headers); i++) { PyObject *tuple = NULL; PyObject *object1 = NULL; PyObject *object2 = NULL; char *name = NULL; char *value = NULL; tuple = PyList_GetItem(self->headers, i); if (!PyTuple_Check(tuple)) { PyErr_Format(PyExc_TypeError, "list of tuple values " "expected, value of type %.200s found", tuple->ob_type->tp_name); return 0; } if (PyTuple_Size(tuple) != 2) { PyErr_Format(PyExc_ValueError, "tuple of length 2 " "expected, length is %d", PyTuple_Size(tuple)); return 0; } object1 = PyTuple_GetItem(tuple, 0); object2 = PyTuple_GetItem(tuple, 1); if (PyString_Check(object1)) { name = PyString_AsString(object1); } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(object1)) { PyObject *latin_object; latin_object = PyUnicode_AsLatin1String(object1); if (!latin_object) { PyErr_Format(PyExc_TypeError, "header name " "contained non 'latin-1' characters "); return 0; } name = PyString_AsString(latin_object); Py_DECREF(latin_object); } #endif else { PyErr_Format(PyExc_TypeError, "expected string object " "for header name, value of type %.200s " "found", object1->ob_type->tp_name); return 0; } if (PyString_Check(object2)) { value = PyString_AsString(object2); } #if PY_MAJOR_VERSION >= 3 else if (PyUnicode_Check(object2)) { PyObject *latin_object; latin_object = PyUnicode_AsLatin1String(object2); if (!latin_object) { PyErr_Format(PyExc_TypeError, "header value " "contained non 'latin-1' characters "); return 0; } value = PyString_AsString(latin_object); Py_DECREF(latin_object); } #endif else { PyErr_Format(PyExc_TypeError, "expected string object " "for header value, value of type %.200s " "found", object2->ob_type->tp_name); return 0; } if (!strcasecmp(name, "Content-Type")) { #if AP_SERVER_MAJORVERSION_NUMBER < 2 r->content_type = apr_pstrdup(r->pool, value); #else /* * In a daemon child process we cannot call the * function ap_set_content_type() as want to * avoid adding any output filters based on the * type of file being served as this will be * done in the main Apache child process which * proxied the request to the daemon process. */ if (*self->config->process_group) r->content_type = apr_pstrdup(r->pool, value); else ap_set_content_type(r, value); #endif } else if (!strcasecmp(name, "Content-Length")) { char *v = value; long l = 0; errno = 0; l = strtol(v, &v, 10); if (*v || errno == ERANGE || l < 0) { PyErr_SetString(PyExc_ValueError, "invalid content length"); return 0; } ap_set_content_length(r, l); self->content_length_set = 1; self->content_length = l; } else if (!strcasecmp(name, "WWW-Authenticate")) { apr_table_add(r->err_headers_out, name, value); } else { apr_table_add(r->headers_out, name, value); } } /* * If content length not set and dealing with iterable * response from application, see if response is a * sequence consisting of only one item and if so use * the current length of data being output as the * content length to use, given that there cannot be * any further data. */ if (self->sequence && PySequence_Check(self->sequence)) { if (PySequence_Size(self->sequence) == 1) { if (!self->content_length_set) { ap_set_content_length(r, length); self->content_length_set = 1; self->content_length = length; } } if (PyErr_Occurred()) PyErr_Clear(); } /* Need to force output of headers when using Apache 1.3. */ Py_BEGIN_ALLOW_THREADS ap_send_http_header(r); Py_END_ALLOW_THREADS /* * Reset flag indicating whether '100 Continue' response * expected. If we don't do this then if an attempt to read * input for the first time is after headers have been * sent, then Apache is wrongly generate the '100 Continue' * response into the response content. Not sure if this is * a bug in Apache, or that it truly believes that input * will never be read after the response headers have been * sent. */ r->expecting_100 = 0; /* No longer need headers now that they have been sent. */ Py_DECREF(self->headers); self->headers = NULL; } /* * If content length was specified, ensure that we don't * actually output more data than was specified as being * sent as otherwise technically in violation of HTTP RFC. */ if (length) { int output_length = length; if (self->content_length_set) { if (self->output_length < self->content_length) { if (self->output_length + length > self->content_length) { length = self->content_length - self->output_length; } } else length = 0; } self->output_length += output_length; } /* Now output any data. */ if (length) { #if defined(MOD_WSGI_WITH_BUCKETS) apr_bucket *b; /* * When using Apache 2.X can use lower level * bucket brigade APIs. This is preferred as * ap_rwrite()/ap_rflush() will grow memory in * the request pool on each call, which will * result in an increase in memory use over time * when streaming of data is being performed. * The memory is still reclaimed, but only at * the end of the request. Using bucket brigade * API avoids this, and also avoids any copying * of response data due to buffering performed * by ap_rwrite(). */ if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "client connection closed"); return 0; } if (!self->bb) { self->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); } b = apr_bucket_transient_create(data, length, r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(self->bb, b); b = apr_bucket_flush_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(self->bb, b); Py_BEGIN_ALLOW_THREADS rv = ap_pass_brigade(r->output_filters, self->bb); Py_END_ALLOW_THREADS if (rv != APR_SUCCESS) { PyErr_SetString(PyExc_IOError, "failed to write data"); return 0; } Py_BEGIN_ALLOW_THREADS apr_brigade_cleanup(self->bb); Py_END_ALLOW_THREADS #else /* * In Apache 1.3, the bucket brigade system doesn't exist, * so have no choice but to use ap_rwrite()/ap_rflush(). * It is not believed that Apache 1.3 suffers the memory * accumulation problem when streaming lots of data. */ Py_BEGIN_ALLOW_THREADS n = ap_rwrite(data, length, r); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "failed to write data"); return 0; } Py_BEGIN_ALLOW_THREADS n = ap_rflush(r); Py_END_ALLOW_THREADS if (n == -1) { PyErr_SetString(PyExc_IOError, "failed to flush data"); return 0; } #endif } /* * Check whether aborted connection was found when data * being written, otherwise will not be flagged until next * time that data is being written. Early detection is * better as it may have been the last data block being * written and application may think that data has all * been written. In a streaming application, we also want * to avoid any additional data processing to generate any * successive data. */ if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "client connection closed"); return 0; } return 1; } #if AP_SERVER_MAJORVERSION_NUMBER >= 2 /* Split buckets at 1GB when sending large files. */ #define MAX_BUCKET_SIZE (0x40000000) static int Adapter_output_file(AdapterObject *self, apr_file_t* tmpfile, apr_off_t offset, apr_off_t len) { request_rec *r; apr_bucket *b; apr_status_t rv; apr_bucket_brigade *bb; r = self->r; if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "client connection closed"); return 0; } if (len == 0) return 1; bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); if (sizeof(apr_off_t) == sizeof(apr_size_t) || len < MAX_BUCKET_SIZE) { /* Can use a single bucket to send file. */ b = apr_bucket_file_create(tmpfile, offset, (apr_size_t)len, r->pool, r->connection->bucket_alloc); } else { /* Need to create multiple buckets to send file. */ b = apr_bucket_file_create(tmpfile, offset, MAX_BUCKET_SIZE, r->pool, r->connection->bucket_alloc); while (len > MAX_BUCKET_SIZE) { apr_bucket *cb; apr_bucket_copy(b, &cb); APR_BRIGADE_INSERT_TAIL(bb, cb); b->start += MAX_BUCKET_SIZE; len -= MAX_BUCKET_SIZE; } /* Resize just the last bucket */ b->length = (apr_size_t)len; } APR_BRIGADE_INSERT_TAIL(bb, b); b = apr_bucket_eos_create(r->connection->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); Py_BEGIN_ALLOW_THREADS rv = ap_pass_brigade(r->output_filters, bb); Py_END_ALLOW_THREADS if (rv != APR_SUCCESS) { PyErr_SetString(PyExc_IOError, "failed to write data"); return 0; } Py_BEGIN_ALLOW_THREADS apr_brigade_destroy(bb); Py_END_ALLOW_THREADS if (r->connection->aborted) { PyErr_SetString(PyExc_IOError, "client connection closed"); return 0; } return 1; } #endif #if AP_SERVER_MAJORVERSION_NUMBER >= 2 APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); static APR_OPTIONAL_FN_TYPE(ssl_is_https) *wsgi_is_https = NULL; #endif static PyObject *Adapter_environ(AdapterObject *self) { request_rec *r = NULL; PyObject *vars = NULL; PyObject *object = NULL; const apr_array_header_t *head = NULL; const apr_table_entry_t *elts = NULL; int i = 0; const char *scheme = NULL; /* Create the WSGI environment dictionary. */ vars = PyDict_New(); /* Merge the CGI environment into the WSGI environment. */ r = self->r; head = apr_table_elts(r->subprocess_env); elts = (apr_table_entry_t *)head->elts; for (i = 0; i < head->nelts; ++i) { if (elts[i].key) { if (elts[i].val) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_DecodeLatin1(elts[i].val, strlen(elts[i].val), NULL); #else object = PyString_FromString(elts[i].val); #endif PyDict_SetItemString(vars, elts[i].key, object); Py_DECREF(object); } else PyDict_SetItemString(vars, elts[i].key, Py_None); } } /* Now setup all the WSGI specific environment values. */ object = Py_BuildValue("(ii)", 1, 0); PyDict_SetItemString(vars, "wsgi.version", object); Py_DECREF(object); object = PyBool_FromLong(wsgi_multithread); PyDict_SetItemString(vars, "wsgi.multithread", object); Py_DECREF(object); object = PyBool_FromLong(wsgi_multiprocess); PyDict_SetItemString(vars, "wsgi.multiprocess", object); Py_DECREF(object); PyDict_SetItemString(vars, "wsgi.run_once", Py_False); scheme = apr_table_get(r->subprocess_env, "HTTPS"); if (scheme && (!strcasecmp(scheme, "On") || !strcmp(scheme, "1"))) { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString("https"); #else object = PyString_FromString("https"); #endif PyDict_SetItemString(vars, "wsgi.url_scheme", object); Py_DECREF(object); } else { #if PY_MAJOR_VERSION >= 3 object = PyUnicode_FromString("http"); #else object = PyString_FromString("http"); #endif PyDict_SetItemString(vars, "wsgi.url_scheme", object); Py_DECREF(object); } /* * Setup log object for WSGI errors. Don't decrement * reference to log object as keep reference to it. */ object = (PyObject *)self->log; PyDict_SetItemString(vars, "wsgi.errors", object); /* Setup input object for request content. */ object = (PyObject *)self->input; PyDict_SetItemString(vars, "wsgi.input", object); /* Setup file wrapper object for efficient file responses. */ object = PyObject_GetAttrString((PyObject *)self, "file_wrapper"); PyDict_SetItemString(vars, "wsgi.file_wrapper", object); Py_DECREF(object); /* * If Apache extensions are enabled and running in embedded * mode add a CObject reference to the Apache request_rec * structure instance. */ if (!wsgi_daemon_pool && self->config->apache_extensions) { object = PyCObject_FromVoidPtr(self->r, 0); PyDict_SetItemString(vars, "apache.request_rec", object); Py_DECREF(object); } return vars; } static int Adapter_process_file_wrapper(AdapterObject *self) { int done = 0; #ifndef WIN32 #if AP_SERVER_MAJORVERSION_NUMBER >= 2 PyObject *filelike = NULL; PyObject *method = NULL; PyObject *object = NULL; apr_status_t rv = 0; apr_os_file_t fd = -1; apr_file_t *tmpfile = NULL; apr_finfo_t finfo; apr_off_t fd_offset = 0; apr_off_t fo_offset = 0; apr_off_t length = 0; /* Perform file wrapper optimisations where possible. */ if (self->sequence->ob_type != &Stream_Type) return 0; /* * Only attempt to perform optimisations if the * write() function returned by start_response() * function has not been called with non zero length * data. In other words if no prior response content * generated. Technically it could be done, but want * to have a consistent rule about how specifying a * content length affects how much of a file is * sent. Don't want to have to take into * consideration whether write() function has been * called or not as just complicates things. */ if (self->output_length != 0) return 0; /* * Work out if file wrapper is associated with a * file like object, where that file object is * associated with a regular file. If it does then * we can optimise how the contents of the file are * sent out. If no such associated file descriptor * then it needs to be processed like any other * iterable value. */ filelike = ((StreamObject *)self->sequence)->filelike; fd = PyObject_AsFileDescriptor(filelike); if (fd == -1) { PyErr_Clear(); return 0; } /* * On some platforms, such as Linux, sendfile() system call * will not work on UNIX sockets. Thus when using daemon mode * cannot enable that feature. */ if (!wsgi_daemon_pool) apr_os_file_put(&tmpfile, &fd, APR_SENDFILE_ENABLED, self->r->pool); else apr_os_file_put(&tmpfile, &fd, 0, self->r->pool); rv = apr_file_info_get(&finfo, APR_FINFO_SIZE|APR_FINFO_TYPE, tmpfile); if (rv != APR_SUCCESS || finfo.filetype != APR_REG) return 0; /* * Because Python file like objects potentially have * their own buffering layering, or use an operating * system FILE object which also has a buffering * layer on top of a normal file descriptor, need to * determine from the file like object its position * within the file and use that as starting position. * Note that it is assumed that user had flushed any * modifications to the file as necessary. Also, we * need to make sure we remember the original file * descriptor position as will need to restore that * position so it matches the upper buffering layers * when done. This is done to avoid any potential * problems if file like object does anything strange * in its close() method which relies on file position * being what it thought it should be. */ rv = apr_file_seek(tmpfile, APR_CUR, &fd_offset); if (rv != APR_SUCCESS) return 0; method = PyObject_GetAttrString(filelike, "tell"); if (!method) return 0; object = PyEval_CallObject(method, NULL); Py_DECREF(method); if (!object) { PyErr_Clear(); return 0; } if (PyLong_Check(object)) { #if defined(HAVE_LONG_LONG) fo_offset = PyLong_AsLongLong(object); #else fo_offset = PyLong_AsLong(object); #endif } #if PY_MAJOR_VERSION < 3 else if (PyInt_Check(object)) { fo_offset = PyInt_AsLong(object); } #endif else { Py_DECREF(object); return 0; } if (PyErr_Occurred()){ Py_DECREF(object); PyErr_Clear(); return 0; } Py_DECREF(object); /* * For a file wrapper object need to always ensure * that response headers are parsed. This is done so * that if the content length header has been * defined we can get its value and use it to limit * how much of a file is being sent. The WSGI 1.0 * specification says that we are meant to send all * available bytes from the file, however this is * questionable as sending more than content length * would violate HTTP RFC. Note that this doesn't * actually flush the headers out when using Apache * 2.X. This is good, as we want to still be able to * set the content length header if none set and file * is seekable. If processing response headers fails, * then need to return as if done, with error being * logged later. */ if (!Adapter_output(self, "", 0)) return 1; /* * If content length wasn't defined then determine * the amount of data which is available to send and * set the content length response header. Either * way, if can work out length then send data * otherwise fall through and treat it as normal * iterable. */ if (!self->content_length_set) { length = finfo.size - fo_offset; self->output_length += length; ap_set_content_length(self->r, length); self->content_length_set = 1; self->content_length = length; if (Adapter_output_file(self, tmpfile, fo_offset, length)) self->result = OK; done = 1; } else { length = finfo.size - fo_offset; self->output_length += length; /* Use user specified content length instead. */ length = self->content_length; if (Adapter_output_file(self, tmpfile, fo_offset, length)) self->result = OK; done = 1; } /* * Restore position of underlying file descriptor. * If this fails, then not much we can do about it. */ apr_file_seek(tmpfile, APR_SET, &fd_offset); #endif #endif return done; } static int Adapter_run(AdapterObject *self, PyObject *object) { PyObject *vars = NULL; PyObject *start = NULL; PyObject *args = NULL; PyObject *iterator = NULL; PyObject *close = NULL; const char *msg = NULL; int length = 0; #if defined(MOD_WSGI_WITH_DAEMONS) if (wsgi_inactivity_timeout) { apr_thread_mutex_lock(wsgi_shutdown_lock); wsgi_inactivity_shutdown_time = apr_time_now(); wsgi_inactivity_shutdown_time += wsgi_inactivity_timeout; apr_thread_mutex_unlock(wsgi_shutdown_lock); } #endif vars = Adapter_environ(self); start = PyObject_GetAttrString((PyObject *)self, "start_response"); args = Py_BuildValue("(OO)", vars, start); self->sequence = PyEval_CallObject(object, args); if (self->sequence != NULL) { if (!Adapter_process_file_wrapper(self)) { iterator = PyObject_GetIter(self->sequence); if (iterator != NULL) { PyObject *item = NULL; while ((item = PyIter_Next(iterator))) { #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { PyErr_Format(PyExc_TypeError, "sequence of " "byte string values expected, value " "containing non 'latin-1' characters " "found"); Py_DECREF(item); break; } Py_DECREF(item); item = latin_item; } #endif if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "sequence of byte " "string values expected, value of " "type %.200s found", item->ob_type->tp_name); Py_DECREF(item); break; } msg = PyString_AsString(item); length = PyString_Size(item); if (!msg) { Py_DECREF(item); break; } if (length && !Adapter_output(self, msg, length)) { Py_DECREF(item); break; } Py_DECREF(item); } } if (!PyErr_Occurred()) { if (Adapter_output(self, "", 0)) self->result = OK; } Py_XDECREF(iterator); } if (PyErr_Occurred()) { /* * Response content has already been sent, so cannot * return an internal server error as Apache will * append its own error page. Thus need to return OK * and just truncate the response. */ if (self->status_line && !self->headers) self->result = OK; wsgi_log_python_error(self->r, self->log, self->r->filename); } if (PyObject_HasAttrString(self->sequence, "close")) { PyObject *args = NULL; PyObject *data = NULL; close = PyObject_GetAttrString(self->sequence, "close"); args = Py_BuildValue("()"); data = PyEval_CallObject(close, args); Py_DECREF(args); Py_XDECREF(data); Py_DECREF(close); } if (PyErr_Occurred()) wsgi_log_python_error(self->r, self->log, self->r->filename); Py_DECREF(self->sequence); self->sequence = NULL; } Py_DECREF(args); Py_DECREF(start); Py_DECREF(vars); /* * Log warning if more response content generated than was * indicated, or less if there was no errors generated by * the application. */ if (self->content_length_set && ((!PyErr_Occurred() && self->output_length != self->content_length) || (self->output_length > self->content_length))) { ap_log_rerror(APLOG_MARK, WSGI_LOG_DEBUG(0), self->r, "mod_wsgi (pid=%d): Content length mismatch, " "expected %s, response generated %s: %s", getpid(), apr_off_t_toa(self->r->pool, self->content_length), apr_off_t_toa(self->r->pool, self->output_length), self->r->filename); } /* Log details of any final Python exceptions. */ if (PyErr_Occurred()) wsgi_log_python_error(self->r, self->log, self->r->filename); /* * If result indicates an internal server error, then * replace the status line in the request object else * that provided by the application will be what is used * in any error page automatically generated by Apache. */ if (self->result == HTTP_INTERNAL_SERVER_ERROR) self->r->status_line = "500 Internal Server Error"; return self->result; } static PyObject *Adapter_write(AdapterObject *self, PyObject *args) { PyObject *item = NULL; const char *data = NULL; int length = 0; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "O:write", &item)) return NULL; #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(item)) { PyObject *latin_item; latin_item = PyUnicode_AsLatin1String(item); if (!latin_item) { PyErr_Format(PyExc_TypeError, "byte string value expected, " "value containing non 'latin-1' characters found"); Py_DECREF(item); return NULL; } Py_DECREF(item); item = latin_item; } #endif if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "byte string value expected, value " "of type %.200s found", item->ob_type->tp_name); Py_DECREF(item); return NULL; } data = PyString_AsString(item); length = PyString_Size(item); if (!Adapter_output(self, data, length)) return NULL; Py_INCREF(Py_None); return Py_None; } static PyObject *newStreamObject(AdapterObject *adapter, PyObject *filelike, apr_size_t blksize); static PyObject *Adapter_file_wrapper(AdapterObject *self, PyObject *args) { PyObject *filelike = NULL; apr_size_t blksize = HUGE_STRING_LEN; PyObject *result = NULL; if (!self->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } if (!PyArg_ParseTuple(args, "O|l:file_wrapper", &filelike, &blksize)) return NULL; return newStreamObject(self, filelike, blksize); } static PyMethodDef Adapter_methods[] = { { "start_response", (PyCFunction)Adapter_start_response, METH_VARARGS, 0}, { "write", (PyCFunction)Adapter_write, METH_VARARGS, 0}, { "file_wrapper", (PyCFunction)Adapter_file_wrapper, METH_VARARGS, 0}, { NULL, NULL} }; static PyTypeObject Adapter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Adapter", /*tp_name*/ sizeof(AdapterObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Adapter_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ Adapter_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; static PyObject *newStreamObject(AdapterObject *adapter, PyObject *filelike, apr_size_t blksize) { StreamObject *self; self = PyObject_New(StreamObject, &Stream_Type); if (self == NULL) return NULL; self->adapter = adapter; self->filelike = filelike; self->blksize = blksize; Py_INCREF(self->adapter); Py_INCREF(self->filelike); return (PyObject *)self; } static void Stream_dealloc(StreamObject *self) { Py_DECREF(self->filelike); Py_DECREF(self->adapter); PyObject_Del(self); } static PyObject *Stream_iter(StreamObject *self) { if (!self->adapter->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } Py_INCREF(self); return (PyObject *)self; } static PyObject *Stream_iternext(StreamObject *self) { PyObject *method = NULL; PyObject *args = NULL; PyObject *result = NULL; if (!self->adapter->r) { PyErr_SetString(PyExc_RuntimeError, "request object has expired"); return NULL; } method = PyObject_GetAttrString(self->filelike, "read"); if (!method) { PyErr_SetString(PyExc_KeyError, "file like object has no read() method"); return 0; } args = Py_BuildValue("(l)", self->blksize); result = PyEval_CallObject(method, args); Py_DECREF(method); Py_DECREF(args); if (!result) return 0; if (PyString_Check(result)) { if (PyString_Size(result) == 0) { PyErr_SetObject(PyExc_StopIteration, Py_None); Py_DECREF(result); return 0; } return result; } #if PY_MAJOR_VERSION >= 3 if (PyUnicode_Check(result)) { if (PyUnicode_GetSize(result) == 0) { PyErr_SetObject(PyExc_StopIteration, Py_None); Py_DECREF(result); return 0; } return result; } #endif Py_DECREF(result); PyErr_SetString(PyExc_TypeError, "file like object yielded non string type"); return 0; } static PyObject *Stream_close(StreamObject *self, PyObject *args) { PyObject *method = NULL; PyObject *result = NULL; method = PyObject_GetAttrString(self->filelike, "close"); if (method) { result = PyEval_CallObject(method, (PyObject *)NULL); if (!result) PyErr_Clear(); Py_DECREF(method); } Py_XDECREF(result); Py_INCREF(Py_None); return Py_None; } static PyMethodDef Stream_methods[] = { { "close", (PyCFunction)Stream_close, METH_VARARGS, 0}, { NULL, NULL} }; static PyTypeObject Stream_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Stream", /*tp_name*/ sizeof(StreamObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Stream_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ #if defined(Py_TPFLAGS_HAVE_ITER) Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /*tp_flags*/ #else Py_TPFLAGS_DEFAULT, /*tp_flags*/ #endif 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ (getiterfunc)Stream_iter, /*tp_iter*/ (iternextfunc)Stream_iternext, /*tp_iternext*/ Stream_methods, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; /* Restricted object to stop access to STDIN/STDOUT. */ typedef struct { PyObject_HEAD const char *s; } RestrictedObject; static PyTypeObject Restricted_Type; static RestrictedObject *newRestrictedObject(const char *s) { RestrictedObject *self; self = PyObject_New(RestrictedObject, &Restricted_Type); if (self == NULL) return NULL; self->s = s; return self; } static void Restricted_dealloc(RestrictedObject *self) { PyObject_Del(self); } static PyObject *Restricted_getattr(RestrictedObject *self, char *name) { PyErr_Format(PyExc_IOError, "%s access restricted by mod_wsgi", self->s); return NULL; } static PyTypeObject Restricted_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Restricted", /*tp_name*/ sizeof(RestrictedObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Restricted_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)Restricted_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ 0, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; /* Function to restrict access to use of signal(). */ static PyObject *wsgi_signal_intercept(PyObject *self, PyObject *args) { PyObject *h = NULL; int n = 0; PyObject *m = NULL; if (!PyArg_ParseTuple(args, "iO:signal", &n, &h)) return NULL; Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_WARNING(0), wsgi_server, "mod_wsgi (pid=%d): Callback registration for " "signal %d ignored.", getpid(), n); Py_END_ALLOW_THREADS m = PyImport_ImportModule("traceback"); if (m) { PyObject *d = NULL; PyObject *o = NULL; d = PyModule_GetDict(m); o = PyDict_GetItemString(d, "print_stack"); if (o) { PyObject *log = NULL; PyObject *args = NULL; PyObject *result = NULL; Py_INCREF(o); log = (PyObject *)newLogObject(NULL, APLOG_WARNING); args = Py_BuildValue("(OOO)", Py_None, Py_None, log); result = PyEval_CallObject(o, args); Py_XDECREF(result); Py_DECREF(args); Py_DECREF(log); Py_DECREF(o); } } Py_INCREF(m); Py_INCREF(h); return h; } static PyMethodDef wsgi_signal_method[] = { { "signal", (PyCFunction)wsgi_signal_intercept, METH_VARARGS, 0 }, { NULL, NULL } }; /* Wrapper around Python interpreter instances. */ static const char *wsgi_python_path = NULL; static const char *wsgi_python_eggs = NULL; typedef struct { PyObject_HEAD char *name; PyInterpreterState *interp; int owner; } InterpreterObject; static PyTypeObject Interpreter_Type; static InterpreterObject *newInterpreterObject(const char *name, PyInterpreterState *interp) { InterpreterObject *self; PyThreadState *tstate = NULL; PyThreadState *save_tstate = NULL; PyObject *module = NULL; PyObject *object = NULL; PyObject *item = NULL; self = PyObject_New(InterpreterObject, &Interpreter_Type); if (self == NULL) return NULL; /* Remember active thread state so can restore it. */ save_tstate = PyThreadState_Swap(NULL); /* Save away the interpreter name. */ self->name = strdup(name); if (interp) { /* * Interpreter provided to us so will not be * responsible for deleting it later. */ ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Attach interpreter '%s'.", getpid(), name); self->interp = interp; self->owner = 0; /* * Need though now to create a thread state * against the interpreter so we can preload * it with our modules and fixups. */ tstate = PyThreadState_New(self->interp); PyThreadState_Swap(tstate); } else { /* * Create the interpreter. If creation of the * interpreter fails it will restore the * existing active thread state for us so don't * need to worry about it in that case. */ tstate = Py_NewInterpreter(); if (!tstate) { PyErr_SetString(PyExc_RuntimeError, "Py_NewInterpreter() failed"); Py_DECREF(self); return NULL; } Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Create interpreter '%s'.", getpid(), name); Py_END_ALLOW_THREADS self->interp = tstate->interp; self->owner = 1; } /* * Install restricted objects for STDIN and STDOUT, * or log object for STDOUT as appropriate. Don't do * this if not running on Win32 and we believe we * are running in single process mode, otherwise * it prevents use of interactive debuggers such as * the 'pdb' module. */ object = (PyObject *)newLogObject(NULL, APLOG_ERR); PySys_SetObject("stderr", object); Py_DECREF(object); #ifndef WIN32 if (wsgi_parent_pid != getpid()) { #endif if (wsgi_server_config->restrict_stdout != 0) { object = (PyObject *)newRestrictedObject("sys.stdout"); PySys_SetObject("stdout", object); Py_DECREF(object); } else { object = (PyObject *)newLogObject(NULL, APLOG_ERR); PySys_SetObject("stdout", object); Py_DECREF(object); } if (wsgi_server_config->restrict_stdin != 0) { object = (PyObject *)newRestrictedObject("sys.stdin"); PySys_SetObject("stdin", object); Py_DECREF(object); } #ifndef WIN32 } #endif /* * Set sys.argv to one element list to fake out * modules that look there for Python command * line arguments as appropriate. */ object = PyList_New(0); #if PY_MAJOR_VERSION >= 3 item = PyUnicode_FromString("mod_wsgi"); #else item = PyString_FromString("mod_wsgi"); #endif PyList_Append(object, item); PySys_SetObject("argv", object); Py_DECREF(item); Py_DECREF(object); /* * Install intercept for signal handler registration * if appropriate. */ if (wsgi_server_config->restrict_signal != 0) { module = PyImport_ImportModule("signal"); PyModule_AddObject(module, "signal", PyCFunction_New( &wsgi_signal_method[0], NULL)); Py_DECREF(module); } /* * If running in daemon process, override HOME environment * variable so that is matches the home directory of the * user that the process is running as. Need to do this as * Apache will inherit HOME from root user or user that ran * sudo and started Apache and this would be wrong. Can't * update HOME for normal Apache child processes as that * would change the expected environment of other Apache * modules. */ #ifndef WIN32 if (wsgi_daemon_pool) { module = PyImport_ImportModule("os"); if (module) { PyObject *dict = NULL; PyObject *key = NULL; PyObject *value = NULL; dict = PyModule_GetDict(module); object = PyDict_GetItemString(dict, "environ"); if (object) { struct passwd *pwent; pwent = getpwuid(geteuid()); #if PY_MAJOR_VERSION >= 3 key = PyUnicode_FromString("HOME"); value = PyUnicode_DecodeFSDefault(pwent->pw_dir); #else key = PyString_FromString("HOME"); value = PyString_FromString(pwent->pw_dir); #endif PyObject_SetItem(object, key, value); Py_DECREF(key); Py_DECREF(value); } Py_DECREF(module); } } #endif /* * Explicitly override the PYTHON_EGG_CACHE variable if it * was defined by Apache configuration. For embedded processes * this would have been done by using WSGIPythonEggs directive. * For daemon processes the 'python-eggs' option to the * WSGIDaemonProcess directive would have needed to be used. */ if (!wsgi_daemon_pool) wsgi_python_eggs = wsgi_server_config->python_eggs; if (wsgi_python_eggs) { module = PyImport_ImportModule("os"); if (module) { PyObject *dict = NULL; PyObject *key = NULL; PyObject *value = NULL; dict = PyModule_GetDict(module); object = PyDict_GetItemString(dict, "environ"); if (object) { #if PY_MAJOR_VERSION >= 3 key = PyUnicode_FromString("PYTHON_EGG_CACHE"); value = PyUnicode_FromString(wsgi_python_eggs); #else key = PyString_FromString("PYTHON_EGG_CACHE"); value = PyString_FromString(wsgi_python_eggs); #endif PyObject_SetItem(object, key, value); Py_DECREF(key); Py_DECREF(value); } Py_DECREF(module); } } /* * Install user defined Python module search path. This is * added using site.addsitedir() so that any Python .pth * files are opened and additional directories so defined * are added to default Python search path as well. This * allows virtual Python environments to work. */ if (!wsgi_daemon_pool) wsgi_python_path = wsgi_server_config->python_path; if (wsgi_python_path) { module = PyImport_ImportModule("site"); if (module) { PyObject *dict = NULL; dict = PyModule_GetDict(module); object = PyDict_GetItemString(dict, "addsitedir"); if (object) { const char *start; const char *end; const char *value; PyObject *item; PyObject *args; PyObject *result = NULL; Py_INCREF(object); start = wsgi_python_path; end = strchr(start, DELIM); if (end) { #if PY_MAJOR_VERSION >= 3 item = PyUnicode_FromStringAndSize(start, end-start); #else item = PyString_FromStringAndSize(start, end-start); #endif start = end+1; value = PyString_AsString(item); Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Adding '%s' to " "path.", getpid(), value); Py_END_ALLOW_THREADS args = Py_BuildValue("(O)", item); result = PyEval_CallObject(object, args); if (!result) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Call to " "'site.addsitedir()' failed for '%s', " "stopping.", getpid(), value); Py_END_ALLOW_THREADS } Py_XDECREF(result); Py_DECREF(item); Py_DECREF(args); end = strchr(start, DELIM); while (result && end) { #if PY_MAJOR_VERSION >= 3 item = PyUnicode_FromStringAndSize(start, end-start); #else item = PyString_FromStringAndSize(start, end-start); #endif start = end+1; value = PyString_AsString(item); Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Adding '%s' to " "path.", getpid(), value); Py_END_ALLOW_THREADS args = Py_BuildValue("(O)", item); result = PyEval_CallObject(object, args); if (!result) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): " "Call to 'site.addsitedir()' failed " "for '%s', stopping.", getpid(), value); Py_END_ALLOW_THREADS } Py_XDECREF(result); Py_DECREF(item); Py_DECREF(args); end = strchr(start, DELIM); } } Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Adding '%s' to " "path.", getpid(), start); Py_END_ALLOW_THREADS args = Py_BuildValue("(s)", start); result = PyEval_CallObject(object, args); if (!result) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Call to " "'site.addsitedir()' failed for '%s'.", getpid(), start); Py_END_ALLOW_THREADS } Py_XDECREF(result); Py_DECREF(args); Py_DECREF(object); } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Unable to locate " "'site.addsitedir()'.", getpid()); Py_END_ALLOW_THREADS } Py_DECREF(module); } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Unable to import 'site' " "module.", getpid()); Py_END_ALLOW_THREADS } } /* * Create 'mod_wsgi' Python module. We first try and import an * external Python module of the same name. The intent is * that this external module would provide optional features * implementable using pure Python code. Don't want to * include them in the main Apache mod_wsgi package as that * complicates that package and also wouldn't allow them to * be released to a separate schedule. It is easier for * people to replace Python modules package with a new * version than it is to replace Apache module package. */ module = PyImport_ImportModule("mod_wsgi"); if (!module) { PyObject *modules = NULL; modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, "mod_wsgi"); if (module) { PyErr_Print(); PyErr_Clear(); PyDict_DelItemString(modules, "mod_wsgi"); } module = PyImport_AddModule("mod_wsgi"); Py_INCREF(module); } else if (!*name) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Imported 'mod_wsgi'.", getpid()); Py_END_ALLOW_THREADS } /* * Add Apache module version information to the Python * 'mod_wsgi' module. */ PyModule_AddObject(module, "version", Py_BuildValue("(ii)", MOD_WSGI_MAJORVERSION_NUMBER, MOD_WSGI_MINORVERSION_NUMBER)); Py_DECREF(module); /* * Create 'apache' Python module. If this is not a daemon * process and it is the first interpreter created by * Python, we first try and import an external Python module * of the same name. The intent is that this external module * would provide the SWIG bindings for the internal Apache * APIs. Only support use of such bindings in the first * interpreter created due to threading issues in SWIG * generated. */ module = NULL; if (!wsgi_daemon_pool) { module = PyImport_ImportModule("apache"); if (!module) { PyObject *modules = NULL; modules = PyImport_GetModuleDict(); module = PyDict_GetItemString(modules, "apache"); if (module) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Unable to import " "'apache' extension module.", getpid()); Py_END_ALLOW_THREADS PyErr_Print(); PyErr_Clear(); PyDict_DelItemString(modules, "apache"); module = NULL; } } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Imported 'apache'.", getpid()); Py_END_ALLOW_THREADS } } if (!module) { module = PyImport_AddModule("apache"); Py_INCREF(module); } /* * Add Apache version information to the Python 'apache' * module. */ PyModule_AddObject(module, "version", Py_BuildValue("(ii)", AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER)); Py_DECREF(module); /* Restore previous thread state. */ PyThreadState_Clear(tstate); PyThreadState_Swap(save_tstate); PyThreadState_Delete(tstate); return self; } static void Interpreter_dealloc(InterpreterObject *self) { PyThreadState *tstate = NULL; PyObject *exitfunc = NULL; PyObject *module = NULL; /* * We should always enter here with the Python GIL held, but * there will be no active thread state. Note that it should * be safe to always assume that the simplified GIL state * API lock was originally unlocked as always calling in * from an Apache thread outside of Python. */ PyEval_ReleaseLock(); if (*self->name) { tstate = PyThreadState_New(self->interp); PyEval_AcquireThread(tstate); } else PyGILState_Ensure(); if (self->owner) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Destroy interpreter '%s'.", getpid(), self->name); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Cleanup interpreter '%s'.", getpid(), self->name); Py_END_ALLOW_THREADS } /* * Because the thread state we are using was created outside * of any Python code and is not the same as the Python main * thread, there is no record of it within the 'threading' * module. We thus need to call the 'currentThread()' * function of the 'threading' module to force it to create * a thread handle for the thread. If we do not do this, * then the 'threading' modules exit function will always * fail because it will not be able to find a handle for * this thread. */ module = PyImport_ImportModule("threading"); if (!module) PyErr_Clear(); if (module) { PyObject *dict = NULL; PyObject *func = NULL; dict = PyModule_GetDict(module); func = PyDict_GetItemString(dict, "currentThread"); if (func) { PyObject *res = NULL; Py_INCREF(func); res = PyEval_CallObject(func, (PyObject *)NULL); if (!res) { PyErr_Clear(); } Py_XDECREF(res); Py_DECREF(func); } } /* * In Python 2.5.1 an exit function is no longer used to * shutdown and wait on non daemon threads which were created * from Python code. Instead, in Py_Main() it explicitly * calls 'threading._shutdown()'. Thus need to emulate this * behaviour for those versions. */ if (module) { PyObject *dict = NULL; PyObject *func = NULL; dict = PyModule_GetDict(module); func = PyDict_GetItemString(dict, "_shutdown"); if (func) { PyObject *res = NULL; Py_INCREF(func); res = PyEval_CallObject(func, (PyObject *)NULL); if (res == NULL) { PyObject *m = NULL; PyObject *result = NULL; PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Exception occurred within " "threading._shutdown().", getpid()); Py_END_ALLOW_THREADS PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); if (!value) { value = Py_None; Py_INCREF(value); } if (!traceback) { traceback = Py_None; Py_INCREF(traceback); } m = PyImport_ImportModule("traceback"); if (m) { PyObject *d = NULL; PyObject *o = NULL; d = PyModule_GetDict(m); o = PyDict_GetItemString(d, "print_exception"); if (o) { PyObject *log = NULL; PyObject *args = NULL; Py_INCREF(o); log = (PyObject *)newLogObject(NULL, APLOG_ERR); args = Py_BuildValue("(OOOOO)", type, value, traceback, Py_None, log); result = PyEval_CallObject(o, args); Py_DECREF(args); Py_DECREF(log); Py_DECREF(o); } } if (!result) { /* * If can't output exception and traceback then * use PyErr_Print to dump out details of the * exception. For SystemExit though if we do * that the process will actually be terminated * so can only clear the exception information * and keep going. */ PyErr_Restore(type, value, traceback); if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { PyErr_Print(); PyErr_Clear(); } else { PyErr_Clear(); } } else { Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); } Py_XDECREF(result); Py_DECREF(m); } Py_XDECREF(res); Py_DECREF(func); } } /* Finally done with 'threading' module. */ if (module) { Py_DECREF(module); } /* Invoke exit functions by calling sys.exitfunc(). */ exitfunc = PySys_GetObject("exitfunc"); if (exitfunc) { PyObject *res = NULL; Py_INCREF(exitfunc); PySys_SetObject("exitfunc", (PyObject *)NULL); res = PyEval_CallObject(exitfunc, (PyObject *)NULL); if (res == NULL) { PyObject *m = NULL; PyObject *result = NULL; PyObject *type = NULL; PyObject *value = NULL; PyObject *traceback = NULL; if (PyErr_ExceptionMatches(PyExc_SystemExit)) { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): SystemExit exception " "raised by sys.exitfunc() ignored.", getpid()); Py_END_ALLOW_THREADS } else { Py_BEGIN_ALLOW_THREADS ap_log_error(APLOG_MARK, WSGI_LOG_ERR(0), wsgi_server, "mod_wsgi (pid=%d): Exception occurred within " "sys.exitfunc().", getpid()); Py_END_ALLOW_THREADS } PyErr_Fetch(&type, &value, &traceback); PyErr_NormalizeException(&type, &value, &traceback); if (!value) { value = Py_None; Py_INCREF(value); } if (!traceback) { traceback = Py_None; Py_INCREF(traceback); } m = PyImport_ImportModule("traceback"); if (m) { PyObject *d = NULL; PyObject *o = NULL; d = PyModule_GetDict(m); o = PyDict_GetItemString(d, "print_exception"); if (o) { PyObject *log = NULL; PyObject *args = NULL; Py_INCREF(o); log = (PyObject *)newLogObject(NULL, APLOG_ERR); args = Py_BuildValue("(OOOOO)", type, value, traceback, Py_None, log); result = PyEval_CallObject(o, args); Py_DECREF(args); Py_DECREF(log); Py_DECREF(o); } } if (!result) { /* * If can't output exception and traceback then * use PyErr_Print to dump out details of the * exception. For SystemExit though if we do * that the process will actually be terminated * so can only clear the exception information * and keep going. */ PyErr_Restore(type, value, traceback); if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { PyErr_Print(); PyErr_Clear(); } else { PyErr_Clear(); } } else { Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); } Py_XDECREF(result); Py_DECREF(m); } Py_XDECREF(res); Py_DECREF(exitfunc); } /* If we own it, we destroy it. */ if (!self->owner) { if (*self->name) { tstate = PyThreadState_Get(); PyThreadState_Clear(tstate); PyEval_ReleaseThread(tstate); PyThreadState_Delete(tstate); } else PyGILState_Release(PyGILState_UNLOCKED); PyEval_AcquireLock(); } else Py_EndInterpreter(tstate); free(self->name); PyObject_Del(self); } static PyTypeObject Interpreter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "mod_wsgi.Interpreter", /*tp_name*/ sizeof(InterpreterObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)Interpreter_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ 0, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ 0, /*tp_methods*/ 0, /*tp_members*/ 0, /*tp_getset*/ 0, /*tp_base*/ 0, /*tp_dict*/ 0, /*tp_descr_get*/ 0, /*tp_descr_set*/ 0, /*tp_dictoffset*/ 0, /*tp_init*/ 0, /*tp_alloc*/ 0, /*tp_new*/ 0, /*tp_free*/ 0, /*tp_is_gc*/ }; /* * Startup and shutdown of Python interpreter. In mod_wsgi if * the Python interpreter hasn't been initialised by another * Apache module such as mod_python, we will take control and * initialise it. Need to remember that we initialised Python as * in doing that we also take responsibility for performing * special Python fixups after Apache is forked and child * process has run. */ static int wsgi_python_initialized = 0; static void wsgi_python_version(void) { const char *compile = PY_VERSION; const char *dynamic = 0; dynamic = strtok((char *)Py_GetVersion(), " "); if (strcmp(compile, dynamic) != 0) { ap_log_error(APLOG_MARK, WSGI_LOG_WARNING(0), wsgi_server, "mod_wsgi: Compiled for Python/%s.", compile); ap_log_error(APLOG_MARK, WSGI_LOG_WARNING(0), wsgi_server, "mod_wsgi: Runtime using Python/%s.", dynamic); ap_log_error(APLOG_MARK, WSGI_LOG_WARNING(0), wsgi_server, "mod_wsgi: Python module path '%s'.", Py_GetPath()); } } static apr_status_t wsgi_python_term(void *data) { PyInterpreterState *interp = NULL; PyThreadState *tstate = NULL; ap_log_error(APLOG_MARK, WSGI_LOG_INFO(0), wsgi_server, "mod_wsgi (pid=%d): Terminating Python.", getpid()); PyEval_AcquireLock(); interp = PyInterpreterState_Head(); while (interp->next) interp = interp->next; tstate = PyThreadState_New(interp); PyThreadState_Swap(tstate); Py_Finalize(); PyT