1729 lines
63 KiB
C
1729 lines
63 KiB
C
/* 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 extensions" 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, APLOGNO(02103)
|
|
"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, APLOGNO(02104)
|
|
"unable to stat, skipping %s", fspec);
|
|
return NULL;
|
|
}
|
|
if (tmp.filetype != APR_REG) {
|
|
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(02105)
|
|
"not a regular file, skipping %s", fspec);
|
|
return NULL;
|
|
}
|
|
|
|
/* Load the extension 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, APLOGNO(02106)
|
|
"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 concatenated 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 overridable, 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, APLOGNO(02107)
|
|
"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, APLOGNO(02108)
|
|
"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, APLOGNO(02109)
|
|
"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, APLOGNO(02110)
|
|
"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;
|
|
};
|
|
|
|
static int APR_THREAD_FUNC regfnGetServerVariable(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;
|
|
}
|
|
|
|
static int APR_THREAD_FUNC regfnReadClient(isapi_cid *cid,
|
|
void *buf_data,
|
|
apr_uint32_t *buf_size)
|
|
{
|
|
request_rec *r = cid->r;
|
|
apr_uint32_t read = 0;
|
|
int res = 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;
|
|
}
|
|
|
|
*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_ex(cid->r, NULL,
|
|
APLOG_MODULE_INDEX, &termch, &termarg, stat, head, NULL);
|
|
}
|
|
else {
|
|
res = ap_scan_script_header_err_strs_ex(cid->r, NULL,
|
|
APLOG_MODULE_INDEX, &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 pedantic 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, APLOGNO(02111)
|
|
"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;
|
|
}
|
|
|
|
static int APR_THREAD_FUNC regfnWriteClient(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, APLOGNO(02984)
|
|
"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);
|
|
}
|
|
|
|
static int APR_THREAD_FUNC regfnServerSupportFunction(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 equivalent per the Jan 2000 SDK.
|
|
* They most definitely 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 = "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, APLOGNO(03177)
|
|
"ServerSupportFunction "
|
|
"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, APLOGNO(02671)
|
|
"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(subreq->filename);
|
|
|
|
if ((subreq->finfo.filetype == APR_DIR)
|
|
&& (!subreq->path_info)
|
|
&& (subreq->filename[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, APLOGNO(02672)
|
|
"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, APLOGNO(02985)
|
|
"%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, APLOGNO(02673)
|
|
"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, APLOGNO(02674)
|
|
"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;
|
|
apr_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, APLOGNO(03178)
|
|
"ServerSupportFunction "
|
|
"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, APLOGNO(02675)
|
|
"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 = 0;
|
|
if (!cid->dconf.fake_async) {
|
|
if (cid->dconf.log_unsupported)
|
|
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02986)
|
|
"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, APLOGNO(02676)
|
|
"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, APLOGNO(02677)
|
|
"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, APLOGNO(02678)
|
|
"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, APLOGNO(03179)
|
|
"ServerSupportFunction "
|
|
"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, APLOGNO(02679)
|
|
"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, APLOGNO(02680)
|
|
"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, APLOGNO(02681)
|
|
"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
|
|
*
|
|
**********************************************************/
|
|
|
|
static 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 = regfnGetServerVariable;
|
|
cid->ecb->WriteClient = regfnWriteClient;
|
|
cid->ecb->ReadClient = regfnReadClient;
|
|
cid->ecb->ServerSupportFunction = regfnServerSupportFunction;
|
|
|
|
/* 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, APLOGNO(02112)
|
|
"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) {
|
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02113)
|
|
"%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, APLOGNO(02114)
|
|
"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, APLOGNO(02115)
|
|
"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, APLOGNO(02116)
|
|
"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, APLOGNO(02117)
|
|
"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, APLOGNO(02118)
|
|
"could not create the isapi cache pool");
|
|
return APR_EGENERAL;
|
|
}
|
|
apr_pool_tag(loaded.pool, "mod_isapi_load");
|
|
|
|
loaded.hash = apr_hash_make(loaded.pool);
|
|
if (!loaded.hash) {
|
|
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(02119)
|
|
"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, APLOG_ERR, rv, NULL, APLOGNO(02682)
|
|
"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);
|
|
}
|
|
|
|
AP_DECLARE_MODULE(isapi) = {
|
|
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 */
|
|
};
|