1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000-2002 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 * mod_isapi.c - Internet Server Application (ISA) module for Apache
61 * by Alexei Kosut <akosut@apache.org>
63 * This module implements Microsoft's ISAPI, allowing Apache (when running
64 * under Windows) to load Internet Server Applications (ISAPI extensions).
65 * It implements all of the ISAPI 2.0 specification, except for the
66 * "Microsoft-only" extensions dealing with asynchronous I/O. All ISAPI
67 * extensions that use only synchronous I/O and are compatible with the
68 * ISAPI 2.0 specification should work (most ISAPI 1.0 extensions should
71 * To load, simply place the ISA in a location in the document tree.
72 * Then add an "AddHandler isapi-isa dll" into your config file.
73 * You should now be able to load ISAPI DLLs just be reffering to their
74 * URLs. Make sure the ExecCGI option is active in the directory
78 #include "apr_strings.h"
79 #include "apr_portable.h"
80 #include "apr_buckets.h"
81 #include "ap_config.h"
83 #include "http_config.h"
84 #include "http_core.h"
85 #include "http_protocol.h"
86 #include "http_request.h"
88 #include "util_script.h"
91 /* We use the exact same header file as the original */
94 #if !defined(HSE_REQ_MAP_URL_TO_PATH_EX) \
95 || !defined(HSE_REQ_SEND_RESPONSE_HEADER_EX)
96 #pragma message("WARNING: This build of Apache is missing the recent changes")
97 #pragma message("in the Microsoft Win32 Platform SDK; some mod_isapi features")
98 #pragma message("will be disabled. To obtain the latest Platform SDK files,")
99 #pragma message("please refer to:")
100 #pragma message("http://msdn.microsoft.com/downloads/sdks/platform/platform.asp")
103 /* TODO: Unknown errors that must be researched for correct codes */
107 /* Seems IIS does not enforce the requirement for \r\n termination on HSE_REQ_SEND_RESPONSE_HEADER,
108 define this to conform */
109 #define RELAX_HEADER_RULE
111 module AP_MODULE_DECLARE_DATA isapi_module;
113 /* Declare the ISAPI functions */
115 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
116 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer);
117 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes,
119 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize);
120 BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
121 LPVOID lpvBuffer, LPDWORD lpdwSize,
122 LPDWORD lpdwDataType);
125 The optimiser blows it totally here. What happens is that autos are addressed relative to the
126 stack pointer, which, of course, moves around. The optimiser seems to lose track of it somewhere
127 between setting HttpExtensionProc's address and calling through it. We work around the problem by
128 forcing it to use frame pointers.
130 The revisions below may eliminate this artifact.
132 #pragma optimize("y",off)
134 /* Our isapi server config structure */
137 apr_array_header_t *loaded;
138 DWORD ReadAheadBuffer;
140 int AppendLogToErrors;
141 int AppendLogToQuery;
144 /* Our loaded isapi module description structure */
147 const char *filename;
148 apr_dso_handle_t *handle;
149 HSE_VERSION_INFO *pVer;
150 PFN_GETEXTENSIONVERSION GetExtensionVersion;
151 PFN_HTTPEXTENSIONPROC HttpExtensionProc;
152 PFN_TERMINATEEXTENSION TerminateExtension;
159 /* Our "Connection ID" structure */
162 LPEXTENSION_CONTROL_BLOCK ecb;
163 isapi_server_conf *sconf;
166 PFN_HSE_IO_COMPLETION completion;
167 PVOID completion_arg;
171 static void *create_isapi_server_config(apr_pool_t *p, server_rec *s)
173 isapi_server_conf *sconf = apr_palloc(p, sizeof(isapi_server_conf));
174 sconf->loaded = apr_array_make(p, 20, sizeof(isapi_loaded*));
176 sconf->ReadAheadBuffer = 49152;
177 sconf->LogNotSupported = -1;
178 sconf->AppendLogToErrors = 0;
179 sconf->AppendLogToQuery = 0;
184 static int compare_loaded(const void *av, const void *bv)
186 const isapi_loaded **a = av;
187 const isapi_loaded **b = bv;
189 return strcmp((*a)->filename, (*b)->filename);
192 static int isapi_post_config(apr_pool_t *p, apr_pool_t *plog,
193 apr_pool_t *ptemp, server_rec *s)
195 isapi_server_conf *sconf = ap_get_module_config(s->module_config,
197 isapi_loaded **elts = (isapi_loaded **)sconf->loaded->elts;
198 int nelts = sconf->loaded->nelts;
200 /* sort the elements of the main_server, by filename */
201 qsort(elts, nelts, sizeof(isapi_loaded*), compare_loaded);
203 /* and make all virtualhosts share the same */
204 for (s = s->next; s; s = s->next) {
205 ap_set_module_config(s->module_config, &isapi_module, sconf);
210 static apr_status_t isapi_unload(isapi_loaded* isa, int force);
212 static apr_status_t cleanup_isapi(void *isa)
214 return isapi_unload((isapi_loaded*) isa, TRUE);
217 static apr_status_t isapi_load(apr_pool_t *p, isapi_server_conf *sconf,
218 request_rec *r, const char *fpath,
221 isapi_loaded **found = (isapi_loaded **)sconf->loaded->elts;
225 for (n = 0; n < sconf->loaded->nelts; ++n) {
226 if (strcasecmp(fpath, (*found)->filename) == 0) {
232 if (n < sconf->loaded->nelts)
240 /* Otherwise we fall through and have to reload the resource
241 * into this existing mod_isapi cache bucket.
246 *isa = apr_pcalloc(p, sizeof(isapi_module));
247 (*isa)->filename = fpath;
248 (*isa)->pVer = apr_pcalloc(p, sizeof(HSE_VERSION_INFO));
250 /* TODO: These need to become overrideable, so that we
251 * assure a given isapi can be fooled into behaving well.
253 (*isa)->timeout = INFINITE; /* microsecs */
254 (*isa)->fakeasync = TRUE;
255 (*isa)->reportversion = MAKELONG(0, 5); /* Revision 5.0 */
258 rv = apr_dso_load(&(*isa)->handle, fpath, p);
261 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
262 "ISAPI %s failed to load", fpath);
263 (*isa)->handle = NULL;
267 rv = apr_dso_sym((void**)&(*isa)->GetExtensionVersion, (*isa)->handle,
268 "GetExtensionVersion");
271 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
272 "ISAPI %s is missing GetExtensionVersion()",
274 apr_dso_unload((*isa)->handle);
275 (*isa)->handle = NULL;
279 rv = apr_dso_sym((void**)&(*isa)->HttpExtensionProc, (*isa)->handle,
280 "HttpExtensionProc");
283 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
284 "ISAPI %s is missing HttpExtensionProc()",
286 apr_dso_unload((*isa)->handle);
287 (*isa)->handle = NULL;
291 /* TerminateExtension() is an optional interface */
292 rv = apr_dso_sym((void**)&(*isa)->TerminateExtension, (*isa)->handle,
293 "TerminateExtension");
296 /* Run GetExtensionVersion() */
297 if (!((*isa)->GetExtensionVersion)((*isa)->pVer)) {
298 apr_status_t rv = apr_get_os_error();
299 ap_log_rerror(APLOG_MARK, APLOG_ALERT, rv, r,
300 "ISAPI %s call GetExtensionVersion() failed",
302 apr_dso_unload((*isa)->handle);
303 (*isa)->handle = NULL;
309 apr_pool_cleanup_register(p, *isa, cleanup_isapi,
310 apr_pool_cleanup_null);
315 static apr_status_t isapi_unload(isapi_loaded* isa, int force)
317 /* All done with the DLL... get rid of it...
319 * If optionally cached, pass HSE_TERM_ADVISORY_UNLOAD,
320 * and if it returns TRUE, unload, otherwise, cache it.
322 if (((--isa->refcount > 0) && !force) || !isa->handle)
324 if (isa->TerminateExtension) {
326 (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
327 else if (!(*isa->TerminateExtension)(HSE_TERM_ADVISORY_UNLOAD))
330 apr_dso_unload(isa->handle);
335 apr_status_t isapi_handler (request_rec *r)
337 isapi_server_conf * sconf;
346 if(strcmp(r->handler, "isapi-isa"))
349 sconf = ap_get_module_config(r->server->module_config, &isapi_module);
350 e = r->subprocess_env;
352 /* Use similar restrictions as CGIs
354 * If this fails, it's pointless to load the isapi dll.
356 if (!(ap_allow_options(r) & OPT_EXECCGI))
357 return HTTP_FORBIDDEN;
359 if (r->finfo.filetype == APR_NOFILE)
360 return HTTP_NOT_FOUND;
362 if (r->finfo.filetype != APR_REG)
363 return HTTP_FORBIDDEN;
365 if (r->path_info && *r->path_info && !r->used_path_info)
366 return HTTP_NOT_FOUND;
368 /* Load the isapi extention without caching (sconf == NULL)
369 * but note that we will recover an existing cached module.
371 if (isapi_load(r->pool, sconf, r, r->filename, &isa) != APR_SUCCESS)
372 return HTTP_INTERNAL_SERVER_ERROR;
374 /* Set up variables */
375 ap_add_common_vars(r);
377 apr_table_setn(e, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
378 if ((val = apr_table_get(e, "HTTPS")) && strcmp(val, "on"))
379 apr_table_setn(e, "SERVER_PORT_SECURE", "1");
381 apr_table_setn(e, "SERVER_PORT_SECURE", "0");
382 apr_table_setn(e, "URL", r->uri);
384 /* Set up connection structure and ecb */
385 cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
386 cid->sconf = ap_get_module_config(r->server->module_config, &isapi_module);
388 cid->ecb = apr_pcalloc(r->pool, sizeof(struct _EXTENSION_CONTROL_BLOCK));
389 cid->ecb->ConnID = (HCONN)cid;
393 cid->complete = NULL;
394 cid->completion = NULL;
396 cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
397 cid->ecb->dwVersion = isa->reportversion;
398 cid->ecb->dwHttpStatusCode = 0;
399 strcpy(cid->ecb->lpszLogData, "");
400 // TODO: are copies really needed here?
401 cid->ecb->lpszMethod = apr_pstrdup(r->pool, (char*) r->method);
402 cid->ecb->lpszQueryString = apr_pstrdup(r->pool,
403 (char*) apr_table_get(e, "QUERY_STRING"));
404 cid->ecb->lpszPathInfo = apr_pstrdup(r->pool,
405 (char*) apr_table_get(e, "PATH_INFO"));
406 cid->ecb->lpszPathTranslated = apr_pstrdup(r->pool,
407 (char*) apr_table_get(e, "PATH_TRANSLATED"));
408 cid->ecb->lpszContentType = apr_pstrdup(r->pool,
409 (char*) apr_table_get(e, "CONTENT_TYPE"));
410 /* Set up the callbacks */
411 cid->ecb->GetServerVariable = GetServerVariable;
412 cid->ecb->WriteClient = WriteClient;
413 cid->ecb->ReadClient = ReadClient;
414 cid->ecb->ServerSupportFunction = ServerSupportFunction;
417 /* Set up client input */
418 res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
420 isapi_unload(isa, FALSE);
424 if (ap_should_client_block(r)) {
425 /* Time to start reading the appropriate amount of data,
426 * and allow the administrator to tweak the number
427 * TODO: add the httpd.conf option for ReadAheadBuffer.
430 cid->ecb->cbTotalBytes = (apr_size_t)r->remaining;
431 if (cid->ecb->cbTotalBytes > cid->sconf->ReadAheadBuffer)
432 cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
434 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
438 cid->ecb->cbTotalBytes = 0xffffffff;
439 cid->ecb->cbAvailable = cid->sconf->ReadAheadBuffer;
442 cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
445 while (read < cid->ecb->cbAvailable &&
446 ((res = ap_get_client_block(r, cid->ecb->lpbData + read,
447 cid->ecb->cbAvailable - read)) > 0)) {
452 isapi_unload(isa, FALSE);
453 return HTTP_INTERNAL_SERVER_ERROR;
456 /* Although it's not to spec, IIS seems to null-terminate
457 * its lpdData string. So we will too.
460 cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
462 cid->ecb->cbAvailable = read;
463 cid->ecb->lpbData[read] = '\0';
466 cid->ecb->cbTotalBytes = 0;
467 cid->ecb->cbAvailable = 0;
468 cid->ecb->lpbData = NULL;
471 /* All right... try and run the sucker */
472 rv = (*isa->HttpExtensionProc)(cid->ecb);
474 /* Check for a log message - and log it */
475 if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
476 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
477 "ISAPI %s: %s", r->filename, cid->ecb->lpszLogData);
480 case 0: /* Strange, but MS isapi accepts this as success */
481 case HSE_STATUS_SUCCESS:
482 case HSE_STATUS_SUCCESS_AND_KEEP_CONN:
483 /* Ignore the keepalive stuff; Apache handles it just fine without
484 * the ISA's "advice".
485 * Per Microsoft: "In IIS versions 4.0 and later, the return
486 * values HSE_STATUS_SUCCESS and HSE_STATUS_SUCCESS_AND_KEEP_CONN
487 * are functionally identical: Keep-Alive connections are
488 * maintained, if supported by the client."
489 * ... so we were pat all this time
493 case HSE_STATUS_PENDING:
494 /* emulating async behavior...
496 * Create a cid->completed event and wait on it for some timeout
497 * so that the app thinks is it running async.
499 * All async ServerSupportFunction calls will be handled through
500 * the registered IO_COMPLETION hook.
503 if (!isa->fakeasync) {
504 if (cid->sconf->LogNotSupported)
506 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
507 "ISAPI %s asynch I/O request refused",
509 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
513 cid->complete = CreateEvent(NULL, FALSE, FALSE, NULL);
514 if (WaitForSingleObject(cid->complete, isa->timeout)
516 /* TODO: Now what... if this hung, then do we kill our own
517 * thread to force its death? For now leave timeout = -1
523 case HSE_STATUS_ERROR:
524 /* end response if we have yet to do so.
526 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
530 /* TODO: log unrecognized retval for debugging
532 cid->r->status = HTTP_INTERNAL_SERVER_ERROR;
536 /* Set the status (for logging) */
537 if (cid->ecb->dwHttpStatusCode) {
538 cid->r->status = cid->ecb->dwHttpStatusCode;
541 /* All done with the DLL... get rid of it... */
542 isapi_unload(isa, FALSE);
544 return OK; /* NOT r->status, even if it has changed. */
546 #pragma optimize("",on)
548 BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
549 LPVOID lpvBuffer, LPDWORD lpdwSizeofBuffer)
551 request_rec *r = ((isapi_cid *)hConn)->r;
555 if (!strcmp(lpszVariableName, "ALL_HTTP"))
557 /* lf delimited, colon split, comma seperated and
558 * null terminated list of HTTP_ vars
560 const apr_array_header_t *arr = apr_table_elts(r->subprocess_env);
561 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
564 for (len = 0, i = 0; i < arr->nelts; i++) {
565 if (!strncmp(elts[i].key, "HTTP_", 5)) {
566 len += strlen(elts[i].key) + strlen(elts[i].val) + 2;
570 if (*lpdwSizeofBuffer < len + 1) {
571 *lpdwSizeofBuffer = len + 1;
572 SetLastError(ERROR_INSUFFICIENT_BUFFER);
576 for (i = 0; i < arr->nelts; i++) {
577 if (!strncmp(elts[i].key, "HTTP_", 5)) {
578 strcpy(lpvBuffer, elts[i].key);
579 ((char*)lpvBuffer) += strlen(elts[i].key);
580 *(((char*)lpvBuffer)++) = ':';
581 strcpy(lpvBuffer, elts[i].val);
582 ((char*)lpvBuffer) += strlen(elts[i].val);
583 *(((char*)lpvBuffer)++) = '\n';
587 *(((char*)lpvBuffer)++) = '\0';
588 *lpdwSizeofBuffer = len;
592 if (!strcmp(lpszVariableName, "ALL_RAW"))
594 /* lf delimited, colon split, comma seperated and
595 * null terminated list of the raw request header
597 const apr_array_header_t *arr = apr_table_elts(r->headers_in);
598 const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
601 for (len = 0, i = 0; i < arr->nelts; i++) {
602 len += strlen(elts[i].key) + strlen(elts[i].val) + 2;
605 if (*lpdwSizeofBuffer < len + 1) {
606 *lpdwSizeofBuffer = len + 1;
607 SetLastError(ERROR_INSUFFICIENT_BUFFER);
611 for (i = 0; i < arr->nelts; i++) {
612 strcpy(lpvBuffer, elts[i].key);
613 ((char*)lpvBuffer) += strlen(elts[i].key);
614 *(((char*)lpvBuffer)++) = ':';
615 *(((char*)lpvBuffer)++) = ' ';
616 strcpy(lpvBuffer, elts[i].val);
617 ((char*)lpvBuffer) += strlen(elts[i].val);
618 *(((char*)lpvBuffer)++) = '\n';
620 *(((char*)lpvBuffer)++) = '\0';
621 *lpdwSizeofBuffer = len;
625 /* Not a special case */
626 result = apr_table_get(r->subprocess_env, lpszVariableName);
629 len = strlen(result);
630 if (*lpdwSizeofBuffer < len + 1) {
631 *lpdwSizeofBuffer = len + 1;
632 SetLastError(ERROR_INSUFFICIENT_BUFFER);
635 strcpy(lpvBuffer, result);
636 *lpdwSizeofBuffer = len;
641 SetLastError(ERROR_INVALID_INDEX);
645 BOOL WINAPI WriteClient (HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes,
648 request_rec *r = ((isapi_cid *)ConnID)->r;
649 apr_bucket_brigade *bb;
652 if (dwReserved == HSE_IO_SYNC)
655 bb = apr_brigade_create(r->pool);
656 b = apr_bucket_transient_create(Buffer, *lpdwBytes);
657 APR_BRIGADE_INSERT_TAIL(bb, b);
658 b = apr_bucket_flush_create();
659 APR_BRIGADE_INSERT_TAIL(bb, b);
660 ap_pass_brigade(r->output_filters, bb);
665 BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize)
667 request_rec *r = ((isapi_cid *)ConnID)->r;
671 if (r->remaining < *lpdwSize) {
672 *lpdwSize = (apr_size_t)r->remaining;
675 while (read < *lpdwSize &&
676 ((res = ap_get_client_block(r, (char*)lpvBuffer + read,
677 *lpdwSize - read)) > 0)) {
685 static apr_ssize_t SendResponseHeaderEx(isapi_cid *cid, const char *stat,
686 const char *head, apr_size_t statlen,
692 if (!stat || statlen == 0 || !*stat) {
693 stat = "Status: 200 OK";
697 newstat = apr_palloc(cid->r->pool, statlen + 9);
698 strcpy(newstat, "Status: ");
699 apr_cpystrn(newstat + 8, stat, statlen + 1);
703 if (!head || headlen == 0 || !*head) {
709 /* Whoops... not NULL terminated */
710 head = apr_pstrndup(cid->r->pool, head, headlen);
714 /* Parse them out, or die trying */
715 cid->r->status= ap_scan_script_header_err_strs(cid->r, NULL, &termch,
716 &termarg, stat, head, NULL);
717 cid->ecb->dwHttpStatusCode = cid->r->status;
718 if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR)
721 /* Headers will actually go when they are good and ready */
723 /* If all went well, tell the caller we consumed the headers complete */
727 /* Any data left is sent directly by the caller, all we
728 * give back is the size of the headers we consumed
730 if (termch && (termarg == 1) && head + headlen > termch) {
731 return termch - head;
736 BOOL WINAPI ServerSupportFunction(HCONN hConn, DWORD dwHSERequest,
737 LPVOID lpvBuffer, LPDWORD lpdwSize,
738 LPDWORD lpdwDataType)
740 isapi_cid *cid = (isapi_cid *)hConn;
741 request_rec *r = cid->r;
744 switch (dwHSERequest) {
745 case 1: /* HSE_REQ_SEND_URL_REDIRECT_RESP */
746 /* Set the status to be returned when the HttpExtensionProc()
748 * WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
749 * and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK.
750 * They most definately are not, even in their own samples.
752 apr_table_set (r->headers_out, "Location", lpvBuffer);
753 cid->r->status = cid->ecb->dwHttpStatusCode
754 = HTTP_MOVED_TEMPORARILY;
757 case 2: /* HSE_REQ_SEND_URL */
758 /* Soak up remaining input */
759 if (r->remaining > 0) {
760 char argsbuffer[HUGE_STRING_LEN];
761 while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
764 /* Reset the method to GET */
765 r->method = apr_pstrdup(r->pool, "GET");
766 r->method_number = M_GET;
768 /* Don't let anyone think there's still data */
769 apr_table_unset(r->headers_in, "Content-Length");
771 /* AV fault per PR3598 - redirected path is lost! */
772 (char*)lpvBuffer = apr_pstrdup(r->pool, (char*)lpvBuffer);
773 ap_internal_redirect((char*)lpvBuffer, r);
776 case 3: /* HSE_REQ_SEND_RESPONSE_HEADER */
778 /* Parse them out, or die trying */
779 apr_size_t statlen = 0, headlen = 0;
782 statlen = strlen((char*) lpvBuffer);
784 headlen = strlen((char*) lpdwDataType);
785 ate = SendResponseHeaderEx(cid, (char*) lpvBuffer,
786 (char*) lpdwDataType,
789 SetLastError(TODO_ERROR);
792 else if ((apr_size_t)ate < headlen) {
793 apr_bucket_brigade *bb;
795 bb = apr_brigade_create(cid->r->pool);
796 b = apr_bucket_transient_create((char*) lpdwDataType + ate,
798 APR_BRIGADE_INSERT_TAIL(bb, b);
799 b = apr_bucket_flush_create();
800 APR_BRIGADE_INSERT_TAIL(bb, b);
801 ap_pass_brigade(cid->r->output_filters, bb);
806 case 4: /* HSE_REQ_DONE_WITH_SESSION */
807 /* Signal to resume the thread completing this request
810 SetEvent(cid->complete);
813 case 1001: /* HSE_REQ_MAP_URL_TO_PATH */
815 /* Map a URL to a filename */
816 char *file = (char *)lpvBuffer;
818 subreq = ap_sub_req_lookup_uri(apr_pstrndup(r->pool, file, *lpdwSize),
821 len = apr_cpystrn(file, subreq->filename, *lpdwSize) - file;
824 /* IIS puts a trailing slash on directories, Apache doesn't */
825 if (subreq->finfo.filetype == APR_DIR) {
826 if (len < *lpdwSize - 1) {
835 case 1002: /* HSE_REQ_GET_SSPI_INFO */
836 if (cid->sconf->LogNotSupported)
837 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
838 "ISAPI ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
839 "is not supported: %s", r->filename);
840 SetLastError(ERROR_INVALID_PARAMETER);
843 case 1003: /* HSE_APPEND_LOG_PARAMETER */
844 /* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field
846 apr_table_set(r->notes, "isapi-parameter", (char*) lpvBuffer);
847 if (cid->sconf->AppendLogToQuery) {
849 r->args = apr_pstrcat(r->pool, r->args, (char*) lpvBuffer, NULL);
851 r->args = apr_pstrdup(r->pool, (char*) lpvBuffer);
853 if (cid->sconf->AppendLogToErrors)
854 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
855 "ISAPI %s: %s", cid->r->filename,
859 case 1005: /* HSE_REQ_IO_COMPLETION */
860 /* Emulates a completion port... Record callback address and
861 * user defined arg, we will call this after any async request
862 * (e.g. transmitfile) as if the request executed async.
863 * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
864 * to HSE_REQ_IO_COMPLETION, and lpvBuffer may be set to NULL.
866 if (!cid->isa->fakeasync) {
867 if (cid->sconf->LogNotSupported)
868 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
869 "ISAPI ServerSupportFunction HSE_REQ_IO_COMPLETION "
870 "is not supported: %s", r->filename);
871 SetLastError(ERROR_INVALID_PARAMETER);
874 cid->completion = (PFN_HSE_IO_COMPLETION) lpvBuffer;
875 cid->completion_arg = (PVOID) lpdwDataType;
878 case 1006: /* HSE_REQ_TRANSMIT_FILE */
880 HSE_TF_INFO *tf = (HSE_TF_INFO*)lpvBuffer;
882 apr_bucket_brigade *bb;
886 if (!cid->isa->fakeasync && (tf->dwFlags & HSE_IO_ASYNC)) {
887 if (cid->sconf->LogNotSupported)
888 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
889 "ISAPI ServerSupportFunction HSE_REQ_TRANSMIT_FILE "
890 "as HSE_IO_ASYNC is not supported: %s", r->filename);
891 SetLastError(ERROR_INVALID_PARAMETER);
895 if ((rv = apr_os_file_put(&fd, tf->hFile, 0, r->pool)) != APR_SUCCESS) {
899 /* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
900 bb = apr_brigade_create(r->pool);
902 if (tf->dwFlags & HSE_IO_SEND_HEADERS)
904 /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
905 * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
906 * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
907 * you must have done so. They document that the pHead headers
908 * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
909 * more flexible and assume with the flag, pHead are the
910 * response headers, and without, pHead simply contains text
911 * (handled after this case).
913 apr_ssize_t ate = SendResponseHeaderEx(cid, tf->pszStatusCode,
915 strlen(tf->pszStatusCode),
919 apr_brigade_destroy(bb);
920 SetLastError(TODO_ERROR);
923 if ((apr_size_t)ate < tf->HeadLength)
925 b = apr_bucket_transient_create((char*)tf->pHead + ate,
926 tf->HeadLength - ate);
927 APR_BRIGADE_INSERT_TAIL(bb, b);
930 else if (tf->pHead && tf->HeadLength) {
931 b = apr_bucket_transient_create((char*)tf->pHead,
933 APR_BRIGADE_INSERT_TAIL(bb, b);
936 b = apr_bucket_file_create(fd, tf->Offset,
937 tf->BytesToWrite, r->pool);
938 APR_BRIGADE_INSERT_TAIL(bb, b);
940 if (tf->pTail && tf->TailLength) {
941 b = apr_bucket_transient_create((char*)tf->pTail,
943 APR_BRIGADE_INSERT_TAIL(bb, b);
946 b = apr_bucket_flush_create();
947 APR_BRIGADE_INSERT_TAIL(bb, b);
948 ap_pass_brigade(r->output_filters, bb);
950 /* we do nothing with (tf->dwFlags & HSE_DISCONNECT_AFTER_SEND)
953 if (tf->dwFlags & HSE_IO_ASYNC) {
954 /* XXX: Fake async response,
955 * use tf->pfnHseIO, or if NULL, then use cid->fnIOComplete
956 * pass pContect to the HseIO callback.
962 case 1007: /* HSE_REQ_REFRESH_ISAPI_ACL */
963 if (cid->sconf->LogNotSupported)
964 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
965 "ISAPI ServerSupportFunction "
966 "HSE_REQ_REFRESH_ISAPI_ACL "
967 "is not supported: %s", r->filename);
968 SetLastError(ERROR_INVALID_PARAMETER);
971 case 1008: /* HSE_REQ_IS_KEEP_CONN */
972 *((LPBOOL) lpvBuffer) = (r->connection->keepalive == 1);
975 case 1010: /* XXX: Fake it : HSE_REQ_ASYNC_READ_CLIENT */
976 if (cid->sconf->LogNotSupported)
977 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
978 "ISAPI asynchronous I/O not supported: %s",
980 SetLastError(ERROR_INVALID_PARAMETER);
983 case 1011: /* HSE_REQ_GET_IMPERSONATION_TOKEN Added in ISAPI 4.0 */
984 if (cid->sconf->LogNotSupported)
985 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
986 "ISAPI ServerSupportFunction "
987 "HSE_REQ_GET_IMPERSONATION_TOKEN "
988 "is not supported: %s", r->filename);
989 SetLastError(ERROR_INVALID_PARAMETER);
992 #ifdef HSE_REQ_MAP_URL_TO_PATH_EX
993 case 1012: /* HSE_REQ_MAP_URL_TO_PATH_EX */
995 /* Map a URL to a filename */
996 LPHSE_URL_MAPEX_INFO info = (LPHSE_URL_MAPEX_INFO) lpdwDataType;
997 char* test_uri = apr_pstrndup(r->pool, (char *)lpvBuffer, *lpdwSize);
999 subreq = ap_sub_req_lookup_uri(test_uri, r, NULL);
1000 info->cchMatchingURL = strlen(test_uri);
1001 info->cchMatchingPath = apr_cpystrn(info->lpszPath, subreq->filename,
1002 MAX_PATH) - info->lpszPath;
1004 /* Mapping started with assuming both strings matched.
1005 * Now roll on the path_info as a mismatch and handle
1006 * terminating slashes for directory matches.
1008 if (subreq->path_info && *subreq->path_info) {
1009 apr_cpystrn(info->lpszPath + info->cchMatchingPath,
1010 subreq->path_info, MAX_PATH - info->cchMatchingPath);
1011 info->cchMatchingURL -= strlen(subreq->path_info);
1012 if (subreq->finfo.filetype == APR_DIR
1013 && info->cchMatchingPath < MAX_PATH - 1) {
1014 /* roll forward over path_info's first slash */
1015 ++info->cchMatchingPath;
1016 ++info->cchMatchingURL;
1019 else if (subreq->finfo.filetype == APR_DIR
1020 && info->cchMatchingPath < MAX_PATH - 1) {
1021 /* Add a trailing slash for directory */
1022 info->lpszPath[info->cchMatchingPath++] = '/';
1023 info->lpszPath[info->cchMatchingPath] = '\0';
1026 /* If the matched isn't a file, roll match back to the prior slash */
1027 if (subreq->finfo.filetype == APR_NOFILE) {
1028 while (info->cchMatchingPath && info->cchMatchingURL) {
1029 if (info->lpszPath[info->cchMatchingPath - 1] == '/')
1031 --info->cchMatchingPath;
1032 --info->cchMatchingURL;
1036 /* Paths returned with back slashes */
1037 for (test_uri = info->lpszPath; *test_uri; ++test_uri)
1038 if (*test_uri == '/')
1041 /* is a combination of:
1042 * HSE_URL_FLAGS_READ 0x001 Allow read
1043 * HSE_URL_FLAGS_WRITE 0x002 Allow write
1044 * HSE_URL_FLAGS_EXECUTE 0x004 Allow execute
1045 * HSE_URL_FLAGS_SSL 0x008 Require SSL
1046 * HSE_URL_FLAGS_DONT_CACHE 0x010 Don't cache (VRoot only)
1047 * HSE_URL_FLAGS_NEGO_CERT 0x020 Allow client SSL cert
1048 * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
1049 * HSE_URL_FLAGS_MAP_CERT 0x080 Map client SSL cert to account
1050 * HSE_URL_FLAGS_SSL128 0x100 Require 128-bit SSL cert
1051 * HSE_URL_FLAGS_SCRIPT 0x200 Allow script execution
1053 * XxX: As everywhere, EXEC flags could use some work...
1054 * and this could go further with more flags, as desired.
1056 info->dwFlags = (subreq->finfo.protection & APR_UREAD ? 0x001 : 0)
1057 | (subreq->finfo.protection & APR_UWRITE ? 0x002 : 0)
1058 | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
1063 case 1014: /* HSE_REQ_ABORTIVE_CLOSE */
1064 if (cid->sconf->LogNotSupported)
1065 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1066 "ISAPI ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
1067 " is not supported: %s", r->filename);
1068 SetLastError(ERROR_INVALID_PARAMETER);
1071 case 1015: /* HSE_REQ_GET_CERT_INFO_EX Added in ISAPI 4.0 */
1072 if (cid->sconf->LogNotSupported)
1073 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1074 "ISAPI ServerSupportFunction "
1075 "HSE_REQ_GET_CERT_INFO_EX "
1076 "is not supported: %s", r->filename);
1077 SetLastError(ERROR_INVALID_PARAMETER);
1080 #ifdef HSE_REQ_SEND_RESPONSE_HEADER_EX
1081 case 1016: /* HSE_REQ_SEND_RESPONSE_HEADER_EX Added in ISAPI 4.0 */
1083 LPHSE_SEND_HEADER_EX_INFO shi
1084 = (LPHSE_SEND_HEADER_EX_INFO) lpvBuffer;
1085 /* XXX: ignore shi->fKeepConn? We shouldn't need the advise */
1086 /* r->connection->keepalive = shi->fKeepConn; */
1087 apr_ssize_t ate = SendResponseHeaderEx(cid, shi->pszStatus,
1092 SetLastError(TODO_ERROR);
1095 else if ((apr_size_t)ate < shi->cchHeader) {
1096 apr_bucket_brigade *bb;
1098 bb = apr_brigade_create(cid->r->pool);
1099 b = apr_bucket_transient_create(shi->pszHeader + ate,
1100 shi->cchHeader - ate);
1101 APR_BRIGADE_INSERT_TAIL(bb, b);
1102 b = apr_bucket_flush_create();
1103 APR_BRIGADE_INSERT_TAIL(bb, b);
1104 ap_pass_brigade(cid->r->output_filters, bb);
1111 case 1017: /* HSE_REQ_CLOSE_CONNECTION Added after ISAPI 4.0 */
1112 if (cid->sconf->LogNotSupported)
1113 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1114 "ISAPI ServerSupportFunction "
1115 "HSE_REQ_CLOSE_CONNECTION "
1116 "is not supported: %s", r->filename);
1117 SetLastError(ERROR_INVALID_PARAMETER);
1120 case 1018: /* HSE_REQ_IS_CONNECTED Added after ISAPI 4.0 */
1121 /* Returns True if client is connected c.f. MSKB Q188346
1122 * assuming the identical return mechanism as HSE_REQ_IS_KEEP_CONN
1124 *((LPBOOL) lpvBuffer) = (r->connection->aborted == 0);
1127 case 1020: /* HSE_REQ_EXTENSION_TRIGGER Added after ISAPI 4.0 */
1128 /* Undocumented - defined by the Microsoft Jan '00 Platform SDK
1130 if (cid->sconf->LogNotSupported)
1131 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1132 "ISAPI ServerSupportFunction "
1133 "HSE_REQ_EXTENSION_TRIGGER "
1134 "is not supported: %s", r->filename);
1135 SetLastError(ERROR_INVALID_PARAMETER);
1139 if (cid->sconf->LogNotSupported)
1140 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
1141 "ISAPI ServerSupportFunction (%d) not supported: "
1142 "%s", dwHSERequest, r->filename);
1143 SetLastError(ERROR_INVALID_PARAMETER);
1149 * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1151 static const char *isapi_cmd_readaheadbuffer(cmd_parms *cmd, void *config,
1154 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1159 if (((val = strtol(arg, (char **) &scan, 10)) <= 0) || *scan)
1160 return "ISAPIReadAheadBuffer must be a legitimate value.";
1162 sconf->ReadAheadBuffer = val;
1167 * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
1169 static const char *isapi_cmd_lognotsupported(cmd_parms *cmd, void *config,
1172 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1175 if (strcasecmp(arg, "on") == 0) {
1176 sconf->LogNotSupported = -1;
1178 else if (strcasecmp(arg, "off") == 0) {
1179 sconf->LogNotSupported = 0;
1182 return "ISAPILogNotSupported must be on or off";
1187 static const char *isapi_cmd_appendlogtoerrors(cmd_parms *cmd, void *config,
1190 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1193 if (strcasecmp(arg, "on") == 0) {
1194 sconf->AppendLogToErrors = -1;
1196 else if (strcasecmp(arg, "off") == 0) {
1197 sconf->AppendLogToErrors = 0;
1200 return "ISAPIAppendLogToErrors must be on or off";
1205 static const char *isapi_cmd_appendlogtoquery(cmd_parms *cmd, void *config,
1208 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1211 if (strcasecmp(arg, "on") == 0) {
1212 sconf->AppendLogToQuery = -1;
1214 else if (strcasecmp(arg, "off") == 0) {
1215 sconf->AppendLogToQuery = 0;
1218 return "ISAPIAppendLogToQuery must be on or off";
1223 static const char *isapi_cmd_cachefile(cmd_parms *cmd, void *dummy,
1224 const char *filename)
1227 isapi_server_conf *sconf = ap_get_module_config(cmd->server->module_config,
1229 isapi_loaded *isa, **newisa;
1234 fspec = ap_server_root_relative(cmd->pool, filename);
1235 if ((rv = apr_stat(&tmp, fspec,
1236 APR_FINFO_TYPE, cmd->temp_pool)) != APR_SUCCESS) {
1237 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
1238 "ISAPI: unable to stat(%s), skipping", filename);
1241 if (tmp.filetype != APR_REG) {
1242 ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
1243 "ISAPI: %s isn't a regular file, skipping", filename);
1247 /* Load the extention as cached (passing sconf) */
1248 rv = isapi_load(cmd->pool, sconf, NULL, fspec, &isa);
1249 if (rv != APR_SUCCESS) {
1250 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, cmd->server,
1251 "ISAPI: unable to cache %s, skipping", filename);
1255 /* Add to cached list of loaded modules */
1256 newisa = apr_array_push(sconf->loaded);
1262 static void isapi_hooks(apr_pool_t *cont)
1264 ap_hook_post_config(isapi_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1265 ap_hook_handler(isapi_handler, NULL, NULL, APR_HOOK_MIDDLE);
1268 static const command_rec isapi_cmds[] = {
1269 AP_INIT_TAKE1("ISAPIReadAheadBuffer", isapi_cmd_readaheadbuffer, NULL, RSRC_CONF,
1270 "Maximum bytes to initially pass to the ISAPI handler"),
1271 AP_INIT_TAKE1("ISAPILogNotSupported", isapi_cmd_lognotsupported, NULL, RSRC_CONF,
1272 "Log requests not supported by the ISAPI server"),
1273 AP_INIT_TAKE1("ISAPIAppendLogToErrors", isapi_cmd_appendlogtoerrors, NULL, RSRC_CONF,
1274 "Send all Append Log requests to the error log"),
1275 AP_INIT_TAKE1("ISAPIAppendLogToQuery", isapi_cmd_appendlogtoquery, NULL, RSRC_CONF,
1276 "Append Log requests are concatinated to the query args"),
1277 AP_INIT_ITERATE("ISAPICacheFile", isapi_cmd_cachefile, NULL, RSRC_CONF,
1278 "Cache the specified ISAPI extension in-process"),
1282 module AP_MODULE_DECLARE_DATA isapi_module = {
1283 STANDARD20_MODULE_STUFF,
1284 NULL, /* create per-dir config */
1285 NULL, /* merge per-dir config */
1286 create_isapi_server_config, /* server config */
1287 NULL, /* merge server config */
1288 isapi_cmds, /* command apr_table_t */
1289 isapi_hooks /* register hooks */