| /* Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * 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 = MAKELONG(0, 5); /* 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"); |
| SetLastError(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_data, |
| apr_uint32_t *buf_size) |
| { |
| request_rec *r = cid->r; |
| const char *result; |
| 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; |
| SetLastError(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); |
| ((char*)buf_data) += strlen(elts[i].key); |
| *(((char*)buf_data)++) = ':'; |
| strcpy(buf_data, elts[i].val); |
| ((char*)buf_data) += strlen(elts[i].val); |
| *(((char*)buf_data)++) = '\r'; |
| *(((char*)buf_data)++) = '\n'; |
| } |
| } |
| |
| *(((char*)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; |
| SetLastError(ERROR_INSUFFICIENT_BUFFER); |
| return 0; |
| } |
| |
| for (i = 0; i < arr->nelts; i++) { |
| strcpy(buf_data, elts[i].key); |
| ((char*)buf_data) += strlen(elts[i].key); |
| *(((char*)buf_data)++) = ':'; |
| *(((char*)buf_data)++) = ' '; |
| strcpy(buf_data, elts[i].val); |
| ((char*)buf_data) += strlen(elts[i].val); |
| *(((char*)buf_data)++) = '\r'; |
| *(((char*)buf_data)++) = '\n'; |
| } |
| *(((char*)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; |
| SetLastError(ERROR_INSUFFICIENT_BUFFER); |
| return 0; |
| } |
| strcpy(buf_data, result); |
| *buf_size = len + 1; |
| return 1; |
| } |
| |
| /* Not Found */ |
| SetLastError(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) { |
| SetLastError(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; |
| 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 |
| */ |
| if (stat) { |
| cid->r->status = ap_scan_script_header_err_strs(cid->r, NULL, |
| &termch, &termarg, stat, head, NULL); |
| cid->ecb->dwHttpStatusCode = cid->r->status; |
| } |
| else { |
| cid->r->status = ap_scan_script_header_err_strs(cid->r, NULL, |
| &termch, &termarg, head, NULL); |
| if (cid->ecb->dwHttpStatusCode && cid->r->status == HTTP_OK |
| && cid->ecb->dwHttpStatusCode != HTTP_OK) { |
| /* We tried every way to Sunday to get the status... |
| * so 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 { |
| cid->ecb->dwHttpStatusCode = 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_data, |
| 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; |
| apr_bucket_brigade *bb; |
| apr_bucket *b; |
| apr_status_t rv; |
| |
| 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, (char*)buf_data, |
| 0, buf_size); |
| if (ate < 0) { |
| SetLastError(ERROR_INVALID_PARAMETER); |
| return 0; |
| } |
| |
| (char*)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 ((flags & HSE_IO_ASYNC) && cid->completion) { |
| if (rv == OK) { |
| 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 == OK); |
| } |
| |
| int APR_THREAD_FUNC ServerSupportFunction(isapi_cid *cid, |
| apr_uint32_t HSE_code, |
| void *buf_data, |
| apr_uint32_t *buf_size, |
| apr_uint32_t *data_type) |
| { |
| request_rec *r = cid->r; |
| conn_rec *c = r->connection; |
| request_rec *subreq; |
| |
| 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! */ |
| (char*)buf_data = apr_pstrdup(r->pool, (char*)buf_data); |
| ap_internal_redirect((char*)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) { |
| SetLastError(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); |
| ap_pass_brigade(cid->r->output_filters, bb); |
| cid->response_sent = 1; |
| } |
| 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); |
| } |
| SetLastError(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(r->pool, file, *buf_size), |
| r, NULL); |
| |
| len = apr_cpystrn(file, subreq->filename, *buf_size) - file; |
| |
| |
| /* IIS puts a trailing slash on directories, Apache doesn't */ |
| if (subreq->finfo.filetype == APR_DIR) { |
| if (len < *buf_size - 1) { |
| file[len++] = '\\'; |
| file[len] = '\0'; |
| } |
| } |
| *buf_size = len; |
| 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); |
| SetLastError(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); |
| SetLastError(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_status_t rv; |
| 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); |
| SetLastError(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) { |
| SetLastError(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); |
| SetLastError(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; |
| #if APR_HAS_LARGE_FILES |
| if (r->finfo.size > AP_MAX_SENDFILE) { |
| /* APR_HAS_LARGE_FILES issue; must split into mutiple buckets, |
| * no greater than MAX(apr_size_t), and more granular than that |
| * in case the brigade code/filters attempt to read it directly. |
| */ |
| b = apr_bucket_file_create(fd, tf->Offset, AP_MAX_SENDFILE, |
| r->pool, c->bucket_alloc); |
| while (fsize > AP_MAX_SENDFILE) { |
| apr_bucket *bc; |
| apr_bucket_copy(b, &bc); |
| APR_BRIGADE_INSERT_TAIL(bb, bc); |
| b->start += AP_MAX_SENDFILE; |
| fsize -= AP_MAX_SENDFILE; |
| } |
| b->length = (apr_size_t)fsize; /* Resize just the last bucket */ |
| } |
| else |
| #endif |
| b = apr_bucket_file_create(fd, tf->Offset, (apr_size_t)fsize, |
| r->pool, c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(bb, b); |
| |
| 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); |
| ap_pass_brigade(r->output_filters, bb); |
| cid->response_sent = 1; |
| |
| /* 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 == OK) { |
| 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 == OK) { |
| cid->completion(cid->ecb, cid->completion_arg, |
| sent, ERROR_SUCCESS); |
| } |
| else { |
| cid->completion(cid->ecb, cid->completion_arg, |
| sent, ERROR_WRITE_FAULT); |
| } |
| } |
| } |
| return (rv == OK); |
| } |
| |
| 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); |
| SetLastError(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); |
| SetLastError(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) { |
| 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); |
| SetLastError(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); |
| SetLastError(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); |
| SetLastError(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) { |
| SetLastError(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); |
| ap_pass_brigade(cid->r->output_filters, bb); |
| cid->response_sent = 1; |
| } |
| 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); |
| SetLastError(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); |
| SetLastError(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); |
| SetLastError(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")) |
| 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) { |
| isapi_unload(isa, 0); |
| 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, cid->ecb->lpbData + read, |
| cid->ecb->cbAvailable - read)) > 0)) { |
| read += res; |
| } |
| |
| if (res < 0) { |
| isapi_unload(isa, 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. |
| */ |
| r->status = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| |
| default: |
| /* TODO: log unrecognized retval for debugging |
| */ |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, |
| "ISAPI: return code %d from HttpExtensionProc() " |
| "was not not recognized", rv); |
| r->status = HTTP_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| |
| /* Flush the response now, including headers-only responses */ |
| if (cid->headers_set) { |
| 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); |
| 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; |
| |
| return OK; /* NOT r->status or cid->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 deal 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 */ |
| }; |