/*
 * Copyright (C) 2000, 2001, 2013, 2024 Gregory Trubetskoy
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Apache Software Foundation
 *
 * 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.
 *
 * Originally developed by Gregory Trubetskoy.
 *
 *
 * mod_python.c
 *
 *
 * See accompanying documentation and source code comments
 * for details.
 *
 */

#include "mod_python.h"

static PyThreadState* global_tstate;

/* Server object for main server as supplied to python_init(). */
static server_rec *main_server = NULL;

/* List of available Python obCallBacks/Interpreters */
static apr_hash_t *interpreters = NULL;
static apr_pool_t *interp_pool = NULL;
static void release_interpreter(interpreterdata *idata);

apr_pool_t *child_init_pool = NULL;

/* Optional functions imported from mod_include when loaded: */
static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *optfn_register_include_handler;
static APR_OPTIONAL_FN_TYPE(ap_ssi_get_tag_and_value)    *optfn_ssi_get_tag_and_value;
static APR_OPTIONAL_FN_TYPE(ap_ssi_parse_string)         *optfn_ssi_parse_string;

/*
 * Python3 uses wchar_t * for strings; Python2 uses char *.
 * So for printing we need a different format string.
 */
#if PY_MAJOR_VERSION < 3
# define PRIs "%s"
#else
# define PRIs "%ls"
#endif

/**
 ** make_obcallback
 **
 *      This function instantiates an obCallBack object.
 *      NOTE: The obCallBack object is instantiated by Python
 *      code. This C module calls into Python code which returns
 *      the reference to obCallBack.
 */

static PyObject * make_obcallback(const char *name)
{

    PyObject *m = NULL;
    PyObject *obCallBack = NULL;

    /* Now execute the equivalent of
     * >>> import <module>
     * >>> <initstring>
     * in the __main__ module to start up Python.
     */

    m = PyImport_ImportModule("mod_python.apache");
    if (!m) {
        PyObject *path;
        char *c_path;

        ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                     "make_obcallback: could not import mod_python.apache.\n");

        PyErr_Print();
        fflush(stderr);

        path = PyObject_Repr(PySys_GetObject("path"));
        MP_ANYSTR_AS_STR(c_path, path, 0);

        ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                     "make_obcallback: Python path being used \"%s\".",
                     c_path);

        Py_DECREF(path);

        return NULL;

    } else {

        /* Make sure that C and Python code have the same version */

        const char *mp_dynamic_version = "<unknown>";
        PyObject *mp = PyImport_ImportModule("mod_python");

        if (mp) {
            PyObject *d = PyModule_GetDict(mp);
            PyObject *o = PyDict_GetItemString(d, "mp_version");
            PyObject *f = PyDict_GetItemString(d, "__file__");

            MP_ANYSTR_AS_STR(mp_dynamic_version, o, 1);
            if (!mp_dynamic_version) {
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                             "make_obcallback: fatal: mp_dynamic_version is NULL.");
                Py_DECREF(o);
                Py_DECREF(mp);
                return NULL;
            }

            if (strcmp(mp_version_string, mp_dynamic_version) != 0) {
                char *c_f;
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server,
                             "WARNING: mod_python version mismatch, expected '%s', found '%s'.",
                             mp_version_string, mp_dynamic_version);
                MP_ANYSTR_AS_STR(c_f, f, 1);
                ap_log_error(APLOG_MARK, APLOG_WARNING, 0, main_server,
                             "WARNING: mod_python modules location '%s'.", c_f);
                Py_DECREF(f); /* MP_ANYSTR_AS_STR */
            }
            Py_DECREF(o); /* MP_ANYSTR_AS_STR */
            Py_XDECREF(mp);

            /* call init to get obCallBack */

            if (! (obCallBack = PyObject_CallMethod(
                       m, "init", "sO", name, MpServer_FromServer(main_server)))) {

                ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                             "make_obcallback: could not call init().");

                PyErr_Print();
                fflush(stderr);
            }
        } else {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                         "make_obcallback: could not import mod_python");
        }
    }

    Py_XDECREF(m);

    return obCallBack;
}

/**
 ** save_interpreter
 **
 *      Save the interpreter. The argument is a PyThreadState
 *      belonging to (i.e. tstate->interp) the interpreter we are
 *      saving. Creates a new interpreterdata and returns a pointer to
 *      it.
 */

static interpreterdata *save_interpreter(const char *name, PyThreadState *tstate)
{
    interpreterdata *idata = NULL;

    idata = (interpreterdata *)malloc(sizeof(interpreterdata));
    if (!idata)
        return NULL;
    idata->tstates = apr_array_make(interp_pool, 128, sizeof(interpreterdata *));
    idata->interp = tstate->interp;
    idata->obcallback = NULL;

    apr_hash_set(interpreters, name, APR_HASH_KEY_STRING, idata);

    return idata;
}

/*
 * python_interpreter_name
 *
 *      Get name of current interpreter. Must be called while lock is held.
 *      This is effectively a shortcut for accessing "apache.interpreter".
 */

PyObject *python_interpreter_name()
{
    PyObject *m = NULL;
    PyObject *d = NULL;
    PyObject *o = NULL;

    m = PyImport_ImportModule("mod_python.apache");
    if (m) {
        d = PyModule_GetDict(m);
        o = PyDict_GetItemString(d, "interpreter");

        if (o) {
            Py_INCREF(o);
            Py_DECREF(m);
            return o;
        }
    }

    return 0;
}

/**
 ** get_interpreter
 **
 *      Get interpreter given its name.
 *
 *  A thread state never outlives a handler; when a phase of a handler
 *  is done, there is no use for this thread state. But there is no
 *  need to malloc() a new thread state at every request, so when a
 *  handler is done, in release_interpreter() we push the tstate into
 *  an array of available tstates for this interpreter. When we need
 *  another tstate, we pop from this array, and only if that returns
 *  nothing do we create a new tstate.
 *
 */

static interpreterdata *get_interpreter(const char *name)
{
    PyThreadState *tstate;
    interpreterdata *idata = NULL;

    if (! name)
        name = MAIN_INTERPRETER;

    /* Py_NewInterpreter requires the GIL held, this is one way to have it so */
    PyEval_RestoreThread(global_tstate);

    idata = apr_hash_get(interpreters, name, APR_HASH_KEY_STRING);

    if (!idata) {

        tstate = Py_NewInterpreter();

        if (!tstate) {
            /* couldn't create an interpreter, this is bad */
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                         "get_interpreter: Py_NewInterpreter() returned NULL. No more memory?");
            return NULL;
        }

        idata = save_interpreter(name, tstate);
        if (!idata) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                         "get_interpreter: save_interpreter() returned NULL. No more memory?");
            return NULL;
        }
    }
    else {

#ifdef WITH_THREAD
        /* use an available tstate, or create a new one */
        PyThreadState ** tstate_pp;
        tstate_pp = (PyThreadState **)apr_array_pop(idata->tstates);
        if (!tstate_pp)
            tstate = PyThreadState_New(idata->interp);
        else
            tstate = *tstate_pp;
#else
        /* always use the first (and only) tstate */
        tstate = idata->interp->tstate_head;
#endif

        PyThreadState_Swap(tstate);

    }
    /* At this point GIL is held and tstate is set, we're ready to run */

    if (!idata->obcallback) {

        idata->obcallback = make_obcallback(name);

        if (!idata->obcallback) {
            release_interpreter(idata);
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                         "get_interpreter: no interpreter callback found.");
            return NULL;
        }
    }

    return idata;
}

/**
 ** release_interpreter
 **
 *      Release interpreter.
 */

static void release_interpreter(interpreterdata *idata)
{
    PyThreadState *tstate = PyThreadState_Get();
#ifdef WITH_THREAD
#if PY_MAJOR_VERSION <= 3 && PY_MINOR_VERSION < 9
    PyThreadState_Clear(tstate);
#endif
    if (idata)
        APR_ARRAY_PUSH(idata->tstates, PyThreadState *) = tstate;
    else
        PyThreadState_Delete(tstate);
    PyEval_ReleaseThread(tstate);
#else
    if (!idata) PyThreadState_Delete(tstate);
#endif
}

/**
 ** pytho_cleanup
 **
 *     This function gets called for clean ups registered
 *     with register_cleanup(). Clean ups registered via
 *     PythonCleanupHandler run in python_cleanup_handler()
 *     below.
 */

apr_status_t python_cleanup(void *data)
{
    interpreterdata *idata;

    cleanup_info *ci = (cleanup_info *)data;

    /* get/create interpreter */
    idata = get_interpreter(ci->interpreter);

    if (!idata) {
        Py_DECREF(ci->handler);
        Py_XDECREF(ci->data);
        free((void *)ci->interpreter);
        free(ci);
        return APR_SUCCESS; /* this is ignored anyway */
    }

    /*
     * Call the cleanup function.
     */
    if (! PyObject_CallFunction(ci->handler, "O", ci->data)) {
        PyObject *ptype;
        PyObject *pvalue;
        PyObject *ptb;
        PyObject *handler;
        PyObject *stype;
        PyObject *svalue;
        char *c_handler, *c_svalue, *c_stype;

        PyErr_Fetch(&ptype, &pvalue, &ptb);
        handler = PyObject_Str(ci->handler);
        stype = PyObject_Str(ptype);
        svalue = PyObject_Str(pvalue);

        Py_XDECREF(ptype);
        Py_XDECREF(pvalue);
        Py_XDECREF(ptb);

        MP_ANYSTR_AS_STR(c_handler, handler, 0);
        if (!c_handler) c_handler = "<ERROR>";
        MP_ANYSTR_AS_STR(c_svalue, svalue, 0);
        if (!c_svalue) c_svalue = "<ERROR>";
        MP_ANYSTR_AS_STR(c_stype, stype, 0);
        if (!c_stype) c_stype = "<ERROR>";

        if (ci->request_rec) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0,
                          ci->request_rec,
                          "python_cleanup: Error calling cleanup object %s",
                          c_handler);
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0,
                          ci->request_rec,
                          "    %s: %s", c_stype, c_svalue);
        }
        else {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0,
                         ci->server_rec,
                         "python_cleanup: Error calling cleanup object %s",
                         c_handler);
            ap_log_error(APLOG_MARK, APLOG_ERR, 0,
                         ci->server_rec,
                          "    %s: %s", c_stype, c_svalue);
        }

        Py_DECREF(handler);
        Py_DECREF(stype);
        Py_DECREF(svalue);
    }

    Py_DECREF(ci->handler);
    Py_DECREF(ci->data);
    free((void *)ci->interpreter);
    free(ci);

    release_interpreter(idata);

    return APR_SUCCESS;
}

