diff options
Diffstat (limited to 'sapi/isapi')
-rw-r--r-- | sapi/isapi/CREDITS | 2 | ||||
-rw-r--r-- | sapi/isapi/config.m4 | 24 | ||||
-rw-r--r-- | sapi/isapi/config.w32 | 13 | ||||
-rw-r--r-- | sapi/isapi/php.sym | 5 | ||||
-rw-r--r-- | sapi/isapi/php5isapi.c | 973 | ||||
-rw-r--r-- | sapi/isapi/php5isapi.def | 5 | ||||
-rw-r--r-- | sapi/isapi/php5isapi.dsp | 165 | ||||
-rw-r--r-- | sapi/isapi/stresstest/getopt.c | 175 | ||||
-rw-r--r-- | sapi/isapi/stresstest/getopt.h | 12 | ||||
-rw-r--r-- | sapi/isapi/stresstest/notes.txt | 56 | ||||
-rw-r--r-- | sapi/isapi/stresstest/stresstest.cpp | 936 | ||||
-rw-r--r-- | sapi/isapi/stresstest/stresstest.dsp | 108 |
12 files changed, 2474 insertions, 0 deletions
diff --git a/sapi/isapi/CREDITS b/sapi/isapi/CREDITS new file mode 100644 index 0000000..11c6fdc --- /dev/null +++ b/sapi/isapi/CREDITS @@ -0,0 +1,2 @@ +ISAPI +Andi Gutmans, Zeev Suraski diff --git a/sapi/isapi/config.m4 b/sapi/isapi/config.m4 new file mode 100644 index 0000000..7c7dcf0 --- /dev/null +++ b/sapi/isapi/config.m4 @@ -0,0 +1,24 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(isapi, for Zeus ISAPI support, +[ --with-isapi[=DIR] Build PHP as an ISAPI module for use with Zeus], no, no) + +if test "$PHP_ISAPI" != "no"; then + if test "$PHP_ISAPI" = "yes"; then + ZEUSPATH=/usr/local/zeus # the default + else + ZEUSPATH=$PHP_ISAPI + fi + test -f "$ZEUSPATH/web/include/httpext.h" || AC_MSG_ERROR(Unable to find httpext.h in $ZEUSPATH/web/include) + PHP_BUILD_THREAD_SAFE + AC_DEFINE(WITH_ZEUS, 1, [ ]) + PHP_ADD_INCLUDE($ZEUSPATH/web/include) + PHP_SELECT_SAPI(isapi, shared, php5isapi.c) + INSTALL_IT="\$(SHELL) \$(srcdir)/install-sh -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$ZEUSPATH/web/bin/" +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/isapi/config.w32 b/sapi/isapi/config.w32 new file mode 100644 index 0000000..8012352 --- /dev/null +++ b/sapi/isapi/config.w32 @@ -0,0 +1,13 @@ +// vim:ft=javascript +// $Id$ + +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', 'php5isapi.c', 'php' + PHP_VERSION + 'isapi.dll', '/D PHP5ISAPI_EXPORTS'); + ADD_FLAG('LDFLAGS_ISAPI', '/DEF:sapi\\isapi\\php5isapi.def'); + } +} diff --git a/sapi/isapi/php.sym b/sapi/isapi/php.sym new file mode 100644 index 0000000..34b50b8 --- /dev/null +++ b/sapi/isapi/php.sym @@ -0,0 +1,5 @@ +GetFilterVersion +HttpFilterProc +GetExtensionVersion +HttpExtensionProc +ZSLMain diff --git a/sapi/isapi/php5isapi.c b/sapi/isapi/php5isapi.c new file mode 100644 index 0000000..002ad2a --- /dev/null +++ b/sapi/isapi/php5isapi.c @@ -0,0 +1,973 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 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 <zeev@zend.com> | + | Ben Mansell <ben@zeus.com> (Zeus Support) | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php.h" +#include <httpext.h> +#include <httpfilt.h> +#include <httpext.h> +#include "php_main.h" +#include "SAPI.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "php_variables.h" +#include "php_ini.h" + +#ifdef PHP_WIN32 +# include <process.h> +#else +# define __try +# define __except(val) +# define __declspec(foo) +#endif + + +#ifdef WITH_ZEUS +# include "httpext.h" +# include <errno.h> +# define GetLastError() errno +#endif + +#ifdef PHP_WIN32 +#define PHP_ENABLE_SEH +#endif + +/* +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 +#define ISAPI_POST_DATA_BUF 1024 + +static zend_bool bFilterLoaded=0; +static zend_bool bTerminateThreadsOnError=0; + +static char *isapi_special_server_variable_names[] = { + "ALL_HTTP", + "HTTPS", +#ifndef WITH_ZEUS + "SCRIPT_NAME", +#endif + 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", +#ifndef WITH_ZEUS + "APPL_MD_PATH", + "APPL_PHYSICAL_PATH", + "INSTANCE_ID", + "INSTANCE_META_PATH", + "LOGON_USER", + "REQUEST_URI", + "URL", +#else + "DOCUMENT_ROOT", +#endif + 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", +#ifdef WITH_ZEUS + "SSL_CLIENT_CN", + "SSL_CLIENT_EMAIL", + "SSL_CLIENT_OU", + "SSL_CLIENT_O", + "SSL_CLIENT_L", + "SSL_CLIENT_ST", + "SSL_CLIENT_C", + "SSL_CLIENT_I_CN", + "SSL_CLIENT_I_EMAIL", + "SSL_CLIENT_I_OU", + "SSL_CLIENT_I_O", + "SSL_CLIENT_I_L", + "SSL_CLIENT_I_ST", + "SSL_CLIENT_I_C", +#endif + NULL +}; + + +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 int sapi_isapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + 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 TSRMLS_DC) +{ + return SAPI_HEADER_ADD; +} + + + +static void accumulate_header_length(sapi_header_struct *sapi_header, uint *total_length TSRMLS_DC) +{ + *total_length += sapi_header->header_len+2; +} + + +static void concat_header(sapi_header_struct *sapi_header, char **combined_headers_ptr TSRMLS_DC) +{ + 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 TSRMLS_DC) +{ + uint 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 TSRMLS_CC); + accumulate_header_length(&default_content_type, (void *) &total_length TSRMLS_CC); + } + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t) accumulate_header_length, (void *) &total_length TSRMLS_CC); + + /* 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 TSRMLS_CC); + 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 TSRMLS_CC); + *combined_headers_ptr++ = '\r'; + *combined_headers_ptr++ = '\n'; + *combined_headers_ptr = 0; + + switch (SG(sapi_headers).http_response_code) { + case 200: + header_info.pszStatus = "200 OK"; + break; + case 302: + header_info.pszStatus = "302 Moved Temporarily"; + break; + case 401: + header_info.pszStatus = "401 Authorization Required"; + break; + default: { + 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 { + status_buf = emalloc(MAX_STATUS_LENGTH + 1); + snprintf(status_buf, MAX_STATUS_LENGTH, "%d Undescribed", SG(sapi_headers).http_response_code); + } + header_info.pszStatus = status_buf; + break; + } + } + 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, 1)==FAILURE) { + return FAILURE; + } else { + bTerminateThreadsOnError = (zend_bool) INI_INT("isapi.terminate_threads_on_error"); + return SUCCESS; + } +} + + +static int sapi_isapi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + 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_buf<count_bytes + && (SG(read_post_bytes)+read_from_buf) < lpECB->cbTotalBytes) { + 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(TSRMLS_D) +{ + 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 STR_EMPTY_ALLOC(); +} + + +#ifdef WITH_ZEUS + +static void sapi_isapi_register_zeus_ssl_variables(LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array TSRMLS_DC) +{ + char static_variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + char static_cons_buf[ISAPI_SERVER_VAR_BUF_SIZE]; + /* + * We need to construct the /C=.../ST=... + * DN's for SSL_CLIENT_DN and SSL_CLIENT_I_DN + */ + strcpy( static_cons_buf, "/C=" ); + if( lpECB->GetServerVariable( lpECB->ConnID, "SSL_CLIENT_C", static_variable_buf, &variable_len ) && static_variable_buf[0] ) { + strlcat( static_cons_buf, static_variable_buf, ISAPI_SERVER_VAR_BUF_SIZE); + } + strlcat( static_cons_buf, "/ST=", ISAPI_SERVER_VAR_BUF_SIZE); + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if( lpECB->GetServerVariable( lpECB->ConnID, "SSL_CLIENT_ST", static_variable_buf, &variable_len ) && static_variable_buf[0] ) { + strlcat( static_cons_buf, static_variable_buf, ISAPI_SERVER_VAR_BUF_SIZE ); + } + php_register_variable( "SSL_CLIENT_DN", static_cons_buf, track_vars_array TSRMLS_CC ); + + strcpy( static_cons_buf, "/C=" ); + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if( lpECB->GetServerVariable( lpECB->ConnID, "SSL_CLIENT_I_C", static_variable_buf, &variable_len ) && static_variable_buf[0] ) { + strlcat( static_cons_buf, static_variable_buf, ISAPI_SERVER_VAR_BUF_SIZE ); + } + strlcat( static_cons_buf, "/ST=", ISAPI_SERVER_VAR_BUF_SIZE); + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if( lpECB->GetServerVariable( lpECB->ConnID, "SSL_CLIENT_I_ST", static_variable_buf, &variable_len ) && static_variable_buf[0] ) { + strlcat( static_cons_buf, static_variable_buf, ISAPI_SERVER_VAR_BUF_SIZE ); + } + php_register_variable( "SSL_CLIENT_I_DN", static_cons_buf, track_vars_array TSRMLS_CC ); +} + +static void sapi_isapi_register_zeus_variables(LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array TSRMLS_DC) +{ + char static_variable_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; + char *strtok_buf = NULL; + + /* Get SCRIPT_NAME, we use this to work out which bit of the URL + * belongs in PHP's version of PATH_INFO + */ + lpECB->GetServerVariable(lpECB->ConnID, "SCRIPT_NAME", static_variable_buf, &scriptname_len); + + /* Adjust Zeus' version of PATH_INFO, set PHP_SELF, + * and generate REQUEST_URI + */ + if ( lpECB->GetServerVariable(lpECB->ConnID, "PATH_INFO", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + + /* PHP_SELF is just PATH_INFO */ + php_register_variable( "PHP_SELF", static_variable_buf, track_vars_array TSRMLS_CC ); + + /* Chop off filename to get just the 'real' PATH_INFO' */ + pathinfo_len = variable_len - scriptname_len; + php_register_variable( "PATH_INFO", static_variable_buf + scriptname_len - 1, track_vars_array TSRMLS_CC ); + /* 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 TSRMLS_CC ); + php_register_variable( "REQUEST_URI", static_variable_buf, track_vars_array TSRMLS_CC ); + } + } + + /* Get and adjust PATH_TRANSLATED to what PHP wants */ + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "PATH_TRANSLATED", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + static_variable_buf[ variable_len - pathinfo_len - 1 ] = '\0'; + php_register_variable( "PATH_TRANSLATED", static_variable_buf, track_vars_array TSRMLS_CC ); + } + + /* Bring in the AUTHENTICATION stuff as needed */ + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "AUTH_USER", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + php_register_variable( "PHP_AUTH_USER", static_variable_buf, track_vars_array TSRMLS_CC ); + } + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "AUTH_PASSWORD", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + php_register_variable( "PHP_AUTH_PW", static_variable_buf, track_vars_array TSRMLS_CC ); + } + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "AUTH_TYPE", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + php_register_variable( "AUTH_TYPE", static_variable_buf, track_vars_array TSRMLS_CC ); + } + + /* And now, for the SSL variables (if applicable) */ + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "CERT_COOKIE", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + sapi_isapi_register_zeus_ssl_variables( lpECB, track_vars_array TSRMLS_CC ); + } + /* Copy some of the variables we need to meet Apache specs */ + variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + if ( lpECB->GetServerVariable(lpECB->ConnID, "SERVER_SOFTWARE", static_variable_buf, &variable_len) && static_variable_buf[0] ) { + php_register_variable( "SERVER_SIGNATURE", static_variable_buf, track_vars_array TSRMLS_CC ); + } +} +#else + +static void sapi_isapi_register_iis_variables(LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array TSRMLS_DC) +{ + 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 TSRMLS_CC); + + /* 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 TSRMLS_CC ); + 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 TSRMLS_CC ); + /* 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 TSRMLS_CC ); + php_register_variable( "REQUEST_URI", static_variable_buf, track_vars_array TSRMLS_CC ); + } + 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 TSRMLS_CC ); + } + 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 TSRMLS_CC); + } + } + + 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 TSRMLS_CC); + } + + 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 TSRMLS_CC); + } + } + + if (SG(request_info).auth_user) { + php_register_variable("PHP_AUTH_USER", SG(request_info).auth_user, track_vars_array TSRMLS_CC ); + } + if (SG(request_info).auth_password) { + php_register_variable("PHP_AUTH_PW", SG(request_info).auth_password, track_vars_array TSRMLS_CC ); + } +} +#endif + +static void sapi_isapi_register_server_variables2(char **server_variables, LPEXTENSION_CONTROL_BLOCK lpECB, zval *track_vars_array, char **recorded_values TSRMLS_DC) +{ + 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 TSRMLS_CC); + 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 TSRMLS_CC); + } + 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 TSRMLS_CC); + } + p++; + } +} + + +static void sapi_isapi_register_server_variables(zval *track_vars_array TSRMLS_DC) +{ + 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 TSRMLS_CC); + if (SG(request_info).cookie_data) { + php_register_variable("HTTP_COOKIE", SG(request_info).cookie_data, track_vars_array TSRMLS_CC); + } + + /* Register the standard ISAPI variables */ + sapi_isapi_register_server_variables2(isapi_server_variable_names, lpECB, track_vars_array, NULL TSRMLS_CC); + + 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 TSRMLS_CC); + } + + if (isapi_special_server_variables[SPECIAL_VAR_HTTPS]) { + efree(isapi_special_server_variables[SPECIAL_VAR_HTTPS]); + } + + +#ifdef WITH_ZEUS + sapi_isapi_register_zeus_variables(lpECB, track_vars_array TSRMLS_CC); +#else + sapi_isapi_register_iis_variables(lpECB, track_vars_array TSRMLS_CC); +#endif + + /* 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 TSRMLS_CC); + 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 TSRMLS_CC); + *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) +{ + TSRMLS_FETCH(); + + 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 TSRMLS_DC) +{ + DWORD variable_len = ISAPI_SERVER_VAR_BUF_SIZE; + char static_variable_buf[ISAPI_SERVER_VAR_BUF_SIZE]; +#ifndef WITH_ZEUS + HSE_URL_MAPEX_INFO humi; +#endif + + 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; + } + +#ifdef WITH_ZEUS + /* PATH_TRANSLATED can contain extra PATH_INFO stuff after the + * file being loaded, so we must use SCRIPT_FILENAME instead + */ + if(lpECB->GetServerVariable(lpECB->ConnID, "SCRIPT_FILENAME", static_variable_buf, &variable_len)) { + SG(request_info).path_translated = estrdup(static_variable_buf); + } else +#else + /* 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 +#endif + /* 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 TSRMLS_DC) +{ + 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 TSRMLS_CC); +} + + +BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) +{ + pVer->dwExtensionVersion = HSE_VERSION; +#ifdef WITH_ZEUS + strncpy( pVer->lpszExtensionDesc, isapi_sapi_module.name, HSE_MAX_EXT_DLL_NAME_LEN); +#else + lstrcpyn(pVer->lpszExtensionDesc, isapi_sapi_module.name, HSE_MAX_EXT_DLL_NAME_LEN); +#endif + return TRUE; +} + + +static void my_endthread() +{ +#ifdef PHP_WIN32 + if (bTerminateThreadsOnError) { + _endthread(); + } +#endif +} + +#ifdef PHP_WIN32 +/* 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; +} +#endif + +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 + TSRMLS_FETCH(); + + zend_first_try { +#ifdef PHP_ENABLE_SEH + __try { +#endif + init_request_info(lpECB TSRMLS_CC); + SG(server_context) = lpECB; + + php_request_startup(TSRMLS_C); + + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.opened_path = NULL; + + /* open the script here so we can 404 if it fails */ + if (file_handle.filename) + retval = php_fopen_primary_script(&file_handle TSRMLS_CC); + + 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 TSRMLS_CC); + } + + 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) { + LPBYTE lpPage; + static SYSTEM_INFO si; + static MEMORY_BASIC_INFORMATION mi; + static DWORD dwOldProtect; + + GetSystemInfo(&si); + + /* Get page ESP is pointing to */ + _asm mov lpPage, esp; + + /* Get stack allocation base */ + VirtualQuery(lpPage, &mi, sizeof(mi)); + + /* Go to the page below the current page */ + lpPage = (LPBYTE) (mi.BaseAddress) - si.dwPageSize; + + /* Free pages below current page */ + if (!VirtualFree(mi.AllocationBase, (LPBYTE)lpPage - (LPBYTE) mi.AllocationBase, MEM_DECOMMIT)) { + _endthread(); + } + + /* Restore the guard page */ + if (!VirtualProtect(lpPage, si.dwPageSize, PAGE_GUARD | PAGE_READWRITE, &dwOldProtect)) { + _endthread(); + } + + CG(unclean_shutdown)=1; + _snprintf(buf, sizeof(buf)-1,"PHP has encountered a Stack overflow"); + php_isapi_report_exception(buf, strlen(buf) TSRMLS_CC); + } 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) TSRMLS_CC); + 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) TSRMLS_CC); + 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: +#ifdef WITH_ZEUS + tsrm_startup(128, 1, TSRM_ERROR_LEVEL_CORE, "TSRM.log"); +#else + tsrm_startup(128, 1, TSRM_ERROR_LEVEL_CORE, "C:\\TSRM.log"); +#endif + 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; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/sapi/isapi/php5isapi.def b/sapi/isapi/php5isapi.def new file mode 100644 index 0000000..596023e --- /dev/null +++ b/sapi/isapi/php5isapi.def @@ -0,0 +1,5 @@ +EXPORTS +HttpFilterProc +GetFilterVersion +HttpExtensionProc +GetExtensionVersion diff --git a/sapi/isapi/php5isapi.dsp b/sapi/isapi/php5isapi.dsp new file mode 100644 index 0000000..3dbab11 --- /dev/null +++ b/sapi/isapi/php5isapi.dsp @@ -0,0 +1,165 @@ +# Microsoft Developer Studio Project File - Name="php5isapi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5isapi - Win32 Debug_TS
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "php5isapi.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "php5isapi.mak" CFG="php5isapi - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5isapi - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5isapi - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5isapi - Win32 Release_TS_inline" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5isapi - Win32 Release_TSDbg" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "php5isapi - Win32 Debug_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug_TS"
+# PROP BASE Intermediate_Dir "Debug_TS"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "..\..\Debug_TS"
+# PROP Intermediate_Dir "Debug_TS"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "_DEBUG" /D "COMPILE_LIBZEND" /D ZEND_DEBUG=1 /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x40d /d "_DEBUG"
+# ADD RSC /l 0x40d /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 wsock32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts_debug.lib /nologo /version:4.0 /dll /debug /machine:I386 /nodefaultlib:"libcmt" /pdbtype:sept /libpath:"..\..\Debug_TS"
+
+!ELSEIF "$(CFG)" == "php5isapi - Win32 Release_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release_TS"
+# PROP BASE Intermediate_Dir "Release_TS"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\Release_TS"
+# PROP Intermediate_Dir "Release_TS"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x40d /d "NDEBUG"
+# ADD RSC /l 0x40d /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS"
+
+!ELSEIF "$(CFG)" == "php5isapi - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "php5isapi___Win32_Release_TS_inline"
+# PROP BASE Intermediate_Dir "php5isapi___Win32_Release_TS_inline"
+# PROP BASE Ignore_Export_Lib 0
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\Release_TS_inline"
+# PROP Intermediate_Dir "Release_TS_inline"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /D "NDEBUG" /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "WIN32" /D "_MBCS" /D ZEND_DEBUG=0 /FR /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x40d /d "NDEBUG"
+# ADD RSC /l 0x40d /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts.lib /nologo /dll /machine:I386 /libpath:"..\..\Release_TS"
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS_inline"
+
+!ELSEIF "$(CFG)" == "php5isapi - Win32 Release_TSDbg"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "php5isapi___Win32_Release_TSDbg"
+# PROP BASE Intermediate_Dir "php5isapi___Win32_Release_TSDbg"
+# PROP BASE Ignore_Export_Lib 0
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\Release_TSDbg"
+# PROP Intermediate_Dir "Release_TSDbg"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /Zi /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP5ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x40d /d "NDEBUG"
+# ADD RSC /l 0x40d /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS"
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php5ts.lib /nologo /version:4.0 /dll /debug /machine:I386 /libpath:"..\..\Release_TSDbg"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5isapi - Win32 Debug_TS"
+# Name "php5isapi - Win32 Release_TS"
+# Name "php5isapi - Win32 Release_TS_inline"
+# Name "php5isapi - Win32 Release_TSDbg"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\php5isapi.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\php5isapi.def
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# End Target
+# End Project
diff --git a/sapi/isapi/stresstest/getopt.c b/sapi/isapi/stresstest/getopt.c new file mode 100644 index 0000000..57faa0f --- /dev/null +++ b/sapi/isapi/stresstest/getopt.c @@ -0,0 +1,175 @@ +/* Borrowed from Apache NT Port */ + +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <stdlib.h> +#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 -<arg> <val> or in the form -<arg><val> */ + 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 <filename>] 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 0000000..a3e278e --- /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
\ No newline at end of file diff --git a/sapi/isapi/stresstest/notes.txt b/sapi/isapi/stresstest/notes.txt new file mode 100644 index 0000000..f58ab3c --- /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:\php5-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 0000000..97824e6 --- /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 <afx.h> +#include <afxtempl.h> +#include <winbase.h> +#include <winerror.h> +#include <httpext.h> +#include <stdio.h> +#include <stdlib.h> +#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<TResults, TResults> 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 <isapi.dll> -d|-l <file> [-t <numthreads>] [-i <numiterations>]\n" + " -m path to isapi dll\n" + " -d <directory> php directory (to run php test files).\n" + " -f <file> 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 'php5isapi.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; j<iterations; j++) { + for (DWORD i=0; i<filecount; i++) { + // execute each file + CString testname = TestNames.GetAt(i); + BOOL ok = FALSE; + if (stress_main(IsapiFileList.GetAt(i), + IsapiGetData.GetAt(i), + IsapiPostData.GetAt(i), + IsapiMatchData.GetAt(i))) { + InterlockedIncrement(&Results[i].ok); + ok = TRUE; + } else { + InterlockedIncrement(&Results[i].bad); + ok = FALSE; + } + + if (testname.IsEmpty()) { + printf("Thread %d File %s\n", GetCurrentThreadId(), IsapiFileList.GetAt(i)); + } else { + printf("tid %d: %s %s\n", GetCurrentThreadId(), testname, ok?"OK":"FAIL"); + } + Sleep(10); + } + } + printf("Thread ending...\n"); + return 0; +} + +/* + * ======================================================================= * + * In the startup of this program, we look at our executable name and * + * replace the ".EXE" with ".DLL" to find the ISAPI DLL we need to load. * + * This means that the executable need only be given the same "name" as * + * the DLL to load. There is no recompilation required. * + * ======================================================================= * +*/ +BOOL stress_main(const char *filename, + const char *arg, + const char *postdata, + const char *matchdata) +{ + + EXTENSION_CONTROL_BLOCK ECB; + DWORD rc; + TIsapiContext context; + + // open output and input files + context.tid = GetCurrentThreadId(); + CString fname; + fname.Format("%08X.out", context.tid); + + context.out = CreateFile(fname, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL); + if (context.out==INVALID_HANDLE_VALUE) { + printf("failed to open output file %s\n", fname); + return 0; + } + + // not using post files + context.in = INVALID_HANDLE_VALUE; + + // + // Fill the ECB with the necessary information + // + if (!FillExtensionControlBlock(&ECB, &context) ) { + fprintf(stderr,"Fill Ext Block Failed\n"); + return -1; + } + + // check for command line argument, + // first arg = filename + // this is added for testing php from command line + + context.env.RemoveAll(); + context.env["PATH_TRANSLATED"]= filename; + context.env["SCRIPT_MAP"]= filename; + context.env["CONTENT_TYPE"]= ""; + context.env["CONTENT_LENGTH"]= ""; + context.env["QUERY_STRING"]= arg; + context.env["METHOD"]="GET"; + context.env["PATH_INFO"] = ""; + context.waitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + char buf[MAX_PATH]; + if (postdata && *postdata !=0) { + ECB.cbAvailable = strlen(postdata); + ECB.cbTotalBytes = ECB.cbAvailable; + ECB.lpbData = (unsigned char *)postdata; + context.env["METHOD"]="POST"; + + _snprintf(buf, sizeof(buf)-1, "%d", ECB.cbTotalBytes); + context.env["CONTENT_LENGTH"]=buf; + + context.env["CONTENT_TYPE"]="application/x-www-form-urlencoded"; + } + ECB.lpszMethod = strdup(context.env["METHOD"]); + ECB.lpszPathTranslated = strdup(filename); + ECB.lpszQueryString = strdup(arg); + ECB.lpszPathInfo = strdup(context.env["PATH_INFO"]); + + + // Call the DLL + // + rc = IsapiHttpExtensionProc(&ECB); + if (rc == HSE_STATUS_PENDING) { + // We will exit in ServerSupportFunction + WaitForSingleObject(context.waitEvent, INFINITE); + } + CloseHandle(context.waitEvent); + //Sleep(75); + free(ECB.lpszPathTranslated); + free(ECB.lpszQueryString); + free(ECB.lpszMethod); + free(ECB.lpszPathInfo); + + BOOL ok = TRUE; + + if (context.out != INVALID_HANDLE_VALUE) CloseHandle(context.out); + + // compare the output with the EXPECT section + if (matchdata && *matchdata != 0) { + ok = CompareFiles(fname, matchdata); + } + + DeleteFile(fname); + + return ok; + +} +// +// GetServerVariable() is how the DLL calls the main program to figure out +// the environment variables it needs. This is a required function. +// +BOOL WINAPI GetServerVariable(HCONN hConn, LPSTR lpszVariableName, + LPVOID lpBuffer, LPDWORD lpdwSize){ + + DWORD rc; + CString value; + TIsapiContext *c = (TIsapiContext *)hConn; + if (!c) return FALSE; + + if (IsapiEnvironment.Lookup(lpszVariableName, value)) { + rc = value.GetLength(); + strncpy((char *)lpBuffer, value, *lpdwSize-1); + } else if (c->env.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);//accomodate 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; +} diff --git a/sapi/isapi/stresstest/stresstest.dsp b/sapi/isapi/stresstest/stresstest.dsp new file mode 100644 index 0000000..fb82303 --- /dev/null +++ b/sapi/isapi/stresstest/stresstest.dsp @@ -0,0 +1,108 @@ +# Microsoft Developer Studio Project File - Name="stresstest" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=stresstest - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "stresstest.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "stresstest.mak" CFG="stresstest - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "stresstest - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "stresstest - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "stresstest - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 2
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_AFXDLL" /FD /c
+# SUBTRACT CPP /YX /Yc /Yu
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG" /d "_AFXDLL"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "stresstest - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 2
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "c:\php-fcgi"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_AFXDLL" /FD /GZ /c
+# SUBTRACT CPP /YX /Yc /Yu
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG" /d "_AFXDLL"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "stresstest - Win32 Release"
+# Name "stresstest - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\getopt.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\getopt.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\stresstest.cpp
+# End Source File
+# End Group
+# Begin Source File
+
+SOURCE=.\notes.txt
+# End Source File
+# End Target
+# End Project
|