upload http
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / modules / arch / win32 / mod_isapi.c
diff --git a/rubbos/app/httpd-2.0.64/modules/arch/win32/mod_isapi.c b/rubbos/app/httpd-2.0.64/modules/arch/win32/mod_isapi.c
new file mode 100644 (file)
index 0000000..859d567
--- /dev/null
@@ -0,0 +1,1760 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/*
+ * mod_isapi.c - Internet Server Application (ISA) module for Apache
+ * by Alexei Kosut <akosut apache.org>, significant overhauls and
+ * redesign by William Rowe <wrowe covalent.net>, and hints from many
+ * other developer/users who have hit on specific flaws.
+ *
+ * This module implements the ISAPI Handler architecture, allowing 
+ * Apache to load Internet Server Applications (ISAPI extensions),
+ * similar to the support in IIS, Zope, O'Reilly's WebSite and others.
+ *
+ * It is a complete implementation of the ISAPI 2.0 specification, 
+ * except for "Microsoft extensions" to the API which provide 
+ * asynchronous I/O.  It is further extended to include additional
+ * "Microsoft extentions" through IIS 5.0, with some deficiencies
+ * where one-to-one mappings don't exist.
+ *
+ * Refer to /manual/mod/mod_isapi.html for additional details on
+ * configuration and use, but check this source for specific support
+ * of the API, 
+ */
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "http_log.h"
+#include "util_script.h"
+#include "mod_core.h"
+#include "apr_lib.h"
+#include "apr_strings.h"
+#include "apr_portable.h"
+#include "apr_buckets.h"
+#include "apr_thread_mutex.h"
+#include "apr_thread_rwlock.h"
+#include "apr_hash.h"
+#include "mod_isapi.h"
+
+/* Retry frequency for a failed-to-load isapi .dll */
+#define ISAPI_RETRY apr_time_from_sec(30)
+
+/**********************************************************
+ *
+ *  ISAPI Module Configuration
+ *
+ **********************************************************/
+
+module AP_MODULE_DECLARE_DATA isapi_module;
+
+#define ISAPI_UNDEF -1
+
+/* Our isapi per-dir config structure */
+typedef struct isapi_dir_conf {
+    int read_ahead_buflen;
+    int log_unsupported;
+    int log_to_errlog;
+    int log_to_query;
+    int fake_async;
+} isapi_dir_conf;
+
+typedef struct isapi_loaded isapi_loaded;
+
+apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
+                          const char *fpath, isapi_loaded** isa);
+
+static void *create_isapi_dir_config(apr_pool_t *p, char *dummy)
+{
+    isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
+
+    dir->read_ahead_buflen = ISAPI_UNDEF;
+    dir->log_unsupported   = ISAPI_UNDEF;
+    dir->log_to_errlog     = ISAPI_UNDEF;
+    dir->log_to_query      = ISAPI_UNDEF;
+    dir->fake_async        = ISAPI_UNDEF;
+
+    return dir;
+}
+
+static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_)
+{
+    isapi_dir_conf *base = (isapi_dir_conf *) base_;
+    isapi_dir_conf *add = (isapi_dir_conf *) add_;
+    isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
+
+    dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF)
+                                ? base->read_ahead_buflen
+                                 : add->read_ahead_buflen;
+    dir->log_unsupported   = (add->log_unsupported == ISAPI_UNDEF)
+                                ? base->log_unsupported
+                                 : add->log_unsupported;
+    dir->log_to_errlog     = (add->log_to_errlog == ISAPI_UNDEF)
+                                ? base->log_to_errlog
+                                 : add->log_to_errlog;
+    dir->log_to_query      = (add->log_to_query == ISAPI_UNDEF)
+                                ? base->log_to_query
+                                 : add->log_to_query;
+    dir->fake_async        = (add->fake_async == ISAPI_UNDEF)
+                                ? base->fake_async
+                                 : add->fake_async;
+
+    return dir;
+}
+
+static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
+                                       const char *filename)
+{
+    isapi_loaded *isa;
+    apr_finfo_t tmp;
+    apr_status_t rv;
+    char *fspec;
+
+    /* ### Just an observation ... it would be terribly cool to be
+     * able to use this per-dir, relative to the directory block being
+     * defined.  The hash result remains global, but shorthand of
+     * <Directory "c:/webapps/isapi">
+     *     ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll
+     * </Directory>
+     * would be very convienent.
+     */
+    fspec = ap_server_root_relative(cmd->pool, filename);
+    if (!fspec) {
+        ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
+                     "ISAPI: invalid module path, skipping %s", filename);
+        return NULL;
+    }
+    if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE,
+                      cmd->temp_pool)) != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
+            "ISAPI: unable to stat, skipping %s", fspec);
+        return NULL;
+    }
+    if (tmp.filetype != APR_REG) {
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
+            "ISAPI: not a regular file, skipping %s", fspec);
+        return NULL;
+    }
+
+    /* Load the extention as cached (with null request_rec) */
+    rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
+                     "ISAPI: unable to cache, skipping %s", fspec);
+        return NULL;
+    }
+
+    return NULL;
+}
+
+static const command_rec isapi_cmds[] = {
+    AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot,
+        (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen),
+        OR_FILEINFO, "Maximum client request body to initially pass to the"
+                     " ISAPI handler (default: 49152)"),
+    AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot,
+        (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported),
+        OR_FILEINFO, "Log requests not supported by the ISAPI server"
+                     " on or off (default: off)"),
+    AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot,
+        (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog),
+        OR_FILEINFO, "Send all Append Log requests to the error log"
+                     " on or off (default: off)"),
+    AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot,
+        (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query),
+        OR_FILEINFO, "Append Log requests are concatinated to the query args"
+                     " on or off (default: on)"),
+    AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot,
+        (void *)APR_OFFSETOF(isapi_dir_conf, fake_async),
+        OR_FILEINFO, "Fake Asynchronous support for isapi callbacks"
+                     " on or off [Experimental] (default: off)"),
+    AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL,
+        RSRC_CONF, "Cache the specified ISAPI extension in-process"),
+    {NULL}
+};
+
+/**********************************************************
+ *
+ *  ISAPI Module Cache handling section
+ *
+ **********************************************************/
+
+/* Our isapi global config values */
+static struct isapi_global_conf {
+    apr_pool_t         *pool;
+    apr_thread_mutex_t *lock;
+    apr_hash_t         *hash;
+} loaded;
+
+/* Our loaded isapi module description structure */
+struct isapi_loaded {
+    const char          *filename;
+    apr_thread_rwlock_t *in_progress;
+    apr_status_t         last_load_rv;
+    apr_time_t           last_load_time;
+    apr_dso_handle_t    *handle;
+    HSE_VERSION_INFO    *isapi_version;
+    apr_uint32_t         report_version;
+    apr_uint32_t         timeout;
+    PFN_GETEXTENSIONVERSION GetExtensionVersion;
+    PFN_HTTPEXTENSIONPROC   HttpExtensionProc;
+    PFN_TERMINATEEXTENSION  TerminateExtension;
+};
+
+static apr_status_t isapi_unload(isapi_loaded *isa, int force)
+{
+    /* All done with the DLL... get rid of it...
+     *
+     * If optionally cached, and we weren't asked to force the unload,
+     * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload,
+     * otherwise, leave it alone (it didn't choose to cooperate.)
+     */
+    if (!isa->handle) {
+        return APR_SUCCESS;
+    }
+    if (isa->TerminateExtension) {
+        if (force) {
+            (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
+        }
+        else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) {
+            return APR_EGENERAL;
+        }
+    }
+    apr_dso_unload(isa->handle);
+    isa->handle = NULL;
+    return APR_SUCCESS;
+}
+
+static apr_status_t cleanup_isapi(void *isa_)
+{
+    isapi_loaded* isa = (isapi_loaded*) isa_;
+
+    /* We must force the module to unload, we are about
+     * to lose the isapi structure's allocation entirely.
+     */
+    return isapi_unload(isa, 1);
+}
+
+static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa)
+{
+    apr_status_t rv;
+
+    isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
+
+    /* TODO: These aught to become overrideable, so that we
+     * assure a given isapi can be fooled into behaving well.
+     *
+     * The tricky bit, they aren't really a per-dir sort of
+     * config, they will always be constant across every
+     * reference to the .dll no matter what context (vhost,
+     * location, etc) they apply to.
+     */
+    isa->report_version = 0x500; /* Revision 5.0 */
+    isa->timeout = 300 * 1000000; /* microsecs, not used */
+
+    rv = apr_dso_load(&isa->handle, isa->filename, p);
+    if (rv)
+    {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "ISAPI: failed to load %s", isa->filename);
+        isa->handle = NULL;
+        return rv;
+    }
+
+    rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle,
+                     "GetExtensionVersion");
+    if (rv)
+    {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "ISAPI: missing GetExtensionVersion() in %s",
+                     isa->filename);
+        apr_dso_unload(isa->handle);
+        isa->handle = NULL;
+        return rv;
+    }
+
+    rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle,
+                     "HttpExtensionProc");
+    if (rv)
+    {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "ISAPI: missing HttpExtensionProc() in %s",
+                     isa->filename);
+        apr_dso_unload(isa->handle);
+        isa->handle = NULL;
+        return rv;
+    }
+
+    /* TerminateExtension() is an optional interface */
+    rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle,
+                     "TerminateExtension");
+    apr_set_os_error(0);
+
+    /* Run GetExtensionVersion() */
+    if (!(isa->GetExtensionVersion)(isa->isapi_version)) {
+        apr_status_t rv = apr_get_os_error();
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "ISAPI: failed call to GetExtensionVersion() in %s",
+                     isa->filename);
+        apr_dso_unload(isa->handle);
+        isa->handle = NULL;
+        return rv;
+    }
+
+    apr_pool_cleanup_register(p, isa, cleanup_isapi,
+                              apr_pool_cleanup_null);
+
+    return APR_SUCCESS;
+}
+
+apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
+                          const char *fpath, isapi_loaded** isa)
+{
+    apr_status_t rv;
+    const char *key;
+
+    if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) {
+        return rv;
+    }
+
+    *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING);
+
+    if (*isa) {
+
+        /* If we find this lock exists, use a set-aside copy of gainlock
+         * to avoid race conditions on NULLing the in_progress variable
+         * when the load has completed.  Release the global isapi hash
+         * lock so other requests can proceed, then rdlock for completion
+         * of loading our desired dll or wrlock if we would like to retry
+         * loading the dll (because last_load_rv failed and retry is up.)
+         */
+        apr_thread_rwlock_t *gainlock = (*isa)->in_progress;
+
+        /* gainlock is NULLed after the module loads successfully.
+         * This free-threaded module can be used without any locking.
+         */
+        if (!gainlock) {
+            rv = (*isa)->last_load_rv;
+            apr_thread_mutex_unlock(loaded.lock);
+            return rv;
+        }
+
+
+        if ((*isa)->last_load_rv == APR_SUCCESS) {
+            apr_thread_mutex_unlock(loaded.lock);
+            if ((rv = apr_thread_rwlock_rdlock(gainlock))
+                    != APR_SUCCESS) {
+                return rv;
+            }
+            rv = (*isa)->last_load_rv;
+            apr_thread_rwlock_unlock(gainlock);
+            return rv;
+        }
+
+        if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) {
+
+            /* Remember last_load_time before releasing the global
+             * hash lock to avoid colliding with another thread
+             * that hit this exception at the same time as our
+             * retry attempt, since we unlock the global mutex
+             * before attempting a write lock for this module.
+             */
+            apr_time_t check_time = (*isa)->last_load_time;
+            apr_thread_mutex_unlock(loaded.lock);
+
+            if ((rv = apr_thread_rwlock_wrlock(gainlock))
+                    != APR_SUCCESS) {
+                return rv;
+            }
+
+            /* If last_load_time is unchanged, we still own this
+             * retry, otherwise presume another thread provided
+             * our retry (for good or ill).  Relock the global
+             * hash for updating last_load_ vars, so their update
+             * is always atomic to the global lock.
+             */
+            if (check_time == (*isa)->last_load_time) {
+
+                rv = isapi_load(loaded.pool, s, *isa);
+
+                apr_thread_mutex_lock(loaded.lock);
+                (*isa)->last_load_rv = rv;
+                (*isa)->last_load_time = apr_time_now();
+                apr_thread_mutex_unlock(loaded.lock);
+            }
+            else {
+                rv = (*isa)->last_load_rv;
+            }
+            apr_thread_rwlock_unlock(gainlock);
+
+            return rv;
+        }
+
+        /* We haven't hit timeup on retry, let's grab the last_rv
+         * within the hash mutex before unlocking.
+         */
+        rv = (*isa)->last_load_rv;
+        apr_thread_mutex_unlock(loaded.lock);
+
+        return rv;
+    }
+
+    /* If the module was not found, it's time to create a hash key entry
+     * before releasing the hash lock to avoid multiple threads from
+     * loading the same module.
+     */
+    key = apr_pstrdup(loaded.pool, fpath);
+    *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded));
+    (*isa)->filename = key;
+    if (r) {
+        /* A mutex that exists only long enough to attempt to
+         * load this isapi dll, the release this module to all
+         * other takers that came along during the one-time
+         * load process.  Short lifetime for this lock would
+         * be great, however, using r->pool is nasty if those
+         * blocked on the lock haven't all unlocked before we
+         * attempt to destroy.  A nastier race condition than
+         * I want to deal with at this moment...
+         */
+        apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
+        apr_thread_rwlock_wrlock((*isa)->in_progress);
+    }
+
+    apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa);
+
+    /* Now attempt to load the isapi on our own time,
+     * allow other isapi processing to resume.
+     */
+    apr_thread_mutex_unlock(loaded.lock);
+
+    rv = isapi_load(loaded.pool, s, *isa);
+    (*isa)->last_load_time = apr_time_now();
+    (*isa)->last_load_rv = rv;
+
+    if (r && (rv == APR_SUCCESS)) {
+        /* Let others who are blocked on this particular
+         * module resume their requests, for better or worse.
+         */
+        apr_thread_rwlock_t *unlock = (*isa)->in_progress;
+        (*isa)->in_progress = NULL;
+        apr_thread_rwlock_unlock(unlock);
+    }
+    else if (!r && (rv != APR_SUCCESS)) {
+        /* We must leave a rwlock around for requests to retry
+         * loading this dll after timeup... since we were in
+         * the setup code we had avoided creating this lock.
+         */
+        apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
+    }
+
+    return (*isa)->last_load_rv;
+}
+
+/**********************************************************
+ *
+ *  ISAPI Module request callbacks section
+ *
+ **********************************************************/
+
+/* Our "Connection ID" structure */
+struct isapi_cid {
+    EXTENSION_CONTROL_BLOCK *ecb;
+    isapi_dir_conf           dconf;
+    isapi_loaded            *isa;
+    request_rec             *r;
+    int                      headers_set;
+    int                      response_sent;
+    PFN_HSE_IO_COMPLETION    completion;
+    void                    *completion_arg;
+    apr_thread_mutex_t      *completed;
+};
+
+int APR_THREAD_FUNC GetServerVariable (isapi_cid    *cid,
+                                       char         *variable_name,
+                                       void         *buf_ptr,
+                                       apr_uint32_t *buf_size)
+{
+    request_rec *r = cid->r;
+    const char *result;
+    char *buf_data = (char*)buf_ptr;
+    apr_uint32_t len;
+
+    if (!strcmp(variable_name, "ALL_HTTP"))
+    {
+        /* crlf delimited, colon split, comma separated and
+         * null terminated list of HTTP_ vars
+         */
+        const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
+        const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+        int i;
+
+        for (len = 0, i = 0; i < arr->nelts; i++) {
+            if (!strncmp(elts[i].key, "HTTP_", 5)) {
+                len += strlen(elts[i].key) + strlen(elts[i].val) + 3;
+            }
+        }
+
+        if (*buf_size < len + 1) {
+            *buf_size = len + 1;
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+            return 0;
+        }
+
+        for (i = 0; i < arr->nelts; i++) {
+            if (!strncmp(elts[i].key, "HTTP_", 5)) {
+                strcpy(buf_data, elts[i].key);
+                buf_data += strlen(elts[i].key);
+                *(buf_data++) = ':';
+                strcpy(buf_data, elts[i].val);
+                buf_data += strlen(elts[i].val);
+                *(buf_data++) = '\r';
+                *(buf_data++) = '\n';
+            }
+        }
+
+        *(buf_data++) = '\0';
+        *buf_size = len + 1;
+        return 1;
+    }
+
+    if (!strcmp(variable_name, "ALL_RAW"))
+    {
+        /* crlf delimited, colon split, comma separated and
+         * null terminated list of the raw request header
+         */
+        const apr_array_header_t *arr = apr_table_elts(r->headers_in);
+        const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
+        int i;
+
+        for (len = 0, i = 0; i < arr->nelts; i++) {
+            len += strlen(elts[i].key) + strlen(elts[i].val) + 4;
+        }
+
+        if (*buf_size < len + 1) {
+            *buf_size = len + 1;
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+            return 0;
+        }
+
+        for (i = 0; i < arr->nelts; i++) {
+            strcpy(buf_data, elts[i].key);
+            buf_data += strlen(elts[i].key);
+            *(buf_data++) = ':';
+            *(buf_data++) = ' ';
+            strcpy(buf_data, elts[i].val);
+            buf_data += strlen(elts[i].val);
+            *(buf_data++) = '\r';
+            *(buf_data++) = '\n';
+        }
+        *(buf_data++) = '\0';
+        *buf_size = len + 1;
+        return 1;
+    }
+
+    /* Not a special case */
+    result = apr_table_get(r->subprocess_env, variable_name);
+
+    if (result) {
+        len = strlen(result);
+        if (*buf_size < len + 1) {
+            *buf_size = len + 1;
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
+            return 0;
+        }
+        strcpy(buf_data, result);
+        *buf_size = len + 1;
+        return 1;
+    }
+
+    /* Not Found */
+    apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX));
+    return 0;
+}
+
+int APR_THREAD_FUNC ReadClient(isapi_cid    *cid,
+                               void         *buf_data,
+                               apr_uint32_t *buf_size)
+{
+    request_rec *r = cid->r;
+    apr_uint32_t read = 0;
+    int res;
+
+    if (r->remaining < *buf_size) {
+        *buf_size = (apr_size_t)r->remaining;
+    }
+
+    while (read < *buf_size &&
+           ((res = ap_get_client_block(r, (char*)buf_data + read,
+                                       *buf_size - read)) > 0)) {
+        read += res;
+    }
+
+    *buf_size = read;
+    if (res < 0) {
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT));
+    }
+    return (res >= 0);
+}
+
+/* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and
+ * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s)
+ * as well as other functions that write responses and presume that
+ * the support functions above are optional.
+ *
+ * Other callers trying to split headers and body bytes should pass
+ * head/headlen alone (leaving stat/statlen NULL/0), so that they
+ * get a proper count of bytes consumed.  The argument passed to stat
+ * isn't counted as the head bytes are.
+ */
+static apr_ssize_t send_response_header(isapi_cid *cid,
+                                        const char *stat,
+                                        const char *head,
+                                        apr_size_t statlen,
+                                        apr_size_t headlen)
+{
+    int head_present = 1;
+    int termarg;
+    int res;
+    int old_status;
+    const char *termch;
+    apr_size_t ate = 0;
+
+    if (!head || headlen == 0 || !*head) {
+        head = stat;
+        stat = NULL;
+        headlen = statlen;
+        statlen = 0;
+        head_present = 0; /* Don't eat the header */
+    }
+
+    if (!stat || statlen == 0 || !*stat) {
+        if (head && headlen && *head && ((stat = memchr(head, '\r', headlen))
+                                      || (stat = memchr(head, '\n', headlen))
+                                      || (stat = memchr(head, '\0', headlen))
+                                      || (stat = head + headlen))) {
+            statlen = stat - head;
+            if (memchr(head, ':', statlen)) {
+                stat = "Status: 200 OK";
+                statlen = strlen(stat);
+            }
+            else {
+                const char *flip = head;
+                head = stat;
+                stat = flip;
+                headlen -= statlen;
+                ate += statlen;
+                if (*head == '\r' && headlen)
+                    ++head, --headlen, ++ate;
+                if (*head == '\n' && headlen)
+                    ++head, --headlen, ++ate;
+            }
+        }
+    }
+
+    if (stat && (statlen > 0) && *stat) {
+        char *newstat;
+        if (!apr_isdigit(*stat)) {
+            const char *stattok = stat;
+            int toklen = statlen;
+            while (toklen && *stattok && !apr_isspace(*stattok)) {
+                ++stattok; --toklen;
+            }
+            while (toklen && apr_isspace(*stattok)) {
+                ++stattok; --toklen;
+            }
+            /* Now decide if we follow the xxx message
+             * or the http/x.x xxx message format
+             */
+            if (toklen && apr_isdigit(*stattok)) {
+                statlen = toklen;
+                stat = stattok;
+            }
+        }
+        newstat = apr_palloc(cid->r->pool, statlen + 9);
+        strcpy(newstat, "Status: ");
+        apr_cpystrn(newstat + 8, stat, statlen + 1);
+        stat = newstat;
+        statlen += 8;
+    }
+
+    if (!head || headlen == 0 || !*head) {
+        head = "\r\n";
+        headlen = 2;
+    }
+    else
+    {
+        if (head[headlen - 1] && head[headlen]) {
+            /* Whoops... not NULL terminated */
+            head = apr_pstrndup(cid->r->pool, head, headlen);
+        }
+    }
+
+    /* Seems IIS does not enforce the requirement for \r\n termination
+     * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic...
+     * ap_scan_script_header_err_strs handles this aspect for us.
+     *
+     * Parse them out, or die trying
+     */
+    old_status = cid->r->status;
+
+    if (stat) {
+        res = ap_scan_script_header_err_strs(cid->r, NULL, &termch, &termarg,
+                stat, head, NULL);
+    }
+    else {
+        res = ap_scan_script_header_err_strs(cid->r, NULL, &termch, &termarg,
+                head, NULL);
+    }
+
+    /* Set our status. */
+    if (res) {
+        /* This is an immediate error result from the parser
+         */
+        cid->r->status = res;
+        cid->r->status_line = ap_get_status_line(cid->r->status);
+        cid->ecb->dwHttpStatusCode = cid->r->status;
+    }
+    else if (cid->r->status) {
+        /* We have a status in r->status, so let's just use it.
+         * This is likely to be the Status: parsed above, and
+         * may also be a delayed error result from the parser.
+         * If it was filled in, status_line should also have
+         * been filled in.
+         */
+        cid->ecb->dwHttpStatusCode = cid->r->status;
+    }
+    else if (cid->ecb->dwHttpStatusCode
+              && cid->ecb->dwHttpStatusCode != HTTP_OK) {
+        /* Now we fall back on dwHttpStatusCode if it appears
+         * ap_scan_script_header fell back on the default code.
+         * Any other results set dwHttpStatusCode to the decoded
+         * status value.
+         */
+        cid->r->status = cid->ecb->dwHttpStatusCode;
+        cid->r->status_line = ap_get_status_line(cid->r->status);
+    }
+    else if (old_status) {
+        /* Well... either there is no dwHttpStatusCode or it's HTTP_OK.
+         * In any case, we don't have a good status to return yet...
+         * Perhaps the one we came in with will be better. Let's use it,
+         * if we were given one (note this is a pendantic case, it would
+         * normally be covered above unless the scan script code unset
+         * the r->status). Should there be a check here as to whether
+         * we are setting a valid response code?
+         */
+        cid->r->status = old_status;
+        cid->r->status_line = ap_get_status_line(cid->r->status);
+        cid->ecb->dwHttpStatusCode = cid->r->status;
+    }
+    else {
+        /* None of dwHttpStatusCode, the parser's r->status nor the 
+         * old value of r->status were helpful, and nothing was decoded
+         * from Status: string passed to us.  Let's just say HTTP_OK 
+         * and get the data out, this was the isapi dev's oversight.
+         */
+        cid->r->status = HTTP_OK;
+        cid->r->status_line = ap_get_status_line(cid->r->status);
+        cid->ecb->dwHttpStatusCode = cid->r->status;
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r,
+                "ISAPI: Could not determine HTTP response code; using %d",
+                cid->r->status);
+    }
+
+    if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) {
+        return -1;
+    }
+
+    /* If only Status was passed, we consumed nothing
+     */
+    if (!head_present)
+        return 0;
+
+    cid->headers_set = 1;
+
+    /* If all went well, tell the caller we consumed the headers complete
+     */
+    if (!termch)
+        return(ate + headlen);
+
+    /* Any data left must be sent directly by the caller, all we
+     * give back is the size of the headers we consumed (which only
+     * happens if the parser got to the head arg, which varies based
+     * on whether we passed stat+head to scan, or only head.
+     */
+    if (termch && (termarg == (stat ? 1 : 0))
+               && head_present && head + headlen > termch) {
+        return ate + termch - head;
+    }
+    return ate;
+}
+
+int APR_THREAD_FUNC WriteClient(isapi_cid    *cid,
+                                void         *buf_ptr,
+                                apr_uint32_t *size_arg,
+                                apr_uint32_t  flags)
+{
+    request_rec *r = cid->r;
+    conn_rec *c = r->connection;
+    apr_uint32_t buf_size = *size_arg;
+    char *buf_data = (char*)buf_ptr;
+    apr_bucket_brigade *bb;
+    apr_bucket *b;
+    apr_status_t rv = APR_SUCCESS;
+
+    if (!cid->headers_set) {
+        /* It appears that the foxisapi module and other clients
+         * presume that WriteClient("headers\n\nbody") will work.
+         * Parse them out, or die trying.
+         */
+        apr_ssize_t ate;
+        ate = send_response_header(cid, NULL, buf_data, 0, buf_size);
+        if (ate < 0) {
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+            return 0;
+        }
+
+        buf_data += ate;
+        buf_size -= ate;
+    }
+
+    if (buf_size) {
+        bb = apr_brigade_create(r->pool, c->bucket_alloc);
+        b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+        b = apr_bucket_flush_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+        rv = ap_pass_brigade(r->output_filters, bb);
+        cid->response_sent = 1;
+        if (rv != APR_SUCCESS)
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+                          "ISAPI: WriteClient ap_pass_brigade "
+                          "failed: %s", r->filename);
+    }
+
+    if ((flags & HSE_IO_ASYNC) && cid->completion) {
+        if (rv == APR_SUCCESS) {
+            cid->completion(cid->ecb, cid->completion_arg,
+                            *size_arg, ERROR_SUCCESS);
+        }
+        else {
+            cid->completion(cid->ecb, cid->completion_arg,
+                            *size_arg, ERROR_WRITE_FAULT);
+        }
+    }
+    return (rv == APR_SUCCESS);
+}
+
+/* A "safe" maximum bucket size, 1Gb */
+#define MAX_BUCKET_SIZE (0x40000000)
+
+apr_bucket *brigade_insert_file(apr_bucket_brigade *bb,
+                                apr_file_t *f,
+                                apr_off_t start,
+                                apr_off_t length,
+                                apr_pool_t *p)
+{
+    apr_bucket *e;
+
+    if (sizeof(apr_off_t) == sizeof(apr_size_t) || length < MAX_BUCKET_SIZE) {
+        e = apr_bucket_file_create(f, start, (apr_size_t)length, p, 
+                                   bb->bucket_alloc);
+    }
+    else {
+        /* Several buckets are needed. */        
+        e = apr_bucket_file_create(f, start, MAX_BUCKET_SIZE, p, 
+                                   bb->bucket_alloc);
+
+        while (length > MAX_BUCKET_SIZE) {
+            apr_bucket *ce;
+            apr_bucket_copy(e, &ce);
+            APR_BRIGADE_INSERT_TAIL(bb, ce);
+            e->start += MAX_BUCKET_SIZE;
+            length -= MAX_BUCKET_SIZE;
+        }
+        e->length = (apr_size_t)length; /* Resize just the last bucket */
+    }
+    
+    APR_BRIGADE_INSERT_TAIL(bb, e);
+    return e;
+}
+
+int APR_THREAD_FUNC ServerSupportFunction(isapi_cid    *cid,
+                                          apr_uint32_t  HSE_code,
+                                          void         *buf_ptr,
+                                          apr_uint32_t *buf_size,
+                                          apr_uint32_t *data_type)
+{
+    request_rec *r = cid->r;
+    conn_rec *c = r->connection;
+    char *buf_data = (char*)buf_ptr;
+    request_rec *subreq;
+    apr_status_t rv;
+
+    switch (HSE_code) {
+    case HSE_REQ_SEND_URL_REDIRECT_RESP:
+        /* Set the status to be returned when the HttpExtensionProc()
+         * is done.
+         * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
+         *          and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK.
+         *          They most definately are not, even in their own samples.
+         */
+        apr_table_set (r->headers_out, "Location", buf_data);
+        cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY;
+        cid->r->status_line = ap_get_status_line(cid->r->status);
+        cid->headers_set = 1;
+        return 1;
+
+    case HSE_REQ_SEND_URL:
+        /* Soak up remaining input */
+        if (r->remaining > 0) {
+            char argsbuffer[HUGE_STRING_LEN];
+            while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
+        }
+
+        /* Reset the method to GET */
+        r->method = apr_pstrdup(r->pool, "GET");
+        r->method_number = M_GET;
+
+        /* Don't let anyone think there's still data */
+        apr_table_unset(r->headers_in, "Content-Length");
+
+        /* AV fault per PR3598 - redirected path is lost! */
+        buf_data = apr_pstrdup(r->pool, (char*)buf_data);
+        ap_internal_redirect(buf_data, r);
+        return 1;
+
+    case HSE_REQ_SEND_RESPONSE_HEADER:
+    {
+        /* Parse them out, or die trying */
+        apr_size_t statlen = 0, headlen = 0;
+        apr_ssize_t ate;
+        if (buf_data)
+            statlen = strlen((char*) buf_data);
+        if (data_type)
+            headlen = strlen((char*) data_type);
+        ate = send_response_header(cid, (char*) buf_data,
+                                   (char*) data_type,
+                                   statlen, headlen);
+        if (ate < 0) {
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+            return 0;
+        }
+        else if ((apr_size_t)ate < headlen) {
+            apr_bucket_brigade *bb;
+            apr_bucket *b;
+            bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
+            b = apr_bucket_transient_create((char*) data_type + ate,
+                                           headlen - ate, c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+            b = apr_bucket_flush_create(c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+            rv = ap_pass_brigade(cid->r->output_filters, bb);
+            cid->response_sent = 1;
+            if (rv != APR_SUCCESS)
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+                              "ISAPI: ServerSupport function "
+                              "HSE_REQ_SEND_RESPONSE_HEADER "
+                              "ap_pass_brigade failed: %s", r->filename);
+            return (rv == APR_SUCCESS);
+        }
+        /* Deliberately hold off sending 'just the headers' to begin to
+         * accumulate the body and speed up the overall response, or at
+         * least wait for the end the session.
+         */
+        return 1;
+    }
+
+    case HSE_REQ_DONE_WITH_SESSION:
+        /* Signal to resume the thread completing this request,
+         * leave it to the pool cleanup to dispose of our mutex.
+         */
+        if (cid->completed) {
+            (void)apr_thread_mutex_unlock(cid->completed);
+            return 1;
+        }
+        else if (cid->dconf.log_unsupported) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_DONE_WITH_SESSION is not supported: %s",
+                          r->filename);
+        }
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_MAP_URL_TO_PATH:
+    {
+        /* Map a URL to a filename */
+        char *file = (char *)buf_data;
+        apr_uint32_t len;
+        subreq = ap_sub_req_lookup_uri(
+                     apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL);
+
+        if (!subreq->filename) {
+            ap_destroy_sub_req(subreq);
+            return 0;
+        }
+
+        len = (apr_uint32_t)strlen(r->filename);
+
+        if ((subreq->finfo.filetype == APR_DIR)
+              && (!subreq->path_info)
+              && (file[len - 1] != '/'))
+            file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL);
+        else
+            file = apr_pstrcat(cid->r->pool, subreq->filename, 
+                                              subreq->path_info, NULL);
+
+        ap_destroy_sub_req(subreq);
+
+#ifdef WIN32
+        /* We need to make this a real Windows path name */
+        apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
+#endif
+
+        *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data;
+
+        return 1;
+    }
+
+    case HSE_REQ_GET_SSPI_INFO:
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                           "ISAPI: ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
+                           "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_APPEND_LOG_PARAMETER:
+        /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field
+         */
+        apr_table_set(r->notes, "isapi-parameter", (char*) buf_data);
+        if (cid->dconf.log_to_query) {
+            if (r->args)
+                r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL);
+            else
+                r->args = apr_pstrdup(r->pool, (char*) buf_data);
+        }
+        if (cid->dconf.log_to_errlog)
+            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                          "ISAPI: %s: %s", cid->r->filename,
+                          (char*) buf_data);
+        return 1;
+
+    case HSE_REQ_IO_COMPLETION:
+        /* Emulates a completion port...  Record callback address and
+         * user defined arg, we will call this after any async request
+         * (e.g. transmitfile) as if the request executed async.
+         * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
+         * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL.
+         */
+        if (cid->dconf.fake_async) {
+            cid->completion = (PFN_HSE_IO_COMPLETION) buf_data;
+            cid->completion_arg = (void *) data_type;
+            return 1;
+        }
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                      "ISAPI: ServerSupportFunction HSE_REQ_IO_COMPLETION "
+                      "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_TRANSMIT_FILE:
+    {
+        /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
+         */
+        HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data;
+        apr_uint32_t sent = 0;
+        apr_ssize_t ate = 0;
+        apr_bucket_brigade *bb;
+        apr_bucket *b;
+        apr_file_t *fd;
+        apr_off_t fsize;
+
+        if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) {
+            if (cid->dconf.log_unsupported)
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                         "ISAPI: ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
+                         "as HSE_IO_ASYNC is not supported: %s", r->filename);
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+            return 0;
+        }
+
+        /* Presume the handle was opened with the CORRECT semantics
+         * for TransmitFile
+         */
+        if ((rv = apr_os_file_put(&fd, &tf->hFile,
+                                  APR_READ | APR_XTHREAD, r->pool))
+                != APR_SUCCESS) {
+            return 0;
+        }
+        if (tf->BytesToWrite) {
+            fsize = tf->BytesToWrite;
+        }
+        else {
+            apr_finfo_t fi;
+            if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
+                apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+                return 0;
+            }
+            fsize = fi.size - tf->Offset;
+        }
+
+        /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
+        bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+        /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
+         * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
+         * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
+         * you must have done so.  They document that the pHead headers
+         * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
+         * more flexible and assume with the flag, pHead are the
+         * response headers, and without, pHead simply contains text
+         * (handled after this case).
+         */
+        if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) {
+            ate = send_response_header(cid, tf->pszStatusCode,
+                                            (char*)tf->pHead,
+                                            strlen(tf->pszStatusCode),
+                                            tf->HeadLength);
+        }
+        else if (!cid->headers_set && tf->pHead && tf->HeadLength
+                                   && *(char*)tf->pHead) {
+            ate = send_response_header(cid, NULL, (char*)tf->pHead,
+                                            0, tf->HeadLength);
+            if (ate < 0)
+            {
+                apr_brigade_destroy(bb);
+                apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+                return 0;
+            }
+        }
+
+        if (tf->pHead && (apr_size_t)ate < tf->HeadLength) {
+            b = apr_bucket_transient_create((char*)tf->pHead + ate,
+                                            tf->HeadLength - ate,
+                                            c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+            sent = tf->HeadLength;
+        }
+
+        sent += (apr_uint32_t)fsize;
+        brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool);
+
+        if (tf->pTail && tf->TailLength) {
+            sent += tf->TailLength;
+            b = apr_bucket_transient_create((char*)tf->pTail,
+                                            tf->TailLength, c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+        }
+
+        b = apr_bucket_flush_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+        rv = ap_pass_brigade(r->output_filters, bb);
+        cid->response_sent = 1;
+        if (rv != APR_SUCCESS)
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+                          "ISAPI: ServerSupport function "
+                          "HSE_REQ_TRANSMIT_FILE "
+                          "ap_pass_brigade failed: %s", r->filename);
+
+        /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete
+         * pass pContect to the HseIO callback.
+         */
+        if (tf->dwFlags & HSE_IO_ASYNC) {
+            if (tf->pfnHseIO) {
+                if (rv == APR_SUCCESS) {
+                    tf->pfnHseIO(cid->ecb, tf->pContext,
+                                 ERROR_SUCCESS, sent);
+                }
+                else {
+                    tf->pfnHseIO(cid->ecb, tf->pContext,
+                                 ERROR_WRITE_FAULT, sent);
+                }
+            }
+            else if (cid->completion) {
+                if (rv == APR_SUCCESS) {
+                    cid->completion(cid->ecb, cid->completion_arg,
+                                    sent, ERROR_SUCCESS);
+                }
+                else {
+                    cid->completion(cid->ecb, cid->completion_arg,
+                                    sent, ERROR_WRITE_FAULT);
+                }
+            }
+        }
+        return (rv == APR_SUCCESS);
+    }
+
+    case HSE_REQ_REFRESH_ISAPI_ACL:
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_REFRESH_ISAPI_ACL "
+                          "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_IS_KEEP_CONN:
+        *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE);
+        return 1;
+
+    case HSE_REQ_ASYNC_READ_CLIENT:
+    {
+        apr_uint32_t read = 0;
+        int res;
+        if (!cid->dconf.fake_async) {
+            if (cid->dconf.log_unsupported)
+                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                            "ISAPI: asynchronous I/O not supported: %s",
+                            r->filename);
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+            return 0;
+        }
+
+        if (r->remaining < *buf_size) {
+            *buf_size = (apr_size_t)r->remaining;
+        }
+
+        while (read < *buf_size &&
+            ((res = ap_get_client_block(r, (char*)buf_data + read,
+                                        *buf_size - read)) > 0)) {
+            read += res;
+        }
+
+        if ((*data_type & HSE_IO_ASYNC) && cid->completion) {
+            /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT
+             * within the completion logic.  An example is MS's own PSDK
+             * sample web/iis/extensions/io/ASyncRead.  This potentially
+             * leads to stack exhaustion.  To refactor, the notification 
+             * logic needs to move to isapi_handler() - differentiating
+             * the cid->completed event with a new flag to indicate 
+             * an async-notice versus the async request completed.
+             */
+            if (res >= 0) {
+                cid->completion(cid->ecb, cid->completion_arg,
+                                read, ERROR_SUCCESS);
+            }
+            else {
+                cid->completion(cid->ecb, cid->completion_arg,
+                                read, ERROR_READ_FAULT);
+            }
+        }
+        return (res >= 0);
+    }
+
+    case HSE_REQ_GET_IMPERSONATION_TOKEN:  /* Added in ISAPI 4.0 */
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_GET_IMPERSONATION_TOKEN "
+                          "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_MAP_URL_TO_PATH_EX:
+    {
+        /* Map a URL to a filename */
+        HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type;
+        char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size);
+
+        subreq = ap_sub_req_lookup_uri(test_uri, r, NULL);
+        info->cchMatchingURL = strlen(test_uri);
+        info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename,
+                                      sizeof(info->lpszPath)) - info->lpszPath;
+
+        /* Mapping started with assuming both strings matched.
+         * Now roll on the path_info as a mismatch and handle
+         * terminating slashes for directory matches.
+         */
+        if (subreq->path_info && *subreq->path_info) {
+            apr_cpystrn(info->lpszPath + info->cchMatchingPath,
+                        subreq->path_info,
+                        sizeof(info->lpszPath) - info->cchMatchingPath);
+            info->cchMatchingURL -= strlen(subreq->path_info);
+            if (subreq->finfo.filetype == APR_DIR
+                 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
+                /* roll forward over path_info's first slash */
+                ++info->cchMatchingPath;
+                ++info->cchMatchingURL;
+            }
+        }
+        else if (subreq->finfo.filetype == APR_DIR
+                 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
+            /* Add a trailing slash for directory */
+            info->lpszPath[info->cchMatchingPath++] = '/';
+            info->lpszPath[info->cchMatchingPath] = '\0';
+        }
+
+        /* If the matched isn't a file, roll match back to the prior slash */
+        if (subreq->finfo.filetype == APR_NOFILE) {
+            while (info->cchMatchingPath && info->cchMatchingURL) {
+                if (info->lpszPath[info->cchMatchingPath - 1] == '/')
+                    break;
+                --info->cchMatchingPath;
+                --info->cchMatchingURL;
+            }
+        }
+
+        /* Paths returned with back slashes */
+        for (test_uri = info->lpszPath; *test_uri; ++test_uri)
+            if (*test_uri == '/')
+                *test_uri = '\\';
+
+        /* is a combination of:
+         * HSE_URL_FLAGS_READ         0x001 Allow read
+         * HSE_URL_FLAGS_WRITE        0x002 Allow write
+         * HSE_URL_FLAGS_EXECUTE      0x004 Allow execute
+         * HSE_URL_FLAGS_SSL          0x008 Require SSL
+         * HSE_URL_FLAGS_DONT_CACHE   0x010 Don't cache (VRoot only)
+         * HSE_URL_FLAGS_NEGO_CERT    0x020 Allow client SSL cert
+         * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
+         * HSE_URL_FLAGS_MAP_CERT     0x080 Map client SSL cert to account
+         * HSE_URL_FLAGS_SSL128       0x100 Require 128-bit SSL cert
+         * HSE_URL_FLAGS_SCRIPT       0x200 Allow script execution
+         *
+         * XxX: As everywhere, EXEC flags could use some work...
+         *      and this could go further with more flags, as desired.
+         */
+        info->dwFlags = (subreq->finfo.protection & APR_UREAD    ? 0x001 : 0)
+                      | (subreq->finfo.protection & APR_UWRITE   ? 0x002 : 0)
+                      | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
+        return 1;
+    }
+
+    case HSE_REQ_ABORTIVE_CLOSE:
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
+                          " is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_GET_CERT_INFO_EX:  /* Added in ISAPI 4.0 */
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_GET_CERT_INFO_EX "
+                          "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_SEND_RESPONSE_HEADER_EX:  /* Added in ISAPI 4.0 */
+    {
+        HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data;
+
+        /*  Ignore shi->fKeepConn - we don't want the advise
+         */
+        apr_ssize_t ate = send_response_header(cid, shi->pszStatus,
+                                               shi->pszHeader,
+                                               shi->cchStatus,
+                                               shi->cchHeader);
+        if (ate < 0) {
+            apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+            return 0;
+        }
+        else if ((apr_size_t)ate < shi->cchHeader) {
+            apr_bucket_brigade *bb;
+            apr_bucket *b;
+            bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
+            b = apr_bucket_transient_create(shi->pszHeader + ate,
+                                            shi->cchHeader - ate,
+                                            c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+            b = apr_bucket_flush_create(c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(bb, b);
+            rv = ap_pass_brigade(cid->r->output_filters, bb);
+            cid->response_sent = 1;
+            if (rv != APR_SUCCESS)
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+                              "ISAPI: ServerSupport function "
+                              "HSE_REQ_SEND_RESPONSE_HEADER_EX "
+                              "ap_pass_brigade failed: %s", r->filename);
+            return (rv == APR_SUCCESS);
+        }
+        /* Deliberately hold off sending 'just the headers' to begin to
+         * accumulate the body and speed up the overall response, or at
+         * least wait for the end the session.
+         */
+        return 1;
+    }
+
+    case HSE_REQ_CLOSE_CONNECTION:  /* Added after ISAPI 4.0 */
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_CLOSE_CONNECTION "
+                          "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    case HSE_REQ_IS_CONNECTED:  /* Added after ISAPI 4.0 */
+        /* Returns True if client is connected c.f. MSKB Q188346
+         * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN
+         */
+        *((int *)buf_data) = (r->connection->aborted == 0);
+        return 1;
+
+    case HSE_REQ_EXTENSION_TRIGGER:  /* Added after ISAPI 4.0 */
+        /*  Undocumented - defined by the Microsoft Jan '00 Platform SDK
+         */
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction "
+                          "HSE_REQ_EXTENSION_TRIGGER "
+                          "is not supported: %s", r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+
+    default:
+        if (cid->dconf.log_unsupported)
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: ServerSupportFunction (%d) not supported: "
+                          "%s", HSE_code, r->filename);
+        apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
+        return 0;
+    }
+}
+
+/**********************************************************
+ *
+ *  ISAPI Module request invocation section
+ *
+ **********************************************************/
+
+apr_status_t isapi_handler (request_rec *r)
+{
+    isapi_dir_conf *dconf;
+    apr_table_t *e;
+    apr_status_t rv;
+    isapi_loaded *isa;
+    isapi_cid *cid;
+    const char *val;
+    apr_uint32_t read;
+    int res;
+
+    if(strcmp(r->handler, "isapi-isa")
+        && strcmp(r->handler, "isapi-handler")) {
+        /* Hang on to the isapi-isa for compatibility with older docs
+         * (wtf did '-isa' mean in the first place?) but introduce
+         * a newer and clearer "isapi-handler" name.
+         */
+        return DECLINED;
+    }
+    dconf = ap_get_module_config(r->per_dir_config, &isapi_module);
+    e = r->subprocess_env;
+
+    /* Use similar restrictions as CGIs
+     *
+     * If this fails, it's pointless to load the isapi dll.
+     */
+    if (!(ap_allow_options(r) & OPT_EXECCGI)) {
+        return HTTP_FORBIDDEN;
+    }
+    if (r->finfo.filetype == APR_NOFILE) {
+        return HTTP_NOT_FOUND;
+    }
+    if (r->finfo.filetype != APR_REG) {
+        return HTTP_FORBIDDEN;
+    }
+    if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
+        r->path_info && *r->path_info) {
+        /* default to accept */
+        return HTTP_NOT_FOUND;
+    }
+
+    if (isapi_lookup(r->pool, r->server, r, r->filename, &isa)
+           != APR_SUCCESS) {
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    /* Set up variables */
+    ap_add_common_vars(r);
+    ap_add_cgi_vars(r);
+    apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
+    if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0))
+        apr_table_setn(e, "SERVER_PORT_SECURE", "1");
+    else
+        apr_table_setn(e, "SERVER_PORT_SECURE", "0");
+    apr_table_setn(e, "URL", r->uri);
+
+    /* Set up connection structure and ecb,
+     * NULL or zero out most fields.
+     */
+    cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
+
+    /* Fixup defaults for dconf */
+    cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF)
+                                     ? 49152 : dconf->read_ahead_buflen;
+    cid->dconf.log_unsupported   = (dconf->log_unsupported == ISAPI_UNDEF)
+                                     ? 0 : dconf->log_unsupported;
+    cid->dconf.log_to_errlog     = (dconf->log_to_errlog == ISAPI_UNDEF)
+                                     ? 0 : dconf->log_to_errlog;
+    cid->dconf.log_to_query      = (dconf->log_to_query == ISAPI_UNDEF)
+                                     ? 1 : dconf->log_to_query;
+    cid->dconf.fake_async        = (dconf->fake_async == ISAPI_UNDEF)
+                                     ? 0 : dconf->fake_async;
+
+    cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK));
+    cid->ecb->ConnID = cid;
+    cid->isa = isa;
+    cid->r = r;
+    r->status = 0;
+
+    cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
+    cid->ecb->dwVersion = isa->report_version;
+    cid->ecb->dwHttpStatusCode = 0;
+    strcpy(cid->ecb->lpszLogData, "");
+    /* TODO: are copies really needed here?
+     */
+    cid->ecb->lpszMethod = (char*) r->method;
+    cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING");
+    cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO");
+    cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED");
+    cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE");
+
+    /* Set up the callbacks */
+    cid->ecb->GetServerVariable = GetServerVariable;
+    cid->ecb->WriteClient = WriteClient;
+    cid->ecb->ReadClient = ReadClient;
+    cid->ecb->ServerSupportFunction = ServerSupportFunction;
+
+    /* Set up client input */
+    res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
+    if (res) {
+        return res;
+    }
+
+    if (ap_should_client_block(r)) {
+        /* Time to start reading the appropriate amount of data,
+         * and allow the administrator to tweak the number
+         */
+        if (r->remaining) {
+            cid->ecb->cbTotalBytes = (apr_size_t)r->remaining;
+            if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen)
+                cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
+            else
+                cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
+        }
+        else
+        {
+            cid->ecb->cbTotalBytes = 0xffffffff;
+            cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
+        }
+
+        cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
+
+        read = 0;
+        while (read < cid->ecb->cbAvailable &&
+               ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read,
+                                        cid->ecb->cbAvailable - read)) > 0)) {
+            read += res;
+        }
+
+        if (res < 0) {
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        /* Although it's not to spec, IIS seems to null-terminate
+         * its lpdData string. So we will too.
+         */
+        if (res == 0)
+            cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
+        else
+            cid->ecb->cbAvailable = read;
+        cid->ecb->lpbData[read] = '\0';
+    }
+    else {
+        cid->ecb->cbTotalBytes = 0;
+        cid->ecb->cbAvailable = 0;
+        cid->ecb->lpbData = NULL;
+    }
+
+    /* To emulate async behavior...
+     *
+     * We create a cid->completed mutex and lock on it so that the
+     * app can believe is it running async.
+     *
+     * This request completes upon a notification through
+     * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which
+     * unlocks this mutex.  If the HttpExtensionProc() returns
+     * HSE_STATUS_PENDING, we will attempt to gain this lock again
+     * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has
+     * unlocked the mutex.
+     */
+    if (cid->dconf.fake_async) {
+        rv = apr_thread_mutex_create(&cid->completed,
+                                     APR_THREAD_MUTEX_UNNESTED,
+                                     r->pool);
+        if (cid->completed && (rv == APR_SUCCESS)) {
+            rv = apr_thread_mutex_lock(cid->completed);
+        }
+
+        if (!cid->completed || (rv != APR_SUCCESS)) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                          "ISAPI: Failed to create completion mutex");
+            return HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    /* All right... try and run the sucker */
+    rv = (*isa->HttpExtensionProc)(cid->ecb);
+
+    /* Check for a log message - and log it */
+    if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
+        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                      "ISAPI: %s: %s", r->filename, cid->ecb->lpszLogData);
+
+    switch(rv) {
+        case 0:  /* Strange, but MS isapi accepts this as success */
+        case HSE_STATUS_SUCCESS:
+        case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
+            /* Ignore the keepalive stuff; Apache handles it just fine without
+             * the ISAPI Handler's "advice".
+             * Per Microsoft: "In IIS versions 4.0 and later, the return
+             * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
+             * are functionally identical: Keep-Alive connections are
+             * maintained, if supported by the client."
+             * ... so we were pat all this time
+             */
+            break;
+
+        case HSE_STATUS_PENDING:
+            /* emulating async behavior...
+             */
+            if (cid->completed) {
+                /* The completion port was locked prior to invoking
+                 * HttpExtensionProc().  Once we can regain the lock,
+                 * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION)
+                 * is called by the extension to release the lock,
+                 * we may finally destroy the request.
+                 */
+                (void)apr_thread_mutex_lock(cid->completed);
+                break;
+            }
+            else if (cid->dconf.log_unsupported) {
+                 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
+                               "ISAPI: asynch I/O result HSE_STATUS_PENDING "
+                               "from HttpExtensionProc() is not supported: %s",
+                               r->filename);
+                 r->status = HTTP_INTERNAL_SERVER_ERROR;
+            }
+            break;
+
+        case HSE_STATUS_ERROR:
+            /* end response if we have yet to do so.
+             */
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r,
+                          "ISAPI: HSE_STATUS_ERROR result from "
+                          "HttpExtensionProc(): %s", r->filename);
+            r->status = HTTP_INTERNAL_SERVER_ERROR;
+            break;
+
+        default:
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r,
+                          "ISAPI: unrecognized result code %d "
+                          "from HttpExtensionProc(): %s ", 
+                          rv, r->filename);
+            r->status = HTTP_INTERNAL_SERVER_ERROR;
+            break;
+    }
+
+    /* Flush the response now, including headers-only responses */
+    if (cid->headers_set || cid->response_sent) {
+        conn_rec *c = r->connection;
+        apr_bucket_brigade *bb;
+        apr_bucket *b;
+        apr_status_t rv;
+
+        bb = apr_brigade_create(r->pool, c->bucket_alloc);
+        b = apr_bucket_eos_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+        rv = ap_pass_brigade(r->output_filters, bb);
+        cid->response_sent = 1;
+
+        if (rv != APR_SUCCESS) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+                          "ISAPI: ap_pass_brigade failed to "
+                          "complete the response: %s ", r->filename);
+        }
+
+        return OK; /* NOT r->status, even if it has changed. */
+    }
+
+    /* As the client returned no error, and if we did not error out
+     * ourselves, trust dwHttpStatusCode to say something relevant.
+     */
+    if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) {
+        r->status = cid->ecb->dwHttpStatusCode;
+    }
+
+    /* For all missing-response situations simply return the status,
+     * and let the core respond to the client.
+     */
+    return r->status;
+}
+
+/**********************************************************
+ *
+ *  ISAPI Module Setup Hooks
+ *
+ **********************************************************/
+
+static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
+{
+    apr_status_t rv;
+
+    apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL);
+    if (!loaded.pool) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL,
+                     "ISAPI: could not create the isapi cache pool");
+        return APR_EGENERAL;
+    }
+
+    loaded.hash = apr_hash_make(loaded.pool);
+    if (!loaded.hash) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
+                     "ISAPI: Failed to create module cache");
+        return APR_EGENERAL;
+    }
+
+    rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT,
+                                 loaded.pool);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, rv, 0, NULL,
+                     "ISAPI: Failed to create module cache lock");
+        return rv;
+    }
+    return OK;
+}
+
+static void isapi_hooks(apr_pool_t *cont)
+{
+    ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+module AP_MODULE_DECLARE_DATA isapi_module = {
+   STANDARD20_MODULE_STUFF,
+   create_isapi_dir_config,     /* create per-dir config */
+   merge_isapi_dir_configs,     /* merge per-dir config */
+   NULL,                        /* server config */
+   NULL,                        /* merge server config */
+   isapi_cmds,                  /* command apr_table_t */
+   isapi_hooks                  /* register hooks */
+};