1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * mod_isapi.c - Internet Server Application (ISA) module for Apache
19 * by Alexei Kosut <akosut apache.org>, significant overhauls and
20 * redesign by William Rowe <wrowe covalent.net>, and hints from many
21 * other developer/users who have hit on specific flaws.
23 * This module implements the ISAPI Handler architecture, allowing
24 * Apache to load Internet Server Applications (ISAPI extensions),
25 * similar to the support in IIS, Zope, O'Reilly's WebSite and others.
27 * It is a complete implementation of the ISAPI 2.0 specification,
28 * except for "Microsoft extensions" to the API which provide
29 * asynchronous I/O. It is further extended to include additional
30 * "Microsoft extentions" through IIS 5.0, with some deficiencies
31 * where one-to-one mappings don't exist.
33 * Refer to /manual/mod/mod_isapi.html for additional details on
34 * configuration and use, but check this source for specific support
38 #include "ap_config.h"
40 #include "http_config.h"
41 #include "http_core.h"
42 #include "http_protocol.h"
43 #include "http_request.h"
45 #include "util_script.h"
48 #include "apr_strings.h"
49 #include "apr_portable.h"
50 #include "apr_buckets.h"
51 #include "apr_thread_mutex.h"
52 #include "apr_thread_rwlock.h"
54 #include "mod_isapi.h"
56 /* Retry frequency for a failed-to-load isapi .dll */
57 #define ISAPI_RETRY apr_time_from_sec(30)
59 /**********************************************************
61 * ISAPI Module Configuration
63 **********************************************************/
65 module AP_MODULE_DECLARE_DATA isapi_module;
67 #define ISAPI_UNDEF -1
69 /* Our isapi per-dir config structure */
70 typedef struct isapi_dir_conf {
71 int read_ahead_buflen;
78 typedef struct isapi_loaded isapi_loaded;
80 apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
81 const char *fpath, isapi_loaded** isa);
83 static void *create_isapi_dir_config(apr_pool_t *p, char *dummy)
85 isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
87 dir->read_ahead_buflen = ISAPI_UNDEF;
88 dir->log_unsupported = ISAPI_UNDEF;
89 dir->log_to_errlog = ISAPI_UNDEF;
90 dir->log_to_query = ISAPI_UNDEF;
91 dir->fake_async = ISAPI_UNDEF;
96 static void *merge_isapi_dir_configs(apr_pool_t *p, void *base_, void *add_)
98 isapi_dir_conf *base = (isapi_dir_conf *) base_;
99 isapi_dir_conf *add = (isapi_dir_conf *) add_;
100 isapi_dir_conf *dir = apr_palloc(p, sizeof(isapi_dir_conf));
102 dir->read_ahead_buflen = (add->read_ahead_buflen == ISAPI_UNDEF)
103 ? base->read_ahead_buflen
104 : add->read_ahead_buflen;
105 dir->log_unsupported = (add->log_unsupported == ISAPI_UNDEF)
106 ? base->log_unsupported
107 : add->log_unsupported;
108 dir->log_to_errlog = (add->log_to_errlog == ISAPI_UNDEF)
109 ? base->log_to_errlog
110 : add->log_to_errlog;
111 dir->log_to_query = (add->log_to_query == ISAPI_UNDEF)
114 dir->fake_async = (add->fake_async == ISAPI_UNDEF)
121 static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
122 const char *filename)
129 /* ### Just an observation ... it would be terribly cool to be
130 * able to use this per-dir, relative to the directory block being
131 * defined. The hash result remains global, but shorthand of
132 * <Directory "c:/webapps/isapi">
133 * ISAPICacheFile myapp.dll anotherapp.dll thirdapp.dll
135 * would be very convienent.
137 fspec = ap_server_root_relative(cmd->pool, filename);
139 ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
140 "ISAPI: invalid module path, skipping %s", filename);
143 if ((rv = apr_stat(&tmp, fspec, APR_FINFO_TYPE,
144 cmd->temp_pool)) != APR_SUCCESS) {
145 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
146 "ISAPI: unable to stat, skipping %s", fspec);
149 if (tmp.filetype != APR_REG) {
150 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
151 "ISAPI: not a regular file, skipping %s", fspec);
155 /* Load the extention as cached (with null request_rec) */
156 rv = isapi_lookup(cmd->pool, cmd->server, NULL, fspec, &isa);
157 if (rv != APR_SUCCESS) {
158 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
159 "ISAPI: unable to cache, skipping %s", fspec);
166 static const command_rec isapi_cmds[] = {
167 AP_INIT_TAKE1("ISAPIReadAheadBuffer", ap_set_int_slot,
168 (void *)APR_OFFSETOF(isapi_dir_conf, read_ahead_buflen),
169 OR_FILEINFO, "Maximum client request body to initially pass to the"
170 " ISAPI handler (default: 49152)"),
171 AP_INIT_FLAG("ISAPILogNotSupported", ap_set_flag_slot,
172 (void *)APR_OFFSETOF(isapi_dir_conf, log_unsupported),
173 OR_FILEINFO, "Log requests not supported by the ISAPI server"
174 " on or off (default: off)"),
175 AP_INIT_FLAG("ISAPIAppendLogToErrors", ap_set_flag_slot,
176 (void *)APR_OFFSETOF(isapi_dir_conf, log_to_errlog),
177 OR_FILEINFO, "Send all Append Log requests to the error log"
178 " on or off (default: off)"),
179 AP_INIT_FLAG("ISAPIAppendLogToQuery", ap_set_flag_slot,
180 (void *)APR_OFFSETOF(isapi_dir_conf, log_to_query),
181 OR_FILEINFO, "Append Log requests are concatinated to the query args"
182 " on or off (default: on)"),
183 AP_INIT_FLAG("ISAPIFakeAsync", ap_set_flag_slot,
184 (void *)APR_OFFSETOF(isapi_dir_conf, fake_async),
185 OR_FILEINFO, "Fake Asynchronous support for isapi callbacks"
186 " on or off [Experimental] (default: off)"),
187 AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL,
188 RSRC_CONF, "Cache the specified ISAPI extension in-process"),
192 /**********************************************************
194 * ISAPI Module Cache handling section
196 **********************************************************/
198 /* Our isapi global config values */
199 static struct isapi_global_conf {
201 apr_thread_mutex_t *lock;
205 /* Our loaded isapi module description structure */
206 struct isapi_loaded {
207 const char *filename;
208 apr_thread_rwlock_t *in_progress;
209 apr_status_t last_load_rv;
210 apr_time_t last_load_time;
211 apr_dso_handle_t *handle;
212 HSE_VERSION_INFO *isapi_version;
213 apr_uint32_t report_version;
214 apr_uint32_t timeout;
215 PFN_GETEXTENSIONVERSION GetExtensionVersion;
216 PFN_HTTPEXTENSIONPROC HttpExtensionProc;
217 PFN_TERMINATEEXTENSION TerminateExtension;
220 static apr_status_t isapi_unload(isapi_loaded *isa, int force)
222 /* All done with the DLL... get rid of it...
224 * If optionally cached, and we weren't asked to force the unload,
225 * pass HSE_TERM_ADVISORY_UNLOAD, and if it returns 1, unload,
226 * otherwise, leave it alone (it didn't choose to cooperate.)
231 if (isa->TerminateExtension) {
233 (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
235 else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD)) {
239 apr_dso_unload(isa->handle);
244 static apr_status_t cleanup_isapi(void *isa_)
246 isapi_loaded* isa = (isapi_loaded*) isa_;
248 /* We must force the module to unload, we are about
249 * to lose the isapi structure's allocation entirely.
251 return isapi_unload(isa, 1);
254 static apr_status_t isapi_load(apr_pool_t *p, server_rec *s, isapi_loaded *isa)
258 isa->isapi_version = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
260 /* TODO: These aught to become overrideable, so that we
261 * assure a given isapi can be fooled into behaving well.
263 * The tricky bit, they aren't really a per-dir sort of
264 * config, they will always be constant across every
265 * reference to the .dll no matter what context (vhost,
266 * location, etc) they apply to.
268 isa->report_version = 0x500; /* Revision 5.0 */
269 isa->timeout = 300 * 1000000; /* microsecs, not used */
271 rv = apr_dso_load(&isa->handle, isa->filename, p);
274 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
275 "ISAPI: failed to load %s", isa->filename);
280 rv = apr_dso_sym((void**)&isa->GetExtensionVersion, isa->handle,
281 "GetExtensionVersion");
284 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
285 "ISAPI: missing GetExtensionVersion() in %s",
287 apr_dso_unload(isa->handle);
292 rv = apr_dso_sym((void**)&isa->HttpExtensionProc, isa->handle,
293 "HttpExtensionProc");
296 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
297 "ISAPI: missing HttpExtensionProc() in %s",
299 apr_dso_unload(isa->handle);
304 /* TerminateExtension() is an optional interface */
305 rv = apr_dso_sym((void**)&isa->TerminateExtension, isa->handle,
306 "TerminateExtension");
309 /* Run GetExtensionVersion() */
310 if (!(isa->GetExtensionVersion)(isa->isapi_version)) {
311 apr_status_t rv = apr_get_os_error();
312 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
313 "ISAPI: failed call to GetExtensionVersion() in %s",
315 apr_dso_unload(isa->handle);
320 apr_pool_cleanup_register(p, isa, cleanup_isapi,
321 apr_pool_cleanup_null);
326 apr_status_t isapi_lookup(apr_pool_t *p, server_rec *s, request_rec *r,
327 const char *fpath, isapi_loaded** isa)
332 if ((rv = apr_thread_mutex_lock(loaded.lock)) != APR_SUCCESS) {
336 *isa = apr_hash_get(loaded.hash, fpath, APR_HASH_KEY_STRING);
340 /* If we find this lock exists, use a set-aside copy of gainlock
341 * to avoid race conditions on NULLing the in_progress variable
342 * when the load has completed. Release the global isapi hash
343 * lock so other requests can proceed, then rdlock for completion
344 * of loading our desired dll or wrlock if we would like to retry
345 * loading the dll (because last_load_rv failed and retry is up.)
347 apr_thread_rwlock_t *gainlock = (*isa)->in_progress;
349 /* gainlock is NULLed after the module loads successfully.
350 * This free-threaded module can be used without any locking.
353 rv = (*isa)->last_load_rv;
354 apr_thread_mutex_unlock(loaded.lock);
359 if ((*isa)->last_load_rv == APR_SUCCESS) {
360 apr_thread_mutex_unlock(loaded.lock);
361 if ((rv = apr_thread_rwlock_rdlock(gainlock))
365 rv = (*isa)->last_load_rv;
366 apr_thread_rwlock_unlock(gainlock);
370 if (apr_time_now() > (*isa)->last_load_time + ISAPI_RETRY) {
372 /* Remember last_load_time before releasing the global
373 * hash lock to avoid colliding with another thread
374 * that hit this exception at the same time as our
375 * retry attempt, since we unlock the global mutex
376 * before attempting a write lock for this module.
378 apr_time_t check_time = (*isa)->last_load_time;
379 apr_thread_mutex_unlock(loaded.lock);
381 if ((rv = apr_thread_rwlock_wrlock(gainlock))
386 /* If last_load_time is unchanged, we still own this
387 * retry, otherwise presume another thread provided
388 * our retry (for good or ill). Relock the global
389 * hash for updating last_load_ vars, so their update
390 * is always atomic to the global lock.
392 if (check_time == (*isa)->last_load_time) {
394 rv = isapi_load(loaded.pool, s, *isa);
396 apr_thread_mutex_lock(loaded.lock);
397 (*isa)->last_load_rv = rv;
398 (*isa)->last_load_time = apr_time_now();
399 apr_thread_mutex_unlock(loaded.lock);
402 rv = (*isa)->last_load_rv;
404 apr_thread_rwlock_unlock(gainlock);
409 /* We haven't hit timeup on retry, let's grab the last_rv
410 * within the hash mutex before unlocking.
412 rv = (*isa)->last_load_rv;
413 apr_thread_mutex_unlock(loaded.lock);
418 /* If the module was not found, it's time to create a hash key entry
419 * before releasing the hash lock to avoid multiple threads from
420 * loading the same module.
422 key = apr_pstrdup(loaded.pool, fpath);
423 *isa = apr_pcalloc(loaded.pool, sizeof(isapi_loaded));
424 (*isa)->filename = key;
426 /* A mutex that exists only long enough to attempt to
427 * load this isapi dll, the release this module to all
428 * other takers that came along during the one-time
429 * load process. Short lifetime for this lock would
430 * be great, however, using r->pool is nasty if those
431 * blocked on the lock haven't all unlocked before we
432 * attempt to destroy. A nastier race condition than
433 * I want to deal with at this moment...
435 apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
436 apr_thread_rwlock_wrlock((*isa)->in_progress);
439 apr_hash_set(loaded.hash, key, APR_HASH_KEY_STRING, *isa);
441 /* Now attempt to load the isapi on our own time,
442 * allow other isapi processing to resume.
444 apr_thread_mutex_unlock(loaded.lock);
446 rv = isapi_load(loaded.pool, s, *isa);
447 (*isa)->last_load_time = apr_time_now();
448 (*isa)->last_load_rv = rv;
450 if (r && (rv == APR_SUCCESS)) {
451 /* Let others who are blocked on this particular
452 * module resume their requests, for better or worse.
454 apr_thread_rwlock_t *unlock = (*isa)->in_progress;
455 (*isa)->in_progress = NULL;
456 apr_thread_rwlock_unlock(unlock);
458 else if (!r && (rv != APR_SUCCESS)) {
459 /* We must leave a rwlock around for requests to retry
460 * loading this dll after timeup... since we were in
461 * the setup code we had avoided creating this lock.
463 apr_thread_rwlock_create(&(*isa)->in_progress, loaded.pool);
466 return (*isa)->last_load_rv;
469 /**********************************************************
471 * ISAPI Module request callbacks section
473 **********************************************************/
475 /* Our "Connection ID" structure */
477 EXTENSION_CONTROL_BLOCK *ecb;
478 isapi_dir_conf dconf;
483 PFN_HSE_IO_COMPLETION completion;
484 void *completion_arg;
485 apr_thread_mutex_t *completed;
488 int APR_THREAD_FUNC GetServerVariable (isapi_cid *cid,
491 apr_uint32_t *buf_size)
493 request_rec *r = cid->r;
495 char *buf_data = (char*)buf_ptr;
498 if (!strcmp(variable_name, "ALL_HTTP"))
500 /* crlf delimited, colon split, comma separated and
501 * null terminated list of HTTP_ vars
503 const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
504 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
507 for (len = 0, i = 0; i < arr->nelts; i++) {
508 if (!strncmp(elts[i].key, "HTTP_", 5)) {
509 len += strlen(elts[i].key) + strlen(elts[i].val) + 3;
513 if (*buf_size < len + 1) {
515 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
519 for (i = 0; i < arr->nelts; i++) {
520 if (!strncmp(elts[i].key, "HTTP_", 5)) {
521 strcpy(buf_data, elts[i].key);
522 buf_data += strlen(elts[i].key);
524 strcpy(buf_data, elts[i].val);
525 buf_data += strlen(elts[i].val);
526 *(buf_data++) = '\r';
527 *(buf_data++) = '\n';
531 *(buf_data++) = '\0';
536 if (!strcmp(variable_name, "ALL_RAW"))
538 /* crlf delimited, colon split, comma separated and
539 * null terminated list of the raw request header
541 const apr_array_header_t *arr = apr_table_elts(r->headers_in);
542 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
545 for (len = 0, i = 0; i < arr->nelts; i++) {
546 len += strlen(elts[i].key) + strlen(elts[i].val) + 4;
549 if (*buf_size < len + 1) {
551 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
555 for (i = 0; i < arr->nelts; i++) {
556 strcpy(buf_data, elts[i].key);
557 buf_data += strlen(elts[i].key);
560 strcpy(buf_data, elts[i].val);
561 buf_data += strlen(elts[i].val);
562 *(buf_data++) = '\r';
563 *(buf_data++) = '\n';
565 *(buf_data++) = '\0';
570 /* Not a special case */
571 result = apr_table_get(r->subprocess_env, variable_name);
574 len = strlen(result);
575 if (*buf_size < len + 1) {
577 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INSUFFICIENT_BUFFER));
580 strcpy(buf_data, result);
586 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_INDEX));
590 int APR_THREAD_FUNC ReadClient(isapi_cid *cid,
592 apr_uint32_t *buf_size)
594 request_rec *r = cid->r;
595 apr_uint32_t read = 0;
598 if (r->remaining < *buf_size) {
599 *buf_size = (apr_size_t)r->remaining;
602 while (read < *buf_size &&
603 ((res = ap_get_client_block(r, (char*)buf_data + read,
604 *buf_size - read)) > 0)) {
610 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_READ_FAULT));
615 /* Common code invoked for both HSE_REQ_SEND_RESPONSE_HEADER and
616 * the newer HSE_REQ_SEND_RESPONSE_HEADER_EX ServerSupportFunction(s)
617 * as well as other functions that write responses and presume that
618 * the support functions above are optional.
620 * Other callers trying to split headers and body bytes should pass
621 * head/headlen alone (leaving stat/statlen NULL/0), so that they
622 * get a proper count of bytes consumed. The argument passed to stat
623 * isn't counted as the head bytes are.
625 static apr_ssize_t send_response_header(isapi_cid *cid,
631 int head_present = 1;
638 if (!head || headlen == 0 || !*head) {
643 head_present = 0; /* Don't eat the header */
646 if (!stat || statlen == 0 || !*stat) {
647 if (head && headlen && *head && ((stat = memchr(head, '\r', headlen))
648 || (stat = memchr(head, '\n', headlen))
649 || (stat = memchr(head, '\0', headlen))
650 || (stat = head + headlen))) {
651 statlen = stat - head;
652 if (memchr(head, ':', statlen)) {
653 stat = "Status: 200 OK";
654 statlen = strlen(stat);
657 const char *flip = head;
662 if (*head == '\r' && headlen)
663 ++head, --headlen, ++ate;
664 if (*head == '\n' && headlen)
665 ++head, --headlen, ++ate;
670 if (stat && (statlen > 0) && *stat) {
672 if (!apr_isdigit(*stat)) {
673 const char *stattok = stat;
674 int toklen = statlen;
675 while (toklen && *stattok && !apr_isspace(*stattok)) {
678 while (toklen && apr_isspace(*stattok)) {
681 /* Now decide if we follow the xxx message
682 * or the http/x.x xxx message format
684 if (toklen && apr_isdigit(*stattok)) {
689 newstat = apr_palloc(cid->r->pool, statlen + 9);
690 strcpy(newstat, "Status: ");
691 apr_cpystrn(newstat + 8, stat, statlen + 1);
696 if (!head || headlen == 0 || !*head) {
702 if (head[headlen - 1] && head[headlen]) {
703 /* Whoops... not NULL terminated */
704 head = apr_pstrndup(cid->r->pool, head, headlen);
708 /* Seems IIS does not enforce the requirement for \r\n termination
709 * on HSE_REQ_SEND_RESPONSE_HEADER, but we won't panic...
710 * ap_scan_script_header_err_strs handles this aspect for us.
712 * Parse them out, or die trying
714 old_status = cid->r->status;
717 res = ap_scan_script_header_err_strs(cid->r, NULL, &termch, &termarg,
721 res = ap_scan_script_header_err_strs(cid->r, NULL, &termch, &termarg,
725 /* Set our status. */
727 /* This is an immediate error result from the parser
729 cid->r->status = res;
730 cid->r->status_line = ap_get_status_line(cid->r->status);
731 cid->ecb->dwHttpStatusCode = cid->r->status;
733 else if (cid->r->status) {
734 /* We have a status in r->status, so let's just use it.
735 * This is likely to be the Status: parsed above, and
736 * may also be a delayed error result from the parser.
737 * If it was filled in, status_line should also have
740 cid->ecb->dwHttpStatusCode = cid->r->status;
742 else if (cid->ecb->dwHttpStatusCode
743 && cid->ecb->dwHttpStatusCode != HTTP_OK) {
744 /* Now we fall back on dwHttpStatusCode if it appears
745 * ap_scan_script_header fell back on the default code.
746 * Any other results set dwHttpStatusCode to the decoded
749 cid->r->status = cid->ecb->dwHttpStatusCode;
750 cid->r->status_line = ap_get_status_line(cid->r->status);
752 else if (old_status) {
753 /* Well... either there is no dwHttpStatusCode or it's HTTP_OK.
754 * In any case, we don't have a good status to return yet...
755 * Perhaps the one we came in with will be better. Let's use it,
756 * if we were given one (note this is a pendantic case, it would
757 * normally be covered above unless the scan script code unset
758 * the r->status). Should there be a check here as to whether
759 * we are setting a valid response code?
761 cid->r->status = old_status;
762 cid->r->status_line = ap_get_status_line(cid->r->status);
763 cid->ecb->dwHttpStatusCode = cid->r->status;
766 /* None of dwHttpStatusCode, the parser's r->status nor the
767 * old value of r->status were helpful, and nothing was decoded
768 * from Status: string passed to us. Let's just say HTTP_OK
769 * and get the data out, this was the isapi dev's oversight.
771 cid->r->status = HTTP_OK;
772 cid->r->status_line = ap_get_status_line(cid->r->status);
773 cid->ecb->dwHttpStatusCode = cid->r->status;
774 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, cid->r,
775 "ISAPI: Could not determine HTTP response code; using %d",
779 if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR) {
783 /* If only Status was passed, we consumed nothing
788 cid->headers_set = 1;
790 /* If all went well, tell the caller we consumed the headers complete
793 return(ate + headlen);
795 /* Any data left must be sent directly by the caller, all we
796 * give back is the size of the headers we consumed (which only
797 * happens if the parser got to the head arg, which varies based
798 * on whether we passed stat+head to scan, or only head.
800 if (termch && (termarg == (stat ? 1 : 0))
801 && head_present && head + headlen > termch) {
802 return ate + termch - head;
807 int APR_THREAD_FUNC WriteClient(isapi_cid *cid,
809 apr_uint32_t *size_arg,
812 request_rec *r = cid->r;
813 conn_rec *c = r->connection;
814 apr_uint32_t buf_size = *size_arg;
815 char *buf_data = (char*)buf_ptr;
816 apr_bucket_brigade *bb;
818 apr_status_t rv = APR_SUCCESS;
820 if (!cid->headers_set) {
821 /* It appears that the foxisapi module and other clients
822 * presume that WriteClient("headers\n\nbody") will work.
823 * Parse them out, or die trying.
826 ate = send_response_header(cid, NULL, buf_data, 0, buf_size);
828 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
837 bb = apr_brigade_create(r->pool, c->bucket_alloc);
838 b = apr_bucket_transient_create(buf_data, buf_size, c->bucket_alloc);
839 APR_BRIGADE_INSERT_TAIL(bb, b);
840 b = apr_bucket_flush_create(c->bucket_alloc);
841 APR_BRIGADE_INSERT_TAIL(bb, b);
842 rv = ap_pass_brigade(r->output_filters, bb);
843 cid->response_sent = 1;
844 if (rv != APR_SUCCESS)
845 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
846 "ISAPI: WriteClient ap_pass_brigade "
847 "failed: %s", r->filename);
850 if ((flags & HSE_IO_ASYNC) && cid->completion) {
851 if (rv == APR_SUCCESS) {
852 cid->completion(cid->ecb, cid->completion_arg,
853 *size_arg, ERROR_SUCCESS);
856 cid->completion(cid->ecb, cid->completion_arg,
857 *size_arg, ERROR_WRITE_FAULT);
860 return (rv == APR_SUCCESS);
863 /* A "safe" maximum bucket size, 1Gb */
864 #define MAX_BUCKET_SIZE (0x40000000)
866 apr_bucket *brigade_insert_file(apr_bucket_brigade *bb,
874 if (sizeof(apr_off_t) == sizeof(apr_size_t) || length < MAX_BUCKET_SIZE) {
875 e = apr_bucket_file_create(f, start, (apr_size_t)length, p,
879 /* Several buckets are needed. */
880 e = apr_bucket_file_create(f, start, MAX_BUCKET_SIZE, p,
883 while (length > MAX_BUCKET_SIZE) {
885 apr_bucket_copy(e, &ce);
886 APR_BRIGADE_INSERT_TAIL(bb, ce);
887 e->start += MAX_BUCKET_SIZE;
888 length -= MAX_BUCKET_SIZE;
890 e->length = (apr_size_t)length; /* Resize just the last bucket */
893 APR_BRIGADE_INSERT_TAIL(bb, e);
897 int APR_THREAD_FUNC ServerSupportFunction(isapi_cid *cid,
898 apr_uint32_t HSE_code,
900 apr_uint32_t *buf_size,
901 apr_uint32_t *data_type)
903 request_rec *r = cid->r;
904 conn_rec *c = r->connection;
905 char *buf_data = (char*)buf_ptr;
910 case HSE_REQ_SEND_URL_REDIRECT_RESP:
911 /* Set the status to be returned when the HttpExtensionProc()
913 * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
914 * and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK.
915 * They most definately are not, even in their own samples.
917 apr_table_set (r->headers_out, "Location", buf_data);
918 cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY;
919 cid->r->status_line = ap_get_status_line(cid->r->status);
920 cid->headers_set = 1;
923 case HSE_REQ_SEND_URL:
924 /* Soak up remaining input */
925 if (r->remaining > 0) {
926 char argsbuffer[HUGE_STRING_LEN];
927 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
930 /* Reset the method to GET */
931 r->method = apr_pstrdup(r->pool, "GET");
932 r->method_number = M_GET;
934 /* Don't let anyone think there's still data */
935 apr_table_unset(r->headers_in, "Content-Length");
937 /* AV fault per PR3598 - redirected path is lost! */
938 buf_data = apr_pstrdup(r->pool, (char*)buf_data);
939 ap_internal_redirect(buf_data, r);
942 case HSE_REQ_SEND_RESPONSE_HEADER:
944 /* Parse them out, or die trying */
945 apr_size_t statlen = 0, headlen = 0;
948 statlen = strlen((char*) buf_data);
950 headlen = strlen((char*) data_type);
951 ate = send_response_header(cid, (char*) buf_data,
955 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
958 else if ((apr_size_t)ate < headlen) {
959 apr_bucket_brigade *bb;
961 bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
962 b = apr_bucket_transient_create((char*) data_type + ate,
963 headlen - ate, c->bucket_alloc);
964 APR_BRIGADE_INSERT_TAIL(bb, b);
965 b = apr_bucket_flush_create(c->bucket_alloc);
966 APR_BRIGADE_INSERT_TAIL(bb, b);
967 rv = ap_pass_brigade(cid->r->output_filters, bb);
968 cid->response_sent = 1;
969 if (rv != APR_SUCCESS)
970 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
971 "ISAPI: ServerSupport function "
972 "HSE_REQ_SEND_RESPONSE_HEADER "
973 "ap_pass_brigade failed: %s", r->filename);
974 return (rv == APR_SUCCESS);
976 /* Deliberately hold off sending 'just the headers' to begin to
977 * accumulate the body and speed up the overall response, or at
978 * least wait for the end the session.
983 case HSE_REQ_DONE_WITH_SESSION:
984 /* Signal to resume the thread completing this request,
985 * leave it to the pool cleanup to dispose of our mutex.
987 if (cid->completed) {
988 (void)apr_thread_mutex_unlock(cid->completed);
991 else if (cid->dconf.log_unsupported) {
992 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
993 "ISAPI: ServerSupportFunction "
994 "HSE_REQ_DONE_WITH_SESSION is not supported: %s",
997 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1000 case HSE_REQ_MAP_URL_TO_PATH:
1002 /* Map a URL to a filename */
1003 char *file = (char *)buf_data;
1005 subreq = ap_sub_req_lookup_uri(
1006 apr_pstrndup(cid->r->pool, file, *buf_size), r, NULL);
1008 if (!subreq->filename) {
1009 ap_destroy_sub_req(subreq);
1013 len = (apr_uint32_t)strlen(r->filename);
1015 if ((subreq->finfo.filetype == APR_DIR)
1016 && (!subreq->path_info)
1017 && (file[len - 1] != '/'))
1018 file = apr_pstrcat(cid->r->pool, subreq->filename, "/", NULL);
1020 file = apr_pstrcat(cid->r->pool, subreq->filename,
1021 subreq->path_info, NULL);
1023 ap_destroy_sub_req(subreq);
1026 /* We need to make this a real Windows path name */
1027 apr_filepath_merge(&file, "", file, APR_FILEPATH_NATIVE, r->pool);
1030 *buf_size = apr_cpystrn(buf_data, file, *buf_size) - buf_data;
1035 case HSE_REQ_GET_SSPI_INFO:
1036 if (cid->dconf.log_unsupported)
1037 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1038 "ISAPI: ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
1039 "is not supported: %s", r->filename);
1040 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1043 case HSE_APPEND_LOG_PARAMETER:
1044 /* Log buf_data, of buf_size bytes, in the URI Query (cs-uri-query) field
1046 apr_table_set(r->notes, "isapi-parameter", (char*) buf_data);
1047 if (cid->dconf.log_to_query) {
1049 r->args = apr_pstrcat(r->pool, r->args, (char*) buf_data, NULL);
1051 r->args = apr_pstrdup(r->pool, (char*) buf_data);
1053 if (cid->dconf.log_to_errlog)
1054 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
1055 "ISAPI: %s: %s", cid->r->filename,
1059 case HSE_REQ_IO_COMPLETION:
1060 /* Emulates a completion port... Record callback address and
1061 * user defined arg, we will call this after any async request
1062 * (e.g. transmitfile) as if the request executed async.
1063 * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
1064 * to HSE_REQ_IO_COMPLETION, and buf_data may be set to NULL.
1066 if (cid->dconf.fake_async) {
1067 cid->completion = (PFN_HSE_IO_COMPLETION) buf_data;
1068 cid->completion_arg = (void *) data_type;
1071 if (cid->dconf.log_unsupported)
1072 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1073 "ISAPI: ServerSupportFunction HSE_REQ_IO_COMPLETION "
1074 "is not supported: %s", r->filename);
1075 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1078 case HSE_REQ_TRANSMIT_FILE:
1080 /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
1082 HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data;
1083 apr_uint32_t sent = 0;
1084 apr_ssize_t ate = 0;
1085 apr_bucket_brigade *bb;
1090 if (!cid->dconf.fake_async && (tf->dwFlags & HSE_IO_ASYNC)) {
1091 if (cid->dconf.log_unsupported)
1092 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1093 "ISAPI: ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
1094 "as HSE_IO_ASYNC is not supported: %s", r->filename);
1095 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1099 /* Presume the handle was opened with the CORRECT semantics
1102 if ((rv = apr_os_file_put(&fd, &tf->hFile,
1103 APR_READ | APR_XTHREAD, r->pool))
1107 if (tf->BytesToWrite) {
1108 fsize = tf->BytesToWrite;
1112 if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
1113 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1116 fsize = fi.size - tf->Offset;
1119 /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
1120 bb = apr_brigade_create(r->pool, c->bucket_alloc);
1122 /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
1123 * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
1124 * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
1125 * you must have done so. They document that the pHead headers
1126 * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
1127 * more flexible and assume with the flag, pHead are the
1128 * response headers, and without, pHead simply contains text
1129 * (handled after this case).
1131 if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) {
1132 ate = send_response_header(cid, tf->pszStatusCode,
1134 strlen(tf->pszStatusCode),
1137 else if (!cid->headers_set && tf->pHead && tf->HeadLength
1138 && *(char*)tf->pHead) {
1139 ate = send_response_header(cid, NULL, (char*)tf->pHead,
1143 apr_brigade_destroy(bb);
1144 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1149 if (tf->pHead && (apr_size_t)ate < tf->HeadLength) {
1150 b = apr_bucket_transient_create((char*)tf->pHead + ate,
1151 tf->HeadLength - ate,
1153 APR_BRIGADE_INSERT_TAIL(bb, b);
1154 sent = tf->HeadLength;
1157 sent += (apr_uint32_t)fsize;
1158 brigade_insert_file(bb, fd, tf->Offset, fsize, r->pool);
1160 if (tf->pTail && tf->TailLength) {
1161 sent += tf->TailLength;
1162 b = apr_bucket_transient_create((char*)tf->pTail,
1163 tf->TailLength, c->bucket_alloc);
1164 APR_BRIGADE_INSERT_TAIL(bb, b);
1167 b = apr_bucket_flush_create(c->bucket_alloc);
1168 APR_BRIGADE_INSERT_TAIL(bb, b);
1169 rv = ap_pass_brigade(r->output_filters, bb);
1170 cid->response_sent = 1;
1171 if (rv != APR_SUCCESS)
1172 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1173 "ISAPI: ServerSupport function "
1174 "HSE_REQ_TRANSMIT_FILE "
1175 "ap_pass_brigade failed: %s", r->filename);
1177 /* Use tf->pfnHseIO + tf->pContext, or if NULL, then use cid->fnIOComplete
1178 * pass pContect to the HseIO callback.
1180 if (tf->dwFlags & HSE_IO_ASYNC) {
1182 if (rv == APR_SUCCESS) {
1183 tf->pfnHseIO(cid->ecb, tf->pContext,
1184 ERROR_SUCCESS, sent);
1187 tf->pfnHseIO(cid->ecb, tf->pContext,
1188 ERROR_WRITE_FAULT, sent);
1191 else if (cid->completion) {
1192 if (rv == APR_SUCCESS) {
1193 cid->completion(cid->ecb, cid->completion_arg,
1194 sent, ERROR_SUCCESS);
1197 cid->completion(cid->ecb, cid->completion_arg,
1198 sent, ERROR_WRITE_FAULT);
1202 return (rv == APR_SUCCESS);
1205 case HSE_REQ_REFRESH_ISAPI_ACL:
1206 if (cid->dconf.log_unsupported)
1207 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1208 "ISAPI: ServerSupportFunction "
1209 "HSE_REQ_REFRESH_ISAPI_ACL "
1210 "is not supported: %s", r->filename);
1211 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1214 case HSE_REQ_IS_KEEP_CONN:
1215 *((int *)buf_data) = (r->connection->keepalive == AP_CONN_KEEPALIVE);
1218 case HSE_REQ_ASYNC_READ_CLIENT:
1220 apr_uint32_t read = 0;
1222 if (!cid->dconf.fake_async) {
1223 if (cid->dconf.log_unsupported)
1224 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1225 "ISAPI: asynchronous I/O not supported: %s",
1227 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1231 if (r->remaining < *buf_size) {
1232 *buf_size = (apr_size_t)r->remaining;
1235 while (read < *buf_size &&
1236 ((res = ap_get_client_block(r, (char*)buf_data + read,
1237 *buf_size - read)) > 0)) {
1241 if ((*data_type & HSE_IO_ASYNC) && cid->completion) {
1242 /* XXX: Many authors issue their next HSE_REQ_ASYNC_READ_CLIENT
1243 * within the completion logic. An example is MS's own PSDK
1244 * sample web/iis/extensions/io/ASyncRead. This potentially
1245 * leads to stack exhaustion. To refactor, the notification
1246 * logic needs to move to isapi_handler() - differentiating
1247 * the cid->completed event with a new flag to indicate
1248 * an async-notice versus the async request completed.
1251 cid->completion(cid->ecb, cid->completion_arg,
1252 read, ERROR_SUCCESS);
1255 cid->completion(cid->ecb, cid->completion_arg,
1256 read, ERROR_READ_FAULT);
1262 case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */
1263 if (cid->dconf.log_unsupported)
1264 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1265 "ISAPI: ServerSupportFunction "
1266 "HSE_REQ_GET_IMPERSONATION_TOKEN "
1267 "is not supported: %s", r->filename);
1268 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1271 case HSE_REQ_MAP_URL_TO_PATH_EX:
1273 /* Map a URL to a filename */
1274 HSE_URL_MAPEX_INFO *info = (HSE_URL_MAPEX_INFO*)data_type;
1275 char* test_uri = apr_pstrndup(r->pool, (char *)buf_data, *buf_size);
1277 subreq = ap_sub_req_lookup_uri(test_uri, r, NULL);
1278 info->cchMatchingURL = strlen(test_uri);
1279 info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename,
1280 sizeof(info->lpszPath)) - info->lpszPath;
1282 /* Mapping started with assuming both strings matched.
1283 * Now roll on the path_info as a mismatch and handle
1284 * terminating slashes for directory matches.
1286 if (subreq->path_info && *subreq->path_info) {
1287 apr_cpystrn(info->lpszPath + info->cchMatchingPath,
1289 sizeof(info->lpszPath) - info->cchMatchingPath);
1290 info->cchMatchingURL -= strlen(subreq->path_info);
1291 if (subreq->finfo.filetype == APR_DIR
1292 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
1293 /* roll forward over path_info's first slash */
1294 ++info->cchMatchingPath;
1295 ++info->cchMatchingURL;
1298 else if (subreq->finfo.filetype == APR_DIR
1299 && info->cchMatchingPath < sizeof(info->lpszPath) - 1) {
1300 /* Add a trailing slash for directory */
1301 info->lpszPath[info->cchMatchingPath++] = '/';
1302 info->lpszPath[info->cchMatchingPath] = '\0';
1305 /* If the matched isn't a file, roll match back to the prior slash */
1306 if (subreq->finfo.filetype == APR_NOFILE) {
1307 while (info->cchMatchingPath && info->cchMatchingURL) {
1308 if (info->lpszPath[info->cchMatchingPath - 1] == '/')
1310 --info->cchMatchingPath;
1311 --info->cchMatchingURL;
1315 /* Paths returned with back slashes */
1316 for (test_uri = info->lpszPath; *test_uri; ++test_uri)
1317 if (*test_uri == '/')
1320 /* is a combination of:
1321 * HSE_URL_FLAGS_READ 0x001 Allow read
1322 * HSE_URL_FLAGS_WRITE 0x002 Allow write
1323 * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute
1324 * HSE_URL_FLAGS_SSL 0x008 Require SSL
1325 * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only)
1326 * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert
1327 * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
1328 * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account
1329 * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert
1330 * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution
1332 * XxX: As everywhere, EXEC flags could use some work...
1333 * and this could go further with more flags, as desired.
1335 info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0)
1336 | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0)
1337 | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
1341 case HSE_REQ_ABORTIVE_CLOSE:
1342 if (cid->dconf.log_unsupported)
1343 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1344 "ISAPI: ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
1345 " is not supported: %s", r->filename);
1346 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1349 case HSE_REQ_GET_CERT_INFO_EX: /* Added in ISAPI 4.0 */
1350 if (cid->dconf.log_unsupported)
1351 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1352 "ISAPI: ServerSupportFunction "
1353 "HSE_REQ_GET_CERT_INFO_EX "
1354 "is not supported: %s", r->filename);
1355 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1358 case HSE_REQ_SEND_RESPONSE_HEADER_EX: /* Added in ISAPI 4.0 */
1360 HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data;
1362 /* Ignore shi->fKeepConn - we don't want the advise
1364 apr_ssize_t ate = send_response_header(cid, shi->pszStatus,
1369 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1372 else if ((apr_size_t)ate < shi->cchHeader) {
1373 apr_bucket_brigade *bb;
1375 bb = apr_brigade_create(cid->r->pool, c->bucket_alloc);
1376 b = apr_bucket_transient_create(shi->pszHeader + ate,
1377 shi->cchHeader - ate,
1379 APR_BRIGADE_INSERT_TAIL(bb, b);
1380 b = apr_bucket_flush_create(c->bucket_alloc);
1381 APR_BRIGADE_INSERT_TAIL(bb, b);
1382 rv = ap_pass_brigade(cid->r->output_filters, bb);
1383 cid->response_sent = 1;
1384 if (rv != APR_SUCCESS)
1385 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1386 "ISAPI: ServerSupport function "
1387 "HSE_REQ_SEND_RESPONSE_HEADER_EX "
1388 "ap_pass_brigade failed: %s", r->filename);
1389 return (rv == APR_SUCCESS);
1391 /* Deliberately hold off sending 'just the headers' to begin to
1392 * accumulate the body and speed up the overall response, or at
1393 * least wait for the end the session.
1398 case HSE_REQ_CLOSE_CONNECTION: /* Added after ISAPI 4.0 */
1399 if (cid->dconf.log_unsupported)
1400 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1401 "ISAPI: ServerSupportFunction "
1402 "HSE_REQ_CLOSE_CONNECTION "
1403 "is not supported: %s", r->filename);
1404 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1407 case HSE_REQ_IS_CONNECTED: /* Added after ISAPI 4.0 */
1408 /* Returns True if client is connected c.f. MSKB Q188346
1409 * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN
1411 *((int *)buf_data) = (r->connection->aborted == 0);
1414 case HSE_REQ_EXTENSION_TRIGGER: /* Added after ISAPI 4.0 */
1415 /* Undocumented - defined by the Microsoft Jan '00 Platform SDK
1417 if (cid->dconf.log_unsupported)
1418 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1419 "ISAPI: ServerSupportFunction "
1420 "HSE_REQ_EXTENSION_TRIGGER "
1421 "is not supported: %s", r->filename);
1422 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1426 if (cid->dconf.log_unsupported)
1427 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1428 "ISAPI: ServerSupportFunction (%d) not supported: "
1429 "%s", HSE_code, r->filename);
1430 apr_set_os_error(APR_FROM_OS_ERROR(ERROR_INVALID_PARAMETER));
1435 /**********************************************************
1437 * ISAPI Module request invocation section
1439 **********************************************************/
1441 apr_status_t isapi_handler (request_rec *r)
1443 isapi_dir_conf *dconf;
1452 if(strcmp(r->handler, "isapi-isa")
1453 && strcmp(r->handler, "isapi-handler")) {
1454 /* Hang on to the isapi-isa for compatibility with older docs
1455 * (wtf did '-isa' mean in the first place?) but introduce
1456 * a newer and clearer "isapi-handler" name.
1460 dconf = ap_get_module_config(r->per_dir_config, &isapi_module);
1461 e = r->subprocess_env;
1463 /* Use similar restrictions as CGIs
1465 * If this fails, it's pointless to load the isapi dll.
1467 if (!(ap_allow_options(r) & OPT_EXECCGI)) {
1468 return HTTP_FORBIDDEN;
1470 if (r->finfo.filetype == APR_NOFILE) {
1471 return HTTP_NOT_FOUND;
1473 if (r->finfo.filetype != APR_REG) {
1474 return HTTP_FORBIDDEN;
1476 if ((r->used_path_info == AP_REQ_REJECT_PATH_INFO) &&
1477 r->path_info && *r->path_info) {
1478 /* default to accept */
1479 return HTTP_NOT_FOUND;
1482 if (isapi_lookup(r->pool, r->server, r, r->filename, &isa)
1484 return HTTP_INTERNAL_SERVER_ERROR;
1486 /* Set up variables */
1487 ap_add_common_vars(r);
1489 apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
1490 if ((val = apr_table_get(e, "HTTPS")) && (strcmp(val, "on") == 0))
1491 apr_table_setn(e, "SERVER_PORT_SECURE", "1");
1493 apr_table_setn(e, "SERVER_PORT_SECURE", "0");
1494 apr_table_setn(e, "URL", r->uri);
1496 /* Set up connection structure and ecb,
1497 * NULL or zero out most fields.
1499 cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
1501 /* Fixup defaults for dconf */
1502 cid->dconf.read_ahead_buflen = (dconf->read_ahead_buflen == ISAPI_UNDEF)
1503 ? 49152 : dconf->read_ahead_buflen;
1504 cid->dconf.log_unsupported = (dconf->log_unsupported == ISAPI_UNDEF)
1505 ? 0 : dconf->log_unsupported;
1506 cid->dconf.log_to_errlog = (dconf->log_to_errlog == ISAPI_UNDEF)
1507 ? 0 : dconf->log_to_errlog;
1508 cid->dconf.log_to_query = (dconf->log_to_query == ISAPI_UNDEF)
1509 ? 1 : dconf->log_to_query;
1510 cid->dconf.fake_async = (dconf->fake_async == ISAPI_UNDEF)
1511 ? 0 : dconf->fake_async;
1513 cid->ecb = apr_pcalloc(r->pool, sizeof(EXTENSION_CONTROL_BLOCK));
1514 cid->ecb->ConnID = cid;
1519 cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
1520 cid->ecb->dwVersion = isa->report_version;
1521 cid->ecb->dwHttpStatusCode = 0;
1522 strcpy(cid->ecb->lpszLogData, "");
1523 /* TODO: are copies really needed here?
1525 cid->ecb->lpszMethod = (char*) r->method;
1526 cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING");
1527 cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO");
1528 cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED");
1529 cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE");
1531 /* Set up the callbacks */
1532 cid->ecb->GetServerVariable = GetServerVariable;
1533 cid->ecb->WriteClient = WriteClient;
1534 cid->ecb->ReadClient = ReadClient;
1535 cid->ecb->ServerSupportFunction = ServerSupportFunction;
1537 /* Set up client input */
1538 res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
1543 if (ap_should_client_block(r)) {
1544 /* Time to start reading the appropriate amount of data,
1545 * and allow the administrator to tweak the number
1548 cid->ecb->cbTotalBytes = (apr_size_t)r->remaining;
1549 if (cid->ecb->cbTotalBytes > (apr_uint32_t)cid->dconf.read_ahead_buflen)
1550 cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
1552 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
1556 cid->ecb->cbTotalBytes = 0xffffffff;
1557 cid->ecb->cbAvailable = cid->dconf.read_ahead_buflen;
1560 cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
1563 while (read < cid->ecb->cbAvailable &&
1564 ((res = ap_get_client_block(r, (char*)cid->ecb->lpbData + read,
1565 cid->ecb->cbAvailable - read)) > 0)) {
1570 return HTTP_INTERNAL_SERVER_ERROR;
1573 /* Although it's not to spec, IIS seems to null-terminate
1574 * its lpdData string. So we will too.
1577 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
1579 cid->ecb->cbAvailable = read;
1580 cid->ecb->lpbData[read] = '\0';
1583 cid->ecb->cbTotalBytes = 0;
1584 cid->ecb->cbAvailable = 0;
1585 cid->ecb->lpbData = NULL;
1588 /* To emulate async behavior...
1590 * We create a cid->completed mutex and lock on it so that the
1591 * app can believe is it running async.
1593 * This request completes upon a notification through
1594 * ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION), which
1595 * unlocks this mutex. If the HttpExtensionProc() returns
1596 * HSE_STATUS_PENDING, we will attempt to gain this lock again
1597 * which may *only* happen once HSE_REQ_DONE_WITH_SESSION has
1598 * unlocked the mutex.
1600 if (cid->dconf.fake_async) {
1601 rv = apr_thread_mutex_create(&cid->completed,
1602 APR_THREAD_MUTEX_UNNESTED,
1604 if (cid->completed && (rv == APR_SUCCESS)) {
1605 rv = apr_thread_mutex_lock(cid->completed);
1608 if (!cid->completed || (rv != APR_SUCCESS)) {
1609 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1610 "ISAPI: Failed to create completion mutex");
1611 return HTTP_INTERNAL_SERVER_ERROR;
1615 /* All right... try and run the sucker */
1616 rv = (*isa->HttpExtensionProc)(cid->ecb);
1618 /* Check for a log message - and log it */
1619 if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
1620 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
1621 "ISAPI: %s: %s", r->filename, cid->ecb->lpszLogData);
1624 case 0: /* Strange, but MS isapi accepts this as success */
1625 case HSE_STATUS_SUCCESS:
1626 case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
1627 /* Ignore the keepalive stuff; Apache handles it just fine without
1628 * the ISAPI Handler's "advice".
1629 * Per Microsoft: "In IIS versions 4.0 and later, the return
1630 * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
1631 * are functionally identical: Keep-Alive connections are
1632 * maintained, if supported by the client."
1633 * ... so we were pat all this time
1637 case HSE_STATUS_PENDING:
1638 /* emulating async behavior...
1640 if (cid->completed) {
1641 /* The completion port was locked prior to invoking
1642 * HttpExtensionProc(). Once we can regain the lock,
1643 * when ServerSupportFunction(HSE_REQ_DONE_WITH_SESSION)
1644 * is called by the extension to release the lock,
1645 * we may finally destroy the request.
1647 (void)apr_thread_mutex_lock(cid->completed);
1650 else if (cid->dconf.log_unsupported) {
1651 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
1652 "ISAPI: asynch I/O result HSE_STATUS_PENDING "
1653 "from HttpExtensionProc() is not supported: %s",
1655 r->status = HTTP_INTERNAL_SERVER_ERROR;
1659 case HSE_STATUS_ERROR:
1660 /* end response if we have yet to do so.
1662 ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r,
1663 "ISAPI: HSE_STATUS_ERROR result from "
1664 "HttpExtensionProc(): %s", r->filename);
1665 r->status = HTTP_INTERNAL_SERVER_ERROR;
1669 ap_log_rerror(APLOG_MARK, APLOG_WARNING, apr_get_os_error(), r,
1670 "ISAPI: unrecognized result code %d "
1671 "from HttpExtensionProc(): %s ",
1673 r->status = HTTP_INTERNAL_SERVER_ERROR;
1677 /* Flush the response now, including headers-only responses */
1678 if (cid->headers_set || cid->response_sent) {
1679 conn_rec *c = r->connection;
1680 apr_bucket_brigade *bb;
1684 bb = apr_brigade_create(r->pool, c->bucket_alloc);
1685 b = apr_bucket_eos_create(c->bucket_alloc);
1686 APR_BRIGADE_INSERT_TAIL(bb, b);
1687 rv = ap_pass_brigade(r->output_filters, bb);
1688 cid->response_sent = 1;
1690 if (rv != APR_SUCCESS) {
1691 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
1692 "ISAPI: ap_pass_brigade failed to "
1693 "complete the response: %s ", r->filename);
1696 return OK; /* NOT r->status, even if it has changed. */
1699 /* As the client returned no error, and if we did not error out
1700 * ourselves, trust dwHttpStatusCode to say something relevant.
1702 if (!ap_is_HTTP_SERVER_ERROR(r->status) && cid->ecb->dwHttpStatusCode) {
1703 r->status = cid->ecb->dwHttpStatusCode;
1706 /* For all missing-response situations simply return the status,
1707 * and let the core respond to the client.
1712 /**********************************************************
1714 * ISAPI Module Setup Hooks
1716 **********************************************************/
1718 static int isapi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp)
1722 apr_pool_create_ex(&loaded.pool, pconf, NULL, NULL);
1724 ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, NULL,
1725 "ISAPI: could not create the isapi cache pool");
1726 return APR_EGENERAL;
1729 loaded.hash = apr_hash_make(loaded.pool);
1731 ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
1732 "ISAPI: Failed to create module cache");
1733 return APR_EGENERAL;
1736 rv = apr_thread_mutex_create(&loaded.lock, APR_THREAD_MUTEX_DEFAULT,
1738 if (rv != APR_SUCCESS) {
1739 ap_log_error(APLOG_MARK, rv, 0, NULL,
1740 "ISAPI: Failed to create module cache lock");
1746 static void isapi_hooks(apr_pool_t *cont)
1748 ap_hook_pre_config(isapi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
1749 ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE);
1752 module AP_MODULE_DECLARE_DATA isapi_module = {
1753 STANDARD20_MODULE_STUFF,
1754 create_isapi_dir_config, /* create per-dir config */
1755 merge_isapi_dir_configs, /* merge per-dir config */
1756 NULL, /* server config */
1757 NULL, /* merge server config */
1758 isapi_cmds, /* command apr_table_t */
1759 isapi_hooks /* register hooks */