static apr_status_t init_mutexes(server_rec *s, apr_pool_t *p, py_global_config *glb)
{
    int max_threads = 0;
    int max_procs = 0;
    int is_threaded = 0;
    int is_forked = 0;
    int max_clients;
    int locks;
    int n;
    const char *val;
    const char *mutex_dir;
    py_config *conf;

    conf = (py_config *) ap_get_module_config(s->module_config,
                                           &python_module);

    /* figure out maximum possible concurrent connections */
    /* MAX_DAEMON_USED seems to account for MaxClients, as opposed to
       MAX_DAEMONS, which is ServerLimit
    */
    ap_mpm_query(AP_MPMQ_IS_THREADED, &is_threaded);
    if (is_threaded != AP_MPMQ_NOT_SUPPORTED) {
        ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads);
    }
    ap_mpm_query(AP_MPMQ_IS_FORKED, &is_forked);
    if (is_forked != AP_MPMQ_NOT_SUPPORTED) {
        /* XXX This looks strange, and it is. prefork.c seems to use
           MAX_DAEMON_USED the same way that worker.c uses
           MAX_DAEMONS (prefork is wrong IMO) */
        ap_mpm_query(AP_MPMQ_MAX_DAEMON_USED, &max_procs);
        if (max_procs == -1) {
            ap_mpm_query(AP_MPMQ_MAX_DAEMONS, &max_procs);
        }
    }
    max_clients = (((max_threads <= 0) ? 1 : max_threads) *
                   ((max_procs <= 0) ? 1 : max_procs));

    /* On some systems the locking mechanism chosen uses valuable
       system resources, notably on RH 8 it will use sysv ipc for
       which Linux by default provides only 128 semaphores
       system-wide, and on many other systems flock is used, which
       results in a relatively large number of open files.

       The maximum number of locks can be specified at
       compile time using "./configure --with-max-locks value" or
       at run time with "PythonOption mod_python.mutex_locks value".

       If the PythonOption directive is used, it must be in a
       server config context, otherwise it will be ignored.

       The optimal number of necessary locks is not clear, perhaps a
       small number is more than sufficient - if someone took the
       time to run some research on this, that'd be most welcome!
    */
    val = apr_table_get(conf->options, "mod_python.mutex_locks");
    if (val) {
        locks = atoi(val);
    } else {
        locks = MAX_LOCKS;
    }

    locks = (max_clients > locks) ? locks : max_clients;

    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
                 "mod_python: Creating %d session mutexes based "
                 "on %d max processes and %d max threads.",
                 locks, max_procs, max_threads);

    glb->g_locks = (apr_global_mutex_t **)
        apr_palloc(p, locks * sizeof(apr_global_mutex_t *));
    glb->nlocks = locks;
    glb->parent_pid = getpid();

#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
    /* On some sytems a directory for the mutex lock files is required.
       This mutex directory can be specifed at compile time using
       "./configure --with-mutex-dir value" or at run time with
       "PythonOption mod_python.mutex_directory value".

       If the PythonOption directive is used, it must be in a
       server config context, otherwise it will be ignored.

       XXX Should we check if mutex_dir exists and has the correct
       permissions?
    */
    mutex_dir = apr_table_get(conf->options, "mod_python.mutex_directory");
    if (!mutex_dir)
        mutex_dir = MUTEX_DIR;

    ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s,
                 "mod_python: using mutex_directory %s ",
                 mutex_dir);
#endif

    for (n=0; n<locks; n++) {
        apr_status_t rc;
        apr_global_mutex_t **mutex = glb->g_locks;

#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
        char fname[255];
        /* XXX What happens if len(mutex_dir) > 255 - len(mpmtx%d%d)? */
        snprintf(fname, 255, "%s/mpmtx%d%d", mutex_dir, glb->parent_pid, n);
#else
        char *fname = NULL;
#endif
        rc = apr_global_mutex_create(&mutex[n], fname, APR_LOCK_DEFAULT,
                                     p);
        if (rc != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
                         "mod_python: Failed to create global mutex %d of %d (%s).",
                         n, locks, (!fname) ? "<null>" : fname);
            if (n > 1) {
                /* we were able to create at least two, so lets just print a
                   warning/hint and proceed
                */
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                             "mod_python: We can probably continue, but with diminished ability "
                             "to process session locks.");
                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                             "mod_python: Hint: On Linux, the problem may be the number of "
                             "available semaphores, check 'sysctl kernel.sem'");
                /* now free two locks so that if there is another
                   module or two that wants a lock, it will be ok */
                apr_global_mutex_destroy(mutex[n-1]);
                glb->nlocks = n-1;
                if (n > 2) {
                  apr_global_mutex_destroy(mutex[n-2]);
                  glb->nlocks = n-2;
                }
                break;

            }
            else {
                return rc;
            }
        }
        else {

#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
#if AP_MODULE_MAGIC_AT_LEAST(20081201,0)
            ap_unixd_set_global_mutex_perms(mutex[n]);
#else
            if (!geteuid()) {
                chown(fname, unixd_config.user_id, -1);
                unixd_set_global_mutex_perms(mutex[n]);
            }
#endif
#endif
        }
    }
    return APR_SUCCESS;
}

static apr_status_t reinit_mutexes(server_rec *s, apr_pool_t *p, py_global_config *glb)
{
    int n;

#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
    /* Determine the directory to use for mutex lock files.
       See init_mutexes function for more details.
    */
    const char *mutex_dir;
    py_config *conf;

    conf = (py_config *) ap_get_module_config(s->module_config,
                                           &python_module);
    mutex_dir = apr_table_get(conf->options, "mod_python.mutex_directory");
    if (!mutex_dir)
        mutex_dir = MUTEX_DIR;
#endif

    for (n=0; n< glb->nlocks; n++) {
        apr_status_t rc;
        apr_global_mutex_t **mutex = glb->g_locks;

#if !defined(OS2) && !defined(WIN32) && !defined(BEOS) && !defined(NETWARE)
        char fname[255];
        snprintf(fname, 255, "%s/mpmtx%d%d", mutex_dir, glb->parent_pid, n);
#else
        char *fname = NULL;
#endif
        rc = apr_global_mutex_child_init(&mutex[n], fname, p);

        if (rc != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_STARTUP, rc, s,
                         "mod_python: Failed to reinit global mutex %s.",
                         (!fname) ? "<null>" : fname);
            return rc;
        }
    }
    return APR_SUCCESS;
}

/**
 ** python_create_global_config
 **
 *      This creates the part of config that survives
 *  server restarts
 *
 */

static py_global_config *python_create_global_config(server_rec *s)
{
    apr_pool_t *pool = s->process->pool;
    py_global_config *glb;

    /* do we already have it in s->process->pool? */
    apr_pool_userdata_get((void **)&glb, MP_CONFIG_KEY, pool);

    if (glb) {
        return glb;
    }

    /* otherwise, create it */

    glb = (py_global_config *)apr_palloc(pool, sizeof(*glb));

    apr_pool_userdata_set(glb, MP_CONFIG_KEY,
                          apr_pool_cleanup_null,
                          pool);

    return glb;
}

/*
 * mp_acquire_interpreter()
 *
 *      Exported function for acquiring named interpreter.
 */

PyInterpreterState *mp_acquire_interpreter(const char *name)
{
    interpreterdata *idata;

    idata = get_interpreter(name);

    return idata->interp;
}

/*
 * mp_release_interpreter()
 *
 *      Exported function for releasing acquired interpreter.
 *
 */

void mp_release_interpreter(void)
{
    release_interpreter(NULL);
}

/*
 * mp_get_request_object(request_rec *req)
 *
 *      Exported function for obtaining wrapper for request object.
 *
 */

PyObject *mp_get_request_object(request_rec *req)
{
    requestobject *request_obj;

    request_obj = python_get_request_object(req, 0);

    return (PyObject *)request_obj;
}

/*
 * mp_get_server_object(server_rec *srv)
 *
 *      Exported function for obtaining wrapper for server object.
 *
 */

PyObject *mp_get_server_object(server_rec *srv)
{
    return (PyObject *)MpServer_FromServer(srv);
}

/*
 * mp_get_connection_object(conn_rec *conn)
 *
 *      Exported function for obtaining wrapper for connection object.
 *
 */

PyObject *mp_get_connection_object(conn_rec *conn)
{
    return (PyObject *)MpConn_FromConn(conn);
}

/**
 ** python_init()
 **
 *      Called by Apache at mod_python initialization time.
 */

static int python_init(apr_pool_t *p, apr_pool_t *ptemp,
                       apr_pool_t *plog, server_rec *s)
{
    char buff[255];
    void *data;
    py_global_config *glb;
    const char *userdata_key = "python_init";
    apr_status_t rc;

    const char *py_dynamic_version = 0;

    /* The "initialized" flag is a fudge for Mac OS X. It
     * addresses two issues. The first is that when an Apache
     * "restart" is performed, Apache will unload the mod_python
     * shared object, but not the Python framework. This means
     * that when "python_init()" is called after the restart,
     * the mod_python initialization will not run if only the
     * initialized state of Python is checked, because Python
     * is already initialized. The second problem is that for
     * some older revisions of Mac OS X, even on the initial
     * startup of Apache, the "Py_IsInitialized()" function
     * would return true and mod_python wouldn't initialize
     * itself correctly and would crash.
     */
    static int initialized = 0;

#ifdef WIN32
    /* No need to run python_init() in Win32 parent processes as
     * the lack of fork on Win32 means we get no benefit as far as
     * inheriting a preinitialized Python interpreter. Further,
     * upon a restart on Win32 platform the python_init() function
     * will be called again in the parent process but without some
     * resources allocated by the previous call having being
     * released properly, resulting in memory and Win32 resource
     * leaks.
     */
    if (!getenv("AP_PARENT_PID"))
        return OK;
#endif /* WIN32 */

    apr_pool_userdata_get(&data, userdata_key, s->process->pool);
    if (!data) {
        apr_pool_userdata_set((const void *)1, userdata_key,
                              apr_pool_cleanup_null, s->process->pool);
        return OK;
    }

    /* mod_python version */
    ap_add_version_component(p, mp_version_component);

    py_dynamic_version = strtok(apr_pstrdup(p, Py_GetVersion()), " ");

    /* Python version */
    sprintf(buff, "Python/%.200s", py_dynamic_version);
    ap_add_version_component(p, buff);

    /* cache main server */
    main_server = s;

    /* global config */
    glb = python_create_global_config(s);
    if ((rc = init_mutexes(s, p, glb)) != APR_SUCCESS) {
        return rc;
    }

    /* initialize global Python interpreter if necessary */
    if (initialized == 0 || !Py_IsInitialized())
    {
        initialized = 1;
#if PY_VERSION_HEX < 0x030C0000
        /* disable user site directories */
        Py_NoUserSiteDirectory = 1;
#endif
        /* Initialze the main interpreter. */
#if PY_MAJOR_VERSION == 2 && \
                (PY_MINOR_VERSION < 7 || (PY_MINOR_VERSION == 7 && PY_MICRO_VERSION < 14))
        /*
         * We do not want site.py to
         * be imported because as of Python 2.7.9 it would cause a
         * circular dependency related to _locale which breaks
         * graceful restart so we set Py_NoSiteFlag to 1 just for this
         * one time. (https://github.com/grisha/mod_python/issues/46)
         */
        Py_NoSiteFlag = 1;
#endif

#if PY_MAJOR_VERSION == 2
        PyMODINIT_FUNC init_apache(void);
        PyImport_AppendInittab("_apache", &init_apache);
#else
        PyMODINIT_FUNC PyInit_apache(void);
        PyImport_AppendInittab("_apache", &PyInit_apache);
#endif

        Py_Initialize();

#if PY_MAJOR_VERSION == 2 && \
                (PY_MINOR_VERSION < 7 || (PY_MINOR_VERSION == 7 && PY_MICRO_VERSION < 14))
        Py_NoSiteFlag = 0;
#endif

        /* see https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads */
#if defined (WITH_THREAD) && PY_VERSION_HEX < 0x03070000
        /* create and acquire the interpreter lock */
        PyEval_InitThreads();
#endif

        /* create the obCallBack dictionary */
        interpreters = apr_hash_make(p);
        interp_pool = p;

        if (! interpreters) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                         "python_init: apr_hash_make() failed! No more memory?");
            exit(1);
        }

        /* save the global_tstate */
        global_tstate = PyEval_SaveThread();
    }

    APR_REGISTER_OPTIONAL_FN(mp_acquire_interpreter);
    APR_REGISTER_OPTIONAL_FN(mp_release_interpreter);
    APR_REGISTER_OPTIONAL_FN(mp_get_request_object);
    APR_REGISTER_OPTIONAL_FN(mp_get_server_object);
    APR_REGISTER_OPTIONAL_FN(mp_get_connection_object);

    return OK;
}

