2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 /***************************************************************************
19 * Description: ISAPI plugin for IIS/PWS *
20 * Author: Gal Shachor <shachor@il.ibm.com> *
21 * Author: Larry Isaacs <larryi@apache.org> *
22 * Author: Ignacio J. Ortega <nacho@apache.org> *
23 * Author: Mladen Turk <mturk@apache.org> *
24 * Version: $Revision: 1129429 $ *
25 ***************************************************************************/
27 // This define is needed to include wincrypt,h, needed to get client certificates
29 #define _WIN32_WINNT 0x0500
36 #include "jk_global.h"
41 #include "jk_service.h"
42 #include "jk_worker.h"
43 #include "jk_uri_worker_map.h"
48 #ifndef POSIX_MALLOC_THRESHOLD
49 #define POSIX_MALLOC_THRESHOLD (10)
54 #define VERSION_STRING "Jakarta/ISAPI/" JK_EXPOSED_VERSION
55 #define FULL_VERSION_STRING "Jakarta/ISAPI/" JK_FULL_EXPOSED_VERSION
56 #define SHM_DEF_NAME "JKISAPISHMEM"
57 #define DEFAULT_WORKER_NAME ("ajp13")
60 * This is default value found inside httpd.conf
63 #define DEFAULT_WORKER_THREADS 250
66 * We use special headers to pass values from the filter to the
67 * extension. These values are:
69 * 1. The real URI before redirection took place
70 * 2. The name of the worker to be used.
71 * 3. The contents of the Translate header, if any
74 #define URI_HEADER_NAME_BASE ("TOMCATURI")
75 #define QUERY_HEADER_NAME_BASE ("TOMCATQUERY")
76 #define WORKER_HEADER_NAME_BASE ("TOMCATWORKER")
77 #define WORKER_HEADER_INDEX_BASE ("TOMCATWORKERIDX")
78 #define TOMCAT_TRANSLATE_HEADER_NAME_BASE ("TOMCATTRANSLATE")
79 #ifndef USE_CGI_HEADERS
80 #define CONTENT_LENGTH ("CONTENT-LENGTH:")
82 #define CONTENT_LENGTH ("CONTENT_LENGTH:")
85 /* The HTTP_ form of the header for use in ExtensionProc */
86 #define HTTP_HEADER_PREFIX "HTTP_"
87 #ifdef USE_CGI_HEADERS
88 #define HTTP_HEADER_PREFIX_LEN 5
91 /* The template used to construct our unique headers
92 * from the base name and module instance
94 #define HEADER_TEMPLATE "%s%p:"
95 #define HTTP_HEADER_TEMPLATE HTTP_HEADER_PREFIX "%s%p"
97 static char URI_HEADER_NAME[MAX_PATH];
98 static char QUERY_HEADER_NAME[MAX_PATH];
99 static char WORKER_HEADER_NAME[MAX_PATH];
100 static char TOMCAT_TRANSLATE_HEADER_NAME[MAX_PATH];
101 static char WORKER_HEADER_INDEX[MAX_PATH];
103 /* The variants of the special headers after IIS adds
104 * "HTTP_" to the front of them
106 static char HTTP_URI_HEADER_NAME[MAX_PATH];
107 static char HTTP_QUERY_HEADER_NAME[MAX_PATH];
108 static char HTTP_WORKER_HEADER_NAME[MAX_PATH];
109 static char HTTP_WORKER_HEADER_INDEX[MAX_PATH];
111 #define REGISTRY_LOCATION ("Software\\Apache Software Foundation\\Jakarta Isapi Redirector\\1.0")
112 #define W3SVC_REGISTRY_KEY ("SYSTEM\\CurrentControlSet\\Services\\W3SVC\\Parameters")
113 #define EXTENSION_URI_TAG ("extension_uri")
115 #define URI_SELECT_TAG ("uri_select")
116 #define URI_SELECT_PARSED_VERB ("parsed")
117 #define URI_SELECT_UNPARSED_VERB ("unparsed")
118 #define URI_SELECT_ESCAPED_VERB ("escaped")
119 #define URI_SELECT_PROXY_VERB ("proxy")
120 #define URI_REWRITE_TAG ("rewrite_rule_file")
121 #define SHM_SIZE_TAG ("shm_size")
122 #define WORKER_MOUNT_RELOAD_TAG ("worker_mount_reload")
123 #define STRIP_SESSION_TAG ("strip_session")
124 #ifndef AUTOMATIC_AUTH_NOTIFICATION
125 #define AUTH_COMPLETE_TAG ("auth_complete")
127 #define REJECT_UNSAFE_TAG ("reject_unsafe")
128 #define WATCHDOG_INTERVAL_TAG ("watchdog_interval")
129 #define ENABLE_CHUNKED_ENCODING_TAG ("enable_chunked_encoding")
130 #define ERROR_PAGE_TAG ("error_page")
132 #define LOG_ROTATION_TIME_TAG ("log_rotationtime")
133 #define LOG_FILESIZE_TAG ("log_filesize")
135 /* HTTP standard headers */
136 #define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE ("Transfer-Encoding: chunked")
137 #define TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN (26)
138 #define TRANSFER_ENCODING_HEADER_NAME ("Transfer-Encoding")
139 #define TRANSFER_ENCODING_HEADER_NAME_LEN (17)
140 #define TRANSFER_ENCODING_IDENTITY_VALUE ("identity")
141 #define TRANSFER_ENCODING_CHUNKED_VALUE ("chunked")
142 #define TRANSFER_ENCODING_CHUNKED_VALUE_LEN (7)
144 #define CONTENT_LENGTH_HEADER_NAME ("Content-Length")
145 #define CONTENT_LENGTH_HEADER_NAME_LEN (14)
147 #define CONNECTION_HEADER_NAME ("Connection")
148 #define CONNECTION_CLOSE_VALUE ("Close")
150 #define TRANSLATE_HEADER ("Translate:")
151 #define TRANSLATE_HEADER_NAME ("Translate")
152 #define TRANSLATE_HEADER_NAME_LC ("translate")
154 /* HTTP protocol CRLF */
155 #define CRLF ("\r\n")
158 /* Transfer-Encoding: chunked content trailer */
159 #define CHUNKED_ENCODING_TRAILER ("0\r\n\r\n")
160 #define CHUNKED_ENCODING_TRAILER_LEN (5)
162 /* Hex of chunk length (one char per byte) + CRLF + terminator. */
163 #define CHUNK_HEADER_BUFFER_SIZE (sizeof(unsigned int)*2+CRLF_LEN+1)
165 #define BAD_REQUEST -1
167 #define MAX_SERVERNAME 128
168 #define MAX_INSTANCEID 32
169 #define MAX_PACKET_SIZE 65536
171 char HTML_ERROR_HEAD[] = "<!--\n"
172 " Licensed to the Apache Software Foundation (ASF) under one or more\n"
173 " contributor license agreements. See the NOTICE file distributed with\n"
174 " this work for additional information regarding copyright ownership.\n"
175 " The ASF licenses this file to You under the Apache License, Version 2.0\n"
176 " (the \"License\"); you may not use this file except in compliance with\n"
177 " the License. You may obtain a copy of the License at\n\n"
178 " http://www.apache.org/licenses/LICENSE-2.0\n\n"
179 " Unless required by applicable law or agreed to in writing, software\n"
180 " distributed under the License is distributed on an \"AS IS\" BASIS,\n"
181 " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
182 " See the License for the specific language governing permissions and\n"
183 " limitations under the License.\n"
185 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n"
186 "\"http://www.w3c.org/TR/REC-html40/loose.dtd\">\n"
188 "<META http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n"
189 "<STYLE TYPE=\"text/css\">\n"
192 " background-color: #FFFFFF;\n"
193 " font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif;\n"
195 " margin: 10px 10px;\n"
198 " text-align: right;\n"
203 #define HTML_ERROR_BODY_FMT "<TITLE>%s!</TITLE>\n</HEAD>\n<BODY>\n<H1>%s!</H1>\n<P>\n%s\n</P>\n"
205 char HTML_ERROR_TAIL[] = "<P>\n<BR/> <BR/> <BR/> <BR/> \n"
206 FULL_VERSION_STRING "\n"
209 "<P id=\"footer\">\n"
210 "Copyright © 1999-2011 Apache Software Foundation<BR/>\n"
211 "All Rights Reserved\n"
212 "</P>\n</BODY>\n</HTML>\n";
214 static struct error_reasons {
218 const char *description;
219 } error_reasons[] = {
226 "Switching Protocols",
246 "Non-Authoritative Information",
298 "Your browser (or proxy) sent a request that "
299 "this server could not understand."
303 "Access is denied due to invalid credentials",
304 "You do not have permission to view this directory or "
305 "page using the credentials that you supplied."
315 "You do not have permission to view this directory or page "
316 "using the credentials that you supplied."
320 "The requested URL was not found on this server",
321 "If you entered the URL manually please check your"
322 "spelling and try again."
325 "Method Not Allowed",
326 "HTTP method used to access this page is not allowed",
327 "The page you are looking for cannot be displayed because an "
328 "invalid method (HTTP method) was used to attempt access."
332 "Client browser does not accept the MIME type of the requested page",
333 "The page you are looking for cannot be opened by your browser "
334 "because it has a file name extension that your browser "
338 "Proxy Authentication Required",
340 "The client must first authenticate itself with the proxy"
345 "The client did not produce a request within the time "
346 "that the server was prepared to wait."
351 "The request could not be completed due to a conflict with "
352 "the current state of the resource."
357 "The requested resource is no longer available at the "
358 "server and no forwarding address is known."
363 "The server refuses to accept the request without a "
364 "defined Content-Length."
367 "Precondition Failed",
369 "The precondition given in one or more of the request "
370 "header fields evaluated to false when it was tested on the server."
373 "Request Entity Too Large",
375 "The HTTP method does not allow the data transmitted, "
376 "or the data volume exceeds the capacity limit."
379 "Request-URI Too Long",
380 "Submitted URI too large",
381 "The length of the requested URL exceeds the capacity limit "
382 "for this server. The request cannot be processed."
385 "Unsupported Media Type",
387 "The server is refusing to service the request because the "
388 "entity of the request is in a format not supported by the "
389 "requested resource for the requested method."
392 "Internal Server Error",
394 "The server encountered an internal error and was "
395 "unable to complete your request."
400 "The server does not support the functionality required "
401 "to fulfill the request."
406 "There is a problem with the page you are looking for, "
407 "and it cannot be displayed. When the Web server (while "
408 "acting as a gateway or proxy) contacted the upstream content "
409 "server, it received an invalid response from the content server."
412 "Service Unavailable",
413 "Service Temporary Unavailable",
414 "The server is temporarily unable to service your "
415 "request due to maintenance downtime or capacity problems. "
416 "Please try again later."
421 "The server, while acting as a gateway or proxy, "
422 "did not receive a timely response from the upstream server"
425 "HTTP Version Not Supported",
427 "The server does not support, or refuses to support, the "
428 "HTTP protocol version that was used in the request message."
439 #define STRNULL_FOR_NULL(x) ((x) ? (x) : "(null)")
440 #ifdef USE_CGI_HEADERS
441 #define JK_TOLOWER(x) ((char)tolower((BYTE)(x)))
444 #define GET_SERVER_VARIABLE_VALUE(name, place) \
447 huge_buf_sz = MAX_PACKET_SIZE; \
448 if (get_server_value(private_data->lpEcb, \
452 (place) = jk_pool_strdup(&private_data->p, \
456 #define GET_SERVER_VARIABLE_VALUE_INT(name, place, def) \
458 huge_buf_sz = MAX_PACKET_SIZE; \
459 if (get_server_value(private_data->lpEcb, \
463 (place) = atoi(huge_buf); \
464 if (((place) == 0) && (errno == EINVAL || \
465 errno == ERANGE)) { \
472 static char dll_file_path[MAX_PATH];
473 static char ini_file_name[MAX_PATH];
474 static int using_ini_file = JK_FALSE;
475 static JK_CRIT_SEC init_cs;
476 static int is_inited = JK_FALSE;
477 static int is_mapread = JK_FALSE;
479 static jk_uri_worker_map_t *uw_map = NULL;
480 static jk_map_t *workers_map = NULL;
481 static jk_map_t *rewrite_map = NULL;
482 static jk_map_t *rregexp_map = NULL;
483 static jk_map_t *jk_environment_map = NULL;
485 static jk_logger_t *logger = NULL;
486 static JK_CRIT_SEC log_cs;
487 static char *SERVER_NAME = "SERVER_NAME";
488 static char *SERVER_SOFTWARE = "SERVER_SOFTWARE";
489 static char *INSTANCE_ID = "INSTANCE_ID";
490 static char *CONTENT_TYPE = "Content-Type:text/html\r\n\r\n";
492 static char extension_uri[INTERNET_MAX_URL_LENGTH] =
493 "/jakarta/isapi_redirect.dll";
494 static char log_file[MAX_PATH * 2];
495 static char log_file_effective[MAX_PATH * 2];
496 static int log_level = JK_LOG_DEF_LEVEL;
497 static long log_rotationtime = 0;
498 static time_t log_next_rotate_time = 0;
499 static ULONGLONG log_filesize = 0;
501 static char worker_file[MAX_PATH * 2];
502 static char worker_mount_file[MAX_PATH * 2] = {0};
503 static int worker_mount_reload = JK_URIMAP_DEF_RELOAD;
504 static char rewrite_rule_file[MAX_PATH * 2] = {0};
505 static size_t shm_config_size = 0;
506 static int strip_session = 0;
507 #ifndef AUTOMATIC_AUTH_NOTIFICATION
508 static int use_auth_notification_flags = 1;
510 static int chunked_encoding_enabled = JK_FALSE;
511 static int reject_unsafe = 0;
512 static int watchdog_interval = 0;
513 static HANDLE watchdog_handle = NULL;
514 static char error_page_buf[INTERNET_MAX_URL_LENGTH] = {0};
515 static char *error_page = NULL;
517 #define URI_SELECT_OPT_PARSED 0
518 #define URI_SELECT_OPT_UNPARSED 1
519 #define URI_SELECT_OPT_ESCAPED 2
520 #define URI_SELECT_OPT_PROXY 3
522 static int uri_select_option = URI_SELECT_OPT_PROXY;
524 static jk_worker_env_t worker_env;
526 typedef struct isapi_private_data_t isapi_private_data_t;
527 struct isapi_private_data_t
531 unsigned int bytes_read_so_far;
532 int chunk_content; /* Whether we're responding with Transfer-Encoding: chunked content */
533 LPEXTENSION_CONTROL_BLOCK lpEcb;
536 typedef struct isapi_log_data_t isapi_log_data_t;
537 struct isapi_log_data_t {
538 char uri[INTERNET_MAX_URL_LENGTH];
539 char query[INTERNET_MAX_URL_LENGTH];
540 int request_matched; /* Whether this request (within a multi-request connection)
541 was handled and needs the log values adjusted */
544 typedef struct iis_info_t iis_info_t;
546 int major; /* The major version */
547 int minor; /* The minor version */
548 DWORD filter_notify_event; /* The primary filter SF_NOTIFY_* event */
551 static iis_info_t iis_info;
553 static int JK_METHOD start_response(jk_ws_service_t *s,
556 const char *const *header_names,
557 const char *const *header_values,
558 unsigned int num_of_headers);
560 static int JK_METHOD iis_read(jk_ws_service_t *s,
561 void *b, unsigned int l, unsigned int *a);
563 static int JK_METHOD iis_write(jk_ws_service_t *s, const void *b, unsigned int l);
565 static int JK_METHOD iis_done(jk_ws_service_t *s);
567 static int init_ws_service(isapi_private_data_t * private_data,
568 jk_ws_service_t *s, char **worker_name);
570 static int init_jk(char *serverName);
573 static int JK_METHOD iis_log_to_file(jk_logger_t *l, int level,
574 int used, char *what);
576 static BOOL initialize_extension(void);
578 static int read_registry_init_data(void);
580 static int get_config_parameter(LPVOID src, const char *tag,
581 char *val, DWORD sz);
583 static int get_config_bool(LPVOID src, const char *tag, int def);
585 static int get_config_int(LPVOID src, const char *tag, int def);
587 static int get_registry_config_parameter(HKEY hkey,
588 const char *tag, char *b, DWORD sz);
590 static int get_registry_config_number(HKEY hkey, const char *tag,
594 static int get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb,
596 char *buf, DWORD bufsz);
598 static int base64_encode_cert_len(int len);
600 static int base64_encode_cert(char *encoded,
601 const char *string, int len);
603 static int get_iis_info(iis_info_t *info);
605 static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length);
607 static char x2c(const char *what)
612 ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
615 (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
619 static int unescape_url(char *url)
621 register int x, y, badesc, badpath;
625 for (x = 0, y = 0; url[y]; ++x, ++y) {
629 if (!isxdigit(url[y + 1]) || !isxdigit(url[y + 2])) {
634 url[x] = x2c(&url[y + 1]);
636 if (url[x] == '/' || url[x] == '\0')
650 static void getparents(char *name)
654 /* Four paseses, as per RFC 1808 */
655 /* a) remove ./ path segments */
657 for (l = 0, w = 0; name[l] != '\0';) {
658 if (name[l] == '.' && name[l + 1] == '/'
659 && (l == 0 || name[l - 1] == '/'))
662 name[w++] = name[l++];
665 /* b) remove trailing . path, segment */
666 if (w == 1 && name[0] == '.')
668 else if (w > 1 && name[w - 1] == '.' && name[w - 2] == '/')
672 /* c) remove all xx/../ segments. (including leading ../ and /../) */
675 while (name[l] != '\0') {
676 if (name[l] == '.' && name[l + 1] == '.' && name[l + 2] == '/' &&
677 (l == 0 || name[l - 1] == '/')) {
678 register int m = l + 3, n;
682 while (l >= 0 && name[l] != '/')
689 while ((name[n] = name[m]) != '\0') {
698 /* d) remove trailing xx/.. segment. */
699 if (l == 2 && name[0] == '.' && name[1] == '.')
701 else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.'
702 && name[l - 3] == '/') {
705 while (l >= 0 && name[l] != '/')
715 /* Apache code to escape a URL */
717 #define T_OS_ESCAPE_PATH (4)
719 static const BYTE test_char_table[256] = {
720 0, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 14, 14, 14, 14, 14,
721 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
722 14, 0, 7, 6, 1, 6, 1, 1, 9, 9, 1, 0, 8, 0, 0, 10,
723 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 15, 15, 8, 15, 15,
724 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
725 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15, 15, 7, 0,
726 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
727 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 7, 15, 1, 14,
728 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
729 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
730 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
731 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
732 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
733 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
734 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
735 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6
738 #define TEST_CHAR(c, f) (test_char_table[(unsigned int)(c)] & (f))
740 static const char c2x_table[] = "0123456789abcdef";
742 static BYTE *c2x(unsigned int what, BYTE *where)
745 *where++ = c2x_table[what >> 4];
746 *where++ = c2x_table[what & 0xf];
750 static const char *status_reason(int status)
752 struct error_reasons *r;
755 while (r->status <= status) {
756 if (r->status == status)
764 static const char *status_title(int status)
766 struct error_reasons *r;
769 while (r->status <= status) {
770 if (r->status == status) {
779 return "Unknown Error";
782 static const char *status_description(int status)
784 struct error_reasons *r;
787 while (r->status <= status) {
788 if (r->status == status) {
790 return r->description;
797 return "Unknown Error";
800 static int escape_url(const char *path, char *dest, int destsize)
802 const BYTE *s = (const BYTE *)path;
803 BYTE *d = (BYTE *)dest;
804 BYTE *e = d + destsize - 1;
805 BYTE *ee = d + destsize - 3;
808 if (TEST_CHAR(*s, T_OS_ESCAPE_PATH)) {
825 * Find the first occurrence of find in s.
827 static char *stristr(const char *s, const char *find)
832 if ((c = tolower((unsigned char)(*find++))) != 0) {
836 if ((sc = tolower((unsigned char)(*s++))) == 0)
839 } while (strnicmp(s, find, len) != 0);
845 static int uri_is_web_inf(const char *uri)
847 if (stristr(uri, "/web-inf")) {
850 if (stristr(uri, "/meta-inf")) {
857 static void write_error_response(PHTTP_FILTER_CONTEXT pfc, int err)
859 char status[MAX_PATH];
860 char body[8192] = "";
864 pfc->AddResponseHeaders(pfc, CONTENT_TYPE, 0);
865 StringCbPrintf(status, MAX_PATH, "%d %s", err, status_reason(err));
866 pfc->ServerSupportFunction(pfc,
867 SF_REQ_SEND_RESPONSE_HEADER,
869 len = (DWORD)(sizeof(HTML_ERROR_HEAD) - 1);
870 pfc->WriteClient(pfc, HTML_ERROR_HEAD, &len,
872 StringCbPrintf(body, sizeof(body), HTML_ERROR_BODY_FMT,
873 status_reason(err), status_title(err),
874 status_description(err));
875 len = (DWORD)(strlen(body));
876 pfc->WriteClient(pfc, body, &len,
878 len = (DWORD)(sizeof(HTML_ERROR_TAIL) - 1);
879 pfc->WriteClient(pfc, HTML_ERROR_TAIL, &len,
883 static void write_error_message(LPEXTENSION_CONTROL_BLOCK lpEcb, int err)
886 char status[MAX_PATH];
887 char body[8192] = "";
890 char error_page_url[INTERNET_MAX_URL_LENGTH] = "";
891 int len_of_error_page;
892 StringCbPrintf(error_page_url, INTERNET_MAX_URL_LENGTH,
893 (LPCSTR)error_page, err);
894 len_of_error_page = (int)strlen(error_page_url);
895 if (!lpEcb->ServerSupportFunction(lpEcb->ConnID,
896 HSE_REQ_SEND_URL_REDIRECT_RESP,
898 (LPDWORD)&len_of_error_page,
900 lpEcb->dwHttpStatusCode = err;
906 lpEcb->dwHttpStatusCode = err;
908 StringCbPrintf(status, MAX_PATH, "%d %s", err, status_reason(err));
909 lpEcb->ServerSupportFunction(lpEcb->ConnID,
910 HSE_REQ_SEND_RESPONSE_HEADER,
913 (LPDWORD)CONTENT_TYPE);
914 /* First write the HEAD */
915 len = (DWORD)(sizeof(HTML_ERROR_HEAD) - 1);
916 lpEcb->WriteClient(lpEcb->ConnID,
917 HTML_ERROR_HEAD, &len,
919 StringCbPrintf(body, sizeof(body), HTML_ERROR_BODY_FMT,
920 status_reason(err), status_title(err),
921 status_description(err));
922 len = (DWORD)(strlen(body));
923 lpEcb->WriteClient(lpEcb->ConnID,
926 len = (DWORD)(sizeof(HTML_ERROR_TAIL) - 1);
927 lpEcb->WriteClient(lpEcb->ConnID,
928 HTML_ERROR_TAIL, &len,
934 static int JK_METHOD start_response(jk_ws_service_t *s,
937 const char *const *header_names,
938 const char *const *header_values,
939 unsigned int num_of_headers)
941 JK_TRACE_ENTER(logger);
942 if (status < 100 || status > 1000) {
943 jk_log(logger, JK_LOG_ERROR,
946 JK_TRACE_EXIT(logger);
950 if (s && s->ws_private) {
952 isapi_private_data_t *p = s->ws_private;
954 /* If we use proxy error pages, still pass
955 * through context headers needed for special status codes.
957 if (s->extension.use_server_error_pages &&
958 status >= s->extension.use_server_error_pages) {
959 if (status == JK_HTTP_UNAUTHORIZED) {
960 int found = JK_FALSE;
962 for (h = 0; h < num_of_headers; h++) {
963 if (!strcasecmp(header_names[h], "WWW-Authenticate")) {
965 * TODO: we need to save a copy of header_values[h]
966 * for later reuse in write_error_message()
967 * which is called later on form HttpExtensionProc
968 * because of use_server_error_pages.
973 if (found == JK_FALSE) {
974 jk_log(logger, JK_LOG_INFO,
975 "origin server sent 401 without"
976 " WWW-Authenticate header");
982 if (!s->response_started) {
983 char *status_str = NULL;
984 char *headers_str = NULL;
985 BOOL keep_alive = FALSE; /* Whether the downstream or us can supply content length */
987 size_t i, len_of_headers = 0;
989 s->response_started = JK_TRUE;
991 if (JK_IS_DEBUG_LEVEL(logger)) {
992 jk_log(logger, JK_LOG_DEBUG, "Starting response for URI '%s' (protocol %s)",
993 s->req_uri, s->protocol);
997 * Create the status line
1000 reason = status_reason(status);
1002 status_str = (char *)malloc((6 + strlen(reason)));
1003 StringCbPrintf(status_str, 6 + strlen(reason), "%d %s", status, reason);
1005 if (chunked_encoding_enabled) {
1006 /* Check if we've got an HTTP/1.1 response */
1007 if (!strcasecmp(s->protocol, "HTTP/1.1")) {
1009 /* Chunking only when HTTP/1.1 client and enabled */
1010 p->chunk_content = JK_TRUE;
1015 * Create response headers string
1018 /* Calculate length of headers block */
1019 for (i = 0; i < num_of_headers; i++) {
1020 len_of_headers += strlen(header_names[i]);
1021 len_of_headers += strlen(header_values[i]);
1022 len_of_headers += 4; /* extra for colon, space and crlf */
1026 * Exclude status codes that MUST NOT include message bodies
1028 if ((status == 204) || (status == 205) || (status == 304)) {
1029 p->chunk_content = JK_FALSE;
1030 /* Keep alive is still possible */
1031 if (JK_IS_DEBUG_LEVEL(logger))
1032 jk_log(logger, JK_LOG_DEBUG, "Response status %d implies no message body", status );
1034 if (p->chunk_content) {
1035 for (i = 0; i < num_of_headers; i++) {
1036 /* Check the downstream response to see whether
1037 * it's appropriate to chunk the response content
1038 * and whether it supports keeping the connection open.
1040 * This implements the rules for HTTP/1.1 message length determination
1041 * with the exception of multipart/byteranges media types.
1042 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4
1044 if (!strcasecmp(CONTENT_LENGTH_HEADER_NAME, header_names[i])) {
1045 p->chunk_content = JK_FALSE;
1046 if (JK_IS_DEBUG_LEVEL(logger))
1047 jk_log(logger, JK_LOG_DEBUG, "Response specifies Content-Length" );
1049 else if (!strcasecmp(CONNECTION_HEADER_NAME, header_names[i])
1050 && !strcasecmp(CONNECTION_CLOSE_VALUE, header_values[i])) {
1052 p->chunk_content = JK_FALSE;
1053 if (JK_IS_DEBUG_LEVEL(logger))
1054 jk_log(logger, JK_LOG_DEBUG, "Response specifies Connection: Close" );
1056 else if (!strcasecmp(TRANSFER_ENCODING_HEADER_NAME, header_names[i])
1057 && !strcasecmp(TRANSFER_ENCODING_IDENTITY_VALUE, header_values[i])) {
1058 /* HTTP states that this must include 'chunked' as the last value.
1059 * 'identity' is the same as absence of the header */
1060 p->chunk_content = JK_FALSE;
1061 if (JK_IS_DEBUG_LEVEL(logger))
1062 jk_log(logger, JK_LOG_DEBUG, "Response specifies Transfer-Encoding" );
1066 /* Provide room in the buffer for the Transfer-Encoding header if we use it. */
1067 len_of_headers += TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE_LEN + 2;
1070 /* Allocate and init the headers string */
1071 len_of_headers += 3; /* crlf and terminating null char */
1072 headers_str = (char *)malloc(len_of_headers);
1073 headers_str[0] = '\0';
1075 /* Copy headers into headers block for sending */
1076 for (i = 0; i < num_of_headers; i++) {
1077 StringCbCat(headers_str, len_of_headers, header_names[i]);
1078 StringCbCat(headers_str, len_of_headers, ": ");
1079 StringCbCat(headers_str, len_of_headers, header_values[i]);
1080 StringCbCat(headers_str, len_of_headers, CRLF);
1083 if (p->chunk_content) {
1084 /* Configure the response if chunked encoding is used */
1085 if (JK_IS_DEBUG_LEVEL(logger))
1086 jk_log(logger, JK_LOG_DEBUG, "Using Transfer-Encoding: chunked");
1088 /** We will supply the transfer-encoding to allow IIS to keep the connection open */
1091 /* Indicate to the client that the content will be chunked
1092 - We've already reserved space for this */
1093 StringCbCat(headers_str, len_of_headers, TRANSFER_ENCODING_CHUNKED_HEADER_COMPLETE);
1094 StringCbCat(headers_str, len_of_headers, CRLF);
1097 /* Terminate the headers */
1098 StringCbCat(headers_str, len_of_headers, CRLF);
1100 if (JK_IS_DEBUG_LEVEL(logger))
1101 jk_log(logger, JK_LOG_DEBUG, "%ssing Keep-Alive", (keep_alive ? "U" : "Not u"));
1104 HSE_SEND_HEADER_EX_INFO hi;
1106 /* Fill in the response */
1107 hi.pszStatus = status_str;
1108 hi.pszHeader = headers_str;
1109 hi.cchStatus = (DWORD)strlen(status_str);
1110 hi.cchHeader = (DWORD)strlen(headers_str);
1113 * Using the extended form of the API means we have to get this right,
1114 * i.e. IIS won't keep connections open if there's a Content-Length and close them if there isn't.
1116 hi.fKeepConn = keep_alive;
1118 /* Send the response to the client */
1119 rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID,
1120 HSE_REQ_SEND_RESPONSE_HEADER_EX,
1125 DWORD status_str_len = (DWORD)strlen(status_str);
1126 /* Old style response - forces Connection: close if Tomcat response doesn't
1127 specify necessary details to allow keep alive */
1128 rc = p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID,
1129 HSE_REQ_SEND_RESPONSE_HEADER,
1132 (LPDWORD)headers_str);
1136 jk_log(logger, JK_LOG_ERROR,
1137 "HSE_REQ_SEND_RESPONSE_HEADER%s failed with error=%d (0x%08x)",
1138 (keep_alive ? "_EX" : ""), GetLastError(), GetLastError());
1146 JK_TRACE_EXIT(logger);
1150 JK_LOG_NULL_PARAMS(logger);
1151 JK_TRACE_EXIT(logger);
1155 static int JK_METHOD iis_read(jk_ws_service_t *s,
1156 void *b, unsigned int l, unsigned int *a)
1158 JK_TRACE_ENTER(logger);
1160 if (s && s->ws_private && b && a) {
1161 isapi_private_data_t *p = s->ws_private;
1163 if (JK_IS_DEBUG_LEVEL(logger)) {
1164 jk_log(logger, JK_LOG_DEBUG,
1165 "Preparing to read %d bytes. "
1166 "ECB reports %d bytes total, with %d available.",
1167 l, p->lpEcb->cbTotalBytes, p->lpEcb->cbAvailable);
1173 DWORD already_read = p->lpEcb->cbAvailable - p->bytes_read_so_far;
1175 if (already_read >= l) {
1176 if (JK_IS_DEBUG_LEVEL(logger)) {
1177 jk_log(logger, JK_LOG_DEBUG,
1178 "Already read %d bytes - supplying %d bytes from buffer",
1181 memcpy(buf, p->lpEcb->lpbData + p->bytes_read_so_far, l);
1182 p->bytes_read_so_far += l;
1187 * Try to copy what we already have
1189 if (already_read > 0) {
1190 if (JK_IS_DEBUG_LEVEL(logger)) {
1191 jk_log(logger, JK_LOG_DEBUG,
1192 "Supplying %d bytes from buffer",
1195 memcpy(buf, p->lpEcb->lpbData + p->bytes_read_so_far,
1197 buf += already_read;
1199 p->bytes_read_so_far = p->lpEcb->cbAvailable;
1205 * Now try to read from the client ...
1207 if (JK_IS_DEBUG_LEVEL(logger)) {
1208 jk_log(logger, JK_LOG_DEBUG,
1209 "Attempting to read %d bytes from client", l);
1211 if (p->lpEcb->ReadClient(p->lpEcb->ConnID, buf, (LPDWORD)&l)) {
1212 /* ReadClient will succeed with dwSize == 0 for last chunk
1213 if request chunk encoded */
1217 jk_log(logger, JK_LOG_ERROR,
1218 "ReadClient failed with %d (0x%08x)", GetLastError(), GetLastError());
1219 JK_TRACE_EXIT(logger);
1224 JK_TRACE_EXIT(logger);
1228 JK_LOG_NULL_PARAMS(logger);
1229 JK_TRACE_EXIT(logger);
1234 * Writes a buffer to the ISAPI response.
1236 static int isapi_write_client(isapi_private_data_t *p, const char *buf, unsigned int write_length)
1238 unsigned int written = 0;
1239 DWORD try_to_write = 0;
1241 JK_TRACE_ENTER(logger);
1243 if (JK_IS_DEBUG_LEVEL(logger))
1244 jk_log(logger, JK_LOG_DEBUG, "Writing %d bytes of data to client", write_length);
1246 while (written < write_length) {
1247 try_to_write = write_length - written;
1248 if (!p->lpEcb->WriteClient(p->lpEcb->ConnID,
1249 (LPVOID)(buf + written), &try_to_write, HSE_IO_SYNC)) {
1250 jk_log(logger, JK_LOG_ERROR,
1251 "WriteClient failed with %d (0x%08x)", GetLastError(), GetLastError());
1252 JK_TRACE_EXIT(logger);
1255 written += try_to_write;
1256 if (JK_IS_DEBUG_LEVEL(logger))
1257 jk_log(logger, JK_LOG_DEBUG, "Wrote %d bytes of data successfully", try_to_write);
1259 JK_TRACE_EXIT(logger);
1264 * Write content to the response.
1265 * If chunked encoding has been enabled and the client supports it
1266 *(and it's appropriate for the response), then this will write a
1267 * single "Transfer-Encoding: chunked" chunk
1269 static int JK_METHOD iis_write(jk_ws_service_t *s, const void *b, unsigned int l)
1271 JK_TRACE_ENTER(logger);
1274 JK_TRACE_EXIT(logger);
1278 if (s && s->ws_private && b) {
1279 isapi_private_data_t *p = s->ws_private;
1280 const char *buf = (const char *)b;
1283 JK_TRACE_EXIT(logger);
1287 if (!s->response_started) {
1288 start_response(s, 200, NULL, NULL, NULL, 0);
1291 if (p->chunk_content == JK_FALSE) {
1292 if (isapi_write_client(p, buf, l) == JK_FALSE) {
1293 JK_TRACE_EXIT(logger);
1298 char chunk_header[CHUNK_HEADER_BUFFER_SIZE];
1300 /* Construct chunk header : HEX CRLF*/
1301 StringCbPrintf(chunk_header, CHUNK_HEADER_BUFFER_SIZE, "%X%s", l, CRLF);
1303 if (iis_info.major >= 6) {
1304 HSE_RESPONSE_VECTOR response_vector;
1305 HSE_VECTOR_ELEMENT response_elements[3];
1307 response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER;
1308 response_elements[0].pvContext = chunk_header;
1309 response_elements[0].cbOffset = 0;
1310 response_elements[0].cbSize = strlen(chunk_header);
1312 response_elements[1].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER;
1313 response_elements[1].pvContext = (PVOID)buf;
1314 response_elements[1].cbOffset = 0;
1315 response_elements[1].cbSize = l;
1317 response_elements[2].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER;
1318 response_elements[2].pvContext = CRLF;
1319 response_elements[2].cbOffset = 0;
1320 response_elements[2].cbSize = CRLF_LEN;
1322 response_vector.dwFlags = HSE_IO_SYNC;
1323 response_vector.pszStatus = NULL;
1324 response_vector.pszHeaders = NULL;
1325 response_vector.nElementCount = 3;
1326 response_vector.lpElementArray = response_elements;
1328 if (JK_IS_DEBUG_LEVEL(logger))
1329 jk_log(logger, JK_LOG_DEBUG,
1330 "Using vector write for chunk encoded %d byte chunk", l);
1332 if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID,
1333 HSE_REQ_VECTOR_SEND,
1337 jk_log(logger, JK_LOG_ERROR,
1338 "Vector write of chunk encoded response failed with %d (0x%08x)",
1339 GetLastError(), GetLastError());
1340 JK_TRACE_EXIT(logger);
1344 /* Write chunk header */
1345 if (JK_IS_DEBUG_LEVEL(logger))
1346 jk_log(logger, JK_LOG_DEBUG,
1347 "Using chunked encoding - writing chunk header for %d byte chunk", l);
1349 if (!isapi_write_client(p, chunk_header, (unsigned int)strlen(chunk_header))) {
1350 jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk header failed");
1351 JK_TRACE_EXIT(logger);
1355 /* Write chunk body (or simple body block) */
1356 if (JK_IS_DEBUG_LEVEL(logger)) {
1357 jk_log(logger, JK_LOG_DEBUG, "Writing %s of size %d",
1358 (p->chunk_content ? "chunk body" : "simple response"), l);
1360 if (!isapi_write_client(p, buf, l)) {
1361 jk_log(logger, JK_LOG_ERROR, "WriteClient for response body chunk failed");
1362 JK_TRACE_EXIT(logger);
1365 /* Write chunk trailer */
1366 if (JK_IS_DEBUG_LEVEL(logger)) {
1367 jk_log(logger, JK_LOG_DEBUG, "Using chunked encoding - writing chunk trailer");
1370 if (!isapi_write_client(p, CRLF, CRLF_LEN)) {
1371 jk_log(logger, JK_LOG_ERROR, "WriteClient for chunk trailer failed");
1372 JK_TRACE_EXIT(logger);
1378 JK_TRACE_EXIT(logger);
1383 JK_LOG_NULL_PARAMS(logger);
1384 JK_TRACE_EXIT(logger);
1389 * In the case of a Transfer-Encoding: chunked response, this will write the terminator chunk.
1391 static int JK_METHOD iis_done(jk_ws_service_t *s)
1393 JK_TRACE_ENTER(logger);
1395 if (s && s->ws_private) {
1396 isapi_private_data_t *p = s->ws_private;
1398 if (p->chunk_content == JK_FALSE) {
1399 JK_TRACE_EXIT(logger);
1403 /* Write last chunk + terminator */
1404 if (iis_info.major >= 6) {
1405 HSE_RESPONSE_VECTOR response_vector;
1406 HSE_VECTOR_ELEMENT response_elements[1];
1408 response_elements[0].ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER;
1409 response_elements[0].pvContext = CHUNKED_ENCODING_TRAILER;
1410 response_elements[0].cbOffset = 0;
1411 response_elements[0].cbSize = CHUNKED_ENCODING_TRAILER_LEN;
1413 /* HSE_IO_FINAL_SEND lets IIS process the response to the client before we return */
1414 response_vector.dwFlags = HSE_IO_SYNC | HSE_IO_FINAL_SEND;
1415 response_vector.pszStatus = NULL;
1416 response_vector.pszHeaders = NULL;
1417 response_vector.nElementCount = 1;
1418 response_vector.lpElementArray = response_elements;
1420 if (JK_IS_DEBUG_LEVEL(logger))
1421 jk_log(logger, JK_LOG_DEBUG,
1422 "Using vector write to terminate chunk encoded response.");
1424 if (!p->lpEcb->ServerSupportFunction(p->lpEcb->ConnID,
1425 HSE_REQ_VECTOR_SEND,
1429 jk_log(logger, JK_LOG_ERROR,
1430 "Vector termination of chunk encoded response failed with %d (0x%08x)",
1431 GetLastError(), GetLastError());
1432 JK_TRACE_EXIT(logger);
1437 if (JK_IS_DEBUG_LEVEL(logger))
1438 jk_log(logger, JK_LOG_DEBUG, "Terminating chunk encoded response");
1440 if (!isapi_write_client(p, CHUNKED_ENCODING_TRAILER, CHUNKED_ENCODING_TRAILER_LEN)) {
1441 jk_log(logger, JK_LOG_ERROR,
1442 "WriteClient for chunked response terminator failed with %d (0x%08x)",
1443 GetLastError(), GetLastError());
1444 JK_TRACE_EXIT(logger);
1449 JK_TRACE_EXIT(logger);
1453 JK_LOG_NULL_PARAMS(logger);
1454 JK_TRACE_EXIT(logger);
1458 BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)
1462 ULONG http_filter_revision = HTTP_FILTER_REVISION;
1464 pVer->dwFilterVersion = pVer->dwServerFilterVersion;
1466 if (pVer->dwFilterVersion > http_filter_revision) {
1467 pVer->dwFilterVersion = http_filter_revision;
1469 JK_ENTER_CS(&(init_cs), rc);
1471 rv = initialize_extension();
1473 JK_LEAVE_CS(&(init_cs), rc);
1474 pVer->dwFlags = SF_NOTIFY_ORDER_HIGH |
1475 SF_NOTIFY_SECURE_PORT |
1476 SF_NOTIFY_NONSECURE_PORT |
1478 iis_info.filter_notify_event;
1480 StringCbCopy(pVer->lpszFilterDesc, SF_MAX_FILTER_DESC_LEN, (VERSION_STRING));
1485 #define AP_REG_ICASE 0x01 /** use a case-insensitive match */
1486 #define AP_REG_NEWLINE 0x02 /** don't match newlines against '.' etc */
1487 #define AP_REG_NOTBOL 0x04 /** ^ will not match against start-of-string */
1488 #define AP_REG_NOTEOL 0x08 /** $ will not match against end-of-string */
1490 #define AP_REG_EXTENDED (0) /** unused */
1491 #define AP_REG_NOSUB (0) /** unused */
1492 /** The max number of regex captures that can be expanded by ap_pregsub */
1493 #define AP_MAX_REG_MATCH 10
1497 AP_REG_ASSERT = 1, /** internal error ? */
1498 AP_REG_ESPACE, /** failed to get memory */
1499 AP_REG_INVARG, /** invalid argument */
1500 AP_REG_NOMATCH /** match failed */
1503 /* The structure representing a compiled regular expression. */
1507 size_t re_erroffset;
1512 /* The structure in which a captured offset is returned. */
1519 /* Table of error strings corresponding to POSIX error codes; must be
1520 * kept in synch with include/ap_regex.h's AP_REG_E* definitions. */
1522 static const char *const pstring[] = {
1523 "", /* Dummy for value 0 */
1524 "internal error", /* AP_REG_ASSERT */
1525 "failed to get memory", /* AP_REG_ESPACE */
1526 "bad argument", /* AP_REG_INVARG */
1527 "match failed" /* AP_REG_NOMATCH */
1530 static size_t ap_regerror(int errcode, const ap_regex_t *preg,
1531 char *errbuf, size_t errbuf_size)
1533 const char *message, *addmessage;
1534 size_t length, addlength;
1536 message = (errcode >= (int)(sizeof(pstring)/sizeof(char *))) ?
1537 "unknown error code" : pstring[errcode];
1538 length = strlen(message) + 1;
1540 addmessage = " at offset ";
1541 addlength = (preg != NULL && (int)preg->re_erroffset != -1)?
1542 strlen(addmessage) + 6 : 0;
1544 if (errbuf_size > 0) {
1545 if (addlength > 0 && errbuf_size >= length + addlength)
1546 StringCbPrintf(errbuf, sizeof(errbuf), "%s%s%-6d",
1547 message, addmessage,
1548 (int)preg->re_erroffset);
1550 strncpy(errbuf, message, errbuf_size - 1);
1551 errbuf[errbuf_size-1] = 0;
1555 return length + addlength;
1558 /*************************************************
1559 * Free store held by a regex *
1560 *************************************************/
1562 static void ap_regfree(ap_regex_t *preg)
1564 (pcre_free)(preg->re_pcre);
1570 /*************************************************
1571 * Compile a regular expression *
1572 *************************************************/
1576 preg points to a structure for recording the compiled expression
1577 pattern the pattern to compile
1578 cflags compilation flags
1580 Returns: 0 on success
1581 various non-zero codes on failure
1584 static int ap_regcomp(ap_regex_t *preg, const char *pattern, int cflags)
1586 const char *errorptr;
1590 if ((cflags & AP_REG_ICASE) != 0) options |= PCRE_CASELESS;
1591 if ((cflags & AP_REG_NEWLINE) != 0) options |= PCRE_MULTILINE;
1593 preg->re_pcre = pcre_compile(pattern, options, &errorptr, &erroffset, NULL);
1594 preg->re_erroffset = erroffset;
1596 if (preg->re_pcre == NULL) return AP_REG_INVARG;
1598 preg->re_nsub = pcre_info((const pcre *)preg->re_pcre, NULL, NULL);
1602 /*************************************************
1603 * Match a regular expression *
1604 *************************************************/
1606 /* Unfortunately, PCRE requires 3 ints of working space for each captured
1607 substring, so we have to get and release working store instead of just using
1608 the POSIX structures as was done in earlier releases when PCRE needed only 2
1609 ints. However, if the number of possible capturing brackets is small, use a
1610 block of store on the stack, to reduce the use of malloc/free. The threshold is
1611 in a macro that can be changed at configure time. */
1613 static int ap_regexec(const ap_regex_t *preg, const char *string,
1614 int nmatch, ap_regmatch_t pmatch[],
1619 int *ovector = NULL;
1620 int small_ovector[POSIX_MALLOC_THRESHOLD * 3];
1621 int allocated_ovector = 0;
1623 if ((eflags & AP_REG_NOTBOL) != 0) options |= PCRE_NOTBOL;
1624 if ((eflags & AP_REG_NOTEOL) != 0) options |= PCRE_NOTEOL;
1626 ((ap_regex_t *)preg)->re_erroffset = (size_t)(-1); /* Only has meaning after compile */
1629 if (nmatch <= POSIX_MALLOC_THRESHOLD) {
1630 ovector = &(small_ovector[0]);
1633 ovector = (int *)malloc(sizeof(int) * nmatch * 3);
1634 if (ovector == NULL)
1635 return AP_REG_ESPACE;
1636 allocated_ovector = 1;
1640 rc = pcre_exec((const pcre *)preg->re_pcre, NULL, string,
1641 (int)strlen(string),
1642 0, options, ovector, nmatch * 3);
1645 rc = nmatch; /* All captured slots were filled in */
1648 for (i = 0; i < rc; i++) {
1649 pmatch[i].rm_so = ovector[i*2];
1650 pmatch[i].rm_eo = ovector[i*2+1];
1652 if (allocated_ovector)
1654 for (; i < nmatch; i++)
1655 pmatch[i].rm_so = pmatch[i].rm_eo = -1;
1659 if (allocated_ovector)
1662 case PCRE_ERROR_NOMATCH: return AP_REG_NOMATCH;
1663 case PCRE_ERROR_NULL: return AP_REG_INVARG;
1664 case PCRE_ERROR_BADOPTION: return AP_REG_INVARG;
1665 case PCRE_ERROR_BADMAGIC: return AP_REG_INVARG;
1666 case PCRE_ERROR_UNKNOWN_NODE: return AP_REG_ASSERT;
1667 case PCRE_ERROR_NOMEMORY: return AP_REG_ESPACE;
1668 #ifdef PCRE_ERROR_MATCHLIMIT
1669 case PCRE_ERROR_MATCHLIMIT: return AP_REG_ESPACE;
1671 #ifdef PCRE_ERROR_BADUTF8
1672 case PCRE_ERROR_BADUTF8: return AP_REG_INVARG;
1674 #ifdef PCRE_ERROR_BADUTF8_OFFSET
1675 case PCRE_ERROR_BADUTF8_OFFSET: return AP_REG_INVARG;
1677 default: return AP_REG_ASSERT;
1682 /* This function substitutes for $0-$9, filling in regular expression
1683 * submatches. Pass it the same nmatch and pmatch arguments that you
1684 * passed ap_regexec(). pmatch should not be greater than the maximum number
1685 * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t.
1687 * input should be the string with the $-expressions, source should be the
1688 * string that was matched against.
1690 * It returns the substituted string, or NULL on error.
1692 * Parts of this code are based on Henry Spencer's regsub(), from his
1693 * AT&T V8 regexp package.
1696 static char *ap_pregsub(const char *input,
1697 const char *source, size_t nmatch,
1698 ap_regmatch_t pmatch[])
1700 const char *src = input;
1711 /* First pass, find the size */
1714 while ((c = *src++) != '\0') {
1717 else if (c == '$' && isdigit((unsigned char)*src))
1722 if (no > 9) { /* Ordinary character. */
1723 if (c == '\\' && (*src == '$' || *src == '&'))
1727 else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
1728 len += pmatch[no].rm_eo - pmatch[no].rm_so;
1733 dest = dst = calloc(1, len + 1);
1735 /* Now actually fill in the string */
1739 while ((c = *src++) != '\0') {
1742 else if (c == '$' && isdigit((unsigned char)*src))
1747 if (no > 9) { /* Ordinary character. */
1748 if (c == '\\' && (*src == '$' || *src == '&'))
1752 else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
1753 len = pmatch[no].rm_eo - pmatch[no].rm_so;
1754 memcpy(dst, source + pmatch[no].rm_so, len);
1763 static int simple_rewrite(char *uri)
1767 char buf[INTERNET_MAX_URL_LENGTH];
1768 for (i = 0; i < jk_map_size(rewrite_map); i++) {
1769 const char *src = jk_map_name_at(rewrite_map, i);
1771 continue; /* Skip regexp rewrites */
1772 if (strncmp(uri, src, strlen(src)) == 0) {
1773 StringCbCopy(buf, INTERNET_MAX_URL_LENGTH, jk_map_value_at(rewrite_map, i));
1774 StringCbCat(buf, INTERNET_MAX_URL_LENGTH, uri + strlen(src));
1775 StringCbCopy(uri, INTERNET_MAX_URL_LENGTH, buf);
1783 static int rregex_rewrite(char *uri)
1785 ap_regmatch_t regm[AP_MAX_REG_MATCH];
1789 for (i = 0; i < jk_map_size(rregexp_map); i++) {
1790 ap_regex_t *regexp = (ap_regex_t *)jk_map_value_at(rregexp_map, i);
1791 if (!ap_regexec(regexp, uri, AP_MAX_REG_MATCH, regm, 0)) {
1792 char *subs = ap_pregsub(regexp->fake, uri,
1793 AP_MAX_REG_MATCH, regm);
1795 char buf[INTERNET_MAX_URL_LENGTH];
1796 size_t diffsz = strlen(subs) - (regm[0].rm_eo - regm[0].rm_so);
1797 memcpy(&buf[0], uri, regm[0].rm_so);
1798 StringCbCopy(&buf[regm[0].rm_so], INTERNET_MAX_URL_LENGTH - regm[0].rm_so, subs);
1799 StringCbCat(&buf[0], INTERNET_MAX_URL_LENGTH, uri + regm[0].rm_eo);
1800 StringCbCopy(uri, INTERNET_MAX_URL_LENGTH, &buf[0]);
1810 DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc,
1811 DWORD dwNotificationType, LPVOID pvNotification)
1816 if (is_inited && !is_mapread) {
1817 char serverName[MAX_SERVERNAME] = "";
1818 char instanceId[MAX_INSTANCEID] = "";
1819 DWORD dwLen = MAX_SERVERNAME - MAX_INSTANCEID - 1;
1821 if (pfc->GetServerVariable(pfc, SERVER_NAME, serverName, &dwLen)) {
1823 serverName[dwLen - 1] = '\0';
1824 dwLen = MAX_INSTANCEID;
1825 if (pfc->GetServerVariable(pfc, INSTANCE_ID, instanceId, &dwLen)) {
1827 instanceId[dwLen - 1] = '\0';
1828 StringCbCat(serverName, MAX_SERVERNAME, "_");
1829 StringCbCat(serverName, MAX_SERVERNAME, instanceId);
1833 JK_ENTER_CS(&(init_cs), rc);
1834 if (!is_mapread && init_jk(serverName))
1835 is_mapread = JK_TRUE;
1836 JK_LEAVE_CS(&(init_cs), rc);
1838 /* If we can't read the map we become dormant */
1840 is_inited = JK_FALSE;
1843 /* In case the initialization failed
1844 * return error. This will make entire IIS
1845 * unusable like with Apache servers
1847 SetLastError(ERROR_INVALID_FUNCTION);
1848 return SF_STATUS_REQ_ERROR;
1850 if (iis_info.filter_notify_event == dwNotificationType) {
1851 char uri[INTERNET_MAX_URL_LENGTH];
1852 char snuri[INTERNET_MAX_URL_LENGTH] = "/";
1853 char Host[INTERNET_MAX_URL_LENGTH] = "";
1854 char Port[INTERNET_MAX_URL_LENGTH] = "";
1855 char Translate[INTERNET_MAX_URL_LENGTH];
1856 char squery[INTERNET_MAX_URL_LENGTH] = "";
1857 char swindex[MAX_INSTANCEID] = "";
1858 BOOL(WINAPI * GetHeader)
1859 (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName,
1860 LPVOID lpvBuffer, LPDWORD lpdwSize);
1861 BOOL(WINAPI * SetHeader)
1862 (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName,
1864 BOOL(WINAPI * AddHeader)
1865 (struct _HTTP_FILTER_CONTEXT * pfc, LPSTR lpszName,
1868 DWORD sz = sizeof(uri);
1869 DWORD szHost = sizeof(Host);
1870 DWORD szPort = sizeof(Port);
1871 DWORD szTranslate = sizeof(Translate);
1873 if (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) {
1875 ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->GetHeader;
1877 ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->SetHeader;
1879 ((PHTTP_FILTER_AUTH_COMPLETE_INFO) pvNotification)->AddHeader;
1883 ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->GetHeader;
1885 ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->SetHeader;
1887 ((PHTTP_FILTER_PREPROC_HEADERS) pvNotification)->AddHeader;
1890 if (JK_IS_DEBUG_LEVEL(logger))
1891 jk_log(logger, JK_LOG_DEBUG, "Filter started");
1894 * Just in case somebody set these headers in the request!
1896 SetHeader(pfc, URI_HEADER_NAME, NULL);
1897 SetHeader(pfc, QUERY_HEADER_NAME, NULL);
1898 SetHeader(pfc, WORKER_HEADER_NAME, NULL);
1899 SetHeader(pfc, WORKER_HEADER_INDEX, NULL);
1900 SetHeader(pfc, TOMCAT_TRANSLATE_HEADER_NAME, NULL);
1902 // Suppress logging of original uri/query when we don't map a URL
1903 if (pfc->pFilterContext) {
1904 isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext;
1905 ld->request_matched = JK_FALSE;
1908 if (!GetHeader(pfc, "url", (LPVOID) uri, (LPDWORD) & sz)) {
1909 jk_log(logger, JK_LOG_ERROR,
1910 "error while getting the url");
1911 return SF_STATUS_REQ_ERROR;
1916 const char *worker = NULL;
1917 rule_extension_t *extensions;
1918 int worker_index = -1;
1919 query = strchr(uri, '?');
1922 StringCbCopy(squery, INTERNET_MAX_URL_LENGTH, query);
1925 rc = unescape_url(uri);
1926 if (rc == BAD_REQUEST) {
1927 jk_log(logger, JK_LOG_ERROR,
1928 "[%s] contains one or more invalid escape sequences.",
1930 write_error_response(pfc, 400);
1931 return SF_STATUS_REQ_FINISHED;
1933 else if (rc == BAD_PATH) {
1934 jk_log(logger, JK_LOG_EMERG,
1935 "[%s] contains forbidden escape sequences.",
1937 write_error_response(pfc, 404);
1938 return SF_STATUS_REQ_FINISHED;
1942 GetServerVariable(pfc, SERVER_NAME, (LPVOID) Host,
1943 (LPDWORD) & szHost)) {
1945 Host[szHost - 1] = '\0';
1950 GetServerVariable(pfc, "SERVER_PORT", (LPVOID) Port,
1951 (LPDWORD) & szPort)) {
1953 Port[szPort - 1] = '\0';
1956 szPort = atoi(Port);
1957 if (szPort != 80 && szPort != 443 && szHost > 0) {
1958 StringCbCat(Host, INTERNET_MAX_URL_LENGTH, ":");
1959 StringCbCat(Host, INTERNET_MAX_URL_LENGTH, Port);
1962 StringCbCat(snuri, INTERNET_MAX_URL_LENGTH, Host);
1963 worker = map_uri_to_worker_ext(uw_map, uri, snuri,
1964 &extensions, &worker_index, logger);
1967 worker = map_uri_to_worker_ext(uw_map, uri, NULL,
1968 &extensions, &worker_index, logger);
1971 * Check if somebody is feading us with his own TOMCAT data headers.
1972 * We reject such postings !
1977 if (JK_IS_DEBUG_LEVEL(logger))
1978 jk_log(logger, JK_LOG_DEBUG,
1979 "check if [%s] points to the web-inf directory",
1982 if (uri_is_web_inf(uri)) {
1983 jk_log(logger, JK_LOG_EMERG,
1984 "[%s] points to the web-inf or meta-inf directory. "
1985 "Somebody tries to hack into the site!!!",
1988 write_error_response(pfc, 404);
1989 return SF_STATUS_REQ_FINISHED;
1992 /* This is a servlet, should redirect ... */
1993 if (JK_IS_DEBUG_LEVEL(logger))
1994 jk_log(logger, JK_LOG_DEBUG,
1995 "[%s] is a servlet url - should redirect to %s",
1998 /* get URI we should forward */
1999 if (uri_select_option == URI_SELECT_OPT_UNPARSED) {
2000 /* get original unparsed URI */
2001 GetHeader(pfc, "url", (LPVOID) uri, (LPDWORD) & sz);
2002 /* restore terminator for uri portion */
2004 *(query - 1) = '\0';
2005 if (JK_IS_DEBUG_LEVEL(logger))
2006 jk_log(logger, JK_LOG_DEBUG,
2007 "forwarding original URI [%s]",
2011 else if (uri_select_option == URI_SELECT_OPT_ESCAPED) {
2012 if (!escape_url(uri, snuri, INTERNET_MAX_URL_LENGTH)) {
2013 jk_log(logger, JK_LOG_ERROR,
2014 "[%s] re-encoding request exceeds maximum buffer size.",
2016 write_error_response(pfc, 400);
2017 return SF_STATUS_REQ_FINISHED;
2019 if (JK_IS_DEBUG_LEVEL(logger))
2020 jk_log(logger, JK_LOG_DEBUG,
2021 "fowarding escaped URI [%s]",
2025 else if (uri_select_option == URI_SELECT_OPT_PROXY) {
2026 if (!jk_canonenc(uri, snuri, INTERNET_MAX_URL_LENGTH)) {
2027 jk_log(logger, JK_LOG_ERROR,
2028 "[%s] re-encoding request exceeds maximum buffer size.",
2030 write_error_response(pfc, 400);
2031 return SF_STATUS_REQ_FINISHED;
2033 if (JK_IS_DEBUG_LEVEL(logger))
2034 jk_log(logger, JK_LOG_DEBUG,
2035 "fowarding escaped URI [%s]",
2042 /* Do a simple rewrite .
2043 * Note that URI can be escaped, so thus the rule has
2044 * to be in that case.
2046 * TODO: Add more advanced regexp rewrite.
2048 if (JK_IS_DEBUG_LEVEL(logger)) {
2049 char duri[INTERNET_MAX_URL_LENGTH];
2050 StringCbCopy(duri, INTERNET_MAX_URL_LENGTH, forwardURI);
2051 if (simple_rewrite(forwardURI)) {
2052 jk_log(logger, JK_LOG_DEBUG,
2053 "rewritten URI [%s]->[%s]",
2056 else if (rregex_rewrite(forwardURI)) {
2057 jk_log(logger, JK_LOG_DEBUG,
2058 "rewritten URI [%s]->[%s]",
2063 if (!simple_rewrite(forwardURI))
2064 rregex_rewrite(forwardURI);
2067 itoa(worker_index, swindex, 10);
2068 if (!AddHeader(pfc, URI_HEADER_NAME, forwardURI) ||
2069 ((strlen(squery) > 0)
2070 ? !AddHeader(pfc, QUERY_HEADER_NAME, squery) : FALSE) ||
2071 !AddHeader(pfc, WORKER_HEADER_NAME, (LPSTR)worker) ||
2072 !AddHeader(pfc, WORKER_HEADER_INDEX, swindex) ||
2073 !SetHeader(pfc, "url", extension_uri)) {
2074 jk_log(logger, JK_LOG_ERROR,
2075 "error while adding request headers");
2076 SetLastError(ERROR_INVALID_PARAMETER);
2077 return SF_STATUS_REQ_ERROR;
2080 /* Move Translate: header to a temporary header so
2081 * that the extension proc will be called.
2082 * This allows the servlet to handle 'Translate: f'.
2085 (pfc, TRANSLATE_HEADER, (LPVOID) Translate,
2086 (LPDWORD) & szTranslate) && Translate != NULL
2087 && szTranslate > 0) {
2089 (pfc, TOMCAT_TRANSLATE_HEADER_NAME, Translate)) {
2090 jk_log(logger, JK_LOG_ERROR,
2091 "error while adding Tomcat-Translate headers");
2092 return SF_STATUS_REQ_ERROR;
2094 SetHeader(pfc, "Translate:", NULL);
2096 if (!pfc->pFilterContext) {
2097 isapi_log_data_t *ld = (isapi_log_data_t *)pfc->AllocMem(pfc, sizeof(isapi_log_data_t), 0);
2099 jk_log(logger, JK_LOG_ERROR,
2100 "error while allocating memory");
2101 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
2102 return SF_STATUS_REQ_ERROR;
2104 memset(ld, 0, sizeof(isapi_log_data_t));
2105 StringCbCopy(ld->uri, INTERNET_MAX_URL_LENGTH, forwardURI);
2106 StringCbCopy(ld->query, INTERNET_MAX_URL_LENGTH, squery);
2107 ld->request_matched = JK_TRUE;
2108 pfc->pFilterContext = ld;
2110 isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext;
2111 memset(ld, 0, sizeof(isapi_log_data_t));
2112 StringCbCopy(ld->uri, INTERNET_MAX_URL_LENGTH, forwardURI);
2113 StringCbCopy(ld->query, INTERNET_MAX_URL_LENGTH, squery);
2114 ld->request_matched = JK_TRUE;
2118 if (JK_IS_DEBUG_LEVEL(logger))
2119 jk_log(logger, JK_LOG_DEBUG,
2120 "[%s] is not a servlet url", uri);
2121 if (strip_session) {
2122 char *jsessionid = strstr(uri, JK_PATH_SESSION_IDENTIFIER);
2124 if (JK_IS_DEBUG_LEVEL(logger))
2125 jk_log(logger, JK_LOG_DEBUG,
2126 "removing session identifier [%s] for non servlet url [%s]",
2129 SetHeader(pfc, "url", uri);
2135 else if (dwNotificationType == SF_NOTIFY_LOG) {
2136 if (pfc->pFilterContext) {
2137 isapi_log_data_t *ld = (isapi_log_data_t *)pfc->pFilterContext;
2138 if (ld->request_matched) {
2139 HTTP_FILTER_LOG *pl = (HTTP_FILTER_LOG *)pvNotification;
2140 pl->pszTarget = ld->uri;
2141 pl->pszParameters = ld->query;
2145 return SF_STATUS_REQ_NEXT_NOTIFICATION;
2149 BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO * pVer)
2154 pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
2156 StringCbCopy(pVer->lpszExtensionDesc, HSE_MAX_EXT_DLL_NAME_LEN, (VERSION_STRING));
2159 JK_ENTER_CS(&(init_cs), rc);
2161 rv = initialize_extension();
2163 JK_LEAVE_CS(&(init_cs), rc);
2168 DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpEcb)
2171 DWORD rc = HSE_STATUS_ERROR;
2173 lpEcb->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR;
2175 JK_TRACE_ENTER(logger);
2178 if (is_inited && !is_mapread) {
2179 char serverName[MAX_SERVERNAME] = "";
2180 char instanceId[MAX_INSTANCEID] = "";
2182 DWORD dwLen = MAX_SERVERNAME - MAX_INSTANCEID - 1;
2183 if (lpEcb->GetServerVariable(lpEcb->ConnID,
2184 SERVER_NAME, serverName, &dwLen)) {
2186 serverName[dwLen - 1] = '\0';
2187 dwLen = MAX_INSTANCEID;
2188 if (lpEcb->GetServerVariable(lpEcb->ConnID,
2189 INSTANCE_ID, instanceId, &dwLen)) {
2191 instanceId[dwLen - 1] = '\0';
2192 StringCbCat(serverName, MAX_SERVERNAME, "_");
2193 StringCbCat(serverName, MAX_SERVERNAME, instanceId);
2197 JK_ENTER_CS(&(init_cs), rv);
2198 if (!is_mapread && init_jk(serverName))
2199 is_mapread = JK_TRUE;
2200 JK_LEAVE_CS(&(init_cs), rv);
2203 is_inited = JK_FALSE;
2207 isapi_private_data_t private_data;
2209 jk_pool_atom_t buf[SMALL_POOL_SIZE];
2212 if (!watchdog_interval)
2213 wc_maintain(logger);
2214 jk_init_ws_service(&s);
2215 jk_open_pool(&private_data.p, buf, sizeof(buf));
2217 private_data.bytes_read_so_far = 0;
2218 private_data.lpEcb = lpEcb;
2219 private_data.chunk_content = JK_FALSE;
2221 s.ws_private = &private_data;
2222 s.pool = &private_data.p;
2224 if (init_ws_service(&private_data, &s, &worker_name)) {
2225 jk_worker_t *worker = wc_get_worker_for_name(worker_name, logger);
2227 if (JK_IS_DEBUG_LEVEL(logger))
2228 jk_log(logger, JK_LOG_DEBUG,
2229 "%s a worker for name %s",
2230 worker ? "got" : "could not get", worker_name);
2233 jk_endpoint_t *e = NULL;
2234 if (worker->get_endpoint(worker, &e, logger)) {
2235 int is_error = JK_HTTP_SERVER_ERROR;
2237 if ((result = e->service(e, &s, logger, &is_error)) > 0) {
2238 if (s.extension.use_server_error_pages &&
2239 s.http_response_status >= s.extension.use_server_error_pages) {
2240 if (JK_IS_DEBUG_LEVEL(logger))
2241 jk_log(logger, JK_LOG_DEBUG, "Forwarding status=%d"
2243 s.http_response_status, worker_name);
2244 lpEcb->dwHttpStatusCode = s.http_response_status;
2245 write_error_message(lpEcb, s.http_response_status);
2248 rc = HSE_STATUS_SUCCESS;
2249 lpEcb->dwHttpStatusCode = s.http_response_status;
2250 if (JK_IS_DEBUG_LEVEL(logger))
2251 jk_log(logger, JK_LOG_DEBUG,
2252 "service() returned OK");
2256 if ((result == JK_CLIENT_ERROR) && (is_error == JK_HTTP_OK)) {
2257 jk_log(logger, JK_LOG_INFO,
2258 "service() failed because client aborted connection");
2261 jk_log(logger, JK_LOG_ERROR,
2262 "service() failed with http error %d", is_error);
2264 lpEcb->dwHttpStatusCode = is_error;
2265 write_error_message(lpEcb, is_error);
2267 e->done(&e, logger);
2270 jk_log(logger, JK_LOG_ERROR,
2271 "Failed to obtain an endpoint to service request - "
2272 "your connection_pool_size is probably less than the threads in your web server!");
2276 jk_log(logger, JK_LOG_ERROR,
2277 "could not get a worker for name %s",
2282 jk_log(logger, JK_LOG_ERROR,
2283 "failed to init service for request.");
2285 jk_close_pool(&private_data.p);
2288 jk_log(logger, JK_LOG_ERROR,
2292 JK_TRACE_EXIT(logger);
2298 BOOL WINAPI TerminateExtension(DWORD dwFlags)
2300 return TerminateFilter(dwFlags);
2303 BOOL WINAPI TerminateFilter(DWORD dwFlags)
2307 UNREFERENCED_PARAMETER(dwFlags);
2309 JK_ENTER_CS(&(init_cs), rc);
2311 jk_log(logger, JK_LOG_INFO, "%s stopping", (FULL_VERSION_STRING));
2312 is_inited = JK_FALSE;
2313 watchdog_interval = 0;
2314 if (watchdog_handle) {
2315 WaitForSingleObject(watchdog_handle, INFINITE);
2316 CloseHandle(watchdog_handle);
2317 watchdog_handle = NULL;
2320 uri_worker_map_free(&uw_map, logger);
2321 is_mapread = JK_FALSE;
2324 jk_map_free(&workers_map);
2327 jk_map_free(&rewrite_map);
2331 for (i = 0; i < jk_map_size(rregexp_map); i++) {
2332 ap_regex_t *regexp = (ap_regex_t *)jk_map_value_at(rregexp_map, i);
2338 jk_map_free(&rregexp_map);
2342 JK_ENTER_CS(&(log_cs), rc);
2344 jk_close_file_logger(&logger);
2346 JK_LEAVE_CS(&(log_cs), rc);
2348 JK_LEAVE_CS(&(init_cs), rc);
2354 BOOL WINAPI DllMain(HINSTANCE hInst, // Instance Handle of the DLL
2355 ULONG ulReason, // Reason why NT called this DLL
2356 LPVOID lpReserved) // Reserved parameter for future use
2359 BOOL fReturn = TRUE;
2360 char fname[MAX_PATH];
2362 UNREFERENCED_PARAMETER(lpReserved);
2365 case DLL_PROCESS_ATTACH:
2366 if (GetModuleFileName(hInst, fname, sizeof(fname))) {
2367 char *p = strrchr(fname, '.');
2370 StringCbCopy(ini_file_name, MAX_PATH, fname);
2371 StringCbCat(ini_file_name, MAX_PATH, ".properties");
2374 /* Cannot obtain file name ? */
2377 if ((p = strrchr(fname, '\\'))) {
2379 StringCbCopy(dll_file_path, MAX_PATH, fname);
2380 jk_map_alloc(&jk_environment_map);
2381 jk_map_add(jk_environment_map, "JKISAPI_PATH", dll_file_path);
2382 jk_map_add(jk_environment_map, "JKISAPI_NAME", p);
2385 /* Cannot obtain file name ? */
2392 /* Construct redirector headers to use for this redirector instance */
2393 StringCbPrintf(URI_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst);
2394 StringCbPrintf(QUERY_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst);
2395 StringCbPrintf(WORKER_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst);
2396 StringCbPrintf(WORKER_HEADER_INDEX, MAX_PATH, HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst);
2397 StringCbPrintf(TOMCAT_TRANSLATE_HEADER_NAME, MAX_PATH, HEADER_TEMPLATE, TOMCAT_TRANSLATE_HEADER_NAME_BASE, hInst);
2399 /* Construct the HTTP_ headers that will be seen in ExtensionProc */
2400 StringCbPrintf(HTTP_URI_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, URI_HEADER_NAME_BASE, hInst);
2401 StringCbPrintf(HTTP_QUERY_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, QUERY_HEADER_NAME_BASE, hInst);
2402 StringCbPrintf(HTTP_WORKER_HEADER_NAME, MAX_PATH, HTTP_HEADER_TEMPLATE, WORKER_HEADER_NAME_BASE, hInst);
2403 StringCbPrintf(HTTP_WORKER_HEADER_INDEX, MAX_PATH, HTTP_HEADER_TEMPLATE, WORKER_HEADER_INDEX_BASE, hInst);
2405 JK_INIT_CS(&init_cs, rc);
2406 JK_INIT_CS(&log_cs, rc);
2409 case DLL_PROCESS_DETACH:
2411 TerminateFilter(HSE_TERM_MUST_UNLOAD);
2415 JK_DELETE_CS(&init_cs, rc);
2416 JK_DELETE_CS(&log_cs, rc);
2426 static DWORD WINAPI watchdog_thread(void *param)
2429 if (JK_IS_DEBUG_LEVEL(logger)) {
2430 jk_log(logger, JK_LOG_DEBUG,
2431 "Watchdog thread initialized with %u second interval",
2434 while (watchdog_interval) {
2435 for (i = 0; i < (watchdog_interval * 10); i++) {
2436 if (!watchdog_interval)
2440 if (!watchdog_interval)
2442 if (JK_IS_DEBUG_LEVEL(logger)) {
2443 jk_log(logger, JK_LOG_DEBUG,
2444 "Watchdog thread running");
2446 if (worker_mount_file[0]) {
2448 uri_worker_map_update(uw_map, 0, logger);
2451 wc_maintain(logger);
2453 if (JK_IS_DEBUG_LEVEL(logger)) {
2454 jk_log(logger, JK_LOG_DEBUG,
2455 "Watchdog thread finished");
2461 * Reinitializes the logger, formatting the log file name if rotation is enabled,
2462 * and calculating the next rotation time if applicable.
2464 static int init_logger(int rotate, jk_logger_t **l)
2467 int log_open = rotate; /* log is assumed open if a rotate is requested */
2468 char *log_file_name;
2469 char log_file_name_buf[MAX_PATH*2];
2471 /* If log rotation is enabled, format the log filename */
2472 if ((log_rotationtime > 0) || (log_filesize > 0)) {
2476 if (log_rotationtime > 0) {
2477 /* Align time to rotationtime intervals */
2478 t = (t / log_rotationtime) * log_rotationtime;
2480 /* Calculate rotate time */
2481 log_next_rotate_time = t + log_rotationtime;
2484 log_file_name = log_file_name_buf;
2485 if (strchr(log_file, '%')) {
2488 /* If there are %s in the log file name, treat it as a sprintf format */
2489 tm_now = localtime(&t);
2490 strftime(log_file_name, sizeof(log_file_name_buf), log_file, tm_now);
2492 /* Otherwise append the number of seconds to the base name */
2493 StringCbPrintf(log_file_name, sizeof(log_file_name_buf), "%s.%d", log_file, (long)t);
2496 log_file_name = log_file;
2499 /* Close the current log file if required, and the effective log file name has changed */
2500 if (log_open && strncmp(log_file_name, log_file_effective, strlen(log_file_name)) != 0) {
2501 FILE* lf = ((jk_file_logger_t* )logger->logger_private)->logfile;
2502 fprintf(lf, "Log rotated to %s\r\n", log_file_name);
2504 rc = jk_close_file_logger(&logger);
2505 log_open = JK_FALSE;
2509 if (jk_open_file_logger(&logger, log_file_name, log_level)) {
2510 logger->log = iis_log_to_file;
2512 /* Remember the current log file name for the next potential rotate */
2513 StringCbCopy(log_file_effective, sizeof(log_file_effective), log_file_name);
2521 /* Update logger being used for this log call so it occurs on new file */
2527 * Checks whether the log needs to be rotated. Must be called while holding the log lock.
2528 * The behaviour here is based on the Apache rotatelogs program.
2529 * http://httpd.apache.org/docs/2.0/programs/rotatelogs.html
2531 static int JK_METHOD rotate_log_file(jk_logger_t **l)
2534 int rotate = JK_FALSE;
2536 if (log_rotationtime > 0) {
2537 time_t t = time(NULL);
2539 if (t >= log_next_rotate_time) {
2542 } else if (log_filesize > 0) {
2543 LARGE_INTEGER filesize;
2544 HANDLE h = (HANDLE)_get_osfhandle(fileno(((jk_file_logger_t *)(*l)->logger_private)->logfile));
2545 GetFileSizeEx(h, &filesize);
2547 if ((ULONGLONG)filesize.QuadPart >= log_filesize) {
2552 rc = init_logger(JK_TRUE, l);
2558 * Log messages to the log file, rotating the log when required.
2560 static int JK_METHOD iis_log_to_file(jk_logger_t *l, int level,
2561 int used, char *what)
2566 (l->level <= level || level == JK_LOG_REQUEST_LEVEL) &&
2567 l->logger_private && what && used > 0) {
2568 jk_file_logger_t *p = l->logger_private;
2572 what[used++] = '\r';
2573 what[used++] = '\n';
2576 /* Perform logging within critical section to protect rotation */
2577 JK_ENTER_CS(&(log_cs), rc);
2578 if (rc && rotate_log_file(&l)) {
2579 /* The rotation process will reallocate the jk_logger_t structure, so refetch */
2580 FILE *rotated = ((jk_file_logger_t *)l->logger_private)->logfile;
2581 fputs(what, rotated);
2583 JK_LEAVE_CS(&(log_cs), rc);
2590 static int init_jk(char *serverName)
2592 char shm_name[MAX_PATH];
2595 init_logger(JK_FALSE, &logger);
2596 /* TODO: Use System logging to notify the user that
2597 * we cannot open the configured log file.
2600 StringCbCopy(shm_name, MAX_PATH, SHM_DEF_NAME);
2602 jk_log(logger, JK_LOG_INFO, "Starting %s", (FULL_VERSION_STRING));
2606 StringCbCat(shm_name, MAX_PATH, "_");
2607 StringCbCat(shm_name, MAX_PATH, serverName);
2608 for(i = 0; i < strlen(shm_name); i++) {
2609 shm_name[i] = toupper(shm_name[i]);
2610 if (!isalnum(shm_name[i]))
2615 jk_set_worker_def_cache_size(DEFAULT_WORKER_THREADS);
2617 /* Logging the initialization type: registry or properties file in virtual dir
2619 if (JK_IS_DEBUG_LEVEL(logger)) {
2620 jk_log(logger, JK_LOG_DEBUG, "Detected IIS version %d.%d", iis_info.major, iis_info.minor);
2621 if (using_ini_file) {
2622 jk_log(logger, JK_LOG_DEBUG, "Using ini file %s.", ini_file_name);
2625 jk_log(logger, JK_LOG_DEBUG, "Using registry.");
2628 jk_log(logger, JK_LOG_DEBUG, "Using log file %s.", log_file);
2629 jk_log(logger, JK_LOG_DEBUG, "Using log level %d.", log_level);
2630 jk_log(logger, JK_LOG_DEBUG, "Using log rotation time %d seconds.", log_rotationtime);
2631 jk_log(logger, JK_LOG_DEBUG, "Using log file size %d bytes.", log_filesize);
2633 jk_log(logger, JK_LOG_DEBUG, "Using extension uri %s.", extension_uri);
2634 jk_log(logger, JK_LOG_DEBUG, "Using worker file %s.", worker_file);
2635 jk_log(logger, JK_LOG_DEBUG, "Using worker mount file %s.",
2637 jk_log(logger, JK_LOG_DEBUG, "Using rewrite rule file %s.",
2639 jk_log(logger, JK_LOG_DEBUG, "Using uri select %d.", uri_select_option);
2640 jk_log(logger, JK_LOG_DEBUG, "Using%s chunked encoding.", (chunked_encoding_enabled ? "" : " no"));
2642 jk_log(logger, JK_LOG_DEBUG, "Using notification event %s (0x%08x)",
2643 (iis_info.filter_notify_event == SF_NOTIFY_AUTH_COMPLETE) ?
2644 "SF_NOTIFY_AUTH_COMPLETE" :
2645 ((iis_info.filter_notify_event == SF_NOTIFY_PREPROC_HEADERS) ?
2646 "SF_NOTIFY_PREPROC_HEADERS" : "UNKNOWN"),
2647 iis_info.filter_notify_event);
2650 jk_log(logger, JK_LOG_DEBUG, "Using error page '%s'.", error_page);
2652 jk_log(logger, JK_LOG_DEBUG, "Using uri header %s.", URI_HEADER_NAME);
2653 jk_log(logger, JK_LOG_DEBUG, "Using query header %s.", QUERY_HEADER_NAME);
2654 jk_log(logger, JK_LOG_DEBUG, "Using worker header %s.", WORKER_HEADER_NAME);
2655 jk_log(logger, JK_LOG_DEBUG, "Using worker index %s.", WORKER_HEADER_INDEX);
2656 jk_log(logger, JK_LOG_DEBUG, "Using translate header %s.", TOMCAT_TRANSLATE_HEADER_NAME);
2657 jk_log(logger, JK_LOG_DEBUG, "Using a default of %d connections per pool.",
2658 DEFAULT_WORKER_THREADS);
2661 if ((log_rotationtime > 0) && (log_filesize > 0)) {
2662 jk_log(logger, JK_LOG_WARNING,
2663 "%s is defined in configuration, but will be ignored because %s is set. ",
2664 LOG_FILESIZE_TAG, LOG_ROTATION_TIME_TAG);
2667 if (rewrite_rule_file[0] && jk_map_alloc(&rewrite_map)) {
2668 if (jk_map_read_properties(rewrite_map, NULL, rewrite_rule_file,
2669 NULL, JK_MAP_HANDLE_RAW, logger)) {
2671 if (JK_IS_DEBUG_LEVEL(logger)) {
2672 jk_log(logger, JK_LOG_DEBUG, "Loaded rewrite rule file %s.",
2676 jk_map_alloc(&rregexp_map);
2677 for (i = 0; i < jk_map_size(rewrite_map); i++) {
2678 const char *src = jk_map_name_at(rewrite_map, i);
2680 ap_regex_t *regexp = malloc(sizeof(ap_regex_t));
2681 const char *val = jk_map_value_at(rewrite_map, i);
2682 /* Skip leading tilde */
2683 regexp->real = src + 1;
2685 if (!ap_regcomp(regexp, regexp->real, AP_REG_EXTENDED)) {
2686 jk_map_add(rregexp_map, regexp->real, regexp);
2687 if (JK_IS_DEBUG_LEVEL(logger)) {
2688 jk_log(logger, JK_LOG_DEBUG,
2689 "Added regular expression rule %s -> %s",
2690 regexp->real, regexp->fake);
2694 jk_log(logger, JK_LOG_ERROR,
2695 "Unable to compile regular expression %s",
2703 jk_map_free(&rewrite_map);
2708 if (uri_worker_map_alloc(&uw_map, NULL, logger)) {
2711 uw_map->reject_unsafe = 1;
2713 uw_map->reject_unsafe = 0;
2714 uw_map->reload = worker_mount_reload;
2715 if (worker_mount_file[0]) {
2716 uw_map->fname = worker_mount_file;
2717 rc = uri_worker_map_load(uw_map, logger);
2722 if (jk_map_alloc(&workers_map)) {
2723 if (jk_map_read_properties(workers_map, NULL, worker_file, NULL,
2724 JK_MAP_HANDLE_DUPLICATES, logger)) {
2727 /* we add the URI->WORKER MAP since workers using AJP14 will feed it */
2729 if (jk_map_resolve_references(workers_map, "worker.", 1, 1, logger) == JK_FALSE) {
2730 jk_log(logger, JK_LOG_ERROR, "Error in resolving configuration references");
2733 * Create named shared memory for each server
2735 if (shm_config_size == 0)
2736 shm_config_size = jk_shm_calculate_size(workers_map, logger);
2738 jk_log(logger, JK_LOG_WARNING,
2739 "The optimal shared memory size can now be determined automatically.");
2740 jk_log(logger, JK_LOG_WARNING,
2741 "You can remove the shm_size attribute if you want to use the optimal size.");
2743 if ((rv = jk_shm_open(shm_name, shm_config_size, logger)) != 0) {
2744 /* TODO: Do not try to open the worker if we cannot create
2745 * the shared memory segment.
2747 jk_log(logger, JK_LOG_ERROR,
2748 "Initializing shm:%s errno=%d. Load balancing workers will not function properly.",
2751 worker_env.uri_to_worker = uw_map;
2752 worker_env.server_name = serverName;
2753 worker_env.pool = NULL;
2755 if (wc_open(workers_map, &worker_env, logger)) {
2758 uri_worker_map_ext(uw_map, logger);
2759 uri_worker_map_switch(uw_map, logger);
2762 jk_log(logger, JK_LOG_EMERG,
2763 "Unable to read worker file %s.", worker_file);
2764 if (rc != JK_TRUE) {
2765 jk_map_free(&workers_map);
2771 if (watchdog_interval) {
2773 watchdog_handle = CreateThread(NULL, 0, watchdog_thread,
2775 if (!watchdog_handle) {
2776 jk_log(logger, JK_LOG_EMERG, "Error %d (0x%08x) creating Watchdog thread",
2777 GetLastError(), GetLastError());
2778 watchdog_interval = 0;
2781 jk_log(logger, JK_LOG_INFO, "%s initialized", (FULL_VERSION_STRING));
2786 static BOOL initialize_extension(void)
2789 if (read_registry_init_data()) {
2790 if (get_iis_info(&iis_info) != JK_TRUE) {
2791 jk_log(logger, JK_LOG_ERROR, "Could not retrieve IIS version from registry");
2793 is_inited = JK_TRUE;
2798 int parse_uri_select(const char *uri_select)
2800 if (0 == strcasecmp(uri_select, URI_SELECT_PARSED_VERB)) {
2801 return URI_SELECT_OPT_PARSED;
2804 if (0 == strcasecmp(uri_select, URI_SELECT_UNPARSED_VERB)) {
2805 return URI_SELECT_OPT_UNPARSED;
2808 if (0 == strcasecmp(uri_select, URI_SELECT_ESCAPED_VERB)) {
2809 return URI_SELECT_OPT_ESCAPED;
2812 if (0 == strcasecmp(uri_select, URI_SELECT_PROXY_VERB)) {
2813 return URI_SELECT_OPT_PROXY;
2819 static int read_registry_init_data(void)
2821 char tmpbuf[MAX_PATH];
2825 jk_map_t *map = NULL;
2827 if (jk_map_alloc(&map)) {
2828 if (jk_map_read_properties(map, jk_environment_map, ini_file_name, NULL,
2829 JK_MAP_HANDLE_DUPLICATES, logger)) {
2830 using_ini_file = JK_TRUE;
2837 if (!using_ini_file) {
2838 long rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_LOCATION,
2839 (DWORD)0, KEY_READ, &hkey);
2840 if (ERROR_SUCCESS != rc) {
2847 ok = ok && get_config_parameter(src, JK_LOG_FILE_TAG, log_file, sizeof(log_file));
2848 if (get_config_parameter(src, JK_LOG_LEVEL_TAG, tmpbuf, sizeof(tmpbuf))) {
2849 log_level = jk_parse_log_level(tmpbuf);
2851 if (get_config_parameter(src, LOG_ROTATION_TIME_TAG, tmpbuf, sizeof(tmpbuf))) {
2852 log_rotationtime = atol(tmpbuf);
2853 if (log_rotationtime < 0) {
2854 log_rotationtime = 0;
2857 if (get_config_parameter(src, LOG_FILESIZE_TAG, tmpbuf, sizeof(tmpbuf))) {
2858 size_t tl = strlen(tmpbuf);
2860 /* rotatelogs has an 'M' suffix on filesize, which we optionally support for consistency */
2861 if (tmpbuf[tl - 1] == 'M') {
2862 tmpbuf[tl - 1] = '\0';
2864 log_filesize = atol(tmpbuf);
2865 if (log_filesize < 0) {
2868 /* Convert to MB as per Apache rotatelogs */
2869 log_filesize *= (1000 * 1000);
2873 ok = ok && get_config_parameter(src, EXTENSION_URI_TAG, extension_uri, sizeof(extension_uri));
2874 ok = ok && get_config_parameter(src, JK_WORKER_FILE_TAG, worker_file, sizeof(worker_file));
2875 ok = ok && get_config_parameter(src, JK_MOUNT_FILE_TAG, worker_mount_file, sizeof(worker_mount_file));
2876 get_config_parameter(src, URI_REWRITE_TAG, rewrite_rule_file, sizeof(rewrite_rule_file));
2877 if (get_config_parameter(src, URI_SELECT_TAG, tmpbuf, sizeof(tmpbuf))) {
2878 int opt = parse_uri_select(tmpbuf);
2880 uri_select_option = opt;
2886 shm_config_size = (size_t) get_config_int(src, SHM_SIZE_TAG, 0);
2887 worker_mount_reload = get_config_int(src, WORKER_MOUNT_RELOAD_TAG, JK_URIMAP_DEF_RELOAD);
2888 strip_session = get_config_bool(src, STRIP_SESSION_TAG, JK_FALSE);
2889 #ifndef AUTOMATIC_AUTH_NOTIFICATION
2890 use_auth_notification_flags = get_config_int(src, AUTH_COMPLETE_TAG, 1);
2892 reject_unsafe = get_config_bool(src, REJECT_UNSAFE_TAG, JK_FALSE);
2893 watchdog_interval = get_config_int(src, WATCHDOG_INTERVAL_TAG, 0);
2894 if (watchdog_interval < 0)
2895 watchdog_interval = 0;
2896 chunked_encoding_enabled = get_config_bool(src, ENABLE_CHUNKED_ENCODING_TAG, JK_FALSE);
2897 if (get_config_parameter(src, ERROR_PAGE_TAG, error_page_buf, sizeof(error_page_buf))) {
2898 error_page = error_page_buf;
2901 if (using_ini_file) {
2910 static int get_config_parameter(LPVOID src, const char *tag,
2911 char *val, DWORD sz)
2913 const char *tmp = NULL;
2914 if (using_ini_file) {
2915 tmp = jk_map_get_string((jk_map_t*)src, tag, NULL);
2916 if (tmp && (strlen(tmp) < sz)) {
2917 StringCbCopy(val, sz, tmp);
2924 return get_registry_config_parameter(*((HKEY*)src), tag, val, sz);
2928 static int get_config_int(LPVOID src, const char *tag, int def)
2930 if (using_ini_file) {
2931 return jk_map_get_int((jk_map_t*)src, tag, def);
2934 if (get_registry_config_number(*((HKEY*)src), tag, &val) ) {
2943 static int get_config_bool(LPVOID src, const char *tag, int def)
2945 if (using_ini_file) {
2946 return jk_map_get_bool((jk_map_t*)src, tag, def);
2949 if (get_registry_config_parameter(*((HKEY*)src), tag,
2950 tmpbuf, sizeof(tmpbuf))) {
2951 return jk_get_bool_code(tmpbuf, def);
2959 static int get_registry_config_parameter(HKEY hkey,
2960 const char *tag, char *b, DWORD sz)
2965 sz = sz - 1; /* Reserve space for RegQueryValueEx to add null terminator */
2966 b[sz] = '\0'; /* Null terminate in case RegQueryValueEx doesn't */
2968 lrc = RegQueryValueEx(hkey, tag, (LPDWORD) 0, &type, (LPBYTE) b, &sz);
2969 if ((ERROR_SUCCESS != lrc) || (type != REG_SZ)) {
2976 static int get_registry_config_number(HKEY hkey,
2977 const char *tag, int *val)
2981 DWORD sz = sizeof(DWORD);
2984 lrc = RegQueryValueEx(hkey, tag, (LPDWORD)0, &type, (LPBYTE)&data, &sz);
2985 if ((ERROR_SUCCESS != lrc) || (type != REG_DWORD)) {
2994 static int init_ws_service(isapi_private_data_t * private_data,
2995 jk_ws_service_t *s, char **worker_name)
2997 char *huge_buf = NULL; /* should be enough for all */
2998 int worker_index = -1;
2999 rule_extension_t *e;
3002 BOOL unknown_content_length = FALSE;
3004 JK_TRACE_ENTER(logger);
3006 s->start_response = start_response;
3008 s->write = iis_write;
3011 if (!(huge_buf = jk_pool_alloc(&private_data->p, MAX_PACKET_SIZE))) {
3012 JK_TRACE_EXIT(logger);
3015 huge_buf_sz = MAX_PACKET_SIZE;
3016 GET_SERVER_VARIABLE_VALUE(HTTP_WORKER_HEADER_NAME, (*worker_name));
3017 GET_SERVER_VARIABLE_VALUE(HTTP_URI_HEADER_NAME, s->req_uri);
3018 GET_SERVER_VARIABLE_VALUE(HTTP_QUERY_HEADER_NAME, s->query_string);
3019 GET_SERVER_VARIABLE_VALUE_INT(HTTP_WORKER_HEADER_INDEX, worker_index, -1);
3021 if (JK_IS_DEBUG_LEVEL(logger)) {
3022 jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_WORKER_HEADER_NAME, (*worker_name));
3023 jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %d", HTTP_WORKER_HEADER_INDEX, worker_index);
3024 jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_URI_HEADER_NAME, s->req_uri);
3025 jk_log(logger, JK_LOG_DEBUG, "Reading extension header %s: %s", HTTP_QUERY_HEADER_NAME, s->query_string);
3028 if (s->req_uri == NULL) {
3029 if (JK_IS_DEBUG_LEVEL(logger))
3030 jk_log(logger, JK_LOG_DEBUG, "No URI header value provided. Defaulting to old behaviour" );
3031 s->query_string = private_data->lpEcb->lpszQueryString;
3032 *worker_name = DEFAULT_WORKER_NAME;
3033 GET_SERVER_VARIABLE_VALUE("URL", s->req_uri);
3034 if (unescape_url(s->req_uri) < 0) {
3035 JK_TRACE_EXIT(logger);
3038 getparents(s->req_uri);
3041 GET_SERVER_VARIABLE_VALUE("AUTH_TYPE", s->auth_type);
3042 GET_SERVER_VARIABLE_VALUE("REMOTE_USER", s->remote_user);
3043 GET_SERVER_VARIABLE_VALUE("SERVER_PROTOCOL", s->protocol);
3044 GET_SERVER_VARIABLE_VALUE("REMOTE_HOST", s->remote_host);
3045 GET_SERVER_VARIABLE_VALUE("REMOTE_ADDR", s->remote_addr);
3046 GET_SERVER_VARIABLE_VALUE("REMOTE_PORT", s->remote_port);
3047 GET_SERVER_VARIABLE_VALUE(SERVER_NAME, s->server_name);
3048 GET_SERVER_VARIABLE_VALUE_INT("SERVER_PORT", s->server_port, 80);
3049 GET_SERVER_VARIABLE_VALUE(SERVER_SOFTWARE, s->server_software);
3050 GET_SERVER_VARIABLE_VALUE_INT("SERVER_PORT_SECURE", s->is_ssl, 0);
3052 s->method = private_data->lpEcb->lpszMethod;
3053 /* Check for Transfer Encoding */
3054 if (get_server_value(private_data->lpEcb,
3055 "HTTP_TRANSFER_ENCODING",
3057 (DWORD)sizeof(temp_buf))) {
3058 if (strcasecmp(temp_buf, TRANSFER_ENCODING_CHUNKED_VALUE) == 0) {
3059 s->is_chunked = JK_TRUE;
3060 if (JK_IS_DEBUG_LEVEL(logger)) {
3061 jk_log(logger, JK_LOG_DEBUG, "Request is Transfer-Encoding: chunked");
3065 /* XXX: What to do with non chunked T-E ?
3067 if (JK_IS_DEBUG_LEVEL(logger))
3068 jk_log(logger, JK_LOG_DEBUG, "Unsupported Transfer-Encoding: %s",
3072 if (private_data->lpEcb->cbTotalBytes == 0xFFFFFFFF) {
3073 /* We have size larger then 4Gb or Transfer-Encoding: chunked
3074 * ReadClient should be called until no more data is returned
3076 unknown_content_length = TRUE;
3079 /* Use the IIS provided content length */
3080 s->content_length = (jk_uint64_t)private_data->lpEcb->cbTotalBytes;
3082 e = get_uri_to_worker_ext(uw_map, worker_index);
3084 if (JK_IS_DEBUG_LEVEL(logger))
3085 jk_log(logger, JK_LOG_DEBUG, "Applying service extensions" );
3086 s->extension.reply_timeout = e->reply_timeout;
3087 s->extension.use_server_error_pages = e->use_server_error_pages;
3088 if (e->activation) {
3089 s->extension.activation = jk_pool_alloc(s->pool, e->activation_size * sizeof(int));
3090 memcpy(s->extension.activation, e->activation, e->activation_size * sizeof(int));
3092 if (e->fail_on_status_size > 0) {
3093 s->extension.fail_on_status_size = e->fail_on_status_size;
3094 s->extension.fail_on_status = jk_pool_alloc(s->pool, e->fail_on_status_size * sizeof(int));
3095 memcpy(s->extension.fail_on_status, e->fail_on_status, e->fail_on_status_size * sizeof(int));
3101 * Add SSL IIS environment
3104 char *ssl_env_names[9] = {
3108 "HTTPS_SERVER_SUBJECT",
3110 "HTTPS_SECRETKEYSIZE",
3111 "CERT_SERIALNUMBER",
3112 "HTTPS_SERVER_ISSUER",
3115 char *ssl_env_values[9] = {
3127 unsigned int num_of_vars = 0;
3129 for (i = 0; i < 9; i++) {
3130 GET_SERVER_VARIABLE_VALUE(ssl_env_names[i], ssl_env_values[i]);
3131 if (ssl_env_values[i]) {
3135 /* XXX: To make the isapi plugin more consistent with the other web servers */
3136 /* we should also set s->ssl_cipher, s->ssl_session, and s->ssl_key_size. */
3140 s->attributes_names =
3141 jk_pool_alloc(&private_data->p, num_of_vars * sizeof(char *));
3142 s->attributes_values =
3143 jk_pool_alloc(&private_data->p, num_of_vars * sizeof(char *));
3146 for (i = 0; i < 9; i++) {
3147 if (ssl_env_values[i]) {
3148 s->attributes_names[j] = ssl_env_names[i];
3149 s->attributes_values[j] = ssl_env_values[i];
3153 s->num_attributes = num_of_vars;
3154 if (ssl_env_values[4] && ssl_env_values[4][0] == '1') {
3156 cc.cbAllocated = MAX_PACKET_SIZE;
3157 cc.CertContext.pbCertEncoded = (BYTE *) huge_buf;
3158 cc.CertContext.cbCertEncoded = 0;
3160 if (private_data->lpEcb->
3161 ServerSupportFunction(private_data->lpEcb->ConnID,
3162 (DWORD) HSE_REQ_GET_CERT_INFO_EX,
3163 (LPVOID) & cc, NULL,
3165 jk_log(logger, JK_LOG_DEBUG,
3166 "Client Certificate encoding:%d sz:%d flags:%ld",
3168 dwCertEncodingType & X509_ASN_ENCODING,
3169 cc.CertContext.cbCertEncoded,
3170 cc.dwCertificateFlags);
3172 jk_pool_alloc(&private_data->p,
3173 base64_encode_cert_len(cc.CertContext.
3176 s->ssl_cert_len = base64_encode_cert(s->ssl_cert,
3185 huge_buf_sz = MAX_PACKET_SIZE;
3186 if (get_server_value(private_data->lpEcb,
3187 #ifndef USE_CGI_HEADERS
3188 "ALL_RAW", huge_buf, huge_buf_sz)) {
3190 "ALL_HTTP", huge_buf, huge_buf_sz)) {
3192 unsigned int cnt = 0;
3195 for (tmp = huge_buf; *tmp; tmp++) {
3202 char *headers_buf = huge_buf;
3204 BOOL need_content_length_header = FALSE;
3206 if (s->content_length == 0 && unknown_content_length == FALSE) {
3207 /* Add content-length=0 only if really zero
3209 need_content_length_header = TRUE;
3212 /* allocate an extra header slot in case we need to add a content-length header */
3214 jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *));
3216 jk_pool_alloc(&private_data->p, (cnt + 1) * sizeof(char *));
3218 if (!s->headers_names || !s->headers_values || !headers_buf) {
3219 JK_TRACE_EXIT(logger);
3223 for (i = 0, tmp = headers_buf; *tmp && i < cnt;) {
3224 int real_header = JK_TRUE;
3226 #ifdef USE_CGI_HEADERS
3227 /* Skip the HTTP_ prefix to the beginning of the header name */
3228 tmp += HTTP_HEADER_PREFIX_LEN;
3231 if (!strnicmp(tmp, URI_HEADER_NAME, strlen(URI_HEADER_NAME))
3232 || !strnicmp(tmp, WORKER_HEADER_NAME, strlen(WORKER_HEADER_NAME))
3233 || !strnicmp(tmp, WORKER_HEADER_INDEX, strlen(WORKER_HEADER_INDEX))
3234 || !strnicmp(tmp, QUERY_HEADER_NAME, strlen(QUERY_HEADER_NAME))) {
3235 /* Skip redirector headers */
3237 real_header = JK_FALSE;
3239 else if (!strnicmp(tmp, CONTENT_LENGTH,
3240 sizeof(CONTENT_LENGTH) - 1)) {
3241 need_content_length_header = FALSE;
3243 /* If the content-length is unknown
3244 * or larger then 4Gb do not send it.
3245 * IIS can also create a synthetic Content-Length header to make
3246 * lpcbTotalBytes and the CONTENT_LENGTH server variable agree
3247 * on small requests where the entire chunk encoded message is
3248 * read into the available buffer.
3250 if (unknown_content_length || s->is_chunked) {
3251 if (JK_IS_DEBUG_LEVEL(logger)) {
3252 jk_log(logger, JK_LOG_DEBUG,
3253 "Disregarding Content-Length in request - content is %s",
3254 s->is_chunked ? "chunked" : "unknown length");
3257 real_header = JK_FALSE;
3260 s->headers_names[i] = tmp;
3263 else if (!strnicmp(tmp, TOMCAT_TRANSLATE_HEADER_NAME,
3264 strlen(TOMCAT_TRANSLATE_HEADER_NAME))) {
3265 s->headers_names[i] = TRANSLATE_HEADER_NAME_LC;
3268 s->headers_names[i] = tmp;
3271 while (':' != *tmp && *tmp) {
3272 #ifdef USE_CGI_HEADERS
3278 *tmp = JK_TOLOWER(*tmp);
3287 /* Skip all the WS chars after the ':' to the beginning of the header value */
3288 while (' ' == *tmp || '\t' == *tmp || '\v' == *tmp) {
3293 s->headers_values[i] = tmp;
3296 while (*tmp && *tmp != '\n' && *tmp != '\r') {
3303 while (*tmp == '\n' || *tmp == '\r') {
3308 if (JK_IS_DEBUG_LEVEL(logger)) {
3309 jk_log(logger, JK_LOG_DEBUG, "Forwarding request header %s : %s",
3310 s->headers_names[i], s->headers_values[i]);
3315 /* Add a content-length = 0 header if needed.
3316 * Ajp13 assumes an absent content-length header means an unknown,
3317 * but non-zero length body.
3319 if (need_content_length_header) {
3320 if (JK_IS_DEBUG_LEVEL(logger)) {
3321 jk_log(logger, JK_LOG_DEBUG, "Incoming request needs explicit Content-Length: 0 in AJP13");
3323 s->headers_names[cnt] = "Content-Length";
3324 s->headers_values[cnt] = "0";
3327 s->num_headers = cnt;
3330 /* We must have our two headers */
3331 JK_TRACE_EXIT(logger);
3336 JK_TRACE_EXIT(logger);
3340 /* Dump all connection param so we can trace what's going to
3343 if (JK_IS_DEBUG_LEVEL(logger)) {
3344 jk_log(logger, JK_LOG_DEBUG,
3345 "Service protocol=%s method=%s host=%s addr=%s name=%s port=%d "
3346 "auth=%s user=%s uri=%s",
3347 STRNULL_FOR_NULL(s->protocol),
3348 STRNULL_FOR_NULL(s->method),
3349 STRNULL_FOR_NULL(s->remote_host),
3350 STRNULL_FOR_NULL(s->remote_addr),
3351 STRNULL_FOR_NULL(s->server_name),
3353 STRNULL_FOR_NULL(s->auth_type),
3354 STRNULL_FOR_NULL(s->remote_user),
3355 STRNULL_FOR_NULL(s->req_uri));
3356 jk_log(logger, JK_LOG_DEBUG,
3357 "Service request headers=%d attributes=%d "
3358 "chunked=%s content-length=%" JK_UINT64_T_FMT " available=%u",
3361 (s->is_chunked == JK_TRUE) ? "yes" : "no",
3363 private_data->lpEcb->cbTotalBytes);
3366 JK_TRACE_EXIT(logger);
3370 static int get_server_value(LPEXTENSION_CONTROL_BLOCK lpEcb,
3371 char *name, char *buf, DWORD bufsz)
3375 if (!lpEcb->GetServerVariable(lpEcb->ConnID, name,
3376 buf, (LPDWORD) &sz))
3384 static const char begin_cert[] = "-----BEGIN CERTIFICATE-----\r\n";
3386 static const char end_cert[] = "-----END CERTIFICATE-----\r\n";
3388 static const char basis_64[] =
3389 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3391 static int base64_encode_cert_len(int len)
3393 int n = ((len + 2) / 3 * 4) + 1; /* base64 encoded size */
3394 n += (n + 63 / 64) * 2; /* add CRLF's */
3395 n += sizeof(begin_cert) + sizeof(end_cert) - 2; /* add enclosing strings. */
3399 static int base64_encode_cert(char *encoded,
3400 const char *string, int len)
3413 for (i = 0; i < len - 2; i += 3) {
3414 *p++ = basis_64[(string[i] >> 2) & 0x3F];
3415 *p++ = basis_64[((string[i] & 0x3) << 4) |
3416 ((int)(string[i + 1] & 0xF0) >> 4)];
3417 *p++ = basis_64[((string[i + 1] & 0xF) << 2) |
3418 ((int)(string[i + 2] & 0xC0) >> 6)];
3419 *p++ = basis_64[string[i + 2] & 0x3F];
3428 *p++ = basis_64[(string[i] >> 2) & 0x3F];
3429 if (i == (len - 1)) {
3430 *p++ = basis_64[((string[i] & 0x3) << 4)];
3434 *p++ = basis_64[((string[i] & 0x3) << 4) |
3435 ((int)(string[i + 1] & 0xF0) >> 4)];
3436 *p++ = basis_64[((string[i + 1] & 0xF) << 2)];
3451 return (int)(p - encoded);
3455 * Determine version info and the primary notification event
3457 static int get_iis_info(iis_info_t* iis_info)
3463 iis_info->major = 0;
3464 iis_info->minor = 0;
3465 iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS;
3467 /* Retrieve the IIS version Major/Minor */
3468 rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
3469 W3SVC_REGISTRY_KEY, (DWORD) 0, KEY_READ, &hkey);
3470 if (ERROR_SUCCESS == rc) {
3471 if (get_registry_config_number(hkey, "MajorVersion", &iis_info->major) == JK_TRUE) {
3472 #ifdef AUTOMATIC_AUTH_NOTIFICATION
3473 if (iis_info->major > 4)
3475 if (use_auth_notification_flags && iis_info->major > 4)
3477 iis_info->filter_notify_event = SF_NOTIFY_AUTH_COMPLETE;
3478 if (get_registry_config_number(hkey, "MinorVersion", &iis_info->minor) == JK_TRUE) {
3480 #ifdef AUTOMATIC_AUTH_NOTIFICATION
3481 /* SF_NOTIFY_AUTH_COMPLETE causes redirect failures
3482 * (ERROR_INVALID_PARAMETER) on IIS 5.1 with OPTIONS/PUT
3483 * and is only available from IIS 5+
3485 if (iis_info->major == 5 && iis_info->minor == 1) {
3486 iis_info->filter_notify_event = SF_NOTIFY_PREPROC_HEADERS;