diff --git a/ext/standard/credits_sapi.h b/ext/standard/credits_sapi.h index 357461d5ac4a8..5c9d763a61d2f 100644 --- a/ext/standard/credits_sapi.h +++ b/ext/standard/credits_sapi.h @@ -15,5 +15,6 @@ CREDIT_LINE("CGI / FastCGI", "Rasmus Lerdorf, Stig Bakken, Shane Caraveo, Dmitry CREDIT_LINE("CLI", "Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui"); CREDIT_LINE("Embed", "Edin Kadribasic"); CREDIT_LINE("FastCGI Process Manager", "Andrei Nigmatulin, dreamcat4, Antony Dovgal, Jerome Loyet"); +CREDIT_LINE("ISAPI", "Andi Gutmans, Zeev Suraski"); CREDIT_LINE("litespeed", "George Wang"); CREDIT_LINE("phpdbg", "Felipe Pena, Joe Watkins, Bob Weinand"); diff --git a/sapi/isapi/CREDITS b/sapi/isapi/CREDITS new file mode 100644 index 0000000000000..11c6fdc73c599 --- /dev/null +++ b/sapi/isapi/CREDITS @@ -0,0 +1,2 @@ +ISAPI +Andi Gutmans, Zeev Suraski diff --git a/sapi/isapi/config.w32 b/sapi/isapi/config.w32 new file mode 100644 index 0000000000000..ea365c4fe4100 --- /dev/null +++ b/sapi/isapi/config.w32 @@ -0,0 +1,10 @@ +ARG_ENABLE('isapi', 'Build ISAPI version of PHP', 'no'); + +if (PHP_ISAPI == "yes") { + if (PHP_ZTS == "no") { + WARNING("ISAPI module requires an --enable-zts build of PHP"); + } else { + SAPI('isapi', 'php_isapi.c', 'php' + PHP_VERSION + 'isapi.dll', '/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1'); + ADD_FLAG('LDFLAGS_ISAPI', '/DEF:sapi\\isapi\\php_isapi.def'); + } +} diff --git a/sapi/isapi/php.sym b/sapi/isapi/php.sym new file mode 100644 index 0000000000000..34b50b851cad0 --- /dev/null +++ b/sapi/isapi/php.sym @@ -0,0 +1,5 @@ +GetFilterVersion +HttpFilterProc +GetExtensionVersion +HttpExtensionProc +ZSLMain diff --git a/sapi/isapi/php_isapi.c b/sapi/isapi/php_isapi.c new file mode 100644 index 0000000000000..35411260a77f8 --- /dev/null +++ b/sapi/isapi/php_isapi.c @@ -0,0 +1,819 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +#include "php.h" +#include +#include +#include +#include "php_main.h" +#include "SAPI.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "php_variables.h" +#include "php_ini.h" +#include + +#define PHP_ENABLE_SEH + +/* +uncomment the following lines to turn off +exception trapping when running under a debugger + +#ifdef _DEBUG +#undef PHP_ENABLE_SEH +#endif +*/ + +#define MAX_STATUS_LENGTH sizeof("xxxx LONGEST POSSIBLE STATUS DESCRIPTION") +#define ISAPI_SERVER_VAR_BUF_SIZE 1024 + +static zend_bool bFilterLoaded=0; +static zend_bool bTerminateThreadsOnError=0; + +static char *isapi_special_server_variable_names[] = { + "ALL_HTTP", + "HTTPS", + "SCRIPT_NAME", + NULL +}; + +#define NUM_SPECIAL_VARS (sizeof(isapi_special_server_variable_names)/sizeof(char *)) +#define SPECIAL_VAR_ALL_HTTP 0 +#define SPECIAL_VAR_HTTPS 1 +#define SPECIAL_VAR_PHP_SELF 2 + +static char *isapi_server_variable_names[] = { + "AUTH_PASSWORD", + "AUTH_TYPE", + "AUTH_USER", + "CONTENT_LENGTH", + "CONTENT_TYPE", + "PATH_TRANSLATED", + "QUERY_STRING", + "REMOTE_ADDR", + "REMOTE_HOST", + "REMOTE_USER", + "REQUEST_METHOD", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", + "SERVER_SOFTWARE", + "APPL_MD_PATH", + "APPL_PHYSICAL_PATH", + "INSTANCE_ID", + "INSTANCE_META_PATH", + "LOGON_USER", + "REQUEST_URI", + "URL", + NULL +}; + + +static char *isapi_secure_server_variable_names[] = { + "CERT_COOKIE", + "CERT_FLAGS", + "CERT_ISSUER", + "CERT_KEYSIZE", + "CERT_SECRETKEYSIZE", + "CERT_SERIALNUMBER", + "CERT_SERVER_ISSUER", + "CERT_SERVER_SUBJECT", + "CERT_SUBJECT", + "HTTPS_KEYSIZE", + "HTTPS_SECRETKEYSIZE", + "HTTPS_SERVER_ISSUER", + "HTTPS_SERVER_SUBJECT", + "SERVER_PORT_SECURE", + NULL +}; + +typedef struct http_status { + int code; + const char* msg; + } http_status; + + static const http_status http_status_codes[] = { + {100, "Continue"}, + {101, "Switching Protocols"}, + {200, "OK"}, + {201, "Created"}, + {202, "Accepted"}, + {203, "Non-Authoritative Information"}, + {204, "No Content"}, + {205, "Reset Content"}, + {206, "Partial Content"}, + {300, "Multiple Choices"}, + {301, "Moved Permanently"}, + {302, "Moved Temporarily"}, + {303, "See Other"}, + {304, "Not Modified"}, + {305, "Use Proxy"}, + {400, "Bad Request"}, + {401, "Unauthorized"}, + {402, "Payment Required"}, + {403, "Forbidden"}, + {404, "Not Found"}, + {405, "Method Not Allowed"}, + {406, "Not Acceptable"}, + {407, "Proxy Authentication Required"}, + {408, "Request Time-out"}, + {409, "Conflict"}, + {410, "Gone"}, + {411, "Length Required"}, + {412, "Precondition Failed"}, + {413, "Request Entity Too Large"}, + {414, "Request-URI Too Large"}, + {415, "Unsupported Media Type"}, + {428, "Precondition Required"}, + {429, "Too Many Requests"}, + {431, "Request Header Fields Too Large"}, + {451, "Unavailable For Legal Reasons"}, + {500, "Internal Server Error"}, + {501, "Not Implemented"}, + {502, "Bad Gateway"}, + {503, "Service Unavailable"}, + {504, "Gateway Time-out"}, + {505, "HTTP Version not supported"}, + {511, "Network Authentication Required"}, + {0, NULL} +}; + +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif + +static void php_info_isapi(ZEND_MODULE_INFO_FUNC_ARGS) +{ + char **p; + char variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + DWORD variable_len; + char **all_variables[] = { + isapi_server_variable_names, + isapi_special_server_variable_names, + isapi_secure_server_variable_names, + NULL + }; + char ***server_variable_names; + LPEXTENSION_CONTROL_BLOCK lpECB; + + lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + + php_info_print_table_start(); + php_info_print_table_header(2, "Server Variable", "Value"); + server_variable_names = all_variables; + while (*server_variable_names) { + p = *server_variable_names; + while (*p) { + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if (lpECB->GetServerVariable(lpECB->ConnID, *p, variable_buf, &variable_len) + && variable_buf[0]) { + php_info_print_table_row(2, *p, variable_buf); + } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + char *tmp_variable_buf; + + tmp_variable_buf = (char *) emalloc(variable_len); + if (lpECB->GetServerVariable(lpECB->ConnID, *p, tmp_variable_buf, &variable_len) + && variable_buf[0]) { + php_info_print_table_row(2, *p, tmp_variable_buf); + } + efree(tmp_variable_buf); + } + p++; + } + server_variable_names++; + } + php_info_print_table_end(); +} + + +static zend_module_entry php_isapi_module = { + STANDARD_MODULE_HEADER, + "ISAPI", + NULL, + NULL, + NULL, + NULL, + NULL, + php_info_isapi, + NULL, + STANDARD_MODULE_PROPERTIES +}; + + +static size_t sapi_isapi_ub_write(const char *str, size_t str_length) +{ + DWORD num_bytes = str_length; + LPEXTENSION_CONTROL_BLOCK ecb; + + ecb = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + if (ecb->WriteClient(ecb->ConnID, (char *) str, &num_bytes, HSE_IO_SYNC) == FALSE) { + php_handle_aborted_connection(); + } + return num_bytes; +} + + +static int sapi_isapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers) +{ + return SAPI_HEADER_ADD; +} + + + +static void accumulate_header_length(sapi_header_struct *sapi_header, unsigned int *total_length) +{ + *total_length += sapi_header->header_len+2; +} + + +static void concat_header(sapi_header_struct *sapi_header, char **combined_headers_ptr) +{ + memcpy(*combined_headers_ptr, sapi_header->header, sapi_header->header_len); + *combined_headers_ptr += sapi_header->header_len; + **combined_headers_ptr = '\r'; + (*combined_headers_ptr)++; + **combined_headers_ptr = '\n'; + (*combined_headers_ptr)++; +} + + +static int sapi_isapi_send_headers(sapi_headers_struct *sapi_headers) +{ + unsigned int total_length = 2; /* account for the trailing \r\n */ + char *combined_headers, *combined_headers_ptr; + LPEXTENSION_CONTROL_BLOCK lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + HSE_SEND_HEADER_EX_INFO header_info; + sapi_header_struct default_content_type; + char *status_buf = NULL; + + /* Obtain headers length */ + if (SG(sapi_headers).send_default_content_type) { + sapi_get_default_content_type_header(&default_content_type); + accumulate_header_length(&default_content_type, (void *) &total_length); + } + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) accumulate_header_length, (void *) &total_length); + + /* Generate headers */ + combined_headers = (char *) emalloc(total_length+1); + combined_headers_ptr = combined_headers; + if (SG(sapi_headers).send_default_content_type) { + concat_header(&default_content_type, (void *) &combined_headers_ptr); + sapi_free_header(&default_content_type); /* we no longer need it */ + } + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) concat_header, (void *) &combined_headers_ptr); + *combined_headers_ptr++ = '\r'; + *combined_headers_ptr++ = '\n'; + *combined_headers_ptr = 0; + + const char *sline = SG(sapi_headers).http_status_line; + int sline_len; + + /* httpd requires that r->status_line is set to the first digit of + * the status-code: */ + if (sline && ((sline_len = strlen(sline)) > 12) && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') { + if ((sline_len - 9) > MAX_STATUS_LENGTH) { + status_buf = estrndup(sline + 9, MAX_STATUS_LENGTH); + } else { + status_buf = estrndup(sline + 9, sline_len - 9); + } + } else { + http_status *status = (http_status*) http_status_codes; + + while (status->code != 0 && status->code != SG(sapi_headers).http_response_code) { + status++; + } + status_buf = emalloc(MAX_STATUS_LENGTH + 1); + if (status->code) { + snprintf(status_buf, MAX_STATUS_LENGTH, "%d %s", status->code, status->msg); + } else { + snprintf(status_buf, MAX_STATUS_LENGTH, "%d Undefined", SG(sapi_headers).http_response_code); + } + } + header_info.pszStatus = status_buf; + header_info.cchStatus = strlen(header_info.pszStatus); + header_info.pszHeader = combined_headers; + header_info.cchHeader = total_length; + header_info.fKeepConn = FALSE; + lpECB->dwHttpStatusCode = SG(sapi_headers).http_response_code; + + lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &header_info, NULL, NULL); + + efree(combined_headers); + if (status_buf) { + efree(status_buf); + } + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + + +static int php_isapi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_isapi_module)==FAILURE) { + return FAILURE; + } else { + bTerminateThreadsOnError = (zend_bool) INI_INT("isapi.terminate_threads_on_error"); + return SUCCESS; + } +} + + +static size_t sapi_isapi_read_post(char *buffer, size_t count_bytes) +{ + LPEXTENSION_CONTROL_BLOCK lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + DWORD read_from_buf=0; + DWORD read_from_input=0; + DWORD total_read=0; + + if ((DWORD) SG(read_post_bytes) < lpECB->cbAvailable) { + read_from_buf = MIN(lpECB->cbAvailable-SG(read_post_bytes), count_bytes); + memcpy(buffer, lpECB->lpbData+SG(read_post_bytes), read_from_buf); + total_read += read_from_buf; + } + if (read_from_bufcbTotalBytes) { + DWORD cbRead=0, cbSize; + + read_from_input = MIN(count_bytes-read_from_buf, lpECB->cbTotalBytes-SG(read_post_bytes)-read_from_buf); + while (cbRead < read_from_input) { + cbSize = read_from_input - cbRead; + if (!lpECB->ReadClient(lpECB->ConnID, buffer+read_from_buf+cbRead, &cbSize) || cbSize==0) { + break; + } + cbRead += cbSize; + } + total_read += cbRead; + } + return total_read; +} + + +static char *sapi_isapi_read_cookies(void) +{ + LPEXTENSION_CONTROL_BLOCK lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + char variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + + if (lpECB->GetServerVariable(lpECB->ConnID, "HTTP_COOKIE", variable_buf, &variable_len)) { + return estrndup(variable_buf, variable_len); + } else if (GetLastError()==ERROR_INSUFFICIENT_BUFFER) { + char *tmp_variable_buf = (char *) emalloc(variable_len+1); + + if (lpECB->GetServerVariable(lpECB->ConnID, "HTTP_COOKIE", tmp_variable_buf, &variable_len)) { + tmp_variable_buf[variable_len] = 0; + return tmp_variable_buf; + } else { + efree(tmp_variable_buf); + } + } + return NULL; +} + +static void sapi_isapi_register_iis_variables(LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array) +{ + char static_variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + char path_info_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + DWORD scriptname_len = ISAPI_SERVER_VAR_BUF_SIZE; + DWORD pathinfo_len = 0; + HSE_URL_MAPEX_INFO humi; + + /* Get SCRIPT_NAME, we use this to work out which bit of the URL + * belongs in PHP's version of PATH_INFO. SCRIPT_NAME also becomes PHP_SELF. + */ + lpECB->GetServerVariable(lpECB->ConnID, "SCRIPT_NAME", static_variable_buf, &scriptname_len); + php_register_variable("SCRIPT_FILENAME", SG(request_info).path_translated, track_vars_array); + + /* Adjust IIS' version of PATH_INFO, set PHP_SELF, + * and generate REQUEST_URI + * Get and adjust PATH_TRANSLATED to what PHP wants + */ + if ( lpECB->GetServerVariable(lpECB->ConnID, "PATH_INFO", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + + /* Chop off filename to get just the 'real' PATH_INFO' */ + php_register_variable( "ORIG_PATH_INFO", static_variable_buf, track_vars_array ); + pathinfo_len = variable_len - scriptname_len; + strncpy(path_info_buf, static_variable_buf + scriptname_len - 1, sizeof(path_info_buf)-1); + php_register_variable( "PATH_INFO", path_info_buf, track_vars_array ); + /* append query string to give url... extra byte for '?' */ + if ( strlen(lpECB->lpszQueryString) + variable_len + 1 < ISAPI_SERVER_VAR_BUF_SIZE ) { + /* append query string only if it is present... */ + if ( strlen(lpECB->lpszQueryString) ) { + static_variable_buf[ variable_len - 1 ] = '?'; + strcpy( static_variable_buf + variable_len, lpECB->lpszQueryString ); + } + php_register_variable( "URL", static_variable_buf, track_vars_array ); + php_register_variable( "REQUEST_URI", static_variable_buf, track_vars_array ); + } + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "PATH_TRANSLATED", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + php_register_variable( "ORIG_PATH_TRANSLATED", static_variable_buf, track_vars_array ); + } + if (lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, path_info_buf, &pathinfo_len, (LPDWORD) &humi)) { + /* Remove trailing \ */ + if (humi.lpszPath[variable_len-2] == '\\') { + humi.lpszPath[variable_len-2] = 0; + } + php_register_variable("PATH_TRANSLATED", humi.lpszPath, track_vars_array); + } + } + + static_variable_buf[0] = '/'; + static_variable_buf[1] = 0; + variable_len = 2; + if (lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, static_variable_buf, &variable_len, (LPDWORD) &humi)) { + /* Remove trailing \ */ + if (humi.lpszPath[variable_len-2] == '\\') { + humi.lpszPath[variable_len-2] = 0; + } + php_register_variable("DOCUMENT_ROOT", humi.lpszPath, track_vars_array); + } + + if (!SG(request_info).auth_user || !SG(request_info).auth_password || + !SG(request_info).auth_user[0] || !SG(request_info).auth_password[0]) { + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if (lpECB->GetServerVariable(lpECB->ConnID, "HTTP_AUTHORIZATION", static_variable_buf, &variable_len) + && static_variable_buf[0]) { + php_handle_auth_data(static_variable_buf); + } + } + + if (SG(request_info).auth_user) { + php_register_variable("PHP_AUTH_USER", SG(request_info).auth_user, track_vars_array ); + } + if (SG(request_info).auth_password) { + php_register_variable("PHP_AUTH_PW", SG(request_info).auth_password, track_vars_array ); + } +} + +static void sapi_isapi_register_server_variables2(char **server_variables, LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array, char **recorded_values) +{ + char **p=server_variables; + DWORD variable_len; + char static_variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + char *variable_buf; + + while (*p) { + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if (lpECB->GetServerVariable(lpECB->ConnID, *p, static_variable_buf, &variable_len) + && static_variable_buf[0]) { + php_register_variable(*p, static_variable_buf, track_vars_array); + if (recorded_values) { + recorded_values[p-server_variables] = estrndup(static_variable_buf, variable_len); + } + } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + variable_buf = (char *) emalloc(variable_len+1); + if (lpECB->GetServerVariable(lpECB->ConnID, *p, variable_buf, &variable_len) + && variable_buf[0]) { + php_register_variable(*p, variable_buf, track_vars_array); + } + if (recorded_values) { + recorded_values[p-server_variables] = variable_buf; + } else { + efree(variable_buf); + } + } else { /* for compatibility with Apache SAPIs */ + php_register_variable(*p, "", track_vars_array); + } + p++; + } +} + + +static void sapi_isapi_register_server_variables(zval *track_vars_array) +{ + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + char *variable; + char *strtok_buf = NULL; + char *isapi_special_server_variables[NUM_SPECIAL_VARS]; + LPEXTENSION_CONTROL_BLOCK lpECB; + + lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + + /* Register the special ISAPI variables */ + memset(isapi_special_server_variables, 0, sizeof(isapi_special_server_variables)); + sapi_isapi_register_server_variables2(isapi_special_server_variable_names, lpECB, track_vars_array, isapi_special_server_variables); + if (SG(request_info).cookie_data) { + php_register_variable("HTTP_COOKIE", SG(request_info).cookie_data, track_vars_array); + } + + /* Register the standard ISAPI variables */ + sapi_isapi_register_server_variables2(isapi_server_variable_names, lpECB, track_vars_array, NULL); + + if (isapi_special_server_variables[SPECIAL_VAR_HTTPS] + && (atoi(isapi_special_server_variables[SPECIAL_VAR_HTTPS]) + || !strcasecmp(isapi_special_server_variables[SPECIAL_VAR_HTTPS], "on")) + ) { + /* Register SSL ISAPI variables */ + sapi_isapi_register_server_variables2(isapi_secure_server_variable_names, lpECB, track_vars_array, NULL); + } + + if (isapi_special_server_variables[SPECIAL_VAR_HTTPS]) { + efree(isapi_special_server_variables[SPECIAL_VAR_HTTPS]); + } + + sapi_isapi_register_iis_variables(lpECB, track_vars_array); + + /* PHP_SELF support */ + if (isapi_special_server_variables[SPECIAL_VAR_PHP_SELF]) { + php_register_variable("PHP_SELF", isapi_special_server_variables[SPECIAL_VAR_PHP_SELF], track_vars_array); + efree(isapi_special_server_variables[SPECIAL_VAR_PHP_SELF]); + } + + if (isapi_special_server_variables[SPECIAL_VAR_ALL_HTTP]) { + /* Register the internal bits of ALL_HTTP */ + variable = php_strtok_r(isapi_special_server_variables[SPECIAL_VAR_ALL_HTTP], "\r\n", &strtok_buf); + while (variable) { + char *colon = strchr(variable, ':'); + + if (colon) { + char *value = colon+1; + + while (*value==' ') { + value++; + } + *colon = 0; + php_register_variable(variable, value, track_vars_array); + *colon = ':'; + } + variable = php_strtok_r(NULL, "\r\n", &strtok_buf); + } + efree(isapi_special_server_variables[SPECIAL_VAR_ALL_HTTP]); + } +} + + +static sapi_module_struct isapi_sapi_module = { + "isapi", /* name */ + "ISAPI", /* pretty name */ + + php_isapi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_isapi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + sapi_isapi_header_handler, /* header handler */ + sapi_isapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_isapi_read_post, /* read POST data */ + sapi_isapi_read_cookies, /* read Cookies */ + + sapi_isapi_register_server_variables, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + + +BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pFilterVersion) +{ + bFilterLoaded = 1; + pFilterVersion->dwFilterVersion = HTTP_FILTER_REVISION; + strcpy(pFilterVersion->lpszFilterDesc, isapi_sapi_module.pretty_name); + pFilterVersion->dwFlags= (SF_NOTIFY_AUTHENTICATION | SF_NOTIFY_PREPROC_HEADERS); + return TRUE; +} + + +DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc, DWORD notificationType, LPVOID pvNotification) +{ + + switch (notificationType) { + case SF_NOTIFY_PREPROC_HEADERS: + SG(request_info).auth_user = NULL; + SG(request_info).auth_password = NULL; + SG(request_info).auth_digest = NULL; + break; + case SF_NOTIFY_AUTHENTICATION: { + char *auth_user = ((HTTP_FILTER_AUTHENT *) pvNotification)->pszUser; + char *auth_password = ((HTTP_FILTER_AUTHENT *) pvNotification)->pszPassword; + + if (auth_user && auth_user[0]) { + SG(request_info).auth_user = estrdup(auth_user); + } + if (auth_password && auth_password[0]) { + SG(request_info).auth_password = estrdup(auth_password); + } + return SF_STATUS_REQ_HANDLED_NOTIFICATION; + } + break; + } + return SF_STATUS_REQ_NEXT_NOTIFICATION; +} + + +static void init_request_info(LPEXTENSION_CONTROL_BLOCK lpECB) +{ + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + char static_variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + HSE_URL_MAPEX_INFO humi; + + SG(request_info).request_method = lpECB->lpszMethod; + SG(request_info).query_string = lpECB->lpszQueryString; + SG(request_info).request_uri = lpECB->lpszPathInfo; + SG(request_info).content_type = lpECB->lpszContentType; + SG(request_info).content_length = lpECB->cbTotalBytes; + SG(sapi_headers).http_response_code = 200; /* I think dwHttpStatusCode is invalid at this stage -RL */ + if (!bFilterLoaded) { /* we don't have valid ISAPI Filter information */ + SG(request_info).auth_user = SG(request_info).auth_password = SG(request_info).auth_digest = NULL; + } + + /* happily, IIS gives us SCRIPT_NAME which is correct (without PATH_INFO stuff) + so we can just map that to the physical path and we have our filename */ + + lpECB->GetServerVariable(lpECB->ConnID, "SCRIPT_NAME", static_variable_buf, &variable_len); + if (lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_MAP_URL_TO_PATH_EX, static_variable_buf, &variable_len, (LPDWORD) &humi)) { + SG(request_info).path_translated = estrdup(humi.lpszPath); + } else + /* if mapping fails, default to what the server tells us */ + SG(request_info).path_translated = estrdup(lpECB->lpszPathTranslated); + + /* some server configurations allow '..' to slip through in the + translated path. We'll just refuse to handle such a path. */ + if (strstr(SG(request_info).path_translated,"..")) { + SG(sapi_headers).http_response_code = 404; + efree(SG(request_info).path_translated); + SG(request_info).path_translated = NULL; + } +} + + +static void php_isapi_report_exception(char *message, int message_len) +{ + if (!SG(headers_sent)) { + HSE_SEND_HEADER_EX_INFO header_info; + LPEXTENSION_CONTROL_BLOCK lpECB = (LPEXTENSION_CONTROL_BLOCK) SG(server_context); + + header_info.pszStatus = "500 Internal Server Error"; + header_info.cchStatus = strlen(header_info.pszStatus); + header_info.pszHeader = "Content-Type: text/html\r\n\r\n"; + header_info.cchHeader = strlen(header_info.pszHeader); + + lpECB->dwHttpStatusCode = 500; + lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &header_info, NULL, NULL); + SG(headers_sent)=1; + } + sapi_isapi_ub_write(message, message_len); +} + + +BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) +{ + pVer->dwExtensionVersion = HSE_VERSION; + lstrcpyn(pVer->lpszExtensionDesc, isapi_sapi_module.name, HSE_MAX_EXT_DLL_NAME_LEN); + return TRUE; +} + + +static void my_endthread() +{ + if (bTerminateThreadsOnError) { + _endthread(); + } +} + +/* ep is accessible only in the context of the __except expression, + * so we have to call this function to obtain it. + */ +BOOL exceptionhandler(LPEXCEPTION_POINTERS *e, LPEXCEPTION_POINTERS ep) +{ + *e=ep; + return TRUE; +} + +DWORD WINAPI HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK lpECB) +{ + zend_file_handle file_handle; + zend_bool stack_overflown=0; + int retval = FAILURE; +#ifdef PHP_ENABLE_SEH + LPEXCEPTION_POINTERS e; +#endif +#ifdef ZTS + /* initial resource fetch */ + (void)ts_resource(0); + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + + zend_first_try { +#ifdef PHP_ENABLE_SEH + __try { +#endif + init_request_info(lpECB); + SG(server_context) = lpECB; + php_request_startup(); + + // FIXME: we're leaking the file_handle.filename + zend_stream_init_filename(&file_handle, (char *) SG(request_info).path_translated); + file_handle.primary_script = 1; + + /* open the script here so we can 404 if it fails */ + if (file_handle.filename) + retval = php_fopen_primary_script(&file_handle); + + if (!file_handle.filename || retval == FAILURE) { + SG(sapi_headers).http_response_code = 404; + PUTS("No input file specified.\n"); + } else { + php_execute_script(&file_handle); + } + zend_destroy_file_handle(&file_handle); + + if (SG(request_info).cookie_data) { + efree(SG(request_info).cookie_data); + } + if (SG(request_info).path_translated) { + efree(SG(request_info).path_translated); + } +#ifdef PHP_ENABLE_SEH + } __except(exceptionhandler(&e, GetExceptionInformation())) { + char buf[1024]; + if (_exception_code()==EXCEPTION_STACK_OVERFLOW) { + if (!_resetstkoflw()) { + _endthread(); + } + + CG(unclean_shutdown)=1; + _snprintf(buf, sizeof(buf)-1,"PHP has encountered a Stack overflow"); + php_isapi_report_exception(buf, strlen(buf)); + } else if (_exception_code()==EXCEPTION_ACCESS_VIOLATION) { + _snprintf(buf, sizeof(buf)-1,"PHP has encountered an Access Violation at %p", e->ExceptionRecord->ExceptionAddress); + php_isapi_report_exception(buf, strlen(buf)); + my_endthread(); + } else { + _snprintf(buf, sizeof(buf)-1,"PHP has encountered an Unhandled Exception Code %d at %p", e->ExceptionRecord->ExceptionCode , e->ExceptionRecord->ExceptionAddress); + php_isapi_report_exception(buf, strlen(buf)); + my_endthread(); + } + } +#endif +#ifdef PHP_ENABLE_SEH + __try { + php_request_shutdown(NULL); + } __except(EXCEPTION_EXECUTE_HANDLER) { + my_endthread(); + } +#else + php_request_shutdown(NULL); +#endif + } zend_catch { + zend_try { + php_request_shutdown(NULL); + } zend_end_try(); + return HSE_STATUS_ERROR; + } zend_end_try(); + + return HSE_STATUS_SUCCESS; +} + + + +__declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + php_tsrm_startup_ex(128); + ZEND_TSRMLS_CACHE_UPDATE(); + sapi_startup(&isapi_sapi_module); + if (isapi_sapi_module.startup) { + isapi_sapi_module.startup(&sapi_module); + } + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + ts_free_thread(); + break; + case DLL_PROCESS_DETACH: + if (isapi_sapi_module.shutdown) { + isapi_sapi_module.shutdown(&sapi_module); + } + sapi_shutdown(); + tsrm_shutdown(); + break; + } + return TRUE; +} diff --git a/sapi/isapi/php_isapi.def b/sapi/isapi/php_isapi.def new file mode 100644 index 0000000000000..596023ef55489 --- /dev/null +++ b/sapi/isapi/php_isapi.def @@ -0,0 +1,5 @@ +EXPORTS +HttpFilterProc +GetFilterVersion +HttpExtensionProc +GetExtensionVersion diff --git a/sapi/isapi/stresstest/getopt.c b/sapi/isapi/stresstest/getopt.c new file mode 100644 index 0000000000000..21056bf883f6a --- /dev/null +++ b/sapi/isapi/stresstest/getopt.c @@ -0,0 +1,175 @@ +/* Borrowed from Apache NT Port */ + +#include +#include +#include +#include +#include "getopt.h" +#define OPTERRCOLON (1) +#define OPTERRNF (2) +#define OPTERRARG (3) + + +char *ap_optarg; +int ap_optind = 1; +static int ap_opterr = 1; +static int ap_optopt; + +static int +ap_optiserr(int argc, char * const *argv, int oint, const char *optstr, + int optchr, int err) +{ + if (ap_opterr) + { + fprintf(stderr, "Error in argument %d, char %d: ", oint, optchr+1); + switch(err) + { + case OPTERRCOLON: + fprintf(stderr, ": in flags\n"); + break; + case OPTERRNF: + fprintf(stderr, "option not found %c\n", argv[oint][optchr]); + break; + case OPTERRARG: + fprintf(stderr, "no argument for option %c\n", argv[oint][optchr]); + break; + default: + fprintf(stderr, "unknown\n"); + break; + } + } + ap_optopt = argv[oint][optchr]; + return('?'); +} + +int ap_getopt(int argc, char* const *argv, const char *optstr) +{ + static int optchr = 0; + static int dash = 0; /* have already seen the - */ + + char *cp; + + if (ap_optind >= argc) + return(EOF); + if (!dash && (argv[ap_optind][0] != '-')) + return(EOF); + if (!dash && (argv[ap_optind][0] == '-') && !argv[ap_optind][1]) + { + /* + * use to specify stdin. Need to let pgm process this and + * the following args + */ + return(EOF); + } + if ((argv[ap_optind][0] == '-') && (argv[ap_optind][1] == '-')) + { + /* -- indicates end of args */ + ap_optind++; + return(EOF); + } + if (!dash) + { + assert((argv[ap_optind][0] == '-') && argv[ap_optind][1]); + dash = 1; + optchr = 1; + } + + /* Check if the guy tries to do a -: kind of flag */ + assert(dash); + if (argv[ap_optind][optchr] == ':') + { + dash = 0; + ap_optind++; + return(ap_optiserr(argc, argv, ap_optind-1, optstr, optchr, OPTERRCOLON)); + } + if (!(cp = strchr(optstr, argv[ap_optind][optchr]))) + { + int errind = ap_optind; + int errchr = optchr; + + if (!argv[ap_optind][optchr+1]) + { + dash = 0; + ap_optind++; + } + else + optchr++; + return(ap_optiserr(argc, argv, errind, optstr, errchr, OPTERRNF)); + } + if (cp[1] == ':') + { + /* Check for cases where the value of the argument + is in the form - or in the form - */ + dash = 0; + if(!argv[ap_optind][2]) { + ap_optind++; + if (ap_optind == argc) + return(ap_optiserr(argc, argv, ap_optind-1, optstr, optchr, OPTERRARG)); + ap_optarg = argv[ap_optind++]; + } + else + { + ap_optarg = &argv[ap_optind][2]; + ap_optind++; + } + return(*cp); + } + else + { + if (!argv[ap_optind][optchr+1]) + { + dash = 0; + ap_optind++; + } + else + optchr++; + return(*cp); + } + assert(0); + return(0); +} + +#ifdef TESTGETOPT +int + main (int argc, char **argv) + { + int c; + extern char *ap_optarg; + extern int ap_optind; + int aflg = 0; + int bflg = 0; + int errflg = 0; + char *ofile = NULL; + + while ((c = ap_getopt(argc, argv, "abo:")) != EOF) + switch (c) { + case 'a': + if (bflg) + errflg++; + else + aflg++; + break; + case 'b': + if (aflg) + errflg++; + else + bflg++; + break; + case 'o': + ofile = ap_optarg; + (void)printf("ofile = %s\n", ofile); + break; + case '?': + errflg++; + } + if (errflg) { + (void)fprintf(stderr, + "usage: cmd [-a|-b] [-o ] files...\n"); + exit (2); + } + for ( ; ap_optind < argc; ap_optind++) + (void)printf("%s\n", argv[ap_optind]); + return 0; + } + +#endif /* TESTGETOPT */ diff --git a/sapi/isapi/stresstest/getopt.h b/sapi/isapi/stresstest/getopt.h new file mode 100644 index 0000000000000..6d4139bfe0dca --- /dev/null +++ b/sapi/isapi/stresstest/getopt.h @@ -0,0 +1,12 @@ +/* Borrowed from Apache NT Port */ +#ifdef __cplusplus +extern "C" { +#endif +extern char *ap_optarg; +extern int ap_optind; + +int ap_getopt(int argc, char* const *argv, const char *optstr); + +#ifdef __cplusplus +} +#endif diff --git a/sapi/isapi/stresstest/notes.txt b/sapi/isapi/stresstest/notes.txt new file mode 100644 index 0000000000000..e6d10dd486a08 --- /dev/null +++ b/sapi/isapi/stresstest/notes.txt @@ -0,0 +1,56 @@ +This stress test program is for debugging threading issues with the ISAPI +module. + +2 ways to use it: + +1: test any php script file on multiple threads +2: run the php test scripts bundled with the source code + + + +GLOBAL SETTINGS +=============== + +If you need to set special environement variables, in addition to your +regular environment, create a file that contains them, one setting per line: + +MY_ENV_VAR=XXXXXXXX + +This can be used to simulate ISAPI environment variables if need be. + +By default, stress test uses 10 threads. To change this, change the define +NUM_THREADS in stresstest.cpp. + + + +1: Test any php script file on multiple threads +=============================================== + +Create a file that contains a list of php script files, one per line. If +you need to provide input, place the GET data, or Query String, after the +filename. File contents would look like: + +e:\inetpub\pages\index.php +e:\inetpub\pages\info.php +e:\inetpub\pages\test.php a=1&b=2 + +Run: stresstest L files.txt + + + +2: Run the php test scripts bundled with the source code +======================================================== + +supply the path to the parent of the "tests" directory (expect a couple +long pauses for a couple of the larger tests) + +Run: stresstest T c:\php7-source + + + +TODO: + +* Make more options configurable: number of threads, iterations, etc. +* Improve stdout output to make it more useful +* Implement support for SKIPIF +* Improve speed of CompareFile function (too slow on big files). diff --git a/sapi/isapi/stresstest/stresstest.cpp b/sapi/isapi/stresstest/stresstest.cpp new file mode 100644 index 0000000000000..ddb06473c2d71 --- /dev/null +++ b/sapi/isapi/stresstest/stresstest.cpp @@ -0,0 +1,936 @@ +/* + * ======================================================================= * + * File: stress .c * + * stress tester for isapi dll's * + * based on cgiwrap * + * ======================================================================= * + * +*/ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include "getopt.h" + +// These are things that go out in the Response Header +// +#define HTTP_VER "HTTP/1.0" +#define SERVER_VERSION "Http-Srv-Beta2/1.0" + +// +// Simple wrappers for the heap APIS +// +#define xmalloc(s) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (s)) +#define xfree(s) HeapFree(GetProcessHeap(), 0, (s)) + +// +// The mandatory exports from the ISAPI DLL +// +DWORD numThreads = 1; +DWORD iterations = 1; + +HANDLE StartNow; +// quick and dirty environment +typedef CMapStringToString TEnvironment; +TEnvironment IsapiEnvironment; + +typedef struct _TResults { + LONG ok; + LONG bad; +} TResults; + +CStringArray IsapiFileList; // list of filenames +CStringArray TestNames; // --TEST-- +CStringArray IsapiGetData; // --GET-- +CStringArray IsapiPostData; // --POST-- +CStringArray IsapiMatchData; // --EXPECT-- +CArray Results; + +typedef struct _TIsapiContext { + HANDLE in; + HANDLE out; + DWORD tid; + TEnvironment env; + HANDLE waitEvent; +} TIsapiContext; + +// +// Prototypes of the functions this sample implements +// +extern "C" { +HINSTANCE hDll; +typedef BOOL (WINAPI *VersionProc)(HSE_VERSION_INFO *) ; +typedef DWORD (WINAPI *HttpExtProc)(EXTENSION_CONTROL_BLOCK *); +typedef BOOL (WINAPI *TerminateProc) (DWORD); +BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *, TIsapiContext *) ; +BOOL WINAPI GetServerVariable(HCONN, LPSTR, LPVOID, LPDWORD ); +BOOL WINAPI ReadClient(HCONN, LPVOID, LPDWORD); +BOOL WINAPI WriteClient(HCONN, LPVOID, LPDWORD, DWORD); +BOOL WINAPI ServerSupportFunction(HCONN, DWORD, LPVOID, LPDWORD, LPDWORD); +VersionProc IsapiGetExtensionVersion; +HttpExtProc IsapiHttpExtensionProc; +TerminateProc TerminateExtensionProc; +HSE_VERSION_INFO version_info; +} + +char * MakeDateStr(VOID); +char * GetEnv(char *); + + + + +DWORD CALLBACK IsapiThread(void *); +int stress_main(const char *filename, + const char *arg, + const char *postfile, + const char *matchdata); + + + +BOOL bUseTestFiles = FALSE; +char temppath[MAX_PATH]; + +void stripcrlf(char *line) +{ + DWORD l = strlen(line)-1; + if (line[l]==10 || line[l]==13) line[l]=0; + l = strlen(line)-1; + if (line[l]==10 || line[l]==13) line[l]=0; +} + +#define COMPARE_BUF_SIZE 1024 + +BOOL CompareFiles(const char*f1, const char*f2) +{ + FILE *fp1, *fp2; + bool retval; + char buf1[COMPARE_BUF_SIZE], buf2[COMPARE_BUF_SIZE]; + int length1, length2; + + if ((fp1=fopen(f1, "r"))==NULL) { + return FALSE; + } + + if ((fp2=fopen(f2, "r"))==NULL) { + fclose(fp1); + return FALSE; + } + + retval = TRUE; // success oriented + while (true) { + length1 = fread(buf1, 1, sizeof(buf1), fp1); + length2 = fread(buf2, 1, sizeof(buf2), fp2); + + // check for end of file + if (feof(fp1)) { + if (!feof(fp2)) { + retval = FALSE; + } + break; + } else if (feof(fp2)) { + if (!feof(fp1)) { + retval = FALSE; + } + break; + } + + // compare data + if (length1!=length2 + || memcmp(buf1, buf2, length1)!=0) { + retval = FALSE; + break; + } + } + fclose(fp1); + fclose(fp2); + + return retval; +} + + +BOOL CompareStringWithFile(const char *filename, const char *str, unsigned int str_length) +{ + FILE *fp; + bool retval; + char buf[COMPARE_BUF_SIZE]; + unsigned int offset=0, readbytes; + fprintf(stderr, "test %s\n",filename); + if ((fp=fopen(filename, "rb"))==NULL) { + fprintf(stderr, "Error opening %s\n",filename); + return FALSE; + } + + retval = TRUE; // success oriented + while (true) { + readbytes = fread(buf, 1, sizeof(buf), fp); + + // check for end of file + + if (offset+readbytes > str_length + || memcmp(buf, str+offset, readbytes)!=NULL) { + fprintf(stderr, "File missmatch %s\n",filename); + retval = FALSE; + break; + } + if (feof(fp)) { + if (!retval) fprintf(stderr, "File zero length %s\n",filename); + break; + } + } + fclose(fp); + + return retval; +} + + +BOOL ReadGlobalEnvironment(const char *environment) +{ + if (environment) { + FILE *fp = fopen(environment, "r"); + DWORD i=0; + if (fp) { + char line[2048]; + while (fgets(line, sizeof(line)-1, fp)) { + // file.php arg1 arg2 etc. + char *p = strchr(line, '='); + if (p) { + *p=0; + IsapiEnvironment[line]=p+1; + } + } + fclose(fp); + return IsapiEnvironment.GetCount() > 0; + } + } + return FALSE; +} + +BOOL ReadFileList(const char *filelist) +{ + FILE *fp = fopen(filelist, "r"); + if (!fp) { + printf("Unable to open %s\r\n", filelist); + } + char line[2048]; + int i=0; + while (fgets(line, sizeof(line)-1, fp)) { + // file.php arg1 arg2 etc. + stripcrlf(line); + if (strlen(line)>3) { + char *p = strchr(line, ' '); + if (p) { + *p = 0; + // get file + + IsapiFileList.Add(line); + IsapiGetData.Add(p+1); + } else { + // just a filename is all + IsapiFileList.Add(line); + IsapiGetData.Add(""); + } + } + + // future use + IsapiPostData.Add(""); + IsapiMatchData.Add(""); + TestNames.Add(""); + + i++; + } + Results.SetSize(TestNames.GetSize()); + + fclose(fp); + return IsapiFileList.GetSize() > 0; +} + +void DoThreads() { + + if (IsapiFileList.GetSize() == 0) { + printf("No Files to test\n"); + return; + } + + printf("Starting Threads...\n"); + // loop creating threads + DWORD tid; + HANDLE *threads = new HANDLE[numThreads]; + DWORD i; + for (i=0; i< numThreads; i++) { + threads[i]=CreateThread(NULL, 0, IsapiThread, NULL, CREATE_SUSPENDED, &tid); + } + for (i=0; i< numThreads; i++) { + if (threads[i]) ResumeThread(threads[i]); + } + // wait for threads to finish + WaitForMultipleObjects(numThreads, threads, TRUE, INFINITE); + for (i=0; i< numThreads; i++) { + CloseHandle(threads[i]); + } + delete [] threads; +} + +void DoFileList(const char *filelist, const char *environment) +{ + // read config files + + if (!ReadFileList(filelist)) { + printf("No Files to test!\r\n"); + return; + } + + ReadGlobalEnvironment(environment); + + DoThreads(); +} + + +/** + * ParseTestFile + * parse a single phpt file and add it to the arrays + */ +BOOL ParseTestFile(const char *path, const char *fn) +{ + // parse the test file + char filename[MAX_PATH]; + _snprintf(filename, sizeof(filename)-1, "%s\\%s", path, fn); + char line[1024]; + memset(line, 0, sizeof(line)); + CString cTest, cSkipIf, cPost, cGet, cFile, cExpect; + printf("Reading %s\r\n", filename); + + enum state {none, test, skipif, post, get, file, expect} parsestate = none; + + FILE *fp = fopen(filename, "rb"); + char *tn = _tempnam(temppath,"pht."); + char *en = _tempnam(temppath,"exp."); + FILE *ft = fopen(tn, "wb+"); + FILE *fe = fopen(en, "wb+"); + if (fp && ft && fe) { + while (fgets(line, sizeof(line)-1, fp)) { + if (line[0]=='-') { + if (_strnicmp(line, "--TEST--", 8)==0) { + parsestate = test; + continue; + } else if (_strnicmp(line, "--SKIPIF--", 10)==0) { + parsestate = skipif; + continue; + } else if (_strnicmp(line, "--POST--", 8)==0) { + parsestate = post; + continue; + } else if (_strnicmp(line, "--GET--", 7)==0) { + parsestate = get; + continue; + } else if (_strnicmp(line, "--FILE--", 8)==0) { + parsestate = file; + continue; + } else if (_strnicmp(line, "--EXPECT--", 10)==0) { + parsestate = expect; + continue; + } + } + switch (parsestate) { + case test: + stripcrlf(line); + cTest = line; + break; + case skipif: + cSkipIf += line; + break; + case post: + cPost += line; + break; + case get: + cGet += line; + break; + case file: + fputs(line, ft); + break; + case expect: + fputs(line, fe); + break; + } + } + + fclose(fp); + fclose(ft); + fclose(fe); + + if (!cTest.IsEmpty()) { + IsapiFileList.Add(tn); + TestNames.Add(cTest); + IsapiGetData.Add(cGet); + IsapiPostData.Add(cPost); + IsapiMatchData.Add(en); + free(tn); + free(en); + return TRUE; + } + } + free(tn); + free(en); + return FALSE; +} + + +/** + * GetTestFiles + * Recurse through the path and subdirectories, parse each phpt file + */ +BOOL GetTestFiles(const char *path) +{ + // find all files .phpt under testpath\tests + char FindPath[MAX_PATH]; + WIN32_FIND_DATA fd; + memset(&fd, 0, sizeof(WIN32_FIND_DATA)); + + _snprintf(FindPath, sizeof(FindPath)-1, "%s\\*.*", path); + HANDLE fh = FindFirstFile(FindPath, &fd); + if (fh != INVALID_HANDLE_VALUE) { + do { + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !strchr(fd.cFileName, '.')) { + // subdirectory, recurse into it + char NewFindPath[MAX_PATH]; + _snprintf(NewFindPath, sizeof(NewFindPath)-1, "%s\\%s", path, fd.cFileName); + GetTestFiles(NewFindPath); + } else if (strstr(fd.cFileName, ".phpt")) { + // got test file, parse it now + if (ParseTestFile(path, fd.cFileName)) { + printf("Test File Added: %s\\%s\r\n", path, fd.cFileName); + } + } + memset(&fd, 0, sizeof(WIN32_FIND_DATA)); + } while (FindNextFile(fh, &fd) != 0); + FindClose(fh); + } + return IsapiFileList.GetSize() > 0; +} + +void DeleteTempFiles(const char *mask) +{ + char FindPath[MAX_PATH]; + WIN32_FIND_DATA fd; + memset(&fd, 0, sizeof(WIN32_FIND_DATA)); + + _snprintf(FindPath, sizeof(FindPath)-1, "%s\\%s", temppath, mask); + HANDLE fh = FindFirstFile(FindPath, &fd); + if (fh != INVALID_HANDLE_VALUE) { + do { + char NewFindPath[MAX_PATH]; + _snprintf(NewFindPath, sizeof(NewFindPath)-1, "%s\\%s", temppath, fd.cFileName); + DeleteFile(NewFindPath); + memset(&fd, 0, sizeof(WIN32_FIND_DATA)); + } while (FindNextFile(fh, &fd) != 0); + FindClose(fh); + } +} + +void DoTestFiles(const char *filelist, const char *environment) +{ + if (!GetTestFiles(filelist)) { + printf("No Files to test!\r\n"); + return; + } + + Results.SetSize(IsapiFileList.GetSize()); + + ReadGlobalEnvironment(environment); + + DoThreads(); + + printf("\r\nRESULTS:\r\n"); + // show results: + DWORD r = Results.GetSize(); + for (DWORD i=0; i< r; i++) { + TResults result = Results.GetAt(i); + printf("%s\r\nOK: %d FAILED: %d\r\n", TestNames.GetAt(i), result.ok, result.bad); + } + + // delete temp files + printf("Deleting Temp Files\r\n"); + DeleteTempFiles("exp.*"); + DeleteTempFiles("pht.*"); + printf("Done\r\n"); +} + +#define OPTSTRING "m:f:d:h:t:i:" +static void _usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "stresstest"; + } + + printf("Usage: %s -m -d|-l [-t ] [-i ]\n" + " -m path to isapi dll\n" + " -d php directory (to run php test files).\n" + " -f file containing list of files to run\n" + " -t number of threads to use (default=1)\n" + " -i number of iterations per thread (default=1)\n" + " -h This help\n", prog); +} +int main(int argc, char* argv[]) +{ + LPVOID lpMsgBuf; + char *filelist=NULL, *environment=NULL, *module=NULL; + int c = NULL; + while ((c=ap_getopt(argc, argv, OPTSTRING))!=-1) { + switch (c) { + case 'd': + bUseTestFiles = TRUE; + filelist = strdup(ap_optarg); + break; + case 'f': + bUseTestFiles = FALSE; + filelist = strdup(ap_optarg); + break; + case 'e': + environment = strdup(ap_optarg); + break; + case 't': + numThreads = atoi(ap_optarg); + break; + case 'i': + iterations = atoi(ap_optarg); + break; + case 'm': + module = strdup(ap_optarg); + break; + case 'h': + _usage(argv[0]); + exit(0); + break; + } + } + if (!module || !filelist) { + _usage(argv[0]); + exit(0); + } + + GetTempPath(sizeof(temppath), temppath); + hDll = LoadLibrary(module); // Load our DLL + + if (!hDll) { + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + fprintf(stderr,"Error: Dll 'php7isapi.dll' not found -%d\n%s\n", GetLastError(), lpMsgBuf); + free (module); + free(filelist); + LocalFree( lpMsgBuf ); + return -1; + } + + // + // Find the exported functions + + IsapiGetExtensionVersion = (VersionProc)GetProcAddress(hDll,"GetExtensionVersion"); + if (!IsapiGetExtensionVersion) { + fprintf(stderr,"Can't Get Extension Version %d\n", GetLastError()); + free (module); + free(filelist); + return -1; + } + IsapiHttpExtensionProc = (HttpExtProc)GetProcAddress(hDll,"HttpExtensionProc"); + if (!IsapiHttpExtensionProc) { + fprintf(stderr,"Can't Get Extension proc %d\n", GetLastError()); + free (module); + free(filelist); + return -1; + } + TerminateExtensionProc = (TerminateProc) GetProcAddress(hDll, + "TerminateExtension"); + + // This should really check if the version information matches what we + // expect. + // + if (!IsapiGetExtensionVersion(&version_info) ) { + fprintf(stderr,"Fatal: GetExtensionVersion failed\n"); + free (module); + free(filelist); + return -1; + } + + if (bUseTestFiles) { + char TestPath[MAX_PATH]; + if (filelist != NULL) + _snprintf(TestPath, sizeof(TestPath)-1, "%s\\tests", filelist); + else strcpy(TestPath, "tests"); + DoTestFiles(TestPath, environment); + } else { + DoFileList(filelist, environment); + } + + // cleanup + if (TerminateExtensionProc) TerminateExtensionProc(0); + + // We should really free memory (e.g., from GetEnv), but we'll be dead + // soon enough + + FreeLibrary(hDll); + free (module); + free(filelist); + return 0; +} + + +DWORD CALLBACK IsapiThread(void *p) +{ + DWORD filecount = IsapiFileList.GetSize(); + + for (DWORD j=0; jenv.Lookup(lpszVariableName, value)) { + rc = value.GetLength(); + strncpy((char *)lpBuffer, value, *lpdwSize-1); + } else + rc = GetEnvironmentVariable(lpszVariableName, (char *)lpBuffer, *lpdwSize) ; + + if (!rc) { // return of 0 indicates the variable was not found + SetLastError(ERROR_NO_DATA); + return FALSE; + } + + if (rc > *lpdwSize) { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + *lpdwSize =rc + 1 ; // GetEnvironmentVariable does not count the NULL + + return TRUE; + +} +// +// Again, we don't have an HCONN, so we simply wrap ReadClient() to +// ReadFile on stdin. The semantics of the two functions are the same +// +BOOL WINAPI ReadClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize) { + TIsapiContext *c = (TIsapiContext *)hConn; + if (!c) return FALSE; + + if (c->in != INVALID_HANDLE_VALUE) + return ReadFile(c->in, lpBuffer, (*lpdwSize), lpdwSize, NULL); + + return FALSE; +} +// +// ditto for WriteClient() +// +BOOL WINAPI WriteClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize, + DWORD dwReserved) { + TIsapiContext *c = (TIsapiContext *)hConn; + if (!c) return FALSE; + + if (c->out != INVALID_HANDLE_VALUE) + return WriteFile(c->out, lpBuffer, *lpdwSize, lpdwSize, NULL); + return FALSE; +} +// +// This is a special callback function used by the DLL for certain extra +// functionality. Look at the API help for details. +// +BOOL WINAPI ServerSupportFunction(HCONN hConn, DWORD dwHSERequest, + LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType){ + + TIsapiContext *c = (TIsapiContext *)hConn; + char *lpszRespBuf; + char * temp = NULL; + DWORD dwBytes; + BOOL bRet = TRUE; + + switch(dwHSERequest) { + case (HSE_REQ_SEND_RESPONSE_HEADER) : + lpszRespBuf = (char *)xmalloc(*lpdwSize);//+ 80);//accommodate our header + if (!lpszRespBuf) + return FALSE; + wsprintf(lpszRespBuf,"%s", + //HTTP_VER, + + /* Default response is 200 Ok */ + + //lpvBuffer?lpvBuffer:"200 Ok", + + /* Create a string for the time. */ + //temp=MakeDateStr(), + + //SERVER_VERSION, + + /* If this exists, it is a pointer to a data buffer to + be sent. */ + lpdwDataType?(char *)lpdwDataType:NULL); + + if (temp) xfree(temp); + + dwBytes = strlen(lpszRespBuf); + bRet = WriteClient(0, lpszRespBuf, &dwBytes, 0); + xfree(lpszRespBuf); + + break; + // + // A real server would do cleanup here + case (HSE_REQ_DONE_WITH_SESSION): + SetEvent(c->waitEvent); + //ExitThread(0); + break; + + // + // This sends a redirect (temporary) to the client. + // The header construction is similar to RESPONSE_HEADER above. + // + case (HSE_REQ_SEND_URL_REDIRECT_RESP): + lpszRespBuf = (char *)xmalloc(*lpdwSize +80) ; + if (!lpszRespBuf) + return FALSE; + wsprintf(lpszRespBuf,"%s %s %s\r\n", + HTTP_VER, + "302 Moved Temporarily", + (lpdwSize > 0)?lpvBuffer:0); + xfree(temp); + dwBytes = strlen(lpszRespBuf); + bRet = WriteClient(0, lpszRespBuf, &dwBytes, 0); + xfree(lpszRespBuf); + break; + default: + return FALSE; + break; + } + return bRet; + +} +// +// Makes a string of the date and time from GetSystemTime(). +// This is in UTC, as required by the HTTP spec.` +// +char * MakeDateStr(void){ + SYSTEMTIME systime; + char *szDate= (char *)xmalloc(64); + + char * DaysofWeek[] = {"Sun","Mon","Tue","Wed","Thurs","Fri","Sat"}; + char * Months[] = {"NULL","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug", + "Sep","Oct","Nov","Dec"}; + + GetSystemTime(&systime); + + wsprintf(szDate,"%s, %d %s %d %d:%d.%d", DaysofWeek[systime.wDayOfWeek], + systime.wDay, + Months[systime.wMonth], + systime.wYear, + systime.wHour, systime.wMinute, + systime.wSecond ); + + return szDate; +} +// +// Fill the ECB up +// +BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *ECB, TIsapiContext *context) { + + char * temp; + ECB->cbSize = sizeof(EXTENSION_CONTROL_BLOCK); + ECB->dwVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); + ECB->ConnID = (void *)context; + // + // Pointers to the functions the DLL will call. + // + ECB->GetServerVariable = GetServerVariable; + ECB->ReadClient = ReadClient; + ECB->WriteClient = WriteClient; + ECB->ServerSupportFunction = ServerSupportFunction; + + // + // Fill in the standard CGI environment variables + // + ECB->lpszMethod = GetEnv("REQUEST_METHOD"); + if (!ECB->lpszMethod) ECB->lpszMethod = "GET"; + + ECB->lpszQueryString = GetEnv("QUERY_STRING"); + ECB->lpszPathInfo = GetEnv("PATH_INFO"); + ECB->lpszPathTranslated = GetEnv("PATH_TRANSLATED"); + ECB->cbTotalBytes=( (temp=GetEnv("CONTENT_LENGTH")) ? (atoi(temp)): 0); + ECB->cbAvailable = 0; + ECB->lpbData = (unsigned char *)""; + ECB->lpszContentType = GetEnv("CONTENT_TYPE"); + return TRUE; + +} + +// +// Works like _getenv(), but uses win32 functions instead. +// +char *GetEnv(LPSTR lpszEnvVar) +{ + + char *var, dummy; + DWORD dwLen; + + if (!lpszEnvVar) + return ""; + + dwLen =GetEnvironmentVariable(lpszEnvVar, &dummy, 1); + + if (dwLen == 0) + return ""; + + var = (char *)xmalloc(dwLen); + if (!var) + return ""; + (void)GetEnvironmentVariable(lpszEnvVar, var, dwLen); + + return var; +}