/**
 ** python_create_config
 **
 *      Called by create_dir_config and create_srv_config
 */

static py_config *python_create_config(apr_pool_t *p)
{
    py_config *conf =
        (py_config *) apr_pcalloc(p, sizeof(py_config));

    conf->authoritative = 1;
    conf->d_is_location = 0;
    conf->options = apr_table_make(p, 4);
    conf->directives = apr_table_make(p, 4);
    conf->hlists = apr_hash_make(p);
    conf->in_filters = apr_hash_make(p);
    conf->out_filters = apr_hash_make(p);

    return conf;
}


/**
 ** python_create_dir_config
 **
 *      Allocate memory and initialize the strucure that will
 *      hold configuration parametes.
 *
 *      This function is called on every hit it seems.
 */

static void *python_create_dir_config(apr_pool_t *p, char *dir)
{
    py_config *conf = python_create_config(p);
    conf->config_dir = dir;

    return conf;
}

/**
 ** python_create_srv_config
 **
 *      Allocate memory and initialize the strucure that will
 *      hold configuration parametes.
 */

static void *python_create_srv_config(apr_pool_t *p, server_rec *srv)
{

    py_config *conf = python_create_config(p);

    return conf;
}

/**
 ** modpython_table_overlap
 **
 * Replaces the apr_table_overlap() function using a specific pool
 * for the resulting table.
 */

static apr_table_t *modpython_table_overlap(apr_pool_t *p,
                                            apr_table_t *current_table,
                                            apr_table_t *new_table)
{
    apr_table_t *merge = apr_table_overlay(p, current_table, new_table);
    apr_table_compress(merge, APR_OVERLAP_TABLES_SET);
    return merge;
}

/**
 ** python_merge_dir_config
 **
 */

static void *python_merge_config(apr_pool_t *p, void *current_conf,
                                 void *new_conf)
{

    py_config *merged_conf =
        (py_config *) apr_pcalloc(p, sizeof(py_config));
    py_config *cc = (py_config *) current_conf;
    py_config *nc = (py_config *) new_conf;

    apr_hash_index_t *hi;
    char *key;
    apr_ssize_t klen;
    hl_entry *hle;
    py_handler *fh;

    /* we basically allow the local configuration to override global,
     * by first copying current values and then new values on top
     */

    /** create **/
    merged_conf->hlists = apr_hash_make(p);
    merged_conf->in_filters = apr_hash_make(p);
    merged_conf->out_filters = apr_hash_make(p);

    /** merge directives and options **/
    merged_conf->directives = modpython_table_overlap(p, cc->directives,
                                                         nc->directives);
    merged_conf->options = modpython_table_overlap(p, cc->options,
                                                      nc->options);

    /** copy current **/
    merged_conf->authoritative = cc->authoritative;
    merged_conf->config_dir = apr_pstrdup(p, cc->config_dir);
    merged_conf->d_is_location = cc->d_is_location;

    for (hi = apr_hash_first(p, cc->hlists); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void **)&key, &klen, (void **)&hle);
        apr_hash_set(merged_conf->hlists, key, klen, (void *)hle);
    }

    for (hi = apr_hash_first(p, cc->in_filters); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void **)&key, &klen, (void **)&fh);
        apr_hash_set(merged_conf->in_filters, key, klen, (void *)fh);
    }

    for (hi = apr_hash_first(p, cc->out_filters); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void **)&key, &klen, (void **)&fh);
        apr_hash_set(merged_conf->out_filters, key, klen, (void *)fh);
    }

    /** copy new **/

    if (nc->authoritative != merged_conf->authoritative)
        merged_conf->authoritative = nc->authoritative;
    if (nc->config_dir) {
        merged_conf->config_dir = apr_pstrdup(p, nc->config_dir);
        merged_conf->d_is_location = nc->d_is_location;
    }

    for (hi = apr_hash_first(p, nc->hlists); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void**)&key, &klen, (void **)&hle);
        apr_hash_set(merged_conf->hlists, key, klen, (void *)hle);
    }

    for (hi = apr_hash_first(p, nc->in_filters); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void**)&key, &klen, (void **)&fh);
        apr_hash_set(merged_conf->in_filters, key, klen, (void *)fh);
    }

    for (hi = apr_hash_first(p, nc->out_filters); hi; hi=apr_hash_next(hi)) {
        apr_hash_this(hi, (const void**)&key, &klen, (void **)&fh);
        apr_hash_set(merged_conf->out_filters, key, klen, (void *)fh);
    }

    return (void *) merged_conf;
}


/**
 ** python_directive
 **
 *  Called by non-handler directives
 *
 */

static const char *python_directive(cmd_parms *cmd, void * mconfig,
                                    char *key, const char *val)
{
    py_config *conf;

    conf = (py_config *) mconfig;
    apr_table_set(conf->directives, key, val);

    return NULL;
}

/* returns a parent if it matches the given directive */
static const ap_directive_t * find_parent(const ap_directive_t *dirp,
                                          const char *what)
{
    while (dirp->parent != NULL) {
        dirp = dirp->parent;

        if (strcasecmp(dirp->directive, what) == 0)
            return dirp;
    }

    return NULL;
}

#ifdef WIN32
#define USE_ICASE AP_REG_ICASE
#else
#define USE_ICASE 0
#endif

static void determine_context(apr_pool_t *p, const cmd_parms* cmd,
                              char **dp, char *dx, char *dl,
                              ap_regex_t **rx)
{
    const ap_directive_t *context = NULL;
    const ap_directive_t *directive = NULL;
    const char *endp, *arg;

    char *directory = NULL;
    char d_is_fnmatch = 0;
    char d_is_location = 0;
    ap_regex_t *regex = NULL;

    directive = cmd->directive;

    /* Skip any enclosing File directive if one exists */

    if ((context = find_parent(directive, "<Files")) ||
        (context = find_parent(directive, "<FilesMatch"))) {
        directive = context;
    }

    if ((context = find_parent(directive, "<Location"))) {
        arg = context->args;
        endp = ap_strrchr_c(arg, '>');
        arg = apr_pstrndup(p, arg, endp - arg);

        directory = ap_getword_conf(p, &arg);
        d_is_location = 1;

        if (!strcmp(directory, "~")) {
            directory = ap_getword_conf(p, &arg);
            regex = ap_pregcomp(p, cmd->path, AP_REG_EXTENDED|USE_ICASE);
        } else if (apr_fnmatch_test(directory)) {
            d_is_fnmatch = 1;
        }
    } else if ((context = find_parent(directive, "<LocationMatch"))) {
        arg = context->args;
        endp = ap_strrchr_c(arg, '>');
        arg = apr_pstrndup(p, arg, endp - arg);

        directory = ap_getword_conf(p, &arg);
        d_is_location = 1;

        regex = ap_pregcomp(p, directory, AP_REG_EXTENDED|USE_ICASE);
    } else if ((context = find_parent(directive, "<Directory"))) {
        arg = context->args;
        endp = ap_strrchr_c(arg, '>');
        arg = apr_pstrndup(p, arg, endp - arg);

        directory = ap_getword_conf(p, &arg);

        if (!strcmp(directory, "~")) {
            directory = ap_getword_conf(p, &arg);
            regex = ap_pregcomp(p, cmd->path, AP_REG_EXTENDED|USE_ICASE);
        } else if (apr_fnmatch_test(directory)) {
            d_is_fnmatch = 1;
        }
    } else if ((context = find_parent(directive, "<DirectoryMatch"))) {
        arg = context->args;
        endp = ap_strrchr_c(arg, '>');
        arg = apr_pstrndup(p, arg, endp - arg);

        directory = ap_getword_conf(p, &arg);
        regex = ap_pregcomp(p, directory, AP_REG_EXTENDED|USE_ICASE);
    }
    else if (cmd->config_file != NULL) {
        /* cmd->config_file is NULL when in main Apache
         * configuration file as the file is completely
         * read in before the directive is processed as
         * EXEC_ON_READ is not set in req_override field
         * of command_struct table entry. Thus know then
         * we are being used in a .htaccess file. */

        directory = ap_make_dirstr_parent(p, directive->filename);
    }

    /* Only canonicalize path and add trailing slash at
     * this point if no pattern matching to be done at
     * a later time. */

    if (directory && !d_is_fnmatch && !regex && !d_is_location) {

        char *newpath = NULL;
        apr_status_t rv;

        /* NB: if directory is not absolute, CWD will be prepended to
         * it. If directory is NULL (we make sure it is not NULL
         * above), then newpath will become CWD */
        rv = apr_filepath_merge(&newpath, NULL, directory,
                                APR_FILEPATH_TRUENAME, p);

        if (rv == APR_SUCCESS || rv == APR_EPATHWILD) {
            /*
             * We are appending a trailing slash here to be consistent
             * with what httpd's core.c does in dirsection(). I [GT]
             * am not very clear why core.c does it, especially given
             * that it appends the trailing slash even to things that
             * aren't really directories, e.g. when directory is an
             * fnmatch pattern such as "/foo/bar*"
             */
            directory = newpath;
            if (directory[strlen(directory) - 1] != '/') {
                directory = apr_pstrcat(p, directory, "/", NULL);
            }
        }
    }

    *dp = directory;
    *dx = d_is_fnmatch;
    *dl = d_is_location;
    *rx = regex;
}

static void python_directive_hl_add(apr_pool_t *p, apr_hash_t *hlists,
                                    const char *phase, const char *handler,
                                    const cmd_parms* cmd, char *directory,
                                    char d_is_fnmatch, char d_is_location,
                                    ap_regex_t* regex, const char silent)
{
    hl_entry *head;
    char *h;

    head = (hl_entry *)apr_hash_get(hlists, phase, APR_HASH_KEY_STRING);

    /* it's possible that handler is multiple handlers separated
       by white space */

    while (*(h = ap_getword_white(p, &handler)) != '\0') {
        if (!head) {
            head = hlist_new(p, h, directory, d_is_fnmatch, d_is_location, regex, silent);
            apr_hash_set(hlists, phase, APR_HASH_KEY_STRING, head);
        }
        else {
            hlist_append(p, head, h, directory, d_is_fnmatch, d_is_location, regex, silent);
        }
    }
}

/**
 ** python_directive_handler
 **
 *  Called by Python*Handler directives.
 *
 *  When used within the same directory, this will have a
 *  cumulative, rather than overriding effect - i.e. values
 *  from same directives specified multiple times will be appended.
 *
 */

static const char *python_directive_handler(cmd_parms *cmd, py_config* conf,
                                            char *key, const char *val, int silent)
{

    char *directory = NULL;
    char d_is_fnmatch = 0;
    char d_is_location = 0;
    ap_regex_t *regex = NULL;

    determine_context(cmd->pool, cmd, &directory, &d_is_fnmatch, &d_is_location, &regex);

    /* d_is_location is used by the map_to_storage handler - there is no need for it to
       run, if we are inside a Location. We also do not prepend a Location to sys.path. */
    conf->d_is_location = d_is_location;

    /* a handler may be restricted to certain file type by
     * extention using the "| .ext1 .ext2" syntax. When this
     * is the case, we will end up with a directive concatenated
     * with the extension, one per, e.g.
     * "PythonHandler foo | .ext1 .ext2" will result in
     * PythonHandler.ext1 foo
     * PythonHandler.ext2 foo
     */

    const char *exts = val;
    val = ap_getword(cmd->pool, &exts, '|');

    if (*exts == '\0') {
        python_directive_hl_add(cmd->pool, conf->hlists, key, val,
                                cmd, directory, d_is_fnmatch, d_is_location, regex, silent);
    }
    else {

        char *ext;

        /* skip blanks */
        while (apr_isspace(*exts)) exts++;

        /* repeat for every extension */
        while (*(ext = ap_getword_white(cmd->pool, &exts)) != '\0') {
            char *s;

            /* append extention to the directive name */
            s = apr_pstrcat(cmd->pool, key, ext, NULL);

            python_directive_hl_add(cmd->pool, conf->hlists, s, val,
                                    cmd, directory, d_is_fnmatch, d_is_location, regex, silent);
        }
    }

    return NULL;
}

/**
 ** python_directive_flag
 **
 *  Called for FLAG directives.
 *
 */

static const char *python_directive_flag(void * mconfig, char *key, int val)
{
    py_config *conf;

    conf = (py_config *) mconfig;

    if (val) {
        apr_table_set(conf->directives, key, "1");
    }
    else {
        apr_table_set(conf->directives, key, "0");
    }

    return NULL;
}

static apr_status_t python_cleanup_handler(void *data);

/**
 ** python_get_request_object
 **
 *      This creates or retrieves a previously created request object.
 *      The pointer to request object is stored in req->request_config.
 */

requestobject *python_get_request_object(request_rec *req, const char *phase)
{
    py_req_config *req_config;
    requestobject *request_obj = NULL;

    /* see if there is a request object already */
    req_config = (py_req_config *) ap_get_module_config(req->request_config,
                                                        &python_module);

    if (req_config) {
        request_obj = req_config->request_obj;
    }
    else {

        request_obj = (requestobject *)MpRequest_FromRequest(req);
        if (!request_obj) return NULL;

        /* store the pointer to this object in request_config */
        req_config = apr_pcalloc(req->pool, sizeof(py_req_config));
        req_config->request_obj = request_obj;
        req_config->dynhls = apr_hash_make(req->pool);
        req_config->in_filters = apr_hash_make(req->pool);
        req_config->out_filters = apr_hash_make(req->pool);
        ap_set_module_config(req->request_config, &python_module, req_config);

        /* register the clean up directive handler */
        apr_pool_cleanup_register(req->pool, (void *)req,
                                  python_cleanup_handler,
                                  apr_pool_cleanup_null);
    }

    /* make a note of which phase we are in right now */
    if (phase)
    {
        Py_XDECREF(request_obj->phase);
        request_obj->phase = MpBytesOrUnicode_FromString(phase);
    }

    return request_obj;
}

/**
 ** resolve_directory
 **
 *      resolve any directory match returning the matched directory
 */

static const char *resolve_directory(request_rec *req, const char *directory,
                                     char d_is_fnmatch, ap_regex_t *regex)
{
    char *prefix;
    int len, dirs, i;

    if (!req || !req->filename || (!d_is_fnmatch && !regex))
        return directory;

    dirs = ap_count_dirs(req->filename) + 1;
    len = strlen(req->filename);
    prefix = (char*)apr_palloc(req->pool, len+1);

    for (i=0; i<=dirs; i++) {
        ap_make_dirstr_prefix(prefix, req->filename, i);
#ifdef WIN32
        if (d_is_fnmatch && apr_fnmatch(directory, prefix,
            APR_FNM_PATHNAME|APR_FNM_CASE_BLIND) == 0) {
#else
        if (d_is_fnmatch && apr_fnmatch(directory, prefix,
            APR_FNM_PATHNAME) == 0) {
#endif
            return prefix;
        }
        else if (regex && ap_regexec(regex, prefix, 0, NULL, 0) == 0) {
            return prefix;
        }

        if (strcmp(prefix, "/") != 0) {
            prefix[strlen(prefix)-1] = '\0';
#ifdef WIN32
            if (d_is_fnmatch && apr_fnmatch(directory, prefix,
                APR_FNM_PATHNAME|APR_FNM_CASE_BLIND) == 0) {
#else
            if (d_is_fnmatch && apr_fnmatch(directory, prefix,
                APR_FNM_PATHNAME) == 0) {
#endif
                prefix[strlen(prefix)] = '/';
                return prefix;
            }
            else if (regex && ap_regexec(regex, prefix, 0, NULL, 0) == 0) {
                prefix[strlen(prefix)] = '/';
                return prefix;
            }
        }
    }

    return directory;
}

/**
 ** select_interp_name
 **
 *      (internal)
 *      figure out the name of the interpreter we should be using
 *      If this is for a handler, then hle is required. If this is
 *      for a filter, then fname and is_input are required. If con
 *      is specified, then its a connection handler.
 */

static const char *select_interp_name(request_rec *req, conn_rec *con,
                                      py_config *conf, hl_entry *hle,
                                      py_handler *fh)
{
    const char *s = NULL;

    if ((s = apr_table_get(conf->directives, "PythonInterpreter"))) {
        /* forced by configuration */
        return s;
    }
    else {
        if ((s = apr_table_get(conf->directives, "PythonInterpPerDirectory")) && (strcmp(s, "1") == 0)) {

            /* base interpreter on directory where the file is found */
            if (req && ap_is_directory(req->pool, req->filename)) {
                /*
                 * We are appending a trailing slash here to be
                 * consistent with what httpd's core.c does in
                 * dirsection(), or else we will end up with a
                 * different interpreter named without the slash.
                 */
                if (req->filename[strlen(req->filename)-1]=='/')
                    return ap_make_dirstr_parent(req->pool, req->filename);
                else
                    return ap_make_dirstr_parent(req->pool,
                                                 apr_pstrcat(req->pool, req->filename,
                                                             "/", NULL ));
            } else {
                if (req && req->filename)
                    return ap_make_dirstr_parent(req->pool, req->filename);
                else
                    /*
                     * In early phases of the request, req->filename is not known,
                     * so this would have to run in the global interpreter.
                     */
                    return NULL;
            }
        }
        else if ((s = apr_table_get(conf->directives, "PythonInterpPerDirective")) && (strcmp(s, "1") == 0)) {

            /*
             * base interpreter name on directory where the handler directive
             * was last found. If it was in http.conf, then we will use the
             * global interpreter.
             */

            if (fh) {
                s = fh->directory;
            }
            else if (hle) {
                s = hle->directory;
            }
            else {
                return NULL;
            }

            if (s && (s[0] == '\0'))
                return NULL;
            else
                return s;
        }
        else {
            /* - default: per server - */
            if (con)
                return con->base_server->server_hostname;
            else
                return req->server->server_hostname;
        }
    }
}

/**
 ** python_handler
 **
 *      A generic python handler. Most handlers should use this.
 */

static int python_handler(request_rec *req, char *phase)
{

    PyObject *resultobject = NULL;
    interpreterdata *idata;
    requestobject *request_obj;
    py_config * conf;
    int result;
    const char *interp_name = NULL;
    char *ext = NULL;
    hl_entry *hle = NULL;
    hl_entry *dynhle = NULL;
    hl_entry *hlohle = NULL;

    py_req_config *req_conf;



    /* get configuration */
    conf = (py_config *) ap_get_module_config(req->per_dir_config,
                                                  &python_module);
    /* get file extension */
    if (req->filename) {        /* filename is null until after transhandler */
        /* get rid of preceeding path */
        if ((ext = (char *)ap_strrchr_c(req->filename, '/')) == NULL)
            ext = req->filename;
        else
            ++ext;
        /* get extension */
        ap_getword(req->pool, (const char **)&ext, '.');
        if (*ext != '\0')
            ext = apr_pstrcat(req->pool, ".", ext, NULL);
    }

    /* is there an hlist entry, i.e. a handler? */
    /* try with extension */
    if (ext) {
        hle = (hl_entry *)apr_hash_get(conf->hlists,
                                       apr_pstrcat(req->pool, phase, ext, NULL),
                                       APR_HASH_KEY_STRING);
    }

    /* try without extension if we don't match */
    if (!hle) {
        hle = (hl_entry *)apr_hash_get(conf->hlists, phase,
                                       APR_HASH_KEY_STRING);

        /* also blank out ext since we didn't succeed with it. this is tested
           further below */
        ext = NULL;
    }

    req_conf = (py_req_config *) ap_get_module_config(req->request_config,
                                                      &python_module);
    if (req_conf) {
        dynhle = (hl_entry *)apr_hash_get(req_conf->dynhls, phase,
                                          APR_HASH_KEY_STRING);
    }

    if (! (hle || dynhle)) {
        /* nothing to do here */
        return DECLINED;
    }

    /* construct list for the handler list object */
    if (!hle) {
        hlohle = hlist_copy(req->pool, dynhle);
    }
    else {
        hlohle = hlist_copy(req->pool, hle);

        if (dynhle)
            hlist_extend(req->pool, hlohle, dynhle);
    }

    /* resolve wildcard or regex directory patterns. we do not resolve
     * Location patterns because there is no use case for it so far */
    hle = hlohle;
    while (hle) {
        if (!hle->d_is_location && (hle->d_is_fnmatch || hle->regex)) {
            hle->directory = resolve_directory(req, hle->directory,
                                               hle->d_is_fnmatch, hle->regex);
            hle->d_is_fnmatch = 0;
            hle->regex = NULL;
        }

        hle = hle->next;
    }

    /* determine interpreter to use */
    interp_name = select_interp_name(req, NULL, conf, hlohle, NULL);

    /* get/create interpreter */
    idata = get_interpreter(interp_name);

    if (!idata) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "python_handler: Can't get/create interpreter.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* create/acquire request object */
    request_obj = python_get_request_object(req, phase);

    /* remember the extension if any. used by publisher */
    if (ext)
        request_obj->extension = apr_pstrdup(req->pool, ext);

    /* construct a new handler list object */
    Py_XDECREF(request_obj->hlo);
    request_obj->hlo = (hlistobject *)MpHList_FromHLEntry(hlohle);

    /*
     * Here is where we call into Python!
     * This is the C equivalent of
     * >>> resultobject = obCallBack.HandlerDispatch(request_object)
     */

    resultobject = PyObject_CallMethod(idata->obcallback, "HandlerDispatch",
                                       "O", request_obj);

    /* clear phase from request object */
    Py_XDECREF(request_obj->phase);
    request_obj->phase = NULL;

    /* release the lock and destroy tstate */
    release_interpreter(idata);

    if (! resultobject) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "python_handler: (%s) HandlerDispatch() returned nothing.", phase);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    else {
        /* Attempt to analyze the result as a string indicating which
           result to return */
#if PY_MAJOR_VERSION < 3
        if (! PyInt_Check(resultobject)) {
#else
        if (! PyLong_Check(resultobject)) {
#endif
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "python_handler: (%s) HandlerDispatch() returned non-integer.", phase);
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        else {
#if PY_MAJOR_VERSION < 3
            result = PyInt_AsLong(resultobject);
#else
            result = PyLong_AsLong(resultobject);
#endif
            /* authen handlers need one more thing
             * if authentication failed and this handler is not
             * authoritative, let the others handle it
             */
            if (strcmp(phase, "PythonAuthenHandler") == 0) {
                /* This is a prevention measure for what is likely a bug
                   in mod_auth.c whereby r->user is used even if null.
                   XXX Remove in the future
                */
                if (result == OK && !req->user) {
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                                  "python_handler: After PythonAuthenHandler req->user is NULL. "
                                  "Assign something to req.user if returning OK to avoid this error.");
                    return HTTP_INTERNAL_SERVER_ERROR;
                }
                if (result == HTTP_UNAUTHORIZED)
                {
                    if   (! conf->authoritative)
                        result = DECLINED;
                    else {
                        /*
                         * This will insert a WWW-Authenticate header
                         * to let the browser know that we are using
                         * Basic authentication. This function does check
                         * to make sure that auth is indeed Basic, no
                         * need to do that here.
                         */
                        ap_note_basic_auth_failure(req);
                    }
                }
            }
        }
    }

    /* When the script sets an error status by using req.status,
     * it can then either provide its own HTML error message or have
     * Apache provide one. To have Apache provide one, you need to send
     * no output and return the error from the handler function. However,
     * if the script is providing HTML, then the return value of the
     * handler should be OK, else the user will get both the script
     * output and the Apache output.
     */

    /* Another note on status. req->status is used to build req->status_line
     * unless status_line is not NULL. req->status has no effect on how the
     * server will behave. The error behaviour is dictated by the return
     * value of this handler. When the handler returns anything other than OK,
     * the server will display the error that matches req->status, unless it is
     * 200 (HTTP_OK), in which case it will just show the error matching the return
     * value. If the req->status and the return of the handle do not match,
     * then the server will first show what req->status shows, then it will
     * print "Additionally, X error was recieved", where X is the return code
     * of the handle. If the req->status or return code is a weird number that the
     * server doesn't know, it will default to 500 Internal Server Error.
     */

    /* clean up */
    Py_XDECREF(resultobject);

    /* return the translated result (or default result) to the Server. */
    return result;

}

/**
 ** python_cleanup_handler
 **
 *    Runs handler registered via PythonCleanupHandler. Clean ups
 *    registered via register_cleanup() run in python_cleanup() above.
 */

static apr_status_t python_cleanup_handler(void *data)
{

    apr_status_t rc;
    py_req_config *req_config;
    request_rec *req = (request_rec *)data;

    rc = python_handler((request_rec *)data, "PythonCleanupHandler");

    req_config = (py_req_config *) ap_get_module_config(req->request_config,
                                                        &python_module);

    if (req_config && req_config->request_obj) {

        interpreterdata *idata;
        requestobject *request_obj = req_config->request_obj;

        /* get interpreter */
        idata = get_interpreter(NULL);
        if (!idata)
            return APR_SUCCESS; /* this return code is ignored by httpd anyway */

        Py_XDECREF(request_obj);

        /* release interpreter */
        release_interpreter(idata);
    }

    return rc;
}


/**
 ** python_connection
 **
 *    connection handler
 */

static apr_status_t python_connection(conn_rec *con)
{

    PyObject *resultobject = NULL;
    interpreterdata *idata;
    connobject *conn_obj;
    py_config * conf;
    int result;
    const char *interp_name = NULL;
    hl_entry *hle = NULL;

    /* get configuration */
    conf = (py_config *) ap_get_module_config(con->base_server->module_config,
                                                  &python_module);

    /* is there a handler? */
    hle = (hl_entry *)apr_hash_get(conf->hlists, "PythonConnectionHandler",
                                   APR_HASH_KEY_STRING);

    if (! hle) {
        /* nothing to do here */
        return DECLINED;
    }

    /* determine interpreter to use */
    interp_name = select_interp_name(NULL, con, conf, hle, NULL);

    /* get/create interpreter */
    idata = get_interpreter(interp_name);

    if (!idata) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, con->base_server,
                      "python_connection: Can't get/create interpreter.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* create connection object */
    conn_obj = (connobject*) MpConn_FromConn(con);

    /* create a handler list object */
    conn_obj->hlo = (hlistobject *)MpHList_FromHLEntry(hle);

    /*
     * Here is where we call into Python!
     * This is the C equivalent of
     * >>> resultobject = obCallBack.ConnectionDispatch(request_object)
     */
    resultobject = PyObject_CallMethod(idata->obcallback, "ConnectionDispatch",
                                       "O", conn_obj);

    /* release the lock and destroy tstate*/
    release_interpreter(idata);

    if (! resultobject) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, con->base_server,
                     "python_connection: ConnectionDispatch() returned nothing.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    else {
        /* Attempt to analyze the result as a string indicating which
           result to return */
#if PY_MAJOR_VERSION < 3
        if (! PyInt_Check(resultobject)) {
#else
        if (! PyLong_Check(resultobject)) {
#endif
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, con->base_server,
                         "python_connection: ConnectionDispatch() returned non-integer.");
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        else
#if PY_MAJOR_VERSION < 3
            result = PyInt_AsLong(resultobject);
#else
            result = PyLong_AsLong(resultobject);
#endif
    }

    /* clean up */
    Py_XDECREF(resultobject);

    /* return the translated result (or default result) to the Server. */
    return result;
}

/**
 ** python_filter
 **
 *    filter
 */

static apr_status_t python_filter(int is_input, ap_filter_t *f,
                                  apr_bucket_brigade *bb,
                                  ap_input_mode_t mode,
                                  apr_read_type_e block,
                                  apr_size_t readbytes) {

    PyObject *resultobject = NULL;
    interpreterdata *idata;
    requestobject *request_obj;
    py_config * conf;
    py_req_config * req_config;
    const char * interp_name = NULL;
    request_rec *req;
    filterobject *filter;
    python_filter_ctx *ctx;
    py_handler *fh;

    /* we only allow request level filters so far */
    req = f->r;

    /* create ctx if not there yet */
    if (!f->ctx) {
        ctx = (python_filter_ctx *) apr_pcalloc(req->pool, sizeof(python_filter_ctx));
        f->ctx = (void *)ctx;
    }
    else {
        ctx = (python_filter_ctx *) f->ctx;
    }

    /* are we in transparent mode? transparent mode is on after an error,
       so a filter can spit out an error without causing infinite loop */
    if (ctx->transparent) {
        if (is_input)
            return ap_get_brigade(f->next, bb, mode, block, readbytes);
        else
            return ap_pass_brigade(f->next, bb);
    }

    /* get configuration */
    conf = (py_config *) ap_get_module_config(req->per_dir_config,
                                                  &python_module);

    req_config = (py_req_config *) ap_get_module_config(req->request_config,
                                                        &python_module);
    /* the name of python function to call */
    if (ctx->name) {
        if (is_input)
            fh = apr_hash_get(req_config->in_filters, ctx->name, APR_HASH_KEY_STRING);
        else
            fh = apr_hash_get(req_config->out_filters, ctx->name, APR_HASH_KEY_STRING);
    } else {
        if (is_input)
            fh = apr_hash_get(conf->in_filters, f->frec->name, APR_HASH_KEY_STRING);
        else
            fh = apr_hash_get(conf->out_filters, f->frec->name, APR_HASH_KEY_STRING);
    }

    if (!fh) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "python_filter: Could not find registered filter.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* determine interpreter to use */
    interp_name = select_interp_name(req, NULL, conf, NULL, fh);

    /* get/create interpreter */
    idata = get_interpreter(interp_name);

    if (!idata) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "python_filter: Can't get/create interpreter.");
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* create/acquire request object */
    request_obj = python_get_request_object(req, 0);

    /* create filter */
    filter = (filterobject *)MpFilter_FromFilter(f, bb, is_input, mode, readbytes,
                                                 fh->handler, fh->directory);

    Py_INCREF(request_obj);
    filter->request_obj = request_obj;

    /*
     * Here is where we call into Python!
     * This is the C equivalent of
     * >>> resultobject = obCallBack.FilterDispatch(filter_object)
     */
    resultobject = PyObject_CallMethod(idata->obcallback, "FilterDispatch", "O",
                                       filter);

    /* clean up */
    Py_XDECREF(resultobject);

    /* release interpreter */
    release_interpreter(idata);

    return filter->rc;
}

/**
 ** python_input_filter
 **
 *    input filter
 */

static apr_status_t python_input_filter(ap_filter_t *f,
                                        apr_bucket_brigade *bb,
                                        ap_input_mode_t mode,
                                        apr_read_type_e block,
                                        apr_off_t readbytes)
{
    return python_filter(1, f, bb, mode, block, readbytes);
}


/**
 ** python_output_filter
 **
 *    output filter
 */

static apr_status_t python_output_filter(ap_filter_t *f,
                                         apr_bucket_brigade *bb)
{
    return python_filter(0, f, bb, 0, 0, 0);
}


/**
 ** handle_python
 **
 *    handler function for mod_include tag
 *
 *    The mod_include tag handler interface changed at:
 *
 *      20030821 (2.1.0-dev) bumped mod_include's entire API
 *
 *    Provide a completely separate implementation for now until
 *    it is determined whether the new SSI_CREATE_ERROR_BUCKET
 *    macro can simply be copied to allow backward compatibility.
 */

#if AP_MODULE_MAGIC_AT_LEAST(20030821,0)

static apr_status_t handle_python(include_ctx_t *ctx,
                                  ap_filter_t *f,
                                  apr_bucket_brigade *bb) {

    py_config *conf;
    const char *interp_name = NULL;
    interpreterdata *idata;
    requestobject *request_obj;
    PyObject *resultobject = NULL;
    filterobject *filter;
    apr_bucket *tmp_buck;

    char *file = f->r->filename;
    char *tag = NULL;
    char *tag_val = NULL;

    PyObject *tagobject = NULL;
    PyObject *codeobject = NULL;

    request_rec *req = f->r;

    if (!(ctx->flags & SSI_FLAG_PRINTING)) {
        return APR_SUCCESS;
    }

    if (ctx->flags & SSI_FLAG_NO_EXEC) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "#python used but not allowed in %s", file);

        SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
        return APR_SUCCESS;
    }

    /* get configuration */
    conf = (py_config *) ap_get_module_config(req->per_dir_config,
                                              &python_module);

    /* determine interpreter to use */
    interp_name = select_interp_name(req, NULL, conf, NULL, NULL);

    /* get/create interpreter */
    idata = get_interpreter(interp_name);

    if (!idata) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "handle_python: Can't get/create interpreter.");

        Py_XDECREF(tagobject);
        Py_XDECREF(codeobject);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* process tags */
    while (1) {
        optfn_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);

        if (!tag || !tag_val)
            break;

        if (!strlen(tag_val)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "empty value for '%s' parameter to tag 'python' in %s",
                          tag, file);

            SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
            Py_XDECREF(tagobject);
            Py_XDECREF(codeobject);
            return APR_SUCCESS;
        }

        if (!strcmp(tag, "eval") || !strcmp(tag, "exec")) {
            if (tagobject) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                              "multiple 'eval/exec' parameters to tag 'python' in %s",
                              file);

                SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
                Py_XDECREF(tagobject);
                Py_XDECREF(codeobject);
                return APR_SUCCESS;
            }

            tagobject = MpBytesOrUnicode_FromString(tag);
            codeobject = MpBytesOrUnicode_FromString(tag_val);
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "unexpected '%s' parameter to tag 'python' in %s",
                          tag, file);

            SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
            Py_XDECREF(tagobject);
            Py_XDECREF(codeobject);
            return APR_SUCCESS;
        }
    }

    if (!tagobject) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "missing 'eval/exec' parameter to tag 'python' in %s",
                      file);

        SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
        return APR_SUCCESS;
    }

    /* create/acquire request object */
    request_obj = python_get_request_object(req, 0);

    /* create filter */
    filter = (filterobject *)MpFilter_FromFilter(f, bb, 0, 0, 0, 0, 0);

    Py_INCREF(request_obj);
    filter->request_obj = request_obj;

    /*
     * Here is where we call into Python!
     */
    resultobject = PyObject_CallMethod(idata->obcallback,
            "IncludeDispatch", "OOO", filter, tagobject, codeobject);

    if (!resultobject)
    {
        SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
        release_interpreter(idata);
        return APR_SUCCESS;
    }

    /* clean up */
    Py_XDECREF(resultobject);

    /* release interpreter */
    release_interpreter(idata);

    return filter->rc;
}

#else

static apr_status_t handle_python(include_ctx_t *ctx,
                                  apr_bucket_brigade **bb,
                                  request_rec *r_bogus,
                                  ap_filter_t *f,
                                  apr_bucket *head_ptr,
                                  apr_bucket **inserted_head) {

    py_config *conf;
    const char *interp_name = NULL;
    interpreterdata *idata;
    requestobject *request_obj;
    PyObject *resultobject = NULL;
    filterobject *filter;
    apr_bucket *tmp_buck;

    char *file = f->r->filename;
    char *tag = NULL;
    char *tag_val = NULL;

    PyObject *tagobject = NULL;
    PyObject *codeobject = NULL;

    request_rec *req = f->r;

    if (!(ctx->flags & FLAG_PRINTING)) {
        return APR_SUCCESS;
    }

    if (ctx->flags & FLAG_NO_EXEC) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "#python used but not allowed in %s", file);

        CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
        return APR_SUCCESS;
    }

    /* process tags */
    while (1) {
        optfn_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1);

        if (!tag || !tag_val)
            break;

        if (!strlen(tag_val)) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "empty value for '%s' parameter to tag 'python' in %s",
                          tag, file);

            CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
            Py_XDECREF(tagobject);
            Py_XDECREF(codeobject);
            return APR_SUCCESS;
        }

        if (!strcmp(tag, "eval") || !strcmp(tag, "exec")) {
            if (tagobject) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                              "multiple 'eval/exec' parameters to tag 'python' in %s",
                              file);

                CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
                Py_XDECREF(tagobject);
                Py_XDECREF(codeobject);
                return APR_SUCCESS;
            }

            tagobject = MpBytesOrUnicode_FromString(tag);
            codeobject = MpBytesOrUnicode_FromString(tag_val);
        } else {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                          "unexpected '%s' parameter to tag 'python' in %s",
                          tag, file);

            CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
            Py_XDECREF(tagobject);
            Py_XDECREF(codeobject);
            return APR_SUCCESS;
        }
    }

    if (!tagobject) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "missing 'eval/exec' parameter to tag 'python' in %s",
                      file);

        CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
        return APR_SUCCESS;
    }

    /* get configuration */
    conf = (py_config *) ap_get_module_config(req->per_dir_config,
                                              &python_module);

    /* determine interpreter to use */
    interp_name = select_interp_name(req, NULL, conf, NULL, NULL);

    /* get/create interpreter */
    idata = get_interpreter(interp_name);

    if (!idata) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, req,
                      "handle_python: Can't get/create interpreter.");

        Py_XDECREF(tagobject);
        Py_XDECREF(codeobject);
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* create/acquire request object */
    request_obj = python_get_request_object(req, 0);

    /* create filter */
    filter = (filterobject *)MpFilter_FromFilter(f, *bb, 0, 0, 0, 0, 0);

    Py_INCREF(request_obj);
    filter->request_obj = request_obj;

    /*
     * Here is where we call into Python!
     */
    resultobject = PyObject_CallMethod(idata->obcallback,
            "IncludeDispatch", "OOO", filter, tagobject, codeobject);

    if (!resultobject)
    {
        CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head);
        release_interpreter(idata);
        return APR_SUCCESS;
    }

    /* clean up */
    Py_XDECREF(resultobject);

    /* release interpreter */
    release_interpreter(idata);

    return filter->rc;
}

#endif

/**
 ** directive_PythonImport
 **
 *      This function called whenever PythonImport directive
 *      is encountered. Note that this function does not actually
 *      import anything, it just remembers what needs to be imported.
 *      The actual importing is done later
 *      in the ChildInitHandler. This is because this function here
 *      is called before the python_init and before the suid and fork.
 *
 */
static const char *directive_PythonImport(cmd_parms *cmd, void *mconfig,
                                          const char *module, const char *interp_name)
{
    py_config *conf = ap_get_module_config(cmd->server->module_config,
                                           &python_module);

    if (!conf->imports)
        conf->imports = apr_table_make(cmd->pool, 4);

    apr_table_add(conf->imports, interp_name, module);

    return NULL;

}

/**
 ** directive_PythonPath
 **
 *      This function called whenever PythonPath directive
 *      is encountered.
 */
static const char *directive_PythonPath(cmd_parms *cmd, void *mconfig,
                                        const char *val) {

    const char *rc = python_directive(cmd, mconfig, "PythonPath", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
        return python_directive(cmd, conf, "PythonPath", val);
    }
    return rc;
}

/**
 ** directive_PythonInterpreter
 **
 *      This function called whenever PythonInterpreter directive
 *      is encountered.
 */
static const char *directive_PythonInterpreter(cmd_parms *cmd, void *mconfig,
                                               const char *val) {
    const char *rc = python_directive(cmd, mconfig, "PythonInterpreter", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
        return python_directive(cmd, conf, "PythonInterpreter", val);
    }
    return rc;
}

/**
 ** directive_PythonDebug
 **
 *      This function called whenever PythonDebug directive
 *      is encountered.
 */
static const char *directive_PythonDebug(cmd_parms *cmd, void *mconfig,
                                         int val) {
    const char *rc = python_directive_flag(mconfig, "PythonDebug", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);

        return python_directive_flag(conf, "PythonDebug", val);
    }
    return rc;
}

/**
 ** directive_PythonEnablePdb
 **
 *      This function called whenever PythonEnablePdb directive
 *      is encountered.
 */
static const char *directive_PythonEnablePdb(cmd_parms *cmd, void *mconfig,
                                             int val) {
    const char *rc = python_directive_flag(mconfig, "PythonEnablePdb", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
        return python_directive_flag(conf, "PythonEnablePdb", val);
    }
    return rc;
}

/**
 ** directive_PythonInterpPerDirective
 **
 *      This function called whenever PythonInterpPerDirective directive
 *      is encountered.
 */

static const char *directive_PythonInterpPerDirective(cmd_parms *cmd,
                                                      void *mconfig, int val) {
    const char *rc = python_directive_flag(mconfig, "PythonInterpPerDirective", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
        return python_directive_flag(conf, "PythonInterpPerDirective", val);
    }
    return rc;
}

/**
 ** directive_PythonInterpPerDirectory
 **
 *      This function called whenever PythonInterpPerDirectory directive
 *      is encountered.
 */

static const char *directive_PythonInterpPerDirectory(cmd_parms *cmd,
                                                      void *mconfig, int val) {
    return python_directive_flag(mconfig, "PythonInterpPerDirectory", val);
}

/**
 ** directive_PythonAutoReload
 **
 *      This function called whenever PythonAutoReload directive
 *      is encountered.
 */

static const char *directive_PythonAutoReload(cmd_parms *cmd,
                                              void *mconfig, int val) {
    const char *rc = python_directive_flag(mconfig, "PythonAutoReload", val);

    if (!cmd->path) {
        py_config *conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
        return python_directive_flag(conf, "PythonAutoReload", val);
    }
    return rc;
}

/**
 ** directive_PythonOption
 **
 *       This function is called every time PythonOption directive
 *       is encountered. It sticks the option into a table containing
 *       a list of options. This table is part of the local config structure.
 */

static const char *directive_PythonOption(cmd_parms *cmd, void * mconfig,
                                          const char *key, const char *val)
{
    py_config *conf;

    conf = (py_config *) mconfig;

    if(val!=NULL) {
        apr_table_set(conf->options, key, val);

        if (!cmd->path) {
            conf = ap_get_module_config(cmd->server->module_config,
                                        &python_module);
            apr_table_set(conf->options, key, val);
        }
    }
    else {
        /** We don't remove the value, but set it
            to an empty string. There is no possibility
            of colliding with an actual value, since
            an entry string precisely means 'remove the value' */
        apr_table_set(conf->options, key, "");

        if (!cmd->path) {
            conf = ap_get_module_config(cmd->server->module_config,
                                        &python_module);
            apr_table_set(conf->options, key, "");
        }
    }

    return NULL;
}

/**
 ** directive_PythonOptimize
 **
 *      This function called whenever PythonOptimize directive
 *      is encountered.
 */

static const char *directive_PythonOptimize(cmd_parms *cmd, void *mconfig,
                                            int val) {
#if PY_VERSION_HEX < 0x030C0000
    if ((val) && (Py_OptimizeFlag != 2))
        Py_OptimizeFlag = 2;
#endif
    return NULL;
}

/**
 ** Python*Handler directives
 **
 */

static const char *directive_PythonAccessHandler(cmd_parms *cmd, void *mconfig,
                                                 const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonAccessHandler", val, NOTSILENT);
}
static const char *directive_PythonAuthenHandler(cmd_parms *cmd, void *mconfig,
                                                 const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonAuthenHandler", val, NOTSILENT);
}
static const char *directive_PythonAuthzHandler(cmd_parms *cmd, void *mconfig,
                                                const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonAuthzHandler", val, NOTSILENT);
}
static const char *directive_PythonCleanupHandler(cmd_parms *cmd, void *mconfig,
                                                  const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonCleanupHandler", val, NOTSILENT);
}
static const char *directive_PythonConnectionHandler(cmd_parms *cmd, void *mconfig,
                                                     const char *val) {
    py_config *conf = ap_get_module_config(cmd->server->module_config,
                                           &python_module);
    return python_directive_handler(cmd, conf, "PythonConnectionHandler", val, NOTSILENT);
}
static const char *directive_PythonFixupHandler(cmd_parms *cmd, void *mconfig,
                                                const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonFixupHandler", val, NOTSILENT);
}
static const char *directive_PythonHandler(cmd_parms *cmd, void *mconfig,
                                           const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonHandler", val, NOTSILENT);
}
static const char *directive_PythonHeaderParserHandler(cmd_parms *cmd, void *mconfig,
                                                       const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonHeaderParserHandler", val, NOTSILENT);
}
static const char *directive_PythonInitHandler(cmd_parms *cmd, void *mconfig,
                                               const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonInitHandler", val, NOTSILENT);
}
static const char *directive_PythonHandlerModule(cmd_parms *cmd, void *mconfig,
                                                 const char *val) {

    /*
     * This handler explodes into all other handlers, but their absense will be
     * silently ignored.
     */

    /*
     * XXX Not used at present. See problems noted against connection
     * handler below.
     *
    py_config *srv_conf = ap_get_module_config(cmd->server->module_config,
                                               &python_module);
     */

    python_directive_handler(cmd, mconfig, "PythonPostReadRequestHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonTransHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonHeaderParserHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonAccessHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonAuthenHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonAuthzHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonTypeHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonFixupHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonInitHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonLogHandler", val, SILENT);
    python_directive_handler(cmd, mconfig, "PythonCleanupHandler", val, SILENT);

    /*
     * XXX There is a bug here with PythonConnectionHandler which can
     * cause an infinite loop when the handler is added to the handler
     * list. Cause is unknown so simply disable it for now. If someone
     * really needs a connection handler, they can use the directive
     * PythonConnectionHandler explicitly still and not rely on the
     * PythonHandlerModule directive doing it automatically.
     *
    python_directive_handler(cmd, srv_conf, "PythonConnectionHandler", val, SILENT);
    */

    return NULL;
}
static const char *directive_PythonPostReadRequestHandler(cmd_parms *cmd,
                                                          void * mconfig,
                                                          const char *val) {

    if (strchr((char *)val, '|'))
        return "PythonPostReadRequestHandler does not accept \"| .ext\" syntax.";

    return python_directive_handler(cmd, mconfig, "PythonPostReadRequestHandler", val,NOTSILENT);
}

static const char *directive_PythonTransHandler(cmd_parms *cmd, void *mconfig,
                                                const char *val) {
    if (strchr((char *)val, '|'))
        return "PythonTransHandler does not accept \"| .ext\" syntax.";

    return python_directive_handler(cmd, mconfig, "PythonTransHandler", val, NOTSILENT);
}
static const char *directive_PythonTypeHandler(cmd_parms *cmd, void *mconfig,
                                               const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonTypeHandler", val, NOTSILENT);
}
static const char *directive_PythonLogHandler(cmd_parms *cmd, void *mconfig,
                                              const char *val) {
    return python_directive_handler(cmd, mconfig, "PythonLogHandler", val, NOTSILENT);
}
static const char *directive_PythonInputFilter(cmd_parms *cmd, void *mconfig,
                                               const char *handler, const char *name) {

    py_config *conf;
    py_handler *fh;
    ap_filter_rec_t *frec;

    char *directory = NULL;
    char d_is_fnmatch = 0;
    char d_is_location = 0;
    ap_regex_t *regex = NULL;

    if (!name)
        name = apr_pstrdup(cmd->pool, handler);

    /* register the filter NOTE - this only works so long as the
       directive is only allowed in the main config. For .htaccess we
       would have to make sure not to duplicate this */
    frec = ap_register_input_filter(name, python_input_filter, NULL, AP_FTYPE_RESOURCE);

    conf = (py_config *) mconfig;

    determine_context(cmd->pool, cmd, &directory, &d_is_fnmatch, &d_is_location, &regex);

    fh = (py_handler *) apr_pcalloc(cmd->pool, sizeof(py_handler));
    fh->handler = (char *)handler;
    fh->directory = directory;
    fh->d_is_fnmatch = d_is_fnmatch;
    fh->d_is_location = d_is_location;
    fh->regex = regex;

    apr_hash_set(conf->in_filters, frec->name, APR_HASH_KEY_STRING, fh);

    return NULL;
}

static const char *directive_PythonOutputFilter(cmd_parms *cmd, void *mconfig,
                                                const char *handler, const char *name) {
    py_config *conf;
    py_handler *fh;
    ap_filter_rec_t *frec;

    char *directory = NULL;
    char d_is_fnmatch = 0;
    char d_is_location = 0;
    ap_regex_t *regex = NULL;

    if (!name)
        name = apr_pstrdup(cmd->pool, handler);

    /* register the filter NOTE - this only works so long as the
       directive is only allowed in the main config. For .htaccess we
       would have to make sure not to duplicate this */
    frec = ap_register_output_filter(name, python_output_filter, NULL, AP_FTYPE_RESOURCE);

    determine_context(cmd->pool, cmd, &directory, &d_is_fnmatch, &d_is_location, &regex);

    conf = (py_config *) mconfig;

    fh = (py_handler *) apr_pcalloc(cmd->pool, sizeof(py_handler));
    fh->handler = (char *)handler;
    fh->directory = directory;
    fh->d_is_fnmatch = d_is_fnmatch;
    fh->d_is_location = d_is_location;
    fh->regex = regex;

    apr_hash_set(conf->out_filters, frec->name, APR_HASH_KEY_STRING, fh);

    return NULL;
}

/**
 ** python_finalize
 **
 *  We create a thread state just so we can run Py_Finalize()
 */

static apr_status_t python_finalize(void *data)
{
    interpreterdata *idata;

    idata = get_interpreter(NULL);

    if (idata) {
        Py_Finalize();
        PyEval_SaveThread();
    }

    return APR_SUCCESS;
}

/**
 ** Handlers
 **
 */

static void PythonChildInitHandler(apr_pool_t *p, server_rec *s)
{


    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    int i;

    py_config *conf = ap_get_module_config(s->module_config, &python_module);
    py_global_config *glb;
    PyObject *resultobject = NULL;

    /* accordig Py C Docs we must do this after forking */
    PyEval_RestoreThread(global_tstate);

#if PY_VERSION_HEX < 0x03070000
    PyOS_AfterFork();
#elif PY_VERSION_HEX < 0x030D0000
    PyOS_AfterFork_Child();
#endif

    interpreterdata *idata = save_interpreter(MAIN_INTERPRETER, PyThreadState_Get());
    if (!idata)
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                     "PythonChildInitHandler: save_interpreter() returned NULL. No more memory?");

    if (PyEval_SaveThread() != global_tstate) {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
                     "PythonChildInitHandler: not in global thread state, aborting.");
        return;
    }

    /*
     * Cleanups registered first will be called last. This will
     * end the Python interpreter *after* all other cleanups.
     */

    /*
     * XXX Trying to cleanup Python on process shutdown causes
     * problems. This seems to mainly be an issue where there
     * are user created threads which are running in parallel as
     * the environment they are running in will be destroyed
     * from underneath them potentially resulting in crashes,
     * process hangs or simply Python errors. There is also a
     * small chance that finalization code can be called within
     * a signal handler in some configurations which could cause
     * problems as well. Thus disable cleanup of Python when
     * child processes are being shutdown. (MODPYTHON-109)
     *
     * apr_pool_cleanup_register(p, NULL, python_finalize, apr_pool_cleanup_null);
     */

    /*
     * Reinit mutexes
     */

    /* this will return it if it already exists */
    glb = python_create_global_config(s);

    reinit_mutexes(s, p, glb);

    /*
     * remember the pool in a global var. we may use it
     * later in server.register_cleanup()
     */
    child_init_pool = p;

    /* register python handler for mod_include. could probably
     * also have done this in python_init() instead */
    optfn_register_include_handler = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
    optfn_ssi_get_tag_and_value = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_get_tag_and_value);
    optfn_ssi_parse_string = APR_RETRIEVE_OPTIONAL_FN(ap_ssi_parse_string);
    if (optfn_register_include_handler && optfn_ssi_get_tag_and_value &&
        optfn_ssi_parse_string) {
        optfn_register_include_handler("python", handle_python);
    }

    /*
     * Now run PythonImports
     */

    if (conf->imports) {
        ah = apr_table_elts (conf->imports);
        elts = (apr_table_entry_t *) ah->elts;

        i = ah->nelts;

        while (i--) {
            if (elts[i].key) {

                interpreterdata *idata;
                const char *interp_name = elts[i].key;
                const char *module_name = elts[i].val;

                /* get interpreter */
                idata = get_interpreter(interp_name);
                if (!idata)
                    return;

                /*
                 * Call into Python to do import.
                 * This is the C equivalent of
                 * >>> resultobject = obCallBack.ImportDispatch(module_name)
                 */
                resultobject = PyObject_CallMethod(idata->obcallback,
                                                   "ImportDispatch", "s", module_name);

                if (!resultobject) {
                    if (PyErr_Occurred()) {
                        PyErr_Print();
                        fflush(stderr);
                    }
                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                                 "directive_PythonImport: error importing %s",
                                 (!module_name) ? "<null>" : module_name);
                }

                /* clean up */
                Py_XDECREF(resultobject);

                /* release interpreter */
                release_interpreter(idata);
            }
        }
    }
}

static int PythonConnectionHandler(conn_rec *con) {
    return python_connection(con);
}
static int PythonAccessHandler(request_rec *req) {
    return python_handler(req, "PythonAccessHandler");
}
static int PythonAuthenHandler(request_rec *req) {
    return python_handler(req, "PythonAuthenHandler");
}
static int PythonAuthzHandler(request_rec *req) {
    return python_handler(req, "PythonAuthzHandler");
}
static int PythonFixupHandler(request_rec *req) {
    return python_handler(req, "PythonFixupHandler");
}
static int PythonHandler(request_rec *req) {
    /*
     * In Apache 2.0, all handlers receive a request and have
     * a chance to process them.  Therefore, we need to only
     * handle those that we explicitly agreed to handle (see
     * above).
     */
    if (!req->handler || (strcmp(req->handler, "mod_python") &&
                          strcmp(req->handler, "python-program")))
        return DECLINED;

    return python_handler(req, "PythonHandler");
}
static int PythonHeaderParserHandler(request_rec *req) {
    int rc;

    /* run PythonInitHandler, if not already */
    if (! apr_table_get(req->notes, "python_init_ran")) {
        rc = python_handler(req, "PythonInitHandler");
        if ((rc != OK) && (rc != DECLINED))
            return rc;
    }
    return python_handler(req, "PythonHeaderParserHandler");
}
static int PythonLogHandler(request_rec *req) {
    return python_handler(req, "PythonLogHandler");
}
static int PythonPostReadRequestHandler(request_rec *req) {
    int rc;

    /* run PythonInitHandler */
    rc = python_handler(req, "PythonInitHandler");
    apr_table_set(req->notes, "python_init_ran", "1");
    if ((rc != OK) && (rc != DECLINED))
        return rc;

    return python_handler(req, "PythonPostReadRequestHandler");
}
static int PythonTransHandler(request_rec *req) {
    return python_handler(req, "PythonTransHandler");
}
static int PythonMapToStorageHandler(request_rec *req) {
    py_config *conf = (py_config *) ap_get_module_config(req->per_dir_config,
                                                         &python_module);
    if (conf->d_is_location) {
        /* there is no storage, no need to hit the slow filesystem to
         * map to storage */
        return OK;
    }
    return DECLINED;
}
static int PythonTypeHandler(request_rec *req) {
    return python_handler(req, "PythonTypeHandler");
}

static void python_register_hooks(apr_pool_t *p)
{

    /* module initializer */
    ap_hook_post_config(python_init,
                        NULL, NULL, APR_HOOK_MIDDLE);

    /* [0] raw connection handling */
    ap_hook_process_connection(PythonConnectionHandler,
                               NULL, NULL, APR_HOOK_MIDDLE);

    /* [1] post read_request handling */
    ap_hook_post_read_request(PythonPostReadRequestHandler,
                              NULL, NULL, APR_HOOK_MIDDLE);

    /* [2] filename-to-URI translation */
    ap_hook_translate_name(PythonTransHandler,
                           NULL, NULL, APR_HOOK_MIDDLE);

    /* [2.5] storage mapping (TODO add me as a valid handler?) */
    ap_hook_translate_name(PythonMapToStorageHandler,
                           NULL, NULL, APR_HOOK_MIDDLE);

    /* [3] header parser */
    ap_hook_header_parser(PythonHeaderParserHandler,
                          NULL, NULL, APR_HOOK_MIDDLE);

    /* [4] check access by host address */
    ap_hook_access_checker(PythonAccessHandler,
                           NULL, NULL, APR_HOOK_MIDDLE);

    /* [5] check/validate user_id */
    ap_hook_check_user_id(PythonAuthenHandler,
                          NULL, NULL, APR_HOOK_MIDDLE);

    /* [6] check user_id is valid *here* */
    ap_hook_auth_checker(PythonAuthzHandler,
                         NULL, NULL, APR_HOOK_MIDDLE);

    /* [7] MIME type checker/setter */
    ap_hook_type_checker(PythonTypeHandler,
                         NULL, NULL, APR_HOOK_MIDDLE);

    /* [8] fixups */
    ap_hook_fixups(PythonFixupHandler,
                   NULL, NULL, APR_HOOK_MIDDLE);

    /* [9] filter insert opportunity */
    /* ap_hook_insert_filter(PythonInsertFilter,
       NULL, NULL, APR_HOOK_MIDDLE); */

    /* [10] is for the handlers; see below */
    ap_hook_handler(PythonHandler, NULL, NULL, APR_HOOK_MIDDLE);

    /* [11] logger */
    ap_hook_log_transaction(PythonLogHandler,
                            NULL, NULL, APR_HOOK_MIDDLE);

    /* dynamic input/output filter entry points */
    ap_register_input_filter(FILTER_NAME, python_input_filter, NULL,
                             AP_FTYPE_RESOURCE);
    ap_register_output_filter(FILTER_NAME, python_output_filter, NULL,
                              AP_FTYPE_RESOURCE);

    /* process initializer */
    ap_hook_child_init(PythonChildInitHandler,
                       NULL, NULL, APR_HOOK_MIDDLE);

}

/* command table */
command_rec python_commands[] =
{
    AP_INIT_RAW_ARGS(
        "PythonAccessHandler", directive_PythonAccessHandler, NULL, OR_ALL,
        "Python access by host address handlers."),
    AP_INIT_RAW_ARGS(
        "PythonAuthenHandler", directive_PythonAuthenHandler, NULL, OR_ALL,
        "Python authentication handlers."),
    AP_INIT_FLAG(
        "PythonAutoReload", directive_PythonAutoReload, NULL, OR_ALL,
        "Set to Off if you don't want changed modules to reload."),
    AP_INIT_RAW_ARGS(
        "PythonAuthzHandler", directive_PythonAuthzHandler, NULL, OR_ALL,
        "Python authorization handlers."),
    AP_INIT_RAW_ARGS(
        "PythonCleanupHandler", directive_PythonCleanupHandler, NULL, OR_ALL,
        "Python clean up handlers."),
    AP_INIT_RAW_ARGS(
        "PythonConnectionHandler", directive_PythonConnectionHandler, NULL, RSRC_CONF,
        "Python connection handlers."),
    AP_INIT_FLAG(
        "PythonDebug", directive_PythonDebug, NULL, OR_ALL,
        "Send (most) Python error output to the client rather than logfile."),
    AP_INIT_FLAG(
        "PythonEnablePdb", directive_PythonEnablePdb, NULL, OR_ALL,
        "Run handlers in PDB (Python Debugger). Use with -DONE_PROCESS."),
    AP_INIT_RAW_ARGS(
        "PythonFixupHandler", directive_PythonFixupHandler, NULL, OR_ALL,
        "Python fixups handlers."),
    AP_INIT_RAW_ARGS(
        "PythonHandler", directive_PythonHandler, NULL, OR_ALL,
        "Python request handlers."),
    AP_INIT_RAW_ARGS(
        "PythonHeaderParserHandler", directive_PythonHeaderParserHandler, NULL, OR_ALL,
        "Python header parser handlers."),
    AP_INIT_TAKE2(
        "PythonImport", directive_PythonImport, NULL, RSRC_CONF,
        "Module and interpreter name to be imported at server/child init time."),
    AP_INIT_RAW_ARGS(
        "PythonInitHandler", directive_PythonInitHandler, NULL, OR_ALL,
        "Python request initialization handler."),
    AP_INIT_FLAG(
        "PythonInterpPerDirective", directive_PythonInterpPerDirective, NULL, OR_ALL,
        "Create subinterpreters per directive."),
    AP_INIT_FLAG(
        "PythonInterpPerDirectory", directive_PythonInterpPerDirectory, NULL, OR_ALL,
        "Create subinterpreters per directory."),
    AP_INIT_TAKE1(
        "PythonInterpreter", directive_PythonInterpreter, NULL, OR_ALL,
        "Forces a specific Python interpreter name to be used here."),
    AP_INIT_RAW_ARGS(
        "PythonLogHandler", directive_PythonLogHandler, NULL, OR_ALL,
        "Python logger handlers."),
    AP_INIT_RAW_ARGS(
        "PythonHandlerModule", directive_PythonHandlerModule, NULL, OR_ALL,
        "A Python module containing handlers to be executed."),
    AP_INIT_FLAG(
        "PythonOptimize", directive_PythonOptimize, NULL, RSRC_CONF,
        "Set the equivalent of the -O command-line flag on the interpreter."),
    AP_INIT_TAKE12(
        "PythonOption", directive_PythonOption, NULL, OR_ALL,
        "Useful to pass custom configuration information to scripts."),
    AP_INIT_TAKE1(
        "PythonPath", directive_PythonPath, NULL, OR_ALL,
        "Python path, specified in Python list syntax."),
    AP_INIT_RAW_ARGS(
        "PythonPostReadRequestHandler", directive_PythonPostReadRequestHandler,
        NULL, RSRC_CONF,
        "Python post read-request handlers."),
    AP_INIT_RAW_ARGS(
        "PythonTransHandler", directive_PythonTransHandler, NULL, RSRC_CONF,
        "Python filename to URI translation handlers."),
    AP_INIT_RAW_ARGS(
        "PythonTypeHandler", directive_PythonTypeHandler, NULL, OR_ALL,
        "Python MIME type checker/setter handlers."),
    AP_INIT_TAKE12(
        "PythonInputFilter", directive_PythonInputFilter, NULL, RSRC_CONF|ACCESS_CONF,
        "Python input filter."),
    AP_INIT_TAKE12(
        "PythonOutputFilter", directive_PythonOutputFilter, NULL, RSRC_CONF|ACCESS_CONF,
        "Python output filter."),
    {NULL}
};


module python_module =
{
    STANDARD20_MODULE_STUFF,
    python_create_dir_config,      /* per-directory config creator */
    python_merge_config,           /* dir config merger */
    python_create_srv_config,      /* server config creator */
    python_merge_config,           /* server config merger */
    python_commands,               /* command table */
    python_register_hooks          /* register hooks */
};
