diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2013-03-14 05:42:27 +0000 |
---|---|---|
committer | <> | 2013-04-03 16:25:08 +0000 |
commit | c4dd7a1a684490673e25aaf4fabec5df138854c4 (patch) | |
tree | 4d57c44caae4480efff02b90b9be86f44bf25409 /sapi | |
download | php2-master.tar.gz |
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'sapi')
317 files changed, 59722 insertions, 0 deletions
diff --git a/sapi/aolserver/CREDITS b/sapi/aolserver/CREDITS new file mode 100644 index 0000000..8f9a4af --- /dev/null +++ b/sapi/aolserver/CREDITS @@ -0,0 +1,2 @@ +AOLserver +Sascha Schumann diff --git a/sapi/aolserver/README b/sapi/aolserver/README new file mode 100644 index 0000000..80c186e --- /dev/null +++ b/sapi/aolserver/README @@ -0,0 +1,69 @@ +AOLserver README ($Id$) + +To compile PHP 4.0 as a module for AOLserver, you need: + +- installed AOLserver 3.1 or later + (see the note below for AOLserver 3.0) + +NOTE: You should not use this module in production. PHP is not 100% stable + yet in threaded mode. To increase reliability enable the Global Lock + by removing #define NO_GLOBAL_LOCK in main/main.c. Also don't use + php_value as it will lead to races in a sub-system (use an ini file + instead). + + +1.) Configuring AOLserver + +Read doc/install.txt in the source distribution + +It usually boils down to changing the INST/PREFIX variable in +include/Makefile.global and running make all install. + +2.) Configuring PHP + +$ ./configure \ + --with-aolserver=/path/to/installed/aolserver \ + <other options> + +NOTE: If you are still using AOLserver 3.0, you need to retain the + AOLserver source code and pass another option to PHP: + + --with-aolserver-src=/path/to/source/distribution + +3.) Compiling and Installing PHP + +$ make install + +4.) Changing nsd.tcl + +a) New section + +Add a new section to pass options to PHP (required): + +ns_section "ns/server/${servername}/module/php" + +You can use the following commands in this section: + +The 'map' command will cause AOLserver to pass all requests to *.php to +the PHP module (can be specified multiple times). Example: + +ns_param map *.php + +The 'php_value "name val"' command assigns the configuration option name +the value val (can be used multiple times). Example: + +ns_param php_value "session.auto_start 1" + +b) Enabling PHP + +Then enable the PHP module: + +ns_section "ns/server/${servername}/modules" +... +ns_param php ${bindir}/libphp5.so + + +============================================================================= +This has been tested with AOLserver release 3.0. + +AOLserver support has been written by Sascha Schumann <sascha@schumann.cx>. diff --git a/sapi/aolserver/aolserver.c b/sapi/aolserver/aolserver.c new file mode 100644 index 0000000..3dcbc8d --- /dev/null +++ b/sapi/aolserver/aolserver.c @@ -0,0 +1,625 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* + * TODO: + * - write documentation + * - CGI/1.1 conformance + */ + +/* $Id$ */ + +/* conflict between PHP and AOLserver headers */ +#define Debug php_Debug +#include "php.h" +#undef Debug + +#ifdef HAVE_AOLSERVER + +#ifndef ZTS +#error AOLserver module is only useable in thread-safe mode +#endif + +#include "ext/standard/info.h" +#define SECTION(name) PUTS("<h2>" name "</h2>\n") + +#define NS_BUF_SIZE 511 + +#include "php_ini.h" +#include "php_globals.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_variables.h" + +#include "ns.h" + +#include "php_version.h" + +/* This symbol is used by AOLserver to tell the API version we expect */ + +int Ns_ModuleVersion = 1; + +#define NSG(v) TSRMG(ns_globals_id, ns_globals_struct *, v) + +/* php_ns_context is per-server (thus only once at all) */ + +typedef struct { + sapi_module_struct *sapi_module; + char *ns_server; + char *ns_module; +} php_ns_context; + +/* ns_globals_struct is per-thread */ + +typedef struct { + Ns_Conn *conn; + size_t data_avail; +} ns_globals_struct; + +/* TSRM id */ + +static int ns_globals_id; + +/* global context */ + +static php_ns_context *global_context; + +static void php_ns_config(php_ns_context *ctx, char global); + +/* + * php_ns_sapi_ub_write() writes data to the client connection. + */ + +static int +php_ns_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int n; + uint sent = 0; + + while (str_length > 0) { + n = Ns_ConnWrite(NSG(conn), (void *) str, str_length); + + if (n == -1) + php_handle_aborted_connection(); + + str += n; + sent += n; + str_length -= n; + } + + return sent; +} + +/* + * php_ns_sapi_header_handler() sets a HTTP reply header to be + * sent to the client. + */ + +static int +php_ns_sapi_header_handler(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content; + char *p; + + header_name = sapi_header->header; + header_content = p = strchr(header_name, ':'); + + if (p) { + *p = '\0'; + do { + header_content++; + } while (*header_content == ' '); + + if (!strcasecmp(header_name, "Content-type")) { + Ns_ConnSetTypeHeader(NSG(conn), header_content); + } else { + Ns_ConnSetHeaders(NSG(conn), header_name, header_content); + } + + *p = ':'; + } + + sapi_free_header(sapi_header); + + return 0; +} + +/* + * php_ns_sapi_send_headers() flushes the headers to the client. + * Called before real content is sent by PHP. + */ + +static int +php_ns_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + if(SG(sapi_headers).send_default_content_type) { + Ns_ConnSetRequiredHeaders(NSG(conn), "text/html", 0); + } + + Ns_ConnFlushHeaders(NSG(conn), SG(sapi_headers).http_response_code); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +/* + * php_ns_sapi_read_post() reads a specified number of bytes from + * the client. Used for POST/PUT requests. + */ + +static int +php_ns_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ + uint max_read; + uint total_read = 0; + + max_read = MIN(NSG(data_avail), count_bytes); + + total_read = Ns_ConnRead(NSG(conn), buf, max_read); + + if(total_read == NS_ERROR) { + total_read = -1; + } else { + NSG(data_avail) -= total_read; + } + + return total_read; +} + +/* + * php_ns_sapi_read_cookies() returns the Cookie header from + * the HTTP request header + */ + +static char *php_ns_sapi_read_cookies(TSRMLS_D) +{ + int i; + char *http_cookie = NULL; + + i = Ns_SetIFind(NSG(conn->headers), "cookie"); + if(i != -1) { + http_cookie = Ns_SetValue(NSG(conn->headers), i); + } + + return http_cookie; +} + +static void php_info_aolserver(ZEND_MODULE_INFO_FUNC_ARGS) +{ + char buf[512]; + int uptime = Ns_InfoUptime(); + int i; + + php_info_print_table_start(); + php_info_print_table_row(2, "SAPI module version", "$Id$"); + php_info_print_table_row(2, "Build date", Ns_InfoBuildDate()); + php_info_print_table_row(2, "Config file path", Ns_InfoConfigFile()); + php_info_print_table_row(2, "Error Log path", Ns_InfoErrorLog()); + php_info_print_table_row(2, "Installation path", Ns_InfoHomePath()); + php_info_print_table_row(2, "Hostname of server", Ns_InfoHostname()); + php_info_print_table_row(2, "Source code label", Ns_InfoLabel()); + php_info_print_table_row(2, "Server platform", Ns_InfoPlatform()); + snprintf(buf, 511, "%s/%s", Ns_InfoServerName(), Ns_InfoServerVersion()); + php_info_print_table_row(2, "Server version", buf); + snprintf(buf, 511, "%d day(s), %02d:%02d:%02d", + uptime / 86400, + (uptime / 3600) % 24, + (uptime / 60) % 60, + uptime % 60); + php_info_print_table_row(2, "Server uptime", buf); + php_info_print_table_end(); + + SECTION("HTTP Headers Information"); + php_info_print_table_start(); + php_info_print_table_colspan_header(2, "HTTP Request Headers"); + php_info_print_table_row(2, "HTTP Request", NSG(conn)->request->line); + for (i = 0; i < Ns_SetSize(NSG(conn)->headers); i++) { + php_info_print_table_row(2, Ns_SetKey(NSG(conn)->headers, i), Ns_SetValue(NSG(conn)->headers, i)); + } + + php_info_print_table_colspan_header(2, "HTTP Response Headers"); + for (i = 0; i < Ns_SetSize(NSG(conn)->outputheaders); i++) { + php_info_print_table_row(2, Ns_SetKey(NSG(conn)->outputheaders, i), Ns_SetValue(NSG(conn)->outputheaders, i)); + } + php_info_print_table_end(); +} + +PHP_FUNCTION(getallheaders); + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_aolserver_getallheaders, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry aolserver_functions[] = { + PHP_FE(getallheaders, arginfo_aolserver_getallheaders) + {NULL, NULL, NULL} +}; + +static zend_module_entry php_aolserver_module = { + STANDARD_MODULE_HEADER, + "AOLserver", + aolserver_functions, + NULL, + NULL, + NULL, + NULL, + php_info_aolserver, + NULL, + STANDARD_MODULE_PROPERTIES +}; + +PHP_FUNCTION(getallheaders) +{ + int i; + + array_init(return_value); + + for (i = 0; i < Ns_SetSize(NSG(conn->headers)); i++) { + char *key = Ns_SetKey(NSG(conn->headers), i); + char *value = Ns_SetValue(NSG(conn->headers), i); + + add_assoc_string(return_value, key, value, 1); + } +} + +static int +php_ns_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_aolserver_module, 1) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + + +/* + * php_ns_sapi_register_variables() populates the php script environment + * with a number of variables. HTTP_* variables are created for + * the HTTP header data, so that a script can access these. + */ + +#define ADD_STRINGX(name, buf) \ + php_register_variable(name, buf, track_vars_array TSRMLS_CC) + +#define ADD_STRING(name) \ + ADD_STRINGX(name, buf) + +static void +php_ns_sapi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + int i; + char buf[NS_BUF_SIZE + 1]; + char *tmp; + + for(i = 0; i < Ns_SetSize(NSG(conn->headers)); i++) { + char *key = Ns_SetKey(NSG(conn->headers), i); + char *value = Ns_SetValue(NSG(conn->headers), i); + char *p; + char c; + + snprintf(buf, NS_BUF_SIZE, "HTTP_%s", key); + + for(p = buf + 5; (c = *p); p++) { + c = toupper(c); + if(c < 'A' || c > 'Z') { + c = '_'; + } + *p = c; + } + + ADD_STRINGX(buf, value); + } + + snprintf(buf, NS_BUF_SIZE, "%s/%s", Ns_InfoServerName(), Ns_InfoServerVersion()); + ADD_STRING("SERVER_SOFTWARE"); + snprintf(buf, NS_BUF_SIZE, "HTTP/%1.1f", NSG(conn)->request->version); + ADD_STRING("SERVER_PROTOCOL"); + + ADD_STRINGX("REQUEST_METHOD", NSG(conn)->request->method); + + if(NSG(conn)->request->query) + ADD_STRINGX("QUERY_STRING", NSG(conn)->request->query); + + ADD_STRINGX("SERVER_BUILDDATE", Ns_InfoBuildDate()); + + ADD_STRINGX("REMOTE_ADDR", Ns_ConnPeer(NSG(conn))); + + snprintf(buf, NS_BUF_SIZE, "%d", Ns_ConnPeerPort(NSG(conn))); + ADD_STRING("REMOTE_PORT"); + + snprintf(buf, NS_BUF_SIZE, "%d", Ns_ConnPort(NSG(conn))); + ADD_STRING("SERVER_PORT"); + + tmp = Ns_ConnHost(NSG(conn)); + if (tmp) + ADD_STRINGX("SERVER_NAME", tmp); + + ADD_STRINGX("PATH_TRANSLATED", SG(request_info).path_translated); + ADD_STRINGX("REQUEST_URI", SG(request_info).request_uri); + ADD_STRINGX("PHP_SELF", SG(request_info).request_uri); + + ADD_STRINGX("GATEWAY_INTERFACE", "CGI/1.1"); + + snprintf(buf, NS_BUF_SIZE, "%d", Ns_InfoBootTime()); + ADD_STRING("SERVER_BOOTTIME"); +} + + + +/* this structure is static (as in "it does not change") */ + +static sapi_module_struct aolserver_sapi_module = { + "aolserver", + "AOLserver", + + php_ns_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_ns_sapi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + php_ns_sapi_header_handler, /* header handler */ + php_ns_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_ns_sapi_read_post, /* read POST data */ + php_ns_sapi_read_cookies, /* read Cookies */ + + php_ns_sapi_register_variables, + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +/* + * php_ns_module_main() is called by the per-request handler and + * "executes" the script + */ + +static int +php_ns_module_main(TSRMLS_D) +{ + zend_file_handle file_handle; + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_ns_config(global_context, 0); + if (php_request_startup(TSRMLS_C) == FAILURE) { + return NS_ERROR; + } + + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + + return NS_OK; +} + +/* + * php_ns_request_ctor() initializes the per-request data structure + * and fills it with data provided by the web server + */ + +static void +php_ns_request_ctor(TSRMLS_D) +{ + char *server; + Ns_DString ds; + char *root; + int index; + char *tmp; + + server = Ns_ConnServer(NSG(conn)); + +#define safe_strdup(x) ((x)?strdup((x)):NULL) + SG(request_info).query_string = safe_strdup(NSG(conn->request->query)); + + Ns_DStringInit(&ds); + Ns_UrlToFile(&ds, server, NSG(conn->request->url)); + + /* path_translated is the absolute path to the file */ + SG(request_info).path_translated = safe_strdup(Ns_DStringValue(&ds)); + Ns_DStringFree(&ds); + root = Ns_PageRoot(server); + SG(request_info).request_uri = strdup(SG(request_info).path_translated + strlen(root)); + SG(request_info).request_method = NSG(conn)->request->method; + if(NSG(conn)->request->version > 1.0) SG(request_info).proto_num = 1001; + else SG(request_info).proto_num = 1000; + SG(request_info).content_length = Ns_ConnContentLength(NSG(conn)); + index = Ns_SetIFind(NSG(conn)->headers, "content-type"); + SG(request_info).content_type = index == -1 ? NULL : + Ns_SetValue(NSG(conn)->headers, index); + SG(sapi_headers).http_response_code = 200; + + tmp = Ns_ConnAuthUser(NSG(conn)); + if (tmp) + tmp = estrdup(tmp); + SG(request_info).auth_user = tmp; + + tmp = Ns_ConnAuthPasswd(NSG(conn)); + if (tmp) + tmp = estrdup(tmp); + SG(request_info).auth_password = tmp; + + NSG(data_avail) = SG(request_info).content_length; +} + +/* + * php_ns_request_dtor() destroys all data associated with + * the per-request structure + */ + +static void +php_ns_request_dtor(TSRMLS_D) +{ + free(SG(request_info).path_translated); + if (SG(request_info).query_string) + free(SG(request_info).query_string); + free(SG(request_info).request_uri); +} + +/* + * The php_ns_request_handler() is called per request and handles + * everything for one request. + */ + +static int +php_ns_request_handler(void *context, Ns_Conn *conn) +{ + int status = NS_OK; + TSRMLS_FETCH(); + + NSG(conn) = conn; + + SG(server_context) = global_context; + + php_ns_request_ctor(TSRMLS_C); + + status = php_ns_module_main(TSRMLS_C); + + php_ns_request_dtor(TSRMLS_C); + + return status; +} + +/* + * php_ns_config() fetches the configuration data. + * + * It understands the "map" and "php_value" command. + */ + +static void +php_ns_config(php_ns_context *ctx, char global) +{ + int i; + char *path; + Ns_Set *set; + + path = Ns_ConfigGetPath(ctx->ns_server, ctx->ns_module, NULL); + set = Ns_ConfigGetSection(path); + + for (i = 0; set && i < Ns_SetSize(set); i++) { + char *key = Ns_SetKey(set, i); + char *value = Ns_SetValue(set, i); + + if (global && !strcasecmp(key, "map")) { + Ns_Log(Notice, "Registering PHP for \"%s\"", value); + Ns_RegisterRequest(ctx->ns_server, "GET", value, php_ns_request_handler, NULL, ctx, 0); + Ns_RegisterRequest(ctx->ns_server, "POST", value, php_ns_request_handler, NULL, ctx, 0); + Ns_RegisterRequest(ctx->ns_server, "HEAD", value, php_ns_request_handler, NULL, ctx, 0); + + /* + * Deactivated for now. The ini system will cause random crashes when + * accessed from here (since there are no locks to protect the global + * known_directives) + */ + + } else if (!global && !strcasecmp(key, "php_value")) { + Ns_Log(Notice, "php_value has been deactivated temporarily. Please use a php.ini file to pass directives to PHP. Thanks."); +#if 0 + char *val; + + val = strchr(value, ' '); + if (val) { + char *new_key; + + new_key = estrndup(value, val - value); + + do { + val++; + } while(*val == ' '); + + Ns_Log(Debug, "PHP configuration option '%s=%s'", new_key, val); + zend_alter_ini_entry(new_key, strlen(new_key) + 1, val, + strlen(val) + 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + + efree(new_key); + } +#endif + } + + } +} + +/* + * php_ns_server_shutdown() performs the last steps before the + * server exits. Shutdowns basic services and frees memory + */ + +static void +php_ns_server_shutdown(void *context) +{ + php_ns_context *ctx = (php_ns_context *) context; + + ctx->sapi_module->shutdown(ctx->sapi_module); + sapi_shutdown(); + tsrm_shutdown(); + + free(ctx->ns_module); + free(ctx->ns_server); + free(ctx); +} + +/* + * Ns_ModuleInit() is called by AOLserver once at startup + * + * This functions allocates basic structures and initializes + * basic services. + */ + +int Ns_ModuleInit(char *server, char *module) +{ + php_ns_context *ctx; + + tsrm_startup(1, 1, 0, NULL); + sapi_startup(&aolserver_sapi_module); + sapi_module.startup(&aolserver_sapi_module); + + /* TSRM is used to allocate a per-thread structure */ + ts_allocate_id(&ns_globals_id, sizeof(ns_globals_struct), NULL, NULL); + + /* the context contains data valid for all threads */ + ctx = malloc(sizeof *ctx); + ctx->sapi_module = &aolserver_sapi_module; + ctx->ns_server = strdup(server); + ctx->ns_module = strdup(module); + + /* read the configuration */ + php_ns_config(ctx, 1); + + global_context = ctx; + + /* register shutdown handler */ + Ns_RegisterServerShutdown(server, php_ns_server_shutdown, ctx); + + return NS_OK; +} + +#endif diff --git a/sapi/aolserver/config.m4 b/sapi/aolserver/config.m4 new file mode 100644 index 0000000..a193bfd --- /dev/null +++ b/sapi/aolserver/config.m4 @@ -0,0 +1,31 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(aolserver,, +[ --with-aolserver=DIR Specify path to the installed AOLserver], no, no) + +AC_MSG_CHECKING([for AOLserver support]) + +if test "$PHP_AOLSERVER" != "no"; then + if test -d "$PHP_AOLSERVER/include"; then + PHP_AOLSERVER_SRC=$PHP_AOLSERVER + fi + if test -z "$PHP_AOLSERVER_SRC" || test ! -d $PHP_AOLSERVER_SRC/include; then + AC_MSG_ERROR(Please specify the path to the source distribution of AOLserver using --with-aolserver-src=DIR) + fi + if test ! -d $PHP_AOLSERVER/bin ; then + AC_MSG_ERROR(Please specify the path to the root of AOLserver using --with-aolserver=DIR) + fi + PHP_BUILD_THREAD_SAFE + PHP_ADD_INCLUDE($PHP_AOLSERVER_SRC/include) + AC_DEFINE(HAVE_AOLSERVER,1,[Whether you have AOLserver]) + PHP_SELECT_SAPI(aolserver, shared, aolserver.c) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$PHP_AOLSERVER/bin/" +fi + +AC_MSG_RESULT([$PHP_AOLSERVER]) + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/aolserver/config.w32 b/sapi/aolserver/config.w32 new file mode 100644 index 0000000..75b4361 --- /dev/null +++ b/sapi/aolserver/config.w32 @@ -0,0 +1,16 @@ +// vim:ft=javascript +// $Id$ + +ARG_WITH('aolserver', 'Build AOLserver support', 'no'); + +if (PHP_AOLSERVER != "no") { + if (PHP_ZTS == "no") { + WARNING("AOLSERVER module requires an --enable-zts build of PHP"); + } else { + if (CHECK_HEADER_ADD_INCLUDE("ns.h", "CFLAGS_AOLSERVER", PHP_AOLSERVER) && CHECK_LIB("nsd.lib", "aolserver", PHP_AOLSERVER)) { + SAPI('aolserver', 'aolserver.c', 'php' + PHP_VERSION + 'aolserver.so', '/D XP_WIN32 '); + } else { + WARNING("sapi/aolserver not enabled: Could not find libraries/headers"); + } + } +} diff --git a/sapi/aolserver/php.sym b/sapi/aolserver/php.sym new file mode 100644 index 0000000..b401ffd --- /dev/null +++ b/sapi/aolserver/php.sym @@ -0,0 +1,2 @@ +Ns_ModuleVersion +Ns_ModuleInit diff --git a/sapi/aolserver/php5aolserver.dsp b/sapi/aolserver/php5aolserver.dsp new file mode 100644 index 0000000..dd6ad71 --- /dev/null +++ b/sapi/aolserver/php5aolserver.dsp @@ -0,0 +1,135 @@ +# Microsoft Developer Studio Project File - Name="php5aolserver" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5aolserver - 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 "php5aolserver.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 "php5aolserver.mak" CFG="php5aolserver - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5aolserver - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5aolserver - Win32 Release_TS_inline" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5aolserver - Win32 Debug_TS" (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)" == "php5aolserver - 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 "PHP5AOLSERVER_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D ZEND_DEBUG=0 /D "NDEBUG" /D "PHP5AOLSERVER_EXPORTS" /D "PHP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /D "HAVE_AOLSERVER" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 nsd.lib php5ts.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 /base:"0x62000000" /version:4.0 /dll /machine:I386 /out:"../../Release_TS/php5aolserver.so" /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Release_TS" /libpath:"..\..\TSRM\Release_TS" /libpath:"..\..\Zend\Release_TS"
+
+!ELSEIF "$(CFG)" == "php5aolserver - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release_TS_inline"
+# PROP BASE Intermediate_Dir "Release_TS_inline"
+# 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 /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP5AOLSERVER_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "NDEBUG" /D "PHPAOLSERVER_EXPORTS" /D "PHP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /D "HAVE_AOLSERVER" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 nsd.lib php5ts.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 /base:"0x62000000" /version:4.0 /dll /machine:I386 /out:"../../Release_TS_inline/php5aolserver.so" /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\TSRM\Release_TS_inline" /libpath:"..\..\Zend\Release_TS_inline"
+
+!ELSEIF "$(CFG)" == "php5aolserver - 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 "PHP5AOLSERVER_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D "_DEBUG" /D ZEND_DEBUG=1 /D "PHP5AOLSERVER_EXPORTS" /D "PHP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /D "HAVE_AOLSERVER" /FR /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /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 nsd.lib php5ts_debug.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 /base:"0x62000000" /version:4.0 /dll /debug /machine:I386 /out:"..\..\Debug_TS/php5aolserver.so" /pdbtype:sept /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Debug_TS" /libpath:"..\..\TSRM\Debug_TS" /libpath:"..\..\Zend\Debug_TS"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5aolserver - Win32 Release_TS"
+# Name "php5aolserver - Win32 Release_TS_inline"
+# Name "php5aolserver - Win32 Debug_TS"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\aolserver.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/sapi/apache/CREDITS b/sapi/apache/CREDITS new file mode 100644 index 0000000..991deb5 --- /dev/null +++ b/sapi/apache/CREDITS @@ -0,0 +1,3 @@ +Apache 1.3 +Rasmus Lerdorf, Zeev Suraski, Stig Bakken, David Sklar + diff --git a/sapi/apache/apMakefile.libdir b/sapi/apache/apMakefile.libdir new file mode 100644 index 0000000..7b52540 --- /dev/null +++ b/sapi/apache/apMakefile.libdir @@ -0,0 +1,4 @@ +This is a place-holder which indicates to Configure that it shouldn't +provide the default targets when building the Makefile in this directory. +Instead it'll just prepend all the important variable definitions, and +copy the Makefile.tmpl onto the end. diff --git a/sapi/apache/apMakefile.tmpl b/sapi/apache/apMakefile.tmpl new file mode 100644 index 0000000..5f77d9c --- /dev/null +++ b/sapi/apache/apMakefile.tmpl @@ -0,0 +1,77 @@ +## +## Apache 1.3 Makefile template for PHP 5.0 Module +## [src/modules/php5/Makefile.tmpl] +## + +# the parametrized target +LIB=libphp5.$(LIBEXT) + +# objects for building the static library +OBJS=mod_php5.o +OBJS_LIB=libmodphp5.a + +# objects for building the shared object library +SHLIB_OBJS=mod_php5.so-o +SHLIB_OBJS_LIB=libmodphp5.a + +# the general targets +all: lib +lib: $(LIB) + +# build the static library by merging the object files +libphp5.a: $(OBJS) $(OBJS_LIB) + cp $(OBJS_LIB) $@ + ar r $@ $(OBJS) + $(RANLIB) $@ + +# ugly hack to support older Apache-1.3 betas that don't set $LIBEXT +libphp5.: $(OBJS) $(OBJS_LIB) + cp $(OBJS_LIB) $@ + ar r $@ $(OBJS) + $(RANLIB) $@ + cp libphp5. libphp5.a + +# build the shared object library by linking the object files +libphp5.so: $(SHLIB_OBJS) $(SHLIB_OBJS_LIB) + rm -f $@ + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $@ $(SHLIB_OBJS) $(SHLIB_OBJS_LIB) $(LIBS) $(PHP_LIBS) + +# 1. extension .o for shared objects cannot be used here because +# first these files aren't still shared objects and second we +# have to use a different name to trigger the different +# implicit Make rule +# 2. extension -so.o (as used elsewhere) cannot be used because +# the suffix feature of Make really wants just .x, so we use +# extension .so-o +.SUFFIXES: .o .so-o +.c.o: + $(CC) -c $(INCLUDES) $(CFLAGS) $(PHP_CFLAGS) $(CPPFLAGS) $(SPACER) $< +.c.so-o: + $(CC) -c $(INCLUDES) $(CFLAGS) $(CFLAGS_SHLIB) $(PHP_CFLAGS) $(CPPFLAGS) $(SPACER) $< && mv $*.o $*.so-o + +# cleanup +clean: + -rm -f $(OBJS) $(SHLIB_OBJS) $(LIB) + +# We really don't expect end users to use this rule. It works only with +# gcc, and rebuilds Makefile.tmpl. You have to re-run Configure after +# using it. +depend: + cp Makefile.tmpl Makefile.tmpl.bak \ + && sed -ne '1,/^# DO NOT REMOVE/p' Makefile.tmpl > Makefile.new \ + && gcc -MM $(INCLUDES) $(CFLAGS) $(PHP_CFLAGS) $(CPPFLAGS) *.c >> Makefile.new \ + && sed -e '1,$$s: $(INCDIR)/: $$(INCDIR)/:g' Makefile.new \ + > Makefile.tmpl \ + && rm Makefile.new + +#Dependencies + +$(OBJS): Makefile + +# DO NOT REMOVE +mod_php5.o: mod_php5.c $(INCDIR)/httpd.h $(INCDIR)/conf.h \ + $(INCDIR)/buff.h \ + $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_request.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h mod_php5.h diff --git a/sapi/apache/config.m4 b/sapi/apache/config.m4 new file mode 100644 index 0000000..af83e9b --- /dev/null +++ b/sapi/apache/config.m4 @@ -0,0 +1,273 @@ +dnl +dnl $Id$ +dnl +AC_DEFUN([PHP_APACHE_FD_CHECK], [ +AC_CACHE_CHECK([for member fd in BUFF *],ac_cv_php_fd_in_buff,[ + save=$CPPFLAGS + if test -n "$APXS_INCLUDEDIR"; then + CPPFLAGS="$CPPFLAGS -I$APXS_INCLUDEDIR" + else + CPPFLAGS="$CPPFLAGS $APACHE_INCLUDE" + fi + AC_TRY_COMPILE([#include <httpd.h>],[conn_rec *c; int fd = c->client->fd;],[ + ac_cv_php_fd_in_buff=yes],[ac_cv_php_fd_in_buff=no],[ac_cv_php_fd_in_buff=no]) + CPPFLAGS=$save +]) +if test "$ac_cv_php_fd_in_buff" = "yes"; then + AC_DEFINE(PHP_APACHE_HAVE_CLIENT_FD,1,[ ]) +fi +]) + +dnl Apache 1.x shared module +PHP_ARG_WITH(apxs,, +[ --with-apxs[=FILE] Build shared Apache 1.x module. FILE is the optional + pathname to the Apache apxs tool [apxs]], no, no) + +AC_MSG_CHECKING([for Apache 1.x module support via DSO through APXS]) + +if test "$PHP_APXS" != "no"; then + if test "$PHP_APXS" = "yes"; then + APXS=apxs + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0" && test -x /usr/sbin/apxs; then #SUSE 6.x + APXS=/usr/sbin/apxs + fi + else + PHP_EXPAND_PATH($PHP_APXS, APXS) + fi + + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0"; then + AC_MSG_RESULT() + AC_MSG_RESULT() + AC_MSG_RESULT([Sorry, I was not able to successfully run APXS. Possible reasons:]) + AC_MSG_RESULT() + AC_MSG_RESULT([1. Perl is not installed;]) + AC_MSG_RESULT([2. Apache was not compiled with DSO support (--enable-module=so);]) + AC_MSG_RESULT([3. 'apxs' is not in your path. Try to use --with-apxs=/path/to/apxs]) + AC_MSG_RESULT([The output of $APXS follows]) + $APXS -q CFLAGS + AC_MSG_ERROR([Aborting]) + fi + + APXS_LDFLAGS="@SYBASE_LFLAGS@ @SYBASE_LIBS@ @SYBASE_CT_LFLAGS@ @SYBASE_CT_LIBS@" + APXS_INCLUDEDIR=`$APXS -q INCLUDEDIR` + APXS_CFLAGS=`$APXS -q CFLAGS` + APXS_HTTPD=`$APXS -q SBINDIR`/`$APXS -q TARGET` + APACHE_INCLUDE=-I$APXS_INCLUDEDIR + + # Test that we're trying to configure with apache 1.x + PHP_AP_EXTRACT_VERSION($APXS_HTTPD) + if test "$APACHE_VERSION" -ge 2000000; then + AC_MSG_ERROR([You have enabled Apache 1.3 support while your server is Apache 2. Please use the appropiate switch --with-apxs2]) + fi + + for flag in $APXS_CFLAGS; do + case $flag in + -D*) APACHE_CPPFLAGS="$APACHE_CPPFLAGS $flag";; + esac + done + + case $host_alias in + *aix*) + APXS_LIBEXECDIR=`$APXS -q LIBEXECDIR` + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-brtl -Wl,-bI:$APXS_LIBEXECDIR/httpd.exp" + PHP_AIX_LDFLAGS="-Wl,-brtl" + build_type=shared + ;; + *darwin*) + MH_BUNDLE_FLAGS="-dynamic -twolevel_namespace -bundle -bundle_loader $APXS_HTTPD" + PHP_SUBST(MH_BUNDLE_FLAGS) + SAPI_SHARED=libs/libphp5.so + build_type=bundle + ;; + *) + build_type=shared + ;; + esac + + PHP_SELECT_SAPI(apache, $build_type, sapi_apache.c mod_php5.c php_apache.c, $APACHE_CPPFLAGS -I$APXS_INCLUDEDIR) + + # Test whether apxs support -S option + $APXS -q -S CFLAGS="$APXS_CFLAGS" CFLAGS >/dev/null 2>&1 + + if test "$?" != "0"; then + APACHE_INSTALL="$APXS -i -a -n php5 $SAPI_SHARED" # Old apxs does not have -S option + else + APXS_LIBEXECDIR='$(INSTALL_ROOT)'`$APXS -q LIBEXECDIR` + if test -z `$APXS -q SYSCONFDIR`; then + APACHE_INSTALL="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -i -n php5 $SAPI_SHARED" + else + APXS_SYSCONFDIR='$(INSTALL_ROOT)'`$APXS -q SYSCONFDIR` + APACHE_INSTALL="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + \$(mkinstalldirs) '$APXS_SYSCONFDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -S SYSCONFDIR='$APXS_SYSCONFDIR' \ + -i -a -n php5 $SAPI_SHARED" + fi + fi + + if test -z "`$APXS -q LD_SHLIB`" || test "`$APXS -q LIBEXECDIR`" = "modules"; then + PHP_APXS_BROKEN=yes + fi + STRONGHOLD= + AC_DEFINE(HAVE_AP_CONFIG_H,1,[ ]) + AC_DEFINE(HAVE_AP_COMPAT_H,1,[ ]) + AC_DEFINE(HAVE_APACHE,1,[ ]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +dnl Apache 1.x static module +PHP_ARG_WITH(apache,, +[ --with-apache[=DIR] Build Apache 1.x module. DIR is the top-level Apache + build directory [/usr/local/apache]], no, no) + +AC_MSG_CHECKING([for Apache 1.x module support]) + +if test "$PHP_SAPI" != "apache" && test "$PHP_APACHE" != "no"; then + + if test "$PHP_APACHE" = "yes"; then + # Apache's default directory + PHP_APACHE=/usr/local/apache + fi + + APACHE_INSTALL_FILES="\$(srcdir)/sapi/apache/mod_php5.* sapi/apache/libphp5.module" + + AC_DEFINE(HAVE_APACHE,1,[ ]) + APACHE_MODULE=yes + PHP_EXPAND_PATH($PHP_APACHE, PHP_APACHE) + # For Apache 1.2.x + if test -f $PHP_APACHE/src/httpd.h; then + APACHE_INCLUDE=-I$PHP_APACHE/src + APACHE_TARGET=$PHP_APACHE/src + PHP_SELECT_SAPI(apache, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + APACHE_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_INSTALL_FILES $APACHE_TARGET" + PHP_LIBS="-L. -lphp3" + AC_MSG_RESULT([yes - Apache 1.2.x]) + STRONGHOLD= + if test -f $PHP_APACHE/src/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H,1,[ ]) + fi + # For Apache 2.0.x + elif test -f $PHP_APACHE/include/httpd.h && test -f $PHP_APACHE/srclib/apr/include/apr_general.h ; then + AC_MSG_ERROR([Use --with-apxs2 with Apache 2.x!]) + # For Apache 1.3.x + elif test -f $PHP_APACHE/src/main/httpd.h; then + APACHE_HAS_REGEX=1 + APACHE_INCLUDE="-I$PHP_APACHE/src/main -I$PHP_APACHE/src/os/unix -I$PHP_APACHE/src/ap" + APACHE_TARGET=$PHP_APACHE/src/modules/php5 + if test ! -d $APACHE_TARGET; then + mkdir $APACHE_TARGET + fi + PHP_SELECT_SAPI(apache, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + APACHE_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_INSTALL_FILES $APACHE_TARGET; cp $srcdir/sapi/apache/apMakefile.tmpl $APACHE_TARGET/Makefile.tmpl; cp $srcdir/sapi/apache/apMakefile.libdir $APACHE_TARGET/Makefile.libdir" + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + AC_MSG_RESULT([yes - Apache 1.3.x]) + STRONGHOLD= + if test -f $PHP_APACHE/src/include/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE/src/include/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE/src/include/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + # Also for Apache 1.3.x + elif test -f $PHP_APACHE/src/include/httpd.h; then + APACHE_HAS_REGEX=1 + APACHE_INCLUDE="-I$PHP_APACHE/src/include -I$PHP_APACHE/src/os/unix" + APACHE_TARGET=$PHP_APACHE/src/modules/php5 + if test ! -d $APACHE_TARGET; then + mkdir $APACHE_TARGET + fi + PHP_SELECT_SAPI(apache, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + APACHE_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_INSTALL_FILES $APACHE_TARGET; cp $srcdir/sapi/apache/apMakefile.tmpl $APACHE_TARGET/Makefile.tmpl; cp $srcdir/sapi/apache/apMakefile.libdir $APACHE_TARGET/Makefile.libdir" + AC_MSG_RESULT([yes - Apache 1.3.x]) + STRONGHOLD= + if test -f $PHP_APACHE/src/include/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE/src/include/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE/src/include/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + # For StrongHold 2.2 + elif test -f $PHP_APACHE/apache/httpd.h; then + APACHE_INCLUDE="-I$PHP_APACHE/apache -I$PHP_APACHE/ssl/include" + APACHE_TARGET=$PHP_APACHE/apache + PHP_SELECT_SAPI(apache, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + APACHE_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_INSTALL_FILES $APACHE_TARGET" + STRONGHOLD=-DSTRONGHOLD=1 + AC_MSG_RESULT([yes - StrongHold]) + if test -f $PHP_APACHE/apache/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE/src/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE/src/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + else + AC_MSG_RESULT(no) + AC_MSG_ERROR([Invalid Apache directory - unable to find httpd.h under $PHP_APACHE]) + fi +else + AC_MSG_RESULT(no) +fi + +# compatibility +if test -z "$enable_mod_charset" && test "$with_mod_charset"; then + enable_mod_charset=$with_mod_charset +fi + +PHP_ARG_ENABLE(mod-charset, whether to enable Apache charset compatibility option, +[ --enable-mod-charset APACHE: Enable transfer tables for mod_charset (Rus Apache)], no, no) + +if test "$PHP_MOD_CHARSET" = "yes"; then + AC_DEFINE(USE_TRANSFER_TABLES, 1, [ ]) +fi + +dnl Build as static module +if test "$APACHE_MODULE" = "yes"; then + PHP_TARGET_RDYNAMIC + $php_shtool mkdir -p sapi/apache + PHP_OUTPUT(sapi/apache/libphp5.module) +fi + +dnl General +if test -n "$APACHE_INSTALL"; then + if test "x$APXS" != "x" -a "`uname -sv`" = "AIX 4" -a "$GCC" != "yes"; then + APXS_EXP=-bE:sapi/apache/mod_php5.exp + fi + + PHP_APACHE_FD_CHECK + INSTALL_IT=$APACHE_INSTALL + + PHP_SUBST(APXS_EXP) + PHP_SUBST(APACHE_INCLUDE) + PHP_SUBST(APACHE_TARGET) + PHP_SUBST(APXS) + PHP_SUBST(APXS_LDFLAGS) + PHP_SUBST(APACHE_INSTALL) + PHP_SUBST(STRONGHOLD) +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/apache/config.w32 b/sapi/apache/config.w32 new file mode 100644 index 0000000..e876d75 --- /dev/null +++ b/sapi/apache/config.w32 @@ -0,0 +1,24 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('apache', 'Build Apache 1.3.x version of PHP', 'no'); + +ARG_WITH('apache-includes', 'Where to find Apache 1.3 headers', null); +ARG_WITH('apache-libs', 'Where to find Apache 1.3 libraries', null); + +if (PHP_APACHE != "no") { + if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE", php_usual_include_suspects + + ";" + PROGRAM_FILES + "\\Apache Group\\Apache\\include" + + ";" + PHP_PHP_BUILD + "\\apache\\src\\include") && + CHECK_LIB("ApacheCore.lib", "apache", php_usual_lib_suspects + + ';' + PROGRAM_FILES + '\\Apache Group\\Apache\\libexec' + + ";" + PHP_PHP_BUILD + "\\apache\\src\\corer")) { + // We need to play tricks to get our readdir.h used by apache + // headers + SAPI('apache', 'mod_php5.c sapi_apache.c php_apache.c', + 'php' + PHP_VERSION + 'apache.dll', + '/D APACHEPHP5_EXPORTS /D APACHE_READDIR_H /I win32'); + } else { + WARNING("Could not find apache libraries/headers"); + } +} diff --git a/sapi/apache/libphp5.module.in b/sapi/apache/libphp5.module.in new file mode 100644 index 0000000..00d9c49 --- /dev/null +++ b/sapi/apache/libphp5.module.in @@ -0,0 +1,11 @@ +Name: php5_module +ConfigStart + RULE_WANTHSREGEX=no + RULE_HIDE=yes + PHP_LIBS="@NATIVE_RPATHS@ @PHP_LDFLAGS@ @PHP_LIBS@ @EXTRA_LDFLAGS@ @EXTRA_LIBS@ $LIBS" + PHP_CFLAGS="$CFLAGS @OPENSSL_INCDIR_OPT@ -I@php_abs_top_builddir@/main -I@php_abs_top_builddir@/Zend -I@php_abs_top_builddir@/TSRM -I@php_abs_top_srcdir@ -I@php_abs_top_srcdir@/sapi/apache -I@php_abs_top_srcdir@/main -I@php_abs_top_srcdir@/Zend -I@php_abs_top_srcdir@/TSRM" + my_outfile="Makefile.config" + echo "PHP_CFLAGS=$PHP_CFLAGS" >>$my_outfile + echo "PHP_LIBS=$PHP_LIBS" >>$my_outfile + LIBS=$PHP_LIBS +ConfigEnd diff --git a/sapi/apache/libpre.c b/sapi/apache/libpre.c new file mode 100644 index 0000000..35b47cf --- /dev/null +++ b/sapi/apache/libpre.c @@ -0,0 +1,55 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef NETWARE + +/* ------------------------------------------------------------------ + * These functions are to be called when the shared NLM starts and + * stops. By using these functions instead of defining a main() + * and calling ExitThread(TSR_THREAD, 0), the load time of the + * shared NLM is faster and memory size reduced. + * + * You may also want to override these in your own Apache module + * to do any cleanup other than the mechanism Apache modules provide. + * ------------------------------------------------------------------ + */ + + +#ifdef __GNUC__ +#include <string.h> /* memset */ +extern char _edata, _end ; /* end of DATA (start of BSS), end of BSS */ +#endif + +int _lib_start() +{ +/* printf("Inside _lib_start\n");*/ +#ifdef __GNUC__ + memset (&_edata, 0, &_end - &_edata); +#endif + return 0; +} + +int _lib_stop() +{ +/* printf("Inside _lib_stop\n");*/ + return 0; +} + +#endif /* NETWARE */ diff --git a/sapi/apache/mod_php5.c b/sapi/apache/mod_php5.c new file mode 100644 index 0000000..11be0ed --- /dev/null +++ b/sapi/apache/mod_php5.c @@ -0,0 +1,1040 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@php.net> | + | (with helpful hints from Dean Gaudet <dgaudet@arctic.org> | + | PHP 4.0 patches by Zeev Suraski <zeev@zend.com> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" +#include "http_conf_globals.h" + +#ifdef NETWARE +#define SIGPIPE SIGINT +#endif + +#undef shutdown + +/* {{{ Prototypes + */ +int apache_php_module_main(request_rec *r, int display_source_mode TSRMLS_DC); +static void php_save_umask(void); +static void php_restore_umask(void); +static int sapi_apache_read_post(char *buffer, uint count_bytes TSRMLS_DC); +static char *sapi_apache_read_cookies(TSRMLS_D); +static int sapi_apache_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC); +static int sapi_apache_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC); +static int send_php(request_rec *r, int display_source_mode, char *filename); +static int send_parsed_php(request_rec * r); +static int send_parsed_php_source(request_rec * r); +static int php_xbithack_handler(request_rec * r); +static void php_init_handler(server_rec *s, pool *p); +/* }}} */ + +#if MODULE_MAGIC_NUMBER >= 19970728 +static void php_child_exit_handler(server_rec *s, pool *p); +#endif + +#if MODULE_MAGIC_NUMBER > 19961007 +#define CONST_PREFIX const +#else +#define CONST_PREFIX +#endif +static CONST_PREFIX char *php_apache_value_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode); +static CONST_PREFIX char *php_apache_value_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_admin_value_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_flag_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_flag_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode); +static CONST_PREFIX char *php_apache_admin_flag_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2); + +/* ### these should be defined in mod_php5.h or somewhere else */ +#define USE_PATH 1 +#define IGNORE_URL 2 +#define MAX_STATUS_LENGTH sizeof("xxxx LONGEST POSSIBLE STATUS DESCRIPTION") + +module MODULE_VAR_EXPORT php5_module; + +int saved_umask; +static unsigned char apache_php_initialized; + +typedef struct _php_per_dir_entry { + char *key; + char *value; + uint key_length; + uint value_length; + int type; + char htaccess; +} php_per_dir_entry; + +/* some systems are missing these from their header files */ + +/* {{{ php_save_umask + */ +static void php_save_umask(void) +{ + saved_umask = umask(077); + umask(saved_umask); +} +/* }}} */ + +/* {{{ sapi_apache_ub_write + */ +static int sapi_apache_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int ret=0; + + if (SG(server_context)) { + ret = rwrite(str, str_length, (request_rec *) SG(server_context)); + } + if (ret != str_length) { + php_handle_aborted_connection(); + } + return ret; +} +/* }}} */ + +/* {{{ sapi_apache_flush + */ +static void sapi_apache_flush(void *server_context) +{ + if (server_context) { +#if MODULE_MAGIC_NUMBER > 19970110 + rflush((request_rec *) server_context); +#else + bflush((request_rec *) server_context->connection->client); +#endif + } +} +/* }}} */ + +/* {{{ sapi_apache_read_post + */ +static int sapi_apache_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + int total_read_bytes=0, read_bytes; + request_rec *r = (request_rec *) SG(server_context); + void (*handler)(int); + + /* + * This handles the situation where the browser sends a Expect: 100-continue header + * and needs to recieve confirmation from the server on whether or not it can send + * the rest of the request. RFC 2616 + * + */ + if (!SG(read_post_bytes) && !ap_should_client_block(r)) { + return total_read_bytes; + } + + handler = signal(SIGPIPE, SIG_IGN); + while (total_read_bytes<count_bytes) { + hard_timeout("Read POST information", r); /* start timeout timer */ + read_bytes = get_client_block(r, buffer+total_read_bytes, count_bytes-total_read_bytes); + reset_timeout(r); + if (read_bytes<=0) { + break; + } + total_read_bytes += read_bytes; + } + signal(SIGPIPE, handler); + return total_read_bytes; +} +/* }}} */ + +/* {{{ sapi_apache_read_cookies + */ +static char *sapi_apache_read_cookies(TSRMLS_D) +{ + return (char *) table_get(((request_rec *) SG(server_context))->subprocess_env, "HTTP_COOKIE"); +} +/* }}} */ + +/* {{{ sapi_apache_header_handler + */ +static int sapi_apache_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + request_rec *r = (request_rec *) SG(server_context); + if(!r) { + return 0; + } + + switch(op) { + case SAPI_HEADER_DELETE_ALL: + clear_table(r->headers_out); + return 0; + + case SAPI_HEADER_DELETE: + table_unset(r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + header_name = sapi_header->header; + + header_content = p = strchr(header_name, ':'); + if (!p) { + return 0; + } + + *p = 0; + do { + header_content++; + } while (*header_content==' '); + + if (!strcasecmp(header_name, "Content-Type")) { + r->content_type = pstrdup(r->pool, header_content); + } else if (!strcasecmp(header_name, "Content-Length")) { + ap_set_content_length(r, strtol(header_content, (char **)NULL, 10)); + } else if (!strcasecmp(header_name, "Set-Cookie")) { + table_add(r->headers_out, header_name, header_content); + } else if (op == SAPI_HEADER_REPLACE) { + table_set(r->headers_out, header_name, header_content); + } else { + table_add(r->headers_out, header_name, header_content); + } + + *p = ':'; /* a well behaved header handler shouldn't change its original arguments */ + + return SAPI_HEADER_ADD; + + default: + return 0; + } +} +/* }}} */ + +/* {{{ sapi_apache_send_headers + */ +static int sapi_apache_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + request_rec *r = SG(server_context); + const char *sline = SG(sapi_headers).http_status_line; + int sline_len; + + if(r == NULL) { /* server_context is not here anymore */ + return SAPI_HEADER_SEND_FAILED; + } + + r->status = SG(sapi_headers).http_response_code; + + /* 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] == ' ' && sline[12] == ' ') { + if ((sline_len - 9) > MAX_STATUS_LENGTH) { + r->status_line = ap_pstrndup(r->pool, sline + 9, MAX_STATUS_LENGTH); + } else { + r->status_line = ap_pstrndup(r->pool, sline + 9, sline_len - 9); + } + } + + if(r->status==304) { + send_error_response(r,0); + } else { + send_http_header(r); + } + return SAPI_HEADER_SENT_SUCCESSFULLY; +} +/* }}} */ + +/* {{{ sapi_apache_register_server_variables + */ +static void sapi_apache_register_server_variables(zval *track_vars_array TSRMLS_DC) +{ + register int i; + array_header *arr = table_elts(((request_rec *) SG(server_context))->subprocess_env); + table_entry *elts = (table_entry *) arr->elts; + zval **path_translated; + HashTable *symbol_table; + unsigned int new_val_len; + + for (i = 0; i < arr->nelts; i++) { + char *val; + int val_len; + + if (elts[i].val) { + val = elts[i].val; + } else { + val = ""; + } + val_len = strlen(val); + if (sapi_module.input_filter(PARSE_SERVER, elts[i].key, &val, val_len, &new_val_len TSRMLS_CC)) { + php_register_variable_safe(elts[i].key, val, new_val_len, track_vars_array TSRMLS_CC); + } + } + + /* If PATH_TRANSLATED doesn't exist, copy it from SCRIPT_FILENAME */ + if (track_vars_array) { + symbol_table = track_vars_array->value.ht; + } else { + symbol_table = NULL; + } + if (symbol_table + && !zend_hash_exists(symbol_table, "PATH_TRANSLATED", sizeof("PATH_TRANSLATED")) + && zend_hash_find(symbol_table, "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME"), (void **) &path_translated)==SUCCESS) { + php_register_variable("PATH_TRANSLATED", Z_STRVAL_PP(path_translated), track_vars_array TSRMLS_CC); + } + + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &((request_rec *) SG(server_context))->uri, strlen(((request_rec *) SG(server_context))->uri), &new_val_len TSRMLS_CC)) { + php_register_variable("PHP_SELF", ((request_rec *) SG(server_context))->uri, track_vars_array TSRMLS_CC); + } +} +/* }}} */ + +/* {{{ php_apache_startup + */ +static int php_apache_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &apache_module_entry, 1) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} +/* }}} */ + +/* {{{ php_apache_log_message + */ +static void php_apache_log_message(char *message TSRMLS_DC) +{ + if (SG(server_context)) { +#if MODULE_MAGIC_NUMBER >= 19970831 + aplog_error(NULL, 0, APLOG_ERR | APLOG_NOERRNO, ((request_rec *) SG(server_context))->server, "%s", message); +#else + log_error(message, ((request_rec *) SG(server_context))->server); +#endif + } else { + fprintf(stderr, "%s\n", message); + } +} +/* }}} */ + +/* {{{ php_apache_request_shutdown + */ +static void php_apache_request_shutdown(void *dummy) +{ + TSRMLS_FETCH(); + + php_output_set_status(PHP_OUTPUT_DISABLED TSRMLS_CC); + if (AP(in_request)) { + AP(in_request) = 0; + php_request_shutdown(dummy); + } + SG(server_context) = NULL; + /* + * The server context (request) is NOT invalid by the time + * run_cleanups() is called + */ +} +/* }}} */ + +/* {{{ php_apache_sapi_activate + */ +static int php_apache_sapi_activate(TSRMLS_D) +{ + request_rec *r = (request_rec *) SG(server_context); + + /* + * For the Apache module version, this bit of code registers a cleanup + * function that gets triggered when our request pool is destroyed. + * We need this because at any point in our code we can be interrupted + * and that may happen before we have had time to free our memory. + * The php_request_shutdown function needs to free all outstanding allocated + * memory. + */ + block_alarms(); + register_cleanup(r->pool, NULL, php_apache_request_shutdown, php_request_shutdown_for_exec); + AP(in_request)=1; + unblock_alarms(); + + /* Override the default headers_only value - sometimes "GET" requests should actually only + * send headers. + */ + SG(request_info).headers_only = r->header_only; + return SUCCESS; +} +/* }}} */ + +/* {{{ php_apache_get_stat + */ +static struct stat *php_apache_get_stat(TSRMLS_D) +{ + return &((request_rec *) SG(server_context))->finfo; +} +/* }}} */ + +/* {{{ php_apache_getenv + */ +static char *php_apache_getenv(char *name, size_t name_len TSRMLS_DC) +{ + if (SG(server_context) == NULL) { + return NULL; + } + + return (char *) table_get(((request_rec *) SG(server_context))->subprocess_env, name); +} +/* }}} */ + +/* {{{ sapi_apache_get_fd + */ +static int sapi_apache_get_fd(int *nfd TSRMLS_DC) +{ +#if PHP_APACHE_HAVE_CLIENT_FD + request_rec *r = SG(server_context); + int fd; + + fd = r->connection->client->fd; + + if (fd >= 0) { + if (nfd) *nfd = fd; + return SUCCESS; + } +#endif + return FAILURE; +} +/* }}} */ + +/* {{{ sapi_apache_force_http_10 + */ +static int sapi_apache_force_http_10(TSRMLS_D) +{ + request_rec *r = SG(server_context); + + r->proto_num = HTTP_VERSION(1,0); + + return SUCCESS; +} +/* }}} */ + +/* {{{ sapi_apache_get_target_uid + */ +static int sapi_apache_get_target_uid(uid_t *obj TSRMLS_DC) +{ + *obj = ap_user_id; + return SUCCESS; +} +/* }}} */ + +/* {{{ sapi_apache_get_target_gid + */ +static int sapi_apache_get_target_gid(gid_t *obj TSRMLS_DC) +{ + *obj = ap_group_id; + return SUCCESS; +} +/* }}} */ + +/* {{{ php_apache_get_request_time + */ +static double php_apache_get_request_time(TSRMLS_D) +{ + return (double) ((request_rec *)SG(server_context))->request_time; +} +/* }}} */ + +/* {{{ sapi_apache_child_terminate + */ +static void sapi_apache_child_terminate(TSRMLS_D) +{ +#ifndef MULTITHREAD + ap_child_terminate((request_rec *)SG(server_context)); +#endif +} +/* }}} */ + +/* {{{ sapi_module_struct apache_sapi_module + */ +static sapi_module_struct apache_sapi_module = { + "apache", /* name */ + "Apache", /* pretty name */ + + php_apache_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + php_apache_sapi_activate, /* activate */ + NULL, /* deactivate */ + + sapi_apache_ub_write, /* unbuffered write */ + sapi_apache_flush, /* flush */ + php_apache_get_stat, /* get uid */ + php_apache_getenv, /* getenv */ + + php_error, /* error handler */ + + sapi_apache_header_handler, /* header handler */ + sapi_apache_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_apache_read_post, /* read POST data */ + sapi_apache_read_cookies, /* read Cookies */ + + sapi_apache_register_server_variables, /* register server variables */ + php_apache_log_message, /* Log message */ + php_apache_get_request_time, /* Get request time */ + sapi_apache_child_terminate, + + NULL, /* php.ini path override */ + +#ifdef PHP_WIN32 + NULL, + NULL, +#else + block_alarms, /* Block interruptions */ + unblock_alarms, /* Unblock interruptions */ +#endif + + NULL, /* default post reader */ + NULL, /* treat data */ + NULL, /* exe location */ + 0, /* ini ignore */ + 0, /* ini ignore cwd */ + sapi_apache_get_fd, + sapi_apache_force_http_10, + sapi_apache_get_target_uid, + sapi_apache_get_target_gid +}; +/* }}} */ + +/* {{{ php_restore_umask + */ +static void php_restore_umask(void) +{ + umask(saved_umask); +} +/* }}} */ + +/* {{{ init_request_info + */ +static void init_request_info(TSRMLS_D) +{ + request_rec *r = ((request_rec *) SG(server_context)); + char *content_length = (char *) table_get(r->subprocess_env, "CONTENT_LENGTH"); + const char *authorization=NULL; + char *tmp, *tmp_user; + + SG(request_info).query_string = r->args; + SG(request_info).path_translated = r->filename; + SG(request_info).request_uri = r->uri; + SG(request_info).request_method = (char *)r->method; + SG(request_info).content_type = (char *) table_get(r->subprocess_env, "CONTENT_TYPE"); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + SG(sapi_headers).http_response_code = r->status; + SG(request_info).proto_num = r->proto_num; + + if (r->headers_in) { + authorization = table_get(r->headers_in, "Authorization"); + } + + SG(request_info).auth_user = NULL; + SG(request_info).auth_password = NULL; + SG(request_info).auth_digest = NULL; + + if (authorization) { + char *p = getword(r->pool, &authorization, ' '); + if (!strcasecmp(p, "Basic")) { + tmp = uudecode(r->pool, authorization); + tmp_user = getword_nulls_nc(r->pool, &tmp, ':'); + if (tmp_user) { + r->connection->user = pstrdup(r->connection->pool, tmp_user); + r->connection->ap_auth_type = "Basic"; + SG(request_info).auth_user = estrdup(tmp_user); + } + if (tmp) { + SG(request_info).auth_password = estrdup(tmp); + } + } else if (!strcasecmp(p, "Digest")) { + r->connection->ap_auth_type = "Digest"; + SG(request_info).auth_digest = estrdup(authorization); + } + } +} +/* }}} */ + +/* {{{ php_apache_alter_ini_entries + */ +static int php_apache_alter_ini_entries(php_per_dir_entry *per_dir_entry TSRMLS_DC) +{ + zend_alter_ini_entry(per_dir_entry->key, per_dir_entry->key_length+1, per_dir_entry->value, per_dir_entry->value_length, per_dir_entry->type, per_dir_entry->htaccess?PHP_INI_STAGE_HTACCESS:PHP_INI_STAGE_ACTIVATE); + return 0; +} +/* }}} */ + +/* {{{ php_apache_get_default_mimetype + */ +static char *php_apache_get_default_mimetype(request_rec *r TSRMLS_DC) +{ + + char *mimetype; + if (SG(default_mimetype) || SG(default_charset)) { + /* Assume output will be of the default MIME type. Individual + scripts may change this later. */ + char *tmpmimetype; + tmpmimetype = sapi_get_default_content_type(TSRMLS_C); + mimetype = pstrdup(r->pool, tmpmimetype); + efree(tmpmimetype); + } else { + mimetype = SAPI_DEFAULT_MIMETYPE "; charset=" SAPI_DEFAULT_CHARSET; + } + return mimetype; +} +/* }}} */ + +/* {{{ send_php + */ +static int send_php(request_rec *r, int display_source_mode, char *filename) +{ + int retval; + HashTable *per_dir_conf; + TSRMLS_FETCH(); + + if (AP(in_request)) { + zend_file_handle fh; + + fh.filename = r->filename; + fh.opened_path = NULL; + fh.free_filename = 0; + fh.type = ZEND_HANDLE_FILENAME; + + zend_execute_scripts(ZEND_INCLUDE TSRMLS_CC, NULL, 1, &fh); + return OK; + } + + SG(server_context) = r; + + zend_first_try { + + /* Make sure file exists */ + if (filename == NULL && r->finfo.st_mode == 0) { + return DECLINED; + } + + per_dir_conf = (HashTable *) get_module_config(r->per_dir_config, &php5_module); + if (per_dir_conf) { + zend_hash_apply((HashTable *) per_dir_conf, (apply_func_t) php_apache_alter_ini_entries TSRMLS_CC); + } + + /* If PHP parser engine has been turned off with an "engine off" + * directive, then decline to handle this request + */ + if (!AP(engine)) { + r->content_type = php_apache_get_default_mimetype(r TSRMLS_CC); + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return DECLINED; + } + if (filename == NULL) { + filename = r->filename; + } + + /* Apache 1.2 has a more complex mechanism for reading POST data */ +#if MODULE_MAGIC_NUMBER > 19961007 + if ((retval = setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return retval; + } +#endif + + if (AP(last_modified)) { +#if MODULE_MAGIC_NUMBER < 19970912 + if ((retval = set_last_modified(r, r->finfo.st_mtime))) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return retval; + } +#else + update_mtime (r, r->finfo.st_mtime); + set_last_modified(r); + set_etag(r); +#endif + } + /* Assume output will be of the default MIME type. Individual + scripts may change this later in the request. */ + r->content_type = php_apache_get_default_mimetype(r TSRMLS_CC); + + /* Init timeout */ + hard_timeout("send", r); + + php_save_umask(); + add_common_vars(r); + add_cgi_vars(r); + + init_request_info(TSRMLS_C); + apache_php_module_main(r, display_source_mode TSRMLS_CC); + + /* Done, restore umask, turn off timeout, close file and return */ + php_restore_umask(); + kill_timeout(r); + } zend_end_try(); + + return OK; +} +/* }}} */ + +/* {{{ send_parsed_php + */ +static int send_parsed_php(request_rec * r) +{ + int result = send_php(r, 0, NULL); + TSRMLS_FETCH(); + + ap_table_setn(r->notes, "mod_php_memory_usage", + ap_psprintf(r->pool, "%lu", zend_memory_peak_usage(1 TSRMLS_CC))); + + return result; +} +/* }}} */ + +/* {{{ send_parsed_php_source + */ +static int send_parsed_php_source(request_rec * r) +{ + return send_php(r, 1, NULL); +} +/* }}} */ + +/* {{{ destroy_per_dir_entry + */ +static void destroy_per_dir_entry(php_per_dir_entry *per_dir_entry) +{ + free(per_dir_entry->key); + free(per_dir_entry->value); +} +/* }}} */ + +/* {{{ copy_per_dir_entry + */ +static void copy_per_dir_entry(php_per_dir_entry *per_dir_entry) +{ + php_per_dir_entry tmp = *per_dir_entry; + + per_dir_entry->key = (char *) malloc(tmp.key_length+1); + memcpy(per_dir_entry->key, tmp.key, tmp.key_length); + per_dir_entry->key[per_dir_entry->key_length] = 0; + + per_dir_entry->value = (char *) malloc(tmp.value_length+1); + memcpy(per_dir_entry->value, tmp.value, tmp.value_length); + per_dir_entry->value[per_dir_entry->value_length] = 0; +} +/* }}} */ + +/* {{{ should_overwrite_per_dir_entry + */ +static zend_bool should_overwrite_per_dir_entry(HashTable *target_ht, php_per_dir_entry *new_per_dir_entry, zend_hash_key *hash_key, void *pData) +{ + php_per_dir_entry *orig_per_dir_entry; + + if (zend_hash_find(target_ht, hash_key->arKey, hash_key->nKeyLength, (void **) &orig_per_dir_entry)==FAILURE) { + return 1; /* does not exist in dest, copy from source */ + } + + if (orig_per_dir_entry->type==PHP_INI_SYSTEM + && new_per_dir_entry->type!=PHP_INI_SYSTEM) { + return 0; + } else { + return 1; + } +} +/* }}} */ + +/* {{{ php_destroy_per_dir_info + */ +static void php_destroy_per_dir_info(HashTable *per_dir_info) +{ + zend_hash_destroy(per_dir_info); + free(per_dir_info); +} +/* }}} */ + +/* {{{ php_create_dir + */ +static void *php_create_dir(pool *p, char *dummy) +{ + HashTable *per_dir_info; + + per_dir_info = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init_ex(per_dir_info, 5, NULL, (void (*)(void *)) destroy_per_dir_entry, 1, 0); + register_cleanup(p, (void *) per_dir_info, (void (*)(void *)) php_destroy_per_dir_info, (void (*)(void *)) zend_hash_destroy); + + return per_dir_info; +} +/* }}} */ + +/* {{{ php_merge_dir + */ +static void *php_merge_dir(pool *p, void *basev, void *addv) +{ + /* This function *must* not modify addv or basev */ + HashTable *new; + + /* need a copy of addv to merge */ + new = php_create_dir(p, "php_merge_dir"); + zend_hash_copy(new, (HashTable *) basev, (copy_ctor_func_t) copy_per_dir_entry, NULL, sizeof(php_per_dir_entry)); + + zend_hash_merge_ex(new, (HashTable *) addv, (copy_ctor_func_t) copy_per_dir_entry, sizeof(php_per_dir_entry), (merge_checker_func_t) should_overwrite_per_dir_entry, NULL); + return new; +} +/* }}} */ + +/* {{{ php_apache_value_handler_ex + */ +static CONST_PREFIX char *php_apache_value_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode) +{ + php_per_dir_entry per_dir_entry; + + if (!apache_php_initialized) { + apache_php_initialized = 1; +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache_sapi_module); + php_apache_startup(&apache_sapi_module); + } + per_dir_entry.type = mode; + per_dir_entry.htaccess = ((cmd->override & (RSRC_CONF|ACCESS_CONF)) == 0); + + if (strcasecmp(arg2, "none") == 0) { + arg2 = ""; + } + + per_dir_entry.key_length = strlen(arg1); + per_dir_entry.value_length = strlen(arg2); + + per_dir_entry.key = (char *) malloc(per_dir_entry.key_length+1); + memcpy(per_dir_entry.key, arg1, per_dir_entry.key_length); + per_dir_entry.key[per_dir_entry.key_length] = 0; + + per_dir_entry.value = (char *) malloc(per_dir_entry.value_length+1); + memcpy(per_dir_entry.value, arg2, per_dir_entry.value_length); + per_dir_entry.value[per_dir_entry.value_length] = 0; + + zend_hash_update(conf, per_dir_entry.key, per_dir_entry.key_length, &per_dir_entry, sizeof(php_per_dir_entry), NULL); + return NULL; +} +/* }}} */ + +/* {{{ php_apache_value_handler + */ +static CONST_PREFIX char *php_apache_value_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2) +{ + return php_apache_value_handler_ex(cmd, conf, arg1, arg2, PHP_INI_PERDIR); +} +/* }}} */ + +/* {{{ php_apache_admin_value_handler + */ +static CONST_PREFIX char *php_apache_admin_value_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2) +{ + return php_apache_value_handler_ex(cmd, conf, arg1, arg2, PHP_INI_SYSTEM); +} +/* }}} */ + +/* {{{ php_apache_flag_handler_ex + */ +static CONST_PREFIX char *php_apache_flag_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode) +{ + char bool_val[2]; + + if (!strcasecmp(arg2, "On") || (arg2[0] == '1' && arg2[1] == '\0')) { + bool_val[0] = '1'; + } else { + bool_val[0] = '0'; + } + bool_val[1] = 0; + + return php_apache_value_handler_ex(cmd, conf, arg1, bool_val, mode); +} +/* }}} */ + +/* {{{ php_apache_flag_handler + */ +static CONST_PREFIX char *php_apache_flag_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2) +{ + return php_apache_flag_handler_ex(cmd, conf, arg1, arg2, PHP_INI_PERDIR); +} +/* }}} */ + +/* {{{ php_apache_admin_flag_handler + */ +static CONST_PREFIX char *php_apache_admin_flag_handler(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2) +{ + return php_apache_flag_handler_ex(cmd, conf, arg1, arg2, PHP_INI_SYSTEM); +} +/* }}} */ + +/* {{{ php_apache_phpini_set + */ +static CONST_PREFIX char *php_apache_phpini_set(cmd_parms *cmd, HashTable *conf, char *arg) +{ + if (apache_sapi_module.php_ini_path_override) { + return "Only first PHPINIDir directive honored per configuration tree - subsequent ones ignored"; + } + apache_sapi_module.php_ini_path_override = ap_server_root_relative(cmd->pool, arg); + return NULL; +} +/* }}} */ + +/* {{{ int php_xbithack_handler(request_rec * r) + */ +static int php_xbithack_handler(request_rec * r) +{ + HashTable *per_dir_conf; + TSRMLS_FETCH(); + + if (!(r->finfo.st_mode & S_IXUSR)) { + return DECLINED; + } + per_dir_conf = (HashTable *) get_module_config(r->per_dir_config, &php5_module); + if (per_dir_conf) { + zend_hash_apply((HashTable *) per_dir_conf, (apply_func_t) php_apache_alter_ini_entries TSRMLS_CC); + } + if(!AP(xbithack)) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return DECLINED; + } + return send_parsed_php(r); +} +/* }}} */ + +/* {{{ apache_php_module_shutdown_wrapper + */ +static void apache_php_module_shutdown_wrapper(void) +{ + apache_php_initialized = 0; + apache_sapi_module.shutdown(&apache_sapi_module); + +#if MODULE_MAGIC_NUMBER >= 19970728 + /* This function is only called on server exit if the apache API + * child_exit handler exists, so shutdown globally + */ + sapi_shutdown(); +#endif + +#ifdef ZTS + tsrm_shutdown(); +#endif +} +/* }}} */ + +#if MODULE_MAGIC_NUMBER >= 19970728 +/* {{{ php_child_exit_handler + */ +static void php_child_exit_handler(server_rec *s, pool *p) +{ +/* apache_php_initialized = 0; */ + apache_sapi_module.shutdown(&apache_sapi_module); + +#ifdef ZTS + tsrm_shutdown(); +#endif +} +/* }}} */ +#endif + +/* {{{ void php_init_handler(server_rec *s, pool *p) + */ +static void php_init_handler(server_rec *s, pool *p) +{ + register_cleanup(p, NULL, (void (*)(void *))apache_php_module_shutdown_wrapper, (void (*)(void *))php_module_shutdown_for_exec); + if (!apache_php_initialized) { + apache_php_initialized = 1; +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache_sapi_module); + php_apache_startup(&apache_sapi_module); + } +#if MODULE_MAGIC_NUMBER >= 19980527 + { + TSRMLS_FETCH(); + if (PG(expose_php)) { + ap_add_version_component("PHP/" PHP_VERSION); + } + } +#endif +} +/* }}} */ + +/* {{{ handler_rec php_handlers[] + */ +handler_rec php_handlers[] = +{ + {"application/x-httpd-php", send_parsed_php}, + {"application/x-httpd-php-source", send_parsed_php_source}, + {"text/html", php_xbithack_handler}, + {NULL} +}; +/* }}} */ + +/* {{{ command_rec php_commands[] + */ +command_rec php_commands[] = +{ + {"php_value", php_apache_value_handler, NULL, OR_OPTIONS, TAKE2, "PHP Value Modifier"}, + {"php_flag", php_apache_flag_handler, NULL, OR_OPTIONS, TAKE2, "PHP Flag Modifier"}, + {"php_admin_value", php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, TAKE2, "PHP Value Modifier (Admin)"}, + {"php_admin_flag", php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, TAKE2, "PHP Flag Modifier (Admin)"}, + {"PHPINIDir", php_apache_phpini_set, NULL, RSRC_CONF, TAKE1, "Directory containing the php.ini file"}, + {NULL} +}; +/* }}} */ + +/* {{{ odule MODULE_VAR_EXPORT php5_module + */ +module MODULE_VAR_EXPORT php5_module = +{ + STANDARD_MODULE_STUFF, + php_init_handler, /* initializer */ + php_create_dir, /* per-directory config creator */ + php_merge_dir, /* dir merger */ + NULL, /* per-server config creator */ + NULL, /* merge server config */ + php_commands, /* command table */ + php_handlers, /* handlers */ + NULL, /* filename translation */ + NULL, /* check_user_id */ + NULL, /* check auth */ + NULL, /* check access */ + NULL, /* type_checker */ + NULL, /* fixups */ + NULL /* logger */ +#if MODULE_MAGIC_NUMBER >= 19970103 + , NULL /* header parser */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970719 + , NULL /* child_init */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970728 + , php_child_exit_handler /* child_exit */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970902 + , NULL /* post read-request */ +#endif +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache/mod_php5.exp b/sapi/apache/mod_php5.exp new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache/mod_php5.exp @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache/mod_php5.h b/sapi/apache/mod_php5.h new file mode 100644 index 0000000..bdbdd47 --- /dev/null +++ b/sapi/apache/mod_php5.h @@ -0,0 +1,60 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Rasmus Lerdorf <rasmus@php.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#ifndef MOD_PHP5_H +#define MOD_PHP5_H + +#if !defined(WIN32) && !defined(WINNT) +#ifndef MODULE_VAR_EXPORT +#define MODULE_VAR_EXPORT +#endif +#endif + +typedef struct { + long engine; + long last_modified; + long xbithack; + long terminate_child; + zend_bool in_request; +} php_apache_info_struct; + +extern zend_module_entry apache_module_entry; + +#ifdef ZTS +extern int php_apache_info_id; +#define AP(v) TSRMG(php_apache_info_id, php_apache_info_struct *, v) +#else +extern php_apache_info_struct php_apache_info; +#define AP(v) (php_apache_info.v) +#endif + +/* fix for gcc4 visibility patch */ +#ifndef PHP_WIN32 +# undef MODULE_VAR_EXPORT +# define MODULE_VAR_EXPORT PHPAPI +#endif + +#endif /* MOD_PHP5_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/sapi/apache/php.sym b/sapi/apache/php.sym new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache/php.sym @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache/php5apache.dsp b/sapi/apache/php5apache.dsp new file mode 100644 index 0000000..fbdb761 --- /dev/null +++ b/sapi/apache/php5apache.dsp @@ -0,0 +1,151 @@ +# Microsoft Developer Studio Project File - Name="php5apache" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5apache - Win32 Release_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 "php5apache.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 "php5apache.mak" CFG="php5apache - Win32 Release_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5apache - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache - Win32 Release_TS_inline" (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)" == "php5apache - 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 "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\includes" /I "..\..\main" /I "..\..\TSRM" /I "..\..\regex" /I "C:\Program Files\Apache Group\Apache\include" /D ZEND_DEBUG=0 /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts.lib ApacheCore.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 /base:"0x60000000" /version:4.0 /dll /machine:I386 /libpath:"..\..\..\php_build\release" /libpath:"..\..\Release_TS" /libpath:"..\..\TSRM\Release_TS" /libpath:"..\..\Zend\Release_TS" /libpath:"C:\Program Files\Apache Group\Apache\libexec"
+
+!ELSEIF "$(CFG)" == "php5apache - Win32 Debug_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Debug_TS"
+# PROP BASE Intermediate_Dir "Debug_TS"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\Debug_TS"
+# PROP Intermediate_Dir "Debug_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 "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\includes" /I "..\..\main" /I "..\..\TSRM" /I "..\..\regex" /I "C:\Program Files\Apache Group\Apache\include" /D "_DEBUG" /D ZEND_DEBUG=1 /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts_debug.lib ApacheCore.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 /base:"0x60000000" /version:4.0 /dll /incremental:yes /debug /machine:I386 /pdbtype:sept /libpath:"..\..\..\php_build\release" /libpath:"..\..\Debug_TS" /libpath:"..\..\TSRM\Debug_TS" /libpath:"..\..\Zend\Debug_TS" /libpath:"C:\Program Files\Apache Group\Apache\libexec"
+
+!ELSEIF "$(CFG)" == "php5apache - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release_TS_inline"
+# PROP BASE Intermediate_Dir "Release_TS_inline"
+# 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 /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\includes" /I "..\..\main" /I "..\..\TSRM" /I "..\..\regex" /I "C:\Program Files\Apache Group\Apache\include" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts.lib ApacheCore.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 /version:4.0 /dll /machine:I386 /libpath:"..\..\..\php_build\release" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\TSRM\Release_TS_inline" /libpath:"..\..\Zend\Release_TS_inline" /libpath:"C:\Program Files\Apache Group\Apache\libexec"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5apache - Win32 Release_TS"
+# Name "php5apache - Win32 Debug_TS"
+# Name "php5apache - Win32 Release_TS_inline"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\mod_php5.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\php_apache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\sapi_apache.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\mod_php5.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\php_apache_http.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/sapi/apache/php_apache.c b/sapi/apache/php_apache.c new file mode 100644 index 0000000..3745197 --- /dev/null +++ b/sapi/apache/php_apache.c @@ -0,0 +1,607 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Sæther Bakken <ssb@php.net> | + | David Sklar <sklar@student.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" + +#if defined(PHP_WIN32) || defined(NETWARE) +#include "zend.h" +#include "ap_compat.h" +#endif + +#ifdef ZTS +int php_apache_info_id; +#else +php_apache_info_struct php_apache_info; +#endif + +#define SECTION(name) PUTS("<h2>" name "</h2>\n") + +#ifndef PHP_WIN32 +extern module *top_module; +extern module **ap_loaded_modules; +#else +extern __declspec(dllimport) module *top_module; +extern __declspec(dllimport) module **ap_loaded_modules; +#endif + +PHP_FUNCTION(virtual); +PHP_FUNCTION(apache_request_headers); +PHP_FUNCTION(apache_response_headers); +PHP_FUNCTION(apachelog); +PHP_FUNCTION(apache_note); +PHP_FUNCTION(apache_lookup_uri); +PHP_FUNCTION(apache_child_terminate); +PHP_FUNCTION(apache_setenv); +PHP_FUNCTION(apache_get_version); +PHP_FUNCTION(apache_get_modules); +PHP_FUNCTION(apache_reset_timeout); + +PHP_MINFO_FUNCTION(apache); + +ZEND_BEGIN_ARG_INFO(arginfo_apache_child_terminate, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_note, 0, 0, 1) + ZEND_ARG_INFO(0, note_name) + ZEND_ARG_INFO(0, note_value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_virtual, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache_request_headers, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache_response_headers, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_setenv, 0, 0, 2) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, value) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache_lookup_uri, 0, 0, 1) + ZEND_ARG_INFO(0, uri) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache_get_version, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache_get_modules, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache_reset_timeout, 0) +ZEND_END_ARG_INFO() + + + +const zend_function_entry apache_functions[] = { + PHP_FE(virtual, arginfo_apache_virtual) + PHP_FE(apache_request_headers, arginfo_apache_request_headers) + PHP_FE(apache_note, arginfo_apache_note) + PHP_FE(apache_lookup_uri, arginfo_apache_lookup_uri) + PHP_FE(apache_child_terminate, arginfo_apache_child_terminate) + PHP_FE(apache_setenv, arginfo_apache_setenv) + PHP_FE(apache_response_headers, arginfo_apache_response_headers) + PHP_FE(apache_get_version, arginfo_apache_get_version) + PHP_FE(apache_get_modules, arginfo_apache_get_modules) + PHP_FE(apache_reset_timeout, arginfo_apache_reset_timeout) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_apache_request_headers) + {NULL, NULL, NULL} +}; + + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("xbithack", "0", PHP_INI_ALL, OnUpdateLong, xbithack, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("engine", "1", PHP_INI_ALL, OnUpdateLong, engine, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("last_modified", "0", PHP_INI_ALL, OnUpdateLong, last_modified, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("child_terminate", "0", PHP_INI_ALL, OnUpdateLong, terminate_child, php_apache_info_struct, php_apache_info) +PHP_INI_END() + + + +static void php_apache_globals_ctor(php_apache_info_struct *apache_globals TSRMLS_DC) +{ + apache_globals->in_request = 0; +} + + +static PHP_MINIT_FUNCTION(apache) +{ +#ifdef ZTS + ts_allocate_id(&php_apache_info_id, sizeof(php_apache_info_struct), (ts_allocate_ctor) php_apache_globals_ctor, NULL); +#else + php_apache_globals_ctor(&php_apache_info TSRMLS_CC); +#endif + REGISTER_INI_ENTRIES(); + return SUCCESS; +} + + +static PHP_MSHUTDOWN_FUNCTION(apache) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} + +zend_module_entry apache_module_entry = { + STANDARD_MODULE_HEADER, + "apache", + apache_functions, + PHP_MINIT(apache), + PHP_MSHUTDOWN(apache), + NULL, + NULL, + PHP_MINFO(apache), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(apache) +{ + char *apv = (char *) ap_get_server_version(); + module *modp = NULL; + char output_buf[128]; +#if !defined(WIN32) && !defined(WINNT) + char name[64]; + char modulenames[1024]; + char *p; +#endif + server_rec *serv; + extern char server_root[MAX_STRING_LEN]; + extern uid_t user_id; + extern char *user_name; + extern gid_t group_id; + extern int max_requests_per_child; + + serv = ((request_rec *) SG(server_context))->server; + + + php_info_print_table_start(); + +#ifdef PHP_WIN32 + php_info_print_table_row(1, "Apache for Windows 95/NT"); + php_info_print_table_end(); + php_info_print_table_start(); +#elif defined(NETWARE) + php_info_print_table_row(1, "Apache for NetWare"); + php_info_print_table_end(); + php_info_print_table_start(); +#else + php_info_print_table_row(2, "APACHE_INCLUDE", PHP_APACHE_INCLUDE); + php_info_print_table_row(2, "APACHE_TARGET", PHP_APACHE_TARGET); +#endif + + if (apv && *apv) { + php_info_print_table_row(2, "Apache Version", apv); + } + +#ifdef APACHE_RELEASE + snprintf(output_buf, sizeof(output_buf), "%d", APACHE_RELEASE); + php_info_print_table_row(2, "Apache Release", output_buf); +#endif + snprintf(output_buf, sizeof(output_buf), "%d", MODULE_MAGIC_NUMBER); + php_info_print_table_row(2, "Apache API Version", output_buf); + snprintf(output_buf, sizeof(output_buf), "%s:%u", serv->server_hostname, serv->port); + php_info_print_table_row(2, "Hostname:Port", output_buf); +#if !defined(WIN32) && !defined(WINNT) + snprintf(output_buf, sizeof(output_buf), "%s(%d)/%d", user_name, (int)user_id, (int)group_id); + php_info_print_table_row(2, "User/Group", output_buf); + snprintf(output_buf, sizeof(output_buf), "Per Child: %d - Keep Alive: %s - Max Per Connection: %d", max_requests_per_child, serv->keep_alive ? "on":"off", serv->keep_alive_max); + php_info_print_table_row(2, "Max Requests", output_buf); +#endif + snprintf(output_buf, sizeof(output_buf), "Connection: %d - Keep-Alive: %d", serv->timeout, serv->keep_alive_timeout); + php_info_print_table_row(2, "Timeouts", output_buf); +#if !defined(WIN32) && !defined(WINNT) +/* + This block seems to be working on NetWare; But it seems to be showing + all modules instead of just the loaded ones +*/ + php_info_print_table_row(2, "Server Root", server_root); + + strcpy(modulenames, ""); + for(modp = top_module; modp; modp = modp->next) { + strlcpy(name, modp->name, sizeof(name)); + if ((p = strrchr(name, '.'))) { + *p='\0'; /* Cut off ugly .c extensions on module names */ + } + strlcat(modulenames, name, sizeof(modulenames)); + if (modp->next) { + strlcat(modulenames, ", ", sizeof(modulenames)); + } + } + php_info_print_table_row(2, "Loaded Modules", modulenames); +#endif + + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); + + { + register int i; + array_header *arr; + table_entry *elts; + request_rec *r; + + r = ((request_rec *) SG(server_context)); + arr = table_elts(r->subprocess_env); + elts = (table_entry *)arr->elts; + + SECTION("Apache Environment"); + php_info_print_table_start(); + php_info_print_table_header(2, "Variable", "Value"); + for (i=0; i < arr->nelts; i++) { + php_info_print_table_row(2, elts[i].key, elts[i].val); + } + php_info_print_table_end(); + } + + { + array_header *env_arr; + table_entry *env; + int i; + request_rec *r; + + r = ((request_rec *) SG(server_context)); + SECTION("HTTP Headers Information"); + php_info_print_table_start(); + php_info_print_table_colspan_header(2, "HTTP Request Headers"); + php_info_print_table_row(2, "HTTP Request", r->the_request); + env_arr = table_elts(r->headers_in); + env = (table_entry *)env_arr->elts; + for (i = 0; i < env_arr->nelts; ++i) { + if (env[i].key) { + php_info_print_table_row(2, env[i].key, env[i].val); + } + } + php_info_print_table_colspan_header(2, "HTTP Response Headers"); + env_arr = table_elts(r->headers_out); + env = (table_entry *)env_arr->elts; + for(i = 0; i < env_arr->nelts; ++i) { + if (env[i].key) { + php_info_print_table_row(2, env[i].key, env[i].val); + } + } + php_info_print_table_end(); + } +} +/* }}} */ + +/* {{{ proto bool apache_child_terminate(void) + Terminate apache process after this request */ +PHP_FUNCTION(apache_child_terminate) +{ +#ifndef MULTITHREAD + if (AP(terminate_child)) { + ap_child_terminate( ((request_rec *)SG(server_context)) ); + RETURN_TRUE; + } else { /* tell them to get lost! */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function is disabled"); + RETURN_FALSE; + } +#else + php_error_docref(NULL TSRMLS_CC, E_WARNING, "This function is not supported in this build"); + RETURN_FALSE; +#endif +} +/* }}} */ + +/* {{{ proto string apache_note(string note_name [, string note_value]) + Get and set Apache request notes */ +PHP_FUNCTION(apache_note) +{ + char *note_name, *note_val = NULL; + int note_name_len, note_val_len; + char *old_val; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", ¬e_name, ¬e_name_len, ¬e_val, ¬e_val_len) == FAILURE) { + return; + } + + old_val = (char *) table_get(((request_rec *)SG(server_context))->notes, note_name); + + if (note_val) { + table_set(((request_rec *)SG(server_context))->notes, note_name, note_val); + } + + if (old_val) { + RETURN_STRING(old_val, 1); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool virtual(string filename) + Perform an Apache sub-request */ +/* This function is equivalent to <!--#include virtual...--> + * in mod_include. It does an Apache sub-request. It is useful + * for including CGI scripts or .shtml files, or anything else + * that you'd parse through Apache (for .phtml files, you'd probably + * want to use <?Include>. This only works when PHP is compiled + * as an Apache module, since it uses the Apache API for doing + * sub requests. + */ +PHP_FUNCTION(virtual) +{ + char *filename; + int filename_len; + request_rec *rr = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = sub_req_lookup_uri (filename, ((request_rec *) SG(server_context))))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + if (rr->status != 200) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + php_output_end_all(TSRMLS_C); + php_header(TSRMLS_C); + + if (run_sub_req(rr)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - request execution failed", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + if (rr) + destroy_sub_req (rr); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array getallheaders(void) + Alias for apache_request_headers() */ +/* }}} */ + +/* {{{ proto array apache_request_headers(void) + Fetch all HTTP request headers */ +PHP_FUNCTION(apache_request_headers) +{ + array_header *env_arr; + table_entry *tenv; + int i; + + array_init(return_value); + env_arr = table_elts(((request_rec *) SG(server_context))->headers_in); + tenv = (table_entry *)env_arr->elts; + for (i = 0; i < env_arr->nelts; ++i) { + if (!tenv[i].key) { + continue; + } + if (add_assoc_string(return_value, tenv[i].key, (tenv[i].val==NULL) ? "" : tenv[i].val, 1)==FAILURE) { + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto array apache_response_headers(void) + Fetch all HTTP response headers */ +PHP_FUNCTION(apache_response_headers) +{ + array_header *env_arr; + table_entry *tenv; + int i; + + array_init(return_value); + env_arr = table_elts(((request_rec *) SG(server_context))->headers_out); + tenv = (table_entry *)env_arr->elts; + for (i = 0; i < env_arr->nelts; ++i) { + if (!tenv[i].key) continue; + if (add_assoc_string(return_value, tenv[i].key, (tenv[i].val==NULL) ? "" : tenv[i].val, 1)==FAILURE) { + RETURN_FALSE; + } + } +} +/* }}} */ + +/* {{{ proto bool apache_setenv(string variable, string value [, bool walk_to_top]) + Set an Apache subprocess_env variable */ +PHP_FUNCTION(apache_setenv) +{ + int var_len, val_len; + zend_bool top=0; + char *var = NULL, *val = NULL; + request_rec *r = (request_rec *) SG(server_context); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", &var, &var_len, &val, &val_len, &top) == FAILURE) { + return; + } + + while(top) { + if(r->prev) r = r->prev; + else break; + } + + ap_table_setn(r->subprocess_env, ap_pstrndup(r->pool, var, var_len), ap_pstrndup(r->pool, val, val_len)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto object apache_lookup_uri(string URI) + Perform a partial request of the given URI to obtain information about it */ +PHP_FUNCTION(apache_lookup_uri) +{ + char *filename; + int filename_len; + request_rec *rr=NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = sub_req_lookup_uri(filename, ((request_rec *) SG(server_context))))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "URI lookup failed '%s'", filename); + RETURN_FALSE; + } + + object_init(return_value); + add_property_long(return_value,"status", rr->status); + + if (rr->the_request) { + add_property_string(return_value,"the_request", rr->the_request, 1); + } + if (rr->status_line) { + add_property_string(return_value,"status_line", (char *)rr->status_line, 1); + } + if (rr->method) { + add_property_string(return_value,"method", (char *)rr->method, 1); + } + if (rr->content_type) { + add_property_string(return_value,"content_type", (char *)rr->content_type, 1); + } + if (rr->handler) { + add_property_string(return_value,"handler", (char *)rr->handler, 1); + } + if (rr->uri) { + add_property_string(return_value,"uri", rr->uri, 1); + } + if (rr->filename) { + add_property_string(return_value,"filename", rr->filename, 1); + } + if (rr->path_info) { + add_property_string(return_value,"path_info", rr->path_info, 1); + } + if (rr->args) { + add_property_string(return_value,"args", rr->args, 1); + } + if (rr->boundary) { + add_property_string(return_value,"boundary", rr->boundary, 1); + } + + add_property_long(return_value,"no_cache", rr->no_cache); + add_property_long(return_value,"no_local_copy", rr->no_local_copy); + add_property_long(return_value,"allowed", rr->allowed); + add_property_long(return_value,"sent_bodyct", rr->sent_bodyct); + add_property_long(return_value,"bytes_sent", rr->bytes_sent); + add_property_long(return_value,"byterange", rr->byterange); + add_property_long(return_value,"clength", rr->clength); + +#if MODULE_MAGIC_NUMBER >= 19980324 + if (rr->unparsed_uri) { + add_property_string(return_value,"unparsed_uri", rr->unparsed_uri, 1); + } + if(rr->mtime) { + add_property_long(return_value,"mtime", rr->mtime); + } +#endif + if(rr->request_time) { + add_property_long(return_value,"request_time", rr->request_time); + } + + destroy_sub_req(rr); +} +/* }}} */ + + +#if 0 +/* +This function is most likely a bad idea. Just playing with it for now. +*/ +PHP_FUNCTION(apache_exec_uri) +{ + char *filename; + int filename_len; + request_rec *rr=NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) { + return; + } + + if(!(rr = ap_sub_req_lookup_uri(filename, ((request_rec *) SG(server_context))))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "URI lookup failed", filename); + RETURN_FALSE; + } + + RETVAL_LONG(ap_run_sub_req(rr)); + ap_destroy_sub_req(rr); +} +#endif + +/* {{{ proto string apache_get_version(void) + Fetch Apache version */ +PHP_FUNCTION(apache_get_version) +{ + char *apv = (char *) ap_get_server_version(); + + if (apv && *apv) { + RETURN_STRING(apv, 1); + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto array apache_get_modules(void) + Get a list of loaded Apache modules */ +PHP_FUNCTION(apache_get_modules) +{ + int n; + char *p; + + array_init(return_value); + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + add_next_index_stringl(return_value, s, (p - s), 1); + } else { + add_next_index_string(return_value, s, 1); + } + } +} +/* }}} */ + +/* {{{ proto bool apache_reset_timeout(void) + Reset the Apache write timer */ +PHP_FUNCTION(apache_reset_timeout) +{ + ap_reset_timeout((request_rec *)SG(server_context)); + RETURN_TRUE; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache/php_apache_http.h b/sapi/apache/php_apache_http.h new file mode 100644 index 0000000..12788d3 --- /dev/null +++ b/sapi/apache/php_apache_http.h @@ -0,0 +1,70 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Sæther Bakken <ssb@php.net> | + | David Sklar <sklar@student.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#define NO_REGEX_EXTRA_H + +#ifdef WIN32 +#include <stddef.h> +#endif + +#ifdef NETWARE +#include <netinet/in.h> +#endif + +#include "zend.h" +#include "ext/ereg/php_regex.h" +#include "php_compat.h" + +#ifdef HAVE_OPENSSL_EXT +/* zlib typedefs free_func which causes problems if the SSL includes happen + * after zlib.h is included */ +# include <openssl/ssl.h> +#endif + +#ifdef regex_t +#undef regex_t +#endif + +#include "httpd.h" +#include "http_config.h" + +#if MODULE_MAGIC_NUMBER > 19980712 +# include "ap_compat.h" +#else +# if MODULE_MAGIC_NUMBER > 19980324 +# include "compat.h" +# endif +#endif + +#include "http_core.h" +#include "http_main.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" + +#include "php_variables.h" +#include "php_main.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" + +#include "mod_php5.h" diff --git a/sapi/apache/sapi_apache.c b/sapi/apache/sapi_apache.c new file mode 100644 index 0000000..88c9985 --- /dev/null +++ b/sapi/apache/sapi_apache.c @@ -0,0 +1,73 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@php.net> | + | (with helpful hints from Dean Gaudet <dgaudet@arctic.org> | + | PHP 4.0 patches by: | + | Zeev Suraski <zeev@zend.com> | + | Stig Bakken <ssb@php.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" + +/* {{{ apache_php_module_main + */ +int apache_php_module_main(request_rec *r, int display_source_mode TSRMLS_DC) +{ + int retval = OK; + zend_file_handle file_handle; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return FAILURE; + } + /* sending a file handle to another dll is not working + so let zend open it. */ + + if (display_source_mode) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + if (highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC) != SUCCESS) { + retval = NOT_FOUND; + } + } else { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.handle.fd = 0; + file_handle.filename = SG(request_info).path_translated; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + + (void) php_execute_script(&file_handle TSRMLS_CC); + } + + AP(in_request) = 0; + + zend_try { + php_request_shutdown(NULL); + } zend_end_try(); + + return retval; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2filter/CREDITS b/sapi/apache2filter/CREDITS new file mode 100644 index 0000000..c298a9b --- /dev/null +++ b/sapi/apache2filter/CREDITS @@ -0,0 +1,2 @@ +Apache 2.0 Filter +Sascha Schumann, Aaron Bannert diff --git a/sapi/apache2filter/EXPERIMENTAL b/sapi/apache2filter/EXPERIMENTAL new file mode 100644 index 0000000..293159a --- /dev/null +++ b/sapi/apache2filter/EXPERIMENTAL @@ -0,0 +1,5 @@ +this module is experimental, +its functions may change their names +or move to extension all together +so do not rely to much on them +you have been warned! diff --git a/sapi/apache2filter/README b/sapi/apache2filter/README new file mode 100644 index 0000000..3054e20 --- /dev/null +++ b/sapi/apache2filter/README @@ -0,0 +1,71 @@ +WHAT IS THIS? + + This module exploits the layered I/O support in Apache 2.0. + +HOW DOES IT WORK? + + In Apache 2.0, you have handlers which generate content (like + reading a script from disk). The content goes then through + a chain of filters. PHP can be such a filter, so that it processes + your script and hands the output to the next filter (which will + usually cause a write to the network). + +DOES IT WORK? + + It is experimental as interfaces in Apache 2.0 might change in the + future. + +HOW TO INSTALL + + This SAPI module is known to work with Apache 2.0.40. + + $ cd apache-2.x + $ cd src + $ ./configure --enable-so + $ make install + + For testing purposes, you might want to use --with-mpm=prefork. + (Albeit PHP also works with threaded MPMs.) + + Configure PHP 4: + + $ cd php-4.x + $ ./configure --with-apxs2=/path/to/apache-2.0/bin/apxs + $ make install + + At the end of conf/httpd.conf, add: + + AddType application/x-httpd-php .php + + If you would like to enable source code highlighting functionality add: + + AddType application/x-httpd-php-source .phps + + That's it. Now start bin/httpd. + +HOW TO CONFIGURE + + The Apache 2.0 PHP module supports a new configuration directive that + allows an admin to override the php.ini search path. For example, + place your php.ini file in Apache's ServerRoot/conf directory and + add this to your httpd.conf file: + + PHPINIDir "conf" + +DEBUGGING APACHE AND PHP + + To debug Apache, we recommened: + + 1. Use the Prefork MPM (Apache 1.3-like process model) by + configuring Apache with '--with-mpm=prefork'. + 2. Start httpd using -DONE_PROCESS (e.g. (gdb) r -DONE_PROCESS). + + If you want to debug a part of the PHP startup procedure, set a + breakpoint on 'load_module'. Step through it until apr_dso_load() is + done. Then you can set a breakpoint on any PHP-related symbol. + +TODO + + PHP functions like apache_sub_req (see php_functions.c) + Protocol handlers + Passing script data to engine without temporary file diff --git a/sapi/apache2filter/apache_config.c b/sapi/apache2filter/apache_config.c new file mode 100644 index 0000000..333c7b8 --- /dev/null +++ b/sapi/apache2filter/apache_config.c @@ -0,0 +1,218 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_ini.h" +#include "php_apache.h" + +#include "apr_strings.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" + +#ifdef PHP_AP_DEBUG +#define phpapdebug(a) fprintf a +#else +#define phpapdebug(a) +#endif + +typedef struct { + HashTable config; +} php_conf_rec; + +typedef struct { + char *value; + size_t value_len; + char status; + char htaccess; +} php_dir_entry; + +static const char *real_value_hnd(cmd_parms *cmd, void *dummy, const char *name, const char *value, int status) +{ + php_conf_rec *d = dummy; + php_dir_entry e; + + phpapdebug((stderr, "Getting %s=%s for %p (%d)\n", name, value, dummy, zend_hash_num_elements(&d->config))); + + if (!strncasecmp(value, "none", sizeof("none"))) { + value = ""; + } + + e.value = apr_pstrdup(cmd->pool, value); + e.value_len = strlen(value); + e.status = status; + e.htaccess = ((cmd->override & (RSRC_CONF|ACCESS_CONF)) == 0); + + zend_hash_update(&d->config, (char *) name, strlen(name) + 1, &e, sizeof(e), NULL); + return NULL; +} + +static const char *php_apache_value_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_value_hnd(cmd, dummy, name, value, PHP_INI_PERDIR); +} + +static const char *php_apache_admin_value_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_value_hnd(cmd, dummy, name, value, PHP_INI_SYSTEM); +} + +static const char *real_flag_hnd(cmd_parms *cmd, void *dummy, const char *arg1, const char *arg2, int status) +{ + char bool_val[2]; + + if (!strcasecmp(arg2, "On") || (arg2[0] == '1' && arg2[1] == '\0')) { + bool_val[0] = '1'; + } else { + bool_val[0] = '0'; + } + bool_val[1] = 0; + + return real_value_hnd(cmd, dummy, arg1, bool_val, status); +} + +static const char *php_apache_flag_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_flag_hnd(cmd, dummy, name, value, PHP_INI_PERDIR); +} + +static const char *php_apache_admin_flag_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_flag_hnd(cmd, dummy, name, value, PHP_INI_SYSTEM); +} + +static const char *php_apache_phpini_set(cmd_parms *cmd, void *mconfig, const char *arg) +{ + if (apache2_php_ini_path_override) { + return "Only first PHPINIDir directive honored per configuration tree - subsequent ones ignored"; + } + apache2_php_ini_path_override = ap_server_root_relative(cmd->pool, arg); + return NULL; +} + + +void *merge_php_config(apr_pool_t *p, void *base_conf, void *new_conf) +{ + php_conf_rec *d = base_conf, *e = new_conf, *n = NULL; + php_dir_entry *pe; + php_dir_entry *data; + char *str; + uint str_len; + ulong num_index; + + n = create_php_config(p, "merge_php_config"); + zend_hash_copy(&n->config, &e->config, NULL, NULL, sizeof(php_dir_entry)); + + phpapdebug((stderr, "Merge dir (%p)+(%p)=(%p)\n", base_conf, new_conf, n)); + for (zend_hash_internal_pointer_reset(&d->config); + zend_hash_get_current_key_ex(&d->config, &str, &str_len, + &num_index, 0, NULL) == HASH_KEY_IS_STRING; + zend_hash_move_forward(&d->config)) { + pe = NULL; + zend_hash_get_current_data(&d->config, (void **) &data); + if (zend_hash_find(&n->config, str, str_len, (void **) &pe) == SUCCESS) { + if (pe->status >= data->status) continue; + } + zend_hash_update(&n->config, str, str_len, data, sizeof(*data), NULL); + phpapdebug((stderr, "ADDING/OVERWRITING %s (%d vs. %d)\n", str, data->status, pe?pe->status:-1)); + } + + return n; +} + +char *get_php_config(void *conf, char *name, size_t name_len) +{ + php_conf_rec *d = conf; + php_dir_entry *pe; + + if (zend_hash_find(&d->config, name, name_len, (void **) &pe) == SUCCESS) { + return pe->value; + } + + return ""; +} + +void apply_config(void *dummy) +{ + php_conf_rec *d = dummy; + char *str; + uint str_len; + php_dir_entry *data; + + for (zend_hash_internal_pointer_reset(&d->config); + zend_hash_get_current_key_ex(&d->config, &str, &str_len, NULL, 0, + NULL) == HASH_KEY_IS_STRING; + zend_hash_move_forward(&d->config)) { + zend_hash_get_current_data(&d->config, (void **) &data); + phpapdebug((stderr, "APPLYING (%s)(%s)\n", str, data->value)); + if (zend_alter_ini_entry(str, str_len, data->value, data->value_len, data->status, data->htaccess?PHP_INI_STAGE_HTACCESS:PHP_INI_STAGE_ACTIVATE) == FAILURE) { + phpapdebug((stderr, "..FAILED\n")); + } + } +} + +const command_rec php_dir_cmds[] = +{ + AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL, OR_OPTIONS, "PHP Value Modifier"), + AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL, OR_OPTIONS, "PHP Flag Modifier"), + AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"), + AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"), + AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL, RSRC_CONF, "Directory containing the php.ini file"), + {NULL} +}; + +static apr_status_t destroy_php_config(void *data) +{ + php_conf_rec *d = data; + + phpapdebug((stderr, "Destroying config %p\n", data)); + zend_hash_destroy(&d->config); + + return APR_SUCCESS; +} + +void *create_php_config(apr_pool_t *p, char *dummy) +{ + php_conf_rec *newx = (php_conf_rec *) apr_pcalloc(p, sizeof(*newx)); + + phpapdebug((stderr, "Creating new config (%p) for %s\n", newx, dummy)); + zend_hash_init(&newx->config, 0, NULL, NULL, 1); + apr_pool_cleanup_register(p, newx, destroy_php_config, apr_pool_cleanup_null); + return (void *) newx; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2filter/config.m4 b/sapi/apache2filter/config.m4 new file mode 100644 index 0000000..c49488d --- /dev/null +++ b/sapi/apache2filter/config.m4 @@ -0,0 +1,139 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(apxs2filter,, +[ --with-apxs2filter[=FILE] + EXPERIMENTAL: Build shared Apache 2.0 Filter module. FILE is the optional + pathname to the Apache apxs tool [apxs]], no, no) + +AC_MSG_CHECKING([for Apache 2.0 filter-module support via DSO through APXS]) + +if test "$PHP_APXS2FILTER" != "no"; then + if test "$PHP_APXS2FILTER" = "yes"; then + APXS=apxs + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0" && test -x /usr/sbin/apxs; then + APXS=/usr/sbin/apxs + fi + else + PHP_EXPAND_PATH($PHP_APXS2FILTER, APXS) + fi + + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0"; then + AC_MSG_RESULT() + AC_MSG_RESULT() + AC_MSG_RESULT([Sorry, I cannot run apxs. Possible reasons follow:]) + AC_MSG_RESULT() + AC_MSG_RESULT([1. Perl is not installed]) + AC_MSG_RESULT([2. apxs was not found. Try to pass the path using --with-apxs2filter=/path/to/apxs]) + AC_MSG_RESULT([3. Apache was not built using --enable-so (the apxs usage page is displayed)]) + AC_MSG_RESULT() + AC_MSG_RESULT([The output of $APXS follows:]) + $APXS -q CFLAGS + AC_MSG_ERROR([Aborting]) + fi + + APXS_INCLUDEDIR=`$APXS -q INCLUDEDIR` + APXS_BINDIR=`$APXS -q BINDIR` + APXS_HTTPD=`$APXS -q SBINDIR`/`$APXS -q TARGET` + APXS_CFLAGS=`$APXS -q CFLAGS` + APU_BINDIR=`$APXS -q APU_BINDIR` + APR_BINDIR=`$APXS -q APR_BINDIR` + + # Pick up ap[ru]-N-config if using httpd >=2.1 + APR_CONFIG=`$APXS -q APR_CONFIG 2>/dev/null || + echo $APR_BINDIR/apr-config` + APU_CONFIG=`$APXS -q APU_CONFIG 2>/dev/null || + echo $APU_BINDIR/apu-config` + + APR_CFLAGS="`$APR_CONFIG --cppflags --includes`" + APU_CFLAGS="`$APU_CONFIG --includes`" + + for flag in $APXS_CFLAGS; do + case $flag in + -D*) APACHE_CPPFLAGS="$APACHE_CPPFLAGS $flag";; + esac + done + + APACHE_CFLAGS="$APACHE_CPPFLAGS -I$APXS_INCLUDEDIR $APR_CFLAGS $APU_CFLAGS" + + # Test that we're trying to configure with apache 2.x + PHP_AP_EXTRACT_VERSION($APXS_HTTPD) + if test "$APACHE_VERSION" -le 2000000; then + AC_MSG_ERROR([You have enabled Apache 2 support while your server is Apache 1.3. Please use the appropiate switch --with-apxs (without the 2)]) + elif test "$APACHE_VERSION" -lt 2000040; then + AC_MSG_ERROR([Please note that Apache version >= 2.0.40 is required]) + fi + + APXS_LIBEXECDIR='$(INSTALL_ROOT)'`$APXS -q LIBEXECDIR` + if test -z `$APXS -q SYSCONFDIR`; then + INSTALL_IT="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -i -n php5" + else + APXS_SYSCONFDIR='$(INSTALL_ROOT)'`$APXS -q SYSCONFDIR` + INSTALL_IT="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + \$(mkinstalldirs) '$APXS_SYSCONFDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -S SYSCONFDIR='$APXS_SYSCONFDIR' \ + -i -a -n php5" + fi + + case $host_alias in + *aix*) + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-brtl -Wl,-bI:$APXS_LIBEXECDIR/httpd.exp" + PHP_SELECT_SAPI(apache2filter, shared, sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + *darwin*) + dnl When using bundles on Darwin, we must resolve all symbols. However, + dnl the linker does not recursively look at the bundle loader and + dnl pull in its dependencies. Therefore, we must pull in the APR + dnl and APR-util libraries. + if test -x "$APR_CONFIG"; then + MH_BUNDLE_FLAGS="`$APR_CONFIG --ldflags --link-ld --libs`" + fi + if test -x "$APU_CONFIG"; then + MH_BUNDLE_FLAGS="`$APU_CONFIG --ldflags --link-ld --libs` $MH_BUNDLE_FLAGS" + fi + MH_BUNDLE_FLAGS="-bundle -bundle_loader $APXS_HTTPD $MH_BUNDLE_FLAGS" + PHP_SUBST(MH_BUNDLE_FLAGS) + PHP_SELECT_SAPI(apache2filter, bundle, sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + SAPI_SHARED=libs/libphp5.so + INSTALL_IT="$INSTALL_IT $SAPI_SHARED" + ;; + *beos*) + if test -f _APP_; then `rm _APP_`; fi + `ln -s $APXS_BINDIR/httpd _APP_` + EXTRA_LIBS="$EXTRA_LIBS _APP_" + PHP_SELECT_SAPI(apache2filter, shared, sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + *) + PHP_SELECT_SAPI(apache2filter, shared, sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + esac + + if test "$APACHE_VERSION" -lt 2004001; then + APXS_MPM=`$APXS -q MPM_NAME` + if test "$APXS_MPM" != "prefork" && test "$APXS_MPM" != "peruser" && test "$APXS_MPM" != "itk"; then + PHP_BUILD_THREAD_SAFE + fi + else + APACHE_THREADED_MPM=`$APXS_HTTPD -V | grep 'threaded:.*yes'` + if test -n "$APACHE_THREADED_MPM"; then + PHP_BUILD_THREAD_SAFE + fi + fi + AC_MSG_RESULT(yes) + PHP_SUBST(APXS) +else + AC_MSG_RESULT(no) +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/apache2filter/config.w32 b/sapi/apache2filter/config.w32 new file mode 100755 index 0000000..361d031 --- /dev/null +++ b/sapi/apache2filter/config.w32 @@ -0,0 +1,39 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('apache2filter', 'Build Apache 2.x filter', 'no'); + +if (PHP_APACHE2FILTER != "no") { + if (PHP_ZTS == "no") { + WARNING("Apache2 module requires an --enable-zts build of PHP on windows"); + } else if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE2FILTER", PHP_PHP_BUILD + "\\include\\apache2") && + CHECK_LIB("libhttpd.lib", "apache2filter", PHP_PHP_BUILD + "\\lib\\apache2") && + CHECK_LIB("libapr.lib", "apache2filter", PHP_PHP_BUILD + "\\lib\\apache2") && + CHECK_LIB("libaprutil.lib", "apache2filter", PHP_PHP_BUILD + "\\lib\\apache2") + ) { + SAPI('apache2filter', 'sapi_apache2.c apache_config.c php_functions.c', + 'php' + PHP_VERSION + 'apache2_filter.dll', + '/D PHP_APACHE2_EXPORTS /I win32'); + } else { + WARNING("Could not find apache2 filter libraries/headers"); + } +} + +ARG_ENABLE('apache2-2filter', 'Build Apache 2.2.x filter', 'no'); + +if (PHP_APACHE2_2FILTER != "no") { + if (PHP_ZTS == "no") { + WARNING("Apache2 module requires an --enable-zts build of PHP on windows"); + } else if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE2_2FILTER", PHP_PHP_BUILD + "\\include\\apache2_2") && + CHECK_LIB("libhttpd.lib", "apache2_2filter", PHP_PHP_BUILD + "\\lib\\apache2_2") && + CHECK_LIB("libapr-1.lib", "apache2_2filter", PHP_PHP_BUILD + "\\lib\\apache2_2") && + CHECK_LIB("libaprutil-1.lib", "apache2_2filter", PHP_PHP_BUILD + "\\lib\\apache2_2") + ) { + SAPI('apache2_2filter', 'sapi_apache2.c apache_config.c php_functions.c', + 'php' + PHP_VERSION + 'apache2_2_filter.dll', + '/D PHP_APACHE2_EXPORTS /I win32', + 'sapi\\apache2_2filter'); + } else { + WARNING("Could not find apache2.2 filter libraries/headers"); + } +} diff --git a/sapi/apache2filter/php.sym b/sapi/apache2filter/php.sym new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache2filter/php.sym @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache2filter/php_apache.h b/sapi/apache2filter/php_apache.h new file mode 100644 index 0000000..4ee3c0a --- /dev/null +++ b/sapi/apache2filter/php_apache.h @@ -0,0 +1,81 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PHP_APACHE_H +#define PHP_APACHE_H + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" + +/* Declare this so we can get to it from outside the sapi_apache2.c file */ +extern module AP_MODULE_DECLARE_DATA php5_module; + +/* A way to specify the location of the php.ini dir in an apache directive */ +extern char *apache2_php_ini_path_override; + +/* The server_context used by PHP */ +typedef struct php_struct { + int state; + request_rec *r; + ap_filter_t *f; /* downstream output filters after the PHP filter. */ + /* Length of post_data buffer */ + int post_len; + /* Index for reading from buffer */ + int post_idx; + /* stat structure of the current file */ + struct stat finfo; + /* Buffer for request body filter */ + char *post_data; + /* Whether or not we've processed PHP in the output filters yet. */ + int request_processed; +} php_struct; + +typedef struct _php_apr_bucket_brigade { + apr_bucket_brigade *bb; +} php_apr_bucket_brigade; + +void *merge_php_config(apr_pool_t *p, void *base_conf, void *new_conf); +void *create_php_config(apr_pool_t *p, char *dummy); +char *get_php_config(void *conf, char *name, size_t name_len); +void apply_config(void *); +extern const command_rec php_dir_cmds[]; + +static size_t php_apache_read_stream(void *, char *, size_t TSRMLS_DC); +static size_t php_apache_fsizer_stream(void * TSRMLS_DC); + +#define APR_ARRAY_FOREACH_OPEN(arr, key, val) \ +{ \ + apr_table_entry_t *elts; \ + int i; \ + elts = (apr_table_entry_t *) arr->elts; \ + for (i = 0; i < arr->nelts; i++) { \ + key = elts[i].key; \ + val = elts[i].val; + +#define APR_ARRAY_FOREACH_CLOSE() }} + +/* fix for gcc4 visibility patch */ +#ifndef PHP_WIN32 +# undef AP_MODULE_DECLARE_DATA +# define AP_MODULE_DECLARE_DATA PHPAPI +#endif + +#endif /* PHP_APACHE_H */ diff --git a/sapi/apache2filter/php_functions.c b/sapi/apache2filter/php_functions.c new file mode 100644 index 0000000..e96ceda --- /dev/null +++ b/sapi/apache2filter/php_functions.c @@ -0,0 +1,425 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "ext/standard/php_smart_str.h" +#include "ext/standard/info.h" +#include "SAPI.h" + +#define CORE_PRIVATE +#include "apr_strings.h" +#include "apr_time.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" + +#include "php_apache.h" + +static request_rec *php_apache_lookup_uri(char *filename TSRMLS_DC) +{ + php_struct *ctx; + + if (!filename) { + return NULL; + } + + ctx = SG(server_context); + return ap_sub_req_lookup_uri(filename, ctx->f->r, ctx->f->next); +} + +/* {{{ proto bool virtual(string uri) + Perform an apache sub-request */ +PHP_FUNCTION(virtual) +{ + char *filename; + int filename_len; + request_rec *rr; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = php_apache_lookup_uri(filename TSRMLS_CC))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + RETURN_FALSE; + } + + if (rr->status == HTTP_OK) { + if (ap_run_sub_req(rr)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - request execution failed", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; + } + ap_destroy_sub_req(rr); + RETURN_TRUE; + } + + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; +} +/* }}} */ + +#define ADD_LONG(name) \ + add_property_long(return_value, #name, rr->name) +#define ADD_TIME(name) \ + add_property_long(return_value, #name, apr_time_sec(rr->name)); +#define ADD_STRING(name) \ + if (rr->name) add_property_string(return_value, #name, (char *) rr->name, 1) + +PHP_FUNCTION(apache_lookup_uri) +{ + request_rec *rr; + char *filename; + int filename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = php_apache_lookup_uri(filename TSRMLS_CC))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + RETURN_FALSE; + } + + if (rr->status == HTTP_OK) { + object_init(return_value); + + ADD_LONG(status); + ADD_STRING(the_request); + ADD_STRING(status_line); + ADD_STRING(method); + ADD_TIME(mtime); + ADD_LONG(clength); +#if MODULE_MAGIC_NUMBER < 20020506 + ADD_STRING(boundary); +#endif + ADD_STRING(range); + ADD_LONG(chunked); + ADD_STRING(content_type); + ADD_STRING(handler); + ADD_LONG(no_cache); + ADD_LONG(no_local_copy); + ADD_STRING(unparsed_uri); + ADD_STRING(uri); + ADD_STRING(filename); + ADD_STRING(path_info); + ADD_STRING(args); + ADD_LONG(allowed); + ADD_LONG(sent_bodyct); + ADD_LONG(bytes_sent); + ADD_LONG(mtime); + ADD_TIME(request_time); + + ap_destroy_sub_req(rr); + return; + } + + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; +} + +/* {{{ proto array getallheaders(void) + Fetch all HTTP request headers */ +PHP_FUNCTION(apache_request_headers) +{ + php_struct *ctx; + const apr_array_header_t *arr; + char *key, *val; + + array_init(return_value); + + ctx = SG(server_context); + arr = apr_table_elts(ctx->f->r->headers_in); + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) val = ""; + add_assoc_string(return_value, key, val, 1); + APR_ARRAY_FOREACH_CLOSE() +} +/* }}} */ + +/* {{{ proto array apache_response_headers(void) + Fetch all HTTP response headers */ +PHP_FUNCTION(apache_response_headers) +{ + php_struct *ctx; + const apr_array_header_t *arr; + char *key, *val; + + array_init(return_value); + + ctx = SG(server_context); + arr = apr_table_elts(ctx->f->r->headers_out); + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) val = ""; + add_assoc_string(return_value, key, val, 1); + APR_ARRAY_FOREACH_CLOSE() +} +/* }}} */ + +/* {{{ proto string apache_note(string note_name [, string note_value]) + Get and set Apache request notes */ +PHP_FUNCTION(apache_note) +{ + php_struct *ctx; + char *note_name, *note_val = NULL; + int note_name_len, note_val_len; + char *old_note_val=NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", ¬e_name, ¬e_name_len, ¬e_val, ¬e_val_len) == FAILURE) { + return; + } + + ctx = SG(server_context); + + old_note_val = (char *) apr_table_get(ctx->r->notes, note_name); + + if (note_val) { + apr_table_set(ctx->r->notes, note_name, note_val); + } + + if (old_note_val) { + RETURN_STRING(old_note_val, 1); + } + + RETURN_FALSE; +} +/* }}} */ + + +/* {{{ proto bool apache_setenv(string variable, string value [, bool walk_to_top]) + Set an Apache subprocess_env variable */ +PHP_FUNCTION(apache_setenv) +{ + php_struct *ctx; + char *variable=NULL, *string_val=NULL; + int variable_len, string_val_len; + zend_bool walk_to_top = 0; + int arg_count = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(arg_count TSRMLS_CC, "ss|b", &variable, &variable_len, &string_val, &string_val_len, &walk_to_top) == FAILURE) { + return; + } + + ctx = SG(server_context); + + if (arg_count == 3 && walk_to_top) { + while(ctx->f->r->prev) { + ctx->f->r = ctx->f->r->prev; + } + } + + apr_table_set(ctx->r->subprocess_env, variable, string_val); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool apache_getenv(string variable [, bool walk_to_top]) + Get an Apache subprocess_env variable */ +PHP_FUNCTION(apache_getenv) +{ + php_struct *ctx; + char *variable=NULL; + int variable_len; + zend_bool walk_to_top = 0; + int arg_count = ZEND_NUM_ARGS(); + char *env_val=NULL; + + if (zend_parse_parameters(arg_count TSRMLS_CC, "s|b", &variable, &variable_len, &walk_to_top) == FAILURE) { + return; + } + + ctx = SG(server_context); + + if (arg_count == 2 && walk_to_top) { + while(ctx->f->r->prev) { + ctx->f->r = ctx->f->r->prev; + } + } + + env_val = (char*) apr_table_get(ctx->r->subprocess_env, variable); + if (env_val != NULL) { + RETURN_STRING(env_val, 1); + } + + RETURN_FALSE; +} +/* }}} */ + +static char *php_apache_get_version() +{ +#if MODULE_MAGIC_NUMBER_MAJOR >= 20060905 + return (char *) ap_get_server_banner(); +#else + return (char *) ap_get_server_version(); +#endif +} + +/* {{{ proto string apache_get_version(void) + Fetch Apache version */ +PHP_FUNCTION(apache_get_version) +{ + char *apv = php_apache_get_version(); + + if (apv && *apv) { + RETURN_STRING(apv, 1); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array apache_get_modules(void) + Get a list of loaded Apache modules */ +PHP_FUNCTION(apache_get_modules) +{ + int n; + char *p; + + array_init(return_value); + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + add_next_index_stringl(return_value, s, (p - s), 1); + } else { + add_next_index_string(return_value, s, 1); + } + } +} +/* }}} */ + +PHP_MINFO_FUNCTION(apache) +{ + char *apv = php_apache_get_version(); + smart_str tmp1 = {0}; + int n; + char *p; + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + smart_str_appendl(&tmp1, s, (p - s)); + } else { + smart_str_appends(&tmp1, s); + } + smart_str_appendc(&tmp1, ' '); + } + if ((tmp1.len - 1) >= 0) { + tmp1.c[tmp1.len - 1] = '\0'; + } + + php_info_print_table_start(); + if (apv && *apv) { + php_info_print_table_row(2, "Apache Version", apv); + } + php_info_print_table_row(2, "Loaded Modules", tmp1.c); + smart_str_free(&tmp1); + php_info_print_table_end(); +} + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2filter_lookup_uri, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2filter_virtual, 0, 0, 1) + ZEND_ARG_INFO(0, uri) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2filter_getallheaders, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2filter_response_headers, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2filter_note, 0, 0, 1) + ZEND_ARG_INFO(0, note_name) + ZEND_ARG_INFO(0, note_value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2filter_setenv, 0, 0, 2) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, value) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2filter_getenv, 0, 0, 1) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2filter_get_version, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2filter_get_modules, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry apache_functions[] = { + PHP_FE(apache_lookup_uri, arginfo_apache2filter_lookup_uri) + PHP_FE(virtual, arginfo_apache2filter_virtual) + PHP_FE(apache_request_headers, arginfo_apache2filter_getallheaders) + PHP_FE(apache_response_headers, arginfo_apache2filter_response_headers) + PHP_FE(apache_setenv, arginfo_apache2filter_setenv) + PHP_FE(apache_getenv, arginfo_apache2filter_getenv) + PHP_FE(apache_note, arginfo_apache2filter_note) + PHP_FE(apache_get_version, arginfo_apache2filter_get_version) + PHP_FE(apache_get_modules, arginfo_apache2filter_get_modules) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_apache2filter_getallheaders) + {NULL, NULL, NULL} +}; + +zend_module_entry php_apache_module = { + STANDARD_MODULE_HEADER, + "apache2filter", + apache_functions, + NULL, + NULL, + NULL, + NULL, + PHP_MINFO(apache), + NULL, + STANDARD_MODULE_PROPERTIES +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2filter/sapi_apache2.c b/sapi/apache2filter/sapi_apache2.c new file mode 100644 index 0000000..8ce490e --- /dev/null +++ b/sapi/apache2filter/sapi_apache2.c @@ -0,0 +1,764 @@ +/* + +----------------------------------------------------------------------+ + | 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: Sascha Schumann <sascha@schumann.cx> | + | Parts based on Apache 1.3 SAPI module by | + | Rasmus Lerdorf and Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include <fcntl.h> + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_main.h" +#include "php_ini.h" +#include "php_variables.h" +#include "SAPI.h" + +#include "ext/standard/php_smart_str.h" +#ifndef NETWARE +#include "ext/standard/php_standard.h" +#else +#include "ext/standard/basic_functions.h" +#endif + +#include "apr_strings.h" +#include "ap_config.h" +#include "apr_buckets.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" +#include "ap_mpm.h" + +#include "php_apache.h" + +/* UnixWare and Netware define shutdown to _shutdown, which causes problems later + * on when using a structure member named shutdown. Since this source + * file does not use the system call shutdown, it is safe to #undef it. + */ +#undef shutdown + +/* A way to specify the location of the php.ini dir in an apache directive */ +char *apache2_php_ini_path_override = NULL; + +static int +php_apache_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + apr_bucket *b; + apr_bucket_brigade *bb; + apr_bucket_alloc_t *ba; + ap_filter_t *f; /* remaining output filters */ + php_struct *ctx; + + ctx = SG(server_context); + f = ctx->f; + + if (str_length == 0) return 0; + + ba = f->c->bucket_alloc; + bb = apr_brigade_create(ctx->r->pool, ba); + + b = apr_bucket_transient_create(str, str_length, ba); + APR_BRIGADE_INSERT_TAIL(bb, b); + + if (ap_pass_brigade(f->next, bb) != APR_SUCCESS || ctx->r->connection->aborted) { + php_handle_aborted_connection(); + } + + return str_length; /* we always consume all the data passed to us. */ +} + +static int +php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + php_struct *ctx; + ap_filter_t *f; + char *val, *ptr; + + ctx = SG(server_context); + f = ctx->r->output_filters; + + switch(op) { + case SAPI_HEADER_DELETE: + apr_table_unset(ctx->r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_DELETE_ALL: + apr_table_clear(ctx->r->headers_out); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + val = strchr(sapi_header->header, ':'); + + if (!val) { + sapi_free_header(sapi_header); + return 0; + } + ptr = val; + + *val = '\0'; + + do { + val++; + } while (*val == ' '); + + if (!strcasecmp(sapi_header->header, "content-type")) + ctx->r->content_type = apr_pstrdup(ctx->r->pool, val); + else if (!strcasecmp(sapi_header->header, "content-length")) + ap_set_content_length(ctx->r, strtol(val, (char **)NULL, 10)); + else if (op == SAPI_HEADER_REPLACE) + apr_table_set(ctx->r->headers_out, sapi_header->header, val); + else + apr_table_add(ctx->r->headers_out, sapi_header->header, val); + + *ptr = ':'; + return SAPI_HEADER_ADD; + + default: + return 0; + } +} + +static int +php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + + ctx->r->status = SG(sapi_headers).http_response_code; + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int +php_apache_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ + int n; + int to_read; + php_struct *ctx = SG(server_context); + + to_read = ctx->post_len - ctx->post_idx; + n = MIN(to_read, count_bytes); + + if (n > 0) { + memcpy(buf, ctx->post_data + ctx->post_idx, n); + ctx->post_idx += n; + } else { + if (ctx->post_data) free(ctx->post_data); + ctx->post_data = NULL; + } + + return n; +} + +static struct stat* +php_apache_sapi_get_stat(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + + ctx->finfo.st_uid = ctx->r->finfo.user; + ctx->finfo.st_gid = ctx->r->finfo.group; + ctx->finfo.st_dev = ctx->r->finfo.device; + ctx->finfo.st_ino = ctx->r->finfo.inode; +#ifdef NETWARE + ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime); +#else + ctx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime); +#endif + + ctx->finfo.st_size = ctx->r->finfo.size; + ctx->finfo.st_nlink = ctx->r->finfo.nlink; + + return &ctx->finfo; +} + +static char * +php_apache_sapi_read_cookies(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + const char *http_cookie; + + http_cookie = apr_table_get(ctx->r->headers_in, "cookie"); + + /* The SAPI interface should use 'const char *' */ + return (char *) http_cookie; +} + +static char * +php_apache_sapi_getenv(char *name, size_t name_len TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + const char *env_var; + + env_var = apr_table_get(ctx->r->subprocess_env, name); + + return (char *) env_var; +} + +static void +php_apache_sapi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env); + char *key, *val; + unsigned int new_val_len; + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len TSRMLS_CC)) { + php_register_variable_safe(key, val, new_val_len, track_vars_array TSRMLS_CC); + } + APR_ARRAY_FOREACH_CLOSE() + + php_register_variable("PHP_SELF", ctx->r->uri, track_vars_array TSRMLS_CC); + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array TSRMLS_CC); + } +} + +static void +php_apache_sapi_flush(void *server_context) +{ + php_struct *ctx; + apr_bucket_brigade *bb; + apr_bucket_alloc_t *ba; + apr_bucket *b; + ap_filter_t *f; /* output filters */ + TSRMLS_FETCH(); + + ctx = server_context; + + /* If we haven't registered a server_context yet, + * then don't bother flushing. */ + if (!server_context) + return; + + sapi_send_headers(TSRMLS_C); + + ctx->r->status = SG(sapi_headers).http_response_code; + SG(headers_sent) = 1; + + f = ctx->f; + + /* Send a flush bucket down the filter chain. The current default + * handler seems to act on the first flush bucket, but ignores + * all further flush buckets. + */ + + ba = ctx->r->connection->bucket_alloc; + bb = apr_brigade_create(ctx->r->pool, ba); + b = apr_bucket_flush_create(ba); + APR_BRIGADE_INSERT_TAIL(bb, b); + if (ap_pass_brigade(f->next, bb) != APR_SUCCESS || ctx->r->connection->aborted) { + php_handle_aborted_connection(); + } +} + +static void php_apache_sapi_log_message(char *msg TSRMLS_DC) +{ + php_struct *ctx; + + ctx = SG(server_context); + + if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg); + } + else { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ctx->r->server, "%s", msg); + } +} + +static int +php_apache_disable_caching(ap_filter_t *f) +{ + /* Identify PHP scripts as non-cacheable, thus preventing + * Apache from sending a 304 status when the browser sends + * If-Modified-Since header. + */ + f->r->no_local_copy = 1; + + return OK; +} + +static double php_apache_sapi_get_request_time(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0; +} + +extern zend_module_entry php_apache_module; + +static int php_apache2_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +static sapi_module_struct apache2_sapi_module = { + "apache2filter", + "Apache 2.0 Filter", + + php_apache2_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_apache_sapi_ub_write, /* unbuffered write */ + php_apache_sapi_flush, /* flush */ + php_apache_sapi_get_stat, /* get uid */ + php_apache_sapi_getenv, /* getenv */ + + php_error, /* error handler */ + + php_apache_sapi_header_handler, /* header handler */ + php_apache_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_apache_sapi_read_post, /* read POST data */ + php_apache_sapi_read_cookies, /* read Cookies */ + + php_apache_sapi_register_variables, + php_apache_sapi_log_message, /* Log message */ + php_apache_sapi_get_request_time, /* Get Request Time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static int php_input_filter(ap_filter_t *f, apr_bucket_brigade *bb, + ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) +{ + php_struct *ctx; + long old_index; + apr_bucket *b; + const char *str; + apr_size_t n; + apr_status_t rv; + TSRMLS_FETCH(); + + if (f->r->proxyreq) { + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + + ctx = SG(server_context); + if (ctx == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, + "php failed to get server context"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + if ((rv = ap_get_brigade(f->next, bb, mode, block, readbytes)) != APR_SUCCESS) { + return rv; + } + + for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { + apr_bucket_read(b, &str, &n, APR_NONBLOCK_READ); + if (n > 0) { + old_index = ctx->post_len; + ctx->post_len += n; + ctx->post_data = realloc(ctx->post_data, ctx->post_len + 1); + memcpy(ctx->post_data + old_index, str, n); + } + } + return APR_SUCCESS; +} + +static void php_apache_request_ctor(ap_filter_t *f, php_struct *ctx TSRMLS_DC) +{ + char *content_type; + char *content_length; + const char *auth; + + PG(during_request_startup) = 0; + SG(sapi_headers).http_response_code = !f->r->status ? HTTP_OK : f->r->status; + SG(request_info).content_type = apr_table_get(f->r->headers_in, "Content-Type"); +#undef safe_strdup +#define safe_strdup(x) ((x)?strdup((x)):NULL) + SG(request_info).query_string = safe_strdup(f->r->args); + SG(request_info).request_method = f->r->method; + SG(request_info).proto_num = f->r->proto_num; + SG(request_info).request_uri = safe_strdup(f->r->uri); + SG(request_info).path_translated = safe_strdup(f->r->filename); + f->r->no_local_copy = 1; + content_type = sapi_get_default_content_type(TSRMLS_C); + f->r->content_type = apr_pstrdup(f->r->pool, content_type); + SG(request_info).post_data = ctx->post_data; + SG(request_info).post_data_length = ctx->post_len; + + efree(content_type); + + content_length = (char *) apr_table_get(f->r->headers_in, "Content-Length"); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + + apr_table_unset(f->r->headers_out, "Content-Length"); + apr_table_unset(f->r->headers_out, "Last-Modified"); + apr_table_unset(f->r->headers_out, "Expires"); + apr_table_unset(f->r->headers_out, "ETag"); + + auth = apr_table_get(f->r->headers_in, "Authorization"); + php_handle_auth_data(auth TSRMLS_CC); + + if (SG(request_info).auth_user == NULL && f->r->user) { + SG(request_info).auth_user = estrdup(f->r->user); + } + + ctx->r->user = apr_pstrdup(ctx->r->pool, SG(request_info).auth_user); + + php_request_startup(TSRMLS_C); +} + +static void php_apache_request_dtor(ap_filter_t *f TSRMLS_DC) +{ + php_apr_bucket_brigade *pbb = (php_apr_bucket_brigade *)f->ctx; + + php_request_shutdown(NULL); + + if (SG(request_info).query_string) { + free(SG(request_info).query_string); + } + if (SG(request_info).request_uri) { + free(SG(request_info).request_uri); + } + if (SG(request_info).path_translated) { + free(SG(request_info).path_translated); + } + + apr_brigade_destroy(pbb->bb); +} + +static int php_output_filter(ap_filter_t *f, apr_bucket_brigade *bb) +{ + php_struct *ctx; + void *conf = ap_get_module_config(f->r->per_dir_config, &php5_module); + char *p = get_php_config(conf, "engine", sizeof("engine")); + zend_file_handle zfd; + php_apr_bucket_brigade *pbb; + apr_bucket *b; + TSRMLS_FETCH(); + + if (f->r->proxyreq) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return ap_pass_brigade(f->next, bb); + } + + /* handle situations where user turns the engine off */ + if (*p == '0') { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return ap_pass_brigade(f->next, bb); + } + + if(f->ctx) { + pbb = (php_apr_bucket_brigade *)f->ctx; + } else { + pbb = f->ctx = apr_palloc(f->r->pool, sizeof(*pbb)); + pbb->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); + } + + if(ap_save_brigade(NULL, &pbb->bb, &bb, f->r->pool) != APR_SUCCESS) { + /* Bad */ + } + + apr_brigade_cleanup(bb); + + /* Check to see if the last bucket in this brigade, it not + * we have to wait until then. */ + if(!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(pbb->bb))) { + return 0; + } + + /* Setup the CGI variables if this is the main request.. */ + if (f->r->main == NULL || + /* .. or if the sub-request envinronment differs from the main-request. */ + f->r->subprocess_env != f->r->main->subprocess_env + ) { + /* setup standard CGI variables */ + ap_add_common_vars(f->r); + ap_add_cgi_vars(f->r); + } + + ctx = SG(server_context); + if (ctx == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, + "php failed to get server context"); + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return HTTP_INTERNAL_SERVER_ERROR; + } + + ctx->f = f->next; /* save whatever filters are after us in the chain. */ + + if (ctx->request_processed) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return ap_pass_brigade(f->next, bb); + } + + apply_config(conf); + php_apache_request_ctor(f, ctx TSRMLS_CC); + + /* It'd be nice if we could highlight based of a zend_file_handle here.... + * ...but we can't. */ + + zfd.type = ZEND_HANDLE_STREAM; + + zfd.handle.stream.handle = pbb; + zfd.handle.stream.reader = php_apache_read_stream; + zfd.handle.stream.closer = NULL; + zfd.handle.stream.fsizer = php_apache_fsizer_stream; + zfd.handle.stream.isatty = 0; + + zfd.filename = f->r->filename; + zfd.opened_path = NULL; + zfd.free_filename = 0; + + php_execute_script(&zfd TSRMLS_CC); + + apr_table_set(ctx->r->notes, "mod_php_memory_usage", + apr_psprintf(ctx->r->pool, "%u", zend_memory_peak_usage(1 TSRMLS_CC))); + + php_apache_request_dtor(f TSRMLS_CC); + + if (!f->r->main) { + ctx->request_processed = 1; + } + + b = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(pbb->bb, b); + + /* Pass whatever is left on the brigade. */ + return ap_pass_brigade(f->next, pbb->bb); +} + +static apr_status_t +php_apache_server_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static void php_apache_add_version(apr_pool_t *p) +{ + TSRMLS_FETCH(); + if (PG(expose_php)) { + ap_add_version_component(p, "PHP/" PHP_VERSION); + } +} + +static int php_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ +#ifndef ZTS + int threaded_mpm; + + ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm); + if(threaded_mpm) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, 0, "Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP."); + return DONE; + } +#endif + /* When this is NULL, apache won't override the hard-coded default + * php.ini path setting. */ + apache2_php_ini_path_override = NULL; + return OK; +} + +static int +php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + void *data = NULL; + const char *userdata_key = "apache2filter_post_config"; + + /* Apache will load, unload and then reload a DSO module. This + * prevents us from starting PHP until the second load. */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + if (data == NULL) { + /* We must use set() here and *not* setn(), otherwise the + * static string pointed to by userdata_key will be mapped + * to a different location when the DSO is reloaded and the + * pointers won't match, causing get() to return NULL when + * we expected it to return non-NULL. */ + apr_pool_userdata_set((const void *)1, userdata_key, + apr_pool_cleanup_null, s->process->pool); + return OK; + } + + /* Set up our overridden path. */ + if (apache2_php_ini_path_override) { + apache2_sapi_module.php_ini_path_override = apache2_php_ini_path_override; + } +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache2_sapi_module); + apache2_sapi_module.startup(&apache2_sapi_module); + apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null); + php_apache_add_version(pconf); + + return OK; +} + +static void php_add_filter(request_rec *r, ap_filter_t *f) +{ + int output = (f == r->output_filters); + + /* for those who still have Set*Filter PHP configured */ + while (f) { + if (strcmp(f->frec->name, "PHP") == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, r->server, + "\"Set%sFilter PHP\" already configured for %s", + output ? "Output" : "Input", r->uri); + return; + } + f = f->next; + } + + if (output) { + ap_add_output_filter("PHP", NULL, r, r->connection); + } else { + ap_add_input_filter("PHP", NULL, r, r->connection); + } +} + +static void php_insert_filter(request_rec *r) +{ + int content_type_len = strlen("application/x-httpd-php"); + + if (r->content_type && !strncmp(r->content_type, "application/x-httpd-php", content_type_len-1)) { + if (r->content_type[content_type_len] == '\0' || !strncmp(r->content_type+content_type_len, "-source", sizeof("-source"))) { + php_add_filter(r, r->output_filters); + php_add_filter(r, r->input_filters); + } + } +} + +static apr_status_t php_server_context_cleanup(void *data_) +{ + void **data = data_; + *data = NULL; + return APR_SUCCESS; +} + +static int php_post_read_request(request_rec *r) +{ + php_struct *ctx; + TSRMLS_FETCH(); + + /* Initialize filter context */ + SG(server_context) = ctx = apr_pcalloc(r->pool, sizeof(*ctx)); + + /* register a cleanup so we clear out the SG(server_context) + * after each request. Note: We pass in the pointer to the + * server_context in case this is handled by a different thread. */ + apr_pool_cleanup_register(r->pool, (void *)&SG(server_context), + php_server_context_cleanup, + apr_pool_cleanup_null); + + /* Save the entire request, so we can get the input or output + * filters if we need them. */ + ctx->r = r; + + return OK; +} + +static void php_register_hook(apr_pool_t *p) +{ + ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_insert_filter(php_insert_filter, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_read_request(php_post_read_request, NULL, NULL, APR_HOOK_MIDDLE); + ap_register_output_filter("PHP", php_output_filter, php_apache_disable_caching, AP_FTYPE_RESOURCE); + ap_register_input_filter("PHP", php_input_filter, php_apache_disable_caching, AP_FTYPE_RESOURCE); +} + +static size_t php_apache_read_stream(void *handle, char *buf, size_t wantlen TSRMLS_DC) +{ + php_apr_bucket_brigade *pbb = (php_apr_bucket_brigade *)handle; + apr_bucket_brigade *rbb; + apr_size_t readlen; + apr_bucket *b = NULL; + + rbb = pbb->bb; + + if((apr_brigade_partition(pbb->bb, wantlen, &b) == APR_SUCCESS) && b){ + pbb->bb = apr_brigade_split(rbb, b); + } + + readlen = wantlen; + apr_brigade_flatten(rbb, buf, &readlen); + apr_brigade_cleanup(rbb); + + return readlen; +} + +static size_t php_apache_fsizer_stream(void *handle TSRMLS_DC) +{ + php_apr_bucket_brigade *pbb = (php_apr_bucket_brigade *)handle; + apr_off_t actual = 0; + + if (apr_brigade_length(pbb->bb, 1, &actual) == APR_SUCCESS) { + return actual; + } + + return 0; +} + +AP_MODULE_DECLARE_DATA module php5_module = { + STANDARD20_MODULE_STUFF, + create_php_config, /* create per-directory config structure */ + merge_php_config, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + php_dir_cmds, /* command apr_table_t */ + php_register_hook /* register hooks */ +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2handler/CREDITS b/sapi/apache2handler/CREDITS new file mode 100644 index 0000000..4a78480 --- /dev/null +++ b/sapi/apache2handler/CREDITS @@ -0,0 +1,2 @@ +Apache 2.0 Handler +Ian Holsman, Justin Erenkrantz (based on Apache 2.0 Filter code) diff --git a/sapi/apache2handler/README b/sapi/apache2handler/README new file mode 100644 index 0000000..5cbd1b9 --- /dev/null +++ b/sapi/apache2handler/README @@ -0,0 +1,76 @@ +WHAT IS THIS? + + This module exploits the layered I/O support in Apache 2.0. + +HOW DOES IT WORK? + + In Apache 2.0, you have handlers which generate content (like + reading a script from disk). The content goes then through + a chain of filters. PHP can be such a filter, so that it processes + your script and hands the output to the next filter (which will + usually cause a write to the network). + +DOES IT WORK? + + Currently the issues with the module are: + * Thread safety of external PHP modules + * The lack of re-entrancy of PHP. due to this I have disabled the 'virtual' + function, and tried to stop any method where a php script can run another php + script while it is being run. + + +HOW TO INSTALL + + This SAPI module is known to work with Apache 2.0.44. + + $ cd apache-2.x + $ cd src + $ ./configure --enable-so + $ make install + + For testing purposes, you might want to use --with-mpm=prefork. + (Albeit PHP also works with threaded MPMs. See Thread Safety note above) + + Configure PHP 4: + + $ cd php-4.x + $ ./configure --with-apxs2=/path/to/apache-2.0/bin/apxs + $ make install + + At the end of conf/httpd.conf, add: + + AddType application/x-httpd-php .php + + If you would like to enable source code highlighting functionality add: + + AddType application/x-httpd-php-source .phps + + That's it. Now start bin/httpd. + +HOW TO CONFIGURE + + The Apache 2.0 PHP module supports a new configuration directive that + allows an admin to override the php.ini search path. For example, + place your php.ini file in Apache's ServerRoot/conf directory and + add this to your httpd.conf file: + + PHPINIDir "conf" + +DEBUGGING APACHE AND PHP + + To debug Apache, we recommened: + + 1. Use the Prefork MPM (Apache 1.3-like process model) by + configuring Apache with '--with-mpm=prefork'. + 2. Start httpd using -DONE_PROCESS (e.g. (gdb) r -DONE_PROCESS). + + If you want to debug a part of the PHP startup procedure, set a + breakpoint on 'load_module'. Step through it until apr_dso_load() is + done. Then you can set a breakpoint on any PHP-related symbol. + +TODO + + PHP functions like apache_sub_req (see php_functions.c) + Source Code Highlighting + Protocol handlers + diff --git a/sapi/apache2handler/apache_config.c b/sapi/apache2handler/apache_config.c new file mode 100644 index 0000000..2b51939 --- /dev/null +++ b/sapi/apache2handler/apache_config.c @@ -0,0 +1,241 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_ini.h" +#include "php_apache.h" + +#include "apr_strings.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" + +#ifdef PHP_AP_DEBUG +#define phpapdebug(a) fprintf a +#else +#define phpapdebug(a) +#endif + +typedef struct { + HashTable config; +} php_conf_rec; + +typedef struct { + char *value; + size_t value_len; + char status; + char htaccess; +} php_dir_entry; + +static const char *real_value_hnd(cmd_parms *cmd, void *dummy, const char *name, const char *value, int status) +{ + php_conf_rec *d = dummy; + php_dir_entry e; + + phpapdebug((stderr, "Getting %s=%s for %p (%d)\n", name, value, dummy, zend_hash_num_elements(&d->config))); + + if (!strncasecmp(value, "none", sizeof("none"))) { + value = ""; + } + + e.value = apr_pstrdup(cmd->pool, value); + e.value_len = strlen(value); + e.status = status; + e.htaccess = ((cmd->override & (RSRC_CONF|ACCESS_CONF)) == 0); + + zend_hash_update(&d->config, (char *) name, strlen(name) + 1, &e, sizeof(e), NULL); + return NULL; +} + +static const char *php_apache_value_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_value_hnd(cmd, dummy, name, value, PHP_INI_PERDIR); +} + +static const char *php_apache_admin_value_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_value_hnd(cmd, dummy, name, value, PHP_INI_SYSTEM); +} + +static const char *real_flag_hnd(cmd_parms *cmd, void *dummy, const char *arg1, const char *arg2, int status) +{ + char bool_val[2]; + + if (!strcasecmp(arg2, "On") || (arg2[0] == '1' && arg2[1] == '\0')) { + bool_val[0] = '1'; + } else { + bool_val[0] = '0'; + } + bool_val[1] = 0; + + return real_value_hnd(cmd, dummy, arg1, bool_val, status); +} + +static const char *php_apache_flag_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_flag_hnd(cmd, dummy, name, value, PHP_INI_PERDIR); +} + +static const char *php_apache_admin_flag_handler(cmd_parms *cmd, void *dummy, const char *name, const char *value) +{ + return real_flag_hnd(cmd, dummy, name, value, PHP_INI_SYSTEM); +} + +static const char *php_apache_phpini_set(cmd_parms *cmd, void *mconfig, const char *arg) +{ + if (apache2_php_ini_path_override) { + return "Only first PHPINIDir directive honored per configuration tree - subsequent ones ignored"; + } + apache2_php_ini_path_override = ap_server_root_relative(cmd->pool, arg); + return NULL; +} + +static zend_bool should_overwrite_per_dir_entry(HashTable *target_ht, php_dir_entry *new_per_dir_entry, zend_hash_key *hash_key, void *pData) +{ + php_dir_entry *orig_per_dir_entry; + + if (zend_hash_find(target_ht, hash_key->arKey, hash_key->nKeyLength, (void **) &orig_per_dir_entry)==FAILURE) { + return 1; /* does not exist in dest, copy from source */ + } + + if (new_per_dir_entry->status >= orig_per_dir_entry->status) { + /* use new entry */ + phpapdebug((stderr, "ADDING/OVERWRITING %s (%d vs. %d)\n", hash_key->arKey, new_per_dir_entry->status, orig_per_dir_entry->status)); + return 1; + } else { + return 0; + } +} + + +void *merge_php_config(apr_pool_t *p, void *base_conf, void *new_conf) +{ + php_conf_rec *d = base_conf, *e = new_conf, *n = NULL; +#if STAS_0 + php_dir_entry *pe; + php_dir_entry *data; + char *str; + uint str_len; + ulong num_index; +#endif + + n = create_php_config(p, "merge_php_config"); + /* copy old config */ + zend_hash_copy(&n->config, &d->config, NULL, NULL, sizeof(php_dir_entry)); + /* merge new config */ + phpapdebug((stderr, "Merge dir (%p)+(%p)=(%p)\n", base_conf, new_conf, n)); + zend_hash_merge_ex(&n->config, &e->config, NULL, sizeof(php_dir_entry), (merge_checker_func_t) should_overwrite_per_dir_entry, NULL); +#if STAS_0 + for (zend_hash_internal_pointer_reset(&d->config); + zend_hash_get_current_key_ex(&d->config, &str, &str_len, + &num_index, 0, NULL) == HASH_KEY_IS_STRING; + zend_hash_move_forward(&d->config)) { + pe = NULL; + zend_hash_get_current_data(&d->config, (void **) &data); + if (zend_hash_find(&n->config, str, str_len, (void **) &pe) == SUCCESS) { + if (pe->status >= data->status) continue; + } + phpapdebug((stderr, "ADDING/OVERWRITING %s (%d vs. %d)\n", str, data->status, pe?pe->status:-1)); + zend_hash_update(&n->config, str, str_len, data, sizeof(*data), NULL); + } +#endif + return n; +} + +char *get_php_config(void *conf, char *name, size_t name_len) +{ + php_conf_rec *d = conf; + php_dir_entry *pe; + + if (zend_hash_find(&d->config, name, name_len, (void **) &pe) == SUCCESS) { + return pe->value; + } + + return ""; +} + +void apply_config(void *dummy) +{ + php_conf_rec *d = dummy; + char *str; + uint str_len; + php_dir_entry *data; + + for (zend_hash_internal_pointer_reset(&d->config); + zend_hash_get_current_key_ex(&d->config, &str, &str_len, NULL, 0, + NULL) == HASH_KEY_IS_STRING; + zend_hash_move_forward(&d->config)) { + if (zend_hash_get_current_data(&d->config, (void **) &data) == SUCCESS) { + phpapdebug((stderr, "APPLYING (%s)(%s)\n", str, data->value)); + if (zend_alter_ini_entry(str, str_len, data->value, data->value_len, data->status, data->htaccess?PHP_INI_STAGE_HTACCESS:PHP_INI_STAGE_ACTIVATE) == FAILURE) { + phpapdebug((stderr, "..FAILED\n")); + } + } + } +} + +const command_rec php_dir_cmds[] = +{ + AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL, OR_OPTIONS, "PHP Value Modifier"), + AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL, OR_OPTIONS, "PHP Flag Modifier"), + AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"), + AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"), + AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL, RSRC_CONF, "Directory containing the php.ini file"), + {NULL} +}; + +static apr_status_t destroy_php_config(void *data) +{ + php_conf_rec *d = data; + + phpapdebug((stderr, "Destroying config %p\n", data)); + zend_hash_destroy(&d->config); + + return APR_SUCCESS; +} + +void *create_php_config(apr_pool_t *p, char *dummy) +{ + php_conf_rec *newx = (php_conf_rec *) apr_pcalloc(p, sizeof(*newx)); + + phpapdebug((stderr, "Creating new config (%p) for %s\n", newx, dummy)); + zend_hash_init(&newx->config, 0, NULL, NULL, 1); + apr_pool_cleanup_register(p, newx, destroy_php_config, apr_pool_cleanup_null); + return (void *) newx; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2handler/config.m4 b/sapi/apache2handler/config.m4 new file mode 100644 index 0000000..702f91f --- /dev/null +++ b/sapi/apache2handler/config.m4 @@ -0,0 +1,138 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(apxs2,, +[ --with-apxs2[=FILE] Build shared Apache 2.0 Handler module. FILE is the optional + pathname to the Apache apxs tool [apxs]], no, no) + +AC_MSG_CHECKING([for Apache 2.0 handler-module support via DSO through APXS]) + +if test "$PHP_APXS2" != "no"; then + if test "$PHP_APXS2" = "yes"; then + APXS=apxs + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0" && test -x /usr/sbin/apxs; then + APXS=/usr/sbin/apxs + fi + else + PHP_EXPAND_PATH($PHP_APXS2, APXS) + fi + + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0"; then + AC_MSG_RESULT() + AC_MSG_RESULT() + AC_MSG_RESULT([Sorry, I cannot run apxs. Possible reasons follow:]) + AC_MSG_RESULT() + AC_MSG_RESULT([1. Perl is not installed]) + AC_MSG_RESULT([2. apxs was not found. Try to pass the path using --with-apxs2=/path/to/apxs]) + AC_MSG_RESULT([3. Apache was not built using --enable-so (the apxs usage page is displayed)]) + AC_MSG_RESULT() + AC_MSG_RESULT([The output of $APXS follows:]) + $APXS -q CFLAGS + AC_MSG_ERROR([Aborting]) + fi + + APXS_INCLUDEDIR=`$APXS -q INCLUDEDIR` + APXS_BINDIR=`$APXS -q BINDIR` + APXS_HTTPD=`$APXS -q SBINDIR`/`$APXS -q TARGET` + APXS_CFLAGS=`$APXS -q CFLAGS` + APU_BINDIR=`$APXS -q APU_BINDIR` + APR_BINDIR=`$APXS -q APR_BINDIR` + + # Pick up ap[ru]-N-config if using httpd >=2.1 + APR_CONFIG=`$APXS -q APR_CONFIG 2>/dev/null || + echo $APR_BINDIR/apr-config` + APU_CONFIG=`$APXS -q APU_CONFIG 2>/dev/null || + echo $APU_BINDIR/apu-config` + + APR_CFLAGS="`$APR_CONFIG --cppflags --includes`" + APU_CFLAGS="`$APU_CONFIG --includes`" + + for flag in $APXS_CFLAGS; do + case $flag in + -D*) APACHE_CPPFLAGS="$APACHE_CPPFLAGS $flag";; + esac + done + + APACHE_CFLAGS="$APACHE_CPPFLAGS -I$APXS_INCLUDEDIR $APR_CFLAGS $APU_CFLAGS" + + # Test that we're trying to configure with apache 2.x + PHP_AP_EXTRACT_VERSION($APXS_HTTPD) + if test "$APACHE_VERSION" -le 2000000; then + AC_MSG_ERROR([You have enabled Apache 2 support while your server is Apache 1.3. Please use the appropiate switch --with-apxs (without the 2)]) + elif test "$APACHE_VERSION" -lt 2000044; then + AC_MSG_ERROR([Please note that Apache version >= 2.0.44 is required]) + fi + + APXS_LIBEXECDIR='$(INSTALL_ROOT)'`$APXS -q LIBEXECDIR` + if test -z `$APXS -q SYSCONFDIR`; then + INSTALL_IT="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -i -n php5" + else + APXS_SYSCONFDIR='$(INSTALL_ROOT)'`$APXS -q SYSCONFDIR` + INSTALL_IT="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + \$(mkinstalldirs) '$APXS_SYSCONFDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -S SYSCONFDIR='$APXS_SYSCONFDIR' \ + -i -a -n php5" + fi + + case $host_alias in + *aix*) + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-brtl -Wl,-bI:$APXS_LIBEXECDIR/httpd.exp" + PHP_SELECT_SAPI(apache2handler, shared, mod_php5.c sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + *darwin*) + dnl When using bundles on Darwin, we must resolve all symbols. However, + dnl the linker does not recursively look at the bundle loader and + dnl pull in its dependencies. Therefore, we must pull in the APR + dnl and APR-util libraries. + if test -x "$APR_CONFIG"; then + MH_BUNDLE_FLAGS="`$APR_CONFIG --ldflags --link-ld --libs`" + fi + if test -x "$APU_CONFIG"; then + MH_BUNDLE_FLAGS="`$APU_CONFIG --ldflags --link-ld --libs` $MH_BUNDLE_FLAGS" + fi + MH_BUNDLE_FLAGS="-bundle -bundle_loader $APXS_HTTPD $MH_BUNDLE_FLAGS" + PHP_SUBST(MH_BUNDLE_FLAGS) + PHP_SELECT_SAPI(apache2handler, bundle, mod_php5.c sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + SAPI_SHARED=libs/libphp5.so + INSTALL_IT="$INSTALL_IT $SAPI_SHARED" + ;; + *beos*) + if test -f _APP_; then `rm _APP_`; fi + `ln -s $APXS_BINDIR/httpd _APP_` + EXTRA_LIBS="$EXTRA_LIBS _APP_" + PHP_SELECT_SAPI(apache2handler, shared, mod_php5.c sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + *) + PHP_SELECT_SAPI(apache2handler, shared, mod_php5.c sapi_apache2.c apache_config.c php_functions.c, $APACHE_CFLAGS) + INSTALL_IT="$INSTALL_IT $SAPI_LIBTOOL" + ;; + esac + + if test "$APACHE_VERSION" -lt 2004001; then + APXS_MPM=`$APXS -q MPM_NAME` + if test "$APXS_MPM" != "prefork" && test "$APXS_MPM" != "peruser" && test "$APXS_MPM" != "itk"; then + PHP_BUILD_THREAD_SAFE + fi + else + APACHE_THREADED_MPM=`$APXS_HTTPD -V | grep 'threaded:.*yes'` + if test -n "$APACHE_THREADED_MPM"; then + PHP_BUILD_THREAD_SAFE + fi + fi + AC_MSG_RESULT(yes) + PHP_SUBST(APXS) +else + AC_MSG_RESULT(no) +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/apache2handler/config.w32 b/sapi/apache2handler/config.w32 new file mode 100644 index 0000000..a754751 --- /dev/null +++ b/sapi/apache2handler/config.w32 @@ -0,0 +1,58 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('apache2handler', 'Build Apache 2.x handler', 'no'); + +if (PHP_APACHE2HANDLER != "no") { + if (PHP_ZTS == "no") { + WARNING("Apache 2.0 module requires an --enable-zts build of PHP on windows"); + } else if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE2HANDLER", PHP_PHP_BUILD + "\\include\\apache2") && + CHECK_LIB("libhttpd.lib", "apache2handler", PHP_PHP_BUILD + "\\lib\\apache2") && + CHECK_LIB("libapr.lib", "apache2handler", PHP_PHP_BUILD + "\\lib\\apache2") && + CHECK_LIB("libaprutil.lib", "apache2handler", PHP_PHP_BUILD + "\\lib\\apache2") + ) { + SAPI('apache2handler', 'mod_php5.c sapi_apache2.c apache_config.c php_functions.c', + 'php' + PHP_VERSION + 'apache2.dll', + '/D PHP_APACHE2_EXPORTS /I win32'); + } else { + WARNING("Could not find apache2 libraries/headers"); + } +} + +ARG_ENABLE('apache2-2handler', 'Build Apache 2.2.x handler', 'no'); + +if (PHP_APACHE2_2HANDLER != "no") { + if (PHP_ZTS == "no") { + WARNING("Apache 2.2 module requires an --enable-zts build of PHP on windows"); + } else if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE2_2HANDLER", PHP_PHP_BUILD + "\\include\\apache2_2") && + CHECK_LIB("libhttpd.lib", "apache2_2handler", PHP_PHP_BUILD + "\\lib\\apache2_2") && + CHECK_LIB("libapr-1.lib", "apache2_2handler", PHP_PHP_BUILD + "\\lib\\apache2_2") && + CHECK_LIB("libaprutil-1.lib", "apache2_2handler", PHP_PHP_BUILD + "\\lib\\apache2_2") + ) { + SAPI('apache2_2handler', 'mod_php5.c sapi_apache2.c apache_config.c php_functions.c', + 'php' + PHP_VERSION + 'apache2_2.dll', + '/D PHP_APACHE2_EXPORTS /I win32', + 'sapi\\apache2_2handler'); + } else { + WARNING("Could not find apache2.2 libraries/headers"); + } +} + +ARG_ENABLE('apache2-4handler', 'Build Apache 2.4.x handler', 'no'); +if (PHP_APACHE2_4HANDLER != "no") { + if (PHP_ZTS == "no") { + WARNING("Apache 2.4 module requires an --enable-zts build of PHP on windows"); + } else if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE2_4HANDLER", PHP_PHP_BUILD + "\\include\\apache2_4") && + CHECK_LIB("libhttpd.lib", "apache2_4handler", PHP_PHP_BUILD + "\\lib\\apache2_4") && + CHECK_LIB("libapr-1.lib", "apache2_4handler", PHP_PHP_BUILD + "\\lib\\apache2_4") && + CHECK_LIB("libaprutil-1.lib", "apache2_4handler", PHP_PHP_BUILD + "\\lib\\apache2_4") + ) { + SAPI('apache2_4handler', 'mod_php5.c sapi_apache2.c apache_config.c php_functions.c', + 'php' + PHP_VERSION + 'apache2_4.dll', + '/D PHP_APACHE2_EXPORTS /I win32', + 'sapi\\apache2handler'); + } else { + WARNING("Could not find apache 2.4 libraries/headers"); + } +} + diff --git a/sapi/apache2handler/mod_php5.c b/sapi/apache2handler/mod_php5.c new file mode 100644 index 0000000..56ef1bc --- /dev/null +++ b/sapi/apache2handler/mod_php5.c @@ -0,0 +1,45 @@ +/* + +----------------------------------------------------------------------+ + | 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: Sascha Schumann <sascha@schumann.cx> | + | Parts based on Apache 1.3 SAPI module by | + | Rasmus Lerdorf and Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_apache.h" + +AP_MODULE_DECLARE_DATA module php5_module = { + STANDARD20_MODULE_STUFF, + create_php_config, /* create per-directory config structure */ + merge_php_config, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + php_dir_cmds, /* command apr_table_t */ + php_ap2_register_hook /* register hooks */ +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2handler/php.sym b/sapi/apache2handler/php.sym new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache2handler/php.sym @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache2handler/php5apache2.dsp b/sapi/apache2handler/php5apache2.dsp new file mode 100644 index 0000000..40cd58c --- /dev/null +++ b/sapi/apache2handler/php5apache2.dsp @@ -0,0 +1,146 @@ +# Microsoft Developer Studio Project File - Name="php5apache2" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5apache2 - 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 "php5apache2.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 "php5apache2.mak" CFG="php5apache2 - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5apache2 - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache2 - Win32 Release_TS_inline" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache2 - Win32 Debug_TS" (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)" == "php5apache2 - 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 "PHP_APACHE2_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\TSRM" /D ZEND_DEBUG=0 /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x407 /d "NDEBUG"
+# ADD RSC /l 0x407 /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 php5ts.lib libhttpd.lib libapr.lib libaprutil.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 /dll /machine:I386 /libpath:"..\..\Release_TS" /libpath:"..\..\TSRM\Release_TS" /libpath:"..\..\Zend\Release_TS"
+
+!ELSEIF "$(CFG)" == "php5apache2 - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release_TS_inline"
+# PROP BASE Intermediate_Dir "Release_TS_inline"
+# 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 /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP_APACHE2_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\TSRM" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x407 /d "NDEBUG"
+# ADD RSC /l 0x407 /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 php5ts.lib libhttpd.lib libapr.lib libaprutil.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 /dll /machine:I386 /out:"..\..\Release_TS_inline/php5apache2.dll" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\TSRM\Release_TS_inline" /libpath:"..\..\Zend\Release_TS_inline"
+
+!ELSEIF "$(CFG)" == "php5apache2 - 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 Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP_APACHE2_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\TSRM" /D "_DEBUG" /D ZEND_DEBUG=1 /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "WIN32" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x407 /d "_DEBUG"
+# ADD RSC /l 0x407 /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 php5ts_debug.lib libhttpd.lib libapr.lib libaprutil.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 /dll /debug /machine:I386 /pdbtype:sept /libpath:"..\..\Debug_TS" /libpath:"..\..\TSRM\Debug_TS" /libpath:"..\..\Zend\Debug_TS"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5apache2 - Win32 Release_TS"
+# Name "php5apache2 - Win32 Release_TS_inline"
+# Name "php5apache2 - Win32 Debug_TS"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\apache_config.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\mod_php5.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\php_functions.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\sapi_apache2.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\php_apache.h
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/sapi/apache2handler/php_apache.h b/sapi/apache2handler/php_apache.h new file mode 100644 index 0000000..8bc4608 --- /dev/null +++ b/sapi/apache2handler/php_apache.h @@ -0,0 +1,91 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#ifndef PHP_APACHE_H +#define PHP_APACHE_H + +#include "httpd.h" +#include "http_config.h" +#include "http_core.h" + +/* Declare this so we can get to it from outside the sapi_apache2.c file */ +extern module AP_MODULE_DECLARE_DATA php5_module; + +/* A way to specify the location of the php.ini dir in an apache directive */ +extern char *apache2_php_ini_path_override; + +/* The server_context used by PHP */ +typedef struct php_struct { + int state; + request_rec *r; + apr_bucket_brigade *brigade; + /* stat structure of the current file */ +#if defined(NETWARE) && defined(CLIB_STAT_PATCH) + struct stat_libc finfo; +#else + struct stat finfo; +#endif + /* Whether or not we've processed PHP in the output filters yet. */ + int request_processed; + /* final content type */ + char *content_type; +} php_struct; + +void *merge_php_config(apr_pool_t *p, void *base_conf, void *new_conf); +void *create_php_config(apr_pool_t *p, char *dummy); +char *get_php_config(void *conf, char *name, size_t name_len); +void apply_config(void *); +extern const command_rec php_dir_cmds[]; +void php_ap2_register_hook(apr_pool_t *p); + +#define APR_ARRAY_FOREACH_OPEN(arr, key, val) \ +{ \ + apr_table_entry_t *elts; \ + int i; \ + elts = (apr_table_entry_t *) arr->elts; \ + for (i = 0; i < arr->nelts; i++) { \ + key = elts[i].key; \ + val = elts[i].val; + +#define APR_ARRAY_FOREACH_CLOSE() }} + +typedef struct { + long engine; + long xbithack; + long last_modified; +} php_apache2_info_struct; + +extern zend_module_entry apache2_module_entry; + +#ifdef ZTS +extern int php_apache2_info_id; +#define AP2(v) TSRMG(php_apache2_info_id, php_apache2_info_struct *, v) +#else +extern php_apache2_info_struct php_apache2_info; +#define AP2(v) (php_apache2_info.v) +#endif + +/* fix for gcc4 visibility patch */ +#ifndef PHP_WIN32 +# undef AP_MODULE_DECLARE_DATA +# define AP_MODULE_DECLARE_DATA PHPAPI +#endif + +#endif /* PHP_APACHE_H */ diff --git a/sapi/apache2handler/php_functions.c b/sapi/apache2handler/php_functions.c new file mode 100644 index 0000000..1f79a53 --- /dev/null +++ b/sapi/apache2handler/php_functions.c @@ -0,0 +1,571 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "ext/standard/php_smart_str.h" +#include "ext/standard/info.h" +#include "ext/standard/head.h" +#include "php_ini.h" +#include "SAPI.h" + +#define CORE_PRIVATE +#include "apr_strings.h" +#include "apr_time.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" +#include "ap_mpm.h" +#if !defined(WIN32) && !defined(WINNT) && !defined(NETWARE) +#include "unixd.h" +#endif + +#include "php_apache.h" + +#ifdef ZTS +int php_apache2_info_id; +#else +php_apache2_info_struct php_apache2_info; +#endif + +#define SECTION(name) PUTS("<h2>" name "</h2>\n") + +static request_rec *php_apache_lookup_uri(char *filename TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + + if (!filename || !ctx || !ctx->r) { + return NULL; + } + + return ap_sub_req_lookup_uri(filename, ctx->r, ctx->r->output_filters); +} + +/* {{{ proto bool virtual(string uri) + Perform an apache sub-request */ +PHP_FUNCTION(virtual) +{ + char *filename; + int filename_len; + request_rec *rr; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = php_apache_lookup_uri(filename TSRMLS_CC))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + RETURN_FALSE; + } + + if (rr->status != HTTP_OK) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; + } + + /* Flush everything. */ + php_output_end_all(TSRMLS_C); + php_header(TSRMLS_C); + + /* Ensure that the ap_r* layer for the main request is flushed, to + * work around http://issues.apache.org/bugzilla/show_bug.cgi?id=17629 */ + ap_rflush(rr->main); + + if (ap_run_sub_req(rr)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - request execution failed", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; + } + ap_destroy_sub_req(rr); + RETURN_TRUE; +} +/* }}} */ + +#define ADD_LONG(name) \ + add_property_long(return_value, #name, rr->name) +#define ADD_TIME(name) \ + add_property_long(return_value, #name, apr_time_sec(rr->name)); +#define ADD_STRING(name) \ + if (rr->name) add_property_string(return_value, #name, (char *) rr->name, 1) + +PHP_FUNCTION(apache_lookup_uri) +{ + request_rec *rr; + char *filename; + int filename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "p", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = php_apache_lookup_uri(filename TSRMLS_CC))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + RETURN_FALSE; + } + + if (rr->status == HTTP_OK) { + object_init(return_value); + + ADD_LONG(status); + ADD_STRING(the_request); + ADD_STRING(status_line); + ADD_STRING(method); + ADD_TIME(mtime); + ADD_LONG(clength); +#if MODULE_MAGIC_NUMBER < 20020506 + ADD_STRING(boundary); +#endif + ADD_STRING(range); + ADD_LONG(chunked); + ADD_STRING(content_type); + ADD_STRING(handler); + ADD_LONG(no_cache); + ADD_LONG(no_local_copy); + ADD_STRING(unparsed_uri); + ADD_STRING(uri); + ADD_STRING(filename); + ADD_STRING(path_info); + ADD_STRING(args); + ADD_LONG(allowed); + ADD_LONG(sent_bodyct); + ADD_LONG(bytes_sent); + ADD_LONG(mtime); + ADD_TIME(request_time); + + ap_destroy_sub_req(rr); + return; + } + + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include '%s' - error finding URI", filename); + ap_destroy_sub_req(rr); + RETURN_FALSE; +} + +/* {{{ proto array getallheaders(void) + Fetch all HTTP request headers */ +PHP_FUNCTION(apache_request_headers) +{ + php_struct *ctx; + const apr_array_header_t *arr; + char *key, *val; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + ctx = SG(server_context); + arr = apr_table_elts(ctx->r->headers_in); + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) val = ""; + add_assoc_string(return_value, key, val, 1); + APR_ARRAY_FOREACH_CLOSE() +} +/* }}} */ + +/* {{{ proto array apache_response_headers(void) + Fetch all HTTP response headers */ +PHP_FUNCTION(apache_response_headers) +{ + php_struct *ctx; + const apr_array_header_t *arr; + char *key, *val; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + ctx = SG(server_context); + arr = apr_table_elts(ctx->r->headers_out); + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) val = ""; + add_assoc_string(return_value, key, val, 1); + APR_ARRAY_FOREACH_CLOSE() +} +/* }}} */ + +/* {{{ proto string apache_note(string note_name [, string note_value]) + Get and set Apache request notes */ +PHP_FUNCTION(apache_note) +{ + php_struct *ctx; + char *note_name, *note_val = NULL; + int note_name_len, note_val_len; + char *old_note_val=NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", ¬e_name, ¬e_name_len, ¬e_val, ¬e_val_len) == FAILURE) { + return; + } + + ctx = SG(server_context); + + old_note_val = (char *) apr_table_get(ctx->r->notes, note_name); + + if (note_val) { + apr_table_set(ctx->r->notes, note_name, note_val); + } + + if (old_note_val) { + RETURN_STRING(old_note_val, 1); + } + + RETURN_FALSE; +} +/* }}} */ + + +/* {{{ proto bool apache_setenv(string variable, string value [, bool walk_to_top]) + Set an Apache subprocess_env variable */ +/* + * XXX this doesn't look right. shouldn't it be the parent ?*/ +PHP_FUNCTION(apache_setenv) +{ + php_struct *ctx; + char *variable=NULL, *string_val=NULL; + int variable_len, string_val_len; + zend_bool walk_to_top = 0; + int arg_count = ZEND_NUM_ARGS(); + request_rec *r; + + if (zend_parse_parameters(arg_count TSRMLS_CC, "ss|b", &variable, &variable_len, &string_val, &string_val_len, &walk_to_top) == FAILURE) { + return; + } + + ctx = SG(server_context); + + r = ctx->r; + if (arg_count == 3) { + if (walk_to_top) { + while(r->prev) { + r = r->prev; + } + } + } + + apr_table_set(r->subprocess_env, variable, string_val); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool apache_getenv(string variable [, bool walk_to_top]) + Get an Apache subprocess_env variable */ +/* + * XXX: shouldn't this be the parent not the 'prev' + */ +PHP_FUNCTION(apache_getenv) +{ + php_struct *ctx; + char *variable=NULL; + int variable_len; + zend_bool walk_to_top = 0; + int arg_count = ZEND_NUM_ARGS(); + char *env_val=NULL; + request_rec *r; + + if (zend_parse_parameters(arg_count TSRMLS_CC, "s|b", &variable, &variable_len, &walk_to_top) == FAILURE) { + return; + } + + ctx = SG(server_context); + + r = ctx->r; + if (arg_count == 2) { + if (walk_to_top) { + while(r->prev) { + r = r->prev; + } + } + } + + env_val = (char*) apr_table_get(r->subprocess_env, variable); + + if (env_val != NULL) { + RETURN_STRING(env_val, 1); + } + + RETURN_FALSE; +} +/* }}} */ + +static char *php_apache_get_version() +{ +#if MODULE_MAGIC_NUMBER_MAJOR >= 20060905 + return (char *) ap_get_server_banner(); +#else + return (char *) ap_get_server_version(); +#endif +} + +/* {{{ proto string apache_get_version(void) + Fetch Apache version */ +PHP_FUNCTION(apache_get_version) +{ + char *apv = php_apache_get_version(); + + if (apv && *apv) { + RETURN_STRING(apv, 1); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array apache_get_modules(void) + Get a list of loaded Apache modules */ +PHP_FUNCTION(apache_get_modules) +{ + int n; + char *p; + + array_init(return_value); + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + add_next_index_stringl(return_value, s, (p - s), 1); + } else { + add_next_index_string(return_value, s, 1); + } + } +} +/* }}} */ + +PHP_MINFO_FUNCTION(apache) +{ + char *apv = php_apache_get_version(); + smart_str tmp1 = {0}; + char tmp[1024]; + int n, max_requests; + char *p; + server_rec *serv = ((php_struct *) SG(server_context))->r->server; +#if !defined(WIN32) && !defined(WINNT) && !defined(NETWARE) +#if MODULE_MAGIC_NUMBER_MAJOR >= 20081201 + AP_DECLARE_DATA extern unixd_config_rec ap_unixd_config; +#else + AP_DECLARE_DATA extern unixd_config_rec unixd_config; +#endif +#endif + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + smart_str_appendl(&tmp1, s, (p - s)); + } else { + smart_str_appends(&tmp1, s); + } + smart_str_appendc(&tmp1, ' '); + } + if ((tmp1.len - 1) >= 0) { + tmp1.c[tmp1.len - 1] = '\0'; + } + + php_info_print_table_start(); + if (apv && *apv) { + php_info_print_table_row(2, "Apache Version", apv); + } + snprintf(tmp, sizeof(tmp), "%d", MODULE_MAGIC_NUMBER); + php_info_print_table_row(2, "Apache API Version", tmp); + + if (serv->server_admin && *(serv->server_admin)) { + php_info_print_table_row(2, "Server Administrator", serv->server_admin); + } + + snprintf(tmp, sizeof(tmp), "%s:%u", serv->server_hostname, serv->port); + php_info_print_table_row(2, "Hostname:Port", tmp); + +#if !defined(WIN32) && !defined(WINNT) && !defined(NETWARE) +#if MODULE_MAGIC_NUMBER_MAJOR >= 20081201 + snprintf(tmp, sizeof(tmp), "%s(%d)/%d", ap_unixd_config.user_name, ap_unixd_config.user_id, ap_unixd_config.group_id); +#else + snprintf(tmp, sizeof(tmp), "%s(%d)/%d", unixd_config.user_name, unixd_config.user_id, unixd_config.group_id); +#endif + php_info_print_table_row(2, "User/Group", tmp); +#endif + + ap_mpm_query(AP_MPMQ_MAX_REQUESTS_DAEMON, &max_requests); + snprintf(tmp, sizeof(tmp), "Per Child: %d - Keep Alive: %s - Max Per Connection: %d", max_requests, (serv->keep_alive ? "on":"off"), serv->keep_alive_max); + php_info_print_table_row(2, "Max Requests", tmp); + + apr_snprintf(tmp, sizeof tmp, + "Connection: %" APR_TIME_T_FMT " - Keep-Alive: %" APR_TIME_T_FMT, + apr_time_sec(serv->timeout), apr_time_sec(serv->keep_alive_timeout)); + php_info_print_table_row(2, "Timeouts", tmp); + + php_info_print_table_row(2, "Virtual Server", (serv->is_virtual ? "Yes" : "No")); + php_info_print_table_row(2, "Server Root", ap_server_root); + php_info_print_table_row(2, "Loaded Modules", tmp1.c); + + smart_str_free(&tmp1); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); + + { + const apr_array_header_t *arr = apr_table_elts(((php_struct *) SG(server_context))->r->subprocess_env); + char *key, *val; + + SECTION("Apache Environment"); + php_info_print_table_start(); + php_info_print_table_header(2, "Variable", "Value"); + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + php_info_print_table_row(2, key, val); + APR_ARRAY_FOREACH_CLOSE() + + php_info_print_table_end(); + + SECTION("HTTP Headers Information"); + php_info_print_table_start(); + php_info_print_table_colspan_header(2, "HTTP Request Headers"); + php_info_print_table_row(2, "HTTP Request", ((php_struct *) SG(server_context))->r->the_request); + + arr = apr_table_elts(((php_struct *) SG(server_context))->r->headers_in); + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + php_info_print_table_row(2, key, val); + APR_ARRAY_FOREACH_CLOSE() + + php_info_print_table_colspan_header(2, "HTTP Response Headers"); + arr = apr_table_elts(((php_struct *) SG(server_context))->r->headers_out); + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + php_info_print_table_row(2, key, val); + APR_ARRAY_FOREACH_CLOSE() + + php_info_print_table_end(); + } +} + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2handler_lookup_uri, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2handler_virtual, 0, 0, 1) + ZEND_ARG_INFO(0, uri) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2handler_response_headers, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2handler_getallheaders, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2handler_note, 0, 0, 1) + ZEND_ARG_INFO(0, note_name) + ZEND_ARG_INFO(0, note_value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2handler_setenv, 0, 0, 2) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, value) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apache2handler_getenv, 0, 0, 1) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2handler_get_version, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apache2handler_get_modules, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry apache_functions[] = { + PHP_FE(apache_lookup_uri, arginfo_apache2handler_lookup_uri) + PHP_FE(virtual, arginfo_apache2handler_virtual) + PHP_FE(apache_request_headers, arginfo_apache2handler_getallheaders) + PHP_FE(apache_response_headers, arginfo_apache2handler_response_headers) + PHP_FE(apache_setenv, arginfo_apache2handler_setenv) + PHP_FE(apache_getenv, arginfo_apache2handler_getenv) + PHP_FE(apache_note, arginfo_apache2handler_note) + PHP_FE(apache_get_version, arginfo_apache2handler_get_version) + PHP_FE(apache_get_modules, arginfo_apache2handler_get_modules) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_apache2handler_getallheaders) + {NULL, NULL, NULL} +}; + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("xbithack", "0", PHP_INI_ALL, OnUpdateLong, xbithack, php_apache2_info_struct, php_apache2_info) + STD_PHP_INI_ENTRY("engine", "1", PHP_INI_ALL, OnUpdateLong, engine, php_apache2_info_struct, php_apache2_info) + STD_PHP_INI_ENTRY("last_modified", "0", PHP_INI_ALL, OnUpdateLong, last_modified, php_apache2_info_struct, php_apache2_info) +PHP_INI_END() + +static PHP_MINIT_FUNCTION(apache) +{ +#ifdef ZTS + ts_allocate_id(&php_apache2_info_id, sizeof(php_apache2_info_struct), (ts_allocate_ctor) NULL, NULL); +#endif + REGISTER_INI_ENTRIES(); + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(apache) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} + +zend_module_entry php_apache_module = { + STANDARD_MODULE_HEADER, + "apache2handler", + apache_functions, + PHP_MINIT(apache), + PHP_MSHUTDOWN(apache), + NULL, + NULL, + PHP_MINFO(apache), + NULL, + STANDARD_MODULE_PROPERTIES +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache2handler/sapi_apache2.c b/sapi/apache2handler/sapi_apache2.c new file mode 100644 index 0000000..bcb2443 --- /dev/null +++ b/sapi/apache2handler/sapi_apache2.c @@ -0,0 +1,718 @@ +/* + +----------------------------------------------------------------------+ + | 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: Sascha Schumann <sascha@schumann.cx> | + | Parts based on Apache 1.3 SAPI module by | + | Rasmus Lerdorf and Zeev Suraski | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_main.h" +#include "php_ini.h" +#include "php_variables.h" +#include "SAPI.h" + +#include <fcntl.h> + +#include "ext/standard/php_smart_str.h" +#ifndef NETWARE +#include "ext/standard/php_standard.h" +#else +#include "ext/standard/basic_functions.h" +#endif + +#include "apr_strings.h" +#include "ap_config.h" +#include "util_filter.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_protocol.h" +#include "http_log.h" +#include "http_main.h" +#include "util_script.h" +#include "http_core.h" +#include "ap_mpm.h" + +#include "php_apache.h" + +#ifdef PHP_WIN32 +# if _MSC_VER <= 1300 +# include "win32/php_strtoi64.h" +# endif +#endif + +/* UnixWare and Netware define shutdown to _shutdown, which causes problems later + * on when using a structure member named shutdown. Since this source + * file does not use the system call shutdown, it is safe to #undef it.K + */ +#undef shutdown + +#define PHP_MAGIC_TYPE "application/x-httpd-php" +#define PHP_SOURCE_MAGIC_TYPE "application/x-httpd-php-source" +#define PHP_SCRIPT "php5-script" + +/* A way to specify the location of the php.ini dir in an apache directive */ +char *apache2_php_ini_path_override = NULL; + +static int +php_apache_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + request_rec *r; + php_struct *ctx; + + ctx = SG(server_context); + r = ctx->r; + + if (ap_rwrite(str, str_length, r) < 0) { + php_handle_aborted_connection(); + } + + return str_length; /* we always consume all the data passed to us. */ +} + +static int +php_apache_sapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + php_struct *ctx; + char *val, *ptr; + + ctx = SG(server_context); + + switch (op) { + case SAPI_HEADER_DELETE: + apr_table_unset(ctx->r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_DELETE_ALL: + apr_table_clear(ctx->r->headers_out); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + val = strchr(sapi_header->header, ':'); + + if (!val) { + return 0; + } + ptr = val; + + *val = '\0'; + + do { + val++; + } while (*val == ' '); + + if (!strcasecmp(sapi_header->header, "content-type")) { + if (ctx->content_type) { + efree(ctx->content_type); + } + ctx->content_type = estrdup(val); + } else if (!strcasecmp(sapi_header->header, "content-length")) { +#ifdef PHP_WIN32 +# ifdef APR_HAS_LARGE_FILES + ap_set_content_length(ctx->r, (apr_off_t) _strtoui64(val, (char **)NULL, 10)); +# else + ap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10)); +# endif +#else + ap_set_content_length(ctx->r, (apr_off_t) strtol(val, (char **)NULL, 10)); +#endif + } else if (op == SAPI_HEADER_REPLACE) { + apr_table_set(ctx->r->headers_out, sapi_header->header, val); + } else { + apr_table_add(ctx->r->headers_out, sapi_header->header, val); + } + + *ptr = ':'; + + return SAPI_HEADER_ADD; + + default: + return 0; + } +} + +static int +php_apache_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + const char *sline = SG(sapi_headers).http_status_line; + + ctx->r->status = SG(sapi_headers).http_response_code; + + /* httpd requires that r->status_line is set to the first digit of + * the status-code: */ + if (sline && strlen(sline) > 12 && strncmp(sline, "HTTP/1.", 7) == 0 && sline[8] == ' ') { + ctx->r->status_line = apr_pstrdup(ctx->r->pool, sline + 9); + ctx->r->proto_num = 1000 + (sline[7]-'0'); + if ((sline[7]-'0') == 0) { + apr_table_set(ctx->r->subprocess_env, "force-response-1.0", "true"); + } + } + + /* call ap_set_content_type only once, else each time we call it, + configured output filters for that content type will be added */ + if (!ctx->content_type) { + ctx->content_type = sapi_get_default_content_type(TSRMLS_C); + } + ap_set_content_type(ctx->r, apr_pstrdup(ctx->r->pool, ctx->content_type)); + efree(ctx->content_type); + ctx->content_type = NULL; + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int +php_apache_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ + apr_size_t len, tlen=0; + php_struct *ctx = SG(server_context); + request_rec *r; + apr_bucket_brigade *brigade; + + r = ctx->r; + brigade = ctx->brigade; + len = count_bytes; + + /* + * This loop is needed because ap_get_brigade() can return us partial data + * which would cause premature termination of request read. Therefor we + * need to make sure that if data is available we fill the buffer completely. + */ + + while (ap_get_brigade(r->input_filters, brigade, AP_MODE_READBYTES, APR_BLOCK_READ, len) == APR_SUCCESS) { + apr_brigade_flatten(brigade, buf, &len); + apr_brigade_cleanup(brigade); + tlen += len; + if (tlen == count_bytes || !len) { + break; + } + buf += len; + len = count_bytes - tlen; + } + + return tlen; +} + +static struct stat* +php_apache_sapi_get_stat(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + + ctx->finfo.st_uid = ctx->r->finfo.user; + ctx->finfo.st_gid = ctx->r->finfo.group; + ctx->finfo.st_dev = ctx->r->finfo.device; + ctx->finfo.st_ino = ctx->r->finfo.inode; +#if defined(NETWARE) && defined(CLIB_STAT_PATCH) + ctx->finfo.st_atime.tv_sec = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime.tv_sec = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime.tv_sec = apr_time_sec(ctx->r->finfo.ctime); +#else + ctx->finfo.st_atime = apr_time_sec(ctx->r->finfo.atime); + ctx->finfo.st_mtime = apr_time_sec(ctx->r->finfo.mtime); + ctx->finfo.st_ctime = apr_time_sec(ctx->r->finfo.ctime); +#endif + + ctx->finfo.st_size = ctx->r->finfo.size; + ctx->finfo.st_nlink = ctx->r->finfo.nlink; + + return &ctx->finfo; +} + +static char * +php_apache_sapi_read_cookies(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + const char *http_cookie; + + http_cookie = apr_table_get(ctx->r->headers_in, "cookie"); + + /* The SAPI interface should use 'const char *' */ + return (char *) http_cookie; +} + +static char * +php_apache_sapi_getenv(char *name, size_t name_len TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + const char *env_var; + + if (ctx == NULL) { + return NULL; + } + + env_var = apr_table_get(ctx->r->subprocess_env, name); + + return (char *) env_var; +} + +static void +php_apache_sapi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + php_struct *ctx = SG(server_context); + const apr_array_header_t *arr = apr_table_elts(ctx->r->subprocess_env); + char *key, *val; + int new_val_len; + + APR_ARRAY_FOREACH_OPEN(arr, key, val) + if (!val) { + val = ""; + } + if (sapi_module.input_filter(PARSE_SERVER, key, &val, strlen(val), &new_val_len TSRMLS_CC)) { + php_register_variable_safe(key, val, new_val_len, track_vars_array TSRMLS_CC); + } + APR_ARRAY_FOREACH_CLOSE() + + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &ctx->r->uri, strlen(ctx->r->uri), &new_val_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", ctx->r->uri, new_val_len, track_vars_array TSRMLS_CC); + } +} + +static void +php_apache_sapi_flush(void *server_context) +{ + php_struct *ctx; + request_rec *r; + TSRMLS_FETCH(); + + ctx = server_context; + + /* If we haven't registered a server_context yet, + * then don't bother flushing. */ + if (!server_context) { + return; + } + + r = ctx->r; + + sapi_send_headers(TSRMLS_C); + + r->status = SG(sapi_headers).http_response_code; + SG(headers_sent) = 1; + + if (ap_rflush(r) < 0 || r->connection->aborted) { + php_handle_aborted_connection(); + } +} + +static void php_apache_sapi_log_message(char *msg TSRMLS_DC) +{ + php_struct *ctx; + + ctx = SG(server_context); + + if (ctx == NULL) { /* we haven't initialized our ctx yet, oh well */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, "%s", msg); + } else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, "%s", msg); + } +} + +static void php_apache_sapi_log_message_ex(char *msg, request_rec *r TSRMLS_DC) +{ + if (r) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, msg, r->filename); + } else { + php_apache_sapi_log_message(msg TSRMLS_CC); + } +} + +static double php_apache_sapi_get_request_time(TSRMLS_D) +{ + php_struct *ctx = SG(server_context); + return ((double) apr_time_as_msec(ctx->r->request_time)) / 1000.0; +} + +extern zend_module_entry php_apache_module; + +static int php_apache2_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_apache_module, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +static sapi_module_struct apache2_sapi_module = { + "apache2handler", + "Apache 2.0 Handler", + + php_apache2_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_apache_sapi_ub_write, /* unbuffered write */ + php_apache_sapi_flush, /* flush */ + php_apache_sapi_get_stat, /* get uid */ + php_apache_sapi_getenv, /* getenv */ + + php_error, /* error handler */ + + php_apache_sapi_header_handler, /* header handler */ + php_apache_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_apache_sapi_read_post, /* read POST data */ + php_apache_sapi_read_cookies, /* read Cookies */ + + php_apache_sapi_register_variables, + php_apache_sapi_log_message, /* Log message */ + php_apache_sapi_get_request_time, /* Request Time */ + NULL, /* Child Terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static apr_status_t php_apache_server_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static apr_status_t php_apache_child_shutdown(void *tmp) +{ + apache2_sapi_module.shutdown(&apache2_sapi_module); +#if defined(ZTS) && !defined(PHP_WIN32) + tsrm_shutdown(); +#endif + return APR_SUCCESS; +} + +static void php_apache_add_version(apr_pool_t *p) +{ + TSRMLS_FETCH(); + if (PG(expose_php)) { + ap_add_version_component(p, "PHP/" PHP_VERSION); + } +} + +static int php_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) +{ +#ifndef ZTS + int threaded_mpm; + + ap_mpm_query(AP_MPMQ_IS_THREADED, &threaded_mpm); + if(threaded_mpm) { + ap_log_error(APLOG_MARK, APLOG_CRIT, 0, 0, "Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe. You need to recompile PHP."); + return DONE; + } +#endif + /* When this is NULL, apache won't override the hard-coded default + * php.ini path setting. */ + apache2_php_ini_path_override = NULL; + return OK; +} + +static int +php_apache_server_startup(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + void *data = NULL; + const char *userdata_key = "apache2hook_post_config"; + + /* Apache will load, unload and then reload a DSO module. This + * prevents us from starting PHP until the second load. */ + apr_pool_userdata_get(&data, userdata_key, s->process->pool); + if (data == NULL) { + /* We must use set() here and *not* setn(), otherwise the + * static string pointed to by userdata_key will be mapped + * to a different location when the DSO is reloaded and the + * pointers won't match, causing get() to return NULL when + * we expected it to return non-NULL. */ + apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool); + return OK; + } + + /* Set up our overridden path. */ + if (apache2_php_ini_path_override) { + apache2_sapi_module.php_ini_path_override = apache2_php_ini_path_override; + } +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache2_sapi_module); + apache2_sapi_module.startup(&apache2_sapi_module); + apr_pool_cleanup_register(pconf, NULL, php_apache_server_shutdown, apr_pool_cleanup_null); + php_apache_add_version(pconf); + + return OK; +} + +static apr_status_t php_server_context_cleanup(void *data_) +{ + void **data = data_; + *data = NULL; + return APR_SUCCESS; +} + +static int php_apache_request_ctor(request_rec *r, php_struct *ctx TSRMLS_DC) +{ + char *content_length; + const char *auth; + + SG(sapi_headers).http_response_code = !r->status ? HTTP_OK : r->status; + SG(request_info).content_type = apr_table_get(r->headers_in, "Content-Type"); + SG(request_info).query_string = apr_pstrdup(r->pool, r->args); + SG(request_info).request_method = r->method; + SG(request_info).proto_num = r->proto_num; + SG(request_info).request_uri = apr_pstrdup(r->pool, r->uri); + SG(request_info).path_translated = apr_pstrdup(r->pool, r->filename); + r->no_local_copy = 1; + + content_length = (char *) apr_table_get(r->headers_in, "Content-Length"); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + + apr_table_unset(r->headers_out, "Content-Length"); + apr_table_unset(r->headers_out, "Last-Modified"); + apr_table_unset(r->headers_out, "Expires"); + apr_table_unset(r->headers_out, "ETag"); + + auth = apr_table_get(r->headers_in, "Authorization"); + php_handle_auth_data(auth TSRMLS_CC); + + if (SG(request_info).auth_user == NULL && r->user) { + SG(request_info).auth_user = estrdup(r->user); + } + + ctx->r->user = apr_pstrdup(ctx->r->pool, SG(request_info).auth_user); + + return php_request_startup(TSRMLS_C); +} + +static void php_apache_request_dtor(request_rec *r TSRMLS_DC) +{ + php_request_shutdown(NULL); +} + +static void php_apache_ini_dtor(request_rec *r, request_rec *p TSRMLS_DC) +{ + if (strcmp(r->protocol, "INCLUDED")) { + zend_try { zend_ini_deactivate(TSRMLS_C); } zend_end_try(); + } else { +typedef struct { + HashTable config; +} php_conf_rec; + char *str; + uint str_len; + php_conf_rec *c = ap_get_module_config(r->per_dir_config, &php5_module); + + for (zend_hash_internal_pointer_reset(&c->config); + zend_hash_get_current_key_ex(&c->config, &str, &str_len, NULL, 0, NULL) == HASH_KEY_IS_STRING; + zend_hash_move_forward(&c->config) + ) { + zend_restore_ini_entry(str, str_len, ZEND_INI_STAGE_SHUTDOWN); + } + } + if (p) { + ((php_struct *)SG(server_context))->r = p; + } else { + apr_pool_cleanup_run(r->pool, (void *)&SG(server_context), php_server_context_cleanup); + } +} + +static int php_handler(request_rec *r) +{ + php_struct * volatile ctx; + void *conf; + apr_bucket_brigade * volatile brigade; + apr_bucket *bucket; + apr_status_t rv; + request_rec * volatile parent_req = NULL; + TSRMLS_FETCH(); + +#define PHPAP_INI_OFF php_apache_ini_dtor(r, parent_req TSRMLS_CC); + + conf = ap_get_module_config(r->per_dir_config, &php5_module); + + /* apply_config() needs r in some cases, so allocate server_context early */ + ctx = SG(server_context); + if (ctx == NULL || (ctx && ctx->request_processed && !strcmp(r->protocol, "INCLUDED"))) { +normal: + ctx = SG(server_context) = apr_pcalloc(r->pool, sizeof(*ctx)); + /* register a cleanup so we clear out the SG(server_context) + * after each request. Note: We pass in the pointer to the + * server_context in case this is handled by a different thread. + */ + apr_pool_cleanup_register(r->pool, (void *)&SG(server_context), php_server_context_cleanup, apr_pool_cleanup_null); + ctx->r = r; + ctx = NULL; /* May look weird to null it here, but it is to catch the right case in the first_try later on */ + } else { + parent_req = ctx->r; + ctx->r = r; + } + apply_config(conf); + + if (strcmp(r->handler, PHP_MAGIC_TYPE) && strcmp(r->handler, PHP_SOURCE_MAGIC_TYPE) && strcmp(r->handler, PHP_SCRIPT)) { + /* Check for xbithack in this case. */ + if (!AP2(xbithack) || strcmp(r->handler, "text/html") || !(r->finfo.protection & APR_UEXECUTE)) { + PHPAP_INI_OFF; + return DECLINED; + } + } + + /* Give a 404 if PATH_INFO is used but is explicitly disabled in + * the configuration; default behaviour is to accept. */ + if (r->used_path_info == AP_REQ_REJECT_PATH_INFO + && r->path_info && r->path_info[0]) { + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + + /* handle situations where user turns the engine off */ + if (!AP2(engine)) { + PHPAP_INI_OFF; + return DECLINED; + } + + if (r->finfo.filetype == 0) { + php_apache_sapi_log_message_ex("script '%s' not found or unable to stat", r TSRMLS_CC); + PHPAP_INI_OFF; + return HTTP_NOT_FOUND; + } + if (r->finfo.filetype == APR_DIR) { + php_apache_sapi_log_message_ex("attempt to invoke directory '%s' as script", r TSRMLS_CC); + PHPAP_INI_OFF; + return HTTP_FORBIDDEN; + } + + /* Setup the CGI variables if this is the main request */ + if (r->main == NULL || + /* .. or if the sub-request environment differs from the main-request. */ + r->subprocess_env != r->main->subprocess_env + ) { + /* setup standard CGI variables */ + ap_add_common_vars(r); + ap_add_cgi_vars(r); + } + +zend_first_try { + + if (ctx == NULL) { + brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc); + ctx = SG(server_context); + ctx->brigade = brigade; + + if (php_apache_request_ctor(r, ctx TSRMLS_CC)!=SUCCESS) { + zend_bailout(); + } + } else { + if (!parent_req) { + parent_req = ctx->r; + } + if (parent_req && parent_req->handler && + strcmp(parent_req->handler, PHP_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SOURCE_MAGIC_TYPE) && + strcmp(parent_req->handler, PHP_SCRIPT)) { + if (php_apache_request_ctor(r, ctx TSRMLS_CC)!=SUCCESS) { + zend_bailout(); + } + } + + /* + * check if comming due to ErrorDocument + * We make a special exception of 413 (Invalid POST request) as the invalidity of the request occurs + * during processing of the request by PHP during POST processing. Therefor we need to re-use the exiting + * PHP instance to handle the request rather then creating a new one. + */ + if (parent_req && parent_req->status != HTTP_OK && parent_req->status != 413 && strcmp(r->protocol, "INCLUDED")) { + parent_req = NULL; + goto normal; + } + ctx->r = r; + brigade = ctx->brigade; + } + + if (AP2(last_modified)) { + ap_update_mtime(r, r->finfo.mtime); + ap_set_last_modified(r); + } + + /* Determine if we need to parse the file or show the source */ + if (strncmp(r->handler, PHP_SOURCE_MAGIC_TYPE, sizeof(PHP_SOURCE_MAGIC_TYPE) - 1) == 0) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file((char *)r->filename, &syntax_highlighter_ini TSRMLS_CC); + } else { + zend_file_handle zfd; + + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = (char *) r->filename; + zfd.free_filename = 0; + zfd.opened_path = NULL; + + if (!parent_req) { + php_execute_script(&zfd TSRMLS_CC); + } else { + zend_execute_scripts(ZEND_INCLUDE TSRMLS_CC, NULL, 1, &zfd); + } + + apr_table_set(r->notes, "mod_php_memory_usage", + apr_psprintf(ctx->r->pool, "%zu", zend_memory_peak_usage(1 TSRMLS_CC))); + } + +} zend_end_try(); + + if (!parent_req) { + php_apache_request_dtor(r TSRMLS_CC); + ctx->request_processed = 1; + bucket = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(brigade, bucket); + + rv = ap_pass_brigade(r->output_filters, brigade); + if (rv != APR_SUCCESS || r->connection->aborted) { +zend_first_try { + php_handle_aborted_connection(); +} zend_end_try(); + } + apr_brigade_cleanup(brigade); + } else { + ctx->r = parent_req; + } + + return OK; +} + +static void php_apache_child_init(apr_pool_t *pchild, server_rec *s) +{ + apr_pool_cleanup_register(pchild, NULL, php_apache_child_shutdown, apr_pool_cleanup_null); +} + +void php_ap2_register_hook(apr_pool_t *p) +{ + ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(php_apache_server_startup, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache_hooks/CREDITS b/sapi/apache_hooks/CREDITS new file mode 100644 index 0000000..86ac27d --- /dev/null +++ b/sapi/apache_hooks/CREDITS @@ -0,0 +1,2 @@ +Apache 1.3 (apache_hooks) +Rasmus Lerdorf, Zeev Suraski, Stig Bakken, David Sklar, George Schlossnagle, Lukas Schroeder diff --git a/sapi/apache_hooks/README b/sapi/apache_hooks/README new file mode 100644 index 0000000..9a5a3e2 --- /dev/null +++ b/sapi/apache_hooks/README @@ -0,0 +1,206 @@ +This is very beta documentation. Clearly better stuff can and will follow. + +INTRO: + +apache_hooks is a full super-set enhancement of the apache 1.3 sapi that allows for +php code to be run on the apache request object at every stage of the apache +request. It supports all of the apache 1.3 sapi commands and configurations, and +additionally supports the following httpd.conf directives: + + +HTTPD.CONF DIRECTIEVS: + +phpRequire /path/to/file = requires a file at the beginning of an +initial apache request + +phpUriHandler /path/to/file = registers a hook that will run the +specified file at the uri translation stage of the apache request +phpUriHandler Class::Method = registers a hook to run Class::Method at +the uri translation stage of the apache request + +phpPostReadHandler /path/to/file = hook for post-read phase +phpPostReadHandlerMethod Class::Method + +phpHeaderHandler = hook for header parsing phase +phpHeaderHandlerMethod + +phpAuthHandler = hook for authentication phase +phpAuthHandlerMethod + +phpAccessHandler = hook for access control phase +phpAccessHandlerMethod + +phpTypeHandler = hook for Type Checking phase +phpTypeHandlerMethod + +phpFixupHandler = hook for 'fixup' phase +phpFixupHandlerMethod + +phpLoggerHandler = hook for logging phase +phpLoggerHandlerMethod + +AddHandler php-script = set's up a special type handler +phpResponseHandler /path/to/file = sets file to be called to handle +response phase +phpResponseHandlerMethod Class::Method + + +All handlers may be stacked, i.e. you can list multiple handler directives +in a single scope and they will be run in order. + + +EXAMPLES: + +So, to set up a 'hello world' location handler (so that any request to +/hello/* returns hello world) you can: + +phpRequire /tmp/setup.php +<Location /hello> +AddHandler php-script +phpResponseHandlerMethod Hello::World +</Location> + +with +#/tmp/setup.php +<? +class Hello { + function World() { + global $request; + $request->send_http_header(); + echo "Hello World"; + } +} +?> + +$request is the apache request. It is instantiated at all stages +automatically. The methods of that class are: + +getallheaders +args +boundary +content_encoding +content_type +filename +handler +hostname +method +path_info +protocol +status_line +the_request +unparsed_uri +uri +allowed +bytes_sent +chunked +content_length +header_only +method_number +mtime +no_cache +no_local_copy +proto_num +proxyreq +read_body +remaining +request_time +status +headers_in +headers_out +err_headers_out +auth_name +auth_type +basic_auth_pw +discard_request_body +is_initial_req +meets_conditions +remote_host +satisfies +server_port +set_etag +set_last_modified +some_auth_required +update_mtime +send_http_header +basic_http_header +send_header_field +send_http_trace +send_http_options +send_error_response +set_content_length +set_keepalive +rputs +log_error +lookup_uri +lookup_file +method_uri +run +internal_redirect + + +These all wrap the ap_* apache EXPORT_API functions using the same +semantics (and are also the same as the Apache::Request methods in +mod_perl if you are familiar with that) + +So, a uri handler to redirect all non-local traffic to /404.php (an +error page) would be + +phpUriHandler /tmp/uri.php + +#/tmp/uri.php +<? + if($REMOTE_ADDR != '127.0.0.1') { + $request->uri('/404.php'); + } + return OK; +?> + +It's important to note that since this is called from the uri +translations phase, this validation is performed for every request to +the server, not just for php pages. + +Also, scope is shared between all the hooks. So in the above, we could +merge the two and do something like: + +#/tmp/uri.php +<? + if($REMOTE_ADDR != '127.0.0.1') { + $whoami = 'Stranger'; + } + else { + $whoami = 'Friend'; + } + return DECLINED; # because we're not redirecting, just messing around +?> + +and then: + +#/tmp/setup.php +<? +class Hello { + function World() { + global $request; + global $whoami; + $request->send_http_header(); + echo "Hello $whoami"; + } +} +?> + +These variables are also in the same scope as a script if your script is +being handled by the standard application/x-httpd-php handler. + +This allows you to make decisions and pass data between your handlers +and scripts at all stages. + +The above are clearly trite examples, but hopefully give you a starting +point. + +One note: all handlers can be validly re-entered 'in sub-requests'. +For this reason you should not define functions/classes here without +anti-redefinition guards (I would just recommend putting them in an +include and using include_one). This is not true for phpRequire, which +is only entered once, at the main request, and so it is safe to make +function/class declarations there (in fact that's what it's for). + +Hope that helps! diff --git a/sapi/apache_hooks/apMakefile.libdir b/sapi/apache_hooks/apMakefile.libdir new file mode 100644 index 0000000..7b52540 --- /dev/null +++ b/sapi/apache_hooks/apMakefile.libdir @@ -0,0 +1,4 @@ +This is a place-holder which indicates to Configure that it shouldn't +provide the default targets when building the Makefile in this directory. +Instead it'll just prepend all the important variable definitions, and +copy the Makefile.tmpl onto the end. diff --git a/sapi/apache_hooks/apMakefile.tmpl b/sapi/apache_hooks/apMakefile.tmpl new file mode 100644 index 0000000..4054e8e --- /dev/null +++ b/sapi/apache_hooks/apMakefile.tmpl @@ -0,0 +1,77 @@ +## +## Apache 1.3 Makefile template for PHP 4.0 Module +## [src/modules/php5/Makefile.tmpl] +## + +# the parametrized target +LIB=libphp5.$(LIBEXT) + +# objects for building the static library +OBJS=mod_php5.o +OBJS_LIB=libmodphp5.a + +# objects for building the shared object library +SHLIB_OBJS=mod_php5.so-o +SHLIB_OBJS_LIB=libmodphp5.a + +# the general targets +all: lib +lib: $(LIB) + +# build the static library by merging the object files +libphp5.a: $(OBJS) $(OBJS_LIB) + cp $(OBJS_LIB) $@ + ar r $@ $(OBJS) + $(RANLIB) $@ + +# ugly hack to support older Apache-1.3 betas that don't set $LIBEXT +libphp5.: $(OBJS) $(OBJS_LIB) + cp $(OBJS_LIB) $@ + ar r $@ $(OBJS) + $(RANLIB) $@ + cp libphp5. libphp5.a + +# build the shared object library by linking the object files +libphp5.so: $(SHLIB_OBJS) $(SHLIB_OBJS_LIB) + rm -f $@ + $(LD_SHLIB) $(LDFLAGS_SHLIB) -o $@ $(SHLIB_OBJS) $(SHLIB_OBJS_LIB) $(LIBS) $(PHP_LIBS) + +# 1. extension .o for shared objects cannot be used here because +# first these files aren't still shared objects and second we +# have to use a different name to trigger the different +# implicit Make rule +# 2. extension -so.o (as used elsewhere) cannot be used because +# the suffix feature of Make really wants just .x, so we use +# extension .so-o +.SUFFIXES: .o .so-o +.c.o: + $(CC) -c $(INCLUDES) $(CFLAGS) $(PHP_CFLAGS) $(CPPFLAGS) $(SPACER) $< +.c.so-o: + $(CC) -c $(INCLUDES) $(CFLAGS) $(CFLAGS_SHLIB) $(PHP_CFLAGS) $(CPPFLAGS) $(SPACER) $< && mv $*.o $*.so-o + +# cleanup +clean: + -rm -f $(OBJS) $(SHLIB_OBJS) $(LIB) + +# We really don't expect end users to use this rule. It works only with +# gcc, and rebuilds Makefile.tmpl. You have to re-run Configure after +# using it. +depend: + cp Makefile.tmpl Makefile.tmpl.bak \ + && sed -ne '1,/^# DO NOT REMOVE/p' Makefile.tmpl > Makefile.new \ + && gcc -MM $(INCLUDES) $(CFLAGS) $(PHP_CFLAGS) $(CPPFLAGS) *.c >> Makefile.new \ + && sed -e '1,$$s: $(INCDIR)/: $$(INCDIR)/:g' Makefile.new \ + > Makefile.tmpl \ + && rm Makefile.new + +#Dependencies + +$(OBJS): Makefile + +# DO NOT REMOVE +mod_php5.o: mod_php5.c $(INCDIR)/httpd.h $(INCDIR)/conf.h \ + $(INCDIR)/buff.h \ + $(INCDIR)/http_config.h \ + $(INCDIR)/http_core.h $(INCDIR)/http_main.h \ + $(INCDIR)/http_protocol.h $(INCDIR)/http_request.h \ + $(INCDIR)/http_log.h $(INCDIR)/util_script.h mod_php5.h diff --git a/sapi/apache_hooks/config.m4 b/sapi/apache_hooks/config.m4 new file mode 100644 index 0000000..4213b7c --- /dev/null +++ b/sapi/apache_hooks/config.m4 @@ -0,0 +1,275 @@ +dnl +dnl $Id$ +dnl +AC_DEFUN([PHP_APACHE_FD_CHECK], [ +AC_CACHE_CHECK([for member fd in BUFF *],ac_cv_php_fd_in_buff,[ + save=$CPPFLAGS + if test -n "$APXS_INCLUDEDIR"; then + CPPFLAGS="$CPPFLAGS -I$APXS_INCLUDEDIR" + else + CPPFLAGS="$CPPFLAGS $APACHE_INCLUDE" + fi + AC_TRY_COMPILE([#include <httpd.h>],[conn_rec *c; int fd = c->client->fd;],[ + ac_cv_php_fd_in_buff=yes],[ac_cv_php_fd_in_buff=no],[ac_cv_php_fd_in_buff=no]) + CPPFLAGS=$save +]) +if test "$ac_cv_php_fd_in_buff" = "yes"; then + AC_DEFINE(PHP_APACHE_HAVE_CLIENT_FD,1,[ ]) +fi +]) + +dnl Apache 1.x shared module +PHP_ARG_WITH(apache-hooks,, +[ --with-apache-hooks[=FILE] + EXPERIMENTAL: Build shared Apache 1.x module. FILE is the optional + pathname to the Apache apxs tool [apxs]], no, no) + +AC_MSG_CHECKING([for Apache 1.x (hooks) module support via DSO through APXS]) + +if test "$PHP_APACHE_HOOKS" != "no"; then + if test "$PHP_APACHE_HOOKS" = "yes"; then + APXS=apxs + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0" && test -x /usr/sbin/apxs; then #SUSE 6.x + APXS=/usr/sbin/apxs + fi + else + PHP_EXPAND_PATH($PHP_APACHE_HOOKS, APXS) + fi + + $APXS -q CFLAGS >/dev/null 2>&1 + if test "$?" != "0"; then + AC_MSG_RESULT() + AC_MSG_RESULT() + AC_MSG_RESULT([Sorry, I was not able to successfully run APXS. Possible reasons:]) + AC_MSG_RESULT() + AC_MSG_RESULT([1. Perl is not installed;]) + AC_MSG_RESULT([2. Apache was not compiled with DSO support (--enable-module=so);]) + AC_MSG_RESULT([3. 'apxs' is not in your path. Try to use --with-apxs=/path/to/apxs]) + AC_MSG_RESULT([The output of $APXS follows]) + $APXS -q CFLAGS + AC_MSG_ERROR([Aborting]) + fi + + APXS_LDFLAGS="@SYBASE_LFLAGS@ @SYBASE_LIBS@ @SYBASE_CT_LFLAGS@ @SYBASE_CT_LIBS@" + APXS_INCLUDEDIR=`$APXS -q INCLUDEDIR` + APXS_CFLAGS=`$APXS -q CFLAGS` + APXS_HTTPD=`$APXS -q SBINDIR`/`$APXS -q TARGET` + APACHE_INCLUDE=-I$APXS_INCLUDEDIR + + # Test that we're trying to configure with apache 1.x + PHP_AP_EXTRACT_VERSION($APXS_HTTPD) + if test "$APACHE_VERSION" -ge 2000000; then + AC_MSG_ERROR([You have enabled Apache 1.3 support while your server is Apache 2. Please use the appropiate switch --with-apxs2]) + fi + + for flag in $APXS_CFLAGS; do + case $flag in + -D*) APACHE_CPPFLAGS="$APACHE_CPPFLAGS $flag";; + esac + done + + case $host_alias in + *aix*) + APXS_LIBEXECDIR=`$APXS -q LIBEXECDIR` + EXTRA_LDFLAGS="$EXTRA_LDFLAGS -Wl,-brtl -Wl,-bI:$APXS_LIBEXECDIR/httpd.exp" + PHP_AIX_LDFLAGS="-Wl,-brtl" + build_type=shared + ;; + *darwin*) + MH_BUNDLE_FLAGS="-dynamic -twolevel_namespace -bundle -bundle_loader $APXS_HTTPD" + PHP_SUBST(MH_BUNDLE_FLAGS) + SAPI_SHARED=libs/libphp5.so + build_type=bundle + ;; + *) + build_type=shared + ;; + esac + + PHP_SELECT_SAPI(apache_hooks, $build_type, sapi_apache.c mod_php5.c php_apache.c, $APACHE_CPPFLAGS -I$APXS_INCLUDEDIR) + + # Test whether apxs support -S option + $APXS -q -S CFLAGS="$APXS_CFLAGS" CFLAGS >/dev/null 2>&1 + + if test "$?" != "0"; then + APACHE_HOOKS_INSTALL="$APXS -i -a -n php5 $SAPI_SHARED" # Old apxs does not have -S option + else + APXS_LIBEXECDIR='$(INSTALL_ROOT)'`$APXS -q LIBEXECDIR` + if test -z `$APXS -q SYSCONFDIR`; then + APACHE_HOOKS_INSTALL="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -i -n php5 $SAPI_SHARED" + else + APXS_SYSCONFDIR='$(INSTALL_ROOT)'`$APXS -q SYSCONFDIR` + APACHE_HOOKS_INSTALL="\$(mkinstalldirs) '$APXS_LIBEXECDIR' && \ + \$(mkinstalldirs) '$APXS_SYSCONFDIR' && \ + $APXS -S LIBEXECDIR='$APXS_LIBEXECDIR' \ + -S SYSCONFDIR='$APXS_SYSCONFDIR' \ + -i -a -n php5 $SAPI_SHARED" + fi + fi + + if test -z "`$APXS -q LD_SHLIB`" || test "`$APXS -q LIBEXECDIR`" = "modules"; then + PHP_APXS_BROKEN=yes + fi + STRONGHOLD= + AC_DEFINE(HAVE_AP_CONFIG_H,1,[ ]) + AC_DEFINE(HAVE_AP_COMPAT_H,1,[ ]) + AC_DEFINE(HAVE_APACHE_HOOKS,1,[ ]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +dnl Apache 1.x static module +PHP_ARG_WITH(apache-hooks-static,, +[ --with-apache-hooks-static[=DIR] + EXPERIMENTAL: Build Apache 1.x module. DIR is the top-level Apache + build directory [/usr/local/apache]], no, no) + +AC_MSG_CHECKING(for Apache 1.x (hooks) module support) + +if test "$PHP_SAPI" != "apache" && test "$PHP_SAPI" != "apache_hooks" && test "$PHP_APACHE_HOOKS_STATIC" != "no"; then + + if test "$PHP_APACHE_HOOKS_STATIC" = "yes"; then + # Apache's default directory + PHP_APACHE_HOOKS_STATIC=/usr/local/apache + fi + + APACHE_HOOKS_INSTALL_FILES="\$(srcdir)/sapi/apache_hooks/mod_php5.* sapi/apache_hooks/libphp5.module" + + AC_DEFINE(HAVE_APACHE,1,[ ]) + APACHE_HOOKS_MODULE=yes + PHP_EXPAND_PATH($PHP_APACHE_HOOKS_STATIC, PHP_APACHE_HOOKS_STATIC) + # For Apache 1.2.x + if test -f $PHP_APACHE_HOOKS_STATIC/src/httpd.h; then + APACHE_INCLUDE=-I$PHP_APACHE_HOOKS_STATIC/src + APACHE_TARGET=$PHP_APACHE_HOOKS_STATIC/src + PHP_SELECT_SAPI(apache_hooks, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + APACHE_HOOKS_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_HOOKS_INSTALL_FILES $APACHE_TARGET" + PHP_LIBS="-L. -lphp3" + AC_MSG_RESULT([yes - Apache 1.2.x]) + STRONGHOLD= + if test -f $PHP_APACHE_HOOKS_STATIC/src/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H,1,[ ]) + fi + # For Apache 2.0.x + elif test -f $PHP_APACHE_HOOKS_STATIC/include/httpd.h && test -f $PHP_APACHE_HOOKS_STATIC/srclib/apr/include/apr_general.h ; then + AC_MSG_ERROR([Use --with-apxs2 with Apache 2.x!]) + # For Apache 1.3.x + elif test -f $PHP_APACHE_HOOKS_STATIC/src/main/httpd.h; then + APACHE_HAS_REGEX=1 + APACHE_INCLUDE="-I$PHP_APACHE_HOOKS_STATIC/src/main -I$PHP_APACHE_HOOKS_STATIC/src/os/unix -I$PHP_APACHE_HOOKS_STATIC/src/ap" + APACHE_TARGET=$PHP_APACHE_HOOKS_STATIC/src/modules/php5 + if test ! -d $APACHE_TARGET; then + mkdir $APACHE_TARGET + fi + PHP_SELECT_SAPI(apache_hooks, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + APACHE_HOOKS_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_HOOKS_INSTALL_FILES $APACHE_TARGET; cp $srcdir/sapi/apache_hooks/apMakefile.tmpl $APACHE_TARGET/Makefile.tmpl; cp $srcdir/sapi/apache_hooks/apMakefile.libdir $APACHE_TARGET/Makefile.libdir" + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + AC_MSG_RESULT([yes - Apache 1.3.x]) + STRONGHOLD= + if test -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE_HOOKS_STATIC/src/include/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + # Also for Apache 1.3.x + elif test -f $PHP_APACHE_HOOKS_STATIC/src/include/httpd.h; then + APACHE_HAS_REGEX=1 + APACHE_INCLUDE="-I$PHP_APACHE_HOOKS_STATIC/src/include -I$PHP_APACHE_HOOKS_STATIC/src/os/unix" + APACHE_TARGET=$PHP_APACHE_HOOKS_STATIC/src/modules/php5 + if test ! -d $APACHE_TARGET; then + mkdir $APACHE_TARGET + fi + PHP_SELECT_SAPI(apache_hooks, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + APACHE_HOOKS_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_HOOKS_INSTALL_FILES $APACHE_TARGET; cp $srcdir/sapi/apache_hooks/apMakefile.tmpl $APACHE_TARGET/Makefile.tmpl; cp $srcdir/sapi/apache_hooks/apMakefile.libdir $APACHE_TARGET/Makefile.libdir" + AC_MSG_RESULT([yes - Apache 1.3.x]) + STRONGHOLD= + if test -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE_HOOKS_STATIC/src/include/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + # For StrongHold 2.2 + elif test -f $PHP_APACHE_HOOKS_STATIC/apache/httpd.h; then + APACHE_INCLUDE="-I$PHP_APACHE_HOOKS_STATIC/apache -I$PHP_APACHE_HOOKS_STATIC/ssl/include" + APACHE_TARGET=$PHP_APACHE_HOOKS_STATIC/apache + PHP_SELECT_SAPI(apache_hooks, static, sapi_apache.c mod_php5.c php_apache.c, $APACHE_INCLUDE) + PHP_LIBS="-Lmodules/php5 -L../modules/php5 -L../../modules/php5 -lmodphp5" + APACHE_HOOKS_INSTALL="mkdir -p $APACHE_TARGET; cp $SAPI_STATIC $APACHE_TARGET/libmodphp5.a; cp $APACHE_HOOKS_INSTALL_FILES $APACHE_TARGET" + STRONGHOLD=-DSTRONGHOLD=1 + AC_MSG_RESULT([yes - StrongHold]) + if test -f $PHP_APACHE_HOOKS_STATIC/apache/ap_config.h; then + AC_DEFINE(HAVE_AP_CONFIG_H, 1, [ ]) + fi + if test -f $PHP_APACHE_HOOKS_STATIC/src/ap_compat.h; then + AC_DEFINE(HAVE_AP_COMPAT_H, 1, [ ]) + if test ! -f $PHP_APACHE_HOOKS_STATIC/src/include/ap_config_auto.h; then + AC_MSG_ERROR([Please run Apache\'s configure or src/Configure program once and try again]) + fi + elif test -f $PHP_APACHE_HOOKS_STATIC/src/compat.h; then + AC_DEFINE(HAVE_OLD_COMPAT_H, 1, [ ]) + fi + else + AC_MSG_RESULT(no) + AC_MSG_ERROR([Invalid Apache directory - unable to find httpd.h under $PHP_APACHE_HOOKS_STATIC]) + fi +else + AC_MSG_RESULT(no) +fi + +# compatibility +if test -z "$enable_mod_charset" && test "$with_mod_charset"; then + enable_mod_charset=$with_mod_charset +fi + +PHP_ARG_ENABLE(mod-charset, whether to enable Apache charset compatibility option, +[ --enable-mod-charset APACHE (hooks): Enable transfer tables for mod_charset (Rus Apache)], no, no) + +if test "$PHP_MOD_CHARSET" = "yes"; then + AC_DEFINE(USE_TRANSFER_TABLES, 1, [ ]) +fi + +dnl Build as static module +if test "$APACHE_HOOKS_MODULE" = "yes"; then + PHP_TARGET_RDYNAMIC + $php_shtool mkdir -p sapi/apache_hooks + PHP_OUTPUT(sapi/apache_hooks/libphp5.module) +fi + +dnl General +if test -n "$APACHE_HOOKS_INSTALL"; then + if test "x$APXS" != "x" -a "`uname -sv`" = "AIX 4" -a "$GCC" != "yes"; then + APXS_EXP=-bE:sapi/apache_hooks/mod_php5.exp + fi + + PHP_APACHE_FD_CHECK + INSTALL_IT=$APACHE_HOOKS_INSTALL + + PHP_SUBST(APXS_EXP) + PHP_SUBST(APACHE_INCLUDE) + PHP_SUBST(APACHE_TARGET) + PHP_SUBST(APXS) + PHP_SUBST(APXS_LDFLAGS) + PHP_SUBST(APACHE_HOOKS_INSTALL) + PHP_SUBST(STRONGHOLD) +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/apache_hooks/config.w32 b/sapi/apache_hooks/config.w32 new file mode 100644 index 0000000..85dc624 --- /dev/null +++ b/sapi/apache_hooks/config.w32 @@ -0,0 +1,21 @@ +// vim:ft=javascript +// $Id$ + +ARG_WITH('apache-hooks', 'Build Apache 1.3.x (hooks) version of PHP', 'no'); + +if (PHP_APACHE_HOOKS != "no") { + if (CHECK_HEADER_ADD_INCLUDE("httpd.h", "CFLAGS_APACHE_HOOKS", php_usual_include_suspects + + ";" + PROGRAM_FILES + "\\Apache Group\\Apache\\include" + + ";" + PHP_PHP_BUILD + "\\apache\\src\\include") && + CHECK_LIB("ApacheCore.lib", "apache_hooks", php_usual_lib_suspects + + ';' + PROGRAM_FILES + '\\Apache Group\\Apache\\libexec' + + ";" + PHP_PHP_BUILD + "\\apache\\src\\corer")) { + // We need to play tricks to get our readdir.h used by apache + // headers + SAPI('apache_hooks', 'mod_php5.c sapi_apache.c php_apache.c', + 'php' + PHP_VERSION + 'apache_hooks.dll', + '/D APACHEPHP5_EXPORTS /D APACHE_READDIR_H /I win32'); + } else { + WARNING("Could not find apache libraries/headers"); + } +} diff --git a/sapi/apache_hooks/libphp5.module.in b/sapi/apache_hooks/libphp5.module.in new file mode 100644 index 0000000..8488181 --- /dev/null +++ b/sapi/apache_hooks/libphp5.module.in @@ -0,0 +1,11 @@ +Name: php5_module +ConfigStart + RULE_WANTHSREGEX=no + RULE_HIDE=yes + PHP_LIBS="@NATIVE_RPATHS@ @PHP_LDFLAGS@ @PHP_LIBS@ @EXTRA_LIBS@ $LIBS" + PHP_CFLAGS="$CFLAGS @OPENSSL_INCDIR_OPT@ -I@php_abs_top_builddir@/main -I@php_abs_top_builddir@/Zend -I@php_abs_top_builddir@/TSRM -I@php_abs_top_srcdir@ -I@php_abs_top_srcdir@/sapi/apache -I@php_abs_top_srcdir@/main -I@php_abs_top_srcdir@/Zend -I@php_abs_top_srcdir@/TSRM" + my_outfile="Makefile.config" + echo "PHP_CFLAGS=$PHP_CFLAGS" >>$my_outfile + echo "PHP_LIBS=$PHP_LIBS" >>$my_outfile + LIBS=$PHP_LIBS +ConfigEnd diff --git a/sapi/apache_hooks/mod_php5.c b/sapi/apache_hooks/mod_php5.c new file mode 100644 index 0000000..dda6e49 --- /dev/null +++ b/sapi/apache_hooks/mod_php5.c @@ -0,0 +1,1493 @@ +/* + +----------------------------------------------------------------------+ + | 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 at through the world-wide-web at | + | 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: Rasmus Lerdorf <rasmus@php.net> | + | (with helpful hints from Dean Gaudet <dgaudet@arctic.org> | + | PHP 4.0 patches by Zeev Suraski <zeev@zend.com> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" + +#ifdef NETWARE +#define SIGPIPE SIGINT +#endif + +#undef shutdown + +/* {{{ Prototypes + */ +int apache_php_module_main(request_rec *r, int display_source_mode TSRMLS_DC); +static void php_save_umask(void); +static void php_restore_umask(void); +static int sapi_apache_read_post(char *buffer, uint count_bytes TSRMLS_DC); +static char *sapi_apache_read_cookies(TSRMLS_D); +static int sapi_apache_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC); +static int sapi_apache_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC); +static int send_php(request_rec *r, int display_source_mode, char *filename); +static int send_parsed_php(request_rec * r); +static int send_parsed_php_source(request_rec * r); +static int php_xbithack_handler(request_rec * r); +static void php_init_handler(server_rec *s, pool *p); +/* }}} */ + +#if MODULE_MAGIC_NUMBER >= 19970728 +static void php_child_exit_handler(server_rec *s, pool *p); +#endif + +#if MODULE_MAGIC_NUMBER > 19961007 +#define CONST_PREFIX const +#else +#define CONST_PREFIX +#endif + + +typedef struct _sapi_stack { + int top, max, persistent; + void **elements; +} sapi_stack; + +typedef struct _php_per_dir_config { + HashTable *ini_settings; + sapi_stack headers_handlers; + sapi_stack auth_handlers; + sapi_stack access_handlers; + sapi_stack type_handlers; + sapi_stack fixup_handlers; + sapi_stack logger_handlers; + sapi_stack post_read_handlers; + sapi_stack response_handlers; +} php_per_dir_config; + +typedef struct _php_per_server_config { + sapi_stack uri_handlers; + sapi_stack requires; +} php_per_server_config; + + +static CONST_PREFIX char *php_apache_value_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode); +static CONST_PREFIX char *php_apache_value_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_admin_value_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_flag_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2); +static CONST_PREFIX char *php_apache_flag_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode); +static CONST_PREFIX char *php_apache_admin_flag_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2); + +/* ### these should be defined in mod_php5.h or somewhere else */ +#define USE_PATH 1 +#define IGNORE_URL 2 + +module MODULE_VAR_EXPORT php5_module; + +int saved_umask; +/* static int setup_env = 0; */ +static unsigned char apache_php_initialized; + +typedef struct _php_per_dir_entry { + char *key; + char *value; + uint key_length; + uint value_length; + int type; +} php_per_dir_entry; + +/* some systems are missing these from their header files */ + +/* {{{ zend stack utility functions + */ + +/* This code is ripped part and parcel from zend_stack.[ch]. Assuming that the + patch supporting zend_stack_init_ex is applied, all but the bottom two + module-specific iterators will be removed + */ + +int sapi_stack_init_ex(sapi_stack *stack, int persistent) +{ + stack->top = 0; + stack->persistent = persistent; + stack->elements = (void **) pemalloc(sizeof(void **) * STACK_BLOCK_SIZE, persistent); + if (!stack->elements) { + return FAILURE; + } else { + stack->max = STACK_BLOCK_SIZE; + return SUCCESS; + } +} +int sapi_stack_push(sapi_stack *stack, void *element) +{ + if (stack->top >= stack->max) { /* we need to allocate more memory */ + stack->elements = (void **) perealloc(stack->elements, + (sizeof(void **) * (stack->max += STACK_BLOCK_SIZE)), stack->persistent); + if (!stack->elements) { + return FAILURE; + } + } + stack->elements[stack->top] = (void *) element; + return stack->top++; +} +void* sapi_stack_pop(sapi_stack *stack) { + if(stack->top == 0) { + return NULL; + } + else { + return stack->elements[--stack->top]; + } +} + +int sapi_stack_destroy(sapi_stack *stack) +{ + return SUCCESS; +} + +int sapi_stack_apply_with_argument_all(sapi_stack *stack, int type, int (*apply_function)(void *element, void *arg), void *arg) +{ + int i, retval; + + switch (type) { + case ZEND_STACK_APPLY_TOPDOWN: + for (i=stack->top-1; i>=0; i--) { + retval = apply_function(stack->elements[i], arg); + } + break; + case ZEND_STACK_APPLY_BOTTOMUP: + for (i=0; i<stack->top; i++) { + retval = apply_function(stack->elements[i], arg); + } + break; + } + return retval; +} + + +int sapi_stack_apply_with_argument_stop_if_equals(sapi_stack *stack, int type, int (*apply_function)(void *element, void *arg), void *arg, int stopval) +{ + int i; + int ret = DECLINED; + switch (type) { + case ZEND_STACK_APPLY_TOPDOWN: + for (i=stack->top-1; i>=0; i--) { + if ((ret = apply_function(stack->elements[i], arg)) == stopval) { + break; + } + } + break; + case ZEND_STACK_APPLY_BOTTOMUP: + for (i=0; i<stack->top; i++) { + if ((ret = apply_function(stack->elements[i], arg)) == stopval) { + break; + } + } + break; + } + return ret; +} + +int sapi_stack_apply_with_argument_stop_if_http_error(sapi_stack *stack, int type, int (*apply_function)(void *element, void *arg), void *arg) +{ + int i; + int ret = DECLINED; + switch (type) { + case ZEND_STACK_APPLY_TOPDOWN: + for (i=stack->top-1; i>=0; i--) { + if ((ret = apply_function(stack->elements[i], arg)) > 0) { + break; + } + } + break; + case ZEND_STACK_APPLY_BOTTOMUP: + for (i=0; i<stack->top; i++) { + if ((ret = apply_function(stack->elements[i], arg)) > 0) { + break; + } + } + break; + } + return ret; +} + +void php_handler_stack_destroy(sapi_stack *stack) +{ + php_handler *ph; + while((ph = (php_handler *)sapi_stack_pop(stack)) != NULL) { + free(ph->name); + free(ph); + } +} +/* }}} */ + +/* {{{ php_save_umask + */ +static void php_save_umask(void) +{ + saved_umask = umask(077); + umask(saved_umask); +} +/* }}} */ + +/* {{{ sapi_apache_ub_write + */ +static int sapi_apache_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int ret=0; + + if (SG(server_context)) { + ret = rwrite(str, str_length, (request_rec *) SG(server_context)); + } + if (ret != str_length) { + php_handle_aborted_connection(); + } + return ret; +} +/* }}} */ + +/* {{{ sapi_apache_flush + */ +static void sapi_apache_flush(void *server_context) +{ + if (server_context) { +#if MODULE_MAGIC_NUMBER > 19970110 + rflush((request_rec *) server_context); +#else + bflush((request_rec *) server_context->connection->client); +#endif + } +} +/* }}} */ + +/* {{{ sapi_apache_read_post + */ +static int sapi_apache_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + uint total_read_bytes=0, read_bytes; + request_rec *r = (request_rec *) SG(server_context); + void (*handler)(int); + + /* + * This handles the situation where the browser sends a Expect: 100-continue header + * and needs to recieve confirmation from the server on whether or not it can send + * the rest of the request. RFC 2616 + * + */ + if (!SG(read_post_bytes) && !ap_should_client_block(r)) { + return total_read_bytes; + } + + handler = signal(SIGPIPE, SIG_IGN); + while (total_read_bytes<count_bytes) { + hard_timeout("Read POST information", r); /* start timeout timer */ + read_bytes = get_client_block(r, buffer+total_read_bytes, count_bytes-total_read_bytes); + reset_timeout(r); + if (read_bytes<=0) { + break; + } + total_read_bytes += read_bytes; + } + signal(SIGPIPE, handler); + return total_read_bytes; +} +/* }}} */ + +/* {{{ sapi_apache_read_cookies + */ +static char *sapi_apache_read_cookies(TSRMLS_D) +{ + return (char *) table_get(((request_rec *) SG(server_context))->subprocess_env, "HTTP_COOKIE"); +} +/* }}} */ + +/* {{{ sapi_apache_header_handler + */ +static int sapi_apache_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + request_rec *r = (request_rec *) SG(server_context); + if(!r) { + return 0; + } + + switch(op) { + case SAPI_HEADER_DELETE_ALL: + clear_table(r->headers_out); + return 0; + + case SAPI_HEADER_DELETE: + table_unset(r->headers_out, sapi_header->header); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + header_name = sapi_header->header; + + header_content = p = strchr(header_name, ':'); + if (!p) { + return 0; + } + + *p = 0; + do { + header_content++; + } while (*header_content==' '); + + if (!strcasecmp(header_name, "Content-Type")) { + r->content_type = pstrdup(r->pool, header_content); + } else if (!strcasecmp(header_name, "Set-Cookie")) { + table_add(r->headers_out, header_name, header_content); + } else if (op == SAPI_HEADER_REPLACE) { + table_set(r->headers_out, header_name, header_content); + } else { + table_add(r->headers_out, header_name, header_content); + } + + *p = ':'; /* a well behaved header handler shouldn't change its original arguments */ + + return SAPI_HEADER_ADD; + + default: + return 0; + } +} +/* }}} */ + +/* {{{ sapi_apache_send_headers + */ +static int sapi_apache_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + if(SG(server_context) == NULL) { /* server_context is not here anymore */ + return SAPI_HEADER_SEND_FAILED; + } + + ((request_rec *) SG(server_context))->status = SG(sapi_headers).http_response_code; + /* check that we haven't sent headers already, we use our own + * headers_sent since we may send headers at anytime + */ + if(!AP(headers_sent)) { + send_http_header((request_rec *) SG(server_context)); + AP(headers_sent) = 1; + } + return SAPI_HEADER_SENT_SUCCESSFULLY; +} +/* }}} */ + +/* {{{ sapi_apache_register_server_variables + */ +static void sapi_apache_register_server_variables(zval *track_vars_array TSRMLS_DC) +{ + register int i; + array_header *arr = table_elts(((request_rec *) SG(server_context))->subprocess_env); + table_entry *elts = (table_entry *) arr->elts; + zval **path_translated; + HashTable *symbol_table; + + for (i = 0; i < arr->nelts; i++) { + char *val; + + if (elts[i].val) { + val = elts[i].val; + } else { + val = ""; + } + php_register_variable(elts[i].key, val, track_vars_array TSRMLS_CC); + } + + /* If PATH_TRANSLATED doesn't exist, copy it from SCRIPT_FILENAME */ + if (track_vars_array) { + symbol_table = track_vars_array->value.ht; + } else { + symbol_table = NULL; + } + if (symbol_table + && !zend_hash_exists(symbol_table, "PATH_TRANSLATED", sizeof("PATH_TRANSLATED")) + && zend_hash_find(symbol_table, "SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME"), (void **) &path_translated)==SUCCESS) { + php_register_variable("PATH_TRANSLATED", Z_STRVAL_PP(path_translated), track_vars_array TSRMLS_CC); + } + + php_register_variable("PHP_SELF", ((request_rec *) SG(server_context))->uri, track_vars_array TSRMLS_CC); +} +/* }}} */ + +/* {{{ php_apache_startup + */ +static int php_apache_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &apache_module_entry, 1) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} +/* }}} */ + +/* {{{ php_apache_log_message + */ +static void php_apache_log_message(char *message TSRMLS_DC) +{ + if (SG(server_context)) { +#if MODULE_MAGIC_NUMBER >= 19970831 + aplog_error(NULL, 0, APLOG_ERR | APLOG_NOERRNO, ((request_rec *) SG(server_context))->server, "%s", message); +#else + log_error(message, ((request_rec *) SG(server_context))->server); +#endif + } else { + fprintf(stderr, "%s", message); + fprintf(stderr, "\n"); + } +} +/* }}} */ + +/* {{{ php_apache_request_shutdown + */ +static void php_apache_request_shutdown(void *dummy) +{ + TSRMLS_FETCH(); + AP(current_hook) = AP_CLEANUP; + php_output_set_status(PHP_OUTPUT_DISABLED TSRMLS_CC); + SG(server_context) = NULL; /* The server context (request) is invalid by the time run_cleanups() is called */ + if(SG(sapi_started)) { + php_request_shutdown(dummy); + SG(sapi_started) = 0; + } + AP(in_request) = 0; + if(AP(setup_env)) { + AP(setup_env) = 0; + } + AP(current_hook) = AP_WAITING_FOR_REQUEST; + AP(headers_sent) = 0; +} +/* }}} */ + +/* {{{ php_apache_sapi_activate + */ +static int php_apache_sapi_activate(TSRMLS_D) +{ + request_rec *r = (request_rec *) SG(server_context); + + /* + * For the Apache module version, this bit of code registers a cleanup + * function that gets triggered when our request pool is destroyed. + * We need this because at any point in our code we can be interrupted + * and that may happen before we have had time to free our memory. + * The php_request_shutdown function needs to free all outstanding allocated + * memory. + */ + block_alarms(); + register_cleanup(r->pool, NULL, php_apache_request_shutdown, php_request_shutdown_for_exec); + AP(in_request)=1; + unblock_alarms(); + + /* Override the default headers_only value - sometimes "GET" requests should actually only + * send headers. + */ + SG(request_info).headers_only = r->header_only; + return SUCCESS; +} +/* }}} */ + +/* {{{ php_apache_get_stat + */ +static struct stat *php_apache_get_stat(TSRMLS_D) +{ + return &((request_rec *) SG(server_context))->finfo; +} +/* }}} */ + +/* {{{ php_apache_getenv + */ +static char *php_apache_getenv(char *name, size_t name_len TSRMLS_DC) +{ + return (char *) table_get(((request_rec *) SG(server_context))->subprocess_env, name); +} +/* }}} */ + +/* {{{ sapi_module_struct apache_sapi_module + */ +static sapi_module_struct apache_sapi_module = { + "apache", /* name */ + "Apache", /* pretty name */ + + php_apache_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + php_apache_sapi_activate, /* activate */ + NULL, /* deactivate */ + + sapi_apache_ub_write, /* unbuffered write */ + sapi_apache_flush, /* flush */ + php_apache_get_stat, /* get uid */ + php_apache_getenv, /* getenv */ + + php_error, /* error handler */ + + sapi_apache_header_handler, /* header handler */ + sapi_apache_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_apache_read_post, /* read POST data */ + sapi_apache_read_cookies, /* read Cookies */ + + sapi_apache_register_server_variables, /* register server variables */ + php_apache_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* child terminate */ + + NULL, /* php.ini path override */ + +#ifdef PHP_WIN32 + NULL, + NULL, +#else + block_alarms, /* Block interruptions */ + unblock_alarms, /* Unblock interruptions */ +#endif + + NULL, /* default post reader */ + NULL, /* treat data */ + NULL, /* exe location */ + 0, /* ini ignore */ + NULL + +}; +/* }}} */ + +/* {{{ php_restore_umask + */ +static void php_restore_umask(void) +{ + umask(saved_umask); +} +/* }}} */ + +/* {{{ init_request_info + */ +static void init_request_info(TSRMLS_D) +{ + request_rec *r = ((request_rec *) SG(server_context)); + char *content_length = (char *) table_get(r->subprocess_env, "CONTENT_LENGTH"); + const char *authorization=NULL; + char *tmp, *tmp_user; + + SG(request_info).query_string = r->args; + SG(request_info).path_translated = r->filename; + SG(request_info).request_uri = r->uri; + SG(request_info).request_method = (char *)r->method; + SG(request_info).proto_num = r->proto_num; + SG(request_info).content_type = (char *) table_get(r->subprocess_env, "CONTENT_TYPE"); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + SG(sapi_headers).http_response_code = r->status; + + if (r->headers_in) { + authorization = table_get(r->headers_in, "Authorization"); + } + + SG(request_info).auth_user = NULL; + SG(request_info).auth_password = NULL; + + if (authorization && !auth_type(r)) { + if (!strcasecmp(getword(r->pool, &authorization, ' '), "Basic")) { + tmp = uudecode(r->pool, authorization); + tmp_user = getword_nulls_nc(r->pool, &tmp, ':'); + if (tmp_user) { + r->connection->user = pstrdup(r->connection->pool, tmp_user); + r->connection->ap_auth_type = "Basic"; + SG(request_info).auth_user = estrdup(tmp_user); + } + if (tmp) { + SG(request_info).auth_password = estrdup(tmp); + } + } else if (!strcasecmp(getword(r->pool, &authorization, ' '), "Digest")) { + r->connection->ap_auth_type = "Digest"; + SG(request_info).auth_digest = estrdup(authorization); + } + } +} +/* }}} */ + +/* {{{ php_apache_alter_ini_entries + */ +static int php_apache_alter_ini_entries(php_per_dir_entry *per_dir_entry TSRMLS_DC) +{ + zend_alter_ini_entry(per_dir_entry->key, per_dir_entry->key_length+1, per_dir_entry->value, per_dir_entry->value_length, per_dir_entry->type, PHP_INI_STAGE_ACTIVATE); + return 0; +} +/* }}} */ + +/* {{{ php_apache_get_default_mimetype + */ +static char *php_apache_get_default_mimetype(request_rec *r TSRMLS_DC) +{ + + char *mimetype; + if (SG(default_mimetype) || SG(default_charset)) { + /* Assume output will be of the default MIME type. Individual + scripts may change this later. */ + char *tmpmimetype; + tmpmimetype = sapi_get_default_content_type(TSRMLS_C); + mimetype = pstrdup(r->pool, tmpmimetype); + efree(tmpmimetype); + } else { + mimetype = SAPI_DEFAULT_MIMETYPE "; charset=" SAPI_DEFAULT_CHARSET; + } + return mimetype; +} +/* }}} */ + +/* {{{ send_php + */ +static int send_php(request_rec *r, int display_source_mode, char *filename) +{ + int retval; + php_per_dir_config *per_dir_conf; + TSRMLS_FETCH(); + if (AP(in_request)) { + zend_file_handle fh; + + fh.filename = r->filename; + fh.opened_path = NULL; + fh.free_filename = 0; + fh.type = ZEND_HANDLE_FILENAME; + + zend_execute_scripts(ZEND_INCLUDE TSRMLS_CC, NULL, 1, &fh); + return OK; + } + + zend_first_try { + + /* Make sure file exists */ + if (filename == NULL && r->finfo.st_mode == 0) { + return DECLINED; + } + + per_dir_conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + if (per_dir_conf) { + zend_hash_apply((HashTable *) per_dir_conf->ini_settings, (apply_func_t) php_apache_alter_ini_entries TSRMLS_CC); + } + + /* If PHP parser engine has been turned off with an "engine off" + * directive, then decline to handle this request + */ + if (!AP(engine)) { + r->content_type = php_apache_get_default_mimetype(r TSRMLS_CC); + r->allowed |= (1 << METHODS) - 1; + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return DECLINED; + } + if (filename == NULL) { + filename = r->filename; + } + + /* Apache 1.2 has a more complex mechanism for reading POST data */ +#if MODULE_MAGIC_NUMBER > 19961007 + if ((retval = setup_client_block(r, REQUEST_CHUNKED_ERROR))) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return retval; + } +#endif + + if (AP(last_modified)) { +#if MODULE_MAGIC_NUMBER < 19970912 + if ((retval = set_last_modified(r, r->finfo.st_mtime))) { + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return retval; + } +#else + update_mtime (r, r->finfo.st_mtime); + set_last_modified(r); + set_etag(r); +#endif + } + /* Assume output will be of the default MIME type. Individual + scripts may change this later in the request. */ + r->content_type = php_apache_get_default_mimetype(r TSRMLS_CC); + + /* Init timeout */ + hard_timeout("send", r); + + SG(server_context) = r; + + php_save_umask(); + if(!AP(setup_env)) { + AP(setup_env) = 1; + add_common_vars(r); + add_cgi_vars(r); + } + init_request_info(TSRMLS_C); + apache_php_module_main(r, display_source_mode TSRMLS_CC); + + /* Done, restore umask, turn off timeout, close file and return */ + php_restore_umask(); + kill_timeout(r); + } zend_end_try(); + + return OK; +} +/* }}} */ + +/* {{{ send_parsed_php + */ +static int send_parsed_php(request_rec * r) +{ + int result = send_php(r, 0, NULL); + TSRMLS_FETCH(); + + ap_table_setn(r->notes, "mod_php_memory_usage", + ap_psprintf(r->pool, "%u", zend_memory_peak_usage(1 TSRMLS_CC))); + + return result; +} +/* }}} */ + +/* {{{ send_parsed_php_source + */ +static int send_parsed_php_source(request_rec * r) +{ + return send_php(r, 1, NULL); +} +/* }}} */ + + +/* {{{ destroy_per_dir_entry + */ +static void destroy_per_dir_entry(php_per_dir_entry *per_dir_entry) +{ + free(per_dir_entry->key); + free(per_dir_entry->value); +} +/* }}} */ + +/* {{{ copy_per_dir_entry + */ +static void copy_per_dir_entry(php_per_dir_entry *per_dir_entry) +{ + php_per_dir_entry tmp = *per_dir_entry; + + per_dir_entry->key = (char *) malloc(tmp.key_length+1); + memcpy(per_dir_entry->key, tmp.key, tmp.key_length); + per_dir_entry->key[per_dir_entry->key_length] = 0; + + per_dir_entry->value = (char *) malloc(tmp.value_length+1); + memcpy(per_dir_entry->value, tmp.value, tmp.value_length); + per_dir_entry->value[per_dir_entry->value_length] = 0; +} +/* }}} */ + +/* {{{ should_overwrite_per_dir_entry; + + */ +static zend_bool should_overwrite_per_dir_entry(HashTable *target_ht, php_per_dir_entry *orig_per_dir_entry, zend_hash_key *hash_key, void *pData) +{ + php_per_dir_entry *new_per_dir_entry; + + if (zend_hash_find(target_ht, hash_key->arKey, hash_key->nKeyLength, (void **) &new_per_dir_entry)==FAILURE) { + return 1; /* does not exist in dest, copy from source */ + } + + if (new_per_dir_entry->type==PHP_INI_SYSTEM + && orig_per_dir_entry->type!=PHP_INI_SYSTEM) { + return 1; + } else { + return 0; + } +} +/* }}} */ +/* {{{ php_destroy_per_server_info + */ +static void php_destroy_per_server_info(php_per_server_config *conf) +{ + php_handler_stack_destroy(&conf->requires); + php_handler_stack_destroy(&conf->uri_handlers); +} +/* }}} */ + +/* {{{ php_destroy_per_dir_info + */ +static void php_destroy_per_dir_info(php_per_dir_config *conf) +{ + zend_hash_destroy(conf->ini_settings); + php_handler_stack_destroy(&conf->response_handlers); + php_handler_stack_destroy(&conf->auth_handlers); + php_handler_stack_destroy(&conf->access_handlers); + php_handler_stack_destroy(&conf->type_handlers); + php_handler_stack_destroy(&conf->fixup_handlers); + php_handler_stack_destroy(&conf->logger_handlers); + php_handler_stack_destroy(&conf->post_read_handlers); + php_handler_stack_destroy(&conf->headers_handlers); + free(conf->ini_settings); +} +/* }}} */ + +/* {{{ php_create_server + */ +static void *php_create_server(pool *p, char *dummy) +{ + php_per_server_config *conf; + conf = (php_per_server_config *) malloc(sizeof(php_per_server_config)); + register_cleanup(p, (void *) conf, (void (*)(void *)) php_destroy_per_server_info, (void (*)(void *)) php_destroy_per_server_info); + + sapi_stack_init_ex(&conf->requires, 1); + sapi_stack_init_ex(&conf->uri_handlers, 1); + return conf; +} + +/* }}} */ + + +/* {{{ php_create_dir + */ +static void *php_create_dir(pool *p, char *dummy) +{ + php_per_dir_config *conf; + conf = (php_per_dir_config *) malloc(sizeof(php_per_dir_config)); + conf->ini_settings = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init_ex(conf->ini_settings, 5, NULL, (void (*)(void *)) destroy_per_dir_entry, 1, 0); + sapi_stack_init_ex(&conf->response_handlers, 1); + sapi_stack_init_ex(&conf->headers_handlers, 1); + sapi_stack_init_ex(&conf->auth_handlers, 1); + sapi_stack_init_ex(&conf->access_handlers, 1); + sapi_stack_init_ex(&conf->type_handlers, 1); + sapi_stack_init_ex(&conf->fixup_handlers, 1); + sapi_stack_init_ex(&conf->logger_handlers, 1); + sapi_stack_init_ex(&conf->post_read_handlers, 1); + register_cleanup(p, (void *) conf, (void (*)(void *)) php_destroy_per_dir_info, (void (*)(void *)) php_destroy_per_dir_info); + + return conf; +} + +/* }}} */ + +/* {{{ php_merge_dir + */ +static void *php_merge_dir(pool *p, void *basev, void *addv) +{ + php_per_dir_config *a = (php_per_dir_config *) addv; + php_per_dir_config *b = (php_per_dir_config *) basev; + /* This function *must* return addv, and not modify basev */ + zend_hash_merge_ex((HashTable *) a->ini_settings, (HashTable *) b->ini_settings, (copy_ctor_func_t) copy_per_dir_entry, sizeof(php_per_dir_entry), (merge_checker_func_t) should_overwrite_per_dir_entry, NULL); + a->headers_handlers = (a->headers_handlers.top)?a->headers_handlers:b->headers_handlers; + a->auth_handlers = (a->auth_handlers.top)?a->auth_handlers:b->auth_handlers; + a->access_handlers = (a->access_handlers.top)?a->access_handlers:b->access_handlers; + a->type_handlers = (a->type_handlers.top)?a->type_handlers:b->type_handlers; + a->fixup_handlers = (a->fixup_handlers.top)?a->fixup_handlers:b->fixup_handlers; + a->logger_handlers = (a->logger_handlers.top)?a->logger_handlers:b->logger_handlers; + a->post_read_handlers = (a->post_read_handlers.top)?a->post_read_handlers:b->post_read_handlers; + a->response_handlers = (a->response_handlers.top)?a->response_handlers:b->response_handlers; + return a; +} +/* }}} */ + +/* {{{ php_apache_value_handler_ex + */ +static CONST_PREFIX char *php_apache_value_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode) +{ + php_per_dir_entry per_dir_entry; + + if (!apache_php_initialized) { + apache_php_initialized = 1; +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache_sapi_module); + php_apache_startup(&apache_sapi_module); + } + per_dir_entry.type = mode; + + if (strcasecmp(arg2, "none") == 0) { + arg2 = ""; + } + + per_dir_entry.key_length = strlen(arg1); + per_dir_entry.value_length = strlen(arg2); + + per_dir_entry.key = (char *) malloc(per_dir_entry.key_length+1); + memcpy(per_dir_entry.key, arg1, per_dir_entry.key_length); + per_dir_entry.key[per_dir_entry.key_length] = 0; + + per_dir_entry.value = (char *) malloc(per_dir_entry.value_length+1); + memcpy(per_dir_entry.value, arg2, per_dir_entry.value_length); + per_dir_entry.value[per_dir_entry.value_length] = 0; + + zend_hash_update(conf, per_dir_entry.key, per_dir_entry.key_length, &per_dir_entry, sizeof(php_per_dir_entry), NULL); + return NULL; +} +/* }}} */ + +static CONST_PREFIX char *php_set_server_handler(server_rec *s, char *arg1, long handler_stage, long handler_type) +{ + php_per_server_config *conf; + php_handler *handler; + handler = (php_handler *) malloc(sizeof(php_handler)); + handler->type = handler_type; + handler->stage = handler_stage; + handler->name = strdup(arg1); + conf = get_module_config(s->module_config, &php5_module); + switch(handler_stage) { + case AP_URI_TRANS: + sapi_stack_push(&conf->uri_handlers, handler); + break; + default: + sapi_stack_push(&conf->requires, handler); + break; + } + return NULL; +} + +static CONST_PREFIX char *php_set_dir_handler(php_per_dir_config *conf, char *arg1, long handler_stage, long handler_type) +{ + php_handler *handler; + handler = (php_handler *) malloc(sizeof(php_handler)); + handler->type = handler_type; + handler->stage = handler_stage; + handler->name = strdup(arg1); + switch(handler_stage) { + case AP_POST_READ: + sapi_stack_push(&conf->post_read_handlers, handler); + break; + case AP_HEADER_PARSE: + sapi_stack_push(&conf->headers_handlers, handler); + break; + case AP_ACCESS_CONTROL: + sapi_stack_push(&conf->access_handlers, handler); + break; + case AP_AUTHENTICATION: + sapi_stack_push(&conf->auth_handlers, handler); + break; + case AP_AUTHORIZATION: + break; + case AP_TYPE_CHECKING: + sapi_stack_push(&conf->type_handlers, handler); + break; + case AP_FIXUP: + sapi_stack_push(&conf->fixup_handlers, handler); + break; + case AP_RESPONSE: + sapi_stack_push(&conf->response_handlers, handler); + break; + case AP_LOGGING: + sapi_stack_push(&conf->logger_handlers, handler); + break; + default: + break; + } + return NULL; +} + +/* {{{ php_set_uri_handler + */ +static CONST_PREFIX char *php_set_uri_handler(cmd_parms *cmd, void *dummy, char *arg1) +{ + return php_set_server_handler(cmd->server, arg1, AP_URI_TRANS, AP_HANDLER_TYPE_FILE); +} +/* }}} */ + +/* {{{ php_set_uri_handler_code */ +static CONST_PREFIX char *php_set_uri_handler_code(cmd_parms *cmd, void *dummy, char *arg1) +{ + return php_set_server_handler(cmd->server, arg1, AP_URI_TRANS, AP_HANDLER_TYPE_METHOD); +} +/* }}} */ + +/* {{{ php_set_header_handler + */ +static CONST_PREFIX char *php_set_header_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_HEADER_PARSE, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_header_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_HEADER_PARSE, AP_HANDLER_TYPE_METHOD); +} +/* }}} */ + +/* {{{ php_set_auth_handler + */ +static CONST_PREFIX char *php_set_auth_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_AUTHENTICATION, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_auth_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_AUTHENTICATION, AP_HANDLER_TYPE_METHOD); +} + +/* }}} */ + +/* {{{ php_set_access_handler + */ +static CONST_PREFIX char *php_set_access_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_ACCESS_CONTROL, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_access_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_ACCESS_CONTROL, AP_HANDLER_TYPE_METHOD); +} + +/* }}} */ + +/* {{{ php_set_type_handler + */ +static CONST_PREFIX char *php_set_type_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_TYPE_CHECKING, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_type_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_TYPE_CHECKING, AP_HANDLER_TYPE_METHOD); +} + +/* }}} */ + +/* {{{ php_set_fixup_handler + */ +static CONST_PREFIX char *php_set_fixup_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_FIXUP, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_fixup_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_FIXUP, AP_HANDLER_TYPE_METHOD); +} +/* }}} */ + +/* {{{ php_set_logger_handler + */ +static CONST_PREFIX char *php_set_logger_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_LOGGING, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_logger_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_LOGGING, AP_HANDLER_TYPE_METHOD); +} + +/* }}} */ + +/* {{{ php_set_post_read_handler + */ +static CONST_PREFIX char *php_set_post_read_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_POST_READ, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_post_read_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_POST_READ, AP_HANDLER_TYPE_METHOD); +} + + +/* }}} */ + +/* {{{ php_set_require + */ + +static CONST_PREFIX char *php_set_require(cmd_parms *cmd, void *dummy, char *arg1) +{ + return php_set_server_handler(cmd->server, arg1, 0, AP_HANDLER_TYPE_FILE); +} +/* }}} */ + +/* {{{ php_set_response_handler + */ +static CONST_PREFIX char *php_set_response_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_RESPONSE, AP_HANDLER_TYPE_FILE); +} +static CONST_PREFIX char *php_set_response_handler_code(cmd_parms *cmd, php_per_dir_config *conf, char *arg1) +{ + return php_set_dir_handler(conf, arg1, AP_RESPONSE, AP_HANDLER_TYPE_METHOD); +} +/* }}} */ + +/* {{{ php_apache_value_handler + */ +static CONST_PREFIX char *php_apache_value_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2) +{ + return php_apache_value_handler_ex(cmd, conf->ini_settings, arg1, arg2, PHP_INI_PERDIR); +} +/* }}} */ + +/* {{{ php_apache_admin_value_handler + */ +static CONST_PREFIX char *php_apache_admin_value_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2) +{ + return php_apache_value_handler_ex(cmd, conf->ini_settings, arg1, arg2, PHP_INI_SYSTEM); +} +/* }}} */ + +/* {{{ php_apache_flag_handler_ex + */ +static CONST_PREFIX char *php_apache_flag_handler_ex(cmd_parms *cmd, HashTable *conf, char *arg1, char *arg2, int mode) +{ + char bool_val[2]; + + if (!strcasecmp(arg2, "On")) { + bool_val[0] = '1'; + } else { + bool_val[0] = '0'; + } + bool_val[1] = 0; + + return php_apache_value_handler_ex(cmd, conf, arg1, bool_val, mode); +} +/* }}} */ + +/* {{{ php_apache_flag_handler + */ +static CONST_PREFIX char *php_apache_flag_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2) +{ + return php_apache_flag_handler_ex(cmd, conf->ini_settings, arg1, arg2, PHP_INI_PERDIR); +} +/* }}} */ + +/* {{{ php_apache_admin_flag_handler + */ +static CONST_PREFIX char *php_apache_admin_flag_handler(cmd_parms *cmd, php_per_dir_config *conf, char *arg1, char *arg2) +{ + return php_apache_flag_handler_ex(cmd, conf->ini_settings, arg1, arg2, PHP_INI_SYSTEM); +} +/* }}} */ + +/* {{{ php_apache_phpini_set + */ +static CONST_PREFIX char *php_apache_phpini_set(cmd_parms *cmd, HashTable *conf, char *arg) +{ + if (apache_sapi_module.php_ini_path_override) { + return "Only first PHPINIDir directive honored per configuration tree - subsequent ones ignored"; + } + apache_sapi_module.php_ini_path_override = ap_server_root_relative(cmd->pool, arg); + return NULL; +} +/* }}} */ + +/* {{{ int php_xbithack_handler(request_rec * r) + */ +static int php_xbithack_handler(request_rec * r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + + if (!(r->finfo.st_mode & S_IXUSR)) { + r->allowed |= (1 << METHODS) - 1; + return DECLINED; + } + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + if (conf) { + zend_hash_apply((HashTable *) conf->ini_settings, (apply_func_t) php_apache_alter_ini_entries TSRMLS_CC); + } + if(!AP(xbithack)) { + r->allowed |= (1 << METHODS) - 1; + zend_try { + zend_ini_deactivate(TSRMLS_C); + } zend_end_try(); + return DECLINED; + } + return send_parsed_php(r); +} +/* }}} */ + +/* {{{ apache_php_module_shutdown_wrapper + */ +static void apache_php_module_shutdown_wrapper(void) +{ + apache_php_initialized = 0; + apache_sapi_module.shutdown(&apache_sapi_module); + +#if MODULE_MAGIC_NUMBER >= 19970728 + /* This function is only called on server exit if the apache API + * child_exit handler exists, so shutdown globally + */ + sapi_shutdown(); +#endif + +#ifdef ZTS + tsrm_shutdown(); +#endif +} +/* }}} */ + +#if MODULE_MAGIC_NUMBER >= 19970728 +/* {{{ php_child_exit_handler + */ +static void php_child_exit_handler(server_rec *s, pool *p) +{ +/* apache_php_initialized = 0; */ + apache_sapi_module.shutdown(&apache_sapi_module); + +#ifdef ZTS + tsrm_shutdown(); +#endif +} +/* }}} */ +#endif + +/* {{{ void php_init_handler(server_rec *s, pool *p) + */ +static void php_init_handler(server_rec *s, pool *p) +{ + register_cleanup(p, NULL, (void (*)(void *))apache_php_module_shutdown_wrapper, (void (*)(void *))php_module_shutdown_for_exec); + if (!apache_php_initialized) { + apache_php_initialized = 1; +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + sapi_startup(&apache_sapi_module); + php_apache_startup(&apache_sapi_module); + } +#if MODULE_MAGIC_NUMBER >= 19980527 + { + TSRMLS_FETCH(); + if (PG(expose_php)) { + ap_add_version_component("PHP/" PHP_VERSION); + } + } +#endif +} +/* }}} */ + +static int php_run_hook(php_handler *handler, request_rec *r) +{ + zval *ret = NULL; + php_per_dir_config *conf; + + TSRMLS_FETCH(); + + if(!AP(apache_config_loaded)) { + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + if (conf) + zend_hash_apply((HashTable *)conf->ini_settings, (apply_func_t) php_apache_alter_ini_entries TSRMLS_CC); + AP(apache_config_loaded) = 1; + } + if (!handler->name) { + return DECLINED; + } + php_save_umask(); + if (!AP(setup_env)) { + AP(setup_env) = 1; + add_common_vars(r); + add_cgi_vars(r); + } + SG(server_context) = r; + init_request_info(TSRMLS_C); + apache_php_module_hook(r, handler, &ret TSRMLS_CC); + php_restore_umask(); + kill_timeout(r); + if (ret) { + convert_to_long(ret); + return Z_LVAL_P(ret); + } + return HTTP_INTERNAL_SERVER_ERROR; +} + + +static int php_uri_translation(request_rec *r) +{ + php_per_server_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_URI_TRANS; + conf = (php_per_server_config *) get_module_config(r->server->module_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_equals(&conf->uri_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, r, OK); +} + +static int php_header_hook(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_HEADER_PARSE; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_http_error(&conf->headers_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, r); +} + +static int php_auth_hook(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_AUTHENTICATION; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_equals(&conf->auth_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, r, OK); +} + +static int php_access_hook(request_rec *r) +{ + php_per_dir_config *conf; + int status = DECLINED; + TSRMLS_FETCH(); + AP(current_hook) = AP_ACCESS_CONTROL; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + status = sapi_stack_apply_with_argument_stop_if_http_error(&conf->access_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, r); + return status; + +} + +static int php_type_hook(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_TYPE_CHECKING; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_equals(&conf->type_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, + r, OK); +} + +static int php_fixup_hook(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_FIXUP; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_http_error(&conf->fixup_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, + r); +} + +static int php_logger_hook(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_LOGGING; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_http_error(&conf->logger_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, + r); +} + +static int php_post_read_hook(request_rec *r) +{ + php_per_dir_config *conf; + php_per_server_config *svr; + TSRMLS_FETCH(); + AP(current_hook) = AP_POST_READ; + svr = get_module_config(r->server->module_config, &php5_module); + if(ap_is_initial_req(r)) { + sapi_stack_apply_with_argument_all(&svr->requires, ZEND_STACK_APPLY_BOTTOMUP, (int (*)(void *element, void *)) php_run_hook, r); + } + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_stop_if_http_error(&conf->post_read_handlers, + ZEND_STACK_APPLY_BOTTOMUP, + (int (*)(void *element, void *)) php_run_hook, r); +} + +static int php_response_handler(request_rec *r) +{ + php_per_dir_config *conf; + TSRMLS_FETCH(); + AP(current_hook) = AP_RESPONSE; + conf = (php_per_dir_config *) get_module_config(r->per_dir_config, &php5_module); + return sapi_stack_apply_with_argument_all(&conf->response_handlers, ZEND_STACK_APPLY_BOTTOMUP, (int (*)(void *element, void *)) php_run_hook, r); +} + +/* {{{ handler_rec php_handlers[] + */ +handler_rec php_handlers[] = +{ + {"application/x-httpd-php", send_parsed_php}, + {"application/x-httpd-php-source", send_parsed_php_source}, + {"text/html", php_xbithack_handler}, + {"php-script", php_response_handler}, + {NULL} +}; +/* }}} */ + +/* {{{ command_rec php_commands[] + */ +command_rec php_commands[] = +{ + {"php_value", php_apache_value_handler, NULL, OR_OPTIONS, TAKE2, "PHP Value Modifier"}, + {"phpUriHandler", php_set_uri_handler, NULL, RSRC_CONF, TAKE1, "PHP Value Modifier"}, + {"phpUriHandlerMethod", php_set_uri_handler_code, NULL, RSRC_CONF, TAKE1, "PHP Value Modifier"}, +#if MODULE_MAGIC_NUMBER >= 19970103 + {"phpHeaderHandler", php_set_header_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpHeaderHandlerMethod", php_set_header_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, +#endif + {"phpAuthHandler", php_set_auth_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpAuthHandlerMethod", php_set_auth_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpAccessHandler", php_set_access_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpAccessHandlerMethod", php_set_access_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpTypeHandler", php_set_type_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpTypeHandlerMethod", php_set_type_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpFixupHandler", php_set_fixup_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpFixupHandlerMethod", php_set_fixup_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpLoggerHandler", php_set_logger_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpLoggerHandlerMethod", php_set_logger_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, +#if MODULE_MAGIC_NUMBER >= 19970902 + {"phpPostReadHandler", php_set_post_read_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpPostReadHandlerMethod", php_set_post_read_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpRequire", php_set_require, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpResponseHandler", php_set_response_handler, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, + {"phpResponseHandlerMethod", php_set_response_handler_code, NULL, OR_OPTIONS, TAKE1, "PHP Value Modifier"}, +#endif + {"php_flag", php_apache_flag_handler, NULL, OR_OPTIONS, TAKE2, "PHP Flag Modifier"}, + {"php_admin_value", php_apache_admin_value_handler, NULL, ACCESS_CONF|RSRC_CONF, TAKE2, "PHP Value Modifier (Admin)"}, + {"php_admin_flag", php_apache_admin_flag_handler, NULL, ACCESS_CONF|RSRC_CONF, TAKE2, "PHP Flag Modifier (Admin)"}, + {"PHPINIDir", php_apache_phpini_set, NULL, RSRC_CONF, TAKE1, "Directory containing the php.ini file"}, + {NULL} +}; +/* }}} */ + +/* {{{ module MODULE_VAR_EXPORT php5_module + */ +module MODULE_VAR_EXPORT php5_module = +{ + STANDARD_MODULE_STUFF, + php_init_handler, /* initializer */ + php_create_dir, /* per-directory config creator */ + php_merge_dir, /* dir merger */ + php_create_server, /* per-server config creator */ + NULL, /* merge server config */ + php_commands, /* command table */ + php_handlers, /* handlers */ + php_uri_translation, /* filename translation */ + NULL, /* check_user_id */ + php_auth_hook, /* check auth */ + php_access_hook, /* check access */ + php_type_hook, /* type_checker */ + php_fixup_hook, /* fixups */ + php_logger_hook /* logger */ +#if MODULE_MAGIC_NUMBER >= 19970103 + , php_header_hook /* header parser */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970719 + , NULL /* child_init */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970728 + , php_child_exit_handler /* child_exit */ +#endif +#if MODULE_MAGIC_NUMBER >= 19970902 + , php_post_read_hook /* post read-request */ +#endif +}; +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache_hooks/mod_php5.exp b/sapi/apache_hooks/mod_php5.exp new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache_hooks/mod_php5.exp @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache_hooks/mod_php5.h b/sapi/apache_hooks/mod_php5.h new file mode 100644 index 0000000..86a5863 --- /dev/null +++ b/sapi/apache_hooks/mod_php5.h @@ -0,0 +1,88 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Rasmus Lerdorf <rasmus@php.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#ifndef MOD_PHP5_H +#define MOD_PHP5_H + +#if !defined(WIN32) && !defined(WINNT) +#ifndef MODULE_VAR_EXPORT +#define MODULE_VAR_EXPORT +#endif +#endif + +typedef struct { + long engine; + long last_modified; + long xbithack; + long terminate_child; + long setup_env; + long current_hook; + zend_bool in_request; + zend_bool apache_config_loaded; + zend_bool headers_sent; +} php_apache_info_struct; + +typedef struct _php_handler { + long type; + long stage; + char *name; +} php_handler; + +#define AP_HANDLER_TYPE_FILE 0 +#define AP_HANDLER_TYPE_METHOD 1 + +extern zend_module_entry apache_module_entry; + +#ifdef ZTS +extern int php_apache_info_id; +#define AP(v) TSRMG(php_apache_info_id, php_apache_info_struct *, v) +#else +extern php_apache_info_struct php_apache_info; +#define AP(v) (php_apache_info.v) +#endif + +/* defines for the various stages of the apache request */ +#define AP_WAITING_FOR_REQUEST 0 +#define AP_POST_READ 1 +#define AP_URI_TRANS 2 +#define AP_HEADER_PARSE 3 +#define AP_ACCESS_CONTROL 4 +#define AP_AUTHENTICATION 5 +#define AP_AUTHORIZATION 6 +#define AP_TYPE_CHECKING 7 +#define AP_FIXUP 8 +#define AP_RESPONSE 9 +#define AP_LOGGING 10 +#define AP_CLEANUP 11 + + +/* fix for gcc4 visibility patch */ +#ifndef PHP_WIN32 +# undef MODULE_VAR_EXPORT +# define MODULE_VAR_EXPORT PHPAPI +#endif + +#endif /* MOD_PHP5_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/sapi/apache_hooks/php.sym b/sapi/apache_hooks/php.sym new file mode 100644 index 0000000..9ad0f0a --- /dev/null +++ b/sapi/apache_hooks/php.sym @@ -0,0 +1 @@ +php5_module diff --git a/sapi/apache_hooks/php5apache_hooks.dsp b/sapi/apache_hooks/php5apache_hooks.dsp new file mode 100755 index 0000000..cc60f4b --- /dev/null +++ b/sapi/apache_hooks/php5apache_hooks.dsp @@ -0,0 +1,151 @@ +# Microsoft Developer Studio Project File - Name="php5apache_hooks" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5apache_hooks - Win32 Release_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 "php5apache_hooks.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 "php5apache_hooks.mak" CFG="php5apache_hooks - Win32 Release_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5apache_hooks - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache_hooks - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5apache_hooks - Win32 Release_TS_inline" (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)" == "php5apache_hooks - 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 "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\..\regex" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\apache\src\include" /I "..\..\main" /I "..\..\TSRM" /D ZEND_DEBUG=0 /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts.lib ApacheCore.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 /base:"0x60000000" /version:4.0 /dll /machine:I386 /libpath:"..\..\..\php_build\apache\src\corer" /libpath:"..\..\Release_TS" /libpath:"..\..\TSRM\Release_TS" /libpath:"..\..\Zend\Release_TS"
+
+!ELSEIF "$(CFG)" == "php5apache_hooks - Win32 Debug_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Debug_TS"
+# PROP BASE Intermediate_Dir "Debug_TS"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\Debug_TS"
+# PROP Intermediate_Dir "Debug_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 "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\..\regex" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\apache\src\include" /I "..\..\main" /I "..\..\TSRM" /D "_DEBUG" /D ZEND_DEBUG=1 /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts_debug.lib ApacheCore.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 /base:"0x60000000" /version:4.0 /dll /incremental:yes /debug /machine:I386 /out:"..\..\Debug_TS/php5apache_hooks.dll" /pdbtype:sept /libpath:"..\..\..\php_build\apache\src\cored" /libpath:"..\..\Debug_TS" /libpath:"..\..\TSRM\Debug_TS" /libpath:"..\..\Zend\Debug_TS"
+
+!ELSEIF "$(CFG)" == "php5apache_hooks - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release_TS_inline"
+# PROP BASE Intermediate_Dir "Release_TS_inline"
+# 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 /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\..\regex" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\..\php_build\apache\src\include" /I "..\..\main" /I "..\..\TSRM" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "NDEBUG" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "APACHEPHP5_EXPORTS" /D "WIN32" /D "_MBCS" /D "APACHE_READDIR_H" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 php5ts.lib ApacheCore.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 /version:4.0 /dll /machine:I386 /libpath:"\apache\src\corer" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\TSRM\Release_TS_inline" /libpath:"..\..\Zend\Release_TS_inline"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5apache_hooks - Win32 Release_TS"
+# Name "php5apache_hooks - Win32 Debug_TS"
+# Name "php5apache_hooks - Win32 Release_TS_inline"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\mod_php5.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\php_apache.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\sapi_apache.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\mod_php5.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\php_apache_http.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/sapi/apache_hooks/php_apache.c b/sapi/apache_hooks/php_apache.c new file mode 100644 index 0000000..dde6d88 --- /dev/null +++ b/sapi/apache_hooks/php_apache.c @@ -0,0 +1,1972 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Sæther Bakken <ssb@php.net> | + | David Sklar <sklar@student.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" + +#if defined(PHP_WIN32) || defined(NETWARE) +#include "zend.h" +#include "ap_compat.h" +#else +#include <build-defs.h> +#endif + +#ifdef ZTS +int php_apache_info_id; +#else +php_apache_info_struct php_apache_info; +#endif + +#define SECTION(name) PUTS("<H2 align=\"center\">" name "</H2>\n") + +#undef offsetof +#define offsetof(s_type,field) ((size_t)&(((s_type*)0)->field)) + +extern module *top_module; +extern module **ap_loaded_modules; +static int le_apachereq; +static zend_class_entry *apacherequest_class_entry; + +static void apache_table_to_zval(table *, zval *return_value); + +PHP_FUNCTION(virtual); +PHP_FUNCTION(apache_request_headers); +PHP_FUNCTION(apache_response_headers); +PHP_FUNCTION(apachelog); +PHP_FUNCTION(apache_note); +PHP_FUNCTION(apache_lookup_uri); +PHP_FUNCTION(apache_child_terminate); +PHP_FUNCTION(apache_setenv); +PHP_FUNCTION(apache_get_version); +PHP_FUNCTION(apache_get_modules); + +PHP_MINFO_FUNCTION(apache); + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apachehooks_virtual, 0, 0, 1) + ZEND_ARG_INFO(0, filename) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apachehooks_setenv, 0, 0, 2) + ZEND_ARG_INFO(0, variable) + ZEND_ARG_INFO(0, value) + ZEND_ARG_INFO(0, walk_to_top) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apachehooks_lookup_uri, 0, 0, 1) + ZEND_ARG_INFO(0, uri) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_apachehooks__void, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_apachehooks_note, 0, 0, 1) + ZEND_ARG_INFO(0, note_name) + ZEND_ARG_INFO(0, note_value) +ZEND_END_ARG_INFO() + +const zend_function_entry apache_functions[] = { + PHP_FE(virtual, arginfo_apachehooks_virtual) + PHP_FE(apache_request_headers, arginfo_apachehooks__void) + PHP_FE(apache_note, arginfo_apachehooks_note) + PHP_FE(apache_lookup_uri, arginfo_apachehooks_lookup_uri) + PHP_FE(apache_child_terminate, arginfo_apachehooks__void) + PHP_FE(apache_setenv, arginfo_apachehooks_setenv) + PHP_FE(apache_response_headers, arginfo_apachehooks__void) + PHP_FE(apache_get_version, arginfo_apachehooks__void) + PHP_FE(apache_get_modules, arginfo_apachehooks__void) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_apachehooks__void) + {NULL, NULL, NULL} +}; + +/* {{{ php_apache ini entries + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("xbithack", "0", PHP_INI_ALL, OnUpdateLong, xbithack, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("engine", "1", PHP_INI_ALL, OnUpdateLong, engine, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("last_modified", "0", PHP_INI_ALL, OnUpdateLong, last_modified, php_apache_info_struct, php_apache_info) + STD_PHP_INI_ENTRY("child_terminate", "0", PHP_INI_ALL, OnUpdateLong, terminate_child, php_apache_info_struct, php_apache_info) +PHP_INI_END() +/* }}} */ + +static void php_apache_globals_ctor(php_apache_info_struct *apache_globals TSRMLS_DC) +{ + apache_globals->in_request = 0; +} + + +#define APREQ_GET_THIS(ZVAL) if (NULL == (ZVAL = getThis())) { \ + php_error(E_WARNING, "%s(): underlying ApacheRequest object missing", \ + get_active_function_name(TSRMLS_C)); \ + RETURN_FALSE; \ + } +#define APREQ_GET_REQUEST(ZVAL, R) APREQ_GET_THIS(ZVAL); \ + R = get_apache_request(ZVAL TSRMLS_CC) + +static void php_apache_request_free(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + zval *z = (zval *)rsrc->ptr; +/* fprintf(stderr, "%s() %p\n", __FUNCTION__, z); */ + zval_ptr_dtor(&z); +} + +static request_rec *get_apache_request(zval *z TSRMLS_DC) +{ + request_rec *r; + zval **addr; + + if (NULL == z) { + php_error(E_WARNING, "get_apache_request() invalid wrapper passed"); + return NULL; + } + + if (Z_TYPE_P(z) != IS_OBJECT) { + php_error(E_WARNING, "%s(): wrapper is not an object", get_active_function_name(TSRMLS_C)); + return NULL; + } + + if (zend_hash_index_find(Z_OBJPROP_P(z), 0, (void **)&addr) == FAILURE) { + php_error(E_WARNING, "%s(): underlying object missing", get_active_function_name(TSRMLS_C)); + return NULL; + } + + r = (request_rec *)Z_LVAL_PP(addr); + if (!r) { + php_error(E_WARNING, "%s(): request_rec invalid", get_active_function_name(TSRMLS_C)); + return NULL; + } + + return r; +} + +/* {{{ php_apache_request_new(request_rec *r) + * create a new zval-instance for ApacheRequest that wraps request_rec + */ +zval *php_apache_request_new(request_rec *r) +{ + zval *req; + zval *addr; + + TSRMLS_FETCH(); + + MAKE_STD_ZVAL(addr); + Z_TYPE_P(addr) = IS_LONG; + Z_LVAL_P(addr) = (int) r; + + MAKE_STD_ZVAL(req); + object_init_ex(req, apacherequest_class_entry); + zend_hash_index_update(Z_OBJPROP_P(req), 0, &addr, sizeof(zval *), NULL); + + return req; +} +/* }}} */ + +/* {{{ apache_request_read_string_slot() + */ +static void apache_request_read_string_slot(int offset, INTERNAL_FUNCTION_PARAMETERS) +{ + zval *id; + request_rec *r; + char *s; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + s = *(char **)((char*)r + offset); + + if (s) { + RETURN_STRING(s, 1); + } + + RETURN_EMPTY_STRING(); +} +/* }}} */ + + +/* {{{ apache_request_string_slot() + */ +static void apache_request_string_slot(int offset, INTERNAL_FUNCTION_PARAMETERS) +{ + zval *id; + request_rec *r; + char *old_value, *new_value = NULL; + int new_value_len; + char **target; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &new_value, &new_value_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + target = (char **)((char*)r + offset); + old_value = *target; + + if (new_value) { + *target = ap_pstrdup(r->pool, new_value); + } + + if (old_value) { + RETURN_STRING(old_value, 1); + } + + RETURN_EMPTY_STRING(); +} +/* }}} */ + +/* {{{ apache_request_read_int_slot() + */ +static void apache_request_read_int_slot(int offset, INTERNAL_FUNCTION_PARAMETERS) +{ + zval *id; + request_rec *r; + long l; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + l = *(long *)((char*)r + offset); + + RETURN_LONG(l); +} +/* }}} */ + +/* {{{ apache_request_int_slot() + */ +static void apache_request_int_slot(int offset, INTERNAL_FUNCTION_PARAMETERS) +{ + zval *id; + request_rec *r; + long old_value, new_value; + long *target; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &new_value) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + target = (long *)((char*)r + offset); + old_value = *target; + + switch (ZEND_NUM_ARGS()) { + case 0: + break; + case 1: + *target = new_value; + break; + default: + WRONG_PARAM_COUNT; + break; + } + + RETURN_LONG(old_value); +} +/* }}} */ + + +/* {{{ access string slots of request rec + */ + +/* {{{ proto string ApacheRequest::filename([string new_filename]) + */ +PHP_FUNCTION(apache_request_filename) +{ + apache_request_string_slot(offsetof(request_rec, filename), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::uri([string new_uri]) + */ +PHP_FUNCTION(apache_request_uri) +{ + apache_request_string_slot(offsetof(request_rec, uri), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::unparsed_uri([string new_unparsed_uri]) + */ +PHP_FUNCTION(apache_request_unparsed_uri) +{ + apache_request_string_slot(offsetof(request_rec, unparsed_uri), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::path_info([string new_path_info]) + */ +PHP_FUNCTION(apache_request_path_info) +{ + apache_request_string_slot(offsetof(request_rec, path_info), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::args([string new_args]) + */ +PHP_FUNCTION(apache_request_args) +{ + apache_request_string_slot(offsetof(request_rec, args), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::boundary() + */ +PHP_FUNCTION(apache_request_boundary) +{ + apache_request_read_string_slot(offsetof(request_rec, boundary), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + + +/* {{{ proto string ApacheRequest::content_type([string new_type]) + */ +PHP_FUNCTION(apache_request_content_type) +{ + apache_request_string_slot(offsetof(request_rec, content_type), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::content_encoding([string new_encoding]) + */ +PHP_FUNCTION(apache_request_content_encoding) +{ + apache_request_string_slot(offsetof(request_rec, content_encoding), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::handler([string new_handler]) + */ +PHP_FUNCTION(apache_request_handler) +{ + apache_request_string_slot(offsetof(request_rec, handler), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::the_request() + */ +PHP_FUNCTION(apache_request_the_request) +{ + apache_request_read_string_slot(offsetof(request_rec, the_request), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::protocol() + */ +PHP_FUNCTION(apache_request_protocol) +{ + apache_request_read_string_slot(offsetof(request_rec, protocol), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::hostname() + */ +PHP_FUNCTION(apache_request_hostname) +{ + apache_request_read_string_slot(offsetof(request_rec, hostname), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::status_line([string new_status_line]) + */ +PHP_FUNCTION(apache_request_status_line) +{ + apache_request_string_slot(offsetof(request_rec, status_line), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto string ApacheRequest::method() + */ +PHP_FUNCTION(apache_request_method) +{ + apache_request_read_string_slot(offsetof(request_rec, method), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* }}} access string slots of request rec */ + +/* {{{ access int slots of request_rec + */ + +/* {{{ proto int ApacheRequest::proto_num() + */ +PHP_FUNCTION(apache_request_proto_num) +{ + apache_request_read_int_slot(offsetof(request_rec, proto_num), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::assbackwards() + */ +PHP_FUNCTION(apache_request_assbackwards) +{ + apache_request_read_int_slot(offsetof(request_rec, assbackwards), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + + +/* {{{ proto int ApacheRequest::proxyreq([int new_proxyreq]) + */ +PHP_FUNCTION(apache_request_proxyreq) +{ + apache_request_int_slot(offsetof(request_rec, proxyreq), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::chunked() + */ +PHP_FUNCTION(apache_request_chunked) +{ + apache_request_read_int_slot(offsetof(request_rec, chunked), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + + +/* {{{ proto int ApacheRequest::header_only() + */ +PHP_FUNCTION(apache_request_header_only) +{ + apache_request_read_int_slot(offsetof(request_rec, header_only), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::request_time() + */ +PHP_FUNCTION(apache_request_request_time) +{ + apache_request_read_int_slot(offsetof(request_rec, request_time), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::status([int new_status]) + */ +PHP_FUNCTION(apache_request_status) +{ + apache_request_int_slot(offsetof(request_rec, status), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::method_number([int method_number]) + */ +PHP_FUNCTION(apache_request_method_number) +{ + apache_request_read_int_slot(offsetof(request_rec, method_number), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::allowed([int allowed]) + */ +PHP_FUNCTION(apache_request_allowed) +{ + apache_request_int_slot(offsetof(request_rec, allowed), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::bytes_sent() + */ +PHP_FUNCTION(apache_request_bytes_sent) +{ + apache_request_read_int_slot(offsetof(request_rec, bytes_sent), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::mtime() + */ +PHP_FUNCTION(apache_request_mtime) +{ + apache_request_read_int_slot(offsetof(request_rec, mtime), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::content_length([int new_content_length]) + */ +PHP_FUNCTION(apache_request_content_length) +{ + zval *id; + long zlen; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &zlen) == FAILURE) { + return; + } + + if (ZEND_NUM_ARGS() == 0) { + apache_request_read_int_slot(offsetof(request_rec, clength), INTERNAL_FUNCTION_PARAM_PASSTHRU); + } else { + APREQ_GET_REQUEST(id, r); + + (void)ap_set_content_length(r, zlen); + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto int ApacheRequest::remaining() + */ +PHP_FUNCTION(apache_request_remaining) +{ + apache_request_read_int_slot(offsetof(request_rec, remaining), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::no_cache() + */ +PHP_FUNCTION(apache_request_no_cache) +{ + apache_request_int_slot(offsetof(request_rec, no_cache), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::no_local_copy() + */ +PHP_FUNCTION(apache_request_no_local_copy) +{ + apache_request_int_slot(offsetof(request_rec, no_local_copy), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ proto int ApacheRequest::read_body() + */ +PHP_FUNCTION(apache_request_read_body) +{ + apache_request_int_slot(offsetof(request_rec, read_body), INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + + +/* }}} access int slots of request_rec */ + + +/* {{{ proto array apache_request_headers_in() + * fetch all incoming request headers + */ +PHP_FUNCTION(apache_request_headers_in) +{ + zval *id; + request_rec *r; + + APREQ_GET_REQUEST(id, r); + + apache_table_to_zval(r->headers_in, return_value); +} +/* }}} */ + + +/* {{{ add_header_to_table +*/ +static void add_header_to_table(table *t, INTERNAL_FUNCTION_PARAMETERS) +{ + zval *first = NULL; + zval *second = NULL; + zval **entry, **value; + char *string_key; + uint string_key_len; + ulong num_key; + + zend_bool replace = 0; + HashPosition pos; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|zb", &first, &second, &replace) == FAILURE) { + RETURN_FALSE; + } + + if (Z_TYPE_P(first) == IS_ARRAY) { + switch(ZEND_NUM_ARGS()) { + case 1: + case 3: + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(first), &pos); + while (zend_hash_get_current_data_ex(Z_ARRVAL_P(first), (void **)&entry, &pos) == SUCCESS) { + switch(zend_hash_get_current_key_ex(Z_ARRVAL_P(first), &string_key, &string_key_len, &num_key, 0, &pos)) { + case HASH_KEY_IS_STRING: + if (zend_hash_find(Z_ARRVAL_P(first), string_key, string_key_len, (void **)&value) == FAILURE) { + zend_hash_move_forward_ex(Z_ARRVAL_P(first), &pos); + continue; + } + if (!value) { + zend_hash_move_forward_ex(Z_ARRVAL_P(first), &pos); + continue; + } + + convert_to_string_ex(value); + if (replace) { + ap_table_set(t, string_key, Z_STRVAL_PP(value)); + } else { + ap_table_merge(t, string_key, Z_STRVAL_PP(value)); + } + break; + case HASH_KEY_IS_LONG: + default: + php_error(E_WARNING, "%s(): Can only add STRING keys to headers!", get_active_function_name(TSRMLS_C)); + break; + } + + zend_hash_move_forward_ex(Z_ARRVAL_P(first), &pos); + } + break; + default: + WRONG_PARAM_COUNT; + break; + } + } else if (Z_TYPE_P(first) == IS_STRING) { + switch(ZEND_NUM_ARGS()) { + case 2: + case 3: + convert_to_string_ex(&second); + if (replace) { + ap_table_set(t, Z_STRVAL_P(first), Z_STRVAL_P(second)); + } else { + ap_table_merge(t, Z_STRVAL_P(first), Z_STRVAL_P(second)); + } + break; + default: + WRONG_PARAM_COUNT; + break; + } + } else { + RETURN_FALSE; + } +} + +/* }}} */ + + +/* {{{ proto array apache_request_headers_out([{string name|array list} [, string value [, bool replace = false]]]) + * fetch all outgoing request headers + */ +PHP_FUNCTION(apache_request_headers_out) +{ + zval *id; + request_rec *r; + + APREQ_GET_REQUEST(id, r); + + if (ZEND_NUM_ARGS() > 0) { + add_header_to_table(r->headers_out, INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + + apache_table_to_zval(r->headers_out, return_value); +} +/* }}} */ + + +/* {{{ proto array apache_request_err_headers_out([{string name|array list} [, string value [, bool replace = false]]]) + * fetch all headers that go out in case of an error or a subrequest + */ +PHP_FUNCTION(apache_request_err_headers_out) +{ + zval *id; + request_rec *r; + + APREQ_GET_REQUEST(id, r); + + if (ZEND_NUM_ARGS() > 0) { + add_header_to_table(r->err_headers_out, INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + + apache_table_to_zval(r->err_headers_out, return_value); +} +/* }}} */ + + +/* {{{ proxy functions for the ap_* functions family + */ + +/* {{{ proto int apache_request_server_port() + */ +PHP_FUNCTION(apache_request_server_port) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_LONG(ap_get_server_port(r)); +} +/* }}} */ + +/* {{{ proto int apache_request_remote_host([int type]) + */ +PHP_FUNCTION(apache_request_remote_host) +{ + zval *id; + long type = 0; + request_rec *r; + char *res; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &type) == FAILURE) { + return; + } + + if (!type) { + type = REMOTE_NAME; + } + + APREQ_GET_REQUEST(id, r); + + res = (char *)ap_get_remote_host(r->connection, r->per_dir_config, (int)type); + + if (res) { + RETURN_STRING(res, 1); + } + + RETURN_EMPTY_STRING(); +} +/* }}} */ + +/* {{{ proto long apache_request_update_mtime([int dependency_mtime]) + */ +PHP_FUNCTION(apache_request_update_mtime) +{ + zval *id; + request_rec *r; + long mtime = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &mtime) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_LONG(ap_update_mtime(r, (int) mtime)); +} +/* }}} */ + + +/* {{{ proto void apache_request_set_etag() + */ +PHP_FUNCTION(apache_request_set_etag) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_set_etag(r); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto void apache_request_set_last_modified() + */ +PHP_FUNCTION(apache_request_set_last_modified) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_set_last_modified(r); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto long apache_request_meets_conditions() + */ +PHP_FUNCTION(apache_request_meets_conditions) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_LONG(ap_meets_conditions(r)); +} +/* }}} */ + +/* {{{ proto long apache_request_discard_request_body() + */ +PHP_FUNCTION(apache_request_discard_request_body) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_LONG(ap_discard_request_body(r)); +} +/* }}} */ + +/* {{{ proto long apache_request_satisfies() + */ +PHP_FUNCTION(apache_request_satisfies) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_LONG(ap_satisfies(r)); +} +/* }}} */ + + +/* {{{ proto bool apache_request_is_initial_req() + */ +PHP_FUNCTION(apache_request_is_initial_req) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_BOOL(ap_is_initial_req(r)); +} +/* }}} */ + +/* {{{ proto bool apache_request_some_auth_required() + */ +PHP_FUNCTION(apache_request_some_auth_required) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + RETURN_BOOL(ap_some_auth_required(r)); +} +/* }}} */ + +/* {{{ proto string apache_request_auth_type() + */ +PHP_FUNCTION(apache_request_auth_type) +{ + zval *id; + request_rec *r; + char *t; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + t = (char *)ap_auth_type(r); + if (!t) { + RETURN_NULL(); + } + + RETURN_STRING(t, 1); +} +/* }}} */ + +/* {{{ proto string apache_request_auth_name() + */ +PHP_FUNCTION(apache_request_auth_name) +{ + zval *id; + request_rec *r; + char *t; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + t = (char *)ap_auth_name(r); + if (!t) { + RETURN_NULL(); + } + + RETURN_STRING(t, 1); +} +/* }}} */ + +/* {{{ proto apache_request_basic_auth_pw() + */ +PHP_FUNCTION(apache_request_basic_auth_pw) +{ + zval *id, *zpw; + request_rec *r; + const char *pw; + long status; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zpw) == FAILURE) { + return; + } + + if (!PZVAL_IS_REF(zpw)) { + zend_error(E_WARNING, "Parameter wasn't passed by reference"); + RETURN_NULL(); + } + + APREQ_GET_REQUEST(id, r); + + pw = NULL; + status = ap_get_basic_auth_pw(r, &pw); + if (status == OK && pw) { + ZVAL_STRING(zpw, (char *)pw, 1); + } else { + ZVAL_NULL(zpw); + } + RETURN_LONG(status); +} +/* }}} */ + + +/* http_protocol.h */ + +PHP_FUNCTION(apache_request_send_http_header) +{ + zval *id; + request_rec *r; + char *type = NULL; + int typelen; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &type, &typelen) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + if(type) { + r->content_type = pstrdup(r->pool, type); + } + ap_send_http_header(r); + SG(headers_sent) = 1; + AP(headers_sent) = 1; + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_basic_http_header) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_basic_http_header((request_rec *)SG(server_context)); + SG(headers_sent) = 1; + AP(headers_sent) = 1; + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_send_http_trace) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_send_http_trace((request_rec *)SG(server_context)); + SG(headers_sent) = 1; + AP(headers_sent) = 1; + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_send_http_options) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_send_http_options((request_rec *)SG(server_context)); + SG(headers_sent) = 1; + AP(headers_sent) = 1; + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_send_error_response) +{ + zval *id; + request_rec *r; + long rec = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &rec) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + ap_send_error_response(r, (int) rec); + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_set_content_length) +{ + long length; + zval *id; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &length) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_set_content_length(r, length); + RETURN_TRUE; +} + +PHP_FUNCTION(apache_request_set_keepalive) +{ + zval *id; + request_rec *r; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + ap_set_keepalive(r); + RETURN_TRUE; +} + +/* This stuff should use streams or however this is implemented now + +PHP_FUNCTION(apache_request_send_fd) +{ +} + +PHP_FUNCTION(apache_request_send_fd_length) +{ +} +*/ + +/* These are for overriding default output behaviour */ +PHP_FUNCTION(apache_request_rputs) +{ + char *buffer; + int buffer_len; + zval *id; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buffer, &buffer_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + ap_rwrite(buffer, buffer_len, (request_rec*)SG(server_context)); +} + +/* This stuff would be useful for custom POST handlers, + which should be supported. Probably by not using + sapi_activate at all inside a phpResponseHandler + and instead using a builtin composed of the below + calls as a apache_read_request_body() and allow + people to custom craft their own. + +PHP_FUNCTION(apache_request_setup_client_block) +{ +} + +PHP_FUNCTION(apache_request_should_client_block) +{ +} + +PHP_FUNCTION(apache_request_get_client_block) +{ +} + +PHP_FUNCTION(apache_request_discard_request_body) +{ +} +*/ + +/* http_log.h */ + +/* {{{ proto boolean apache_request_log_error(string message, [long facility]) + */ +PHP_FUNCTION(apache_request_log_error) +{ + zval *id; + char *z_errstr; + int z_errstr_len; + long facility = APLOG_ERR; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &z_errstr, &z_errstr_len, &facility) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + ap_log_error(APLOG_MARK, (int) facility, r->server, "%s", z_errstr); + RETURN_TRUE; +} +/* }}} */ +/* http_main.h */ + +/* {{{ proto object apache_request_sub_req_lookup_uri(string uri) + Returns sub-request for the specified uri. You would + need to run it yourself with run() +*/ +PHP_FUNCTION(apache_request_sub_req_lookup_uri) +{ + zval *id; + char *file; + int file_len; + request_rec *r, *sub_r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &file, &file_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + sub_r = ap_sub_req_lookup_uri(file, r); + + if (!sub_r) { + RETURN_FALSE; + } + return_value = php_apache_request_new(sub_r); +} +/* }}} */ + +/* {{{ proto object apache_request_sub_req_lookup_file(string file) + Returns sub-request for the specified file. You would + need to run it yourself with run(). +*/ +PHP_FUNCTION(apache_request_sub_req_lookup_file) +{ + zval *id; + char *file; + int file_len; + request_rec *r, *sub_r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &file, &file_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + sub_r = ap_sub_req_lookup_file(file, r); + + if (!sub_r) { + RETURN_FALSE; + } + return_value = php_apache_request_new(sub_r); +} +/* }}} */ + +/* {{{ proto object apache_request_sub_req_method_uri(string method, string uri) + Returns sub-request for the specified file. You would + need to run it yourself with run(). +*/ +PHP_FUNCTION(apache_request_sub_req_method_uri) +{ + zval *id; + char *file, *method; + int file_len, method_len; + request_rec *r, *sub_r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &method, &method_len, &file, &file_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + sub_r = ap_sub_req_method_uri(method, file, r); + + if (!sub_r) { + RETURN_FALSE; + } + return_value = php_apache_request_new(sub_r); +} +/* }}} */ + +/* {{{ proto long apache_request_run() + This is a wrapper for ap_sub_run_req and ap_destory_sub_req. It takes + sub_request, runs it, destroys it, and returns it's status. +*/ +PHP_FUNCTION(apache_request_run) +{ + zval *id; + request_rec *r; + int status; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + if (!r || ap_is_initial_req(r)) { + RETURN_FALSE; + } + status = ap_run_sub_req(r); + ap_destroy_sub_req(r); + RETURN_LONG(status); +} +/* }}} */ + +PHP_FUNCTION(apache_request_internal_redirect) +{ + zval *id; + char *new_uri; + int new_uri_len; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &new_uri, &new_uri_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_internal_redirect(new_uri, r); +} + +PHP_FUNCTION(apache_request_send_header_field) +{ + char *fieldname, *fieldval; + int fieldname_len, fieldval_len; + zval *id; + request_rec *r; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &fieldname, &fieldname_len, &fieldval, &fieldval_len) == FAILURE) { + return; + } + + APREQ_GET_REQUEST(id, r); + + ap_send_header_field(r, fieldname, fieldval); + SG(headers_sent) = 1; + AP(headers_sent) = 1; +} + + + +/* }}} */ + +/* {{{ php_apache_request_class_functions + */ +const static zend_function_entry php_apache_request_class_functions[] = { + /* string slots */ + PHP_FALIAS(args, apache_request_args, NULL) + PHP_FALIAS(boundary, apache_request_boundary, NULL) + PHP_FALIAS(content_encoding, apache_request_content_encoding, NULL) + PHP_FALIAS(content_type, apache_request_content_type, NULL) + PHP_FALIAS(filename, apache_request_filename, NULL) + PHP_FALIAS(handler, apache_request_handler, NULL) + PHP_FALIAS(hostname, apache_request_hostname, NULL) + PHP_FALIAS(method, apache_request_method, NULL) + PHP_FALIAS(path_info, apache_request_path_info, NULL) + PHP_FALIAS(protocol, apache_request_protocol, NULL) + PHP_FALIAS(status_line, apache_request_status_line, NULL) + PHP_FALIAS(the_request, apache_request_the_request, NULL) + PHP_FALIAS(unparsed_uri, apache_request_unparsed_uri, NULL) + PHP_FALIAS(uri, apache_request_uri, NULL) + + /* int slots */ + PHP_FALIAS(allowed, apache_request_allowed, NULL) + PHP_FALIAS(bytes_sent, apache_request_bytes_sent, NULL) + PHP_FALIAS(chunked, apache_request_chunked, NULL) + PHP_FALIAS(content_length, apache_request_content_length, NULL) + PHP_FALIAS(header_only, apache_request_header_only, NULL) + PHP_FALIAS(method_number, apache_request_method_number, NULL) + PHP_FALIAS(mtime, apache_request_mtime, NULL) + PHP_FALIAS(no_cache, apache_request_no_cache, NULL) + PHP_FALIAS(no_local_copy, apache_request_no_local_copy, NULL) + PHP_FALIAS(proto_num, apache_request_proto_num, NULL) + PHP_FALIAS(proxyreq, apache_request_proxyreq, NULL) + PHP_FALIAS(read_body, apache_request_read_body, NULL) + PHP_FALIAS(remaining, apache_request_remaining, NULL) + PHP_FALIAS(request_time, apache_request_request_time, NULL) + PHP_FALIAS(status, apache_request_status, NULL) + + /* tables & arrays */ + PHP_FALIAS(headers_in, apache_request_headers_in, NULL) + PHP_FALIAS(headers_out, apache_request_headers_out, NULL) + PHP_FALIAS(err_headers_out, apache_request_err_headers_out, NULL) + + + /* proxy functions for the ap_* functions family */ +#undef auth_name +#undef auth_type +#undef discard_request_body +#undef is_initial_req +#undef meets_conditions +#undef satisfies +#undef set_etag +#undef set_last_modified +#undef some_auth_required +#undef update_mtime +#undef send_http_header +#undef send_header_field +#undef basic_http_header +#undef send_http_trace +#undef send_http_options +#undef send_error_response +#undef set_content_length +#undef set_keepalive +#undef rputs +#undef log_error +#undef lookup_uri +#undef lookup_file +#undef method_uri +#undef run +#undef internal_redirect + PHP_FALIAS(auth_name, apache_request_auth_name, NULL) + PHP_FALIAS(auth_type, apache_request_auth_type, NULL) + PHP_FALIAS(basic_auth_pw, apache_request_basic_auth_pw, NULL) + PHP_FALIAS(discard_request_body, apache_request_discard_request_body, NULL) + PHP_FALIAS(is_initial_req, apache_request_is_initial_req, NULL) + PHP_FALIAS(meets_conditions, apache_request_meets_conditions, NULL) + PHP_FALIAS(remote_host, apache_request_remote_host, NULL) + PHP_FALIAS(satisfies, apache_request_satisfies, NULL) + PHP_FALIAS(server_port, apache_request_server_port, NULL) + PHP_FALIAS(set_etag, apache_request_set_etag, NULL) + PHP_FALIAS(set_last_modified, apache_request_set_last_modified, NULL) + PHP_FALIAS(some_auth_required, apache_request_some_auth_required, NULL) + PHP_FALIAS(update_mtime, apache_request_update_mtime, NULL) + PHP_FALIAS(send_http_header, apache_request_send_http_header, NULL) + PHP_FALIAS(basic_http_header, apache_request_basic_http_header, NULL) + PHP_FALIAS(send_header_field, apache_request_send_header_field, NULL) + PHP_FALIAS(send_http_trace, apache_request_send_http_trace, NULL) + PHP_FALIAS(send_http_options, apache_request_send_http_trace, NULL) + PHP_FALIAS(send_error_response, apache_request_send_error_response, NULL) + PHP_FALIAS(set_content_length, apache_request_set_content_length, NULL) + PHP_FALIAS(set_keepalive, apache_request_set_keepalive, NULL) + PHP_FALIAS(rputs, apache_request_rputs, NULL) + PHP_FALIAS(log_error, apache_request_log_error, NULL) + PHP_FALIAS(lookup_uri, apache_request_sub_req_lookup_uri, NULL) + PHP_FALIAS(lookup_file, apache_request_sub_req_lookup_file, NULL) + PHP_FALIAS(method_uri, apache_request_sub_req_method_uri, NULL) + PHP_FALIAS(run, apache_request_run, NULL) + PHP_FALIAS(internal_redirect, apache_request_internal_redirect, NULL) + PHP_FE_END +}; +/* }}} */ + + +static PHP_MINIT_FUNCTION(apache) +{ + zend_class_entry ce; + +#ifdef ZTS + ts_allocate_id(&php_apache_info_id, sizeof(php_apache_info_struct), (ts_allocate_ctor) php_apache_globals_ctor, NULL); +#else + php_apache_globals_ctor(&php_apache_info TSRMLS_CC); +#endif + REGISTER_INI_ENTRIES(); + + + le_apachereq = zend_register_list_destructors_ex(php_apache_request_free, NULL, "ApacheRequest", module_number); + INIT_OVERLOADED_CLASS_ENTRY(ce, "ApacheRequest", php_apache_request_class_functions, NULL, NULL, NULL); + apacherequest_class_entry = zend_register_internal_class_ex(&ce, NULL, NULL TSRMLS_CC); + + REGISTER_LONG_CONSTANT("OK", OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("DECLINED", DECLINED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FORBIDDEN", FORBIDDEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("AUTH_REQUIRED", AUTH_REQUIRED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("DONE", DONE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SERVER_ERROR", SERVER_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REDIRECT", REDIRECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("BAD_REQUEST", BAD_REQUEST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("NOT_FOUND", NOT_FOUND, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_CONTINUE", HTTP_CONTINUE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_SWITCHING_PROTOCOLS", HTTP_SWITCHING_PROTOCOLS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_PROCESSING", HTTP_PROCESSING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_OK", HTTP_OK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_CREATED", HTTP_CREATED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_ACCEPTED", HTTP_ACCEPTED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NON_AUTHORITATIVE", HTTP_NON_AUTHORITATIVE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NO_CONTENT", HTTP_NO_CONTENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_RESET_CONTENT", HTTP_RESET_CONTENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_PARTIAL_CONTENT", HTTP_PARTIAL_CONTENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_MULTI_STATUS", HTTP_MULTI_STATUS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_MULTIPLE_CHOICES", HTTP_MULTIPLE_CHOICES, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_MOVED_PERMANENTLY", HTTP_MOVED_PERMANENTLY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_MOVED_TEMPORARILY", HTTP_MOVED_TEMPORARILY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_SEE_OTHER", HTTP_SEE_OTHER, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NOT_MODIFIED", HTTP_NOT_MODIFIED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_USE_PROXY", HTTP_USE_PROXY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_TEMPORARY_REDIRECT", HTTP_TEMPORARY_REDIRECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_BAD_REQUEST", HTTP_BAD_REQUEST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_UNAUTHORIZED", HTTP_UNAUTHORIZED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_PAYMENT_REQUIRED", HTTP_PAYMENT_REQUIRED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_FORBIDDEN", HTTP_FORBIDDEN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NOT_FOUND", HTTP_NOT_FOUND, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_METHOD_NOT_ALLOWED", HTTP_METHOD_NOT_ALLOWED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NOT_ACCEPTABLE", HTTP_NOT_ACCEPTABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_PROXY_AUTHENTICATION_REQUIRED", HTTP_PROXY_AUTHENTICATION_REQUIRED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_REQUEST_TIME_OUT", HTTP_REQUEST_TIME_OUT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_CONFLICT", HTTP_CONFLICT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_GONE", HTTP_GONE, CONST_CS | CONST_PERSISTENT);REGISTER_LONG_CONSTANT("HTTP_LENGTH_REQUIRED", HTTP_LENGTH_REQUIRED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_PRECONDITION_FAILED", HTTP_PRECONDITION_FAILED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_REQUEST_ENTITY_TOO_LARGE", HTTP_REQUEST_ENTITY_TOO_LARGE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_REQUEST_URI_TOO_LARGE", HTTP_REQUEST_URI_TOO_LARGE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_UNSUPPORTED_MEDIA_TYPE", HTTP_UNSUPPORTED_MEDIA_TYPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_RANGE_NOT_SATISFIABLE", HTTP_RANGE_NOT_SATISFIABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_EXPECTATION_FAILED", HTTP_EXPECTATION_FAILED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_UNPROCESSABLE_ENTITY", HTTP_UNPROCESSABLE_ENTITY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_LOCKED", HTTP_LOCKED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_FAILED_DEPENDENCY", HTTP_FAILED_DEPENDENCY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_INTERNAL_SERVER_ERROR", HTTP_INTERNAL_SERVER_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NOT_IMPLEMENTED", HTTP_NOT_IMPLEMENTED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_BAD_GATEWAY", HTTP_BAD_GATEWAY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_SERVICE_UNAVAILABLE", HTTP_SERVICE_UNAVAILABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_GATEWAY_TIME_OUT", HTTP_GATEWAY_TIME_OUT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_VERSION_NOT_SUPPORTED", HTTP_VERSION_NOT_SUPPORTED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_VARIANT_ALSO_VARIES", HTTP_VARIANT_ALSO_VARIES, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_INSUFFICIENT_STORAGE", HTTP_INSUFFICIENT_STORAGE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_NOT_EXTENDED", HTTP_NOT_EXTENDED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_EMERG", APLOG_EMERG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_ALERT", APLOG_ALERT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_CRIT", APLOG_CRIT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_ERR", APLOG_ERR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_WARNING", APLOG_WARNING, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_NOTICE", APLOG_NOTICE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_INFO", APLOG_INFO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("APLOG_DEBUG", APLOG_DEBUG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_GET", M_GET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_PUT", M_PUT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_POST", M_POST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_DELETE", M_DELETE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_CONNECT", M_CONNECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_OPTIONS", M_OPTIONS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_TRACE", M_TRACE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_PATCH", M_PATCH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_PROPFIND", M_PROPFIND, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_PROPPATCH", M_PROPPATCH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_MKCOL", M_MKCOL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_COPY", M_COPY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_MOVE", M_MOVE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_LOCK", M_LOCK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_UNLOCK", M_UNLOCK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("M_INVALID", M_INVALID, CONST_CS | CONST_PERSISTENT); + + /* Possible values for request_rec.read_body (set by handling module): + * REQUEST_NO_BODY Send 413 error if message has any body + * REQUEST_CHUNKED_ERROR Send 411 error if body without Content-Length + * REQUEST_CHUNKED_DECHUNK If chunked, remove the chunks for me. + * REQUEST_CHUNKED_PASS Pass the chunks to me without removal. + */ + REGISTER_LONG_CONSTANT("REQUEST_NO_BODY", REQUEST_NO_BODY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REQUEST_CHUNKED_ERROR", REQUEST_CHUNKED_ERROR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REQUEST_CHUNKED_DECHUNK", REQUEST_CHUNKED_DECHUNK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REQUEST_CHUNKED_PASS", REQUEST_CHUNKED_PASS, CONST_CS | CONST_PERSISTENT); + + /* resolve types for remote_host() */ + REGISTER_LONG_CONSTANT("REMOTE_HOST", REMOTE_HOST, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REMOTE_NAME", REMOTE_NAME, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REMOTE_NOLOOKUP", REMOTE_NOLOOKUP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("REMOTE_DOUBLE_REV", REMOTE_DOUBLE_REV, CONST_CS | CONST_PERSISTENT); + + return SUCCESS; +} + + +static PHP_MSHUTDOWN_FUNCTION(apache) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} + +zend_module_entry apache_module_entry = { + STANDARD_MODULE_HEADER, + "apache", + apache_functions, + PHP_MINIT(apache), + PHP_MSHUTDOWN(apache), + NULL, + NULL, + PHP_MINFO(apache), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +/* {{{ proto bool apache_child_terminate(void) + Terminate apache process after this request */ +PHP_FUNCTION(apache_child_terminate) +{ +#ifndef MULTITHREAD + if (AP(terminate_child)) { + ap_child_terminate( ((request_rec *)SG(server_context)) ); + RETURN_TRUE; + } else { /* tell them to get lost! */ + php_error(E_WARNING, "apache.child_terminate is disabled"); + RETURN_FALSE; + } +#else + php_error(E_WARNING, "apache_child_terminate() is not supported in this build"); + RETURN_FALSE; +#endif +} +/* }}} */ + +/* {{{ proto string apache_note(string note_name [, string note_value]) + Get and set Apache request notes */ +PHP_FUNCTION(apache_note) +{ + char *arg_name, *arg_val = NULL; + int arg_name_len, arg_val_len; + char *note_val; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &arg_name, &arg_name_len, &arg_val, &arg_val_len) == FAILURE) { + return; + } + + note_val = (char *) table_get(((request_rec *)SG(server_context))->notes, arg_name); + + if (arg_val) { + table_set(((request_rec *)SG(server_context))->notes, arg_name, arg_val); + } + + if (!note_val) { + RETURN_FALSE; + } + + RETURN_STRING(note_val, 1); +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(apache) +{ + module *modp = NULL; + char output_buf[128]; +#if !defined(WIN32) && !defined(WINNT) + char name[64]; + char modulenames[1024]; + char *p; +#endif + server_rec *serv; + extern char server_root[MAX_STRING_LEN]; + extern uid_t user_id; + extern char *user_name; + extern gid_t group_id; + extern int max_requests_per_child; + + serv = ((request_rec *) SG(server_context))->server; + + + php_info_print_table_start(); + +#ifdef PHP_WIN32 + php_info_print_table_row(1, "Apache for Windows 95/NT"); + php_info_print_table_end(); + php_info_print_table_start(); +#elif defined(NETWARE) + php_info_print_table_row(1, "Apache for NetWare"); + php_info_print_table_end(); + php_info_print_table_start(); +#else + php_info_print_table_row(2, "APACHE_INCLUDE", PHP_APACHE_INCLUDE); + php_info_print_table_row(2, "APACHE_TARGET", PHP_APACHE_TARGET); +#endif + + php_info_print_table_row(2, "Apache Version", SERVER_VERSION); + +#ifdef APACHE_RELEASE + snprintf(output_buf, sizeof(output_buf), "%d", APACHE_RELEASE); + php_info_print_table_row(2, "Apache Release", output_buf); +#endif + snprintf(output_buf, sizeof(output_buf), "%d", MODULE_MAGIC_NUMBER); + php_info_print_table_row(2, "Apache API Version", output_buf); + snprintf(output_buf, sizeof(output_buf), "%s:%u", serv->server_hostname, serv->port); + php_info_print_table_row(2, "Hostname:Port", output_buf); +#if !defined(WIN32) && !defined(WINNT) + snprintf(output_buf, sizeof(output_buf), "%s(%d)/%d", user_name, (int)user_id, (int)group_id); + php_info_print_table_row(2, "User/Group", output_buf); + snprintf(output_buf, sizeof(output_buf), "Per Child: %d - Keep Alive: %s - Max Per Connection: %d", max_requests_per_child, serv->keep_alive ? "on":"off", serv->keep_alive_max); + php_info_print_table_row(2, "Max Requests", output_buf); +#endif + snprintf(output_buf, sizeof(output_buf), "Connection: %d - Keep-Alive: %d", serv->timeout, serv->keep_alive_timeout); + php_info_print_table_row(2, "Timeouts", output_buf); +#if !defined(WIN32) && !defined(WINNT) +/* + This block seems to be working on NetWare; But it seems to be showing + all modules instead of just the loaded ones +*/ + php_info_print_table_row(2, "Server Root", server_root); + + strcpy(modulenames, ""); + for(modp = top_module; modp; modp = modp->next) { + strlcpy(name, modp->name, sizeof(name)); + if ((p = strrchr(name, '.'))) { + *p='\0'; /* Cut off ugly .c extensions on module names */ + } + strlcat(modulenames, name, sizeof(modulenames)); + if (modp->next) { + strlcat(modulenames, ", ", sizeof(modulenames)); + } + } + php_info_print_table_row(2, "Loaded Modules", modulenames); +#endif + + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); + + { + register int i; + array_header *arr; + table_entry *elts; + request_rec *r; + + r = ((request_rec *) SG(server_context)); + arr = table_elts(r->subprocess_env); + elts = (table_entry *)arr->elts; + + SECTION("Apache Environment"); + php_info_print_table_start(); + php_info_print_table_header(2, "Variable", "Value"); + for (i=0; i < arr->nelts; i++) { + php_info_print_table_row(2, elts[i].key, elts[i].val); + } + php_info_print_table_end(); + } + + { + array_header *env_arr; + table_entry *env; + int i; + request_rec *r; + + r = ((request_rec *) SG(server_context)); + SECTION("HTTP Headers Information"); + php_info_print_table_start(); + php_info_print_table_colspan_header(2, "HTTP Request Headers"); + php_info_print_table_row(2, "HTTP Request", r->the_request); + env_arr = table_elts(r->headers_in); + env = (table_entry *)env_arr->elts; + for (i = 0; i < env_arr->nelts; ++i) { + if (env[i].key) { + php_info_print_table_row(2, env[i].key, env[i].val); + } + } + php_info_print_table_colspan_header(2, "HTTP Response Headers"); + env_arr = table_elts(r->headers_out); + env = (table_entry *)env_arr->elts; + for(i = 0; i < env_arr->nelts; ++i) { + if (env[i].key) { + php_info_print_table_row(2, env[i].key, env[i].val); + } + } + php_info_print_table_end(); + } +} +/* }}} */ + +/* {{{ proto bool virtual(string filename) + Perform an Apache sub-request */ +/* This function is equivalent to <!--#include virtual...--> + * in mod_include. It does an Apache sub-request. It is useful + * for including CGI scripts or .shtml files, or anything else + * that you'd parse through Apache (for .phtml files, you'd probably + * want to use <?Include>. This only works when PHP is compiled + * as an Apache module, since it uses the Apache API for doing + * sub requests. + */ +PHP_FUNCTION(virtual) +{ + char *filename; + int filename_len; + request_rec *rr = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) { + return; + } + + if (!(rr = sub_req_lookup_uri (filename, ((request_rec *) SG(server_context))))) { + php_error(E_WARNING, "Unable to include '%s' - URI lookup failed", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + if (rr->status != 200) { + php_error(E_WARNING, "Unable to include '%s' - error finding URI", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + php_output_end_all(TSRMLS_C); + php_header(TSRMLS_C); + + if (run_sub_req(rr)) { + php_error(E_WARNING, "Unable to include '%s' - request execution failed", filename); + if (rr) + destroy_sub_req (rr); + RETURN_FALSE; + } + + if (rr) + destroy_sub_req (rr); + RETURN_TRUE; +} +/* }}} */ + + +/* {{{ apache_table_to_zval(table *, zval *return_value) + Fetch all HTTP request headers */ +static void apache_table_to_zval(table *t, zval *return_value) +{ + array_header *env_arr; + table_entry *tenv; + int i; + + array_init(return_value); + env_arr = table_elts(t); + tenv = (table_entry *)env_arr->elts; + for (i = 0; i < env_arr->nelts; ++i) { + if (!tenv[i].key) { + continue; + } + if (add_assoc_string(return_value, tenv[i].key, (tenv[i].val==NULL) ? "" : tenv[i].val, 1)==FAILURE) { + RETURN_FALSE; + } + } + +} +/* }}} */ + + +/* {{{ proto array getallheaders(void) +*/ +/* Alias for apache_request_headers() */ +/* }}} */ + +/* {{{ proto array apache_request_headers(void) + Fetch all HTTP request headers */ +PHP_FUNCTION(apache_request_headers) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + apache_table_to_zval(((request_rec *)SG(server_context))->headers_in, return_value); +} +/* }}} */ + +/* {{{ proto array apache_response_headers(void) + Fetch all HTTP response headers */ +PHP_FUNCTION(apache_response_headers) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + apache_table_to_zval(((request_rec *) SG(server_context))->headers_out, return_value); +} +/* }}} */ + +/* {{{ proto bool apache_setenv(string variable, string value [, bool walk_to_top]) + Set an Apache subprocess_env variable */ +PHP_FUNCTION(apache_setenv) +{ + int var_len, val_len; + zend_bool top=0; + char *var = NULL, *val = NULL; + request_rec *r = (request_rec *) SG(server_context); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|b", &var, &var_len, &val, &val_len, &top) == FAILURE) { + RETURN_FALSE; + } + + while(top) { + if (r->prev) { + r = r->prev; + } + else break; + } + + ap_table_setn(r->subprocess_env, ap_pstrndup(r->pool, var, var_len), ap_pstrndup(r->pool, val, val_len)); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto object apache_lookup_uri(string URI) + Perform a partial request of the given URI to obtain information about it */ +PHP_FUNCTION(apache_lookup_uri) +{ + char *filename; + int filename_len; + request_rec *rr=NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &filename, &filename_len) == FAILURE) { + return; + } + + if(!(rr = sub_req_lookup_uri(filename, ((request_rec *) SG(server_context))))) { + php_error(E_WARNING, "URI lookup failed", filename); + RETURN_FALSE; + } + + object_init(return_value); + add_property_long(return_value,"status", rr->status); + + if (rr->the_request) { + add_property_string(return_value,"the_request", rr->the_request, 1); + } + if (rr->status_line) { + add_property_string(return_value,"status_line", (char *)rr->status_line, 1); + } + if (rr->method) { + add_property_string(return_value,"method", (char *)rr->method, 1); + } + if (rr->content_type) { + add_property_string(return_value,"content_type", (char *)rr->content_type, 1); + } + if (rr->handler) { + add_property_string(return_value,"handler", (char *)rr->handler, 1); + } + if (rr->uri) { + add_property_string(return_value,"uri", rr->uri, 1); + } + if (rr->filename) { + add_property_string(return_value,"filename", rr->filename, 1); + } + if (rr->path_info) { + add_property_string(return_value,"path_info", rr->path_info, 1); + } + if (rr->args) { + add_property_string(return_value,"args", rr->args, 1); + } + if (rr->boundary) { + add_property_string(return_value,"boundary", rr->boundary, 1); + } + add_property_long(return_value,"no_cache", rr->no_cache); + add_property_long(return_value,"no_local_copy", rr->no_local_copy); + add_property_long(return_value,"allowed", rr->allowed); + add_property_long(return_value,"sent_bodyct", rr->sent_bodyct); + add_property_long(return_value,"bytes_sent", rr->bytes_sent); + add_property_long(return_value,"byterange", rr->byterange); + add_property_long(return_value,"clength", rr->clength); + +#if MODULE_MAGIC_NUMBER >= 19980324 + if (rr->unparsed_uri) { + add_property_string(return_value,"unparsed_uri", rr->unparsed_uri, 1); + } + if(rr->mtime) { + add_property_long(return_value,"mtime", rr->mtime); + } +#endif + if(rr->request_time) { + add_property_long(return_value,"request_time", rr->request_time); + } + + destroy_sub_req(rr); +} +/* }}} */ + + +#if 0 +/* +This function is most likely a bad idea. Just playing with it for now. +*/ + +PHP_FUNCTION(apache_exec_uri) +{ + zval **filename; + request_rec *rr=NULL; + + if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &filename) == FAILURE) { + WRONG_PARAM_COUNT; + } + convert_to_string_ex(filename); + + if(!(rr = ap_sub_req_lookup_uri((*filename)->value.str.val, ((request_rec *) SG(server_context))))) { + php_error(E_WARNING, "URI lookup failed", (*filename)->value.str.val); + RETURN_FALSE; + } + RETVAL_LONG(ap_run_sub_req(rr)); + ap_destroy_sub_req(rr); +} +#endif + +/* {{{ proto string apache_get_version(void) + Fetch Apache version */ +PHP_FUNCTION(apache_get_version) +{ + char *apv = (char *) ap_get_server_version(); + + if (apv && *apv) { + RETURN_STRING(apv, 1); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ proto array apache_get_modules(void) + Get a list of loaded Apache modules */ +PHP_FUNCTION(apache_get_modules) +{ + int n; + char *p; + + array_init(return_value); + + for (n = 0; ap_loaded_modules[n]; ++n) { + char *s = (char *) ap_loaded_modules[n]->name; + if ((p = strchr(s, '.'))) { + add_next_index_stringl(return_value, s, (p - s), 1); + } else { + add_next_index_string(return_value, s, 1); + } + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/apache_hooks/php_apache_http.h b/sapi/apache_hooks/php_apache_http.h new file mode 100644 index 0000000..23cf7fe --- /dev/null +++ b/sapi/apache_hooks/php_apache_http.h @@ -0,0 +1,44 @@ +#define NO_REGEX_EXTRA_H + +#ifdef WIN32 +#include <winsock2.h> +#include <stddef.h> +#endif + +#ifdef NETWARE +#include <netinet/in.h> +#endif + +#include "zend.h" +#include "zend_stack.h" +#include "ext/ereg/php_regex.h" + +#include "httpd.h" +#include "http_config.h" + +#if MODULE_MAGIC_NUMBER > 19980712 +# include "ap_compat.h" +#else +# if MODULE_MAGIC_NUMBER > 19980324 +# include "compat.h" +# endif +#endif + +#include "http_core.h" +#include "http_main.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" +#include "util_script.h" + +#include "php_variables.h" +#include "php_main.h" +#include "php_ini.h" +#include "ext/standard/php_standard.h" + +#include "mod_php5.h" + + +zval *php_apache_request_new(request_rec *r); + +int apache_php_module_hook(request_rec *r, php_handler *handler, zval **ret TSRMLS_DC); diff --git a/sapi/apache_hooks/sapi_apache.c b/sapi/apache_hooks/sapi_apache.c new file mode 100644 index 0000000..a9c9d67 --- /dev/null +++ b/sapi/apache_hooks/sapi_apache.c @@ -0,0 +1,131 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@php.net> | + | (with helpful hints from Dean Gaudet <dgaudet@arctic.org> | + | PHP 4.0 patches by: | + | Zeev Suraski <zeev@zend.com> | + | Stig Bakken <ssb@php.net> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php_apache_http.h" + +/* {{{ apache_php_module_main + */ +int apache_php_module_main(request_rec *r, int display_source_mode TSRMLS_DC) +{ + zend_file_handle file_handle; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return FAILURE; + } + /* sending a file handle to another dll is not working + so let zend open it. */ + + if (display_source_mode) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + if (highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC)){ + return OK; + } else { + return NOT_FOUND; + } + } else { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.handle.fd = 0; + file_handle.filename = SG(request_info).path_translated; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + (void) php_execute_script(&file_handle TSRMLS_CC); + } + AP(in_request) = 0; + + return (OK); +} +/* }}} */ + +/* {{{ apache_php_module_hook + */ +int apache_php_module_hook(request_rec *r, php_handler *handler, zval **ret TSRMLS_DC) +{ + zend_file_handle file_handle; + zval *req; + char *tmp; + +#if PHP_SIGCHILD + signal(SIGCHLD, sigchld_handler); +#endif + if(AP(current_hook) == AP_RESPONSE) { + if (php_request_startup_for_hook(TSRMLS_C) == FAILURE) + return FAILURE; + } + else { + if (php_request_startup_for_hook(TSRMLS_C) == FAILURE) + return FAILURE; + } + + req = php_apache_request_new(r); + php_register_variable_ex("request", req, PG(http_globals)[TRACK_VARS_SERVER] TSRMLS_CC); + + switch(handler->type) { + case AP_HANDLER_TYPE_FILE: + php_register_variable("PHP_SELF_HOOK", handler->name, PG(http_globals)[TRACK_VARS_SERVER] TSRMLS_CC); + memset(&file_handle, 0, sizeof(file_handle)); + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = handler->name; + (void) php_execute_simple_script(&file_handle, ret TSRMLS_CC); + break; + case AP_HANDLER_TYPE_METHOD: + if( (tmp = strstr(handler->name, "::")) != NULL && *(tmp+2) != '\0' ) { + zval *class; + zval *method; + *tmp = '\0'; + ALLOC_ZVAL(class); + ZVAL_STRING(class, handler->name, 1); + ALLOC_ZVAL(method); + ZVAL_STRING(method, tmp +2, 1); + *tmp = ':'; + call_user_function_ex(EG(function_table), &class, method, ret, 0, NULL, 0, NULL TSRMLS_CC); + zval_dtor(class); + zval_dtor(method); + } + else { + php_error(E_ERROR, "Unable to call %s - not a Class::Method\n", handler->name); + /* not a class::method */ + } + break; + default: + /* not a valid type */ + assert(0); + break; + } + zval_dtor(req); + AP(in_request) = 0; + + return OK; +} + +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/caudium/CREDITS b/sapi/caudium/CREDITS new file mode 100644 index 0000000..45789db --- /dev/null +++ b/sapi/caudium/CREDITS @@ -0,0 +1,2 @@ +Caudium / Roxen +David Hedbor diff --git a/sapi/caudium/README b/sapi/caudium/README new file mode 100644 index 0000000..86ef656 --- /dev/null +++ b/sapi/caudium/README @@ -0,0 +1,16 @@ +Embedded Caudium PHP support. It seems to work but isn't tested +much. Due to a design choice it requires _Pike_ threads and a +thread-safe PHP. It doesn't however require _Caudium_ to run in +threaded mode. Due to the design, utilization of multiple processors +should be pretty good. + +It requires a new Pike 7.0 and Caudium. It will not work with +Roxen. Also, with the help of the VIRTUAL_DIR code in PHP, PHP +execution should be very similar to that of mod_php or the cgi +script. + +If you have any questions, please contact me, David Hedbor +(neotron@php.net or neotron@caudium.net). For more information on +Caudium, see http://caudium.net/ and http://caudium.org/. + + diff --git a/sapi/caudium/TODO b/sapi/caudium/TODO new file mode 100644 index 0000000..b8217c5 --- /dev/null +++ b/sapi/caudium/TODO @@ -0,0 +1,30 @@ +TODO: + +- per-virtual-server configuration +- configurable limit of number of concurrent PHP executions +- fix setting of auth info. + +FIXED: ++ => fixed +- => not fixed and no fix planned +? => Maybe fixed, maybe not. + ++ Allow multiple headers + This is now fixed. ++ fix backtraces + Uses th_farm, thus problem is fixed ++ exit in PHP exits Caudium + Name conflict with do_exit in Pike and PHP. Reported to both teams. ++ POST newline added? + Was a Caudium bug. ++ use th_farm + Yeppers. Works great. +- change cwd in single threaded mode + There will be no single threaded mode support. The Caudium module + will requite PHP ZTS and Pike threads to run. Single threaded PHP + is rather uninteresting anyway. +? Recursive mutex lock problem: + Dunno if this is fixed. Major rewrite so it would have to be + retested. + + diff --git a/sapi/caudium/caudium.c b/sapi/caudium/caudium.c new file mode 100644 index 0000000..d3b834f --- /dev/null +++ b/sapi/caudium/caudium.c @@ -0,0 +1,784 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: David Hedbor <neotron@php.net> | + | Based on aolserver SAPI by Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include "php.h" +#ifdef HAVE_CAUDIUM + +#include "php_ini.h" +#include "php_globals.h" +#include "SAPI.h" +#include "php_main.h" +#include "ext/standard/info.h" + +#include "php_version.h" + +/* Pike Include Files + * + * conflicts with pike avoided by only using long names. Requires a new + * Pike 0.7 since it was implemented for this interface only. + * + */ +#define NO_PIKE_SHORTHAND + +/* Ok, we are now using Pike level threads to handle PHP5 since + * the nice th_farm threads aren't working on Linux with glibc 2.2 + * (why this is I don't know). + */ +#define USE_PIKE_LEVEL_THREADS + +#include <fdlib.h> +#include <program.h> +#include <pike_types.h> +#include <interpret.h> +#include <module_support.h> +#include <array.h> +#include <backend.h> +#include <stralloc.h> +#include <mapping.h> +#include <object.h> +#include <threads.h> +#include <builtin_functions.h> +#include <operators.h> +#include <version.h> + +#if (PIKE_MAJOR_VERSION == 7 && PIKE_MINOR_VERSION == 1 && PIKE_BUILD_VERSION >= 12) || PIKE_MAJOR_VERSION > 7 || (PIKE_MAJOR_VERSION == 7 && PIKE_MINOR_VERSION > 1) +# include "pike_error.h" +#else +# include "error.h" +# ifndef Pike_error +# define Pike_error error +# endif +#endif + +/* Pike 7.x and newer */ +#define MY_MAPPING_LOOP(md, COUNT, KEY) \ + for(COUNT=0;COUNT < md->data->hashsize; COUNT++ ) \ + for(KEY=md->data->hash[COUNT];KEY;KEY=KEY->next) + +#ifndef ZTS +/* Need thread safety */ +#error You need to compile PHP with threads. +#endif + +#ifndef PIKE_THREADS +#error The PHP5 module requires that your Pike has thread support. +#endif + +#undef HIDE_GLOBAL_VARIABLES +#undef REVEAL_GLOBAL_VARIABLES +#define HIDE_GLOBAL_VARIABLES() +#define REVEAL_GLOBAL_VARIABLES() + +/* php_caudium_request is per-request object storage */ + +typedef struct +{ + struct mapping *request_data; + struct object *my_fd_obj; + struct svalue done_cb; + struct pike_string *filename; + int my_fd; + int written; + TSRMLS_D; +} php_caudium_request; + + +void pike_module_init(void); +void pike_module_exit(void); +static void free_struct(TSRMLS_D); +void f_php_caudium_request_handler(INT32 args); + +/* Defines to get to the data supplied when the script is started. */ + +/* Per thread storage area id... */ +static int caudium_globals_id; + +#define GET_THIS() php_caudium_request *_request = ts_resource(caudium_globals_id) +#define THIS _request +#define PTHIS ((php_caudium_request *)(Pike_fp->current_storage)) +/* File descriptor integer. Used to write directly to the FD without + * passing Pike + */ +#define MY_FD (THIS->my_fd) + +/* FD object. Really a PHPScript object from Pike which implements a couple + * of functions to handle headers, writing and buffering. + */ +#define MY_FD_OBJ ((struct object *)(THIS->my_fd_obj)) + +/* Mapping with data supplied from the calling Caudium module. Contains + * a mapping with headers, an FD object etc. + */ +#define REQUEST_DATA ((struct mapping *)(THIS->request_data)) + +extern int fd_from_object(struct object *o); +static unsigned char caudium_php_initialized; + +#ifndef mt_lock_interpreter +#define mt_lock_interpreter() mt_lock(&interpreter_lock); +#define mt_unlock_interpreter() mt_unlock(&interpreter_lock); +#endif + + +/* This allows calling of pike functions from the PHP callbacks, + * which requires the Pike interpreter to be locked. + */ +#define THREAD_SAFE_RUN(COMMAND, what) do {\ + struct thread_state *state;\ + if((state = thread_state_for_id(th_self()))!=NULL) {\ + if(!state->swapped) {\ + COMMAND;\ + } else {\ + mt_lock_interpreter();\ + SWAP_IN_THREAD(state);\ + COMMAND;\ + SWAP_OUT_THREAD(state);\ + mt_unlock_interpreter();\ + }\ + }\ +} while(0) + + + +/* Low level header lookup. Basically looks for the named header in the mapping + * headers in the supplied options mapping. + */ + +INLINE static struct svalue *lookup_header(char *headername) +{ + struct svalue *headers, *value; + struct pike_string *sind; + GET_THIS(); + sind = make_shared_string("env"); + headers = low_mapping_string_lookup(REQUEST_DATA, sind); + free_string(sind); + if(!headers || headers->type != PIKE_T_MAPPING) return NULL; + sind = make_shared_string(headername); + value = low_mapping_string_lookup(headers->u.mapping, sind); + free_string(sind); + if(!value) return NULL; + return value; +} + +/* Lookup a header in the mapping and return the value as a string, or + * return the default if it's missing + */ +INLINE static char *lookup_string_header(char *headername, char *default_value) +{ + struct svalue *head = NULL; + THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup"); + if(!head || head->type != PIKE_T_STRING) + return default_value; + return head->u.string->str; +} + +/* Lookup a header in the mapping and return the value as if it's an integer + * and otherwise return the default. + */ +INLINE static int lookup_integer_header(char *headername, int default_value) +{ + struct svalue *head = NULL; + THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup"); + if(!head || head->type != PIKE_T_INT) + return default_value; + return head->u.integer; +} + +/* + * php_caudium_low_ub_write() writes data to the client connection. Might be + * rewritten to do more direct IO to save CPU and the need to lock the + * interpreter for better threading. + */ + +INLINE static int +php_caudium_low_ub_write(const char *str, uint str_length TSRMLS_DC) { + int sent_bytes = 0; + struct pike_string *to_write = NULL; + GET_THIS(); + if(!MY_FD_OBJ->prog) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return -1; + } + to_write = make_shared_binary_string(str, str_length); + push_string(to_write); + safe_apply(MY_FD_OBJ, "write", 1); + if(Pike_sp[-1].type == PIKE_T_INT) + sent_bytes = Pike_sp[-1].u.integer; + pop_stack(); + if(sent_bytes != str_length) { + /* This means the connection is closed. Dead. Gone. *sniff* */ + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + } + return sent_bytes; +} + +/* + * php_caudium_sapi_ub_write() calls php_caudium_low_ub_write in a Pike thread + * safe manner or writes directly to the output FD if RXML post-parsing is + * disabled. + */ + +static int +php_caudium_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + GET_THIS(); + int sent_bytes = 0, fd = MY_FD; + if(fd) + { + for(sent_bytes=0;sent_bytes < str_length;) + { + int written; + written = fd_write(fd, str + sent_bytes, str_length - sent_bytes); + if(written < 0) + { + switch(errno) + { + default: + /* This means the connection is closed. Dead. Gone. *sniff* */ + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + THIS->written += sent_bytes; + return sent_bytes; + case EINTR: + case EWOULDBLOCK: + continue; + } + } else { + sent_bytes += written; + } + } + THIS->written += sent_bytes; + } else { + THREAD_SAFE_RUN(sent_bytes = php_caudium_low_ub_write(str, str_length TSRMLS_CC), + "write"); + } + return sent_bytes; +} + +/* php_caudium_set_header() sets a header in the header mapping. Called in a + * thread safe manner from php_caudium_sapi_header_handler. + */ +INLINE static void +php_caudium_set_header(char *header_name, char *value, char *p) +{ + struct svalue hsval; + struct pike_string *hval, *ind, *hind; + struct mapping *headermap; + struct svalue *s_headermap, *soldval; + int vallen; + GET_THIS(); + /* hval = make_shared_string(value); */ + ind = make_shared_string(" _headers"); + hind = make_shared_binary_string(header_name, + (int)(p - header_name)); + + s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind); + if(!s_headermap || s_headermap->type != PIKE_T_MAPPING) + { + struct svalue mappie; + mappie.type = PIKE_T_MAPPING; + headermap = allocate_mapping(1); + mappie.u.mapping = headermap; + mapping_string_insert(REQUEST_DATA, ind, &mappie); + free_mapping(headermap); + hval = make_shared_string(value); + } else { + headermap = s_headermap->u.mapping; + soldval = low_mapping_string_lookup(headermap, hind); + vallen = strlen(value); + if(soldval != NULL && + soldval->type == PIKE_T_STRING && + soldval->u.string->size_shift == 0) { + /* Existing, valid header. Prepend.*/ + hval = begin_shared_string(soldval->u.string->len + 1 + vallen); + MEMCPY(hval->str, soldval->u.string->str, soldval->u.string->len); + STR0(hval)[soldval->u.string->len] = '\0'; + MEMCPY(hval->str+soldval->u.string->len+1, value, vallen); + hval = end_shared_string(hval); + } else { + hval = make_shared_string(value); + } + } + hsval.type = PIKE_T_STRING; + hsval.u.string = hval; + + mapping_string_insert(headermap, hind, &hsval); + + free_string(hval); + free_string(ind); + free_string(hind); +} + +/* + * php_caudium_sapi_header_handler() sets a HTTP reply header to be + * sent to the client. + */ +static int +php_caudium_sapi_header_handler(sapi_header_struct *sapi_header, + sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + header_name = sapi_header->header; + header_content = p = strchr(header_name, ':'); + + if(p) { + do { + header_content++; + } while(*header_content == ' '); + THREAD_SAFE_RUN(php_caudium_set_header(header_name, header_content, p), "header handler"); + } + sapi_free_header(sapi_header); + return 0; +} + +/* + * php_caudium_sapi_send_headers() flushes the headers to the client. + * Called before real content is sent by PHP. + */ + +INLINE static int +php_caudium_low_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + struct pike_string *ind; + struct svalue *s_headermap; + GET_THIS(); + if(!MY_FD_OBJ->prog) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return SAPI_HEADER_SEND_FAILED; + } + ind = make_shared_string(" _headers"); + s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind); + free_string(ind); + + push_int(SG(sapi_headers).http_response_code); + if(s_headermap && s_headermap->type == PIKE_T_MAPPING) + ref_push_mapping(s_headermap->u.mapping); + else + push_int(0); + safe_apply(MY_FD_OBJ, "send_headers", 2); + pop_stack(); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int +php_caudium_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + int res = 0; + THREAD_SAFE_RUN(res = php_caudium_low_send_headers(sapi_headers TSRMLS_CC), "send headers"); + return res; +} + +/* + * php_caudium_sapi_read_post() reads a specified number of bytes from + * the client. Used for POST/PUT requests. + */ + +INLINE static int php_caudium_low_read_post(char *buf, uint count_bytes) +{ + uint total_read = 0; + GET_THIS(); + TSRMLS_FETCH(); + + if(!MY_FD_OBJ->prog) + { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return -1; + } + push_int(count_bytes); + safe_apply(MY_FD_OBJ, "read_post", 1); + if(Pike_sp[-1].type == PIKE_T_STRING) { + MEMCPY(buf, Pike_sp[-1].u.string->str, + (total_read = Pike_sp[-1].u.string->len)); + buf[total_read] = '\0'; + } else + total_read = 0; + pop_stack(); + return total_read; +} + +static int +php_caudium_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ + uint total_read = 0; + THREAD_SAFE_RUN(total_read = php_caudium_low_read_post(buf, count_bytes), "read post"); + return total_read; +} + +/* + * php_caudium_sapi_read_cookies() returns the Cookie header from + * the HTTP request header + */ + +static char * +php_caudium_sapi_read_cookies(TSRMLS_D) +{ + char *cookies; + cookies = lookup_string_header("HTTP_COOKIE", NULL); + return cookies; +} + +static void php_info_caudium(ZEND_MODULE_INFO_FUNC_ARGS) +{ + /* char buf[512]; */ + php_info_print_table_start(); + php_info_print_table_row(2, "SAPI module version", "$Id$"); + /* php_info_print_table_row(2, "Build date", Ns_InfoBuildDate()); + php_info_print_table_row(2, "Config file path", Ns_InfoConfigFile()); + php_info_print_table_row(2, "Error Log path", Ns_InfoErrorLog()); + php_info_print_table_row(2, "Installation path", Ns_InfoHomePath()); + php_info_print_table_row(2, "Hostname of server", Ns_InfoHostname()); + php_info_print_table_row(2, "Source code label", Ns_InfoLabel()); + php_info_print_table_row(2, "Server platform", Ns_InfoPlatform()); + snprintf(buf, 511, "%s/%s", Ns_InfoServerName(), Ns_InfoServerVersion()); + php_info_print_table_row(2, "Server version", buf); + snprintf(buf, 511, "%d day(s), %02d:%02d:%02d", + uptime / 86400, + (uptime / 3600) % 24, + (uptime / 60) % 60, + uptime % 60); + php_info_print_table_row(2, "Server uptime", buf); + */ + php_info_print_table_end(); +} + +static zend_module_entry php_caudium_module = { + STANDARD_MODULE_HEADER, + "Caudium", + NULL, + NULL, + NULL, + NULL, + NULL, + php_info_caudium, + NULL, + STANDARD_MODULE_PROPERTIES +}; + + +INLINE static void low_sapi_caudium_register_variables(zval *track_vars_array TSRMLS_DC) +{ + int i; + struct keypair *k; + struct svalue *headers; + struct pike_string *sind; + struct svalue *ind; + struct svalue *val; + GET_THIS(); + php_register_variable("PHP_SELF", SG(request_info).request_uri, + track_vars_array TSRMLS_CC); + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", + track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_METHOD", + (char *) SG(request_info).request_method, + track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_URI", SG(request_info).request_uri, + track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", SG(request_info).path_translated, + track_vars_array TSRMLS_CC); + + sind = make_shared_string("env"); + headers = low_mapping_string_lookup(REQUEST_DATA, sind); + free_string(sind); + if(headers && headers->type == PIKE_T_MAPPING) { + MY_MAPPING_LOOP(headers->u.mapping, i, k) { + ind = &k->ind; + val = &k->val; + if(ind && ind->type == PIKE_T_STRING && + val && val->type == PIKE_T_STRING) { + php_register_variable(ind->u.string->str, val->u.string->str, + track_vars_array TSRMLS_CC ); + } + } + } +} + +static void sapi_caudium_register_variables(zval *track_vars_array TSRMLS_DC) +{ + THREAD_SAFE_RUN(low_sapi_caudium_register_variables(track_vars_array TSRMLS_CC), "register_variables"); +} + + +static int php_caudium_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_caudium_module, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + + +/* this structure is static (as in "it does not change") */ +static sapi_module_struct caudium_sapi_module = { + "caudium", + "Caudium", + php_caudium_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + NULL, /* activate */ + NULL, /* deactivate */ + php_caudium_sapi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + php_error, /* error handler */ + php_caudium_sapi_header_handler, /* header handler */ + php_caudium_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + php_caudium_sapi_read_post, /* read POST data */ + php_caudium_sapi_read_cookies, /* read cookies */ + sapi_caudium_register_variables, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +/* + * php_caudium_module_main() is called by the per-request handler and + * "executes" the script + */ + +static void php_caudium_module_main(php_caudium_request *ureq) +{ + int res; + zend_file_handle file_handle; +#ifndef USE_PIKE_LEVEL_THREADS + struct thread_state *state; + extern struct program *thread_id_prog; +#endif + TSRMLS_FETCH(); + GET_THIS(); + THIS->filename = ureq->filename; + THIS->done_cb = ureq->done_cb; + THIS->my_fd_obj = ureq->my_fd_obj; + THIS->my_fd = ureq->my_fd; + THIS->request_data = ureq->request_data; + free(ureq); + +#ifndef USE_PIKE_LEVEL_THREADS + mt_lock_interpreter(); + init_interpreter(); +#if PIKE_MAJOR_VERSION == 7 && PIKE_MINOR_VERSION < 1 + thread_id = low_clone(thread_id_prog); + state = OBJ2THREAD(thread_id); + Pike_stack_top=((char *)&state)+ (thread_stack_size-16384) * STACK_DIRECTION; + recoveries = NULL; + call_c_initializers(thread_id); + OBJ2THREAD(thread_id)->id=th_self(); + num_threads++; + thread_table_insert(thread_id); + state->status=THREAD_RUNNING; +#else + Pike_interpreter.thread_id = low_clone(thread_id_prog); + state = OBJ2THREAD(Pike_interpreter.thread_id); + Pike_interpreter.stack_top=((char *)&state)+ (thread_stack_size-16384) * STACK_DIRECTION; + Pike_interpreter.recoveries = NULL; + call_c_initializers(Pike_interpreter.thread_id); + state->id=th_self(); + /* SWAP_OUT_THREAD(OBJ2THREAD(Pike_interpreter.thread_id)); */ + num_threads++; + thread_table_insert(Pike_interpreter.thread_id); + state->status=THREAD_RUNNING; +#endif + state->swapped = 0; +#endif + SG(request_info).query_string = lookup_string_header("QUERY_STRING", 0); + SG(server_context) = (void *)1; /* avoid server_context == NULL */ + + /* path_translated is apparently the absolute path to the file, not + the translated PATH_INFO + */ + SG(request_info).path_translated = + lookup_string_header("SCRIPT_FILENAME", NULL); + SG(request_info).request_uri = lookup_string_header("DOCUMENT_URI", NULL); + if(!SG(request_info).request_uri) + SG(request_info).request_uri = lookup_string_header("SCRIPT_NAME", NULL); + SG(request_info).request_method = lookup_string_header("REQUEST_METHOD", "GET"); + SG(request_info).content_length = lookup_integer_header("HTTP_CONTENT_LENGTH", 0); + SG(request_info).content_type = lookup_string_header("HTTP_CONTENT_TYPE", NULL); + SG(sapi_headers).http_response_code = 200; + if (!strcmp(SG(request_info).request_method, "HEAD")) { + SG(request_info).headers_only = 1; + } else { + SG(request_info).headers_only = 0; + } + + /* Let PHP5 handle the deconding of the AUTH */ + php_handle_auth_data(lookup_string_header("HTTP_AUTHORIZATION", NULL), TSRMLS_C); + /* Swap out this thread and release the interpreter lock to allow + * Pike threads to run. We wait since the above would otherwise require + * a lot of unlock/lock. + */ +#ifndef USE_PIKE_LEVEL_THREADS + SWAP_OUT_THREAD(state); + mt_unlock_interpreter(); +#else + THREADS_ALLOW(); +#endif + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = THIS->filename->str; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + + THIS->written = 0; + res = php_request_startup(TSRMLS_C); + + if(res == FAILURE) { + THREAD_SAFE_RUN({ + apply_svalue(&THIS->done_cb, 0); + pop_stack(); + free_struct(TSRMLS_C); + }, "Negative run response"); + } else { + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + THREAD_SAFE_RUN({ + push_int(THIS->written); + apply_svalue(&THIS->done_cb, 1); + pop_stack(); + free_struct(TSRMLS_C); + }, "positive run response"); + } + +#ifndef USE_PIKE_LEVEL_THREADS + mt_lock_interpreter(); + SWAP_IN_THREAD(state); +#if PIKE_MAJOR_VERSION == 7 && PIKE_MINOR_VERSION < 1 + state->status=THREAD_EXITED; + co_signal(& state->status_change); + thread_table_delete(thread_id); + free_object(thread_id); + thread_id=NULL; +#else + state->status=THREAD_EXITED; + co_signal(& state->status_change); + thread_table_delete(Pike_interpreter.thread_id); + free_object(Pike_interpreter.thread_id); + Pike_interpreter.thread_id=NULL; +#endif + cleanup_interpret(); + num_threads--; + mt_unlock_interpreter(); +#else + THREADS_DISALLOW(); +#endif +} + +/* + * The php_caudium_request_handler() is called per request and handles + * everything for one request. + */ + +void f_php_caudium_request_handler(INT32 args) +{ + struct object *my_fd_obj; + struct mapping *request_data; + struct svalue *done_callback; + struct pike_string *script; + struct svalue *raw_fd; + struct pike_string *ind; + php_caudium_request *_request; + THIS = malloc(sizeof(php_caudium_request)); + if(THIS == NULL) + Pike_error("Out of memory."); + + get_all_args("PHP5.Interpreter->run", args, "%S%m%O%*", &script, + &request_data, &my_fd_obj, &done_callback); + if(done_callback->type != PIKE_T_FUNCTION) + Pike_error("PHP5.Interpreter->run: Bad argument 4, expected function.\n"); + add_ref(request_data); + add_ref(my_fd_obj); + add_ref(script); + + THIS->request_data = request_data; + THIS->my_fd_obj = my_fd_obj; + THIS->filename = script; + assign_svalue_no_free(&THIS->done_cb, done_callback); + + ind = make_shared_binary_string("my_fd", 5); + raw_fd = low_mapping_string_lookup(THIS->request_data, ind); + if(raw_fd && raw_fd->type == PIKE_T_OBJECT) + { + int fd = fd_from_object(raw_fd->u.object); + if(fd == -1) + THIS->my_fd = 0; /* Don't send directly to this FD... */ + else + THIS->my_fd = fd; + } else + THIS->my_fd = 0; +#ifdef USE_PIKE_LEVEL_THREADS + php_caudium_module_main(THIS); +#else + th_farm((void (*)(void *))php_caudium_module_main, THIS); +#endif + pop_n_elems(args); +} + +static void free_struct(TSRMLS_D) +{ + GET_THIS(); + if(THIS->request_data) free_mapping(THIS->request_data); + if(THIS->my_fd_obj) free_object(THIS->my_fd_obj); + free_svalue(&THIS->done_cb); + if(THIS->filename) free_string(THIS->filename); + MEMSET(THIS, 0, sizeof(php_caudium_request)); +} + + +/* + * pike_module_init() is called by Pike once at startup + * + * This functions allocates basic structures + */ + +void pike_module_init( void ) +{ + if (!caudium_php_initialized) { + caudium_php_initialized = 1; + tsrm_startup(1, 1, 0, NULL); + ts_allocate_id(&caudium_globals_id, sizeof(php_caudium_request), NULL, NULL); + sapi_startup(&caudium_sapi_module); + sapi_module.startup(&caudium_sapi_module); + } + start_new_program(); /* Text */ + pike_add_function("run", f_php_caudium_request_handler, + "function(string, mapping, object, function:void)", 0); + end_class("Interpreter", 0); +} + +/* + * pike_module_exit() performs the last steps before the + * server exists. Shutdowns basic services and frees memory + */ + +void pike_module_exit(void) +{ + caudium_php_initialized = 0; + sapi_module.shutdown(&caudium_sapi_module); + tsrm_shutdown(); +} +#endif diff --git a/sapi/caudium/config.m4 b/sapi/caudium/config.m4 new file mode 100644 index 0000000..8aba33e --- /dev/null +++ b/sapi/caudium/config.m4 @@ -0,0 +1,98 @@ +dnl +dnl $Id$ +dnl + +RESULT=no +PHP_ARG_WITH(caudium,, +[ --with-caudium[=DIR] Build PHP as a Pike module for use with Caudium. + DIR is the Caudium server dir [/usr/local/caudium/server]], no, no) + +AC_MSG_CHECKING([for Caudium support]) + +if test "$PHP_CAUDIUM" != "no"; then + if test "$prefix" = "NONE"; then CPREF=/usr/local/; fi + if test ! -d $PHP_CAUDIUM ; then + if test "$prefix" = "NONE"; then + PHP_CAUDIUM=/usr/local/caudium/server/ + else + PHP_CAUDIUM=$prefix/caudium/server/ + fi + fi + if test -f $PHP_CAUDIUM/bin/caudium; then + PIKE=$PHP_CAUDIUM/bin/caudium + elif test -f $PHP_CAUDIUM/bin/pike; then + PIKE=$PHP_CAUDIUM/bin/pike + else + AC_MSG_ERROR([Could not find a pike in $PHP_CAUDIUM/bin/]) + fi + if $PIKE -e 'float v; int rel;sscanf(version(), "Pike v%f release %d", v, rel);v += rel/10000.0; if(v < 7.0268) exit(1); exit(0);'; then + PIKE_MODULE_DIR=`$PIKE --show-paths 2>&1| grep '^Module' | sed -e 's/.*: //'` + PIKE_INCLUDE_DIR=`echo $PIKE_MODULE_DIR | sed -e 's,lib/pike/modules,include/pike,' -e 's,lib/modules,include/pike,' ` + if test -z "$PIKE_INCLUDE_DIR" || test -z "$PIKE_MODULE_DIR"; then + AC_MSG_ERROR(Failed to figure out Pike module and include directories) + fi + AC_MSG_RESULT(yes) + PIKE=`echo $PIKE | pike -e 'int tries=100; + string orig,pike=Stdio.File("stdin")->read()-"\n"; + orig=pike; + if(search(orig, "/")) + orig = combine_path(getcwd(), orig); + while(!catch(pike=readlink(pike)) && tries--) + ; + write(combine_path(dirname(orig), pike)); '` + PHP_ADD_INCLUDE($PIKE_INCLUDE_DIR) + if test "$prefix" != "NONE"; then + PIKE_C_INCLUDE=$prefix/include/`basename $PIKE` + else + PIKE_C_INCLUDE=/usr/local/include/`basename $PIKE` + fi + AC_MSG_CHECKING([for C includes in $PIKE_C_INCLUDE]) + if test -f $PIKE_C_INCLUDE/version.h; then + PIKE_TEST_VER=`$PIKE -e 'string v; int rel;sscanf(version(), "Pike v%s release %d", v, rel); write(v+"."+rel);'` + ###### VERSION MATCH CHECK ####### + PMAJOR="^#define PIKE_MAJOR_VERSION" + PMINOR="^#define PIKE_MINOR_VERSION" + PBUILD="^#define PIKE_BUILD_VERSION" + + PIKE_CMAJOR_VERSION=0 + PIKE_CMINOR_VERSION=0 + PIKE_CBUILD_VERSION=0 + + PIKE_CMAJOR_VERSION=`grep "$PMAJOR" $PIKE_C_INCLUDE/version.h | sed -e 's/\(#define.*N \)\(.*\)/\2/'` + if test -z "$PIKE_CMAJOR_VERSION"; then + if test -n "`grep f_version $PIKE_C_INCLUDE/version.h`"; then + PIKE_CMAJOR_VERSION=6 + fi + else + PIKE_CMINOR_VERSION=`grep "$PMINOR" $PIKE_C_INCLUDE/version.h | sed -e 's/\(#define.*N \)\(.*\)/\2/'` + PIKE_CBUILD_VERSION=`grep "$PBUILD" $PIKE_C_INCLUDE/version.h | sed -e 's/\(#define.*N \)\(.*\)/\2/'` + fi + + if test "$PIKE_TEST_VER" = "${PIKE_CMAJOR_VERSION}.${PIKE_CMINOR_VERSION}.${PIKE_CBUILD_VERSION}"; then + PHP_ADD_INCLUDE($PIKE_C_INCLUDE) + PIKE_INCLUDE_DIR="$PIKE_INCLUDE_DIR, $PIKE_C_INCLUDE" + AC_MSG_RESULT(found) + else + AC_MSG_RESULT(version mismatch) + fi + else + AC_MSG_RESULT(not found) + fi + else + AC_MSG_ERROR([Caudium PHP5 requires Pike 7.0 or newer]) + fi + PIKE_VERSION=`$PIKE -e 'string v; int rel;sscanf(version(), "Pike v%s release %d", v, rel); write(v+"."+rel);'` + AC_DEFINE(HAVE_CAUDIUM,1,[Whether to compile with Caudium support]) + PHP_SELECT_SAPI(caudium, shared, caudium.c) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED $PHP_CAUDIUM/lib/$PIKE_VERSION/PHP5.so" + RESULT=" *** Pike binary used: $PIKE + *** Pike include dir(s) used: $PIKE_INCLUDE_DIR + *** Pike version: $PIKE_VERSION" + dnl Always use threads since thread-free support really blows. + PHP_BUILD_THREAD_SAFE +fi +AC_MSG_RESULT($RESULT) + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/cgi/CHANGES b/sapi/cgi/CHANGES new file mode 100755 index 0000000..de02947 --- /dev/null +++ b/sapi/cgi/CHANGES @@ -0,0 +1,34 @@ +In PHP5.3 all additional configure options (except --enable-cgi) are removed: + + --enable-fastcgi CGI: If this is enabled, the cgi module will + be built with support for fastcgi also + + Now fastcgi is always enabled + + --disable-path-info-check CGI: If this is disabled, paths such as + /info.php/test?a=b will fail to work + + Now it is enabled by default, but can be disabled + with ini directive "cgi.fix_pathinfo=0" + + --enable-force-cgi-redirect + CGI: Enable the security check for internal server + redirects. You should use this if you are + running the CGI version with Apache + + Now it is enabled by default, but can be disabled + with ini directive "cgi.force_redirect=0" + + --enable-discard-path CGI: If this is enabled, the PHP CGI binary + can safely be placed outside of the + web tree and people will not be able + to circumvent .htaccess security + + This option had effect only with + --disable-path-info-check or "cgi.fix_pathinfo=0". + Seems it needs only for CGI configuration that + require each script start from "#! /usr/bin/php". + + Now it is disabled by default, but can be enabled + with ini directive "cgi.discard_path=1". + diff --git a/sapi/cgi/CREDITS b/sapi/cgi/CREDITS new file mode 100644 index 0000000..1a2ec49 --- /dev/null +++ b/sapi/cgi/CREDITS @@ -0,0 +1,2 @@ +CGI / FastCGI +Rasmus Lerdorf, Stig Bakken, Shane Caraveo, Dmitry Stogov diff --git a/sapi/cgi/Makefile.frag b/sapi/cgi/Makefile.frag new file mode 100644 index 0000000..505119e --- /dev/null +++ b/sapi/cgi/Makefile.frag @@ -0,0 +1,9 @@ +cgi: $(SAPI_CGI_PATH) + +$(SAPI_CGI_PATH): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_CGI_OBJS) + $(BUILD_CGI) + +install-cgi: $(SAPI_CGI_PATH) + @echo "Installing PHP CGI binary: $(INSTALL_ROOT)$(bindir)/" + @$(INSTALL) -m 0755 $(SAPI_CGI_PATH) $(INSTALL_ROOT)$(bindir)/$(program_prefix)php-cgi$(program_suffix)$(EXEEXT) + diff --git a/sapi/cgi/README.FastCGI b/sapi/cgi/README.FastCGI new file mode 100644 index 0000000..3dda295 --- /dev/null +++ b/sapi/cgi/README.FastCGI @@ -0,0 +1,151 @@ +Credits: +Ben Mansell, Stephen Landamore, Daniel Silverstone, Shane Caraveo + +Building PHP +------------ + +You must add '--enable-fastcgi' to the configure command on Linux or +OSX based systems to get fastcgi support in the php-cgi binary. You +also must not use '--enable-discard-path'. + +Running the FastCGI PHP module +------------------------------ + +There are two ways to run the resulting 'php' binary after the fastcgi +version has been built: + +1) Configure your web server to run the PHP binary itself. + +This is the simplest method, obviously you will have to configure your +web server appropriately. Some web servers may also not support this method, +or may not be as efficient. + +2) Run PHP separately from the web server. + +In this setup, PHP is started as a separate process entirely from the web +server. It will listen on a socket for new FastCGI requests, and deliver +PHP pages as appropriate. This is the recommended way of running PHP-FastCGI. +To run this way, you must start the PHP binary running by giving it an IP +and a port number to listen to on the command line, e.g.: + + ./php -b 127.0.0.1:8002 + +The above line is the recommended way of running FastCGI. You usually +want the FastCGI server to provide services to the localhost, not +everyone on the Internet. + +If your web server sits on a remote host, you can make FastCGI listen +on all interfaces: + + ./php -b :8002 + ./php -b "*:8002" + +Note that hostnames are not supported. + +You must also configure your web server to connect to the appropriate port +in order to talk to the PHP FastCGI process. + +The advantage of running PHP in this way is that it entirely separates the +web server and PHP process, so that one cannot disrupt the other. It also +allows PHP to be on an entirely separate machine from the web server if need +be, you could even have several web servers utilising the same running PHP +process if required! + + +Using FastCGI PHP with Apache +============================= + +First of all, you may well ask 'Why?'. After all, Apache already has mod_php. +However, there are advantages to running PHP with FastCGI. Separating the +PHP code from the web server removes 'bloat' from the main server, and should +improve the performance of non-PHP requests. Secondly, having one permanent +PHP process as opposed to one per apache process means that shared resources +like persistent database connections are used more efficiently. + +First of all, make sure that the FastCGI module is enabled. You should have +a line in your config like: + + LoadModule fastcgi_module /usr/lib/apache/2.0/mod_fastcgi.so + +Don't load mod_php, by the way. Make sure it is commented out! + + #LoadModule php5_module /usr/lib/apache/2.0/libphp5.so + +Now, we'll create a fcgi-bin directory, just like you would do with normal +CGI scripts. You'll need to create a directory somewhere to store your +FastCGI binaries. We'll use /space/fcgi-bin/ for this example. Remember to +copy the FastCGI-PHP binary in there. (named 'php-cgi') This sets up +php to run under mod_fastcgi as a dynamic server. + + ScriptAlias /fcgi-bin/ /space/fcgi-bin/ + <Location /fcgi-bin/> + Options ExecCGI + SetHandler fastcgi-script + </Location> + +To setup a specific static configuration for php, you have to use +the FastCgiServer configuration for mod_fastcgi. For this, do not +use the above configuration, but rather the following. +(see mod_fastcgi docs for more configuration information): + + Alias /fcgi-bin/ /space/fcgi-bin/ + FastCgiServer /path/to/php-cgi -processes 5 + +For either of the above configurations, we need to tell Apache to +use the FastCGI binary /fcgi-bin/php to deliver PHP pages. +All that is needed is: + + AddType application/x-httpd-fastphp .php + Action application/x-httpd-fastphp /fcgi-bin/php-cgi + +Now, if you restart Apache, php pages should now be delivered! + +Using FastCGI PHP with IIS or iPlanet +===================================== + +FastCGI server plugins are available at www.caraveo.com/fastcgi/ +Documentation on these are sparse. iPlanet is not very tested, +and no makefile exists yet for unix based iPlanet servers. + + +Security +-------- + +Be sure to run the php binary as an appropriate userid. Also, firewall out +the port that PHP is listening on. In addition, you can set the environment +variable FCGI_WEB_SERVER_ADDRS to control who can connect to the FastCGI. +Set it to a comma separated list of IP addresses, e.g.: + +export FCGI_WEB_SERVER_ADDRS=199.170.183.28,199.170.183.71 + + +Tuning +------ + +There are a few tuning parameters that can be tweaked to control the +performance of FastCGI PHP. The following are environment variables that can +be set before running the PHP binary: + +PHP_FCGI_CHILDREN (default value: 0) + +This controls how many child processes the PHP process spawns. When the +fastcgi starts, it creates a number of child processes which handle one +page request at a time. Value 0 means that PHP willnot start additional +processes and main process will handle FastCGI requests by itself. Note that +this process may die (because of PHP_FCGI_MAX_REQUESTS) and it willnot +respawned automatic. Values 1 and above force PHP start additioanl processes +those will handle requests. The main process will restart children in case of +their death. So by default, you will be able to handle 1 concurrent PHP page +requests. Further requests will be queued. Increasing this number will allow +for better concurrency, especially if you have pages that take a significant +time to create, or supply a lot of data (e.g. downloading huge files via PHP). +On the other hand, having more processes running will use more RAM, and letting +too many PHP pages be generated concurrently will mean that each request will +be slow. + +PHP_FCGI_MAX_REQUESTS (default value: 500) + +This controls how many requests each child process will handle before +exitting. When one process exits, another will be created. This tuning is +necessary because several PHP functions are known to have memory leaks. If the +PHP processes were left around forever, they would be become very inefficient. diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c new file mode 100644 index 0000000..c8dfec0 --- /dev/null +++ b/sapi/cgi/cgi_main.c @@ -0,0 +1,2606 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Bakken <ssb@php.net> | + | Zeev Suraski <zeev@zend.com> | + | FastCGI: Ben Mansell <php@slimyhorror.com> | + | Shane Caraveo <shane@caraveo.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_modules.h" + +#include "SAPI.h" + +#include <stdio.h> +#include "php.h" + +#ifdef PHP_WIN32 +# include "win32/time.h" +# include "win32/signal.h" +# include <process.h> +#endif + +#if HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if HAVE_SIGNAL_H +# include <signal.h> +#endif + +#if HAVE_SETLOCALE +# include <locale.h> +#endif + +#if HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif + +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "ext/standard/php_standard.h" +#include "ext/standard/url.h" + +#ifdef PHP_WIN32 +# include <io.h> +# include <fcntl.h> +# include "win32/php_registry.h" +#endif + +#ifdef __riscos__ +# include <unixlib/local.h> +int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; +#endif + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" + +#include "php_getopt.h" + +#include "fastcgi.h" + +#ifndef PHP_WIN32 +/* XXX this will need to change later when threaded fastcgi is implemented. shane */ +struct sigaction act, old_term, old_quit, old_int; +#endif + +static void (*php_php_import_environment_variables)(zval *array_ptr TSRMLS_DC); + +#ifndef PHP_WIN32 +/* these globals used for forking children on unix systems */ +/** + * Number of child processes that will get created to service requests + */ +static int children = 0; + + +/** + * Set to non-zero if we are the parent process + */ +static int parent = 1; + +/* Did parent received exit signals SIG_TERM/SIG_INT/SIG_QUIT */ +static int exit_signal = 0; + +/* Is Parent waiting for children to exit */ +static int parent_waiting = 0; + +/** + * Process group + */ +static pid_t pgroup; +#endif + +#define PHP_MODE_STANDARD 1 +#define PHP_MODE_HIGHLIGHT 2 +#define PHP_MODE_INDENT 3 +#define PHP_MODE_LINT 4 +#define PHP_MODE_STRIP 5 + +static char *php_optarg = NULL; +static int php_optind = 1; +static zend_module_entry cgi_module_entry; + +static const opt_struct OPTIONS[] = { + {'a', 0, "interactive"}, + {'b', 1, "bindpath"}, + {'C', 0, "no-chdir"}, + {'c', 1, "php-ini"}, + {'d', 1, "define"}, + {'e', 0, "profile-info"}, + {'f', 1, "file"}, + {'h', 0, "help"}, + {'i', 0, "info"}, + {'l', 0, "syntax-check"}, + {'m', 0, "modules"}, + {'n', 0, "no-php-ini"}, + {'q', 0, "no-header"}, + {'s', 0, "syntax-highlight"}, + {'s', 0, "syntax-highlighting"}, + {'w', 0, "strip"}, + {'?', 0, "usage"},/* help alias (both '?' and 'usage') */ + {'v', 0, "version"}, + {'z', 1, "zend-extension"}, + {'T', 1, "timing"}, + {'-', 0, NULL} /* end of args */ +}; + +typedef struct _php_cgi_globals_struct { + zend_bool rfc2616_headers; + zend_bool nph; + zend_bool check_shebang_line; + zend_bool fix_pathinfo; + zend_bool force_redirect; + zend_bool discard_path; + zend_bool fcgi_logging; + char *redirect_status_env; +#ifdef PHP_WIN32 + zend_bool impersonate; +#endif + HashTable user_config_cache; +} php_cgi_globals_struct; + +/* {{{ user_config_cache + * + * Key for each cache entry is dirname(PATH_TRANSLATED). + * + * NOTE: Each cache entry config_hash contains the combination from all user ini files found in + * the path starting from doc_root throught to dirname(PATH_TRANSLATED). There is no point + * storing per-file entries as it would not be possible to detect added / deleted entries + * between separate files. + */ +typedef struct _user_config_cache_entry { + time_t expires; + HashTable *user_config; +} user_config_cache_entry; + +static void user_config_cache_entry_dtor(user_config_cache_entry *entry) +{ + zend_hash_destroy(entry->user_config); + free(entry->user_config); +} +/* }}} */ + +#ifdef ZTS +static int php_cgi_globals_id; +#define CGIG(v) TSRMG(php_cgi_globals_id, php_cgi_globals_struct *, v) +#else +static php_cgi_globals_struct php_cgi_globals; +#define CGIG(v) (php_cgi_globals.v) +#endif + +#ifdef PHP_WIN32 +#define TRANSLATE_SLASHES(path) \ + { \ + char *tmp = path; \ + while (*tmp) { \ + if (*tmp == '\\') *tmp = '/'; \ + tmp++; \ + } \ + } +#else +#define TRANSLATE_SLASHES(path) +#endif + +static int print_module_info(zend_module_entry *module, void *arg TSRMLS_DC) +{ + php_printf("%s\n", module->name); + return 0; +} + +static int module_name_cmp(const void *a, const void *b TSRMLS_DC) +{ + Bucket *f = *((Bucket **) a); + Bucket *s = *((Bucket **) b); + + return strcasecmp( ((zend_module_entry *)f->pData)->name, + ((zend_module_entry *)s->pData)->name); +} + +static void print_modules(TSRMLS_D) +{ + HashTable sorted_registry; + zend_module_entry tmp; + + zend_hash_init(&sorted_registry, 50, NULL, NULL, 1); + zend_hash_copy(&sorted_registry, &module_registry, NULL, &tmp, sizeof(zend_module_entry)); + zend_hash_sort(&sorted_registry, zend_qsort, module_name_cmp, 0 TSRMLS_CC); + zend_hash_apply_with_argument(&sorted_registry, (apply_func_arg_t) print_module_info, NULL TSRMLS_CC); + zend_hash_destroy(&sorted_registry); +} + +static int print_extension_info(zend_extension *ext, void *arg TSRMLS_DC) +{ + php_printf("%s\n", ext->name); + return 0; +} + +static int extension_name_cmp(const zend_llist_element **f, const zend_llist_element **s TSRMLS_DC) +{ + return strcmp( ((zend_extension *)(*f)->data)->name, + ((zend_extension *)(*s)->data)->name); +} + +static void print_extensions(TSRMLS_D) +{ + zend_llist sorted_exts; + + zend_llist_copy(&sorted_exts, &zend_extensions); + sorted_exts.dtor = NULL; + zend_llist_sort(&sorted_exts, extension_name_cmp TSRMLS_CC); + zend_llist_apply_with_argument(&sorted_exts, (llist_apply_with_arg_func_t) print_extension_info, NULL TSRMLS_CC); + zend_llist_destroy(&sorted_exts); +} + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +static inline size_t sapi_cgi_single_write(const char *str, uint str_length TSRMLS_DC) +{ +#ifdef PHP_WRITE_STDOUT + long ret; + + ret = write(STDOUT_FILENO, str, str_length); + if (ret <= 0) return 0; + return ret; +#else + size_t ret; + + ret = fwrite(str, 1, MIN(str_length, 16384), stdout); + return ret; +#endif +} + +static int sapi_cgi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + const char *ptr = str; + uint remaining = str_length; + size_t ret; + + while (remaining > 0) { + ret = sapi_cgi_single_write(ptr, remaining TSRMLS_CC); + if (!ret) { + php_handle_aborted_connection(); + return str_length - remaining; + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + +static int sapi_fcgi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + const char *ptr = str; + uint remaining = str_length; + fcgi_request *request = (fcgi_request*) SG(server_context); + + while (remaining > 0) { + long ret = fcgi_write(request, FCGI_STDOUT, ptr, remaining); + + if (ret <= 0) { + php_handle_aborted_connection(); + return str_length - remaining; + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + +static void sapi_cgi_flush(void *server_context) +{ + if (fflush(stdout) == EOF) { + php_handle_aborted_connection(); + } +} + +static void sapi_fcgi_flush(void *server_context) +{ + fcgi_request *request = (fcgi_request*) server_context; + + if ( +#ifndef PHP_WIN32 + !parent && +#endif + request && !fcgi_flush(request, 0)) { + + php_handle_aborted_connection(); + } +} + +#define SAPI_CGI_MAX_HEADER_LENGTH 1024 + +typedef struct _http_error { + int code; + const char* msg; +} http_error; + +static const http_error http_error_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"}, + {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} +}; + +static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char buf[SAPI_CGI_MAX_HEADER_LENGTH]; + sapi_header_struct *h; + zend_llist_position pos; + zend_bool ignore_status = 0; + int response_status = SG(sapi_headers).http_response_code; + + if (SG(request_info).no_headers == 1) { + return SAPI_HEADER_SENT_SUCCESSFULLY; + } + + if (CGIG(nph) || SG(sapi_headers).http_response_code != 200) + { + int len; + zend_bool has_status = 0; + + if (CGIG(rfc2616_headers) && SG(sapi_headers).http_status_line) { + char *s; + len = slprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH, "%s\r\n", SG(sapi_headers).http_status_line); + if ((s = strchr(SG(sapi_headers).http_status_line, ' '))) { + response_status = atoi((s + 1)); + } + + if (len > SAPI_CGI_MAX_HEADER_LENGTH) { + len = SAPI_CGI_MAX_HEADER_LENGTH; + } + + } else { + char *s; + + if (SG(sapi_headers).http_status_line && + (s = strchr(SG(sapi_headers).http_status_line, ' ')) != 0 && + (s - SG(sapi_headers).http_status_line) >= 5 && + strncasecmp(SG(sapi_headers).http_status_line, "HTTP/", 5) == 0 + ) { + len = slprintf(buf, sizeof(buf), "Status:%s\r\n", s); + response_status = atoi((s + 1)); + } else { + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if (h->header_len > sizeof("Status:")-1 && + strncasecmp(h->header, "Status:", sizeof("Status:")-1) == 0 + ) { + has_status = 1; + break; + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + if (!has_status) { + http_error *err = (http_error*)http_error_codes; + + while (err->code != 0) { + if (err->code == SG(sapi_headers).http_response_code) { + break; + } + err++; + } + if (err->msg) { + len = slprintf(buf, sizeof(buf), "Status: %d %s\r\n", SG(sapi_headers).http_response_code, err->msg); + } else { + len = slprintf(buf, sizeof(buf), "Status: %d\r\n", SG(sapi_headers).http_response_code); + } + } + } + } + + if (!has_status) { + PHPWRITE_H(buf, len); + ignore_status = 1; + } + } + + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + /* prevent CRLFCRLF */ + if (h->header_len) { + if (h->header_len > sizeof("Status:")-1 && + strncasecmp(h->header, "Status:", sizeof("Status:")-1) == 0 + ) { + if (!ignore_status) { + ignore_status = 1; + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } else if (response_status == 304 && h->header_len > sizeof("Content-Type:")-1 && + strncasecmp(h->header, "Content-Type:", sizeof("Content-Type:")-1) == 0 + ) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } else { + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + PHPWRITE_H("\r\n", 2); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +#endif + +static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + uint read_bytes = 0; + int tmp_read_bytes; + + count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes)); + while (read_bytes < count_bytes) { + tmp_read_bytes = read(STDIN_FILENO, buffer + read_bytes, count_bytes - read_bytes); + if (tmp_read_bytes <= 0) { + break; + } + read_bytes += tmp_read_bytes; + } + return read_bytes; +} + +static int sapi_fcgi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + uint read_bytes = 0; + int tmp_read_bytes; + fcgi_request *request = (fcgi_request*) SG(server_context); + + count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes)); + while (read_bytes < count_bytes) { + tmp_read_bytes = fcgi_read(request, buffer + read_bytes, count_bytes - read_bytes); + if (tmp_read_bytes <= 0) { + break; + } + read_bytes += tmp_read_bytes; + } + return read_bytes; +} + +static char *sapi_cgi_getenv(char *name, size_t name_len TSRMLS_DC) +{ + return getenv(name); +} + +static char *sapi_fcgi_getenv(char *name, size_t name_len TSRMLS_DC) +{ + /* when php is started by mod_fastcgi, no regular environment + * is provided to PHP. It is always sent to PHP at the start + * of a request. So we have to do our own lookup to get env + * vars. This could probably be faster somehow. */ + fcgi_request *request = (fcgi_request*) SG(server_context); + char *ret = fcgi_getenv(request, name, name_len); + + if (ret) return ret; + /* if cgi, or fastcgi and not found in fcgi env + check the regular environment */ + return getenv(name); +} + +static char *_sapi_cgi_putenv(char *name, int name_len, char *value) +{ +#if !HAVE_SETENV || !HAVE_UNSETENV + int len; + char *buf; +#endif + +#if HAVE_SETENV + if (value) { + setenv(name, value, 1); + } +#endif +#if HAVE_UNSETENV + if (!value) { + unsetenv(name); + } +#endif + +#if !HAVE_SETENV || !HAVE_UNSETENV + /* if cgi, or fastcgi and not found in fcgi env + check the regular environment + this leaks, but it's only cgi anyway, we'll fix + it for 5.0 + */ + len = name_len + (value ? strlen(value) : 0) + sizeof("=") + 2; + buf = (char *) malloc(len); + if (buf == NULL) { + return getenv(name); + } +#endif +#if !HAVE_SETENV + if (value) { + len = slprintf(buf, len - 1, "%s=%s", name, value); + putenv(buf); + } +#endif +#if !HAVE_UNSETENV + if (!value) { + len = slprintf(buf, len - 1, "%s=", name); + putenv(buf); + } +#endif + return getenv(name); +} + +static char *sapi_cgi_read_cookies(TSRMLS_D) +{ + return getenv("HTTP_COOKIE"); +} + +static char *sapi_fcgi_read_cookies(TSRMLS_D) +{ + fcgi_request *request = (fcgi_request*) SG(server_context); + + return FCGI_GETENV(request, "HTTP_COOKIE"); +} + +static void cgi_php_load_env_var(char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg TSRMLS_DC) +{ + zval *array_ptr = (zval*)arg; + int filter_arg = (array_ptr == PG(http_globals)[TRACK_VARS_ENV])?PARSE_ENV:PARSE_SERVER; + unsigned int new_val_len; + + if (sapi_module.input_filter(filter_arg, var, &val, strlen(val), &new_val_len TSRMLS_CC)) { + php_register_variable_safe(var, val, new_val_len, array_ptr TSRMLS_CC); + } +} + +static void cgi_php_import_environment_variables(zval *array_ptr TSRMLS_DC) +{ + if (PG(http_globals)[TRACK_VARS_ENV] && + array_ptr != PG(http_globals)[TRACK_VARS_ENV] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_ENV]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_ENV])) > 0 + ) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_ENV]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } else if (PG(http_globals)[TRACK_VARS_SERVER] && + array_ptr != PG(http_globals)[TRACK_VARS_SERVER] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER])) > 0 + ) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_SERVER]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } + + /* call php's original import as a catch-all */ + php_php_import_environment_variables(array_ptr TSRMLS_CC); + + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + fcgi_loadenv(request, cgi_php_load_env_var, array_ptr TSRMLS_CC); + } +} + +static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + unsigned int php_self_len; + char *php_self; + + /* In CGI mode, we consider the environment to be a part of the server + * variables + */ + php_import_environment_variables(track_vars_array TSRMLS_CC); + + if (CGIG(fix_pathinfo)) { + char *script_name = SG(request_info).request_uri; + char *path_info; + int free_php_self; + ALLOCA_FLAG(use_heap) + + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + path_info = FCGI_GETENV(request, "PATH_INFO"); + } else { + path_info = getenv("PATH_INFO"); + } + + if (path_info) { + unsigned int path_info_len = strlen(path_info); + + if (script_name) { + unsigned int script_name_len = strlen(script_name); + + php_self_len = script_name_len + path_info_len; + php_self = do_alloca(php_self_len + 1, use_heap); + memcpy(php_self, script_name, script_name_len + 1); + memcpy(php_self + script_name_len, path_info, path_info_len + 1); + free_php_self = 1; + } else { + php_self = path_info; + php_self_len = path_info_len; + free_php_self = 0; + } + } else if (script_name) { + php_self = script_name; + php_self_len = strlen(script_name); + free_php_self = 0; + } else { + php_self = ""; + php_self_len = 0; + free_php_self = 0; + } + + /* Build the special-case PHP_SELF variable for the CGI version */ + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array TSRMLS_CC); + } + if (free_php_self) { + free_alloca(php_self, use_heap); + } + } else { + php_self = SG(request_info).request_uri ? SG(request_info).request_uri : ""; + php_self_len = strlen(php_self); + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array TSRMLS_CC); + } + } +} + +static void sapi_cgi_log_message(char *message TSRMLS_DC) +{ + if (fcgi_is_fastcgi() && CGIG(fcgi_logging)) { + fcgi_request *request; + + request = (fcgi_request*) SG(server_context); + if (request) { + int len = strlen(message); + char *buf = malloc(len+2); + + memcpy(buf, message, len); + memcpy(buf + len, "\n", sizeof("\n")); + fcgi_write(request, FCGI_STDERR, buf, len+1); + free(buf); + } else { + fprintf(stderr, "%s\n", message); + } + /* ignore return code */ + } else { + fprintf(stderr, "%s\n", message); + } +} + +/* {{{ php_cgi_ini_activate_user_config + */ +static void php_cgi_ini_activate_user_config(char *path, int path_len, const char *doc_root, int doc_root_len, int start TSRMLS_DC) +{ + char *ptr; + user_config_cache_entry *new_entry, *entry; + time_t request_time = sapi_get_request_time(TSRMLS_C); + + /* Find cached config entry: If not found, create one */ + if (zend_hash_find(&CGIG(user_config_cache), path, path_len + 1, (void **) &entry) == FAILURE) { + new_entry = pemalloc(sizeof(user_config_cache_entry), 1); + new_entry->expires = 0; + new_entry->user_config = (HashTable *) pemalloc(sizeof(HashTable), 1); + zend_hash_init(new_entry->user_config, 0, NULL, (dtor_func_t) config_zval_dtor, 1); + zend_hash_update(&CGIG(user_config_cache), path, path_len + 1, new_entry, sizeof(user_config_cache_entry), (void **) &entry); + free(new_entry); + } + + /* Check whether cache entry has expired and rescan if it is */ + if (request_time > entry->expires) { + char *real_path = NULL; + int real_path_len; + char *s1, *s2; + int s_len; + + /* Clear the expired config */ + zend_hash_clean(entry->user_config); + + if (!IS_ABSOLUTE_PATH(path, path_len)) { + real_path = tsrm_realpath(path, NULL TSRMLS_CC); + if (real_path == NULL) { + return; + } + real_path_len = strlen(real_path); + path = real_path; + path_len = real_path_len; + } + + if (path_len > doc_root_len) { + s1 = (char *) doc_root; + s2 = path; + s_len = doc_root_len; + } else { + s1 = path; + s2 = (char *) doc_root; + s_len = path_len; + } + + /* we have to test if path is part of DOCUMENT_ROOT. + if it is inside the docroot, we scan the tree up to the docroot + to find more user.ini, if not we only scan the current path. + */ +#ifdef PHP_WIN32 + if (strnicmp(s1, s2, s_len) == 0) { +#else + if (strncmp(s1, s2, s_len) == 0) { +#endif + ptr = s2 + start; /* start is the point where doc_root ends! */ + while ((ptr = strchr(ptr, DEFAULT_SLASH)) != NULL) { + *ptr = 0; + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config TSRMLS_CC); + *ptr = '/'; + ptr++; + } + } else { + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config TSRMLS_CC); + } + + if (real_path) { + free(real_path); + } + entry->expires = request_time + PG(user_ini_cache_ttl); + } + + /* Activate ini entries with values from the user config hash */ + php_ini_activate_config(entry->user_config, PHP_INI_PERDIR, PHP_INI_STAGE_HTACCESS TSRMLS_CC); +} +/* }}} */ + +static int sapi_cgi_activate(TSRMLS_D) +{ + char *path, *doc_root, *server_name; + uint path_len, doc_root_len, server_name_len; + + /* PATH_TRANSLATED should be defined at this stage but better safe than sorry :) */ + if (!SG(request_info).path_translated) { + return FAILURE; + } + + if (php_ini_has_per_host_config()) { + /* Activate per-host-system-configuration defined in php.ini and stored into configuration_hash during startup */ + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + server_name = FCGI_GETENV(request, "SERVER_NAME"); + } else { + server_name = getenv("SERVER_NAME"); + } + /* SERVER_NAME should also be defined at this stage..but better check it anyway */ + if (server_name) { + server_name_len = strlen(server_name); + server_name = estrndup(server_name, server_name_len); + zend_str_tolower(server_name, server_name_len); + php_ini_activate_per_host_config(server_name, server_name_len + 1 TSRMLS_CC); + efree(server_name); + } + } + + if (php_ini_has_per_dir_config() || + (PG(user_ini_filename) && *PG(user_ini_filename)) + ) { + /* Prepare search path */ + path_len = strlen(SG(request_info).path_translated); + + /* Make sure we have trailing slash! */ + if (!IS_SLASH(SG(request_info).path_translated[path_len])) { + path = emalloc(path_len + 2); + memcpy(path, SG(request_info).path_translated, path_len + 1); + path_len = zend_dirname(path, path_len); + path[path_len++] = DEFAULT_SLASH; + } else { + path = estrndup(SG(request_info).path_translated, path_len); + path_len = zend_dirname(path, path_len); + } + path[path_len] = 0; + + /* Activate per-dir-system-configuration defined in php.ini and stored into configuration_hash during startup */ + php_ini_activate_per_dir_config(path, path_len TSRMLS_CC); /* Note: for global settings sake we check from root to path */ + + /* Load and activate user ini files in path starting from DOCUMENT_ROOT */ + if (PG(user_ini_filename) && *PG(user_ini_filename)) { + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + doc_root = FCGI_GETENV(request, "DOCUMENT_ROOT"); + } else { + doc_root = getenv("DOCUMENT_ROOT"); + } + + /* DOCUMENT_ROOT should also be defined at this stage..but better check it anyway */ + if (doc_root) { + doc_root_len = strlen(doc_root); + if (doc_root_len > 0 && IS_SLASH(doc_root[doc_root_len - 1])) { + --doc_root_len; + } +#ifdef PHP_WIN32 + /* paths on windows should be case-insensitive */ + doc_root = estrndup(doc_root, doc_root_len); + zend_str_tolower(doc_root, doc_root_len); +#endif + php_cgi_ini_activate_user_config(path, path_len, doc_root, doc_root_len, doc_root_len - 1 TSRMLS_CC); + +#ifdef PHP_WIN32 + efree(doc_root); +#endif + } + } + + efree(path); + } + + return SUCCESS; +} + +static int sapi_cgi_deactivate(TSRMLS_D) +{ + /* flush only when SAPI was started. The reasons are: + 1. SAPI Deactivate is called from two places: module init and request shutdown + 2. When the first call occurs and the request is not set up, flush fails on FastCGI. + */ + if (SG(sapi_started)) { + if (fcgi_is_fastcgi()) { + if ( +#ifndef PHP_WIN32 + !parent && +#endif + !fcgi_finish_request((fcgi_request*)SG(server_context), 0)) { + php_handle_aborted_connection(); + } + } else { + sapi_cgi_flush(SG(server_context)); + } + } + return SUCCESS; +} + +static int php_cgi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &cgi_module_entry, 1) == FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +/* {{{ sapi_module_struct cgi_sapi_module + */ +static sapi_module_struct cgi_sapi_module = { + "cgi-fcgi", /* name */ + "CGI/FastCGI", /* pretty name */ + + php_cgi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + sapi_cgi_activate, /* activate */ + sapi_cgi_deactivate, /* deactivate */ + + sapi_cgi_ub_write, /* unbuffered write */ + sapi_cgi_flush, /* flush */ + NULL, /* get uid */ + sapi_cgi_getenv, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_cgi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_cgi_read_post, /* read POST data */ + sapi_cgi_read_cookies, /* read Cookies */ + + sapi_cgi_register_variables, /* register server variables */ + sapi_cgi_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ arginfo ext/standard/dl.c */ +ZEND_BEGIN_ARG_INFO(arginfo_dl, 0) + ZEND_ARG_INFO(0, extension_filename) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry additional_functions[] = { + ZEND_FE(dl, arginfo_dl) + {NULL, NULL, NULL} +}; + +/* {{{ php_cgi_usage + */ +static void php_cgi_usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "php"; + } + + php_printf( "Usage: %s [-q] [-h] [-s] [-v] [-i] [-f <file>]\n" + " %s <file> [args...]\n" + " -a Run interactively\n" + " -b <address:port>|<port> Bind Path for external FASTCGI Server mode\n" + " -C Do not chdir to the script's directory\n" + " -c <path>|<file> Look for php.ini file in this directory\n" + " -n No php.ini file will be used\n" + " -d foo[=bar] Define INI entry foo with value 'bar'\n" + " -e Generate extended information for debugger/profiler\n" + " -f <file> Parse <file>. Implies `-q'\n" + " -h This help\n" + " -i PHP information\n" + " -l Syntax check only (lint)\n" + " -m Show compiled in modules\n" + " -q Quiet-mode. Suppress HTTP Header output.\n" + " -s Display colour syntax highlighted source.\n" + " -v Version number\n" + " -w Display source with stripped comments and whitespace.\n" + " -z <file> Load Zend extension <file>.\n" + " -T <count> Measure execution time of script repeated <count> times.\n", + prog, prog); +} +/* }}} */ + +/* {{{ is_valid_path + * + * some server configurations allow '..' to slip through in the + * translated path. We'll just refuse to handle such a path. + */ +static int is_valid_path(const char *path) +{ + const char *p = path; + + if (UNEXPECTED(!p)) { + return 0; + } + if (UNEXPECTED(*p == '.') && *(p+1) == '.' && (!*(p+2) || IS_SLASH(*(p+2)))) { + return 0; + } + while (*p) { + if (IS_SLASH(*p)) { + p++; + if (UNEXPECTED(*p == '.')) { + p++; + if (UNEXPECTED(*p == '.')) { + p++; + if (UNEXPECTED(!*p) || UNEXPECTED(IS_SLASH(*p))) { + return 0; + } + } + } + } + p++; + } + return 1; +} +/* }}} */ + +#define CGI_GETENV(name) \ + ((request) ? \ + FCGI_GETENV(request, name) : \ + getenv(name)) + +#define CGI_PUTENV(name, value) \ + ((request) ? \ + FCGI_PUTENV(request, name, value) : \ + _sapi_cgi_putenv(name, sizeof(name)-1, value)) + +/* {{{ init_request_info + + initializes request_info structure + + specificly in this section we handle proper translations + for: + + PATH_INFO + derived from the portion of the URI path following + the script name but preceding any query data + may be empty + + PATH_TRANSLATED + derived by taking any path-info component of the + request URI and performing any virtual-to-physical + translation appropriate to map it onto the server's + document repository structure + + empty if PATH_INFO is empty + + The env var PATH_TRANSLATED **IS DIFFERENT** than the + request_info.path_translated variable, the latter should + match SCRIPT_FILENAME instead. + + SCRIPT_NAME + set to a URL path that could identify the CGI script + rather than the interpreter. PHP_SELF is set to this + + REQUEST_URI + uri section following the domain:port part of a URI + + SCRIPT_FILENAME + The virtual-to-physical translation of SCRIPT_NAME (as per + PATH_TRANSLATED) + + These settings are documented at + http://cgi-spec.golux.com/ + + + Based on the following URL request: + + http://localhost/info.php/test?a=b + + should produce, which btw is the same as if + we were running under mod_cgi on apache (ie. not + using ScriptAlias directives): + + PATH_INFO=/test + PATH_TRANSLATED=/docroot/test + SCRIPT_NAME=/info.php + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/docroot/info.php + QUERY_STRING=a=b + + but what we get is (cgi/mod_fastcgi under apache): + + PATH_INFO=/info.php/test + PATH_TRANSLATED=/docroot/info.php/test + SCRIPT_NAME=/php/php-cgi (from the Action setting I suppose) + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/path/to/php/bin/php-cgi (Action setting translated) + QUERY_STRING=a=b + + Comments in the code below refer to using the above URL in a request + + */ +static void init_request_info(fcgi_request *request TSRMLS_DC) +{ + char *env_script_filename = CGI_GETENV("SCRIPT_FILENAME"); + char *env_path_translated = CGI_GETENV("PATH_TRANSLATED"); + char *script_path_translated = env_script_filename; + + /* some broken servers do not have script_filename or argv0 + * an example, IIS configured in some ways. then they do more + * broken stuff and set path_translated to the cgi script location */ + if (!script_path_translated && env_path_translated) { + script_path_translated = env_path_translated; + } + + /* initialize the defaults */ + SG(request_info).path_translated = NULL; + SG(request_info).request_method = NULL; + SG(request_info).proto_num = 1000; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = NULL; + SG(request_info).content_type = NULL; + SG(request_info).content_length = 0; + SG(sapi_headers).http_response_code = 200; + + /* script_path_translated being set is a good indication that + * we are running in a cgi environment, since it is always + * null otherwise. otherwise, the filename + * of the script will be retreived later via argc/argv */ + if (script_path_translated) { + const char *auth; + char *content_length = CGI_GETENV("CONTENT_LENGTH"); + char *content_type = CGI_GETENV("CONTENT_TYPE"); + char *env_path_info = CGI_GETENV("PATH_INFO"); + char *env_script_name = CGI_GETENV("SCRIPT_NAME"); + +#ifdef PHP_WIN32 + /* Hack for buggy IIS that sets incorrect PATH_INFO */ + char *env_server_software = CGI_GETENV("SERVER_SOFTWARE"); + + if (env_server_software && + env_script_name && + env_path_info && + strncmp(env_server_software, "Microsoft-IIS", sizeof("Microsoft-IIS")-1) == 0 && + strncmp(env_path_info, env_script_name, strlen(env_script_name)) == 0 + ) { + env_path_info = CGI_PUTENV("ORIG_PATH_INFO", env_path_info); + env_path_info += strlen(env_script_name); + if (*env_path_info == 0) { + env_path_info = NULL; + } + env_path_info = CGI_PUTENV("PATH_INFO", env_path_info); + } +#endif + + if (CGIG(fix_pathinfo)) { + struct stat st; + char *real_path = NULL; + char *env_redirect_url = CGI_GETENV("REDIRECT_URL"); + char *env_document_root = CGI_GETENV("DOCUMENT_ROOT"); + char *orig_path_translated = env_path_translated; + char *orig_path_info = env_path_info; + char *orig_script_name = env_script_name; + char *orig_script_filename = env_script_filename; + int script_path_translated_len; + + if (!env_document_root && PG(doc_root)) { + env_document_root = CGI_PUTENV("DOCUMENT_ROOT", PG(doc_root)); + /* fix docroot */ + TRANSLATE_SLASHES(env_document_root); + } + + if (env_path_translated != NULL && env_redirect_url != NULL && + env_path_translated != script_path_translated && + strcmp(env_path_translated, script_path_translated) != 0) { + /* + * pretty much apache specific. If we have a redirect_url + * then our script_filename and script_name point to the + * php executable + */ + script_path_translated = env_path_translated; + /* we correct SCRIPT_NAME now in case we don't have PATH_INFO */ + env_script_name = env_redirect_url; + } + +#ifdef __riscos__ + /* Convert path to unix format*/ + __riscosify_control |= __RISCOSIFY_DONT_CHECK_DIR; + script_path_translated = __unixify(script_path_translated, 0, NULL, 1, 0); +#endif + + /* + * if the file doesn't exist, try to extract PATH_INFO out + * of it by stat'ing back through the '/' + * this fixes url's like /info.php/test + */ + if (script_path_translated && + (script_path_translated_len = strlen(script_path_translated)) > 0 && + (script_path_translated[script_path_translated_len-1] == '/' || +#ifdef PHP_WIN32 + script_path_translated[script_path_translated_len-1] == '\\' || +#endif + (real_path = tsrm_realpath(script_path_translated, NULL TSRMLS_CC)) == NULL) + ) { + char *pt = estrndup(script_path_translated, script_path_translated_len); + int len = script_path_translated_len; + char *ptr; + + while ((ptr = strrchr(pt, '/')) || (ptr = strrchr(pt, '\\'))) { + *ptr = 0; + if (stat(pt, &st) == 0 && S_ISREG(st.st_mode)) { + /* + * okay, we found the base script! + * work out how many chars we had to strip off; + * then we can modify PATH_INFO + * accordingly + * + * we now have the makings of + * PATH_INFO=/test + * SCRIPT_FILENAME=/docroot/info.php + * + * we now need to figure out what docroot is. + * if DOCUMENT_ROOT is set, this is easy, otherwise, + * we have to play the game of hide and seek to figure + * out what SCRIPT_NAME should be + */ + int slen = len - strlen(pt); + int pilen = env_path_info ? strlen(env_path_info) : 0; + char *path_info = env_path_info ? env_path_info + pilen - slen : NULL; + + if (orig_path_info != path_info) { + if (orig_path_info) { + char old; + + CGI_PUTENV("ORIG_PATH_INFO", orig_path_info); + old = path_info[0]; + path_info[0] = 0; + if (!orig_script_name || + strcmp(orig_script_name, env_path_info) != 0) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_path_info); + } else { + SG(request_info).request_uri = orig_script_name; + } + path_info[0] = old; + } + env_path_info = CGI_PUTENV("PATH_INFO", path_info); + } + if (!orig_script_filename || + strcmp(orig_script_filename, pt) != 0) { + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", pt); + } + TRANSLATE_SLASHES(pt); + + /* figure out docroot + * SCRIPT_FILENAME minus SCRIPT_NAME + */ + if (env_document_root) { + int l = strlen(env_document_root); + int path_translated_len = 0; + char *path_translated = NULL; + + if (l && env_document_root[l - 1] == '/') { + --l; + } + + /* we have docroot, so we should have: + * DOCUMENT_ROOT=/docroot + * SCRIPT_FILENAME=/docroot/info.php + */ + + /* PATH_TRANSLATED = DOCUMENT_ROOT + PATH_INFO */ + path_translated_len = l + (env_path_info ? strlen(env_path_info) : 0); + path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, env_document_root, l); + if (env_path_info) { + memcpy(path_translated + l, env_path_info, (path_translated_len - l)); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + } + env_path_translated = CGI_PUTENV("PATH_TRANSLATED", path_translated); + efree(path_translated); + } else if ( env_script_name && + strstr(pt, env_script_name) + ) { + /* PATH_TRANSLATED = PATH_TRANSLATED - SCRIPT_NAME + PATH_INFO */ + int ptlen = strlen(pt) - strlen(env_script_name); + int path_translated_len = ptlen + (env_path_info ? strlen(env_path_info) : 0); + char *path_translated = NULL; + + path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, pt, ptlen); + if (env_path_info) { + memcpy(path_translated + ptlen, env_path_info, path_translated_len - ptlen); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + } + env_path_translated = CGI_PUTENV("PATH_TRANSLATED", path_translated); + efree(path_translated); + } + break; + } + } + if (!ptr) { + /* + * if we stripped out all the '/' and still didn't find + * a valid path... we will fail, badly. of course we would + * have failed anyway... we output 'no input file' now. + */ + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", NULL); + SG(sapi_headers).http_response_code = 404; + } + if (!SG(request_info).request_uri) { + if (!orig_script_name || + strcmp(orig_script_name, env_script_name) != 0) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_script_name); + } else { + SG(request_info).request_uri = orig_script_name; + } + } + if (pt) { + efree(pt); + } + } else { + /* make sure path_info/translated are empty */ + if (!orig_script_filename || + (script_path_translated != orig_script_filename && + strcmp(script_path_translated, orig_script_filename) != 0)) { + if (orig_script_filename) { + CGI_PUTENV("ORIG_SCRIPT_FILENAME", orig_script_filename); + } + script_path_translated = CGI_PUTENV("SCRIPT_FILENAME", script_path_translated); + } + if (env_redirect_url) { + if (orig_path_info) { + CGI_PUTENV("ORIG_PATH_INFO", orig_path_info); + CGI_PUTENV("PATH_INFO", NULL); + } + if (orig_path_translated) { + CGI_PUTENV("ORIG_PATH_TRANSLATED", orig_path_translated); + CGI_PUTENV("PATH_TRANSLATED", NULL); + } + } + if (env_script_name != orig_script_name) { + if (orig_script_name) { + CGI_PUTENV("ORIG_SCRIPT_NAME", orig_script_name); + } + SG(request_info).request_uri = CGI_PUTENV("SCRIPT_NAME", env_script_name); + } else { + SG(request_info).request_uri = env_script_name; + } + free(real_path); + } + } else { + /* pre 4.3 behaviour, shouldn't be used but provides BC */ + if (env_path_info) { + SG(request_info).request_uri = env_path_info; + } else { + SG(request_info).request_uri = env_script_name; + } + if (!CGIG(discard_path) && env_path_translated) { + script_path_translated = env_path_translated; + } + } + + if (is_valid_path(script_path_translated)) { + SG(request_info).path_translated = estrdup(script_path_translated); + } + + SG(request_info).request_method = CGI_GETENV("REQUEST_METHOD"); + /* FIXME - Work out proto_num here */ + SG(request_info).query_string = CGI_GETENV("QUERY_STRING"); + SG(request_info).content_type = (content_type ? content_type : "" ); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + + /* The CGI RFC allows servers to pass on unvalidated Authorization data */ + auth = CGI_GETENV("HTTP_AUTHORIZATION"); + php_handle_auth_data(auth TSRMLS_CC); + } +} +/* }}} */ + +#ifndef PHP_WIN32 +/** + * Clean up child processes upon exit + */ +void fastcgi_cleanup(int signal) +{ +#ifdef DEBUG_FASTCGI + fprintf(stderr, "FastCGI shutdown, pid %d\n", getpid()); +#endif + + sigaction(SIGTERM, &old_term, 0); + + /* Kill all the processes in our process group */ + kill(-pgroup, SIGTERM); + + if (parent && parent_waiting) { + exit_signal = 1; + } else { + exit(0); + } +} +#endif + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.check_shebang_line", "1", PHP_INI_SYSTEM, OnUpdateBool, check_shebang_line, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.force_redirect", "1", PHP_INI_SYSTEM, OnUpdateBool, force_redirect, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.redirect_status_env", NULL, PHP_INI_SYSTEM, OnUpdateString, redirect_status_env, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals) +#ifdef PHP_WIN32 + STD_PHP_INI_ENTRY("fastcgi.impersonate", "0", PHP_INI_SYSTEM, OnUpdateBool, impersonate, php_cgi_globals_struct, php_cgi_globals) +#endif +PHP_INI_END() + +/* {{{ php_cgi_globals_ctor + */ +static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals TSRMLS_DC) +{ + php_cgi_globals->rfc2616_headers = 0; + php_cgi_globals->nph = 0; + php_cgi_globals->check_shebang_line = 1; + php_cgi_globals->force_redirect = 1; + php_cgi_globals->redirect_status_env = NULL; + php_cgi_globals->fix_pathinfo = 1; + php_cgi_globals->discard_path = 0; + php_cgi_globals->fcgi_logging = 1; +#ifdef PHP_WIN32 + php_cgi_globals->impersonate = 0; +#endif + zend_hash_init(&php_cgi_globals->user_config_cache, 0, NULL, (dtor_func_t) user_config_cache_entry_dtor, 1); +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +static PHP_MINIT_FUNCTION(cgi) +{ +#ifdef ZTS + ts_allocate_id(&php_cgi_globals_id, sizeof(php_cgi_globals_struct), (ts_allocate_ctor) php_cgi_globals_ctor, NULL); +#else + php_cgi_globals_ctor(&php_cgi_globals TSRMLS_CC); +#endif + REGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(cgi) +{ + zend_hash_destroy(&CGIG(user_config_cache)); + + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +static PHP_MINFO_FUNCTION(cgi) +{ + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +PHP_FUNCTION(apache_child_terminate) /* {{{ */ +{ + if (ZEND_NUM_ARGS() > 0) { + WRONG_PARAM_COUNT; + } + if (fcgi_is_fastcgi()) { + fcgi_terminate(); + } +} +/* }}} */ + +static void add_request_header(char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg TSRMLS_DC) /* {{{ */ +{ + zval *return_value = (zval*)arg; + char *str = NULL; + char *p; + ALLOCA_FLAG(use_heap) + + if (var_len > 5 && + var[0] == 'H' && + var[1] == 'T' && + var[2] == 'T' && + var[3] == 'P' && + var[4] == '_') { + + var_len -= 5; + p = var + 5; + var = str = do_alloca(var_len + 1, use_heap); + *str++ = *p++; + while (*p) { + if (*p == '_') { + *str++ = '-'; + p++; + if (*p) { + *str++ = *p++; + } + } else if (*p >= 'A' && *p <= 'Z') { + *str++ = (*p++ - 'A' + 'a'); + } else { + *str++ = *p++; + } + } + *str = 0; + } else if (var_len == sizeof("CONTENT_TYPE")-1 && + memcmp(var, "CONTENT_TYPE", sizeof("CONTENT_TYPE")-1) == 0) { + var = "Content-Type"; + } else if (var_len == sizeof("CONTENT_LENGTH")-1 && + memcmp(var, "CONTENT_LENGTH", sizeof("CONTENT_LENGTH")-1) == 0) { + var = "Content-Length"; + } else { + return; + } + add_assoc_stringl_ex(return_value, var, var_len+1, val, val_len, 1); + if (str) { + free_alloca(var, use_heap); + } +} +/* }}} */ + +PHP_FUNCTION(apache_request_headers) /* {{{ */ +{ + if (ZEND_NUM_ARGS() > 0) { + WRONG_PARAM_COUNT; + } + array_init(return_value); + if (fcgi_is_fastcgi()) { + fcgi_request *request = (fcgi_request*) SG(server_context); + + fcgi_loadenv(request, add_request_header, return_value TSRMLS_CC); + } else { + char buf[128]; + char **env, *p, *q, *var, *val, *t = buf; + size_t alloc_size = sizeof(buf); + unsigned long var_len; + + for (env = environ; env != NULL && *env != NULL; env++) { + val = strchr(*env, '='); + if (!val) { /* malformed entry? */ + continue; + } + var_len = val - *env; + if (var_len >= alloc_size) { + alloc_size = var_len + 64; + t = (t == buf ? emalloc(alloc_size): erealloc(t, alloc_size)); + } + var = *env; + if (var_len > 5 && + var[0] == 'H' && + var[1] == 'T' && + var[2] == 'T' && + var[3] == 'P' && + var[4] == '_') { + + var_len -= 5; + + if (var_len >= alloc_size) { + alloc_size = var_len + 64; + t = (t == buf ? emalloc(alloc_size): erealloc(t, alloc_size)); + } + p = var + 5; + + var = q = t; + /* First char keep uppercase */ + *q++ = *p++; + while (*p) { + if (*p == '=') { + /* End of name */ + break; + } else if (*p == '_') { + *q++ = '-'; + p++; + /* First char after - keep uppercase */ + if (*p && *p!='=') { + *q++ = *p++; + } + } else if (*p >= 'A' && *p <= 'Z') { + /* lowercase */ + *q++ = (*p++ - 'A' + 'a'); + } else { + *q++ = *p++; + } + } + *q = 0; + } else if (var_len == sizeof("CONTENT_TYPE")-1 && + memcmp(var, "CONTENT_TYPE", sizeof("CONTENT_TYPE")-1) == 0) { + var = "Content-Type"; + } else if (var_len == sizeof("CONTENT_LENGTH")-1 && + memcmp(var, "CONTENT_LENGTH", sizeof("CONTENT_LENGTH")-1) == 0) { + var = "Content-Length"; + } else { + continue; + } + val++; + add_assoc_string_ex(return_value, var, var_len+1, val, 1); + } + if (t != buf && t != NULL) { + efree(t); + } + } +} +/* }}} */ + +static void add_response_header(sapi_header_struct *h, zval *return_value TSRMLS_DC) /* {{{ */ +{ + char *s, *p; + int len; + ALLOCA_FLAG(use_heap) + + if (h->header_len > 0) { + p = strchr(h->header, ':'); + len = p - h->header; + if (p && (len > 0)) { + while (len > 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) { + len--; + } + if (len) { + s = do_alloca(len + 1, use_heap); + memcpy(s, h->header, len); + s[len] = 0; + do { + p++; + } while (*p == ' ' || *p == '\t'); + add_assoc_stringl_ex(return_value, s, len+1, p, h->header_len - (p - h->header), 1); + free_alloca(s, use_heap); + } + } + } +} +/* }}} */ + +PHP_FUNCTION(apache_response_headers) /* {{{ */ +{ + if (ZEND_NUM_ARGS() > 0) { + WRONG_PARAM_COUNT; + } + + if (!&SG(sapi_headers).headers) { + RETURN_FALSE; + } + array_init(return_value); + zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value TSRMLS_CC); +} +/* }}} */ + +ZEND_BEGIN_ARG_INFO(arginfo_no_args, 0) +ZEND_END_ARG_INFO() + +const zend_function_entry cgi_functions[] = { + PHP_FE(apache_child_terminate, arginfo_no_args) + PHP_FE(apache_request_headers, arginfo_no_args) + PHP_FE(apache_response_headers, arginfo_no_args) + PHP_FALIAS(getallheaders, apache_request_headers, arginfo_no_args) + {NULL, NULL, NULL} +}; + +static zend_module_entry cgi_module_entry = { + STANDARD_MODULE_HEADER, + "cgi-fcgi", + cgi_functions, + PHP_MINIT(cgi), + PHP_MSHUTDOWN(cgi), + NULL, + NULL, + PHP_MINFO(cgi), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +/* {{{ main + */ +int main(int argc, char *argv[]) +{ + int free_query_string = 0; + int exit_status = SUCCESS; + int cgi = 0, c, i, len; + zend_file_handle file_handle; + char *s; + + /* temporary locals */ + int behavior = PHP_MODE_STANDARD; + int no_headers = 0; + int orig_optind = php_optind; + char *orig_optarg = php_optarg; + char *script_file = NULL; + int ini_entries_len = 0; + /* end of temporary locals */ + +#ifdef ZTS + void ***tsrm_ls; +#endif + + int max_requests = 500; + int requests = 0; + int fastcgi; + char *bindpath = NULL; + int fcgi_fd = 0; + fcgi_request *request = NULL; + int repeats = 1; + int benchmark = 0; +#if HAVE_GETTIMEOFDAY + struct timeval start, end; +#else + time_t start, end; +#endif +#ifndef PHP_WIN32 + int status = 0; +#endif + char *query_string; + char *decoded_query_string; + int skip_getopt = 0; + +#if 0 && defined(PHP_DEBUG) + /* IIS is always making things more difficult. This allows + * us to stop PHP and attach a debugger before much gets started */ + { + char szMessage [256]; + wsprintf (szMessage, "Please attach a debugger to the process 0x%X [%d] (%s) and click OK", GetCurrentProcessId(), GetCurrentProcessId(), argv[0]); + MessageBox(NULL, szMessage, "CGI Debug Time!", MB_OK|MB_SERVICE_NOTIFICATION); + } +#endif + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif +#endif + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + tsrm_ls = ts_resource(0); +#endif + + sapi_startup(&cgi_sapi_module); + fastcgi = fcgi_is_fastcgi(); + cgi_sapi_module.php_ini_path_override = NULL; + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /* sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + if (!fastcgi) { + /* Make sure we detect we are a cgi - a bit redundancy here, + * but the default case is that we have to check only the first one. */ + if (getenv("SERVER_SOFTWARE") || + getenv("SERVER_NAME") || + getenv("GATEWAY_INTERFACE") || + getenv("REQUEST_METHOD") + ) { + cgi = 1; + } + } + + if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) { + /* we've got query string that has no = - apache CGI will pass it to command line */ + unsigned char *p; + decoded_query_string = strdup(query_string); + php_url_decode(decoded_query_string, strlen(decoded_query_string)); + for (p = decoded_query_string; *p && *p <= ' '; p++) { + /* skip all leading spaces */ + } + if(*p == '-') { + skip_getopt = 1; + } + free(decoded_query_string); + } + + while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + case 'c': + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + cgi_sapi_module.php_ini_path_override = strdup(php_optarg); + break; + case 'n': + cgi_sapi_module.php_ini_ignore = 1; + break; + case 'd': { + /* define ini entries on command line */ + int len = strlen(php_optarg); + char *val; + + if ((val = strchr(php_optarg, '='))) { + val++; + if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\"\"\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, (val - php_optarg)); + ini_entries_len += (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"", 1); + ini_entries_len++; + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, val, len - (val - php_optarg)); + ini_entries_len += len - (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0")); + ini_entries_len += sizeof("\n\0\"") - 2; + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0")); + ini_entries_len += len + sizeof("\n\0") - 2; + } + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("=1\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0")); + ini_entries_len += len + sizeof("=1\n\0") - 2; + } + break; + } + /* if we're started on command line, check to see if + * we are being started as an 'external' fastcgi + * server by accepting a bindpath parameter. */ + case 'b': + if (!fastcgi) { + bindpath = strdup(php_optarg); + } + break; + case 's': /* generate highlighted HTML from source */ + behavior = PHP_MODE_HIGHLIGHT; + break; + } + } + php_optind = orig_optind; + php_optarg = orig_optarg; + + if (fastcgi || bindpath) { + /* Override SAPI callbacks */ + cgi_sapi_module.ub_write = sapi_fcgi_ub_write; + cgi_sapi_module.flush = sapi_fcgi_flush; + cgi_sapi_module.read_post = sapi_fcgi_read_post; + cgi_sapi_module.getenv = sapi_fcgi_getenv; + cgi_sapi_module.read_cookies = sapi_fcgi_read_cookies; + } + +#ifdef ZTS + SG(request_info).path_translated = NULL; +#endif + + cgi_sapi_module.executable_location = argv[0]; + if (!cgi && !fastcgi && !bindpath) { + cgi_sapi_module.additional_functions = additional_functions; + } + + /* startup after we get the above ini override se we get things right */ + if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) { +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + + /* check force_cgi after startup, so we have proper output */ + if (cgi && CGIG(force_redirect)) { + /* Apache will generate REDIRECT_STATUS, + * Netscape and redirect.so will generate HTTP_REDIRECT_STATUS. + * redirect.so and installation instructions available from + * http://www.koehntopp.de/php. + * -- kk@netuse.de + */ + if (!getenv("REDIRECT_STATUS") && + !getenv ("HTTP_REDIRECT_STATUS") && + /* this is to allow a different env var to be configured + * in case some server does something different than above */ + (!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env))) + ) { + zend_try { + SG(sapi_headers).http_response_code = 400; + PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\ +<p>This PHP CGI binary was compiled with force-cgi-redirect enabled. This\n\ +means that a page will only be served up if the REDIRECT_STATUS CGI variable is\n\ +set, e.g. via an Apache Action directive.</p>\n\ +<p>For more information as to <i>why</i> this behaviour exists, see the <a href=\"http://php.net/security.cgi-bin\">\ +manual page for CGI security</a>.</p>\n\ +<p>For more information about changing this behaviour or re-enabling this webserver,\n\ +consult the installation file that came with this distribution, or visit \n\ +<a href=\"http://php.net/install.windows\">the manual page</a>.</p>\n"); + } zend_catch { + } zend_end_try(); +#if defined(ZTS) && !defined(PHP_DEBUG) + /* XXX we're crashing here in msvc6 debug builds at + * php_message_handler_for_zend:839 because + * SG(request_info).path_translated is an invalid pointer. + * It still happens even though I set it to null, so something + * weird is going on. + */ + tsrm_shutdown(); +#endif + return FAILURE; + } + } + + if (bindpath) { + fcgi_fd = fcgi_listen(bindpath, 128); + if (fcgi_fd < 0) { + fprintf(stderr, "Couldn't create FastCGI listen socket on port %s\n", bindpath); +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + fastcgi = fcgi_is_fastcgi(); + } + if (fastcgi) { + /* How many times to run PHP scripts before dying */ + if (getenv("PHP_FCGI_MAX_REQUESTS")) { + max_requests = atoi(getenv("PHP_FCGI_MAX_REQUESTS")); + if (max_requests < 0) { + fprintf(stderr, "PHP_FCGI_MAX_REQUESTS is not valid\n"); + return FAILURE; + } + } + + /* make php call us to get _ENV vars */ + php_php_import_environment_variables = php_import_environment_variables; + php_import_environment_variables = cgi_php_import_environment_variables; + + /* library is already initialized, now init our request */ + request = fcgi_init_request(fcgi_fd); + +#ifndef PHP_WIN32 + /* Pre-fork, if required */ + if (getenv("PHP_FCGI_CHILDREN")) { + char * children_str = getenv("PHP_FCGI_CHILDREN"); + children = atoi(children_str); + if (children < 0) { + fprintf(stderr, "PHP_FCGI_CHILDREN is not valid\n"); + return FAILURE; + } + fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, children_str, strlen(children_str)); + /* This is the number of concurrent requests, equals FCGI_MAX_CONNS */ + fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, children_str, strlen(children_str)); + } else { + fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, "1", sizeof("1")-1); + fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, "1", sizeof("1")-1); + } + + if (children) { + int running = 0; + pid_t pid; + + /* Create a process group for ourself & children */ + setsid(); + pgroup = getpgrp(); +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Process group %d\n", pgroup); +#endif + + /* Set up handler to kill children upon exit */ + act.sa_flags = 0; + act.sa_handler = fastcgi_cleanup; + if (sigaction(SIGTERM, &act, &old_term) || + sigaction(SIGINT, &act, &old_int) || + sigaction(SIGQUIT, &act, &old_quit) + ) { + perror("Can't set signals"); + exit(1); + } + + if (fcgi_in_shutdown()) { + goto parent_out; + } + + while (parent) { + do { +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Forking, %d running\n", running); +#endif + pid = fork(); + switch (pid) { + case 0: + /* One of the children. + * Make sure we don't go round the + * fork loop any more + */ + parent = 0; + + /* don't catch our signals */ + sigaction(SIGTERM, &old_term, 0); + sigaction(SIGQUIT, &old_quit, 0); + sigaction(SIGINT, &old_int, 0); + break; + case -1: + perror("php (pre-forking)"); + exit(1); + break; + default: + /* Fine */ + running++; + break; + } + } while (parent && (running < children)); + + if (parent) { +#ifdef DEBUG_FASTCGI + fprintf(stderr, "Wait for kids, pid %d\n", getpid()); +#endif + parent_waiting = 1; + while (1) { + if (wait(&status) >= 0) { + running--; + break; + } else if (exit_signal) { + break; + } + } + if (exit_signal) { +#if 0 + while (running > 0) { + while (wait(&status) < 0) { + } + running--; + } +#endif + goto parent_out; + } + } + } + } else { + parent = 0; + } + +#endif /* WIN32 */ + } + + zend_first_try { + while (!skip_getopt && (c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 1, 2)) != -1) { + switch (c) { + case 'T': + benchmark = 1; + repeats = atoi(php_optarg); +#ifdef HAVE_GETTIMEOFDAY + gettimeofday(&start, NULL); +#else + time(&start); +#endif + break; + case 'h': + case '?': + if (request) { + fcgi_destroy_request(request); + } + fcgi_shutdown(); + no_headers = 1; + SG(headers_sent) = 1; + php_cgi_usage(argv[0]); + php_output_end_all(TSRMLS_C); + exit_status = 0; + goto out; + } + } + php_optind = orig_optind; + php_optarg = orig_optarg; + + /* start of FAST CGI loop */ + /* Initialise FastCGI request structure */ +#ifdef PHP_WIN32 + /* attempt to set security impersonation for fastcgi + * will only happen on NT based OS, others will ignore it. */ + if (fastcgi && CGIG(impersonate)) { + fcgi_impersonate(); + } +#endif + while (!fastcgi || fcgi_accept_request(request) >= 0) { + SG(server_context) = fastcgi ? (void *) request : (void *) 1; + init_request_info(request TSRMLS_CC); + CG(interactive) = 0; + + if (!cgi && !fastcgi) { + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + + case 'a': /* interactive mode */ + printf("Interactive mode enabled\n\n"); + CG(interactive) = 1; + break; + + case 'C': /* don't chdir to the script directory */ + SG(options) |= SAPI_OPTION_NO_CHDIR; + break; + + case 'e': /* enable extended info output */ + CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; + break; + + case 'f': /* parse file */ + if (script_file) { + efree(script_file); + } + script_file = estrdup(php_optarg); + no_headers = 1; + break; + + case 'i': /* php info & quit */ + if (script_file) { + efree(script_file); + } + if (php_request_startup(TSRMLS_C) == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FAILURE; + } + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + php_print_info(0xFFFFFFFF TSRMLS_CC); + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = 0; + goto out; + + case 'l': /* syntax check mode */ + no_headers = 1; + behavior = PHP_MODE_LINT; + break; + + case 'm': /* list compiled in modules */ + if (script_file) { + efree(script_file); + } + SG(headers_sent) = 1; + php_printf("[PHP Modules]\n"); + print_modules(TSRMLS_C); + php_printf("\n[Zend Modules]\n"); + print_extensions(TSRMLS_C); + php_printf("\n"); + php_output_end_all(TSRMLS_C); + fcgi_shutdown(); + exit_status = 0; + goto out; + +#if 0 /* not yet operational, see also below ... */ + case '': /* generate indented source mode*/ + behavior=PHP_MODE_INDENT; + break; +#endif + + case 'q': /* do not generate HTTP headers */ + no_headers = 1; + break; + + case 'v': /* show php version & quit */ + if (script_file) { + efree(script_file); + } + no_headers = 1; + if (php_request_startup(TSRMLS_C) == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FAILURE; + } + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } +#if ZEND_DEBUG + php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#else + php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#endif + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = 0; + goto out; + + case 'w': + behavior = PHP_MODE_STRIP; + break; + + case 'z': /* load extension file */ + zend_load_extension(php_optarg); + break; + + default: + break; + } + } + + if (script_file) { + /* override path_translated if -f on command line */ + STR_FREE(SG(request_info).path_translated); + SG(request_info).path_translated = script_file; + /* before registering argv to module exchange the *new* argv[0] */ + /* we can achieve this without allocating more memory */ + SG(request_info).argc = argc - (php_optind - 1); + SG(request_info).argv = &argv[php_optind - 1]; + SG(request_info).argv[0] = script_file; + } else if (argc > php_optind) { + /* file is on command line, but not in -f opt */ + STR_FREE(SG(request_info).path_translated); + SG(request_info).path_translated = estrdup(argv[php_optind]); + /* arguments after the file are considered script args */ + SG(request_info).argc = argc - php_optind; + SG(request_info).argv = &argv[php_optind]; + } + + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + + /* all remaining arguments are part of the query string + * this section of code concatenates all remaining arguments + * into a single string, seperating args with a & + * this allows command lines like: + * + * test.php v1=test v2=hello+world! + * test.php "v1=test&v2=hello world!" + * test.php v1=test "v2=hello world!" + */ + if (!SG(request_info).query_string && argc > php_optind) { + int slen = strlen(PG(arg_separator).input); + len = 0; + for (i = php_optind; i < argc; i++) { + if (i < (argc - 1)) { + len += strlen(argv[i]) + slen; + } else { + len += strlen(argv[i]); + } + } + + len += 2; + s = malloc(len); + *s = '\0'; /* we are pretending it came from the environment */ + for (i = php_optind; i < argc; i++) { + strlcat(s, argv[i], len); + if (i < (argc - 1)) { + strlcat(s, PG(arg_separator).input, len); + } + } + SG(request_info).query_string = s; + free_query_string = 1; + } + } /* end !cgi && !fastcgi */ + + /* + we never take stdin if we're (f)cgi, always + rely on the web server giving us the info + we need in the environment. + */ + if (SG(request_info).path_translated || cgi || fastcgi) { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.handle.fp = NULL; + } else { + file_handle.filename = "-"; + file_handle.type = ZEND_HANDLE_FP; + file_handle.handle.fp = stdin; + } + + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + + /* request startup only after we've done all we can to + * get path_translated */ + if (php_request_startup(TSRMLS_C) == FAILURE) { + if (fastcgi) { + fcgi_finish_request(request, 1); + } + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FAILURE; + } + if (no_headers) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + } + + /* + at this point path_translated will be set if: + 1. we are running from shell and got filename was there + 2. we are running as cgi or fastcgi + */ + if (cgi || fastcgi || SG(request_info).path_translated) { + if (php_fopen_primary_script(&file_handle TSRMLS_CC) == FAILURE) { + zend_try { + if (errno == EACCES) { + SG(sapi_headers).http_response_code = 403; + PUTS("Access denied.\n"); + } else { + SG(sapi_headers).http_response_code = 404; + PUTS("No input file specified.\n"); + } + } zend_catch { + } zend_end_try(); + /* we want to serve more requests if this is fastcgi + * so cleanup and continue, request shutdown is + * handled later */ + if (fastcgi) { + goto fastcgi_request_done; + } + + STR_FREE(SG(request_info).path_translated); + + if (free_query_string && SG(request_info).query_string) { + free(SG(request_info).query_string); + SG(request_info).query_string = NULL; + } + + php_request_shutdown((void *) 0); + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + } + + if (CGIG(check_shebang_line)) { + /* #!php support */ + switch (file_handle.type) { + case ZEND_HANDLE_FD: + if (file_handle.handle.fd < 0) { + break; + } + file_handle.type = ZEND_HANDLE_FP; + file_handle.handle.fp = fdopen(file_handle.handle.fd, "rb"); + /* break missing intentionally */ + case ZEND_HANDLE_FP: + if (!file_handle.handle.fp || + (file_handle.handle.fp == stdin)) { + break; + } + c = fgetc(file_handle.handle.fp); + if (c == '#') { + while (c != '\n' && c != '\r' && c != EOF) { + c = fgetc(file_handle.handle.fp); /* skip to end of line */ + } + /* handle situations where line is terminated by \r\n */ + if (c == '\r') { + if (fgetc(file_handle.handle.fp) != '\n') { + long pos = ftell(file_handle.handle.fp); + fseek(file_handle.handle.fp, pos - 1, SEEK_SET); + } + } + CG(start_lineno) = 2; + } else { + rewind(file_handle.handle.fp); + } + break; + case ZEND_HANDLE_STREAM: + c = php_stream_getc((php_stream*)file_handle.handle.stream.handle); + if (c == '#') { + while (c != '\n' && c != '\r' && c != EOF) { + c = php_stream_getc((php_stream*)file_handle.handle.stream.handle); /* skip to end of line */ + } + /* handle situations where line is terminated by \r\n */ + if (c == '\r') { + if (php_stream_getc((php_stream*)file_handle.handle.stream.handle) != '\n') { + long pos = php_stream_tell((php_stream*)file_handle.handle.stream.handle); + php_stream_seek((php_stream*)file_handle.handle.stream.handle, pos - 1, SEEK_SET); + } + } + CG(start_lineno) = 2; + } else { + php_stream_rewind((php_stream*)file_handle.handle.stream.handle); + } + break; + case ZEND_HANDLE_MAPPED: + if (file_handle.handle.stream.mmap.buf[0] == '#') { + int i = 1; + + c = file_handle.handle.stream.mmap.buf[i++]; + while (c != '\n' && c != '\r' && c != EOF) { + c = file_handle.handle.stream.mmap.buf[i++]; + } + if (c == '\r') { + if (file_handle.handle.stream.mmap.buf[i] == '\n') { + i++; + } + } + file_handle.handle.stream.mmap.buf += i; + file_handle.handle.stream.mmap.len -= i; + } + break; + default: + break; + } + } + + switch (behavior) { + case PHP_MODE_STANDARD: + php_execute_script(&file_handle TSRMLS_CC); + break; + case PHP_MODE_LINT: + PG(during_request_startup) = 0; + exit_status = php_lint_script(&file_handle TSRMLS_CC); + if (exit_status == SUCCESS) { + zend_printf("No syntax errors detected in %s\n", file_handle.filename); + } else { + zend_printf("Errors parsing %s\n", file_handle.filename); + } + break; + case PHP_MODE_STRIP: + if (open_file_for_scanning(&file_handle TSRMLS_CC) == SUCCESS) { + zend_strip(TSRMLS_C); + zend_file_handle_dtor(&file_handle TSRMLS_CC); + php_output_teardown(); + } + return SUCCESS; + break; + case PHP_MODE_HIGHLIGHT: + { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + if (open_file_for_scanning(&file_handle TSRMLS_CC) == SUCCESS) { + php_get_highlight_struct(&syntax_highlighter_ini); + zend_highlight(&syntax_highlighter_ini TSRMLS_CC); + if (fastcgi) { + goto fastcgi_request_done; + } + zend_file_handle_dtor(&file_handle TSRMLS_CC); + php_output_teardown(); + } + return SUCCESS; + } + break; +#if 0 + /* Zeev might want to do something with this one day */ + case PHP_MODE_INDENT: + open_file_for_scanning(&file_handle TSRMLS_CC); + zend_indent(); + zend_file_handle_dtor(&file_handle TSRMLS_CC); + php_output_teardown(); + return SUCCESS; + break; +#endif + } + +fastcgi_request_done: + { + STR_FREE(SG(request_info).path_translated); + + php_request_shutdown((void *) 0); + + if (exit_status == 0) { + exit_status = EG(exit_status); + } + + if (free_query_string && SG(request_info).query_string) { + free(SG(request_info).query_string); + SG(request_info).query_string = NULL; + } + } + + if (!fastcgi) { + if (benchmark) { + repeats--; + if (repeats > 0) { + script_file = NULL; + php_optind = orig_optind; + php_optarg = orig_optarg; + continue; + } + } + break; + } + + /* only fastcgi will get here */ + requests++; + if (max_requests && (requests == max_requests)) { + fcgi_finish_request(request, 1); + if (bindpath) { + free(bindpath); + } + if (max_requests != 1) { + /* no need to return exit_status of the last request */ + exit_status = 0; + } + break; + } + /* end of fastcgi loop */ + } + if (request) { + fcgi_destroy_request(request); + } + fcgi_shutdown(); + + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + if (cgi_sapi_module.ini_entries) { + free(cgi_sapi_module.ini_entries); + } + } zend_catch { + exit_status = 255; + } zend_end_try(); + +out: + if (benchmark) { + int sec; +#ifdef HAVE_GETTIMEOFDAY + int usec; + + gettimeofday(&end, NULL); + sec = (int)(end.tv_sec - start.tv_sec); + if (end.tv_usec >= start.tv_usec) { + usec = (int)(end.tv_usec - start.tv_usec); + } else { + sec -= 1; + usec = (int)(end.tv_usec + 1000000 - start.tv_usec); + } + fprintf(stderr, "\nElapsed time: %d.%06d sec\n", sec, usec); +#else + time(&end); + sec = (int)(end - start); + fprintf(stderr, "\nElapsed time: %d sec\n", sec); +#endif + } + +#ifndef PHP_WIN32 +parent_out: +#endif + + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); + +#ifdef ZTS + tsrm_shutdown(); +#endif + +#if defined(PHP_WIN32) && ZEND_DEBUG && 0 + _CrtDumpMemoryLeaks(); +#endif + + return exit_status; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cgi/config.w32 b/sapi/cgi/config.w32 new file mode 100644 index 0000000..8d1d431 --- /dev/null +++ b/sapi/cgi/config.w32 @@ -0,0 +1,10 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('cgi', 'Build CGI version of PHP', 'yes'); + +if (PHP_CGI == "yes") { + ADD_FLAG("LDFLAGS_CGI", "/stack:8388608"); + SAPI('cgi', 'cgi_main.c fastcgi.c', 'php-cgi.exe'); + ADD_FLAG('LIBS_CGI', 'ws2_32.lib kernel32.lib advapi32.lib'); +} diff --git a/sapi/cgi/config9.m4 b/sapi/cgi/config9.m4 new file mode 100644 index 0000000..67251ae --- /dev/null +++ b/sapi/cgi/config9.m4 @@ -0,0 +1,76 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_ENABLE(cgi,, +[ --disable-cgi Disable building CGI version of PHP], yes, no) + +dnl +dnl CGI setup +dnl +AC_MSG_CHECKING(for CGI build) +if test "$PHP_CGI" != "no"; then + AC_MSG_RESULT(yes) + AC_MSG_CHECKING([for socklen_t in sys/socket.h]) + AC_EGREP_HEADER([socklen_t], [sys/socket.h], + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_SOCKLEN_T], [1], + [Define if the socklen_t typedef is in sys/socket.h])], + AC_MSG_RESULT([no])) + + AC_MSG_CHECKING([for sun_len in sys/un.h]) + AC_EGREP_HEADER([sun_len], [sys/un.h], + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_SOCKADDR_UN_SUN_LEN], [1], + [Define if sockaddr_un in sys/un.h contains a sun_len component])], + AC_MSG_RESULT([no])) + + AC_MSG_CHECKING([whether cross-process locking is required by accept()]) + case "`uname -sr`" in + IRIX\ 5.* | SunOS\ 5.* | UNIX_System_V\ 4.0) + AC_MSG_RESULT([yes]) + AC_DEFINE([USE_LOCKING], [1], + [Define if cross-process locking is required by accept()]) + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac + + PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/cgi/Makefile.frag) + + dnl Set filename + case $host_alias in + *cygwin* ) + SAPI_CGI_PATH=sapi/cgi/php-cgi.exe + ;; + * ) + SAPI_CGI_PATH=sapi/cgi/php-cgi + ;; + esac + + dnl Select SAPI + PHP_SELECT_SAPI(cgi, program, cgi_main.c fastcgi.c,, '$(SAPI_CGI_PATH)') + + case $host_alias in + *aix*) + if test "$php_sapi_module" = "shared"; then + BUILD_CGI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CGI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/.libs\/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_CGI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" + else + BUILD_CGI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CGI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_CGI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" + fi + ;; + *darwin*) + BUILD_CGI="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_BINARY_OBJS:.lo=.o) \$(PHP_CGI_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" + ;; + *) + BUILD_CGI="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CGI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CGI_PATH)" + ;; + esac + + dnl Expose to Makefile + PHP_SUBST(SAPI_CGI_PATH) + PHP_SUBST(BUILD_CGI) +else + AC_MSG_RESULT(yes) +fi diff --git a/sapi/cgi/fastcgi.c b/sapi/cgi/fastcgi.c new file mode 100644 index 0000000..72977b6 --- /dev/null +++ b/sapi/cgi/fastcgi.c @@ -0,0 +1,1535 @@ +/* + +----------------------------------------------------------------------+ + | 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "fastcgi.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> + +#ifdef _WIN32 + +#include <windows.h> + + typedef unsigned int in_addr_t; + + struct sockaddr_un { + short sun_family; + char sun_path[MAXPATHLEN]; + }; + + static HANDLE fcgi_accept_mutex = INVALID_HANDLE_VALUE; + static int is_impersonate = 0; + +#define FCGI_LOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + DWORD ret; \ + while ((ret = WaitForSingleObject(fcgi_accept_mutex, 1000)) == WAIT_TIMEOUT) { \ + if (in_shutdown) return -1; \ + } \ + if (ret == WAIT_FAILED) { \ + fprintf(stderr, "WaitForSingleObject() failed\n"); \ + return -1; \ + } \ + } + +#define FCGI_UNLOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + ReleaseMutex(fcgi_accept_mutex); \ + } + +#else + +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +# include <fcntl.h> +# include <sys/socket.h> +# include <sys/un.h> +# include <netinet/in.h> +# include <netinet/tcp.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <signal.h> + +# define closesocket(s) close(s) + +# if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL) +# include <sys/poll.h> +# endif +# if defined(HAVE_SYS_SELECT_H) +# include <sys/select.h> +# endif + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +# ifndef HAVE_SOCKLEN_T + typedef unsigned int socklen_t; +# endif + +# ifdef USE_LOCKING +# define FCGI_LOCK(fd) \ + do { \ + struct flock lock; \ + lock.l_type = F_WRLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLKW, &lock) != -1) { \ + break; \ + } else if (errno != EINTR || in_shutdown) { \ + return -1; \ + } \ + } while (1) + +# define FCGI_UNLOCK(fd) \ + do { \ + int orig_errno = errno; \ + while (1) { \ + struct flock lock; \ + lock.l_type = F_UNLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLK, &lock) != -1) { \ + break; \ + } else if (errno != EINTR) { \ + return -1; \ + } \ + } \ + errno = orig_errno; \ + } while (0) +# else +# define FCGI_LOCK(fd) +# define FCGI_UNLOCK(fd) +# endif + +#endif + +typedef union _sa_t { + struct sockaddr sa; + struct sockaddr_un sa_unix; + struct sockaddr_in sa_inet; +} sa_t; + +static HashTable fcgi_mgmt_vars; + +static int is_initialized = 0; +static int is_fastcgi = 0; +static int in_shutdown = 0; +static in_addr_t *allowed_clients = NULL; + +/* hash table */ + +#define FCGI_HASH_TABLE_SIZE 128 +#define FCGI_HASH_TABLE_MASK (FCGI_HASH_TABLE_SIZE - 1) +#define FCGI_HASH_SEG_SIZE 4096 + +typedef struct _fcgi_hash_bucket { + unsigned int hash_value; + unsigned int var_len; + char *var; + unsigned int val_len; + char *val; + struct _fcgi_hash_bucket *next; + struct _fcgi_hash_bucket *list_next; +} fcgi_hash_bucket; + +typedef struct _fcgi_hash_buckets { + unsigned int idx; + struct _fcgi_hash_buckets *next; + struct _fcgi_hash_bucket data[FCGI_HASH_TABLE_SIZE]; +} fcgi_hash_buckets; + +typedef struct _fcgi_data_seg { + char *pos; + char *end; + struct _fcgi_data_seg *next; + char data[1]; +} fcgi_data_seg; + +typedef struct _fcgi_hash { + fcgi_hash_bucket *hash_table[FCGI_HASH_TABLE_SIZE]; + fcgi_hash_bucket *list; + fcgi_hash_buckets *buckets; + fcgi_data_seg *data; +} fcgi_hash; + +static void fcgi_hash_init(fcgi_hash *h) +{ + memset(h->hash_table, 0, sizeof(h->hash_table)); + h->list = NULL; + h->buckets = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets)); + h->buckets->idx = 0; + h->buckets->next = NULL; + h->data = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + FCGI_HASH_SEG_SIZE); + h->data->pos = h->data->data; + h->data->end = h->data->pos + FCGI_HASH_SEG_SIZE; + h->data->next = NULL; +} + +static void fcgi_hash_destroy(fcgi_hash *h) +{ + fcgi_hash_buckets *b; + fcgi_data_seg *p; + + b = h->buckets; + while (b) { + fcgi_hash_buckets *q = b; + b = b->next; + free(q); + } + p = h->data; + while (p) { + fcgi_data_seg *q = p; + p = p->next; + free(q); + } +} + +static void fcgi_hash_clean(fcgi_hash *h) +{ + memset(h->hash_table, 0, sizeof(h->hash_table)); + h->list = NULL; + /* delete all bucket blocks except the first one */ + while (h->buckets->next) { + fcgi_hash_buckets *q = h->buckets; + + h->buckets = h->buckets->next; + free(q); + } + h->buckets->idx = 0; + /* delete all data segments except the first one */ + while (h->data->next) { + fcgi_data_seg *q = h->data; + + h->data = h->data->next; + free(q); + } + h->data->pos = h->data->data; +} + +static inline char* fcgi_hash_strndup(fcgi_hash *h, char *str, unsigned int str_len) +{ + char *ret; + + if (UNEXPECTED(h->data->pos + str_len + 1 >= h->data->end)) { + unsigned int seg_size = (str_len + 1 > FCGI_HASH_SEG_SIZE) ? str_len + 1 : FCGI_HASH_SEG_SIZE; + fcgi_data_seg *p = (fcgi_data_seg*)malloc(sizeof(fcgi_data_seg) - 1 + seg_size); + + p->pos = p->data; + p->end = p->pos + seg_size; + p->next = h->data; + h->data = p; + } + ret = h->data->pos; + memcpy(ret, str, str_len); + ret[str_len] = 0; + h->data->pos += str_len + 1; + return ret; +} + +static char* fcgi_hash_set(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, char *val, unsigned int val_len) +{ + unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK; + fcgi_hash_bucket *p = h->hash_table[idx]; + + while (UNEXPECTED(p != NULL)) { + if (UNEXPECTED(p->hash_value == hash_value) && + p->var_len == var_len && + memcmp(p->var, var, var_len) == 0) { + + p->val_len = val_len; + p->val = fcgi_hash_strndup(h, val, val_len); + return p->val; + } + p = p->next; + } + + if (UNEXPECTED(h->buckets->idx >= FCGI_HASH_TABLE_SIZE)) { + fcgi_hash_buckets *b = (fcgi_hash_buckets*)malloc(sizeof(fcgi_hash_buckets)); + b->idx = 0; + b->next = h->buckets; + h->buckets = b; + } + p = h->buckets->data + h->buckets->idx; + h->buckets->idx++; + p->next = h->hash_table[idx]; + h->hash_table[idx] = p; + p->list_next = h->list; + h->list = p; + p->hash_value = hash_value; + p->var_len = var_len; + p->var = fcgi_hash_strndup(h, var, var_len); + p->val_len = val_len; + p->val = fcgi_hash_strndup(h, val, val_len); + return p->val; +} + +static void fcgi_hash_del(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len) +{ + unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK; + fcgi_hash_bucket **p = &h->hash_table[idx]; + + while (*p != NULL) { + if ((*p)->hash_value == hash_value && + (*p)->var_len == var_len && + memcmp((*p)->var, var, var_len) == 0) { + + (*p)->val = NULL; /* NULL value means deleted */ + (*p)->val_len = 0; + *p = (*p)->next; + return; + } + p = &(*p)->next; + } +} + +static char *fcgi_hash_get(fcgi_hash *h, unsigned int hash_value, char *var, unsigned int var_len, unsigned int *val_len) +{ + unsigned int idx = hash_value & FCGI_HASH_TABLE_MASK; + fcgi_hash_bucket *p = h->hash_table[idx]; + + while (p != NULL) { + if (p->hash_value == hash_value && + p->var_len == var_len && + memcmp(p->var, var, var_len) == 0) { + *val_len = p->val_len; + return p->val; + } + p = p->next; + } + return NULL; +} + +static void fcgi_hash_apply(fcgi_hash *h, fcgi_apply_func func, void *arg TSRMLS_DC) +{ + fcgi_hash_bucket *p = h->list; + + while (p) { + if (EXPECTED(p->val != NULL)) { + func(p->var, p->var_len, p->val, p->val_len, arg TSRMLS_CC); + } + p = p->list_next; + } +} + +struct _fcgi_request { + int listen_socket; + int tcp; + int fd; + int id; + int keep; +#ifdef TCP_NODELAY + int nodelay; +#endif + int closed; + + int in_len; + int in_pad; + + fcgi_header *out_hdr; + unsigned char *out_pos; + unsigned char out_buf[1024*8]; + unsigned char reserved[sizeof(fcgi_end_request_rec)]; + + int has_env; + fcgi_hash env; +}; + +#ifdef _WIN32 + +static DWORD WINAPI fcgi_shutdown_thread(LPVOID arg) +{ + HANDLE shutdown_event = (HANDLE) arg; + WaitForSingleObject(shutdown_event, INFINITE); + in_shutdown = 1; + return 0; +} + +#else + +static void fcgi_signal_handler(int signo) +{ + if (signo == SIGUSR1 || signo == SIGTERM) { + in_shutdown = 1; + } +} + +static void fcgi_setup_signals(void) +{ + struct sigaction new_sa, old_sa; + + sigemptyset(&new_sa.sa_mask); + new_sa.sa_flags = 0; + new_sa.sa_handler = fcgi_signal_handler; + sigaction(SIGUSR1, &new_sa, NULL); + sigaction(SIGTERM, &new_sa, NULL); + sigaction(SIGPIPE, NULL, &old_sa); + if (old_sa.sa_handler == SIG_DFL) { + sigaction(SIGPIPE, &new_sa, NULL); + } +} +#endif + +int fcgi_in_shutdown(void) +{ + return in_shutdown; +} + +void fcgi_terminate(void) +{ + in_shutdown = 1; +} + +int fcgi_init(void) +{ + if (!is_initialized) { +#ifndef _WIN32 + sa_t sa; + socklen_t len = sizeof(sa); +#endif + zend_hash_init(&fcgi_mgmt_vars, 0, NULL, fcgi_free_mgmt_var_cb, 1); + fcgi_set_mgmt_var("FCGI_MPXS_CONNS", sizeof("FCGI_MPXS_CONNS")-1, "0", sizeof("0")-1); + + is_initialized = 1; +#ifdef _WIN32 +# if 0 + /* TODO: Support for TCP sockets */ + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2,0), &wsaData)) { + fprintf(stderr, "Error starting Windows Sockets. Error: %d", WSAGetLastError()); + return 0; + } +# endif + if ((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_ERROR_HANDLE) == INVALID_HANDLE_VALUE) && + (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE)) { + char *str; + DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_WAIT; + HANDLE pipe = GetStdHandle(STD_INPUT_HANDLE); + + SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL); + + str = getenv("_FCGI_SHUTDOWN_EVENT_"); + if (str != NULL) { + HANDLE shutdown_event = (HANDLE) atoi(str); + if (!CreateThread(NULL, 0, fcgi_shutdown_thread, + shutdown_event, 0, NULL)) { + return -1; + } + } + str = getenv("_FCGI_MUTEX_"); + if (str != NULL) { + fcgi_accept_mutex = (HANDLE) atoi(str); + } + return is_fastcgi = 1; + } else { + return is_fastcgi = 0; + } +#else + errno = 0; + if (getpeername(0, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) { + fcgi_setup_signals(); + return is_fastcgi = 1; + } else { + return is_fastcgi = 0; + } +#endif + } + return is_fastcgi; +} + + +int fcgi_is_fastcgi(void) +{ + if (!is_initialized) { + return fcgi_init(); + } else { + return is_fastcgi; + } +} + +void fcgi_shutdown(void) +{ + if (is_initialized) { + zend_hash_destroy(&fcgi_mgmt_vars); + } + is_fastcgi = 0; + if (allowed_clients) { + free(allowed_clients); + } +} + +#ifdef _WIN32 +/* Do some black magic with the NT security API. + * We prepare a DACL (Discretionary Access Control List) so that + * we, the creator, are allowed all access, while "Everyone Else" + * is only allowed to read and write to the pipe. + * This avoids security issues on shared hosts where a luser messes + * with the lower-level pipe settings and screws up the FastCGI service. + */ +static PACL prepare_named_pipe_acl(PSECURITY_DESCRIPTOR sd, LPSECURITY_ATTRIBUTES sa) +{ + DWORD req_acl_size; + char everyone_buf[32], owner_buf[32]; + PSID sid_everyone, sid_owner; + SID_IDENTIFIER_AUTHORITY + siaWorld = SECURITY_WORLD_SID_AUTHORITY, + siaCreator = SECURITY_CREATOR_SID_AUTHORITY; + PACL acl; + + sid_everyone = (PSID)&everyone_buf; + sid_owner = (PSID)&owner_buf; + + req_acl_size = sizeof(ACL) + + (2 * ((sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD)) + GetSidLengthRequired(1))); + + acl = malloc(req_acl_size); + + if (acl == NULL) { + return NULL; + } + + if (!InitializeSid(sid_everyone, &siaWorld, 1)) { + goto out_fail; + } + *GetSidSubAuthority(sid_everyone, 0) = SECURITY_WORLD_RID; + + if (!InitializeSid(sid_owner, &siaCreator, 1)) { + goto out_fail; + } + *GetSidSubAuthority(sid_owner, 0) = SECURITY_CREATOR_OWNER_RID; + + if (!InitializeAcl(acl, req_acl_size, ACL_REVISION)) { + goto out_fail; + } + + if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_GENERIC_READ | FILE_GENERIC_WRITE, sid_everyone)) { + goto out_fail; + } + + if (!AddAccessAllowedAce(acl, ACL_REVISION, FILE_ALL_ACCESS, sid_owner)) { + goto out_fail; + } + + if (!InitializeSecurityDescriptor(sd, SECURITY_DESCRIPTOR_REVISION)) { + goto out_fail; + } + + if (!SetSecurityDescriptorDacl(sd, TRUE, acl, FALSE)) { + goto out_fail; + } + + sa->lpSecurityDescriptor = sd; + + return acl; + +out_fail: + free(acl); + return NULL; +} +#endif + +static int is_port_number(const char *bindpath) +{ + while (*bindpath) { + if (*bindpath < '0' || *bindpath > '9') { + return 0; + } + bindpath++; + } + return 1; +} + +int fcgi_listen(const char *path, int backlog) +{ + char *s; + int tcp = 0; + char host[MAXPATHLEN]; + short port = 0; + int listen_socket; + sa_t sa; + socklen_t sock_len; +#ifdef SO_REUSEADDR +# ifdef _WIN32 + BOOL reuse = 1; +# else + int reuse = 1; +# endif +#endif + + if ((s = strchr(path, ':'))) { + port = atoi(s+1); + if (port != 0 && (s-path) < MAXPATHLEN) { + strncpy(host, path, s-path); + host[s-path] = '\0'; + tcp = 1; + } + } else if (is_port_number(path)) { + port = atoi(path); + if (port != 0) { + host[0] = '\0'; + tcp = 1; + } + } + + /* Prepare socket address */ + if (tcp) { + memset(&sa.sa_inet, 0, sizeof(sa.sa_inet)); + sa.sa_inet.sin_family = AF_INET; + sa.sa_inet.sin_port = htons(port); + sock_len = sizeof(sa.sa_inet); + + if (!*host || !strncmp(host, "*", sizeof("*")-1)) { + sa.sa_inet.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + sa.sa_inet.sin_addr.s_addr = inet_addr(host); + if (sa.sa_inet.sin_addr.s_addr == INADDR_NONE) { + struct hostent *hep; + + hep = gethostbyname(host); + if (!hep || hep->h_addrtype != AF_INET || !hep->h_addr_list[0]) { + fprintf(stderr, "Cannot resolve host name '%s'!\n", host); + return -1; + } else if (hep->h_addr_list[1]) { + fprintf(stderr, "Host '%s' has multiple addresses. You must choose one explicitly!\n", host); + return -1; + } + sa.sa_inet.sin_addr.s_addr = ((struct in_addr*)hep->h_addr_list[0])->s_addr; + } + } + } else { +#ifdef _WIN32 + SECURITY_DESCRIPTOR sd; + SECURITY_ATTRIBUTES saw; + PACL acl; + HANDLE namedPipe; + + memset(&sa, 0, sizeof(saw)); + saw.nLength = sizeof(saw); + saw.bInheritHandle = FALSE; + acl = prepare_named_pipe_acl(&sd, &saw); + + namedPipe = CreateNamedPipe(path, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_READMODE_BYTE, + PIPE_UNLIMITED_INSTANCES, + 8192, 8192, 0, &saw); + if (namedPipe == INVALID_HANDLE_VALUE) { + return -1; + } + listen_socket = _open_osfhandle((long)namedPipe, 0); + if (!is_initialized) { + fcgi_init(); + } + is_fastcgi = 1; + return listen_socket; + +#else + int path_len = strlen(path); + + if (path_len >= sizeof(sa.sa_unix.sun_path)) { + fprintf(stderr, "Listening socket's path name is too long.\n"); + return -1; + } + + memset(&sa.sa_unix, 0, sizeof(sa.sa_unix)); + sa.sa_unix.sun_family = AF_UNIX; + memcpy(sa.sa_unix.sun_path, path, path_len + 1); + sock_len = (size_t)(((struct sockaddr_un *)0)->sun_path) + path_len; +#ifdef HAVE_SOCKADDR_UN_SUN_LEN + sa.sa_unix.sun_len = sock_len; +#endif + unlink(path); +#endif + } + + /* Create, bind socket and start listen on it */ + if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || +#ifdef SO_REUSEADDR + setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)) < 0 || +#endif + bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 || + listen(listen_socket, backlog) < 0) { + + fprintf(stderr, "Cannot bind/listen socket - [%d] %s.\n",errno, strerror(errno)); + return -1; + } + + if (!tcp) { + chmod(path, 0777); + } else { + char *ip = getenv("FCGI_WEB_SERVER_ADDRS"); + char *cur, *end; + int n; + + if (ip) { + ip = strdup(ip); + cur = ip; + n = 0; + while (*cur) { + if (*cur == ',') n++; + cur++; + } + allowed_clients = malloc(sizeof(in_addr_t) * (n+2)); + n = 0; + cur = ip; + while (cur) { + end = strchr(cur, ','); + if (end) { + *end = 0; + end++; + } + allowed_clients[n] = inet_addr(cur); + if (allowed_clients[n] == INADDR_NONE) { + fprintf(stderr, "Wrong IP address '%s' in FCGI_WEB_SERVER_ADDRS\n", cur); + } + n++; + cur = end; + } + allowed_clients[n] = INADDR_NONE; + free(ip); + } + } + + if (!is_initialized) { + fcgi_init(); + } + is_fastcgi = 1; + +#ifdef _WIN32 + if (tcp) { + listen_socket = _open_osfhandle((long)listen_socket, 0); + } +#else + fcgi_setup_signals(); +#endif + return listen_socket; +} + +fcgi_request *fcgi_init_request(int listen_socket) +{ + fcgi_request *req = (fcgi_request*)calloc(1, sizeof(fcgi_request)); + req->listen_socket = listen_socket; + req->fd = -1; + req->id = -1; + + req->in_len = 0; + req->in_pad = 0; + + req->out_hdr = NULL; + req->out_pos = req->out_buf; + +#ifdef _WIN32 + req->tcp = !GetNamedPipeInfo((HANDLE)_get_osfhandle(req->listen_socket), NULL, NULL, NULL, NULL); +#endif + +#ifdef TCP_NODELAY + req->nodelay = 0; +#endif + + fcgi_hash_init(&req->env); + + return req; +} + +void fcgi_destroy_request(fcgi_request *req) +{ + fcgi_hash_destroy(&req->env); + free(req); +} + +static inline ssize_t safe_write(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + errno = 0; +#ifdef _WIN32 + if (!req->tcp) { + ret = write(req->fd, ((char*)buf)+n, count-n); + } else { + ret = send(req->fd, ((char*)buf)+n, count-n, 0); + if (ret <= 0) { + errno = WSAGetLastError(); + } + } +#else + ret = write(req->fd, ((char*)buf)+n, count-n); +#endif + if (ret > 0) { + n += ret; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + errno = 0; +#ifdef _WIN32 + if (!req->tcp) { + ret = read(req->fd, ((char*)buf)+n, count-n); + } else { + ret = recv(req->fd, ((char*)buf)+n, count-n, 0); + if (ret <= 0) { + errno = WSAGetLastError(); + } + } +#else + ret = read(req->fd, ((char*)buf)+n, count-n); +#endif + if (ret > 0) { + n += ret; + } else if (ret == 0 && errno == 0) { + return n; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline int fcgi_make_header(fcgi_header *hdr, fcgi_request_type type, int req_id, int len) +{ + int pad = ((len + 7) & ~7) - len; + + hdr->contentLengthB0 = (unsigned char)(len & 0xff); + hdr->contentLengthB1 = (unsigned char)((len >> 8) & 0xff); + hdr->paddingLength = (unsigned char)pad; + hdr->requestIdB0 = (unsigned char)(req_id & 0xff); + hdr->requestIdB1 = (unsigned char)((req_id >> 8) & 0xff); + hdr->reserved = 0; + hdr->type = type; + hdr->version = FCGI_VERSION_1; + if (pad) { + memset(((unsigned char*)hdr) + sizeof(fcgi_header) + len, 0, pad); + } + return pad; +} + +static int fcgi_get_params(fcgi_request *req, unsigned char *p, unsigned char *end) +{ + unsigned int name_len, val_len; + + while (p < end) { + name_len = *p++; + if (UNEXPECTED(name_len >= 128)) { + if (UNEXPECTED(p + 3 >= end)) return 0; + name_len = ((name_len & 0x7f) << 24); + name_len |= (*p++ << 16); + name_len |= (*p++ << 8); + name_len |= *p++; + } + if (UNEXPECTED(p >= end)) return 0; + val_len = *p++; + if (UNEXPECTED(val_len >= 128)) { + if (UNEXPECTED(p + 3 >= end)) return 0; + val_len = ((val_len & 0x7f) << 24); + val_len |= (*p++ << 16); + val_len |= (*p++ << 8); + val_len |= *p++; + } + if (UNEXPECTED(name_len + val_len > (unsigned int) (end - p))) { + /* Malformated request */ + return 0; + } + fcgi_hash_set(&req->env, FCGI_HASH_FUNC(p, name_len), (char*)p, name_len, (char*)p + name_len, val_len); + p += name_len + val_len; + } + return 1; +} + +static int fcgi_read_request(fcgi_request *req) +{ + fcgi_header hdr; + int len, padding; + unsigned char buf[FCGI_MAX_LENGTH+8]; + + req->keep = 0; + req->closed = 0; + req->in_len = 0; + req->out_hdr = NULL; + req->out_pos = req->out_buf; + req->has_env = 1; + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_STDIN && len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + + if (len + padding > FCGI_MAX_LENGTH) { + return 0; + } + + req->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0; + + if (hdr.type == FCGI_BEGIN_REQUEST && len == sizeof(fcgi_begin_request)) { + if (safe_read(req, buf, len+padding) != len+padding) { + return 0; + } + + req->keep = (((fcgi_begin_request*)buf)->flags & FCGI_KEEP_CONN); +#ifdef TCP_NODELAY + if (req->keep && req->tcp && !req->nodelay) { +# ifdef _WIN32 + BOOL on = 1; +# else + int on = 1; +# endif + + setsockopt(req->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on)); + req->nodelay = 1; + } +#endif + switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) { + case FCGI_RESPONDER: + fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "RESPONDER", sizeof("RESPONDER")-1); + break; + case FCGI_AUTHORIZER: + fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "AUTHORIZER", sizeof("AUTHORIZER")-1); + break; + case FCGI_FILTER: + fcgi_hash_set(&req->env, FCGI_HASH_FUNC("FCGI_ROLE", sizeof("FCGI_ROLE")-1), "FCGI_ROLE", sizeof("FCGI_ROLE")-1, "FILTER", sizeof("FILTER")-1); + break; + default: + return 0; + } + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_PARAMS && len > 0) { + if (len + padding > FCGI_MAX_LENGTH) { + return 0; + } + + if (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + + if (!fcgi_get_params(req, buf, buf+len)) { + req->keep = 0; + return 0; + } + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + req->keep = 0; + return 0; + } + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + } else if (hdr.type == FCGI_GET_VALUES) { + unsigned char *p = buf + sizeof(fcgi_header); + zval ** value; + unsigned int zlen; + fcgi_hash_bucket *q; + + if (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + + if (!fcgi_get_params(req, buf, buf+len)) { + req->keep = 0; + return 0; + } + + q = req->env.list; + while (q != NULL) { + if (zend_hash_find(&fcgi_mgmt_vars, q->var, q->var_len, (void**) &value) != SUCCESS) { + continue; + } + zlen = Z_STRLEN_PP(value); + if ((p + 4 + 4 + q->var_len + zlen) >= (buf + sizeof(buf))) { + break; + } + if (q->var_len < 0x80) { + *p++ = q->var_len; + } else { + *p++ = ((q->var_len >> 24) & 0xff) | 0x80; + *p++ = (q->var_len >> 16) & 0xff; + *p++ = (q->var_len >> 8) & 0xff; + *p++ = q->var_len & 0xff; + } + if (zlen < 0x80) { + *p++ = zlen; + } else { + *p++ = ((zlen >> 24) & 0xff) | 0x80; + *p++ = (zlen >> 16) & 0xff; + *p++ = (zlen >> 8) & 0xff; + *p++ = zlen & 0xff; + } + memcpy(p, q->var, q->var_len); + p += q->var_len; + memcpy(p, Z_STRVAL_PP(value), zlen); + p += zlen; + } + len = p - buf - sizeof(fcgi_header); + len += fcgi_make_header((fcgi_header*)buf, FCGI_GET_VALUES_RESULT, 0, len); + if (safe_write(req, buf, sizeof(fcgi_header)+len) != (int)sizeof(fcgi_header)+len) { + req->keep = 0; + return 0; + } + return 0; + } else { + return 0; + } + + return 1; +} + +int fcgi_read(fcgi_request *req, char *str, int len) +{ + int ret, n, rest; + fcgi_header hdr; + unsigned char buf[255]; + + n = 0; + rest = len; + while (rest > 0) { + if (req->in_len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1 || + hdr.type != FCGI_STDIN) { + req->keep = 0; + return 0; + } + req->in_len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + req->in_pad = hdr.paddingLength; + if (req->in_len == 0) { + return n; + } + } + + if (req->in_len >= rest) { + ret = safe_read(req, str, rest); + } else { + ret = safe_read(req, str, req->in_len); + } + if (ret < 0) { + req->keep = 0; + return ret; + } else if (ret > 0) { + req->in_len -= ret; + rest -= ret; + n += ret; + str += ret; + if (req->in_len == 0) { + if (req->in_pad) { + if (safe_read(req, buf, req->in_pad) != req->in_pad) { + req->keep = 0; + return ret; + } + } + } else { + return n; + } + } else { + return n; + } + } + return n; +} + +static inline void fcgi_close(fcgi_request *req, int force, int destroy) +{ + if (destroy && req->has_env) { + fcgi_hash_clean(&req->env); + req->has_env = 0; + } + +#ifdef _WIN32 + if (is_impersonate && !req->tcp) { + RevertToSelf(); + } +#endif + + if ((force || !req->keep) && req->fd >= 0) { +#ifdef _WIN32 + if (!req->tcp) { + HANDLE pipe = (HANDLE)_get_osfhandle(req->fd); + + if (!force) { + FlushFileBuffers(pipe); + } + DisconnectNamedPipe(pipe); + } else { + if (!force) { + fcgi_header buf; + + shutdown(req->fd, 1); + /* read the last FCGI_STDIN header (it may be omitted) */ + recv(req->fd, &buf, sizeof(buf), 0); + } + closesocket(req->fd); + } +#else + if (!force) { + fcgi_header buf; + + shutdown(req->fd, 1); + /* read the last FCGI_STDIN header (it may be omitted) */ + recv(req->fd, &buf, sizeof(buf), 0); + } + close(req->fd); +#endif +#ifdef TCP_NODELAY + req->nodelay = 0; +#endif + req->fd = -1; + } +} + +int fcgi_accept_request(fcgi_request *req) +{ +#ifdef _WIN32 + HANDLE pipe; + OVERLAPPED ov; +#endif + + while (1) { + if (req->fd < 0) { + while (1) { + if (in_shutdown) { + return -1; + } +#ifdef _WIN32 + if (!req->tcp) { + pipe = (HANDLE)_get_osfhandle(req->listen_socket); + FCGI_LOCK(req->listen_socket); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ConnectNamedPipe(pipe, &ov)) { + errno = GetLastError(); + if (errno == ERROR_IO_PENDING) { + while (WaitForSingleObject(ov.hEvent, 1000) == WAIT_TIMEOUT) { + if (in_shutdown) { + CloseHandle(ov.hEvent); + FCGI_UNLOCK(req->listen_socket); + return -1; + } + } + } else if (errno != ERROR_PIPE_CONNECTED) { + } + } + CloseHandle(ov.hEvent); + req->fd = req->listen_socket; + FCGI_UNLOCK(req->listen_socket); + } else { + SOCKET listen_socket = (SOCKET)_get_osfhandle(req->listen_socket); +#else + { + int listen_socket = req->listen_socket; +#endif + sa_t sa; + socklen_t len = sizeof(sa); + + FCGI_LOCK(req->listen_socket); + req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len); + FCGI_UNLOCK(req->listen_socket); + if (req->fd >= 0) { + if (((struct sockaddr *)&sa)->sa_family == AF_INET) { +#ifndef _WIN32 + req->tcp = 1; +#endif + if (allowed_clients) { + int n = 0; + int allowed = 0; + + while (allowed_clients[n] != INADDR_NONE) { + if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) { + allowed = 1; + break; + } + n++; + } + if (!allowed) { + fprintf(stderr, "Connection from disallowed IP address '%s' is dropped.\n", inet_ntoa(sa.sa_inet.sin_addr)); + closesocket(req->fd); + req->fd = -1; + continue; + } + } +#ifndef _WIN32 + } else { + req->tcp = 0; +#endif + } + } + } + +#ifdef _WIN32 + if (req->fd < 0 && (in_shutdown || errno != EINTR)) { +#else + if (req->fd < 0 && (in_shutdown || (errno != EINTR && errno != ECONNABORTED))) { +#endif + return -1; + } + +#ifdef _WIN32 + break; +#else + if (req->fd >= 0) { +#if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL) + struct pollfd fds; + int ret; + + fds.fd = req->fd; + fds.events = POLLIN; + fds.revents = 0; + do { + errno = 0; + ret = poll(&fds, 1, 5000); + } while (ret < 0 && errno == EINTR); + if (ret > 0 && (fds.revents & POLLIN)) { + break; + } + fcgi_close(req, 1, 0); +#else + if (req->fd < FD_SETSIZE) { + struct timeval tv = {5,0}; + fd_set set; + int ret; + + FD_ZERO(&set); + FD_SET(req->fd, &set); + do { + errno = 0; + ret = select(req->fd + 1, &set, NULL, NULL, &tv) >= 0; + } while (ret < 0 && errno == EINTR); + if (ret > 0 && FD_ISSET(req->fd, &set)) { + break; + } + fcgi_close(req, 1, 0); + } else { + fprintf(stderr, "Too many open file descriptors. FD_SETSIZE limit exceeded."); + fcgi_close(req, 1, 0); + } +#endif + } +#endif + } + } else if (in_shutdown) { + return -1; + } + if (fcgi_read_request(req)) { +#ifdef _WIN32 + if (is_impersonate && !req->tcp) { + pipe = (HANDLE)_get_osfhandle(req->fd); + if (!ImpersonateNamedPipeClient(pipe)) { + fcgi_close(req, 1, 1); + continue; + } + } +#endif + return req->fd; + } else { + fcgi_close(req, 1, 1); + } + } +} + +static inline fcgi_header* open_packet(fcgi_request *req, fcgi_request_type type) +{ + req->out_hdr = (fcgi_header*) req->out_pos; + req->out_hdr->type = type; + req->out_pos += sizeof(fcgi_header); + return req->out_hdr; +} + +static inline void close_packet(fcgi_request *req) +{ + if (req->out_hdr) { + int len = req->out_pos - ((unsigned char*)req->out_hdr + sizeof(fcgi_header)); + + req->out_pos += fcgi_make_header(req->out_hdr, (fcgi_request_type)req->out_hdr->type, req->id, len); + req->out_hdr = NULL; + } +} + +int fcgi_flush(fcgi_request *req, int close) +{ + int len; + + close_packet(req); + + len = req->out_pos - req->out_buf; + + if (close) { + fcgi_end_request_rec *rec = (fcgi_end_request_rec*)(req->out_pos); + + fcgi_make_header(&rec->hdr, FCGI_END_REQUEST, req->id, sizeof(fcgi_end_request)); + rec->body.appStatusB3 = 0; + rec->body.appStatusB2 = 0; + rec->body.appStatusB1 = 0; + rec->body.appStatusB0 = 0; + rec->body.protocolStatus = FCGI_REQUEST_COMPLETE; + len += sizeof(fcgi_end_request_rec); + } + + if (safe_write(req, req->out_buf, len) != len) { + req->keep = 0; + return 0; + } + + req->out_pos = req->out_buf; + return 1; +} + +int fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len) +{ + int limit, rest; + + if (len <= 0) { + return 0; + } + + if (req->out_hdr && req->out_hdr->type != type) { + close_packet(req); + } +#if 0 + /* Unoptimized, but clear version */ + rest = len; + while (rest > 0) { + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + + if (!req->out_hdr) { + if (limit < sizeof(fcgi_header)) { + if (!fcgi_flush(req, 0)) { + return -1; + } + } + open_packet(req, type); + } + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + if (rest < limit) { + memcpy(req->out_pos, str, rest); + req->out_pos += rest; + return len; + } else { + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + rest -= limit; + str += limit; + if (!fcgi_flush(req, 0)) { + return -1; + } + } + } +#else + /* Optimized version */ + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + if (!req->out_hdr) { + limit -= sizeof(fcgi_header); + if (limit < 0) limit = 0; + } + + if (len < limit) { + if (!req->out_hdr) { + open_packet(req, type); + } + memcpy(req->out_pos, str, len); + req->out_pos += len; + } else if (len - limit < sizeof(req->out_buf) - sizeof(fcgi_header)) { + if (!req->out_hdr) { + open_packet(req, type); + } + if (limit > 0) { + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + } + if (!fcgi_flush(req, 0)) { + return -1; + } + if (len > limit) { + open_packet(req, type); + memcpy(req->out_pos, str + limit, len - limit); + req->out_pos += len - limit; + } + } else { + int pos = 0; + int pad; + + close_packet(req); + while ((len - pos) > 0xffff) { + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, 0xfff8); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, 0xfff8) != 0xfff8) { + req->keep = 0; + return -1; + } + pos += 0xfff8; + } + + pad = (((len - pos) + 7) & ~7) - (len - pos); + rest = pad ? 8 - pad : 0; + + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, (len - pos) - rest); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, (len - pos) - rest) != (len - pos) - rest) { + req->keep = 0; + return -1; + } + if (pad) { + open_packet(req, type); + memcpy(req->out_pos, str + len - rest, rest); + req->out_pos += rest; + } + } +#endif + return len; +} + +int fcgi_finish_request(fcgi_request *req, int force_close) +{ + int ret = 1; + + if (req->fd >= 0) { + if (!req->closed) { + ret = fcgi_flush(req, 1); + req->closed = 1; + } + fcgi_close(req, force_close, 1); + } + return ret; +} + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len) +{ + unsigned int val_len; + + if (!req) return NULL; + + return fcgi_hash_get(&req->env, FCGI_HASH_FUNC(var, var_len), (char*)var, var_len, &val_len); +} + +char* fcgi_quick_getenv(fcgi_request *req, const char* var, int var_len, unsigned int hash_value) +{ + unsigned int val_len; + + return fcgi_hash_get(&req->env, hash_value, (char*)var, var_len, &val_len); +} + +char* fcgi_putenv(fcgi_request *req, char* var, int var_len, char* val) +{ + if (!req) return NULL; + if (val == NULL) { + fcgi_hash_del(&req->env, FCGI_HASH_FUNC(var, var_len), var, var_len); + return NULL; + } else { + return fcgi_hash_set(&req->env, FCGI_HASH_FUNC(var, var_len), var, var_len, val, strlen(val)); + } +} + +char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val) +{ + if (val == NULL) { + fcgi_hash_del(&req->env, hash_value, var, var_len); + return NULL; + } else { + return fcgi_hash_set(&req->env, hash_value, var, var_len, val, strlen(val)); + } +} + +void fcgi_loadenv(fcgi_request *req, fcgi_apply_func func, zval *array TSRMLS_DC) +{ + fcgi_hash_apply(&req->env, func, array TSRMLS_CC); +} + +#ifdef _WIN32 +void fcgi_impersonate(void) +{ + char *os_name; + + os_name = getenv("OS"); + if (os_name && stricmp(os_name, "Windows_NT") == 0) { + is_impersonate = 1; + } +} +#endif + +void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len) +{ + zval * zvalue; + zvalue = pemalloc(sizeof(*zvalue), 1); + Z_TYPE_P(zvalue) = IS_STRING; + Z_STRVAL_P(zvalue) = pestrndup(value, value_len, 1); + Z_STRLEN_P(zvalue) = value_len; + zend_hash_add(&fcgi_mgmt_vars, name, name_len, &zvalue, sizeof(zvalue), NULL); +} + +void fcgi_free_mgmt_var_cb(void * ptr) +{ + zval ** var = (zval **)ptr; + pefree(Z_STRVAL_PP(var), 1); + pefree(*var, 1); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cgi/fastcgi.h b/sapi/cgi/fastcgi.h new file mode 100644 index 0000000..f1f464d --- /dev/null +++ b/sapi/cgi/fastcgi.h @@ -0,0 +1,151 @@ +/* + +----------------------------------------------------------------------+ + | 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +/* FastCGI protocol */ + +#define FCGI_VERSION_1 1 + +#define FCGI_MAX_LENGTH 0xffff + +#define FCGI_KEEP_CONN 1 + +/* this is near the perfect hash function for most useful FastCGI variables + * which combines efficiency and minimal hash collisions + */ + +#define FCGI_HASH_FUNC(var, var_len) \ + (UNEXPECTED(var_len < 3) ? var_len : \ + (((unsigned int)var[3]) << 2) + \ + (((unsigned int)var[var_len-2]) << 4) + \ + (((unsigned int)var[var_len-1]) << 2) + \ + var_len) + +#define FCGI_GETENV(request, name) \ + fcgi_quick_getenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1)) + +#define FCGI_PUTENV(request, name, value) \ + fcgi_quick_putenv(request, name, sizeof(name)-1, FCGI_HASH_FUNC(name, sizeof(name)-1), value) + +typedef enum _fcgi_role { + FCGI_RESPONDER = 1, + FCGI_AUTHORIZER = 2, + FCGI_FILTER = 3 +} fcgi_role; + +typedef enum _fcgi_request_type { + FCGI_BEGIN_REQUEST = 1, /* [in] */ + FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */ + FCGI_END_REQUEST = 3, /* [out] */ + FCGI_PARAMS = 4, /* [in] environment variables */ + FCGI_STDIN = 5, /* [in] post data */ + FCGI_STDOUT = 6, /* [out] response */ + FCGI_STDERR = 7, /* [out] errors */ + FCGI_DATA = 8, /* [in] filter data (not supported) */ + FCGI_GET_VALUES = 9, /* [in] */ + FCGI_GET_VALUES_RESULT = 10 /* [out] */ +} fcgi_request_type; + +typedef enum _fcgi_protocol_status { + FCGI_REQUEST_COMPLETE = 0, + FCGI_CANT_MPX_CONN = 1, + FCGI_OVERLOADED = 2, + FCGI_UNKNOWN_ROLE = 3 +} dcgi_protocol_status; + +typedef struct _fcgi_header { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} fcgi_header; + +typedef struct _fcgi_begin_request { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} fcgi_begin_request; + +typedef struct _fcgi_begin_request_rec { + fcgi_header hdr; + fcgi_begin_request body; +} fcgi_begin_request_rec; + +typedef struct _fcgi_end_request { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} fcgi_end_request; + +typedef struct _fcgi_end_request_rec { + fcgi_header hdr; + fcgi_end_request body; +} fcgi_end_request_rec; + +/* FastCGI client API */ + +typedef void (*fcgi_apply_func)(char *var, unsigned int var_len, char *val, unsigned int val_len, void *arg TSRMLS_DC); + +typedef struct _fcgi_request fcgi_request; + +int fcgi_init(void); +void fcgi_shutdown(void); +int fcgi_is_fastcgi(void); +int fcgi_in_shutdown(void); +void fcgi_terminate(void); +int fcgi_listen(const char *path, int backlog); +fcgi_request* fcgi_init_request(int listen_socket); +void fcgi_destroy_request(fcgi_request *req); +int fcgi_accept_request(fcgi_request *req); +int fcgi_finish_request(fcgi_request *req, int force_close); + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len); +char* fcgi_putenv(fcgi_request *req, char* var, int var_len, char* val); +char* fcgi_quick_getenv(fcgi_request *req, const char* var, int var_len, unsigned int hash_value); +char* fcgi_quick_putenv(fcgi_request *req, char* var, int var_len, unsigned int hash_value, char* val); +void fcgi_loadenv(fcgi_request *req, fcgi_apply_func load_func, zval *array TSRMLS_DC); + +int fcgi_read(fcgi_request *req, char *str, int len); + +int fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len); +int fcgi_flush(fcgi_request *req, int close); + +#ifdef PHP_WIN32 +void fcgi_impersonate(void); +#endif + +void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len); +void fcgi_free_mgmt_var_cb(void * ptr); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cgi/php.sym b/sapi/cgi/php.sym new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sapi/cgi/php.sym diff --git a/sapi/cgi/tests/001.phpt b/sapi/cgi/tests/001.phpt new file mode 100644 index 0000000..74c694f --- /dev/null +++ b/sapi/cgi/tests/001.phpt @@ -0,0 +1,22 @@ +--TEST-- +version string +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +var_dump(`$php -n -v`); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "PHP %s (cgi%s (built: %s +Copyright (c) 1997-20%s The PHP Group +Zend Engine v%s, Copyright (c) 1998-20%s Zend Technologies +" +Done diff --git a/sapi/cgi/tests/002.phpt b/sapi/cgi/tests/002.phpt new file mode 100644 index 0000000..884e652 --- /dev/null +++ b/sapi/cgi/tests/002.phpt @@ -0,0 +1,52 @@ +--TEST-- +defining INI options with -d +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$file = dirname(__FILE__)."/002.test.php"; + +file_put_contents($file, '<?php var_dump(ini_get("max_execution_time")); ?>'); + +var_dump(`$php -n -d max_execution_time=111 $file`); +var_dump(`$php -n -d max_execution_time=500 $file`); +var_dump(`$php -n -d max_execution_time=500 -d max_execution_time=555 $file`); + +file_put_contents($file, '<?php var_dump(ini_get("max_execution_time")); var_dump(ini_get("upload_tmp_dir")); ?>'); + +var_dump(`$php -n -d upload_tmp_dir=/test/path -d max_execution_time=555 $file`); + +unlink($file); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +%unicode|string%(3) "111" +" +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +%unicode|string%(3) "500" +" +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +%unicode|string%(3) "555" +" +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +%unicode|string%(3) "555" +%unicode|string%(10) "/test/path" +" +Done diff --git a/sapi/cgi/tests/003.phpt b/sapi/cgi/tests/003.phpt new file mode 100644 index 0000000..5337433 --- /dev/null +++ b/sapi/cgi/tests/003.phpt @@ -0,0 +1,62 @@ +--TEST-- +strip comments and whitespace with -w +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$filename = dirname(__FILE__).'/003.test.php'; +$code =' +<?php +/* some test script */ + +class test { /* {{{ */ + public $var = "test"; //test var +#perl style comment + private $pri; /* private attr */ + + function foo(/* void */) { + } +} +/* }}} */ + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`$php -n -w "$filename"`); +var_dump(`$php -n -w "wrong"`); +var_dump(`echo "<?php /* comment */ class test {\n // comment \n function foo() {} } ?>" | $php -n -w`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + + +<?php + class test { public $var = "test"; private $pri; function foo() { } } ?> +" +string(%d) "Status: 404 Not Found +X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +No input file specified. +" +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +<?php class test { function foo() {} } ?> +" +Done diff --git a/sapi/cgi/tests/004.phpt b/sapi/cgi/tests/004.phpt new file mode 100644 index 0000000..c841b68 --- /dev/null +++ b/sapi/cgi/tests/004.phpt @@ -0,0 +1,43 @@ +--TEST-- +execute a file with -f +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$filename = dirname(__FILE__).'/004.test.php'; +$code =' +<?php + +class test { + private $pri; +} + +var_dump(test::$pri); +?> +'; + +file_put_contents($filename, $code); + +var_dump(`$php -n -f "$filename" 2>/dev/null`); +var_dump(`$php -n -f "wrong"`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) " +<br /> +<b>Fatal error</b>: Cannot access private property test::$pri in <b>%s004.test.php</b> on line <b>8</b><br /> +" +string(25) "No input file specified. +" +Done diff --git a/sapi/cgi/tests/005.phpt b/sapi/cgi/tests/005.phpt new file mode 100644 index 0000000..34a28f9 --- /dev/null +++ b/sapi/cgi/tests/005.phpt @@ -0,0 +1,27 @@ +--TEST-- +using invalid combinations of cmdline options +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +var_dump(`$php -n -a -f 'wrong'`); +var_dump(`$php -n -f 'wrong' -a`); + +echo "Done\n"; +?> +--EXPECTF-- +string(51) "No input file specified. +Interactive mode enabled + +" +string(51) "No input file specified. +Interactive mode enabled + +" +Done diff --git a/sapi/cgi/tests/006.phpt b/sapi/cgi/tests/006.phpt new file mode 100644 index 0000000..a2b2b29 --- /dev/null +++ b/sapi/cgi/tests/006.phpt @@ -0,0 +1,62 @@ +--TEST-- +syntax check +--SKIPIF-- +<?php include "skipif.inc"; ?> +--INI-- +display_errors=stdout +--FILE-- +<?php +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$filename = dirname(__FILE__)."/006.test.php"; + +$code = ' +<?php + +$test = "var"; + +class test { + private $var; +} + +echo test::$var; + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -l "$filename"`); +var_dump(`"$php" -n -l some.unknown`); + +$code = ' +<?php + +class test + private $var; +} + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -l "$filename" 2>/dev/null`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "No syntax errors detected in %s006.test.php +" +string(%d) "No input file specified. +" +string(%d) "<br /> +<b>Parse error</b>: %s expecting %s{%s in <b>%s006.test.php</b> on line <b>5</b><br /> +Errors parsing %s006.test.php +" +Done diff --git a/sapi/cgi/tests/007.phpt b/sapi/cgi/tests/007.phpt new file mode 100644 index 0000000..f2c9c02 --- /dev/null +++ b/sapi/cgi/tests/007.phpt @@ -0,0 +1,22 @@ +--TEST-- +invalid arguments and error messages +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +var_dump(`"$php" -n -f some.php -f some.php`); +var_dump(`"$php" -n -s -w -l`); + +?> +===DONE=== +--EXPECTF-- +string(25) "No input file specified. +" +string(31) "No syntax errors detected in - +" +===DONE=== diff --git a/sapi/cgi/tests/008.phpt b/sapi/cgi/tests/008.phpt new file mode 100644 index 0000000..7537664 --- /dev/null +++ b/sapi/cgi/tests/008.phpt @@ -0,0 +1,54 @@ +--TEST-- +syntax highlighting +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$filename = dirname(__FILE__)."/008.test.php"; +$code = ' +<?php +$test = "var"; //var +/* test class */ +class test { + private $var = array(); + + public static function foo(Test $arg) { + echo "hello"; + var_dump($this); + } +} + +$o = new test; +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -s "$filename"`); +var_dump(`"$php" -n -s "unknown"`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +<code><span style="color: #000000"> +<br /><span style="color: #0000BB"><?php<br />$test </span><span style="color: #007700">= </span><span style="color: #DD0000">"var"</span><span style="color: #007700">; </span><span style="color: #FF8000">//var<br />/* test class */<br /></span><span style="color: #007700">class </span><span style="color: #0000BB">test </span><span style="color: #007700">{<br /> private </span><span style="color: #0000BB">$var </span><span style="color: #007700">= array();<br /><br /> public static function </span><span style="color: #0000BB">foo</span><span style="color: #007700">(</span><span style="color: #0000BB">Test $arg</span><span style="color: #007700">) {<br /> echo </span><span style="color: #DD0000">"hello"</span><span style="color: #007700">;<br /> </span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$this</span><span style="color: #007700">);<br /> }<br />}<br /><br /></span><span style="color: #0000BB">$o </span><span style="color: #007700">= new </span><span style="color: #0000BB">test</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">?><br /></span> +</span> +</code>" +string(%d) "Status: 404 Not Found +X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +No input file specified. +" +Done diff --git a/sapi/cgi/tests/009.phpt b/sapi/cgi/tests/009.phpt new file mode 100644 index 0000000..c8e9ae1 --- /dev/null +++ b/sapi/cgi/tests/009.phpt @@ -0,0 +1,30 @@ +--TEST-- +path info request without exported PATH_INFO +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$f = tempnam(sys_get_temp_dir(), 'cgitest'); + +putenv("TRANSLATED_PATH=".$f."/x"); +putenv("SCRIPT_FILENAME=".$f."/x"); +file_put_contents($f, '<?php var_dump($_SERVER["TRANSLATED_PATH"]); ?>'); + +echo (`$php -n $f`); + +echo "Done\n"; + +@unlink($f); +?> +--EXPECTF-- +X-Powered-By: PHP/%s +Content-type: text/html%r; charset=.*|%r + +string(%d) "%s/x" +Done diff --git a/sapi/cgi/tests/010.phpt b/sapi/cgi/tests/010.phpt new file mode 100644 index 0000000..e633ad2 --- /dev/null +++ b/sapi/cgi/tests/010.phpt @@ -0,0 +1,53 @@ +--TEST-- +Bug #45860 (header() function fails to correctly replace all Status lines) +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$f = tempnam(sys_get_temp_dir(), 'cgitest'); + +putenv("TRANSLATED_PATH=".$f."/x"); +putenv("SCRIPT_FILENAME=".$f."/x"); +file_put_contents($f, '<?php +header("HTTP/1.1 403 Forbidden"); +header("Status: 403 Also Forbidden"); +?>'); + +echo (`$php -n $f`); + +file_put_contents($f, '<?php +header("HTTP/1.1 403 Forbidden"); +?>'); + +echo (`$php -n $f`); + +file_put_contents($f, '<?php +header("Status: 403 Also Forbidden"); +?>'); + +echo (`$php -n $f`); + +echo "Done\n"; + +@unlink($f); +?> +--EXPECTF-- +Status: 403 Forbidden +X-Powered-By: PHP/%s +Content-type: text/html + +Status: 403 Forbidden +X-Powered-By: PHP/%s +Content-type: text/html + +X-Powered-By: PHP/%s +Status: 403 Also Forbidden +Content-type: text/html + +Done diff --git a/sapi/cgi/tests/011.phpt b/sapi/cgi/tests/011.phpt new file mode 100644 index 0000000..177df02 --- /dev/null +++ b/sapi/cgi/tests/011.phpt @@ -0,0 +1,165 @@ +--TEST-- +header_remove() +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$f = tempnam(sys_get_temp_dir(), 'cgitest'); + +function test($script) { + file_put_contents($GLOBALS['f'], $script); + $cmd = escapeshellcmd($GLOBALS['php']); + $cmd .= ' -n -dreport_zend_debug=0 -dhtml_errors=0 ' . escapeshellarg($GLOBALS['f']); + echo "----------\n"; + echo rtrim($script) . "\n"; + echo "----------\n"; + passthru($cmd); +} + +test('<?php ?>'); +test('<?php header_remove(); ?>'); +test('<?php header_remove("X-Foo"); ?>'); +test('<?php +header("X-Foo: Bar"); +?>'); +test('<?php +header("X-Foo: Bar"); +header("X-Bar: Baz"); +header_remove("X-Foo"); +?>'); +test('<?php +header("X-Foo: Bar"); +header_remove("X-Foo: Bar"); +?>'); +test('<?php +header("X-Foo: Bar"); +header_remove("X-Foo:"); +?>'); +test('<?php +header("X-Foo: Bar"); +header_remove(); +?>'); +test('<?php +header_remove(""); +?>'); +test('<?php +header_remove(":"); +?>'); +test('<?php +header("X-Foo: Bar"); +echo "flush\n"; +flush(); +header_remove("X-Foo"); +?>'); + +@unlink($f); +?> +--EXPECTF-- +---------- +<?php ?> +---------- +X-Powered-By: PHP/%s +Content-type: text/html + +---------- +<?php header_remove(); ?> +---------- +Content-type: text/html + +---------- +<?php header_remove("X-Foo"); ?> +---------- +X-Powered-By: PHP/%s +Content-type: text/html + +---------- +<?php +header("X-Foo: Bar"); +?> +---------- +X-Powered-By: PHP/%s +X-Foo: Bar +Content-type: text/html + +---------- +<?php +header("X-Foo: Bar"); +header("X-Bar: Baz"); +header_remove("X-Foo"); +?> +---------- +X-Powered-By: PHP/%s +X-Bar: Baz +Content-type: text/html + +---------- +<?php +header("X-Foo: Bar"); +header_remove("X-Foo: Bar"); +?> +---------- +X-Powered-By: PHP/%s +X-Foo: Bar +Content-type: text/html + + +Warning: Header to delete may not contain colon. in %s on line 3 +---------- +<?php +header("X-Foo: Bar"); +header_remove("X-Foo:"); +?> +---------- +X-Powered-By: PHP/%s +X-Foo: Bar +Content-type: text/html + + +Warning: Header to delete may not contain colon. in %s on line 3 +---------- +<?php +header("X-Foo: Bar"); +header_remove(); +?> +---------- +Content-type: text/html + +---------- +<?php +header_remove(""); +?> +---------- +X-Powered-By: PHP/%s +Content-type: text/html + +---------- +<?php +header_remove(":"); +?> +---------- +X-Powered-By: PHP/%s +Content-type: text/html + + +Warning: Header to delete may not contain colon. in %s on line 2 +---------- +<?php +header("X-Foo: Bar"); +echo "flush\n"; +flush(); +header_remove("X-Foo"); +?> +---------- +X-Powered-By: PHP/%s +X-Foo: Bar +Content-type: text/html + +flush + +Warning: Cannot modify header information - headers already sent by (output started at %s:3) in %s on line 5 diff --git a/sapi/cgi/tests/apache_request_headers.phpt b/sapi/cgi/tests/apache_request_headers.phpt new file mode 100644 index 0000000..fd36e30 --- /dev/null +++ b/sapi/cgi/tests/apache_request_headers.phpt @@ -0,0 +1,51 @@ +--TEST-- +apache_request_headers() stack overflow. +--INI-- +default_charset="UTF-8" +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "include.inc"; + +$php = get_cgi_path(); +reset_env_vars(); + +$file = dirname(__FILE__)."/012.test.php"; + +file_put_contents($file, '<?php print_r(apache_request_headers()); ?>'); + +passthru("$php -n $file"); + +$names = array('HTTP_X_TEST', 'HTTP_X__TEST', 'HTTP_X_'); +foreach ($names as $name) { + putenv($name."=".str_repeat("A", 256)); + passthru("$php -n -q $file"); + putenv($name); +} +unlink($file); + +echo "Done\n"; +?> +--EXPECTF-- +X-Powered-By: PHP/%s +Content-type: text/%s + +Array +( +) +Array +( + [X-Test] => AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +) +Array +( + [X-_test] => AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +) +Array +( + [X-] => AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +) +Done diff --git a/sapi/cgi/tests/bug61605.phpt b/sapi/cgi/tests/bug61605.phpt new file mode 100644 index 0000000..c6e4cf2 --- /dev/null +++ b/sapi/cgi/tests/bug61605.phpt @@ -0,0 +1,34 @@ +--TEST-- +Bug #61605 (header_remove() does not remove all headers) +--SKIPIF-- +<?php include "skipif.inc"; ?> +--GET-- +foo=bar +--FILE-- +<?php +header("A: first"); +header("A: second", TRUE); +$headers1 = headers_list(); +header("A: third", FALSE); +$headers2 = headers_list(); +header_remove("A"); +$headers3 = headers_list(); +print_r($headers1); +print_r($headers2); +print_r($headers3); +--EXPECTF-- +Array +( + [0] => X-Powered-By: %s + [1] => A: second +) +Array +( + [0] => X-Powered-By: %s + [1] => A: second + [2] => A: third +) +Array +( + [0] => X-Powered-By: %s +) diff --git a/sapi/cgi/tests/include.inc b/sapi/cgi/tests/include.inc new file mode 100644 index 0000000..2d8ed8a --- /dev/null +++ b/sapi/cgi/tests/include.inc @@ -0,0 +1,57 @@ +<?php + +function get_cgi_path() /* {{{ */ +{ + $php = getenv("TEST_PHP_EXECUTABLE"); + + $cli = false; + $cgi = false; + + if (file_exists($php) && is_executable($php)) { + $version = `$php -n -v`; + if (strstr($version, "(cli)")) { + /* that's cli */ + $cli = true; + } else if (strpos($version, "(cgi")) { + /* that's cgi */ + return $php; + } + } + + if ($cli) { + /* trying to guess ... */ + $php_path = $php; + for ($i = 0; $i < 2; $i++) { + $slash_pos = strrpos($php_path, "/"); + if ($slash_pos) { + $php_path = substr($php_path, 0, $slash_pos); + } else { + return FALSE; + } + } + + if ($php_path && is_dir($php_path) && file_exists($php_path."/cgi/php-cgi") && is_executable($php_path."/cgi/php-cgi")) { + /* gotcha */ + return $php_path."/cgi/php-cgi"; + } + return false; + } + /* uhm? what's that then? */ + return false; +} +/* }}} */ + +function reset_env_vars() /* {{{ */ +{ + putenv("REDIRECT_STATUS"); + putenv("QUERY_STRING"); + putenv("PATH_TRANSLATED"); + putenv("SCRIPT_FILENAME"); + putenv("SERVER_SOFTWARE"); + putenv("SERVER_NAME"); + putenv("GATEWAY_INTERFACE"); + putenv("REQUEST_METHOD"); +} +/* }}} */ + +?> diff --git a/sapi/cgi/tests/skipif.inc b/sapi/cgi/tests/skipif.inc new file mode 100644 index 0000000..9da8b79 --- /dev/null +++ b/sapi/cgi/tests/skipif.inc @@ -0,0 +1,17 @@ +<?php + +if (substr(php_sapi_name(), 0, 3) == "cgi") { + exit; +} + +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} + +include dirname(__FILE__)."/include.inc"; + +if (!get_cgi_path()) { + die("skip CGI not found"); +} + +?> diff --git a/sapi/cli/CREDITS b/sapi/cli/CREDITS new file mode 100644 index 0000000..463d20a --- /dev/null +++ b/sapi/cli/CREDITS @@ -0,0 +1,2 @@ +CLI +Edin Kadribasic, Marcus Boerger, Johannes Schlueter, Moriyoshi Koizumi, Xinchen Hui diff --git a/sapi/cli/Makefile.frag b/sapi/cli/Makefile.frag new file mode 100644 index 0000000..8f4f400 --- /dev/null +++ b/sapi/cli/Makefile.frag @@ -0,0 +1,13 @@ +cli: $(SAPI_CLI_PATH) + +$(SAPI_CLI_PATH): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_CLI_OBJS) + $(BUILD_CLI) + +install-cli: $(SAPI_CLI_PATH) + @echo "Installing PHP CLI binary: $(INSTALL_ROOT)$(bindir)/" + @$(mkinstalldirs) $(INSTALL_ROOT)$(bindir) + @$(INSTALL) -m 0755 $(SAPI_CLI_PATH) $(INSTALL_ROOT)$(bindir)/$(program_prefix)php$(program_suffix)$(EXEEXT) + @echo "Installing PHP CLI man page: $(INSTALL_ROOT)$(mandir)/man1/" + @$(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man1 + @$(INSTALL_DATA) sapi/cli/php.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)php$(program_suffix).1 + diff --git a/sapi/cli/README b/sapi/cli/README new file mode 100644 index 0000000..8720250 --- /dev/null +++ b/sapi/cli/README @@ -0,0 +1,20 @@ +The CLI (command line interface) SAPI has been introduced +with a goal of making PHP better at supporting the creation of +stand alone applications. + +It is based on CGI SAPI with all CGI specific things removed. + +The main differences between the two: + +* CLI is started up in quiet mode by default. + (-q switch kept for compatibility) +* It does not change the working directory to that of the script. + (-C switch kept for compatibility) +* Plain text error message +* $argc and $argv registered irrespective of the register_argc_argv + php.ini setting. +* implicit_flush always on +* -r option which allows execution of PHP code directly from + the command line (e.g. php -r 'echo md5("test");' ) +* Other more sophisticated command line switches (see: man php) +* max_execution_time is set to unlimited, overriding php.ini setting. diff --git a/sapi/cli/TODO b/sapi/cli/TODO new file mode 100644 index 0000000..22e6689 --- /dev/null +++ b/sapi/cli/TODO @@ -0,0 +1,2 @@ +TODO: + diff --git a/sapi/cli/cli.h b/sapi/cli/cli.h new file mode 100644 index 0000000..7686522 --- /dev/null +++ b/sapi/cli/cli.h @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Johannes Schlueter <johannes@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef CLI_H +#define CLI_H + +#ifdef PHP_WIN32 +# define PHP_CLI_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_CLI_API __attribute__ ((visibility("default"))) +#else +# define PHP_CLI_API +#endif + + +extern PHP_CLI_API size_t sapi_cli_single_write(const char *str, uint str_length TSRMLS_DC); + +typedef struct { + size_t (*cli_shell_write)(const char *str, uint str_length TSRMLS_DC); + int (*cli_shell_ub_write)(const char *str, uint str_length TSRMLS_DC); + int (*cli_shell_run)(TSRMLS_D); +} cli_shell_callbacks_t; + +extern PHP_CLI_API cli_shell_callbacks_t *php_cli_get_shell_callbacks(); + +#endif /* CLI_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/sapi/cli/cli_win32.c b/sapi/cli/cli_win32.c new file mode 100644 index 0000000..4407fd0 --- /dev/null +++ b/sapi/cli/cli_win32.c @@ -0,0 +1,2 @@ +#define PHP_CLI_WIN32_NO_CONSOLE 1 +#include "php_cli.c" diff --git a/sapi/cli/config.m4 b/sapi/cli/config.m4 new file mode 100644 index 0000000..cdfa1f7 --- /dev/null +++ b/sapi/cli/config.m4 @@ -0,0 +1,50 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_ENABLE(cli,, +[ --disable-cli Disable building CLI version of PHP + (this forces --without-pear)], yes, no) + +AC_MSG_CHECKING(for CLI build) +if test "$PHP_CLI" != "no"; then + PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/cli/Makefile.frag) + + dnl Set filename + SAPI_CLI_PATH=sapi/cli/php + + dnl Select SAPI + PHP_SELECT_SAPI(cli, program, php_cli.c php_http_parser.c php_cli_server.c,, '$(SAPI_CLI_PATH)') + + case $host_alias in + *aix*) + if test "$php_sapi_module" = "shared"; then + BUILD_CLI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CLI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/.libs\/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_CLI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CLI_PATH)" + else + BUILD_CLI="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CLI_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_CLI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CLI_PATH)" + fi + ;; + *darwin*) + BUILD_CLI="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_BINARY_OBJS:.lo=.o) \$(PHP_CLI_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CLI_PATH)" + ;; + *netware*) + BUILD_CLI="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_BINARY_OBJS) \$(PHP_CLI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -Lnetware -lphp5lib -o \$(SAPI_CLI_PATH)" + ;; + *) + BUILD_CLI="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_CLI_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_CLI_PATH)" + ;; + esac + + dnl Set executable for tests + PHP_EXECUTABLE="\$(top_builddir)/\$(SAPI_CLI_PATH)" + PHP_SUBST(PHP_EXECUTABLE) + + dnl Expose to Makefile + PHP_SUBST(SAPI_CLI_PATH) + PHP_SUBST(BUILD_CLI) + + PHP_OUTPUT(sapi/cli/php.1) + + PHP_INSTALL_HEADERS([sapi/cli/cli.h]) +fi +AC_MSG_RESULT($PHP_CLI) diff --git a/sapi/cli/config.w32 b/sapi/cli/config.w32 new file mode 100644 index 0000000..4d0dad5 --- /dev/null +++ b/sapi/cli/config.w32 @@ -0,0 +1,21 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('cli', 'Build CLI version of PHP', 'yes'); +ARG_ENABLE('crt-debug', 'Enable CRT memory dumps for debugging sent to STDERR', 'no'); +ARG_ENABLE('cli-win32', 'Build console-less CLI version of PHP', 'no'); + +if (PHP_CLI == "yes") { + SAPI('cli', 'php_cli.c php_http_parser.c php_cli_server.c', 'php.exe'); + ADD_FLAG("LIBS_CLI", "ws2_32.lib"); + if (PHP_CRT_DEBUG == "yes") { + ADD_FLAG("CFLAGS_CLI", "/D PHP_WIN32_DEBUG_HEAP"); + } + ADD_FLAG("LDFLAGS_CLI", "/stack:8388608"); +} + +if (PHP_CLI_WIN32 == "yes") { + SAPI('cli_win32', 'cli_win32.c', 'php-win.exe'); + ADD_FLAG("LDFLAGS_CLI_WIN32", "/stack:8388608"); +} + diff --git a/sapi/cli/php.1.in b/sapi/cli/php.1.in new file mode 100644 index 0000000..186b128 --- /dev/null +++ b/sapi/cli/php.1.in @@ -0,0 +1,451 @@ +.TH PHP 1 "2010" "The PHP Group" "Scripting Language" +.SH NAME +php \- PHP Command Line Interface 'CLI' +.SH SYNOPSIS +.B php +[options] [ +.B \-f\fP ] +.IR file +[[\-\-] +.IR args.\|.\|. ] +.LP +.B php +[options] +.B \-r +.IR code +[[\-\-] +.IR args.\|.\|. ] +.LP +.B php +[options] [\-B +.IR code ] +.B \-R +.IR code +[\-E +.IR code ] +[[\-\-] +.IR args.\|.\|. ] +.LP +.B php +[options] [\-B +.IR code ] +.B \-F +.IR file +[\-E +.IR code ] +[[\-\-] +.IR args.\|.\|. ] +.LP +.B php +[options] \-\- [ +.IR args.\|.\|. ] +.LP +\fBphp \fP[options] \fB\-a\fP +.LP +.SH DESCRIPTION +\fBPHP\fP is a widely\-used general\-purpose scripting language that is especially suited for +Web development and can be embedded into HTML. This is the command line interface +that enables you to do the following: +.P +You can parse and execute files by using parameter \-f followed by the name of the +.IR file +to be executed. +.LP +Using parameter \-r you can directly execute PHP +.IR code +simply as you would do inside a +.B \.php +file when using the +.B eval() +function. +.LP +It is also possible to process the standard input line by line using either +the parameter \-R or \-F. In this mode each separate input line causes the +.IR code +specified by \-R or the +.IR file +specified by \-F to be executed. +You can access the input line by \fB$argn\fP. While processing the input lines +.B $argi +contains the number of the actual line being processed. Further more +the parameters \-B and \-E can be used to execute +.IR code +(see \-r) before and +after all input lines have been processed respectively. Notice that the +input is read from +.B STDIN +and therefore reading from +.B STDIN +explicitly changes the next input line or skips input lines. +.LP +If none of \-r \-f \-B \-R \-F or \-E is present but a single parameter is given +then this parameter is taken as the filename to parse and execute (same as +with \-f). If no parameter is present then the standard input is read and +executed. +.SH OPTIONS +.TP 15 +.PD 0 +.B \-\-interactive +.TP +.PD 1 +.B \-a +Run PHP interactively. This lets you enter snippets of PHP code that directly +get executed. When readline support is enabled you can edit the lines and also +have history support. +.TP +.PD 0 +.B \-\-bindpath \fIaddress:port\fP|\fIport\fP +.TP +.PD 1 +.B \-b \fIaddress:port\fP|\fIport\fP +Bind Path for external FASTCGI Server mode (CGI only). +.TP +.PD 0 +.B \-\-no\-chdir +.TP +.PD 1 +.B \-C +Do not chdir to the script's directory (CGI only). +.TP +.PD 0 +.B \-\-no\-header +.TP +.PD 1 +.B \-q +Quiet-mode. Suppress HTTP header output (CGI only). +.TP +.PD 0 +.B \-\-timing \fIcount\fP +.TP +.PD 1 +.B \-T \fIcount\fP +Measure execution time of script repeated count times (CGI only). +.TP +.PD 0 +.B \-\-php\-ini \fIpath\fP|\fIfile\fP +.TP +.PD 1 +.B \-c \fIpath\fP|\fIfile\fP +Look for +.B php.ini +file in the directory +.IR path +or use the specified +.IR file +.TP +.PD 0 +.B \-\-no\-php\-ini +.TP +.PD 1 +.B \-n +No +.B php.ini +file will be used +.TP +.PD 0 +.B \-\-define \fIfoo\fP[=\fIbar\fP] +.TP +.PD 1 +.B \-d \fIfoo\fP[=\fIbar\fP] +Define INI entry +.IR foo +with value +.IR bar +.TP +.B \-e +Generate extended information for debugger/profiler +.TP +.PD 0 +.B \-\-file \fIfile\fP +.TP +.PD 1 +.B \-f \fIfile\fP +Parse and execute +.IR file +.TP +.PD 0 +.B \-\-global \fIname\fP +.TP +.PD 1 +.B \-g \fIname\fP +Make variable +.IR name +global in script. +.TP +.PD 0 +.B \-\-help +.TP +.PD 1 +.B \-h +This help +.TP +.PD 0 +.B \-\-hide\-args +.TP +.PD 1 +.B \-H +Hide script name (\fIfile\fP) and parameters (\fIargs\.\.\.\fP) from external +tools. For example you may want to use this when a php script is started as +a daemon and the command line contains sensitive data such as passwords. +.TP +.PD 0 +.B \-\-info +.TP +.PD 1 +.B \-i +PHP information and configuration +.TP +.PD 0 +.B \-\-syntax\-check +.TP +.PD 1 +.B \-l +Syntax check only (lint) +.TP +.PD 0 +.B \-\-modules +.TP +.PD 1 +.B \-m +Show compiled in modules +.TP +.PD 0 +.B \-\-run \fIcode\fP +.TP +.PD 1 +.B \-r \fIcode\fP +Run PHP +.IR code +without using script tags +.B '<?..?>' +.TP +.PD 0 +.B \-\-process\-begin \fIcode\fP +.TP +.PD 1 +.B \-B \fIcode\fP +Run PHP +.IR code +before processing input lines +.TP +.PD 0 +.B \-\-process\-code \fIcode\fP +.TP +.PD 1 +.B \-R \fIcode\fP +Run PHP +.IR code +for every input line +.TP +.PD 0 +.B \-\-process\-file \fIfile\fP +.TP +.PD 1 +.B \-F \fIfile\fP +Parse and execute +.IR file +for every input line +.TP +.PD 0 +.B \-\-process\-end \fIcode\fP +.TP +.PD 1 +.B \-E \fIcode\fP +Run PHP +.IR code +after processing all input lines +.TP +.PD 0 +.B \-\-syntax\-highlight +.TP +.PD 1 +.B \-s +Output HTML syntax highlighted source +.TP +.PD 0 +.B \-\-version +.TP +.PD 1 +.B \-v +Version number +.TP +.PD 0 +.B \-\-stripped +.TP +.PD 1 +.B \-w +Output source with stripped comments and whitespace +.TP +.PD 0 +.B \-\-zend\-extension \fIfile\fP +.TP +.PD 1 +.B \-z \fIfile\fP +Load Zend extension +.IR file +.TP +.IR args.\|.\|. +Arguments passed to script. Use +.B '\-\-' +.IR args +when first argument starts with +.B '\-' +or script is read from stdin +.TP +.PD 0 +.B \-\-rfunction +.IR name +.TP +.PD 1 +.B \-\-rf +.IR name +Shows information about function +.B name +.TP +.PD 0 +.B \-\-rclass +.IR name +.TP +.PD 1 +.B \-\-rc +.IR name +Shows information about class +.B name +.TP +.PD 0 +.B \-\-rextension +.IR name +.TP +.PD 1 +.B \-\-re +.IR name +Shows information about extension +.B name +.TP +.PD 0 +.B \-\-rzendextension +.IR name +.TP +.PD 1 +.B \-\-rz +.IR name +Shows information about Zend extension +.B name +.TP +.PD 0 +.B \-\-rextinfo +.IR name +.TP +.PD 1 +.B \-\-ri +.IR name +Shows configuration for extension +.B name +.TP +.B \-\-ini +Show configuration file names +.SH FILES +.TP 15 +.B php\-cli.ini +The configuration file for the CLI version of PHP. +.TP +.B php.ini +The standard configuration file will only be used when +.B php\-cli.ini +cannot be found. +.SH EXAMPLES +.TP 5 +\fIphp \-r 'echo "Hello World\\n";'\fP +This command simply writes the text "Hello World" to standard out. +.TP +\fIphp \-r 'print_r(gd_info());'\fP +This shows the configuration of your gd extension. You can use this +to easily check which image formats you can use. If you have any +dynamic modules you may want to use the same ini file that php uses +when executed from your webserver. There are more extensions which +have such a function. For dba use: +.RS +\fIphp \-r 'print_r(dba_handlers(1));'\fP +.RE +.TP +\fIphp \-R 'echo strip_tags($argn)."\\n";'\fP +This PHP command strips off the HTML tags line by line and outputs the +result. To see how it works you can first look at the following PHP command +\'\fIphp \-d html_errors=1 \-i\fP\' which uses PHP to output HTML formatted +configuration information. If you then combine those two +\'\fIphp \.\.\.|php \.\.\.\fP\' you'll see what happens. +.TP +\fIphp \-E 'echo "Lines: $argi\\n";'\fP +Using this PHP command you can count the lines being input. +.TP +\fIphp \-R '@$l+=count(file($argn));' \-E 'echo "Lines:$l\\n";'\fP +In this example PHP expects each input line being a file. It counts all lines +of the files specified by each input line and shows the summarized result. +You may combine this with tools like find and change the php scriptlet. +.TP +\fIphp \-R 'echo "$argn\\n"; fgets(STDIN);'\fP +Since you have access to STDIN from within \-B \-R \-F and \-E you can skip +certain input lines with your code. But note that in such cases $argi only +counts the lines being processed by php itself. Having read this you will +guess what the above program does: skipping every second input line. +.SH TIPS +You can use a shebang line to automatically invoke php +from scripts. Only the CLI version of PHP will ignore +such a first line as shown below: +.P +.PD 0 +.RS +#!/bin/php +.P +<?php +.P + // your script +.P +?> +.RE +.PD 1 +.P +.SH SEE ALSO +For a more or less complete description of PHP look here: +.PD 0 +.P +.B http://www.php.net/manual/ +.PD 1 +.P +.SH BUGS +You can view the list of known bugs or report any new bug you +found at: +.PD 0 +.P +.B http://bugs.php.net +.PD 1 +.SH AUTHORS +The PHP Group: Thies C. Arntzen, Stig Bakken, Andi Gutmans, Rasmus Lerdorf, Sam Ruby, Sascha Schumann, Zeev Suraski, Jim Winstead, Andrei Zmievski. +.P +Additional work for the CLI sapi was done by Edin Kadribasic, Marcus Boerger and Johannes Schlueter. +.P +A List of active developers can be found here: +.PD 0 +.P +.B http://www.php.net/credits.php +.PD 1 +.P +And last but not least PHP was developed with the help of a huge amount of +contributors all around the world. +.SH VERSION INFORMATION +This manpage describes \fBphp\fP, version @PHP_VERSION@. +.SH COPYRIGHT +Copyright \(co 1997\-2010 The PHP Group +.LP +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: +.PD 0 +.P +.B http://www.php.net/license/3_01.txt +.PD 1 +.P +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 +.B license@php.net +so we can mail you a copy immediately. diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c new file mode 100644 index 0000000..c01f370 --- /dev/null +++ b/sapi/cli/php_cli.c @@ -0,0 +1,1399 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Edin Kadribasic <edink@php.net> | + | Marcus Boerger <helly@php.net> | + | Johannes Schlueter <johannes@php.net> | + | Parts based on CGI SAPI Module by | + | Rasmus Lerdorf, Stig Bakken and Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_hash.h" +#include "zend_modules.h" +#include "zend_interfaces.h" + +#include "ext/reflection/php_reflection.h" + +#include "SAPI.h" + +#include <stdio.h> +#include "php.h" +#ifdef PHP_WIN32 +#include "win32/time.h" +#include "win32/signal.h" +#include <process.h> +#endif +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SIGNAL_H +#include <signal.h> +#endif +#if HAVE_SETLOCALE +#include <locale.h> +#endif +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "ext/standard/php_standard.h" +#include "cli.h" +#ifdef PHP_WIN32 +#include <io.h> +#include <fcntl.h> +#include "win32/php_registry.h" +#endif + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +#ifdef __riscos__ +#include <unixlib/local.h> +#endif + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" +#include "zend_exceptions.h" + +#include "php_getopt.h" + +#ifndef PHP_CLI_WIN32_NO_CONSOLE +#include "php_cli_server.h" +#endif + +#ifndef PHP_WIN32 +# define php_select(m, r, w, e, t) select(m, r, w, e, t) +#else +# include "win32/select.h" +#endif + +PHPAPI extern char *php_ini_opened_path; +PHPAPI extern char *php_ini_scanned_path; +PHPAPI extern char *php_ini_scanned_files; + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define PHP_MODE_STANDARD 1 +#define PHP_MODE_HIGHLIGHT 2 +#define PHP_MODE_INDENT 3 +#define PHP_MODE_LINT 4 +#define PHP_MODE_STRIP 5 +#define PHP_MODE_CLI_DIRECT 6 +#define PHP_MODE_PROCESS_STDIN 7 +#define PHP_MODE_REFLECTION_FUNCTION 8 +#define PHP_MODE_REFLECTION_CLASS 9 +#define PHP_MODE_REFLECTION_EXTENSION 10 +#define PHP_MODE_REFLECTION_EXT_INFO 11 +#define PHP_MODE_REFLECTION_ZEND_EXTENSION 12 +#define PHP_MODE_SHOW_INI_CONFIG 13 + +cli_shell_callbacks_t cli_shell_callbacks = { NULL, NULL, NULL }; +PHP_CLI_API cli_shell_callbacks_t *php_cli_get_shell_callbacks() +{ + return &cli_shell_callbacks; +} + +const char HARDCODED_INI[] = + "html_errors=0\n" + "register_argc_argv=1\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n\0"; + + +const opt_struct OPTIONS[] = { + {'a', 0, "interactive"}, + {'B', 1, "process-begin"}, + {'C', 0, "no-chdir"}, /* for compatibility with CGI (do not chdir to script directory) */ + {'c', 1, "php-ini"}, + {'d', 1, "define"}, + {'E', 1, "process-end"}, + {'e', 0, "profile-info"}, + {'F', 1, "process-file"}, + {'f', 1, "file"}, + {'h', 0, "help"}, + {'i', 0, "info"}, + {'l', 0, "syntax-check"}, + {'m', 0, "modules"}, + {'n', 0, "no-php-ini"}, + {'q', 0, "no-header"}, /* for compatibility with CGI (do not generate HTTP headers) */ + {'R', 1, "process-code"}, + {'H', 0, "hide-args"}, + {'r', 1, "run"}, + {'s', 0, "syntax-highlight"}, + {'s', 0, "syntax-highlighting"}, + {'S', 1, "server"}, + {'t', 1, "docroot"}, + {'w', 0, "strip"}, + {'?', 0, "usage"},/* help alias (both '?' and 'usage') */ + {'v', 0, "version"}, + {'z', 1, "zend-extension"}, + {10, 1, "rf"}, + {10, 1, "rfunction"}, + {11, 1, "rc"}, + {11, 1, "rclass"}, + {12, 1, "re"}, + {12, 1, "rextension"}, + {13, 1, "rz"}, + {13, 1, "rzendextension"}, + {14, 1, "ri"}, + {14, 1, "rextinfo"}, + {15, 0, "ini"}, + {'-', 0, NULL} /* end of args */ +}; + +static int print_module_info(zend_module_entry *module TSRMLS_DC) /* {{{ */ +{ + php_printf("%s\n", module->name); + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int module_name_cmp(const void *a, const void *b TSRMLS_DC) /* {{{ */ +{ + Bucket *f = *((Bucket **) a); + Bucket *s = *((Bucket **) b); + + return strcasecmp(((zend_module_entry *)f->pData)->name, + ((zend_module_entry *)s->pData)->name); +} +/* }}} */ + +static void print_modules(TSRMLS_D) /* {{{ */ +{ + HashTable sorted_registry; + zend_module_entry tmp; + + zend_hash_init(&sorted_registry, 50, NULL, NULL, 1); + zend_hash_copy(&sorted_registry, &module_registry, NULL, &tmp, sizeof(zend_module_entry)); + zend_hash_sort(&sorted_registry, zend_qsort, module_name_cmp, 0 TSRMLS_CC); + zend_hash_apply(&sorted_registry, (apply_func_t) print_module_info TSRMLS_CC); + zend_hash_destroy(&sorted_registry); +} +/* }}} */ + +static int print_extension_info(zend_extension *ext, void *arg TSRMLS_DC) /* {{{ */ +{ + php_printf("%s\n", ext->name); + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int extension_name_cmp(const zend_llist_element **f, const zend_llist_element **s TSRMLS_DC) /* {{{ */ +{ + return strcmp(((zend_extension *)(*f)->data)->name, + ((zend_extension *)(*s)->data)->name); +} +/* }}} */ + +static void print_extensions(TSRMLS_D) /* {{{ */ +{ + zend_llist sorted_exts; + + zend_llist_copy(&sorted_exts, &zend_extensions); + sorted_exts.dtor = NULL; + zend_llist_sort(&sorted_exts, extension_name_cmp TSRMLS_CC); + zend_llist_apply(&sorted_exts, (llist_apply_func_t) print_extension_info TSRMLS_CC); + zend_llist_destroy(&sorted_exts); +} +/* }}} */ + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +static inline int sapi_cli_select(int fd TSRMLS_DC) +{ + fd_set wfd, dfd; + struct timeval tv; + int ret; + + FD_ZERO(&wfd); + FD_ZERO(&dfd); + + PHP_SAFE_FD_SET(fd, &wfd); + + tv.tv_sec = FG(default_socket_timeout); + tv.tv_usec = 0; + + ret = php_select(fd+1, &dfd, &wfd, &dfd, &tv); + + return ret != -1; +} + +PHP_CLI_API size_t sapi_cli_single_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */ +{ +#ifdef PHP_WRITE_STDOUT + long ret; +#else + size_t ret; +#endif + + if (cli_shell_callbacks.cli_shell_write) { + size_t shell_wrote; + shell_wrote = cli_shell_callbacks.cli_shell_write(str, str_length TSRMLS_CC); + if (shell_wrote > -1) { + return shell_wrote; + } + } + +#ifdef PHP_WRITE_STDOUT + do { + ret = write(STDOUT_FILENO, str, str_length); + } while (ret <= 0 && errno == EAGAIN && sapi_cli_select(STDOUT_FILENO TSRMLS_CC)); + + if (ret <= 0) { + return 0; + } + + return ret; +#else + ret = fwrite(str, 1, MIN(str_length, 16384), stdout); + return ret; +#endif +} +/* }}} */ + +static int sapi_cli_ub_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */ +{ + const char *ptr = str; + uint remaining = str_length; + size_t ret; + + if (!str_length) { + return 0; + } + + if (cli_shell_callbacks.cli_shell_ub_write) { + int ub_wrote; + ub_wrote = cli_shell_callbacks.cli_shell_ub_write(str, str_length TSRMLS_CC); + if (ub_wrote > -1) { + return ub_wrote; + } + } + + while (remaining > 0) + { + ret = sapi_cli_single_write(ptr, remaining TSRMLS_CC); + if (!ret) { +#ifndef PHP_CLI_WIN32_NO_CONSOLE + php_handle_aborted_connection(); +#endif + break; + } + ptr += ret; + remaining -= ret; + } + + return (ptr - str); +} +/* }}} */ + +static void sapi_cli_flush(void *server_context) /* {{{ */ +{ + /* Ignore EBADF here, it's caused by the fact that STDIN/STDOUT/STDERR streams + * are/could be closed before fflush() is called. + */ + if (fflush(stdout)==EOF && errno!=EBADF) { +#ifndef PHP_CLI_WIN32_NO_CONSOLE + php_handle_aborted_connection(); +#endif + } +} +/* }}} */ + +static char *php_self = ""; +static char *script_filename = ""; + +static void sapi_cli_register_variables(zval *track_vars_array TSRMLS_DC) /* {{{ */ +{ + unsigned int len; + char *docroot = ""; + + /* In CGI mode, we consider the environment to be a part of the server + * variables + */ + php_import_environment_variables(track_vars_array TSRMLS_CC); + + /* Build the special-case PHP_SELF variable for the CLI version */ + len = strlen(php_self); + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, len, &len TSRMLS_CC)) { + php_register_variable("PHP_SELF", php_self, track_vars_array TSRMLS_CC); + } + if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_NAME", &php_self, len, &len TSRMLS_CC)) { + php_register_variable("SCRIPT_NAME", php_self, track_vars_array TSRMLS_CC); + } + /* filenames are empty for stdin */ + len = strlen(script_filename); + if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_FILENAME", &script_filename, len, &len TSRMLS_CC)) { + php_register_variable("SCRIPT_FILENAME", script_filename, track_vars_array TSRMLS_CC); + } + if (sapi_module.input_filter(PARSE_SERVER, "PATH_TRANSLATED", &script_filename, len, &len TSRMLS_CC)) { + php_register_variable("PATH_TRANSLATED", script_filename, track_vars_array TSRMLS_CC); + } + /* just make it available */ + len = 0U; + if (sapi_module.input_filter(PARSE_SERVER, "DOCUMENT_ROOT", &docroot, len, &len TSRMLS_CC)) { + php_register_variable("DOCUMENT_ROOT", docroot, track_vars_array TSRMLS_CC); + } +} +/* }}} */ + +static void sapi_cli_log_message(char *message TSRMLS_DC) /* {{{ */ +{ + fprintf(stderr, "%s\n", message); +} +/* }}} */ + +static int sapi_cli_deactivate(TSRMLS_D) /* {{{ */ +{ + fflush(stdout); + if(SG(request_info).argv0) { + free(SG(request_info).argv0); + SG(request_info).argv0 = NULL; + } + return SUCCESS; +} +/* }}} */ + +static char* sapi_cli_read_cookies(TSRMLS_D) /* {{{ */ +{ + return NULL; +} +/* }}} */ + +static int sapi_cli_header_handler(sapi_header_struct *h, sapi_header_op_enum op, sapi_headers_struct *s TSRMLS_DC) /* {{{ */ +{ + return 0; +} +/* }}} */ + +static int sapi_cli_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) /* {{{ */ +{ + /* We do nothing here, this function is needed to prevent that the fallback + * header handling is called. */ + return SAPI_HEADER_SENT_SUCCESSFULLY; +} +/* }}} */ + +static void sapi_cli_send_header(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC) /* {{{ */ +{ +} +/* }}} */ + +static int php_cli_startup(sapi_module_struct *sapi_module) /* {{{ */ +{ + if (php_module_startup(sapi_module, NULL, 0)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +/* {{{ sapi_cli_ini_defaults */ + +/* overwriteable ini defaults must be set in sapi_cli_ini_defaults() */ +#define INI_DEFAULT(name,value)\ + Z_SET_REFCOUNT(tmp, 0);\ + Z_UNSET_ISREF(tmp); \ + ZVAL_STRINGL(&tmp, zend_strndup(value, sizeof(value)-1), sizeof(value)-1, 0);\ + zend_hash_update(configuration_hash, name, sizeof(name), &tmp, sizeof(zval), NULL);\ + +static void sapi_cli_ini_defaults(HashTable *configuration_hash) +{ + zval tmp; + INI_DEFAULT("report_zend_debug", "0"); + INI_DEFAULT("display_errors", "1"); +} +/* }}} */ + +/* {{{ sapi_module_struct cli_sapi_module + */ +static sapi_module_struct cli_sapi_module = { + "cli", /* name */ + "Command Line Interface", /* pretty name */ + + php_cli_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + sapi_cli_deactivate, /* deactivate */ + + sapi_cli_ub_write, /* unbuffered write */ + sapi_cli_flush, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + sapi_cli_header_handler, /* header handler */ + sapi_cli_send_headers, /* send headers handler */ + sapi_cli_send_header, /* send header handler */ + + NULL, /* read POST data */ + sapi_cli_read_cookies, /* read Cookies */ + + sapi_cli_register_variables, /* register server variables */ + sapi_cli_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ arginfo ext/standard/dl.c */ +ZEND_BEGIN_ARG_INFO(arginfo_dl, 0) + ZEND_ARG_INFO(0, extension_filename) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry additional_functions[] = { + ZEND_FE(dl, arginfo_dl) + {NULL, NULL, NULL} +}; + +/* {{{ php_cli_usage + */ +static void php_cli_usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "php"; + } + + printf( "Usage: %s [options] [-f] <file> [--] [args...]\n" + " %s [options] -r <code> [--] [args...]\n" + " %s [options] [-B <begin_code>] -R <code> [-E <end_code>] [--] [args...]\n" + " %s [options] [-B <begin_code>] -F <file> [-E <end_code>] [--] [args...]\n" + " %s [options] -- [args...]\n" + " %s [options] -a\n" + "\n" +#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE) + " -a Run as interactive shell\n" +#else + " -a Run interactively\n" +#endif + " -c <path>|<file> Look for php.ini file in this directory\n" + " -n No php.ini file will be used\n" + " -d foo[=bar] Define INI entry foo with value 'bar'\n" + " -e Generate extended information for debugger/profiler\n" + " -f <file> Parse and execute <file>.\n" + " -h This help\n" + " -i PHP information\n" + " -l Syntax check only (lint)\n" + " -m Show compiled in modules\n" + " -r <code> Run PHP <code> without using script tags <?..?>\n" + " -B <begin_code> Run PHP <begin_code> before processing input lines\n" + " -R <code> Run PHP <code> for every input line\n" + " -F <file> Parse and execute <file> for every input line\n" + " -E <end_code> Run PHP <end_code> after processing all input lines\n" + " -H Hide any passed arguments from external tools.\n" + " -S <addr>:<port> Run with built-in web server.\n" + " -t <docroot> Specify document root <docroot> for built-in web server.\n" + " -s Output HTML syntax highlighted source.\n" + " -v Version number\n" + " -w Output source with stripped comments and whitespace.\n" + " -z <file> Load Zend extension <file>.\n" + "\n" + " args... Arguments passed to script. Use -- args when first argument\n" + " starts with - or script is read from stdin\n" + "\n" + " --ini Show configuration file names\n" + "\n" + " --rf <name> Show information about function <name>.\n" + " --rc <name> Show information about class <name>.\n" + " --re <name> Show information about extension <name>.\n" + " --rz <name> Show information about Zend extension <name>.\n" + " --ri <name> Show configuration for extension <name>.\n" + "\n" + , prog, prog, prog, prog, prog, prog); +} +/* }}} */ + +static php_stream *s_in_process = NULL; + +static void cli_register_file_handles(TSRMLS_D) /* {{{ */ +{ + zval *zin, *zout, *zerr; + php_stream *s_in, *s_out, *s_err; + php_stream_context *sc_in=NULL, *sc_out=NULL, *sc_err=NULL; + zend_constant ic, oc, ec; + + MAKE_STD_ZVAL(zin); + MAKE_STD_ZVAL(zout); + MAKE_STD_ZVAL(zerr); + + s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in); + s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out); + s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err); + + if (s_in==NULL || s_out==NULL || s_err==NULL) { + FREE_ZVAL(zin); + FREE_ZVAL(zout); + FREE_ZVAL(zerr); + if (s_in) php_stream_close(s_in); + if (s_out) php_stream_close(s_out); + if (s_err) php_stream_close(s_err); + return; + } + +#if PHP_DEBUG + /* do not close stdout and stderr */ + s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE; + s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE; +#endif + + s_in_process = s_in; + + php_stream_to_zval(s_in, zin); + php_stream_to_zval(s_out, zout); + php_stream_to_zval(s_err, zerr); + + ic.value = *zin; + ic.flags = CONST_CS; + ic.name = zend_strndup(ZEND_STRL("STDIN")); + ic.name_len = sizeof("STDIN"); + ic.module_number = 0; + zend_register_constant(&ic TSRMLS_CC); + + oc.value = *zout; + oc.flags = CONST_CS; + oc.name = zend_strndup(ZEND_STRL("STDOUT")); + oc.name_len = sizeof("STDOUT"); + oc.module_number = 0; + zend_register_constant(&oc TSRMLS_CC); + + ec.value = *zerr; + ec.flags = CONST_CS; + ec.name = zend_strndup(ZEND_STRL("STDERR")); + ec.name_len = sizeof("STDERR"); + ec.module_number = 0; + zend_register_constant(&ec TSRMLS_CC); + + FREE_ZVAL(zin); + FREE_ZVAL(zout); + FREE_ZVAL(zerr); +} +/* }}} */ + +static const char *param_mode_conflict = "Either execute direct code, process stdin or use a file.\n"; + +/* {{{ cli_seek_file_begin + */ +static int cli_seek_file_begin(zend_file_handle *file_handle, char *script_file, int *lineno TSRMLS_DC) +{ + int c; + + *lineno = 1; + + file_handle->type = ZEND_HANDLE_FP; + file_handle->opened_path = NULL; + file_handle->free_filename = 0; + if (!(file_handle->handle.fp = VCWD_FOPEN(script_file, "rb"))) { + php_printf("Could not open input file: %s\n", script_file); + return FAILURE; + } + file_handle->filename = script_file; + + /* #!php support */ + c = fgetc(file_handle->handle.fp); + if (c == '#' && (c = fgetc(file_handle->handle.fp)) == '!') { + while (c != '\n' && c != '\r' && c != EOF) { + c = fgetc(file_handle->handle.fp); /* skip to end of line */ + } + /* handle situations where line is terminated by \r\n */ + if (c == '\r') { + if (fgetc(file_handle->handle.fp) != '\n') { + long pos = ftell(file_handle->handle.fp); + fseek(file_handle->handle.fp, pos - 1, SEEK_SET); + } + } + *lineno = 2; + } else { + rewind(file_handle->handle.fp); + } + + return SUCCESS; +} +/* }}} */ + +static int do_cli(int argc, char **argv TSRMLS_DC) /* {{{ */ +{ + int c; + zend_file_handle file_handle; + int behavior = PHP_MODE_STANDARD; + char *reflection_what = NULL; + volatile int request_started = 0; + volatile int exit_status = 0; + char *php_optarg = NULL, *orig_optarg = NULL; + int php_optind = 1, orig_optind = 1; + char *exec_direct=NULL, *exec_run=NULL, *exec_begin=NULL, *exec_end=NULL; + char *arg_free=NULL, **arg_excp=&arg_free; + char *script_file=NULL, *translated_path = NULL; + int interactive=0; + int lineno = 0; + const char *param_error=NULL; + int hide_argv = 0; + + zend_try { + + CG(in_compilation) = 0; /* not initialized but needed for several options */ + EG(uninitialized_zval_ptr) = NULL; + + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + + case 'i': /* php info & quit */ + if (php_request_startup(TSRMLS_C)==FAILURE) { + goto err; + } + request_started = 1; + php_print_info(0xFFFFFFFF TSRMLS_CC); + php_output_end_all(TSRMLS_C); + exit_status = (c == '?' && argc > 1 && !strchr(argv[1], c)); + goto out; + + case 'v': /* show php version & quit */ + php_printf("PHP %s (%s) (built: %s %s) %s\nCopyright (c) 1997-2013 The PHP Group\n%s", + PHP_VERSION, cli_sapi_module.name, __DATE__, __TIME__, +#if ZEND_DEBUG && defined(HAVE_GCOV) + "(DEBUG GCOV)", +#elif ZEND_DEBUG + "(DEBUG)", +#elif defined(HAVE_GCOV) + "(GCOV)", +#else + "", +#endif + get_zend_version() + ); + sapi_deactivate(TSRMLS_C); + goto out; + + case 'm': /* list compiled in modules */ + if (php_request_startup(TSRMLS_C)==FAILURE) { + goto err; + } + request_started = 1; + php_printf("[PHP Modules]\n"); + print_modules(TSRMLS_C); + php_printf("\n[Zend Modules]\n"); + print_extensions(TSRMLS_C); + php_printf("\n"); + php_output_end_all(TSRMLS_C); + exit_status=0; + goto out; + + default: + break; + } + } + + /* Set some CLI defaults */ + SG(options) |= SAPI_OPTION_NO_CHDIR; + + php_optind = orig_optind; + php_optarg = orig_optarg; + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + + case 'a': /* interactive mode */ + if (!interactive) { + if (behavior != PHP_MODE_STANDARD) { + param_error = param_mode_conflict; + break; + } + + interactive=1; + } + break; + + case 'C': /* don't chdir to the script directory */ + /* This is default so NOP */ + break; + + case 'F': + if (behavior == PHP_MODE_PROCESS_STDIN) { + if (exec_run || script_file) { + param_error = "You can use -R or -F only once.\n"; + break; + } + } else if (behavior != PHP_MODE_STANDARD) { + param_error = param_mode_conflict; + break; + } + behavior=PHP_MODE_PROCESS_STDIN; + script_file = php_optarg; + break; + + case 'f': /* parse file */ + if (behavior == PHP_MODE_CLI_DIRECT || behavior == PHP_MODE_PROCESS_STDIN) { + param_error = param_mode_conflict; + break; + } else if (script_file) { + param_error = "You can use -f only once.\n"; + break; + } + script_file = php_optarg; + break; + + case 'l': /* syntax check mode */ + if (behavior != PHP_MODE_STANDARD) { + break; + } + behavior=PHP_MODE_LINT; + break; + +#if 0 /* not yet operational, see also below ... */ + case '': /* generate indented source mode*/ + if (behavior == PHP_MODE_CLI_DIRECT || behavior == PHP_MODE_PROCESS_STDIN) { + param_error = "Source indenting only works for files.\n"; + break; + } + behavior=PHP_MODE_INDENT; + break; +#endif + + case 'q': /* do not generate HTTP headers */ + /* This is default so NOP */ + break; + + case 'r': /* run code from command line */ + if (behavior == PHP_MODE_CLI_DIRECT) { + if (exec_direct || script_file) { + param_error = "You can use -r only once.\n"; + break; + } + } else if (behavior != PHP_MODE_STANDARD || interactive) { + param_error = param_mode_conflict; + break; + } + behavior=PHP_MODE_CLI_DIRECT; + exec_direct=php_optarg; + break; + + case 'R': + if (behavior == PHP_MODE_PROCESS_STDIN) { + if (exec_run || script_file) { + param_error = "You can use -R or -F only once.\n"; + break; + } + } else if (behavior != PHP_MODE_STANDARD) { + param_error = param_mode_conflict; + break; + } + behavior=PHP_MODE_PROCESS_STDIN; + exec_run=php_optarg; + break; + + case 'B': + if (behavior == PHP_MODE_PROCESS_STDIN) { + if (exec_begin) { + param_error = "You can use -B only once.\n"; + break; + } + } else if (behavior != PHP_MODE_STANDARD || interactive) { + param_error = param_mode_conflict; + break; + } + behavior=PHP_MODE_PROCESS_STDIN; + exec_begin=php_optarg; + break; + + case 'E': + if (behavior == PHP_MODE_PROCESS_STDIN) { + if (exec_end) { + param_error = "You can use -E only once.\n"; + break; + } + } else if (behavior != PHP_MODE_STANDARD || interactive) { + param_error = param_mode_conflict; + break; + } + behavior=PHP_MODE_PROCESS_STDIN; + exec_end=php_optarg; + break; + + case 's': /* generate highlighted HTML from source */ + if (behavior == PHP_MODE_CLI_DIRECT || behavior == PHP_MODE_PROCESS_STDIN) { + param_error = "Source highlighting only works for files.\n"; + break; + } + behavior=PHP_MODE_HIGHLIGHT; + break; + + case 'w': + if (behavior == PHP_MODE_CLI_DIRECT || behavior == PHP_MODE_PROCESS_STDIN) { + param_error = "Source stripping only works for files.\n"; + break; + } + behavior=PHP_MODE_STRIP; + break; + + case 'z': /* load extension file */ + zend_load_extension(php_optarg); + break; + case 'H': + hide_argv = 1; + break; + case 10: + behavior=PHP_MODE_REFLECTION_FUNCTION; + reflection_what = php_optarg; + break; + case 11: + behavior=PHP_MODE_REFLECTION_CLASS; + reflection_what = php_optarg; + break; + case 12: + behavior=PHP_MODE_REFLECTION_EXTENSION; + reflection_what = php_optarg; + break; + case 13: + behavior=PHP_MODE_REFLECTION_ZEND_EXTENSION; + reflection_what = php_optarg; + break; + case 14: + behavior=PHP_MODE_REFLECTION_EXT_INFO; + reflection_what = php_optarg; + break; + case 15: + behavior = PHP_MODE_SHOW_INI_CONFIG; + break; + default: + break; + } + } + + if (param_error) { + PUTS(param_error); + exit_status=1; + goto err; + } + + if (interactive) { +#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE) + printf("Interactive shell\n\n"); +#else + printf("Interactive mode enabled\n\n"); +#endif + fflush(stdout); + } + + CG(interactive) = interactive; + + /* only set script_file if not set already and not in direct mode and not at end of parameter list */ + if (argc > php_optind + && !script_file + && behavior!=PHP_MODE_CLI_DIRECT + && behavior!=PHP_MODE_PROCESS_STDIN + && strcmp(argv[php_optind-1],"--")) + { + script_file=argv[php_optind]; + php_optind++; + } + if (script_file) { + if (cli_seek_file_begin(&file_handle, script_file, &lineno TSRMLS_CC) != SUCCESS) { + goto err; + } else { + char real_path[MAXPATHLEN]; + if (VCWD_REALPATH(script_file, real_path)) { + translated_path = strdup(real_path); + } + script_filename = script_file; + } + } else { + /* We could handle PHP_MODE_PROCESS_STDIN in a different manner */ + /* here but this would make things only more complicated. And it */ + /* is consitent with the way -R works where the stdin file handle*/ + /* is also accessible. */ + file_handle.filename = "-"; + file_handle.handle.fp = stdin; + } + file_handle.type = ZEND_HANDLE_FP; + file_handle.opened_path = NULL; + file_handle.free_filename = 0; + php_self = (char*)file_handle.filename; + + /* before registering argv to module exchange the *new* argv[0] */ + /* we can achieve this without allocating more memory */ + SG(request_info).argc=argc-php_optind+1; + arg_excp = argv+php_optind-1; + arg_free = argv[php_optind-1]; + SG(request_info).path_translated = translated_path? translated_path: (char*)file_handle.filename; + argv[php_optind-1] = (char*)file_handle.filename; + SG(request_info).argv=argv+php_optind-1; + + if (php_request_startup(TSRMLS_C)==FAILURE) { + *arg_excp = arg_free; + fclose(file_handle.handle.fp); + PUTS("Could not startup.\n"); + goto err; + } + request_started = 1; + CG(start_lineno) = lineno; + *arg_excp = arg_free; /* reconstuct argv */ + + if (hide_argv) { + int i; + for (i = 1; i < argc; i++) { + memset(argv[i], 0, strlen(argv[i])); + } + } + + zend_is_auto_global("_SERVER", sizeof("_SERVER")-1 TSRMLS_CC); + + PG(during_request_startup) = 0; + switch (behavior) { + case PHP_MODE_STANDARD: + if (strcmp(file_handle.filename, "-")) { + cli_register_file_handles(TSRMLS_C); + } + + if (interactive && cli_shell_callbacks.cli_shell_run) { + exit_status = cli_shell_callbacks.cli_shell_run(TSRMLS_C); + } else { + php_execute_script(&file_handle TSRMLS_CC); + exit_status = EG(exit_status); + } + break; + case PHP_MODE_LINT: + exit_status = php_lint_script(&file_handle TSRMLS_CC); + if (exit_status==SUCCESS) { + zend_printf("No syntax errors detected in %s\n", file_handle.filename); + } else { + zend_printf("Errors parsing %s\n", file_handle.filename); + } + break; + case PHP_MODE_STRIP: + if (open_file_for_scanning(&file_handle TSRMLS_CC)==SUCCESS) { + zend_strip(TSRMLS_C); + } + goto out; + break; + case PHP_MODE_HIGHLIGHT: + { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + if (open_file_for_scanning(&file_handle TSRMLS_CC)==SUCCESS) { + php_get_highlight_struct(&syntax_highlighter_ini); + zend_highlight(&syntax_highlighter_ini TSRMLS_CC); + } + goto out; + } + break; +#if 0 + /* Zeev might want to do something with this one day */ + case PHP_MODE_INDENT: + open_file_for_scanning(&file_handle TSRMLS_CC); + zend_indent(); + zend_file_handle_dtor(file_handle.handle TSRMLS_CC); + goto out; + break; +#endif + case PHP_MODE_CLI_DIRECT: + cli_register_file_handles(TSRMLS_C); + if (zend_eval_string_ex(exec_direct, NULL, "Command line code", 1 TSRMLS_CC) == FAILURE) { + exit_status=254; + } + break; + + case PHP_MODE_PROCESS_STDIN: + { + char *input; + size_t len, index = 0; + zval *argn, *argi; + + cli_register_file_handles(TSRMLS_C); + + if (exec_begin && zend_eval_string_ex(exec_begin, NULL, "Command line begin code", 1 TSRMLS_CC) == FAILURE) { + exit_status=254; + } + ALLOC_ZVAL(argi); + Z_TYPE_P(argi) = IS_LONG; + Z_LVAL_P(argi) = index; + INIT_PZVAL(argi); + zend_hash_update(&EG(symbol_table), "argi", sizeof("argi"), &argi, sizeof(zval *), NULL); + while (exit_status == SUCCESS && (input=php_stream_gets(s_in_process, NULL, 0)) != NULL) { + len = strlen(input); + while (len-- && (input[len]=='\n' || input[len]=='\r')) { + input[len] = '\0'; + } + ALLOC_ZVAL(argn); + Z_TYPE_P(argn) = IS_STRING; + Z_STRLEN_P(argn) = ++len; + Z_STRVAL_P(argn) = estrndup(input, len); + INIT_PZVAL(argn); + zend_hash_update(&EG(symbol_table), "argn", sizeof("argn"), &argn, sizeof(zval *), NULL); + Z_LVAL_P(argi) = ++index; + if (exec_run) { + if (zend_eval_string_ex(exec_run, NULL, "Command line run code", 1 TSRMLS_CC) == FAILURE) { + exit_status=254; + } + } else { + if (script_file) { + if (cli_seek_file_begin(&file_handle, script_file, &lineno TSRMLS_CC) != SUCCESS) { + exit_status = 1; + } else { + CG(start_lineno) = lineno; + php_execute_script(&file_handle TSRMLS_CC); + exit_status = EG(exit_status); + } + } + } + efree(input); + } + if (exec_end && zend_eval_string_ex(exec_end, NULL, "Command line end code", 1 TSRMLS_CC) == FAILURE) { + exit_status=254; + } + + break; + } + case PHP_MODE_REFLECTION_FUNCTION: + case PHP_MODE_REFLECTION_CLASS: + case PHP_MODE_REFLECTION_EXTENSION: + case PHP_MODE_REFLECTION_ZEND_EXTENSION: + { + zend_class_entry *pce = NULL; + zval *arg, *ref; + zend_execute_data execute_data; + + switch (behavior) { + default: + break; + case PHP_MODE_REFLECTION_FUNCTION: + if (strstr(reflection_what, "::")) { + pce = reflection_method_ptr; + } else { + pce = reflection_function_ptr; + } + break; + case PHP_MODE_REFLECTION_CLASS: + pce = reflection_class_ptr; + break; + case PHP_MODE_REFLECTION_EXTENSION: + pce = reflection_extension_ptr; + break; + case PHP_MODE_REFLECTION_ZEND_EXTENSION: + pce = reflection_zend_extension_ptr; + break; + } + + MAKE_STD_ZVAL(arg); + ZVAL_STRING(arg, reflection_what, 1); + ALLOC_ZVAL(ref); + object_init_ex(ref, pce); + INIT_PZVAL(ref); + + memset(&execute_data, 0, sizeof(zend_execute_data)); + EG(current_execute_data) = &execute_data; + EX(function_state).function = pce->constructor; + zend_call_method_with_1_params(&ref, pce, &pce->constructor, "__construct", NULL, arg); + + if (EG(exception)) { + zval *msg = zend_read_property(zend_exception_get_default(TSRMLS_C), EG(exception), "message", sizeof("message")-1, 0 TSRMLS_CC); + zend_printf("Exception: %s\n", Z_STRVAL_P(msg)); + zval_ptr_dtor(&EG(exception)); + EG(exception) = NULL; + } else { + zend_call_method_with_1_params(NULL, reflection_ptr, NULL, "export", NULL, ref); + } + zval_ptr_dtor(&ref); + zval_ptr_dtor(&arg); + + break; + } + case PHP_MODE_REFLECTION_EXT_INFO: + { + int len = strlen(reflection_what); + char *lcname = zend_str_tolower_dup(reflection_what, len); + zend_module_entry *module; + + if (zend_hash_find(&module_registry, lcname, len+1, (void**)&module) == FAILURE) { + if (!strcmp(reflection_what, "main")) { + display_ini_entries(NULL); + } else { + zend_printf("Extension '%s' not present.\n", reflection_what); + exit_status = 1; + } + } else { + php_info_print_module(module TSRMLS_CC); + } + + efree(lcname); + break; + } + case PHP_MODE_SHOW_INI_CONFIG: + { + zend_printf("Configuration File (php.ini) Path: %s\n", PHP_CONFIG_FILE_PATH); + zend_printf("Loaded Configuration File: %s\n", php_ini_opened_path ? php_ini_opened_path : "(none)"); + zend_printf("Scan for additional .ini files in: %s\n", php_ini_scanned_path ? php_ini_scanned_path : "(none)"); + zend_printf("Additional .ini files parsed: %s\n", php_ini_scanned_files ? php_ini_scanned_files : "(none)"); + break; + } + } + } zend_end_try(); + +out: + if (request_started) { + php_request_shutdown((void *) 0); + } + if (translated_path) { + free(translated_path); + } + if (exit_status == 0) { + exit_status = EG(exit_status); + } + return exit_status; +err: + sapi_deactivate(TSRMLS_C); + zend_ini_deactivate(TSRMLS_C); + exit_status = 1; + goto out; +} +/* }}} */ + +/* {{{ main + */ +#ifdef PHP_CLI_WIN32_NO_CONSOLE +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +#else +int main(int argc, char *argv[]) +#endif +{ +#ifdef ZTS + void ***tsrm_ls; +#endif +#ifdef PHP_CLI_WIN32_NO_CONSOLE + int argc = __argc; + char **argv = __argv; +#endif + int c; + int exit_status = SUCCESS; + int module_started = 0, sapi_started = 0; + char *php_optarg = NULL; + int php_optind = 1, use_extended_info = 0; + char *ini_path_override = NULL; + char *ini_entries = NULL; + int ini_entries_len = 0; + int ini_ignore = 0; + sapi_module_struct *sapi_module = &cli_sapi_module; + + cli_sapi_module.additional_functions = additional_functions; + +#if defined(PHP_WIN32) && defined(_DEBUG) && defined(PHP_WIN32_DEBUG_HEAP) + { + int tmp_flag; + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + tmp_flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + tmp_flag |= _CRTDBG_DELAY_FREE_MEM_DF; + tmp_flag |= _CRTDBG_LEAK_CHECK_DF; + + _CrtSetDbgFlag(tmp_flag); + } +#endif + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif +#endif + + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + tsrm_ls = ts_resource(0); +#endif + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /*sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) { + switch (c) { + case 'c': + if (ini_path_override) { + free(ini_path_override); + } + ini_path_override = strdup(php_optarg); + break; + case 'n': + ini_ignore = 1; + break; + case 'd': { + /* define ini entries on command line */ + int len = strlen(php_optarg); + char *val; + + if ((val = strchr(php_optarg, '='))) { + val++; + if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') { + ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("\"\"\n\0")); + memcpy(ini_entries + ini_entries_len, php_optarg, (val - php_optarg)); + ini_entries_len += (val - php_optarg); + memcpy(ini_entries + ini_entries_len, "\"", 1); + ini_entries_len++; + memcpy(ini_entries + ini_entries_len, val, len - (val - php_optarg)); + ini_entries_len += len - (val - php_optarg); + memcpy(ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0")); + ini_entries_len += sizeof("\n\0\"") - 2; + } else { + ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("\n\0")); + memcpy(ini_entries + ini_entries_len, php_optarg, len); + memcpy(ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0")); + ini_entries_len += len + sizeof("\n\0") - 2; + } + } else { + ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("=1\n\0")); + memcpy(ini_entries + ini_entries_len, php_optarg, len); + memcpy(ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0")); + ini_entries_len += len + sizeof("=1\n\0") - 2; + } + break; + } +#ifndef PHP_CLI_WIN32_NO_CONSOLE + case 'S': + sapi_module = &cli_server_sapi_module; + break; +#endif + case 'h': /* help & quit */ + case '?': + php_cli_usage(argv[0]); + goto out; + case 'i': case 'v': case 'm': + sapi_module = &cli_sapi_module; + goto exit_loop; + case 'e': /* enable extended info output */ + use_extended_info = 1; + break; + } + } +exit_loop: + + sapi_module->ini_defaults = sapi_cli_ini_defaults; + sapi_module->php_ini_path_override = ini_path_override; + sapi_module->phpinfo_as_text = 1; + sapi_module->php_ini_ignore_cwd = 1; + sapi_startup(sapi_module); + sapi_started = 1; + + sapi_module->php_ini_ignore = ini_ignore; + + sapi_module->executable_location = argv[0]; + + if (sapi_module == &cli_sapi_module) { + if (ini_entries) { + ini_entries = realloc(ini_entries, ini_entries_len + sizeof(HARDCODED_INI)); + memmove(ini_entries + sizeof(HARDCODED_INI) - 2, ini_entries, ini_entries_len + 1); + memcpy(ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI) - 2); + } else { + ini_entries = malloc(sizeof(HARDCODED_INI)); + memcpy(ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI)); + } + ini_entries_len += sizeof(HARDCODED_INI) - 2; + } + + sapi_module->ini_entries = ini_entries; + + /* startup after we get the above ini override se we get things right */ + if (sapi_module->startup(sapi_module) == FAILURE) { + /* there is no way to see if we must call zend_ini_deactivate() + * since we cannot check if EG(ini_directives) has been initialised + * because the executor's constructor does not set initialize it. + * Apart from that there seems no need for zend_ini_deactivate() yet. + * So we goto out_err.*/ + exit_status = 1; + goto out; + } + module_started = 1; + + /* -e option */ + if (use_extended_info) { + CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; + } + + zend_first_try { +#ifndef PHP_CLI_WIN32_NO_CONSOLE + if (sapi_module == &cli_sapi_module) { +#endif + exit_status = do_cli(argc, argv TSRMLS_CC); +#ifndef PHP_CLI_WIN32_NO_CONSOLE + } else { + exit_status = do_cli_server(argc, argv TSRMLS_CC); + } +#endif + } zend_end_try(); +out: + if (ini_path_override) { + free(ini_path_override); + } + if (ini_entries) { + free(ini_entries); + } + if (module_started) { + php_module_shutdown(TSRMLS_C); + } + if (sapi_started) { + sapi_shutdown(); + } +#ifdef ZTS + tsrm_shutdown(); +#endif + + exit(exit_status); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c new file mode 100644 index 0000000..ab7f4cf --- /dev/null +++ b/sapi/cli/php_cli_server.c @@ -0,0 +1,2425 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Moriyoshi Koizumi <moriyoshi@php.net> | + | Xinchen Hui <laruence@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: php_cli.c 306938 2011-01-01 02:17:06Z felipe $ */ + +#include <stdio.h> +#include <fcntl.h> +#include <assert.h> + +#ifdef PHP_WIN32 +#include <process.h> +#include <io.h> +#include "win32/time.h" +#include "win32/signal.h" +#include "win32/php_registry.h" +#else +# include "php_config.h" +#endif + +#ifdef __riscos__ +#include <unixlib/local.h> +#endif + + +#if HAVE_TIME_H +#include <time.h> +#endif +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SIGNAL_H +#include <signal.h> +#endif +#if HAVE_SETLOCALE +#include <locale.h> +#endif +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#include "SAPI.h" +#include "php.h" +#include "php_ini.h" +#include "php_main.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_hash.h" +#include "zend_modules.h" +#include "fopen_wrappers.h" + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" +#include "zend_exceptions.h" + +#include "php_getopt.h" + +#ifndef PHP_WIN32 +# define php_select(m, r, w, e, t) select(m, r, w, e, t) +# define SOCK_EINVAL EINVAL +# define SOCK_EAGAIN EAGAIN +# define SOCK_EINTR EINTR +# define SOCK_EADDRINUSE EADDRINUSE +#else +# include "win32/select.h" +# define SOCK_EINVAL WSAEINVAL +# define SOCK_EAGAIN WSAEWOULDBLOCK +# define SOCK_EINTR WSAEINTR +# define SOCK_EADDRINUSE WSAEADDRINUSE +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#endif + +#include "ext/standard/file.h" /* for php_set_sock_blocking() :-( */ +#include "ext/standard/php_smart_str.h" +#include "ext/standard/html.h" +#include "ext/standard/url.h" /* for php_url_decode() */ +#include "ext/standard/php_string.h" /* for php_dirname() */ +#include "php_network.h" + +#include "php_http_parser.h" +#include "php_cli_server.h" + +#define OUTPUT_NOT_CHECKED -1 +#define OUTPUT_IS_TTY 1 +#define OUTPUT_NOT_TTY 0 + +typedef struct php_cli_server_poller { + fd_set rfds, wfds; + struct { + fd_set rfds, wfds; + } active; + php_socket_t max_fd; +} php_cli_server_poller; + +typedef struct php_cli_server_request { + enum php_http_method request_method; + int protocol_version; + char *request_uri; + size_t request_uri_len; + char *vpath; + size_t vpath_len; + char *path_translated; + size_t path_translated_len; + char *path_info; + size_t path_info_len; + char *query_string; + size_t query_string_len; + HashTable headers; + char *content; + size_t content_len; + const char *ext; + size_t ext_len; + struct stat sb; +} php_cli_server_request; + +typedef struct php_cli_server_chunk { + struct php_cli_server_chunk *next; + enum php_cli_server_chunk_type { + PHP_CLI_SERVER_CHUNK_HEAP, + PHP_CLI_SERVER_CHUNK_IMMORTAL + } type; + union { + struct { void *block; char *p; size_t len; } heap; + struct { const char *p; size_t len; } immortal; + } data; +} php_cli_server_chunk; + +typedef struct php_cli_server_buffer { + php_cli_server_chunk *first; + php_cli_server_chunk *last; +} php_cli_server_buffer; + +typedef struct php_cli_server_content_sender { + php_cli_server_buffer buffer; +} php_cli_server_content_sender; + +typedef struct php_cli_server_client { + struct php_cli_server *server; + php_socket_t sock; + struct sockaddr *addr; + socklen_t addr_len; + char *addr_str; + size_t addr_str_len; + php_http_parser parser; + unsigned int request_read:1; + char *current_header_name; + size_t current_header_name_len; + unsigned int current_header_name_allocated:1; + size_t post_read_offset; + php_cli_server_request request; + unsigned int content_sender_initialized:1; + php_cli_server_content_sender content_sender; + int file_fd; +} php_cli_server_client; + +typedef struct php_cli_server { + php_socket_t server_sock; + php_cli_server_poller poller; + int is_running; + char *host; + int port; + int address_family; + char *document_root; + size_t document_root_len; + char *router; + size_t router_len; + socklen_t socklen; + HashTable clients; +} php_cli_server; + +typedef struct php_cli_server_http_reponse_status_code_pair { + int code; + const char *str; +} php_cli_server_http_reponse_status_code_pair; + +typedef struct php_cli_server_ext_mime_type_pair { + const char *ext; + const char *mime_type; +} php_cli_server_ext_mime_type_pair; + +static php_cli_server_http_reponse_status_code_pair status_map[] = { + { 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, "Found" }, + { 303, "See Other" }, + { 304, "Not Modified" }, + { 305, "Use Proxy" }, + { 307, "Temporary Redirect" }, + { 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 Timeout" }, + { 409, "Conflict" }, + { 410, "Gone" }, + { 411, "Length Required" }, + { 412, "Precondition Failed" }, + { 413, "Request Entity Too Large" }, + { 414, "Request-URI Too Long" }, + { 415, "Unsupported Media Type" }, + { 416, "Requested Range Not Satisfiable" }, + { 417, "Expectation Failed" }, + { 428, "Precondition Required" }, + { 429, "Too Many Requests" }, + { 431, "Request Header Fields Too Large" }, + { 500, "Internal Server Error" }, + { 501, "Not Implemented" }, + { 502, "Bad Gateway" }, + { 503, "Service Unavailable" }, + { 504, "Gateway Timeout" }, + { 505, "HTTP Version Not Supported" }, + { 511, "Network Authentication Required" }, +}; + +static php_cli_server_http_reponse_status_code_pair template_map[] = { + { 400, "<h1>%s</h1><p>Your browser sent a request that this server could not understand.</p>" }, + { 404, "<h1>%s</h1><p>The requested resource %s was not found on this server.</p>" }, + { 500, "<h1>%s</h1><p>The server is temporarily unavailable.</p>" }, + { 501, "<h1>%s</h1><p>Request method not supported.</p>" } +}; + +static php_cli_server_ext_mime_type_pair mime_type_map[] = { + { "html", "text/html" }, + { "htm", "text/html" }, + { "js", "text/javascript" }, + { "css", "text/css" }, + { "gif", "image/gif" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "jpe", "image/jpeg" }, + { "png", "image/png" }, + { "svg", "image/svg+xml" }, + { "txt", "text/plain" }, + { "webm", "video/webm" }, + { "ogv", "video/ogg" }, + { "ogg", "audio/ogg" }, + { NULL, NULL } +}; + +static int php_cli_output_is_tty = OUTPUT_NOT_CHECKED; + +static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len); +static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len); +static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk); +static void php_cli_server_logf(const char *format TSRMLS_DC, ...); +static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message TSRMLS_DC); + +ZEND_DECLARE_MODULE_GLOBALS(cli_server); + +/* {{{ static char php_cli_server_css[] + * copied from ext/standard/info.c + */ +static const char php_cli_server_css[] = "<style>\n" \ + "body { background-color: #ffffff; color: #000000; }\n" \ + "h1 { font-family: sans-serif; font-size: 150%; background-color: #9999cc; font-weight: bold; color: #000000; margin-top: 0;}\n" \ + "</style>\n"; +/* }}} */ + +static void char_ptr_dtor_p(char **p) /* {{{ */ +{ + pefree(*p, 1); +} /* }}} */ + +static char *get_last_error() /* {{{ */ +{ + return pestrdup(strerror(errno), 1); +} /* }}} */ + +static const char *get_status_string(int code) /* {{{ */ +{ + size_t e = (sizeof(status_map) / sizeof(php_cli_server_http_reponse_status_code_pair)); + size_t s = 0; + + while (e != s) { + size_t c = MIN((e + s + 1) / 2, e - 1); + int d = status_map[c].code; + if (d > code) { + e = c; + } else if (d < code) { + s = c; + } else { + return status_map[c].str; + } + } + return NULL; +} /* }}} */ + +static const char *get_template_string(int code) /* {{{ */ +{ + size_t e = (sizeof(template_map) / sizeof(php_cli_server_http_reponse_status_code_pair)); + size_t s = 0; + + while (e != s) { + size_t c = MIN((e + s + 1) / 2, e - 1); + int d = template_map[c].code; + if (d > code) { + e = c; + } else if (d < code) { + s = c; + } else { + return template_map[c].str; + } + } + return NULL; +} /* }}} */ + +static void append_http_status_line(smart_str *buffer, int protocol_version, int response_code, int persistent) /* {{{ */ +{ + if (!response_code) { + response_code = 200; + } + smart_str_appendl_ex(buffer, "HTTP", 4, persistent); + smart_str_appendc_ex(buffer, '/', persistent); + smart_str_append_generic_ex(buffer, protocol_version / 100, persistent, int, _unsigned); + smart_str_appendc_ex(buffer, '.', persistent); + smart_str_append_generic_ex(buffer, protocol_version % 100, persistent, int, _unsigned); + smart_str_appendc_ex(buffer, ' ', persistent); + smart_str_append_generic_ex(buffer, response_code, persistent, int, _unsigned); + smart_str_appendc_ex(buffer, ' ', persistent); + smart_str_appends_ex(buffer, get_status_string(response_code), persistent); + smart_str_appendl_ex(buffer, "\r\n", 2, persistent); +} /* }}} */ + +static void append_essential_headers(smart_str* buffer, php_cli_server_client *client, int persistent) /* {{{ */ +{ + { + char **val; + if (SUCCESS == zend_hash_find(&client->request.headers, "Host", sizeof("Host"), (void**)&val)) { + smart_str_appendl_ex(buffer, "Host", sizeof("Host") - 1, persistent); + smart_str_appendl_ex(buffer, ": ", sizeof(": ") - 1, persistent); + smart_str_appends_ex(buffer, *val, persistent); + smart_str_appendl_ex(buffer, "\r\n", 2, persistent); + } + } + smart_str_appendl_ex(buffer, "Connection: close\r\n", sizeof("Connection: close\r\n") - 1, persistent); +} /* }}} */ + +static const char *get_mime_type(const char *ext, size_t ext_len) /* {{{ */ +{ + php_cli_server_ext_mime_type_pair *pair; + for (pair = mime_type_map; pair->ext; pair++) { + size_t len = strlen(pair->ext); + if (len == ext_len && memcmp(pair->ext, ext, len) == 0) { + return pair->mime_type; + } + } + return NULL; +} /* }}} */ + +/* {{{ cli_server module + */ + +static void cli_server_init_globals(zend_cli_server_globals *cg TSRMLS_DC) +{ + cg->color = 0; +} + +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN("cli_server.color", "0", PHP_INI_ALL, OnUpdateBool, color, zend_cli_server_globals, cli_server_globals) +PHP_INI_END() + +static PHP_MINIT_FUNCTION(cli_server) +{ + ZEND_INIT_MODULE_GLOBALS(cli_server, cli_server_init_globals, NULL); + REGISTER_INI_ENTRIES(); + return SUCCESS; +} + +static PHP_MSHUTDOWN_FUNCTION(cli_server) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} + +static PHP_MINFO_FUNCTION(cli_server) +{ + DISPLAY_INI_ENTRIES(); +} + +zend_module_entry cli_server_module_entry = { + STANDARD_MODULE_HEADER, + "cli_server", + NULL, + PHP_MINIT(cli_server), + PHP_MSHUTDOWN(cli_server), + NULL, + NULL, + PHP_MINFO(cli_server), + PHP_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +static int sapi_cli_server_startup(sapi_module_struct *sapi_module) /* {{{ */ +{ + if (php_module_startup(sapi_module, &cli_server_module_entry, 1) == FAILURE) { + return FAILURE; + } + return SUCCESS; +} /* }}} */ + +static int sapi_cli_server_ub_write(const char *str, uint str_length TSRMLS_DC) /* {{{ */ +{ + php_cli_server_client *client = SG(server_context); + if (!client) { + return 0; + } + return php_cli_server_client_send_through(client, str, str_length); +} /* }}} */ + +static void sapi_cli_server_flush(void *server_context) /* {{{ */ +{ + php_cli_server_client *client = server_context; + TSRMLS_FETCH(); + + if (!client) { + return; + } + + if (client->sock < 0) { + php_handle_aborted_connection(); + return; + } + + if (!SG(headers_sent)) { + sapi_send_headers(TSRMLS_C); + SG(headers_sent) = 1; + } +} /* }}} */ + +static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) /* {{{ */{ + return SAPI_HEADER_SENT_SUCCESSFULLY; +} +/* }}} */ + +static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) /* {{{ */ +{ + php_cli_server_client *client = SG(server_context); + smart_str buffer = { 0 }; + sapi_header_struct *h; + zend_llist_position pos; + + if (client == NULL || SG(request_info).no_headers) { + return SAPI_HEADER_SENT_SUCCESSFULLY; + } + + if (SG(sapi_headers).http_status_line) { + smart_str_appends(&buffer, SG(sapi_headers).http_status_line); + smart_str_appendl(&buffer, "\r\n", 2); + } else { + append_http_status_line(&buffer, client->request.protocol_version, SG(sapi_headers).http_response_code, 0); + } + + append_essential_headers(&buffer, client, 0); + + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if (!h->header_len) { + continue; + } + smart_str_appendl(&buffer, h->header, h->header_len); + smart_str_appendl(&buffer, "\r\n", 2); + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + smart_str_appendl(&buffer, "\r\n", 2); + + php_cli_server_client_send_through(client, buffer.c, buffer.len); + + smart_str_free(&buffer); + return SAPI_HEADER_SENT_SUCCESSFULLY; +} +/* }}} */ + +static char *sapi_cli_server_read_cookies(TSRMLS_D) /* {{{ */ +{ + php_cli_server_client *client = SG(server_context); + char **val; + if (FAILURE == zend_hash_find(&client->request.headers, "Cookie", sizeof("Cookie"), (void**)&val)) { + return NULL; + } + return *val; +} /* }}} */ + +static int sapi_cli_server_read_post(char *buf, uint count_bytes TSRMLS_DC) /* {{{ */ +{ + php_cli_server_client *client = SG(server_context); + if (client->request.content) { + size_t content_len = client->request.content_len; + size_t nbytes_copied = MIN(client->post_read_offset + count_bytes, content_len) - client->post_read_offset; + memmove(buf, client->request.content + client->post_read_offset, nbytes_copied); + client->post_read_offset += nbytes_copied; + return nbytes_copied; + } + return 0; +} /* }}} */ + +static void sapi_cli_server_register_variable(zval *track_vars_array, const char *key, const char *val TSRMLS_DC) /* {{{ */ +{ + char *new_val = (char *)val; + uint new_val_len; + if (sapi_module.input_filter(PARSE_SERVER, (char*)key, &new_val, strlen(val), &new_val_len TSRMLS_CC)) { + php_register_variable_safe((char *)key, new_val, new_val_len, track_vars_array TSRMLS_CC); + } +} /* }}} */ + +static int sapi_cli_server_register_entry_cb(char **entry TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ { + zval *track_vars_array = va_arg(args, zval *); + if (hash_key->nKeyLength) { + char *real_key, *key; + uint i; + key = estrndup(hash_key->arKey, hash_key->nKeyLength); + for(i=0; i<hash_key->nKeyLength; i++) { + if (key[i] == '-') { + key[i] = '_'; + } else { + key[i] = toupper(key[i]); + } + } + spprintf(&real_key, 0, "%s_%s", "HTTP", key); + sapi_cli_server_register_variable(track_vars_array, real_key, *entry TSRMLS_CC); + efree(key); + efree(real_key); + } + + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static void sapi_cli_server_register_variables(zval *track_vars_array TSRMLS_DC) /* {{{ */ +{ + php_cli_server_client *client = SG(server_context); + sapi_cli_server_register_variable(track_vars_array, "DOCUMENT_ROOT", client->server->document_root TSRMLS_CC); + { + char *tmp; + if ((tmp = strrchr(client->addr_str, ':'))) { + char addr[64], port[8]; + strncpy(port, tmp + 1, 8); + port[7] = '\0'; + strncpy(addr, client->addr_str, tmp - client->addr_str); + addr[tmp - client->addr_str] = '\0'; + sapi_cli_server_register_variable(track_vars_array, "REMOTE_ADDR", addr TSRMLS_CC); + sapi_cli_server_register_variable(track_vars_array, "REMOTE_PORT", port TSRMLS_CC); + } else { + sapi_cli_server_register_variable(track_vars_array, "REMOTE_ADDR", client->addr_str TSRMLS_CC); + } + } + { + char *tmp; + spprintf(&tmp, 0, "PHP %s Development Server", PHP_VERSION); + sapi_cli_server_register_variable(track_vars_array, "SERVER_SOFTWARE", tmp TSRMLS_CC); + efree(tmp); + } + { + char *tmp; + spprintf(&tmp, 0, "HTTP/%d.%d", client->request.protocol_version / 100, client->request.protocol_version % 100); + sapi_cli_server_register_variable(track_vars_array, "SERVER_PROTOCOL", tmp TSRMLS_CC); + efree(tmp); + } + sapi_cli_server_register_variable(track_vars_array, "SERVER_NAME", client->server->host TSRMLS_CC); + { + char *tmp; + spprintf(&tmp, 0, "%i", client->server->port); + sapi_cli_server_register_variable(track_vars_array, "SERVER_PORT", tmp TSRMLS_CC); + efree(tmp); + } + + sapi_cli_server_register_variable(track_vars_array, "REQUEST_URI", client->request.request_uri TSRMLS_CC); + sapi_cli_server_register_variable(track_vars_array, "REQUEST_METHOD", SG(request_info).request_method TSRMLS_CC); + sapi_cli_server_register_variable(track_vars_array, "SCRIPT_NAME", client->request.vpath TSRMLS_CC); + if (SG(request_info).path_translated) { + sapi_cli_server_register_variable(track_vars_array, "SCRIPT_FILENAME", SG(request_info).path_translated TSRMLS_CC); + } else if (client->server->router) { + char *temp; + spprintf(&temp, 0, "%s/%s", client->server->document_root, client->server->router); + sapi_cli_server_register_variable(track_vars_array, "SCRIPT_FILENAME", temp TSRMLS_CC); + efree(temp); + } + if (client->request.path_info) { + sapi_cli_server_register_variable(track_vars_array, "PATH_INFO", client->request.path_info TSRMLS_CC); + } + if (client->request.path_info_len) { + char *tmp; + spprintf(&tmp, 0, "%s%s", client->request.vpath, client->request.path_info); + sapi_cli_server_register_variable(track_vars_array, "PHP_SELF", tmp TSRMLS_CC); + efree(tmp); + } else { + sapi_cli_server_register_variable(track_vars_array, "PHP_SELF", client->request.vpath TSRMLS_CC); + } + if (client->request.query_string) { + sapi_cli_server_register_variable(track_vars_array, "QUERY_STRING", client->request.query_string TSRMLS_CC); + } + zend_hash_apply_with_arguments(&client->request.headers TSRMLS_CC, (apply_func_args_t)sapi_cli_server_register_entry_cb, 1, track_vars_array); +} /* }}} */ + +static void sapi_cli_server_log_message(char *msg TSRMLS_DC) /* {{{ */ +{ + struct timeval tv; + struct tm tm; + char buf[52]; + gettimeofday(&tv, NULL); + php_localtime_r(&tv.tv_sec, &tm); + php_asctime_r(&tm, buf); + { + size_t l = strlen(buf); + if (l > 0) { + buf[l - 1] = '\0'; + } else { + memmove(buf, "unknown", sizeof("unknown")); + } + } + fprintf(stderr, "[%s] %s\n", buf, msg); +} /* }}} */ + +/* {{{ sapi_module_struct cli_server_sapi_module + */ +sapi_module_struct cli_server_sapi_module = { + "cli-server", /* name */ + "Built-in HTTP server", /* pretty name */ + + sapi_cli_server_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_cli_server_ub_write, /* unbuffered write */ + sapi_cli_server_flush, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_cli_server_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_cli_server_read_post, /* read POST data */ + sapi_cli_server_read_cookies, /* read Cookies */ + + sapi_cli_server_register_variables, /* register server variables */ + sapi_cli_server_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; /* }}} */ + +static int php_cli_server_poller_ctor(php_cli_server_poller *poller) /* {{{ */ +{ + FD_ZERO(&poller->rfds); + FD_ZERO(&poller->wfds); + poller->max_fd = -1; + return SUCCESS; +} /* }}} */ + +static void php_cli_server_poller_add(php_cli_server_poller *poller, int mode, int fd) /* {{{ */ +{ + if (mode & POLLIN) { + PHP_SAFE_FD_SET(fd, &poller->rfds); + } + if (mode & POLLOUT) { + PHP_SAFE_FD_SET(fd, &poller->wfds); + } + if (fd > poller->max_fd) { + poller->max_fd = fd; + } +} /* }}} */ + +static void php_cli_server_poller_remove(php_cli_server_poller *poller, int mode, int fd) /* {{{ */ +{ + if (mode & POLLIN) { + PHP_SAFE_FD_CLR(fd, &poller->rfds); + } + if (mode & POLLOUT) { + PHP_SAFE_FD_CLR(fd, &poller->wfds); + } +#ifndef PHP_WIN32 + if (fd == poller->max_fd) { + while (fd > 0) { + fd--; + if (PHP_SAFE_FD_ISSET(fd, &poller->rfds) || PHP_SAFE_FD_ISSET(fd, &poller->wfds)) { + break; + } + } + poller->max_fd = fd; + } +#endif +} /* }}} */ + +static int php_cli_server_poller_poll(php_cli_server_poller *poller, const struct timeval *tv) /* {{{ */ +{ + memmove(&poller->active.rfds, &poller->rfds, sizeof(poller->rfds)); + memmove(&poller->active.wfds, &poller->wfds, sizeof(poller->wfds)); + return php_select(poller->max_fd + 1, &poller->active.rfds, &poller->active.wfds, NULL, (struct timeval *)tv); +} /* }}} */ + +static int php_cli_server_poller_iter_on_active(php_cli_server_poller *poller, void *opaque, int(*callback)(void *, int fd, int events)) /* {{{ */ +{ + int retval = SUCCESS; +#ifdef PHP_WIN32 + struct socket_entry { + SOCKET fd; + int events; + } entries[FD_SETSIZE * 2]; + php_socket_t fd = 0; + size_t i; + struct socket_entry *n = entries, *m; + + for (i = 0; i < poller->active.rfds.fd_count; i++) { + n->events = POLLIN; + n->fd = poller->active.rfds.fd_array[i]; + n++; + } + + m = n; + for (i = 0; i < poller->active.wfds.fd_count; i++) { + struct socket_entry *e; + SOCKET fd = poller->active.wfds.fd_array[i]; + for (e = entries; e < m; e++) { + if (e->fd == fd) { + e->events |= POLLOUT; + } + } + if (e == m) { + assert(n < entries + FD_SETSIZE * 2); + n->events = POLLOUT; + n->fd = fd; + n++; + } + } + + { + struct socket_entry *e = entries; + for (; e < n; e++) { + if (SUCCESS != callback(opaque, e->fd, e->events)) { + retval = FAILURE; + } + } + } + +#else + php_socket_t fd; + const php_socket_t max_fd = poller->max_fd; + + for (fd=0 ; fd<=max_fd ; fd++) { + if (PHP_SAFE_FD_ISSET(fd, &poller->active.rfds)) { + if (SUCCESS != callback(opaque, fd, POLLIN)) { + retval = FAILURE; + } + } + if (PHP_SAFE_FD_ISSET(fd, &poller->active.wfds)) { + if (SUCCESS != callback(opaque, fd, POLLOUT)) { + retval = FAILURE; + } + } + } +#endif + return retval; +} /* }}} */ + +static size_t php_cli_server_chunk_size(const php_cli_server_chunk *chunk) /* {{{ */ +{ + switch (chunk->type) { + case PHP_CLI_SERVER_CHUNK_HEAP: + return chunk->data.heap.len; + case PHP_CLI_SERVER_CHUNK_IMMORTAL: + return chunk->data.immortal.len; + } + return 0; +} /* }}} */ + +static void php_cli_server_chunk_dtor(php_cli_server_chunk *chunk) /* {{{ */ +{ + switch (chunk->type) { + case PHP_CLI_SERVER_CHUNK_HEAP: + if (chunk->data.heap.block != chunk) { + pefree(chunk->data.heap.block, 1); + } + break; + case PHP_CLI_SERVER_CHUNK_IMMORTAL: + break; + } +} /* }}} */ + +static void php_cli_server_buffer_dtor(php_cli_server_buffer *buffer) /* {{{ */ +{ + php_cli_server_chunk *chunk, *next; + for (chunk = buffer->first; chunk; chunk = next) { + next = chunk->next; + php_cli_server_chunk_dtor(chunk); + pefree(chunk, 1); + } +} /* }}} */ + +static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */ +{ + buffer->first = NULL; + buffer->last = NULL; +} /* }}} */ + +static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */ +{ + php_cli_server_chunk *last; + for (last = chunk; last->next; last = last->next); + if (!buffer->last) { + buffer->first = chunk; + } else { + buffer->last->next = chunk; + } + buffer->last = last; +} /* }}} */ + +static void php_cli_server_buffer_prepend(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */ +{ + php_cli_server_chunk *last; + for (last = chunk; last->next; last = last->next); + last->next = buffer->first; + if (!buffer->last) { + buffer->last = last; + } + buffer->first = chunk; +} /* }}} */ + +static size_t php_cli_server_buffer_size(const php_cli_server_buffer *buffer) /* {{{ */ +{ + php_cli_server_chunk *chunk; + size_t retval = 0; + for (chunk = buffer->first; chunk; chunk = chunk->next) { + retval += php_cli_server_chunk_size(chunk); + } + return retval; +} /* }}} */ + +static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, size_t len) /* {{{ */ +{ + php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1); + if (!chunk) { + return NULL; + } + + chunk->type = PHP_CLI_SERVER_CHUNK_IMMORTAL; + chunk->next = NULL; + chunk->data.immortal.p = buf; + chunk->data.immortal.len = len; + return chunk; +} /* }}} */ + +static php_cli_server_chunk *php_cli_server_chunk_heap_new(char *block, char *buf, size_t len) /* {{{ */ +{ + php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1); + if (!chunk) { + return NULL; + } + + chunk->type = PHP_CLI_SERVER_CHUNK_HEAP; + chunk->next = NULL; + chunk->data.heap.block = block; + chunk->data.heap.p = buf; + chunk->data.heap.len = len; + return chunk; +} /* }}} */ + +static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */ +{ + php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk) + len, 1); + if (!chunk) { + return NULL; + } + + chunk->type = PHP_CLI_SERVER_CHUNK_HEAP; + chunk->next = NULL; + chunk->data.heap.block = chunk; + chunk->data.heap.p = (char *)(chunk + 1); + chunk->data.heap.len = len; + return chunk; +} /* }}} */ + +static void php_cli_server_content_sender_dtor(php_cli_server_content_sender *sender) /* {{{ */ +{ + php_cli_server_buffer_dtor(&sender->buffer); +} /* }}} */ + +static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *sender) /* {{{ */ +{ + php_cli_server_buffer_ctor(&sender->buffer); +} /* }}} */ + +static int php_cli_server_content_sender_send(php_cli_server_content_sender *sender, php_socket_t fd, size_t *nbytes_sent_total) /* {{{ */ +{ + php_cli_server_chunk *chunk, *next; + size_t _nbytes_sent_total = 0; + + for (chunk = sender->buffer.first; chunk; chunk = next) { + ssize_t nbytes_sent; + next = chunk->next; + + switch (chunk->type) { + case PHP_CLI_SERVER_CHUNK_HEAP: + nbytes_sent = send(fd, chunk->data.heap.p, chunk->data.heap.len, 0); + if (nbytes_sent < 0) { + *nbytes_sent_total = _nbytes_sent_total; + return php_socket_errno(); + } else if (nbytes_sent == chunk->data.heap.len) { + php_cli_server_chunk_dtor(chunk); + pefree(chunk, 1); + sender->buffer.first = next; + if (!next) { + sender->buffer.last = NULL; + } + } else { + chunk->data.heap.p += nbytes_sent; + chunk->data.heap.len -= nbytes_sent; + } + _nbytes_sent_total += nbytes_sent; + break; + + case PHP_CLI_SERVER_CHUNK_IMMORTAL: + nbytes_sent = send(fd, chunk->data.immortal.p, chunk->data.immortal.len, 0); + if (nbytes_sent < 0) { + *nbytes_sent_total = _nbytes_sent_total; + return php_socket_errno(); + } else if (nbytes_sent == chunk->data.immortal.len) { + php_cli_server_chunk_dtor(chunk); + pefree(chunk, 1); + sender->buffer.first = next; + if (!next) { + sender->buffer.last = NULL; + } + } else { + chunk->data.immortal.p += nbytes_sent; + chunk->data.immortal.len -= nbytes_sent; + } + _nbytes_sent_total += nbytes_sent; + break; + } + } + *nbytes_sent_total = _nbytes_sent_total; + return 0; +} /* }}} */ + +static int php_cli_server_content_sender_pull(php_cli_server_content_sender *sender, int fd, size_t *nbytes_read) /* {{{ */ +{ + ssize_t _nbytes_read; + php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(131072); + + _nbytes_read = read(fd, chunk->data.heap.p, chunk->data.heap.len); + if (_nbytes_read < 0) { + char *errstr = get_last_error(); + TSRMLS_FETCH(); + php_cli_server_logf("%s" TSRMLS_CC, errstr); + pefree(errstr, 1); + php_cli_server_chunk_dtor(chunk); + pefree(chunk, 1); + return 1; + } + chunk->data.heap.len = _nbytes_read; + php_cli_server_buffer_append(&sender->buffer, chunk); + *nbytes_read = _nbytes_read; + return 0; +} /* }}} */ + +#if HAVE_UNISTD_H +static int php_cli_is_output_tty() /* {{{ */ +{ + if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) { + php_cli_output_is_tty = isatty(STDOUT_FILENO); + } + return php_cli_output_is_tty; +} /* }}} */ +#endif + +static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message TSRMLS_DC) /* {{{ */ +{ + int color = 0, effective_status = status; + char *basic_buf, *message_buf = "", *error_buf = ""; + zend_bool append_error_message = 0; + + if (PG(last_error_message)) { + switch (PG(last_error_type)) { + case E_ERROR: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + case E_PARSE: + if (status == 200) { + /* the status code isn't changed by a fatal error, so fake it */ + effective_status = 500; + } + + append_error_message = 1; + break; + } + } + +#if HAVE_UNISTD_H + if (CLI_SERVER_G(color) && php_cli_is_output_tty() == OUTPUT_IS_TTY) { + if (effective_status >= 500) { + /* server error: red */ + color = 1; + } else if (effective_status >= 400) { + /* client error: yellow */ + color = 3; + } else if (effective_status >= 200) { + /* success: green */ + color = 2; + } + } +#endif + + /* basic */ + spprintf(&basic_buf, 0, "%s [%d]: %s", client->addr_str, status, client->request.request_uri); + if (!basic_buf) { + return; + } + + /* message */ + if (message) { + spprintf(&message_buf, 0, " - %s", message); + if (!message_buf) { + efree(basic_buf); + return; + } + } + + /* error */ + if (append_error_message) { + spprintf(&error_buf, 0, " - %s in %s on line %d", PG(last_error_message), PG(last_error_file), PG(last_error_lineno)); + if (!error_buf) { + efree(basic_buf); + if (message) { + efree(message_buf); + } + return; + } + } + + if (color) { + php_cli_server_logf("\x1b[3%dm%s%s%s\x1b[0m" TSRMLS_CC, color, basic_buf, message_buf, error_buf); + } else { + php_cli_server_logf("%s%s%s" TSRMLS_CC, basic_buf, message_buf, error_buf); + } + + efree(basic_buf); + if (message) { + efree(message_buf); + } + if (append_error_message) { + efree(error_buf); + } +} /* }}} */ + +static void php_cli_server_logf(const char *format TSRMLS_DC, ...) /* {{{ */ +{ + char *buf = NULL; + va_list ap; +#ifdef ZTS + va_start(ap, tsrm_ls); +#else + va_start(ap, format); +#endif + vspprintf(&buf, 0, format, ap); + va_end(ap); + + if (!buf) { + return; + } + + if (sapi_module.log_message) { + sapi_module.log_message(buf TSRMLS_CC); + } + + efree(buf); +} /* }}} */ + +static int php_network_listen_socket(const char *host, int *port, int socktype, int *af, socklen_t *socklen, char **errstr TSRMLS_DC) /* {{{ */ +{ + int retval = SOCK_ERR; + int err = 0; + struct sockaddr *sa = NULL, **p, **sal; + + int num_addrs = php_network_getaddresses(host, socktype, &sal, errstr TSRMLS_CC); + if (num_addrs == 0) { + return -1; + } + for (p = sal; *p; p++) { + if (sa) { + pefree(sa, 1); + sa = NULL; + } + + retval = socket((*p)->sa_family, socktype, 0); + if (retval == SOCK_ERR) { + continue; + } + + switch ((*p)->sa_family) { +#if HAVE_GETADDRINFO && HAVE_IPV6 + case AF_INET6: + sa = pemalloc(sizeof(struct sockaddr_in6), 1); + if (!sa) { + closesocket(retval); + retval = SOCK_ERR; + *errstr = NULL; + goto out; + } + *(struct sockaddr_in6 *)sa = *(struct sockaddr_in6 *)*p; + ((struct sockaddr_in6 *)sa)->sin6_port = htons(*port); + *socklen = sizeof(struct sockaddr_in6); + break; +#endif + case AF_INET: + sa = pemalloc(sizeof(struct sockaddr_in), 1); + if (!sa) { + closesocket(retval); + retval = SOCK_ERR; + *errstr = NULL; + goto out; + } + *(struct sockaddr_in *)sa = *(struct sockaddr_in *)*p; + ((struct sockaddr_in *)sa)->sin_port = htons(*port); + *socklen = sizeof(struct sockaddr_in); + break; + default: + /* Unknown family */ + *socklen = 0; + closesocket(retval); + continue; + } + +#ifdef SO_REUSEADDR + { + int val = 1; + setsockopt(retval, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val)); + } +#endif + + if (bind(retval, sa, *socklen) == SOCK_CONN_ERR) { + err = php_socket_errno(); + if (err == SOCK_EINVAL || err == SOCK_EADDRINUSE) { + goto out; + } + closesocket(retval); + retval = SOCK_ERR; + continue; + } + err = 0; + + *af = sa->sa_family; + if (*port == 0) { + if (getsockname(retval, sa, socklen)) { + err = php_socket_errno(); + goto out; + } + switch (sa->sa_family) { +#if HAVE_GETADDRINFO && HAVE_IPV6 + case AF_INET6: + *port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port); + break; +#endif + case AF_INET: + *port = ntohs(((struct sockaddr_in *)sa)->sin_port); + break; + } + } + + break; + } + + if (retval == SOCK_ERR) { + goto out; + } + + if (listen(retval, SOMAXCONN)) { + err = php_socket_errno(); + goto out; + } + +out: + if (sa) { + pefree(sa, 1); + } + if (sal) { + php_network_freeaddresses(sal); + } + if (err) { + if (retval >= 0) { + closesocket(retval); + } + if (errstr) { + *errstr = php_socket_strerror(err, NULL, 0); + } + return SOCK_ERR; + } + return retval; +} /* }}} */ + +static int php_cli_server_request_ctor(php_cli_server_request *req) /* {{{ */ +{ + req->protocol_version = 0; + req->request_uri = NULL; + req->request_uri_len = 0; + req->vpath = NULL; + req->vpath_len = 0; + req->path_translated = NULL; + req->path_translated_len = 0; + req->path_info = NULL; + req->path_info_len = 0; + req->query_string = NULL; + req->query_string_len = 0; + zend_hash_init(&req->headers, 0, NULL, (void(*)(void*))char_ptr_dtor_p, 1); + req->content = NULL; + req->content_len = 0; + req->ext = NULL; + req->ext_len = 0; + return SUCCESS; +} /* }}} */ + +static void php_cli_server_request_dtor(php_cli_server_request *req) /* {{{ */ +{ + if (req->request_uri) { + pefree(req->request_uri, 1); + } + if (req->vpath) { + pefree(req->vpath, 1); + } + if (req->path_translated) { + pefree(req->path_translated, 1); + } + if (req->path_info) { + pefree(req->path_info, 1); + } + if (req->query_string) { + pefree(req->query_string, 1); + } + zend_hash_destroy(&req->headers); + if (req->content) { + pefree(req->content, 1); + } +} /* }}} */ + +static void php_cli_server_request_translate_vpath(php_cli_server_request *request, const char *document_root, size_t document_root_len) /* {{{ */ +{ + struct stat sb; + static const char *index_files[] = { "index.php", "index.html", NULL }; + char *buf = safe_pemalloc(1, request->vpath_len, 1 + document_root_len + 1 + sizeof("index.html"), 1); + char *p = buf, *prev_path = NULL, *q, *vpath; + size_t prev_path_len; + int is_static_file = 0; + + if (!buf) { + return; + } + + memmove(p, document_root, document_root_len); + p += document_root_len; + vpath = p; + if (request->vpath_len > 0 && request->vpath[0] != '/') { + *p++ = DEFAULT_SLASH; + } + q = request->vpath + request->vpath_len; + while (q > request->vpath) { + if (*q-- == '.') { + is_static_file = 1; + break; + } + } + memmove(p, request->vpath, request->vpath_len); +#ifdef PHP_WIN32 + q = p + request->vpath_len; + do { + if (*q == '/') { + *q = '\\'; + } + } while (q-- > p); +#endif + p += request->vpath_len; + *p = '\0'; + q = p; + while (q > buf) { + if (!stat(buf, &sb)) { + if (sb.st_mode & S_IFDIR) { + const char **file = index_files; + if (q[-1] != DEFAULT_SLASH) { + *q++ = DEFAULT_SLASH; + } + while (*file) { + size_t l = strlen(*file); + memmove(q, *file, l + 1); + if (!stat(buf, &sb) && (sb.st_mode & S_IFREG)) { + q += l; + break; + } + file++; + } + if (!*file || is_static_file) { + if (prev_path) { + pefree(prev_path, 1); + } + pefree(buf, 1); + return; + } + } + break; /* regular file */ + } + if (prev_path) { + pefree(prev_path, 1); + *q = DEFAULT_SLASH; + } + while (q > buf && *(--q) != DEFAULT_SLASH); + prev_path_len = p - q; + prev_path = pestrndup(q, prev_path_len, 1); + *q = '\0'; + } + if (prev_path) { + request->path_info_len = prev_path_len; +#ifdef PHP_WIN32 + while (prev_path_len--) { + if (prev_path[prev_path_len] == '\\') { + prev_path[prev_path_len] = '/'; + } + } +#endif + request->path_info = prev_path; + pefree(request->vpath, 1); + request->vpath = pestrndup(vpath, q - vpath, 1); + request->vpath_len = q - vpath; + request->path_translated = buf; + request->path_translated_len = q - buf; + } else { + pefree(request->vpath, 1); + request->vpath = pestrndup(vpath, q - vpath, 1); + request->vpath_len = q - vpath; + request->path_translated = buf; + request->path_translated_len = q - buf; + } +#ifdef PHP_WIN32 + { + uint i = 0; + for (;i<request->vpath_len;i++) { + if (request->vpath[i] == '\\') { + request->vpath[i] = '/'; + } + } + } +#endif + request->sb = sb; +} /* }}} */ + +static void normalize_vpath(char **retval, size_t *retval_len, const char *vpath, size_t vpath_len, int persistent) /* {{{ */ +{ + char *decoded_vpath = NULL; + char *decoded_vpath_end; + char *p; + + *retval = NULL; + + decoded_vpath = pestrndup(vpath, vpath_len, persistent); + if (!decoded_vpath) { + return; + } + + decoded_vpath_end = decoded_vpath + php_url_decode(decoded_vpath, vpath_len); + + p = decoded_vpath; + + if (p < decoded_vpath_end && *p == '/') { + char *n = p; + while (n < decoded_vpath_end && *n == '/') n++; + memmove(++p, n, decoded_vpath_end - n); + decoded_vpath_end -= n - p; + } + + while (p < decoded_vpath_end) { + char *n = p; + while (n < decoded_vpath_end && *n != '/') n++; + if (n - p == 2 && p[0] == '.' && p[1] == '.') { + if (p > decoded_vpath) { + --p; + for (;;) { + if (p == decoded_vpath) { + if (*p == '/') { + p++; + } + break; + } + if (*(--p) == '/') { + p++; + break; + } + } + } + while (n < decoded_vpath_end && *n == '/') n++; + memmove(p, n, decoded_vpath_end - n); + decoded_vpath_end -= n - p; + } else if (n - p == 1 && p[0] == '.') { + while (n < decoded_vpath_end && *n == '/') n++; + memmove(p, n, decoded_vpath_end - n); + decoded_vpath_end -= n - p; + } else { + if (n < decoded_vpath_end) { + char *nn = n; + while (nn < decoded_vpath_end && *nn == '/') nn++; + p = n + 1; + memmove(p, nn, decoded_vpath_end - nn); + decoded_vpath_end -= nn - p; + } else { + p = n; + } + } + } + + *decoded_vpath_end = '\0'; + *retval = decoded_vpath; + *retval_len = decoded_vpath_end - decoded_vpath; +} /* }}} */ + +/* {{{ php_cli_server_client_read_request */ +static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser) +{ + return 0; +} + +static int php_cli_server_client_read_request_on_path(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + { + char *vpath; + size_t vpath_len; + normalize_vpath(&vpath, &vpath_len, at, length, 1); + client->request.vpath = vpath; + client->request.vpath_len = vpath_len; + } + return 0; +} + +static int php_cli_server_client_read_request_on_query_string(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + client->request.query_string = pestrndup(at, length, 1); + client->request.query_string_len = length; + return 0; +} + +static int php_cli_server_client_read_request_on_url(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + client->request.request_method = parser->method; + client->request.request_uri = pestrndup(at, length, 1); + client->request.request_uri_len = length; + return 0; +} + +static int php_cli_server_client_read_request_on_fragment(php_http_parser *parser, const char *at, size_t length) +{ + return 0; +} + +static int php_cli_server_client_read_request_on_header_field(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + if (client->current_header_name_allocated) { + pefree(client->current_header_name, 1); + client->current_header_name_allocated = 0; + } + client->current_header_name = (char *)at; + client->current_header_name_len = length; + return 0; +} + +static int php_cli_server_client_read_request_on_header_value(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + char *value = pestrndup(at, length, 1); + if (!value) { + return 1; + } + { + char *header_name = client->current_header_name; + size_t header_name_len = client->current_header_name_len; + char c = header_name[header_name_len]; + header_name[header_name_len] = '\0'; + zend_hash_add(&client->request.headers, header_name, header_name_len + 1, &value, sizeof(char *), NULL); + header_name[header_name_len] = c; + } + + if (client->current_header_name_allocated) { + pefree(client->current_header_name, 1); + client->current_header_name_allocated = 0; + } + return 0; +} + +static int php_cli_server_client_read_request_on_headers_complete(php_http_parser *parser) +{ + php_cli_server_client *client = parser->data; + if (client->current_header_name_allocated) { + pefree(client->current_header_name, 1); + client->current_header_name_allocated = 0; + } + client->current_header_name = NULL; + return 0; +} + +static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length) +{ + php_cli_server_client *client = parser->data; + if (!client->request.content) { + client->request.content = pemalloc(parser->content_length, 1); + if (!client->request.content) { + return -1; + } + client->request.content_len = 0; + } + memmove(client->request.content + client->request.content_len, at, length); + client->request.content_len += length; + return 0; +} + +static int php_cli_server_client_read_request_on_message_complete(php_http_parser *parser) +{ + php_cli_server_client *client = parser->data; + client->request.protocol_version = parser->http_major * 100 + parser->http_minor; + php_cli_server_request_translate_vpath(&client->request, client->server->document_root, client->server->document_root_len); + { + const char *vpath = client->request.vpath, *end = vpath + client->request.vpath_len, *p = end; + client->request.ext = end; + client->request.ext_len = 0; + while (p > vpath) { + --p; + if (*p == '.') { + ++p; + client->request.ext = p; + client->request.ext_len = end - p; + break; + } + } + } + client->request_read = 1; + return 0; +} + +static int php_cli_server_client_read_request(php_cli_server_client *client, char **errstr TSRMLS_DC) +{ + char buf[16384]; + static const php_http_parser_settings settings = { + php_cli_server_client_read_request_on_message_begin, + php_cli_server_client_read_request_on_path, + php_cli_server_client_read_request_on_query_string, + php_cli_server_client_read_request_on_url, + php_cli_server_client_read_request_on_fragment, + php_cli_server_client_read_request_on_header_field, + php_cli_server_client_read_request_on_header_value, + php_cli_server_client_read_request_on_headers_complete, + php_cli_server_client_read_request_on_body, + php_cli_server_client_read_request_on_message_complete + }; + size_t nbytes_consumed; + int nbytes_read; + if (client->request_read) { + return 1; + } + nbytes_read = recv(client->sock, buf, sizeof(buf) - 1, 0); + if (nbytes_read < 0) { + int err = php_socket_errno(); + if (err == SOCK_EAGAIN) { + return 0; + } + *errstr = php_socket_strerror(err, NULL, 0); + return -1; + } else if (nbytes_read == 0) { + *errstr = estrdup("Unexpected EOF"); + return -1; + } + client->parser.data = client; + nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read); + if (nbytes_consumed != nbytes_read) { + if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) { + *errstr = estrdup("Unsupported SSL request"); + } else { + *errstr = estrdup("Malformed HTTP request"); + } + return -1; + } + if (client->current_header_name) { + char *header_name = safe_pemalloc(client->current_header_name_len, 1, 1, 1); + if (!header_name) { + return -1; + } + memmove(header_name, client->current_header_name, client->current_header_name_len); + client->current_header_name = header_name; + client->current_header_name_allocated = 1; + } + return client->request_read ? 1: 0; +} +/* }}} */ + +static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len) /* {{{ */ +{ + struct timeval tv = { 10, 0 }; + ssize_t nbytes_left = str_len; + do { + ssize_t nbytes_sent = send(client->sock, str + str_len - nbytes_left, nbytes_left, 0); + if (nbytes_sent < 0) { + int err = php_socket_errno(); + if (err == SOCK_EAGAIN) { + int nfds = php_pollfd_for(client->sock, POLLOUT, &tv); + if (nfds > 0) { + continue; + } else if (nfds < 0) { + /* error */ + php_handle_aborted_connection(); + return nbytes_left; + } else { + /* timeout */ + php_handle_aborted_connection(); + return nbytes_left; + } + } else { + php_handle_aborted_connection(); + return nbytes_left; + } + } + nbytes_left -= nbytes_sent; + } while (nbytes_left > 0); + + return str_len; +} /* }}} */ + +static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */ +{ + char **val; + + request_info->request_method = php_http_method_str(client->request.request_method); + request_info->proto_num = client->request.protocol_version; + request_info->request_uri = client->request.request_uri; + request_info->path_translated = client->request.path_translated; + request_info->query_string = client->request.query_string; + request_info->post_data = client->request.content; + request_info->content_length = request_info->post_data_length = client->request.content_len; + request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL; + if (SUCCESS == zend_hash_find(&client->request.headers, "Content-Type", sizeof("Content-Type"), (void**)&val)) { + request_info->content_type = *val; + } +} /* }}} */ + +static void destroy_request_info(sapi_request_info *request_info) /* {{{ */ +{ +} /* }}} */ + +static int php_cli_server_client_ctor(php_cli_server_client *client, php_cli_server *server, int client_sock, struct sockaddr *addr, socklen_t addr_len TSRMLS_DC) /* {{{ */ +{ + client->server = server; + client->sock = client_sock; + client->addr = addr; + client->addr_len = addr_len; + { + char *addr_str = 0; + long addr_str_len = 0; + php_network_populate_name_from_sockaddr(addr, addr_len, &addr_str, &addr_str_len, NULL, 0 TSRMLS_CC); + client->addr_str = pestrndup(addr_str, addr_str_len, 1); + client->addr_str_len = addr_str_len; + efree(addr_str); + } + php_http_parser_init(&client->parser, PHP_HTTP_REQUEST); + client->request_read = 0; + client->current_header_name = NULL; + client->current_header_name_len = 0; + client->current_header_name_allocated = 0; + client->post_read_offset = 0; + if (FAILURE == php_cli_server_request_ctor(&client->request)) { + return FAILURE; + } + client->content_sender_initialized = 0; + client->file_fd = -1; + return SUCCESS; +} /* }}} */ + +static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */ +{ + php_cli_server_request_dtor(&client->request); + if (client->file_fd >= 0) { + close(client->file_fd); + client->file_fd = -1; + } + pefree(client->addr, 1); + pefree(client->addr_str, 1); + if (client->content_sender_initialized) { + php_cli_server_content_sender_dtor(&client->content_sender); + } +} /* }}} */ + +static void php_cli_server_close_connection(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ +#ifdef DEBUG + php_cli_server_logf("%s Closing" TSRMLS_CC, client->addr_str); +#endif + zend_hash_index_del(&server->clients, client->sock); +} /* }}} */ + +static int php_cli_server_send_error_page(php_cli_server *server, php_cli_server_client *client, int status TSRMLS_DC) /* {{{ */ +{ + char *escaped_request_uri = NULL; + size_t escaped_request_uri_len; + const char *status_string = get_status_string(status); + const char *content_template = get_template_string(status); + char *errstr = get_last_error(); + assert(status_string && content_template); + + php_cli_server_content_sender_ctor(&client->content_sender); + client->content_sender_initialized = 1; + + escaped_request_uri = php_escape_html_entities_ex((unsigned char *)client->request.request_uri, client->request.request_uri_len, &escaped_request_uri_len, 0, ENT_QUOTES, NULL, 0 TSRMLS_CC); + + { + static const char prologue_template[] = "<!doctype html><html><head><title>%d %s</title>"; + php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(prologue_template) + 3 + strlen(status_string) + 1); + if (!chunk) { + goto fail; + } + snprintf(chunk->data.heap.p, chunk->data.heap.len, prologue_template, status, status_string, escaped_request_uri); + chunk->data.heap.len = strlen(chunk->data.heap.p); + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + { + php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(php_cli_server_css, sizeof(php_cli_server_css) - 1); + if (!chunk) { + goto fail; + } + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + { + static const char template[] = "</head><body>"; + php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(template, sizeof(template) - 1); + if (!chunk) { + goto fail; + } + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + { + php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + escaped_request_uri_len + 3 + strlen(status_string) + 1); + if (!chunk) { + goto fail; + } + snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, escaped_request_uri); + chunk->data.heap.len = strlen(chunk->data.heap.p); + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + { + static const char epilogue_template[] = "</body></html>"; + php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(epilogue_template, sizeof(epilogue_template) - 1); + if (!chunk) { + goto fail; + } + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + + { + php_cli_server_chunk *chunk; + smart_str buffer = { 0 }; + append_http_status_line(&buffer, client->request.protocol_version, status, 1); + if (!buffer.c) { + /* out of memory */ + goto fail; + } + append_essential_headers(&buffer, client, 1); + smart_str_appends_ex(&buffer, "Content-Type: text/html; charset=UTF-8\r\n", 1); + smart_str_appends_ex(&buffer, "Content-Length: ", 1); + smart_str_append_generic_ex(&buffer, php_cli_server_buffer_size(&client->content_sender.buffer), 1, size_t, _unsigned); + smart_str_appendl_ex(&buffer, "\r\n", 2, 1); + smart_str_appendl_ex(&buffer, "\r\n", 2, 1); + + chunk = php_cli_server_chunk_heap_new(buffer.c, buffer.c, buffer.len); + if (!chunk) { + smart_str_free_ex(&buffer, 1); + goto fail; + } + php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk); + } + + php_cli_server_log_response(client, status, errstr ? errstr : "?" TSRMLS_CC); + php_cli_server_poller_add(&server->poller, POLLOUT, client->sock); + if (errstr) { + pefree(errstr, 1); + } + efree(escaped_request_uri); + return SUCCESS; + +fail: + if (errstr) { + pefree(errstr, 1); + } + efree(escaped_request_uri); + return FAILURE; +} /* }}} */ + +static int php_cli_server_dispatch_script(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + if (strlen(client->request.path_translated) != client->request.path_translated_len) { + /* can't handle paths that contain nul bytes */ + return php_cli_server_send_error_page(server, client, 400 TSRMLS_CC); + } + { + zend_file_handle zfd; + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = SG(request_info).path_translated; + zfd.handle.fp = NULL; + zfd.free_filename = 0; + zfd.opened_path = NULL; + zend_try { + php_execute_script(&zfd TSRMLS_CC); + } zend_end_try(); + } + + php_cli_server_log_response(client, SG(sapi_headers).http_response_code, NULL TSRMLS_CC); + return SUCCESS; +} /* }}} */ + +static int php_cli_server_begin_send_static(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + int fd; + int status = 200; + + if (client->request.path_translated && strlen(client->request.path_translated) != client->request.path_translated_len) { + /* can't handle paths that contain nul bytes */ + return php_cli_server_send_error_page(server, client, 400 TSRMLS_CC); + } + + fd = client->request.path_translated ? open(client->request.path_translated, O_RDONLY): -1; + if (fd < 0) { + return php_cli_server_send_error_page(server, client, 404 TSRMLS_CC); + } + + php_cli_server_content_sender_ctor(&client->content_sender); + client->content_sender_initialized = 1; + client->file_fd = fd; + + { + php_cli_server_chunk *chunk; + smart_str buffer = { 0 }; + const char *mime_type = get_mime_type(client->request.ext, client->request.ext_len); + if (!mime_type) { + mime_type = "application/octet-stream"; + } + + append_http_status_line(&buffer, client->request.protocol_version, status, 1); + if (!buffer.c) { + /* out of memory */ + php_cli_server_log_response(client, 500, NULL TSRMLS_CC); + return FAILURE; + } + append_essential_headers(&buffer, client, 1); + smart_str_appendl_ex(&buffer, "Content-Type: ", sizeof("Content-Type: ") - 1, 1); + smart_str_appends_ex(&buffer, mime_type, 1); + if (strncmp(mime_type, "text/", 5) == 0) { + smart_str_appends_ex(&buffer, "; charset=UTF-8", 1); + } + smart_str_appendl_ex(&buffer, "\r\n", 2, 1); + smart_str_appends_ex(&buffer, "Content-Length: ", 1); + smart_str_append_generic_ex(&buffer, client->request.sb.st_size, 1, size_t, _unsigned); + smart_str_appendl_ex(&buffer, "\r\n", 2, 1); + smart_str_appendl_ex(&buffer, "\r\n", 2, 1); + chunk = php_cli_server_chunk_heap_new(buffer.c, buffer.c, buffer.len); + if (!chunk) { + smart_str_free_ex(&buffer, 1); + php_cli_server_log_response(client, 500, NULL TSRMLS_CC); + return FAILURE; + } + php_cli_server_buffer_append(&client->content_sender.buffer, chunk); + } + php_cli_server_log_response(client, 200, NULL TSRMLS_CC); + php_cli_server_poller_add(&server->poller, POLLOUT, client->sock); + return SUCCESS; +} +/* }}} */ + +static int php_cli_server_request_startup(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) { /* {{{ */ + char **auth; + php_cli_server_client_populate_request_info(client, &SG(request_info)); + if (SUCCESS == zend_hash_find(&client->request.headers, "Authorization", sizeof("Authorization"), (void**)&auth)) { + php_handle_auth_data(*auth TSRMLS_CC); + } + SG(sapi_headers).http_response_code = 200; + if (FAILURE == php_request_startup(TSRMLS_C)) { + /* should never be happen */ + destroy_request_info(&SG(request_info)); + return FAILURE; + } + PG(during_request_startup) = 0; + + return SUCCESS; +} +/* }}} */ + +static int php_cli_server_request_shutdown(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) { /* {{{ */ + php_request_shutdown(0); + php_cli_server_close_connection(server, client TSRMLS_CC); + destroy_request_info(&SG(request_info)); + SG(server_context) = NULL; + SG(rfc1867_uploaded_files) = NULL; + return SUCCESS; +} +/* }}} */ + +static int php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + int decline = 0; + if (!php_handle_special_queries(TSRMLS_C)) { + zend_file_handle zfd; + char *old_cwd; + + ALLOCA_FLAG(use_heap) + old_cwd = do_alloca(MAXPATHLEN, use_heap); + old_cwd[0] = '\0'; + php_ignore_value(VCWD_GETCWD(old_cwd, MAXPATHLEN - 1)); + + zfd.type = ZEND_HANDLE_FILENAME; + zfd.filename = server->router; + zfd.handle.fp = NULL; + zfd.free_filename = 0; + zfd.opened_path = NULL; + + zend_try { + zval *retval = NULL; + if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE TSRMLS_CC, &retval, 1, &zfd)) { + if (retval) { + decline = Z_TYPE_P(retval) == IS_BOOL && !Z_LVAL_P(retval); + zval_ptr_dtor(&retval); + } + } else { + decline = 1; + } + } zend_end_try(); + + if (old_cwd[0] != '\0') { + php_ignore_value(VCWD_CHDIR(old_cwd)); + } + + free_alloca(old_cwd, use_heap); + } + + return decline; +} +/* }}} */ + +static int php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + int is_static_file = 0; + + SG(server_context) = client; + if (client->request.ext_len != 3 || memcmp(client->request.ext, "php", 3) || !client->request.path_translated) { + is_static_file = 1; + } + + if (server->router || !is_static_file) { + if (FAILURE == php_cli_server_request_startup(server, client TSRMLS_CC)) { + SG(server_context) = NULL; + php_cli_server_close_connection(server, client TSRMLS_CC); + destroy_request_info(&SG(request_info)); + return SUCCESS; + } + } + + if (server->router) { + if (!php_cli_server_dispatch_router(server, client TSRMLS_CC)) { + php_cli_server_request_shutdown(server, client TSRMLS_CC); + return SUCCESS; + } + } + + if (!is_static_file) { + if (SUCCESS == php_cli_server_dispatch_script(server, client TSRMLS_CC) + || SUCCESS != php_cli_server_send_error_page(server, client, 500 TSRMLS_CC)) { + php_cli_server_request_shutdown(server, client TSRMLS_CC); + return SUCCESS; + } + } else { + if (server->router) { + static int (*send_header_func)(sapi_headers_struct * TSRMLS_DC); + send_header_func = sapi_module.send_headers; + /* do not generate default content type header */ + SG(sapi_headers).send_default_content_type = 0; + /* we don't want headers to be sent */ + sapi_module.send_headers = sapi_cli_server_discard_headers; + php_request_shutdown(0); + sapi_module.send_headers = send_header_func; + SG(sapi_headers).send_default_content_type = 1; + SG(rfc1867_uploaded_files) = NULL; + } + if (SUCCESS != php_cli_server_begin_send_static(server, client TSRMLS_CC)) { + php_cli_server_close_connection(server, client TSRMLS_CC); + } + SG(server_context) = NULL; + return SUCCESS; + } + + SG(server_context) = NULL; + destroy_request_info(&SG(request_info)); + return SUCCESS; +} +/* }}} */ + +static void php_cli_server_dtor(php_cli_server *server TSRMLS_DC) /* {{{ */ +{ + zend_hash_destroy(&server->clients); + if (server->server_sock >= 0) { + closesocket(server->server_sock); + } + if (server->host) { + pefree(server->host, 1); + } + if (server->document_root) { + pefree(server->document_root, 1); + } + if (server->router) { + pefree(server->router, 1); + } +} /* }}} */ + +static void php_cli_server_client_dtor_wrapper(php_cli_server_client **p) /* {{{ */ +{ + closesocket((*p)->sock); + php_cli_server_poller_remove(&(*p)->server->poller, POLLIN | POLLOUT, (*p)->sock); + php_cli_server_client_dtor(*p); + pefree(*p, 1); +} /* }}} */ + +static int php_cli_server_ctor(php_cli_server *server, const char *addr, const char *document_root, const char *router TSRMLS_DC) /* {{{ */ +{ + int retval = SUCCESS; + char *host = NULL; + char *errstr = NULL; + char *_document_root = NULL; + char *_router = NULL; + int err = 0; + int port = 3000; + php_socket_t server_sock = SOCK_ERR; + char *p = NULL; + + if (addr[0] == '[') { + host = pestrdup(addr + 1, 1); + if (!host) { + return FAILURE; + } + p = strchr(host, ']'); + if (p) { + *p++ = '\0'; + if (*p == ':') { + port = strtol(p + 1, &p, 10); + if (port <= 0) { + p = NULL; + } + } else if (*p != '\0') { + p = NULL; + } + } + } else { + host = pestrdup(addr, 1); + if (!host) { + return FAILURE; + } + p = strchr(host, ':'); + if (p) { + *p++ = '\0'; + port = strtol(p, &p, 10); + if (port <= 0) { + p = NULL; + } + } + } + if (!p) { + fprintf(stderr, "Invalid address: %s\n", addr); + retval = FAILURE; + goto out; + } + + server_sock = php_network_listen_socket(host, &port, SOCK_STREAM, &server->address_family, &server->socklen, &errstr TSRMLS_CC); + if (server_sock == SOCK_ERR) { + php_cli_server_logf("Failed to listen on %s:%d (reason: %s)" TSRMLS_CC, host, port, errstr ? errstr: "?"); + efree(errstr); + retval = FAILURE; + goto out; + } + server->server_sock = server_sock; + + err = php_cli_server_poller_ctor(&server->poller); + if (SUCCESS != err) { + goto out; + } + + php_cli_server_poller_add(&server->poller, POLLIN, server_sock); + + server->host = host; + server->port = port; + + zend_hash_init(&server->clients, 0, NULL, (void(*)(void*))php_cli_server_client_dtor_wrapper, 1); + + { + size_t document_root_len = strlen(document_root); + _document_root = pestrndup(document_root, document_root_len, 1); + if (!_document_root) { + retval = FAILURE; + goto out; + } + server->document_root = _document_root; + server->document_root_len = document_root_len; + } + + if (router) { + size_t router_len = strlen(router); + _router = pestrndup(router, router_len, 1); + if (!_router) { + retval = FAILURE; + goto out; + } + server->router = _router; + server->router_len = router_len; + } else { + server->router = NULL; + server->router_len = 0; + } + + server->is_running = 1; +out: + if (retval != SUCCESS) { + if (host) { + pefree(host, 1); + } + if (_document_root) { + pefree(_document_root, 1); + } + if (_router) { + pefree(_router, 1); + } + if (server_sock >= -1) { + closesocket(server_sock); + } + } + return retval; +} /* }}} */ + +static int php_cli_server_recv_event_read_request(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + char *errstr = NULL; + int status = php_cli_server_client_read_request(client, &errstr TSRMLS_CC); + if (status < 0) { + php_cli_server_logf("%s Invalid request (%s)" TSRMLS_CC, client->addr_str, errstr); + efree(errstr); + php_cli_server_close_connection(server, client TSRMLS_CC); + return FAILURE; + } else if (status == 1 && client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) { + return php_cli_server_send_error_page(server, client, 501 TSRMLS_CC); + } else if (status == 1) { + php_cli_server_poller_remove(&server->poller, POLLIN, client->sock); + php_cli_server_dispatch(server, client TSRMLS_CC); + } else { + php_cli_server_poller_add(&server->poller, POLLIN, client->sock); + } + + return SUCCESS; +} /* }}} */ + +static int php_cli_server_send_event(php_cli_server *server, php_cli_server_client *client TSRMLS_DC) /* {{{ */ +{ + if (client->content_sender_initialized) { + if (client->file_fd >= 0 && !client->content_sender.buffer.first) { + size_t nbytes_read; + if (php_cli_server_content_sender_pull(&client->content_sender, client->file_fd, &nbytes_read)) { + php_cli_server_close_connection(server, client TSRMLS_CC); + return FAILURE; + } + if (nbytes_read == 0) { + close(client->file_fd); + client->file_fd = -1; + } + } + { + size_t nbytes_sent; + int err = php_cli_server_content_sender_send(&client->content_sender, client->sock, &nbytes_sent); + if (err && err != SOCK_EAGAIN) { + php_cli_server_close_connection(server, client TSRMLS_CC); + return FAILURE; + } + } + if (!client->content_sender.buffer.first && client->file_fd < 0) { + php_cli_server_close_connection(server, client TSRMLS_CC); + } + } + return SUCCESS; +} +/* }}} */ + +typedef struct php_cli_server_do_event_for_each_fd_callback_params { +#ifdef ZTS + void ***tsrm_ls; +#endif + php_cli_server *server; + int(*rhandler)(php_cli_server*, php_cli_server_client* TSRMLS_DC); + int(*whandler)(php_cli_server*, php_cli_server_client* TSRMLS_DC); +} php_cli_server_do_event_for_each_fd_callback_params; + +static int php_cli_server_do_event_for_each_fd_callback(void *_params, int fd, int event) /* {{{ */ +{ + php_cli_server_do_event_for_each_fd_callback_params *params = _params; +#ifdef ZTS + void ***tsrm_ls = params->tsrm_ls; +#endif + php_cli_server *server = params->server; + if (server->server_sock == fd) { + php_cli_server_client *client = NULL; + php_socket_t client_sock; + socklen_t socklen = server->socklen; + struct sockaddr *sa = pemalloc(server->socklen, 1); + if (!sa) { + return FAILURE; + } + client_sock = accept(server->server_sock, sa, &socklen); + if (client_sock < 0) { + char *errstr; + errstr = php_socket_strerror(php_socket_errno(), NULL, 0); + php_cli_server_logf("Failed to accept a client (reason: %s)" TSRMLS_CC, errstr); + efree(errstr); + pefree(sa, 1); + return SUCCESS; + } + if (SUCCESS != php_set_sock_blocking(client_sock, 0 TSRMLS_CC)) { + pefree(sa, 1); + closesocket(client_sock); + return SUCCESS; + } + if (!(client = pemalloc(sizeof(php_cli_server_client), 1)) || FAILURE == php_cli_server_client_ctor(client, server, client_sock, sa, socklen TSRMLS_CC)) { + php_cli_server_logf("Failed to create a new request object" TSRMLS_CC); + pefree(sa, 1); + closesocket(client_sock); + return SUCCESS; + } +#ifdef DEBUG + php_cli_server_logf("%s Accepted" TSRMLS_CC, client->addr_str); +#endif + zend_hash_index_update(&server->clients, client_sock, &client, sizeof(client), NULL); + php_cli_server_recv_event_read_request(server, client TSRMLS_CC); + } else { + php_cli_server_client **client; + if (SUCCESS == zend_hash_index_find(&server->clients, fd, (void **)&client)) { + if (event & POLLIN) { + params->rhandler(server, *client TSRMLS_CC); + } + if (event & POLLOUT) { + params->whandler(server, *client TSRMLS_CC); + } + } + } + return SUCCESS; +} /* }}} */ + +static void php_cli_server_do_event_for_each_fd(php_cli_server *server, int(*rhandler)(php_cli_server*, php_cli_server_client* TSRMLS_DC), int(*whandler)(php_cli_server*, php_cli_server_client* TSRMLS_DC) TSRMLS_DC) /* {{{ */ +{ + php_cli_server_do_event_for_each_fd_callback_params params = { +#ifdef ZTS + tsrm_ls, +#endif + server, + rhandler, + whandler + }; + + php_cli_server_poller_iter_on_active(&server->poller, ¶ms, php_cli_server_do_event_for_each_fd_callback); +} /* }}} */ + +static int php_cli_server_do_event_loop(php_cli_server *server TSRMLS_DC) /* {{{ */ +{ + int retval = SUCCESS; + while (server->is_running) { + static const struct timeval tv = { 1, 0 }; + int n = php_cli_server_poller_poll(&server->poller, &tv); + if (n > 0) { + php_cli_server_do_event_for_each_fd(server, + php_cli_server_recv_event_read_request, + php_cli_server_send_event TSRMLS_CC); + } else if (n == 0) { + /* do nothing */ + } else { + int err = php_socket_errno(); + if (err != SOCK_EINTR) { + char *errstr = php_socket_strerror(err, NULL, 0); + php_cli_server_logf("%s" TSRMLS_CC, errstr); + efree(errstr); + retval = FAILURE; + goto out; + } + } + } +out: + return retval; +} /* }}} */ + +static php_cli_server server; + +static void php_cli_server_sigint_handler(int sig) /* {{{ */ +{ + server.is_running = 0; +} +/* }}} */ + +int do_cli_server(int argc, char **argv TSRMLS_DC) /* {{{ */ +{ + char *php_optarg = NULL; + int php_optind = 1; + int c; + const char *server_bind_address = NULL; + extern const opt_struct OPTIONS[]; + const char *document_root = NULL; + const char *router = NULL; + char document_root_buf[MAXPATHLEN]; + + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) { + switch (c) { + case 'S': + server_bind_address = php_optarg; + break; + case 't': + document_root = php_optarg; + break; + } + } + + if (document_root) { + struct stat sb; + + if (stat(document_root, &sb)) { + fprintf(stderr, "Directory %s does not exist.\n", document_root); + return 1; + } + if (!S_ISDIR(sb.st_mode)) { + fprintf(stderr, "%s is not a directory.\n", document_root); + return 1; + } + if (VCWD_REALPATH(document_root, document_root_buf)) { + document_root = document_root_buf; + } + } else { + char *ret = NULL; + +#if HAVE_GETCWD + ret = VCWD_GETCWD(document_root_buf, MAXPATHLEN); +#elif HAVE_GETWD + ret = VCWD_GETWD(document_root_buf); +#endif + document_root = ret ? document_root_buf: "."; + } + + if (argc > php_optind) { + router = argv[php_optind]; + } + + if (FAILURE == php_cli_server_ctor(&server, server_bind_address, document_root, router TSRMLS_CC)) { + return 1; + } + sapi_module.phpinfo_as_text = 0; + + { + struct timeval tv; + struct tm tm; + char buf[52]; + gettimeofday(&tv, NULL); + php_localtime_r(&tv.tv_sec, &tm); + php_asctime_r(&tm, buf); + printf("PHP %s Development Server started at %s" + "Listening on http://%s\n" + "Document root is %s\n" + "Press Ctrl-C to quit.\n", + PHP_VERSION, buf, server_bind_address, document_root); + } + +#if defined(HAVE_SIGNAL_H) && defined(SIGINT) + signal(SIGINT, php_cli_server_sigint_handler); +#endif + php_cli_server_do_event_loop(&server TSRMLS_CC); + php_cli_server_dtor(&server TSRMLS_CC); + return 0; +} /* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/sapi/cli/php_cli_server.h b/sapi/cli/php_cli_server.h new file mode 100644 index 0000000..ed716f9 --- /dev/null +++ b/sapi/cli/php_cli_server.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Moriyoshi Koizumi <moriyoshi@php.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: php_cli.c 306938 2011-01-01 02:17:06Z felipe $ */ + +#ifndef PHP_CLI_SERVER_H +#define PHP_CLI_SERVER_H + +#include "SAPI.h" + +extern sapi_module_struct cli_server_sapi_module; +extern int do_cli_server(int argc, char **argv TSRMLS_DC); + +ZEND_BEGIN_MODULE_GLOBALS(cli_server) + short color; +ZEND_END_MODULE_GLOBALS(cli_server) + +#ifdef ZTS +#define CLI_SERVER_G(v) TSRMG(cli_server_globals_id, zend_cli_server_globals *, v) +#else +#define CLI_SERVER_G(v) (cli_server_globals.v) +#endif + +#endif /* PHP_CLI_SERVER_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/sapi/cli/php_http_parser.c b/sapi/cli/php_http_parser.c new file mode 100644 index 0000000..d3bc496 --- /dev/null +++ b/sapi/cli/php_http_parser.c @@ -0,0 +1,1608 @@ +/* Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include <assert.h> +#include <stddef.h> +#include "php_http_parser.h" + + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + + +#define CALLBACK2(FOR) \ +do { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser)) return (p - data); \ + } \ +} while (0) + + +#define MARK(FOR) \ +do { \ + FOR##_mark = p; \ +} while (0) + +#define CALLBACK_NOCLEAR(FOR) \ +do { \ + if (FOR##_mark) { \ + if (settings->on_##FOR) { \ + if (0 != settings->on_##FOR(parser, \ + FOR##_mark, \ + p - FOR##_mark)) \ + { \ + return (p - data); \ + } \ + } \ + } \ +} while (0) + +#ifdef PHP_WIN32 +# undef CALLBACK +#endif +#define CALLBACK(FOR) \ +do { \ + CALLBACK_NOCLEAR(FOR); \ + FOR##_mark = NULL; \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { "DELETE" + , "GET" + , "HEAD" + , "POST" + , "PUT" + , "PATCH" + , "CONNECT" + , "OPTIONS" + , "TRACE" + , "COPY" + , "LOCK" + , "MKCOL" + , "MOVE" + , "PROPFIND" + , "PROPPATCH" + , "UNLOCK" + , "REPORT" + , "MKACTIVITY" + , "CHECKOUT" + , "MERGE" + , "M-SEARCH" + , "NOTIFY" + , "SUBSCRIBE" + , "UNSUBSCRIBE" + , "NOTIMPLEMENTED" + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1*<any CHAR except CTLs or separators> + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + ' ', '!', '"', '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', '/', +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', '}', '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +static const uint8_t normal_url_char[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, 1, 1, 0, 1, 1, 1, 1, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1, 1, 1, 1, 1, 1, 1, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1, 1, 1, 1, 1, 1, 1, 1, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1, 1, 1, 1, 1, 1, 1, 0 }; + + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_first_http_major + , s_res_http_major + , s_res_first_http_minor + , s_res_http_minor + , s_res_first_status_code + , s_res_status_code + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_host + , s_req_port + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_first_http_major + , s_req_http_major + , s_req_first_http_minor + , s_req_http_minor + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_start + , s_header_value + + , s_header_almost_done + + , s_headers_almost_done + /* Important: 's_headers_almost_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + , s_chunk_size_start + , s_chunk_size + , s_chunk_size_almost_done + , s_chunk_parameters + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + }; + + +#define PARSING_HEADER(state) (state <= s_headers_almost_done && 0 == (parser->flags & F_TRAILING)) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_keep_alive + , h_matching_connection_close + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + }; + + +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 + , F_SKIPBODY = 1 << 5 + }; + + +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define TOKEN(c) tokens[(unsigned char)c] + + +#define start_state (parser->type == PHP_HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) if (cond) goto error +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +size_t php_http_parser_execute (php_http_parser *parser, + const php_http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + const char *p = data, *pe; + size_t to_read; + + enum state state = (enum state) parser->state; + enum header_states header_state = (enum header_states) parser->header_state; + uint32_t index = parser->index; + uint32_t nread = parser->nread; + + /* technically we could combine all of these (except for url_mark) into one + variable, saving stack space, but it seems more clear to have them + separated. */ + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *fragment_mark = 0; + const char *query_string_mark = 0; + const char *path_mark = 0; + const char *url_mark = 0; + + if (len == 0) { + if (state == s_body_identity_eof) { + CALLBACK2(message_complete); + } + return 0; + } + + if (state == s_header_field) + header_field_mark = data; + if (state == s_header_value) + header_value_mark = data; + if (state == s_req_fragment) + fragment_mark = data; + if (state == s_req_query_string) + query_string_mark = data; + if (state == s_req_path) + path_mark = data; + if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash + || state == s_req_schema_slash_slash || state == s_req_port + || state == s_req_query_string_start || state == s_req_query_string + || state == s_req_host + || state == s_req_fragment_start || state == s_req_fragment) + url_mark = data; + + for (p=data, pe=data+len; p != pe; p++) { + ch = *p; + + if (PARSING_HEADER(state)) { + ++nread; + /* Buffer overflow attack */ + if (nread > PHP_HTTP_MAX_HEADER_SIZE) goto error; + } + + switch (state) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (ch == 'H') + state = s_res_or_resp_H; + else { + parser->type = PHP_HTTP_REQUEST; + goto start_req_method_assign; + } + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = PHP_HTTP_RESPONSE; + state = s_res_HT; + } else { + if (ch != 'E') goto error; + parser->type = PHP_HTTP_REQUEST; + parser->method = PHP_HTTP_HEAD; + index = 2; + state = s_req_method; + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + switch (ch) { + case 'H': + state = s_res_H; + break; + + case CR: + case LF: + break; + + default: + goto error; + } + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + state = s_res_HT; + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + state = s_res_HTT; + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + state = s_res_HTTP; + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + state = s_res_first_http_major; + break; + + case s_res_first_http_major: + if (ch < '1' || ch > '9') goto error; + parser->http_major = ch - '0'; + state = s_res_http_major; + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + state = s_res_first_http_minor; + break; + } + + if (ch < '0' || ch > '9') goto error; + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) goto error; + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (ch < '0' || ch > '9') goto error; + parser->http_minor = ch - '0'; + state = s_res_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + state = s_res_first_status_code; + break; + } + + if (ch < '0' || ch > '9') goto error; + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) goto error; + break; + } + + case s_res_first_status_code: + { + if (ch < '0' || ch > '9') { + if (ch == ' ') { + break; + } + goto error; + } + parser->status_code = ch - '0'; + state = s_res_status_code; + break; + } + + case s_res_status_code: + { + if (ch < '0' || ch > '9') { + switch (ch) { + case ' ': + state = s_res_status; + break; + case CR: + state = s_res_line_almost_done; + break; + case LF: + state = s_header_field_start; + break; + default: + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (parser->status_code > 999) goto error; + break; + } + + case s_res_status: + /* the human readable status. e.g. "NOT FOUND" + * we are not humans so just ignore this */ + if (ch == CR) { + state = s_res_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + state = s_header_field_start; + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = -1; + + CALLBACK2(message_begin); + + if (ch < 'A' || 'Z' < ch) goto error; + + start_req_method_assign: + parser->method = (enum php_http_method) 0; + index = 1; + switch (ch) { + case 'C': parser->method = PHP_HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = PHP_HTTP_DELETE; break; + case 'G': parser->method = PHP_HTTP_GET; break; + case 'H': parser->method = PHP_HTTP_HEAD; break; + case 'L': parser->method = PHP_HTTP_LOCK; break; + case 'M': parser->method = PHP_HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break; + case 'N': parser->method = PHP_HTTP_NOTIFY; break; + case 'O': parser->method = PHP_HTTP_OPTIONS; break; + case 'P': parser->method = PHP_HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break; + case 'R': parser->method = PHP_HTTP_REPORT; break; + case 'S': parser->method = PHP_HTTP_SUBSCRIBE; break; + case 'T': parser->method = PHP_HTTP_TRACE; break; + case 'U': parser->method = PHP_HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: parser->method = PHP_HTTP_NOT_IMPLEMENTED; break; + } + state = s_req_method; + break; + } + + case s_req_method: + { + const char *matcher; + if (ch == '\0') + goto error; + + matcher = method_strings[parser->method]; + if (ch == ' ' && (matcher[index] == '\0' || parser->method == PHP_HTTP_NOT_IMPLEMENTED)) { + state = s_req_spaces_before_url; + } else if (ch == matcher[index]) { + ; /* nada */ + } else if (parser->method == PHP_HTTP_CONNECT) { + if (index == 1 && ch == 'H') { + parser->method = PHP_HTTP_CHECKOUT; + } else if (index == 2 && ch == 'P') { + parser->method = PHP_HTTP_COPY; + } + } else if (parser->method == PHP_HTTP_MKCOL) { + if (index == 1 && ch == 'O') { + parser->method = PHP_HTTP_MOVE; + } else if (index == 1 && ch == 'E') { + parser->method = PHP_HTTP_MERGE; + } else if (index == 1 && ch == '-') { + parser->method = PHP_HTTP_MSEARCH; + } else if (index == 2 && ch == 'A') { + parser->method = PHP_HTTP_MKACTIVITY; + } + } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'R') { + parser->method = PHP_HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'U') { + parser->method = PHP_HTTP_PUT; + } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'A') { + parser->method = PHP_HTTP_PATCH; + } else if (index == 2 && parser->method == PHP_HTTP_UNLOCK && ch == 'S') { + parser->method = PHP_HTTP_UNSUBSCRIBE; + } else if (index == 4 && parser->method == PHP_HTTP_PROPFIND && ch == 'P') { + parser->method = PHP_HTTP_PROPPATCH; + } else { + parser->method = PHP_HTTP_NOT_IMPLEMENTED; + } + + ++index; + break; + } + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + if (ch == '/' || ch == '*') { + MARK(url); + MARK(path); + state = s_req_path; + break; + } + + c = LOWER(ch); + + if (c >= 'a' && c <= 'z') { + MARK(url); + state = s_req_schema; + break; + } + + goto error; + } + + case s_req_schema: + { + c = LOWER(ch); + + if (c >= 'a' && c <= 'z') break; + + if (ch == ':') { + state = s_req_schema_slash; + break; + } else if (ch == '.') { + state = s_req_host; + break; + } else if ('0' <= ch && ch <= '9') { + state = s_req_host; + break; + } + + goto error; + } + + case s_req_schema_slash: + STRICT_CHECK(ch != '/'); + state = s_req_schema_slash_slash; + break; + + case s_req_schema_slash_slash: + STRICT_CHECK(ch != '/'); + state = s_req_host; + break; + + case s_req_host: + { + c = LOWER(ch); + if (c >= 'a' && c <= 'z') break; + if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break; + switch (ch) { + case ':': + state = s_req_port; + break; + case '/': + MARK(path); + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + default: + goto error; + } + break; + } + + case s_req_port: + { + if (ch >= '0' && ch <= '9') break; + switch (ch) { + case '/': + MARK(path); + state = s_req_path; + break; + case ' ': + /* The request line looks like: + * "GET http://foo.bar.com:1234 HTTP/1.1" + * That is, there is no path. + */ + CALLBACK(url); + state = s_req_http_start; + break; + default: + goto error; + } + break; + } + + case s_req_path: + { + if (normal_url_char[(unsigned char)ch]) break; + + switch (ch) { + case ' ': + CALLBACK(url); + CALLBACK(path); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + CALLBACK(path); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + CALLBACK(path); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + CALLBACK(path); + state = s_req_query_string_start; + break; + case '#': + CALLBACK(path); + state = s_req_fragment_start; + break; + default: + goto error; + } + break; + } + + case s_req_query_string_start: + { + if (normal_url_char[(unsigned char)ch]) { + MARK(query_string); + state = s_req_query_string; + break; + } + + switch (ch) { + case '?': + break; /* XXX ignore extra '?' ... is this right? */ + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + state = s_req_fragment_start; + break; + default: + goto error; + } + break; + } + + case s_req_query_string: + { + if (normal_url_char[(unsigned char)ch]) break; + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + break; + case ' ': + CALLBACK(url); + CALLBACK(query_string); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + CALLBACK(query_string); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + CALLBACK(query_string); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '#': + CALLBACK(query_string); + state = s_req_fragment_start; + break; + default: + goto error; + } + break; + } + + case s_req_fragment_start: + { + if (normal_url_char[(unsigned char)ch]) { + MARK(fragment); + state = s_req_fragment; + break; + } + + switch (ch) { + case ' ': + CALLBACK(url); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + MARK(fragment); + state = s_req_fragment; + break; + case '#': + break; + default: + goto error; + } + break; + } + + case s_req_fragment: + { + if (normal_url_char[(unsigned char)ch]) break; + + switch (ch) { + case ' ': + CALLBACK(url); + CALLBACK(fragment); + state = s_req_http_start; + break; + case CR: + CALLBACK(url); + CALLBACK(fragment); + parser->http_major = 0; + parser->http_minor = 9; + state = s_req_line_almost_done; + break; + case LF: + CALLBACK(url); + CALLBACK(fragment); + parser->http_major = 0; + parser->http_minor = 9; + state = s_header_field_start; + break; + case '?': + case '#': + break; + default: + goto error; + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + state = s_req_http_H; + break; + case ' ': + break; + default: + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HT; + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + state = s_req_http_HTT; + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + state = s_req_http_HTTP; + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + state = s_req_first_http_major; + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (ch < '1' || ch > '9') goto error; + parser->http_major = ch - '0'; + state = s_req_http_major; + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + state = s_req_first_http_minor; + break; + } + + if (ch < '0' || ch > '9') goto error; + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (parser->http_major > 999) goto error; + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (ch < '0' || ch > '9') goto error; + parser->http_minor = ch - '0'; + state = s_req_http_minor; + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + state = s_req_line_almost_done; + break; + } + + if (ch == LF) { + state = s_header_field_start; + break; + } + + /* XXX allow spaces after digit? */ + + if (ch < '0' || ch > '9') goto error; + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (parser->http_minor > 999) goto error; + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (ch != LF) goto error; + state = s_header_field_start; + break; + } + + case s_header_field_start: + { + if (ch == CR) { + state = s_headers_almost_done; + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + state = s_headers_almost_done; + goto headers_almost_done; + } + + c = TOKEN(ch); + + if (!c) goto error; + + MARK(header_field); + + index = 0; + state = s_header_field; + + switch (c) { + case 'c': + header_state = h_C; + break; + + case 'p': + header_state = h_matching_proxy_connection; + break; + + case 't': + header_state = h_matching_transfer_encoding; + break; + + case 'u': + header_state = h_matching_upgrade; + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + c = TOKEN(ch); + + if (c) { + switch (header_state) { + case h_general: + break; + + case h_C: + index++; + header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + index++; + header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + index++; + switch (c) { + case 'n': + header_state = h_matching_connection; + break; + case 't': + header_state = h_matching_content_length; + break; + default: + header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + index++; + if (index > sizeof(CONNECTION)-1 + || c != CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + index++; + if (index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[index]) { + header_state = h_general; + } else if (index == sizeof(PROXY_CONNECTION)-2) { + header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + index++; + if (index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[index]) { + header_state = h_general; + } else if (index == sizeof(CONTENT_LENGTH)-2) { + header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + index++; + if (index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[index]) { + header_state = h_general; + } else if (index == sizeof(TRANSFER_ENCODING)-2) { + header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + index++; + if (index > sizeof(UPGRADE)-1 + || c != UPGRADE[index]) { + header_state = h_general; + } else if (index == sizeof(UPGRADE)-2) { + header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + break; + } + + if (ch == ':') { + CALLBACK(header_field); + state = s_header_value_start; + break; + } + + if (ch == CR) { + state = s_header_almost_done; + CALLBACK(header_field); + break; + } + + if (ch == LF) { + CALLBACK(header_field); + state = s_header_field_start; + break; + } + + goto error; + } + + case s_header_value_start: + { + if (ch == ' ') break; + + MARK(header_value); + + state = s_header_value; + index = 0; + + c = LOWER(ch); + + if (ch == CR) { + CALLBACK(header_value); + header_state = h_general; + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + state = s_header_field_start; + break; + } + + switch (header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + header_state = h_matching_transfer_encoding_chunked; + } else { + header_state = h_general; + } + break; + + case h_content_length: + if (ch < '0' || ch > '9') goto error; + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + header_state = h_matching_connection_close; + } else { + header_state = h_general; + } + break; + + default: + header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + c = LOWER(ch); + + if (ch == CR) { + CALLBACK(header_value); + state = s_header_almost_done; + break; + } + + if (ch == LF) { + CALLBACK(header_value); + goto header_almost_done; + } + + switch (header_state) { + case h_general: + break; + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + if (ch == ' ') break; + if (ch < '0' || ch > '9') goto error; + parser->content_length *= 10; + parser->content_length += ch - '0'; + break; + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + index++; + if (index > sizeof(CHUNKED)-1 + || c != CHUNKED[index]) { + header_state = h_general; + } else if (index == sizeof(CHUNKED)-2) { + header_state = h_transfer_encoding_chunked; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + index++; + if (index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[index]) { + header_state = h_general; + } else if (index == sizeof(KEEP_ALIVE)-2) { + header_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + index++; + if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) { + header_state = h_general; + } else if (index == sizeof(CLOSE)-2) { + header_state = h_connection_close; + } + break; + + case h_transfer_encoding_chunked: + case h_connection_keep_alive: + case h_connection_close: + if (ch != ' ') header_state = h_general; + break; + + default: + state = s_header_value; + header_state = h_general; + break; + } + break; + } + + case s_header_almost_done: + header_almost_done: + { + STRICT_CHECK(ch != LF); + + state = s_header_field_start; + + switch (header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + break; + } + + case s_headers_almost_done: + headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + break; + } + + nread = 0; + + if (parser->flags & F_UPGRADE || parser->method == PHP_HTTP_CONNECT) { + parser->upgrade = 1; + } + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + return p - data; /* Error */ + } + } + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + CALLBACK2(message_complete); + return (p - data); + } + + if (parser->flags & F_SKIPBODY) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + state = s_chunk_size_start; + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else if (parser->content_length > 0) { + /* Content-Length header given and non-zero */ + state = s_body_identity; + } else { + if (parser->type == PHP_HTTP_REQUEST || php_http_should_keep_alive(parser)) { + /* Assume content-length 0 - read the next */ + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } else { + /* Read body until EOF */ + state = s_body_identity_eof; + } + } + } + + break; + } + + case s_body_identity: + to_read = MIN(pe - p, (size_t)parser->content_length); + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + parser->content_length -= to_read; + if (parser->content_length == 0) { + CALLBACK2(message_complete); + state = NEW_MESSAGE(); + } + } + break; + + /* read until EOF */ + case s_body_identity_eof: + to_read = pe - p; + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + break; + + case s_chunk_size_start: + { + assert(parser->flags & F_CHUNKED); + + c = unhex[(unsigned char)ch]; + if (c == -1) goto error; + parser->content_length = c; + state = s_chunk_size; + break; + } + + case s_chunk_size: + { + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + + c = unhex[(unsigned char)ch]; + + if (c == -1) { + if (ch == ';' || ch == ' ') { + state = s_chunk_parameters; + break; + } + goto error; + } + + parser->content_length *= 16; + parser->content_length += c; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + state = s_chunk_size_almost_done; + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + state = s_header_field_start; + } else { + state = s_chunk_data; + } + break; + } + + case s_chunk_data: + { + assert(parser->flags & F_CHUNKED); + + to_read = MIN(pe - p, (size_t)(parser->content_length)); + + if (to_read > 0) { + if (settings->on_body) settings->on_body(parser, p, to_read); + p += to_read - 1; + } + + if (to_read == parser->content_length) { + state = s_chunk_data_almost_done; + } + + parser->content_length -= to_read; + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != CR); + state = s_chunk_data_done; + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + state = s_chunk_size_start; + break; + + default: + assert(0 && "unhandled state"); + goto error; + } + } + + CALLBACK_NOCLEAR(header_field); + CALLBACK_NOCLEAR(header_value); + CALLBACK_NOCLEAR(fragment); + CALLBACK_NOCLEAR(query_string); + CALLBACK_NOCLEAR(path); + CALLBACK_NOCLEAR(url); + + parser->state = state; + parser->header_state = header_state; + parser->index = index; + parser->nread = nread; + + return len; + +error: + parser->state = s_dead; + return (p - data); +} + + +int +php_http_should_keep_alive (php_http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } else { + return 1; + } + } else { + /* HTTP/1.0 or earlier */ + if (parser->flags & F_CONNECTION_KEEP_ALIVE) { + return 1; + } else { + return 0; + } + } +} + + +const char * php_http_method_str (enum php_http_method m) +{ + return method_strings[m]; +} + + +void +php_http_parser_init (php_http_parser *parser, enum php_http_parser_type t) +{ + parser->type = t; + parser->state = (t == PHP_HTTP_REQUEST ? s_start_req : (t == PHP_HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->nread = 0; + parser->upgrade = 0; + parser->flags = 0; + parser->method = 0; +} diff --git a/sapi/cli/php_http_parser.h b/sapi/cli/php_http_parser.h new file mode 100644 index 0000000..2bf2356 --- /dev/null +++ b/sapi/cli/php_http_parser.h @@ -0,0 +1,180 @@ +/* Copyright 2009,2010 Ryan Dahl <ry@tinyclouds.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +/* modified by Moriyoshi Koizumi <moriyoshi@php.net> to make it fit to PHP source tree. */ +#ifndef php_http_parser_h +#define php_http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + + +#include <sys/types.h> +#if defined(_WIN32) && !defined(__MINGW32__) +# include <windows.h> +# include "win32/php_stdint.h" +# include "config.w32.h" +#else +# include "php_config.h" +# ifdef HAVE_STDINT_H +# include <stdint.h> +# endif +#endif + +/* Compile with -DPHP_HTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef PHP_HTTP_PARSER_STRICT +# define PHP_HTTP_PARSER_STRICT 1 +#else +# define PHP_HTTP_PARSER_STRICT 0 +#endif + + +/* Maximium header size allowed */ +#define PHP_HTTP_MAX_HEADER_SIZE (80*1024) + + +typedef struct php_http_parser php_http_parser; +typedef struct php_http_parser_settings php_http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a PHP_HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * http_data_cb does not return data chunks. It will be call arbitrarally + * many times for each string. E.G. you might get 10 callbacks for "on_path" + * each providing just a few characters more data. + */ +typedef int (*php_http_data_cb) (php_http_parser*, const char *at, size_t length); +typedef int (*php_http_cb) (php_http_parser*); + + +/* Request Methods */ +enum php_http_method + { PHP_HTTP_DELETE = 0 + , PHP_HTTP_GET + , PHP_HTTP_HEAD + , PHP_HTTP_POST + , PHP_HTTP_PUT + , PHP_HTTP_PATCH + /* pathological */ + , PHP_HTTP_CONNECT + , PHP_HTTP_OPTIONS + , PHP_HTTP_TRACE + /* webdav */ + , PHP_HTTP_COPY + , PHP_HTTP_LOCK + , PHP_HTTP_MKCOL + , PHP_HTTP_MOVE + , PHP_HTTP_PROPFIND + , PHP_HTTP_PROPPATCH + , PHP_HTTP_UNLOCK + /* subversion */ + , PHP_HTTP_REPORT + , PHP_HTTP_MKACTIVITY + , PHP_HTTP_CHECKOUT + , PHP_HTTP_MERGE + /* upnp */ + , PHP_HTTP_MSEARCH + , PHP_HTTP_NOTIFY + , PHP_HTTP_SUBSCRIBE + , PHP_HTTP_UNSUBSCRIBE + /* unknown, not implemented */ + , PHP_HTTP_NOT_IMPLEMENTED + }; + + +enum php_http_parser_type { PHP_HTTP_REQUEST, PHP_HTTP_RESPONSE, PHP_HTTP_BOTH }; + + +struct php_http_parser { + /** PRIVATE **/ + unsigned char type : 2; + unsigned char flags : 6; + unsigned char state; + unsigned char header_state; + unsigned char index; + + uint32_t nread; + ssize_t content_length; + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned short status_code; /* responses only */ + unsigned char method; /* requests only */ + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + char upgrade; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct php_http_parser_settings { + php_http_cb on_message_begin; + php_http_data_cb on_path; + php_http_data_cb on_query_string; + php_http_data_cb on_url; + php_http_data_cb on_fragment; + php_http_data_cb on_header_field; + php_http_data_cb on_header_value; + php_http_cb on_headers_complete; + php_http_data_cb on_body; + php_http_cb on_message_complete; +}; + + +void php_http_parser_init(php_http_parser *parser, enum php_http_parser_type type); + + +size_t php_http_parser_execute(php_http_parser *parser, + const php_http_parser_settings *settings, + const char *data, + size_t len); + + +/* If php_http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns true, then this will be should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int php_http_should_keep_alive(php_http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *php_http_method_str(enum php_http_method); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/sapi/cli/tests/001.phpt b/sapi/cli/tests/001.phpt new file mode 100644 index 0000000..6fbd608 --- /dev/null +++ b/sapi/cli/tests/001.phpt @@ -0,0 +1,19 @@ +--TEST-- +version string +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n -v`); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "PHP %s (cli) (built: %s)%s +Copyright (c) 1997-20%d The PHP Group +Zend Engine v%s, Copyright (c) 1998-20%d Zend Technologies +" +Done diff --git a/sapi/cli/tests/002-win32.phpt b/sapi/cli/tests/002-win32.phpt new file mode 100644 index 0000000..5a00c67 --- /dev/null +++ b/sapi/cli/tests/002-win32.phpt @@ -0,0 +1,22 @@ +--TEST-- +running code with -r +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) != 'WIN') { + die ("skip only for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n -r "var_dump('hello');"`); + +echo "Done\n"; +?> +--EXPECTF-- +string(18) "string(5) "hello" +" +Done diff --git a/sapi/cli/tests/002.phpt b/sapi/cli/tests/002.phpt new file mode 100644 index 0000000..be2b633 --- /dev/null +++ b/sapi/cli/tests/002.phpt @@ -0,0 +1,22 @@ +--TEST-- +running code with -r +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n -r 'var_dump("hello");'`); + +echo "Done\n"; +?> +--EXPECTF-- +string(18) "string(5) "hello" +" +Done diff --git a/sapi/cli/tests/003-2.phpt b/sapi/cli/tests/003-2.phpt new file mode 100644 index 0000000..2ed9b07 --- /dev/null +++ b/sapi/cli/tests/003-2.phpt @@ -0,0 +1,25 @@ +--TEST-- +defining INI options with -d (as 2nd arg) +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`"$php" -nd max_execution_time=111 -r 'var_dump(ini_get("max_execution_time"));'`); +var_dump(`"$php" -nd max_execution_time=500 -r 'var_dump(ini_get("max_execution_time"));'`); + +?> +===DONE=== +--EXPECTF-- +string(16) "string(3) "111" +" +string(16) "string(3) "500" +" +===DONE=== diff --git a/sapi/cli/tests/003.phpt b/sapi/cli/tests/003.phpt new file mode 100644 index 0000000..d62360e --- /dev/null +++ b/sapi/cli/tests/003.phpt @@ -0,0 +1,32 @@ +--TEST-- +defining INI options with -d +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n -d max_execution_time=111 -r 'var_dump(ini_get("max_execution_time"));'`); +var_dump(`$php -n -d max_execution_time=500 -r 'var_dump(ini_get("max_execution_time"));'`); +var_dump(`$php -n -d max_execution_time=500 -d max_execution_time=555 -r 'var_dump(ini_get("max_execution_time"));'`); +var_dump(`$php -n -d upload_tmp_dir=/test/path -d max_execution_time=555 -r 'var_dump(ini_get("max_execution_time")); var_dump(ini_get("upload_tmp_dir"));'`); + +echo "Done\n"; +?> +--EXPECTF-- +string(16) "string(3) "111" +" +string(16) "string(3) "500" +" +string(16) "string(3) "555" +" +string(40) "string(3) "555" +string(10) "/test/path" +" +Done diff --git a/sapi/cli/tests/004.phpt b/sapi/cli/tests/004.phpt new file mode 100644 index 0000000..378515d --- /dev/null +++ b/sapi/cli/tests/004.phpt @@ -0,0 +1,34 @@ +--TEST-- +show information about function +--SKIPIF-- +<?php +include "skipif.inc"; +if (!extension_loaded("reflection")) { + die("skip reflection extension required"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n --rf unknown`); +var_dump(`$php -n --rf echo`); +var_dump(`$php -n --rf phpinfo`); + +echo "Done\n"; +?> +--EXPECTF-- +string(45) "Exception: Function unknown() does not exist +" +string(42) "Exception: Function echo() does not exist +" +string(119) "Function [ <internal:standard> function phpinfo ] { + + - Parameters [1] { + Parameter #0 [ <optional> $what ] + } +} + +" +Done diff --git a/sapi/cli/tests/005.phpt b/sapi/cli/tests/005.phpt new file mode 100644 index 0000000..84b0f98 --- /dev/null +++ b/sapi/cli/tests/005.phpt @@ -0,0 +1,104 @@ +--TEST-- +show information about class +--SKIPIF-- +<?php +include "skipif.inc"; +if (!extension_loaded("reflection")) { + die("skip reflection extension required"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`"$php" -n --rc unknown`); +var_dump(`"$php" -n --rc stdclass`); +var_dump(`"$php" -n --rc exception`); + +echo "Done\n"; +?> +--EXPECTF-- +string(40) "Exception: Class unknown does not exist +" +string(183) "Class [ <internal:Core> class stdClass ] { + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [0] { + } + + - Methods [0] { + } +} + +" +string(1355) "Class [ <internal:Core> class Exception ] { + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [7] { + Property [ <default> protected $message ] + Property [ <default> private $string ] + Property [ <default> protected $code ] + Property [ <default> protected $file ] + Property [ <default> protected $line ] + Property [ <default> private $trace ] + Property [ <default> private $previous ] + } + + - Methods [10] { + Method [ <internal:Core> final private method __clone ] { + } + + Method [ <internal:Core, ctor> public method __construct ] { + + - Parameters [3] { + Parameter #0 [ <optional> $message ] + Parameter #1 [ <optional> $code ] + Parameter #2 [ <optional> $previous ] + } + } + + Method [ <internal:Core> final public method getMessage ] { + } + + Method [ <internal:Core> final public method getCode ] { + } + + Method [ <internal:Core> final public method getFile ] { + } + + Method [ <internal:Core> final public method getLine ] { + } + + Method [ <internal:Core> final public method getTrace ] { + } + + Method [ <internal:Core> final public method getPrevious ] { + } + + Method [ <internal:Core> final public method getTraceAsString ] { + } + + Method [ <internal:Core> public method __toString ] { + } + } +} + +" +Done diff --git a/sapi/cli/tests/006.phpt b/sapi/cli/tests/006.phpt new file mode 100644 index 0000000..3d5c916 --- /dev/null +++ b/sapi/cli/tests/006.phpt @@ -0,0 +1,140 @@ +--TEST-- +show information about extension +--SKIPIF-- +<?php +include "skipif.inc"; +if (!extension_loaded("reflection") || !extension_loaded("session")) { + die("skip reflection and session extensions required"); +} +?> +--INI-- +date.timezone= +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n --re unknown`); +var_dump(`$php -n --re ""`); +var_dump(`$php -n --re pcre`); + +echo "Done\n"; +?> +--EXPECTF-- +string(44) "Exception: Extension unknown does not exist +" +string(37) "Exception: Extension does not exist +" +string(%d) "Extension [ <persistent> extension #%d pcre version <no_version> ] { + + - INI { + Entry [ pcre.backtrack_limit <ALL> ] + Current = '%d' + } + Entry [ pcre.recursion_limit <ALL> ] + Current = '%d' + } + } + + - Constants [14] { + Constant [ integer PREG_PATTERN_ORDER ] { 1 } + Constant [ integer PREG_SET_ORDER ] { 2 } + Constant [ integer PREG_OFFSET_CAPTURE ] { 256 } + Constant [ integer PREG_SPLIT_NO_EMPTY ] { 1 } + Constant [ integer PREG_SPLIT_DELIM_CAPTURE ] { 2 } + Constant [ integer PREG_SPLIT_OFFSET_CAPTURE ] { 4 } + Constant [ integer PREG_GREP_INVERT ] { 1 } + Constant [ integer PREG_NO_ERROR ] { 0 } + Constant [ integer PREG_INTERNAL_ERROR ] { 1 } + Constant [ integer PREG_BACKTRACK_LIMIT_ERROR ] { 2 } + Constant [ integer PREG_RECURSION_LIMIT_ERROR ] { 3 } + Constant [ integer PREG_BAD_UTF8_ERROR ] { 4 } + Constant [ integer PREG_BAD_UTF8_OFFSET_ERROR ] { 5 } + Constant [ string PCRE_VERSION ] { %s } + } + + - Functions { + Function [ <internal:pcre> function preg_match ] { + + - Parameters [5] { + Parameter #0 [ <required> $pattern ] + Parameter #1 [ <required> $subject ] + Parameter #2 [ <optional> &$subpatterns ] + Parameter #3 [ <optional> $flags ] + Parameter #4 [ <optional> $offset ] + } + } + Function [ <internal:pcre> function preg_match_all ] { + + - Parameters [5] { + Parameter #0 [ <required> $pattern ] + Parameter #1 [ <required> $subject ] + Parameter #2 [ <optional> &$subpatterns ] + Parameter #3 [ <optional> $flags ] + Parameter #4 [ <optional> $offset ] + } + } + Function [ <internal:pcre> function preg_replace ] { + + - Parameters [5] { + Parameter #0 [ <required> $regex ] + Parameter #1 [ <required> $replace ] + Parameter #2 [ <required> $subject ] + Parameter #3 [ <optional> $limit ] + Parameter #4 [ <optional> &$count ] + } + } + Function [ <internal:pcre> function preg_replace_callback ] { + + - Parameters [5] { + Parameter #0 [ <required> $regex ] + Parameter #1 [ <required> $callback ] + Parameter #2 [ <required> $subject ] + Parameter #3 [ <optional> $limit ] + Parameter #4 [ <optional> &$count ] + } + } + Function [ <internal:pcre> function preg_filter ] { + + - Parameters [5] { + Parameter #0 [ <required> $regex ] + Parameter #1 [ <required> $replace ] + Parameter #2 [ <required> $subject ] + Parameter #3 [ <optional> $limit ] + Parameter #4 [ <optional> &$count ] + } + } + Function [ <internal:pcre> function preg_split ] { + + - Parameters [4] { + Parameter #0 [ <required> $pattern ] + Parameter #1 [ <required> $subject ] + Parameter #2 [ <optional> $limit ] + Parameter #3 [ <optional> $flags ] + } + } + Function [ <internal:pcre> function preg_quote ] { + + - Parameters [2] { + Parameter #0 [ <required> $str ] + Parameter #1 [ <optional> $delim_char ] + } + } + Function [ <internal:pcre> function preg_grep ] { + + - Parameters [3] { + Parameter #0 [ <required> $regex ] + Parameter #1 [ <required> $input ] + Parameter #2 [ <optional> $flags ] + } + } + Function [ <internal:pcre> function preg_last_error ] { + + - Parameters [0] { + } + } + } +} + +" +Done diff --git a/sapi/cli/tests/007.phpt b/sapi/cli/tests/007.phpt new file mode 100644 index 0000000..0bf4070 --- /dev/null +++ b/sapi/cli/tests/007.phpt @@ -0,0 +1,52 @@ +--TEST-- +strip comments and whitespace with -w +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = dirname(__FILE__).'/007.test.php'; +$code =' +<?php +/* some test script */ + +class test { /* {{{ */ + public $var = "test"; //test var +#perl style comment + private $pri; /* private attr */ + + function foo(/* void */) { + } +} +/* }}} */ + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`$php -n -w "$filename"`); +var_dump(`$php -n -w "wrong"`); +var_dump(`echo "<?php /* comment */ class test {\n // comment \n function foo() {} } ?>" | $php -n -w`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(81) " +<?php + class test { public $var = "test"; private $pri; function foo() { } } ?> +" +string(33) "Could not open input file: wrong +" +string(43) "<?php class test { function foo() {} } ?> +" +Done diff --git a/sapi/cli/tests/008.phpt b/sapi/cli/tests/008.phpt new file mode 100644 index 0000000..e14338f --- /dev/null +++ b/sapi/cli/tests/008.phpt @@ -0,0 +1,43 @@ +--TEST-- +execute a file with -f +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = dirname(__FILE__).'/008.test.php'; +$code =' +<?php + +class test { + private $pri; +} + +var_dump(test::$pri); +?> +'; + +file_put_contents($filename, $code); + +var_dump(`$php -n -f "$filename"`); +var_dump(`$php -n -f "wrong"`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) " + +Fatal error: Cannot access private property test::$pri in %s on line %d +" +string(33) "Could not open input file: wrong +" +Done diff --git a/sapi/cli/tests/009.phpt b/sapi/cli/tests/009.phpt new file mode 100644 index 0000000..33f859f --- /dev/null +++ b/sapi/cli/tests/009.phpt @@ -0,0 +1,20 @@ +--TEST-- +using invalid combinations of cmdline options +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`$php -n -a -r "echo hello;"`); +var_dump(`$php -n -r "echo hello;" -a`); + +echo "Done\n"; +?> +--EXPECTF-- +string(57) "Either execute direct code, process stdin or use a file. +" +string(57) "Either execute direct code, process stdin or use a file. +" +Done diff --git a/sapi/cli/tests/010-2.phpt b/sapi/cli/tests/010-2.phpt new file mode 100644 index 0000000..bd33d2c --- /dev/null +++ b/sapi/cli/tests/010-2.phpt @@ -0,0 +1,35 @@ +--TEST-- +executing a code with -R +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename_txt = dirname(__FILE__)."/010.test.txt"; + +$txt = ' +test +hello +'; + +file_put_contents($filename_txt, $txt); + +var_dump(`cat "$filename_txt" | "$php" -n -R "var_dump(1);"`); + +@unlink($filename_txt); + +echo "Done\n"; +?> +--EXPECTF-- +string(21) "int(1) +int(1) +int(1) +" +Done diff --git a/sapi/cli/tests/010.phpt b/sapi/cli/tests/010.phpt new file mode 100644 index 0000000..77c76c1 --- /dev/null +++ b/sapi/cli/tests/010.phpt @@ -0,0 +1,46 @@ +--TEST-- +executing a file with -F +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = __DIR__."/010.test.php"; +$filename_txt = __DIR__."/010.test.txt"; + +$code = ' +<?php +var_dump(fread(STDIN, 10)); +?> +'; + +file_put_contents($filename, $code); + +$txt = ' +test +hello'; + +file_put_contents($filename_txt, $txt); + +var_dump(`cat "$filename_txt" | "$php" -n -F "$filename"`); + +?> +===DONE=== +--CLEAN-- +<?php +@unlink(__DIR__."/010.test.php"); +@unlink(__DIR__."/010.test.txt"); +?> +--EXPECTF-- +string(25) " +string(10) "test +hello" +" +===DONE=== diff --git a/sapi/cli/tests/011.phpt b/sapi/cli/tests/011.phpt new file mode 100644 index 0000000..6154693 --- /dev/null +++ b/sapi/cli/tests/011.phpt @@ -0,0 +1,58 @@ +--TEST-- +syntax check +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = dirname(__FILE__)."/011.test.php"; + +$code = ' +<?php + +$test = "var"; + +class test { + private $var; +} + +echo test::$var; + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -l $filename`); +var_dump(`"$php" -n -l some.unknown`); + +$code = ' +<?php + +class test + private $var; +} + +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -l $filename`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(%d) "No syntax errors detected in %s011.test.php +" +string(40) "Could not open input file: some.unknown +" +string(%d) " +Parse error: %s expecting %s{%s in %s on line %d +Errors parsing %s011.test.php +" +Done diff --git a/sapi/cli/tests/012.phpt b/sapi/cli/tests/012.phpt new file mode 100644 index 0000000..c1e4f6a --- /dev/null +++ b/sapi/cli/tests/012.phpt @@ -0,0 +1,38 @@ +--TEST-- +invalid arguments and error messages +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +var_dump(`"$php" -n -F some.php -F some.php`); +var_dump(`"$php" -n -F some.php -R some.php`); +var_dump(`"$php" -n -R some.php -F some.php`); +var_dump(`"$php" -n -R some.php -R some.php`); +var_dump(`"$php" -n -f some.php -f some.php`); +var_dump(`"$php" -n -B '' -B ''`); +var_dump(`"$php" -n -E '' -E ''`); +var_dump(`"$php" -n -r '' -r ''`); + +echo "Done\n"; +?> +--EXPECTF-- +string(32) "You can use -R or -F only once. +" +string(32) "You can use -R or -F only once. +" +string(32) "You can use -R or -F only once. +" +string(32) "You can use -R or -F only once. +" +string(26) "You can use -f only once. +" +string(26) "You can use -B only once. +" +string(26) "You can use -E only once. +" +string(26) "You can use -r only once. +" +Done diff --git a/sapi/cli/tests/013.phpt b/sapi/cli/tests/013.phpt new file mode 100644 index 0000000..99bfe5e --- /dev/null +++ b/sapi/cli/tests/013.phpt @@ -0,0 +1,34 @@ +--TEST-- +running PHP code before and after processing input lines with -B and -E +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename_txt = dirname(__FILE__)."/013.test.txt"; +file_put_contents($filename_txt, "test\nfile\ncontents\n"); + +var_dump(`cat "$filename_txt" | "$php" -n -B 'var_dump("start");'`); +var_dump(`cat "$filename_txt" | "$php" -n -E 'var_dump("end");'`); +var_dump(`cat "$filename_txt" | "$php" -n -B 'var_dump("start");' -E 'var_dump("end");'`); + +@unlink($filename_txt); + +echo "Done\n"; +?> +--EXPECTF-- +string(18) "string(5) "start" +" +string(16) "string(3) "end" +" +string(34) "string(5) "start" +string(3) "end" +" +Done diff --git a/sapi/cli/tests/014.phpt b/sapi/cli/tests/014.phpt new file mode 100644 index 0000000..e8c5203 --- /dev/null +++ b/sapi/cli/tests/014.phpt @@ -0,0 +1,44 @@ +--TEST-- +syntax highlighting +--SKIPIF-- +<?php include "skipif.inc"; ?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = dirname(__FILE__)."/014.test.php"; +$code = ' +<?php +$test = "var"; //var +/* test class */ +class test { + private $var = array(); + + public static function foo(Test $arg) { + echo "hello"; + var_dump($this); + } +} + +$o = new test; +?> +'; + +file_put_contents($filename, $code); + +var_dump(`"$php" -n -s $filename`); +var_dump(`"$php" -n -s unknown`); + +@unlink($filename); + +echo "Done\n"; +?> +--EXPECTF-- +string(1478) "<code><span style="color: #000000"> +<br /><span style="color: #0000BB"><?php<br />$test </span><span style="color: #007700">= </span><span style="color: #DD0000">"var"</span><span style="color: #007700">; </span><span style="color: #FF8000">//var<br />/* test class */<br /></span><span style="color: #007700">class </span><span style="color: #0000BB">test </span><span style="color: #007700">{<br /> private </span><span style="color: #0000BB">$var </span><span style="color: #007700">= array();<br /><br /> public static function </span><span style="color: #0000BB">foo</span><span style="color: #007700">(</span><span style="color: #0000BB">Test $arg</span><span style="color: #007700">) {<br /> echo </span><span style="color: #DD0000">"hello"</span><span style="color: #007700">;<br /> </span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #0000BB">$this</span><span style="color: #007700">);<br /> }<br />}<br /><br /></span><span style="color: #0000BB">$o </span><span style="color: #007700">= new </span><span style="color: #0000BB">test</span><span style="color: #007700">;<br /></span><span style="color: #0000BB">?><br /></span> +</span> +</code>" +string(35) "Could not open input file: unknown +" +Done diff --git a/sapi/cli/tests/015.phpt b/sapi/cli/tests/015.phpt new file mode 100644 index 0000000..ab5918b --- /dev/null +++ b/sapi/cli/tests/015.phpt @@ -0,0 +1,35 @@ +--TEST-- +CLI long options +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + + +echo `"$php" -n --version | grep built:`; +echo `echo "<?php print_r(\\\$argv);" | "$php" -n -- foo bar baz`, "\n"; +echo `"$php" -n --version foo bar baz | grep built:`; +echo `"$php" -n --notexisting foo bar baz | grep Usage:`; + +echo "Done\n"; +?> +--EXPECTF-- +PHP %d.%d.%d%s(cli) (built: %s)%s +Array +( + [0] => - + [1] => foo + [2] => bar + [3] => baz +) + +PHP %d.%d.%d%s(cli) (built: %s)%s +Usage: %s [options] [-f] <file> [--] [args...] +Done diff --git a/sapi/cli/tests/016.phpt b/sapi/cli/tests/016.phpt new file mode 100644 index 0000000..31c1a40 --- /dev/null +++ b/sapi/cli/tests/016.phpt @@ -0,0 +1,130 @@ +--TEST-- +CLI -a and readline +--SKIPIF-- +<?php +include "skipif.inc"; +if (!extension_loaded('readline') || readline_info('done') === NULL) { + die ("skip need readline support"); +} +?> +--FILE-- +<?php +$php = getenv('TEST_PHP_EXECUTABLE'); + +// disallow console escape sequences that may break the output +putenv('TERM=VT100'); + +$codes = array(); + +$codes[1] = <<<EOT +echo 'Hello world'; +exit +EOT; + +$codes[] = <<<EOT +echo 'multine +single +quote'; +exit +EOT; + +$codes[] = <<<EOT +echo <<<HEREDOC +Here +comes +the +doc +HEREDOC; +EOT; + +$codes[] = <<<EOT +if (0) { + echo "I'm not there"; +} +echo "Done"; +EOT; + +$codes[] = <<<EOT +function a_function_with_some_name() { + echo "I was called!"; +} +a_function_w ); +EOT; + +foreach ($codes as $key => $code) { + echo "\n--------------\nSnippet no. $key:\n--------------\n"; + $code = escapeshellarg($code); + echo `echo $code | "$php" -a`, "\n"; +} + +echo "\nDone\n"; +?> +--XFAIL-- +https://bugs.php.net/bug.php?id=55496 +--EXPECTF-- +-------------- +Snippet no. 1: +-------------- +Interactive shell + +php > echo 'Hello world'; +Hello world +php > exit + + +-------------- +Snippet no. 2: +-------------- +Interactive shell + +php > echo 'multine +php ' single +php ' quote'; +multine +single +quote +php > exit + + +-------------- +Snippet no. 3: +-------------- +Interactive shell + +php > echo <<<HEREDOC +<<< > Here +<<< > comes +<<< > the +<<< > doc +<<< > HEREDOC; +Here +comes +the +doc +php > + +-------------- +Snippet no. 4: +-------------- +Interactive shell + +php > if (0) { +php { echo "I'm not there"; +php { } +php > echo "Done"; +Done +php > + +-------------- +Snippet no. 5: +-------------- +Interactive shell + +php > function a_function_with_some_name() { +php { echo "I was called!"; +php { } +php > a_function_with_some_name(); +I was called! +php > + +Done diff --git a/sapi/cli/tests/017.phpt b/sapi/cli/tests/017.phpt new file mode 100644 index 0000000..efaf977 --- /dev/null +++ b/sapi/cli/tests/017.phpt @@ -0,0 +1,106 @@ +--TEST-- +CLI -a and libedit +--SKIPIF-- +<?php +include "skipif.inc"; +if (!extension_loaded('readline') || readline_info('done') !== NULL) { + die ("skip need readline support using libedit"); +} +?> +--FILE-- +<?php +$php = getenv('TEST_PHP_EXECUTABLE'); + +$codes = array(); + +$codes[1] = <<<EOT +echo 'Hello world'; +exit +EOT; + +$codes[] = <<<EOT +echo 'multine +single +quote'; +exit +EOT; + +$codes[] = <<<EOT +echo <<<HEREDOC +Here +comes +the +doc +HEREDOC; +EOT; + +$codes[] = <<<EOT +if (0) { + echo "I'm not there"; +} +echo "Done"; +EOT; + +$codes[] = <<<EOT +function a_function_with_some_name() { + echo "I was called!"; +} +a_function_w ); +EOT; + +foreach ($codes as $key => $code) { + echo "\n--------------\nSnippet no. $key:\n--------------\n"; + $code = escapeshellarg($code); + echo `echo $code | "$php" -a`, "\n"; +} + +echo "\nDone\n"; +?> +--EXPECTF-- +-------------- +Snippet no. 1: +-------------- +Interactive shell + +Hello world + + +-------------- +Snippet no. 2: +-------------- +Interactive shell + +multine +single +quote + + +-------------- +Snippet no. 3: +-------------- +Interactive shell + +Here +comes +the +doc + + +-------------- +Snippet no. 4: +-------------- +Interactive shell + +Done + + +-------------- +Snippet no. 5: +-------------- +Interactive shell + + +Parse error: syntax error, unexpected ')' in php shell code on line 1 + + +Done diff --git a/sapi/cli/tests/018.phpt b/sapi/cli/tests/018.phpt new file mode 100644 index 0000000..56921bd --- /dev/null +++ b/sapi/cli/tests/018.phpt @@ -0,0 +1,27 @@ +--TEST-- +CLI php -m +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + + +echo `"$php" -n -m`; + +echo "Done\n"; +?> +--EXPECTF-- +[PHP Modules] +%a +pcre +%a + +[Zend Modules] +%aDone diff --git a/sapi/cli/tests/019.phpt b/sapi/cli/tests/019.phpt new file mode 100644 index 0000000..c98155d --- /dev/null +++ b/sapi/cli/tests/019.phpt @@ -0,0 +1,36 @@ +--TEST-- +CLI php -i +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + + +echo `"$php" -n -i`; + +echo "\nDone\n"; +?> +--EXPECTF-- +phpinfo() +PHP Version => %s +%a +PHP License +This program is free software; you can redistribute it and/or modify +it under the terms of the PHP License as published by the PHP Group +and included in the distribution in the file: LICENSE + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +If you did not receive a copy of the PHP license, or have any +questions about PHP licensing, please contact license@php.net. + +Done diff --git a/sapi/cli/tests/020.phpt b/sapi/cli/tests/020.phpt new file mode 100644 index 0000000..62be4ba --- /dev/null +++ b/sapi/cli/tests/020.phpt @@ -0,0 +1,32 @@ +--TEST-- +CLI php --ri +--SKIPIF-- +<?php +include "skipif.inc"; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + + +echo `"$php" -n --ri this_extension_does_not_exist_568537753423`; +echo `"$php" -n --ri standard`; + +echo "\nDone\n"; +?> +--EXPECTF-- +Extension 'this_extension_does_not_exist_568537753423' not present. + +standard + +%a + +Directive => Local Value => Master Value +%a + +Done + diff --git a/sapi/cli/tests/021.phpt b/sapi/cli/tests/021.phpt new file mode 100644 index 0000000..2ddd688 --- /dev/null +++ b/sapi/cli/tests/021.phpt @@ -0,0 +1,43 @@ +--TEST-- +CLI shell shebang +--SKIPIF-- +<?php +include 'skipif.inc'; +if (substr(PHP_OS, 0, 3) == 'WIN') { + die ("skip not for Windows"); +} + +if (strlen("#!".getenv('TEST_PHP_EXECUTABLE')) > 127) { + die ("skip shebang is too long, see http://www.in-ulm.de/~mascheck/various/shebang/#results"); +} +?> +--FILE-- +<?php + +$php = getenv('TEST_PHP_EXECUTABLE'); + +$filename = __DIR__.'/021.tmp.php'; + +$script = "#!$php -n\n". + "ola\n". + "<?php echo 1+1,'\n';\n". + "?>\n". + "adeus\n"; + +file_put_contents($filename, $script); +chmod($filename, 0777); + +echo `$filename`; + +echo "\nDone\n"; +?> +--CLEAN-- +<?php +unlink(__DIR__.'/021.tmp.php'); +?> +--EXPECTF-- +ola +2 +adeus + +Done diff --git a/sapi/cli/tests/022.inc b/sapi/cli/tests/022.inc new file mode 100644 index 0000000..b77512f --- /dev/null +++ b/sapi/cli/tests/022.inc @@ -0,0 +1,14 @@ +<?php + +ob_start(); +var_dump(STDIN); + +$fd = fopen("php://stdin","r"); +var_dump($fd); + +$client_socket = stream_socket_accept($fd); + +$data = ob_get_clean(); +fwrite($client_socket, $data); + +?> diff --git a/sapi/cli/tests/022.phpt b/sapi/cli/tests/022.phpt new file mode 100644 index 0000000..0110220 --- /dev/null +++ b/sapi/cli/tests/022.phpt @@ -0,0 +1,47 @@ +--TEST-- +STDIN/OUT/ERR stream type +--SKIPIF-- +<?php +if (!getenv("TEST_PHP_EXECUTABLE")) die("skip TEST_PHP_EXECUTABLE not set"); +if (substr(PHP_OS, 0, 3) == "WIN") die("skip non windows test"); +?> +--FILE-- +<?php +$php = getenv("TEST_PHP_EXECUTABLE"); +$socket_file = tempnam(sys_get_temp_dir(), pathinfo(__FILE__, PATHINFO_FILENAME) . '.sock'); +$test_file = dirname(__FILE__) . '/' . pathinfo(__FILE__, PATHINFO_FILENAME) . '.inc'; +if (file_exists($socket_file)) { + unlink($socket_file); +} +$socket = stream_socket_server('unix://' . $socket_file); +var_dump($socket); +if (!$socket) { + exit(1); +} +$desc = array( + 0 => $socket, + 1 => STDOUT, + 2 => STDERR, +); +$pipes = array(); +$proc = proc_open("$php -n " . escapeshellarg($test_file), $desc, $pipes); +var_dump($proc); +if (!$proc) { + exit(1); +} + +$client_socket = stream_socket_client('unix://' . $socket_file); +var_dump($client_socket); +echo stream_get_contents($client_socket); +fclose($client_socket); + +proc_terminate($proc); +proc_close($proc); +unlink($socket_file); +?> +--EXPECTF-- +resource(%d) of type (stream) +resource(%d) of type (process) +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) diff --git a/sapi/cli/tests/bug43177.phpt b/sapi/cli/tests/bug43177.phpt new file mode 100644 index 0000000..36b5504 --- /dev/null +++ b/sapi/cli/tests/bug43177.phpt @@ -0,0 +1,82 @@ +--TEST-- +Bug #61977 Test exit code for various errors +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'SCRIPT' + ini_set('display_errors', 0); + switch($_SERVER["REQUEST_URI"]) { + case "/parse": + eval("this is a parse error"); + echo "OK\n"; + break; + case "/fatal": + eval("foo();"); + echo "OK\n"; + break; + case "/compile": + eval("class foo { final private final function bar() {} }"); + echo "OK\n"; + break; + case "/fatal2": + foo(); + echo "OK\n"; + break; + default: + return false; + } +SCRIPT +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +foreach(array("parse", "fatal", "fatal2", "compile") as $url) { + $fp = fsockopen($host, $port, $errno, $errstr, 0.5); + if (!$fp) { + die("connect failed"); + } + + if(fwrite($fp, <<<HEADER +GET /$url HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } + } +} + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: localhost +Connection: close +X-Powered-By: %s +Content-type: text/html + +OK +HTTP/1.0 500 Internal Server Error +Host: localhost +Connection: close +X-Powered-By: %s +Content-type: text/html + +HTTP/1.0 500 Internal Server Error +Host: localhost +Connection: close +X-Powered-By: %s +Content-type: text/html + +HTTP/1.0 500 Internal Server Error +Host: localhost +Connection: close +X-Powered-By: %s +Content-type: text/html diff --git a/sapi/cli/tests/bug44564.phpt b/sapi/cli/tests/bug44564.phpt new file mode 100644 index 0000000..7dca62a --- /dev/null +++ b/sapi/cli/tests/bug44564.phpt @@ -0,0 +1,22 @@ +--TEST-- +Bug #44564 (escapeshellarg removes UTF-8 multi-byte characters) +--SKIPIF-- +<?php +if (false == setlocale(LC_CTYPE, "UTF8", "en_US.UTF-8")) { + die("skip setlocale() failed\n"); +} +?> +--FILE-- +<?php +setlocale(LC_CTYPE, "UTF8", "en_US.UTF-8"); +var_dump(escapeshellcmd('f{o}<€>')); +var_dump(escapeshellarg('f~|;*Þ?')); +var_dump(escapeshellcmd('?€®đæ?')); +var_dump(escapeshellarg('aŊł€')); + +?> +--EXPECT-- +string(13) "f\{o\}\<€\>" +string(10) "'f~|;*Þ?'" +string(13) "\?€®đæ\?" +string(10) "'aŊł€'" diff --git a/sapi/cli/tests/bug61546.phpt b/sapi/cli/tests/bug61546.phpt new file mode 100644 index 0000000..071edb7 --- /dev/null +++ b/sapi/cli/tests/bug61546.phpt @@ -0,0 +1,31 @@ +--TEST-- +Bug #61546 (functions related to current script failed when chdir() in cli sapi) +--FILE-- +<?php +// reference doc for getmyinode() on php.net states that it returns an integer or FALSE on error +// on Windows, getmyinode() returns 0 which normally casts to FALSE +// however, the implementation of getmyinode() (in pageinfo.c) returns an explicit FALSE in the +// event that the internal page_inode structure is less than 0, otherwise it returns the long value +// of page_inode. therefore, an explicit 0 should be a passing value for this test. +// +// the ext/standard/tests/file/statpage.phpt test also tests getmyinode() returns an integer and will +// pass even if that integer is 0. on Windows, the getmyinode() call in statpage.phpt returns 0 and +// passes on Windows. +$php = getenv("TEST_PHP_EXECUTABLE"); +$test_code = <<<PHP +<?php +chdir('..'); +var_dump(get_current_user() != ""); +chdir('..'); +var_dump(getmyinode() !== false); +var_dump(getlastmod() != false); +PHP; + +file_put_contents("bug61546_sub.php", $test_code); +system($php . ' -n bug61546_sub.php'); +unlink("bug61546_sub.php"); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/sapi/cli/tests/bug61679.phpt b/sapi/cli/tests/bug61679.phpt new file mode 100644 index 0000000..819ce2f --- /dev/null +++ b/sapi/cli/tests/bug61679.phpt @@ -0,0 +1,43 @@ +--TEST-- +Bug #61679 (Error on non-standard HTTP methods) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'PHP' +echo "This should never echo"; +PHP +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +// Send a request with a fictitious request method, +// I like smurfs, the smurf everything. +if(fwrite($fp, <<<HEADER +SMURF / HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + // Only echo the first line from the response, + // the rest is not interesting + break; + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 501 Not Implemented diff --git a/sapi/cli/tests/bug61977.phpt b/sapi/cli/tests/bug61977.phpt new file mode 100644 index 0000000..09a6ba6 --- /dev/null +++ b/sapi/cli/tests/bug61977.phpt @@ -0,0 +1,55 @@ +--TEST-- +Bug #61977 test CLI web-server support for Mime Type File extensions mapping +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('<?php ?>', true); + +/* + * If a Mime Type is added in php_cli_server.c, add it to this array and update + * the EXPECTF section accordingly + */ +$mimetypes = ['html', 'htm', 'svg', 'css', 'js', 'png', 'webm', 'ogv', 'ogg']; + +function test_mimetypes($mimetypes) { + foreach ($mimetypes as $mimetype) { + list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); + $port = intval($port) ? : 80; + $fp = fsockopen($host, $port, $errno, $errstr, 0.5); + if (!$fp) die('Connect failed'); + file_put_contents(__DIR__ . "/foo.{$mimetype}", ''); + $header = <<<HEADER +GET /foo.{$mimetype} HTTP/1.1 +Host: {$host} + + +HEADER; + if (fwrite($fp, $header)) { + while (!feof($fp)) { + $text = fgets($fp); + if (strncasecmp("Content-type:", $text, 13) == 0) { + echo "foo.{$mimetype} => ", $text; + } + } + @unlink(__DIR__ . "/foo.{$mimetype}"); + fclose($fp); + } + } +} + +test_mimetypes($mimetypes); +?> +--EXPECTF-- +foo.html => Content-Type: text/html; charset=UTF-8 +foo.htm => Content-Type: text/html; charset=UTF-8 +foo.svg => Content-Type: image/svg+xml +foo.css => Content-Type: text/css; charset=UTF-8 +foo.js => Content-Type: text/javascript; charset=UTF-8 +foo.png => Content-Type: image/png +foo.webm => Content-Type: video/webm +foo.ogv => Content-Type: video/ogg +foo.ogg => Content-Type: audio/ogg diff --git a/sapi/cli/tests/php_cli_server.inc b/sapi/cli/tests/php_cli_server.inc new file mode 100644 index 0000000..40c5361 --- /dev/null +++ b/sapi/cli/tests/php_cli_server.inc @@ -0,0 +1,62 @@ +<?php +define ("PHP_CLI_SERVER_HOSTNAME", "localhost"); +define ("PHP_CLI_SERVER_PORT", 8964); +define ("PHP_CLI_SERVER_ADDRESS", PHP_CLI_SERVER_HOSTNAME.":".PHP_CLI_SERVER_PORT); + +function php_cli_server_start($code = 'echo "Hello world";', $no_router = FALSE) { + $php_executable = getenv('TEST_PHP_EXECUTABLE'); + $doc_root = __DIR__; + $router = "index.php"; + + if ($code) { + file_put_contents($doc_root . '/' . $router, '<?php ' . $code . ' ?>'); + } + + $descriptorspec = array( + 0 => STDIN, + 1 => STDOUT, + 2 => STDERR, + ); + + if (substr(PHP_OS, 0, 3) == 'WIN') { + $cmd = "{$php_executable} -t {$doc_root} -n -S " . PHP_CLI_SERVER_ADDRESS; + if (!$no_router) { + $cmd .= " {$router}"; + } + + $handle = proc_open(addslashes($cmd), $descriptorspec, $pipes, $doc_root, NULL, array("bypass_shell" => true, "suppress_errors" => true)); + } else { + $cmd = "exec {$php_executable} -t {$doc_root} -n -S " . PHP_CLI_SERVER_ADDRESS; + if (!$no_router) { + $cmd .= " {$router}"; + } + $cmd .= " 2>/dev/null"; + + $handle = proc_open($cmd, $descriptorspec, $pipes, $doc_root); + } + + // note: even when server prints 'Listening on localhost:8964...Press Ctrl-C to quit.' + // it might not be listening yet...need to wait until fsockopen() call returns + $i = 0; + while (($i++ < 30) && !($fp = @fsockopen(PHP_CLI_SERVER_HOSTNAME, PHP_CLI_SERVER_PORT))) { + usleep(10000); + } + + if ($fp) { + fclose($fp); + } + + register_shutdown_function( + function($handle) use($router) { + proc_terminate($handle); + @unlink(__DIR__ . "/{$router}"); + }, + $handle + ); + // don't bother sleeping, server is already up + // server can take a variable amount of time to be up, so just sleeping a guessed amount of time + // does not work. this is why tests sometimes pass and sometimes fail. to get a reliable pass + // sleeping doesn't work. +} +?> + diff --git a/sapi/cli/tests/php_cli_server_001.phpt b/sapi/cli/tests/php_cli_server_001.phpt new file mode 100644 index 0000000..3f1083e --- /dev/null +++ b/sapi/cli/tests/php_cli_server_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +basic function +--INI-- +allow_url_fopen=1 +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(); +var_dump(file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS)); +?> +--EXPECT-- +string(11) "Hello world" diff --git a/sapi/cli/tests/php_cli_server_002.phpt b/sapi/cli/tests/php_cli_server_002.phpt new file mode 100644 index 0000000..93151c1 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_002.phpt @@ -0,0 +1,20 @@ +--TEST-- +$_SERVER variable +--INI-- +allow_url_fopen=1 +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_SERVER["DOCUMENT_ROOT"], $_SERVER["SERVER_SOFTWARE"], $_SERVER["SERVER_NAME"], $_SERVER["SERVER_PORT"]);'); +var_dump(file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS)); +?> +--EXPECTF-- +string(%d) "string(%d) "%stests" +string(%d) "PHP %s Development Server" +string(%d) "localhost" +string(%d) "8964" +" diff --git a/sapi/cli/tests/php_cli_server_003.phpt b/sapi/cli/tests/php_cli_server_003.phpt new file mode 100644 index 0000000..d1e95fe --- /dev/null +++ b/sapi/cli/tests/php_cli_server_003.phpt @@ -0,0 +1,18 @@ +--TEST-- +Bug #55726 (Changing the working directory makes router script inaccessible) +--INI-- +allow_url_fopen=1 +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('chdir(__DIR__); echo "okey";'); +var_dump(file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS)); +var_dump(file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS)); +?> +--EXPECTF-- +string(4) "okey" +string(4) "okey" diff --git a/sapi/cli/tests/php_cli_server_004.phpt b/sapi/cli/tests/php_cli_server_004.phpt new file mode 100644 index 0000000..b61f886 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_004.phpt @@ -0,0 +1,48 @@ +--TEST-- +Bug #55747 (request headers missed in $_SERVER) +--INI-- +allow_url_fopen=1 +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('foreach($_SERVER as $k=>$v) { if (!strncmp($k, "HTTP", 4)) var_dump( $k . ":" . $v); }'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.1 +Host:{$host} +User-Agent:dummy +Custom:foo +Referer:http://www.php.net/ + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(19) "HTTP_HOST:localhost" +string(21) "HTTP_USER_AGENT:dummy" +string(15) "HTTP_CUSTOM:foo" +string(32) "HTTP_REFERER:http://www.php.net/" diff --git a/sapi/cli/tests/php_cli_server_005.phpt b/sapi/cli/tests/php_cli_server_005.phpt new file mode 100644 index 0000000..ccc0f8f --- /dev/null +++ b/sapi/cli/tests/php_cli_server_005.phpt @@ -0,0 +1,71 @@ +--TEST-- +Post a file +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_FILES);'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +$post_data = <<<POST +-----------------------------114782935826962 +Content-Disposition: form-data; name="userfile"; filename="laruence.txt" +Content-Type: text/plain + +I am not sure about this. + +-----------------------------114782935826962-- + + +POST; + +$post_len = strlen($post_data); + +if(fwrite($fp, <<<HEADER +POST / HTTP/1.1 +Host: {$host} +Content-Type: multipart/form-data; boundary=---------------------------114782935826962 +Content-Length: {$post_len} + + +{$post_data} +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +array(1) { + ["userfile"]=> + array(5) { + ["name"]=> + string(12) "laruence.txt" + ["type"]=> + string(10) "text/plain" + ["tmp_name"]=> + string(%d) "%s" + ["error"]=> + int(0) + ["size"]=> + int(26) + } +} diff --git a/sapi/cli/tests/php_cli_server_006.phpt b/sapi/cli/tests/php_cli_server_006.phpt new file mode 100644 index 0000000..09e7ab0 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_006.phpt @@ -0,0 +1,42 @@ +--TEST-- +Bug #55755 (SegFault when outputting header WWW-Authenticate) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_SERVER["PHP_AUTH_USER"], $_SERVER["PHP_AUTH_PW"]);'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.1 +Host: {$host} +Authorization: Basic Zm9vOmJhcg== + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(3) "foo" +string(3) "bar" diff --git a/sapi/cli/tests/php_cli_server_007.phpt b/sapi/cli/tests/php_cli_server_007.phpt new file mode 100644 index 0000000..64d4df0 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_007.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug #55758 (Digest Authenticate missed in 5.4) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('header(\'WWW-Authenticate: Digest realm="foo",qop="auth",nonce="XXXXX",opaque="'.md5("foo").'"\');'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.1 +Host: {$host} +Authorization: Basic Zm9vOmJhcg== + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +?> +--EXPECTF-- +HTTP/1.1 401 Unauthorized +Host: %s +Connection: close +X-Powered-By: PHP/%s +WWW-Authenticate: Digest realm="foo",qop="auth",nonce="XXXXX",opaque="acbd18db4cc2f85cedef654fccc4a4d8" +Content-type: text/html diff --git a/sapi/cli/tests/php_cli_server_008.phpt b/sapi/cli/tests/php_cli_server_008.phpt new file mode 100644 index 0000000..2e68e24 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_008.phpt @@ -0,0 +1,68 @@ +--TEST-- +SERVER_PROTOCOL header availability +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_SERVER["SERVER_PROTOCOL"]);'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + + +if(fwrite($fp, <<<HEADER +GET / HTTP/1.0 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(8) "HTTP/1.1" +HTTP/1.0 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(8) "HTTP/1.0" diff --git a/sapi/cli/tests/php_cli_server_009.phpt b/sapi/cli/tests/php_cli_server_009.phpt new file mode 100644 index 0000000..2beaeed --- /dev/null +++ b/sapi/cli/tests/php_cli_server_009.phpt @@ -0,0 +1,93 @@ +--TEST-- +PATH_INFO (relevant to #60112) +--DESCRIPTION-- +After this fix(#60112), previously 404 request like "localhost/foo/bar" +now could serve correctly with request_uri "index.php" and PATH_INFO "/foo/bar/" +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_SERVER["PATH_INFO"]);', TRUE); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET /foo/bar HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + + +if(fwrite($fp, <<<HEADER +GET /foo/bar/ HTTP/1.0 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + + +if(fwrite($fp, <<<HEADER +GET /foo/bar.js HTTP/1.0 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + break; + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(8) "/foo/bar" +HTTP/1.0 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(9) "/foo/bar/" +HTTP/1.0 404 Not Found diff --git a/sapi/cli/tests/php_cli_server_010.phpt b/sapi/cli/tests/php_cli_server_010.phpt new file mode 100644 index 0000000..2ef018b --- /dev/null +++ b/sapi/cli/tests/php_cli_server_010.phpt @@ -0,0 +1,75 @@ +--TEST-- +Bug #60180 ($_SERVER["PHP_SELF"] incorrect) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('var_dump($_SERVER["PHP_SELF"], $_SERVER["SCRIPT_NAME"], $_SERVER["PATH_INFO"], $_SERVER["QUERY_STRING"]);', TRUE); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET /foo/bar?foo=bar HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + + +if(fwrite($fp, <<<HEADER +GET /index.php/foo/bar/?foo=bar HTTP/1.0 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(18) "/index.php/foo/bar" +string(10) "/index.php" +string(8) "/foo/bar" +string(7) "foo=bar" +HTTP/1.0 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +string(19) "/index.php/foo/bar/" +string(10) "/index.php" +string(9) "/foo/bar/" +string(7) "foo=bar" diff --git a/sapi/cli/tests/php_cli_server_011.phpt b/sapi/cli/tests/php_cli_server_011.phpt new file mode 100644 index 0000000..a957a8e --- /dev/null +++ b/sapi/cli/tests/php_cli_server_011.phpt @@ -0,0 +1,41 @@ +--TEST-- +Bug #60180 ($_SERVER["PHP_SELF"] incorrect) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('sytanx error;', TRUE); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +$logo_id = php_logo_guid(); + +if(fwrite($fp, <<<HEADER +GET /?={$logo_id} HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + if (("Content-Type: image/gif") == trim(fgets($fp))) { + echo "okey"; + break; + } + } +} + +fclose($fp); + +?> +--EXPECTF-- +okey diff --git a/sapi/cli/tests/php_cli_server_012.phpt b/sapi/cli/tests/php_cli_server_012.phpt new file mode 100644 index 0000000..9a1e60c --- /dev/null +++ b/sapi/cli/tests/php_cli_server_012.phpt @@ -0,0 +1,55 @@ +--TEST-- +Bug #60159 (Router returns false, but POST is not passed to requested resource) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('print_r($_REQUEST); $_REQUEST["foo"] = "bar"; return FALSE;'); +$doc_root = __DIR__; +file_put_contents($doc_root . '/request.php', '<?php print_r($_REQUEST); ?>'); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +POST /request.php HTTP/1.1 +Host: {$host} +Content-Type: application/x-www-form-urlencoded +Content-Length: 3 + +a=b +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); +@unlink($doc_root . '/request.php'); + +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +Array +( + [a] => b +) +Array +( + [a] => b + [foo] => bar +) diff --git a/sapi/cli/tests/php_cli_server_013.phpt b/sapi/cli/tests/php_cli_server_013.phpt new file mode 100644 index 0000000..570798a --- /dev/null +++ b/sapi/cli/tests/php_cli_server_013.phpt @@ -0,0 +1,108 @@ +--TEST-- +No router, no script +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(NULL, TRUE); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; +$output = ''; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + + +if(fwrite($fp, <<<HEADER +POST / HTTP/1.1 +Host: {$host} +Content-Type: application/x-www-form-urlencoded +Content-Length: 3 + +a=b +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} + +echo preg_replace("/<style>(.*?)<\/style>/s", "<style>AAA</style>", $output), "\n"; +fclose($fp); + + +$output = ''; +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET /main/style.css HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} + +echo preg_replace("/<style>(.*?)<\/style>/s", "<style>AAA</style>", $output), "\n"; +fclose($fp); + +$output = ''; +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +HEAD /main/foo/bar HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} + +echo preg_replace("/<style>(.*?)<\/style>/s", "<style>AAA</style>", $output), "\n"; +fclose($fp); +?> +--EXPECTF-- + +HTTP/1.1 404 Not Found +Host: %s +Connection: close +Content-Type: text/html; charset=UTF-8 +Content-Length: %d + +<!doctype html><html><head><title>404 Not Found</title><style>AAA</style> +</head><body><h1>Not Found</h1><p>The requested resource / was not found on this server.</p></body></html> +HTTP/1.1 404 Not Found +Host: %s +Connection: close +Content-Type: text/html; charset=UTF-8 +Content-Length: %d + +<!doctype html><html><head><title>404 Not Found</title><style>AAA</style> +</head><body><h1>Not Found</h1><p>The requested resource /main/style.css was not found on this server.</p></body></html> +HTTP/1.1 404 Not Found +Host: %s +Connection: close +Content-Type: text/html; charset=UTF-8 +Content-Length: %d + +<!doctype html><html><head><title>404 Not Found</title><style>AAA</style> +</head><body><h1>Not Found</h1><p>The requested resource /main/foo/bar was not found on this server.</p></body></html> + diff --git a/sapi/cli/tests/php_cli_server_014.phpt b/sapi/cli/tests/php_cli_server_014.phpt new file mode 100644 index 0000000..f8a9905 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_014.phpt @@ -0,0 +1,80 @@ +--TEST-- +Bug #60477: Segfault after two multipart/form-data POST requestes +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('echo done, "\n";', TRUE); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; +$output = ''; + +// note: select() on Windows (& some other platforms) has historical issues with +// timeouts less than 1000 millis(0.5). it may be better to increase these +// timeouts to 1000 millis(1.0) (fsockopen eventually calls select()). +// see articles like: http://support.microsoft.com/kb/257821 +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +POST /index.php HTTP/1.1 +Host: {$host} +Content-Type: multipart/form-data; boundary=---------123456789 +Content-Length: 70 + +---------123456789 +Content-Type: application/x-www-form-urlencoded +a=b +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} + +fclose($fp); + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if(fwrite($fp, <<<HEADER +POST /main/no-exists.php HTTP/1.1 +Host: {$host} +Content-Type: multipart/form-data; boundary=---------123456789 +Content-Length: 70 + +---------123456789 +Content-Type: application/x-www-form-urlencoded +a=b +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} + +echo preg_replace("/<style>(.*?)<\/style>/s", "<style>AAA</style>", $output), "\n"; +fclose($fp); + +?> +--EXPECTF-- + +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: %s +Content-type: %s + +done +HTTP/1.1 404 Not Found +Host: %s +Connection: close +Content-Type: %s +Content-Length: %d + +<!doctype html><html><head><title>404 Not Found</title><style>AAA</style> +</head><body><h1>Not Found</h1><p>The requested resource /main/no-exists.php was not found on this server.</p></body></html> diff --git a/sapi/cli/tests/php_cli_server_015.phpt b/sapi/cli/tests/php_cli_server_015.phpt new file mode 100644 index 0000000..6fb0169 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_015.phpt @@ -0,0 +1,49 @@ +--TEST-- +Bug #60523 (PHP Errors are not reported in browsers using built-in SAPI) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--INI-- +display_errors=1 +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start('require("syntax_error.php");'); +$dir = realpath(dirname(__FILE__)); + +file_put_contents($dir . "/syntax_error.php", "<?php non_exists_function(); ?>"); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; +$output = ''; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +GET /index.php HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + $output .= fgets($fp); + } +} +echo $output; +@unlink($dir . "/syntax_error.php"); +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: PHP/%s +Content-type: text/html + +<br /> +<b>Fatal error</b>: Call to undefined function non_exists_function() in <b>%ssyntax_error.php</b> on line <b>%s</b><br /> diff --git a/sapi/cli/tests/php_cli_server_016.phpt b/sapi/cli/tests/php_cli_server_016.phpt new file mode 100644 index 0000000..f15aff1 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_016.phpt @@ -0,0 +1,46 @@ +--TEST-- +Bug #60591 (Memory leak when access a non-exists file) +--DESCRIPTION-- +this is an indirect test for bug 60591, since mem leak is reproted in the server side +and require php compiled with --enable-debug +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'PHP' +if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) + return false; // serve the requested resource as-is. +else { + echo "here"; +} +PHP +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +POST /no-exists.jpg HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + break; + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 404 Not Found diff --git a/sapi/cli/tests/php_cli_server_017.phpt b/sapi/cli/tests/php_cli_server_017.phpt new file mode 100644 index 0000000..73530af --- /dev/null +++ b/sapi/cli/tests/php_cli_server_017.phpt @@ -0,0 +1,44 @@ +--TEST-- +Implement Req #60850 (Built in web server does not set $_SERVER['SCRIPT_FILENAME'] when using router) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'PHP' +var_dump($_SERVER['SCRIPT_FILENAME']); +PHP +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +POST / HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: %s +Content-type: text/html + +string(%d) "%sindex.php" diff --git a/sapi/cli/tests/php_cli_server_018.phpt b/sapi/cli/tests/php_cli_server_018.phpt new file mode 100644 index 0000000..deb9348 --- /dev/null +++ b/sapi/cli/tests/php_cli_server_018.phpt @@ -0,0 +1,44 @@ +--TEST-- +Implement Req #61679 (Support HTTP PATCH method) +--SKIPIF-- +<?php +include "skipif.inc"; +?> +--FILE-- +<?php +include "php_cli_server.inc"; +php_cli_server_start(<<<'PHP' +var_dump($_SERVER['REQUEST_METHOD']); +PHP +); + +list($host, $port) = explode(':', PHP_CLI_SERVER_ADDRESS); +$port = intval($port)?:80; + +$fp = fsockopen($host, $port, $errno, $errstr, 0.5); +if (!$fp) { + die("connect failed"); +} + +if(fwrite($fp, <<<HEADER +PATCH / HTTP/1.1 +Host: {$host} + + +HEADER +)) { + while (!feof($fp)) { + echo fgets($fp); + } +} + +fclose($fp); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Host: %s +Connection: close +X-Powered-By: %s +Content-type: text/html + +string(5) "PATCH" diff --git a/sapi/cli/tests/skipif.inc b/sapi/cli/tests/skipif.inc new file mode 100644 index 0000000..79e6c91 --- /dev/null +++ b/sapi/cli/tests/skipif.inc @@ -0,0 +1,7 @@ +<?php + +if (php_sapi_name() != "cli") { + die("skip CLI only"); +} + +?> diff --git a/sapi/continuity/CREDITS b/sapi/continuity/CREDITS new file mode 100644 index 0000000..35335e9 --- /dev/null +++ b/sapi/continuity/CREDITS @@ -0,0 +1,2 @@ +Continuity +Alex Leigh (based on nsapi code) diff --git a/sapi/continuity/capi.c b/sapi/continuity/capi.c new file mode 100644 index 0000000..c635b52 --- /dev/null +++ b/sapi/continuity/capi.c @@ -0,0 +1,508 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Alex Leigh <php (at) postfin (dot) com> | + +----------------------------------------------------------------------+ +*/ + +/* For more information on Continuity: http://www.ashpool.com/ */ + +/* + * This code is based on the PHP5 SAPI module for NSAPI by Jayakumar + * Muthukumarasamy + */ + +/* PHP includes */ +#define CONTINUITY 1 +#define CAPI_DEBUG + +/* Define for CDP specific extensions */ +#undef CONTINUITY_CDPEXT + +#include "php.h" +#include "php_variables.h" +#include "ext/standard/info.h" +#include "php_ini.h" +#include "php_globals.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_version.h" +#include "TSRM.h" +#include "ext/standard/php_standard.h" + +/* + * CAPI includes + */ +#include <continuity.h> +#include <http.h> + +#define NSLS_D struct capi_request_context *request_context +#define NSLS_DC , NSLS_D +#define NSLS_C request_context +#define NSLS_CC , NSLS_C +#define NSG(v) (request_context->v) + +/* + * ZTS needs to be defined for CAPI to work + */ +#if !defined(ZTS) +#error "CAPI module needs ZTS to be defined" +#endif + +/* + * Structure to encapsulate the CAPI request in SAPI + */ +typedef struct capi_request_context { + httpTtrans *t; + int read_post_bytes; +} capi_request_context; + +/**************/ + +PHP_MINIT_FUNCTION(continuity); +PHP_MSHUTDOWN_FUNCTION(continuity); +PHP_RINIT_FUNCTION(continuity); +PHP_RSHUTDOWN_FUNCTION(continuity); +PHP_MINFO_FUNCTION(continuity); + +PHP_FUNCTION(continuity_virtual); +PHP_FUNCTION(continuity_request_headers); +PHP_FUNCTION(continuity_response_headers); + +const zend_function_entry continuity_functions[] = { + {NULL, NULL, NULL} +}; + +zend_module_entry continuity_module_entry = { + STANDARD_MODULE_HEADER, + "continuity", + continuity_functions, + PHP_MINIT(continuity), + PHP_MSHUTDOWN(continuity), + NULL, + NULL, + PHP_MINFO(continuity), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +PHP_MINIT_FUNCTION(continuity) +{ + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(continuity) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(continuity) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "Continuity Module Revision", "$Id: 26762a5a963c1eb2e5bf91efbccadce7a8b2e40f $"); + php_info_print_table_row(2, "Server Version", conFget_build()); +#ifdef CONTINUITY_CDPEXT + php_info_print_table_row(2,"CDP Extensions", "enabled"); +#else + php_info_print_table_row(2,"CDP Extensions", "disabled"); +#endif + php_info_print_table_end(); + +/* DISPLAY_INI_ENTRIES(); */ +} + +/**************/ + +/* + * sapi_capi_ub_write: Write len bytes to the connection output. + */ +static int sapi_capi_ub_write(const char *str, unsigned int str_length TSRMLS_DC) +{ + int retval; + capi_request_context *rc; + + rc = (capi_request_context *) SG(server_context); + retval = httpFwrite(rc->t, (char *) str, str_length); + if (retval == -1 || retval == 0) + php_handle_aborted_connection(); + return retval; +} + +/* + * sapi_capi_header_handler: Add/update response headers with those provided + * by the PHP engine. + */ +static int sapi_capi_header_handler(sapi_header_struct * sapi_header, sapi_headers_struct * sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + capi_request_context *rc = (capi_request_context *) SG(server_context); + + lstFset_delete_key(rc->t->res_hdrs, "Content-Type"); + + header_name = sapi_header->header; + header_content = p = strchr(header_name, ':'); + if (p == NULL) { + return 0; + } + *p = 0; + do { + header_content++; + } while (*header_content == ' '); + + lstFset_add(rc->t->res_hdrs, header_name, header_content); + + *p = ':'; /* restore '*p' */ + + efree(sapi_header->header); + + return 0; /* don't use the default SAPI mechanism, CAPI + * duplicates this functionality */ +} + +/* + * sapi_capi_send_headers: Transmit the headers to the client. This has the + * effect of starting the response under Continuity. + */ +static int sapi_capi_send_headers(sapi_headers_struct * sapi_headers TSRMLS_DC) +{ + int retval; + capi_request_context *rc = (capi_request_context *) SG(server_context); + + /* + * We could probably just do this in the header_handler. But, I don't know + * what the implication of doing it there is. + */ + + if (SG(sapi_headers).send_default_content_type) { + /* lstFset_delete_key(rc->t->res_hdrs, "Content-Type"); */ + lstFset_update(rc->t->res_hdrs, "Content-Type", "text/html"); + } + httpFset_status(rc->t, SG(sapi_headers).http_response_code, NULL); + httpFstart_response(rc->t); + + return SAPI_HEADER_SENT_SUCCESSFULLY; + +} + +static int sapi_capi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + unsigned int max_read, total_read = 0; + capi_request_context *rc = (capi_request_context *) SG(server_context); + + if (rc->read_post_bytes == -1) { + max_read = MIN(count_bytes, SG(request_info).content_length); + } else { + if (rc->read_post_bytes == 0) + return 0; + max_read = MIN(count_bytes, (SG(request_info).content_length - rc->read_post_bytes)); + } + + total_read = httpFread(rc->t, buffer, max_read); + + if (total_read < 0) + total_read = -1; + else + rc->read_post_bytes = total_read; + + return total_read; +} + +/* + * sapi_capi_read_cookies: Return cookie information into PHP. + */ +static char *sapi_capi_read_cookies(TSRMLS_D) +{ + char *cookie_string; + capi_request_context *rc = (capi_request_context *) SG(server_context); + + cookie_string = lstFset_get(rc->t->req_hdrs, "cookie"); + return cookie_string; +} + +static void sapi_capi_register_server_variables(zval * track_vars_array TSRMLS_DC) +{ + capi_request_context *rc = (capi_request_context *) SG(server_context); + size_t i; + char *value; + char buf[128]; + + /* PHP_SELF and REQUEST_URI */ + value = lstFset_get(rc->t->vars, "uri"); + if (value != NULL) { + php_register_variable("PHP_SELF", value, track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_URI", value, track_vars_array TSRMLS_CC); + } + + /* COUNTRY CODE */ + value = lstFset_get(rc->t->vars, "ccode"); + if(value!=NULL) + php_register_variable("COUNTRY_CODE", value, track_vars_array TSRMLS_CC); + + /* argv */ + value = lstFset_get(rc->t->vars, "query"); + if (value != NULL) + php_register_variable("argv", value, track_vars_array TSRMLS_CC); + + /* GATEWAY_INTERFACE */ + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC); + + /* SERVER_NAME and HTTP_HOST */ + value = lstFset_get(rc->t->req_hdrs, "host"); + if (value != NULL) { + php_register_variable("HTTP_HOST", value, track_vars_array TSRMLS_CC); + /* TODO: This should probably scrub the port value if one is present. */ + php_register_variable("SERVER_NAME", value, track_vars_array TSRMLS_CC); + } + /* SERVER_SOFTWARE */ + value = lstFset_get(rc->t->res_hdrs, "Server"); + if (value != NULL) + php_register_variable("SERVER_SOFTWARE", value, track_vars_array TSRMLS_CC); + + /* SERVER_PROTOCOL */ + value = lstFset_get(rc->t->vars, "protocol"); + if (value != NULL) + php_register_variable("SERVER_PROTOCOL", value, track_vars_array TSRMLS_CC); + + /* REQUEST_METHOD */ + value = lstFset_get(rc->t->vars, "method"); + if (value != NULL) + php_register_variable("REQUEST_METHOD", value, track_vars_array TSRMLS_CC); + + /* QUERY_STRING */ + value = lstFset_get(rc->t->vars, "query"); + if (value != NULL) + php_register_variable("QUERY_STRING", value, track_vars_array TSRMLS_CC); + + /* DOCUMENT_ROOT */ + value = lstFset_get(rc->t->vars, "docroot"); + if (value != NULL) + php_register_variable("DOCUMENT_ROOT", value, track_vars_array TSRMLS_CC); + + /* HTTP_ACCEPT */ + value = lstFset_get(rc->t->req_hdrs, "accept"); + if (value != NULL) + php_register_variable("HTTP_ACCEPT", value, track_vars_array TSRMLS_CC); + + /* HTTP_ACCEPT_CHARSET */ + value = lstFset_get(rc->t->req_hdrs, "accept-charset"); + if (value != NULL) + php_register_variable("HTTP_ACCEPT_CHARSET", value, track_vars_array TSRMLS_CC); + + /* HTTP_ACCEPT_ENCODING */ + value = lstFset_get(rc->t->req_hdrs, "accept-encoding"); + if (value != NULL) + php_register_variable("HTTP_ACCEPT_ENCODING", value, track_vars_array TSRMLS_CC); + + /* HTTP_ACCEPT_LANGUAGE */ + value = lstFset_get(rc->t->req_hdrs, "accept-language"); + if (value != NULL) + php_register_variable("HTTP_ACCEPT_LANGUAGE", value, track_vars_array TSRMLS_CC); + + /* HTTP_CONNECTION */ + value = lstFset_get(rc->t->req_hdrs, "connection"); + if (value != NULL) + php_register_variable("HTTP_CONNECTION", value, track_vars_array TSRMLS_CC); + + /* HTTP_REFERER */ + value = lstFset_get(rc->t->req_hdrs, "referer"); + if (value != NULL) + php_register_variable("HTTP_REFERER", value, track_vars_array TSRMLS_CC); + + /* HTTP_USER_AGENT */ + value = lstFset_get(rc->t->req_hdrs, "user-agent"); + if (value != NULL) + php_register_variable("HTTP_USER_AGENT", value, track_vars_array TSRMLS_CC); + + /* REMOTE_ADDR */ + utlFip_to_str(rc->t->cli_ipv4_addr, buf, sizeof(buf)); + php_register_variable("REMOTE_ADDR", buf, track_vars_array TSRMLS_CC); + + /* REMOTE_PORT */ + + /* SCRIPT_FILENAME and PATH_TRANSLATED */ + value = lstFset_get(rc->t->vars, "path"); + if (value != NULL) { + php_register_variable("SCRIPT_FILENAME", value, track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", value, track_vars_array TSRMLS_CC); + } + /* SERVER_ADMIN */ + /* Not applicable */ + + /* SERVER_PORT */ + +} + +static void capi_log_message(char *message TSRMLS_DC) +{ + capi_request_context *rc = (capi_request_context *) SG(server_context); + logFmsg(0, "mod/php: %s", message); +} + +static int php_capi_startup(sapi_module_struct *sapi_module); + +sapi_module_struct capi_sapi_module = { + "Continuity", /* name */ + "Continuity Server Enterprise Edition", /* pretty name */ + + php_capi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_capi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + sapi_capi_header_handler, /* header handler */ + sapi_capi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_capi_read_post, /* read POST data */ + sapi_capi_read_cookies, /* read Cookies */ + + sapi_capi_register_server_variables, /* register server variables */ + capi_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static int php_capi_startup(sapi_module_struct *sapi_module) { + if(php_module_startup(sapi_module,&continuity_module_entry,1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + + +static char * + capi_strdup(char *str) +{ + if (str != NULL) + return strFcopy(str); + return NULL; +} + +static void capi_free(void *addr) +{ + if (addr != NULL) + free(addr); +} + +static void capi_request_ctor(NSLS_D TSRMLS_DC) +{ + char *query_string = lstFset_get(NSG(t->vars), "query"); + char *uri = lstFset_get(NSG(t->vars), "uri"); + char *path_info = lstFset_get(NSG(t->vars), "path-info"); + char *path_translated = lstFset_get(NSG(t->vars), "path"); + char *request_method = lstFset_get(NSG(t->vars), "method"); + char *content_type = lstFset_get(NSG(t->req_hdrs), "content-type"); + char *content_length = lstFset_get(NSG(t->req_hdrs), "content-length"); + + SG(request_info).query_string = capi_strdup(query_string); + SG(request_info).request_uri = capi_strdup(uri); + SG(request_info).request_method = capi_strdup(request_method); + SG(request_info).path_translated = capi_strdup(path_translated); + SG(request_info).content_type = capi_strdup(content_type); + SG(request_info).content_length = (content_length == NULL) ? 0 : strtoul(content_length, 0, 0); + SG(sapi_headers).http_response_code = 200; +} + +static void capi_request_dtor(NSLS_D TSRMLS_DC) +{ + capi_free(SG(request_info).query_string); + capi_free(SG(request_info).request_uri); + capi_free(SG(request_info).request_method); + capi_free(SG(request_info).path_translated); + capi_free(SG(request_info).content_type); +} + +int capi_module_main(NSLS_D TSRMLS_DC) +{ + zend_file_handle file_handle; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return FAILURE; + } + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + + return SUCCESS; +} + +int phpFinit(lstTset * opt) +{ + php_core_globals *core_globals; + + tsrm_startup(128, 1, 0, NULL); + core_globals = ts_resource(core_globals_id); + + logFmsg(0, "mod/php: PHP Interface v3 (module)"); + logFmsg(0, "mod/php: Copyright (c) 1999-2005 The PHP Group. All rights reserved."); + + sapi_startup(&capi_sapi_module); + capi_sapi_module.startup(&capi_sapi_module); + + return STATUS_PROCEED; +} + +int phpFservice(httpTtrans * t, lstTset * opts) +{ + int retval; + capi_request_context *request_context; + + TSRMLS_FETCH(); + + request_context = (capi_request_context *) malloc(sizeof(capi_request_context)); + request_context->t = t; + request_context->read_post_bytes = -1; + + SG(server_context) = request_context; + + capi_request_ctor(NSLS_C TSRMLS_CC); + retval = capi_module_main(NSLS_C TSRMLS_CC); + capi_request_dtor(NSLS_C TSRMLS_CC); + + free(request_context); + + /* + * This call is ostensibly provided to free the memory from PHP/TSRM when + * the thread terminated, but, it leaks a structure in some hash list + * according to the developers. Not calling this will leak the entire + * interpreter, around 100k, but calling it and then terminating the + * thread will leak the struct (around a k). The only answer with the + * current TSRM implementation is to reuse the threads that allocate TSRM + * resources. + */ + /* ts_free_thread(); */ + + if (retval == SUCCESS) { + return STATUS_EXIT; + } else { + return STATUS_ERROR; + } +} diff --git a/sapi/continuity/config.m4 b/sapi/continuity/config.m4 new file mode 100644 index 0000000..8d27419 --- /dev/null +++ b/sapi/continuity/config.m4 @@ -0,0 +1,28 @@ +dnl ## $Id$ -*- sh -*- + +PHP_ARG_WITH(continuity, for Continuity support, +[ --with-continuity=DIR Build PHP as Continuity Server module. + DIR is path to the installed Continuity Server root], no, no) + +if test "$PHP_CONTINUITY" != "no"; then + if test ! -d $PHP_CONTINUITY; then + AC_MSG_ERROR([Please specify the path to the root of your Continuity server using --with-continuity=DIR]) + fi + AC_MSG_CHECKING([for Continuity include files]) + if test -d $PHP_CONTINUITY/include ; then + CAPI_INCLUDE=$PHP_CONTINUITY/include + AC_MSG_RESULT([Continuity Binary Distribution]) + else + AC_MSG_ERROR([Cannot find your CAPI include files in either DIR/src or DIR/include]) + fi + + PHP_SELECT_SAPI(continuity, shared, capi.c) + PHP_ADD_INCLUDE($CAPI_INCLUDE) + PHP_BUILD_THREAD_SAFE + AC_DEFINE(HAVE_CONTINUITY, 1, [Whether you have a Continuity Server]) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$PHP_CONTINUITY/lib/" +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/embed/CREDITS b/sapi/embed/CREDITS new file mode 100644 index 0000000..a5227b4 --- /dev/null +++ b/sapi/embed/CREDITS @@ -0,0 +1,2 @@ +Embed +Edin Kadribasic diff --git a/sapi/embed/EXPERIMENTAL b/sapi/embed/EXPERIMENTAL new file mode 100644 index 0000000..293159a --- /dev/null +++ b/sapi/embed/EXPERIMENTAL @@ -0,0 +1,5 @@ +this module is experimental, +its functions may change their names +or move to extension all together +so do not rely to much on them +you have been warned! diff --git a/sapi/embed/config.m4 b/sapi/embed/config.m4 new file mode 100644 index 0000000..3a61b45 --- /dev/null +++ b/sapi/embed/config.m4 @@ -0,0 +1,33 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_ENABLE(embed,, +[ --enable-embed[=TYPE] EXPERIMENTAL: Enable building of embedded SAPI library + TYPE is either 'shared' or 'static'. [TYPE=shared]], no, no) + +AC_MSG_CHECKING([for embedded SAPI library support]) + +if test "$PHP_EMBED" != "no"; then + case "$PHP_EMBED" in + yes|shared) + PHP_EMBED_TYPE=shared + INSTALL_IT="\$(mkinstalldirs) \$(INSTALL_ROOT)\$(prefix)/lib; \$(INSTALL) -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)\$(prefix)/lib" + ;; + static) + PHP_EMBED_TYPE=static + INSTALL_IT="\$(mkinstalldirs) \$(INSTALL_ROOT)\$(prefix)/lib; \$(INSTALL) -m 0644 $SAPI_STATIC \$(INSTALL_ROOT)\$(prefix)/lib" + ;; + *) + PHP_EMBED_TYPE=no + ;; + esac + if test "$PHP_EMBED_TYPE" != "no"; then + PHP_SELECT_SAPI(embed, $PHP_EMBED_TYPE, php_embed.c) + PHP_INSTALL_HEADERS([sapi/embed/php_embed.h]) + fi + AC_MSG_RESULT([$PHP_EMBED_TYPE]) +else + AC_MSG_RESULT(no) +fi + diff --git a/sapi/embed/config.w32 b/sapi/embed/config.w32 new file mode 100644 index 0000000..f3cc60d --- /dev/null +++ b/sapi/embed/config.w32 @@ -0,0 +1,9 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('embed', 'Embedded SAPI library', 'no'); + +if (PHP_EMBED != "no") { + SAPI('embed', 'php_embed.c', 'php' + PHP_VERSION + 'embed.lib'); + PHP_INSTALL_HEADERS("sapi/embed", "php_embed.h"); +} diff --git a/sapi/embed/php5embed.dsp b/sapi/embed/php5embed.dsp new file mode 100644 index 0000000..8564b11 --- /dev/null +++ b/sapi/embed/php5embed.dsp @@ -0,0 +1,100 @@ +# Microsoft Developer Studio Project File - Name="php5embed" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Static Library" 0x0104
+
+CFG=php5embed - 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 "php5embed.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 "php5embed.mak" CFG="php5embed - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5embed - Win32 Debug_TS" (based on "Win32 (x86) Static Library")
+!MESSAGE "php5embed - Win32 Release_TS" (based on "Win32 (x86) Static Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "php5embed - 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 Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "..\.." /I "..\..\main" /I "..\..\Zend" /I "..\..\TSRM" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D ZEND_DEBUG=1 /YX /FD /GZ /c
+# ADD BASE RSC /l 0x406 /d "_DEBUG"
+# ADD RSC /l 0x406 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"..\..\Debug_TS\php5embed.lib"
+
+!ELSEIF "$(CFG)" == "php5embed - 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 Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\.." /I "..\..\main" /I "..\..\Zend" /I "..\..\TSRM" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D ZEND_DEBUG=0 /YX /FD /c
+# ADD BASE RSC /l 0x406 /d "NDEBUG"
+# ADD RSC /l 0x406 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo /out:"..\..\Release_TS\php5embed.lib"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5embed - Win32 Debug_TS"
+# Name "php5embed - Win32 Release_TS"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=php_embed.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=php_embed.h
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/sapi/embed/php_embed.c b/sapi/embed/php_embed.c new file mode 100644 index 0000000..414b4db --- /dev/null +++ b/sapi/embed/php_embed.c @@ -0,0 +1,241 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Edin Kadribasic <edink@php.net> | + +----------------------------------------------------------------------+ +*/ +/* $Id$ */ + +#include "php_embed.h" +#include "ext/standard/php_standard.h" + +#ifdef PHP_WIN32 +#include <io.h> +#include <fcntl.h> +#endif + +const char HARDCODED_INI[] = + "html_errors=0\n" + "register_argc_argv=1\n" + "implicit_flush=1\n" + "output_buffering=0\n" + "max_execution_time=0\n" + "max_input_time=-1\n\0"; + +static char* php_embed_read_cookies(TSRMLS_D) +{ + return NULL; +} + +static int php_embed_deactivate(TSRMLS_D) +{ + fflush(stdout); + return SUCCESS; +} + +static inline size_t php_embed_single_write(const char *str, uint str_length) +{ +#ifdef PHP_WRITE_STDOUT + long ret; + + ret = write(STDOUT_FILENO, str, str_length); + if (ret <= 0) return 0; + return ret; +#else + size_t ret; + + ret = fwrite(str, 1, MIN(str_length, 16384), stdout); + return ret; +#endif +} + + +static int php_embed_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + const char *ptr = str; + uint remaining = str_length; + size_t ret; + + while (remaining > 0) { + ret = php_embed_single_write(ptr, remaining); + if (!ret) { + php_handle_aborted_connection(); + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + +static void php_embed_flush(void *server_context) +{ + if (fflush(stdout)==EOF) { + php_handle_aborted_connection(); + } +} + +static void php_embed_send_header(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC) +{ +} + +static void php_embed_log_message(char *message TSRMLS_DC) +{ + fprintf (stderr, "%s\n", message); +} + +static void php_embed_register_variables(zval *track_vars_array TSRMLS_DC) +{ + php_import_environment_variables(track_vars_array TSRMLS_CC); +} + +static int php_embed_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, NULL, 0)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +extern EMBED_SAPI_API sapi_module_struct php_embed_module = { + "embed", /* name */ + "PHP Embedded Library", /* pretty name */ + + php_embed_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + php_embed_deactivate, /* deactivate */ + + php_embed_ub_write, /* unbuffered write */ + php_embed_flush, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + NULL, /* send headers handler */ + php_embed_send_header, /* send header handler */ + + NULL, /* read POST data */ + php_embed_read_cookies, /* read Cookies */ + + php_embed_register_variables, /* register server variables */ + php_embed_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ arginfo ext/standard/dl.c */ +ZEND_BEGIN_ARG_INFO(arginfo_dl, 0) + ZEND_ARG_INFO(0, extension_filename) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry additional_functions[] = { + ZEND_FE(dl, arginfo_dl) + {NULL, NULL, NULL} +}; + +EMBED_SAPI_API int php_embed_init(int argc, char **argv PTSRMLS_DC) +{ + zend_llist global_vars; +#ifdef ZTS + void ***tsrm_ls = NULL; +#endif + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif +#endif + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + tsrm_ls = ts_resource(0); + *ptsrm_ls = tsrm_ls; +#endif + + sapi_startup(&php_embed_module); + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /*sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + php_embed_module.ini_entries = malloc(sizeof(HARDCODED_INI)); + memcpy(php_embed_module.ini_entries, HARDCODED_INI, sizeof(HARDCODED_INI)); + + php_embed_module.additional_functions = additional_functions; + + if (argv) { + php_embed_module.executable_location = argv[0]; + } + + if (php_embed_module.startup(&php_embed_module)==FAILURE) { + return FAILURE; + } + + zend_llist_init(&global_vars, sizeof(char *), NULL, 0); + + /* Set some Embedded PHP defaults */ + SG(options) |= SAPI_OPTION_NO_CHDIR; + SG(request_info).argc=argc; + SG(request_info).argv=argv; + + if (php_request_startup(TSRMLS_C)==FAILURE) { + php_module_shutdown(TSRMLS_C); + return FAILURE; + } + + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_register_variable("PHP_SELF", "-", NULL TSRMLS_CC); + + return SUCCESS; +} + +EMBED_SAPI_API void php_embed_shutdown(TSRMLS_D) +{ + php_request_shutdown((void *) 0); + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + if (php_embed_module.ini_entries) { + free(php_embed_module.ini_entries); + php_embed_module.ini_entries = NULL; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/embed/php_embed.h b/sapi/embed/php_embed.h new file mode 100644 index 0000000..ad5f4bc --- /dev/null +++ b/sapi/embed/php_embed.h @@ -0,0 +1,73 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Edin Kadribasic <edink@php.net> | + +----------------------------------------------------------------------+ +*/ +/* $Id$ */ + +#ifndef _PHP_EMBED_H_ +#define _PHP_EMBED_H_ + +#include <main/php.h> +#include <main/SAPI.h> +#include <main/php_main.h> +#include <main/php_variables.h> +#include <main/php_ini.h> +#include <zend_ini.h> + +#ifdef ZTS +#define PTSRMLS_D void ****ptsrm_ls +#define PTSRMLS_DC , PTSRMLS_D +#define PTSRMLS_C &tsrm_ls +#define PTSRMLS_CC , PTSRMLS_C + +#define PHP_EMBED_START_BLOCK(x,y) { \ + void ***tsrm_ls; \ + php_embed_init(x, y PTSRMLS_CC); \ + zend_first_try { + +#else +#define PTSRMLS_D +#define PTSRMLS_DC +#define PTSRMLS_C +#define PTSRMLS_CC + +#define PHP_EMBED_START_BLOCK(x,y) { \ + php_embed_init(x, y); \ + zend_first_try { + +#endif + +#define PHP_EMBED_END_BLOCK() \ + } zend_catch { \ + /* int exit_status = EG(exit_status); */ \ + } zend_end_try(); \ + php_embed_shutdown(TSRMLS_C); \ +} + +#ifndef PHP_WIN32 + #define EMBED_SAPI_API SAPI_API +#else + #define EMBED_SAPI_API +#endif + +BEGIN_EXTERN_C() +EMBED_SAPI_API int php_embed_init(int argc, char **argv PTSRMLS_DC); +EMBED_SAPI_API void php_embed_shutdown(TSRMLS_D); +extern EMBED_SAPI_API sapi_module_struct php_embed_module; +END_EXTERN_C() + + +#endif /* _PHP_EMBED_H_ */ diff --git a/sapi/fpm/CREDITS b/sapi/fpm/CREDITS new file mode 100644 index 0000000..cd87daa --- /dev/null +++ b/sapi/fpm/CREDITS @@ -0,0 +1,2 @@ +FastCGI Process Manager +Andrei Nigmatulin, dreamcat4, Antony Dovgal, Jerome Loyet diff --git a/sapi/fpm/LICENSE b/sapi/fpm/LICENSE new file mode 100644 index 0000000..4e1dd99 --- /dev/null +++ b/sapi/fpm/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2007-2009, Andrei Nigmatulin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/sapi/fpm/Makefile.frag b/sapi/fpm/Makefile.frag new file mode 100644 index 0000000..6ed9e4a --- /dev/null +++ b/sapi/fpm/Makefile.frag @@ -0,0 +1,23 @@ +fpm: $(SAPI_FPM_PATH) + +$(SAPI_FPM_PATH): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_FPM_OBJS) + $(BUILD_FPM) + +install-fpm: $(SAPI_FPM_PATH) + @echo "Installing PHP FPM binary: $(INSTALL_ROOT)$(sbindir)/" + @$(mkinstalldirs) $(INSTALL_ROOT)$(sbindir) + @$(mkinstalldirs) $(INSTALL_ROOT)$(localstatedir)/log + @$(mkinstalldirs) $(INSTALL_ROOT)$(localstatedir)/run + @$(INSTALL) -m 0755 $(SAPI_FPM_PATH) $(INSTALL_ROOT)$(sbindir)/$(program_prefix)php-fpm$(program_suffix)$(EXEEXT) + + @echo "Installing PHP FPM config: $(INSTALL_ROOT)$(sysconfdir)/" && \ + $(mkinstalldirs) $(INSTALL_ROOT)$(sysconfdir) || : + @$(INSTALL_DATA) sapi/fpm/php-fpm.conf $(INSTALL_ROOT)$(sysconfdir)/php-fpm.conf.default || : + + @echo "Installing PHP FPM man page: $(INSTALL_ROOT)$(mandir)/man8/" + @$(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man8 + @$(INSTALL_DATA) sapi/fpm/php-fpm.8 $(INSTALL_ROOT)$(mandir)/man8/php-fpm$(program_suffix).8 + + @echo "Installing PHP FPM status page: $(INSTALL_ROOT)$(datadir)/fpm/" + @$(mkinstalldirs) $(INSTALL_ROOT)$(datadir)/fpm + @$(INSTALL_DATA) sapi/fpm/status.html $(INSTALL_ROOT)$(datadir)/fpm/status.html diff --git a/sapi/fpm/config.m4 b/sapi/fpm/config.m4 new file mode 100644 index 0000000..6191c32 --- /dev/null +++ b/sapi/fpm/config.m4 @@ -0,0 +1,657 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_ENABLE(fpm,, +[ --enable-fpm Enable building of the fpm SAPI executable], no, no) + +dnl configure checks {{{ +AC_DEFUN([AC_FPM_STDLIBS], +[ + AC_CHECK_FUNCS(setenv clearenv setproctitle) + + AC_SEARCH_LIBS(socket, socket) + AC_SEARCH_LIBS(inet_addr, nsl) + + AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h sys/uio.h]) + AC_CHECK_HEADERS([sys/select.h sys/socket.h sys/time.h]) + AC_CHECK_HEADERS([arpa/inet.h netinet/in.h]) + AC_CHECK_HEADERS([sysexits.h]) +]) + +AC_DEFUN([AC_FPM_PRCTL], +[ + AC_MSG_CHECKING([for prctl]) + + AC_TRY_COMPILE([ #include <sys/prctl.h> ], [prctl(0, 0, 0, 0, 0);], [ + AC_DEFINE([HAVE_PRCTL], 1, [do we have prctl?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) + +AC_DEFUN([AC_FPM_CLOCK], +[ + have_clock_gettime=no + + AC_MSG_CHECKING([for clock_gettime]) + + AC_TRY_LINK([ #include <time.h> ], [struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);], [ + have_clock_gettime=yes + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + + if test "$have_clock_gettime" = "no"; then + AC_MSG_CHECKING([for clock_gettime in -lrt]) + + SAVED_LIBS="$LIBS" + LIBS="$LIBS -lrt" + + AC_TRY_LINK([ #include <time.h> ], [struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);], [ + have_clock_gettime=yes + AC_MSG_RESULT([yes]) + ], [ + LIBS="$SAVED_LIBS" + AC_MSG_RESULT([no]) + ]) + fi + + if test "$have_clock_gettime" = "yes"; then + AC_DEFINE([HAVE_CLOCK_GETTIME], 1, [do we have clock_gettime?]) + fi + + have_clock_get_time=no + + if test "$have_clock_gettime" = "no"; then + AC_MSG_CHECKING([for clock_get_time]) + + AC_TRY_RUN([ #include <mach/mach.h> + #include <mach/clock.h> + #include <mach/mach_error.h> + + int main() + { + kern_return_t ret; clock_serv_t aClock; mach_timespec_t aTime; + ret = host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &aClock); + + if (ret != KERN_SUCCESS) { + return 1; + } + + ret = clock_get_time(aClock, &aTime); + if (ret != KERN_SUCCESS) { + return 2; + } + + return 0; + } + ], [ + have_clock_get_time=yes + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + fi + + if test "$have_clock_get_time" = "yes"; then + AC_DEFINE([HAVE_CLOCK_GET_TIME], 1, [do we have clock_get_time?]) + fi +]) + +AC_DEFUN([AC_FPM_TRACE], +[ + have_ptrace=no + have_broken_ptrace=no + + AC_MSG_CHECKING([for ptrace]) + + AC_TRY_COMPILE([ + #include <sys/types.h> + #include <sys/ptrace.h> ], [ptrace(0, 0, (void *) 0, 0);], [ + have_ptrace=yes + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + + if test "$have_ptrace" = "yes"; then + AC_MSG_CHECKING([whether ptrace works]) + + AC_TRY_RUN([ + #include <unistd.h> + #include <signal.h> + #include <sys/wait.h> + #include <sys/types.h> + #include <sys/ptrace.h> + #include <errno.h> + + #if !defined(PTRACE_ATTACH) && defined(PT_ATTACH) + #define PTRACE_ATTACH PT_ATTACH + #endif + + #if !defined(PTRACE_DETACH) && defined(PT_DETACH) + #define PTRACE_DETACH PT_DETACH + #endif + + #if !defined(PTRACE_PEEKDATA) && defined(PT_READ_D) + #define PTRACE_PEEKDATA PT_READ_D + #endif + + int main() + { + long v1 = (unsigned int) -1; /* copy will fail if sizeof(long) == 8 and we've got "int ptrace()" */ + long v2; + pid_t child; + int status; + + if ( (child = fork()) ) { /* parent */ + int ret = 0; + + if (0 > ptrace(PTRACE_ATTACH, child, 0, 0)) { + return 2; + } + + waitpid(child, &status, 0); + + #ifdef PT_IO + struct ptrace_io_desc ptio = { + .piod_op = PIOD_READ_D, + .piod_offs = &v1, + .piod_addr = &v2, + .piod_len = sizeof(v1) + }; + + if (0 > ptrace(PT_IO, child, (void *) &ptio, 0)) { + ret = 3; + } + #else + errno = 0; + + v2 = ptrace(PTRACE_PEEKDATA, child, (void *) &v1, 0); + + if (errno) { + ret = 4; + } + #endif + ptrace(PTRACE_DETACH, child, (void *) 1, 0); + + kill(child, SIGKILL); + + return ret ? ret : (v1 != v2); + } + else { /* child */ + sleep(10); + return 0; + } + } + ], [ + AC_MSG_RESULT([yes]) + ], [ + have_ptrace=no + have_broken_ptrace=yes + AC_MSG_RESULT([no]) + ], [ + AC_MSG_RESULT([skipped (cross compiling)]) + ]) + fi + + if test "$have_ptrace" = "yes"; then + AC_DEFINE([HAVE_PTRACE], 1, [do we have ptrace?]) + fi + + have_mach_vm_read=no + + if test "$have_broken_ptrace" = "yes"; then + AC_MSG_CHECKING([for mach_vm_read]) + + AC_TRY_COMPILE([ #include <mach/mach.h> + #include <mach/mach_vm.h> + ], [ + mach_vm_read((vm_map_t)0, (mach_vm_address_t)0, (mach_vm_size_t)0, (vm_offset_t *)0, (mach_msg_type_number_t*)0); + ], [ + have_mach_vm_read=yes + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + fi + + if test "$have_mach_vm_read" = "yes"; then + AC_DEFINE([HAVE_MACH_VM_READ], 1, [do we have mach_vm_read?]) + fi + + proc_mem_file="" + + if test -r /proc/$$/mem ; then + proc_mem_file="mem" + else + if test -r /proc/$$/as ; then + proc_mem_file="as" + fi + fi + + if test -n "$proc_mem_file" ; then + AC_MSG_CHECKING([for proc mem file]) + + AC_TRY_RUN([ + #define _GNU_SOURCE + #define _FILE_OFFSET_BITS 64 + #include <stdint.h> + #include <unistd.h> + #include <sys/types.h> + #include <sys/stat.h> + #include <fcntl.h> + #include <stdio.h> + int main() + { + long v1 = (unsigned int) -1, v2 = 0; + char buf[128]; + int fd; + sprintf(buf, "/proc/%d/$proc_mem_file", getpid()); + fd = open(buf, O_RDONLY); + if (0 > fd) { + return 1; + } + if (sizeof(long) != pread(fd, &v2, sizeof(long), (uintptr_t) &v1)) { + close(fd); + return 1; + } + close(fd); + return v1 != v2; + } + ], [ + AC_MSG_RESULT([$proc_mem_file]) + ], [ + proc_mem_file="" + AC_MSG_RESULT([no]) + ], [ + AC_MSG_RESULT([skipped (cross compiling)]) + ]) + fi + + if test -n "$proc_mem_file"; then + AC_DEFINE_UNQUOTED([PROC_MEM_FILE], "$proc_mem_file", [/proc/pid/mem interface]) + fi + + fpm_trace_type="" + + if test "$have_ptrace" = "yes"; then + fpm_trace_type=ptrace + + elif test -n "$proc_mem_file"; then + fpm_trace_type=pread + + elif test "$have_mach_vm_read" = "yes" ; then + fpm_trace_type=mach + + else + AC_MSG_WARN([FPM Trace - ptrace, pread, or mach: could not be found]) + fi + +]) + +AC_DEFUN([AC_FPM_BUILTIN_ATOMIC], +[ + AC_MSG_CHECKING([if gcc supports __sync_bool_compare_and_swap]) + AC_TRY_LINK(, + [ + int variable = 1; + return (__sync_bool_compare_and_swap(&variable, 1, 2) + && __sync_add_and_fetch(&variable, 1)) ? 1 : 0; + ], + [ + AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_BUILTIN_ATOMIC, 1, [Define to 1 if gcc supports __sync_bool_compare_and_swap() a.o.]) + ], + [ + AC_MSG_RESULT([no]) + ]) +]) + +AC_DEFUN([AC_FPM_LQ], +[ + have_lq=no + + AC_MSG_CHECKING([for TCP_INFO]) + + AC_TRY_COMPILE([ #include <netinet/tcp.h> ], [struct tcp_info ti; int x = TCP_INFO;], [ + have_lq=tcp_info + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + + if test "$have_lq" = "tcp_info"; then + AC_DEFINE([HAVE_LQ_TCP_INFO], 1, [do we have TCP_INFO?]) + fi + + if test "$have_lq" = "no" ; then + AC_MSG_CHECKING([for SO_LISTENQLEN]) + + AC_TRY_COMPILE([ #include <sys/socket.h> ], [int x = SO_LISTENQLIMIT; int y = SO_LISTENQLEN;], [ + have_lq=so_listenq + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) + + if test "$have_lq" = "tcp_info"; then + AC_DEFINE([HAVE_LQ_SO_LISTENQ], 1, [do we have SO_LISTENQxxx?]) + fi + fi +]) +dnl }}} + +AC_DEFUN([AC_FPM_SYSCONF], +[ + AC_MSG_CHECKING([for sysconf]) + + AC_TRY_COMPILE([ #include <unistd.h> ], [sysconf(_SC_CLK_TCK);], [ + AC_DEFINE([HAVE_SYSCONF], 1, [do we have sysconf?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_TIMES], +[ + AC_MSG_CHECKING([for times]) + + AC_TRY_COMPILE([ #include <sys/times.h> ], [struct tms t; times(&t);], [ + AC_DEFINE([HAVE_TIMES], 1, [do we have times?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_KQUEUE], +[ + AC_MSG_CHECKING([for kqueue]) + + AC_TRY_COMPILE( + [ + #include <sys/types.h> + #include <sys/event.h> + #include <sys/time.h> + ], [ + int kfd; + struct kevent k; + kfd = kqueue(); + /* 0 -> STDIN_FILENO */ + EV_SET(&k, 0, EVFILT_READ , EV_ADD | EV_CLEAR, 0, 0, NULL); + ], [ + AC_DEFINE([HAVE_KQUEUE], 1, [do we have kqueue?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_PORT], +[ + AC_MSG_CHECKING([for port framework]) + + AC_TRY_COMPILE( + [ + #include <port.h> + ], [ + int port; + + port = port_create(); + if (port < 0) { + return 1; + } + ], [ + AC_DEFINE([HAVE_PORT], 1, [do we have port framework?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_DEVPOLL], +[ + AC_MSG_CHECKING([for /dev/poll]) + + AC_TRY_COMPILE( + [ + #include <stdio.h> + #include <sys/devpoll.h> + ], [ + int n, dp; + struct dvpoll dvp; + dp = 0; + dvp.dp_fds = NULL; + dvp.dp_nfds = 0; + dvp.dp_timeout = 0; + n = ioctl(dp, DP_POLL, &dvp) + ], [ + AC_DEFINE([HAVE_DEVPOLL], 1, [do we have /dev/poll?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_EPOLL], +[ + AC_MSG_CHECKING([for epoll]) + + AC_TRY_COMPILE( + [ + #include <sys/epoll.h> + ], [ + int epollfd; + struct epoll_event e; + + epollfd = epoll_create(1); + if (epollfd < 0) { + return 1; + } + + e.events = EPOLLIN | EPOLLET; + e.data.fd = 0; + + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, 0, &e) == -1) { + return 1; + } + + e.events = 0; + if (epoll_wait(epollfd, &e, 1, 1) < 0) { + return 1; + } + ], [ + AC_DEFINE([HAVE_EPOLL], 1, [do we have epoll?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_POLL], +[ + AC_MSG_CHECKING([for poll]) + + AC_TRY_COMPILE( + [ + #include <poll.h> + ], [ + struct pollfd fds[2]; + + fds[0].fd = 0; + fds[0].events = POLLIN; + + fds[1].fd = 0; + fds[1].events = POLLIN; + + poll(fds, 2, 1); + ], [ + AC_DEFINE([HAVE_POLL], 1, [do we have poll?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + +AC_DEFUN([AC_FPM_SELECT], +[ + AC_MSG_CHECKING([for select]) + + AC_TRY_COMPILE( + [ + /* According to POSIX.1-2001 */ + #include <sys/select.h> + + /* According to earlier standards */ + #include <sys/time.h> + #include <sys/types.h> + #include <unistd.h> + ], [ + fd_set fds; + struct timeval t; + t.tv_sec = 0; + t.tv_usec = 42; + FD_ZERO(&fds); + /* 0 -> STDIN_FILENO */ + FD_SET(0, &fds); + select(FD_SETSIZE, &fds, NULL, NULL, &t); + ], [ + AC_DEFINE([HAVE_SELECT], 1, [do we have select?]) + AC_MSG_RESULT([yes]) + ], [ + AC_MSG_RESULT([no]) + ]) +]) +dnl }}} + + +AC_MSG_CHECKING(for FPM build) +if test "$PHP_FPM" != "no"; then + AC_MSG_RESULT($PHP_FPM) + + AC_FPM_STDLIBS + AC_FPM_PRCTL + AC_FPM_CLOCK + AC_FPM_TRACE + AC_FPM_BUILTIN_ATOMIC + AC_FPM_LQ + AC_FPM_SYSCONF + AC_FPM_TIMES + AC_FPM_KQUEUE + AC_FPM_PORT + AC_FPM_DEVPOLL + AC_FPM_EPOLL + AC_FPM_POLL + AC_FPM_SELECT + + PHP_ARG_WITH(fpm-user,, + [ --with-fpm-user[=USER] Set the user for php-fpm to run as. (default: nobody)], nobody, no) + + PHP_ARG_WITH(fpm-group,, + [ --with-fpm-group[=GRP] Set the group for php-fpm to run as. For a system user, this + should usually be set to match the fpm username (default: nobody)], nobody, no) + + if test -z "$PHP_FPM_USER" -o "$PHP_FPM_USER" = "yes" -o "$PHP_FPM_USER" = "no"; then + php_fpm_user="nobody" + else + php_fpm_user="$PHP_FPM_USER" + fi + + if test -z "$PHP_FPM_GROUP" -o "$PHP_FPM_GROUP" = "yes" -o "$PHP_FPM_GROUP" = "no"; then + php_fpm_group="nobody" + else + php_fpm_group="$PHP_FPM_GROUP" + fi + + PHP_SUBST_OLD(php_fpm_user) + PHP_SUBST_OLD(php_fpm_group) + php_fpm_sysconfdir=`eval echo $sysconfdir` + PHP_SUBST_OLD(php_fpm_sysconfdir) + php_fpm_localstatedir=`eval echo $localstatedir` + PHP_SUBST_OLD(php_fpm_localstatedir) + php_fpm_prefix=`eval echo $prefix` + PHP_SUBST_OLD(php_fpm_prefix) + + AC_DEFINE_UNQUOTED(PHP_FPM_USER, "$php_fpm_user", [fpm user name]) + AC_DEFINE_UNQUOTED(PHP_FPM_GROUP, "$php_fpm_group", [fpm group name]) + + AC_DEFINE_UNQUOTED(PHP_FPM_USER, "$php_fpm_user", [fpm user name]) + AC_DEFINE_UNQUOTED(PHP_FPM_GROUP, "$php_fpm_group", [fpm group name]) + + PHP_ADD_BUILD_DIR(sapi/fpm/fpm) + PHP_ADD_BUILD_DIR(sapi/fpm/fpm/events) + PHP_OUTPUT(sapi/fpm/php-fpm.conf sapi/fpm/init.d.php-fpm sapi/fpm/php-fpm.service sapi/fpm/php-fpm.8 sapi/fpm/status.html) + PHP_ADD_MAKEFILE_FRAGMENT([$abs_srcdir/sapi/fpm/Makefile.frag]) + + SAPI_FPM_PATH=sapi/fpm/php-fpm + + + if test "$fpm_trace_type" && test -f "$abs_srcdir/sapi/fpm/fpm/fpm_trace_$fpm_trace_type.c"; then + PHP_FPM_TRACE_FILES="fpm/fpm_trace.c fpm/fpm_trace_$fpm_trace_type.c" + fi + + PHP_FPM_CFLAGS="-I$abs_srcdir/sapi/fpm" + + PHP_FPM_FILES="fpm/fastcgi.c \ + fpm/fpm.c \ + fpm/fpm_children.c \ + fpm/fpm_cleanup.c \ + fpm/fpm_clock.c \ + fpm/fpm_conf.c \ + fpm/fpm_env.c \ + fpm/fpm_events.c \ + fpm/fpm_log.c \ + fpm/fpm_main.c \ + fpm/fpm_php.c \ + fpm/fpm_php_trace.c \ + fpm/fpm_process_ctl.c \ + fpm/fpm_request.c \ + fpm/fpm_shm.c \ + fpm/fpm_scoreboard.c \ + fpm/fpm_signals.c \ + fpm/fpm_sockets.c \ + fpm/fpm_status.c \ + fpm/fpm_stdio.c \ + fpm/fpm_unix.c \ + fpm/fpm_worker_pool.c \ + fpm/zlog.c \ + fpm/events/select.c \ + fpm/events/poll.c \ + fpm/events/epoll.c \ + fpm/events/kqueue.c \ + fpm/events/devpoll.c \ + fpm/events/port.c \ + " + + PHP_SELECT_SAPI(fpm, program, $PHP_FPM_FILES $PHP_FPM_TRACE_FILES, $PHP_FPM_CFLAGS, '$(SAPI_FPM_PATH)') + + case $host_alias in + *aix*) + BUILD_FPM="echo '\#! .' > php.sym && echo >>php.sym && nm -BCpg \`echo \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_FPM_OBJS) | sed 's/\([A-Za-z0-9_]*\)\.lo/\1.o/g'\` | \$(AWK) '{ if (((\$\$2 == \"T\") || (\$\$2 == \"D\") || (\$\$2 == \"B\")) && (substr(\$\$3,1,1) != \".\")) { print \$\$3 } }' | sort -u >> php.sym && \$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) -Wl,-brtl -Wl,-bE:php.sym \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_FPM_OBJS) \$(EXTRA_LIBS) \$(FPM_EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_FPM_PATH)" + ;; + *darwin*) + BUILD_FPM="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_BINARY_OBJS:.lo=.o) \$(PHP_FPM_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(FPM_EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_FPM_PATH)" + ;; + *) + BUILD_FPM="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_FPM_OBJS) \$(EXTRA_LIBS) \$(FPM_EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_FPM_PATH)" + ;; + esac + + PHP_SUBST(SAPI_FPM_PATH) + PHP_SUBST(BUILD_FPM) + +else + AC_MSG_RESULT(no) +fi diff --git a/sapi/fpm/fpm/events/devpoll.c b/sapi/fpm/fpm/events/devpoll.c new file mode 100644 index 0000000..223d072 --- /dev/null +++ b/sapi/fpm/fpm/events/devpoll.c @@ -0,0 +1,248 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_DEVPOLL + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <poll.h> +#include <sys/devpoll.h> +#include <errno.h> + +static int fpm_event_devpoll_init(int max); +static int fpm_event_devpoll_clean(); +static int fpm_event_devpoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_devpoll_add(struct fpm_event_s *ev); +static int fpm_event_devpoll_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s devpoll_module = { + .name = "/dev/poll", + .support_edge_trigger = 0, + .init = fpm_event_devpoll_init, + .clean = fpm_event_devpoll_clean, + .wait = fpm_event_devpoll_wait, + .add = fpm_event_devpoll_add, + .remove = fpm_event_devpoll_remove, +}; + +int dpfd = -1; +static struct pollfd *pollfds = NULL; +static struct pollfd *active_pollfds = NULL; +static int npollfds = 0; + +#endif /* HAVE_DEVPOLL */ + +struct fpm_event_module_s *fpm_event_devpoll_module() /* {{{ */ +{ +#if HAVE_DEVPOLL + return &devpoll_module; +#else + return NULL; +#endif /* HAVE_DEVPOLL */ +} +/* }}} */ + +#if HAVE_DEVPOLL + +/* + * Init module + */ +static int fpm_event_devpoll_init(int max) /* {{{ */ +{ + int i; + + /* open /dev/poll for future usages */ + dpfd = open("/dev/poll", O_RDWR); + if (dpfd < 0) { + zlog(ZLOG_ERROR, "Unable to open /dev/poll"); + return -1; + } + + if (max < 1) { + return 0; + } + + /* alloc and clear pollfds */ + pollfds = malloc(sizeof(struct pollfd) * max); + if (!pollfds) { + zlog(ZLOG_ERROR, "poll: unable to allocate %d events", max); + return -1; + } + memset(pollfds, 0, sizeof(struct pollfd) * max); + + /* set all fd to -1 in order to ensure it's not set */ + for (i = 0; i < max; i++) { + pollfds[i].fd = -1; + } + + /* alloc and clear active_pollfds */ + active_pollfds = malloc(sizeof(struct pollfd) * max); + if (!active_pollfds) { + free(pollfds); + zlog(ZLOG_ERROR, "poll: unable to allocate %d events", max); + return -1; + } + memset(active_pollfds, 0, sizeof(struct pollfd) * max); + + /* save max */ + npollfds = max; + + return 0; +} +/* }}} */ + +/* + * Clean the module + */ +static int fpm_event_devpoll_clean() /* {{{ */ +{ + /* close /dev/poll if open */ + if (dpfd > -1) { + close(dpfd); + dpfd = -1; + } + + /* free pollfds */ + if (pollfds) { + free(pollfds); + pollfds = NULL; + } + + /* free active_pollfds */ + if (active_pollfds) { + free(active_pollfds); + active_pollfds = NULL; + } + + npollfds = 0; + return 0; +} +/* }}} */ + +/* + * wait for events or timeout + */ +static int fpm_event_devpoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + int ret, i; + struct fpm_event_queue_s *q; + struct dvpoll dopoll; + + /* setup /dev/poll */ + dopoll.dp_fds = active_pollfds; + dopoll.dp_nfds = npollfds; + dopoll.dp_timeout = (int)timeout; + + /* wait for inconming event or timeout */ + ret = ioctl(dpfd, DP_POLL, &dopoll); + + if (ret < 0) { + + /* trigger error unless signal interrupt */ + if (errno != EINTR) { + zlog(ZLOG_WARNING, "/dev/poll: ioctl() returns %d", errno); + return -1; + } + } + + /* iterate throught triggered events */ + for (i = 0; i < ret; i++) { + + /* find the corresponding event */ + q = queue; + while (q) { + + /* found */ + if (q->ev && q->ev->fd == active_pollfds[i].fd) { + + /* fire the event */ + fpm_event_fire(q->ev); + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + break; /* next triggered event */ + } + q = q->next; /* iterate */ + } + } + + return ret; +} +/* }}} */ + +/* + * Add a FD from the fd set + */ +static int fpm_event_devpoll_add(struct fpm_event_s *ev) /* {{{ */ +{ + struct pollfd pollfd; + + /* fill pollfd with event informations */ + pollfd.fd = ev->fd; + pollfd.events = POLLIN; + pollfd.revents = 0; + + /* add the event to the internal queue */ + if (write(dpfd, &pollfd, sizeof(struct pollfd)) != sizeof(struct pollfd)) { + zlog(ZLOG_ERROR, "/dev/poll: Unable to add the event in the internal queue"); + return -1; + } + + /* mark the event as registered */ + ev->index = ev->fd; + + return 0; +} +/* }}} */ + +/* + * Remove a FD from the fd set + */ +static int fpm_event_devpoll_remove(struct fpm_event_s *ev) /* {{{ */ +{ + struct pollfd pollfd; + + /* fill pollfd with the same informations as fpm_event_devpoll_add */ + pollfd.fd = ev->fd; + pollfd.events = POLLIN | POLLREMOVE; + pollfd.revents = 0; + + /* add the event to the internal queue */ + if (write(dpfd, &pollfd, sizeof(struct pollfd)) != sizeof(struct pollfd)) { + zlog(ZLOG_ERROR, "/dev/poll: Unable to remove the event in the internal queue"); + return -1; + } + + /* mark the event as registered */ + ev->index = -1; + + return 0; +} +/* }}} */ + +#endif /* HAVE_DEVPOLL */ diff --git a/sapi/fpm/fpm/events/devpoll.h b/sapi/fpm/fpm/events/devpoll.h new file mode 100644 index 0000000..f9bc4af --- /dev/null +++ b/sapi/fpm/fpm/events/devpoll.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_DEVPOLL_H +#define FPM_EVENTS_DEVPOLL_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_devpoll_module(); + +#endif /* FPM_EVENTS_DEVPOLL_H */ diff --git a/sapi/fpm/fpm/events/epoll.c b/sapi/fpm/fpm/events/epoll.c new file mode 100644 index 0000000..c9c7f1f --- /dev/null +++ b/sapi/fpm/fpm/events/epoll.c @@ -0,0 +1,211 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_EPOLL + +#include <sys/epoll.h> +#include <errno.h> + +static int fpm_event_epoll_init(int max); +static int fpm_event_epoll_clean(); +static int fpm_event_epoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_epoll_add(struct fpm_event_s *ev); +static int fpm_event_epoll_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s epoll_module = { + .name = "epoll", + .support_edge_trigger = 1, + .init = fpm_event_epoll_init, + .clean = fpm_event_epoll_clean, + .wait = fpm_event_epoll_wait, + .add = fpm_event_epoll_add, + .remove = fpm_event_epoll_remove, +}; + +static struct epoll_event *epollfds = NULL; +static int nepollfds = 0; +static int epollfd = 0; + +#endif /* HAVE_EPOLL */ + +struct fpm_event_module_s *fpm_event_epoll_module() /* {{{ */ +{ +#if HAVE_EPOLL + return &epoll_module; +#else + return NULL; +#endif /* HAVE_EPOLL */ +} +/* }}} */ + +#if HAVE_EPOLL + +/* + * Init the module + */ +static int fpm_event_epoll_init(int max) /* {{{ */ +{ + if (max < 1) { + return 0; + } + + /* init epoll */ + epollfd = epoll_create(max + 1); + if (epollfd < 0) { + zlog(ZLOG_ERROR, "epoll: unable to initialize"); + return -1; + } + + /* allocate fds */ + epollfds = malloc(sizeof(struct epoll_event) * max); + if (!epollfds) { + zlog(ZLOG_ERROR, "epoll: unable to allocate %d events", max); + return -1; + } + memset(epollfds, 0, sizeof(struct epoll_event) * max); + + /* save max */ + nepollfds = max; + + return 0; +} +/* }}} */ + +/* + * Clean the module + */ +static int fpm_event_epoll_clean() /* {{{ */ +{ + /* free epollfds */ + if (epollfds) { + free(epollfds); + epollfds = NULL; + } + + nepollfds = 0; + + return 0; +} +/* }}} */ + +/* + * wait for events or timeout + */ +static int fpm_event_epoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + int ret, i; + + /* ensure we have a clean epoolfds before calling epoll_wait() */ + memset(epollfds, 0, sizeof(struct epoll_event) * nepollfds); + + /* wait for inconming event or timeout */ + ret = epoll_wait(epollfd, epollfds, nepollfds, timeout); + if (ret == -1) { + + /* trigger error unless signal interrupt */ + if (errno != EINTR) { + zlog(ZLOG_WARNING, "epoll_wait() returns %d", errno); + return -1; + } + } + + /* events have been triggered, let's fire them */ + for (i = 0; i < ret; i++) { + + /* do we have a valid ev ptr ? */ + if (!epollfds[i].data.ptr) { + continue; + } + + /* fire the event */ + fpm_event_fire((struct fpm_event_s *)epollfds[i].data.ptr); + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + } + + return ret; +} +/* }}} */ + +/* + * Add a FD to the fd set + */ +static int fpm_event_epoll_add(struct fpm_event_s *ev) /* {{{ */ +{ + struct epoll_event e; + + /* fill epoll struct */ + e.events = EPOLLIN; + e.data.fd = ev->fd; + e.data.ptr = (void *)ev; + + if (ev->flags & FPM_EV_EDGE) { + e.events = e.events | EPOLLET; + } + + /* add the event to epoll internal queue */ + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ev->fd, &e) == -1) { + zlog(ZLOG_ERROR, "epoll: unable to add fd %d", ev->fd); + return -1; + } + + /* mark the event as registered */ + ev->index = ev->fd; + return 0; +} +/* }}} */ + +/* + * Remove a FD from the fd set + */ +static int fpm_event_epoll_remove(struct fpm_event_s *ev) /* {{{ */ +{ + struct epoll_event e; + + /* fill epoll struct the same way we did in fpm_event_epoll_add() */ + e.events = EPOLLIN; + e.data.fd = ev->fd; + e.data.ptr = (void *)ev; + + if (ev->flags & FPM_EV_EDGE) { + e.events = e.events | EPOLLET; + } + + /* remove the event from epoll internal queue */ + if (epoll_ctl(epollfd, EPOLL_CTL_DEL, ev->fd, &e) == -1) { + zlog(ZLOG_ERROR, "epoll: unable to remove fd %d", ev->fd); + return -1; + } + + /* mark the event as not registered */ + ev->index = -1; + return 0; +} +/* }}} */ + +#endif /* HAVE_EPOLL */ diff --git a/sapi/fpm/fpm/events/epoll.h b/sapi/fpm/fpm/events/epoll.h new file mode 100644 index 0000000..abc5a21 --- /dev/null +++ b/sapi/fpm/fpm/events/epoll.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_EPOLL_H +#define FPM_EVENTS_EPOLL_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_epoll_module(); + +#endif /* FPM_EVENTS_EPOLL_H */ diff --git a/sapi/fpm/fpm/events/kqueue.c b/sapi/fpm/fpm/events/kqueue.c new file mode 100644 index 0000000..7ce0760 --- /dev/null +++ b/sapi/fpm/fpm/events/kqueue.c @@ -0,0 +1,208 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_KQUEUE + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> + +#include <errno.h> + +static int fpm_event_kqueue_init(int max); +static int fpm_event_kqueue_clean(); +static int fpm_event_kqueue_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_kqueue_add(struct fpm_event_s *ev); +static int fpm_event_kqueue_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s kqueue_module = { + .name = "kqueue", + .support_edge_trigger = 1, + .init = fpm_event_kqueue_init, + .clean = fpm_event_kqueue_clean, + .wait = fpm_event_kqueue_wait, + .add = fpm_event_kqueue_add, + .remove = fpm_event_kqueue_remove, +}; + +static struct kevent *kevents = NULL; +static int nkevents = 0; +static int kfd = 0; + +#endif /* HAVE_KQUEUE */ + +/* + * Return the module configuration + */ +struct fpm_event_module_s *fpm_event_kqueue_module() /* {{{ */ +{ +#if HAVE_KQUEUE + return &kqueue_module; +#else + return NULL; +#endif /* HAVE_KQUEUE */ +} +/* }}} */ + +#if HAVE_KQUEUE + +/* + * init kqueue and stuff + */ +static int fpm_event_kqueue_init(int max) /* {{{ */ +{ + if (max < 1) { + return 0; + } + + kfd = kqueue(); + if (kfd < 0) { + zlog(ZLOG_ERROR, "kqueue: unable to initialize"); + return -1; + } + + kevents = malloc(sizeof(struct kevent) * max); + if (!kevents) { + zlog(ZLOG_ERROR, "epoll: unable to allocate %d events", max); + return -1; + } + + memset(kevents, 0, sizeof(struct kevent) * max); + + nkevents = max; + + return 0; +} +/* }}} */ + +/* + * release kqueue stuff + */ +static int fpm_event_kqueue_clean() /* {{{ */ +{ + if (kevents) { + free(kevents); + kevents = NULL; + } + + nkevents = 0; + + return 0; +} +/* }}} */ + +/* + * wait for events or timeout + */ +static int fpm_event_kqueue_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + struct timespec t; + int ret, i; + + /* ensure we have a clean kevents before calling kevent() */ + memset(kevents, 0, sizeof(struct kevent) * nkevents); + + /* convert ms to timespec struct */ + t.tv_sec = timeout / 1000; + t.tv_nsec = (timeout % 1000) * 1000 * 1000; + + /* wait for incoming event or timeout */ + ret = kevent(kfd, NULL, 0, kevents, nkevents, &t); + if (ret == -1) { + + /* trigger error unless signal interrupt */ + if (errno != EINTR) { + zlog(ZLOG_WARNING, "epoll_wait() returns %d", errno); + return -1; + } + } + + /* fire triggered events */ + for (i = 0; i < ret; i++) { + if (kevents[i].udata) { + struct fpm_event_s *ev = (struct fpm_event_s *)kevents[i].udata; + fpm_event_fire(ev); + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + } + } + + return ret; +} +/* }}} */ + +/* + * Add a FD to to kevent queue + */ +static int fpm_event_kqueue_add(struct fpm_event_s *ev) /* {{{ */ +{ + struct kevent k; + int flags = EV_ADD; + + if (ev->flags & FPM_EV_EDGE) { + flags = flags | EV_CLEAR; + } + + EV_SET(&k, ev->fd, EVFILT_READ, flags, 0, 0, (void *)ev); + + if (kevent(kfd, &k, 1, NULL, 0, NULL) < 0) { + zlog(ZLOG_ERROR, "kevent: unable to add event"); + return -1; + } + + /* mark the event as registered */ + ev->index = ev->fd; + return 0; +} +/* }}} */ + +/* + * Remove a FD from the kevent queue + */ +static int fpm_event_kqueue_remove(struct fpm_event_s *ev) /* {{{ */ +{ + struct kevent k; + int flags = EV_DELETE; + + if (ev->flags & FPM_EV_EDGE) { + flags = flags | EV_CLEAR; + } + + EV_SET(&k, ev->fd, EVFILT_READ, flags, 0, 0, (void *)ev); + + if (kevent(kfd, &k, 1, NULL, 0, NULL) < 0) { + zlog(ZLOG_ERROR, "kevent: unable to add event"); + return -1; + } + + /* mark the vent as not registered */ + ev->index = -1; + return 0; +} +/* }}} */ + +#endif /* HAVE_KQUEUE */ diff --git a/sapi/fpm/fpm/events/kqueue.h b/sapi/fpm/fpm/events/kqueue.h new file mode 100644 index 0000000..2642aca --- /dev/null +++ b/sapi/fpm/fpm/events/kqueue.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_KQUEUE_H +#define FPM_EVENTS_KQUEUE_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_kqueue_module(); + +#endif /* FPM_EVENTS_KQUEUE_H */ diff --git a/sapi/fpm/fpm/events/poll.c b/sapi/fpm/fpm/events/poll.c new file mode 100644 index 0000000..185eceb --- /dev/null +++ b/sapi/fpm/fpm/events/poll.c @@ -0,0 +1,276 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_POLL + +#include <poll.h> +#include <errno.h> +#include <string.h> + +static int fpm_event_poll_init(int max); +static int fpm_event_poll_clean(); +static int fpm_event_poll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_poll_add(struct fpm_event_s *ev); +static int fpm_event_poll_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s poll_module = { + .name = "poll", + .support_edge_trigger = 0, + .init = fpm_event_poll_init, + .clean = fpm_event_poll_clean, + .wait = fpm_event_poll_wait, + .add = fpm_event_poll_add, + .remove = fpm_event_poll_remove, +}; + +static struct pollfd *pollfds = NULL; +static struct pollfd *active_pollfds = NULL; +static int npollfds = 0; +static int next_free_slot = 0; +#endif /* HAVE_POLL */ + +/* + * return the module configuration + */ +struct fpm_event_module_s *fpm_event_poll_module() /* {{{ */ +{ +#if HAVE_POLL + return &poll_module; +#else + return NULL; +#endif /* HAVE_POLL */ +} +/* }}} */ + +#if HAVE_POLL + +/* + * Init the module + */ +static int fpm_event_poll_init(int max) /* {{{ */ +{ + int i; + + if (max < 1) { + return 0; + } + + /* alloc and clear pollfds */ + pollfds = malloc(sizeof(struct pollfd) * max); + if (!pollfds) { + zlog(ZLOG_ERROR, "poll: unable to allocate %d events", max); + return -1; + } + memset(pollfds, 0, sizeof(struct pollfd) * max); + + /* set all fd to -1 in order to ensure it's not set */ + for (i = 0; i < max; i++) { + pollfds[i].fd = -1; + } + + /* alloc and clear active_pollfds */ + active_pollfds = malloc(sizeof(struct pollfd) * max); + if (!active_pollfds) { + free(pollfds); + zlog(ZLOG_ERROR, "poll: unable to allocate %d events", max); + return -1; + } + memset(active_pollfds, 0, sizeof(struct pollfd) * max); + + /* save max */ + npollfds = max; + return 0; +} +/* }}} */ + +/* + * Clean the module + */ +static int fpm_event_poll_clean() /* {{{ */ +{ + /* free pollfds */ + if (pollfds) { + free(pollfds); + pollfds = NULL; + } + + /* free active_pollfds */ + if (active_pollfds) { + free(active_pollfds); + active_pollfds = NULL; + } + + npollfds = 0; + return 0; +} +/* }}} */ + +/* + * wait for events or timeout + */ +static int fpm_event_poll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + int ret; + struct fpm_event_queue_s *q; + + if (npollfds > 0) { + /* copy pollfds because poll() alters it */ + memcpy(active_pollfds, pollfds, sizeof(struct pollfd) * npollfds); + } + + /* wait for inconming event or timeout */ + ret = poll(active_pollfds, npollfds, timeout); + if (ret == -1) { + + /* trigger error unless signal interrupt */ + if (errno != EINTR) { + zlog(ZLOG_WARNING, "poll() returns %d", errno); + return -1; + } + } + + /* events have been triggered */ + if (ret > 0) { + + /* trigger POLLIN events */ + q = queue; + while (q) { + /* ensure ev->index is valid */ + if (q->ev && q->ev->index >= 0 && q->ev->index < npollfds && q->ev->fd == active_pollfds[q->ev->index].fd) { + + /* has the event has been triggered ? */ + if (active_pollfds[q->ev->index].revents & POLLIN) { + + /* fire the event */ + fpm_event_fire(q->ev); + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + } + } + q = q->next; /* iterate */ + } + } + + return ret; +} +/* }}} */ + +/* + * Add a FD to the fd set + */ +static int fpm_event_poll_add(struct fpm_event_s *ev) /* {{{ */ +{ + int i; + + /* do we have a direct free slot */ + if (pollfds[next_free_slot].fd == -1) { + /* register the event */ + pollfds[next_free_slot].fd = ev->fd; + pollfds[next_free_slot].events = POLLIN; + + /* remember the event place in the fd list and suppose next slot is free */ + ev->index = next_free_slot++; + if (next_free_slot >= npollfds) { + next_free_slot = 0; + } + return 0; + } + + /* let's search */ + for (i = 0; i < npollfds; i++) { + if (pollfds[i].fd != -1) { + /* not free */ + continue; + } + + /* register the event */ + pollfds[i].fd = ev->fd; + pollfds[i].events = POLLIN; + + /* remember the event place in the fd list and suppose next slot is free */ + ev->index = next_free_slot++; + if (next_free_slot >= npollfds) { + next_free_slot = 0; + } + return 0; + } + + zlog(ZLOG_ERROR, "poll: not enought space to add event (fd=%d)", ev->fd); + return -1; +} +/* }}} */ + +/* + * Remove a FD from the fd set + */ +static int fpm_event_poll_remove(struct fpm_event_s *ev) /* {{{ */ +{ + int i; + + /* do we have a direct access */ + if (ev->index >= 0 && ev->index < npollfds && pollfds[ev->index].fd == ev->fd) { + /* remember this slot as free */ + next_free_slot = ev->index; + + /* clear event in pollfds */ + pollfds[ev->index].fd = -1; + pollfds[ev->index].events = 0; + + /* mark the event as not registered */ + ev->index = -1; + + return 0; + } + + /* let's search */ + for (i = 0; i < npollfds; i++) { + + if (pollfds[i].fd != ev->fd) { + /* not found */ + continue; + } + + /* remember this slot as free */ + next_free_slot = i; + + /* clear event in pollfds */ + pollfds[i].fd = -1; + pollfds[i].events = 0; + + /* mark the event as not registered */ + ev->index = -1; + + return 0; + } + + zlog(ZLOG_ERROR, "poll: unable to remove event: not found (fd=%d, index=%d)", ev->fd, ev->index); + return -1; +} +/* }}} */ + +#endif /* HAVE_POLL */ diff --git a/sapi/fpm/fpm/events/poll.h b/sapi/fpm/fpm/events/poll.h new file mode 100644 index 0000000..d553192 --- /dev/null +++ b/sapi/fpm/fpm/events/poll.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_POLL_H +#define FPM_EVENTS_POLL_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_poll_module(); + +#endif /* FPM_EVENTS_POLL_H */ diff --git a/sapi/fpm/fpm/events/port.c b/sapi/fpm/fpm/events/port.c new file mode 100644 index 0000000..3cbf092 --- /dev/null +++ b/sapi/fpm/fpm/events/port.c @@ -0,0 +1,185 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_PORT + +#include <port.h> +#include <poll.h> +#include <errno.h> + +static int fpm_event_port_init(int max); +static int fpm_event_port_clean(); +static int fpm_event_port_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_port_add(struct fpm_event_s *ev); +static int fpm_event_port_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s port_module = { + .name = "port", + .support_edge_trigger = 0, + .init = fpm_event_port_init, + .clean = fpm_event_port_clean, + .wait = fpm_event_port_wait, + .add = fpm_event_port_add, + .remove = fpm_event_port_remove, +}; + +port_event_t *events = NULL; +int nevents = 0; +static int pfd = -1; + +#endif /* HAVE_PORT */ + +struct fpm_event_module_s *fpm_event_port_module() /* {{{ */ +{ +#if HAVE_PORT + return &port_module; +#else + return NULL; +#endif /* HAVE_PORT */ +} +/* }}} */ + +#if HAVE_PORT + +/* + * Init the module + */ +static int fpm_event_port_init(int max) /* {{{ */ +{ + /* open port */ + pfd = port_create(); + if (pfd < 0) { + zlog(ZLOG_ERROR, "port: unable to initialize port_create()"); + return -1; + } + + if (max < 1) { + return 0; + } + + /* alloc and clear active_pollfds */ + events = malloc(sizeof(port_event_t) * max); + if (!events) { + zlog(ZLOG_ERROR, "port: Unable to allocate %d events", max); + return -1; + } + + nevents = max; + return 0; +} +/* }}} */ + +/* + * Clean the module + */ +static int fpm_event_port_clean() /* {{{ */ +{ + if (pfd > -1) { + close(pfd); + pfd = -1; + } + + if (events) { + free(events); + events = NULL; + } + + nevents = 0; + return 0; +} +/* }}} */ + +/* + * wait for events or timeout + */ +static int fpm_event_port_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + int ret, i, nget; + timespec_t t; + + /* convert timeout into timespec_t */ + t.tv_sec = (int)(timeout / 1000); + t.tv_nsec = (timeout % 1000) * 1000 * 1000; + + /* wait for inconming event or timeout. We want at least one event or timeout */ + nget = 1; + ret = port_getn(pfd, events, nevents, &nget, &t); + if (ret < 0) { + + /* trigger error unless signal interrupt or timeout */ + if (errno != EINTR && errno != ETIME) { + zlog(ZLOG_WARNING, "poll() returns %d", errno); + return -1; + } + } + + for (i = 0; i < nget; i++) { + + /* do we have a ptr to the event ? */ + if (!events[i].portev_user) { + continue; + } + + /* fire the event */ + fpm_event_fire((struct fpm_event_s *)events[i].portev_user); + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + } + return nget; +} +/* }}} */ + +/* + * Add a FD to the fd set + */ +static int fpm_event_port_add(struct fpm_event_s *ev) /* {{{ */ +{ + /* add the event to port */ + if (port_associate(pfd, PORT_SOURCE_FD, ev->fd, POLLIN, (void *)ev) < 0) { + zlog(ZLOG_ERROR, "port: unable to add the event"); + return -1; + } + return 0; +} +/* }}} */ + +/* + * Remove a FD from the fd set + */ +static int fpm_event_port_remove(struct fpm_event_s *ev) /* {{{ */ +{ + /* remove the event from port */ + if (port_dissociate(pfd, PORT_SOURCE_FD, ev->fd) < 0) { + zlog(ZLOG_ERROR, "port: unable to add the event"); + return -1; + } + return 0; +} +/* }}} */ + +#endif /* HAVE_PORT */ diff --git a/sapi/fpm/fpm/events/port.h b/sapi/fpm/fpm/events/port.h new file mode 100644 index 0000000..666a157 --- /dev/null +++ b/sapi/fpm/fpm/events/port.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_PORT_H +#define FPM_EVENTS_PORT_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_port_module(); + +#endif /* FPM_EVENTS_PORT_H */ diff --git a/sapi/fpm/fpm/events/select.c b/sapi/fpm/fpm/events/select.c new file mode 100644 index 0000000..e3af067 --- /dev/null +++ b/sapi/fpm/fpm/events/select.c @@ -0,0 +1,175 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "../fpm_config.h" +#include "../fpm_events.h" +#include "../fpm.h" +#include "../zlog.h" + +#if HAVE_SELECT + +/* According to POSIX.1-2001 */ +#include <sys/select.h> + +/* According to earlier standards */ +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include <errno.h> + +static int fpm_event_select_init(int max); +static int fpm_event_select_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); +static int fpm_event_select_add(struct fpm_event_s *ev); +static int fpm_event_select_remove(struct fpm_event_s *ev); + +static struct fpm_event_module_s select_module = { + .name = "select", + .support_edge_trigger = 0, + .init = fpm_event_select_init, + .clean = NULL, + .wait = fpm_event_select_wait, + .add = fpm_event_select_add, + .remove = fpm_event_select_remove, +}; + +static fd_set fds; + +#endif /* HAVE_SELECT */ + +/* + * return the module configuration + */ +struct fpm_event_module_s *fpm_event_select_module() /* {{{ */ +{ +#if HAVE_SELECT + return &select_module; +#else + return NULL; +#endif /* HAVE_SELECT */ +} +/* }}} */ + +#if HAVE_SELECT + +/* + * Init the module + */ +static int fpm_event_select_init(int max) /* {{{ */ +{ + FD_ZERO(&fds); + return 0; +} +/* }}} */ + + +/* + * wait for events or timeout + */ +static int fpm_event_select_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ +{ + int ret; + struct fpm_event_queue_s *q; + fd_set current_fds; + struct timeval t; + + /* copy fds because select() alters it */ + current_fds = fds; + + /* fill struct timeval with timeout */ + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + + /* wait for inconming event or timeout */ + ret = select(FD_SETSIZE, ¤t_fds, NULL, NULL, &t); + if (ret == -1) { + + /* trigger error unless signal interrupt */ + if (errno != EINTR) { + zlog(ZLOG_WARNING, "poll() returns %d", errno); + return -1; + } + } + + /* events have been triggered */ + if (ret > 0) { + + /* trigger POLLIN events */ + q = queue; + while (q) { + if (q->ev) { /* sanity check */ + + /* check if the event has been triggered */ + if (FD_ISSET(q->ev->fd, ¤t_fds)) { + + /* fire the event */ + fpm_event_fire(q->ev); + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return -2; + } + } + } + q = q->next; /* iterate */ + } + } + return ret; + +} +/* }}} */ + +/* + * Add a FD to the fd set + */ +static int fpm_event_select_add(struct fpm_event_s *ev) /* {{{ */ +{ + /* check size limitation */ + if (ev->fd >= FD_SETSIZE) { + zlog(ZLOG_ERROR, "select: not enough space in the select fd list (max = %d). Please consider using another event mechanism.", FD_SETSIZE); + return -1; + } + + /* add the FD if not already in */ + if (!FD_ISSET(ev->fd, &fds)) { + FD_SET(ev->fd, &fds); + ev->index = ev->fd; + } + + return 0; +} +/* }}} */ + +/* + * Remove a FD from the fd set + */ +static int fpm_event_select_remove(struct fpm_event_s *ev) /* {{{ */ +{ + /* remove the fd if it's in */ + if (FD_ISSET(ev->fd, &fds)) { + FD_CLR(ev->fd, &fds); + ev->index = -1; + } + + return 0; +} +/* }}} */ + +#endif /* HAVE_SELECT */ diff --git a/sapi/fpm/fpm/events/select.h b/sapi/fpm/fpm/events/select.h new file mode 100644 index 0000000..db52503 --- /dev/null +++ b/sapi/fpm/fpm/events/select.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Jerome Loyet <jerome@loyet.net> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef FPM_EVENTS_SELECT_H +#define FPM_EVENTS_SELECT_H + +#include "../fpm_config.h" +#include "../fpm_events.h" + +struct fpm_event_module_s *fpm_event_select_module(); + +#endif /* FPM_EVENTS_SELECT_H */ diff --git a/sapi/fpm/fpm/fastcgi.c b/sapi/fpm/fpm/fastcgi.c new file mode 100644 index 0000000..cf3f098 --- /dev/null +++ b/sapi/fpm/fpm/fastcgi.c @@ -0,0 +1,1111 @@ +/* + +----------------------------------------------------------------------+ + | 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: fastcgi.c 287777 2009-08-26 19:17:32Z pajoye $ */ + +#include "php.h" +#include "fastcgi.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <limits.h> + +#include <php_config.h> +#include "fpm.h" +#include "fpm_request.h" +#include "zlog.h" + +#ifdef _WIN32 + +#include <windows.h> + + typedef unsigned int in_addr_t; + + struct sockaddr_un { + short sun_family; + char sun_path[MAXPATHLEN]; + }; + + static HANDLE fcgi_accept_mutex = INVALID_HANDLE_VALUE; + static int is_impersonate = 0; + +#define FCGI_LOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + DWORD ret; \ + while ((ret = WaitForSingleObject(fcgi_accept_mutex, 1000)) == WAIT_TIMEOUT) { \ + if (in_shutdown) return -1; \ + } \ + if (ret == WAIT_FAILED) { \ + fprintf(stderr, "WaitForSingleObject() failed\n"); \ + return -1; \ + } \ + } + +#define FCGI_UNLOCK(fd) \ + if (fcgi_accept_mutex != INVALID_HANDLE_VALUE) { \ + ReleaseMutex(fcgi_accept_mutex); \ + } + +#else + +# include <sys/types.h> +# include <sys/stat.h> +# include <unistd.h> +# include <fcntl.h> +# include <sys/socket.h> +# include <sys/un.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <signal.h> + +# define closesocket(s) close(s) + +# if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL) +# include <sys/poll.h> +# endif +# if defined(HAVE_SYS_SELECT_H) +# include <sys/select.h> +# endif + +#ifndef INADDR_NONE +#define INADDR_NONE ((unsigned long) -1) +#endif + +# ifndef HAVE_SOCKLEN_T + typedef unsigned int socklen_t; +# endif + +# ifdef USE_LOCKING +# define FCGI_LOCK(fd) \ + do { \ + struct flock lock; \ + lock.l_type = F_WRLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLKW, &lock) != -1) { \ + break; \ + } else if (errno != EINTR || in_shutdown) { \ + return -1; \ + } \ + } while (1) + +# define FCGI_UNLOCK(fd) \ + do { \ + int orig_errno = errno; \ + while (1) { \ + struct flock lock; \ + lock.l_type = F_UNLCK; \ + lock.l_start = 0; \ + lock.l_whence = SEEK_SET; \ + lock.l_len = 0; \ + if (fcntl(fd, F_SETLK, &lock) != -1) { \ + break; \ + } else if (errno != EINTR) { \ + return -1; \ + } \ + } \ + errno = orig_errno; \ + } while (0) +# else +# define FCGI_LOCK(fd) +# define FCGI_UNLOCK(fd) +# endif + +#endif + +typedef union _sa_t { + struct sockaddr sa; + struct sockaddr_un sa_unix; + struct sockaddr_in sa_inet; +} sa_t; + +static HashTable fcgi_mgmt_vars; + +static int is_initialized = 0; +static int in_shutdown = 0; +static in_addr_t *allowed_clients = NULL; + +static sa_t client_sa; + +#ifdef _WIN32 + +static DWORD WINAPI fcgi_shutdown_thread(LPVOID arg) +{ + HANDLE shutdown_event = (HANDLE) arg; + WaitForSingleObject(shutdown_event, INFINITE); + in_shutdown = 1; + return 0; +} + +#else + +static void fcgi_signal_handler(int signo) +{ + if (signo == SIGUSR1 || signo == SIGTERM) { + in_shutdown = 1; + } +} + +static void fcgi_setup_signals(void) +{ + struct sigaction new_sa, old_sa; + + sigemptyset(&new_sa.sa_mask); + new_sa.sa_flags = 0; + new_sa.sa_handler = fcgi_signal_handler; + sigaction(SIGUSR1, &new_sa, NULL); + sigaction(SIGTERM, &new_sa, NULL); + sigaction(SIGPIPE, NULL, &old_sa); + if (old_sa.sa_handler == SIG_DFL) { + sigaction(SIGPIPE, &new_sa, NULL); + } +} +#endif + +int fcgi_init(void) +{ + if (!is_initialized) { + zend_hash_init(&fcgi_mgmt_vars, 0, NULL, fcgi_free_mgmt_var_cb, 1); + fcgi_set_mgmt_var("FCGI_MPXS_CONNS", sizeof("FCGI_MPXS_CONNS") - 1, "0", sizeof("0")-1); + + is_initialized = 1; +#ifdef _WIN32 +# if 0 + /* TODO: Support for TCP sockets */ + WSADATA wsaData; + + if (WSAStartup(MAKEWORD(2,0), &wsaData)) { + fprintf(stderr, "Error starting Windows Sockets. Error: %d", WSAGetLastError()); + return 0; + } +# endif + { + char *str; + DWORD pipe_mode = PIPE_READMODE_BYTE | PIPE_WAIT; + HANDLE pipe = GetStdHandle(STD_INPUT_HANDLE); + + SetNamedPipeHandleState(pipe, &pipe_mode, NULL, NULL); + + str = getenv("_FCGI_SHUTDOWN_EVENT_"); + if (str != NULL) { + HANDLE shutdown_event = (HANDLE) atoi(str); + if (!CreateThread(NULL, 0, fcgi_shutdown_thread, + shutdown_event, 0, NULL)) { + return -1; + } + } + str = getenv("_FCGI_MUTEX_"); + if (str != NULL) { + fcgi_accept_mutex = (HANDLE) atoi(str); + } + return 1; + } +#else + fcgi_setup_signals(); + return 1; +#endif + } + return 1; +} + +void fcgi_set_in_shutdown(int new_value) +{ + in_shutdown = new_value; +} + +void fcgi_shutdown(void) +{ + if (is_initialized) { + zend_hash_destroy(&fcgi_mgmt_vars); + } + if (allowed_clients) { + free(allowed_clients); + } +} + +void fcgi_set_allowed_clients(char *ip) +{ + char *cur, *end; + int n; + + if (ip) { + ip = strdup(ip); + cur = ip; + n = 0; + while (*cur) { + if (*cur == ',') n++; + cur++; + } + if (allowed_clients) free(allowed_clients); + allowed_clients = malloc(sizeof(in_addr_t) * (n+2)); + n = 0; + cur = ip; + while (cur) { + end = strchr(cur, ','); + if (end) { + *end = 0; + end++; + } + allowed_clients[n] = inet_addr(cur); + if (allowed_clients[n] == INADDR_NONE) { + zlog(ZLOG_ERROR, "Wrong IP address '%s' in listen.allowed_clients", cur); + } + n++; + cur = end; + } + allowed_clients[n] = INADDR_NONE; + free(ip); + } +} + +void fcgi_init_request(fcgi_request *req, int listen_socket) +{ + memset(req, 0, sizeof(fcgi_request)); + req->listen_socket = listen_socket; + req->fd = -1; + req->id = -1; + + req->in_len = 0; + req->in_pad = 0; + + req->out_hdr = NULL; + req->out_pos = req->out_buf; + +#ifdef _WIN32 + req->tcp = !GetNamedPipeInfo((HANDLE)_get_osfhandle(req->listen_socket), NULL, NULL, NULL, NULL); +#endif +} + +static inline ssize_t safe_write(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + errno = 0; +#ifdef _WIN32 + if (!req->tcp) { + ret = write(req->fd, ((char*)buf)+n, count-n); + } else { + ret = send(req->fd, ((char*)buf)+n, count-n, 0); + if (ret <= 0) { + errno = WSAGetLastError(); + } + } +#else + ret = write(req->fd, ((char*)buf)+n, count-n); +#endif + if (ret > 0) { + n += ret; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + errno = 0; +#ifdef _WIN32 + if (!req->tcp) { + ret = read(req->fd, ((char*)buf)+n, count-n); + } else { + ret = recv(req->fd, ((char*)buf)+n, count-n, 0); + if (ret <= 0) { + errno = WSAGetLastError(); + } + } +#else + ret = read(req->fd, ((char*)buf)+n, count-n); +#endif + if (ret > 0) { + n += ret; + } else if (ret == 0 && errno == 0) { + return n; + } else if (ret <= 0 && errno != 0 && errno != EINTR) { + return ret; + } + } while (n != count); + return n; +} + +static inline int fcgi_make_header(fcgi_header *hdr, fcgi_request_type type, int req_id, int len) +{ + int pad = ((len + 7) & ~7) - len; + + hdr->contentLengthB0 = (unsigned char)(len & 0xff); + hdr->contentLengthB1 = (unsigned char)((len >> 8) & 0xff); + hdr->paddingLength = (unsigned char)pad; + hdr->requestIdB0 = (unsigned char)(req_id & 0xff); + hdr->requestIdB1 = (unsigned char)((req_id >> 8) & 0xff); + hdr->reserved = 0; + hdr->type = type; + hdr->version = FCGI_VERSION_1; + if (pad) { + memset(((unsigned char*)hdr) + sizeof(fcgi_header) + len, 0, pad); + } + return pad; +} + +static inline size_t fcgi_get_params_len( int *result, unsigned char *p, unsigned char *end) +{ + size_t ret = 0; + + if (p < end) { + *result = p[0]; + if (*result < 128) { + ret = 1; + } + else if (p + 3 < end) { + *result = ((*result & 0x7f) << 24); + *result |= (p[1] << 16); + *result |= (p[2] << 8); + *result |= p[3]; + ret = 4; + } + } + if (*result < 0) { + ret = 0; + } + return ret; +} + +static inline int fcgi_param_get_eff_len( unsigned char *p, unsigned char *end, uint *eff_len) +{ + int ret = 1; + int zero_found = 0; + *eff_len = 0; + for (; p != end; ++p) { + if (*p == '\0') { + zero_found = 1; + } + else { + if (zero_found) { + ret = 0; + break; + } + if (*eff_len < ((uint)-1)) { + ++*eff_len; + } + else { + ret = 0; + break; + } + } + } + return ret; +} + +static int fcgi_get_params(fcgi_request *req, unsigned char *p, unsigned char *end) +{ + char buf[128]; + char *tmp = buf; + size_t buf_size = sizeof(buf); + int name_len, val_len; + uint eff_name_len; + char *s; + int ret = 1; + size_t bytes_consumed; + + while (p < end) { + bytes_consumed = fcgi_get_params_len(&name_len, p, end); + if (!bytes_consumed) { + /* Malformated request */ + ret = 0; + break; + } + p += bytes_consumed; + bytes_consumed = fcgi_get_params_len(&val_len, p, end); + if (!bytes_consumed) { + /* Malformated request */ + ret = 0; + break; + } + p += bytes_consumed; + if (name_len > (INT_MAX - val_len) || /* would the addition overflow? */ + name_len + val_len > end - p) { /* would we exceed the buffer? */ + /* Malformated request */ + ret = 0; + break; + } + + /* + * get the effective length of the name in case it's not a valid string + * don't do this on the value because it can be binary data + */ + if (!fcgi_param_get_eff_len(p, p+name_len, &eff_name_len)){ + /* Malicious request */ + ret = 0; + break; + } + if (eff_name_len >= buf_size-1) { + if (eff_name_len > ((uint)-1)-64) { + ret = 0; + break; + } + buf_size = eff_name_len + 64; + tmp = (tmp == buf ? emalloc(buf_size): erealloc(tmp, buf_size)); + if (tmp == NULL) { + ret = 0; + break; + } + } + memcpy(tmp, p, eff_name_len); + tmp[eff_name_len] = 0; + s = estrndup((char*)p + name_len, val_len); + if (s == NULL) { + ret = 0; + break; + } + zend_hash_update(req->env, tmp, eff_name_len+1, &s, sizeof(char*), NULL); + p += name_len + val_len; + } + if (tmp != buf && tmp != NULL) { + efree(tmp); + } + return ret; +} + +static void fcgi_free_var(char **s) +{ + efree(*s); +} + +static int fcgi_read_request(fcgi_request *req) +{ + fcgi_header hdr; + int len, padding; + unsigned char buf[FCGI_MAX_LENGTH+8]; + + req->keep = 0; + req->closed = 0; + req->in_len = 0; + req->out_hdr = NULL; + req->out_pos = req->out_buf; + ALLOC_HASHTABLE(req->env); + zend_hash_init(req->env, 0, NULL, (void (*)(void *)) fcgi_free_var, 0); + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_STDIN && len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + + if (len + padding > FCGI_MAX_LENGTH) { + return 0; + } + + req->id = (hdr.requestIdB1 << 8) + hdr.requestIdB0; + + if (hdr.type == FCGI_BEGIN_REQUEST && len == sizeof(fcgi_begin_request)) { + char *val; + + if (safe_read(req, buf, len+padding) != len+padding) { + return 0; + } + + req->keep = (((fcgi_begin_request*)buf)->flags & FCGI_KEEP_CONN); + switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) { + case FCGI_RESPONDER: + val = estrdup("RESPONDER"); + zend_hash_update(req->env, "FCGI_ROLE", sizeof("FCGI_ROLE"), &val, sizeof(char*), NULL); + break; + case FCGI_AUTHORIZER: + val = estrdup("AUTHORIZER"); + zend_hash_update(req->env, "FCGI_ROLE", sizeof("FCGI_ROLE"), &val, sizeof(char*), NULL); + break; + case FCGI_FILTER: + val = estrdup("FILTER"); + zend_hash_update(req->env, "FCGI_ROLE", sizeof("FCGI_ROLE"), &val, sizeof(char*), NULL); + break; + default: + return 0; + } + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + return 0; + } + + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + + while (hdr.type == FCGI_PARAMS && len > 0) { + if (len + padding > FCGI_MAX_LENGTH) { + return 0; + } + + if (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + + if (!fcgi_get_params(req, buf, buf+len)) { + req->keep = 0; + return 0; + } + + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1) { + req->keep = 0; + return 0; + } + len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + padding = hdr.paddingLength; + } + } else if (hdr.type == FCGI_GET_VALUES) { + unsigned char *p = buf + sizeof(fcgi_header); + HashPosition pos; + char * str_index; + uint str_length; + ulong num_index; + int key_type; + zval ** value; + + if (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + + if (!fcgi_get_params(req, buf, buf+len)) { + req->keep = 0; + return 0; + } + + zend_hash_internal_pointer_reset_ex(req->env, &pos); + while ((key_type = zend_hash_get_current_key_ex(req->env, &str_index, &str_length, &num_index, 0, &pos)) != HASH_KEY_NON_EXISTANT) { + int zlen; + zend_hash_move_forward_ex(req->env, &pos); + if (key_type != HASH_KEY_IS_STRING) { + continue; + } + if (zend_hash_find(&fcgi_mgmt_vars, str_index, str_length, (void**) &value) != SUCCESS) { + continue; + } + --str_length; + zlen = Z_STRLEN_PP(value); + if ((p + 4 + 4 + str_length + zlen) >= (buf + sizeof(buf))) { + break; + } + if (str_length < 0x80) { + *p++ = str_length; + } else { + *p++ = ((str_length >> 24) & 0xff) | 0x80; + *p++ = (str_length >> 16) & 0xff; + *p++ = (str_length >> 8) & 0xff; + *p++ = str_length & 0xff; + } + if (zlen < 0x80) { + *p++ = zlen; + } else { + *p++ = ((zlen >> 24) & 0xff) | 0x80; + *p++ = (zlen >> 16) & 0xff; + *p++ = (zlen >> 8) & 0xff; + *p++ = zlen & 0xff; + } + memcpy(p, str_index, str_length); + p += str_length; + memcpy(p, Z_STRVAL_PP(value), zlen); + p += zlen; + } + len = p - buf - sizeof(fcgi_header); + len += fcgi_make_header((fcgi_header*)buf, FCGI_GET_VALUES_RESULT, 0, len); + if (safe_write(req, buf, sizeof(fcgi_header)+len) != (int)sizeof(fcgi_header)+len) { + req->keep = 0; + return 0; + } + return 0; + } else { + return 0; + } + + return 1; +} + +int fcgi_read(fcgi_request *req, char *str, int len) +{ + int ret, n, rest; + fcgi_header hdr; + unsigned char buf[255]; + + n = 0; + rest = len; + while (rest > 0) { + if (req->in_len == 0) { + if (safe_read(req, &hdr, sizeof(fcgi_header)) != sizeof(fcgi_header) || + hdr.version < FCGI_VERSION_1 || + hdr.type != FCGI_STDIN) { + req->keep = 0; + return 0; + } + req->in_len = (hdr.contentLengthB1 << 8) | hdr.contentLengthB0; + req->in_pad = hdr.paddingLength; + if (req->in_len == 0) { + return n; + } + } + + if (req->in_len >= rest) { + ret = safe_read(req, str, rest); + } else { + ret = safe_read(req, str, req->in_len); + } + if (ret < 0) { + req->keep = 0; + return ret; + } else if (ret > 0) { + req->in_len -= ret; + rest -= ret; + n += ret; + str += ret; + if (req->in_len == 0) { + if (req->in_pad) { + if (safe_read(req, buf, req->in_pad) != req->in_pad) { + req->keep = 0; + return ret; + } + } + } else { + return n; + } + } else { + return n; + } + } + return n; +} + +void fcgi_close(fcgi_request *req, int force, int destroy) +{ + if (destroy && req->env) { + zend_hash_destroy(req->env); + FREE_HASHTABLE(req->env); + req->env = NULL; + } + +#ifdef _WIN32 + if (is_impersonate && !req->tcp) { + RevertToSelf(); + } +#endif + + if ((force || !req->keep) && req->fd >= 0) { +#ifdef _WIN32 + if (!req->tcp) { + HANDLE pipe = (HANDLE)_get_osfhandle(req->fd); + + if (!force) { + FlushFileBuffers(pipe); + } + DisconnectNamedPipe(pipe); + } else { + if (!force) { + char buf[8]; + + shutdown(req->fd, 1); + while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} + } + closesocket(req->fd); + } +#else + if (!force) { + char buf[8]; + + shutdown(req->fd, 1); + while (recv(req->fd, buf, sizeof(buf), 0) > 0) {} + } + close(req->fd); +#endif + req->fd = -1; + fpm_request_finished(); + } +} + +int fcgi_accept_request(fcgi_request *req) +{ +#ifdef _WIN32 + HANDLE pipe; + OVERLAPPED ov; +#endif + + while (1) { + if (req->fd < 0) { + while (1) { + if (in_shutdown) { + return -1; + } +#ifdef _WIN32 + if (!req->tcp) { + pipe = (HANDLE)_get_osfhandle(req->listen_socket); + FCGI_LOCK(req->listen_socket); + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ConnectNamedPipe(pipe, &ov)) { + errno = GetLastError(); + if (errno == ERROR_IO_PENDING) { + while (WaitForSingleObject(ov.hEvent, 1000) == WAIT_TIMEOUT) { + if (in_shutdown) { + CloseHandle(ov.hEvent); + FCGI_UNLOCK(req->listen_socket); + return -1; + } + } + } else if (errno != ERROR_PIPE_CONNECTED) { + } + } + CloseHandle(ov.hEvent); + req->fd = req->listen_socket; + FCGI_UNLOCK(req->listen_socket); + } else { + SOCKET listen_socket = (SOCKET)_get_osfhandle(req->listen_socket); +#else + { + int listen_socket = req->listen_socket; +#endif + sa_t sa; + socklen_t len = sizeof(sa); + + fpm_request_accepting(); + + FCGI_LOCK(req->listen_socket); + req->fd = accept(listen_socket, (struct sockaddr *)&sa, &len); + FCGI_UNLOCK(req->listen_socket); + + client_sa = sa; + if (sa.sa.sa_family == AF_INET && req->fd >= 0 && allowed_clients) { + int n = 0; + int allowed = 0; + + while (allowed_clients[n] != INADDR_NONE) { + if (allowed_clients[n] == sa.sa_inet.sin_addr.s_addr) { + allowed = 1; + break; + } + n++; + } + if (!allowed) { + zlog(ZLOG_ERROR, "Connection disallowed: IP address '%s' has been dropped.", inet_ntoa(sa.sa_inet.sin_addr)); + closesocket(req->fd); + req->fd = -1; + continue; + } + } + } + +#ifdef _WIN32 + if (req->fd < 0 && (in_shutdown || errno != EINTR)) { +#else + if (req->fd < 0 && (in_shutdown || (errno != EINTR && errno != ECONNABORTED))) { +#endif + return -1; + } + +#ifdef _WIN32 + break; +#else + if (req->fd >= 0) { +#if defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL) + struct pollfd fds; + int ret; + + fpm_request_reading_headers(); + + fds.fd = req->fd; + fds.events = POLLIN; + fds.revents = 0; + do { + errno = 0; + ret = poll(&fds, 1, 5000); + } while (ret < 0 && errno == EINTR); + if (ret > 0 && (fds.revents & POLLIN)) { + break; + } + fcgi_close(req, 1, 0); +#else + fpm_request_reading_headers(); + + if (req->fd < FD_SETSIZE) { + struct timeval tv = {5,0}; + fd_set set; + int ret; + + FD_ZERO(&set); + FD_SET(req->fd, &set); + do { + errno = 0; + ret = select(req->fd + 1, &set, NULL, NULL, &tv) >= 0; + } while (ret < 0 && errno == EINTR); + if (ret > 0 && FD_ISSET(req->fd, &set)) { + break; + } + fcgi_close(req, 1, 0); + } else { + zlog(ZLOG_ERROR, "Too many open file descriptors. FD_SETSIZE limit exceeded."); + fcgi_close(req, 1, 0); + } +#endif + } +#endif + } + } else if (in_shutdown) { + return -1; + } + if (fcgi_read_request(req)) { +#ifdef _WIN32 + if (is_impersonate && !req->tcp) { + pipe = (HANDLE)_get_osfhandle(req->fd); + if (!ImpersonateNamedPipeClient(pipe)) { + fcgi_close(req, 1, 1); + continue; + } + } +#endif + return req->fd; + } else { + fcgi_close(req, 1, 1); + } + } +} + +static inline fcgi_header* open_packet(fcgi_request *req, fcgi_request_type type) +{ + req->out_hdr = (fcgi_header*) req->out_pos; + req->out_hdr->type = type; + req->out_pos += sizeof(fcgi_header); + return req->out_hdr; +} + +static inline void close_packet(fcgi_request *req) +{ + if (req->out_hdr) { + int len = req->out_pos - ((unsigned char*)req->out_hdr + sizeof(fcgi_header)); + + req->out_pos += fcgi_make_header(req->out_hdr, (fcgi_request_type)req->out_hdr->type, req->id, len); + req->out_hdr = NULL; + } +} + +int fcgi_flush(fcgi_request *req, int close) +{ + int len; + + close_packet(req); + + len = req->out_pos - req->out_buf; + + if (close) { + fcgi_end_request_rec *rec = (fcgi_end_request_rec*)(req->out_pos); + + fcgi_make_header(&rec->hdr, FCGI_END_REQUEST, req->id, sizeof(fcgi_end_request)); + rec->body.appStatusB3 = 0; + rec->body.appStatusB2 = 0; + rec->body.appStatusB1 = 0; + rec->body.appStatusB0 = 0; + rec->body.protocolStatus = FCGI_REQUEST_COMPLETE; + len += sizeof(fcgi_end_request_rec); + } + + if (safe_write(req, req->out_buf, len) != len) { + req->keep = 0; + return 0; + } + + req->out_pos = req->out_buf; + return 1; +} + +ssize_t fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len) +{ + int limit, rest; + + if (len <= 0) { + return 0; + } + + if (req->out_hdr && req->out_hdr->type != type) { + close_packet(req); + } + + /* Optimized version */ + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + if (!req->out_hdr) { + limit -= sizeof(fcgi_header); + if (limit < 0) limit = 0; + } + + if (len < limit) { + if (!req->out_hdr) { + open_packet(req, type); + } + memcpy(req->out_pos, str, len); + req->out_pos += len; + } else if (len - limit < sizeof(req->out_buf) - sizeof(fcgi_header)) { + if (!req->out_hdr) { + open_packet(req, type); + } + if (limit > 0) { + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + } + if (!fcgi_flush(req, 0)) { + return -1; + } + if (len > limit) { + open_packet(req, type); + memcpy(req->out_pos, str + limit, len - limit); + req->out_pos += len - limit; + } + } else { + int pos = 0; + int pad; + + close_packet(req); + while ((len - pos) > 0xffff) { + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, 0xfff8); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, 0xfff8) != 0xfff8) { + req->keep = 0; + return -1; + } + pos += 0xfff8; + } + + pad = (((len - pos) + 7) & ~7) - (len - pos); + rest = pad ? 8 - pad : 0; + + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, (len - pos) - rest); + req->out_hdr = NULL; + if (!fcgi_flush(req, 0)) { + return -1; + } + if (safe_write(req, str + pos, (len - pos) - rest) != (len - pos) - rest) { + req->keep = 0; + return -1; + } + if (pad) { + open_packet(req, type); + memcpy(req->out_pos, str + len - rest, rest); + req->out_pos += rest; + } + } + + return len; +} + +int fcgi_finish_request(fcgi_request *req, int force_close) +{ + int ret = 1; + + if (req->fd >= 0) { + if (!req->closed) { + ret = fcgi_flush(req, 1); + req->closed = 1; + } + fcgi_close(req, force_close, 1); + } + return ret; +} + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len) +{ + char **val; + + if (!req) return NULL; + + if (zend_hash_find(req->env, (char*)var, var_len+1, (void**)&val) == SUCCESS) { + return *val; + } + return NULL; +} + +char* fcgi_putenv(fcgi_request *req, char* var, int var_len, char* val) +{ + if (var && req) { + if (val == NULL) { + zend_hash_del(req->env, var, var_len+1); + } else { + char **ret; + + val = estrdup(val); + if (zend_hash_update(req->env, var, var_len+1, &val, sizeof(char*), (void**)&ret) == SUCCESS) { + return *ret; + } + } + } + return NULL; +} + +void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len) +{ + zval * zvalue; + zvalue = pemalloc(sizeof(*zvalue), 1); + Z_TYPE_P(zvalue) = IS_STRING; + Z_STRVAL_P(zvalue) = pestrndup(value, value_len, 1); + Z_STRLEN_P(zvalue) = value_len; + zend_hash_add(&fcgi_mgmt_vars, name, name_len + 1, &zvalue, sizeof(zvalue), NULL); +} + +void fcgi_free_mgmt_var_cb(void * ptr) +{ + zval ** var = (zval **)ptr; + pefree(Z_STRVAL_PP(var), 1); + pefree(*var, 1); +} + +char *fcgi_get_last_client_ip() /* {{{ */ +{ + if (client_sa.sa.sa_family == AF_UNIX) { + return NULL; + } + return inet_ntoa(client_sa.sa_inet.sin_addr); +} +/* }}} */ +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/fpm/fpm/fastcgi.h b/sapi/fpm/fpm/fastcgi.h new file mode 100644 index 0000000..ee78675 --- /dev/null +++ b/sapi/fpm/fpm/fastcgi.h @@ -0,0 +1,145 @@ +/* + +----------------------------------------------------------------------+ + | 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: fastcgi.h 272370 2008-12-31 11:15:49Z sebastian $ */ + +/* FastCGI protocol */ + +#define FCGI_VERSION_1 1 + +#define FCGI_MAX_LENGTH 0xffff + +#define FCGI_KEEP_CONN 1 + +typedef enum _fcgi_role { + FCGI_RESPONDER = 1, + FCGI_AUTHORIZER = 2, + FCGI_FILTER = 3 +} fcgi_role; + +typedef enum _fcgi_request_type { + FCGI_BEGIN_REQUEST = 1, /* [in] */ + FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */ + FCGI_END_REQUEST = 3, /* [out] */ + FCGI_PARAMS = 4, /* [in] environment variables */ + FCGI_STDIN = 5, /* [in] post data */ + FCGI_STDOUT = 6, /* [out] response */ + FCGI_STDERR = 7, /* [out] errors */ + FCGI_DATA = 8, /* [in] filter data (not supported) */ + FCGI_GET_VALUES = 9, /* [in] */ + FCGI_GET_VALUES_RESULT = 10 /* [out] */ +} fcgi_request_type; + +typedef enum _fcgi_protocol_status { + FCGI_REQUEST_COMPLETE = 0, + FCGI_CANT_MPX_CONN = 1, + FCGI_OVERLOADED = 2, + FCGI_UNKNOWN_ROLE = 3 +} dcgi_protocol_status; + +typedef struct _fcgi_header { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} fcgi_header; + +typedef struct _fcgi_begin_request { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} fcgi_begin_request; + +typedef struct _fcgi_begin_request_rec { + fcgi_header hdr; + fcgi_begin_request body; +} fcgi_begin_request_rec; + +typedef struct _fcgi_end_request { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} fcgi_end_request; + +typedef struct _fcgi_end_request_rec { + fcgi_header hdr; + fcgi_end_request body; +} fcgi_end_request_rec; + +/* FastCGI client API */ + +typedef struct _fcgi_request { + int listen_socket; +#ifdef _WIN32 + int tcp; +#endif + int fd; + int id; + int keep; + int closed; + + int in_len; + int in_pad; + + fcgi_header *out_hdr; + unsigned char *out_pos; + unsigned char out_buf[1024*8]; + unsigned char reserved[sizeof(fcgi_end_request_rec)]; + + HashTable *env; +} fcgi_request; + +int fcgi_init(void); +void fcgi_shutdown(void); +void fcgi_init_request(fcgi_request *req, int listen_socket); +int fcgi_accept_request(fcgi_request *req); +int fcgi_finish_request(fcgi_request *req, int force_close); + +void fcgi_set_in_shutdown(int); +void fcgi_set_allowed_clients(char *); +void fcgi_close(fcgi_request *req, int force, int destroy); + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len); +char* fcgi_putenv(fcgi_request *req, char* var, int var_len, char* val); + +int fcgi_read(fcgi_request *req, char *str, int len); + +ssize_t fcgi_write(fcgi_request *req, fcgi_request_type type, const char *str, int len); +int fcgi_flush(fcgi_request *req, int close); + +void fcgi_set_mgmt_var(const char * name, size_t name_len, const char * value, size_t value_len); +void fcgi_free_mgmt_var_cb(void * ptr); + +char *fcgi_get_last_client_ip(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/fpm/fpm/fpm.c b/sapi/fpm/fpm/fpm.c new file mode 100644 index 0000000..b866f37 --- /dev/null +++ b/sapi/fpm/fpm/fpm.c @@ -0,0 +1,123 @@ + + /* $Id: fpm.c,v 1.23 2008/07/20 16:38:31 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <stdlib.h> /* for exit */ + +#include "fpm.h" +#include "fpm_children.h" +#include "fpm_signals.h" +#include "fpm_env.h" +#include "fpm_events.h" +#include "fpm_cleanup.h" +#include "fpm_php.h" +#include "fpm_sockets.h" +#include "fpm_unix.h" +#include "fpm_process_ctl.h" +#include "fpm_conf.h" +#include "fpm_worker_pool.h" +#include "fpm_scoreboard.h" +#include "fpm_stdio.h" +#include "fpm_log.h" +#include "zlog.h" + +struct fpm_globals_s fpm_globals = { + .parent_pid = 0, + .argc = 0, + .argv = NULL, + .config = NULL, + .prefix = NULL, + .pid = NULL, + .running_children = 0, + .error_log_fd = 0, + .log_level = 0, + .listening_socket = 0, + .max_requests = 0, + .is_child = 0, + .test_successful = 0, + .heartbeat = 0, + .run_as_root = 0, + .send_config_pipe = {0, 0}, +}; + +int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon) /* {{{ */ +{ + fpm_globals.argc = argc; + fpm_globals.argv = argv; + if (config && *config) { + fpm_globals.config = strdup(config); + } + fpm_globals.prefix = prefix; + fpm_globals.pid = pid; + fpm_globals.run_as_root = run_as_root; + + if (0 > fpm_php_init_main() || + 0 > fpm_stdio_init_main() || + 0 > fpm_conf_init_main(test_conf, force_daemon) || + 0 > fpm_unix_init_main() || + 0 > fpm_scoreboard_init_main() || + 0 > fpm_pctl_init_main() || + 0 > fpm_env_init_main() || + 0 > fpm_signals_init_main() || + 0 > fpm_children_init_main() || + 0 > fpm_sockets_init_main() || + 0 > fpm_worker_pool_init_main() || + 0 > fpm_event_init_main()) { + + if (fpm_globals.test_successful) { + exit(FPM_EXIT_OK); + } else { + zlog(ZLOG_ERROR, "FPM initialization failed"); + return -1; + } + } + + if (0 > fpm_conf_write_pid()) { + zlog(ZLOG_ERROR, "FPM initialization failed"); + return -1; + } + + fpm_stdio_init_final(); + zlog(ZLOG_NOTICE, "fpm is running, pid %d", (int) fpm_globals.parent_pid); + + return 0; +} +/* }}} */ + +/* children: return listening socket + parent: never return */ +int fpm_run(int *max_requests) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + /* create initial children in all pools */ + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + int is_parent; + + is_parent = fpm_children_create_initial(wp); + + if (!is_parent) { + goto run_child; + } + + /* handle error */ + if (is_parent == 2) { + fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); + fpm_event_loop(1); + } + } + + /* run event loop forever */ + fpm_event_loop(0); + +run_child: /* only workers reach this point */ + + fpm_cleanups_run(FPM_CLEANUP_CHILD); + + *max_requests = fpm_globals.max_requests; + return fpm_globals.listening_socket; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm.h b/sapi/fpm/fpm/fpm.h new file mode 100644 index 0000000..65d0e0d --- /dev/null +++ b/sapi/fpm/fpm/fpm.h @@ -0,0 +1,63 @@ + + /* $Id: fpm.h,v 1.13 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_H +#define FPM_H 1 + +#include <unistd.h> + +#ifdef HAVE_SYSEXITS_H +#include <sysexits.h> +#endif + +#ifdef EX_OK +#define FPM_EXIT_OK EX_OK +#else +#define FPM_EXIT_OK 0 +#endif + +#ifdef EX_USAGE +#define FPM_EXIT_USAGE EX_USAGE +#else +#define FPM_EXIT_USAGE 64 +#endif + +#ifdef EX_SOFTWARE +#define FPM_EXIT_SOFTWARE EX_SOFTWARE +#else +#define FPM_EXIT_SOFTWARE 70 +#endif + +#ifdef EX_CONFIG +#define FPM_EXIT_CONFIG EX_CONFIG +#else +#define FPM_EXIT_CONFIG 78 +#endif + + +int fpm_run(int *max_requests); +int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon); + +struct fpm_globals_s { + pid_t parent_pid; + int argc; + char **argv; + char *config; + char *prefix; + char *pid; + int running_children; + int error_log_fd; + int log_level; + int listening_socket; /* for this child */ + int max_requests; /* for this child */ + int is_child; + int test_successful; + int heartbeat; + int run_as_root; + int send_config_pipe[2]; +}; + +extern struct fpm_globals_s fpm_globals; + +#endif diff --git a/sapi/fpm/fpm/fpm_arrays.h b/sapi/fpm/fpm/fpm_arrays.h new file mode 100644 index 0000000..02846b7 --- /dev/null +++ b/sapi/fpm/fpm/fpm_arrays.h @@ -0,0 +1,116 @@ + + /* $Id: fpm_arrays.h,v 1.2 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_ARRAYS_H +#define FPM_ARRAYS_H 1 + +#include <stdlib.h> +#include <string.h> + +struct fpm_array_s { + void *data; + size_t sz; + size_t used; + size_t allocated; +}; + +static inline struct fpm_array_s *fpm_array_init(struct fpm_array_s *a, unsigned int sz, unsigned int initial_num) /* {{{ */ +{ + void *allocated = 0; + + if (!a) { + a = malloc(sizeof(struct fpm_array_s)); + + if (!a) { + return 0; + } + + allocated = a; + } + + a->sz = sz; + + a->data = calloc(sz, initial_num); + + if (!a->data) { + free(allocated); + return 0; + } + + a->allocated = initial_num; + a->used = 0; + + return a; +} +/* }}} */ + +static inline void *fpm_array_item(struct fpm_array_s *a, unsigned int n) /* {{{ */ +{ + char *ret; + + ret = (char *) a->data + a->sz * n; + + return ret; +} +/* }}} */ + +static inline void *fpm_array_item_last(struct fpm_array_s *a) /* {{{ */ +{ + return fpm_array_item(a, a->used - 1); +} +/* }}} */ + +static inline int fpm_array_item_remove(struct fpm_array_s *a, unsigned int n) /* {{{ */ +{ + int ret = -1; + + if (n < a->used - 1) { + void *last = fpm_array_item(a, a->used - 1); + void *to_remove = fpm_array_item(a, n); + + memcpy(to_remove, last, a->sz); + + ret = n; + } + + --a->used; + + return ret; +} +/* }}} */ + +static inline void *fpm_array_push(struct fpm_array_s *a) /* {{{ */ +{ + void *ret; + + if (a->used == a->allocated) { + size_t new_allocated = a->allocated ? a->allocated * 2 : 20; + void *new_ptr = realloc(a->data, a->sz * new_allocated); + + if (!new_ptr) { + return 0; + } + + a->data = new_ptr; + a->allocated = new_allocated; + } + + ret = fpm_array_item(a, a->used); + + ++a->used; + + return ret; +} +/* }}} */ + +static inline void fpm_array_free(struct fpm_array_s *a) /* {{{ */ +{ + free(a->data); + a->data = 0; + a->sz = 0; + a->used = a->allocated = 0; +} +/* }}} */ + +#endif diff --git a/sapi/fpm/fpm/fpm_atomic.h b/sapi/fpm/fpm/fpm_atomic.h new file mode 100644 index 0000000..662dd47 --- /dev/null +++ b/sapi/fpm/fpm/fpm_atomic.h @@ -0,0 +1,168 @@ + + /* $Id: fpm_atomic.h,v 1.3 2008/09/18 23:34:11 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_ATOMIC_H +#define FPM_ATOMIC_H 1 + +#if HAVE_INTTYPES_H +# include <inttypes.h> +#else +# include <stdint.h> +#endif +#include <sched.h> + +#ifdef HAVE_BUILTIN_ATOMIC + +/** + * all the cases below (as provided by upstream) define: + * word as atomic_int_t, and + * unsigned word as atomic_uint_t + * and only use volatile atomic_uint_t as atomic_t + */ + +typedef volatile unsigned long atomic_t; +#define atomic_cmp_set(a,b,c) __sync_bool_compare_and_swap(a,b,c) + +#elif ( __i386__ || __i386 ) + +typedef int32_t atomic_int_t; +typedef uint32_t atomic_uint_t; +typedef volatile atomic_uint_t atomic_t; + + +static inline atomic_int_t atomic_fetch_add(atomic_t *value, atomic_int_t add) /* {{{ */ +{ + __asm__ volatile ( "lock;" "xaddl %0, %1;" : + "+r" (add) : "m" (*value) : "memory"); + + return add; +} +/* }}} */ + +static inline atomic_uint_t atomic_cmp_set(atomic_t *lock, atomic_uint_t old, atomic_uint_t set) /* {{{ */ +{ + unsigned char res; + + __asm__ volatile ( "lock;" "cmpxchgl %3, %1;" "sete %0;" : + "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "memory"); + + return res; +} +/* }}} */ + +#elif ( __amd64__ || __amd64 || __x86_64__ ) + +typedef int64_t atomic_int_t; +typedef uint64_t atomic_uint_t; +typedef volatile atomic_uint_t atomic_t; + +static inline atomic_int_t atomic_fetch_add(atomic_t *value, atomic_int_t add) /* {{{ */ +{ + __asm__ volatile ( "lock;" "xaddq %0, %1;" : + "+r" (add) : "m" (*value) : "memory"); + + return add; +} +/* }}} */ + +static inline atomic_uint_t atomic_cmp_set(atomic_t *lock, atomic_uint_t old, atomic_uint_t set) /* {{{ */ +{ + unsigned char res; + + __asm__ volatile ( "lock;" "cmpxchgq %3, %1;" "sete %0;" : + "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "memory"); + + return res; +} +/* }}} */ + +#if (__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 2)) + +#elif ( __arm__ || __arm ) /* W-Mark Kubacki */ + +#if (__arch64__ || __arch64) +typedef int64_t atomic_int_t; +typedef uint64_t atomic_uint_t; +#else +typedef int32_t atomic_int_t; +typedef uint32_t atomic_uint_t; +#endif + +#define atomic_cmp_set(a,b,c) __sync_bool_compare_and_swap(a,b,c) + +#endif /* defined (__GNUC__) &&... */ + +#elif ( __sparc__ || __sparc ) /* Marcin Ochab */ + +#if (__sparcv9 || __sparcv9__) + +#if (__arch64__ || __arch64) +typedef uint64_t atomic_uint_t; +typedef volatile atomic_uint_t atomic_t; + +static inline int atomic_cas_64(atomic_t *lock, atomic_uint_t old, atomic_uint_t new) /* {{{ */ +{ + __asm__ __volatile__("casx [%2], %3, %0 " : "=&r"(new) : "0"(new), "r"(lock), "r"(old): "memory"); + + return new; +} +/* }}} */ + +static inline atomic_uint_t atomic_cmp_set(atomic_t *lock, atomic_uint_t old, atomic_uint_t set) /* {{{ */ +{ + return (atomic_cas_64(lock, old, set)==old); +} +/* }}} */ +#else +typedef uint32_t atomic_uint_t; +typedef volatile atomic_uint_t atomic_t; + +static inline int atomic_cas_32(atomic_t *lock, atomic_uint_t old, atomic_uint_t new) /* {{{ */ +{ + __asm__ __volatile__("cas [%2], %3, %0 " : "=&r"(new) : "0"(new), "r"(lock), "r"(old): "memory"); + + return new; +} +/* }}} */ + +static inline atomic_uint_t atomic_cmp_set(atomic_t *lock, atomic_uint_t old, atomic_uint_t set) /* {{{ */ +{ + return (atomic_cas_32(lock, old, set)==old); +} +/* }}} */ +#endif + +#else /* #if (__sparcv9 || __sparcv9__) */ +#error Sparc v8 and predecessors are not and will not be supported (see bug report 53310) +#endif /* #if (__sparcv9 || __sparcv9__) */ + +#else + +#error Unsupported processor. Please open a bug report (bugs.php.net). + +#endif + +static inline int fpm_spinlock(atomic_t *lock, int try_once) /* {{{ */ +{ + if (try_once) { + return atomic_cmp_set(lock, 0, 1) ? 1 : 0; + } + + for (;;) { + + if (atomic_cmp_set(lock, 0, 1)) { + break; + } + + sched_yield(); + } + + return 1; +} +/* }}} */ + +#define fpm_unlock(lock) lock = 0 + +#endif + diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c new file mode 100644 index 0000000..84a9474 --- /dev/null +++ b/sapi/fpm/fpm/fpm_children.c @@ -0,0 +1,478 @@ + + /* $Id: fpm_children.c,v 1.32.2.2 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include "fpm.h" +#include "fpm_children.h" +#include "fpm_signals.h" +#include "fpm_worker_pool.h" +#include "fpm_sockets.h" +#include "fpm_process_ctl.h" +#include "fpm_php.h" +#include "fpm_conf.h" +#include "fpm_cleanup.h" +#include "fpm_events.h" +#include "fpm_clock.h" +#include "fpm_stdio.h" +#include "fpm_unix.h" +#include "fpm_env.h" +#include "fpm_scoreboard.h" +#include "fpm_status.h" +#include "fpm_log.h" + +#include "zlog.h" + +static time_t *last_faults; +static int fault; + +static void fpm_children_cleanup(int which, void *arg) /* {{{ */ +{ + free(last_faults); +} +/* }}} */ + +static struct fpm_child_s *fpm_child_alloc() /* {{{ */ +{ + struct fpm_child_s *ret; + + ret = malloc(sizeof(struct fpm_child_s)); + + if (!ret) { + return 0; + } + + memset(ret, 0, sizeof(*ret)); + ret->scoreboard_i = -1; + return ret; +} +/* }}} */ + +static void fpm_child_free(struct fpm_child_s *child) /* {{{ */ +{ + free(child); +} +/* }}} */ + +static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ */ +{ + if (child->fd_stdout != -1) { + if (in_event_loop) { + fpm_event_fire(&child->ev_stdout); + } + if (child->fd_stdout != -1) { + close(child->fd_stdout); + } + } + + if (child->fd_stderr != -1) { + if (in_event_loop) { + fpm_event_fire(&child->ev_stderr); + } + if (child->fd_stderr != -1) { + close(child->fd_stderr); + } + } + + fpm_child_free(child); +} +/* }}} */ + +static void fpm_child_link(struct fpm_child_s *child) /* {{{ */ +{ + struct fpm_worker_pool_s *wp = child->wp; + + ++wp->running_children; + ++fpm_globals.running_children; + + child->next = wp->children; + if (child->next) { + child->next->prev = child; + } + child->prev = 0; + wp->children = child; +} +/* }}} */ + +static void fpm_child_unlink(struct fpm_child_s *child) /* {{{ */ +{ + --child->wp->running_children; + --fpm_globals.running_children; + + if (child->prev) { + child->prev->next = child->next; + } else { + child->wp->children = child->next; + } + + if (child->next) { + child->next->prev = child->prev; + } +} +/* }}} */ + +static struct fpm_child_s *fpm_child_find(pid_t pid) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + struct fpm_child_s *child = 0; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + + for (child = wp->children; child; child = child->next) { + if (child->pid == pid) { + break; + } + } + + if (child) break; + } + + if (!child) { + return 0; + } + + return child; +} +/* }}} */ + +static void fpm_child_init(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + fpm_globals.max_requests = wp->config->pm_max_requests; + + if (0 > fpm_stdio_init_child(wp) || + 0 > fpm_log_init_child(wp) || + 0 > fpm_status_init_child(wp) || + 0 > fpm_unix_init_child(wp) || + 0 > fpm_signals_init_child() || + 0 > fpm_env_init_child(wp) || + 0 > fpm_php_init_child(wp)) { + + zlog(ZLOG_ERROR, "[pool %s] child failed to initialize", wp->config->name); + exit(FPM_EXIT_SOFTWARE); + } +} +/* }}} */ + +int fpm_children_free(struct fpm_child_s *child) /* {{{ */ +{ + struct fpm_child_s *next; + + for (; child; child = next) { + next = child->next; + fpm_child_close(child, 0 /* in_event_loop */); + } + + return 0; +} +/* }}} */ + +void fpm_children_bury() /* {{{ */ +{ + int status; + pid_t pid; + struct fpm_child_s *child; + + while ( (pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + char buf[128]; + int severity = ZLOG_NOTICE; + int restart_child = 1; + + child = fpm_child_find(pid); + + if (WIFEXITED(status)) { + + snprintf(buf, sizeof(buf), "with code %d", WEXITSTATUS(status)); + + /* if it's been killed because of dynamic process management + * don't restart it automaticaly + */ + if (child && child->idle_kill) { + restart_child = 0; + } + + if (WEXITSTATUS(status) != FPM_EXIT_OK) { + severity = ZLOG_WARNING; + } + + } else if (WIFSIGNALED(status)) { + const char *signame = fpm_signal_names[WTERMSIG(status)]; + const char *have_core = WCOREDUMP(status) ? " - core dumped" : ""; + + if (signame == NULL) { + signame = ""; + } + + snprintf(buf, sizeof(buf), "on signal %d (%s%s)", WTERMSIG(status), signame, have_core); + + /* if it's been killed because of dynamic process management + * don't restart it automaticaly + */ + if (child && child->idle_kill && WTERMSIG(status) == SIGQUIT) { + restart_child = 0; + } + + if (WTERMSIG(status) != SIGQUIT) { /* possible request loss */ + severity = ZLOG_WARNING; + } + } else if (WIFSTOPPED(status)) { + + zlog(ZLOG_NOTICE, "child %d stopped for tracing", (int) pid); + + if (child && child->tracer) { + child->tracer(child); + } + + continue; + } + + if (child) { + struct fpm_worker_pool_s *wp = child->wp; + struct timeval tv1, tv2; + + fpm_child_unlink(child); + + fpm_scoreboard_proc_free(wp->scoreboard, child->scoreboard_i); + + fpm_clock_get(&tv1); + + timersub(&tv1, &child->started, &tv2); + + if (restart_child) { + if (!fpm_pctl_can_spawn_children()) { + severity = ZLOG_DEBUG; + } + zlog(severity, "[pool %s] child %d exited %s after %ld.%06d seconds from start", child->wp->config->name, (int) pid, buf, tv2.tv_sec, (int) tv2.tv_usec); + } else { + zlog(ZLOG_DEBUG, "[pool %s] child %d has been killed by the process managment after %ld.%06d seconds from start", child->wp->config->name, (int) pid, tv2.tv_sec, (int) tv2.tv_usec); + } + + fpm_child_close(child, 1 /* in event_loop */); + + fpm_pctl_child_exited(); + + if (last_faults && (WTERMSIG(status) == SIGSEGV || WTERMSIG(status) == SIGBUS)) { + time_t now = tv1.tv_sec; + int restart_condition = 1; + int i; + + last_faults[fault++] = now; + + if (fault == fpm_global_config.emergency_restart_threshold) { + fault = 0; + } + + for (i = 0; i < fpm_global_config.emergency_restart_threshold; i++) { + if (now - last_faults[i] > fpm_global_config.emergency_restart_interval) { + restart_condition = 0; + break; + } + } + + if (restart_condition) { + + zlog(ZLOG_WARNING, "failed processes threshold (%d in %d sec) is reached, initiating reload", fpm_global_config.emergency_restart_threshold, fpm_global_config.emergency_restart_interval); + + fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); + } + } + + if (restart_child) { + fpm_children_make(wp, 1 /* in event loop */, 1, 0); + + if (fpm_globals.is_child) { + break; + } + } + } else { + zlog(ZLOG_ALERT, "oops, unknown child (%d) exited %s. Please open a bug report (https://bugs.php.net).", pid, buf); + } + } +} +/* }}} */ + +static struct fpm_child_s *fpm_resources_prepare(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct fpm_child_s *c; + + c = fpm_child_alloc(); + + if (!c) { + zlog(ZLOG_ERROR, "[pool %s] unable to malloc new child", wp->config->name); + return 0; + } + + c->wp = wp; + c->fd_stdout = -1; c->fd_stderr = -1; + + if (0 > fpm_stdio_prepare_pipes(c)) { + fpm_child_free(c); + return 0; + } + + if (0 > fpm_scoreboard_proc_alloc(wp->scoreboard, &c->scoreboard_i)) { + fpm_stdio_discard_pipes(c); + fpm_child_free(c); + return 0; + } + + return c; +} +/* }}} */ + +static void fpm_resources_discard(struct fpm_child_s *child) /* {{{ */ +{ + fpm_scoreboard_proc_free(child->wp->scoreboard, child->scoreboard_i); + fpm_stdio_discard_pipes(child); + fpm_child_free(child); +} +/* }}} */ + +static void fpm_child_resources_use(struct fpm_child_s *child) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (wp == child->wp) { + continue; + } + fpm_scoreboard_free(wp->scoreboard); + } + + fpm_scoreboard_child_use(child->wp->scoreboard, child->scoreboard_i, getpid()); + fpm_stdio_child_use_pipes(child); + fpm_child_free(child); +} +/* }}} */ + +static void fpm_parent_resources_use(struct fpm_child_s *child) /* {{{ */ +{ + fpm_stdio_parent_use_pipes(child); + fpm_child_link(child); +} +/* }}} */ + +int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug) /* {{{ */ +{ + pid_t pid; + struct fpm_child_s *child; + int max; + static int warned = 0; + + if (wp->config->pm == PM_STYLE_DYNAMIC) { + if (!in_event_loop) { /* starting */ + max = wp->config->pm_start_servers; + } else { + max = wp->running_children + nb_to_spawn; + } + } else if (wp->config->pm == PM_STYLE_ONDEMAND) { + if (!in_event_loop) { /* starting */ + max = 0; /* do not create any child at startup */ + } else { + max = wp->running_children + nb_to_spawn; + } + } else { /* PM_STYLE_STATIC */ + max = wp->config->pm_max_children; + } + + /* + * fork children while: + * - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload) + * - wp->running_children < max : there is less than the max process for the current pool + * - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max): + * if fpm_global_config.process_max is set, FPM has not fork this number of processes (globaly) + */ + while (fpm_pctl_can_spawn_children() && wp->running_children < max && (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max)) { + + warned = 0; + child = fpm_resources_prepare(wp); + + if (!child) { + return 2; + } + + pid = fork(); + + switch (pid) { + + case 0 : + fpm_child_resources_use(child); + fpm_globals.is_child = 1; + fpm_child_init(wp); + return 0; + + case -1 : + zlog(ZLOG_SYSERROR, "fork() failed"); + + fpm_resources_discard(child); + return 2; + + default : + child->pid = pid; + fpm_clock_get(&child->started); + fpm_parent_resources_use(child); + + zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid); + } + + } + + if (!warned && fpm_global_config.process_max > 0 && fpm_globals.running_children >= fpm_global_config.process_max) { + warned = 1; + zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'"); + } + + return 1; /* we are done */ +} +/* }}} */ + +int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + if (wp->config->pm == PM_STYLE_ONDEMAND) { + wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s)); + + if (!wp->ondemand_event) { + zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name); + // FIXME handle crash + return 1; + } + + memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s)); + fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp); + wp->socket_event_set = 1; + fpm_event_add(wp->ondemand_event, 0); + + return 1; + } + return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1); +} +/* }}} */ + +int fpm_children_init_main() /* {{{ */ +{ + if (fpm_global_config.emergency_restart_threshold && + fpm_global_config.emergency_restart_interval) { + + last_faults = malloc(sizeof(time_t) * fpm_global_config.emergency_restart_threshold); + + if (!last_faults) { + return -1; + } + + memset(last_faults, 0, sizeof(time_t) * fpm_global_config.emergency_restart_threshold); + } + + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_children_cleanup, 0)) { + return -1; + } + + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_children.h b/sapi/fpm/fpm/fpm_children.h new file mode 100644 index 0000000..9c79f23 --- /dev/null +++ b/sapi/fpm/fpm/fpm_children.h @@ -0,0 +1,36 @@ + + /* $Id: fpm_children.h,v 1.9 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_CHILDREN_H +#define FPM_CHILDREN_H 1 + +#include <sys/time.h> +#include <sys/types.h> + +#include "fpm_worker_pool.h" +#include "fpm_events.h" + +int fpm_children_create_initial(struct fpm_worker_pool_s *wp); +int fpm_children_free(struct fpm_child_s *child); +void fpm_children_bury(); +int fpm_children_init_main(); +int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug); + +struct fpm_child_s; + +struct fpm_child_s { + struct fpm_child_s *prev, *next; + struct timeval started; + struct fpm_worker_pool_s *wp; + struct fpm_event_s ev_stdout, ev_stderr; + int shm_slot_i; + int fd_stdout, fd_stderr; + void (*tracer)(struct fpm_child_s *); + struct timeval slow_logged; + int idle_kill; + pid_t pid; + int scoreboard_i; +}; + +#endif diff --git a/sapi/fpm/fpm/fpm_cleanup.c b/sapi/fpm/fpm/fpm_cleanup.c new file mode 100644 index 0000000..260ddb3 --- /dev/null +++ b/sapi/fpm/fpm/fpm_cleanup.c @@ -0,0 +1,52 @@ + + /* $Id: fpm_cleanup.c,v 1.8 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <stdlib.h> + +#include "fpm_arrays.h" +#include "fpm_cleanup.h" + +struct cleanup_s { + int type; + void (*cleanup)(int, void *); + void *arg; +}; + +static struct fpm_array_s cleanups = { .sz = sizeof(struct cleanup_s) }; + +int fpm_cleanup_add(int type, void (*cleanup)(int, void *), void *arg) /* {{{ */ +{ + struct cleanup_s *c; + + c = fpm_array_push(&cleanups); + + if (!c) { + return -1; + } + + c->type = type; + c->cleanup = cleanup; + c->arg = arg; + + return 0; +} +/* }}} */ + +void fpm_cleanups_run(int type) /* {{{ */ +{ + struct cleanup_s *c = fpm_array_item_last(&cleanups); + int cl = cleanups.used; + + for ( ; cl--; c--) { + if (c->type & type) { + c->cleanup(type, c->arg); + } + } + + fpm_array_free(&cleanups); +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_cleanup.h b/sapi/fpm/fpm/fpm_cleanup.h new file mode 100644 index 0000000..4d7cf39 --- /dev/null +++ b/sapi/fpm/fpm/fpm_cleanup.h @@ -0,0 +1,21 @@ + + /* $Id: fpm_cleanup.h,v 1.5 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_CLEANUP_H +#define FPM_CLEANUP_H 1 + +int fpm_cleanup_add(int type, void (*cleanup)(int, void *), void *); +void fpm_cleanups_run(int type); + +enum { + FPM_CLEANUP_CHILD = (1 << 0), + FPM_CLEANUP_PARENT_EXIT = (1 << 1), + FPM_CLEANUP_PARENT_EXIT_MAIN = (1 << 2), + FPM_CLEANUP_PARENT_EXEC = (1 << 3), + FPM_CLEANUP_PARENT = (1 << 1) | (1 << 2) | (1 << 3), + FPM_CLEANUP_ALL = ~0, +}; + +#endif + diff --git a/sapi/fpm/fpm/fpm_clock.c b/sapi/fpm/fpm/fpm_clock.c new file mode 100644 index 0000000..66751b8 --- /dev/null +++ b/sapi/fpm/fpm/fpm_clock.c @@ -0,0 +1,121 @@ + + /* $Id: fpm_clock.c,v 1.4 2008/09/18 23:19:59 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#if defined(HAVE_CLOCK_GETTIME) +#include <time.h> /* for CLOCK_MONOTONIC */ +#endif + +#include "fpm_clock.h" +#include "zlog.h" + + +/* posix monotonic clock - preferred source of time */ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + +static int monotonic_works; + +int fpm_clock_init() /* {{{ */ +{ + struct timespec ts; + + monotonic_works = 0; + + if (0 == clock_gettime(CLOCK_MONOTONIC, &ts)) { + monotonic_works = 1; + } + + return 0; +} +/* }}} */ + +int fpm_clock_get(struct timeval *tv) /* {{{ */ +{ + if (monotonic_works) { + struct timespec ts; + + if (0 > clock_gettime(CLOCK_MONOTONIC, &ts)) { + zlog(ZLOG_SYSERROR, "clock_gettime() failed"); + return -1; + } + + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + return 0; + } + + return gettimeofday(tv, 0); +} +/* }}} */ + +/* macosx clock */ +#elif defined(HAVE_CLOCK_GET_TIME) + +#include <mach/mach.h> +#include <mach/clock.h> +#include <mach/mach_error.h> + +static clock_serv_t mach_clock; + +/* this code borrowed from here: http://lists.apple.com/archives/Darwin-development/2002/Mar/msg00746.html */ +/* mach_clock also should be re-initialized in child process after fork */ +int fpm_clock_init() /* {{{ */ +{ + kern_return_t ret; + mach_timespec_t aTime; + + ret = host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &mach_clock); + + if (ret != KERN_SUCCESS) { + zlog(ZLOG_ERROR, "host_get_clock_service() failed: %s", mach_error_string(ret)); + return -1; + } + + /* test if it works */ + ret = clock_get_time(mach_clock, &aTime); + + if (ret != KERN_SUCCESS) { + zlog(ZLOG_ERROR, "clock_get_time() failed: %s", mach_error_string(ret)); + return -1; + } + + return 0; +} +/* }}} */ + +int fpm_clock_get(struct timeval *tv) /* {{{ */ +{ + kern_return_t ret; + mach_timespec_t aTime; + + ret = clock_get_time(mach_clock, &aTime); + + if (ret != KERN_SUCCESS) { + zlog(ZLOG_ERROR, "clock_get_time() failed: %s", mach_error_string(ret)); + return -1; + } + + tv->tv_sec = aTime.tv_sec; + tv->tv_usec = aTime.tv_nsec / 1000; + + return 0; +} +/* }}} */ + +#else /* no clock */ + +int fpm_clock_init() /* {{{ */ +{ + return 0; +} +/* }}} */ + +int fpm_clock_get(struct timeval *tv) /* {{{ */ +{ + return gettimeofday(tv, 0); +} +/* }}} */ + +#endif diff --git a/sapi/fpm/fpm/fpm_clock.h b/sapi/fpm/fpm/fpm_clock.h new file mode 100644 index 0000000..6aab959 --- /dev/null +++ b/sapi/fpm/fpm/fpm_clock.h @@ -0,0 +1,13 @@ + + /* $Id: fpm_clock.h,v 1.2 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_CLOCK_H +#define FPM_CLOCK_H 1 + +#include <sys/time.h> + +int fpm_clock_init(); +int fpm_clock_get(struct timeval *tv); + +#endif diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c new file mode 100644 index 0000000..25e2cc4 --- /dev/null +++ b/sapi/fpm/fpm/fpm_conf.c @@ -0,0 +1,1658 @@ + + /* $Id: fpm_conf.c,v 1.33.2.3 2008/12/13 03:50:29 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#if HAVE_INTTYPES_H +# include <inttypes.h> +#else +# include <stdint.h> +#endif +#ifdef HAVE_GLOB +# ifndef PHP_WIN32 +# include <glob.h> +# else +# include "win32/glob.h" +# endif +#endif + +#include <stdio.h> +#include <unistd.h> + +#include "php.h" +#include "zend_ini_scanner.h" +#include "zend_globals.h" +#include "zend_stream.h" +#include "php_syslog.h" + +#include "fpm.h" +#include "fpm_conf.h" +#include "fpm_stdio.h" +#include "fpm_worker_pool.h" +#include "fpm_cleanup.h" +#include "fpm_php.h" +#include "fpm_sockets.h" +#include "fpm_shm.h" +#include "fpm_status.h" +#include "fpm_log.h" +#include "fpm_events.h" +#include "zlog.h" + +#define STR2STR(a) (a ? a : "undefined") +#define BOOL2STR(a) (a ? "yes" : "no") +#define GO(field) offsetof(struct fpm_global_config_s, field) +#define WPO(field) offsetof(struct fpm_worker_pool_config_s, field) + +static int fpm_conf_load_ini_file(char *filename TSRMLS_DC); +static char *fpm_conf_set_integer(zval *value, void **config, intptr_t offset); +#if 0 /* not used for now */ +static char *fpm_conf_set_long(zval *value, void **config, intptr_t offset); +#endif +static char *fpm_conf_set_time(zval *value, void **config, intptr_t offset); +static char *fpm_conf_set_boolean(zval *value, void **config, intptr_t offset); +static char *fpm_conf_set_string(zval *value, void **config, intptr_t offset); +static char *fpm_conf_set_log_level(zval *value, void **config, intptr_t offset); +static char *fpm_conf_set_rlimit_core(zval *value, void **config, intptr_t offset); +static char *fpm_conf_set_pm(zval *value, void **config, intptr_t offset); +#ifdef HAVE_SYSLOG_H +static char *fpm_conf_set_syslog_facility(zval *value, void **config, intptr_t offset); +#endif + +struct fpm_global_config_s fpm_global_config = { + .daemonize = 1, +#ifdef HAVE_SYSLOG_H + .syslog_facility = -1, +#endif + .process_max = 0, + .process_priority = 64, /* 64 means unset */ +}; +static struct fpm_worker_pool_s *current_wp = NULL; +static int ini_recursion = 0; +static char *ini_filename = NULL; +static int ini_lineno = 0; +static char *ini_include = NULL; + +/* + * Please keep the same order as in fpm_conf.h and in php-fpm.conf.in + */ +static struct ini_value_parser_s ini_fpm_global_options[] = { + { "pid", &fpm_conf_set_string, GO(pid_file) }, + { "error_log", &fpm_conf_set_string, GO(error_log) }, +#ifdef HAVE_SYSLOG_H + { "syslog.ident", &fpm_conf_set_string, GO(syslog_ident) }, + { "syslog.facility", &fpm_conf_set_syslog_facility, GO(syslog_facility) }, +#endif + { "log_level", &fpm_conf_set_log_level, GO(log_level) }, + { "emergency_restart_threshold", &fpm_conf_set_integer, GO(emergency_restart_threshold) }, + { "emergency_restart_interval", &fpm_conf_set_time, GO(emergency_restart_interval) }, + { "process_control_timeout", &fpm_conf_set_time, GO(process_control_timeout) }, + { "process.max", &fpm_conf_set_integer, GO(process_max) }, + { "process.priority", &fpm_conf_set_integer, GO(process_priority) }, + { "daemonize", &fpm_conf_set_boolean, GO(daemonize) }, + { "rlimit_files", &fpm_conf_set_integer, GO(rlimit_files) }, + { "rlimit_core", &fpm_conf_set_rlimit_core, GO(rlimit_core) }, + { "events.mechanism", &fpm_conf_set_string, GO(events_mechanism) }, + { 0, 0, 0 } +}; + +/* + * Please keep the same order as in fpm_conf.h and in php-fpm.conf.in + */ +static struct ini_value_parser_s ini_fpm_pool_options[] = { + { "prefix", &fpm_conf_set_string, WPO(prefix) }, + { "user", &fpm_conf_set_string, WPO(user) }, + { "group", &fpm_conf_set_string, WPO(group) }, + { "listen", &fpm_conf_set_string, WPO(listen_address) }, + { "listen.backlog", &fpm_conf_set_integer, WPO(listen_backlog) }, + { "listen.owner", &fpm_conf_set_string, WPO(listen_owner) }, + { "listen.group", &fpm_conf_set_string, WPO(listen_group) }, + { "listen.mode", &fpm_conf_set_string, WPO(listen_mode) }, + { "listen.allowed_clients", &fpm_conf_set_string, WPO(listen_allowed_clients) }, + { "process.priority", &fpm_conf_set_integer, WPO(process_priority) }, + { "pm", &fpm_conf_set_pm, WPO(pm) }, + { "pm.max_children", &fpm_conf_set_integer, WPO(pm_max_children) }, + { "pm.start_servers", &fpm_conf_set_integer, WPO(pm_start_servers) }, + { "pm.min_spare_servers", &fpm_conf_set_integer, WPO(pm_min_spare_servers) }, + { "pm.max_spare_servers", &fpm_conf_set_integer, WPO(pm_max_spare_servers) }, + { "pm.process_idle_timeout", &fpm_conf_set_time, WPO(pm_process_idle_timeout) }, + { "pm.max_requests", &fpm_conf_set_integer, WPO(pm_max_requests) }, + { "pm.status_path", &fpm_conf_set_string, WPO(pm_status_path) }, + { "ping.path", &fpm_conf_set_string, WPO(ping_path) }, + { "ping.response", &fpm_conf_set_string, WPO(ping_response) }, + { "access.log", &fpm_conf_set_string, WPO(access_log) }, + { "access.format", &fpm_conf_set_string, WPO(access_format) }, + { "slowlog", &fpm_conf_set_string, WPO(slowlog) }, + { "request_slowlog_timeout", &fpm_conf_set_time, WPO(request_slowlog_timeout) }, + { "request_terminate_timeout", &fpm_conf_set_time, WPO(request_terminate_timeout) }, + { "rlimit_files", &fpm_conf_set_integer, WPO(rlimit_files) }, + { "rlimit_core", &fpm_conf_set_rlimit_core, WPO(rlimit_core) }, + { "chroot", &fpm_conf_set_string, WPO(chroot) }, + { "chdir", &fpm_conf_set_string, WPO(chdir) }, + { "catch_workers_output", &fpm_conf_set_boolean, WPO(catch_workers_output) }, + { "security.limit_extensions", &fpm_conf_set_string, WPO(security_limit_extensions) }, + { 0, 0, 0 } +}; + +static int fpm_conf_is_dir(char *path) /* {{{ */ +{ + struct stat sb; + + if (stat(path, &sb) != 0) { + return 0; + } + + return (sb.st_mode & S_IFMT) == S_IFDIR; +} +/* }}} */ + +/* + * Expands the '$pool' token in a dynamically allocated string + */ +static int fpm_conf_expand_pool_name(char **value) { + char *token; + + if (!value || !*value) { + return 0; + } + + while (*value && (token = strstr(*value, "$pool"))) { + char *buf; + char *p2 = token + strlen("$pool"); + + /* If we are not in a pool, we cannot expand this name now */ + if (!current_wp || !current_wp->config || !current_wp->config->name) { + return -1; + } + + /* "aaa$poolbbb" becomes "aaa\0oolbbb" */ + token[0] = '\0'; + + /* Build a brand new string with the expanded token */ + spprintf(&buf, 0, "%s%s%s", *value, current_wp->config->name, p2); + + /* Free the previous value and save the new one */ + free(*value); + *value = strdup(buf); + efree(buf); + } + + return 0; +} + +static char *fpm_conf_set_boolean(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + long value_y = !strcasecmp(val, "1"); + long value_n = !strcasecmp(val, ""); + + if (!value_y && !value_n) { + return "invalid boolean value"; + } + + * (int *) ((char *) *config + offset) = value_y ? 1 : 0; + return NULL; +} +/* }}} */ + +static char *fpm_conf_set_string(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char **config_val = (char **) ((char *) *config + offset); + + if (!config_val) { + return "internal error: NULL value"; + } + + /* Check if there is a previous value to deallocate */ + if (*config_val) { + free(*config_val); + } + + *config_val = strdup(Z_STRVAL_P(value)); + if (!*config_val) { + return "fpm_conf_set_string(): strdup() failed"; + } + if (fpm_conf_expand_pool_name(config_val) == -1) { + return "Can't use '$pool' when the pool is not defined"; + } + + return NULL; +} +/* }}} */ + +static char *fpm_conf_set_integer(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + char *p; + + /* we don't use strtol because we don't want to allow negative values */ + for (p = val; *p; p++) { + if (p == val && *p == '-') continue; + if (*p < '0' || *p > '9') { + return "is not a valid number (greater or equal than zero)"; + } + } + * (int *) ((char *) *config + offset) = atoi(val); + return NULL; +} +/* }}} */ + +#if 0 /* not used for now */ +static char *fpm_conf_set_long(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + char *p; + + for (p = val; *p; p++) { + if ( p == val && *p == '-' ) continue; + if (*p < '0' || *p > '9') { + return "is not a valid number (greater or equal than zero)"; + } + } + * (long int *) ((char *) *config + offset) = atol(val); + return NULL; +} +/* }}} */ +#endif + +static char *fpm_conf_set_time(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + int len = strlen(val); + char suffix; + int seconds; + if (!len) { + return "invalid time value"; + } + + suffix = val[len-1]; + switch (suffix) { + case 'm' : + val[len-1] = '\0'; + seconds = 60 * atoi(val); + break; + case 'h' : + val[len-1] = '\0'; + seconds = 60 * 60 * atoi(val); + break; + case 'd' : + val[len-1] = '\0'; + seconds = 24 * 60 * 60 * atoi(val); + break; + case 's' : /* s is the default suffix */ + val[len-1] = '\0'; + suffix = '0'; + default : + if (suffix < '0' || suffix > '9') { + return "unknown suffix used in time value"; + } + seconds = atoi(val); + break; + } + + * (int *) ((char *) *config + offset) = seconds; + return NULL; +} +/* }}} */ + +static char *fpm_conf_set_log_level(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + int log_level; + + if (!strcasecmp(val, "debug")) { + log_level = ZLOG_DEBUG; + } else if (!strcasecmp(val, "notice")) { + log_level = ZLOG_NOTICE; + } else if (!strcasecmp(val, "warning") || !strcasecmp(val, "warn")) { + log_level = ZLOG_WARNING; + } else if (!strcasecmp(val, "error")) { + log_level = ZLOG_ERROR; + } else if (!strcasecmp(val, "alert")) { + log_level = ZLOG_ALERT; + } else { + return "invalid value for 'log_level'"; + } + + * (int *) ((char *) *config + offset) = log_level; + return NULL; +} +/* }}} */ + +#ifdef HAVE_SYSLOG_H +static char *fpm_conf_set_syslog_facility(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + int *conf = (int *) ((char *) *config + offset); + +#ifdef LOG_AUTH + if (!strcasecmp(val, "AUTH")) { + *conf = LOG_AUTH; + return NULL; + } +#endif + +#ifdef LOG_AUTHPRIV + if (!strcasecmp(val, "AUTHPRIV")) { + *conf = LOG_AUTHPRIV; + return NULL; + } +#endif + +#ifdef LOG_CRON + if (!strcasecmp(val, "CRON")) { + *conf = LOG_CRON; + return NULL; + } +#endif + +#ifdef LOG_DAEMON + if (!strcasecmp(val, "DAEMON")) { + *conf = LOG_DAEMON; + return NULL; + } +#endif + +#ifdef LOG_FTP + if (!strcasecmp(val, "FTP")) { + *conf = LOG_FTP; + return NULL; + } +#endif + +#ifdef LOG_KERN + if (!strcasecmp(val, "KERN")) { + *conf = LOG_KERN; + return NULL; + } +#endif + +#ifdef LOG_LPR + if (!strcasecmp(val, "LPR")) { + *conf = LOG_LPR; + return NULL; + } +#endif + +#ifdef LOG_MAIL + if (!strcasecmp(val, "MAIL")) { + *conf = LOG_MAIL; + return NULL; + } +#endif + +#ifdef LOG_NEWS + if (!strcasecmp(val, "NEWS")) { + *conf = LOG_NEWS; + return NULL; + } +#endif + +#ifdef LOG_SYSLOG + if (!strcasecmp(val, "SYSLOG")) { + *conf = LOG_SYSLOG; + return NULL; + } +#endif + +#ifdef LOG_USER + if (!strcasecmp(val, "USER")) { + *conf = LOG_USER; + return NULL; + } +#endif + +#ifdef LOG_UUCP + if (!strcasecmp(val, "UUCP")) { + *conf = LOG_UUCP; + return NULL; + } +#endif + +#ifdef LOG_LOCAL0 + if (!strcasecmp(val, "LOCAL0")) { + *conf = LOG_LOCAL0; + return NULL; + } +#endif + +#ifdef LOG_LOCAL1 + if (!strcasecmp(val, "LOCAL1")) { + *conf = LOG_LOCAL1; + return NULL; + } +#endif + +#ifdef LOG_LOCAL2 + if (!strcasecmp(val, "LOCAL2")) { + *conf = LOG_LOCAL2; + return NULL; + } +#endif + +#ifdef LOG_LOCAL3 + if (!strcasecmp(val, "LOCAL3")) { + *conf = LOG_LOCAL3; + return NULL; + } +#endif + +#ifdef LOG_LOCAL4 + if (!strcasecmp(val, "LOCAL4")) { + *conf = LOG_LOCAL4; + return NULL; + } +#endif + +#ifdef LOG_LOCAL5 + if (!strcasecmp(val, "LOCAL5")) { + *conf = LOG_LOCAL5; + return NULL; + } +#endif + +#ifdef LOG_LOCAL6 + if (!strcasecmp(val, "LOCAL6")) { + *conf = LOG_LOCAL6; + return NULL; + } +#endif + +#ifdef LOG_LOCAL7 + if (!strcasecmp(val, "LOCAL7")) { + *conf = LOG_LOCAL7; + return NULL; + } +#endif + + return "invalid value"; +} +/* }}} */ +#endif + +static char *fpm_conf_set_rlimit_core(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + int *ptr = (int *) ((char *) *config + offset); + + if (!strcasecmp(val, "unlimited")) { + *ptr = -1; + } else { + int int_value; + void *subconf = &int_value; + char *error; + + error = fpm_conf_set_integer(value, &subconf, 0); + + if (error) { + return error; + } + + if (int_value < 0) { + return "must be greater than zero or 'unlimited'"; + } + + *ptr = int_value; + } + + return NULL; +} +/* }}} */ + +static char *fpm_conf_set_pm(zval *value, void **config, intptr_t offset) /* {{{ */ +{ + char *val = Z_STRVAL_P(value); + struct fpm_worker_pool_config_s *c = *config; + if (!strcasecmp(val, "static")) { + c->pm = PM_STYLE_STATIC; + } else if (!strcasecmp(val, "dynamic")) { + c->pm = PM_STYLE_DYNAMIC; + } else if (!strcasecmp(val, "ondemand")) { + c->pm = PM_STYLE_ONDEMAND; + } else { + return "invalid process manager (static, dynamic or ondemand)"; + } + return NULL; +} +/* }}} */ + +static char *fpm_conf_set_array(zval *key, zval *value, void **config, int convert_to_bool) /* {{{ */ +{ + struct key_value_s *kv; + struct key_value_s ***parent = (struct key_value_s ***) config; + int b; + void *subconf = &b; + + kv = malloc(sizeof(*kv)); + + if (!kv) { + return "malloc() failed"; + } + + memset(kv, 0, sizeof(*kv)); + kv->key = strdup(Z_STRVAL_P(key)); + + if (!kv->key) { + return "fpm_conf_set_array: strdup(key) failed"; + } + + if (convert_to_bool) { + char *err = fpm_conf_set_boolean(value, &subconf, 0); + if (err) return err; + kv->value = strdup(b ? "1" : "0"); + } else { + kv->value = strdup(Z_STRVAL_P(value)); + if (fpm_conf_expand_pool_name(&kv->value) == -1) { + return "Can't use '$pool' when the pool is not defined"; + } + } + + if (!kv->value) { + free(kv->key); + return "fpm_conf_set_array: strdup(value) failed"; + } + + kv->next = **parent; + **parent = kv; + return NULL; +} +/* }}} */ + +static void *fpm_worker_pool_config_alloc() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + wp = fpm_worker_pool_alloc(); + + if (!wp) { + return 0; + } + + wp->config = malloc(sizeof(struct fpm_worker_pool_config_s)); + + if (!wp->config) { + return 0; + } + + memset(wp->config, 0, sizeof(struct fpm_worker_pool_config_s)); + wp->config->listen_backlog = FPM_BACKLOG_DEFAULT; + wp->config->pm_process_idle_timeout = 10; /* 10s by default */ + wp->config->process_priority = 64; /* 64 means unset */ + + if (!fpm_worker_all_pools) { + fpm_worker_all_pools = wp; + } else { + struct fpm_worker_pool_s *tmp = fpm_worker_all_pools; + while (tmp) { + if (!tmp->next) { + tmp->next = wp; + break; + } + tmp = tmp->next; + } + } + + current_wp = wp; + return wp->config; +} +/* }}} */ + +int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc) /* {{{ */ +{ + struct key_value_s *kv, *kv_next; + + free(wpc->name); + free(wpc->prefix); + free(wpc->user); + free(wpc->group); + free(wpc->listen_address); + free(wpc->listen_owner); + free(wpc->listen_group); + free(wpc->listen_mode); + free(wpc->listen_allowed_clients); + free(wpc->pm_status_path); + free(wpc->ping_path); + free(wpc->ping_response); + free(wpc->access_log); + free(wpc->access_format); + free(wpc->slowlog); + free(wpc->chroot); + free(wpc->chdir); + free(wpc->security_limit_extensions); + + for (kv = wpc->php_values; kv; kv = kv_next) { + kv_next = kv->next; + free(kv->key); + free(kv->value); + free(kv); + } + for (kv = wpc->php_admin_values; kv; kv = kv_next) { + kv_next = kv->next; + free(kv->key); + free(kv->value); + free(kv); + } + for (kv = wpc->env; kv; kv = kv_next) { + kv_next = kv->next; + free(kv->key); + free(kv->value); + free(kv); + } + + return 0; +} +/* }}} */ + +static int fpm_evaluate_full_path(char **path, struct fpm_worker_pool_s *wp, char *default_prefix, int expand) /* {{{ */ +{ + char *prefix = NULL; + char *full_path; + + if (!path || !*path || **path == '/') { + return 0; + } + + if (wp && wp->config) { + prefix = wp->config->prefix; + } + + /* if the wp prefix is not set */ + if (prefix == NULL) { + prefix = fpm_globals.prefix; + } + + /* if the global prefix is not set */ + if (prefix == NULL) { + prefix = default_prefix ? default_prefix : PHP_PREFIX; + } + + if (expand) { + char *tmp; + tmp = strstr(*path, "$prefix"); + if (tmp != NULL) { + + if (tmp != *path) { + zlog(ZLOG_ERROR, "'$prefix' must be use at the begining of the value"); + return -1; + } + + if (strlen(*path) > strlen("$prefix")) { + free(*path); + tmp = strdup((*path) + strlen("$prefix")); + *path = tmp; + } else { + free(*path); + *path = NULL; + } + } + } + + if (*path) { + spprintf(&full_path, 0, "%s/%s", prefix, *path); + free(*path); + *path = strdup(full_path); + efree(full_path); + } else { + *path = strdup(prefix); + } + + if (**path != '/' && wp != NULL && wp->config) { + return fpm_evaluate_full_path(path, NULL, default_prefix, expand); + } + return 0; +} +/* }}} */ + +static int fpm_conf_process_all_pools() /* {{{ */ +{ + struct fpm_worker_pool_s *wp, *wp2; + + if (!fpm_worker_all_pools) { + zlog(ZLOG_ERROR, "No pool defined. at least one pool section must be specified in config file"); + return -1; + } + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + + /* prefix */ + if (wp->config->prefix && *wp->config->prefix) { + fpm_evaluate_full_path(&wp->config->prefix, NULL, NULL, 0); + + if (!fpm_conf_is_dir(wp->config->prefix)) { + zlog(ZLOG_ERROR, "[pool %s] the prefix '%s' does not exist or is not a directory", wp->config->name, wp->config->prefix); + return -1; + } + } + + /* alert if user is not set only if we are not root*/ + if (!wp->config->user && !geteuid()) { + zlog(ZLOG_ALERT, "[pool %s] user has not been defined", wp->config->name); + return -1; + } + + /* listen */ + if (wp->config->listen_address && *wp->config->listen_address) { + wp->listen_address_domain = fpm_sockets_domain_from_address(wp->config->listen_address); + + if (wp->listen_address_domain == FPM_AF_UNIX && *wp->config->listen_address != '/') { + fpm_evaluate_full_path(&wp->config->listen_address, wp, NULL, 0); + } + } else { + zlog(ZLOG_ALERT, "[pool %s] no listen address have been defined!", wp->config->name); + return -1; + } + + if (wp->config->process_priority != 64 && (wp->config->process_priority < -19 || wp->config->process_priority > 20)) { + zlog(ZLOG_ERROR, "[pool %s] process.priority must be included into [-19,20]", wp->config->name); + return -1; + } + + /* pm */ + if (wp->config->pm != PM_STYLE_STATIC && wp->config->pm != PM_STYLE_DYNAMIC && wp->config->pm != PM_STYLE_ONDEMAND) { + zlog(ZLOG_ALERT, "[pool %s] the process manager is missing (static, dynamic or ondemand)", wp->config->name); + return -1; + } + + /* pm.max_children */ + if (wp->config->pm_max_children < 1) { + zlog(ZLOG_ALERT, "[pool %s] pm.max_children must be a positive value", wp->config->name); + return -1; + } + + /* pm.start_servers, pm.min_spare_servers, pm.max_spare_servers */ + if (wp->config->pm == PM_STYLE_DYNAMIC) { + struct fpm_worker_pool_config_s *config = wp->config; + + if (config->pm_min_spare_servers <= 0) { + zlog(ZLOG_ALERT, "[pool %s] pm.min_spare_servers(%d) must be a positive value", wp->config->name, config->pm_min_spare_servers); + return -1; + } + + if (config->pm_max_spare_servers <= 0) { + zlog(ZLOG_ALERT, "[pool %s] pm.max_spare_servers(%d) must be a positive value", wp->config->name, config->pm_max_spare_servers); + return -1; + } + + if (config->pm_min_spare_servers > config->pm_max_children || + config->pm_max_spare_servers > config->pm_max_children) { + zlog(ZLOG_ALERT, "[pool %s] pm.min_spare_servers(%d) and pm.max_spare_servers(%d) cannot be greater than pm.max_children(%d)", wp->config->name, config->pm_min_spare_servers, config->pm_max_spare_servers, config->pm_max_children); + return -1; + } + + if (config->pm_max_spare_servers < config->pm_min_spare_servers) { + zlog(ZLOG_ALERT, "[pool %s] pm.max_spare_servers(%d) must not be less than pm.min_spare_servers(%d)", wp->config->name, config->pm_max_spare_servers, config->pm_min_spare_servers); + return -1; + } + + if (config->pm_start_servers <= 0) { + config->pm_start_servers = config->pm_min_spare_servers + ((config->pm_max_spare_servers - config->pm_min_spare_servers) / 2); + zlog(ZLOG_WARNING, "[pool %s] pm.start_servers is not set. It's been set to %d.", wp->config->name, config->pm_start_servers); + + } else if (config->pm_start_servers < config->pm_min_spare_servers || config->pm_start_servers > config->pm_max_spare_servers) { + zlog(ZLOG_ALERT, "[pool %s] pm.start_servers(%d) must not be less than pm.min_spare_servers(%d) and not greater than pm.max_spare_servers(%d)", wp->config->name, config->pm_start_servers, config->pm_min_spare_servers, config->pm_max_spare_servers); + return -1; + } + } else if (wp->config->pm == PM_STYLE_ONDEMAND) { + struct fpm_worker_pool_config_s *config = wp->config; + + if (!fpm_event_support_edge_trigger()) { + zlog(ZLOG_ALERT, "[pool %s] ondemand process manager can ONLY be used when events.mechanisme is either epoll (Linux) or kqueue (*BSD).", wp->config->name); + return -1; + } + + if (config->pm_process_idle_timeout < 1) { + zlog(ZLOG_ALERT, "[pool %s] pm.process_idle_timeout(%ds) must be greater than 0s", wp->config->name, config->pm_process_idle_timeout); + return -1; + } + + if (config->listen_backlog < FPM_BACKLOG_DEFAULT) { + zlog(ZLOG_WARNING, "[pool %s] listen.backlog(%d) was too low for the ondemand process manager. I updated it for you to %d.", wp->config->name, config->listen_backlog, FPM_BACKLOG_DEFAULT); + config->listen_backlog = FPM_BACKLOG_DEFAULT; + } + + /* certainely useless but proper */ + config->pm_start_servers = 0; + config->pm_min_spare_servers = 0; + config->pm_max_spare_servers = 0; + } + + /* status */ + if (wp->config->pm_status_path && *wp->config->pm_status_path) { + int i; + char *status = wp->config->pm_status_path; + + if (*status != '/') { + zlog(ZLOG_ERROR, "[pool %s] the status path '%s' must start with a '/'", wp->config->name, status); + return -1; + } + + if (strlen(status) < 2) { + zlog(ZLOG_ERROR, "[pool %s] the status path '%s' is not long enough", wp->config->name, status); + return -1; + } + + for (i = 0; i < strlen(status); i++) { + if (!isalnum(status[i]) && status[i] != '/' && status[i] != '-' && status[i] != '_' && status[i] != '.') { + zlog(ZLOG_ERROR, "[pool %s] the status path '%s' must contain only the following characters '[alphanum]/_-.'", wp->config->name, status); + return -1; + } + } + } + + /* ping */ + if (wp->config->ping_path && *wp->config->ping_path) { + char *ping = wp->config->ping_path; + int i; + + if (*ping != '/') { + zlog(ZLOG_ERROR, "[pool %s] the ping path '%s' must start with a '/'", wp->config->name, ping); + return -1; + } + + if (strlen(ping) < 2) { + zlog(ZLOG_ERROR, "[pool %s] the ping path '%s' is not long enough", wp->config->name, ping); + return -1; + } + + for (i = 0; i < strlen(ping); i++) { + if (!isalnum(ping[i]) && ping[i] != '/' && ping[i] != '-' && ping[i] != '_' && ping[i] != '.') { + zlog(ZLOG_ERROR, "[pool %s] the ping path '%s' must containt only the following characters '[alphanum]/_-.'", wp->config->name, ping); + return -1; + } + } + + if (!wp->config->ping_response) { + wp->config->ping_response = strdup("pong"); + } else { + if (strlen(wp->config->ping_response) < 1) { + zlog(ZLOG_ERROR, "[pool %s] the ping response page '%s' is not long enough", wp->config->name, wp->config->ping_response); + return -1; + } + } + } else { + if (wp->config->ping_response) { + free(wp->config->ping_response); + wp->config->ping_response = NULL; + } + } + + /* access.log, access.format */ + if (wp->config->access_log && *wp->config->access_log) { + fpm_evaluate_full_path(&wp->config->access_log, wp, NULL, 0); + if (!wp->config->access_format) { + wp->config->access_format = strdup("%R - %u %t \"%m %r\" %s"); + } + } + + if (wp->config->request_terminate_timeout) { + fpm_globals.heartbeat = fpm_globals.heartbeat ? MIN(fpm_globals.heartbeat, (wp->config->request_terminate_timeout * 1000) / 3) : (wp->config->request_terminate_timeout * 1000) / 3; + } + + /* slowlog */ + if (wp->config->slowlog && *wp->config->slowlog) { + fpm_evaluate_full_path(&wp->config->slowlog, wp, NULL, 0); + } + + /* request_slowlog_timeout */ + if (wp->config->request_slowlog_timeout) { +#if HAVE_FPM_TRACE + if (! (wp->config->slowlog && *wp->config->slowlog)) { + zlog(ZLOG_ERROR, "[pool %s] 'slowlog' must be specified for use with 'request_slowlog_timeout'", wp->config->name); + return -1; + } +#else + static int warned = 0; + + if (!warned) { + zlog(ZLOG_WARNING, "[pool %s] 'request_slowlog_timeout' is not supported on your system", wp->config->name); + warned = 1; + } + + wp->config->request_slowlog_timeout = 0; +#endif + + if (wp->config->slowlog && *wp->config->slowlog) { + int fd; + + fd = open(wp->config->slowlog, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); + + if (0 > fd) { + zlog(ZLOG_SYSERROR, "Unable to create or open slowlog(%s)", wp->config->slowlog); + return -1; + } + close(fd); + } + + fpm_globals.heartbeat = fpm_globals.heartbeat ? MIN(fpm_globals.heartbeat, (wp->config->request_slowlog_timeout * 1000) / 3) : (wp->config->request_slowlog_timeout * 1000) / 3; + + if (wp->config->request_terminate_timeout && wp->config->request_slowlog_timeout > wp->config->request_terminate_timeout) { + zlog(ZLOG_ERROR, "[pool %s] 'request_slowlog_timeout' (%d) can't be greater than 'request_terminate_timeout' (%d)", wp->config->name, wp->config->request_slowlog_timeout, wp->config->request_terminate_timeout); + return -1; + } + } + + /* chroot */ + if (wp->config->chroot && *wp->config->chroot) { + + fpm_evaluate_full_path(&wp->config->chroot, wp, NULL, 1); + + if (*wp->config->chroot != '/') { + zlog(ZLOG_ERROR, "[pool %s] the chroot path '%s' must start with a '/'", wp->config->name, wp->config->chroot); + return -1; + } + + if (!fpm_conf_is_dir(wp->config->chroot)) { + zlog(ZLOG_ERROR, "[pool %s] the chroot path '%s' does not exist or is not a directory", wp->config->name, wp->config->chroot); + return -1; + } + } + + /* chdir */ + if (wp->config->chdir && *wp->config->chdir) { + + fpm_evaluate_full_path(&wp->config->chdir, wp, NULL, 0); + + if (*wp->config->chdir != '/') { + zlog(ZLOG_ERROR, "[pool %s] the chdir path '%s' must start with a '/'", wp->config->name, wp->config->chdir); + return -1; + } + + if (wp->config->chroot) { + char *buf; + + spprintf(&buf, 0, "%s/%s", wp->config->chroot, wp->config->chdir); + + if (!fpm_conf_is_dir(buf)) { + zlog(ZLOG_ERROR, "[pool %s] the chdir path '%s' within the chroot path '%s' ('%s') does not exist or is not a directory", wp->config->name, wp->config->chdir, wp->config->chroot, buf); + efree(buf); + return -1; + } + + efree(buf); + } else { + if (!fpm_conf_is_dir(wp->config->chdir)) { + zlog(ZLOG_ERROR, "[pool %s] the chdir path '%s' does not exist or is not a directory", wp->config->name, wp->config->chdir); + return -1; + } + } + } + + /* security.limit_extensions */ + if (!wp->config->security_limit_extensions) { + wp->config->security_limit_extensions = strdup(".php .phar"); + } + + if (*wp->config->security_limit_extensions) { + int nb_ext; + char *ext; + char *security_limit_extensions; + char *limit_extensions; + + + /* strdup because strtok(3) alters the string it parses */ + security_limit_extensions = strdup(wp->config->security_limit_extensions); + limit_extensions = security_limit_extensions; + nb_ext = 0; + + /* find the number of extensions */ + while ((ext = strtok(limit_extensions, " \t"))) { + limit_extensions = NULL; + nb_ext++; + } + free(security_limit_extensions); + + /* if something found */ + if (nb_ext > 0) { + + /* malloc the extension array */ + wp->limit_extensions = malloc(sizeof(char *) * (nb_ext + 1)); + if (!wp->limit_extensions) { + zlog(ZLOG_ERROR, "[pool %s] unable to malloc extensions array", wp->config->name); + return -1; + } + + /* strdup because strtok(3) alters the string it parses */ + security_limit_extensions = strdup(wp->config->security_limit_extensions); + limit_extensions = security_limit_extensions; + nb_ext = 0; + + /* parse the string and save the extension in the array */ + while ((ext = strtok(security_limit_extensions, " \t"))) { + security_limit_extensions = NULL; + wp->limit_extensions[nb_ext++] = strdup(ext); + } + + /* end the array with NULL in order to parse it */ + wp->limit_extensions[nb_ext] = NULL; + free(security_limit_extensions); + } + } + + /* env[], php_value[], php_admin_values[] */ + if (!wp->config->chroot) { + struct key_value_s *kv; + char *options[] = FPM_PHP_INI_TO_EXPAND; + char **p; + + for (kv = wp->config->php_values; kv; kv = kv->next) { + for (p = options; *p; p++) { + if (!strcasecmp(kv->key, *p)) { + fpm_evaluate_full_path(&kv->value, wp, NULL, 0); + } + } + } + for (kv = wp->config->php_admin_values; kv; kv = kv->next) { + for (p = options; *p; p++) { + if (!strcasecmp(kv->key, *p)) { + fpm_evaluate_full_path(&kv->value, wp, NULL, 0); + } + } + } + } + } + + /* ensure 2 pools do not use the same listening address */ + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + for (wp2 = fpm_worker_all_pools; wp2; wp2 = wp2->next) { + if (wp == wp2) { + continue; + } + + if (wp->config->listen_address && *wp->config->listen_address && wp2->config->listen_address && *wp2->config->listen_address && !strcmp(wp->config->listen_address, wp2->config->listen_address)) { + zlog(ZLOG_ERROR, "[pool %s] unable to set listen address as it's already used in another pool '%s'", wp2->config->name, wp->config->name); + return -1; + } + } + } + return 0; +} +/* }}} */ + +int fpm_conf_unlink_pid() /* {{{ */ +{ + if (fpm_global_config.pid_file) { + if (0 > unlink(fpm_global_config.pid_file)) { + zlog(ZLOG_SYSERROR, "Unable to remove the PID file (%s).", fpm_global_config.pid_file); + return -1; + } + } + return 0; +} +/* }}} */ + +int fpm_conf_write_pid() /* {{{ */ +{ + int fd; + + if (fpm_global_config.pid_file) { + char buf[64]; + int len; + + unlink(fpm_global_config.pid_file); + fd = creat(fpm_global_config.pid_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (fd < 0) { + zlog(ZLOG_SYSERROR, "Unable to create the PID file (%s).", fpm_global_config.pid_file); + return -1; + } + + len = sprintf(buf, "%d", (int) fpm_globals.parent_pid); + + if (len != write(fd, buf, len)) { + zlog(ZLOG_SYSERROR, "Unable to write to the PID file."); + return -1; + } + close(fd); + } + return 0; +} +/* }}} */ + +static int fpm_conf_post_process(int force_daemon TSRMLS_DC) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + if (fpm_global_config.pid_file) { + fpm_evaluate_full_path(&fpm_global_config.pid_file, NULL, PHP_LOCALSTATEDIR, 0); + } + + if (force_daemon >= 0) { + /* forced from command line options */ + fpm_global_config.daemonize = force_daemon; + } + + fpm_globals.log_level = fpm_global_config.log_level; + + if (fpm_global_config.process_max < 0) { + zlog(ZLOG_ERROR, "process_max can't be negative"); + return -1; + } + + if (fpm_global_config.process_priority != 64 && (fpm_global_config.process_priority < -19 || fpm_global_config.process_priority > 20)) { + zlog(ZLOG_ERROR, "process.priority must be included into [-19,20]"); + return -1; + } + + if (!fpm_global_config.error_log) { + fpm_global_config.error_log = strdup("log/php-fpm.log"); + } + +#ifdef HAVE_SYSLOG_H + if (!fpm_global_config.syslog_ident) { + fpm_global_config.syslog_ident = strdup("php-fpm"); + } + + if (fpm_global_config.syslog_facility < 0) { + fpm_global_config.syslog_facility = LOG_DAEMON; + } + + if (strcasecmp(fpm_global_config.error_log, "syslog") != 0) +#endif + { + fpm_evaluate_full_path(&fpm_global_config.error_log, NULL, PHP_LOCALSTATEDIR, 0); + } + + if (0 > fpm_stdio_open_error_log(0)) { + return -1; + } + + if (0 > fpm_log_open(0)) { + return -1; + } + + if (0 > fpm_event_pre_init(fpm_global_config.events_mechanism)) { + return -1; + } + + if (0 > fpm_conf_process_all_pools()) { + return -1; + } + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (!wp->config->access_log || !*wp->config->access_log) { + continue; + } + if (0 > fpm_log_write(wp->config->access_format TSRMLS_CC)) { + zlog(ZLOG_ERROR, "[pool %s] wrong format for access.format '%s'", wp->config->name, wp->config->access_format); + return -1; + } + } + + return 0; +} +/* }}} */ + +static void fpm_conf_cleanup(int which, void *arg) /* {{{ */ +{ + free(fpm_global_config.pid_file); + free(fpm_global_config.error_log); + free(fpm_global_config.events_mechanism); + fpm_global_config.pid_file = 0; + fpm_global_config.error_log = 0; +#ifdef HAVE_SYSLOG_H + free(fpm_global_config.syslog_ident); + fpm_global_config.syslog_ident = 0; +#endif + free(fpm_globals.config); +} +/* }}} */ + +static void fpm_conf_ini_parser_include(char *inc, void *arg TSRMLS_DC) /* {{{ */ +{ + char *filename; + int *error = (int *)arg;; +#ifdef HAVE_GLOB + glob_t g; +#endif + int i; + + if (!inc || !arg) return; + if (*error) return; /* We got already an error. Switch to the end. */ + spprintf(&filename, 0, "%s", ini_filename); + +#ifdef HAVE_GLOB + { + g.gl_offs = 0; + if ((i = glob(inc, GLOB_ERR | GLOB_MARK | GLOB_NOSORT, NULL, &g)) != 0) { +#ifdef GLOB_NOMATCH + if (i == GLOB_NOMATCH) { + zlog(ZLOG_WARNING, "Nothing matches the include pattern '%s' from %s at line %d.", inc, filename, ini_lineno); + efree(filename); + return; + } +#endif /* GLOB_NOMATCH */ + zlog(ZLOG_ERROR, "Unable to globalize '%s' (ret=%d) from %s at line %d.", inc, i, filename, ini_lineno); + *error = 1; + efree(filename); + return; + } + + for (i = 0; i < g.gl_pathc; i++) { + int len = strlen(g.gl_pathv[i]); + if (len < 1) continue; + if (g.gl_pathv[i][len - 1] == '/') continue; /* don't parse directories */ + if (0 > fpm_conf_load_ini_file(g.gl_pathv[i] TSRMLS_CC)) { + zlog(ZLOG_ERROR, "Unable to include %s from %s at line %d", g.gl_pathv[i], filename, ini_lineno); + *error = 1; + efree(filename); + return; + } + } + globfree(&g); + } +#else /* HAVE_GLOB */ + if (0 > fpm_conf_load_ini_file(inc TSRMLS_CC)) { + zlog(ZLOG_ERROR, "Unable to include %s from %s at line %d", inc, filename, ini_lineno); + *error = 1; + efree(filename); + return; + } +#endif /* HAVE_GLOB */ + + efree(filename); +} +/* }}} */ + +static void fpm_conf_ini_parser_section(zval *section, void *arg TSRMLS_DC) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + struct fpm_worker_pool_config_s *config; + int *error = (int *)arg; + + /* switch to global conf */ + if (!strcasecmp(Z_STRVAL_P(section), "global")) { + current_wp = NULL; + return; + } + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (!wp->config) continue; + if (!wp->config->name) continue; + if (!strcasecmp(wp->config->name, Z_STRVAL_P(section))) { + /* Found a wp with the same name. Bring it back */ + current_wp = wp; + return; + } + } + + /* it's a new pool */ + config = (struct fpm_worker_pool_config_s *)fpm_worker_pool_config_alloc(); + if (!current_wp || !config) { + zlog(ZLOG_ERROR, "[%s:%d] Unable to alloc a new WorkerPool for worker '%s'", ini_filename, ini_lineno, Z_STRVAL_P(section)); + *error = 1; + return; + } + config->name = strdup(Z_STRVAL_P(section)); + if (!config->name) { + zlog(ZLOG_ERROR, "[%s:%d] Unable to alloc memory for configuration name for worker '%s'", ini_filename, ini_lineno, Z_STRVAL_P(section)); + *error = 1; + return; + } +} +/* }}} */ + +static void fpm_conf_ini_parser_entry(zval *name, zval *value, void *arg TSRMLS_DC) /* {{{ */ +{ + struct ini_value_parser_s *parser; + void *config = NULL; + + int *error = (int *)arg; + if (!value) { + zlog(ZLOG_ERROR, "[%s:%d] value is NULL for a ZEND_INI_PARSER_ENTRY", ini_filename, ini_lineno); + *error = 1; + return; + } + + if (!strcmp(Z_STRVAL_P(name), "include")) { + if (ini_include) { + zlog(ZLOG_ERROR, "[%s:%d] two includes at the same time !", ini_filename, ini_lineno); + *error = 1; + return; + } + ini_include = strdup(Z_STRVAL_P(value)); + return; + } + + if (!current_wp) { /* we are in the global section */ + parser = ini_fpm_global_options; + config = &fpm_global_config; + } else { + parser = ini_fpm_pool_options; + config = current_wp->config; + } + + for (; parser->name; parser++) { + if (!strcasecmp(parser->name, Z_STRVAL_P(name))) { + char *ret; + if (!parser->parser) { + zlog(ZLOG_ERROR, "[%s:%d] the parser for entry '%s' is not defined", ini_filename, ini_lineno, parser->name); + *error = 1; + return; + } + + ret = parser->parser(value, &config, parser->offset); + if (ret) { + zlog(ZLOG_ERROR, "[%s:%d] unable to parse value for entry '%s': %s", ini_filename, ini_lineno, parser->name, ret); + *error = 1; + return; + } + + /* all is good ! */ + return; + } + } + + /* nothing has been found if we got here */ + zlog(ZLOG_ERROR, "[%s:%d] unknown entry '%s'", ini_filename, ini_lineno, Z_STRVAL_P(name)); + *error = 1; +} +/* }}} */ + +static void fpm_conf_ini_parser_array(zval *name, zval *key, zval *value, void *arg TSRMLS_DC) /* {{{ */ +{ + int *error = (int *)arg; + char *err = NULL; + void *config; + + if (!Z_STRVAL_P(key) || !Z_STRVAL_P(value) || !*Z_STRVAL_P(key)) { + zlog(ZLOG_ERROR, "[%s:%d] Misspelled array ?", ini_filename, ini_lineno); + *error = 1; + return; + } + if (!current_wp || !current_wp->config) { + zlog(ZLOG_ERROR, "[%s:%d] Array are not allowed in the global section", ini_filename, ini_lineno); + *error = 1; + return; + } + + if (!strcmp("env", Z_STRVAL_P(name))) { + if (!*Z_STRVAL_P(value)) { + zlog(ZLOG_ERROR, "[%s:%d] empty value", ini_filename, ini_lineno); + *error = 1; + return; + } + config = (char *)current_wp->config + WPO(env); + err = fpm_conf_set_array(key, value, &config, 0); + + } else if (!strcmp("php_value", Z_STRVAL_P(name))) { + config = (char *)current_wp->config + WPO(php_values); + err = fpm_conf_set_array(key, value, &config, 0); + + } else if (!strcmp("php_admin_value", Z_STRVAL_P(name))) { + config = (char *)current_wp->config + WPO(php_admin_values); + err = fpm_conf_set_array(key, value, &config, 0); + + } else if (!strcmp("php_flag", Z_STRVAL_P(name))) { + config = (char *)current_wp->config + WPO(php_values); + err = fpm_conf_set_array(key, value, &config, 1); + + } else if (!strcmp("php_admin_flag", Z_STRVAL_P(name))) { + config = (char *)current_wp->config + WPO(php_admin_values); + err = fpm_conf_set_array(key, value, &config, 1); + + } else { + zlog(ZLOG_ERROR, "[%s:%d] unknown directive '%s'", ini_filename, ini_lineno, Z_STRVAL_P(name)); + *error = 1; + return; + } + + if (err) { + zlog(ZLOG_ERROR, "[%s:%d] error while parsing '%s[%s]' : %s", ini_filename, ini_lineno, Z_STRVAL_P(name), Z_STRVAL_P(key), err); + *error = 1; + return; + } +} +/* }}} */ + +static void fpm_conf_ini_parser(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg TSRMLS_DC) /* {{{ */ +{ + int *error; + + if (!arg1 || !arg) return; + error = (int *)arg; + if (*error) return; /* We got already an error. Switch to the end. */ + + switch(callback_type) { + case ZEND_INI_PARSER_ENTRY: + fpm_conf_ini_parser_entry(arg1, arg2, error TSRMLS_CC); + break;; + case ZEND_INI_PARSER_SECTION: + fpm_conf_ini_parser_section(arg1, error TSRMLS_CC); + break;; + case ZEND_INI_PARSER_POP_ENTRY: + fpm_conf_ini_parser_array(arg1, arg3, arg2, error TSRMLS_CC); + break;; + default: + zlog(ZLOG_ERROR, "[%s:%d] Unknown INI syntax", ini_filename, ini_lineno); + *error = 1; + break;; + } +} +/* }}} */ + +int fpm_conf_load_ini_file(char *filename TSRMLS_DC) /* {{{ */ +{ + int error = 0; + char buf[1024+1]; + int fd, n; + int nb_read = 1; + char c = '*'; + + int ret = 1; + + if (!filename || !filename[0]) { + zlog(ZLOG_ERROR, "configuration filename is empty"); + return -1; + } + + fd = open(filename, O_RDONLY, 0); + if (fd < 0) { + zlog(ZLOG_SYSERROR, "failed to open configuration file '%s'", filename); + return -1; + } + + if (ini_recursion++ > 4) { + zlog(ZLOG_ERROR, "failed to include more than 5 files recusively"); + return -1; + } + + ini_lineno = 0; + while (nb_read > 0) { + int tmp; + memset(buf, 0, sizeof(char) * (1024 + 1)); + for (n = 0; n < 1024 && (nb_read = read(fd, &c, sizeof(char))) == sizeof(char) && c != '\n'; n++) { + buf[n] = c; + } + buf[n++] = '\n'; + ini_lineno++; + ini_filename = filename; + tmp = zend_parse_ini_string(buf, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fpm_conf_ini_parser, &error TSRMLS_CC); + ini_filename = filename; + if (error || tmp == FAILURE) { + if (ini_include) free(ini_include); + ini_recursion--; + close(fd); + return -1; + } + if (ini_include) { + char *tmp = ini_include; + ini_include = NULL; + fpm_evaluate_full_path(&tmp, NULL, NULL, 0); + fpm_conf_ini_parser_include(tmp, &error TSRMLS_CC); + if (error) { + free(tmp); + ini_recursion--; + close(fd); + return -1; + } + free(tmp); + } + } + + ini_recursion--; + close(fd); + return ret; + +} +/* }}} */ + +static void fpm_conf_dump() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + /* + * Please keep the same order as in fpm_conf.h and in php-fpm.conf.in + */ + zlog(ZLOG_NOTICE, "[General]"); + zlog(ZLOG_NOTICE, "\tpid = %s", STR2STR(fpm_global_config.pid_file)); + zlog(ZLOG_NOTICE, "\terror_log = %s", STR2STR(fpm_global_config.error_log)); +#ifdef HAVE_SYSLOG_H + zlog(ZLOG_NOTICE, "\tsyslog.ident = %s", STR2STR(fpm_global_config.syslog_ident)); + zlog(ZLOG_NOTICE, "\tsyslog.facility = %d", fpm_global_config.syslog_facility); /* FIXME: convert to string */ +#endif + zlog(ZLOG_NOTICE, "\tlog_level = %s", zlog_get_level_name(fpm_globals.log_level)); + zlog(ZLOG_NOTICE, "\temergency_restart_interval = %ds", fpm_global_config.emergency_restart_interval); + zlog(ZLOG_NOTICE, "\temergency_restart_threshold = %d", fpm_global_config.emergency_restart_threshold); + zlog(ZLOG_NOTICE, "\tprocess_control_timeout = %ds", fpm_global_config.process_control_timeout); + zlog(ZLOG_NOTICE, "\tprocess.max = %d", fpm_global_config.process_max); + if (fpm_global_config.process_priority == 64) { + zlog(ZLOG_NOTICE, "\tprocess.priority = undefined"); + } else { + zlog(ZLOG_NOTICE, "\tprocess.priority = %d", fpm_global_config.process_priority); + } + zlog(ZLOG_NOTICE, "\tdaemonize = %s", BOOL2STR(fpm_global_config.daemonize)); + zlog(ZLOG_NOTICE, "\trlimit_files = %d", fpm_global_config.rlimit_files); + zlog(ZLOG_NOTICE, "\trlimit_core = %d", fpm_global_config.rlimit_core); + zlog(ZLOG_NOTICE, "\tevents.mechanism = %s", fpm_event_machanism_name()); + zlog(ZLOG_NOTICE, " "); + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + struct key_value_s *kv; + if (!wp->config) continue; + zlog(ZLOG_NOTICE, "[%s]", STR2STR(wp->config->name)); + zlog(ZLOG_NOTICE, "\tprefix = %s", STR2STR(wp->config->prefix)); + zlog(ZLOG_NOTICE, "\tuser = %s", STR2STR(wp->config->user)); + zlog(ZLOG_NOTICE, "\tgroup = %s", STR2STR(wp->config->group)); + zlog(ZLOG_NOTICE, "\tlisten = %s", STR2STR(wp->config->listen_address)); + zlog(ZLOG_NOTICE, "\tlisten.backlog = %d", wp->config->listen_backlog); + zlog(ZLOG_NOTICE, "\tlisten.owner = %s", STR2STR(wp->config->listen_owner)); + zlog(ZLOG_NOTICE, "\tlisten.group = %s", STR2STR(wp->config->listen_group)); + zlog(ZLOG_NOTICE, "\tlisten.mode = %s", STR2STR(wp->config->listen_mode)); + zlog(ZLOG_NOTICE, "\tlisten.allowed_clients = %s", STR2STR(wp->config->listen_allowed_clients)); + if (wp->config->process_priority == 64) { + zlog(ZLOG_NOTICE, "\tprocess.priority = undefined"); + } else { + zlog(ZLOG_NOTICE, "\tprocess.priority = %d", wp->config->process_priority); + } + zlog(ZLOG_NOTICE, "\tpm = %s", PM2STR(wp->config->pm)); + zlog(ZLOG_NOTICE, "\tpm.max_children = %d", wp->config->pm_max_children); + zlog(ZLOG_NOTICE, "\tpm.start_servers = %d", wp->config->pm_start_servers); + zlog(ZLOG_NOTICE, "\tpm.min_spare_servers = %d", wp->config->pm_min_spare_servers); + zlog(ZLOG_NOTICE, "\tpm.max_spare_servers = %d", wp->config->pm_max_spare_servers); + zlog(ZLOG_NOTICE, "\tpm.process_idle_timeout = %d", wp->config->pm_process_idle_timeout); + zlog(ZLOG_NOTICE, "\tpm.max_requests = %d", wp->config->pm_max_requests); + zlog(ZLOG_NOTICE, "\tpm.status_path = %s", STR2STR(wp->config->pm_status_path)); + zlog(ZLOG_NOTICE, "\tping.path = %s", STR2STR(wp->config->ping_path)); + zlog(ZLOG_NOTICE, "\tping.response = %s", STR2STR(wp->config->ping_response)); + zlog(ZLOG_NOTICE, "\taccess.log = %s", STR2STR(wp->config->access_log)); + zlog(ZLOG_NOTICE, "\taccess.format = %s", STR2STR(wp->config->access_format)); + zlog(ZLOG_NOTICE, "\tslowlog = %s", STR2STR(wp->config->slowlog)); + zlog(ZLOG_NOTICE, "\trequest_slowlog_timeout = %ds", wp->config->request_slowlog_timeout); + zlog(ZLOG_NOTICE, "\trequest_terminate_timeout = %ds", wp->config->request_terminate_timeout); + zlog(ZLOG_NOTICE, "\trlimit_files = %d", wp->config->rlimit_files); + zlog(ZLOG_NOTICE, "\trlimit_core = %d", wp->config->rlimit_core); + zlog(ZLOG_NOTICE, "\tchroot = %s", STR2STR(wp->config->chroot)); + zlog(ZLOG_NOTICE, "\tchdir = %s", STR2STR(wp->config->chdir)); + zlog(ZLOG_NOTICE, "\tcatch_workers_output = %s", BOOL2STR(wp->config->catch_workers_output)); + zlog(ZLOG_NOTICE, "\tsecurity.limit_extensions = %s", wp->config->security_limit_extensions); + + for (kv = wp->config->env; kv; kv = kv->next) { + zlog(ZLOG_NOTICE, "\tenv[%s] = %s", kv->key, kv->value); + } + + for (kv = wp->config->php_values; kv; kv = kv->next) { + zlog(ZLOG_NOTICE, "\tphp_value[%s] = %s", kv->key, kv->value); + } + + for (kv = wp->config->php_admin_values; kv; kv = kv->next) { + zlog(ZLOG_NOTICE, "\tphp_admin_value[%s] = %s", kv->key, kv->value); + } + zlog(ZLOG_NOTICE, " "); + } +} +/* }}} */ + +int fpm_conf_init_main(int test_conf, int force_daemon) /* {{{ */ +{ + int ret; + TSRMLS_FETCH(); + + if (fpm_globals.prefix && *fpm_globals.prefix) { + if (!fpm_conf_is_dir(fpm_globals.prefix)) { + zlog(ZLOG_ERROR, "the global prefix '%s' does not exist or is not a directory", fpm_globals.prefix); + return -1; + } + } + + if (fpm_globals.pid && *fpm_globals.pid) { + fpm_global_config.pid_file = strdup(fpm_globals.pid); + } + + if (fpm_globals.config == NULL) { + char *tmp; + + if (fpm_globals.prefix == NULL) { + spprintf(&tmp, 0, "%s/php-fpm.conf", PHP_SYSCONFDIR); + } else { + spprintf(&tmp, 0, "%s/etc/php-fpm.conf", fpm_globals.prefix); + } + + if (!tmp) { + zlog(ZLOG_SYSERROR, "spprintf() failed (tmp for fpm_globals.config)"); + return -1; + } + + fpm_globals.config = strdup(tmp); + efree(tmp); + + if (!fpm_globals.config) { + zlog(ZLOG_SYSERROR, "spprintf() failed (fpm_globals.config)"); + return -1; + } + } + + ret = fpm_conf_load_ini_file(fpm_globals.config TSRMLS_CC); + + if (0 > ret) { + zlog(ZLOG_ERROR, "failed to load configuration file '%s'", fpm_globals.config); + return -1; + } + + if (0 > fpm_conf_post_process(force_daemon TSRMLS_CC)) { + zlog(ZLOG_ERROR, "failed to post process the configuration"); + return -1; + } + + if (test_conf) { + if (test_conf > 1) { + fpm_conf_dump(); + } + zlog(ZLOG_NOTICE, "configuration file %s test is successful\n", fpm_globals.config); + fpm_globals.test_successful = 1; + return -1; + } + + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_conf_cleanup, 0)) { + return -1; + } + + return 0; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_conf.h b/sapi/fpm/fpm/fpm_conf.h new file mode 100644 index 0000000..dc54133 --- /dev/null +++ b/sapi/fpm/fpm/fpm_conf.h @@ -0,0 +1,106 @@ + + /* $Id: fpm_conf.h,v 1.12.2.2 2008/12/13 03:46:49 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_CONF_H +#define FPM_CONF_H 1 + +#include <stdint.h> +#include "php.h" + +#define PM2STR(a) (a == PM_STYLE_STATIC ? "static" : (a == PM_STYLE_DYNAMIC ? "dynamic" : "ondemand")) + +#define FPM_CONF_MAX_PONG_LENGTH 64 + +struct key_value_s; + +struct key_value_s { + struct key_value_s *next; + char *key; + char *value; +}; + +/* + * Please keep the same order as in fpm_conf.c and in php-fpm.conf.in + */ +struct fpm_global_config_s { + char *pid_file; + char *error_log; +#ifdef HAVE_SYSLOG_H + char *syslog_ident; + int syslog_facility; +#endif + int log_level; + int emergency_restart_threshold; + int emergency_restart_interval; + int process_control_timeout; + int process_max; + int process_priority; + int daemonize; + int rlimit_files; + int rlimit_core; + char *events_mechanism; +}; + +extern struct fpm_global_config_s fpm_global_config; + +/* + * Please keep the same order as in fpm_conf.c and in php-fpm.conf.in + */ +struct fpm_worker_pool_config_s { + char *name; + char *prefix; + char *user; + char *group; + char *listen_address; + int listen_backlog; + char *listen_owner; + char *listen_group; + char *listen_mode; + char *listen_allowed_clients; + int process_priority; + int pm; + int pm_max_children; + int pm_start_servers; + int pm_min_spare_servers; + int pm_max_spare_servers; + int pm_process_idle_timeout; + int pm_max_requests; + char *pm_status_path; + char *ping_path; + char *ping_response; + char *access_log; + char *access_format; + char *slowlog; + int request_slowlog_timeout; + int request_terminate_timeout; + int rlimit_files; + int rlimit_core; + char *chroot; + char *chdir; + int catch_workers_output; + char *security_limit_extensions; + struct key_value_s *env; + struct key_value_s *php_admin_values; + struct key_value_s *php_values; +}; + +struct ini_value_parser_s { + char *name; + char *(*parser)(zval *, void **, intptr_t); + intptr_t offset; +}; + +enum { + PM_STYLE_STATIC = 1, + PM_STYLE_DYNAMIC = 2, + PM_STYLE_ONDEMAND = 3 +}; + +int fpm_conf_init_main(int test_conf, int force_daemon); +int fpm_worker_pool_config_free(struct fpm_worker_pool_config_s *wpc); +int fpm_conf_write_pid(); +int fpm_conf_unlink_pid(); + +#endif + diff --git a/sapi/fpm/fpm/fpm_config.h b/sapi/fpm/fpm/fpm_config.h new file mode 100644 index 0000000..856414a --- /dev/null +++ b/sapi/fpm/fpm/fpm_config.h @@ -0,0 +1,79 @@ +/* $Id: fpm_config.h,v 1.16 2008/05/25 00:30:43 anight Exp $ */ +/* (c) 2007,2008 Andrei Nigmatulin */ + +#include <php_config.h> + +/* Solaris does not have it */ +#ifndef INADDR_NONE +# define INADDR_NONE (-1) +#endif + + +/* If we're not using GNU C, elide __attribute__ */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +/* Missing timer* macros (for solaris) */ +#ifndef timerisset +# define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#endif + +#ifndef timerclear +# define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#endif + +#ifndef timersub +# define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +#ifndef timeradd +# define timeradd(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ + if ((result)->tv_usec >= 1000000) \ + { \ + ++(result)->tv_sec; \ + (result)->tv_usec -= 1000000; \ + } \ + } while (0) +#endif + +#ifndef timercmp +/* does not work for >= and <= */ +# define timercmp(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_usec CMP (b)->tv_usec) : \ + ((a)->tv_sec CMP (b)->tv_sec)) +#endif +/* endof timer* macros */ + +#ifndef MIN +# define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +# define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +#if defined(HAVE_PTRACE) || defined(PROC_MEM_FILE) || defined(HAVE_MACH_VM_READ) +# define HAVE_FPM_TRACE 1 +#else +# define HAVE_FPM_TRACE 0 +#endif + +#if defined(HAVE_LQ_TCP_INFO) || defined(HAVE_LQ_SO_LISTENQ) +# define HAVE_FPM_LQ 1 +#else +# define HAVE_FPM_LQ 0 +#endif + diff --git a/sapi/fpm/fpm/fpm_env.c b/sapi/fpm/fpm/fpm_env.c new file mode 100644 index 0000000..6b64fed --- /dev/null +++ b/sapi/fpm/fpm/fpm_env.c @@ -0,0 +1,276 @@ + + /* $Id: fpm_env.c,v 1.15 2008/09/18 23:19:59 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#ifdef HAVE_ALLOCA_H +#include <alloca.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fpm_env.h" +#include "fpm.h" + +#ifndef HAVE_SETPROCTITLE +#ifdef __linux__ +static char **fpm_env_argv = NULL; +static size_t fpm_env_argv_len = 0; +#endif +#endif + +#ifndef HAVE_SETENV +# ifdef (__sparc__ || __sparc) +int setenv(char *name, char *value, int clobber) /* {{{ */ +{ + char *malloc(); + char *getenv(); + char *cp; + + if (clobber == 0 && getenv(name) != 0) { + return 0; + } + + if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0) { + return 1; + } + sprintf(cp, "%s=%s", name, value); + return putenv(cp); +} +/* }}} */ +# else +int setenv(char *name, char *value, int overwrite) /* {{{ */ +{ + int name_len = strlen(name); + int value_len = strlen(value); + char *var = alloca(name_len + 1 + value_len + 1); + + memcpy(var, name, name_len); + + var[name_len] = '='; + + memcpy(var + name_len + 1, value, value_len); + + var[name_len + 1 + value_len] = '\0'; + + return putenv(var); +} +/* }}} */ +# endif +#endif + +#ifndef HAVE_CLEARENV +void clearenv() /* {{{ */ +{ + char **envp; + char *s; + + /* this algo is the only one known to me + that works well on all systems */ + while (*(envp = environ)) { + char *eq = strchr(*envp, '='); + + s = strdup(*envp); + + if (eq) s[eq - *envp] = '\0'; + + unsetenv(s); + free(s); + } + +} +/* }}} */ +#endif + +#ifndef HAVE_UNSETENV +void unsetenv(const char *name) /* {{{ */ +{ + if(getenv(name) != NULL) { + int ct = 0; + int del = 0; + + while(environ[ct] != NULL) { + if (nvmatch(name, environ[ct]) != 0) del=ct; /* <--- WTF?! */ + { ct++; } /* <--- WTF?! */ + } + /* isn't needed free here?? */ + environ[del] = environ[ct-1]; + environ[ct-1] = NULL; + } +} +/* }}} */ + +static char * nvmatch(char *s1, char *s2) /* {{{ */ +{ + while(*s1 == *s2++) + { + if(*s1++ == '=') { + return s2; + } + } + if(*s1 == '\0' && *(s2-1) == '=') { + return s2; + } + return NULL; +} +/* }}} */ +#endif + +void fpm_env_setproctitle(char *title) /* {{{ */ +{ +#ifdef HAVE_SETPROCTITLE + setproctitle("%s", title); +#else +#ifdef __linux__ + if (fpm_env_argv != NULL && fpm_env_argv_len > strlen(SETPROCTITLE_PREFIX) + 3) { + memset(fpm_env_argv[0], 0, fpm_env_argv_len); + strncpy(fpm_env_argv[0], SETPROCTITLE_PREFIX, fpm_env_argv_len - 2); + strncpy(fpm_env_argv[0] + strlen(SETPROCTITLE_PREFIX), title, fpm_env_argv_len - strlen(SETPROCTITLE_PREFIX) - 2); + fpm_env_argv[1] = NULL; + } +#endif +#endif +} +/* }}} */ + +int fpm_env_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct key_value_s *kv; + char *title; + spprintf(&title, 0, "pool %s", wp->config->name); + fpm_env_setproctitle(title); + efree(title); + + clearenv(); + + for (kv = wp->config->env; kv; kv = kv->next) { + setenv(kv->key, kv->value, 1); + } + + if (wp->user) { + setenv("USER", wp->user, 1); + } + + if (wp->home) { + setenv("HOME", wp->home, 1); + } + + return 0; +} +/* }}} */ + +static int fpm_env_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct key_value_s *kv; + + for (kv = wp->config->env; kv; kv = kv->next) { + if (*kv->value == '$') { + char *value = getenv(kv->value + 1); + + if (!value) { + value = ""; + } + + free(kv->value); + kv->value = strdup(value); + } + + /* autodetected values should be removed + if these vars specified in config */ + if (!strcmp(kv->key, "USER")) { + free(wp->user); + wp->user = 0; + } + + if (!strcmp(kv->key, "HOME")) { + free(wp->home); + wp->home = 0; + } + } + + return 0; +} +/* }}} */ + +int fpm_env_init_main() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int i; + char *first = NULL; + char *last = NULL; + char *title; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (0 > fpm_env_conf_wp(wp)) { + return -1; + } + } +#ifndef HAVE_SETPROCTITLE +#ifdef __linux__ + /* + * This piece of code has been inspirated from nginx and pureftpd code, whic + * are under BSD licence. + * + * To change the process title in Linux we have to set argv[1] to NULL + * and to copy the title to the same place where the argv[0] points to. + * However, argv[0] may be too small to hold a new title. Fortunately, Linux + * store argv[] and environ[] one after another. So we should ensure that is + * the continuous memory and then we allocate the new memory for environ[] + * and copy it. After this we could use the memory starting from argv[0] for + * our process title. + */ + + for (i = 0; i < fpm_globals.argc; i++) { + if (first == NULL) { + first = fpm_globals.argv[i]; + } + if (last == NULL || fpm_globals.argv[i] == last + 1) { + last = fpm_globals.argv[i] + strlen(fpm_globals.argv[i]); + } + } + if (environ) { + for (i = 0; environ[i]; i++) { + if (first == NULL) { + first = environ[i]; + } + if (last == NULL || environ[i] == last + 1) { + last = environ[i] + strlen(environ[i]); + } + } + } + if (first == NULL || last == NULL) { + return 0; + } + + fpm_env_argv_len = last - first; + fpm_env_argv = fpm_globals.argv; + if (environ != NULL) { + char **new_environ; + unsigned int env_nb = 0U; + + while (environ[env_nb]) { + env_nb++; + } + + if ((new_environ = malloc((1U + env_nb) * sizeof (char *))) == NULL) { + return -1; + } + new_environ[env_nb] = NULL; + while (env_nb > 0U) { + env_nb--; + new_environ[env_nb] = strdup(environ[env_nb]); + } + environ = new_environ; + } +#endif +#endif + + spprintf(&title, 0, "master process (%s)", fpm_globals.config); + fpm_env_setproctitle(title); + efree(title); + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_env.h b/sapi/fpm/fpm/fpm_env.h new file mode 100644 index 0000000..bf47236 --- /dev/null +++ b/sapi/fpm/fpm/fpm_env.h @@ -0,0 +1,27 @@ + + /* $Id: fpm_env.h,v 1.9 2008/09/18 23:19:59 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_ENV_H +#define FPM_ENV_H 1 + +#include "fpm_worker_pool.h" + +#define SETPROCTITLE_PREFIX "php-fpm: " + +int fpm_env_init_child(struct fpm_worker_pool_s *wp); +int fpm_env_init_main(); +void fpm_env_setproctitle(char *title); + +extern char **environ; + +#ifndef HAVE_SETENV +int setenv(char *name, char *value, int overwrite); +#endif + +#ifndef HAVE_CLEARENV +void clearenv(); +#endif + +#endif + diff --git a/sapi/fpm/fpm/fpm_events.c b/sapi/fpm/fpm/fpm_events.c new file mode 100644 index 0000000..d5835f0 --- /dev/null +++ b/sapi/fpm/fpm/fpm_events.c @@ -0,0 +1,533 @@ + + /* $Id: fpm_events.c,v 1.21.2.2 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> /* for putenv */ +#include <string.h> + +#include <php.h> + +#include "fpm.h" +#include "fpm_process_ctl.h" +#include "fpm_events.h" +#include "fpm_cleanup.h" +#include "fpm_stdio.h" +#include "fpm_signals.h" +#include "fpm_children.h" +#include "zlog.h" +#include "fpm_clock.h" +#include "fpm_log.h" + +#include "events/select.h" +#include "events/poll.h" +#include "events/epoll.h" +#include "events/devpoll.h" +#include "events/port.h" +#include "events/kqueue.h" + +#define fpm_event_set_timeout(ev, now) timeradd(&(now), &(ev)->frequency, &(ev)->timeout); + +static void fpm_event_cleanup(int which, void *arg); +static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg); +static struct fpm_event_s *fpm_event_queue_isset(struct fpm_event_queue_s *queue, struct fpm_event_s *ev); +static int fpm_event_queue_add(struct fpm_event_queue_s **queue, struct fpm_event_s *ev); +static int fpm_event_queue_del(struct fpm_event_queue_s **queue, struct fpm_event_s *ev); +static void fpm_event_queue_destroy(struct fpm_event_queue_s **queue); + +static struct fpm_event_module_s *module; +static struct fpm_event_queue_s *fpm_event_queue_timer = NULL; +static struct fpm_event_queue_s *fpm_event_queue_fd = NULL; + +static void fpm_event_cleanup(int which, void *arg) /* {{{ */ +{ + fpm_event_queue_destroy(&fpm_event_queue_timer); + fpm_event_queue_destroy(&fpm_event_queue_fd); +} +/* }}} */ + +static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + char c; + int res, ret; + int fd = ev->fd; + + do { + do { + res = read(fd, &c, 1); + } while (res == -1 && errno == EINTR); + + if (res <= 0) { + if (res < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + zlog(ZLOG_SYSERROR, "unable to read from the signal pipe"); + } + return; + } + + switch (c) { + case 'C' : /* SIGCHLD */ + zlog(ZLOG_DEBUG, "received SIGCHLD"); + fpm_children_bury(); + break; + case 'I' : /* SIGINT */ + zlog(ZLOG_DEBUG, "received SIGINT"); + zlog(ZLOG_NOTICE, "Terminating ..."); + fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); + break; + case 'T' : /* SIGTERM */ + zlog(ZLOG_DEBUG, "received SIGTERM"); + zlog(ZLOG_NOTICE, "Terminating ..."); + fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET); + break; + case 'Q' : /* SIGQUIT */ + zlog(ZLOG_DEBUG, "received SIGQUIT"); + zlog(ZLOG_NOTICE, "Finishing ..."); + fpm_pctl(FPM_PCTL_STATE_FINISHING, FPM_PCTL_ACTION_SET); + break; + case '1' : /* SIGUSR1 */ + zlog(ZLOG_DEBUG, "received SIGUSR1"); + if (0 == fpm_stdio_open_error_log(1)) { + zlog(ZLOG_NOTICE, "error log file re-opened"); + } else { + zlog(ZLOG_ERROR, "unable to re-opened error log file"); + } + + ret = fpm_log_open(1); + if (ret == 0) { + zlog(ZLOG_NOTICE, "access log file re-opened"); + } else if (ret == -1) { + zlog(ZLOG_ERROR, "unable to re-opened access log file"); + } + /* else no access log are set */ + + break; + case '2' : /* SIGUSR2 */ + zlog(ZLOG_DEBUG, "received SIGUSR2"); + zlog(ZLOG_NOTICE, "Reloading in progress ..."); + fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); + break; + } + + if (fpm_globals.is_child) { + break; + } + } while (1); + return; +} +/* }}} */ + +static struct fpm_event_s *fpm_event_queue_isset(struct fpm_event_queue_s *queue, struct fpm_event_s *ev) /* {{{ */ +{ + if (!ev) { + return NULL; + } + + while (queue) { + if (queue->ev == ev) { + return ev; + } + queue = queue->next; + } + + return NULL; +} +/* }}} */ + +static int fpm_event_queue_add(struct fpm_event_queue_s **queue, struct fpm_event_s *ev) /* {{{ */ +{ + struct fpm_event_queue_s *elt; + + if (!queue || !ev) { + return -1; + } + + if (fpm_event_queue_isset(*queue, ev)) { + return 0; + } + + if (!(elt = malloc(sizeof(struct fpm_event_queue_s)))) { + zlog(ZLOG_SYSERROR, "Unable to add the event to queue: malloc() failed"); + return -1; + } + elt->prev = NULL; + elt->next = NULL; + elt->ev = ev; + + if (*queue) { + (*queue)->prev = elt; + elt->next = *queue; + } + *queue = elt; + + /* ask the event module to add the fd from its own queue */ + if (*queue == fpm_event_queue_fd && module->add) { + module->add(ev); + } + + return 0; +} +/* }}} */ + +static int fpm_event_queue_del(struct fpm_event_queue_s **queue, struct fpm_event_s *ev) /* {{{ */ +{ + struct fpm_event_queue_s *q; + if (!queue || !ev) { + return -1; + } + q = *queue; + while (q) { + if (q->ev == ev) { + if (q->prev) { + q->prev->next = q->next; + } + if (q->next) { + q->next->prev = q->prev; + } + if (q == *queue) { + *queue = q->next; + if (*queue) { + (*queue)->prev = NULL; + } + } + + /* ask the event module to remove the fd from its own queue */ + if (*queue == fpm_event_queue_fd && module->remove) { + module->remove(ev); + } + + free(q); + return 0; + } + q = q->next; + } + return -1; +} +/* }}} */ + +static void fpm_event_queue_destroy(struct fpm_event_queue_s **queue) /* {{{ */ +{ + struct fpm_event_queue_s *q, *tmp; + + if (!queue) { + return; + } + + if (*queue == fpm_event_queue_fd && module->clean) { + module->clean(); + } + + q = *queue; + while (q) { + tmp = q; + q = q->next; + /* q->prev = NULL */ + free(tmp); + } + *queue = NULL; +} +/* }}} */ + +int fpm_event_pre_init(char *machanism) /* {{{ */ +{ + /* kqueue */ + module = fpm_event_kqueue_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + /* port */ + module = fpm_event_port_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + /* epoll */ + module = fpm_event_epoll_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + /* /dev/poll */ + module = fpm_event_devpoll_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + /* poll */ + module = fpm_event_poll_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + /* select */ + module = fpm_event_select_module(); + if (module) { + if (!machanism || strcasecmp(module->name, machanism) == 0) { + return 0; + } + } + + if (machanism) { + zlog(ZLOG_ERROR, "event mechanism '%s' is not available on this system", machanism); + } else { + zlog(ZLOG_ERROR, "unable to find a suitable event mechanism on this system"); + } + return -1; +} +/* }} */ + +const char *fpm_event_machanism_name() /* {{{ */ +{ + return module ? module->name : NULL; +} +/* }}} */ + +int fpm_event_support_edge_trigger() /* {{{ */ +{ + return module ? module->support_edge_trigger : 0; +} +/* }}} */ + +int fpm_event_init_main() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int max; + + if (!module) { + zlog(ZLOG_ERROR, "no event module found"); + return -1; + } + + if (!module->wait) { + zlog(ZLOG_ERROR, "Incomplete event implementation. Please open a bug report on https://bugs.php.net."); + return -1; + } + + /* count the max number of necessary fds for polling */ + max = 1; /* only one FD is necessary at startup for the master process signal pipe */ + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (!wp->config) continue; + if (wp->config->catch_workers_output && wp->config->pm_max_children > 0) { + max += (wp->config->pm_max_children * 2); + } + } + + if (module->init(max) < 0) { + zlog(ZLOG_ERROR, "Unable to initialize the event module %s", module->name); + return -1; + } + + zlog(ZLOG_DEBUG, "event module is %s and %d fds have been reserved", module->name, max); + + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_event_cleanup, NULL)) { + return -1; + } + return 0; +} +/* }}} */ + +void fpm_event_loop(int err) /* {{{ */ +{ + static struct fpm_event_s signal_fd_event; + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return; + } + + fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL); + fpm_event_add(&signal_fd_event, 0); + + /* add timers */ + if (fpm_globals.heartbeat > 0) { + fpm_pctl_heartbeat(NULL, 0, NULL); + } + + if (!err) { + fpm_pctl_perform_idle_server_maintenance_heartbeat(NULL, 0, NULL); + + zlog(ZLOG_DEBUG, "%zu bytes have been reserved in SHM", fpm_shm_get_size_allocated()); + zlog(ZLOG_NOTICE, "ready to handle connections"); + } + + while (1) { + struct fpm_event_queue_s *q, *q2; + struct timeval ms; + struct timeval tmp; + struct timeval now; + unsigned long int timeout; + int ret; + + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return; + } + + fpm_clock_get(&now); + timerclear(&ms); + + /* search in the timeout queue for the next timer to trigger */ + q = fpm_event_queue_timer; + while (q) { + if (!timerisset(&ms)) { + ms = q->ev->timeout; + } else { + if (timercmp(&q->ev->timeout, &ms, <)) { + ms = q->ev->timeout; + } + } + q = q->next; + } + + /* 1s timeout if none has been set */ + if (!timerisset(&ms) || timercmp(&ms, &now, <) || timercmp(&ms, &now, ==)) { + timeout = 1000; + } else { + timersub(&ms, &now, &tmp); + timeout = (tmp.tv_sec * 1000) + (tmp.tv_usec / 1000) + 1; + } + + ret = module->wait(fpm_event_queue_fd, timeout); + + /* is a child, nothing to do here */ + if (ret == -2) { + return; + } + + if (ret > 0) { + zlog(ZLOG_DEBUG, "event module triggered %d events", ret); + } + + /* trigger timers */ + q = fpm_event_queue_timer; + while (q) { + fpm_clock_get(&now); + if (q->ev) { + if (timercmp(&now, &q->ev->timeout, >) || timercmp(&now, &q->ev->timeout, ==)) { + fpm_event_fire(q->ev); + /* sanity check */ + if (fpm_globals.parent_pid != getpid()) { + return; + } + if (q->ev->flags & FPM_EV_PERSIST) { + fpm_event_set_timeout(q->ev, now); + } else { /* delete the event */ + q2 = q; + if (q->prev) { + q->prev->next = q->next; + } + if (q->next) { + q->next->prev = q->prev; + } + if (q == fpm_event_queue_timer) { + fpm_event_queue_timer = q->next; + if (fpm_event_queue_timer) { + fpm_event_queue_timer->prev = NULL; + } + } + q = q->next; + free(q2); + continue; + } + } + } + q = q->next; + } + } +} +/* }}} */ + +void fpm_event_fire(struct fpm_event_s *ev) /* {{{ */ +{ + if (!ev || !ev->callback) { + return; + } + + (*ev->callback)( (struct fpm_event_s *) ev, ev->which, ev->arg); +} +/* }}} */ + +int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg) /* {{{ */ +{ + if (!ev || !callback || fd < -1) { + return -1; + } + memset(ev, 0, sizeof(struct fpm_event_s)); + ev->fd = fd; + ev->callback = callback; + ev->arg = arg; + ev->flags = flags; + return 0; +} +/* }}} */ + +int fpm_event_add(struct fpm_event_s *ev, unsigned long int frequency) /* {{{ */ +{ + struct timeval now; + struct timeval tmp; + + if (!ev) { + return -1; + } + + ev->index = -1; + + /* it's a triggered event on incoming data */ + if (ev->flags & FPM_EV_READ) { + ev->which = FPM_EV_READ; + if (fpm_event_queue_add(&fpm_event_queue_fd, ev) != 0) { + return -1; + } + return 0; + } + + /* it's a timer event */ + ev->which = FPM_EV_TIMEOUT; + + fpm_clock_get(&now); + if (frequency >= 1000) { + tmp.tv_sec = frequency / 1000; + tmp.tv_usec = (frequency % 1000) * 1000; + } else { + tmp.tv_sec = 0; + tmp.tv_usec = frequency * 1000; + } + ev->frequency = tmp; + fpm_event_set_timeout(ev, now); + + if (fpm_event_queue_add(&fpm_event_queue_timer, ev) != 0) { + return -1; + } + + return 0; +} +/* }}} */ + +int fpm_event_del(struct fpm_event_s *ev) /* {{{ */ +{ + if (ev->index >= 0 && fpm_event_queue_del(&fpm_event_queue_fd, ev) != 0) { + return -1; + } + + if (ev->index < 0 && fpm_event_queue_del(&fpm_event_queue_timer, ev) != 0) { + return -1; + } + + return 0; +} +/* }}} */ + +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_events.h b/sapi/fpm/fpm/fpm_events.h new file mode 100644 index 0000000..416ec6e --- /dev/null +++ b/sapi/fpm/fpm/fpm_events.h @@ -0,0 +1,52 @@ + + /* $Id: fpm_events.h,v 1.9 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_EVENTS_H +#define FPM_EVENTS_H 1 + +#define FPM_EV_TIMEOUT (1 << 0) +#define FPM_EV_READ (1 << 1) +#define FPM_EV_PERSIST (1 << 2) +#define FPM_EV_EDGE (1 << 3) + +#define fpm_event_set_timer(ev, flags, cb, arg) fpm_event_set((ev), -1, (flags), (cb), (arg)) + +struct fpm_event_s { + int fd; /* not set with FPM_EV_TIMEOUT */ + struct timeval timeout; /* next time to trigger */ + struct timeval frequency; + void (*callback)(struct fpm_event_s *, short, void *); + void *arg; + int flags; + int index; /* index of the fd in the ufds array */ + short which; /* type of event */ +}; + +typedef struct fpm_event_queue_s { + struct fpm_event_queue_s *prev; + struct fpm_event_queue_s *next; + struct fpm_event_s *ev; +} fpm_event_queue; + +struct fpm_event_module_s { + const char *name; + int support_edge_trigger; + int (*init)(int max_fd); + int (*clean)(void); + int (*wait)(struct fpm_event_queue_s *queue, unsigned long int timeout); + int (*add)(struct fpm_event_s *ev); + int (*remove)(struct fpm_event_s *ev); +}; + +void fpm_event_loop(int err); +void fpm_event_fire(struct fpm_event_s *ev); +int fpm_event_init_main(); +int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg); +int fpm_event_add(struct fpm_event_s *ev, unsigned long int timeout); +int fpm_event_del(struct fpm_event_s *ev); +int fpm_event_pre_init(char *machanism); +const char *fpm_event_machanism_name(); +int fpm_event_support_edge_trigger(); + +#endif diff --git a/sapi/fpm/fpm/fpm_log.c b/sapi/fpm/fpm/fpm_log.c new file mode 100644 index 0000000..6b014b5 --- /dev/null +++ b/sapi/fpm/fpm/fpm_log.c @@ -0,0 +1,466 @@ + + /* $Id: fpm_status.c 312262 2011-06-18 17:41:56Z felipe $ */ + /* (c) 2009 Jerome Loyet */ + +#include "php.h" +#include "SAPI.h" +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#ifdef HAVE_TIMES +#include <sys/times.h> +#endif + +#include "fpm_config.h" +#include "fpm_log.h" +#include "fpm_clock.h" +#include "fpm_process_ctl.h" +#include "fpm_signals.h" +#include "fpm_scoreboard.h" +#include "fastcgi.h" +#include "zlog.h" + +#ifdef MAX_LINE_LENGTH +# define FPM_LOG_BUFFER MAX_LINE_LENGTH +#else +# define FPM_LOG_BUFFER 1024 +#endif + +static char *fpm_log_format = NULL; +static int fpm_log_fd = -1; + +int fpm_log_open(int reopen) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int ret = 1; + + int fd; + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (!wp->config->access_log) { + continue; + } + ret = 0; + + fd = open(wp->config->access_log, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); + if (0 > fd) { + zlog(ZLOG_SYSERROR, "failed to open access log (%s)", wp->config->access_log); + return -1; + } + + if (reopen) { + dup2(fd, wp->log_fd); + close(fd); + fd = wp->log_fd; + fpm_pctl_kill_all(SIGQUIT); + } else { + wp->log_fd = fd; + } + + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + } + + return ret; +} +/* }}} */ + +/* }}} */ +int fpm_log_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + if (!wp || !wp->config) { + return -1; + } + + if (wp->config->access_log && *wp->config->access_log) { + if (wp->config->access_format) { + fpm_log_format = strdup(wp->config->access_format); + } + } + + if (fpm_log_fd == -1) { + fpm_log_fd = wp->log_fd; + } + + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (wp->log_fd > -1 && wp->log_fd != fpm_log_fd) { + close(wp->log_fd); + wp->log_fd = -1; + } + } + + return 0; +} +/* }}} */ + +int fpm_log_write(char *log_format TSRMLS_DC) /* {{{ */ +{ + char *s, *b; + char buffer[FPM_LOG_BUFFER+1]; + int token, test; + size_t len, len2; + struct fpm_scoreboard_proc_s proc, *proc_p; + struct fpm_scoreboard_s *scoreboard; + char tmp[129]; + char format[129]; + time_t now_epoch; +#ifdef HAVE_TIMES + clock_t tms_total; +#endif + + if (!log_format && (!fpm_log_format || fpm_log_fd == -1)) { + return -1; + } + + if (!log_format) { + log_format = fpm_log_format; + test = 0; + } else { + test = 1; + } + + now_epoch = time(NULL); + + if (!test) { + scoreboard = fpm_scoreboard_get(); + if (!scoreboard) { + zlog(ZLOG_WARNING, "unable to get scoreboard while preparing the access log"); + return -1; + } + proc_p = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (!proc_p) { + zlog(ZLOG_WARNING, "[pool %s] Unable to acquire shm slot while preparing the access log", scoreboard->pool); + return -1; + } + proc = *proc_p; + fpm_scoreboard_proc_release(proc_p); + } + + token = 0; + + memset(buffer, '\0', sizeof(buffer)); + b = buffer; + len = 0; + + + s = log_format; + + while (*s != '\0') { + /* Test is we have place for 1 more char. */ + if (len >= FPM_LOG_BUFFER) { + zlog(ZLOG_NOTICE, "the log buffer is full (%d). The access log request has been truncated.", FPM_LOG_BUFFER); + len = FPM_LOG_BUFFER; + break; + } + + if (!token && *s == '%') { + token = 1; + memset(format, '\0', sizeof(format)); /* reset format */ + s++; + continue; + } + + if (token) { + token = 0; + len2 = 0; + switch (*s) { + + case '%': /* '%' */ + *b = '%'; + len2 = 1; + break; + +#ifdef HAVE_TIMES + case 'C': /* %CPU */ + if (format[0] == '\0' || !strcasecmp(format, "total")) { + if (!test) { + tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime; + } + } else if (!strcasecmp(format, "user")) { + if (!test) { + tms_total = proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_cutime; + } + } else if (!strcasecmp(format, "system")) { + if (!test) { + tms_total = proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cstime; + } + } else { + zlog(ZLOG_WARNING, "only 'total', 'user' or 'system' are allowed as a modifier for %%%c ('%s')", *s, format); + return -1; + } + + format[0] = '\0'; + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.2f", tms_total / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.); + } + break; +#endif + + case 'd': /* duration µs */ + /* seconds */ + if (format[0] == '\0' || !strcasecmp(format, "seconds")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec + proc.duration.tv_usec / 1000000.); + } + + /* miliseconds */ + } else if (!strcasecmp(format, "miliseconds") || !strcasecmp(format, "mili")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%.3f", proc.duration.tv_sec * 1000. + proc.duration.tv_usec / 1000.); + } + + /* microseconds */ + } else if (!strcasecmp(format, "microseconds") || !strcasecmp(format, "micro")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", proc.duration.tv_sec * 1000000UL + proc.duration.tv_usec); + } + + } else { + zlog(ZLOG_WARNING, "only 'seconds', 'mili', 'miliseconds', 'micro' or 'microseconds' are allowed as a modifier for %%%c ('%s')", *s, format); + return -1; + } + format[0] = '\0'; + break; + + case 'e': /* fastcgi env */ + if (format[0] == '\0') { + zlog(ZLOG_WARNING, "the name of the environment variable must be set between embraces for %%%c", *s); + return -1; + } + + if (!test) { + char *env = fcgi_getenv((fcgi_request*) SG(server_context), format, strlen(format)); + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", env ? env : "-"); + } + format[0] = '\0'; + break; + + case 'f': /* script */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.script_filename && *proc.script_filename ? proc.script_filename : "-"); + } + break; + + case 'l': /* content length */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.content_length); + } + break; + + case 'm': /* method */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.request_method && *proc.request_method ? proc.request_method : "-"); + } + break; + + case 'M': /* memory */ + /* seconds */ + if (format[0] == '\0' || !strcasecmp(format, "bytes")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%zu", proc.memory); + } + + /* kilobytes */ + } else if (!strcasecmp(format, "kilobytes") || !strcasecmp(format, "kilo")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", proc.memory / 1024); + } + + /* megabytes */ + } else if (!strcasecmp(format, "megabytes") || !strcasecmp(format, "mega")) { + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%lu", proc.memory / 1024 / 1024); + } + + } else { + zlog(ZLOG_WARNING, "only 'bytes', 'kilo', 'kilobytes', 'mega' or 'megabytes' are allowed as a modifier for %%%c ('%s')", *s, format); + return -1; + } + format[0] = '\0'; + break; + + case 'n': /* pool name */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", scoreboard->pool[0] ? scoreboard->pool : "-"); + } + break; + + case 'o': /* header output */ + if (format[0] == '\0') { + zlog(ZLOG_WARNING, "the name of the header must be set between embraces for %%%c", *s); + return -1; + } + if (!test) { + sapi_header_struct *h; + zend_llist_position pos; + sapi_headers_struct *sapi_headers = &SG(sapi_headers); + size_t format_len = strlen(format); + + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + char *header; + if (!h->header_len) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } + if (!strstr(h->header, format)) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } + + /* test if enought char after the header name + ': ' */ + if (h->header_len <= format_len + 2) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } + + if (h->header[format_len] != ':' || h->header[format_len + 1] != ' ') { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } + + header = h->header + format_len + 2; + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", header && *header ? header : "-"); + + /* found, done */ + break; + } + if (!len2) { + len2 = 1; + *b = '-'; + } + } + format[0] = '\0'; + break; + + case 'p': /* PID */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%ld", (long)getpid()); + } + break; + + case 'P': /* PID */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%ld", (long)getppid()); + } + break; + + case 'q': /* query_string */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.query_string ? proc.query_string : ""); + } + break; + + case 'Q': /* '?' */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.query_string && *proc.query_string ? "?" : ""); + } + break; + + case 'r': /* request URI */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.request_uri ? proc.request_uri : "-"); + } + break; + + case 'R': /* remote IP address */ + if (!test) { + char *tmp = fcgi_get_last_client_ip(); + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp ? tmp : "-"); + } + break; + + case 's': /* status */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%d", SG(sapi_headers).http_response_code); + } + break; + + case 'T': + case 't': /* time */ + if (!test) { + time_t *t; + if (*s == 't') { + t = &proc.accepted_epoch; + } else { + t = &now_epoch; + } + if (format[0] == '\0') { + strftime(tmp, sizeof(tmp) - 1, "%d/%b/%Y:%H:%M:%S %z", localtime(t)); + } else { + strftime(tmp, sizeof(tmp) - 1, format, localtime(t)); + } + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", tmp); + } + format[0] = '\0'; + break; + + case 'u': /* remote user */ + if (!test) { + len2 = snprintf(b, FPM_LOG_BUFFER - len, "%s", proc.auth_user ? proc.auth_user : "-"); + } + break; + + case '{': /* complex var */ + token = 1; + { + char *start; + size_t l; + + start = ++s; + + while (*s != '\0') { + if (*s == '}') { + l = s - start; + + if (l >= sizeof(format) - 1) { + l = sizeof(format) - 1; + } + + memcpy(format, start, l); + format[l] = '\0'; + break; + } + s++; + } + if (s[1] == '\0') { + zlog(ZLOG_WARNING, "missing closing embrace in the access.format"); + return -1; + } + } + break; + + default: + zlog(ZLOG_WARNING, "Invalid token in the access.format (%%%c)", *s); + return -1; + } + + if (*s != '}' && format[0] != '\0') { + zlog(ZLOG_WARNING, "embrace is not allowed for modifier %%%c", *s); + return -1; + } + s++; + if (!test) { + b += len2; + len += len2; + } + continue; + } + + if (!test) { + // push the normal char to the output buffer + *b = *s; + b++; + len++; + } + s++; + } + + if (!test && strlen(buffer) > 0) { + buffer[len] = '\n'; + write(fpm_log_fd, buffer, len + 1); + } + + return 0; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_log.h b/sapi/fpm/fpm/fpm_log.h new file mode 100644 index 0000000..f0199d9 --- /dev/null +++ b/sapi/fpm/fpm/fpm_log.h @@ -0,0 +1,13 @@ + + /* $Id: fpm_status.h 312263 2011-06-18 17:46:16Z felipe $ */ + /* (c) 2009 Jerome Loyet */ + +#ifndef FPM_LOG_H +#define FPM_LOG_H 1 +#include "fpm_worker_pool.h" + +int fpm_log_init_child(struct fpm_worker_pool_s *wp); +int fpm_log_write(char *log_format TSRMLS_DC); +int fpm_log_open(int reopen); + +#endif diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c new file mode 100644 index 0000000..61088c4 --- /dev/null +++ b/sapi/fpm/fpm/fpm_main.c @@ -0,0 +1,2000 @@ +/* + +----------------------------------------------------------------------+ + | 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: Rasmus Lerdorf <rasmus@lerdorf.on.ca> | + | Stig Bakken <ssb@php.net> | + | Zeev Suraski <zeev@zend.com> | + | FastCGI: Ben Mansell <php@slimyhorror.com> | + | Shane Caraveo <shane@caraveo.com> | + | Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: cgi_main.c 291497 2009-11-30 14:43:22Z dmitry $ */ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_modules.h" +#include "php.h" +#include "zend_ini_scanner.h" +#include "zend_globals.h" +#include "zend_stream.h" + +#include "SAPI.h" + +#include <stdio.h> +#include "php.h" + +#ifdef PHP_WIN32 +# include "win32/time.h" +# include "win32/signal.h" +# include <process.h> +#endif + +#if HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif + +#if HAVE_SIGNAL_H +# include <signal.h> +#endif + +#if HAVE_SETLOCALE +# include <locale.h> +#endif + +#if HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif + +#if HAVE_SYS_WAIT_H +# include <sys/wait.h> +#endif + +#if HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "ext/standard/php_standard.h" + +#ifdef PHP_WIN32 +# include <io.h> +# include <fcntl.h> +# include "win32/php_registry.h" +#endif + +#ifdef __riscos__ +# include <unixlib/local.h> +int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; +#endif + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" + +#include "php_getopt.h" + +#include "fastcgi.h" + +#include <php_config.h> +#include "fpm.h" +#include "fpm_request.h" +#include "fpm_status.h" +#include "fpm_conf.h" +#include "fpm_php.h" +#include "fpm_log.h" +#include "zlog.h" + +#ifndef PHP_WIN32 +/* XXX this will need to change later when threaded fastcgi is implemented. shane */ +struct sigaction act, old_term, old_quit, old_int; +#endif + +static void (*php_php_import_environment_variables)(zval *array_ptr TSRMLS_DC); + +#ifndef PHP_WIN32 +/* these globals used for forking children on unix systems */ + +/** + * Set to non-zero if we are the parent process + */ +static int parent = 1; +#endif + +static int request_body_fd; +static int fpm_is_running = 0; + +static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC); +static void fastcgi_ini_parser(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg TSRMLS_DC); + +#define PHP_MODE_STANDARD 1 +#define PHP_MODE_HIGHLIGHT 2 +#define PHP_MODE_INDENT 3 +#define PHP_MODE_LINT 4 +#define PHP_MODE_STRIP 5 + +static char *php_optarg = NULL; +static int php_optind = 1; +static zend_module_entry cgi_module_entry; + +static const opt_struct OPTIONS[] = { + {'c', 1, "php-ini"}, + {'d', 1, "define"}, + {'e', 0, "profile-info"}, + {'h', 0, "help"}, + {'i', 0, "info"}, + {'m', 0, "modules"}, + {'n', 0, "no-php-ini"}, + {'?', 0, "usage"},/* help alias (both '?' and 'usage') */ + {'v', 0, "version"}, + {'y', 1, "fpm-config"}, + {'t', 0, "test"}, + {'p', 1, "prefix"}, + {'g', 1, "pid"}, + {'R', 0, "allow-to-run-as-root"}, + {'D', 0, "daemonize"}, + {'F', 0, "nodaemonize"}, + {'-', 0, NULL} /* end of args */ +}; + +typedef struct _php_cgi_globals_struct { + zend_bool rfc2616_headers; + zend_bool nph; + zend_bool fix_pathinfo; + zend_bool force_redirect; + zend_bool discard_path; + zend_bool fcgi_logging; + char *redirect_status_env; + HashTable user_config_cache; + char *error_header; + char *fpm_config; +} php_cgi_globals_struct; + +/* {{{ user_config_cache + * + * Key for each cache entry is dirname(PATH_TRANSLATED). + * + * NOTE: Each cache entry config_hash contains the combination from all user ini files found in + * the path starting from doc_root throught to dirname(PATH_TRANSLATED). There is no point + * storing per-file entries as it would not be possible to detect added / deleted entries + * between separate files. + */ +typedef struct _user_config_cache_entry { + time_t expires; + HashTable *user_config; +} user_config_cache_entry; + +static void user_config_cache_entry_dtor(user_config_cache_entry *entry) +{ + zend_hash_destroy(entry->user_config); + free(entry->user_config); +} +/* }}} */ + +#ifdef ZTS +static int php_cgi_globals_id; +#define CGIG(v) TSRMG(php_cgi_globals_id, php_cgi_globals_struct *, v) +#else +static php_cgi_globals_struct php_cgi_globals; +#define CGIG(v) (php_cgi_globals.v) +#endif + +#ifdef PHP_WIN32 +#define TRANSLATE_SLASHES(path) \ + { \ + char *tmp = path; \ + while (*tmp) { \ + if (*tmp == '\\') *tmp = '/'; \ + tmp++; \ + } \ + } +#else +#define TRANSLATE_SLASHES(path) +#endif + +static int print_module_info(zend_module_entry *module, void *arg TSRMLS_DC) +{ + php_printf("%s\n", module->name); + return 0; +} + +static int module_name_cmp(const void *a, const void *b TSRMLS_DC) +{ + Bucket *f = *((Bucket **) a); + Bucket *s = *((Bucket **) b); + + return strcasecmp( ((zend_module_entry *)f->pData)->name, + ((zend_module_entry *)s->pData)->name); +} + +static void print_modules(TSRMLS_D) +{ + HashTable sorted_registry; + zend_module_entry tmp; + + zend_hash_init(&sorted_registry, 50, NULL, NULL, 1); + zend_hash_copy(&sorted_registry, &module_registry, NULL, &tmp, sizeof(zend_module_entry)); + zend_hash_sort(&sorted_registry, zend_qsort, module_name_cmp, 0 TSRMLS_CC); + zend_hash_apply_with_argument(&sorted_registry, (apply_func_arg_t) print_module_info, NULL TSRMLS_CC); + zend_hash_destroy(&sorted_registry); +} + +static int print_extension_info(zend_extension *ext, void *arg TSRMLS_DC) +{ + php_printf("%s\n", ext->name); + return 0; +} + +static int extension_name_cmp(const zend_llist_element **f, const zend_llist_element **s TSRMLS_DC) +{ + return strcmp( ((zend_extension *)(*f)->data)->name, + ((zend_extension *)(*s)->data)->name); +} + +static void print_extensions(TSRMLS_D) +{ + zend_llist sorted_exts; + + zend_llist_copy(&sorted_exts, &zend_extensions); + sorted_exts.dtor = NULL; + zend_llist_sort(&sorted_exts, extension_name_cmp TSRMLS_CC); + zend_llist_apply_with_argument(&sorted_exts, (llist_apply_with_arg_func_t) print_extension_info, NULL TSRMLS_CC); + zend_llist_destroy(&sorted_exts); +} + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC) +{ + ssize_t ret; + + /* sapi has started which means everyhting must be send through fcgi */ + if (fpm_is_running) { + fcgi_request *request = (fcgi_request*) SG(server_context); + ret = fcgi_write(request, FCGI_STDOUT, str, str_length); + if (ret <= 0) { + return 0; + } + return (size_t)ret; + } + + /* sapi has not started, output to stdout instead of fcgi */ +#ifdef PHP_WRITE_STDOUT + ret = write(STDOUT_FILENO, str, str_length); + if (ret <= 0) { + return 0; + } + return (size_t)ret; +#else + return fwrite(str, 1, MIN(str_length, 16384), stdout); +#endif +} + +static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + const char *ptr = str; + uint remaining = str_length; + size_t ret; + + while (remaining > 0) { + ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC); + if (!ret) { + php_handle_aborted_connection(); + return str_length - remaining; + } + ptr += ret; + remaining -= ret; + } + + return str_length; +} + + +static void sapi_cgibin_flush(void *server_context) +{ + /* fpm has started, let use fcgi instead of stdout */ + if (fpm_is_running) { + fcgi_request *request = (fcgi_request*) server_context; + if ( +#ifndef PHP_WIN32 + !parent && +#endif + request && !fcgi_flush(request, 0)) { + php_handle_aborted_connection(); + } + return; + } + + /* fpm has not started yet, let use stdout instead of fcgi */ + if (fflush(stdout) == EOF) { + php_handle_aborted_connection(); + } +} + +#define SAPI_CGI_MAX_HEADER_LENGTH 1024 + +typedef struct _http_error { + int code; + const char* msg; +} http_error; + +static const http_error http_error_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"}, + {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} +}; + +static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char buf[SAPI_CGI_MAX_HEADER_LENGTH]; + sapi_header_struct *h; + zend_llist_position pos; + zend_bool ignore_status = 0; + int response_status = SG(sapi_headers).http_response_code; + + if (SG(request_info).no_headers == 1) { + return SAPI_HEADER_SENT_SUCCESSFULLY; + } + + if (CGIG(nph) || SG(sapi_headers).http_response_code != 200) + { + int len; + zend_bool has_status = 0; + + if (CGIG(rfc2616_headers) && SG(sapi_headers).http_status_line) { + char *s; + len = slprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH, "%s\r\n", SG(sapi_headers).http_status_line); + if ((s = strchr(SG(sapi_headers).http_status_line, ' '))) { + response_status = atoi((s + 1)); + } + + if (len > SAPI_CGI_MAX_HEADER_LENGTH) { + len = SAPI_CGI_MAX_HEADER_LENGTH; + } + + } else { + char *s; + + if (SG(sapi_headers).http_status_line && + (s = strchr(SG(sapi_headers).http_status_line, ' ')) != 0 && + (s - SG(sapi_headers).http_status_line) >= 5 && + strncasecmp(SG(sapi_headers).http_status_line, "HTTP/", 5) == 0 + ) { + len = slprintf(buf, sizeof(buf), "Status:%s\r\n", s); + response_status = atoi((s + 1)); + } else { + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if (h->header_len > sizeof("Status:") - 1 && + strncasecmp(h->header, "Status:", sizeof("Status:") - 1) == 0 + ) { + has_status = 1; + break; + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + if (!has_status) { + http_error *err = (http_error*)http_error_codes; + + while (err->code != 0) { + if (err->code == SG(sapi_headers).http_response_code) { + break; + } + err++; + } + if (err->msg) { + len = slprintf(buf, sizeof(buf), "Status: %d %s\r\n", SG(sapi_headers).http_response_code, err->msg); + } else { + len = slprintf(buf, sizeof(buf), "Status: %d\r\n", SG(sapi_headers).http_response_code); + } + } + } + } + + if (!has_status) { + PHPWRITE_H(buf, len); + ignore_status = 1; + } + } + + h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + /* prevent CRLFCRLF */ + if (h->header_len) { + if (h->header_len > sizeof("Status:") - 1 && + strncasecmp(h->header, "Status:", sizeof("Status:") - 1) == 0 + ) { + if (!ignore_status) { + ignore_status = 1; + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } else if (response_status == 304 && h->header_len > sizeof("Content-Type:") - 1 && + strncasecmp(h->header, "Content-Type:", sizeof("Content-Type:") - 1) == 0 + ) { + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + continue; + } else { + PHPWRITE_H(h->header, h->header_len); + PHPWRITE_H("\r\n", 2); + } + } + h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + PHPWRITE_H("\r\n", 2); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +#ifndef STDIN_FILENO +# define STDIN_FILENO 0 +#endif + +static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + uint read_bytes = 0; + int tmp_read_bytes; + + count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes)); + while (read_bytes < count_bytes) { + fcgi_request *request = (fcgi_request*) SG(server_context); + if (request_body_fd == -1) { + char *request_body_filename = sapi_cgibin_getenv((char *) "REQUEST_BODY_FILE", + sizeof("REQUEST_BODY_FILE") - 1 TSRMLS_CC); + + if (request_body_filename && *request_body_filename) { + request_body_fd = open(request_body_filename, O_RDONLY); + + if (0 > request_body_fd) { + php_error(E_WARNING, "REQUEST_BODY_FILE: open('%s') failed: %s (%d)", + request_body_filename, strerror(errno), errno); + return 0; + } + } + } + + /* If REQUEST_BODY_FILE variable not available - read post body from fastcgi stream */ + if (request_body_fd < 0) { + tmp_read_bytes = fcgi_read(request, buffer + read_bytes, count_bytes - read_bytes); + } else { + tmp_read_bytes = read(request_body_fd, buffer + read_bytes, count_bytes - read_bytes); + } + if (tmp_read_bytes <= 0) { + break; + } + read_bytes += tmp_read_bytes; + } + return read_bytes; +} + +static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC) +{ + /* if fpm has started, use fcgi env */ + if (fpm_is_running) { + fcgi_request *request = (fcgi_request*) SG(server_context); + return fcgi_getenv(request, name, name_len); + } + + /* if fpm has not started yet, use std env */ + return getenv(name); +} + +static char *_sapi_cgibin_putenv(char *name, char *value TSRMLS_DC) +{ + int name_len; + + if (!name) { + return NULL; + } + name_len = strlen(name); + + fcgi_request *request = (fcgi_request*) SG(server_context); + return fcgi_putenv(request, name, name_len, value); +} + +static char *sapi_cgi_read_cookies(TSRMLS_D) +{ + return sapi_cgibin_getenv((char *) "HTTP_COOKIE", sizeof("HTTP_COOKIE") - 1 TSRMLS_CC); +} + +void cgi_php_import_environment_variables(zval *array_ptr TSRMLS_DC) +{ + fcgi_request *request; + HashPosition pos; + char *var, **val; + uint var_len; + ulong idx; + int filter_arg; + + + if (PG(http_globals)[TRACK_VARS_ENV] && + array_ptr != PG(http_globals)[TRACK_VARS_ENV] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_ENV]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_ENV])) > 0 + ) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_ENV]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } else if (PG(http_globals)[TRACK_VARS_SERVER] && + array_ptr != PG(http_globals)[TRACK_VARS_SERVER] && + Z_TYPE_P(PG(http_globals)[TRACK_VARS_SERVER]) == IS_ARRAY && + zend_hash_num_elements(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER])) > 0 + ) { + zval_dtor(array_ptr); + *array_ptr = *PG(http_globals)[TRACK_VARS_SERVER]; + INIT_PZVAL(array_ptr); + zval_copy_ctor(array_ptr); + return; + } + + /* call php's original import as a catch-all */ + php_php_import_environment_variables(array_ptr TSRMLS_CC); + + request = (fcgi_request*) SG(server_context); + filter_arg = (array_ptr == PG(http_globals)[TRACK_VARS_ENV])?PARSE_ENV:PARSE_SERVER; + + for (zend_hash_internal_pointer_reset_ex(request->env, &pos); + zend_hash_get_current_key_ex(request->env, &var, &var_len, &idx, 0, &pos) == HASH_KEY_IS_STRING && + zend_hash_get_current_data_ex(request->env, (void **) &val, &pos) == SUCCESS; + zend_hash_move_forward_ex(request->env, &pos) + ) { + unsigned int new_val_len; + + if (sapi_module.input_filter(filter_arg, var, val, strlen(*val), &new_val_len TSRMLS_CC)) { + php_register_variable_safe(var, *val, new_val_len, array_ptr TSRMLS_CC); + } + } +} + +static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + unsigned int php_self_len; + char *php_self; + + /* In CGI mode, we consider the environment to be a part of the server + * variables + */ + php_import_environment_variables(track_vars_array TSRMLS_CC); + + if (CGIG(fix_pathinfo)) { + char *script_name = SG(request_info).request_uri; + unsigned int script_name_len = script_name ? strlen(script_name) : 0; + char *path_info = sapi_cgibin_getenv("PATH_INFO", sizeof("PATH_INFO") - 1 TSRMLS_CC); + unsigned int path_info_len = path_info ? strlen(path_info) : 0; + + php_self_len = script_name_len + path_info_len; + php_self = emalloc(php_self_len + 1); + + /* Concat script_name and path_info into php_self */ + if (script_name) { + memcpy(php_self, script_name, script_name_len + 1); + } + if (path_info) { + memcpy(php_self + script_name_len, path_info, path_info_len + 1); + } + + /* Build the special-case PHP_SELF variable for the CGI version */ + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array TSRMLS_CC); + } + efree(php_self); + } else { + php_self = SG(request_info).request_uri ? SG(request_info).request_uri : ""; + php_self_len = strlen(php_self); + if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len TSRMLS_CC)) { + php_register_variable_safe("PHP_SELF", php_self, php_self_len, track_vars_array TSRMLS_CC); + } + } +} + +/* {{{ sapi_cgi_log_fastcgi + * + * Ignore level, we want to send all messages through fastcgi + */ +void sapi_cgi_log_fastcgi(int level, char *message, size_t len) +{ + TSRMLS_FETCH(); + + fcgi_request *request = (fcgi_request*) SG(server_context); + + /* ensure we want: + * - to log (fastcgi.logging in php.ini) + * - we are currently dealing with a request + * - the message is not empty + */ + if (CGIG(fcgi_logging) && request && message && len > 0) { + char *buf = malloc(len + 2); + memcpy(buf, message, len); + memcpy(buf + len, "\n", sizeof("\n")); + fcgi_write(request, FCGI_STDERR, buf, len+1); + free(buf); + } +} +/* }}} */ + +/* {{{ sapi_cgi_log_message + */ +static void sapi_cgi_log_message(char *message) +{ + zlog(ZLOG_NOTICE, "PHP message: %s", message); +} +/* }}} */ + +/* {{{ php_cgi_ini_activate_user_config + */ +static void php_cgi_ini_activate_user_config(char *path, int path_len, const char *doc_root, int doc_root_len, int start TSRMLS_DC) +{ + char *ptr; + user_config_cache_entry *new_entry, *entry; + time_t request_time = sapi_get_request_time(TSRMLS_C); + + /* Find cached config entry: If not found, create one */ + if (zend_hash_find(&CGIG(user_config_cache), path, path_len + 1, (void **) &entry) == FAILURE) { + new_entry = pemalloc(sizeof(user_config_cache_entry), 1); + new_entry->expires = 0; + new_entry->user_config = (HashTable *) pemalloc(sizeof(HashTable), 1); + zend_hash_init(new_entry->user_config, 0, NULL, (dtor_func_t) config_zval_dtor, 1); + zend_hash_update(&CGIG(user_config_cache), path, path_len + 1, new_entry, sizeof(user_config_cache_entry), (void **) &entry); + free(new_entry); + } + + /* Check whether cache entry has expired and rescan if it is */ + if (request_time > entry->expires) { + char * real_path; + int real_path_len; + char *s1, *s2; + int s_len; + + /* Clear the expired config */ + zend_hash_clean(entry->user_config); + + if (!IS_ABSOLUTE_PATH(path, path_len)) { + real_path = tsrm_realpath(path, NULL TSRMLS_CC); + if (real_path == NULL) { + return; + } + real_path_len = strlen(real_path); + path = real_path; + path_len = real_path_len; + } + + if (path_len > doc_root_len) { + s1 = (char *) doc_root; + s2 = path; + s_len = doc_root_len; + } else { + s1 = path; + s2 = (char *) doc_root; + s_len = path_len; + } + + /* we have to test if path is part of DOCUMENT_ROOT. + if it is inside the docroot, we scan the tree up to the docroot + to find more user.ini, if not we only scan the current path. + */ +#ifdef PHP_WIN32 + if (strnicmp(s1, s2, s_len) == 0) { +#else + if (strncmp(s1, s2, s_len) == 0) { +#endif + ptr = s2 + start; /* start is the point where doc_root ends! */ + while ((ptr = strchr(ptr, DEFAULT_SLASH)) != NULL) { + *ptr = 0; + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config TSRMLS_CC); + *ptr = '/'; + ptr++; + } + } else { + php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config TSRMLS_CC); + } + + entry->expires = request_time + PG(user_ini_cache_ttl); + } + + /* Activate ini entries with values from the user config hash */ + php_ini_activate_config(entry->user_config, PHP_INI_PERDIR, PHP_INI_STAGE_HTACCESS TSRMLS_CC); +} +/* }}} */ + +static int sapi_cgi_activate(TSRMLS_D) +{ + char *path, *doc_root, *server_name; + uint path_len, doc_root_len, server_name_len; + + /* PATH_TRANSLATED should be defined at this stage but better safe than sorry :) */ + if (!SG(request_info).path_translated) { + return FAILURE; + } + + if (php_ini_has_per_host_config()) { + /* Activate per-host-system-configuration defined in php.ini and stored into configuration_hash during startup */ + server_name = sapi_cgibin_getenv("SERVER_NAME", sizeof("SERVER_NAME") - 1 TSRMLS_CC); + /* SERVER_NAME should also be defined at this stage..but better check it anyway */ + if (server_name) { + server_name_len = strlen(server_name); + server_name = estrndup(server_name, server_name_len); + zend_str_tolower(server_name, server_name_len); + php_ini_activate_per_host_config(server_name, server_name_len + 1 TSRMLS_CC); + efree(server_name); + } + } + + if (php_ini_has_per_dir_config() || + (PG(user_ini_filename) && *PG(user_ini_filename)) + ) { + /* Prepare search path */ + path_len = strlen(SG(request_info).path_translated); + + /* Make sure we have trailing slash! */ + if (!IS_SLASH(SG(request_info).path_translated[path_len])) { + path = emalloc(path_len + 2); + memcpy(path, SG(request_info).path_translated, path_len + 1); + path_len = zend_dirname(path, path_len); + path[path_len++] = DEFAULT_SLASH; + } else { + path = estrndup(SG(request_info).path_translated, path_len); + path_len = zend_dirname(path, path_len); + } + path[path_len] = 0; + + /* Activate per-dir-system-configuration defined in php.ini and stored into configuration_hash during startup */ + php_ini_activate_per_dir_config(path, path_len TSRMLS_CC); /* Note: for global settings sake we check from root to path */ + + /* Load and activate user ini files in path starting from DOCUMENT_ROOT */ + if (PG(user_ini_filename) && *PG(user_ini_filename)) { + doc_root = sapi_cgibin_getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT") - 1 TSRMLS_CC); + /* DOCUMENT_ROOT should also be defined at this stage..but better check it anyway */ + if (doc_root) { + doc_root_len = strlen(doc_root); + if (doc_root_len > 0 && IS_SLASH(doc_root[doc_root_len - 1])) { + --doc_root_len; + } +#ifdef PHP_WIN32 + /* paths on windows should be case-insensitive */ + doc_root = estrndup(doc_root, doc_root_len); + zend_str_tolower(doc_root, doc_root_len); +#endif + php_cgi_ini_activate_user_config(path, path_len, doc_root, doc_root_len, doc_root_len - 1 TSRMLS_CC); + } + } + +#ifdef PHP_WIN32 + efree(doc_root); +#endif + efree(path); + } + + return SUCCESS; +} + +static int sapi_cgi_deactivate(TSRMLS_D) +{ + /* flush only when SAPI was started. The reasons are: + 1. SAPI Deactivate is called from two places: module init and request shutdown + 2. When the first call occurs and the request is not set up, flush fails on FastCGI. + */ + if (SG(sapi_started)) { + if ( +#ifndef PHP_WIN32 + !parent && +#endif + !fcgi_finish_request((fcgi_request*)SG(server_context), 0)) { + php_handle_aborted_connection(); + } + } + return SUCCESS; +} + +static int php_cgi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &cgi_module_entry, 1) == FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +/* {{{ sapi_module_struct cgi_sapi_module + */ +static sapi_module_struct cgi_sapi_module = { + "fpm-fcgi", /* name */ + "FPM/FastCGI", /* pretty name */ + + php_cgi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + sapi_cgi_activate, /* activate */ + sapi_cgi_deactivate, /* deactivate */ + + sapi_cgibin_ub_write, /* unbuffered write */ + sapi_cgibin_flush, /* flush */ + NULL, /* get uid */ + sapi_cgibin_getenv, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_cgi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_cgi_read_post, /* read POST data */ + sapi_cgi_read_cookies, /* read Cookies */ + + sapi_cgi_register_variables, /* register server variables */ + sapi_cgi_log_message, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ arginfo ext/standard/dl.c */ +ZEND_BEGIN_ARG_INFO(arginfo_dl, 0) + ZEND_ARG_INFO(0, extension_filename) +ZEND_END_ARG_INFO() +/* }}} */ + +static const zend_function_entry additional_functions[] = { + ZEND_FE(dl, arginfo_dl) + {NULL, NULL, NULL} +}; + +/* {{{ php_cgi_usage + */ +static void php_cgi_usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "php"; + } + + php_printf( "Usage: %s [-n] [-e] [-h] [-i] [-m] [-v] [-t] [-p <prefix>] [-g <pid>] [-c <file>] [-d foo[=bar]] [-y <file>] [-D] [-F]\n" + " -c <path>|<file> Look for php.ini file in this directory\n" + " -n No php.ini file will be used\n" + " -d foo[=bar] Define INI entry foo with value 'bar'\n" + " -e Generate extended information for debugger/profiler\n" + " -h This help\n" + " -i PHP information\n" + " -m Show compiled in modules\n" + " -v Version number\n" + " -p, --prefix <dir>\n" + " Specify alternative prefix path to FastCGI process manager (default: %s).\n" + " -g, --pid <file>\n" + " Specify the PID file location.\n" + " -y, --fpm-config <file>\n" + " Specify alternative path to FastCGI process manager config file.\n" + " -t, --test Test FPM configuration and exit\n" + " -D, --daemonize force to run in background, and ignore daemonize option from config file\n" + " -F, --nodaemonize\n" + " force to stay in foreground, and ignore daemonize option from config file\n" + " -R, --allow-to-run-as-root\n" + " Allow pool to run as root (disabled by default)\n", + prog, PHP_PREFIX); +} +/* }}} */ + +/* {{{ is_valid_path + * + * some server configurations allow '..' to slip through in the + * translated path. We'll just refuse to handle such a path. + */ +static int is_valid_path(const char *path) +{ + const char *p; + + if (!path) { + return 0; + } + p = strstr(path, ".."); + if (p) { + if ((p == path || IS_SLASH(*(p-1))) && + (*(p+2) == 0 || IS_SLASH(*(p+2))) + ) { + return 0; + } + while (1) { + p = strstr(p+1, ".."); + if (!p) { + break; + } + if (IS_SLASH(*(p-1)) && + (*(p+2) == 0 || IS_SLASH(*(p+2))) + ) { + return 0; + } + } + } + return 1; +} +/* }}} */ + +/* {{{ init_request_info + + initializes request_info structure + + specificly in this section we handle proper translations + for: + + PATH_INFO + derived from the portion of the URI path following + the script name but preceding any query data + may be empty + + PATH_TRANSLATED + derived by taking any path-info component of the + request URI and performing any virtual-to-physical + translation appropriate to map it onto the server's + document repository structure + + empty if PATH_INFO is empty + + The env var PATH_TRANSLATED **IS DIFFERENT** than the + request_info.path_translated variable, the latter should + match SCRIPT_FILENAME instead. + + SCRIPT_NAME + set to a URL path that could identify the CGI script + rather than the interpreter. PHP_SELF is set to this + + REQUEST_URI + uri section following the domain:port part of a URI + + SCRIPT_FILENAME + The virtual-to-physical translation of SCRIPT_NAME (as per + PATH_TRANSLATED) + + These settings are documented at + http://cgi-spec.golux.com/ + + + Based on the following URL request: + + http://localhost/info.php/test?a=b + + should produce, which btw is the same as if + we were running under mod_cgi on apache (ie. not + using ScriptAlias directives): + + PATH_INFO=/test + PATH_TRANSLATED=/docroot/test + SCRIPT_NAME=/info.php + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/docroot/info.php + QUERY_STRING=a=b + + but what we get is (cgi/mod_fastcgi under apache): + + PATH_INFO=/info.php/test + PATH_TRANSLATED=/docroot/info.php/test + SCRIPT_NAME=/php/php-cgi (from the Action setting I suppose) + REQUEST_URI=/info.php/test?a=b + SCRIPT_FILENAME=/path/to/php/bin/php-cgi (Action setting translated) + QUERY_STRING=a=b + + Comments in the code below refer to using the above URL in a request + + */ +static void init_request_info(TSRMLS_D) +{ + char *env_script_filename = sapi_cgibin_getenv("SCRIPT_FILENAME", sizeof("SCRIPT_FILENAME") - 1 TSRMLS_CC); + char *env_path_translated = sapi_cgibin_getenv("PATH_TRANSLATED", sizeof("PATH_TRANSLATED") - 1 TSRMLS_CC); + char *script_path_translated = env_script_filename; + char *ini; + int apache_was_here = 0; + + /* some broken servers do not have script_filename or argv0 + * an example, IIS configured in some ways. then they do more + * broken stuff and set path_translated to the cgi script location */ + if (!script_path_translated && env_path_translated) { + script_path_translated = env_path_translated; + } + + /* initialize the defaults */ + SG(request_info).path_translated = NULL; + SG(request_info).request_method = NULL; + SG(request_info).proto_num = 1000; + SG(request_info).query_string = NULL; + SG(request_info).request_uri = NULL; + SG(request_info).content_type = NULL; + SG(request_info).content_length = 0; + SG(sapi_headers).http_response_code = 200; + + /* script_path_translated being set is a good indication that + * we are running in a cgi environment, since it is always + * null otherwise. otherwise, the filename + * of the script will be retreived later via argc/argv */ + if (script_path_translated) { + const char *auth; + char *content_length = sapi_cgibin_getenv("CONTENT_LENGTH", sizeof("CONTENT_LENGTH") - 1 TSRMLS_CC); + char *content_type = sapi_cgibin_getenv("CONTENT_TYPE", sizeof("CONTENT_TYPE") - 1 TSRMLS_CC); + char *env_path_info = sapi_cgibin_getenv("PATH_INFO", sizeof("PATH_INFO") - 1 TSRMLS_CC); + char *env_script_name = sapi_cgibin_getenv("SCRIPT_NAME", sizeof("SCRIPT_NAME") - 1 TSRMLS_CC); + + /* Hack for buggy IIS that sets incorrect PATH_INFO */ + char *env_server_software = sapi_cgibin_getenv("SERVER_SOFTWARE", sizeof("SERVER_SOFTWARE") - 1 TSRMLS_CC); + if (env_server_software && + env_script_name && + env_path_info && + strncmp(env_server_software, "Microsoft-IIS", sizeof("Microsoft-IIS") - 1) == 0 && + strncmp(env_path_info, env_script_name, strlen(env_script_name)) == 0 + ) { + env_path_info = _sapi_cgibin_putenv("ORIG_PATH_INFO", env_path_info TSRMLS_CC); + env_path_info += strlen(env_script_name); + if (*env_path_info == 0) { + env_path_info = NULL; + } + env_path_info = _sapi_cgibin_putenv("PATH_INFO", env_path_info TSRMLS_CC); + } + +#define APACHE_PROXY_FCGI_PREFIX "proxy:fcgi://" + /* Fix proxy URLs in SCRIPT_FILENAME generated by Apache mod_proxy_fcgi: + * proxy:fcgi://localhost:9000/some-dir/info.php/test + * should be changed to: + * /some-dir/info.php/test + * See: http://bugs.php.net/bug.php?id=54152 + * https://issues.apache.org/bugzilla/show_bug.cgi?id=50851 + */ + if (env_script_filename && + strncasecmp(env_script_filename, APACHE_PROXY_FCGI_PREFIX, sizeof(APACHE_PROXY_FCGI_PREFIX) - 1) == 0) { + /* advance to first character of hostname */ + char *p = env_script_filename + (sizeof(APACHE_PROXY_FCGI_PREFIX) - 1); + while (*p != '\0' && *p != '/') { + p++; /* move past hostname and port */ + } + if (*p != '\0') { + /* Copy path portion in place to avoid memory leak. Note + * that this also affects what script_path_translated points + * to. */ + memmove(env_script_filename, p, strlen(p) + 1); + apache_was_here = 1; + } + } + + if (CGIG(fix_pathinfo)) { + struct stat st; + char *real_path = NULL; + char *env_redirect_url = sapi_cgibin_getenv("REDIRECT_URL", sizeof("REDIRECT_URL") - 1 TSRMLS_CC); + char *env_document_root = sapi_cgibin_getenv("DOCUMENT_ROOT", sizeof("DOCUMENT_ROOT") - 1 TSRMLS_CC); + char *orig_path_translated = env_path_translated; + char *orig_path_info = env_path_info; + char *orig_script_name = env_script_name; + char *orig_script_filename = env_script_filename; + int script_path_translated_len; + + if (!env_document_root && PG(doc_root)) { + env_document_root = _sapi_cgibin_putenv("DOCUMENT_ROOT", PG(doc_root) TSRMLS_CC); + /* fix docroot */ + TRANSLATE_SLASHES(env_document_root); + } + + if (env_path_translated != NULL && env_redirect_url != NULL && + env_path_translated != script_path_translated && + strcmp(env_path_translated, script_path_translated) != 0) { + /* + * pretty much apache specific. If we have a redirect_url + * then our script_filename and script_name point to the + * php executable + */ + script_path_translated = env_path_translated; + /* we correct SCRIPT_NAME now in case we don't have PATH_INFO */ + env_script_name = env_redirect_url; + } + +#ifdef __riscos__ + /* Convert path to unix format*/ + __riscosify_control |= __RISCOSIFY_DONT_CHECK_DIR; + script_path_translated = __unixify(script_path_translated, 0, NULL, 1, 0); +#endif + + /* + * if the file doesn't exist, try to extract PATH_INFO out + * of it by stat'ing back through the '/' + * this fixes url's like /info.php/test + */ + if (script_path_translated && + (script_path_translated_len = strlen(script_path_translated)) > 0 && + (script_path_translated[script_path_translated_len-1] == '/' || +#ifdef PHP_WIN32 + script_path_translated[script_path_translated_len-1] == '\\' || +#endif + (real_path = tsrm_realpath(script_path_translated, NULL TSRMLS_CC)) == NULL) + ) { + char *pt = estrndup(script_path_translated, script_path_translated_len); + int len = script_path_translated_len; + char *ptr; + + while ((ptr = strrchr(pt, '/')) || (ptr = strrchr(pt, '\\'))) { + *ptr = 0; + if (stat(pt, &st) == 0 && S_ISREG(st.st_mode)) { + /* + * okay, we found the base script! + * work out how many chars we had to strip off; + * then we can modify PATH_INFO + * accordingly + * + * we now have the makings of + * PATH_INFO=/test + * SCRIPT_FILENAME=/docroot/info.php + * + * we now need to figure out what docroot is. + * if DOCUMENT_ROOT is set, this is easy, otherwise, + * we have to play the game of hide and seek to figure + * out what SCRIPT_NAME should be + */ + int ptlen = strlen(pt); + int slen = len - ptlen; + int pilen = env_path_info ? strlen(env_path_info) : 0; + int tflag = 0; + char *path_info; + if (apache_was_here) { + /* recall that PATH_INFO won't exist */ + path_info = script_path_translated + ptlen; + tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0)); + } else { + path_info = env_path_info ? env_path_info + pilen - slen : NULL; + tflag = (orig_path_info != path_info); + } + + if (tflag) { + if (orig_path_info) { + char old; + + _sapi_cgibin_putenv("ORIG_PATH_INFO", orig_path_info TSRMLS_CC); + old = path_info[0]; + path_info[0] = 0; + if (!orig_script_name || + strcmp(orig_script_name, env_path_info) != 0) { + if (orig_script_name) { + _sapi_cgibin_putenv("ORIG_SCRIPT_NAME", orig_script_name TSRMLS_CC); + } + SG(request_info).request_uri = _sapi_cgibin_putenv("SCRIPT_NAME", env_path_info TSRMLS_CC); + } else { + SG(request_info).request_uri = orig_script_name; + } + path_info[0] = old; + } + env_path_info = _sapi_cgibin_putenv("PATH_INFO", path_info TSRMLS_CC); + } + if (!orig_script_filename || + strcmp(orig_script_filename, pt) != 0) { + if (orig_script_filename) { + _sapi_cgibin_putenv("ORIG_SCRIPT_FILENAME", orig_script_filename TSRMLS_CC); + } + script_path_translated = _sapi_cgibin_putenv("SCRIPT_FILENAME", pt TSRMLS_CC); + } + TRANSLATE_SLASHES(pt); + + /* figure out docroot + * SCRIPT_FILENAME minus SCRIPT_NAME + */ + if (env_document_root) { + int l = strlen(env_document_root); + int path_translated_len = 0; + char *path_translated = NULL; + + if (l && env_document_root[l - 1] == '/') { + --l; + } + + /* we have docroot, so we should have: + * DOCUMENT_ROOT=/docroot + * SCRIPT_FILENAME=/docroot/info.php + */ + + /* PATH_TRANSLATED = DOCUMENT_ROOT + PATH_INFO */ + path_translated_len = l + (env_path_info ? strlen(env_path_info) : 0); + path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, env_document_root, l); + if (env_path_info) { + memcpy(path_translated + l, env_path_info, (path_translated_len - l)); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + _sapi_cgibin_putenv("ORIG_PATH_TRANSLATED", orig_path_translated TSRMLS_CC); + } + env_path_translated = _sapi_cgibin_putenv("PATH_TRANSLATED", path_translated TSRMLS_CC); + efree(path_translated); + } else if ( env_script_name && + strstr(pt, env_script_name) + ) { + /* PATH_TRANSLATED = PATH_TRANSLATED - SCRIPT_NAME + PATH_INFO */ + int ptlen = strlen(pt) - strlen(env_script_name); + int path_translated_len = ptlen + (env_path_info ? strlen(env_path_info) : 0); + char *path_translated = NULL; + + path_translated = (char *) emalloc(path_translated_len + 1); + memcpy(path_translated, pt, ptlen); + if (env_path_info) { + memcpy(path_translated + ptlen, env_path_info, path_translated_len - ptlen); + } + path_translated[path_translated_len] = '\0'; + if (orig_path_translated) { + _sapi_cgibin_putenv("ORIG_PATH_TRANSLATED", orig_path_translated TSRMLS_CC); + } + env_path_translated = _sapi_cgibin_putenv("PATH_TRANSLATED", path_translated TSRMLS_CC); + efree(path_translated); + } + break; + } + } + if (!ptr) { + /* + * if we stripped out all the '/' and still didn't find + * a valid path... we will fail, badly. of course we would + * have failed anyway... we output 'no input file' now. + */ + if (orig_script_filename) { + _sapi_cgibin_putenv("ORIG_SCRIPT_FILENAME", orig_script_filename TSRMLS_CC); + } + script_path_translated = _sapi_cgibin_putenv("SCRIPT_FILENAME", NULL TSRMLS_CC); + SG(sapi_headers).http_response_code = 404; + } + if (!SG(request_info).request_uri) { + if (!orig_script_name || + strcmp(orig_script_name, env_script_name) != 0) { + if (orig_script_name) { + _sapi_cgibin_putenv("ORIG_SCRIPT_NAME", orig_script_name TSRMLS_CC); + } + SG(request_info).request_uri = _sapi_cgibin_putenv("SCRIPT_NAME", env_script_name TSRMLS_CC); + } else { + SG(request_info).request_uri = orig_script_name; + } + } + if (pt) { + efree(pt); + } + } else { + /* make sure path_info/translated are empty */ + if (!orig_script_filename || + (script_path_translated != orig_script_filename && + strcmp(script_path_translated, orig_script_filename) != 0)) { + if (orig_script_filename) { + _sapi_cgibin_putenv("ORIG_SCRIPT_FILENAME", orig_script_filename TSRMLS_CC); + } + script_path_translated = _sapi_cgibin_putenv("SCRIPT_FILENAME", script_path_translated TSRMLS_CC); + } + if (env_redirect_url) { + if (orig_path_info) { + _sapi_cgibin_putenv("ORIG_PATH_INFO", orig_path_info TSRMLS_CC); + _sapi_cgibin_putenv("PATH_INFO", NULL TSRMLS_CC); + } + if (orig_path_translated) { + _sapi_cgibin_putenv("ORIG_PATH_TRANSLATED", orig_path_translated TSRMLS_CC); + _sapi_cgibin_putenv("PATH_TRANSLATED", NULL TSRMLS_CC); + } + } + if (env_script_name != orig_script_name) { + if (orig_script_name) { + _sapi_cgibin_putenv("ORIG_SCRIPT_NAME", orig_script_name TSRMLS_CC); + } + SG(request_info).request_uri = _sapi_cgibin_putenv("SCRIPT_NAME", env_script_name TSRMLS_CC); + } else { + SG(request_info).request_uri = env_script_name; + } + free(real_path); + } + } else { + /* pre 4.3 behaviour, shouldn't be used but provides BC */ + if (env_path_info) { + SG(request_info).request_uri = env_path_info; + } else { + SG(request_info).request_uri = env_script_name; + } + if (!CGIG(discard_path) && env_path_translated) { + script_path_translated = env_path_translated; + } + } + + if (is_valid_path(script_path_translated)) { + SG(request_info).path_translated = estrdup(script_path_translated); + } + + SG(request_info).request_method = sapi_cgibin_getenv("REQUEST_METHOD", sizeof("REQUEST_METHOD") - 1 TSRMLS_CC); + /* FIXME - Work out proto_num here */ + SG(request_info).query_string = sapi_cgibin_getenv("QUERY_STRING", sizeof("QUERY_STRING") - 1 TSRMLS_CC); + SG(request_info).content_type = (content_type ? content_type : "" ); + SG(request_info).content_length = (content_length ? atol(content_length) : 0); + + /* The CGI RFC allows servers to pass on unvalidated Authorization data */ + auth = sapi_cgibin_getenv("HTTP_AUTHORIZATION", sizeof("HTTP_AUTHORIZATION") - 1 TSRMLS_CC); + php_handle_auth_data(auth TSRMLS_CC); + } + + /* INI stuff */ + ini = sapi_cgibin_getenv("PHP_VALUE", sizeof("PHP_VALUE") - 1 TSRMLS_CC); + if (ini) { + int mode = ZEND_INI_USER; + char *tmp; + spprintf(&tmp, 0, "%s\n", ini); + zend_parse_ini_string(tmp, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fastcgi_ini_parser, &mode TSRMLS_CC); + efree(tmp); + } + + ini = sapi_cgibin_getenv("PHP_ADMIN_VALUE", sizeof("PHP_ADMIN_VALUE") - 1 TSRMLS_CC); + if (ini) { + int mode = ZEND_INI_SYSTEM; + char *tmp; + spprintf(&tmp, 0, "%s\n", ini); + zend_parse_ini_string(tmp, 1, ZEND_INI_SCANNER_NORMAL, (zend_ini_parser_cb_t)fastcgi_ini_parser, &mode TSRMLS_CC); + efree(tmp); + } +} +/* }}} */ + +static void fastcgi_ini_parser(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *arg TSRMLS_DC) /* {{{ */ +{ + int *mode = (int *)arg; + char *key; + char *value = NULL; + struct key_value_s kv; + + if (!mode || !arg1) return; + + if (callback_type != ZEND_INI_PARSER_ENTRY) { + zlog(ZLOG_ERROR, "Passing INI directive through FastCGI: only classic entries are allowed"); + return; + } + + key = Z_STRVAL_P(arg1); + + if (!key || strlen(key) < 1) { + zlog(ZLOG_ERROR, "Passing INI directive through FastCGI: empty key"); + return; + } + + if (arg2) { + value = Z_STRVAL_P(arg2); + } + + if (!value) { + zlog(ZLOG_ERROR, "Passing INI directive through FastCGI: empty value for key '%s'", key); + return; + } + + kv.key = key; + kv.value = value; + kv.next = NULL; + if (fpm_php_apply_defines_ex(&kv, *mode) == -1) { + zlog(ZLOG_ERROR, "Passing INI directive through FastCGI: unable to set '%s'", key); + } +} +/* }}} */ + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("cgi.rfc2616_headers", "0", PHP_INI_ALL, OnUpdateBool, rfc2616_headers, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.nph", "0", PHP_INI_ALL, OnUpdateBool, nph, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.force_redirect", "1", PHP_INI_SYSTEM, OnUpdateBool, force_redirect, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.redirect_status_env", NULL, PHP_INI_SYSTEM, OnUpdateString, redirect_status_env, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.fix_pathinfo", "1", PHP_INI_SYSTEM, OnUpdateBool, fix_pathinfo, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("cgi.discard_path", "0", PHP_INI_SYSTEM, OnUpdateBool, discard_path, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fastcgi.logging", "1", PHP_INI_SYSTEM, OnUpdateBool, fcgi_logging, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fastcgi.error_header", NULL, PHP_INI_SYSTEM, OnUpdateString, error_header, php_cgi_globals_struct, php_cgi_globals) + STD_PHP_INI_ENTRY("fpm.config", NULL, PHP_INI_SYSTEM, OnUpdateString, fpm_config, php_cgi_globals_struct, php_cgi_globals) +PHP_INI_END() + +/* {{{ php_cgi_globals_ctor + */ +static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals TSRMLS_DC) +{ + php_cgi_globals->rfc2616_headers = 0; + php_cgi_globals->nph = 0; + php_cgi_globals->force_redirect = 1; + php_cgi_globals->redirect_status_env = NULL; + php_cgi_globals->fix_pathinfo = 1; + php_cgi_globals->discard_path = 0; + php_cgi_globals->fcgi_logging = 1; + zend_hash_init(&php_cgi_globals->user_config_cache, 0, NULL, (dtor_func_t) user_config_cache_entry_dtor, 1); + php_cgi_globals->error_header = NULL; + php_cgi_globals->fpm_config = NULL; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +static PHP_MINIT_FUNCTION(cgi) +{ +#ifdef ZTS + ts_allocate_id(&php_cgi_globals_id, sizeof(php_cgi_globals_struct), (ts_allocate_ctor) php_cgi_globals_ctor, NULL); +#else + php_cgi_globals_ctor(&php_cgi_globals TSRMLS_CC); +#endif + REGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(cgi) +{ + zend_hash_destroy(&CGIG(user_config_cache)); + + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +static PHP_MINFO_FUNCTION(cgi) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "php-fpm", "active"); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +PHP_FUNCTION(fastcgi_finish_request) /* {{{ */ +{ + fcgi_request *request = (fcgi_request*) SG(server_context); + + if (request->fd >= 0) { + + php_output_end_all(TSRMLS_C); + php_header(TSRMLS_C); + + fcgi_flush(request, 1); + fcgi_close(request, 0, 0); + RETURN_TRUE; + } + + RETURN_FALSE; + +} +/* }}} */ + +static const zend_function_entry cgi_fcgi_sapi_functions[] = { + PHP_FE(fastcgi_finish_request, NULL) + {NULL, NULL, NULL} +}; + +static zend_module_entry cgi_module_entry = { + STANDARD_MODULE_HEADER, + "cgi-fcgi", + cgi_fcgi_sapi_functions, + PHP_MINIT(cgi), + PHP_MSHUTDOWN(cgi), + NULL, + NULL, + PHP_MINFO(cgi), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +/* {{{ main + */ +int main(int argc, char *argv[]) +{ + int exit_status = FPM_EXIT_OK; + int cgi = 0, c, use_extended_info = 0; + zend_file_handle file_handle; + + /* temporary locals */ + int orig_optind = php_optind; + char *orig_optarg = php_optarg; + int ini_entries_len = 0; + /* end of temporary locals */ + +#ifdef ZTS + void ***tsrm_ls; +#endif + + int max_requests = 500; + int requests = 0; + int fcgi_fd = 0; + fcgi_request request; + char *fpm_config = NULL; + char *fpm_prefix = NULL; + char *fpm_pid = NULL; + int test_conf = 0; + int force_daemon = -1; + int php_information = 0; + int php_allow_to_run_as_root = 0; + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif +#endif + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + tsrm_ls = ts_resource(0); +#endif + + sapi_startup(&cgi_sapi_module); + cgi_sapi_module.php_ini_path_override = NULL; + cgi_sapi_module.php_ini_ignore_cwd = 1; + + fcgi_init(); + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /* sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) { + switch (c) { + case 'c': + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + cgi_sapi_module.php_ini_path_override = strdup(php_optarg); + break; + + case 'n': + cgi_sapi_module.php_ini_ignore = 1; + break; + + case 'd': { + /* define ini entries on command line */ + int len = strlen(php_optarg); + char *val; + + if ((val = strchr(php_optarg, '='))) { + val++; + if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\"\"\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, (val - php_optarg)); + ini_entries_len += (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"", 1); + ini_entries_len++; + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, val, len - (val - php_optarg)); + ini_entries_len += len - (val - php_optarg); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0")); + ini_entries_len += sizeof("\n\0\"") - 2; + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0")); + ini_entries_len += len + sizeof("\n\0") - 2; + } + } else { + cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("=1\n\0")); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len); + memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0")); + ini_entries_len += len + sizeof("=1\n\0") - 2; + } + break; + } + + case 'y': + fpm_config = php_optarg; + break; + + case 'p': + fpm_prefix = php_optarg; + break; + + case 'g': + fpm_pid = php_optarg; + break; + + case 'e': /* enable extended info output */ + use_extended_info = 1; + break; + + case 't': + test_conf++; + break; + + case 'm': /* list compiled in modules */ + cgi_sapi_module.startup(&cgi_sapi_module); + php_output_activate(TSRMLS_C); + SG(headers_sent) = 1; + php_printf("[PHP Modules]\n"); + print_modules(TSRMLS_C); + php_printf("\n[Zend Modules]\n"); + print_extensions(TSRMLS_C); + php_printf("\n"); + php_output_end_all(TSRMLS_C); + php_output_deactivate(TSRMLS_C); + fcgi_shutdown(); + exit_status = FPM_EXIT_OK; + goto out; + + case 'i': /* php info & quit */ + php_information = 1; + break; + + case 'R': /* allow to run as root */ + php_allow_to_run_as_root = 1; + break; + + case 'D': /* daemonize */ + force_daemon = 1; + break; + + case 'F': /* nodaemonize */ + force_daemon = 0; + break; + + default: + case 'h': + case '?': + cgi_sapi_module.startup(&cgi_sapi_module); + php_output_activate(TSRMLS_C); + SG(headers_sent) = 1; + php_cgi_usage(argv[0]); + php_output_end_all(TSRMLS_C); + php_output_deactivate(TSRMLS_C); + fcgi_shutdown(); + exit_status = (c == 'h') ? FPM_EXIT_OK : FPM_EXIT_USAGE; + goto out; + + case 'v': /* show php version & quit */ + cgi_sapi_module.startup(&cgi_sapi_module); + if (php_request_startup(TSRMLS_C) == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FPM_EXIT_SOFTWARE; + } + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + +#if ZEND_DEBUG + php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#else + php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#endif + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = FPM_EXIT_OK; + goto out; + } + } + + if (php_information) { + cgi_sapi_module.phpinfo_as_text = 1; + cgi_sapi_module.startup(&cgi_sapi_module); + if (php_request_startup(TSRMLS_C) == FAILURE) { + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FPM_EXIT_SOFTWARE; + } + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_print_info(0xFFFFFFFF TSRMLS_CC); + php_request_shutdown((void *) 0); + fcgi_shutdown(); + exit_status = FPM_EXIT_OK; + goto out; + } + + /* No other args are permitted here as there is no interactive mode */ + if (argc != php_optind) { + cgi_sapi_module.startup(&cgi_sapi_module); + php_output_activate(TSRMLS_C); + SG(headers_sent) = 1; + php_cgi_usage(argv[0]); + php_output_end_all(TSRMLS_C); + php_output_deactivate(TSRMLS_C); + fcgi_shutdown(); + exit_status = FPM_EXIT_USAGE; + goto out; + } + + php_optind = orig_optind; + php_optarg = orig_optarg; + +#ifdef ZTS + SG(request_info).path_translated = NULL; +#endif + + cgi_sapi_module.additional_functions = additional_functions; + cgi_sapi_module.executable_location = argv[0]; + + /* startup after we get the above ini override se we get things right */ + if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) { +#ifdef ZTS + tsrm_shutdown(); +#endif + return FPM_EXIT_SOFTWARE; + } + + if (use_extended_info) { + CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; + } + + /* check force_cgi after startup, so we have proper output */ + if (cgi && CGIG(force_redirect)) { + /* Apache will generate REDIRECT_STATUS, + * Netscape and redirect.so will generate HTTP_REDIRECT_STATUS. + * redirect.so and installation instructions available from + * http://www.koehntopp.de/php. + * -- kk@netuse.de + */ + if (!getenv("REDIRECT_STATUS") && + !getenv ("HTTP_REDIRECT_STATUS") && + /* this is to allow a different env var to be configured + * in case some server does something different than above */ + (!CGIG(redirect_status_env) || !getenv(CGIG(redirect_status_env))) + ) { + zend_try { + SG(sapi_headers).http_response_code = 400; + PUTS("<b>Security Alert!</b> The PHP CGI cannot be accessed directly.\n\n\ +<p>This PHP CGI binary was compiled with force-cgi-redirect enabled. This\n\ +means that a page will only be served up if the REDIRECT_STATUS CGI variable is\n\ +set, e.g. via an Apache Action directive.</p>\n\ +<p>For more information as to <i>why</i> this behaviour exists, see the <a href=\"http://php.net/security.cgi-bin\">\ +manual page for CGI security</a>.</p>\n\ +<p>For more information about changing this behaviour or re-enabling this webserver,\n\ +consult the installation file that came with this distribution, or visit \n\ +<a href=\"http://php.net/install.windows\">the manual page</a>.</p>\n"); + } zend_catch { + } zend_end_try(); +#if defined(ZTS) && !defined(PHP_DEBUG) + /* XXX we're crashing here in msvc6 debug builds at + * php_message_handler_for_zend:839 because + * SG(request_info).path_translated is an invalid pointer. + * It still happens even though I set it to null, so something + * weird is going on. + */ + tsrm_shutdown(); +#endif + return FPM_EXIT_SOFTWARE; + } + } + + if (0 > fpm_init(argc, argv, fpm_config ? fpm_config : CGIG(fpm_config), fpm_prefix, fpm_pid, test_conf, php_allow_to_run_as_root, force_daemon)) { + + if (fpm_globals.send_config_pipe[1]) { + int writeval = 0; + zlog(ZLOG_DEBUG, "Sending \"0\" (error) to parent via fd=%d", fpm_globals.send_config_pipe[1]); + write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval)); + close(fpm_globals.send_config_pipe[1]); + } + return FPM_EXIT_CONFIG; + } + + if (fpm_globals.send_config_pipe[1]) { + int writeval = 1; + zlog(ZLOG_DEBUG, "Sending \"1\" (OK) to parent via fd=%d", fpm_globals.send_config_pipe[1]); + write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval)); + close(fpm_globals.send_config_pipe[1]); + } + fpm_is_running = 1; + + fcgi_fd = fpm_run(&max_requests); + parent = 0; + + /* onced forked tell zlog to also send messages through sapi_cgi_log_fastcgi() */ + zlog_set_external_logger(sapi_cgi_log_fastcgi); + + /* make php call us to get _ENV vars */ + php_php_import_environment_variables = php_import_environment_variables; + php_import_environment_variables = cgi_php_import_environment_variables; + + /* library is already initialized, now init our request */ + fcgi_init_request(&request, fcgi_fd); + + zend_first_try { + while (fcgi_accept_request(&request) >= 0) { + request_body_fd = -1; + SG(server_context) = (void *) &request; + init_request_info(TSRMLS_C); + CG(interactive) = 0; + char *primary_script = NULL; + + fpm_request_info(); + + /* request startup only after we've done all we can to + * get path_translated */ + if (php_request_startup(TSRMLS_C) == FAILURE) { + fcgi_finish_request(&request, 1); + SG(server_context) = NULL; + php_module_shutdown(TSRMLS_C); + return FPM_EXIT_SOFTWARE; + } + + /* check if request_method has been sent. + * if not, it's certainly not an HTTP over fcgi request */ + if (!SG(request_info).request_method) { + goto fastcgi_request_done; + } + + if (fpm_status_handle_request(TSRMLS_C)) { + goto fastcgi_request_done; + } + + /* If path_translated is NULL, terminate here with a 404 */ + if (!SG(request_info).path_translated) { + zend_try { + zlog(ZLOG_DEBUG, "Primary script unknown"); + SG(sapi_headers).http_response_code = 404; + PUTS("File not found.\n"); + } zend_catch { + } zend_end_try(); + goto fastcgi_request_done; + } + + if (fpm_php_limit_extensions(SG(request_info).path_translated)) { + SG(sapi_headers).http_response_code = 403; + PUTS("Access denied.\n"); + goto fastcgi_request_done; + } + + /* + * have to duplicate SG(request_info).path_translated to be able to log errrors + * php_fopen_primary_script seems to delete SG(request_info).path_translated on failure + */ + primary_script = estrdup(SG(request_info).path_translated); + + /* path_translated exists, we can continue ! */ + if (php_fopen_primary_script(&file_handle TSRMLS_CC) == FAILURE) { + zend_try { + zlog(ZLOG_ERROR, "Unable to open primary script: %s (%s)", primary_script, strerror(errno)); + if (errno == EACCES) { + SG(sapi_headers).http_response_code = 403; + PUTS("Access denied.\n"); + } else { + SG(sapi_headers).http_response_code = 404; + PUTS("No input file specified.\n"); + } + } zend_catch { + } zend_end_try(); + /* we want to serve more requests if this is fastcgi + * so cleanup and continue, request shutdown is + * handled later */ + + goto fastcgi_request_done; + } + + fpm_request_executing(); + + php_execute_script(&file_handle TSRMLS_CC); + +fastcgi_request_done: + if (primary_script) { + efree(primary_script); + } + + if (request_body_fd != -1) { + close(request_body_fd); + } + request_body_fd = -2; + + if (EG(exit_status) == 255) { + if (CGIG(error_header) && *CGIG(error_header)) { + sapi_header_line ctr = {0}; + + ctr.line = CGIG(error_header); + ctr.line_len = strlen(CGIG(error_header)); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC); + } + } + + fpm_request_end(TSRMLS_C); + fpm_log_write(NULL TSRMLS_CC); + + STR_FREE(SG(request_info).path_translated); + SG(request_info).path_translated = NULL; + + php_request_shutdown((void *) 0); + + requests++; + if (max_requests && (requests == max_requests)) { + fcgi_finish_request(&request, 1); + break; + } + /* end of fastcgi loop */ + } + fcgi_shutdown(); + + if (cgi_sapi_module.php_ini_path_override) { + free(cgi_sapi_module.php_ini_path_override); + } + if (cgi_sapi_module.ini_entries) { + free(cgi_sapi_module.ini_entries); + } + } zend_catch { + exit_status = FPM_EXIT_SOFTWARE; + } zend_end_try(); + +out: + + SG(server_context) = NULL; + if (parent) { + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); + } + +#ifdef ZTS + tsrm_shutdown(); +#endif + +#if defined(PHP_WIN32) && ZEND_DEBUG && 0 + _CrtDumpMemoryLeaks(); +#endif + + return exit_status; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/fpm/fpm/fpm_php.c b/sapi/fpm/fpm/fpm_php.c new file mode 100644 index 0000000..cd4d3ae --- /dev/null +++ b/sapi/fpm/fpm/fpm_php.c @@ -0,0 +1,297 @@ + + /* $Id: fpm_php.c,v 1.22.2.4 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "php.h" +#include "php_main.h" +#include "php_ini.h" +#include "ext/standard/dl.h" + +#include "fastcgi.h" + +#include "fpm.h" +#include "fpm_php.h" +#include "fpm_cleanup.h" +#include "fpm_worker_pool.h" +#include "zlog.h" + +static char **limit_extensions = NULL; + +static int fpm_php_zend_ini_alter_master(char *name, int name_length, char *new_value, int new_value_length, int mode, int stage TSRMLS_DC) /* {{{ */ +{ + zend_ini_entry *ini_entry; + char *duplicate; + + if (zend_hash_find(EG(ini_directives), name, name_length, (void **) &ini_entry) == FAILURE) { + return FAILURE; + } + + duplicate = strdup(new_value); + + if (!ini_entry->on_modify + || ini_entry->on_modify(ini_entry, duplicate, new_value_length, + ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage TSRMLS_CC) == SUCCESS) { + ini_entry->value = duplicate; + ini_entry->value_length = new_value_length; + ini_entry->modifiable = mode; + } else { + free(duplicate); + } + + return SUCCESS; +} +/* }}} */ + +static void fpm_php_disable(char *value, int (*zend_disable)(char *, uint TSRMLS_DC) TSRMLS_DC) /* {{{ */ +{ + char *s = 0, *e = value; + + while (*e) { + switch (*e) { + case ' ': + case ',': + if (s) { + *e = '\0'; + zend_disable(s, e - s TSRMLS_CC); + s = 0; + } + break; + default: + if (!s) { + s = e; + } + break; + } + e++; + } + + if (s) { + zend_disable(s, e - s TSRMLS_CC); + } +} +/* }}} */ + +int fpm_php_apply_defines_ex(struct key_value_s *kv, int mode) /* {{{ */ +{ + TSRMLS_FETCH(); + + char *name = kv->key; + char *value = kv->value; + int name_len = strlen(name); + int value_len = strlen(value); + + if (!strcmp(name, "extension") && *value) { + zval zv; + php_dl(value, MODULE_PERSISTENT, &zv, 1 TSRMLS_CC); + return Z_BVAL(zv) ? 1 : -1; + } + + if (fpm_php_zend_ini_alter_master(name, name_len+1, value, value_len, mode, PHP_INI_STAGE_ACTIVATE TSRMLS_CC) == FAILURE) { + return -1; + } + + if (!strcmp(name, "disable_functions") && *value) { + char *v = strdup(value); + PG(disable_functions) = v; + fpm_php_disable(v, zend_disable_function TSRMLS_CC); + return 1; + } + + if (!strcmp(name, "disable_classes") && *value) { + char *v = strdup(value); + PG(disable_classes) = v; + fpm_php_disable(v, zend_disable_class TSRMLS_CC); + return 1; + } + + return 1; +} +/* }}} */ + +static int fpm_php_apply_defines(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct key_value_s *kv; + + for (kv = wp->config->php_values; kv; kv = kv->next) { + if (fpm_php_apply_defines_ex(kv, ZEND_INI_USER) == -1) { + zlog(ZLOG_ERROR, "Unable to set php_value '%s'", kv->key); + } + } + + for (kv = wp->config->php_admin_values; kv; kv = kv->next) { + if (fpm_php_apply_defines_ex(kv, ZEND_INI_SYSTEM) == -1) { + zlog(ZLOG_ERROR, "Unable to set php_admin_value '%s'", kv->key); + } + } + + return 0; +} + +static int fpm_php_set_allowed_clients(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + if (wp->listen_address_domain == FPM_AF_INET) { + fcgi_set_allowed_clients(wp->config->listen_allowed_clients); + } + return 0; +} +/* }}} */ + +#if 0 /* Comment out this non used function. It could be used later. */ +static int fpm_php_set_fcgi_mgmt_vars(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + char max_workers[10 + 1]; /* 4294967295 */ + int len; + + len = sprintf(max_workers, "%u", (unsigned int) wp->config->pm_max_children); + + fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, max_workers, len); + fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, max_workers, len); + return 0; +} +/* }}} */ +#endif + +char *fpm_php_script_filename(TSRMLS_D) /* {{{ */ +{ + return SG(request_info).path_translated; +} +/* }}} */ + +char *fpm_php_request_uri(TSRMLS_D) /* {{{ */ +{ + return (char *) SG(request_info).request_uri; +} +/* }}} */ + +char *fpm_php_request_method(TSRMLS_D) /* {{{ */ +{ + return (char *) SG(request_info).request_method; +} +/* }}} */ + +char *fpm_php_query_string(TSRMLS_D) /* {{{ */ +{ + return SG(request_info).query_string; +} +/* }}} */ + +char *fpm_php_auth_user(TSRMLS_D) /* {{{ */ +{ + return SG(request_info).auth_user; +} +/* }}} */ + +size_t fpm_php_content_length(TSRMLS_D) /* {{{ */ +{ + return SG(request_info).content_length; +} +/* }}} */ + +static void fpm_php_cleanup(int which, void *arg) /* {{{ */ +{ + TSRMLS_FETCH(); + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); +} +/* }}} */ + +void fpm_php_soft_quit() /* {{{ */ +{ + fcgi_set_in_shutdown(1); +} +/* }}} */ + +int fpm_php_init_main() /* {{{ */ +{ + if (0 > fpm_cleanup_add(FPM_CLEANUP_PARENT, fpm_php_cleanup, 0)) { + return -1; + } + return 0; +} +/* }}} */ + +int fpm_php_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + if (0 > fpm_php_apply_defines(wp) || + 0 > fpm_php_set_allowed_clients(wp)) { + return -1; + } + + if (wp->limit_extensions) { + limit_extensions = wp->limit_extensions; + } + return 0; +} +/* }}} */ + +int fpm_php_limit_extensions(char *path) /* {{{ */ +{ + char **p; + size_t path_len; + + if (!path || !limit_extensions) { + return 0; /* allowed by default */ + } + + p = limit_extensions; + path_len = strlen(path); + while (p && *p) { + size_t ext_len = strlen(*p); + if (path_len > ext_len) { + char *path_ext = path + path_len - ext_len; + if (strcmp(*p, path_ext) == 0) { + return 0; /* allow as the extension has been found */ + } + } + p++; + } + + + zlog(ZLOG_NOTICE, "Access to the script '%s' has been denied (see security.limit_extensions)", path); + return 1; /* extension not found: not allowed */ +} +/* }}} */ + +char* fpm_php_get_string_from_table(char *table, char *key TSRMLS_DC) /* {{{ */ +{ + zval **data, **tmp; + char *string_key; + uint string_len; + ulong num_key; + if (!table || !key) { + return NULL; + } + + /* inspired from ext/standard/info.c */ + + zend_is_auto_global(table, strlen(table) TSRMLS_CC); + + /* find the table and ensure it's an array */ + if (zend_hash_find(&EG(symbol_table), table, strlen(table) + 1, (void **) &data) == SUCCESS && Z_TYPE_PP(data) == IS_ARRAY) { + + /* reset the internal pointer */ + zend_hash_internal_pointer_reset(Z_ARRVAL_PP(data)); + + /* parse the array to look for our key */ + while (zend_hash_get_current_data(Z_ARRVAL_PP(data), (void **) &tmp) == SUCCESS) { + /* ensure the key is a string */ + if (zend_hash_get_current_key_ex(Z_ARRVAL_PP(data), &string_key, &string_len, &num_key, 0, NULL) == HASH_KEY_IS_STRING) { + /* compare to our key */ + if (!strncmp(string_key, key, string_len)) { + return Z_STRVAL_PP(tmp); + } + } + zend_hash_move_forward(Z_ARRVAL_PP(data)); + } + } + + return NULL; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_php.h b/sapi/fpm/fpm/fpm_php.h new file mode 100644 index 0000000..d605473 --- /dev/null +++ b/sapi/fpm/fpm/fpm_php.h @@ -0,0 +1,50 @@ + + /* $Id: fpm_php.h,v 1.10.2.1 2008/11/15 00:57:24 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_PHP_H +#define FPM_PHP_H 1 + +#include <TSRM.h> + +#include "php.h" +#include "build-defs.h" /* for PHP_ defines */ +#include "fpm/fpm_conf.h" + +#define FPM_PHP_INI_TO_EXPAND \ + { \ + "error_log", \ + "extension_dir", \ + "mime_magic.magicfile", \ + "sendmail_path", \ + "session.cookie_path", \ + "session_pgsql.sem_file_name", \ + "soap.wsdl_cache_dir", \ + "uploadprogress.file.filename_template", \ + "xdebug.output_dir", \ + "xdebug.profiler_output_dir", \ + "xdebug.trace_output_dir", \ + "xmms.path", \ + "axis2.client_home", \ + "blenc.key_file", \ + "coin_acceptor.device", \ + NULL \ + } + +struct fpm_worker_pool_s; + +int fpm_php_init_child(struct fpm_worker_pool_s *wp); +char *fpm_php_script_filename(TSRMLS_D); +char *fpm_php_request_uri(TSRMLS_D); +char *fpm_php_request_method(TSRMLS_D); +char *fpm_php_query_string(TSRMLS_D); +char *fpm_php_auth_user(TSRMLS_D); +size_t fpm_php_content_length(TSRMLS_D); +void fpm_php_soft_quit(); +int fpm_php_init_main(); +int fpm_php_apply_defines_ex(struct key_value_s *kv, int mode); +int fpm_php_limit_extensions(char *path); +char* fpm_php_get_string_from_table(char *table, char *key TSRMLS_DC); + +#endif + diff --git a/sapi/fpm/fpm/fpm_php_trace.c b/sapi/fpm/fpm/fpm_php_trace.c new file mode 100644 index 0000000..d95d66a --- /dev/null +++ b/sapi/fpm/fpm/fpm_php_trace.c @@ -0,0 +1,177 @@ + + /* $Id: fpm_php_trace.c,v 1.27.2.1 2008/11/15 00:57:24 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#if HAVE_FPM_TRACE + +#include "php.h" +#include "php_main.h" + +#include <stdio.h> +#include <stddef.h> +#if HAVE_INTTYPES_H +# include <inttypes.h> +#else +# include <stdint.h> +#endif +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <errno.h> + +#include "fpm_trace.h" +#include "fpm_php_trace.h" +#include "fpm_children.h" +#include "fpm_worker_pool.h" +#include "fpm_process_ctl.h" +#include "fpm_scoreboard.h" + +#include "zlog.h" + + +#define valid_ptr(p) ((p) && 0 == ((p) & (sizeof(long) - 1))) + +#if SIZEOF_LONG == 4 +#define PTR_FMT "08" +#elif SIZEOF_LONG == 8 +#define PTR_FMT "016" +#endif + + +static int fpm_php_trace_dump(struct fpm_child_s *child, FILE *slowlog TSRMLS_DC) /* {{{ */ +{ + int callers_limit = 20; + pid_t pid = child->pid; + struct timeval tv; + static const int buf_size = 1024; + char buf[buf_size]; + long execute_data; + long l; + + gettimeofday(&tv, 0); + + zlog_print_time(&tv, buf, buf_size); + + fprintf(slowlog, "\n%s [pool %s] pid %d\n", buf, child->wp->config->name, (int) pid); + + if (0 > fpm_trace_get_strz(buf, buf_size, (long) &SG(request_info).path_translated)) { + return -1; + } + + fprintf(slowlog, "script_filename = %s\n", buf); + + if (0 > fpm_trace_get_long((long) &EG(current_execute_data), &l)) { + return -1; + } + + execute_data = l; + + while (execute_data) { + long function; + uint lineno = 0; + + fprintf(slowlog, "[0x%" PTR_FMT "lx] ", execute_data); + + if (0 > fpm_trace_get_long(execute_data + offsetof(zend_execute_data, function_state.function), &l)) { + return -1; + } + + function = l; + + if (valid_ptr(function)) { + if (0 > fpm_trace_get_strz(buf, buf_size, function + offsetof(zend_function, common.function_name))) { + return -1; + } + + fprintf(slowlog, "%s()", buf); + } else { + fprintf(slowlog, "???"); + } + + if (0 > fpm_trace_get_long(execute_data + offsetof(zend_execute_data, op_array), &l)) { + return -1; + } + + *buf = '\0'; + + if (valid_ptr(l)) { + long op_array = l; + + if (0 > fpm_trace_get_strz(buf, buf_size, op_array + offsetof(zend_op_array, filename))) { + return -1; + } + } + + if (0 > fpm_trace_get_long(execute_data + offsetof(zend_execute_data, opline), &l)) { + return -1; + } + + if (valid_ptr(l)) { + long opline = l; + uint *lu = (uint *) &l; + + if (0 > fpm_trace_get_long(opline + offsetof(struct _zend_op, lineno), &l)) { + return -1; + } + + lineno = *lu; + } + + fprintf(slowlog, " %s:%u\n", *buf ? buf : "unknown", lineno); + + if (0 > fpm_trace_get_long(execute_data + offsetof(zend_execute_data, prev_execute_data), &l)) { + return -1; + } + + execute_data = l; + + if (0 == --callers_limit) { + break; + } + } + return 0; +} +/* }}} */ + +void fpm_php_trace(struct fpm_child_s *child) /* {{{ */ +{ + TSRMLS_FETCH(); + fpm_scoreboard_update(0, 0, 0, 0, 0, 0, 1, FPM_SCOREBOARD_ACTION_SET, child->wp->scoreboard); + FILE *slowlog; + + zlog(ZLOG_NOTICE, "about to trace %d", (int) child->pid); + + slowlog = fopen(child->wp->config->slowlog, "a+"); + + if (!slowlog) { + zlog(ZLOG_SYSERROR, "unable to open slowlog (%s)", child->wp->config->slowlog); + goto done0; + } + + if (0 > fpm_trace_ready(child->pid)) { + goto done1; + } + + if (0 > fpm_php_trace_dump(child, slowlog TSRMLS_CC)) { + fprintf(slowlog, "+++ dump failed\n"); + } + + if (0 > fpm_trace_close(child->pid)) { + goto done1; + } + +done1: + fclose(slowlog); + +done0: + fpm_pctl_kill(child->pid, FPM_PCTL_CONT); + child->tracer = 0; + + zlog(ZLOG_NOTICE, "finished trace of %d", (int) child->pid); +} +/* }}} */ + +#endif + diff --git a/sapi/fpm/fpm/fpm_php_trace.h b/sapi/fpm/fpm/fpm_php_trace.h new file mode 100644 index 0000000..af5e456 --- /dev/null +++ b/sapi/fpm/fpm/fpm_php_trace.h @@ -0,0 +1,13 @@ + + /* $Id: fpm_php_trace.h,v 1.2 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_PHP_TRACE_H +#define FPM_PHP_TRACE_H 1 + +struct fpm_child_s; + +void fpm_php_trace(struct fpm_child_s *); + +#endif + diff --git a/sapi/fpm/fpm/fpm_process_ctl.c b/sapi/fpm/fpm/fpm_process_ctl.c new file mode 100644 index 0000000..76ea4d3 --- /dev/null +++ b/sapi/fpm/fpm/fpm_process_ctl.c @@ -0,0 +1,539 @@ + + /* $Id: fpm_process_ctl.c,v 1.19.2.2 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "fpm.h" +#include "fpm_clock.h" +#include "fpm_children.h" +#include "fpm_signals.h" +#include "fpm_events.h" +#include "fpm_process_ctl.h" +#include "fpm_cleanup.h" +#include "fpm_request.h" +#include "fpm_worker_pool.h" +#include "fpm_scoreboard.h" +#include "fpm_sockets.h" +#include "zlog.h" + + +static int fpm_state = FPM_PCTL_STATE_NORMAL; +static int fpm_signal_sent = 0; + + +static const char *fpm_state_names[] = { + [FPM_PCTL_STATE_NORMAL] = "normal", + [FPM_PCTL_STATE_RELOADING] = "reloading", + [FPM_PCTL_STATE_TERMINATING] = "terminating", + [FPM_PCTL_STATE_FINISHING] = "finishing" +}; + +static int saved_argc; +static char **saved_argv; + +static void fpm_pctl_cleanup(int which, void *arg) /* {{{ */ +{ + int i; + if (which != FPM_CLEANUP_PARENT_EXEC) { + for (i = 0; i < saved_argc; i++) { + free(saved_argv[i]); + } + free(saved_argv); + } +} +/* }}} */ + +static struct fpm_event_s pctl_event; + +static void fpm_pctl_action(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_TIMEOUT); +} +/* }}} */ + +static int fpm_pctl_timeout_set(int sec) /* {{{ */ +{ + fpm_event_set_timer(&pctl_event, 0, &fpm_pctl_action, NULL); + fpm_event_add(&pctl_event, sec * 1000); + return 0; +} +/* }}} */ + +static void fpm_pctl_exit() /* {{{ */ +{ + zlog(ZLOG_NOTICE, "exiting, bye-bye!"); + + fpm_conf_unlink_pid(); + fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT_MAIN); + exit(FPM_EXIT_OK); +} +/* }}} */ + +#define optional_arg(c) (saved_argc > c ? ", \"" : ""), (saved_argc > c ? saved_argv[c] : ""), (saved_argc > c ? "\"" : "") + +static void fpm_pctl_exec() /* {{{ */ +{ + + zlog(ZLOG_NOTICE, "reloading: execvp(\"%s\", {\"%s\"" + "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" + "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" "%s%s%s" + "})", + saved_argv[0], saved_argv[0], + optional_arg(1), + optional_arg(2), + optional_arg(3), + optional_arg(4), + optional_arg(5), + optional_arg(6), + optional_arg(7), + optional_arg(8), + optional_arg(9), + optional_arg(10) + ); + + fpm_cleanups_run(FPM_CLEANUP_PARENT_EXEC); + execvp(saved_argv[0], saved_argv); + zlog(ZLOG_SYSERROR, "failed to reload: execvp() failed"); + exit(FPM_EXIT_SOFTWARE); +} +/* }}} */ + +static void fpm_pctl_action_last() /* {{{ */ +{ + switch (fpm_state) { + case FPM_PCTL_STATE_RELOADING: + fpm_pctl_exec(); + break; + + case FPM_PCTL_STATE_FINISHING: + case FPM_PCTL_STATE_TERMINATING: + fpm_pctl_exit(); + break; + } +} +/* }}} */ + +int fpm_pctl_kill(pid_t pid, int how) /* {{{ */ +{ + int s = 0; + + switch (how) { + case FPM_PCTL_TERM : + s = SIGTERM; + break; + case FPM_PCTL_STOP : + s = SIGSTOP; + break; + case FPM_PCTL_CONT : + s = SIGCONT; + break; + case FPM_PCTL_QUIT : + s = SIGQUIT; + break; + default : + break; + } + return kill(pid, s); +} +/* }}} */ + +void fpm_pctl_kill_all(int signo) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int alive_children = 0; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + struct fpm_child_s *child; + + for (child = wp->children; child; child = child->next) { + int res = kill(child->pid, signo); + + zlog(ZLOG_DEBUG, "[pool %s] sending signal %d %s to child %d", + child->wp->config->name, signo, + fpm_signal_names[signo] ? fpm_signal_names[signo] : "", (int) child->pid); + + if (res == 0) { + ++alive_children; + } + } + } + + if (alive_children) { + zlog(ZLOG_DEBUG, "%d child(ren) still alive", alive_children); + } +} +/* }}} */ + +static void fpm_pctl_action_next() /* {{{ */ +{ + int sig, timeout; + + if (!fpm_globals.running_children) { + fpm_pctl_action_last(); + } + + if (fpm_signal_sent == 0) { + if (fpm_state == FPM_PCTL_STATE_TERMINATING) { + sig = SIGTERM; + } else { + sig = SIGQUIT; + } + timeout = fpm_global_config.process_control_timeout; + } else { + if (fpm_signal_sent == SIGQUIT) { + sig = SIGTERM; + } else { + sig = SIGKILL; + } + timeout = 1; + } + + fpm_pctl_kill_all(sig); + fpm_signal_sent = sig; + fpm_pctl_timeout_set(timeout); +} +/* }}} */ + +void fpm_pctl(int new_state, int action) /* {{{ */ +{ + switch (action) { + case FPM_PCTL_ACTION_SET : + if (fpm_state == new_state) { /* already in progress - just ignore duplicate signal */ + return; + } + + switch (fpm_state) { /* check which states can be overridden */ + case FPM_PCTL_STATE_NORMAL : + /* 'normal' can be overridden by any other state */ + break; + case FPM_PCTL_STATE_RELOADING : + /* 'reloading' can be overridden by 'finishing' */ + if (new_state == FPM_PCTL_STATE_FINISHING) break; + case FPM_PCTL_STATE_FINISHING : + /* 'reloading' and 'finishing' can be overridden by 'terminating' */ + if (new_state == FPM_PCTL_STATE_TERMINATING) break; + case FPM_PCTL_STATE_TERMINATING : + /* nothing can override 'terminating' state */ + zlog(ZLOG_DEBUG, "not switching to '%s' state, because already in '%s' state", + fpm_state_names[new_state], fpm_state_names[fpm_state]); + return; + } + + fpm_signal_sent = 0; + fpm_state = new_state; + + zlog(ZLOG_DEBUG, "switching to '%s' state", fpm_state_names[fpm_state]); + /* fall down */ + + case FPM_PCTL_ACTION_TIMEOUT : + fpm_pctl_action_next(); + break; + case FPM_PCTL_ACTION_LAST_CHILD_EXITED : + fpm_pctl_action_last(); + break; + + } +} +/* }}} */ + +int fpm_pctl_can_spawn_children() /* {{{ */ +{ + return fpm_state == FPM_PCTL_STATE_NORMAL; +} +/* }}} */ + +int fpm_pctl_child_exited() /* {{{ */ +{ + if (fpm_state == FPM_PCTL_STATE_NORMAL) { + return 0; + } + + if (!fpm_globals.running_children) { + fpm_pctl(FPM_PCTL_STATE_UNSPECIFIED, FPM_PCTL_ACTION_LAST_CHILD_EXITED); + } + return 0; +} +/* }}} */ + +int fpm_pctl_init_main() /* {{{ */ +{ + int i; + + saved_argc = fpm_globals.argc; + saved_argv = malloc(sizeof(char *) * (saved_argc + 1)); + + if (!saved_argv) { + return -1; + } + + for (i = 0; i < saved_argc; i++) { + saved_argv[i] = strdup(fpm_globals.argv[i]); + + if (!saved_argv[i]) { + return -1; + } + } + + saved_argv[i] = 0; + + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_pctl_cleanup, 0)) { + return -1; + } + return 0; +} +/* }}} */ + +static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + int terminate_timeout = wp->config->request_terminate_timeout; + int slowlog_timeout = wp->config->request_slowlog_timeout; + struct fpm_child_s *child; + + if (terminate_timeout || slowlog_timeout) { + for (child = wp->children; child; child = child->next) { + fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout); + } + } + } +} +/* }}} */ + +static void fpm_pctl_perform_idle_server_maintenance(struct timeval *now) /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + struct fpm_child_s *child; + struct fpm_child_s *last_idle_child = NULL; + int idle = 0; + int active = 0; + int children_to_fork; + unsigned cur_lq = 0; + + if (wp->config == NULL) continue; + + for (child = wp->children; child; child = child->next) { + if (fpm_request_is_idle(child)) { + if (last_idle_child == NULL) { + last_idle_child = child; + } else { + if (timercmp(&child->started, &last_idle_child->started, <)) { + last_idle_child = child; + } + } + idle++; + } else { + active++; + } + } + + /* update status structure for all PMs */ + if (wp->listen_address_domain == FPM_AF_INET) { + if (0 > fpm_socket_get_listening_queue(wp->listening_socket, &cur_lq, NULL)) { + cur_lq = 0; +#if 0 + } else { + if (cur_lq > 0) { + if (!wp->warn_lq) { + zlog(ZLOG_WARNING, "[pool %s] listening queue is not empty, #%d requests are waiting to be served, consider raising pm.max_children setting (%d)", wp->config->name, cur_lq, wp->config->pm_max_children); + wp->warn_lq = 1; + } + } else { + wp->warn_lq = 0; + } +#endif + } + } + fpm_scoreboard_update(idle, active, cur_lq, -1, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard); + + /* this is specific to PM_STYLE_ONDEMAND */ + if (wp->config->pm == PM_STYLE_ONDEMAND) { + struct timeval last, now; + + zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children", wp->config->name, active, idle); + + if (!last_idle_child) continue; + + fpm_request_last_activity(last_idle_child, &last); + fpm_clock_get(&now); + if (last.tv_sec < now.tv_sec - wp->config->pm_process_idle_timeout) { + last_idle_child->idle_kill = 1; + fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT); + } + + continue; + } + + /* the rest is only used by PM_STYLE_DYNAMIC */ + if (wp->config->pm != PM_STYLE_DYNAMIC) continue; + + zlog(ZLOG_DEBUG, "[pool %s] currently %d active children, %d spare children, %d running children. Spawning rate %d", wp->config->name, active, idle, wp->running_children, wp->idle_spawn_rate); + + if (idle > wp->config->pm_max_spare_servers && last_idle_child) { + last_idle_child->idle_kill = 1; + fpm_pctl_kill(last_idle_child->pid, FPM_PCTL_QUIT); + wp->idle_spawn_rate = 1; + continue; + } + + if (idle < wp->config->pm_min_spare_servers) { + if (wp->running_children >= wp->config->pm_max_children) { + if (!wp->warn_max_children) { + fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard); + zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children); + wp->warn_max_children = 1; + } + wp->idle_spawn_rate = 1; + continue; + } + + if (wp->idle_spawn_rate >= 8) { + zlog(ZLOG_WARNING, "[pool %s] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning %d children, there are %d idle, and %d total children", wp->config->name, wp->idle_spawn_rate, idle, wp->running_children); + } + + /* compute the number of idle process to spawn */ + children_to_fork = MIN(wp->idle_spawn_rate, wp->config->pm_min_spare_servers - idle); + + /* get sure it won't exceed max_children */ + children_to_fork = MIN(children_to_fork, wp->config->pm_max_children - wp->running_children); + if (children_to_fork <= 0) { + if (!wp->warn_max_children) { + fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard); + zlog(ZLOG_WARNING, "[pool %s] server reached pm.max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children); + wp->warn_max_children = 1; + } + wp->idle_spawn_rate = 1; + continue; + } + wp->warn_max_children = 0; + + fpm_children_make(wp, 1, children_to_fork, 1); + + /* if it's a child, stop here without creating the next event + * this event is reserved to the master process + */ + if (fpm_globals.is_child) { + return; + } + + zlog(ZLOG_DEBUG, "[pool %s] %d child(ren) have been created dynamically", wp->config->name, children_to_fork); + + /* Double the spawn rate for the next iteration */ + if (wp->idle_spawn_rate < FPM_MAX_SPAWN_RATE) { + wp->idle_spawn_rate *= 2; + } + continue; + } + wp->idle_spawn_rate = 1; + } +} +/* }}} */ + +void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + static struct fpm_event_s heartbeat; + struct timeval now; + + if (fpm_globals.parent_pid != getpid()) { + return; /* sanity check */ + } + + if (which == FPM_EV_TIMEOUT) { + fpm_clock_get(&now); + fpm_pctl_check_request_timeout(&now); + return; + } + + /* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */ + fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT); + + /* first call without setting to initialize the timer */ + zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat); + fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL); + fpm_event_add(&heartbeat, fpm_globals.heartbeat); +} +/* }}} */ + +void fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + static struct fpm_event_s heartbeat; + struct timeval now; + + if (fpm_globals.parent_pid != getpid()) { + return; /* sanity check */ + } + + if (which == FPM_EV_TIMEOUT) { + fpm_clock_get(&now); + if (fpm_pctl_can_spawn_children()) { + fpm_pctl_perform_idle_server_maintenance(&now); + + /* if it's a child, stop here without creating the next event + * this event is reserved to the master process + */ + if (fpm_globals.is_child) { + return; + } + } + return; + } + + /* first call without setting which to initialize the timer */ + fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_perform_idle_server_maintenance_heartbeat, NULL); + fpm_event_add(&heartbeat, FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT); +} +/* }}} */ + +void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + struct fpm_worker_pool_s *wp = (struct fpm_worker_pool_s *)arg; + struct fpm_child_s *child; + + + if (fpm_globals.parent_pid != getpid()) { + /* prevent a event race condition when child process + * have not set up its own event loop */ + return; + } + + wp->socket_event_set = 0; + +/* zlog(ZLOG_DEBUG, "[pool %s] heartbeat running_children=%d", wp->config->name, wp->running_children);*/ + + if (wp->running_children >= wp->config->pm_max_children) { + if (!wp->warn_max_children) { + fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard); + zlog(ZLOG_WARNING, "[pool %s] server reached max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children); + wp->warn_max_children = 1; + } + + return; + } + + for (child = wp->children; child; child = child->next) { + /* if there is at least on idle child, it will handle the connection, stop here */ + if (fpm_request_is_idle(child)) { + return; + } + } + + wp->warn_max_children = 0; + fpm_children_make(wp, 1, 1, 1); + + if (fpm_globals.is_child) { + return; + } + + zlog(ZLOG_DEBUG, "[pool %s] got accept without idle child available .... I forked", wp->config->name); +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_process_ctl.h b/sapi/fpm/fpm/fpm_process_ctl.h new file mode 100644 index 0000000..86a6ef0 --- /dev/null +++ b/sapi/fpm/fpm/fpm_process_ctl.h @@ -0,0 +1,53 @@ + + /* $Id: fpm_process_ctl.h,v 1.6 2008/07/20 21:33:10 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_PROCESS_CTL_H +#define FPM_PROCESS_CTL_H 1 + +#include "fpm_events.h" + +/* spawn max 32 children at once */ +#define FPM_MAX_SPAWN_RATE (32) +/* 1s (in ms) heartbeat for idle server maintenance */ +#define FPM_IDLE_SERVER_MAINTENANCE_HEARTBEAT (1000) +/* a minimum of 130ms heartbeat for pctl */ +#define FPM_PCTL_MIN_HEARTBEAT (130) + + +struct fpm_child_s; + +void fpm_pctl(int new_state, int action); +int fpm_pctl_can_spawn_children(); +int fpm_pctl_kill(pid_t pid, int how); +void fpm_pctl_kill_all(int signo); +void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg); +void fpm_pctl_perform_idle_server_maintenance_heartbeat(struct fpm_event_s *ev, short which, void *arg); +void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg); +int fpm_pctl_child_exited(); +int fpm_pctl_init_main(); + + +enum { + FPM_PCTL_STATE_UNSPECIFIED, + FPM_PCTL_STATE_NORMAL, + FPM_PCTL_STATE_RELOADING, + FPM_PCTL_STATE_TERMINATING, + FPM_PCTL_STATE_FINISHING +}; + +enum { + FPM_PCTL_ACTION_SET, + FPM_PCTL_ACTION_TIMEOUT, + FPM_PCTL_ACTION_LAST_CHILD_EXITED +}; + +enum { + FPM_PCTL_TERM, + FPM_PCTL_STOP, + FPM_PCTL_CONT, + FPM_PCTL_QUIT +}; + +#endif + diff --git a/sapi/fpm/fpm/fpm_request.c b/sapi/fpm/fpm/fpm_request.c new file mode 100644 index 0000000..bf431a0 --- /dev/null +++ b/sapi/fpm/fpm/fpm_request.c @@ -0,0 +1,316 @@ + + /* $Id: fpm_request.c,v 1.9.2.1 2008/11/15 00:57:24 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ +#ifdef HAVE_TIMES +#include <sys/times.h> +#endif + +#include "fpm_config.h" + +#include "fpm.h" +#include "fpm_php.h" +#include "fpm_str.h" +#include "fpm_clock.h" +#include "fpm_conf.h" +#include "fpm_trace.h" +#include "fpm_php_trace.h" +#include "fpm_process_ctl.h" +#include "fpm_children.h" +#include "fpm_scoreboard.h" +#include "fpm_status.h" +#include "fpm_request.h" +#include "fpm_log.h" + +#include "zlog.h" + +static const char *requests_stages[] = { + [FPM_REQUEST_ACCEPTING] = "Idle", + [FPM_REQUEST_READING_HEADERS] = "Reading headers", + [FPM_REQUEST_INFO] = "Getting request informations", + [FPM_REQUEST_EXECUTING] = "Running", + [FPM_REQUEST_END] = "Ending", + [FPM_REQUEST_FINISHED] = "Finishing", +}; + +const char *fpm_request_get_stage_name(int stage) { + return requests_stages[stage]; +} + +void fpm_request_accepting() /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + struct timeval now; + + fpm_clock_get(&now); + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + + proc->request_stage = FPM_REQUEST_ACCEPTING; + proc->tv = now; + fpm_scoreboard_proc_release(proc); + + /* idle++, active-- */ + fpm_scoreboard_update(1, -1, 0, 0, 0, 0, 0, FPM_SCOREBOARD_ACTION_INC, NULL); +} +/* }}} */ + +void fpm_request_reading_headers() /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + + struct timeval now; + clock_t now_epoch; +#ifdef HAVE_TIMES + struct tms cpu; +#endif + + fpm_clock_get(&now); + now_epoch = time(NULL); +#ifdef HAVE_TIMES + times(&cpu); +#endif + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + + proc->request_stage = FPM_REQUEST_READING_HEADERS; + proc->tv = now; + proc->accepted = now; + proc->accepted_epoch = now_epoch; +#ifdef HAVE_TIMES + proc->cpu_accepted = cpu; +#endif + proc->requests++; + proc->request_uri[0] = '\0'; + proc->request_method[0] = '\0'; + proc->script_filename[0] = '\0'; + proc->query_string[0] = '\0'; + proc->query_string[0] = '\0'; + proc->auth_user[0] = '\0'; + proc->content_length = 0; + fpm_scoreboard_proc_release(proc); + + /* idle--, active++, request++ */ + fpm_scoreboard_update(-1, 1, 0, 0, 1, 0, 0, FPM_SCOREBOARD_ACTION_INC, NULL); +} +/* }}} */ + +void fpm_request_info() /* {{{ */ +{ + TSRMLS_FETCH(); + struct fpm_scoreboard_proc_s *proc; + char *request_uri = fpm_php_request_uri(TSRMLS_C); + char *request_method = fpm_php_request_method(TSRMLS_C); + char *script_filename = fpm_php_script_filename(TSRMLS_C); + char *query_string = fpm_php_query_string(TSRMLS_C); + char *auth_user = fpm_php_auth_user(TSRMLS_C); + size_t content_length = fpm_php_content_length(TSRMLS_C); + struct timeval now; + + fpm_clock_get(&now); + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + + proc->request_stage = FPM_REQUEST_INFO; + proc->tv = now; + + if (request_uri) { + strlcpy(proc->request_uri, request_uri, sizeof(proc->request_uri)); + } + + if (request_method) { + strlcpy(proc->request_method, request_method, sizeof(proc->request_method)); + } + + if (query_string) { + strlcpy(proc->query_string, query_string, sizeof(proc->query_string)); + } + + if (auth_user) { + strlcpy(proc->auth_user, auth_user, sizeof(proc->auth_user)); + } + + proc->content_length = content_length; + + /* if cgi.fix_pathinfo is set to "1" and script cannot be found (404) + the sapi_globals.request_info.path_translated is set to NULL */ + if (script_filename) { + strlcpy(proc->script_filename, script_filename, sizeof(proc->script_filename)); + } + + fpm_scoreboard_proc_release(proc); +} +/* }}} */ + +void fpm_request_executing() /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + struct timeval now; + + fpm_clock_get(&now); + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + + proc->request_stage = FPM_REQUEST_EXECUTING; + proc->tv = now; + fpm_scoreboard_proc_release(proc); +} +/* }}} */ + +void fpm_request_end(TSRMLS_D) /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + struct timeval now; +#ifdef HAVE_TIMES + struct tms cpu; +#endif + size_t memory = zend_memory_peak_usage(1 TSRMLS_CC); + + fpm_clock_get(&now); +#ifdef HAVE_TIMES + times(&cpu); +#endif + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + proc->request_stage = FPM_REQUEST_FINISHED; + proc->tv = now; + timersub(&now, &proc->accepted, &proc->duration); +#ifdef HAVE_TIMES + timersub(&proc->tv, &proc->accepted, &proc->cpu_duration); + proc->last_request_cpu.tms_utime = cpu.tms_utime - proc->cpu_accepted.tms_utime; + proc->last_request_cpu.tms_stime = cpu.tms_stime - proc->cpu_accepted.tms_stime; + proc->last_request_cpu.tms_cutime = cpu.tms_cutime - proc->cpu_accepted.tms_cutime; + proc->last_request_cpu.tms_cstime = cpu.tms_cstime - proc->cpu_accepted.tms_cstime; +#endif + proc->memory = memory; + fpm_scoreboard_proc_release(proc); +} +/* }}} */ + +void fpm_request_finished() /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + struct timeval now; + + fpm_clock_get(&now); + + proc = fpm_scoreboard_proc_acquire(NULL, -1, 0); + if (proc == NULL) { + zlog(ZLOG_WARNING, "failed to acquire proc scoreboard"); + return; + } + + proc->request_stage = FPM_REQUEST_FINISHED; + proc->tv = now; + memset(&proc->accepted, 0, sizeof(proc->accepted)); + proc->accepted_epoch = 0; + fpm_scoreboard_proc_release(proc); +} +/* }}} */ + +void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *now, int terminate_timeout, int slowlog_timeout) /* {{{ */ +{ + struct fpm_scoreboard_proc_s proc, *proc_p; + + proc_p = fpm_scoreboard_proc_acquire(child->wp->scoreboard, child->scoreboard_i, 1); + if (!proc_p) { + zlog(ZLOG_WARNING, "failed to acquire scoreboard"); + return; + } + + proc = *proc_p; + fpm_scoreboard_proc_release(proc_p); + +#if HAVE_FPM_TRACE + if (child->slow_logged.tv_sec) { + if (child->slow_logged.tv_sec != proc.accepted.tv_sec || child->slow_logged.tv_usec != proc.accepted.tv_usec) { + child->slow_logged.tv_sec = 0; + child->slow_logged.tv_usec = 0; + } + } +#endif + + if (proc.request_stage > FPM_REQUEST_ACCEPTING && proc.request_stage < FPM_REQUEST_END) { + char purified_script_filename[sizeof(proc.script_filename)]; + struct timeval tv; + + timersub(now, &proc.accepted, &tv); + +#if HAVE_FPM_TRACE + if (child->slow_logged.tv_sec == 0 && slowlog_timeout && + proc.request_stage == FPM_REQUEST_EXECUTING && tv.tv_sec >= slowlog_timeout) { + + str_purify_filename(purified_script_filename, proc.script_filename, sizeof(proc.script_filename)); + + child->slow_logged = proc.accepted; + child->tracer = fpm_php_trace; + + fpm_trace_signal(child->pid); + + zlog(ZLOG_WARNING, "[pool %s] child %d, script '%s' (request: \"%s %s\") executing too slow (%d.%06d sec), logging", + child->wp->config->name, (int) child->pid, purified_script_filename, proc.request_method, proc.request_uri, + (int) tv.tv_sec, (int) tv.tv_usec); + } + else +#endif + if (terminate_timeout && tv.tv_sec >= terminate_timeout) { + str_purify_filename(purified_script_filename, proc.script_filename, sizeof(proc.script_filename)); + fpm_pctl_kill(child->pid, FPM_PCTL_TERM); + + zlog(ZLOG_WARNING, "[pool %s] child %d, script '%s' (request: \"%s %s\") execution timed out (%d.%06d sec), terminating", + child->wp->config->name, (int) child->pid, purified_script_filename, proc.request_method, proc.request_uri, + (int) tv.tv_sec, (int) tv.tv_usec); + } + } +} +/* }}} */ + +int fpm_request_is_idle(struct fpm_child_s *child) /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + + /* no need in atomicity here */ + proc = fpm_scoreboard_proc_get(child->wp->scoreboard, child->scoreboard_i); + if (!proc) { + return 0; + } + + return proc->request_stage == FPM_REQUEST_ACCEPTING; +} +/* }}} */ + +int fpm_request_last_activity(struct fpm_child_s *child, struct timeval *tv) /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + + if (!tv) return -1; + + proc = fpm_scoreboard_proc_get(child->wp->scoreboard, child->scoreboard_i); + if (!proc) { + return -1; + } + + *tv = proc->tv; + + return 1; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_request.h b/sapi/fpm/fpm/fpm_request.h new file mode 100644 index 0000000..aebd36c --- /dev/null +++ b/sapi/fpm/fpm/fpm_request.h @@ -0,0 +1,32 @@ + + /* $Id: fpm_request.h,v 1.4 2008/07/20 01:47:16 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_REQUEST_H +#define FPM_REQUEST_H 1 + +void fpm_request_accepting(); /* hanging in accept() */ +void fpm_request_reading_headers(); /* start reading fastcgi request from very first byte */ +void fpm_request_info(); /* not a stage really but a point in the php code, where all request params have become known to sapi */ +void fpm_request_executing(); /* the script is executing */ +void fpm_request_end(TSRMLS_D); /* request ended: script response have been sent to web server */ +void fpm_request_finished(); /* request processed: cleaning current request */ + +struct fpm_child_s; +struct timeval; + +void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *tv, int terminate_timeout, int slowlog_timeout); +int fpm_request_is_idle(struct fpm_child_s *child); +const char *fpm_request_get_stage_name(int stage); +int fpm_request_last_activity(struct fpm_child_s *child, struct timeval *tv); + +enum fpm_request_stage_e { + FPM_REQUEST_ACCEPTING = 1, + FPM_REQUEST_READING_HEADERS, + FPM_REQUEST_INFO, + FPM_REQUEST_EXECUTING, + FPM_REQUEST_END, + FPM_REQUEST_FINISHED +}; + +#endif diff --git a/sapi/fpm/fpm/fpm_scoreboard.c b/sapi/fpm/fpm/fpm_scoreboard.c new file mode 100644 index 0000000..24463a9 --- /dev/null +++ b/sapi/fpm/fpm/fpm_scoreboard.c @@ -0,0 +1,331 @@ + + /* $Id: fpm_status.c 312399 2011-06-23 08:03:52Z fat $ */ + /* (c) 2009 Jerome Loyet */ + +#include "php.h" +#include "SAPI.h" +#include <stdio.h> +#include <time.h> + +#include "fpm_config.h" +#include "fpm_scoreboard.h" +#include "fpm_shm.h" +#include "fpm_sockets.h" +#include "fpm_worker_pool.h" +#include "fpm_clock.h" +#include "zlog.h" + +static struct fpm_scoreboard_s *fpm_scoreboard = NULL; +static int fpm_scoreboard_i = -1; +#ifdef HAVE_TIMES +static float fpm_scoreboard_tick; +#endif + + +int fpm_scoreboard_init_main() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int i; + +#ifdef HAVE_TIMES +#if (defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)) + fpm_scoreboard_tick = sysconf(_SC_CLK_TCK); +#else /* _SC_CLK_TCK */ +#ifdef HZ + fpm_scoreboard_tick = HZ; +#else /* HZ */ + fpm_scoreboard_tick = 100; +#endif /* HZ */ +#endif /* _SC_CLK_TCK */ + zlog(ZLOG_DEBUG, "got clock tick '%.0f'", fpm_scoreboard_tick); +#endif /* HAVE_TIMES */ + + + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (wp->config->pm_max_children < 1) { + zlog(ZLOG_ERROR, "[pool %s] Unable to create scoreboard SHM because max_client is not set", wp->config->name); + return -1; + } + + if (wp->scoreboard) { + zlog(ZLOG_ERROR, "[pool %s] Unable to create scoreboard SHM because it already exists", wp->config->name); + return -1; + } + + wp->scoreboard = fpm_shm_alloc(sizeof(struct fpm_scoreboard_s) + (wp->config->pm_max_children - 1) * sizeof(struct fpm_scoreboard_proc_s *)); + if (!wp->scoreboard) { + return -1; + } + wp->scoreboard->nprocs = wp->config->pm_max_children; + for (i = 0; i < wp->scoreboard->nprocs; i++) { + wp->scoreboard->procs[i] = fpm_shm_alloc(sizeof(struct fpm_scoreboard_proc_s)); + if (!wp->scoreboard->procs[i]) { + return -1; + } + memset(wp->scoreboard->procs[i], 0, sizeof(struct fpm_scoreboard_proc_s)); + } + + wp->scoreboard->pm = wp->config->pm; + wp->scoreboard->start_epoch = time(NULL); + strlcpy(wp->scoreboard->pool, wp->config->name, sizeof(wp->scoreboard->pool)); + } + return 0; +} +/* }}} */ + +void fpm_scoreboard_update(int idle, int active, int lq, int lq_len, int requests, int max_children_reached, int slow_rq, int action, struct fpm_scoreboard_s *scoreboard) /* {{{ */ +{ + if (!scoreboard) { + scoreboard = fpm_scoreboard; + } + if (!scoreboard) { + zlog(ZLOG_WARNING, "Unable to update scoreboard: the SHM has not been found"); + return; + } + + + fpm_spinlock(&scoreboard->lock, 0); + if (action == FPM_SCOREBOARD_ACTION_SET) { + if (idle >= 0) { + scoreboard->idle = idle; + } + if (active >= 0) { + scoreboard->active = active; + } + if (lq >= 0) { + scoreboard->lq = lq; + } + if (lq_len >= 0) { + scoreboard->lq_len = lq_len; + } +#ifdef HAVE_FPM_LQ /* prevent unnecessary test */ + if (scoreboard->lq > scoreboard->lq_max) { + scoreboard->lq_max = scoreboard->lq; + } +#endif + if (requests >= 0) { + scoreboard->requests = requests; + } + + if (max_children_reached >= 0) { + scoreboard->max_children_reached = max_children_reached; + } + if (slow_rq > 0) { + scoreboard->slow_rq += slow_rq; + } + } else { + if (scoreboard->idle + idle > 0) { + scoreboard->idle += idle; + } else { + scoreboard->idle = 0; + } + + if (scoreboard->active + active > 0) { + scoreboard->active += active; + } else { + scoreboard->active = 0; + } + + if (scoreboard->requests + requests > 0) { + scoreboard->requests += requests; + } else { + scoreboard->requests = 0; + } + + if (scoreboard->max_children_reached + max_children_reached > 0) { + scoreboard->max_children_reached += max_children_reached; + } else { + scoreboard->max_children_reached = 0; + } + } + + if (scoreboard->active > scoreboard->active_max) { + scoreboard->active_max = scoreboard->active; + } + + fpm_unlock(scoreboard->lock); +} +/* }}} */ + +struct fpm_scoreboard_s *fpm_scoreboard_get() /* {{{*/ +{ + return fpm_scoreboard; +} +/* }}} */ + +struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get(struct fpm_scoreboard_s *scoreboard, int child_index) /* {{{*/ +{ + if (!scoreboard) { + scoreboard = fpm_scoreboard; + } + + if (!scoreboard) { + return NULL; + } + + if (child_index < 0) { + child_index = fpm_scoreboard_i; + } + + if (child_index < 0 || child_index >= scoreboard->nprocs) { + return NULL; + } + + return scoreboard->procs[child_index]; +} +/* }}} */ + +struct fpm_scoreboard_s *fpm_scoreboard_acquire(struct fpm_scoreboard_s *scoreboard, int nohang) /* {{{ */ +{ + struct fpm_scoreboard_s *s; + + s = scoreboard ? scoreboard : fpm_scoreboard; + if (!s) { + return NULL; + } + + if (!fpm_spinlock(&s->lock, nohang)) { + return NULL; + } + return s; +} +/* }}} */ + +void fpm_scoreboard_release(struct fpm_scoreboard_s *scoreboard) { + if (!scoreboard) { + return; + } + + scoreboard->lock = 0; +} + +struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_acquire(struct fpm_scoreboard_s *scoreboard, int child_index, int nohang) /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + + proc = fpm_scoreboard_proc_get(scoreboard, child_index); + if (!proc) { + return NULL; + } + + if (!fpm_spinlock(&proc->lock, nohang)) { + return NULL; + } + + return proc; +} +/* }}} */ + +void fpm_scoreboard_proc_release(struct fpm_scoreboard_proc_s *proc) /* {{{ */ +{ + if (!proc) { + return; + } + + proc->lock = 0; +} + +void fpm_scoreboard_free(struct fpm_scoreboard_s *scoreboard) /* {{{ */ +{ + int i; + + if (!scoreboard) { + zlog(ZLOG_ERROR, "**scoreboard is NULL"); + return; + } + + for (i = 0; i < scoreboard->nprocs; i++) { + if (!scoreboard->procs[i]) { + continue; + } + fpm_shm_free(scoreboard->procs[i], sizeof(struct fpm_scoreboard_proc_s)); + } + fpm_shm_free(scoreboard, sizeof(struct fpm_scoreboard_s)); +} +/* }}} */ + +void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_index, pid_t pid) /* {{{ */ +{ + struct fpm_scoreboard_proc_s *proc; + fpm_scoreboard = scoreboard; + fpm_scoreboard_i = child_index; + proc = fpm_scoreboard_proc_get(scoreboard, child_index); + if (!proc) { + return; + } + proc->pid = pid; + proc->start_epoch = time(NULL); +} +/* }}} */ + +void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_index) /* {{{ */ +{ + if (!scoreboard) { + return; + } + + if (child_index < 0 || child_index >= scoreboard->nprocs) { + return; + } + + if (scoreboard->procs[child_index] && scoreboard->procs[child_index]->used > 0) { + memset(scoreboard->procs[child_index], 0, sizeof(struct fpm_scoreboard_proc_s)); + } + + /* set this slot as free to avoid search on next alloc */ + scoreboard->free_proc = child_index; +} +/* }}} */ + +int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_index) /* {{{ */ +{ + int i = -1; + + if (!scoreboard || !child_index) { + return -1; + } + + /* first try the slot which is supposed to be free */ + if (scoreboard->free_proc >= 0 && scoreboard->free_proc < scoreboard->nprocs) { + if (scoreboard->procs[scoreboard->free_proc] && !scoreboard->procs[scoreboard->free_proc]->used) { + i = scoreboard->free_proc; + } + } + + if (i < 0) { /* the supposed free slot is not, let's search for a free slot */ + zlog(ZLOG_DEBUG, "[pool %s] the proc->free_slot was not free. Let's search", scoreboard->pool); + for (i = 0; i < scoreboard->nprocs; i++) { + if (scoreboard->procs[i] && !scoreboard->procs[i]->used) { /* found */ + break; + } + } + } + + /* no free slot */ + if (i < 0 || i >= scoreboard->nprocs) { + zlog(ZLOG_ERROR, "[pool %s] no free scoreboard slot", scoreboard->pool); + return -1; + } + + scoreboard->procs[i]->used = 1; + *child_index = i; + + /* supposed next slot is free */ + if (i + 1 >= scoreboard->nprocs) { + scoreboard->free_proc = 0; + } else { + scoreboard->free_proc = i + 1; + } + + return 0; +} +/* }}} */ + +#ifdef HAVE_TIMES +float fpm_scoreboard_get_tick() /* {{{ */ +{ + return fpm_scoreboard_tick; +} +/* }}} */ +#endif + diff --git a/sapi/fpm/fpm/fpm_scoreboard.h b/sapi/fpm/fpm/fpm_scoreboard.h new file mode 100644 index 0000000..f58a287 --- /dev/null +++ b/sapi/fpm/fpm/fpm_scoreboard.h @@ -0,0 +1,94 @@ + + /* $Id: fpm_status.h 312263 2011-06-18 17:46:16Z felipe $ */ + /* (c) 2009 Jerome Loyet */ + +#ifndef FPM_SCOREBOARD_H +#define FPM_SCOREBOARD_H 1 + +#include <sys/time.h> +#ifdef HAVE_TIMES +#include <sys/times.h> +#endif + +#include "fpm_request.h" +#include "fpm_worker_pool.h" +#include "fpm_atomic.h" + +#define FPM_SCOREBOARD_ACTION_SET 0 +#define FPM_SCOREBOARD_ACTION_INC 1 + +struct fpm_scoreboard_proc_s { + union { + atomic_t lock; + char dummy[16]; + }; + int used; + time_t start_epoch; + pid_t pid; + unsigned long requests; + enum fpm_request_stage_e request_stage; + struct timeval accepted; + struct timeval duration; + time_t accepted_epoch; + struct timeval tv; + char request_uri[128]; + char query_string[512]; + char request_method[16]; + size_t content_length; /* used with POST only */ + char script_filename[256]; + char auth_user[32]; +#ifdef HAVE_TIMES + struct tms cpu_accepted; + struct timeval cpu_duration; + struct tms last_request_cpu; + struct timeval last_request_cpu_duration; +#endif + size_t memory; +}; + +struct fpm_scoreboard_s { + union { + atomic_t lock; + char dummy[16]; + }; + char pool[32]; + int pm; + time_t start_epoch; + int idle; + int active; + int active_max; + unsigned long int requests; + unsigned int max_children_reached; + int lq; + int lq_max; + unsigned int lq_len; + unsigned int nprocs; + int free_proc; + unsigned long int slow_rq; + struct fpm_scoreboard_proc_s *procs[]; +}; + +int fpm_scoreboard_init_main(); +int fpm_scoreboard_init_child(struct fpm_worker_pool_s *wp); + +void fpm_scoreboard_update(int idle, int active, int lq, int lq_len, int requests, int max_children_reached, int slow_rq, int action, struct fpm_scoreboard_s *scoreboard); +struct fpm_scoreboard_s *fpm_scoreboard_get(); +struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_get(struct fpm_scoreboard_s *scoreboard, int child_index); + +struct fpm_scoreboard_s *fpm_scoreboard_acquire(struct fpm_scoreboard_s *scoreboard, int nohang); +void fpm_scoreboard_release(struct fpm_scoreboard_s *scoreboard); +struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_acquire(struct fpm_scoreboard_s *scoreboard, int child_index, int nohang); +void fpm_scoreboard_proc_release(struct fpm_scoreboard_proc_s *proc); + +void fpm_scoreboard_free(struct fpm_scoreboard_s *scoreboard); + +void fpm_scoreboard_child_use(struct fpm_scoreboard_s *scoreboard, int child_index, pid_t pid); + +void fpm_scoreboard_proc_free(struct fpm_scoreboard_s *scoreboard, int child_index); +int fpm_scoreboard_proc_alloc(struct fpm_scoreboard_s *scoreboard, int *child_index); + +#ifdef HAVE_TIMES +float fpm_scoreboard_get_tick(); +#endif + +#endif diff --git a/sapi/fpm/fpm/fpm_shm.c b/sapi/fpm/fpm/fpm_shm.c new file mode 100644 index 0000000..9226adf --- /dev/null +++ b/sapi/fpm/fpm/fpm_shm.c @@ -0,0 +1,70 @@ + + /* $Id: fpm_shm.c,v 1.3 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin, Jerome Loyet */ + +#include <sys/mman.h> +#include <errno.h> +#include <string.h> + +#include "fpm_shm.h" +#include "zlog.h" + + +/* MAP_ANON is deprecated, but not in macosx */ +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif + +static size_t fpm_shm_size = 0; + +void *fpm_shm_alloc(size_t size) /* {{{ */ +{ + void *mem; + + mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + +#ifdef MAP_FAILED + if (mem == MAP_FAILED) { + zlog(ZLOG_SYSERROR, "unable to allocate %zu bytes in shared memory: %s", size, strerror(errno)); + return NULL; + } +#endif + + if (!mem) { + zlog(ZLOG_SYSERROR, "unable to allocate %zu bytes in shared memory", size); + return NULL; + } + + memset(mem, 0, size); + fpm_shm_size += size; + return mem; +} +/* }}} */ + +int fpm_shm_free(void *mem, size_t size) /* {{{ */ +{ + if (!mem) { + zlog(ZLOG_ERROR, "mem is NULL"); + return 0; + } + + if (munmap(mem, size) == -1) { + zlog(ZLOG_SYSERROR, "Unable to free shm"); + return 0; + } + + if (fpm_shm_size - size > 0) { + fpm_shm_size -= size; + } else { + fpm_shm_size = 0; + } + + return 1; +} +/* }}} */ + +size_t fpm_shm_get_size_allocated() /* {{{*/ +{ + return fpm_shm_size; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_shm.h b/sapi/fpm/fpm/fpm_shm.h new file mode 100644 index 0000000..bcb6099 --- /dev/null +++ b/sapi/fpm/fpm/fpm_shm.h @@ -0,0 +1,13 @@ + + /* $Id: fpm_shm.h,v 1.3 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_SHM_H +#define FPM_SHM_H 1 + +void *fpm_shm_alloc(size_t size); +int fpm_shm_free(void *mem, size_t size); +size_t fpm_shm_get_size_allocated(); + +#endif + diff --git a/sapi/fpm/fpm/fpm_signals.c b/sapi/fpm/fpm/fpm_signals.c new file mode 100644 index 0000000..8993a86 --- /dev/null +++ b/sapi/fpm/fpm/fpm_signals.c @@ -0,0 +1,251 @@ + + /* $Id: fpm_signals.c,v 1.24 2008/08/26 15:09:15 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <signal.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "fpm.h" +#include "fpm_signals.h" +#include "fpm_sockets.h" +#include "fpm_php.h" +#include "zlog.h" + +static int sp[2]; + +const char *fpm_signal_names[NSIG + 1] = { +#ifdef SIGHUP + [SIGHUP] = "SIGHUP", +#endif +#ifdef SIGINT + [SIGINT] = "SIGINT", +#endif +#ifdef SIGQUIT + [SIGQUIT] = "SIGQUIT", +#endif +#ifdef SIGILL + [SIGILL] = "SIGILL", +#endif +#ifdef SIGTRAP + [SIGTRAP] = "SIGTRAP", +#endif +#ifdef SIGABRT + [SIGABRT] = "SIGABRT", +#endif +#ifdef SIGEMT + [SIGEMT] = "SIGEMT", +#endif +#ifdef SIGBUS + [SIGBUS] = "SIGBUS", +#endif +#ifdef SIGFPE + [SIGFPE] = "SIGFPE", +#endif +#ifdef SIGKILL + [SIGKILL] = "SIGKILL", +#endif +#ifdef SIGUSR1 + [SIGUSR1] = "SIGUSR1", +#endif +#ifdef SIGSEGV + [SIGSEGV] = "SIGSEGV", +#endif +#ifdef SIGUSR2 + [SIGUSR2] = "SIGUSR2", +#endif +#ifdef SIGPIPE + [SIGPIPE] = "SIGPIPE", +#endif +#ifdef SIGALRM + [SIGALRM] = "SIGALRM", +#endif +#ifdef SIGTERM + [SIGTERM] = "SIGTERM", +#endif +#ifdef SIGCHLD + [SIGCHLD] = "SIGCHLD", +#endif +#ifdef SIGCONT + [SIGCONT] = "SIGCONT", +#endif +#ifdef SIGSTOP + [SIGSTOP] = "SIGSTOP", +#endif +#ifdef SIGTSTP + [SIGTSTP] = "SIGTSTP", +#endif +#ifdef SIGTTIN + [SIGTTIN] = "SIGTTIN", +#endif +#ifdef SIGTTOU + [SIGTTOU] = "SIGTTOU", +#endif +#ifdef SIGURG + [SIGURG] = "SIGURG", +#endif +#ifdef SIGXCPU + [SIGXCPU] = "SIGXCPU", +#endif +#ifdef SIGXFSZ + [SIGXFSZ] = "SIGXFSZ", +#endif +#ifdef SIGVTALRM + [SIGVTALRM] = "SIGVTALRM", +#endif +#ifdef SIGPROF + [SIGPROF] = "SIGPROF", +#endif +#ifdef SIGWINCH + [SIGWINCH] = "SIGWINCH", +#endif +#ifdef SIGINFO + [SIGINFO] = "SIGINFO", +#endif +#ifdef SIGIO + [SIGIO] = "SIGIO", +#endif +#ifdef SIGPWR + [SIGPWR] = "SIGPWR", +#endif +#ifdef SIGSYS + [SIGSYS] = "SIGSYS", +#endif +#ifdef SIGWAITING + [SIGWAITING] = "SIGWAITING", +#endif +#ifdef SIGLWP + [SIGLWP] = "SIGLWP", +#endif +#ifdef SIGFREEZE + [SIGFREEZE] = "SIGFREEZE", +#endif +#ifdef SIGTHAW + [SIGTHAW] = "SIGTHAW", +#endif +#ifdef SIGCANCEL + [SIGCANCEL] = "SIGCANCEL", +#endif +#ifdef SIGLOST + [SIGLOST] = "SIGLOST", +#endif +}; + +static void sig_soft_quit(int signo) /* {{{ */ +{ + int saved_errno = errno; + + /* closing fastcgi listening socket will force fcgi_accept() exit immediately */ + close(0); + socket(AF_UNIX, SOCK_STREAM, 0); + fpm_php_soft_quit(); + errno = saved_errno; +} +/* }}} */ + +static void sig_handler(int signo) /* {{{ */ +{ + static const char sig_chars[NSIG + 1] = { + [SIGTERM] = 'T', + [SIGINT] = 'I', + [SIGUSR1] = '1', + [SIGUSR2] = '2', + [SIGQUIT] = 'Q', + [SIGCHLD] = 'C' + }; + char s; + int saved_errno; + + if (fpm_globals.parent_pid != getpid()) { + /* prevent a signal race condition when child process + have not set up it's own signal handler yet */ + return; + } + + saved_errno = errno; + s = sig_chars[signo]; + write(sp[1], &s, sizeof(s)); + errno = saved_errno; +} +/* }}} */ + +int fpm_signals_init_main() /* {{{ */ +{ + struct sigaction act; + + if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) { + zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()"); + return -1; + } + + if (0 > fd_set_blocked(sp[0], 0) || 0 > fd_set_blocked(sp[1], 0)) { + zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()"); + return -1; + } + + if (0 > fcntl(sp[0], F_SETFD, FD_CLOEXEC) || 0 > fcntl(sp[1], F_SETFD, FD_CLOEXEC)) { + zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)"); + return -1; + } + + memset(&act, 0, sizeof(act)); + act.sa_handler = sig_handler; + sigfillset(&act.sa_mask); + + if (0 > sigaction(SIGTERM, &act, 0) || + 0 > sigaction(SIGINT, &act, 0) || + 0 > sigaction(SIGUSR1, &act, 0) || + 0 > sigaction(SIGUSR2, &act, 0) || + 0 > sigaction(SIGCHLD, &act, 0) || + 0 > sigaction(SIGQUIT, &act, 0)) { + + zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()"); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_signals_init_child() /* {{{ */ +{ + struct sigaction act, act_dfl; + + memset(&act, 0, sizeof(act)); + memset(&act_dfl, 0, sizeof(act_dfl)); + + act.sa_handler = &sig_soft_quit; + act.sa_flags |= SA_RESTART; + + act_dfl.sa_handler = SIG_DFL; + + close(sp[0]); + close(sp[1]); + + if (0 > sigaction(SIGTERM, &act_dfl, 0) || + 0 > sigaction(SIGINT, &act_dfl, 0) || + 0 > sigaction(SIGUSR1, &act_dfl, 0) || + 0 > sigaction(SIGUSR2, &act_dfl, 0) || + 0 > sigaction(SIGCHLD, &act_dfl, 0) || + 0 > sigaction(SIGQUIT, &act, 0)) { + + zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()"); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_signals_get_fd() /* {{{ */ +{ + return sp[0]; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_signals.h b/sapi/fpm/fpm/fpm_signals.h new file mode 100644 index 0000000..eb80fae --- /dev/null +++ b/sapi/fpm/fpm/fpm_signals.h @@ -0,0 +1,16 @@ + + /* $Id: fpm_signals.h,v 1.5 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_SIGNALS_H +#define FPM_SIGNALS_H 1 + +#include <signal.h> + +int fpm_signals_init_main(); +int fpm_signals_init_child(); +int fpm_signals_get_fd(); + +extern const char *fpm_signal_names[NSIG + 1]; + +#endif diff --git a/sapi/fpm/fpm/fpm_sockets.c b/sapi/fpm/fpm/fpm_sockets.c new file mode 100644 index 0000000..76759e7 --- /dev/null +++ b/sapi/fpm/fpm/fpm_sockets.c @@ -0,0 +1,477 @@ + + /* $Id: fpm_sockets.c,v 1.20.2.1 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#ifdef HAVE_ALLOCA_H +#include <alloca.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> /* for chmod(2) */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include "zlog.h" +#include "fpm_arrays.h" +#include "fpm_sockets.h" +#include "fpm_worker_pool.h" +#include "fpm_unix.h" +#include "fpm_str.h" +#include "fpm_env.h" +#include "fpm_cleanup.h" +#include "fpm_scoreboard.h" + +struct listening_socket_s { + int refcount; + int sock; + int type; + char *key; +}; + +static struct fpm_array_s sockets_list; + +static int fpm_sockets_resolve_af_inet(char *node, char *service, struct sockaddr_in *addr) /* {{{ */ +{ + struct addrinfo *res; + struct addrinfo hints; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + ret = getaddrinfo(node, service, &hints, &res); + + if (ret != 0) { + zlog(ZLOG_ERROR, "can't resolve hostname '%s%s%s': getaddrinfo said: %s%s%s\n", + node, service ? ":" : "", service ? service : "", + gai_strerror(ret), ret == EAI_SYSTEM ? ", system error: " : "", ret == EAI_SYSTEM ? strerror(errno) : ""); + return -1; + } + + *addr = *(struct sockaddr_in *) res->ai_addr; + freeaddrinfo(res); + return 0; +} +/* }}} */ + +enum { FPM_GET_USE_SOCKET = 1, FPM_STORE_SOCKET = 2, FPM_STORE_USE_SOCKET = 3 }; + +static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */ +{ + unsigned i; + char *env_value = 0; + int p = 0; + struct listening_socket_s *ls = sockets_list.data; + + for (i = 0; i < sockets_list.used; i++, ls++) { + if (which != FPM_CLEANUP_PARENT_EXEC) { + close(ls->sock); + } else { /* on PARENT EXEC we want socket fds to be inherited through environment variable */ + char fd[32]; + sprintf(fd, "%d", ls->sock); + env_value = realloc(env_value, p + (p ? 1 : 0) + strlen(ls->key) + 1 + strlen(fd) + 1); + p += sprintf(env_value + p, "%s%s=%s", p ? "," : "", ls->key, fd); + } + + if (which == FPM_CLEANUP_PARENT_EXIT_MAIN) { + if (ls->type == FPM_AF_UNIX) { + unlink(ls->key); + } + } + free(ls->key); + } + + if (env_value) { + setenv("FPM_SOCKETS", env_value, 1); + free(env_value); + } + + fpm_array_free(&sockets_list); +} +/* }}} */ + +static int fpm_sockets_hash_op(int sock, struct sockaddr *sa, char *key, int type, int op) /* {{{ */ +{ + if (key == NULL) { + switch (type) { + case FPM_AF_INET : { + struct sockaddr_in *sa_in = (struct sockaddr_in *) sa; + key = alloca(sizeof("xxx.xxx.xxx.xxx:ppppp")); + sprintf(key, "%u.%u.%u.%u:%u", IPQUAD(&sa_in->sin_addr), (unsigned int) ntohs(sa_in->sin_port)); + break; + } + + case FPM_AF_UNIX : { + struct sockaddr_un *sa_un = (struct sockaddr_un *) sa; + key = alloca(strlen(sa_un->sun_path) + 1); + strcpy(key, sa_un->sun_path); + break; + } + + default : + return -1; + } + } + + switch (op) { + + case FPM_GET_USE_SOCKET : + { + unsigned i; + struct listening_socket_s *ls = sockets_list.data; + + for (i = 0; i < sockets_list.used; i++, ls++) { + if (!strcmp(ls->key, key)) { + ++ls->refcount; + return ls->sock; + } + } + break; + } + + case FPM_STORE_SOCKET : /* inherited socket */ + case FPM_STORE_USE_SOCKET : /* just created */ + { + struct listening_socket_s *ls; + + ls = fpm_array_push(&sockets_list); + if (!ls) { + break; + } + + if (op == FPM_STORE_SOCKET) { + ls->refcount = 0; + } else { + ls->refcount = 1; + } + ls->type = type; + ls->sock = sock; + ls->key = strdup(key); + + return 0; + } + } + return -1; +} +/* }}} */ + +static int fpm_sockets_new_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */ +{ + int flags = 1; + int sock; + mode_t saved_umask; + + sock = socket(sa->sa_family, SOCK_STREAM, 0); + + if (0 > sock) { + zlog(ZLOG_SYSERROR, "failed to create new listening socket: socket()"); + return -1; + } + + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)); + + if (wp->listen_address_domain == FPM_AF_UNIX) { + if (fpm_socket_unix_test_connect((struct sockaddr_un *)sa, socklen) == 0) { + zlog(ZLOG_ERROR, "An another FPM instance seems to already listen on %s", ((struct sockaddr_un *) sa)->sun_path); + return -1; + } + unlink( ((struct sockaddr_un *) sa)->sun_path); + saved_umask = umask(0777 ^ wp->socket_mode); + } + + if (0 > bind(sock, sa, socklen)) { + zlog(ZLOG_SYSERROR, "unable to bind listening socket for address '%s'", wp->config->listen_address); + if (wp->listen_address_domain == FPM_AF_UNIX) { + umask(saved_umask); + } + return -1; + } + + if (wp->listen_address_domain == FPM_AF_UNIX) { + char *path = ((struct sockaddr_un *) sa)->sun_path; + + umask(saved_umask); + + if (wp->socket_uid != -1 || wp->socket_gid != -1) { + if (0 > chown(path, wp->socket_uid, wp->socket_gid)) { + zlog(ZLOG_SYSERROR, "failed to chown() the socket '%s'", wp->config->listen_address); + return -1; + } + } + } + + if (0 > listen(sock, wp->config->listen_backlog)) { + zlog(ZLOG_SYSERROR, "failed to listen to address '%s'", wp->config->listen_address); + return -1; + } + + return sock; +} +/* }}} */ + +static int fpm_sockets_get_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */ +{ + int sock; + + sock = fpm_sockets_hash_op(0, sa, 0, wp->listen_address_domain, FPM_GET_USE_SOCKET); + if (sock >= 0) { + return sock; + } + + sock = fpm_sockets_new_listening_socket(wp, sa, socklen); + fpm_sockets_hash_op(sock, sa, 0, wp->listen_address_domain, FPM_STORE_USE_SOCKET); + + return sock; +} +/* }}} */ + +enum fpm_address_domain fpm_sockets_domain_from_address(char *address) /* {{{ */ +{ + if (strchr(address, ':')) { + return FPM_AF_INET; + } + + if (strlen(address) == strspn(address, "0123456789")) { + return FPM_AF_INET; + } + return FPM_AF_UNIX; +} +/* }}} */ + +static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct sockaddr_in sa_in; + char *dup_address = strdup(wp->config->listen_address); + char *port_str = strchr(dup_address, ':'); + char *addr = NULL; + int port = 0; + + if (port_str) { /* this is host:port pair */ + *port_str++ = '\0'; + port = atoi(port_str); + addr = dup_address; + } else if (strlen(dup_address) == strspn(dup_address, "0123456789")) { /* this is port */ + port = atoi(dup_address); + port_str = dup_address; + } + + if (port == 0) { + zlog(ZLOG_ERROR, "invalid port value '%s'", port_str); + return -1; + } + + memset(&sa_in, 0, sizeof(sa_in)); + + if (addr) { + sa_in.sin_addr.s_addr = inet_addr(addr); + if (sa_in.sin_addr.s_addr == INADDR_NONE) { /* do resolve */ + if (0 > fpm_sockets_resolve_af_inet(addr, NULL, &sa_in)) { + return -1; + } + zlog(ZLOG_NOTICE, "address '%s' resolved as %u.%u.%u.%u", addr, IPQUAD(&sa_in.sin_addr)); + } + } else { + sa_in.sin_addr.s_addr = htonl(INADDR_ANY); + } + sa_in.sin_family = AF_INET; + sa_in.sin_port = htons(port); + free(dup_address); + return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_in, sizeof(struct sockaddr_in)); +} +/* }}} */ + +static int fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct sockaddr_un sa_un; + + memset(&sa_un, 0, sizeof(sa_un)); + strlcpy(sa_un.sun_path, wp->config->listen_address, sizeof(sa_un.sun_path)); + sa_un.sun_family = AF_UNIX; + return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_un, sizeof(struct sockaddr_un)); +} +/* }}} */ + +int fpm_sockets_init_main() /* {{{ */ +{ + unsigned i, lq_len; + struct fpm_worker_pool_s *wp; + char *inherited = getenv("FPM_SOCKETS"); + struct listening_socket_s *ls; + + if (0 == fpm_array_init(&sockets_list, sizeof(struct listening_socket_s), 10)) { + return -1; + } + + /* import inherited sockets */ + while (inherited && *inherited) { + char *comma = strchr(inherited, ','); + int type, fd_no; + char *eq; + + if (comma) { + *comma = '\0'; + } + + eq = strchr(inherited, '='); + if (eq) { + *eq = '\0'; + fd_no = atoi(eq + 1); + type = fpm_sockets_domain_from_address(inherited); + zlog(ZLOG_NOTICE, "using inherited socket fd=%d, \"%s\"", fd_no, inherited); + fpm_sockets_hash_op(fd_no, 0, inherited, type, FPM_STORE_SOCKET); + } + + if (comma) { + inherited = comma + 1; + } else { + inherited = 0; + } + } + + /* create all required sockets */ + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + switch (wp->listen_address_domain) { + case FPM_AF_INET : + wp->listening_socket = fpm_socket_af_inet_listening_socket(wp); + break; + + case FPM_AF_UNIX : + if (0 > fpm_unix_resolve_socket_premissions(wp)) { + return -1; + } + wp->listening_socket = fpm_socket_af_unix_listening_socket(wp); + break; + } + + if (wp->listening_socket == -1) { + return -1; + } + + if (wp->listen_address_domain == FPM_AF_INET && fpm_socket_get_listening_queue(wp->listening_socket, NULL, &lq_len) >= 0) { + fpm_scoreboard_update(-1, -1, -1, (int)lq_len, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard); + } + } + + /* close unused sockets that was inherited */ + ls = sockets_list.data; + + for (i = 0; i < sockets_list.used; ) { + if (ls->refcount == 0) { + close(ls->sock); + if (ls->type == FPM_AF_UNIX) { + unlink(ls->key); + } + free(ls->key); + fpm_array_item_remove(&sockets_list, i); + } else { + ++i; + ++ls; + } + } + + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_sockets_cleanup, 0)) { + return -1; + } + return 0; +} +/* }}} */ + +#if HAVE_FPM_LQ + +#ifdef HAVE_LQ_TCP_INFO + +#include <netinet/tcp.h> + +int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq) +{ + struct tcp_info info; + socklen_t len = sizeof(info); + + if (0 > getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &len)) { + zlog(ZLOG_SYSERROR, "failed to retrieve TCP_INFO for socket"); + return -1; + } + + /* kernel >= 2.6.24 return non-zero here, that means operation is supported */ + if (info.tcpi_sacked == 0) { + return -1; + } + + if (cur_lq) { + *cur_lq = info.tcpi_unacked; + } + + if (max_lq) { + *max_lq = info.tcpi_sacked; + } + + return 0; +} + +#endif + +#ifdef HAVE_LQ_SO_LISTENQ + +int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq) +{ + int val; + socklen_t len = sizeof(val); + + if (cur_lq) { + if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLEN, &val, &len)) { + return -1; + } + + *cur_lq = val; + } + + if (max_lq) { + if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &val, &len)) { + return -1; + } + + *max_lq = val; + } + + return 0; +} + +#endif + +#else + +int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq) +{ + return -1; +} + +#endif + +int fpm_socket_unix_test_connect(struct sockaddr_un *sock, size_t socklen) /* {{{ */ +{ + int fd; + + if (!sock || sock->sun_family != AF_UNIX) { + return -1; + } + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + return -1; + } + + if (connect(fd, (struct sockaddr *)sock, socklen) == -1) { + return -1; + } + + close(fd); + return 0; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_sockets.h b/sapi/fpm/fpm/fpm_sockets.h new file mode 100644 index 0000000..cce5712 --- /dev/null +++ b/sapi/fpm/fpm/fpm_sockets.h @@ -0,0 +1,54 @@ + + /* $Id: fpm_sockets.h,v 1.12 2008/08/26 15:09:15 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_MISC_H +#define FPM_MISC_H 1 + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <fcntl.h> + +#include "fpm_worker_pool.h" + +/* + On FreeBSD and OpenBSD, backlog negative values are truncated to SOMAXCONN +*/ +#if (__FreeBSD__) || (__OpenBSD__) +#define FPM_BACKLOG_DEFAULT -1 +#else +#define FPM_BACKLOG_DEFAULT 128 +#endif + +enum fpm_address_domain fpm_sockets_domain_from_address(char *addr); +int fpm_sockets_init_main(); +int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq); +int fpm_socket_unix_test_connect(struct sockaddr_un *sock, size_t socklen); + + +static inline int fd_set_blocked(int fd, int blocked) /* {{{ */ +{ + int flags = fcntl(fd, F_GETFL); + + if (flags < 0) { + return -1; + } + + if (blocked) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + return fcntl(fd, F_SETFL, flags); +} +/* }}} */ + +#define IPQUAD(sin_addr) \ + (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[0], \ + (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[1], \ + (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[2], \ + (unsigned int) ((unsigned char *) &(sin_addr)->s_addr)[3] + +#endif diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c new file mode 100644 index 0000000..2363b57 --- /dev/null +++ b/sapi/fpm/fpm/fpm_status.c @@ -0,0 +1,477 @@ + + /* $Id$ */ + /* (c) 2009 Jerome Loyet */ + +#include "php.h" +#include "SAPI.h" +#include <stdio.h> + +#include "fpm_config.h" +#include "fpm_scoreboard.h" +#include "fpm_status.h" +#include "fpm_clock.h" +#include "fpm_scoreboard.h" +#include "zlog.h" +#include "fpm_atomic.h" +#include "fpm_conf.h" +#include "fpm_php.h" +#include <ext/standard/html.h> + +static char *fpm_status_uri = NULL; +static char *fpm_status_ping_uri = NULL; +static char *fpm_status_ping_response = NULL; + + +int fpm_status_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + if (!wp || !wp->config) { + zlog(ZLOG_ERROR, "unable to init fpm_status because conf structure is NULL"); + return -1; + } + + if (wp->config->pm_status_path) { + fpm_status_uri = strdup(wp->config->pm_status_path); + } + + if (wp->config->ping_path) { + if (!wp->config->ping_response) { + zlog(ZLOG_ERROR, "[pool %s] ping is set (%s) but ping.response is not set.", wp->config->name, wp->config->ping_path); + return -1; + } + fpm_status_ping_uri = strdup(wp->config->ping_path); + fpm_status_ping_response = strdup(wp->config->ping_response); + } + + return 0; +} +/* }}} */ + +int fpm_status_handle_request(TSRMLS_D) /* {{{ */ +{ + struct fpm_scoreboard_s scoreboard, *scoreboard_p; + struct fpm_scoreboard_proc_s proc; + char *buffer, *time_format, time_buffer[64]; + time_t now_epoch; + int full, encode; + char *short_syntax, *short_post; + char *full_pre, *full_syntax, *full_post, *full_separator; + + if (!SG(request_info).request_uri) { + return 0; + } + + /* PING */ + if (fpm_status_ping_uri && fpm_status_ping_response && !strcmp(fpm_status_ping_uri, SG(request_info).request_uri)) { + fpm_request_executing(); + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC); + SG(sapi_headers).http_response_code = 200; + + /* handle HEAD */ + if (SG(request_info).headers_only) { + return 1; + } + + PUTS(fpm_status_ping_response); + return 1; + } + + /* STATUS */ + if (fpm_status_uri && !strcmp(fpm_status_uri, SG(request_info).request_uri)) { + fpm_request_executing(); + + scoreboard_p = fpm_scoreboard_get(); + if (!scoreboard_p) { + zlog(ZLOG_ERROR, "status: unable to find or access status shared memory"); + SG(sapi_headers).http_response_code = 500; + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC); + PUTS("Internal error. Please review log file for errors."); + return 1; + } + + if (!fpm_spinlock(&scoreboard_p->lock, 1)) { + zlog(ZLOG_NOTICE, "[pool %s] status: scoreboard already in used.", scoreboard_p->pool); + SG(sapi_headers).http_response_code = 503; + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC); + PUTS("Server busy. Please try again later."); + return 1; + } + /* copy the scoreboard not to bother other processes */ + scoreboard = *scoreboard_p; + fpm_unlock(scoreboard_p->lock); + + if (scoreboard.idle < 0 || scoreboard.active < 0) { + zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard.pool); + SG(sapi_headers).http_response_code = 500; + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC); + PUTS("Internal error. Please review log file for errors."); + return 1; + } + + /* send common headers */ + sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1 TSRMLS_CC); + sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1 TSRMLS_CC); + SG(sapi_headers).http_response_code = 200; + + /* handle HEAD */ + if (SG(request_info).headers_only) { + return 1; + } + + /* full status ? */ + full = (fpm_php_get_string_from_table("_GET", "full" TSRMLS_CC) != NULL); + short_syntax = short_post = NULL; + full_separator = full_pre = full_syntax = full_post = NULL; + encode = 0; + + /* HTML */ + if (fpm_php_get_string_from_table("_GET", "html" TSRMLS_CC)) { + sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1 TSRMLS_CC); + time_format = "%d/%b/%Y:%H:%M:%S %z"; + encode = 1; + + short_syntax = + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" + "<head><title>PHP-FPM Status Page</title></head>\n" + "<body>\n" + "<table>\n" + "<tr><th>pool</th><td>%s</td></tr>\n" + "<tr><th>process manager</th><td>%s</td></tr>\n" + "<tr><th>start time</th><td>%s</td></tr>\n" + "<tr><th>start since</th><td>%lu</td></tr>\n" + "<tr><th>accepted conn</th><td>%lu</td></tr>\n" +#ifdef HAVE_FPM_LQ + "<tr><th>listen queue</th><td>%u</td></tr>\n" + "<tr><th>max listen queue</th><td>%u</td></tr>\n" + "<tr><th>listen queue len</th><td>%d</td></tr>\n" +#endif + "<tr><th>idle processes</th><td>%d</td></tr>\n" + "<tr><th>active processes</th><td>%d</td></tr>\n" + "<tr><th>total processes</th><td>%d</td></tr>\n" + "<tr><th>max active processes</th><td>%d</td></tr>\n" + "<tr><th>max children reached</th><td>%u</td></tr>\n" + "<tr><th>slow requests</th><td>%lu</td></tr>\n" + "</table>\n"; + + if (!full) { + short_post = "</body></html>"; + } else { + full_pre = + "<table border=\"1\">\n" + "<tr>" + "<th>pid</th>" + "<th>state</th>" + "<th>start time</th>" + "<th>start since</th>" + "<th>requests</th>" + "<th>request duration</th>" + "<th>request method</th>" + "<th>request uri</th>" + "<th>content length</th>" + "<th>user</th>" + "<th>script</th>" +#ifdef HAVE_FPM_LQ + "<th>last request cpu</th>" +#endif + "<th>last request memory</th>" + "</tr>\n"; + + full_syntax = + "<tr>" + "<td>%d</td>" + "<td>%s</td>" + "<td>%s</td>" + "<td>%lu</td>" + "<td>%lu</td>" + "<td>%lu</td>" + "<td>%s</td>" + "<td>%s%s%s</td>" + "<td>%zu</td>" + "<td>%s</td>" + "<td>%s</td>" +#ifdef HAVE_FPM_LQ + "<td>%.2f</td>" +#endif + "<td>%zu</td>" + "</tr>\n"; + + full_post = "</table></body></html>"; + } + + /* XML */ + } else if (fpm_php_get_string_from_table("_GET", "xml" TSRMLS_CC)) { + sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1 TSRMLS_CC); + time_format = "%s"; + encode = 1; + + short_syntax = + "<?xml version=\"1.0\" ?>\n" + "<status>\n" + "<pool>%s</pool>\n" + "<process-manager>%s</process-manager>\n" + "<start-time>%s</start-time>\n" + "<start-since>%lu</start-since>\n" + "<accepted-conn>%lu</accepted-conn>\n" +#ifdef HAVE_FPM_LQ + "<listen-queue>%u</listen-queue>\n" + "<max-listen-queue>%u</max-listen-queue>\n" + "<listen-queue-len>%d</listen-queue-len>\n" +#endif + "<idle-processes>%d</idle-processes>\n" + "<active-processes>%d</active-processes>\n" + "<total-processes>%d</total-processes>\n" + "<max-active-processes>%d</max-active-processes>\n" + "<max-children-reached>%u</max-children-reached>\n" + "<slow-requests>%lu</slow-requests>\n"; + + if (!full) { + short_post = "</status>"; + } else { + full_pre = "<processes>\n"; + full_syntax = + "<process>" + "<pid>%d</pid>" + "<state>%s</state>" + "<start-time>%s</start-time>" + "<start-since>%lu</start-since>" + "<requests>%lu</requests>" + "<request-duration>%lu</request-duration>" + "<request-method>%s</request-method>" + "<request-uri>%s%s%s</request-uri>" + "<content-length>%zu</content-length>" + "<user>%s</user>" + "<script>%s</script>" +#ifdef HAVE_FPM_LQ + "<last-request-cpu>%.2f</last-request-cpu>" +#endif + "<last-request-memory>%zu</last-request-memory>" + "</process>\n" + ; + full_post = "</processes>\n</status>"; + } + + /* JSON */ + } else if (fpm_php_get_string_from_table("_GET", "json" TSRMLS_CC)) { + sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1 TSRMLS_CC); + time_format = "%s"; + + short_syntax = + "{" + "\"pool\":\"%s\"," + "\"process manager\":\"%s\"," + "\"start time\":%s," + "\"start since\":%lu," + "\"accepted conn\":%lu," +#ifdef HAVE_FPM_LQ + "\"listen queue\":%u," + "\"max listen queue\":%u," + "\"listen queue len\":%d," +#endif + "\"idle processes\":%d," + "\"active processes\":%d," + "\"total processes\":%d," + "\"max active processes\":%d," + "\"max children reached\":%u," + "\"slow requests\":%lu"; + + if (!full) { + short_post = "}"; + } else { + full_separator = ","; + full_pre = ", \"processes\":["; + + full_syntax = "{" + "\"pid\":%d," + "\"state\":\"%s\"," + "\"start time\":%s," + "\"start since\":%lu," + "\"requests\":%lu," + "\"request duration\":%lu," + "\"request method\":\"%s\"," + "\"request uri\":\"%s%s%s\"," + "\"content length\":%zu," + "\"user\":\"%s\"," + "\"script\":\"%s\"," +#ifdef HAVE_FPM_LQ + "\"last request cpu\":%.2f," +#endif + "\"last request memory\":%zu" + "}"; + + full_post = "]}"; + } + + /* TEXT */ + } else { + sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1 TSRMLS_CC); + time_format = "%d/%b/%Y:%H:%M:%S %z"; + + short_syntax = + "pool: %s\n" + "process manager: %s\n" + "start time: %s\n" + "start since: %lu\n" + "accepted conn: %lu\n" +#ifdef HAVE_FPM_LQ + "listen queue: %u\n" + "max listen queue: %u\n" + "listen queue len: %d\n" +#endif + "idle processes: %d\n" + "active processes: %d\n" + "total processes: %d\n" + "max active processes: %d\n" + "max children reached: %u\n" + "slow requests: %lu\n"; + + if (full) { + full_syntax = + "\n" + "************************\n" + "pid: %d\n" + "state: %s\n" + "start time: %s\n" + "start since: %lu\n" + "requests: %lu\n" + "request duration: %lu\n" + "request method: %s\n" + "request URI: %s%s%s\n" + "content length: %zu\n" + "user: %s\n" + "script: %s\n" +#ifdef HAVE_FPM_LQ + "last request cpu: %.2f\n" +#endif + "last request memory: %zu\n"; + } + } + + strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard.start_epoch)); + now_epoch = time(NULL); + spprintf(&buffer, 0, short_syntax, + scoreboard.pool, + PM2STR(scoreboard.pm), + time_buffer, + now_epoch - scoreboard.start_epoch, + scoreboard.requests, +#ifdef HAVE_FPM_LQ + scoreboard.lq, + scoreboard.lq_max, + scoreboard.lq_len, +#endif + scoreboard.idle, + scoreboard.active, + scoreboard.idle + scoreboard.active, + scoreboard.active_max, + scoreboard.max_children_reached, + scoreboard.slow_rq); + + PUTS(buffer); + efree(buffer); + + if (short_post) { + PUTS(short_post); + } + + /* no need to test the var 'full' */ + if (full_syntax) { + int i, first; + size_t len; + char *query_string; + struct timeval duration, now; +#ifdef HAVE_FPM_LQ + float cpu; +#endif + + fpm_clock_get(&now); + + if (full_pre) { + PUTS(full_pre); + } + + first = 1; + for (i=0; i<scoreboard_p->nprocs; i++) { + if (!scoreboard_p->procs[i] || !scoreboard_p->procs[i]->used) { + continue; + } + proc = *scoreboard_p->procs[i]; + + if (first) { + first = 0; + } else { + if (full_separator) { + PUTS(full_separator); + } + } + + query_string = NULL; + len = 0; + if (proc.query_string[0] != '\0') { + if (!encode) { + query_string = proc.query_string; + } else { + query_string = php_escape_html_entities_ex((unsigned char *)proc.query_string, strlen(proc.query_string), &len, 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, 1 TSRMLS_CC); + } + } + +#ifdef HAVE_FPM_LQ + /* prevent NaN */ + if (proc.cpu_duration.tv_sec == 0 && proc.cpu_duration.tv_usec == 0) { + cpu = 0.; + } else { + cpu = (proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime) / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.; + } +#endif + + if (proc.request_stage == FPM_REQUEST_ACCEPTING) { + duration = proc.duration; + } else { + timersub(&now, &proc.accepted, &duration); + } + strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&proc.start_epoch)); + spprintf(&buffer, 0, full_syntax, + proc.pid, + fpm_request_get_stage_name(proc.request_stage), + time_buffer, + now_epoch - proc.start_epoch, + proc.requests, + duration.tv_sec * 1000000UL + duration.tv_usec, + proc.request_method[0] != '\0' ? proc.request_method : "-", + proc.request_uri[0] != '\0' ? proc.request_uri : "-", + query_string ? "?" : "", + query_string ? query_string : "", + proc.content_length, + proc.auth_user[0] != '\0' ? proc.auth_user : "-", + proc.script_filename[0] != '\0' ? proc.script_filename : "-", +#ifdef HAVE_FPM_LQ + proc.request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., +#endif + proc.request_stage == FPM_REQUEST_ACCEPTING ? proc.memory : 0); + PUTS(buffer); + efree(buffer); + + if (len > 0 && query_string) { + efree(query_string); + } + } + + if (full_post) { + PUTS(full_post); + } + } + + return 1; + } + + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_status.h b/sapi/fpm/fpm/fpm_status.h new file mode 100644 index 0000000..8f3daf9 --- /dev/null +++ b/sapi/fpm/fpm/fpm_status.h @@ -0,0 +1,35 @@ + + /* $Id$ */ + /* (c) 2009 Jerome Loyet */ + +#ifndef FPM_STATUS_H +#define FPM_STATUS_H 1 +#include "fpm_worker_pool.h" +#include "fpm_shm.h" + +#define FPM_STATUS_BUFFER_SIZE 512 + +struct fpm_status_s { + int pm; + int idle; + int active; + int total; + unsigned cur_lq; + int max_lq; + unsigned long int accepted_conn; + unsigned int max_children_reached; + struct timeval last_update; +}; + +int fpm_status_init_child(struct fpm_worker_pool_s *wp); +void fpm_status_update_activity(struct fpm_shm_s *shm, int idle, int active, int total, unsigned cur_lq, int max_lq, int clear_last_update); +void fpm_status_update_accepted_conn(struct fpm_shm_s *shm, unsigned long int accepted_conn); +void fpm_status_increment_accepted_conn(struct fpm_shm_s *shm); +void fpm_status_set_pm(struct fpm_shm_s *shm, int pm); +void fpm_status_update_max_children_reached(struct fpm_shm_s *shm, unsigned int max_children_reached); +void fpm_status_increment_max_children_reached(struct fpm_shm_s *shm); +int fpm_status_handle_request(TSRMLS_D); + +extern struct fpm_shm_s *fpm_status_shm; + +#endif diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c new file mode 100644 index 0000000..6a587d0 --- /dev/null +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -0,0 +1,301 @@ + + /* $Id: fpm_stdio.c,v 1.22.2.2 2008/12/13 03:32:24 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#include "php_syslog.h" + +#include "fpm.h" +#include "fpm_children.h" +#include "fpm_events.h" +#include "fpm_sockets.h" +#include "fpm_stdio.h" +#include "zlog.h" + +static int fd_stdout[2]; +static int fd_stderr[2]; + +int fpm_stdio_init_main() /* {{{ */ +{ + int fd = open("/dev/null", O_RDWR); + + if (0 > fd) { + zlog(ZLOG_SYSERROR, "failed to init stdio: open(\"/dev/null\")"); + return -1; + } + + if (0 > dup2(fd, STDIN_FILENO) || 0 > dup2(fd, STDOUT_FILENO)) { + zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()"); + return -1; + } + close(fd); + return 0; +} +/* }}} */ + +int fpm_stdio_init_final() /* {{{ */ +{ + if (fpm_global_config.daemonize) { + /* prevent duping if logging to syslog */ + if (fpm_globals.error_log_fd > 0 && fpm_globals.error_log_fd != STDERR_FILENO) { + + /* there might be messages to stderr from other parts of the code, we need to log them all */ + if (0 > dup2(fpm_globals.error_log_fd, STDERR_FILENO)) { + zlog(ZLOG_SYSERROR, "failed to init stdio: dup2()"); + return -1; + } + } + } + zlog_set_launched(); + return 0; +} +/* }}} */ + +int fpm_stdio_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ +#ifdef HAVE_SYSLOG_H + if (fpm_globals.error_log_fd == ZLOG_SYSLOG) { + closelog(); /* ensure to close syslog not to interrupt with PHP syslog code */ + } else +#endif + if (fpm_globals.error_log_fd > 0) { + close(fpm_globals.error_log_fd); + } + fpm_globals.error_log_fd = -1; + zlog_set_fd(-1); + + if (wp->listening_socket != STDIN_FILENO) { + if (0 > dup2(wp->listening_socket, STDIN_FILENO)) { + zlog(ZLOG_SYSERROR, "failed to init child stdio: dup2()"); + return -1; + } + } + return 0; +} +/* }}} */ + +static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ +{ + static const int max_buf_size = 1024; + int fd = ev->fd; + char buf[max_buf_size]; + struct fpm_child_s *child; + int is_stdout; + struct fpm_event_s *event; + int fifo_in = 1, fifo_out = 1; + int is_last_message = 0; + int in_buf = 0; + int res; + + if (!arg) { + return; + } + child = (struct fpm_child_s *)arg; + is_stdout = (fd == child->fd_stdout); + if (is_stdout) { + event = &child->ev_stdout; + } else { + event = &child->ev_stderr; + } + + while (fifo_in || fifo_out) { + if (fifo_in) { + res = read(fd, buf + in_buf, max_buf_size - 1 - in_buf); + if (res <= 0) { /* no data */ + fifo_in = 0; + if (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + /* just no more data ready */ + } else { /* error or pipe is closed */ + + if (res < 0) { /* error */ + zlog(ZLOG_SYSERROR, "unable to read what child say"); + } + + fpm_event_del(event); + is_last_message = 1; + + if (is_stdout) { + close(child->fd_stdout); + child->fd_stdout = -1; + } else { + close(child->fd_stderr); + child->fd_stderr = -1; + } + } + } else { + in_buf += res; + } + } + + if (fifo_out) { + if (in_buf == 0) { + fifo_out = 0; + } else { + char *nl; + int should_print = 0; + buf[in_buf] = '\0'; + + /* FIXME: there might be binary data */ + + /* we should print if no more space in the buffer */ + if (in_buf == max_buf_size - 1) { + should_print = 1; + } + + /* we should print if no more data to come */ + if (!fifo_in) { + should_print = 1; + } + + nl = strchr(buf, '\n'); + if (nl || should_print) { + + if (nl) { + *nl = '\0'; + } + + zlog(ZLOG_WARNING, "[pool %s] child %d said into %s: \"%s\"%s", child->wp->config->name, + (int) child->pid, is_stdout ? "stdout" : "stderr", buf, is_last_message ? ", pipe is closed" : ""); + + if (nl) { + int out_buf = 1 + nl - buf; + memmove(buf, buf + out_buf, in_buf - out_buf); + in_buf -= out_buf; + } else { + in_buf = 0; + } + } + } + } + } +} +/* }}} */ + +int fpm_stdio_prepare_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + if (0 > pipe(fd_stdout)) { + zlog(ZLOG_SYSERROR, "failed to prepare the stdout pipe"); + return -1; + } + + if (0 > pipe(fd_stderr)) { + zlog(ZLOG_SYSERROR, "failed to prepare the stderr pipe"); + close(fd_stdout[0]); + close(fd_stdout[1]); + return -1; + } + + if (0 > fd_set_blocked(fd_stdout[0], 0) || 0 > fd_set_blocked(fd_stderr[0], 0)) { + zlog(ZLOG_SYSERROR, "failed to unblock pipes"); + close(fd_stdout[0]); + close(fd_stdout[1]); + close(fd_stderr[0]); + close(fd_stderr[1]); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + close(fd_stdout[1]); + close(fd_stderr[1]); + + child->fd_stdout = fd_stdout[0]; + child->fd_stderr = fd_stderr[0]; + + fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child); + fpm_event_add(&child->ev_stdout, 0); + + fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child); + fpm_event_add(&child->ev_stderr, 0); + return 0; +} +/* }}} */ + +int fpm_stdio_discard_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (0 == child->wp->config->catch_workers_output) { /* not required */ + return 0; + } + + close(fd_stdout[1]); + close(fd_stderr[1]); + + close(fd_stdout[0]); + close(fd_stderr[0]); + return 0; +} +/* }}} */ + +void fpm_stdio_child_use_pipes(struct fpm_child_s *child) /* {{{ */ +{ + if (child->wp->config->catch_workers_output) { + dup2(fd_stdout[1], STDOUT_FILENO); + dup2(fd_stderr[1], STDERR_FILENO); + close(fd_stdout[0]); close(fd_stdout[1]); + close(fd_stderr[0]); close(fd_stderr[1]); + } else { + /* stdout of parent is always /dev/null */ + dup2(STDOUT_FILENO, STDERR_FILENO); + } +} +/* }}} */ + +int fpm_stdio_open_error_log(int reopen) /* {{{ */ +{ + int fd; + +#ifdef HAVE_SYSLOG_H + if (!strcasecmp(fpm_global_config.error_log, "syslog")) { + openlog(fpm_global_config.syslog_ident, LOG_PID | LOG_CONS, fpm_global_config.syslog_facility); + fpm_globals.error_log_fd = ZLOG_SYSLOG; + if (fpm_global_config.daemonize) { + zlog_set_fd(fpm_globals.error_log_fd); + } + return 0; + } +#endif + + fd = open(fpm_global_config.error_log, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); + if (0 > fd) { + zlog(ZLOG_SYSERROR, "failed to open error_log (%s)", fpm_global_config.error_log); + return -1; + } + + if (reopen) { + if (fpm_global_config.daemonize) { + dup2(fd, STDERR_FILENO); + } + + dup2(fd, fpm_globals.error_log_fd); + close(fd); + fd = fpm_globals.error_log_fd; /* for FD_CLOSEXEC to work */ + } else { + fpm_globals.error_log_fd = fd; + if (fpm_global_config.daemonize) { + zlog_set_fd(fpm_globals.error_log_fd); + } + } + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_stdio.h b/sapi/fpm/fpm/fpm_stdio.h new file mode 100644 index 0000000..d3d61e4 --- /dev/null +++ b/sapi/fpm/fpm/fpm_stdio.h @@ -0,0 +1,20 @@ + + /* $Id: fpm_stdio.h,v 1.9 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_STDIO_H +#define FPM_STDIO_H 1 + +#include "fpm_worker_pool.h" + +int fpm_stdio_init_main(); +int fpm_stdio_init_final(); +int fpm_stdio_init_child(struct fpm_worker_pool_s *wp); +int fpm_stdio_prepare_pipes(struct fpm_child_s *child); +void fpm_stdio_child_use_pipes(struct fpm_child_s *child); +int fpm_stdio_parent_use_pipes(struct fpm_child_s *child); +int fpm_stdio_discard_pipes(struct fpm_child_s *child); +int fpm_stdio_open_error_log(int reopen); + +#endif + diff --git a/sapi/fpm/fpm/fpm_str.h b/sapi/fpm/fpm/fpm_str.h new file mode 100644 index 0000000..65db545 --- /dev/null +++ b/sapi/fpm/fpm/fpm_str.h @@ -0,0 +1,29 @@ + + /* $Id: fpm_str.h,v 1.3 2008/05/24 17:38:47 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_STR_H +#define FPM_STR_H 1 + +static inline char *str_purify_filename(char *dst, char *src, size_t size) /* {{{ */ +{ + char *d, *end; + + d = dst; + end = dst + size - 1; + + for (; d < end && *src; ++d, ++src) { + if (* (unsigned char *) src < ' ' || * (unsigned char *) src > '\x7f') { + *d = '.'; + } else { + *d = *src; + } + } + + *d = '\0'; + + return d; +} +/* }}} */ + +#endif diff --git a/sapi/fpm/fpm/fpm_trace.c b/sapi/fpm/fpm/fpm_trace.c new file mode 100644 index 0000000..366af5a --- /dev/null +++ b/sapi/fpm/fpm/fpm_trace.c @@ -0,0 +1,41 @@ + + /* $Id: fpm_trace.c,v 1.1 2008/07/20 20:59:00 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/types.h> + +#include "fpm_trace.h" + +int fpm_trace_get_strz(char *buf, size_t sz, long addr) /* {{{ */ +{ + int i; + long l; + char *lc = (char *) &l; + + if (0 > fpm_trace_get_long(addr, &l)) { + return -1; + } + + i = l % SIZEOF_LONG; + l -= i; + for (addr = l; ; addr += SIZEOF_LONG) { + if (0 > fpm_trace_get_long(addr, &l)) { + return -1; + } + for ( ; i < SIZEOF_LONG; i++) { + --sz; + if (sz && lc[i]) { + *buf++ = lc[i]; + continue; + } + *buf = '\0'; + return 0; + } + i = 0; + } +} +/* }}} */ + + diff --git a/sapi/fpm/fpm/fpm_trace.h b/sapi/fpm/fpm/fpm_trace.h new file mode 100644 index 0000000..b421172 --- /dev/null +++ b/sapi/fpm/fpm/fpm_trace.h @@ -0,0 +1,17 @@ + + /* $Id: fpm_trace.h,v 1.3 2008/07/20 22:43:39 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_TRACE_H +#define FPM_TRACE_H 1 + +#include <unistd.h> + +int fpm_trace_signal(pid_t pid); +int fpm_trace_ready(pid_t pid); +int fpm_trace_close(pid_t pid); +int fpm_trace_get_long(long addr, long *data); +int fpm_trace_get_strz(char *buf, size_t sz, long addr); + +#endif + diff --git a/sapi/fpm/fpm/fpm_trace_mach.c b/sapi/fpm/fpm/fpm_trace_mach.c new file mode 100644 index 0000000..3b85e6a --- /dev/null +++ b/sapi/fpm/fpm/fpm_trace_mach.c @@ -0,0 +1,99 @@ + + /* $Id: fpm_trace_mach.c,v 1.4 2008/08/26 15:09:15 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <mach/mach.h> +#include <mach/mach_vm.h> + +#include <unistd.h> + +#include "fpm_trace.h" +#include "fpm_process_ctl.h" +#include "fpm_unix.h" +#include "zlog.h" + + +static mach_port_name_t target; +static vm_offset_t target_page_base; +static vm_offset_t local_page; +static mach_msg_type_number_t local_size; + +static void fpm_mach_vm_deallocate() /* {{{ */ +{ + if (local_page) { + mach_vm_deallocate(mach_task_self(), local_page, local_size); + target_page_base = 0; + local_page = 0; + local_size = 0; + } +} +/* }}} */ + +static int fpm_mach_vm_read_page(vm_offset_t page) /* {{{ */ +{ + kern_return_t kr; + + kr = mach_vm_read(target, page, fpm_pagesize, &local_page, &local_size); + if (kr != KERN_SUCCESS) { + zlog(ZLOG_ERROR, "failed to read vm page: mach_vm_read(): %s (%d)", mach_error_string(kr), kr); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_signal(pid_t pid) /* {{{ */ +{ + if (0 > fpm_pctl_kill(pid, FPM_PCTL_STOP)) { + zlog(ZLOG_SYSERROR, "failed to send SIGSTOP to %d", pid); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_ready(pid_t pid) /* {{{ */ +{ + kern_return_t kr; + + kr = task_for_pid(mach_task_self(), pid, &target); + if (kr != KERN_SUCCESS) { + char *msg = ""; + + if (kr == KERN_FAILURE) { + msg = " It seems that master process does not have enough privileges to trace processes."; + } + zlog(ZLOG_ERROR, "task_for_pid() failed: %s (%d)%s", mach_error_string(kr), kr, msg); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_close(pid_t pid) /* {{{ */ +{ + fpm_mach_vm_deallocate(); + target = 0; + return 0; +} +/* }}} */ + +int fpm_trace_get_long(long addr, long *data) /* {{{ */ +{ + size_t offset = ((uintptr_t) (addr) % fpm_pagesize); + vm_offset_t base = (uintptr_t) (addr) - offset; + + if (base != target_page_base) { + fpm_mach_vm_deallocate(); + if (0 > fpm_mach_vm_read_page(base)) { + return -1; + } + } + *data = * (long *) (local_page + offset); + return 0; +} +/* }}} */ + + diff --git a/sapi/fpm/fpm/fpm_trace_pread.c b/sapi/fpm/fpm/fpm_trace_pread.c new file mode 100644 index 0000000..6a61557 --- /dev/null +++ b/sapi/fpm/fpm/fpm_trace_pread.c @@ -0,0 +1,67 @@ + + /* $Id: fpm_trace_pread.c,v 1.7 2008/08/26 15:09:15 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#define _GNU_SOURCE +#define _FILE_OFFSET_BITS 64 + +#include "fpm_config.h" + +#include <unistd.h> + +#include <fcntl.h> +#include <stdio.h> +#if HAVE_INTTYPES_H +# include <inttypes.h> +#else +# include <stdint.h> +#endif + +#include "fpm_trace.h" +#include "fpm_process_ctl.h" +#include "zlog.h" + +static int mem_file = -1; + +int fpm_trace_signal(pid_t pid) /* {{{ */ +{ + if (0 > fpm_pctl_kill(pid, FPM_PCTL_STOP)) { + zlog(ZLOG_SYSERROR, "failed to send SIGSTOP to %d", pid); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_ready(pid_t pid) /* {{{ */ +{ + char buf[128]; + + sprintf(buf, "/proc/%d/" PROC_MEM_FILE, (int) pid); + mem_file = open(buf, O_RDONLY); + if (0 > mem_file) { + zlog(ZLOG_SYSERROR, "failed to open %s", buf); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_close(pid_t pid) /* {{{ */ +{ + close(mem_file); + mem_file = -1; + return 0; +} +/* }}} */ + +int fpm_trace_get_long(long addr, long *data) /* {{{ */ +{ + if (sizeof(*data) != pread(mem_file, (void *) data, sizeof(*data), (uintptr_t) addr)) { + zlog(ZLOG_SYSERROR, "pread() failed"); + return -1; + } + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_trace_ptrace.c b/sapi/fpm/fpm/fpm_trace_ptrace.c new file mode 100644 index 0000000..838c618 --- /dev/null +++ b/sapi/fpm/fpm/fpm_trace_ptrace.c @@ -0,0 +1,82 @@ + + /* $Id: fpm_trace_ptrace.c,v 1.7 2008/09/18 23:34:11 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <sys/wait.h> +#include <sys/ptrace.h> +#include <unistd.h> +#include <errno.h> + +#if defined(PT_ATTACH) && !defined(PTRACE_ATTACH) +#define PTRACE_ATTACH PT_ATTACH +#endif + +#if defined(PT_DETACH) && !defined(PTRACE_DETACH) +#define PTRACE_DETACH PT_DETACH +#endif + +#if defined(PT_READ_D) && !defined(PTRACE_PEEKDATA) +#define PTRACE_PEEKDATA PT_READ_D +#endif + +#include "fpm_trace.h" +#include "zlog.h" + +static pid_t traced_pid; + +int fpm_trace_signal(pid_t pid) /* {{{ */ +{ + if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) { + zlog(ZLOG_SYSERROR, "failed to ptrace(ATTACH) child %d", pid); + return -1; + } + return 0; +} +/* }}} */ + +int fpm_trace_ready(pid_t pid) /* {{{ */ +{ + traced_pid = pid; + return 0; +} +/* }}} */ + +int fpm_trace_close(pid_t pid) /* {{{ */ +{ + if (0 > ptrace(PTRACE_DETACH, pid, (void *) 1, 0)) { + zlog(ZLOG_SYSERROR, "failed to ptrace(DETACH) child %d", pid); + return -1; + } + traced_pid = 0; + return 0; +} +/* }}} */ + +int fpm_trace_get_long(long addr, long *data) /* {{{ */ +{ +#ifdef PT_IO + struct ptrace_io_desc ptio = { + .piod_op = PIOD_READ_D, + .piod_offs = (void *) addr, + .piod_addr = (void *) data, + .piod_len = sizeof(long) + }; + + if (0 > ptrace(PT_IO, traced_pid, (void *) &ptio, 0)) { + zlog(ZLOG_SYSERROR, "failed to ptrace(PT_IO) pid %d", traced_pid); + return -1; + } +#else + errno = 0; + *data = ptrace(PTRACE_PEEKDATA, traced_pid, (void *) addr, 0); + if (errno) { + zlog(ZLOG_SYSERROR, "failed to ptrace(PEEKDATA) pid %d", traced_pid); + return -1; + } +#endif + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c new file mode 100644 index 0000000..48249e8 --- /dev/null +++ b/sapi/fpm/fpm/fpm_unix.c @@ -0,0 +1,368 @@ + + /* $Id: fpm_unix.c,v 1.25.2.1 2008/12/13 03:18:23 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <string.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> + +#ifdef HAVE_PRCTL +#include <sys/prctl.h> +#endif + +#include "fpm.h" +#include "fpm_conf.h" +#include "fpm_cleanup.h" +#include "fpm_clock.h" +#include "fpm_stdio.h" +#include "fpm_unix.h" +#include "fpm_signals.h" +#include "zlog.h" + +size_t fpm_pagesize; + +int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct fpm_worker_pool_config_s *c = wp->config; + + /* uninitialized */ + wp->socket_uid = -1; + wp->socket_gid = -1; + wp->socket_mode = 0666; + + if (!c) { + return 0; + } + + if (c->listen_owner && *c->listen_owner) { + struct passwd *pwd; + + pwd = getpwnam(c->listen_owner); + if (!pwd) { + zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, c->listen_owner); + return -1; + } + + wp->socket_uid = pwd->pw_uid; + wp->socket_gid = pwd->pw_gid; + } + + if (c->listen_group && *c->listen_group) { + struct group *grp; + + grp = getgrnam(c->listen_group); + if (!grp) { + zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, c->listen_group); + return -1; + } + wp->socket_gid = grp->gr_gid; + } + + if (c->listen_mode && *c->listen_mode) { + wp->socket_mode = strtoul(c->listen_mode, 0, 8); + } + return 0; +} +/* }}} */ + +static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + struct passwd *pwd; + int is_root = !geteuid(); + + if (is_root) { + if (wp->config->user && *wp->config->user) { + if (strlen(wp->config->user) == strspn(wp->config->user, "0123456789")) { + wp->set_uid = strtoul(wp->config->user, 0, 10); + } else { + struct passwd *pwd; + + pwd = getpwnam(wp->config->user); + if (!pwd) { + zlog(ZLOG_ERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, wp->config->user); + return -1; + } + + wp->set_uid = pwd->pw_uid; + wp->set_gid = pwd->pw_gid; + + wp->user = strdup(pwd->pw_name); + wp->home = strdup(pwd->pw_dir); + } + } + + if (wp->config->group && *wp->config->group) { + if (strlen(wp->config->group) == strspn(wp->config->group, "0123456789")) { + wp->set_gid = strtoul(wp->config->group, 0, 10); + } else { + struct group *grp; + + grp = getgrnam(wp->config->group); + if (!grp) { + zlog(ZLOG_ERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, wp->config->group); + return -1; + } + wp->set_gid = grp->gr_gid; + } + } + + if (!fpm_globals.run_as_root) { + if (wp->set_uid == 0 || wp->set_gid == 0) { + zlog(ZLOG_ERROR, "[pool %s] please specify user and group other than root", wp->config->name); + return -1; + } + } + } else { /* not root */ + if (wp->config->user && *wp->config->user) { + zlog(ZLOG_NOTICE, "[pool %s] 'user' directive is ignored when FPM is not running as root", wp->config->name); + } + if (wp->config->group && *wp->config->group) { + zlog(ZLOG_NOTICE, "[pool %s] 'group' directive is ignored when FPM is not running as root", wp->config->name); + } + if (wp->config->chroot && *wp->config->chroot) { + zlog(ZLOG_NOTICE, "[pool %s] 'chroot' directive is ignored when FPM is not running as root", wp->config->name); + } + if (wp->config->process_priority != 64) { + zlog(ZLOG_NOTICE, "[pool %s] 'process.priority' directive is ignored when FPM is not running as root", wp->config->name); + } + + /* set up HOME and USER anyway */ + pwd = getpwuid(getuid()); + if (pwd) { + wp->user = strdup(pwd->pw_name); + wp->home = strdup(pwd->pw_dir); + } + } + return 0; +} +/* }}} */ + +int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ +{ + int is_root = !geteuid(); + int made_chroot = 0; + + if (wp->config->rlimit_files) { + struct rlimit r; + + r.rlim_max = r.rlim_cur = (rlim_t) wp->config->rlimit_files; + + if (0 > setrlimit(RLIMIT_NOFILE, &r)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to set rlimit_files for this pool. Please check your system limits or decrease rlimit_files. setrlimit(RLIMIT_NOFILE, %d)", wp->config->name, wp->config->rlimit_files); + } + } + + if (wp->config->rlimit_core) { + struct rlimit r; + + r.rlim_max = r.rlim_cur = wp->config->rlimit_core == -1 ? (rlim_t) RLIM_INFINITY : (rlim_t) wp->config->rlimit_core; + + if (0 > setrlimit(RLIMIT_CORE, &r)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_core. setrlimit(RLIMIT_CORE, %d)", wp->config->name, wp->config->rlimit_core); + } + } + + if (is_root && wp->config->chroot && *wp->config->chroot) { + if (0 > chroot(wp->config->chroot)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to chroot(%s)", wp->config->name, wp->config->chroot); + return -1; + } + made_chroot = 1; + } + + if (wp->config->chdir && *wp->config->chdir) { + if (0 > chdir(wp->config->chdir)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to chdir(%s)", wp->config->name, wp->config->chdir); + return -1; + } + } else if (made_chroot) { + chdir("/"); + } + + if (is_root) { + + if (wp->config->process_priority != 64) { + if (setpriority(PRIO_PROCESS, 0, wp->config->process_priority) < 0) { + zlog(ZLOG_SYSERROR, "[pool %s] Unable to set priority for this new process", wp->config->name); + return -1; + } + } + + if (wp->set_gid) { + if (0 > setgid(wp->set_gid)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%d)", wp->config->name, wp->set_gid); + return -1; + } + } + if (wp->set_uid) { + if (0 > initgroups(wp->config->user, wp->set_gid)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %d)", wp->config->name, wp->config->user, wp->set_gid); + return -1; + } + if (0 > setuid(wp->set_uid)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%d)", wp->config->name, wp->set_uid); + return -1; + } + } + } + +#ifdef HAVE_PRCTL + if (0 > prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)) { + zlog(ZLOG_SYSERROR, "[pool %s] failed to prctl(PR_SET_DUMPABLE)", wp->config->name); + } +#endif + + if (0 > fpm_clock_init()) { + return -1; + } + return 0; +} +/* }}} */ + +int fpm_unix_init_main() /* {{{ */ +{ + struct fpm_worker_pool_s *wp; + int is_root = !geteuid(); + + if (fpm_global_config.rlimit_files) { + struct rlimit r; + + r.rlim_max = r.rlim_cur = (rlim_t) fpm_global_config.rlimit_files; + + if (0 > setrlimit(RLIMIT_NOFILE, &r)) { + zlog(ZLOG_SYSERROR, "failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_files. setrlimit(RLIMIT_NOFILE, %d)", fpm_global_config.rlimit_files); + return -1; + } + } + + if (fpm_global_config.rlimit_core) { + struct rlimit r; + + r.rlim_max = r.rlim_cur = fpm_global_config.rlimit_core == -1 ? (rlim_t) RLIM_INFINITY : (rlim_t) fpm_global_config.rlimit_core; + + if (0 > setrlimit(RLIMIT_CORE, &r)) { + zlog(ZLOG_SYSERROR, "failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_core. setrlimit(RLIMIT_CORE, %d)", fpm_global_config.rlimit_core); + return -1; + } + } + + fpm_pagesize = getpagesize(); + if (fpm_global_config.daemonize) { + /* + * If daemonize, the calling process will die soon + * and the master process continues to initialize itself. + * + * The parent process has then to wait for the master + * process to initialize to return a consistent exit + * value. For this pupose, the master process will + * send \"1\" into the pipe if everything went well + * and \"0\" otherwise. + */ + + + struct timeval tv; + fd_set rfds; + int ret; + + if (pipe(fpm_globals.send_config_pipe) == -1) { + zlog(ZLOG_SYSERROR, "failed to create pipe"); + return -1; + } + + /* then fork */ + pid_t pid = fork(); + switch (pid) { + + case -1 : /* error */ + zlog(ZLOG_SYSERROR, "failed to daemonize"); + return -1; + + case 0 : /* children */ + close(fpm_globals.send_config_pipe[0]); /* close the read side of the pipe */ + break; + + default : /* parent */ + close(fpm_globals.send_config_pipe[1]); /* close the write side of the pipe */ + + /* + * wait for 10s before exiting with error + * the child is supposed to send 1 or 0 into the pipe to tell the parent + * how it goes for it + */ + FD_ZERO(&rfds); + FD_SET(fpm_globals.send_config_pipe[0], &rfds); + + tv.tv_sec = 10; + tv.tv_usec = 0; + + zlog(ZLOG_DEBUG, "The calling process is waiting for the master process to ping via fd=%d", fpm_globals.send_config_pipe[0]); + ret = select(fpm_globals.send_config_pipe[0] + 1, &rfds, NULL, NULL, &tv); + if (ret == -1) { + zlog(ZLOG_SYSERROR, "failed to select"); + exit(FPM_EXIT_SOFTWARE); + } + if (ret) { /* data available */ + int readval; + ret = read(fpm_globals.send_config_pipe[0], &readval, sizeof(readval)); + if (ret == -1) { + zlog(ZLOG_SYSERROR, "failed to read from pipe"); + exit(FPM_EXIT_SOFTWARE); + } + + if (ret == 0) { + zlog(ZLOG_ERROR, "no data have been read from pipe"); + exit(FPM_EXIT_SOFTWARE); + } else { + if (readval == 1) { + zlog(ZLOG_DEBUG, "I received a valid acknoledge from the master process, I can exit without error"); + fpm_cleanups_run(FPM_CLEANUP_PARENT_EXIT); + exit(FPM_EXIT_OK); + } else { + zlog(ZLOG_DEBUG, "The master process returned an error !"); + exit(FPM_EXIT_SOFTWARE); + } + } + } else { /* no date sent ! */ + zlog(ZLOG_ERROR, "the master process didn't send back its status (via the pipe to the calling process)"); + exit(FPM_EXIT_SOFTWARE); + } + exit(FPM_EXIT_SOFTWARE); + } + } + + /* continue as a child */ + setsid(); + if (0 > fpm_clock_init()) { + return -1; + } + + if (fpm_global_config.process_priority != 64) { + if (is_root) { + if (setpriority(PRIO_PROCESS, 0, fpm_global_config.process_priority) < 0) { + zlog(ZLOG_SYSERROR, "Unable to set priority for the master process"); + return -1; + } + } else { + zlog(ZLOG_NOTICE, "'process.priority' directive is ignored when FPM is not running as root"); + } + } + + fpm_globals.parent_pid = getpid(); + for (wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (0 > fpm_unix_conf_wp(wp)) { + return -1; + } + } + + zlog_set_level(fpm_globals.log_level); + return 0; +} +/* }}} */ + diff --git a/sapi/fpm/fpm/fpm_unix.h b/sapi/fpm/fpm/fpm_unix.h new file mode 100644 index 0000000..3451db1 --- /dev/null +++ b/sapi/fpm/fpm/fpm_unix.h @@ -0,0 +1,17 @@ + + /* $Id: fpm_unix.h,v 1.8 2008/05/25 13:21:13 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_UNIX_H +#define FPM_UNIX_H 1 + +#include "fpm_worker_pool.h" + +int fpm_unix_resolve_socket_premissions(struct fpm_worker_pool_s *wp); +int fpm_unix_init_child(struct fpm_worker_pool_s *wp); +int fpm_unix_init_main(); + +extern size_t fpm_pagesize; + +#endif + diff --git a/sapi/fpm/fpm/fpm_worker_pool.c b/sapi/fpm/fpm/fpm_worker_pool.c new file mode 100644 index 0000000..123f989 --- /dev/null +++ b/sapi/fpm/fpm/fpm_worker_pool.c @@ -0,0 +1,65 @@ + + /* $Id: fpm_worker_pool.c,v 1.15.2.1 2008/12/13 03:21:18 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "fpm.h" +#include "fpm_worker_pool.h" +#include "fpm_cleanup.h" +#include "fpm_children.h" +#include "fpm_shm.h" +#include "fpm_scoreboard.h" +#include "fpm_conf.h" + +struct fpm_worker_pool_s *fpm_worker_all_pools; + +static void fpm_worker_pool_cleanup(int which, void *arg) /* {{{ */ +{ + struct fpm_worker_pool_s *wp, *wp_next; + + for (wp = fpm_worker_all_pools; wp; wp = wp_next) { + wp_next = wp->next; + fpm_worker_pool_config_free(wp->config); + fpm_children_free(wp->children); + if ((which & FPM_CLEANUP_CHILD) == 0 && fpm_globals.parent_pid == getpid()) { + fpm_scoreboard_free(wp->scoreboard); + } + free(wp->config); + free(wp->user); + free(wp->home); + free(wp); + } + fpm_worker_all_pools = NULL; +} +/* }}} */ + +struct fpm_worker_pool_s *fpm_worker_pool_alloc() /* {{{ */ +{ + struct fpm_worker_pool_s *ret; + + ret = malloc(sizeof(struct fpm_worker_pool_s)); + if (!ret) { + return 0; + } + + memset(ret, 0, sizeof(struct fpm_worker_pool_s)); + + ret->idle_spawn_rate = 1; + ret->log_fd = -1; + return ret; +} +/* }}} */ + +int fpm_worker_pool_init_main() /* {{{ */ +{ + if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_worker_pool_cleanup, 0)) { + return -1; + } + return 0; +} +/* }}} */ diff --git a/sapi/fpm/fpm/fpm_worker_pool.h b/sapi/fpm/fpm/fpm_worker_pool.h new file mode 100644 index 0000000..6688e6d --- /dev/null +++ b/sapi/fpm/fpm/fpm_worker_pool.h @@ -0,0 +1,53 @@ + + /* $Id: fpm_worker_pool.h,v 1.13 2008/08/26 15:09:15 anight Exp $ */ + /* (c) 2007,2008 Andrei Nigmatulin */ + +#ifndef FPM_WORKER_POOL_H +#define FPM_WORKER_POOL_H 1 + +#include "fpm_conf.h" +#include "fpm_shm.h" + +struct fpm_worker_pool_s; +struct fpm_child_s; +struct fpm_child_stat_s; +struct fpm_shm_s; + +enum fpm_address_domain { + FPM_AF_UNIX = 1, + FPM_AF_INET = 2 +}; + +struct fpm_worker_pool_s { + struct fpm_worker_pool_s *next; + struct fpm_worker_pool_config_s *config; + char *user, *home; /* for setting env USER and HOME */ + enum fpm_address_domain listen_address_domain; + int listening_socket; + int set_uid, set_gid; /* config uid and gid */ + int socket_uid, socket_gid, socket_mode; + + /* runtime */ + struct fpm_child_s *children; + int running_children; + int idle_spawn_rate; + int warn_max_children; +#if 0 + int warn_lq; +#endif + struct fpm_scoreboard_s *scoreboard; + int log_fd; + char **limit_extensions; + + /* for ondemand PM */ + struct fpm_event_s *ondemand_event; + int socket_event_set; +}; + +struct fpm_worker_pool_s *fpm_worker_pool_alloc(); +int fpm_worker_pool_init_main(); + +extern struct fpm_worker_pool_s *fpm_worker_all_pools; + +#endif + diff --git a/sapi/fpm/fpm/zlog.c b/sapi/fpm/fpm/zlog.c new file mode 100644 index 0000000..80db9d8 --- /dev/null +++ b/sapi/fpm/fpm/zlog.c @@ -0,0 +1,199 @@ + + /* $Id: zlog.c,v 1.7 2008/05/22 21:08:32 anight Exp $ */ + /* (c) 2004-2007 Andrei Nigmatulin */ + +#include "fpm_config.h" + +#include <stdio.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <stdarg.h> +#include <sys/time.h> +#include <errno.h> + +#include "php_syslog.h" + +#include "zlog.h" +#include "fpm.h" + +#define MAX_LINE_LENGTH 1024 + +static int zlog_fd = -1; +static int zlog_level = ZLOG_NOTICE; +static int launched = 0; +static void (*external_logger)(int, char *, size_t) = NULL; + +static const char *level_names[] = { + [ZLOG_DEBUG] = "DEBUG", + [ZLOG_NOTICE] = "NOTICE", + [ZLOG_WARNING] = "WARNING", + [ZLOG_ERROR] = "ERROR", + [ZLOG_ALERT] = "ALERT", +}; + +#ifdef HAVE_SYSLOG_H +const int syslog_priorities[] = { + [ZLOG_DEBUG] = LOG_DEBUG, + [ZLOG_NOTICE] = LOG_NOTICE, + [ZLOG_WARNING] = LOG_WARNING, + [ZLOG_ERROR] = LOG_ERR, + [ZLOG_ALERT] = LOG_ALERT, +}; +#endif + +void zlog_set_external_logger(void (*logger)(int, char *, size_t)) /* {{{ */ +{ + external_logger = logger; +} +/* }}} */ + +const char *zlog_get_level_name(int log_level) /* {{{ */ +{ + if (log_level < 0) { + log_level = zlog_level; + } else if (log_level < ZLOG_DEBUG || log_level > ZLOG_ALERT) { + return "unknown value"; + } + + return level_names[log_level]; +} +/* }}} */ + +void zlog_set_launched(void) { + launched = 1; +} + +size_t zlog_print_time(struct timeval *tv, char *timebuf, size_t timebuf_len) /* {{{ */ +{ + struct tm t; + size_t len; + + len = strftime(timebuf, timebuf_len, "[%d-%b-%Y %H:%M:%S", localtime_r((const time_t *) &tv->tv_sec, &t)); + if (zlog_level == ZLOG_DEBUG) { + len += snprintf(timebuf + len, timebuf_len - len, ".%06d", (int) tv->tv_usec); + } + len += snprintf(timebuf + len, timebuf_len - len, "] "); + return len; +} +/* }}} */ + +int zlog_set_fd(int new_fd) /* {{{ */ +{ + int old_fd = zlog_fd; + + zlog_fd = new_fd; + return old_fd; +} +/* }}} */ + +int zlog_set_level(int new_value) /* {{{ */ +{ + int old_value = zlog_level; + + if (new_value < ZLOG_DEBUG || new_value > ZLOG_ALERT) return old_value; + + zlog_level = new_value; + return old_value; +} +/* }}} */ + +void zlog_ex(const char *function, int line, int flags, const char *fmt, ...) /* {{{ */ +{ + struct timeval tv; + char buf[MAX_LINE_LENGTH]; + const size_t buf_size = MAX_LINE_LENGTH; + va_list args; + size_t len = 0; + int truncated = 0; + int saved_errno; + + if (external_logger) { + va_start(args, fmt); + len = vsnprintf(buf, buf_size, fmt, args); + va_end(args); + if (len >= buf_size) { + memcpy(buf + buf_size - sizeof("..."), "...", sizeof("...") - 1); + len = buf_size - 1; + } + external_logger(flags & ZLOG_LEVEL_MASK, buf, len); + len = 0; + memset(buf, '\0', buf_size); + } + + if ((flags & ZLOG_LEVEL_MASK) < zlog_level) { + return; + } + + saved_errno = errno; +#ifdef HAVE_SYSLOG_H + if (zlog_fd == ZLOG_SYSLOG /* && !fpm_globals.is_child */) { + len = 0; + if (zlog_level == ZLOG_DEBUG) { + len += snprintf(buf, buf_size, "[%s] %s(), line %d: ", level_names[flags & ZLOG_LEVEL_MASK], function, line); + } else { + len += snprintf(buf, buf_size, "[%s] ", level_names[flags & ZLOG_LEVEL_MASK]); + } + } else +#endif + { + if (!fpm_globals.is_child) { + gettimeofday(&tv, 0); + len = zlog_print_time(&tv, buf, buf_size); + } + if (zlog_level == ZLOG_DEBUG) { + if (!fpm_globals.is_child) { + len += snprintf(buf + len, buf_size - len, "%s: pid %d, %s(), line %d: ", level_names[flags & ZLOG_LEVEL_MASK], getpid(), function, line); + } else { + len += snprintf(buf + len, buf_size - len, "%s: %s(), line %d: ", level_names[flags & ZLOG_LEVEL_MASK], function, line); + } + } else { + len += snprintf(buf + len, buf_size - len, "%s: ", level_names[flags & ZLOG_LEVEL_MASK]); + } + } + + if (len > buf_size - 1) { + truncated = 1; + } + + if (!truncated) { + va_start(args, fmt); + len += vsnprintf(buf + len, buf_size - len, fmt, args); + va_end(args); + if (len >= buf_size) { + truncated = 1; + } + } + + if (!truncated) { + if (flags & ZLOG_HAVE_ERRNO) { + len += snprintf(buf + len, buf_size - len, ": %s (%d)", strerror(saved_errno), saved_errno); + if (len >= buf_size) { + truncated = 1; + } + } + } + + if (truncated) { + memcpy(buf + buf_size - sizeof("..."), "...", sizeof("...") - 1); + len = buf_size - 1; + } + +#ifdef HAVE_SYSLOG_H + if (zlog_fd == ZLOG_SYSLOG) { + buf[len] = '\0'; + php_syslog(syslog_priorities[zlog_level], "%s", buf); + buf[len++] = '\n'; + } else +#endif + { + buf[len++] = '\n'; + write(zlog_fd > -1 ? zlog_fd : STDERR_FILENO, buf, len); + } + + if (zlog_fd != STDERR_FILENO && zlog_fd != -1 && !launched && (flags & ZLOG_LEVEL_MASK) >= ZLOG_NOTICE) { + write(STDERR_FILENO, buf, len); + } +} +/* }}} */ + diff --git a/sapi/fpm/fpm/zlog.h b/sapi/fpm/fpm/zlog.h new file mode 100644 index 0000000..1945922 --- /dev/null +++ b/sapi/fpm/fpm/zlog.h @@ -0,0 +1,45 @@ + + /* $Id: zlog.h,v 1.7 2008/05/22 21:08:32 anight Exp $ */ + /* (c) 2004-2007 Andrei Nigmatulin */ + +#ifndef ZLOG_H +#define ZLOG_H 1 + +#define zlog(flags,...) zlog_ex(__func__, __LINE__, flags, __VA_ARGS__) + +struct timeval; + +void zlog_set_external_logger(void (*logger)(int, char *, size_t)); +int zlog_set_fd(int new_fd); +int zlog_set_level(int new_value); +const char *zlog_get_level_name(int log_level); +void zlog_set_launched(void); + +size_t zlog_print_time(struct timeval *tv, char *timebuf, size_t timebuf_len); + +void zlog_ex(const char *function, int line, int flags, const char *fmt, ...) + __attribute__ ((format(printf,4,5))); + +#ifdef HAVE_SYSLOG_H +extern const int syslog_priorities[]; +#endif + +enum { + ZLOG_DEBUG = 1, + ZLOG_NOTICE = 2, + ZLOG_WARNING = 3, + ZLOG_ERROR = 4, + ZLOG_ALERT = 5, +}; + +#define ZLOG_LEVEL_MASK 7 + +#define ZLOG_HAVE_ERRNO 0x100 + +#define ZLOG_SYSERROR (ZLOG_ERROR | ZLOG_HAVE_ERRNO) + +#ifdef HAVE_SYSLOG_H +#define ZLOG_SYSLOG -2 +#endif + +#endif diff --git a/sapi/fpm/init.d.php-fpm.in b/sapi/fpm/init.d.php-fpm.in new file mode 100644 index 0000000..49cce79 --- /dev/null +++ b/sapi/fpm/init.d.php-fpm.in @@ -0,0 +1,138 @@ +#! /bin/sh + +### BEGIN INIT INFO +# Provides: php-fpm +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: starts php-fpm +# Description: starts the PHP FastCGI Process Manager daemon +### END INIT INFO + +prefix=@prefix@ +exec_prefix=@exec_prefix@ + +php_fpm_BIN=@sbindir@/php-fpm +php_fpm_CONF=@sysconfdir@/php-fpm.conf +php_fpm_PID=@localstatedir@/run/php-fpm.pid + + +php_opts="--fpm-config $php_fpm_CONF --pid $php_fpm_PID" + + +wait_for_pid () { + try=0 + + while test $try -lt 35 ; do + + case "$1" in + 'created') + if [ -f "$2" ] ; then + try='' + break + fi + ;; + + 'removed') + if [ ! -f "$2" ] ; then + try='' + break + fi + ;; + esac + + echo -n . + try=`expr $try + 1` + sleep 1 + + done + +} + +case "$1" in + start) + echo -n "Starting php-fpm " + + $php_fpm_BIN --daemonize $php_opts + + if [ "$?" != 0 ] ; then + echo " failed" + exit 1 + fi + + wait_for_pid created $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + stop) + echo -n "Gracefully shutting down php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -QUIT `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed. Use force-quit" + exit 1 + else + echo " done" + fi + ;; + + force-quit) + echo -n "Terminating php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -TERM `cat $php_fpm_PID` + + wait_for_pid removed $php_fpm_PID + + if [ -n "$try" ] ; then + echo " failed" + exit 1 + else + echo " done" + fi + ;; + + restart) + $0 stop + $0 start + ;; + + reload) + + echo -n "Reload service php-fpm " + + if [ ! -r $php_fpm_PID ] ; then + echo "warning, no pid file found - php-fpm is not running ?" + exit 1 + fi + + kill -USR2 `cat $php_fpm_PID` + + echo " done" + ;; + + *) + echo "Usage: $0 {start|stop|force-quit|restart|reload}" + exit 1 + ;; + +esac diff --git a/sapi/fpm/php-fpm.8.in b/sapi/fpm/php-fpm.8.in new file mode 100644 index 0000000..a4e7e74 --- /dev/null +++ b/sapi/fpm/php-fpm.8.in @@ -0,0 +1,220 @@ +.TH PHP-FPM 8 "2009" "The PHP Group" "Scripting Language" +.SH NAME +.TP 15 +php-fpm \- PHP FastCGI Process Manager 'PHP-FPM' +.SH SYNOPSIS +.B php-fpm +[options] +.LP +.SH DESCRIPTION +\fBPHP\fP is a widely\-used general\-purpose scripting language that is especially suited for +Web development and can be embedded into HTML. This is a variant of PHP that will run in the background as a daemon, listening for CGI requests. Output is logged to @php_fpm_localstatedir@/log/php-fpm.log. +.LP +Most options are set in the configuration file. The configuration file is @php_fpm_sysconfdir@/php-fpm.conf. By default, php-fpm will respond to CGI requests listening on localhost http port 9000. Therefore php-fpm expects your webserver to forward all requests for '.php' files to port 9000 and you should edit your webserver configuration file appropriately. +.SH OPTIONS +.TP 15 +.B \-C +Do not chdir to the script's directory +.TP +.PD 0 +.B \-\-php\-ini \fIpath\fP|\fIfile\fP +.TP +.PD 1 +.B \-c \fIpath\fP|\fIfile\fP +Look for +.B php.ini +file in the directory +.IR path +or use the specified +.IR file +.TP +.PD 0 +.B \-\-no\-php\-ini +.TP +.PD 1 +.B \-n +No +.B php.ini +file will be used +.TP +.PD 0 +.B \-\-define \fIfoo\fP[=\fIbar\fP] +.TP +.PD 1 +.B \-d \fIfoo\fP[=\fIbar\fP] +Define INI entry +.IR foo +with value +.IR bar +.TP +.B \-e +Generate extended information for debugger/profiler +.TP +.PD 0 +.B \-\-help +.TP +.PD 1 +.B \-h +This help +.TP +.PD 0 +.B \-\-info +.TP +.PD 1 +.B \-i +PHP information and configuration +.TP +.PD 0 +.B \-\-modules +.TP +.PD 1 +.B \-m +Show compiled in modules +.TP +.PD 0 +.B \-\-version +.TP +.PD 1 +.B \-v +Version number +.B \-\-prefix \fIpath\fP +.TP +.PD 1 +.B \-p +Specify alternative prefix path (the default is @php_fpm_prefix@) +.TP +.PD 0 +.B \-\-fpm\-config \fIfile\fP +.TP +.PD 1 +.B \-y +Specify alternative path to FastCGI process manager configuration file (the default is @php_fpm_sysconfdir@/php-fpm.conf) +.TP +.PD 0 +.B \-\-test +.TP +.PD 1 +.B \-t +Test FPM configuration file and exit +If called twice (-tt), the configuration is dumped before exiting. +.TP +.PD 0 +.B \-\-daemonize +.TP +.PD 1 +.B \-D +Force to run in background and ignore daemonize option from configuration file. +.TP +.PD 0 +.B \-\-nodaemonize +.TP +.PD 1 +.B \-F +Force to stay in foreground and ignore daemonize option from configuration file. +.TP +.PD 0 +.B \-\-zend\-extension \fIfile\fP +.TP +.PD 1 +.B \-z \fIfile\fP +Load Zend extension +.IR file +.SH FILES +.TP 15 +.B php-fpm.conf +The configuration file for the php-fpm daemon. +.TP +.B php.ini +The standard php configuration file. +.SH EXAMPLES +For any unix systems which use init.d for their main process manager, you should use the init script provided to start and stop the php-fpm daemon. +.P +.PD 1 +.RS +sudo /etc/init.d/php-fpm start +.RE +.TP +For any unix systems which use systemd for their main process manager, you should use the unit file provided to start and stop the php-fpm daemon. +.P +.PD 1 +.RS +sudo systemctl start php-fpm.service +.RE +.TP +If your installation has no appropriate init script, launch php-fpm with no arguments. It will launch as a daemon (background process) by default. The file @php_fpm_localstatedir@/run/php-fpm.pid determines whether php-fpm is already up and running. Once started, php-fpm then responds to several POSIX signals: +.P +.PD 0 +.RS +.B SIGINT,SIGTERM \fPimmediate termination +.TP +.B SIGQUIT \fPgraceful stop +.TP +.B SIGUSR1 \fPre-open log file +.TP +.B SIGUSR2 \fPgraceful reload of all workers + reload of fpm conf/binary +.RE +.PD 1 +.P +.SH TIPS +The PHP-FPM CGI daemon will work well with most popular webservers, including Apache2, lighttpd and nginx. +.PD 1 +.P +.SH SEE ALSO +The PHP-FPM website: +.PD 0 +.P +.B http://php-fpm.org +.PD 1 +.P +For a more or less complete description of PHP look here: +.PD 0 +.P +.B http://www.php.net/manual/ +.PD 1 +.P +A nice introduction to PHP by Stig Bakken can be found here: +.PD 0 +.P +.B http://www.zend.com/zend/art/intro.php +.PD 1 +.SH BUGS +You can view the list of known bugs or report any new bug you +found at: +.PD 0 +.P +.B http://bugs.php.net +.PD 1 +.SH AUTHORS +PHP-FPM SAPI was written by Andrei Nigmatulin. The mailing-lists are highload-php-en (English) and highload-php-ru (Russian). +.P +The PHP Group: Thies C. Arntzen, Stig Bakken, Andi Gutmans, Rasmus Lerdorf, Sam Ruby, Sascha Schumann, Zeev Suraski, Jim Winstead, Andrei Zmievski. +.P +A List of active developers can be found here: +.PD 0 +.P +.B http://www.php.net/credits.php +.PD 1 +.P +And last but not least PHP was developed with the help of a huge amount of +contributors all around the world. +.SH VERSION INFORMATION +This manpage describes \fBphp-fpm\fP, version @PHP_VERSION@. +.SH COPYRIGHT +Copyright \(co 1997\-2009 The PHP Group +.PD 0 +.P +Copyright (c) 2007-2009, Andrei Nigmatulin +.PD 1 +.LP +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: +.PD 0 +.P +.B http://www.php.net/license/3_01.txt +.PD 1 +.P +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 +.B license@php.net +so we can mail you a copy immediately. diff --git a/sapi/fpm/php-fpm.conf.in b/sapi/fpm/php-fpm.conf.in new file mode 100644 index 0000000..a63dec7 --- /dev/null +++ b/sapi/fpm/php-fpm.conf.in @@ -0,0 +1,510 @@ +;;;;;;;;;;;;;;;;;;;;; +; FPM Configuration ; +;;;;;;;;;;;;;;;;;;;;; + +; All relative paths in this configuration file are relative to PHP's install +; prefix (@prefix@). This prefix can be dynamicaly changed by using the +; '-p' argument from the command line. + +; Include one or more files. If glob(3) exists, it is used to include a bunch of +; files from a glob(3) pattern. This directive can be used everywhere in the +; file. +; Relative path can also be used. They will be prefixed by: +; - the global prefix if it's been set (-p arguement) +; - @prefix@ otherwise +;include=etc/fpm.d/*.conf + +;;;;;;;;;;;;;;;;;; +; Global Options ; +;;;;;;;;;;;;;;;;;; + +[global] +; Pid file +; Note: the default prefix is @EXPANDED_LOCALSTATEDIR@ +; Default Value: none +;pid = run/php-fpm.pid + +; Error log file +; If it's set to "syslog", log is sent to syslogd instead of being written +; in a local file. +; Note: the default prefix is @EXPANDED_LOCALSTATEDIR@ +; Default Value: log/php-fpm.log +;error_log = log/php-fpm.log + +; syslog_facility is used to specify what type of program is logging the +; message. This lets syslogd specify that messages from different facilities +; will be handled differently. +; See syslog(3) for possible values (ex daemon equiv LOG_DAEMON) +; Default Value: daemon +;syslog.facility = daemon + +; syslog_ident is prepended to every message. If you have multiple FPM +; instances running on the same server, you can change the default value +; which must suit common needs. +; Default Value: php-fpm +;syslog.ident = php-fpm + +; Log level +; Possible Values: alert, error, warning, notice, debug +; Default Value: notice +;log_level = notice + +; If this number of child processes exit with SIGSEGV or SIGBUS within the time +; interval set by emergency_restart_interval then FPM will restart. A value +; of '0' means 'Off'. +; Default Value: 0 +;emergency_restart_threshold = 0 + +; Interval of time used by emergency_restart_interval to determine when +; a graceful restart will be initiated. This can be useful to work around +; accidental corruptions in an accelerator's shared memory. +; Available Units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;emergency_restart_interval = 0 + +; Time limit for child processes to wait for a reaction on signals from master. +; Available units: s(econds), m(inutes), h(ours), or d(ays) +; Default Unit: seconds +; Default Value: 0 +;process_control_timeout = 0 + +; The maximum number of processes FPM will fork. This has been design to control +; the global number of processes when using dynamic PM within a lot of pools. +; Use it with caution. +; Note: A value of 0 indicates no limit +; Default Value: 0 +; process.max = 128 + +; Specify the nice(2) priority to apply to the master process (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool process will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; process.priority = -19 + +; Send FPM to background. Set to 'no' to keep FPM in foreground for debugging. +; Default Value: yes +;daemonize = yes + +; Set open file descriptor rlimit for the master process. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit for the master process. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Specify the event mechanism FPM will use. The following is available: +; - select (any POSIX os) +; - poll (any POSIX os) +; - epoll (linux >= 2.5.44) +; - kqueue (FreeBSD >= 4.1, OpenBSD >= 2.9, NetBSD >= 2.0) +; - /dev/poll (Solaris >= 7) +; - port (Solaris >= 10) +; Default Value: not set (auto detection) +; events.mechanism = epoll + +;;;;;;;;;;;;;;;;;;;; +; Pool Definitions ; +;;;;;;;;;;;;;;;;;;;; + +; Multiple pools of child processes may be started with different listening +; ports and different management options. The name of the pool will be +; used in logs and stats. There is no limitation on the number of pools which +; FPM can handle. Your system will tell you anyway :) + +; Start a new pool named 'www'. +; the variable $pool can we used in any directive and will be replaced by the +; pool name ('www' here) +[www] + +; Per pool prefix +; It only applies on the following directives: +; - 'slowlog' +; - 'listen' (unixsocket) +; - 'chroot' +; - 'chdir' +; - 'php_values' +; - 'php_admin_values' +; When not set, the global prefix (or @php_fpm_prefix@) applies instead. +; Note: This directive can also be relative to the global prefix. +; Default Value: none +;prefix = /path/to/pools/$pool + +; Unix user/group of processes +; Note: The user is mandatory. If the group is not set, the default user's group +; will be used. +user = @php_fpm_user@ +group = @php_fpm_group@ + +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses on a +; specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = 127.0.0.1:9000 + +; Set listen(2) backlog. +; Default Value: 128 (-1 on FreeBSD and OpenBSD) +;listen.backlog = 128 + +; Set permissions for unix socket, if one is used. In Linux, read/write +; permissions must be set in order to allow connections from a web server. Many +; BSD-derived systems allow connections regardless of permissions. +; Default Values: user and group are set as the running user +; mode is set to 0666 +;listen.owner = @php_fpm_user@ +;listen.group = @php_fpm_group@ +;listen.mode = 0666 + +; List of ipv4 addresses of FastCGI clients which are allowed to connect. +; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original +; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address +; must be separated by a comma. If this value is left blank, connections will be +; accepted from any ip address. +; Default Value: any +;listen.allowed_clients = 127.0.0.1 + +; Specify the nice(2) priority to apply to the pool processes (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool processes will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; priority = -19 + +; Choose how the process manager will control the number of child processes. +; Possible Values: +; static - a fixed number (pm.max_children) of child processes; +; dynamic - the number of child processes are set dynamically based on the +; following directives. With this process management, there will be +; always at least 1 children. +; pm.max_children - the maximum number of children that can +; be alive at the same time. +; pm.start_servers - the number of children created on startup. +; pm.min_spare_servers - the minimum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is less than this +; number then some children will be created. +; pm.max_spare_servers - the maximum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is greater than this +; number then some children will be killed. +; ondemand - no children are created at startup. Children will be forked when +; new requests will connect. The following parameter are used: +; pm.max_children - the maximum number of children that +; can be alive at the same time. +; pm.process_idle_timeout - The number of seconds after which +; an idle process will be killed. +; Note: This value is mandatory. +pm = dynamic + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 5 + +; The number of child processes created on startup. +; Note: Used only when pm is set to 'dynamic' +; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 +pm.start_servers = 2 + +; The desired minimum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.min_spare_servers = 1 + +; The desired maximum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.max_spare_servers = 3 + +; The number of seconds after which an idle process will be killed. +; Note: Used only when pm is set to 'ondemand' +; Default Value: 10s +;pm.process_idle_timeout = 10s; + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +; Default Value: 0 +;pm.max_requests = 500 + +; The URI to view the FPM status page. If this value is not set, no URI will be +; recognized as a status page. It shows the following informations: +; pool - the name of the pool; +; process manager - static, dynamic or ondemand; +; start time - the date and time FPM has started; +; start since - number of seconds since FPM has started; +; accepted conn - the number of request accepted by the pool; +; listen queue - the number of request in the queue of pending +; connections (see backlog in listen(2)); +; max listen queue - the maximum number of requests in the queue +; of pending connections since FPM has started; +; listen queue len - the size of the socket queue of pending connections; +; idle processes - the number of idle processes; +; active processes - the number of active processes; +; total processes - the number of idle + active processes; +; max active processes - the maximum number of active processes since FPM +; has started; +; max children reached - number of times, the process limit has been reached, +; when pm tries to start more children (works only for +; pm 'dynamic' and 'ondemand'); +; Value are updated in real time. +; Example output: +; pool: www +; process manager: static +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 62636 +; accepted conn: 190460 +; listen queue: 0 +; max listen queue: 1 +; listen queue len: 42 +; idle processes: 4 +; active processes: 11 +; total processes: 15 +; max active processes: 12 +; max children reached: 0 +; +; By default the status page output is formatted as text/plain. Passing either +; 'html', 'xml' or 'json' in the query string will return the corresponding +; output syntax. Example: +; http://www.foo.bar/status +; http://www.foo.bar/status?json +; http://www.foo.bar/status?html +; http://www.foo.bar/status?xml +; +; By default the status page only outputs short status. Passing 'full' in the +; query string will also return status for each pool process. +; Example: +; http://www.foo.bar/status?full +; http://www.foo.bar/status?json&full +; http://www.foo.bar/status?html&full +; http://www.foo.bar/status?xml&full +; The Full status returns for each process: +; pid - the PID of the process; +; state - the state of the process (Idle, Running, ...); +; start time - the date and time the process has started; +; start since - the number of seconds since the process has started; +; requests - the number of requests the process has served; +; request duration - the duration in µs of the requests; +; request method - the request method (GET, POST, ...); +; request URI - the request URI with the query string; +; content length - the content length of the request (only with POST); +; user - the user (PHP_AUTH_USER) (or '-' if not set); +; script - the main script called (or '-' if not set); +; last request cpu - the %cpu the last request consumed +; it's always 0 if the process is not in Idle state +; because CPU calculation is done when the request +; processing has terminated; +; last request memory - the max amount of memory the last request consumed +; it's always 0 if the process is not in Idle state +; because memory calculation is done when the request +; processing has terminated; +; If the process is in Idle state, then informations are related to the +; last request the process has served. Otherwise informations are related to +; the current request being served. +; Example output: +; ************************ +; pid: 31330 +; state: Running +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 63087 +; requests: 12808 +; request duration: 1250261 +; request method: GET +; request URI: /test_mem.php?N=10000 +; content length: 0 +; user: - +; script: /home/fat/web/docs/php/test_mem.php +; last request cpu: 0.00 +; last request memory: 0 +; +; Note: There is a real-time FPM status monitoring sample web page available +; It's available in: @EXPANDED_DATADIR@/fpm/status.html +; +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;pm.status_path = /status + +; The ping URI to call the monitoring page of FPM. If this value is not set, no +; URI will be recognized as a ping page. This could be used to test from outside +; that FPM is alive and responding, or to +; - create a graph of FPM availability (rrd or such); +; - remove a server from a group if it is not responding (load balancing); +; - trigger alerts for the operating team (24/7). +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;ping.path = /ping + +; This directive may be used to customize the response of a ping request. The +; response is formatted as text/plain with a 200 response code. +; Default Value: pong +;ping.response = pong + +; The access log file +; Default: not set +;access.log = log/$pool.access.log + +; The access log format. +; The following syntax is allowed +; %%: the '%' character +; %C: %CPU used by the request +; it can accept the following format: +; - %{user}C for user CPU only +; - %{system}C for system CPU only +; - %{total}C for user + system CPU (default) +; %d: time taken to serve the request +; it can accept the following format: +; - %{seconds}d (default) +; - %{miliseconds}d +; - %{mili}d +; - %{microseconds}d +; - %{micro}d +; %e: an environment variable (same as $_ENV or $_SERVER) +; it must be associated with embraces to specify the name of the env +; variable. Some exemples: +; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e +; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e +; %f: script filename +; %l: content-length of the request (for POST request only) +; %m: request method +; %M: peak of memory allocated by PHP +; it can accept the following format: +; - %{bytes}M (default) +; - %{kilobytes}M +; - %{kilo}M +; - %{megabytes}M +; - %{mega}M +; %n: pool name +; %o: ouput header +; it must be associated with embraces to specify the name of the header: +; - %{Content-Type}o +; - %{X-Powered-By}o +; - %{Transfert-Encoding}o +; - .... +; %p: PID of the child that serviced the request +; %P: PID of the parent of the child that serviced the request +; %q: the query string +; %Q: the '?' character if query string exists +; %r: the request URI (without the query string, see %q and %Q) +; %R: remote IP address +; %s: status (response code) +; %t: server time the request was received +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; %T: time the log has been written (the request has finished) +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; %u: remote user +; +; Default: "%R - %u %t \"%m %r\" %s" +;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" + +; The log file for slow requests +; Default Value: not set +; Note: slowlog is mandatory if request_slowlog_timeout is set +;slowlog = log/$pool.log.slow + +; The timeout for serving a single request after which a PHP backtrace will be +; dumped to the 'slowlog' file. A value of '0s' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_slowlog_timeout = 0 + +; The timeout for serving a single request after which the worker process will +; be killed. This option should be used when the 'max_execution_time' ini option +; does not stop script execution for some reason. A value of '0' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_terminate_timeout = 0 + +; Set open file descriptor rlimit. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Chroot to this directory at the start. This value must be defined as an +; absolute path. When this value is not set, chroot is not used. +; Note: you can prefix with '$prefix' to chroot to the pool prefix or one +; of its subdirectories. If the pool prefix is not set, the global prefix +; will be used instead. +; Note: chrooting is a great security feature and should be used whenever +; possible. However, all PHP paths will be relative to the chroot +; (error_log, sessions.save_path, ...). +; Default Value: not set +;chroot = + +; Chdir to this directory at the start. +; Note: relative path can be used. +; Default Value: current directory or / when chroot +;chdir = /var/www + +; Redirect worker stdout and stderr into main error log. If not set, stdout and +; stderr will be redirected to /dev/null according to FastCGI specs. +; Note: on highloaded environement, this can cause some delay in the page +; process time (several ms). +; Default Value: no +;catch_workers_output = yes + +; Limits the extensions of the main script FPM will allow to parse. This can +; prevent configuration mistakes on the web server side. You should only limit +; FPM to .php extensions to prevent malicious users to use other extensions to +; exectute php code. +; Note: set an empty value to allow all extensions. +; Default Value: .php +;security.limit_extensions = .php .php3 .php4 .php5 + +; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from +; the current environment. +; Default Value: clean env +;env[HOSTNAME] = $HOSTNAME +;env[PATH] = /usr/local/bin:/usr/bin:/bin +;env[TMP] = /tmp +;env[TMPDIR] = /tmp +;env[TEMP] = /tmp + +; Additional php.ini defines, specific to this pool of workers. These settings +; overwrite the values previously defined in the php.ini. The directives are the +; same as the PHP SAPI: +; php_value/php_flag - you can set classic ini defines which can +; be overwritten from PHP call 'ini_set'. +; php_admin_value/php_admin_flag - these directives won't be overwritten by +; PHP call 'ini_set' +; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. + +; Defining 'extension' will load the corresponding shared extension from +; extension_dir. Defining 'disable_functions' or 'disable_classes' will not +; overwrite previously defined php.ini values, but will append the new value +; instead. + +; Note: path INI options can be relative and will be expanded with the prefix +; (pool, global or @prefix@) + +; Default Value: nothing is defined by default except the values in php.ini and +; specified at startup with the -d argument +;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com +;php_flag[display_errors] = off +;php_admin_value[error_log] = /var/log/fpm-php.www.log +;php_admin_flag[log_errors] = on +;php_admin_value[memory_limit] = 32M diff --git a/sapi/fpm/php-fpm.service.in b/sapi/fpm/php-fpm.service.in new file mode 100644 index 0000000..396a88d --- /dev/null +++ b/sapi/fpm/php-fpm.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=The PHP FastCGI Process Manager +After=syslog.target network.target + +[Service] +PIDFile=@localstatedir@/run/php-fpm.pid +ExecStart=@sbindir@/php-fpm --nodaemonize --fpm-config @sysconfdir@/php-fpm.conf +ExecReload=/bin/kill -USR2 $MAINPID + +[Install] +WantedBy=multi-user.target + diff --git a/sapi/fpm/status.html.in b/sapi/fpm/status.html.in new file mode 100644 index 0000000..86492d7 --- /dev/null +++ b/sapi/fpm/status.html.in @@ -0,0 +1,459 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- + $Id$ + (c) 2011 Jerome Loyet + The PHP License, version 3.01 + This is sample real-time status page for FPM. You can change it to better feet your needs. +--> + <head> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> + <style type="text/css"> + body {background-color: #ffffff; color: #000000;} + body, td, th, h1, h2 {font-family: sans-serif;} + pre {margin: 0px; font-family: monospace;} + a:link {color: #000099; text-decoration: none; background-color: #ffffff;} + a:hover {text-decoration: underline;} + table {border-collapse: collapse;} + .center {text-align: center;} + .center table { margin-left: auto; margin-right: auto; text-align: left;} + .center th { text-align: center !important; } + td, th { border: 1px solid #000000; font-size: 75%; vertical-align: baseline;} + h1 {font-size: 150%;} + h2 {font-size: 125%;} + .p {text-align: left;} + .e {background-color: #ccccff; font-weight: bold; color: #000000;} + .h {background-color: #9999cc; font-weight: bold; color: #000000;} + + .v {background-color: #cccccc; color: #000000;} + .w {background-color: #ccccff; color: #000000;} + + .h th { + cursor: pointer; + } + img {float: right; border: 0px;} + hr {width: 600px; background-color: #cccccc; border: 0px; height: 1px; color: #000000;} + </style> + <title>PHP-FPM status page</title> + <meta name="ROBOTS" content="NOINDEX,NOFOLLOW,NOARCHIVE" /></head> + <body> + <div class="center"> + <table border="0" cellpadding="3" width="95%"> + <tr class="h"> + <td> + <a href="http://www.php.net/"><img border="0" src="https://static.php.net/www.php.net/images/php.gif" alt="PHP Logo" /></a><h1 class="p">PHP-FPM real-time status page</h1> + </td> + </tr> + </table> + <br /> + <table border="0" cellpadding="3" width="95%"> + <tr><td class="e">Status URL</td><td class="v"><input type="text" id="url" size="45" /></td></tr> + <tr><td class="e">Ajax status</td><td class="v" id="status"></td></tr> + <tr><td class="e">Refresh Rate</td><td class="v"><input type="text" id="rate" value="1" /></td></tr> + <tr> + <td class="e">Actions</td> + <td class="v"> + <button onclick="javascript:refresh();">Manual Refresh</button> + <button id="play" onclick="javascript:playpause();">Play</button> + </td> + </tr> + </table> + <h1>Pool Status</h1> + <table border="0" cellpadding="3" width="95%" id="short"> + <tr style="display: none;"><td> </td></tr> + </table> + <h1>Active Processes status</h1> + <table border="0" cellpadding="3" width="95%" id="active"> + <tr class="h"><th>PID↓</th><th>Start Time</th><th>Start Since</th><th>Requests Served</th><th>Request Duration</th><th>Request method</th><th>Request URI</th><th>Content Length</th><th>User</th><th>Script</th></tr> + </table> + <h1>Idle Processes status</h1> + <table border="0" cellpadding="3" width="95%" id="idle"> + <tr class="h"><th>PID↓</th><th>Start Time</th><th>Start Since</th><th>Requests Served</th><th>Request Duration</th><th>Request method</th><th>Request URI</th><th>Content Length</th><th>User</th><th>Script</th><th>Last Request %CPU</th><th>Last Request Memory</th></tr> + </table> + </div> + <p> + <a href="http://validator.w3.org/check?uri=referer"> + <img src="http://www.w3.org/Icons/valid-xhtml10" alt="Valid XHTML 1.0 Transitional" height="31" width="88" /> + </a> + </p> + <script type="text/javascript"> +<!-- + var xhr_object = null; + var doc_url = document.getElementById("url"); + var doc_rate = document.getElementById("rate"); + var doc_status = document.getElementById("status"); + var doc_play = document.getElementById("play"); + var doc_short = document.getElementById("short"); + var doc_active = document.getElementById("active"); + var doc_idle = document.getElementById("idle"); + var rate = 0; + var play=0; + var delay = 1000; + var order_active_index = 0; + var order_active_reverse = 0; + var order_idle_index = 0; + var order_idle_reverse = 0; + var sort_index; + var sort_order; + + doc_url.value = location.protocol + '//' + location.host + "/status?json&full"; + + ths = document.getElementsByTagName("th"); + for (var i=0; i<ths.length; i++) { + var th = ths[i]; + if (th.parentNode.className == "h") { + th.onclick = function() { order(this); return false; }; + } + } + + xhr_object = create_ajax(); + + function create_ajax() { + if (window.XMLHttpRequest) { + return new XMLHttpRequest(); + } + var names = [ + "Msxml2.XMLHTTP.6.0", + "Msxml2.XMLHTTP.3.0", + "Msxml2.XMLHTTP", + "Microsoft.XMLHTTP" + ]; + for(var i in names) + { + try { + return new ActiveXObject(names[i]); + } catch(e){} + } + alert("Browser not compatible ..."); + } + + function order(cell) { + var table; + + if (cell.constructor != HTMLTableCellElement && cell.constructor != HTMLTableHeaderCellElement) { + return; + } + + table = cell.parentNode.parentNode.parentNode; + + if (table == doc_active) { + if (order_active_index == cell.cellIndex) { + if (order_active_reverse == 0) { + cell.innerHTML = cell.innerHTML.replace(/.$/, "↑"); + order_active_reverse = 1; + } else { + cell.innerHTML = cell.innerHTML.replace(/.$/, "↓"); + order_active_reverse = 0; + } + } else { + var c = doc_active.rows[0].cells[order_active_index]; + c.innerHTML = c.innerHTML.replace(/.$/, ""); + cell.innerHTML = cell.innerHTML.replace(/$/, order_active_reverse == 0 ? "↓" : "↑"); + order_active_index = cell.cellIndex; + } + reorder(table, order_active_index, order_active_reverse); + return; + } + + if (table == doc_idle) { + if (order_idle_index == cell.cellIndex) { + if (order_idle_reverse == 0) { + cell.innerHTML = cell.innerHTML.replace(/.$/, "↑"); + order_idle_reverse = 1; + } else { + cell.innerHTML = cell.innerHTML.replace(/.$/, "↓"); + order_idle_reverse = 0; + } + } else { + var c = doc_idle.rows[0].cells[order_idle_index]; + c.innerHTML = c.innerHTML.replace(/.$/, ""); + cell.innerHTML = cell.innerHTML.replace(/$/, order_idle_reverse == 0 ? "↓" : "↑"); + order_idle_index = cell.cellIndex; + } + reorder(table, order_idle_index, order_idle_reverse); + return; + } + } + + function reorder(table, index, order) { + var rows = []; + while (table.rows.length > 1) { + rows.push(table.rows[1]); + table.deleteRow(1); + } + sort_index = index; + sort_order = order; + rows.sort(sort_table); + for (var i in rows) { + table.appendChild(rows[i]); + } + var odd = 1; + for (var i=1; i<table.rows.length; i++) { + table.rows[i].className = odd++ % 2 == 0 ? "v" : "w"; + } + return; + } + + function sort_table(a, b) { + if (a.cells[0].tagName == "TH") return -1; + if (b.cells[0].tagName == "TH") return 1; + + if (a.cells[sort_index].__search_t == 0) { /* integer */ + if (!sort_order) return a.cells[sort_index].__search_v - b.cells[sort_index].__search_v; + return b.cells[sort_index].__search_v - a.cells[sort_index].__search_v;; + } + + /* string */ + if (!sort_order) return a.cells[sort_index].__search_v.localeCompare(b.cells[sort_index].__search_v); + else return b.cells[sort_index].__search_v.localeCompare(a.cells[sort_index].__search_v); + } + + function playpause() { + rate = 0; + if (play) { + play = 0; + doc_play.innerHTML = "Play"; + doc_rate.disabled = false; + } else { + delay = parseInt(doc_rate.value); + if (!delay || delay < 1) { + doc_status.innerHTML = "Not valid 'refresh' value"; + return; + } + play = 1; + doc_rate.disabled = true; + doc_play.innerHTML = "Pause"; + setTimeout("callback()", delay * 1000); + } + } + + function refresh() { + if (xhr_object == null) return; + if (xhr_object.readyState > 0 && xhr_object.readyState < 4) { + return; /* request is running */ + } + xhr_object.open("GET", doc_url.value, true); + xhr_object.onreadystatechange = function() { + switch(xhr_object.readyState) { + case 0: + doc_status.innerHTML = "uninitialized"; + break; + case 1: + doc_status.innerHTML = "loading ..."; + break; + case 2: + doc_status.innerHTML = "loaded"; + break; + case 3: + doc_status.innerHTML = "interactive"; + break; + case 4: + doc_status.innerHTML = "complete"; + if (xhr_object.status == 200) { + fpm_status(xhr_object.responseText); + } else { + doc_status.innerHTML = "Error " + xhr_object.status; + } + break; + } + } + xhr_object.send(); + } + + function callback() { + if (!play) return; + refresh(); + setTimeout("callback()", delay * 1000); + } + + function fpm_status(txt) { + var json = null; + + while (doc_short.rows.length > 0) { + doc_short.deleteRow(0); + } + + while (doc_active.rows.length > 1) { + doc_active.deleteRow(1); + } + + while (doc_idle.rows.length > 1) { + doc_idle.deleteRow(1); + } + + try { + json = JSON.parse(txt); + } catch (e) { + doc_status.innerHTML = "Error while parsing json: '" + e + "': <br /><pre>" + txt + "</pre>"; + return; + } + + for (var key in json) { + if (key == "processes") continue; + if (key == "state") continue; + var row = doc_short.insertRow(doc_short.rows.length); + var value = json[key]; + if (key == "start time") { + value = new Date(value * 1000).toLocaleString(); + } + if (key == "start since") { + value = time_s(value); + } + var cell = row.insertCell(row.cells.length); + cell.className = "e"; + cell.innerHTML = key; + + cell = row.insertCell(row.cells.length); + cell.className = "v"; + cell.innerHTML = value; + } + + if (json.processes) { + process_full(json.processes, doc_active, "Idle", 0, 0); + reorder(doc_active, order_active_index, order_active_reverse); + + process_full(json.processes, doc_idle, "Idle", 1, 1); + reorder(doc_idle, order_idle_index, order_idle_reverse); + } + } + + function process_full(processes, table, state, equal, cpumem) { + var odd = 1; + + for (var i in processes) { + var proc = processes[i]; + if ((equal && proc.state == state) || (!equal && proc.state != state)) { + var c = odd++ % 2 == 0 ? "v" : "w"; + var row = table.insertRow(-1); + row.className = c; + row.insertCell(-1).innerHTML = proc.pid; + row.cells[row.cells.length - 1].__search_v = proc.pid; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = date(proc['start time'] * 1000);; + row.cells[row.cells.length - 1].__search_v = proc['start time']; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = time_s(proc['start since']); + row.cells[row.cells.length - 1].__search_v = proc['start since']; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = proc.requests; + row.cells[row.cells.length - 1].__search_v = proc.requests; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = time_u(proc['request duration']); + row.cells[row.cells.length - 1].__search_v = proc['request duration']; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = proc['request method']; + row.cells[row.cells.length - 1].__search_v = proc['request method']; + row.cells[row.cells.length - 1].__search_t = 1; + + row.insertCell(-1).innerHTML = proc['request uri']; + row.cells[row.cells.length - 1].__search_v = proc['request uri']; + row.cells[row.cells.length - 1].__search_t = 1; + + row.insertCell(-1).innerHTML = proc['content length']; + row.cells[row.cells.length - 1].__search_v = proc['content length']; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = proc.user; + row.cells[row.cells.length - 1].__search_v = proc.user; + row.cells[row.cells.length - 1].__search_t = 1; + + row.insertCell(-1).innerHTML = proc.script; + row.cells[row.cells.length - 1].__search_v = proc.script; + row.cells[row.cells.length - 1].__search_t = 1; + + if (cpumem) { + row.insertCell(-1).innerHTML = cpu(proc['last request cpu']); + row.cells[row.cells.length - 1].__search_v = proc['last request cpu']; + row.cells[row.cells.length - 1].__search_t = 0; + + row.insertCell(-1).innerHTML = memory(proc['last request memory']); + row.cells[row.cells.length - 1].__search_v = proc['last request memory']; + row.cells[row.cells.length - 1].__search_t = 0; + } + } + } + } + + function date(d) { + var t = new Date(d); + var r = ""; + + r += (t.getDate() < 10 ? '0' : '') + t.getDate(); + r += '/'; + r += (t.getMonth() + 1 < 10 ? '0' : '') + (t.getMonth() + 1); + r += '/'; + r += t.getFullYear(); + r += ' '; + r += (t.getHours() < 10 ? '0' : '') + t.getHours(); + r += ':'; + r += (t.getMinutes() < 10 ? '0' : '') + t.getMinutes(); + r += ':'; + r += (t.getSeconds() < 10 ? '0' : '') + t.getSeconds(); + + + return r; + } + + function cpu(c) { + if (c == 0) return 0; + return c + "%"; + } + + function memory(mem) { + if (mem == 0) return 0; + if (mem < 1024) { + return mem + "B"; + } + if (mem < 1024 * 1024) { + return mem/1024 + "KB"; + } + if (mem < 1024*1024*1024) { + return mem/1024/1024 + "MB"; + } + } + + function time_s(t) { + var r = ""; + if (t < 60) { + return t + 's'; + } + + r = (t % 60) + 's'; + t = Math.floor(t / 60); + if (t < 60) { + return t + 'm ' + r; + } + + r = (t % 60) + 'm ' + r; + t = Math.floor(t/60); + + if (t < 24) { + return t + 'h ' + r; + } + + return Math.floor(t/24) + 'd ' + (t % 24) + 'h ' + t; + } + + function time_u(t) { + var r = ""; + if (t < 1000) { + return t + 'µs' + } + + r = (t % 1000) + 'µs'; + t = Math.floor(t / 1000); + if (t < 1000) { + return t + 'ms ' + r; + } + + return time_s(Math.floor(t/1000)) + ' ' + (t%1000) + 'ms ' + r; + } +--> + </script> + </body> +</html> 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
diff --git a/sapi/litespeed/CREDITS b/sapi/litespeed/CREDITS new file mode 100644 index 0000000..2fa192e --- /dev/null +++ b/sapi/litespeed/CREDITS @@ -0,0 +1,2 @@ +litespeed +George Wang diff --git a/sapi/litespeed/Makefile.frag b/sapi/litespeed/Makefile.frag new file mode 100644 index 0000000..b70e5e8 --- /dev/null +++ b/sapi/litespeed/Makefile.frag @@ -0,0 +1,9 @@ +litespeed: $(SAPI_LITESPEED_PATH) + +$(SAPI_LITESPEED_PATH): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_LITESPEED_OBJS) + $(BUILD_LITESPEED) + +install-litespeed: $(SAPI_LITESPEED_PATH) + @echo "Installing PHP LitSpeed binary: $(INSTALL_ROOT)$(bindir)/" + @$(INSTALL) -m 0755 $(SAPI_LITESPEED_PATH) $(INSTALL_ROOT)$(bindir)/lsphp + diff --git a/sapi/litespeed/README b/sapi/litespeed/README new file mode 100644 index 0000000..e548ec9 --- /dev/null +++ b/sapi/litespeed/README @@ -0,0 +1,225 @@ +Introduction +============ + +LiteSpeed SAPI module is a dedicated interface for PHP integration with +LiteSpeed Web Server. LiteSpeed SAPI has similar architecture to the +FastCGI SAPI with there major enhancements: better performance, dynamic +spawning and PHP configuration modification through web server +configuration and .htaccess files. + +Our simple benchmark test ("hello world") shows that PHP with +LiteSpeed SAPI has 30% better performance over PHP with FastCGI SAPI, +which is nearly twice the performance that Apache mod_php can deliver. + +A major drawback of FastCGI PHP comparing to Apache mod_php is lacking +the flexibilities in PHP configurations. PHP configurations cannot be +changed at runtime via configuration files like .htaccess files or web +server's virtual host configuration. In shared hosting environment, +each hosting account will has its own "open_basedir" overridden in +server configuration to enhance server security when mod_php is used. +usually, FastCGI PHP is not an option in shared hosting environment +due to lacking of this flexibility. LiteSpeed SAPI is carefully designed +to address this issue. PHP configurations can be modified the same way +as that in mod_php with the same configuration directives. + +PHP with LiteSpeed SAPI is highly recommended over FastCGI PHP for +PHP scripting with LiteSpeed web server. + + +Building PHP with LiteSpeed SAPI +================================ + +You need to add "--with-litespeed" to the configure command to build +PHP with LiteSpeed SAPI, all other SAPI related configure options +should be removed. + +For example: + ./configure --with-litespeed + make + +You should find an executable called 'php' under sapi/litespeed/ +directory after the compilation succeeds. Copy it to +'lsws/fcgi-bin/lsphp' or wherever you prefer, if LiteSpeed web server +has been configured to run PHP with LiteSpeed SAPI already, you just +need to overwrite the old executable with this one and you are all +set. + +Start PHP from command line +=========================== + +Usually, lsphp is managed by LiteSpeed web server in a single server +installation. lsphp can be used in clustered environment with one +LiteSpeed web server at the front, load balancing lsphp processes +running on multiple backend servers. In such environment, lsphp can be +start manually from command with option "-b <socket_address>", socket +address can be IPv4, IPv6 or Unix Domain Socket address. +for example: + + ./lsphp -b [::]:3000 + +have lsphp bind to port 3000 on all IPv4 and IPv6 address, + + ./lsphp -b *:3000 + +have lsphp bind to port 300 on all IPv4 address. + + ./lsphp -b 192.168.0.2:3000 + +have lsphp bind to address 192.168.0.2:3000. + + ./lsphp -b /tmp/lsphp_manual.sock + +have lsphp accept request on Unix domain socket "/tmp/lsphp_manual.sock" + + +Using LiteSpeed PHP with LiteSpeed Web Server +============================================= + +Detailed information about how to configure LiteSpeed web server with +PHP support is available from our website, at: + +http://www.litespeedtech.com/docs/HowTo_QA.html + +Usually, PHP support has been configured out of box, you don't need to +change it unless you want to change PHP interface from FastCGI to +LiteSpeed SAPI or vice versa. + +Brief instructions are as follow: + +1) Login to web administration interface, go to 'Server'->'Ext App' tab, + add an external application of type "LSAPI app", "Command" should be + set to a shell command that executes the PHP binary you just built. + "Instances" should be set to "1". Add "LSAPI_CHILDREN" environment + variable to match the value of "Max Connections". More tunable + environment variable described below can be added. + +2) Go to 'Server'->'Script Handler' tab, add a script handler + configuration: set 'suffix' to 'php', 'Handler Type' to 'LiteSpeed + API', 'Handler Name' should be the name of external application + just defined. + + +3) Click 'Apply Changes' link on the top left of the page, then click + 'graceful restart'. Now PHP is running with LiteSpeed SAPI. + +Tunings +------- + +There are a few environment variables that can be tweaked to control the +behavior of LSAPI application. + +* LSAPI_CHILDREN or PHP_LSAPI_CHILDREN (default: 0) + +There are two ways to let PHP handle multiple requests concurrently, +Server Managed Mode and Self Managed Mode. In Server Managed Mode, +LiteSpeed web server dynamically spawn/stop PHP processes, in this mode +"Instances" should match "Max Connections" configuration for PHP +external application. To start PHP in Self Managed Mode, "Instances" +should be set to "1", while "LSAPI_CHILDREN" environment variable should +be set to match the value of "Max Connections" and >1. Web Server will +start one PHP process, this process will start/stop children PHP processes +dynamically based on on demand. If "LSAPI_CHILDREN" <=1, PHP will be +started in server managed mode. + +Self Managed Mode is preferred because all PHP processes can share one +shared memory block for the opcode cache. + +Usually, there is no need to set value of LSAPI_CHILDREN over 100 in +most server environment. + + +* LSAPI_AVOID_FORK (default: 0) + +LSAPI_AVOID_FORK specifies the policy of the internal process manager in +"Self Managed Mode". When set to 0, the internal process manager will stop +and start children process on demand to save system resource. This is +preferred in a shared hosting environment. When set to 1, the internal +process manager will try to avoid freqently stopping and starting children +process. This might be preferred in a dedicate hosting environment. + + +* LSAPI_EXTRA_CHILDREN (default: 1/3 of LSAPI_CHILDREN or 0) + +LSAPI_EXTRA_CHILDREN controls the maximum number of extra children processes +can be started when some or all existing children processes are in +malfunctioning state. Total number of children processes will be reduced to +LSAPI_CHILDREN level as soon as service is back to normal. +When LSAPI_AVOID_FORK is set to 0, the default value is 1/3 of +LSAPI_CHIDLREN, When LSAPI_AVOID_FORK is set to 1, the default value is 0. + + +* LSAPI_MAX_REQS or PHP_LSAPI_MAX_REQUESTS (default value: 10000) + +This controls how many requests each child process will handle before +it exits automatically. Several PHP functions have been identified +having memory leaks. This parameter can help reducing memory usage +of leaky PHP functions. + + +* LSAPI_MAX_IDLE (default value: 300 seconds) + +In Self Managed Mode, LSAPI_MAX_IDLE controls how long a idle child +process will wait for a new request before it exits. This option help +releasing system resources taken by idle processes. + + +* LSAPI_MAX_IDLE_CHILDREN + (default value: 1/3 of LSAPI_CHILDREN or LSAPI_CHILDREN) + +In Self Managed Mode, LSAI_MAX_IDLE_CHILDREN controls how many idle +children processes are allowed. Excessive idle children processes +will be killed by the parent process immediately. +When LSAPI_AVOID_FORK is set to 0, the default value is 1/3 of +LSAPI_CHIDLREN, When LSAPI_AVOID_FORK is set to 1, the default value +is LSAPI_CHILDREN. + + +* LSAPI_MAX_PROCESS_TIME (default value: 300 seconds) + +In Self Managed Mode, LSAPI_MAX_PROCESS_TIME controls the maximum +processing time allowed when processing a request. If a child process +can not finish processing of a request in the given time period, it +will be killed by the parent process. This option can help getting rid +of dead or runaway child process. + + +* LSAPI_PGRP_MAX_IDLE (default value: FOREVER ) + +In Self Managed Mode, LSAPI_PGRP_MAX_IDLE controls how long the parent +process will wait before exiting when there is no child process. +This option help releasing system resources taken by an idle parent +process. + + +* LSAPI_PPID_NO_CHECK + +By default a LSAPI application check the existence of its parent process +and exits automatically if the parent process died. This is to reduce +orphan process when web server is restarted. However, it is desireable +to disable this feature, such as when a LSAPI process was started +manually from command line. LSAPI_PPID_NO_CHECK should be set when +you want to disable the checking of existence of parent process. +When PHP started by "-b" option, it is disabled automatically. + + +Compatibility with Apache mod_php +================================= + +LSAPI PHP supports PHP configuration overridden via web server configuration +as well as .htaccess. +Since 4.0 release "apache_response_headers" function is supported. + + + +Contact +======= + +For support questions, please post to our free support forum, at: + +http://www.litespeedtech.com/forum/ + +For bug report, please send bug report to bug [at] litespeedtech.com. + + + + diff --git a/sapi/litespeed/config.m4 b/sapi/litespeed/config.m4 new file mode 100644 index 0000000..268b9ae --- /dev/null +++ b/sapi/litespeed/config.m4 @@ -0,0 +1,31 @@ +dnl +dnl $Id$ +dnl + +AC_MSG_CHECKING(for LiteSpeed support) + +PHP_ARG_WITH(litespeed,, +[ --with-litespeed Build PHP as litespeed module], no) + +if test "$PHP_LITESPEED" != "no"; then + PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/litespeed/Makefile.frag,$abs_srcdir/sapi/litespeed,sapi/litespeed) + SAPI_LITESPEED_PATH=sapi/litespeed/php + PHP_SELECT_SAPI(litespeed, program, lsapi_main.c lsapilib.c, "", '$(SAPI_LITESPEED_PATH)') + case $host_alias in + *darwin*) + BUILD_LITESPEED="\$(CC) \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(NATIVE_RPATHS) \$(PHP_GLOBAL_OBJS:.lo=.o) \$(PHP_BINARY_OBJS:.lo=.o) \$(PHP_LITESPEED_OBJS:.lo=.o) \$(PHP_FRAMEWORKS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_LITESPEED_PATH)" + ;; + *cygwin*) + SAPI_LITESPEED_PATH=sapi/litespeed/php.exe + BUILD_LITESPEED="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_LITESPEED_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_LITESPEED_PATH)" + ;; + *) + BUILD_LITESPEED="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_LITESPEED_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_LITESPEED_PATH)" + ;; + esac + + PHP_SUBST(SAPI_LITESPEED_PATH) + PHP_SUBST(BUILD_LITESPEED) +fi + +AC_MSG_RESULT($PHP_LITESPEED) diff --git a/sapi/litespeed/lsapi_main.c b/sapi/litespeed/lsapi_main.c new file mode 100644 index 0000000..f33b049 --- /dev/null +++ b/sapi/litespeed/lsapi_main.c @@ -0,0 +1,1118 @@ +/* + +----------------------------------------------------------------------+ + | 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 at 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. | + +----------------------------------------------------------------------+ + | Author: George Wang <gwang@litespeedtech.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_ini.h" +#include "php_variables.h" +#include "zend_highlight.h" +#include "zend.h" + +#include "lsapilib.h" + +#include <stdio.h> + +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef PHP_WIN32 + +#include <io.h> +#include <fcntl.h> +#include "win32/php_registry.h" + +#else + +#include <sys/wait.h> + +#endif + +#include <sys/stat.h> + +#if HAVE_SYS_TYPES_H + +#include <sys/types.h> + +#endif + +#if HAVE_SIGNAL_H + +#include <signal.h> + +#endif + +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> + + +#define SAPI_LSAPI_MAX_HEADER_LENGTH 2048 + +static int lsapi_mode = 1; +static char *php_self = ""; +static char *script_filename = ""; +static int source_highlight = 0; +static char * argv0 = NULL; +static int engine = 1; +#ifdef ZTS +zend_compiler_globals *compiler_globals; +zend_executor_globals *executor_globals; +php_core_globals *core_globals; +sapi_globals_struct *sapi_globals; +void ***tsrm_ls; +#endif + +zend_module_entry litespeed_module_entry; + +/* {{{ php_lsapi_startup + */ +static int php_lsapi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, NULL, 0)==FAILURE) { + return FAILURE; + } + argv0 = sapi_module->executable_location; + return SUCCESS; +} +/* }}} */ + +/* {{{ sapi_lsapi_ini_defaults */ + +/* overwriteable ini defaults must be set in sapi_cli_ini_defaults() */ +#define INI_DEFAULT(name,value)\ + ZVAL_STRING(tmp, value, 0);\ + zend_hash_update(configuration_hash, name, sizeof(name), tmp, sizeof(zval), (void**)&entry);\ + Z_STRVAL_P(entry) = zend_strndup(Z_STRVAL_P(entry), Z_STRLEN_P(entry)) + +static void sapi_lsapi_ini_defaults(HashTable *configuration_hash) +{ + zval *tmp, *entry; + +#if PHP_MAJOR_VERSION > 4 +/* + MAKE_STD_ZVAL(tmp); + + INI_DEFAULT("register_long_arrays", "0"); + + FREE_ZVAL(tmp); +*/ +#endif + +} +/* }}} */ + + +/* {{{ sapi_lsapi_ub_write + */ +static int sapi_lsapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int ret; + int remain; + if ( lsapi_mode ) { + ret = LSAPI_Write( str, str_length ); + if ( ret < str_length ) { + php_handle_aborted_connection(); + return str_length - ret; + } + } else { + remain = str_length; + while( remain > 0 ) { + ret = write( 1, str, remain ); + if ( ret <= 0 ) { + php_handle_aborted_connection(); + return str_length - remain; + } + str += ret; + remain -= ret; + } + } + return str_length; +} +/* }}} */ + + +/* {{{ sapi_lsapi_flush + */ +static void sapi_lsapi_flush( void * server_context ) +{ + if ( lsapi_mode ) { + if ( LSAPI_Flush() == -1) { + php_handle_aborted_connection(); + } + } +} +/* }}} */ + + +/* {{{ sapi_lsapi_deactivate + */ +static int sapi_lsapi_deactivate(TSRMLS_D) +{ + if ( SG(request_info).path_translated ) + { + efree( SG(request_info).path_translated ); + } + + return SUCCESS; +} +/* }}} */ + + + + +/* {{{ sapi_lsapi_getenv + */ +static char *sapi_lsapi_getenv( char * name, size_t name_len TSRMLS_DC ) +{ + if ( lsapi_mode ) { + return LSAPI_GetEnv( name ); + } else { + return getenv( name ); + } +} +/* }}} */ + + +/* +static int add_variable( const char * pKey, int keyLen, const char * pValue, int valLen, + void * arg ) +{ + php_register_variable_safe((char *)pKey, (char *)pValue, valLen, (zval *)arg TSRMLS_CC); + return 1; +} +*/ + +static int add_variable( const char * pKey, int keyLen, const char * pValue, int valLen, + void * arg ) +{ + zval * gpc_element, **gpc_element_p; + HashTable * symtable1 = Z_ARRVAL_P((zval * )arg); + register char * pKey1 = (char *)pKey; + + MAKE_STD_ZVAL(gpc_element); + Z_STRLEN_P( gpc_element ) = valLen; + Z_STRVAL_P( gpc_element ) = estrndup(pValue, valLen); + Z_TYPE_P( gpc_element ) = IS_STRING; +#if PHP_MAJOR_VERSION > 4 + zend_symtable_update( symtable1, pKey1, keyLen + 1, &gpc_element, sizeof( zval *), (void **) &gpc_element_p ); +#else + zend_hash_update( symtable1, pKey1, keyLen + 1, &gpc_element, sizeof( zval *), (void **) &gpc_element_p ); +#endif + return 1; +} + + +#if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 4) || PHP_MAJOR_VERSION < 5) +static int add_variable_magic_quote( const char * pKey, int keyLen, const char * pValue, int valLen, + void * arg ) +{ + zval * gpc_element, **gpc_element_p; + HashTable * symtable1 = Z_ARRVAL_P((zval * )arg); + register char * pKey1 = (char *)pKey; + + MAKE_STD_ZVAL(gpc_element); + Z_STRLEN_P( gpc_element ) = valLen; + Z_STRVAL_P( gpc_element ) = php_addslashes((char *)pValue, valLen, &Z_STRLEN_P( gpc_element ), 0 ); + Z_TYPE_P( gpc_element ) = IS_STRING; +#if PHP_MAJOR_VERSION > 4 + zend_symtable_update( symtable1, pKey1, keyLen + 1, &gpc_element, sizeof( zval *), (void **) &gpc_element_p ); +#else + zend_hash_update( symtable1, pKey1, keyLen + 1, &gpc_element, sizeof( zval *), (void **) &gpc_element_p ); +#endif + return 1; +} + +#endif + +/* {{{ sapi_lsapi_register_variables + */ +static void sapi_lsapi_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char * php_self = ""; + if ( lsapi_mode ) { + if ( (SG(request_info).request_uri ) ) + php_self = (SG(request_info).request_uri ); + +#if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 4) || PHP_MAJOR_VERSION < 5) + if (!PG(magic_quotes_gpc)) { +#endif + LSAPI_ForeachHeader( add_variable, track_vars_array ); + LSAPI_ForeachEnv( add_variable, track_vars_array ); + add_variable("PHP_SELF", 8, php_self, strlen( php_self ), track_vars_array ); +#if ((PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 4) || PHP_MAJOR_VERSION < 5) + } else { + LSAPI_ForeachHeader( add_variable_magic_quote, track_vars_array ); + LSAPI_ForeachEnv( add_variable_magic_quote, track_vars_array ); + add_variable_magic_quote("PHP_SELF", 8, php_self, strlen( php_self ), track_vars_array ); + } +#endif + php_import_environment_variables(track_vars_array TSRMLS_CC); + } else { + php_import_environment_variables(track_vars_array TSRMLS_CC); + + php_register_variable("PHP_SELF", php_self, track_vars_array TSRMLS_CC); + php_register_variable("SCRIPT_NAME", php_self, track_vars_array TSRMLS_CC); + php_register_variable("SCRIPT_FILENAME", script_filename, track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", script_filename, track_vars_array TSRMLS_CC); + php_register_variable("DOCUMENT_ROOT", "", track_vars_array TSRMLS_CC); + + } +} +/* }}} */ + + +/* {{{ sapi_lsapi_read_post + */ +static int sapi_lsapi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + if ( lsapi_mode ) { + return LSAPI_ReadReqBody( buffer, count_bytes ); + } else { + return 0; + } +} +/* }}} */ + + + + +/* {{{ sapi_lsapi_read_cookies + */ +static char *sapi_lsapi_read_cookies(TSRMLS_D) +{ + if ( lsapi_mode ) { + return LSAPI_GetHeader( H_COOKIE ); + } else { + return NULL; + } +} +/* }}} */ + + +/* {{{ sapi_lsapi_send_headers + */ +static int sapi_lsapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + sapi_header_struct *h; + zend_llist_position pos; + if ( lsapi_mode ) { + LSAPI_SetRespStatus( SG(sapi_headers).http_response_code ); + + h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if ( h->header_len > 0 ) { + LSAPI_AppendRespHeader(h->header, h->header_len); + } + h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + if (SG(sapi_headers).send_default_content_type) { + char *hd; + int len; + char headerBuf[SAPI_LSAPI_MAX_HEADER_LENGTH]; + + hd = sapi_get_default_content_type(TSRMLS_C); + len = snprintf( headerBuf, SAPI_LSAPI_MAX_HEADER_LENGTH - 1, + "Content-type: %s", hd ); + efree(hd); + + LSAPI_AppendRespHeader( headerBuf, len ); + } + } + LSAPI_FinalizeRespHeaders(); + return SAPI_HEADER_SENT_SUCCESSFULLY; + + +} +/* }}} */ + + +/* {{{ sapi_lsapi_send_headers + */ +static void sapi_lsapi_log_message(char *message TSRMLS_DC) +{ + int len = strlen( message ); + LSAPI_Write_Stderr( message, len); +} +/* }}} */ + + +/* {{{ sapi_module_struct cgi_sapi_module + */ +static sapi_module_struct lsapi_sapi_module = +{ + "litespeed", + "LiteSpeed V5.5", + + php_lsapi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + sapi_lsapi_deactivate, /* deactivate */ + + sapi_lsapi_ub_write, /* unbuffered write */ + sapi_lsapi_flush, /* flush */ + NULL, /* get uid */ + sapi_lsapi_getenv, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_lsapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_lsapi_read_post, /* read POST data */ + sapi_lsapi_read_cookies, /* read Cookies */ + + sapi_lsapi_register_variables, /* register server variables */ + sapi_lsapi_log_message, /* Log message */ + + NULL, /* php.ini path override */ + NULL, /* block interruptions */ + NULL, /* unblock interruptions */ + NULL, /* default post reader */ + NULL, /* treat data */ + NULL, /* executable location */ + + 0, /* php.ini ignore */ + + STANDARD_SAPI_MODULE_PROPERTIES + +}; +/* }}} */ + +static int init_request_info( TSRMLS_D ) +{ + char * pContentType = LSAPI_GetHeader( H_CONTENT_TYPE ); + char * pAuth; + + SG(request_info).content_type = pContentType ? pContentType : ""; + SG(request_info).request_method = LSAPI_GetRequestMethod(); + SG(request_info).query_string = LSAPI_GetQueryString(); + SG(request_info).request_uri = LSAPI_GetScriptName(); + SG(request_info).content_length = LSAPI_GetReqBodyLen(); + SG(request_info).path_translated = estrdup( LSAPI_GetScriptFileName()); + + /* It is not reset by zend engine, set it to 0. */ + SG(sapi_headers).http_response_code = 0; + + pAuth = LSAPI_GetHeader( H_AUTHORIZATION ); + php_handle_auth_data(pAuth TSRMLS_CC); +} + +static char s_cur_chdir[4096] = ""; + +static int lsapi_chdir_primary_script( zend_file_handle * file_handle ) +{ +#if PHP_MAJOR_VERSION > 4 + char * p; + char ch; + + SG(options) |= SAPI_OPTION_NO_CHDIR; + getcwd( s_cur_chdir, sizeof( s_cur_chdir ) ); + + p = strrchr( file_handle->filename, '/' ); + if ( *p ) + { + *p = 0; + if ( strcmp( file_handle->filename, s_cur_chdir ) != 0 ) { + chdir( file_handle->filename ); + } + *p++ = '/'; + ch = *p; + *p = 0; + if ( !CWDG(cwd).cwd || + ( strcmp( file_handle->filename, CWDG(cwd).cwd ) != 0 ) ) { + CWDG(cwd).cwd_length = p - file_handle->filename; + CWDG(cwd).cwd = (char *) realloc(CWDG(cwd).cwd, CWDG(cwd).cwd_length+1); + memmove( CWDG(cwd).cwd, file_handle->filename, CWDG(cwd).cwd_length+1 ); + } + *p = ch; + } + /* virtual_file_ex(&CWDG(cwd), file_handle->filename, NULL, CWD_REALPATH); */ +#else + VCWD_CHDIR_FILE( file_handle->filename ); +#endif + return 0; +} + +static int lsapi_fopen_primary_script( zend_file_handle * file_handle ) +{ + FILE * fp; + char * p; + fp = fopen( SG(request_info).path_translated, "rb" ); + if ( !fp ) + { + return -1; + } + file_handle->type = ZEND_HANDLE_FP; + file_handle->handle.fp = fp; + file_handle->filename = SG(request_info).path_translated; + file_handle->free_filename = 0; + file_handle->opened_path = NULL; + + lsapi_chdir_primary_script( file_handle ); + + return 0; +} + +static int lsapi_execute_script( zend_file_handle * file_handle TSRMLS_DC) +{ + char *p; + int len; + file_handle->type = ZEND_HANDLE_FILENAME; + file_handle->handle.fd = 0; + file_handle->filename = SG(request_info).path_translated; + file_handle->free_filename = 0; + file_handle->opened_path = NULL; + + p = argv0; + *p++ = ':'; + len = strlen( SG(request_info).path_translated ); + if ( len > 45 ) + len = len - 45; + else + len = 0; + memccpy( p, SG(request_info).path_translated + len, 0, 46 ); + + php_execute_script(file_handle TSRMLS_CC); + return 0; + +} + + +static int lsapi_module_main(int show_source TSRMLS_DC) +{ + zend_file_handle file_handle = {0}; + + if (php_request_startup(TSRMLS_C) == FAILURE ) { + return -1; + } + if (show_source) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC); + } else { + lsapi_execute_script( &file_handle TSRMLS_CC); + } + zend_try { + php_request_shutdown(NULL); + *argv0 = 0; + } zend_end_try(); + return 0; +} + + +static int alter_ini( const char * pKey, int keyLen, const char * pValue, int valLen, + void * arg ) +{ + int type = ZEND_INI_PERDIR; + if ( '\001' == *pKey ) { + ++pKey; + if ( *pKey == 4 ) { + type = ZEND_INI_SYSTEM; + } + ++pKey; + --keyLen; + if (( keyLen == 7 )&&( strncasecmp( pKey, "engine", 6 )== 0 )) + { + if ( *pValue == '0' ) + engine = 0; + } + else + zend_alter_ini_entry((char *)pKey, keyLen, + (char *)pValue, valLen, + type, PHP_INI_STAGE_ACTIVATE); + } + return 1; +} + + +static void override_ini() +{ + + LSAPI_ForeachSpecialEnv( alter_ini, NULL ); + +} + +static int processReq( TSRMLS_D ) +{ + int ret = 0; + zend_first_try { + /* avoid server_context==NULL checks */ + SG(server_context) = (void *) 1; + + engine = 1; + override_ini(); + + if ( engine ) { + init_request_info( TSRMLS_C ); + + if ( lsapi_module_main( source_highlight TSRMLS_CC ) == -1 ) { + ret = -1; + } + } else { + LSAPI_AppendRespHeader( "status: 403", 11 ); + LSAPI_AppendRespHeader( "content-type: text/html", 23 ); + LSAPI_Write( "Forbidden: PHP engine is disable.\n", 34 ); + } + } zend_end_try(); + return ret; +} + +static void cli_usage( TSRMLS_D ) +{ + static const char * usage = + "Usage: php\n" + " php -[b|c|h|i|q|s|v|?] [<file>] [args...]\n" + " Run in LSAPI mode, only '-b', '-s' and '-c' are effective\n" + " Run in Command Line Interpreter mode when parameters are specified\n" + "\n" + " -b <address:port>|<port> Bind Path for external LSAPI Server mode\n" + " -c <path>|<file> Look for php.ini file in this directory\n" + " -h This help\n" + " -i PHP information\n" + " -q Quiet-mode. Suppress HTTP Header output.\n" + " -s Display colour syntax highlighted source.\n" + " -v Version number\n" + " -? This help\n" + "\n" + " args... Arguments passed to script.\n"; + php_output_startup(); + php_output_activate(TSRMLS_C); + php_printf( "%s", usage ); +#ifdef PHP_OUTPUT_NEWAPI + php_output_end_all(TSRMLS_C); +#else + php_end_ob_buffers(1 TSRMLS_CC); +#endif +} + +static int parse_opt( int argc, char * argv[], int *climode, + char **php_ini_path, char ** php_bind ) +{ + char ** p = &argv[1]; + char ** argend= &argv[argc]; + int c; + while (( p < argend )&&(**p == '-' )) { + c = *((*p)+1); + ++p; + switch( c ) { + case 'b': + if ( p >= argend ) { + fprintf( stderr, "TCP or socket address must be specified following '-b' option.\n"); + return -1; + } + *php_bind = *p++; + break; + + case 'c': + if ( p >= argend ) { + fprintf( stderr, "<path> or <file> must be specified following '-c' option.\n"); + + return -1; + } + *php_ini_path = *p++; + break; + case 's': + source_highlight = 1; + break; + case 'h': + case 'i': + case 'q': + case 'v': + case '?': + default: + *climode = 1; + break; + } + } + if ( p - argv < argc ) { + *climode = 1; + } + return 0; +} + +static int cli_main( int argc, char * argv[] ) +{ + + static const char * ini_defaults[] = { + "report_zend_debug", "0", + "display_errors", "1", + "register_argc_argv", "1", + "html_errors", "0", + "implicit_flush", "1", + "output_buffering", "0", + "max_execution_time", "0", + "max_input_time", "-1", + NULL + }; + + const char ** ini; + char ** p = &argv[1]; + char ** argend= &argv[argc]; + int ret = 0; + int c; + lsapi_mode = 0; /* enter CLI mode */ + +#ifdef PHP_WIN32 + _fmode = _O_BINARY; /*sets default for file streams to binary */ + setmode(_fileno(stdin), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */ + setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */ +#endif + + zend_first_try { + SG(server_context) = (void *) 1; + + zend_uv.html_errors = 0; /* tell the engine we're in non-html mode */ + CG(in_compilation) = 0; /* not initialized but needed for several options */ + EG(uninitialized_zval_ptr) = NULL; + + for( ini = ini_defaults; *ini; ini+=2 ) { + zend_alter_ini_entry( (char *)*ini, strlen( *ini )+1, + (char *)*(ini+1), strlen( *(ini+1) ), + PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + } + + while (( p < argend )&&(**p == '-' )) { + c = *((*p)+1); + ++p; + switch( c ) { + case 'q': + break; + case 'i': + if (php_request_startup(TSRMLS_C) != FAILURE) { + php_print_info(0xFFFFFFFF TSRMLS_CC); +#ifdef PHP_OUTPUT_NEWAPI + php_output_end_all(TSRMLS_C); +#else + php_end_ob_buffers(1 TSRMLS_CC); +#endif + php_request_shutdown( NULL ); + } + ret = 1; + break; + case 'v': + if (php_request_startup(TSRMLS_C) != FAILURE) { +#if ZEND_DEBUG + php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#else + php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); +#endif +#ifdef PHP_OUTPUT_NEWAPI + php_output_end_all(TSRMLS_C); +#else + php_end_ob_buffers(1 TSRMLS_CC); +#endif + php_request_shutdown( NULL ); + } + ret = 1; + break; + case 'c': + ++p; + /* fall through */ + case 's': + break; + + case 'h': + case '?': + default: + cli_usage(TSRMLS_C); + ret = 1; + break; + + } + } + if ( !ret ) { + if ( *p ) { + zend_file_handle file_handle = {0}; + + file_handle.type = ZEND_HANDLE_FP; + file_handle.handle.fp = VCWD_FOPEN(*p, "rb"); + + if ( file_handle.handle.fp ) { + script_filename = *p; + php_self = *p; + + SG(request_info).path_translated = estrdup(*p); + SG(request_info).argc = argc - (p - argv); + SG(request_info).argv = p; + + if (php_request_startup(TSRMLS_C) == FAILURE ) { + fclose( file_handle.handle.fp ); + ret = 2; + } else { + if (source_highlight) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC); + } else { + file_handle.filename = *p; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + } + + php_request_shutdown( NULL ); + } + } else { + php_printf("Could not open input file: %s.\n", *p); + } + } else { + cli_usage(TSRMLS_C); + } + } + + }zend_end_try(); + + php_module_shutdown(TSRMLS_C); + +#ifdef ZTS + tsrm_shutdown(); +#endif + return ret; +} + +static int s_stop; +void litespeed_cleanup(int signal) +{ + s_stop = signal; +} + + +void start_children( int children ) +{ + struct sigaction act, old_term, old_quit, old_int, old_usr1; + int running = 0; + int status; + pid_t pid; + + /* Create a process group */ + setsid(); + + /* Set up handler to kill children upon exit */ + act.sa_flags = 0; + act.sa_handler = litespeed_cleanup; + if( sigaction( SIGTERM, &act, &old_term ) || + sigaction( SIGINT, &act, &old_int ) || + sigaction( SIGUSR1, &act, &old_usr1 ) || + sigaction( SIGQUIT, &act, &old_quit )) { + perror( "Can't set signals" ); + exit( 1 ); + } + s_stop = 0; + while( 1 ) { + while((!s_stop )&&( running < children )) { + pid = fork(); + switch( pid ) { + case 0: /* children process */ + + /* don't catch our signals */ + sigaction( SIGTERM, &old_term, 0 ); + sigaction( SIGQUIT, &old_quit, 0 ); + sigaction( SIGINT, &old_int, 0 ); + sigaction( SIGUSR1, &old_usr1, 0 ); + return ; + case -1: + perror( "php (pre-forking)" ); + exit( 1 ); + break; + default: /* parent process */ + running++; + break; + } + } + if ( s_stop ) { + break; + } + pid = wait( &status ); + running--; + } + kill( -getpgrp(), SIGUSR1 ); + exit( 0 ); +} + + + +#include <fcntl.h> +int main( int argc, char * argv[] ) +{ + int ret; + int bindFd; + + char * php_ini_path = NULL; + char * php_bind = NULL; + char * p; + int n; + int climode = 0; + struct timeval tv_req_begin; + struct timeval tv_req_end; + int slow_script_msec = 0; + char time_buf[40]; + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); +#endif +#endif + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#endif + + if (argc > 1 ) { + if ( parse_opt( argc, argv, &climode, + &php_ini_path, &php_bind ) == -1 ) { + return 1; + } + } + if ( climode ) { + lsapi_sapi_module.phpinfo_as_text = 1; + } + argv0 = argv[0] + strlen( argv[0] ); + sapi_startup(&lsapi_sapi_module); + +#ifdef ZTS + compiler_globals = ts_resource(compiler_globals_id); + executor_globals = ts_resource(executor_globals_id); + core_globals = ts_resource(core_globals_id); + sapi_globals = ts_resource(sapi_globals_id); + tsrm_ls = ts_resource(0); + + SG(request_info).path_translated = NULL; +#endif + + lsapi_sapi_module.executable_location = argv[0]; + + if ( php_ini_path ) { + lsapi_sapi_module.php_ini_path_override = php_ini_path; + } + + + lsapi_sapi_module.ini_defaults = sapi_lsapi_ini_defaults; + + if (php_module_startup(&lsapi_sapi_module, &litespeed_module_entry, 1) == FAILURE) { +#ifdef ZTS + tsrm_shutdown(); +#endif + return FAILURE; + } + + if ( climode ) { + return cli_main(argc, argv); + } + + if ( php_bind ) { + bindFd = LSAPI_CreateListenSock( php_bind, 10 ); + if ( bindFd == -1 ) { + fprintf( stderr, + "Failed to bind socket [%s]: %s\n", php_bind, strerror( errno ) ); + exit( 2 ); + } + if ( bindFd != 0 ) { + dup2( bindFd, 0 ); + close( bindFd ); + } + } + + LSAPI_Init(); + + LSAPI_Init_Env_Parameters( NULL ); + + slow_script_msec = LSAPI_Get_Slow_Req_Msecs(); + + if ( php_bind ) { + LSAPI_No_Check_ppid(); + } + + while( LSAPI_Prefork_Accept_r( &g_req ) >= 0 ) { + if ( slow_script_msec ) { + gettimeofday( &tv_req_begin, NULL ); + } + ret = processReq(TSRMLS_C); + if ( slow_script_msec ) { + gettimeofday( &tv_req_end, NULL ); + n = ((long) tv_req_end.tv_sec - tv_req_begin.tv_sec ) * 1000 + + (tv_req_end.tv_usec - tv_req_begin.tv_usec) / 1000; + if ( n > slow_script_msec ) + { + strftime( time_buf, 30, "%d/%b/%Y:%H:%M:%S", localtime( &tv_req_end.tv_sec ) ); + fprintf( stderr, "[%s] Slow PHP script: %d ms\n URL: %s %s\n Query String: %s\n Script: %s\n", + time_buf, n, LSAPI_GetRequestMethod(), + LSAPI_GetScriptName(), LSAPI_GetQueryString(), + LSAPI_GetScriptFileName() ); + + } + } + LSAPI_Finish(); + if ( ret ) { + break; + } + } + php_module_shutdown(TSRMLS_C); + +#ifdef ZTS + tsrm_shutdown(); +#endif + return ret; +} + + +/* LiteSpeed PHP module starts here */ + +#if PHP_MAJOR_VERSION > 4 + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO(arginfo_litespeed__void, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +#else +#define arginfo_litespeed__void NULL +#endif + +PHP_FUNCTION(litespeed_request_headers); +PHP_FUNCTION(litespeed_response_headers); + +PHP_MINFO_FUNCTION(litespeed); + +zend_function_entry litespeed_functions[] = { + PHP_FE(litespeed_request_headers, arginfo_litespeed__void) + PHP_FE(litespeed_response_headers, arginfo_litespeed__void) + PHP_FALIAS(getallheaders, litespeed_request_headers, arginfo_litespeed__void) + PHP_FALIAS(apache_request_headers, litespeed_request_headers, arginfo_litespeed__void) + PHP_FALIAS(apache_response_headers, litespeed_response_headers, arginfo_litespeed__void) + {NULL, NULL, NULL} +}; + +static PHP_MINIT_FUNCTION(litespeed) +{ + /* REGISTER_INI_ENTRIES(); */ + return SUCCESS; +} + + +static PHP_MSHUTDOWN_FUNCTION(litespeed) +{ + /* UNREGISTER_INI_ENTRIES(); */ + return SUCCESS; +} + +zend_module_entry litespeed_module_entry = { + STANDARD_MODULE_HEADER, + "litespeed", + litespeed_functions, + PHP_MINIT(litespeed), + PHP_MSHUTDOWN(litespeed), + NULL, + NULL, + NULL, + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; + +static int add_associate_array( const char * pKey, int keyLen, const char * pValue, int valLen, + void * arg ) +{ + add_assoc_string_ex( (zval *)arg, (char *)pKey, keyLen+1, (char *)pValue, 1 ); + return 1; +} + + +/* {{{ proto array litespeed_request_headers(void) + Fetch all HTTP request headers */ +PHP_FUNCTION(litespeed_request_headers) +{ + /* TODO: */ + if (ZEND_NUM_ARGS() > 0) { + WRONG_PARAM_COUNT; + } + array_init(return_value); + + if ( lsapi_mode ) + LSAPI_ForeachOrgHeader( add_associate_array, return_value ); + +} +/* }}} */ + + + +/* {{{ proto array litespeed_response_headers(void) + Fetch all HTTP response headers */ +PHP_FUNCTION(litespeed_response_headers) +{ + sapi_header_struct *h; + zend_llist_position pos; + char * p; + int len; + char headerBuf[SAPI_LSAPI_MAX_HEADER_LENGTH]; + + if (ZEND_NUM_ARGS() > 0) { + WRONG_PARAM_COUNT; + } + + if (!&SG(sapi_headers).headers) { + RETURN_FALSE; + } + array_init(return_value); + + h = zend_llist_get_first_ex(&SG(sapi_headers).headers, &pos); + while (h) { + if ( h->header_len > 0 ) { + p = strchr( h->header, ':' ); + len = p - h->header; + if (( p )&&( len > 0 )) { + memmove( headerBuf, h->header, len ); + while( len > 0 && (isspace( headerBuf[len-1])) ) { + --len; + } + headerBuf[len] = 0; + if ( len ) { + while( isspace(*++p)); + add_assoc_string_ex(return_value, headerBuf, len+1, p, 1 ); + } + } + } + h = zend_llist_get_next_ex(&SG(sapi_headers).headers, &pos); + } +} + +/* }}} */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ + + diff --git a/sapi/litespeed/lsapidef.h b/sapi/litespeed/lsapidef.h new file mode 100644 index 0000000..c8940a9 --- /dev/null +++ b/sapi/litespeed/lsapidef.h @@ -0,0 +1,196 @@ + +/* + +----------------------------------------------------------------------+ + | 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 at 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. | + +----------------------------------------------------------------------+ + | Author: George Wang <gwang@litespeedtech.com> | + +----------------------------------------------------------------------+ +*/ + + +/* +Copyright (c) 2007, Lite Speed Technologies Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Lite Speed Technologies Inc nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef _LSAPIDEF_H_ +#define _LSAPIDEF_H_ + +#include <inttypes.h> + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +enum +{ + H_ACCEPT = 0, + H_ACC_CHARSET, + H_ACC_ENCODING, + H_ACC_LANG, + H_AUTHORIZATION, + H_CONNECTION, + H_CONTENT_TYPE, + H_CONTENT_LENGTH, + H_COOKIE, + H_COOKIE2, + H_HOST, + H_PRAGMA, + H_REFERER, + H_USERAGENT, + H_CACHE_CTRL, + H_IF_MODIFIED_SINCE, + H_IF_MATCH, + H_IF_NO_MATCH, + H_IF_RANGE, + H_IF_UNMOD_SINCE, + H_KEEP_ALIVE, + H_RANGE, + H_X_FORWARDED_FOR, + H_VIA, + H_TRANSFER_ENCODING + +}; +#define LSAPI_SOCK_FILENO 0 + +#define LSAPI_VERSION_B0 'L' +#define LSAPI_VERSION_B1 'S' + +/* Values for m_flag in lsapi_packet_header */ +#define LSAPI_ENDIAN_LITTLE 0 +#define LSAPI_ENDIAN_BIG 1 +#define LSAPI_ENDIAN_BIT 1 + +#if defined(__i386__)||defined( __x86_64 )||defined( __x86_64__ ) +#define LSAPI_ENDIAN LSAPI_ENDIAN_LITTLE +#else +#define LSAPI_ENDIAN LSAPI_ENDIAN_BIG +#endif + +/* Values for m_type in lsapi_packet_header */ +#define LSAPI_BEGIN_REQUEST 1 +#define LSAPI_ABORT_REQUEST 2 +#define LSAPI_RESP_HEADER 3 +#define LSAPI_RESP_STREAM 4 +#define LSAPI_RESP_END 5 +#define LSAPI_STDERR_STREAM 6 +#define LSAPI_REQ_RECEIVED 7 + + +#define LSAPI_MAX_HEADER_LEN 65535 +#define LSAPI_MAX_DATA_PACKET_LEN 16384 + +#define LSAPI_RESP_HTTP_HEADER_MAX 4096 +#define LSAPI_PACKET_HEADER_LEN 8 + + +struct lsapi_packet_header +{ + char m_versionB0; /* LSAPI protocol version */ + char m_versionB1; + char m_type; + char m_flag; + union + { + int32_t m_iLen; /* include this header */ + char m_bytes[4]; + }m_packetLen; +}; + +/* + LSAPI request header packet + + 1. struct lsapi_req_header + 2. struct lsapi_http_header_index + 3. lsapi_header_offset * unknownHeaders + 4. org http request header + 5. request body if available +*/ + +struct lsapi_req_header +{ + struct lsapi_packet_header m_pktHeader; + + int32_t m_httpHeaderLen; + int32_t m_reqBodyLen; + int32_t m_scriptFileOff; /* path to the script file. */ + int32_t m_scriptNameOff; /* decrypted URI, without pathinfo, */ + int32_t m_queryStringOff; /* Query string inside env */ + int32_t m_requestMethodOff; + int32_t m_cntUnknownHeaders; + int32_t m_cntEnv; + int32_t m_cntSpecialEnv; +} ; + + +struct lsapi_http_header_index +{ + int16_t m_headerLen[H_TRANSFER_ENCODING+1]; + int32_t m_headerOff[H_TRANSFER_ENCODING+1]; +} ; + +struct lsapi_header_offset +{ + int32_t nameOff; + int32_t nameLen; + int32_t valueOff; + int32_t valueLen; +} ; + +struct lsapi_resp_info +{ + int32_t m_cntHeaders; + int32_t m_status; +}; + +struct lsapi_resp_header +{ + struct lsapi_packet_header m_pktHeader; + struct lsapi_resp_info m_respInfo; +}; + +#if defined (c_plusplus) || defined (__cplusplus) +} +#endif + + +#endif + diff --git a/sapi/litespeed/lsapilib.c b/sapi/litespeed/lsapilib.c new file mode 100644 index 0000000..10ea749 --- /dev/null +++ b/sapi/litespeed/lsapilib.c @@ -0,0 +1,2227 @@ +/* + +----------------------------------------------------------------------+ + | 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 at 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. | + +----------------------------------------------------------------------+ + | Author: George Wang <gwang@litespeedtech.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +/* +Copyright (c) 2007, Lite Speed Technologies Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Lite Speed Technologies Inc nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <lsapilib.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/un.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#define LSAPI_ST_REQ_HEADER 1 +#define LSAPI_ST_REQ_BODY 2 +#define LSAPI_ST_RESP_HEADER 4 +#define LSAPI_ST_RESP_BODY 8 + +#define LSAPI_RESP_BUF_SIZE 8192 +#define LSAPI_INIT_RESP_HEADER_LEN 4096 + + +static int g_inited = 0; +static int g_running = 1; +static int s_ppid; +static int s_slow_req_msecs = 0; +LSAPI_Request g_req = { -1, -1 }; + +void Flush_RespBuf_r( LSAPI_Request * pReq ); + +static const char *CGI_HEADERS[H_TRANSFER_ENCODING+1] = +{ + "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", + "HTTP_ACCEPT_ENCODING", + "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHORIZATION", + "HTTP_CONNECTION", "CONTENT_TYPE", + "CONTENT_LENGTH", "HTTP_COOKIE", "HTTP_COOKIE2", + "HTTP_HOST", "HTTP_PRAGMA", + "HTTP_REFERER", "HTTP_USER_AGENT", + "HTTP_CACHE_CONTROL", + "HTTP_IF_MODIFIED_SINCE", "HTTP_IF_MATCH", + "HTTP_IF_NONE_MATCH", + "HTTP_IF_RANGE", + "HTTP_IF_UNMODIFIED_SINCE", + "HTTP_KEEP_ALIVE", + "HTTP_RANGE", + "HTTP_X_FORWARDED_FOR", + "HTTP_VIA", + "HTTP_TRANSFER_ENCODING" +}; + +static int CGI_HEADER_LEN[H_TRANSFER_ENCODING+1] = { + 11, 19, 20, 20, 18, 15, 12, 14, 11, 12, 9, 11, 12, 15, 18, + 22, 13, 18, 13, 24, 15, 10, 20, 8, 22 +}; + + +static const char *HTTP_HEADERS[H_TRANSFER_ENCODING+1] = { + "Accept", "Accept-Charset", + "Accept-Encoding", + "Accept-Language", "Authorization", + "Connection", "Content-Type", + "Content-Length", "Cookie", "Cookie2", + "Host", "Pragma", + "Referer", "User-Agent", + "Cache-Control", + "If-Modified-Since", "If-Match", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Keep-Alive", + "Range", + "X-Forwarded-For", + "Via", + "Transfer-Encoding" +}; + +static int HTTP_HEADER_LEN[H_TRANSFER_ENCODING+1] = { + 6, 14, 15, 15, 13, 10, 12, 14, 6, 7, 4, 6, 7, 10, /* user-agent */ + 13,17, 8, 13, 8, 19, 10, 5, 15, 3, 17 +}; + +static void lsapi_sigpipe( int sig ) +{ +} +static void lsapi_siguser1( int sig ) +{ + g_running = 0; +} + +#ifndef sighandler_t +typedef void (*sighandler_t)(int); +#endif + +static void lsapi_signal(int signo, sighandler_t handler) +{ + struct sigaction sa; + + sigaction(signo, NULL, &sa); + + if (sa.sa_handler == SIG_DFL) { + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = handler; + sigaction(signo, &sa, NULL); + } +} + + +static inline void lsapi_buildPacketHeader( struct lsapi_packet_header * pHeader, + char type, int len ) +{ + pHeader->m_versionB0 = LSAPI_VERSION_B0; /* LSAPI protocol version */ + pHeader->m_versionB1 = LSAPI_VERSION_B1; + pHeader->m_type = type; + pHeader->m_flag = LSAPI_ENDIAN; + pHeader->m_packetLen.m_iLen = len; +} + +static int lsapi_set_nblock( int fd, int nonblock ) +{ + int val = fcntl( fd, F_GETFL, 0 ); + if ( nonblock ) + { + if (!( val & O_NONBLOCK )) + { + return fcntl( fd, F_SETFL, val | O_NONBLOCK ); + } + } + else + { + if ( val & O_NONBLOCK ) + { + return fcntl( fd, F_SETFL, val &(~O_NONBLOCK) ); + } + } + return 0; +} + + +static int lsapi_close( int fd ) +{ + int ret; + while( 1 ) { + ret = close( fd ); + if (( ret == -1 )&&( errno == EINTR )&&(g_running)) { + continue; + } + return ret; + } +} + +static inline int lsapi_read( int fd, void * pBuf, int len ) +{ + int ret; + while( 1 ) { + ret = read( fd, (char *)pBuf, len ); + if (( ret == -1 )&&( errno == EINTR )&&(g_running)) { + continue; + } + return ret; + } +} + + +static int lsapi_writev( int fd, struct iovec ** pVec, int count, int totalLen ) +{ + int ret; + int left = totalLen; + int n = count; + while(( left > 0 )&&g_running ) { + ret = writev( fd, *pVec, n ); + if ( ret > 0 ) { + left -= ret; + if (( left <= 0)||( !g_running )) { + return totalLen - left; + } + while( ret > 0 ) { + if ( (*pVec)->iov_len <= ret ) { + ret -= (*pVec)->iov_len; + ++(*pVec); + } else { + (*pVec)->iov_base = (char *)(*pVec)->iov_base + ret; + (*pVec)->iov_len -= ret; + break; + } + } + } else if ( ret == -1 ) { + if ( errno == EAGAIN ) { + if ( totalLen - left > 0 ) { + return totalLen - left; + } else { + return -1; + } + } else { + if ( errno != EINTR ) { + return ret; + } + } + } + } + return totalLen - left; +} + +static inline int allocateBuf( LSAPI_Request * pReq, int size ) +{ + char * pBuf = (char *)realloc( pReq->m_pReqBuf, size ); + if ( pBuf ) { + pReq->m_pReqBuf = pBuf; + pReq->m_reqBufSize = size; + pReq->m_pHeader = (struct lsapi_req_header *)pReq->m_pReqBuf; + return 0; + } + return -1; +} + + +static int allocateIovec( LSAPI_Request * pReq, int n ) +{ + struct iovec * p = (struct iovec *)realloc( + pReq->m_pIovec, sizeof(struct iovec) * n ); + if ( !p ) { + return -1; + } + pReq->m_pIovecToWrite = p + ( pReq->m_pIovecToWrite - pReq->m_pIovec ); + pReq->m_pIovecCur = p + ( pReq->m_pIovecCur - pReq->m_pIovec ); + pReq->m_pIovec = p; + pReq->m_pIovecEnd = p + n; + return 0; +} + +static int allocateRespHeaderBuf( LSAPI_Request * pReq, int size ) +{ + char * p = (char *)realloc( pReq->m_pRespHeaderBuf, size ); + if ( !p ) { + return -1; + } + pReq->m_pRespHeaderBufPos = p + ( pReq->m_pRespHeaderBufPos - pReq->m_pRespHeaderBuf ); + pReq->m_pRespHeaderBuf = p; + pReq->m_pRespHeaderBufEnd = p + size; + return 0; +} + + +static inline int verifyHeader( struct lsapi_packet_header * pHeader, char pktType ) +{ + if (( LSAPI_VERSION_B0 != pHeader->m_versionB0 )|| + ( LSAPI_VERSION_B1 != pHeader->m_versionB1 )|| + ( pktType != pHeader->m_type )) { + return -1; + } + if ( LSAPI_ENDIAN != (pHeader->m_flag & LSAPI_ENDIAN_BIT )) { + register char b; + b = pHeader->m_packetLen.m_bytes[0]; + pHeader->m_packetLen.m_bytes[0] = pHeader->m_packetLen.m_bytes[3]; + pHeader->m_packetLen.m_bytes[3] = b; + b = pHeader->m_packetLen.m_bytes[1]; + pHeader->m_packetLen.m_bytes[1] = pHeader->m_packetLen.m_bytes[2]; + pHeader->m_packetLen.m_bytes[2] = b; + } + return pHeader->m_packetLen.m_iLen; +} + +static int allocateEnvList( struct LSAPI_key_value_pair ** pEnvList, + int *curSize, int newSize ) +{ + struct LSAPI_key_value_pair * pBuf; + if ( *curSize >= newSize ) { + return 0; + } + if ( newSize > 8192 ) { + return -1; + } + pBuf = (struct LSAPI_key_value_pair *)realloc( *pEnvList, newSize * + sizeof(struct LSAPI_key_value_pair) ); + if ( pBuf ) { + *pEnvList = pBuf; + *curSize = newSize; + return 0; + } else { + return -1; + } + +} + +static inline int isPipe( int fd ) +{ + char achPeer[128]; + socklen_t len = 128; + if (( getpeername( fd, (struct sockaddr *)achPeer, &len ) != 0 )&& + ( errno == ENOTCONN )) { + return 0; + } else { + return 1; + } +} + +static int parseEnv( struct LSAPI_key_value_pair * pEnvList, int count, + char **pBegin, char * pEnd ) +{ + struct LSAPI_key_value_pair * pEnvEnd; + int keyLen = 0, valLen = 0; + if ( count > 8192 ) { + return -1; + } + pEnvEnd = pEnvList + count; + while( pEnvList != pEnvEnd ) { + if ( pEnd - *pBegin < 4 ) { + return -1; + } + keyLen = *((unsigned char *)((*pBegin)++)); + keyLen = (keyLen << 8) + *((unsigned char *)((*pBegin)++)); + valLen = *((unsigned char *)((*pBegin)++)); + valLen = (valLen << 8) + *((unsigned char *)((*pBegin)++)); + if ( *pBegin + keyLen + valLen > pEnd ) { + return -1; + } + if (( !keyLen )||( !valLen )) { + return -1; + } + + pEnvList->pKey = *pBegin; + *pBegin += keyLen; + pEnvList->pValue = *pBegin; + *pBegin += valLen; + + pEnvList->keyLen = keyLen - 1; + pEnvList->valLen = valLen - 1; + ++pEnvList; + } + if ( memcmp( *pBegin, "\0\0\0\0", 4 ) != 0 ) { + return -1; + } + *pBegin += 4; + return 0; +} + +static inline void swapIntEndian( int * pInteger ) +{ + char * p = (char *)pInteger; + register char b; + b = p[0]; + p[0] = p[3]; + p[3] = b; + b = p[1]; + p[1] = p[2]; + p[2] = b; + +} + +static inline void fixEndian( LSAPI_Request * pReq ) +{ + struct lsapi_req_header *p= pReq->m_pHeader; + swapIntEndian( &p->m_httpHeaderLen ); + swapIntEndian( &p->m_reqBodyLen ); + swapIntEndian( &p->m_scriptFileOff ); + swapIntEndian( &p->m_scriptNameOff ); + swapIntEndian( &p->m_queryStringOff ); + swapIntEndian( &p->m_requestMethodOff ); + swapIntEndian( &p->m_cntUnknownHeaders ); + swapIntEndian( &p->m_cntEnv ); + swapIntEndian( &p->m_cntSpecialEnv ); +} + +static void fixHeaderIndexEndian( LSAPI_Request * pReq ) +{ + int i; + for( i = 0; i < H_TRANSFER_ENCODING; ++i ) { + if ( pReq->m_pHeaderIndex->m_headerOff[i] ) { + register char b; + char * p = (char *)(&pReq->m_pHeaderIndex->m_headerLen[i]); + b = p[0]; + p[0] = p[1]; + p[1] = b; + swapIntEndian( &pReq->m_pHeaderIndex->m_headerOff[i] ); + } + } + if ( pReq->m_pHeader->m_cntUnknownHeaders > 0 ) { + struct lsapi_header_offset * pCur, *pEnd; + pCur = pReq->m_pUnknownHeader; + pEnd = pCur + pReq->m_pHeader->m_cntUnknownHeaders; + while( pCur < pEnd ) { + swapIntEndian( &pCur->nameOff ); + swapIntEndian( &pCur->nameLen ); + swapIntEndian( &pCur->valueOff ); + swapIntEndian( &pCur->valueLen ); + ++pCur; + } + } +} + +static int parseRequest( LSAPI_Request * pReq, int totalLen ) +{ + int shouldFixEndian; + char * pBegin = pReq->m_pReqBuf + sizeof( struct lsapi_req_header ); + char * pEnd = pReq->m_pReqBuf + totalLen; + shouldFixEndian = ( LSAPI_ENDIAN != ( + pReq->m_pHeader->m_pktHeader.m_flag & LSAPI_ENDIAN_BIT ) ); + if ( shouldFixEndian ) { + fixEndian( pReq ); + } + if ( (pReq->m_specialEnvListSize < pReq->m_pHeader->m_cntSpecialEnv )&& + allocateEnvList( &pReq->m_pSpecialEnvList, + &pReq->m_specialEnvListSize, + pReq->m_pHeader->m_cntSpecialEnv ) == -1 ) { + return -1; + } + if ( (pReq->m_envListSize < pReq->m_pHeader->m_cntEnv )&& + allocateEnvList( &pReq->m_pEnvList, &pReq->m_envListSize, + pReq->m_pHeader->m_cntEnv ) == -1 ) { + return -1; + } + if ( parseEnv( pReq->m_pSpecialEnvList, + pReq->m_pHeader->m_cntSpecialEnv, + &pBegin, pEnd ) == -1 ) { + return -1; + } + if ( parseEnv( pReq->m_pEnvList, pReq->m_pHeader->m_cntEnv, + &pBegin, pEnd ) == -1 ) { + return -1; + } + pReq->m_pScriptFile = pReq->m_pReqBuf + pReq->m_pHeader->m_scriptFileOff; + pReq->m_pScriptName = pReq->m_pReqBuf + pReq->m_pHeader->m_scriptNameOff; + pReq->m_pQueryString = pReq->m_pReqBuf + pReq->m_pHeader->m_queryStringOff; + pReq->m_pRequestMethod = pReq->m_pReqBuf + pReq->m_pHeader->m_requestMethodOff; + + pBegin = pReq->m_pReqBuf + (( pBegin - pReq->m_pReqBuf + 7 ) & (~0x7)); + pReq->m_pHeaderIndex = ( struct lsapi_http_header_index * )pBegin; + pBegin += sizeof( struct lsapi_http_header_index ); + + pReq->m_pUnknownHeader = (struct lsapi_header_offset *)pBegin; + pBegin += sizeof( struct lsapi_header_offset) * + pReq->m_pHeader->m_cntUnknownHeaders; + + pReq->m_pHttpHeader = pBegin; + pBegin += pReq->m_pHeader->m_httpHeaderLen; + if ( pBegin != pEnd ) { + return -1; + } + + if ( shouldFixEndian ) { + fixHeaderIndexEndian( pReq ); + } + + return 0; +} + +static int s_accept_notify = 0; + +static struct lsapi_packet_header ack = {'L', 'S', + LSAPI_REQ_RECEIVED, LSAPI_ENDIAN, {LSAPI_PACKET_HEADER_LEN} }; +static inline int notify_req_received( int fd ) +{ + if ( write( fd, &ack, LSAPI_PACKET_HEADER_LEN ) + < LSAPI_PACKET_HEADER_LEN ) { + return -1; + } + return 0; +} + + +static int readReq( LSAPI_Request * pReq ) +{ + int len; + int packetLen; + if ( !pReq ) { + return -1; + } + if ( pReq->m_reqBufSize < 8192 ) { + if ( allocateBuf( pReq, 8192 ) == -1 ) { + return -1; + } + } + + while ( pReq->m_bufRead < LSAPI_PACKET_HEADER_LEN ) { + len = lsapi_read( pReq->m_fd, pReq->m_pReqBuf, pReq->m_reqBufSize ); + if ( len <= 0 ) { + return -1; + } + pReq->m_bufRead += len; + } + pReq->m_reqState = LSAPI_ST_REQ_HEADER; + + packetLen = verifyHeader( &pReq->m_pHeader->m_pktHeader, LSAPI_BEGIN_REQUEST ); + if ( packetLen < 0 ) { + return -1; + } + if ( packetLen > LSAPI_MAX_HEADER_LEN ) { + return -1; + } + + if ( packetLen + 1024 > pReq->m_reqBufSize ) { + if ( allocateBuf( pReq, packetLen + 1024 ) == -1 ) { + return -1; + } + } + while( packetLen > pReq->m_bufRead ) { + len = lsapi_read( pReq->m_fd, pReq->m_pReqBuf + pReq->m_bufRead, packetLen - pReq->m_bufRead ); + if ( len <= 0 ) { + return -1; + } + pReq->m_bufRead += len; + } + if ( parseRequest( pReq, packetLen ) < 0 ) { + return -1; + } + pReq->m_bufProcessed = packetLen; + pReq->m_reqState = LSAPI_ST_REQ_BODY | LSAPI_ST_RESP_HEADER; + + if ( !s_accept_notify ) + return notify_req_received( pReq->m_fd ); + else + return 0; +} + + + +int LSAPI_Init(void) +{ + if ( !g_inited ) { + lsapi_signal(SIGPIPE, lsapi_sigpipe); + lsapi_signal(SIGUSR1, lsapi_siguser1); + +#if defined(SIGXFSZ) && defined(SIG_IGN) + signal(SIGXFSZ, SIG_IGN); +#endif + /* let STDOUT function as STDERR, + just in case writing to STDOUT directly */ + dup2( 2, 1 ); + + if ( LSAPI_InitRequest( &g_req, LSAPI_SOCK_FILENO ) == -1 ) { + return -1; + } + g_inited = 1; + s_ppid = getppid(); + } + return 0; +} + +void LSAPI_Stop(void) +{ + g_running = 0; +} + +int LSAPI_IsRunning(void) +{ + return g_running; +} + +int LSAPI_InitRequest( LSAPI_Request * pReq, int fd ) +{ + if ( !pReq ) { + return -1; + } + memset( pReq, 0, sizeof( LSAPI_Request ) ); + if ( allocateIovec( pReq, 16 ) == -1 ) { + return -1; + } + pReq->m_pRespBuf = pReq->m_pRespBufPos = (char *)malloc( LSAPI_RESP_BUF_SIZE ); + if ( !pReq->m_pRespBuf ) { + return -1; + } + pReq->m_pRespBufEnd = pReq->m_pRespBuf + LSAPI_RESP_BUF_SIZE; + pReq->m_pIovecCur = pReq->m_pIovecToWrite = pReq->m_pIovec + 1; + pReq->m_respPktHeaderEnd = &pReq->m_respPktHeader[5]; + if ( allocateRespHeaderBuf( pReq, LSAPI_INIT_RESP_HEADER_LEN ) == -1 ) { + return -1; + } + + if ( isPipe( fd ) ) { + pReq->m_fdListen = -1; + pReq->m_fd = fd; + } else { + pReq->m_fdListen = fd; + pReq->m_fd = -1; + lsapi_set_nblock( fd, 1 ); + } + return 0; +} + +int LSAPI_Is_Listen( void ) +{ + return LSAPI_Is_Listen_r( &g_req ); +} + +int LSAPI_Is_Listen_r( LSAPI_Request * pReq) +{ + return pReq->m_fdListen != -1; +} + + + +int LSAPI_Accept_r( LSAPI_Request * pReq ) +{ + char achPeer[128]; + socklen_t len; + int nodelay = 1; + + if ( !pReq ) { + return -1; + } + if ( LSAPI_Finish_r( pReq ) == -1 ) { + return -1; + } + while( g_running ) { + if ( pReq->m_fd == -1 ) { + if ( pReq->m_fdListen != -1) { + len = sizeof( achPeer ); + pReq->m_fd = accept( pReq->m_fdListen, + (struct sockaddr *)&achPeer, &len ); + if ( pReq->m_fd == -1 ) { + if (( errno == EINTR )||( errno == EAGAIN)) { + continue; + } else { + return -1; + } + } else { + lsapi_set_nblock( pReq->m_fd , 0 ); + if (((struct sockaddr *)&achPeer)->sa_family == AF_INET ) { + setsockopt(pReq->m_fd, IPPROTO_TCP, TCP_NODELAY, + (char *)&nodelay, sizeof(nodelay)); + } + + if ( s_accept_notify ) + return notify_req_received( pReq->m_fd ); + + } + } else { + return -1; + } + } + if ( !readReq( pReq ) ) { + break; + } + lsapi_close( pReq->m_fd ); + pReq->m_fd = -1; + LSAPI_Reset_r( pReq ); + } + return 0; +} + +static struct lsapi_packet_header finish = {'L', 'S', + LSAPI_RESP_END, LSAPI_ENDIAN, {LSAPI_PACKET_HEADER_LEN} }; + +int LSAPI_Finish_r( LSAPI_Request * pReq ) +{ + /* finish req body */ + if ( !pReq ) { + return -1; + } + if (pReq->m_reqState) { + if ( pReq->m_fd != -1 ) { + if ( pReq->m_reqState & LSAPI_ST_RESP_HEADER ) { + LSAPI_FinalizeRespHeaders_r( pReq ); + } + if ( pReq->m_pRespBufPos != pReq->m_pRespBuf ) { + Flush_RespBuf_r( pReq ); + } + + pReq->m_pIovecCur->iov_base = (void *)&finish; + pReq->m_pIovecCur->iov_len = LSAPI_PACKET_HEADER_LEN; + pReq->m_totalLen += LSAPI_PACKET_HEADER_LEN; + ++pReq->m_pIovecCur; + LSAPI_Flush_r( pReq ); + } + LSAPI_Reset_r( pReq ); + } + return 0; +} + + +void LSAPI_Reset_r( LSAPI_Request * pReq ) +{ + pReq->m_pRespBufPos = pReq->m_pRespBuf; + pReq->m_pIovecCur = pReq->m_pIovecToWrite = pReq->m_pIovec + 1; + pReq->m_pRespHeaderBufPos = pReq->m_pRespHeaderBuf; + + memset( &pReq->m_pHeaderIndex, 0, + (char *)(pReq->m_respHeaderLen) - (char *)&pReq->m_pHeaderIndex ); +} + + +int LSAPI_Release_r( LSAPI_Request * pReq ) +{ + if ( pReq->m_pReqBuf ) { + free( pReq->m_pReqBuf ); + } + if ( pReq->m_pSpecialEnvList ) { + free( pReq->m_pSpecialEnvList ); + } + if ( pReq->m_pEnvList ) { + free( pReq->m_pEnvList ); + } + if ( pReq->m_pRespHeaderBuf ) { + free( pReq->m_pRespHeaderBuf ); + } + return 0; +} + + +char * LSAPI_GetHeader_r( LSAPI_Request * pReq, int headerIndex ) +{ + int off; + if ( !pReq || ((unsigned int)headerIndex > H_TRANSFER_ENCODING) ) { + return NULL; + } + off = pReq->m_pHeaderIndex->m_headerOff[ headerIndex ]; + if ( !off ) { + return NULL; + } + if ( *(pReq->m_pHttpHeader + off + + pReq->m_pHeaderIndex->m_headerLen[ headerIndex ]) ) { + *( pReq->m_pHttpHeader + off + + pReq->m_pHeaderIndex->m_headerLen[ headerIndex ]) = 0; + } + return pReq->m_pHttpHeader + off; +} + +static int readBodyToReqBuf( LSAPI_Request * pReq ) +{ + int bodyLeft; + int len = pReq->m_bufRead - pReq->m_bufProcessed; + if ( len > 0 ) { + return len; + } + pReq->m_bufRead = pReq->m_bufProcessed = pReq->m_pHeader->m_pktHeader.m_packetLen.m_iLen; + + bodyLeft = pReq->m_pHeader->m_reqBodyLen - pReq->m_reqBodyRead; + len = pReq->m_reqBufSize - pReq->m_bufRead; + if ( len < 0 ) { + return -1; + } + if ( len > bodyLeft ) { + len = bodyLeft; + } + len = lsapi_read( pReq->m_fd, pReq->m_pReqBuf + pReq->m_bufRead, len ); + if ( len > 0 ) { + pReq->m_bufRead += len; + } + return len; +} + + +int LSAPI_ReqBodyGetChar_r( LSAPI_Request * pReq ) +{ + if (!pReq || (pReq->m_fd ==-1) ) { + return EOF; + } + if ( pReq->m_bufProcessed >= pReq->m_bufRead ) { + if ( readBodyToReqBuf( pReq ) <= 0 ) { + return EOF; + } + } + ++pReq->m_reqBodyRead; + return (unsigned char)*(pReq->m_pReqBuf + pReq->m_bufProcessed++); +} + + + +int LSAPI_ReqBodyGetLine_r( LSAPI_Request * pReq, char * pBuf, int bufLen, int *getLF ) +{ + int len; + int left; + char * pBufEnd = pBuf + bufLen - 1; + char * pBufCur = pBuf; + char * pCur; + char * p; + if (!pReq || (pReq->m_fd ==-1) ||( !pBuf )||(bufLen < 0 )|| !getLF ) { + return -1; + } + *getLF = 0; + while( (left = pBufEnd - pBufCur ) > 0 ) { + + len = pReq->m_bufRead - pReq->m_bufProcessed; + if ( len <= 0 ) { + if ( (len = readBodyToReqBuf( pReq )) <= 0 ) { + *getLF = 1; + break; + } + } + if ( len > left ) { + len = left; + } + pCur = pReq->m_pReqBuf + pReq->m_bufProcessed; + p = memchr( pCur, '\n', len ); + if ( p ) { + len = p - pCur + 1; + } + memmove( pBufCur, pCur, len ); + pBufCur += len; + pReq->m_bufProcessed += len; + + pReq->m_reqBodyRead += len; + + if ( p ) { + *getLF = 1; + break; + } + } + *pBufCur = 0; + + return pBufCur - pBuf; +} + + +int LSAPI_ReadReqBody_r( LSAPI_Request * pReq, char * pBuf, int bufLen ) +{ + int len; + int total; + /* char *pOldBuf = pBuf; */ + if (!pReq || (pReq->m_fd ==-1) || ( !pBuf )||(bufLen < 0 )) { + return -1; + } + total = pReq->m_pHeader->m_reqBodyLen - pReq->m_reqBodyRead; + + if ( total <= 0 ) { + return 0; + } + if ( total < bufLen ) { + bufLen = total; + } + + total = 0; + len = pReq->m_bufRead - pReq->m_bufProcessed; + if ( len > 0 ) { + if ( len > bufLen ) { + len = bufLen; + } + memmove( pBuf, pReq->m_pReqBuf + pReq->m_bufProcessed, len ); + pReq->m_bufProcessed += len; + total += len; + pBuf += len; + bufLen -= len; + } + while( bufLen > 0 ) { + len = lsapi_read( pReq->m_fd, pBuf, bufLen ); + if ( len > 0 ) { + total += len; + pBuf += len; + bufLen -= len; + } else { + if ( len <= 0 ) { + if ( !total) { + return -1; + } + break; + } + } + } + pReq->m_reqBodyRead += total; + return total; + +} + + +int LSAPI_Write_r( LSAPI_Request * pReq, const char * pBuf, int len ) +{ + struct lsapi_packet_header * pHeader; + const char * pEnd; + const char * p; + int bufLen; + int toWrite; + int packetLen; + + if ( !pReq || !pBuf || (pReq->m_fd == -1) ) { + return -1; + } + if ( len < pReq->m_pRespBufEnd - pReq->m_pRespBufPos ) { + memmove( pReq->m_pRespBufPos, pBuf, len ); + pReq->m_pRespBufPos += len; + return len; + } + + if ( pReq->m_reqState & LSAPI_ST_RESP_HEADER ) { + LSAPI_FinalizeRespHeaders_r( pReq ); + } + pReq->m_reqState |= LSAPI_ST_RESP_BODY; + + pHeader = pReq->m_respPktHeader; + p = pBuf; + pEnd = pBuf + len; + bufLen = pReq->m_pRespBufPos - pReq->m_pRespBuf; + + while( ( toWrite = pEnd - p ) > 0 ) { + packetLen = toWrite + bufLen; + if ( LSAPI_MAX_DATA_PACKET_LEN < packetLen) { + packetLen = LSAPI_MAX_DATA_PACKET_LEN; + toWrite = packetLen - bufLen; + } + + lsapi_buildPacketHeader( pHeader, LSAPI_RESP_STREAM, + packetLen + LSAPI_PACKET_HEADER_LEN ); + pReq->m_totalLen += packetLen + LSAPI_PACKET_HEADER_LEN; + + pReq->m_pIovecCur->iov_base = (void *)pHeader; + pReq->m_pIovecCur->iov_len = LSAPI_PACKET_HEADER_LEN; + ++pReq->m_pIovecCur; + ++pHeader; + if ( bufLen > 0 ) { + pReq->m_pIovecCur->iov_base = (void *)pReq->m_pRespBuf; + pReq->m_pIovecCur->iov_len = bufLen; + pReq->m_pRespBufPos = pReq->m_pRespBuf; + ++pReq->m_pIovecCur; + bufLen = 0; + } + + pReq->m_pIovecCur->iov_base = (void *)p; + pReq->m_pIovecCur->iov_len = toWrite; + ++pReq->m_pIovecCur; + p += toWrite; + + if ( pHeader >= pReq->m_respPktHeaderEnd - 1) { + if ( LSAPI_Flush_r( pReq ) == -1 ) { + return -1; + } + pHeader = pReq->m_respPktHeader; + } + } + if ( pHeader != pReq->m_respPktHeader ) { + if ( LSAPI_Flush_r( pReq ) == -1 ) { + return -1; + } + } + return p - pBuf; +} + +void Flush_RespBuf_r( LSAPI_Request * pReq ) +{ + struct lsapi_packet_header * pHeader = pReq->m_respPktHeader; + int bufLen = pReq->m_pRespBufPos - pReq->m_pRespBuf; + pReq->m_reqState |= LSAPI_ST_RESP_BODY; + lsapi_buildPacketHeader( pHeader, LSAPI_RESP_STREAM, + bufLen + LSAPI_PACKET_HEADER_LEN ); + pReq->m_totalLen += bufLen + LSAPI_PACKET_HEADER_LEN; + + pReq->m_pIovecCur->iov_base = (void *)pHeader; + pReq->m_pIovecCur->iov_len = LSAPI_PACKET_HEADER_LEN; + ++pReq->m_pIovecCur; + ++pHeader; + if ( bufLen > 0 ) { + pReq->m_pIovecCur->iov_base = (void *)pReq->m_pRespBuf; + pReq->m_pIovecCur->iov_len = bufLen; + pReq->m_pRespBufPos = pReq->m_pRespBuf; + ++pReq->m_pIovecCur; + bufLen = 0; + } +} + + + + +int LSAPI_Flush_r( LSAPI_Request * pReq ) +{ + int ret = 0; + int n; + if ( !pReq ) { + return -1; + } + n = pReq->m_pIovecCur - pReq->m_pIovecToWrite; + if (( 0 == n )&&( pReq->m_pRespBufPos == pReq->m_pRespBuf )) { + return 0; + } + if ( pReq->m_fd == -1 ) { + pReq->m_pRespBufPos = pReq->m_pRespBuf; + pReq->m_totalLen = 0; + pReq->m_pIovecCur = pReq->m_pIovecToWrite = pReq->m_pIovec; + return -1; + } + if ( pReq->m_reqState & LSAPI_ST_RESP_HEADER ) { + LSAPI_FinalizeRespHeaders_r( pReq ); + } + if ( pReq->m_pRespBufPos != pReq->m_pRespBuf ) { + Flush_RespBuf_r( pReq ); + } + + n = pReq->m_pIovecCur - pReq->m_pIovecToWrite; + if ( n > 0 ) { + + ret = lsapi_writev( pReq->m_fd, &pReq->m_pIovecToWrite, + n, pReq->m_totalLen ); + if ( ret < pReq->m_totalLen ) { + lsapi_close( pReq->m_fd ); + pReq->m_fd = -1; + ret = -1; + } + pReq->m_totalLen = 0; + pReq->m_pIovecCur = pReq->m_pIovecToWrite = pReq->m_pIovec; + } + return ret; +} + + +int LSAPI_Write_Stderr_r( LSAPI_Request * pReq, const char * pBuf, int len ) +{ + struct lsapi_packet_header header; + const char * pEnd; + const char * p; + int packetLen; + int totalLen; + int ret; + struct iovec iov[2]; + struct iovec *pIov; + + if ( !pReq ) { + return -1; + } + if (( pReq->m_fd == -1 )||(pReq->m_fd == pReq->m_fdListen )) { + return write( 2, pBuf, len ); + } + if ( pReq->m_pRespBufPos != pReq->m_pRespBuf ) { + LSAPI_Flush_r( pReq ); + } + + p = pBuf; + pEnd = pBuf + len; + + while( ( packetLen = pEnd - p ) > 0 ) { + if ( LSAPI_MAX_DATA_PACKET_LEN < packetLen) { + packetLen = LSAPI_MAX_DATA_PACKET_LEN; + } + + lsapi_buildPacketHeader( &header, LSAPI_STDERR_STREAM, + packetLen + LSAPI_PACKET_HEADER_LEN ); + totalLen = packetLen + LSAPI_PACKET_HEADER_LEN; + + iov[0].iov_base = (void *)&header; + iov[0].iov_len = LSAPI_PACKET_HEADER_LEN; + + iov[1].iov_base = (void *)p; + iov[1].iov_len = packetLen; + p += packetLen; + pIov = iov; + ret = lsapi_writev( pReq->m_fd, &pIov, + 2, totalLen ); + if ( ret < totalLen ) { + lsapi_close( pReq->m_fd ); + pReq->m_fd = -1; + ret = -1; + } + } + return p - pBuf; +} + +static char * GetHeaderVar( LSAPI_Request * pReq, const char * name ) +{ + int i; + for( i = 0; i < H_TRANSFER_ENCODING; ++i ) { + if ( pReq->m_pHeaderIndex->m_headerOff[i] ) { + if ( strcmp( name, CGI_HEADERS[i] ) == 0 ) { + return pReq->m_pHttpHeader + pReq->m_pHeaderIndex->m_headerOff[i]; + } + } + } + if ( pReq->m_pHeader->m_cntUnknownHeaders > 0 ) { + const char *p; + char *pKey; + char *pKeyEnd; + int keyLen; + struct lsapi_header_offset * pCur, *pEnd; + pCur = pReq->m_pUnknownHeader; + pEnd = pCur + pReq->m_pHeader->m_cntUnknownHeaders; + while( pCur < pEnd ) { + pKey = pReq->m_pHttpHeader + pCur->nameOff; + keyLen = pCur->nameLen; + pKeyEnd = pKey + keyLen; + p = &name[5]; + + while(( pKey < pKeyEnd )&&( *p )) { + char ch = toupper( *pKey ); + if ((ch != *p )||(( *p == '_' )&&( ch != '-'))) { + break; + } + ++p; ++pKey; + } + if (( pKey == pKeyEnd )&& (!*p )) { + return pReq->m_pHttpHeader + pCur->valueOff; + } + ++pCur; + } + } + return NULL; +} + + +char * LSAPI_GetEnv_r( LSAPI_Request * pReq, const char * name ) +{ + struct LSAPI_key_value_pair * pBegin = pReq->m_pEnvList; + struct LSAPI_key_value_pair * pEnd = pBegin + pReq->m_pHeader->m_cntEnv; + if ( !pReq || !name ) { + return NULL; + } + if ( strncmp( name, "HTTP_", 5 ) == 0 ) { + return GetHeaderVar( pReq, name ); + } + while( pBegin < pEnd ) { + if ( strcmp( name, pBegin->pKey ) == 0 ) { + return pBegin->pValue; + } + ++pBegin; + } + return NULL; +} + +int LSAPI_ForeachOrgHeader_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ) +{ + int i; + int len = 0; + char * pValue; + int ret; + int count = 0; + if ( !pReq || !fn ) { + return -1; + } + for( i = 0; i < H_TRANSFER_ENCODING; ++i ) { + if ( pReq->m_pHeaderIndex->m_headerOff[i] ) { + len = pReq->m_pHeaderIndex->m_headerLen[i]; + pValue = pReq->m_pHttpHeader + pReq->m_pHeaderIndex->m_headerOff[i]; + *(pValue + len ) = 0; + ret = (*fn)( HTTP_HEADERS[i], HTTP_HEADER_LEN[i], + pValue, len, arg ); + ++count; + if ( ret <= 0 ) { + return ret; + } + } + } + if ( pReq->m_pHeader->m_cntUnknownHeaders > 0 ) { + char *pKey; + int keyLen; + struct lsapi_header_offset * pCur, *pEnd; + pCur = pReq->m_pUnknownHeader; + pEnd = pCur + pReq->m_pHeader->m_cntUnknownHeaders; + while( pCur < pEnd ) { + pKey = pReq->m_pHttpHeader + pCur->nameOff; + keyLen = pCur->nameLen; + + pValue = pReq->m_pHttpHeader + pCur->valueOff; + *(pValue + pCur->valueLen ) = 0; + ret = (*fn)( pKey, keyLen, + pValue, pCur->valueLen, arg ); + if ( ret <= 0 ) { + return ret; + } + ++pCur; + } + } + return count + pReq->m_pHeader->m_cntUnknownHeaders; + +} + + +int LSAPI_ForeachHeader_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ) +{ + int i; + int len = 0; + char * pValue; + int ret; + int count = 0; + if ( !pReq || !fn ) { + return -1; + } + for( i = 0; i < H_TRANSFER_ENCODING; ++i ) { + if ( pReq->m_pHeaderIndex->m_headerOff[i] ) { + len = pReq->m_pHeaderIndex->m_headerLen[i]; + pValue = pReq->m_pHttpHeader + pReq->m_pHeaderIndex->m_headerOff[i]; + *(pValue + len ) = 0; + ret = (*fn)( CGI_HEADERS[i], CGI_HEADER_LEN[i], + pValue, len, arg ); + ++count; + if ( ret <= 0 ) { + return ret; + } + } + } + if ( pReq->m_pHeader->m_cntUnknownHeaders > 0 ) { + char achHeaderName[256]; + char *p; + char *pKey; + char *pKeyEnd ; + int keyLen; + struct lsapi_header_offset * pCur, *pEnd; + pCur = pReq->m_pUnknownHeader; + pEnd = pCur + pReq->m_pHeader->m_cntUnknownHeaders; + while( pCur < pEnd ) { + pKey = pReq->m_pHttpHeader + pCur->nameOff; + keyLen = pCur->nameLen; + if ( keyLen > 250 ) { + keyLen = 250; + } + + pKeyEnd = pKey + keyLen; + memcpy( achHeaderName, "HTTP_", 5 ); + p = &achHeaderName[5]; + + while( pKey < pKeyEnd ) { + char ch = *pKey++; + if ( ch == '-' ) { + *p++ = '_'; + } else { + *p++ = toupper( ch ); + } + } + *p = 0; + keyLen += 5; + + pValue = pReq->m_pHttpHeader + pCur->valueOff; + *(pValue + pCur->valueLen ) = 0; + ret = (*fn)( achHeaderName, keyLen, + pValue, pCur->valueLen, arg ); + if ( ret <= 0 ) { + return ret; + } + ++pCur; + } + } + return count + pReq->m_pHeader->m_cntUnknownHeaders; + +} + +static int EnvForeach( struct LSAPI_key_value_pair * pEnv, + int n, LSAPI_CB_EnvHandler fn, void * arg ) +{ + struct LSAPI_key_value_pair * pEnd = pEnv + n; + int ret; + if ( !pEnv || !fn ) { + return -1; + } + while( pEnv < pEnd ) { + ret = (*fn)( pEnv->pKey, pEnv->keyLen, + pEnv->pValue, pEnv->valLen, arg ); + if ( ret <= 0 ) { + return ret; + } + ++pEnv; + } + return n; +} + + + +int LSAPI_ForeachEnv_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ) +{ + if ( !pReq || !fn ) { + return -1; + } + if ( pReq->m_pHeader->m_cntEnv > 0 ) { + return EnvForeach( pReq->m_pEnvList, pReq->m_pHeader->m_cntEnv, + fn, arg ); + } + return 0; +} + + + +int LSAPI_ForeachSpecialEnv_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ) +{ + if ( !pReq || !fn ) { + return -1; + } + if ( pReq->m_pHeader->m_cntSpecialEnv > 0 ) { + return EnvForeach( pReq->m_pSpecialEnvList, + pReq->m_pHeader->m_cntSpecialEnv, + fn, arg ); + } + return 0; + +} + + + +int LSAPI_FinalizeRespHeaders_r( LSAPI_Request * pReq ) +{ + if ( !pReq || !pReq->m_pIovec ) { + return -1; + } + if ( !( pReq->m_reqState & LSAPI_ST_RESP_HEADER ) ) { + return 0; + } + pReq->m_reqState &= ~LSAPI_ST_RESP_HEADER; + if ( pReq->m_pRespHeaderBufPos > pReq->m_pRespHeaderBuf ) { + pReq->m_pIovecCur->iov_base = (void *)pReq->m_pRespHeaderBuf; + pReq->m_pIovecCur->iov_len = pReq->m_pRespHeaderBufPos - pReq->m_pRespHeaderBuf; + pReq->m_totalLen += pReq->m_pIovecCur->iov_len; + ++pReq->m_pIovecCur; + } + + pReq->m_pIovec->iov_len = sizeof( struct lsapi_resp_header) + + pReq->m_respHeader.m_respInfo.m_cntHeaders * sizeof( short ); + pReq->m_totalLen += pReq->m_pIovec->iov_len; + + lsapi_buildPacketHeader( &pReq->m_respHeader.m_pktHeader, + LSAPI_RESP_HEADER, pReq->m_totalLen ); + pReq->m_pIovec->iov_base = (void *)&pReq->m_respHeader; + pReq->m_pIovecToWrite = pReq->m_pIovec; + return 0; +} + + + + +int LSAPI_AppendRespHeader_r( LSAPI_Request * pReq, char * pBuf, int len ) +{ + if ( !pReq || !pBuf || len <= 0 || len > LSAPI_RESP_HTTP_HEADER_MAX ) { + return -1; + } + if ( pReq->m_reqState & LSAPI_ST_RESP_BODY ) { + return -1; + } + if ( pReq->m_respHeader.m_respInfo.m_cntHeaders >= LSAPI_MAX_RESP_HEADERS ) { + return -1; + } + if ( pReq->m_pRespHeaderBufPos + len + 1 > pReq->m_pRespHeaderBufEnd ) { + int newlen = pReq->m_pRespHeaderBufPos + len + 4096 - pReq->m_pRespHeaderBuf; + newlen -= newlen % 4096; + if ( allocateRespHeaderBuf( pReq, newlen ) == -1 ) { + return -1; + } + } + memmove( pReq->m_pRespHeaderBufPos, pBuf, len ); + pReq->m_pRespHeaderBufPos += len; + *pReq->m_pRespHeaderBufPos++ = 0; + ++len; /* add one byte padding for \0 */ + pReq->m_respHeaderLen[pReq->m_respHeader.m_respInfo.m_cntHeaders] = len; + ++pReq->m_respHeader.m_respInfo.m_cntHeaders; + return 0; +} + + +int LSAPI_CreateListenSock2( const struct sockaddr * pServerAddr, int backlog ) +{ + int ret; + int fd; + int flag = 1; + int addr_len; + + switch( pServerAddr->sa_family ) { + case AF_INET: + addr_len = 16; + break; + case AF_INET6: + addr_len = sizeof( struct sockaddr_in6 ); + break; + case AF_UNIX: + addr_len = sizeof( struct sockaddr_un ); + unlink( ((struct sockaddr_un *)pServerAddr)->sun_path ); + break; + default: + return -1; + } + + fd = socket( pServerAddr->sa_family, SOCK_STREAM, 0 ); + if ( fd == -1 ) { + return -1; + } + + fcntl( fd, F_SETFD, FD_CLOEXEC ); + + if(setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, + (char *)( &flag ), sizeof(flag)) == 0) { + ret = bind( fd, pServerAddr, addr_len ); + if ( !ret ) { + ret = listen( fd, backlog ); + if ( !ret ) { + return fd; + } + } + } + + ret = errno; + close(fd); + errno = ret; + return -1; + +} + +int LSAPI_ParseSockAddr( const char * pBind, struct sockaddr * pAddr ) +{ + char achAddr[256]; + char * p = achAddr; + char * pEnd; + struct addrinfo *res, hints; + int doAddrInfo = 0; + int port; + + if ( !pBind ) { + return -1; + } + + while( isspace( *pBind ) ) { + ++pBind; + } + + strncpy( achAddr, pBind, 256 ); + + switch( *p ) { + case '/': + pAddr->sa_family = AF_UNIX; + strncpy( ((struct sockaddr_un *)pAddr)->sun_path, p, + sizeof(((struct sockaddr_un *)pAddr)->sun_path) ); + return 0; + + case '[': + pAddr->sa_family = AF_INET6; + ++p; + pEnd = strchr( p, ']' ); + if ( !pEnd ) + return -1; + *pEnd++ = 0; + + if ( *p == '*' ) { + strcpy( achAddr, "::" ); + p = achAddr; + } + doAddrInfo = 1; + break; + + default: + pAddr->sa_family = AF_INET; + pEnd = strchr( p, ':' ); + if ( !pEnd ) { + return -1; + } + *pEnd++ = 0; + + doAddrInfo = 0; + if ( *p == '*' ) { + ((struct sockaddr_in *)pAddr)->sin_addr.s_addr = htonl(INADDR_ANY); + } else { + if (!strcasecmp( p, "localhost" ) ) { + ((struct sockaddr_in *)pAddr)->sin_addr.s_addr = htonl( INADDR_LOOPBACK ); + } else { + ((struct sockaddr_in *)pAddr)->sin_addr.s_addr = inet_addr( p ); + if ( ((struct sockaddr_in *)pAddr)->sin_addr.s_addr == INADDR_BROADCAST) { + doAddrInfo = 1; + } + } + } + break; + } + if ( *pEnd == ':' ) { + ++pEnd; + } + + port = atoi( pEnd ); + if (( port <= 0 )||( port > 65535 )) { + return -1; + } + if ( doAddrInfo ) { + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = pAddr->sa_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if ( getaddrinfo(p, NULL, &hints, &res) ) { + return -1; + } + + memcpy(pAddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + } + + if ( pAddr->sa_family == AF_INET ) { + ((struct sockaddr_in *)pAddr)->sin_port = htons( port ); + } else { + ((struct sockaddr_in6 *)pAddr)->sin6_port = htons( port ); + } + return 0; + +} + +int LSAPI_CreateListenSock( const char * pBind, int backlog ) +{ + char serverAddr[128]; + int ret; + int fd = -1; + ret = LSAPI_ParseSockAddr( pBind, (struct sockaddr *)serverAddr ); + if ( !ret ) { + fd = LSAPI_CreateListenSock2( (struct sockaddr *)serverAddr, backlog ); + } + return fd; +} + +static fn_select_t g_fnSelect = select; + +typedef struct _lsapi_child_status +{ + int m_pid; + + volatile short m_iKillSent; + volatile short m_inProcess; + + volatile long m_tmWaitBegin; + volatile long m_tmReqBegin; + volatile long m_tmLastCheckPoint; +} +lsapi_child_status; + +static lsapi_child_status * s_pChildStatus = NULL; + +typedef struct _lsapi_prefork_server +{ + int m_fd; + int m_iMaxChildren; + int m_iExtraChildren; + int m_iCurChildren; + int m_iMaxIdleChildren; + int m_iServerMaxIdle; + int m_iChildrenMaxIdleTime; + int m_iMaxReqProcessTime; + int m_iAvoidFork; + + lsapi_child_status * m_pChildrenStatus; + +}lsapi_prefork_server; + +static lsapi_prefork_server * g_prefork_server = NULL; + +int LSAPI_Init_Prefork_Server( int max_children, fn_select_t fp, int avoidFork ) +{ + if ( g_prefork_server ) { + return 0; + } + if ( max_children <= 1 ) { + return -1; + } + if ( max_children >= 10000) { + max_children = 10000; + } + + + g_prefork_server = (lsapi_prefork_server *)malloc( sizeof( lsapi_prefork_server ) ); + if ( !g_prefork_server ) { + return -1; + } + memset( g_prefork_server, 0, sizeof( lsapi_prefork_server ) ); + + if ( fp != NULL ) { + g_fnSelect = fp; + } + + s_ppid = getppid(); + g_prefork_server->m_iAvoidFork = avoidFork; + g_prefork_server->m_iMaxChildren = max_children; + + g_prefork_server->m_iExtraChildren = ( avoidFork ) ? 0 : (max_children / 3) ; + g_prefork_server->m_iMaxIdleChildren = ( avoidFork ) ? (max_children + 1) : (max_children / 3); + g_prefork_server->m_iChildrenMaxIdleTime = 300; + g_prefork_server->m_iMaxReqProcessTime = 300; + return 0; +} + +void LSAPI_Set_Server_fd( int fd ) +{ + if( g_prefork_server ) { + g_prefork_server->m_fd = fd; + } +} + + +static int lsapi_accept( int fdListen ) +{ + int fd; + int nodelay = 1; + socklen_t len; + char achPeer[128]; + + len = sizeof( achPeer ); + fd = accept( fdListen, (struct sockaddr *)&achPeer, &len ); + if ( fd != -1 ) { + if (((struct sockaddr *)&achPeer)->sa_family == AF_INET ) { + setsockopt( fd, IPPROTO_TCP, TCP_NODELAY, + (char *)&nodelay, sizeof(nodelay)); + } + + if ( s_accept_notify ) + notify_req_received( fd ); + } + return fd; + +} + + + + +static int s_req_processed = 0; +static int s_max_reqs = 10000; +static int s_max_idle_secs = 300; + +static int s_stop; + +static void lsapi_cleanup(int signal) +{ + s_stop = signal; +} + +static lsapi_child_status * find_child_status( int pid ) +{ + lsapi_child_status * pStatus = g_prefork_server->m_pChildrenStatus; + lsapi_child_status * pEnd = g_prefork_server->m_pChildrenStatus + g_prefork_server->m_iMaxChildren * 2; + while( pStatus < pEnd ) { + if ( pStatus->m_pid == pid ) { + return pStatus; + } + ++pStatus; + } + return NULL; +} + + + +static void lsapi_sigchild( int signal ) +{ + int status, pid; + lsapi_child_status * child_status; + while( 1 ) { + pid = waitpid( -1, &status, WNOHANG|WUNTRACED ); + if ( pid <= 0 ) { + break; + } + child_status = find_child_status( pid ); + if ( child_status ) { + child_status->m_pid = 0; + } + --g_prefork_server->m_iCurChildren; + } + +} + +static int lsapi_init_children_status() +{ + int size = 4096; + + char * pBuf; + size = g_prefork_server->m_iMaxChildren * sizeof( lsapi_child_status ) * 2; + size = (size + 4095 ) / 4096 * 4096; + pBuf =( char*) mmap( NULL, size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_SHARED, -1, 0 ); + if ( pBuf == MAP_FAILED ) { + perror( "Anonymous mmap() failed" ); + return -1; + } + memset( pBuf, 0, size ); + g_prefork_server->m_pChildrenStatus = (lsapi_child_status *)pBuf; + return 0; +} + +static void lsapi_check_child_status( long tmCur ) +{ + int idle = 0; + int tobekilled; + int dying = 0; + lsapi_child_status * pStatus = g_prefork_server->m_pChildrenStatus; + lsapi_child_status * pEnd = g_prefork_server->m_pChildrenStatus + g_prefork_server->m_iMaxChildren * 2; + while( pStatus < pEnd ) { + tobekilled = pStatus->m_iKillSent; + if ( pStatus->m_pid != 0 ) { + if ( !tobekilled ) { + if ( !pStatus->m_inProcess ) { + + if (( g_prefork_server->m_iCurChildren - dying > g_prefork_server->m_iMaxChildren)|| + ( idle >= g_prefork_server->m_iMaxIdleChildren )) { + + tobekilled = 1; + } else { + if (( s_max_idle_secs> 0)&&(tmCur - pStatus->m_tmWaitBegin > s_max_idle_secs + 5 )) { + tobekilled = 1; + } + } + if ( !tobekilled ) { + ++idle; + } + } else { + if ( tmCur - pStatus->m_tmReqBegin > + g_prefork_server->m_iMaxReqProcessTime ) { + tobekilled = 1; + } + } + } else { + if ( pStatus->m_inProcess ) { + tobekilled = pStatus->m_iKillSent = 0; + } + } + if ( tobekilled ) { + tobekilled = 0; + if ( pStatus->m_iKillSent > 5 ) { + tobekilled = SIGKILL; + } else { + if ( pStatus->m_iKillSent == 3 ) { + tobekilled = SIGTERM; + } else { + if ( pStatus->m_iKillSent == 1 ) { + tobekilled = SIGUSR1; + } + } + } + if ( tobekilled ) { + kill( pStatus->m_pid, tobekilled ); + } + ++pStatus->m_iKillSent; + ++dying; + } + + } else { + ++dying; + } + ++pStatus; + } +} + +static int lsapi_all_children_must_die() +{ + int maxWait; + int sec =0; + g_prefork_server->m_iMaxReqProcessTime = 10; + g_prefork_server->m_iMaxIdleChildren = -1; + maxWait = 15; + + while( g_prefork_server->m_iCurChildren && (sec < maxWait) ) { + lsapi_check_child_status(time(NULL)); + sleep( 1 ); + sec++; + } + if ( g_prefork_server->m_iCurChildren != 0 ) { + kill( -getpgrp(), SIGKILL ); + } + return 0; +} + + + +static int lsapi_prefork_server_accept( lsapi_prefork_server * pServer, LSAPI_Request * pReq ) +{ + struct sigaction act, old_term, old_quit, old_int, + old_usr1, old_child; + lsapi_child_status * child_status; + int wait_secs = 0; + int ret = 0; + int pid; + time_t lastTime = 0; + time_t curTime = 0; + fd_set readfds; + struct timeval timeout; + + lsapi_init_children_status(); + + setsid(); + + act.sa_flags = 0; + act.sa_handler = lsapi_sigchild; + if( sigaction( SIGCHLD, &act, &old_child ) ) { + perror( "Can't set signal handler for SIGCHILD" ); + return -1; + } + + /* Set up handler to kill children upon exit */ + act.sa_flags = 0; + act.sa_handler = lsapi_cleanup; + if( sigaction( SIGTERM, &act, &old_term ) || + sigaction( SIGINT, &act, &old_int ) || + sigaction( SIGUSR1, &act, &old_usr1 ) || + sigaction( SIGQUIT, &act, &old_quit )) { + perror( "Can't set signals" ); + return -1; + } + s_stop = 0; + while( !s_stop ) { + if ( ret ) { + curTime = time( NULL ); + } else { + ++curTime; + } + if (curTime != lastTime ) { + lastTime = curTime; + if (s_ppid && (getppid() != s_ppid )) { + break; + } + lsapi_check_child_status(curTime ); + if (pServer->m_iServerMaxIdle) { + if ( pServer->m_iCurChildren <= 0 ) { + ++wait_secs; + if ( wait_secs > pServer->m_iServerMaxIdle ) { + return -1; + } + } else { + wait_secs = 0; + } + } + } + + if ( pServer->m_iCurChildren >= (pServer->m_iMaxChildren + pServer->m_iExtraChildren ) ) { + usleep( 100000 ); + continue; + } + + FD_ZERO( &readfds ); + FD_SET( pServer->m_fd, &readfds ); + timeout.tv_sec = 1; timeout.tv_usec = 0; + if ((ret = (*g_fnSelect)(pServer->m_fd+1, &readfds, NULL, NULL, &timeout)) == 1 ) { + if ( pServer->m_iCurChildren >= 0 ) { + usleep( 10 ); + FD_ZERO( &readfds ); + FD_SET( pServer->m_fd, &readfds ); + timeout.tv_sec = 0; timeout.tv_usec = 0; + if ( (*g_fnSelect)(pServer->m_fd+1, &readfds, NULL, NULL, &timeout) == 0 ) { + continue; + } + } + } else { + if ( ret == -1 ) { + if ( errno == EINTR ) { + continue; + } + /* perror( "select()" ); */ + break; + } else { + continue; + } + } + + pReq->m_fd = lsapi_accept( pServer->m_fd ); + if ( pReq->m_fd != -1 ) { + child_status = find_child_status( 0 ); + pid = fork(); + if ( !pid ) { + g_prefork_server = NULL; + s_ppid = getppid(); + s_req_processed = 0; + s_pChildStatus = child_status; + child_status->m_iKillSent = 0; + lsapi_set_nblock( pReq->m_fd, 0 ); + + /* don't catch our signals */ + sigaction( SIGCHLD, &old_child, 0 ); + sigaction( SIGTERM, &old_term, 0 ); + sigaction( SIGQUIT, &old_quit, 0 ); + sigaction( SIGINT, &old_int, 0 ); + sigaction( SIGUSR1, &old_usr1, 0 ); + return 0; + } else { + if ( pid == -1 ) { + perror( "fork() failed, please increase process limit" ); + } else { + ++pServer->m_iCurChildren; + if ( child_status ) { + child_status->m_pid = pid; + child_status->m_iKillSent = 0; + child_status->m_tmWaitBegin = time(NULL); + } + } + } + close( pReq->m_fd ); + pReq->m_fd = -1; + + } else { + if (( errno == EINTR )||( errno == EAGAIN)) { + continue; + } + perror( "accept() failed" ); + return -1; + } + } + sigaction( SIGUSR1, &old_usr1, 0 ); + kill( -getpgrp(), SIGUSR1 ); + lsapi_all_children_must_die(); /* Sorry, children ;-) */ + return -1; + +} + +int LSAPI_Prefork_Accept_r( LSAPI_Request * pReq ) +{ + int fd; + int ret; + int wait_secs; + fd_set readfds; + struct timeval timeout; + + LSAPI_Finish_r( pReq ); + + + if ( g_prefork_server ) { + if ( g_prefork_server->m_fd != -1 ) { + if ( lsapi_prefork_server_accept( g_prefork_server, pReq ) == -1 ) { + return -1; + } + } + } + if ( s_req_processed >= s_max_reqs ) { + return -1; + } + + if ( s_pChildStatus ) { + s_pChildStatus->m_tmWaitBegin = time( NULL ); + } + + while( g_running ) { + if ( pReq->m_fd != -1 ) { + fd = pReq->m_fd; + } else { + if ( pReq->m_fdListen != -1 ) { + fd = pReq->m_fdListen; + } else { + return -1; + } + } + wait_secs = 0; + while( 1 ) { + if ( !g_running ) { + return -1; + } + if (( s_pChildStatus )&&( s_pChildStatus->m_iKillSent )) { + return -1; + } + FD_ZERO( &readfds ); + FD_SET( fd, &readfds ); + timeout.tv_sec = 1; + timeout.tv_usec = 0; + ret = (*g_fnSelect)(fd+1, &readfds, NULL, NULL, &timeout); + if ( ret == 0 ) { + if ( s_pChildStatus ) { + s_pChildStatus->m_inProcess = 0; + } + ++wait_secs; + if (( s_max_idle_secs > 0 )&&(wait_secs >= s_max_idle_secs )) { + return -1; + } + if ( s_ppid &&( getppid() != s_ppid)) { + return -1; + } + } else { + if ( ret == -1 ) { + if ( errno == EINTR ) { + continue; + } else { + return -1; + } + } else { + if ( ret >= 1 ) { + if (( s_pChildStatus )&&( s_pChildStatus->m_iKillSent )) { + return -1; + } + if ( fd == pReq->m_fdListen ) { + pReq->m_fd = lsapi_accept( pReq->m_fdListen ); + if ( pReq->m_fd != -1 ) { + fd = pReq->m_fd; + lsapi_set_nblock( fd, 0 ); + } else { + if (( errno == EINTR )||( errno == EAGAIN)) { + continue; + } + return -1; + } + } else { + break; + } + } + } + } + } + if ( !readReq( pReq ) ) { + if ( s_pChildStatus ) { + s_pChildStatus->m_inProcess = 1; + s_pChildStatus->m_tmReqBegin = s_pChildStatus->m_tmLastCheckPoint = time(NULL); + } + ++s_req_processed; + return 0; + } + lsapi_close( pReq->m_fd ); + pReq->m_fd = -1; + LSAPI_Reset_r( pReq ); + } + return -1; + +} + +void LSAPI_Set_Max_Reqs( int reqs ) +{ + s_max_reqs = reqs; +} + +void LSAPI_Set_Max_Idle( int secs ) +{ + s_max_idle_secs = secs; +} + +void LSAPI_Set_Max_Children( int maxChildren ) +{ + if ( g_prefork_server ) { + g_prefork_server->m_iMaxChildren = maxChildren; + } +} + +void LSAPI_Set_Extra_Children( int extraChildren ) +{ + if (( g_prefork_server )&&( extraChildren >= 0 )) { + g_prefork_server->m_iExtraChildren = extraChildren; + } +} + +void LSAPI_Set_Max_Process_Time( int secs ) +{ + if (( g_prefork_server )&&( secs > 0 )) { + g_prefork_server->m_iMaxReqProcessTime = secs; + } +} + + +void LSAPI_Set_Max_Idle_Children( int maxIdleChld ) +{ + if (( g_prefork_server )&&( maxIdleChld > 0 )) { + g_prefork_server->m_iMaxIdleChildren = maxIdleChld; + } +} + +void LSAPI_Set_Server_Max_Idle_Secs( int serverMaxIdle ) +{ + if ( g_prefork_server ) { + g_prefork_server->m_iServerMaxIdle = serverMaxIdle; + } +} + +void LSAPI_Set_Slow_Req_Msecs( int msecs ) +{ + s_slow_req_msecs = msecs; +} + +int LSAPI_Get_Slow_Req_Msecs() +{ + return s_slow_req_msecs; +} + +void LSAPI_No_Check_ppid() +{ + s_ppid = 0; +} + +#if defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) +#include <crt_externs.h> +#else +extern char ** environ; +#endif +static void unset_lsapi_envs() +{ + char **env; +#if defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) + env = *_NSGetEnviron(); +#else + env = environ; +#endif + while( env != NULL && *env != NULL ) { + if ( !strncmp(*env, "LSAPI_", 6) || + !strncmp( *env, "PHP_LSAPI_", 10 ) ) { + char ** del = env; + do { + *del = del[1]; + } while( *del++ ); + } else { + ++env; + } + } +} + +void LSAPI_Init_Env_Parameters( fn_select_t fp ) +{ + const char *p; + int n; + int avoidFork = 0; + p = getenv( "PHP_LSAPI_MAX_REQUESTS" ); + if ( !p ) { + p = getenv( "LSAPI_MAX_REQS" ); + } + if ( p ) { + n = atoi( p ); + if ( n > 0 ) { + LSAPI_Set_Max_Reqs( n ); + } + } + + p = getenv( "LSAPI_AVOID_FORK" ); + if ( p ) { + avoidFork = atoi( p ); + } + + p = getenv( "LSAPI_ACCEPT_NOTIFY" ); + if ( p ) { + s_accept_notify = atoi( p ); + } + + p = getenv( "LSAPI_SLOW_REQ_MSECS" ); + if ( p ) { + n = atoi( p ); + LSAPI_Set_Slow_Req_Msecs( n ); + } + + +#if defined( RLIMIT_CORE ) + p = getenv( "LSAPI_ALLOW_CORE_DUMP" ); + if ( !p ) { + struct rlimit limit = { 0, 0 }; + setrlimit( RLIMIT_CORE, &limit ); + } +#endif + + p = getenv( "LSAPI_MAX_IDLE" ); + if ( p ) { + n = atoi( p ); + LSAPI_Set_Max_Idle( n ); + } + + if ( LSAPI_Is_Listen() ) { + n = 0; + p = getenv( "PHP_LSAPI_CHILDREN" ); + if ( !p ) { + p = getenv( "LSAPI_CHILDREN" ); + } + if ( p ) { + n = atoi( p ); + } + if ( n > 1 ) { + LSAPI_Init_Prefork_Server( n, fp, avoidFork ); + LSAPI_Set_Server_fd( g_req.m_fdListen ); + } + + p = getenv( "LSAPI_EXTRA_CHILDREN" ); + if ( p ) { + LSAPI_Set_Extra_Children( atoi( p ) ); + } + + p = getenv( "LSAPI_MAX_IDLE_CHILDREN" ); + if ( p ) { + LSAPI_Set_Max_Idle_Children( atoi( p ) ); + } + p = getenv( "LSAPI_PGRP_MAX_IDLE" ); + if ( p ) { + LSAPI_Set_Server_Max_Idle_Secs( atoi( p ) ); + } + + p = getenv( "LSAPI_MAX_PROCESS_TIME" ); + if ( p ) { + LSAPI_Set_Max_Process_Time( atoi( p ) ); + } + if ( getenv( "LSAPI_PPID_NO_CHECK" ) ) { + LSAPI_No_Check_ppid(); + } + } + unset_lsapi_envs(); +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ + + diff --git a/sapi/litespeed/lsapilib.h b/sapi/litespeed/lsapilib.h new file mode 100644 index 0000000..3a987b4 --- /dev/null +++ b/sapi/litespeed/lsapilib.h @@ -0,0 +1,363 @@ + +/* + +----------------------------------------------------------------------+ + | 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 at 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. | + +----------------------------------------------------------------------+ + | Author: George Wang <gwang@litespeedtech.com> | + +----------------------------------------------------------------------+ +*/ + +/* +Copyright (c) 2007, Lite Speed Technologies Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Lite Speed Technologies Inc nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + + +#ifndef _LSAPILIB_H_ +#define _LSAPILIB_H_ + +#if defined (c_plusplus) || defined (__cplusplus) +extern "C" { +#endif + +#include <stddef.h> +#include <lsapidef.h> + +#include <sys/time.h> +#include <sys/types.h> + +struct LSAPI_key_value_pair +{ + char * pKey; + char * pValue; + int keyLen; + int valLen; +}; + + +#define LSAPI_MAX_RESP_HEADERS 100 + +typedef struct lsapi_request +{ + int m_fdListen; + int m_fd; + + long m_lLastActive; + long m_lReqBegin; + + char * m_pReqBuf; + int m_reqBufSize; + + char * m_pRespBuf; + char * m_pRespBufEnd; + char * m_pRespBufPos; + + char * m_pRespHeaderBuf; + char * m_pRespHeaderBufEnd; + char * m_pRespHeaderBufPos; + + + struct iovec * m_pIovec; + struct iovec * m_pIovecEnd; + struct iovec * m_pIovecCur; + struct iovec * m_pIovecToWrite; + + struct lsapi_packet_header * m_respPktHeaderEnd; + + struct lsapi_req_header * m_pHeader; + struct LSAPI_key_value_pair * m_pEnvList; + struct LSAPI_key_value_pair * m_pSpecialEnvList; + int m_envListSize; + int m_specialEnvListSize; + + struct lsapi_http_header_index * m_pHeaderIndex; + struct lsapi_header_offset * m_pUnknownHeader; + + char * m_pScriptFile; + char * m_pScriptName; + char * m_pQueryString; + char * m_pHttpHeader; + char * m_pRequestMethod; + int m_totalLen; + int m_reqState; + int m_reqBodyRead; + int m_bufProcessed; + int m_bufRead; + + struct lsapi_packet_header m_respPktHeader[5]; + + struct lsapi_resp_header m_respHeader; + short m_respHeaderLen[LSAPI_MAX_RESP_HEADERS]; + +}LSAPI_Request; + +extern LSAPI_Request g_req; + + +/* return: >0 continue, ==0 stop, -1 failed */ +typedef int (*LSAPI_CB_EnvHandler )( const char * pKey, int keyLen, + const char * pValue, int valLen, void * arg ); + + +int LSAPI_Init(void); + +void LSAPI_Stop(void); + +int LSAPI_Is_Listen_r( LSAPI_Request * pReq); + +int LSAPI_InitRequest( LSAPI_Request * pReq, int fd ); + +int LSAPI_Accept_r( LSAPI_Request * pReq ); + +void LSAPI_Reset_r( LSAPI_Request * pReq ); + +int LSAPI_Finish_r( LSAPI_Request * pReq ); + +int LSAPI_Release_r( LSAPI_Request * pReq ); + +char * LSAPI_GetHeader_r( LSAPI_Request * pReq, int headerIndex ); + +int LSAPI_ForeachHeader_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ); + +int LSAPI_ForeachOrgHeader_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ); + +int LSAPI_ForeachEnv_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ); + +int LSAPI_ForeachSpecialEnv_r( LSAPI_Request * pReq, + LSAPI_CB_EnvHandler fn, void * arg ); + +char * LSAPI_GetEnv_r( LSAPI_Request * pReq, const char * name ); + + +int LSAPI_ReadReqBody_r( LSAPI_Request * pReq, char * pBuf, int len ); + +int LSAPI_ReqBodyGetChar_r( LSAPI_Request * pReq ); + +int LSAPI_ReqBodyGetLine_r( LSAPI_Request * pReq, char * pBuf, int bufLen, int *getLF ); + + +int LSAPI_FinalizeRespHeaders_r( LSAPI_Request * pReq ); + +int LSAPI_Write_r( LSAPI_Request * pReq, const char * pBuf, int len ); + +int LSAPI_Write_Stderr_r( LSAPI_Request * pReq, const char * pBuf, int len ); + +int LSAPI_Flush_r( LSAPI_Request * pReq ); + +int LSAPI_AppendRespHeader_r( LSAPI_Request * pHeader, char * pBuf, int len ); + +static inline int LSAPI_SetRespStatus_r( LSAPI_Request * pReq, int code ) +{ + if ( !pReq ) + return -1; + pReq->m_respHeader.m_respInfo.m_status = code; + return 0; +} + +static inline char * LSAPI_GetQueryString_r( LSAPI_Request * pReq ) +{ + if ( pReq ) + return pReq->m_pQueryString; + return NULL; +} + + +static inline char * LSAPI_GetScriptFileName_r( LSAPI_Request * pReq ) +{ + if ( pReq ) + return pReq->m_pScriptFile; + return NULL; +} + + +static inline char * LSAPI_GetScriptName_r( LSAPI_Request * pReq ) +{ + if ( pReq ) + return pReq->m_pScriptName; + return NULL; +} + + +static inline char * LSAPI_GetRequestMethod_r( LSAPI_Request * pReq) +{ + if ( pReq ) + return pReq->m_pRequestMethod; + return NULL; +} + + + +static inline int LSAPI_GetReqBodyLen_r( LSAPI_Request * pReq ) +{ + if ( pReq ) + return pReq->m_pHeader->m_reqBodyLen; + return -1; +} + +static inline int LSAPI_GetReqBodyRemain_r( LSAPI_Request * pReq ) +{ + if ( pReq ) + return pReq->m_pHeader->m_reqBodyLen - pReq->m_reqBodyRead; + return -1; +} + + +int LSAPI_Is_Listen(void); + +static inline int LSAPI_Accept( void ) +{ return LSAPI_Accept_r( &g_req ); } + +static inline int LSAPI_Finish(void) +{ return LSAPI_Finish_r( &g_req ); } + +static inline char * LSAPI_GetHeader( int headerIndex ) +{ return LSAPI_GetHeader_r( &g_req, headerIndex ); } + +static inline int LSAPI_ForeachHeader( LSAPI_CB_EnvHandler fn, void * arg ) +{ return LSAPI_ForeachHeader_r( &g_req, fn, arg ); } + +static inline int LSAPI_ForeachOrgHeader( + LSAPI_CB_EnvHandler fn, void * arg ) +{ return LSAPI_ForeachOrgHeader_r( &g_req, fn, arg ); } + +static inline int LSAPI_ForeachEnv( LSAPI_CB_EnvHandler fn, void * arg ) +{ return LSAPI_ForeachEnv_r( &g_req, fn, arg ); } + +static inline int LSAPI_ForeachSpecialEnv( LSAPI_CB_EnvHandler fn, void * arg ) +{ return LSAPI_ForeachSpecialEnv_r( &g_req, fn, arg ); } + +static inline char * LSAPI_GetEnv( const char * name ) +{ return LSAPI_GetEnv_r( &g_req, name ); } + +static inline char * LSAPI_GetQueryString() +{ return LSAPI_GetQueryString_r( &g_req ); } + +static inline char * LSAPI_GetScriptFileName() +{ return LSAPI_GetScriptFileName_r( &g_req ); } + +static inline char * LSAPI_GetScriptName() +{ return LSAPI_GetScriptName_r( &g_req ); } + +static inline char * LSAPI_GetRequestMethod() +{ return LSAPI_GetRequestMethod_r( &g_req ); } + +static inline int LSAPI_GetReqBodyLen() +{ return LSAPI_GetReqBodyLen_r( &g_req ); } + +static inline int LSAPI_GetReqBodyRemain() +{ return LSAPI_GetReqBodyRemain_r( &g_req ); } + +static inline int LSAPI_ReadReqBody( char * pBuf, int len ) +{ return LSAPI_ReadReqBody_r( &g_req, pBuf, len ); } + +static inline int LSAPI_ReqBodyGetChar() +{ return LSAPI_ReqBodyGetChar_r( &g_req ); } + +static inline int LSAPI_ReqBodyGetLine( char * pBuf, int len, int *getLF ) +{ return LSAPI_ReqBodyGetLine_r( &g_req, pBuf, len, getLF ); } + + + +static inline int LSAPI_FinalizeRespHeaders(void) +{ return LSAPI_FinalizeRespHeaders_r( &g_req ); } + +static inline int LSAPI_Write( const char * pBuf, int len ) +{ return LSAPI_Write_r( &g_req, pBuf, len ); } + +static inline int LSAPI_Write_Stderr( const char * pBuf, int len ) +{ return LSAPI_Write_Stderr_r( &g_req, pBuf, len ); } + +static inline int LSAPI_Flush() +{ return LSAPI_Flush_r( &g_req ); } + +static inline int LSAPI_AppendRespHeader( char * pBuf, int len ) +{ return LSAPI_AppendRespHeader_r( &g_req, pBuf, len ); } + +static inline int LSAPI_SetRespStatus( int code ) +{ return LSAPI_SetRespStatus_r( &g_req, code ); } + +int LSAPI_IsRunning(void); + +int LSAPI_CreateListenSock( const char * pBind, int backlog ); + +typedef int (*fn_select_t)( int, fd_set *, fd_set *, fd_set *, struct timeval * ); + +int LSAPI_Init_Prefork_Server( int max_children, fn_select_t fp, int avoidFork ); + +void LSAPI_Set_Server_fd( int fd ); + +int LSAPI_Prefork_Accept_r( LSAPI_Request * pReq ); + +void LSAPI_Set_Max_Reqs( int reqs ); + +void LSAPI_Set_Max_Idle( int secs ); + +void LSAPI_Set_Max_Children( int maxChildren ); + +void LSAPI_Set_Max_Idle_Children( int maxIdleChld ); + +void LSAPI_Set_Server_Max_Idle_Secs( int serverMaxIdle ); + +void LSAPI_Set_Max_Process_Time( int secs ); + +void LSAPI_Init_Env_Parameters( fn_select_t fp ); + +void LSAPI_Set_Slow_Req_Msecs( int msecs ); + +int LSAPI_Get_Slow_Req_Msecs( ); + + +#if defined (c_plusplus) || defined (__cplusplus) +} +#endif + + +#endif + + + + + + + diff --git a/sapi/milter/CREDITS b/sapi/milter/CREDITS new file mode 100644 index 0000000..cd00d67 --- /dev/null +++ b/sapi/milter/CREDITS @@ -0,0 +1,2 @@ +Sendmail Milter +Harald Radi diff --git a/sapi/milter/EXPERIMENTAL b/sapi/milter/EXPERIMENTAL new file mode 100644 index 0000000..293159a --- /dev/null +++ b/sapi/milter/EXPERIMENTAL @@ -0,0 +1,5 @@ +this module is experimental, +its functions may change their names +or move to extension all together +so do not rely to much on them +you have been warned! diff --git a/sapi/milter/Makefile.frag b/sapi/milter/Makefile.frag new file mode 100644 index 0000000..26200a1 --- /dev/null +++ b/sapi/milter/Makefile.frag @@ -0,0 +1,8 @@ +milter: $(SAPI_MILTER_PATH) + +$(SAPI_MILTER_PATH): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_SAPI_OBJS) + $(BUILD_MILTER) + +install-milter: $(SAPI_MILTER_PATH) + @$(INSTALL) -m 0755 $(SAPI_MILTER_PATH) $(bindir)/php-milter + diff --git a/sapi/milter/TODO b/sapi/milter/TODO new file mode 100644 index 0000000..4a427ea --- /dev/null +++ b/sapi/milter/TODO @@ -0,0 +1,5 @@ +threaded version still leaks mem, don't know why +extensions aren't loaded +stdout to syslog +testing +documentation
\ No newline at end of file diff --git a/sapi/milter/config.m4 b/sapi/milter/config.m4 new file mode 100644 index 0000000..48c7a5d --- /dev/null +++ b/sapi/milter/config.m4 @@ -0,0 +1,31 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(milter, for Milter support, +[ --with-milter[=DIR] Build PHP as Milter application], no, no) + +if test "$PHP_MILTER" != "no"; then + if test "$PHP_MILTER" = "yes"; then + if test -f /usr/lib/libmilter.a ; then + MILTERPATH=/usr/lib + else + if test -f /usr/lib/libmilter/libmilter.a ; then + MILTERPATH=/usr/lib/libmilter + else + AC_MSG_ERROR([Unable to find libmilter.a]) + fi + fi + else + MILTERPATH=$PHP_MILTER + fi + + SAPI_MILTER_PATH=sapi/milter/php-milter + PHP_BUILD_THREAD_SAFE + PHP_ADD_MAKEFILE_FRAGMENT($abs_srcdir/sapi/milter/Makefile.frag) + PHP_SELECT_SAPI(milter, program, php_milter.c getopt.c,,'$(SAPI_MILTER_PATH)') + PHP_ADD_LIBRARY_WITH_PATH(milter, $MILTERPATH,) + BUILD_MILTER="\$(LIBTOOL) --mode=link \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS) \$(LDFLAGS) \$(PHP_RPATHS) \$(PHP_GLOBAL_OBJS) \$(PHP_BINARY_OBJS) \$(PHP_MILTER_OBJS) \$(EXTRA_LIBS) \$(ZEND_EXTRA_LIBS) -o \$(SAPI_MILTER_PATH)" + PHP_SUBST(SAPI_MILTER_PATH) + PHP_SUBST(BUILD_MILTER) +fi diff --git a/sapi/milter/getopt.c b/sapi/milter/getopt.c new file mode 100644 index 0000000..f5874d5 --- /dev/null +++ b/sapi/milter/getopt.c @@ -0,0 +1,173 @@ +/* Borrowed from Apache NT Port */ + +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <stdlib.h> +#include "php_getopt.h" +#define OPTERRCOLON (1) +#define OPTERRNF (2) +#define OPTERRARG (3) + + +char *ap_php_optarg; +int ap_php_optind = 1; +static int ap_php_opterr = 1; + +static int +ap_php_optiserr(int argc, char * const *argv, int oint, const char *optstr, + int optchr, int err) +{ + if (ap_php_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; + } + } + return('?'); +} + +int ap_php_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_php_optind >= argc) + return(EOF); + if (!dash && (argv[ap_php_optind][0] != '-')) + return(EOF); + if (!dash && (argv[ap_php_optind][0] == '-') && !argv[ap_php_optind][1]) + { + /* + * use to specify stdin. Need to let pgm process this and + * the following args + */ + return(EOF); + } + if ((argv[ap_php_optind][0] == '-') && (argv[ap_php_optind][1] == '-')) + { + /* -- indicates end of args */ + ap_php_optind++; + return(EOF); + } + if (!dash) + { + assert((argv[ap_php_optind][0] == '-') && argv[ap_php_optind][1]); + dash = 1; + optchr = 1; + } + + /* Check if the guy tries to do a -: kind of flag */ + assert(dash); + if (argv[ap_php_optind][optchr] == ':') + { + dash = 0; + ap_php_optind++; + return(ap_php_optiserr(argc, argv, ap_php_optind-1, optstr, optchr, OPTERRCOLON)); + } + if (!(cp = strchr(optstr, argv[ap_php_optind][optchr]))) + { + int errind = ap_php_optind; + int errchr = optchr; + + if (!argv[ap_php_optind][optchr+1]) + { + dash = 0; + ap_php_optind++; + } + else + optchr++; + return(ap_php_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_php_optind][2]) { + ap_php_optind++; + if (ap_php_optind == argc) + return(ap_php_optiserr(argc, argv, ap_php_optind-1, optstr, optchr, OPTERRARG)); + ap_php_optarg = argv[ap_php_optind++]; + } + else + { + ap_php_optarg = &argv[ap_php_optind][2]; + ap_php_optind++; + } + return(*cp); + } + else + { + if (!argv[ap_php_optind][optchr+1]) + { + dash = 0; + ap_php_optind++; + } + else + optchr++; + return(*cp); + } + assert(0); + return(0); /* never reached */ +} + +#ifdef TESTGETOPT +int + main (int argc, char **argv) + { + int c; + extern char *ap_php_optarg; + extern int ap_php_optind; + int aflg = 0; + int bflg = 0; + int errflg = 0; + char *ofile = NULL; + + while ((c = ap_php_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_php_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_php_optind < argc; ap_php_optind++) + (void)printf("%s\n", argv[ap_php_optind]); + return 0; + } + +#endif /* TESTGETOPT */ diff --git a/sapi/milter/milter.php b/sapi/milter/milter.php new file mode 100644 index 0000000..0878f2a --- /dev/null +++ b/sapi/milter/milter.php @@ -0,0 +1,132 @@ +<?php +/** + * example milter script + * + * run: php-milter -D -p /path/to/sock milter.php + * + * for details on how to set up sendmail and configure the milter see + * http://www.sendmail.com/partner/resources/development/milter_api/ + * + * for api details see + * http://www.sendmail.com/partner/resources/development/milter_api/api.html + * + * below is a list of all callbacks, that are available through the milter sapi, + * if you leave one or more out they simply won't get called (e.g. if you secify an + * empty php file, the milter would do nothing :) + */ + +/** + * this function is called once on sapi startup, + * here you can specify the actions the filter may take + * + * see http://www.sendmail.com/partner/resources/development/milter_api/smfi_register.html#flags + */ + +function milter_log($msg) +{ + $GLOBALS['log'] = fopen("/tmp/milter.log", "a"); + fwrite($GLOBALS['log'], date("[H:i:s d.m.Y]") . "\t{$msg}\n"); + fclose($GLOBALS['log']); +} + +function milter_init() { + milter_log("-- startup --"); + milter_log("milter_init()"); + smfi_setflags(SMFIF_ADDHDRS); +} + +/** + * is called once, at the start of each SMTP connection + */ +function milter_connect($connect) +{ + milter_log("milter_connect('$connect')"); +} + +/** + * is called whenever the client sends a HELO/EHLO command. + * It may therefore be called between zero and three times. + */ +function milter_helo($helo) +{ + milter_log("milter_helo('$helo')"); +} + +/** + * is called once at the beginning of each message, + * before milter_envrcpt. + */ +function milter_envfrom($args) +{ + milter_log("milter_envfrom(args[])"); + foreach ($args as $ix => $arg) { + milter_log("\targs[$ix] = $arg"); + } +} + +/** + * is called once per recipient, hence one or more times per message, + * immediately after milter_envfrom + */ +function milter_envrcpt($args) +{ + milter_log("milter_envrcpt(args[])"); + foreach ($args as $ix => $arg) { + milter_log("\targs[$ix] = $arg"); + } +} + +/** + * is called zero or more times between milter_envrcpt and milter_eoh, + * once per message header + */ +function milter_header($header, $value) +{ + milter_log("milter_header('$header', '$value')"); +} + +/** + * is called once after all headers have been sent and processed. + */ +function milter_eoh() +{ + milter_log("milter_eoh()"); +} + +/** + * is called zero or more times between milter_eoh and milter_eom. + */ +function milter_body($bodypart) +{ + milter_log("milter_body('$bodypart')"); +} + +/** + * is called once after all calls to milter_body for a given message. + * most of the api functions, that alter the message can only be called + * within this callback. + */ +function milter_eom() +{ + milter_log("milter_eom()"); + /* add PHP header to the message */ + smfi_addheader("X-PHP", phpversion()); +} + +/** + * may be called at any time during message processing + * (i.e. between some message-oriented routine and milter_eom). + */ +function milter_abort() +{ + milter_log("milter_abort()"); +} + +/** + * is always called once at the end of each connection. + */ +function milter_close() +{ + milter_log("milter_close()"); +} +?> diff --git a/sapi/milter/php_getopt.h b/sapi/milter/php_getopt.h new file mode 100644 index 0000000..40da432 --- /dev/null +++ b/sapi/milter/php_getopt.h @@ -0,0 +1,7 @@ +/* Borrowed from Apache NT Port */ +#include "php.h" + +extern char *ap_php_optarg; +extern int ap_php_optind; + +int ap_php_getopt(int argc, char* const *argv, const char *optstr); diff --git a/sapi/milter/php_milter.c b/sapi/milter/php_milter.c new file mode 100644 index 0000000..6856c07 --- /dev/null +++ b/sapi/milter/php_milter.c @@ -0,0 +1,1209 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Harald Radi <phanto@php.net> | + | Parts based on CGI SAPI Module by | + | Rasmus Lerdorf, Stig Bakken and Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_modules.h" + +#ifndef ZTS +#error SRM sapi module is only useable in thread-safe mode +#endif + +#include "SAPI.h" + +#include <stdio.h> +#include "php.h" +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SIGNAL_H +#include <signal.h> +#endif +#if HAVE_SETLOCALE +#include <locale.h> +#endif +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "ext/standard/php_standard.h" + +#ifdef __riscos__ +#include <unixlib/local.h> +#endif + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" + +#include "libmilter/mfapi.h" + +#include "php_getopt.h" + +#define OPTSTRING "ac:d:Def:hnp:vVz:?" +#define MG(v) TSRMG(milter_globals_id, zend_milter_globals *, v) + +#define IS_NONE "%s(): This function must not be called outside of a milter callback function's scope" +#define NOT_EOM "%s(): This function can only be used inside the milter_eom callback's scope" +#define NOT_INIT "%s(): This function can only be used inside the milter_init callback's scope" + +#define MLFI_NONE 0 +#define MLFI_CONNECT 1 +#define MLFI_HELO 2 +#define MLFI_ENVFROM 3 +#define MLFI_ENVRCPT 4 +#define MLFI_HEADER 5 +#define MLFI_EOH 6 +#define MLFI_BODY 7 +#define MLFI_EOM 8 +#define MLFI_ABORT 9 +#define MLFI_CLOSE 10 +#define MLFI_INIT 11 + +/* {{{ globals + */ +extern char *ap_php_optarg; +extern int ap_php_optind; + +static int flag_debug=0; +static char *filename = NULL; + +/* per thread */ +ZEND_BEGIN_MODULE_GLOBALS(milter) + SMFICTX *ctx; + int state; + int initialized; +ZEND_END_MODULE_GLOBALS(milter) + +ZEND_DECLARE_MODULE_GLOBALS(milter) +/* }}} */ + +/* this method is called only once when the milter starts */ +/* {{{ Init Milter +*/ +static int mlfi_init() +{ + int ret = 0; + zend_file_handle file_handle; + zval function_name, retval; + int status; + TSRMLS_FETCH(); + + /* request startup */ + if (php_request_startup(TSRMLS_C)==FAILURE) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_request_shutdown((void *) 0); + + return -1; + } + + /* disable headers */ + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + + if (filename == NULL) { + php_printf("No input file specified"); + return SMFIS_TEMPFAIL; + } + + if (!(file_handle.handle.fp = VCWD_FOPEN(filename, "rb"))) { + php_printf("Could not open input file: %s\n", filename); + return SMFIS_TEMPFAIL; + } + + file_handle.type = ZEND_HANDLE_FP; + file_handle.filename = filename; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + + /* call userland */ + INIT_ZVAL(function_name); + + ZVAL_STRING(&function_name, "milter_init", 0); + + /* set the milter context for possible use in API functions */ + MG(state) = MLFI_INIT; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 0, NULL TSRMLS_CC); + + MG(state) = MLFI_NONE; + MG(initialized) = 1; + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + ret = Z_LVAL(retval); + } + + php_request_shutdown((void *) 0); + + return ret; +} +/* }}} */ + +/* {{{ Milter callback functions + */ + +/* connection info filter, is called whenever sendmail connects to the milter */ +/* {{{ mlfi_connect() +*/ +static sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) +{ + zend_file_handle file_handle; + zval function_name, retval, *param[1]; + int status; + TSRMLS_FETCH(); + + /* request startup */ + if (php_request_startup(TSRMLS_C)==FAILURE) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_request_shutdown((void *) 0); + + return SMFIS_TEMPFAIL; + } + + /* disable headers */ + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + + if (filename == NULL) { + php_printf("No input file specified"); + return SMFIS_TEMPFAIL; + } + + if (!(file_handle.handle.fp = VCWD_FOPEN(filename, "rb"))) { + php_printf("Could not open input file: %s\n", filename); + return SMFIS_TEMPFAIL; + } + + file_handle.type = ZEND_HANDLE_FP; + file_handle.filename = filename; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + INIT_PZVAL(param[0]); + + ZVAL_STRING(&function_name, "milter_connect", 0); + ZVAL_STRING(param[0], hostname, 1); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_CONNECT; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 1, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + zval_ptr_dtor(param); + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* SMTP HELO command filter */ +/* {{{ mlfi_helo() +*/ +static sfsistat mlfi_helo(SMFICTX *ctx, char *helohost) +{ + zval function_name, retval, *param[1]; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + INIT_PZVAL(param[0]); + + ZVAL_STRING(&function_name, "milter_helo", 0); + ZVAL_STRING(param[0], helohost, 1); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_HELO; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 1, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + zval_ptr_dtor(param); + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* envelope sender filter */ +/* {{{ mlfi_envform() +*/ +static sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv) +{ + zval function_name, retval, *param[1]; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + INIT_PZVAL(param[0]); + + ZVAL_STRING(&function_name, "milter_envfrom", 0); + array_init(param[0]); + + while (*argv) { + add_next_index_string(param[0], *argv, 1); + argv++; + } + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_ENVFROM; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 1, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + zval_ptr_dtor(param); + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* envelope recipient filter */ +/* {{{ mlfi_envrcpt() +*/ +static sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv) +{ + zval function_name, retval, *param[1]; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + INIT_PZVAL(param[0]); + + ZVAL_STRING(&function_name, "milter_envrcpt", 0); + array_init(param[0]); + + while (*argv) { + add_next_index_string(param[0], *argv, 1); + argv++; + } + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_ENVRCPT; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 1, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + + zval_ptr_dtor(param); + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* header filter */ +/* {{{ mlfi_header() +*/ +static sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) +{ + zval function_name, retval, *param[2]; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + ALLOC_ZVAL(param[1]); + INIT_PZVAL(param[0]); + INIT_PZVAL(param[1]); + + ZVAL_STRING(&function_name, "milter_header", 0); + ZVAL_STRING(param[0], headerf, 1); + ZVAL_STRING(param[1], headerv, 1); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_HEADER; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 2, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + + zval_ptr_dtor(¶m[0]); + zval_ptr_dtor(¶m[1]); + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* end of header */ +/* {{{ mlfi_eoh() +*/ +static sfsistat mlfi_eoh(SMFICTX *ctx) +{ + zval function_name, retval; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + ZVAL_STRING(&function_name, "milter_eoh", 0); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_EOH; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 0, NULL TSRMLS_CC); + + MG(state) = MLFI_NONE; + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* body block */ +/* {{{ mlfi_body() +*/ +static sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t len) +{ + zval function_name, retval, *param[1]; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + + ALLOC_ZVAL(param[0]); + INIT_PZVAL(param[0]); + + ZVAL_STRING(&function_name, "milter_body", 0); + ZVAL_STRINGL(param[0], (char*)bodyp, len, 1); /*alex*/ + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_BODY; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 1, param TSRMLS_CC); + + MG(state) = MLFI_NONE; + + zval_ptr_dtor(param); + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* end of message */ +/* {{{ mlfi_eom() +*/ +static sfsistat mlfi_eom(SMFICTX *ctx) +{ + zval function_name, retval; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + ZVAL_STRING(&function_name, "milter_eom", 0); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_EOM; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 0, NULL TSRMLS_CC); + + MG(state) = MLFI_NONE; + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* message aborted */ +/* {{{ mlfi_abort() +*/ +static sfsistat mlfi_abort(SMFICTX *ctx) +{ + zval function_name, retval; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + ZVAL_STRING(&function_name, "milter_abort", 0); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_ABORT; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 0, NULL TSRMLS_CC); + + MG(state) = MLFI_NONE; + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + return Z_LVAL(retval); + } + + return SMFIS_CONTINUE; +} +/* }}} */ + +/* connection cleanup */ +/* {{{ mlfi_close() +*/ +static sfsistat mlfi_close(SMFICTX *ctx) +{ + int ret = SMFIS_CONTINUE; + zval function_name, retval; + int status; + TSRMLS_FETCH(); + + /* call userland */ + INIT_ZVAL(function_name); + ZVAL_STRING(&function_name, "milter_close", 0); + + /* set the milter context for possible use in API functions */ + MG(ctx) = ctx; + MG(state) = MLFI_CLOSE; + + status = call_user_function(CG(function_table), NULL, &function_name, &retval, 0, NULL TSRMLS_CC); + + MG(state) = MLFI_NONE; + + if (status == SUCCESS && Z_TYPE(retval) == IS_LONG) { + ret = Z_LVAL(retval); + } + + php_request_shutdown((void *) 0); + + return ret; +} +/* }}} */ +/* }}} */ + +/* {{{ Milter entry struct + */ +struct smfiDesc smfilter = { + "php-milter", /* filter name */ + SMFI_VERSION, /* version code -- leave untouched */ + 0, /* flags */ + mlfi_connect, /* info filter callback */ + mlfi_helo, /* HELO filter callback */ + mlfi_envfrom, /* envelope filter callback */ + mlfi_envrcpt, /* envelope recipient filter callback */ + mlfi_header, /* header filter callback */ + mlfi_eoh, /* end of header callback */ + mlfi_body, /* body filter callback */ + mlfi_eom, /* end of message callback */ + mlfi_abort, /* message aborted callback */ + mlfi_close, /* connection cleanup callback */ +}; +/* }}} */ + +/* {{{ PHP Milter API + */ + +/* {{{ proto void smfi_setflags(long flags) + Sets the flags describing the actions the filter may take. */ +PHP_FUNCTION(smfi_setflags) +{ + long flags; + + /* valid only in the init callback */ + if (MG(state) != MLFI_INIT) { + php_error(E_WARNING, NOT_INIT, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "l", &flags) == SUCCESS) { + flags = flags & (SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_CHGBODY|SMFIF_ADDRCPT|SMFIF_DELRCPT); + smfilter.xxfi_flags = flags; + } +} +/* }}} */ + +/* {{{ proto void smfi_settimeout(long timeout) + Sets the number of seconds libmilter will wait for an MTA connection before timing out a socket. */ +PHP_FUNCTION(smfi_settimeout) +{ + long timeout; + + /* valid only in the init callback */ + if (MG(state) != MLFI_INIT) { + php_error(E_WARNING, NOT_INIT, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "l", &timeout) == SUCCESS) { + smfi_settimeout(timeout); + } +} +/* }}} */ + +/* {{{ proto string smfi_getsymval(string macro) + Returns the value of the given macro or NULL if the macro is not defined. */ +PHP_FUNCTION(smfi_getsymval) +{ + char *symname, *ret; + int len; + + /* valid in any callback */ + if (MG(state) == MLFI_NONE) { + php_error(E_WARNING, IS_NONE, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "s", &symname, &len) == SUCCESS) { + if ((ret = smfi_getsymval(MG(ctx), symname)) != NULL) { + RETURN_STRING(ret, 1); + } + } + + RETURN_NULL(); +} +/* }}} */ + +/* {{{ proto bool smfi_setreply(string rcode, string xcode, string message) + Directly set the SMTP error reply code for this connection. + This code will be used on subsequent error replies resulting from actions taken by this filter. */ +PHP_FUNCTION(smfi_setreply) +{ + char *rcode, *xcode, *message; + int len; + + /* valid in any callback */ + if (MG(state) == MLFI_NONE) { + php_error(E_WARNING, IS_NONE, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(3 TSRMLS_CC, "sss", &rcode, &len, &xcode, &len, &message, &len) == SUCCESS) { + if (smfi_setreply(MG(ctx), rcode, xcode, message) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool smfi_addheader(string headerf, string headerv) + Adds a header to the current message. */ +PHP_FUNCTION(smfi_addheader) +{ + char *f, *v; + int len; + + /* valid only in milter_eom */ + if (MG(state) != MLFI_EOM) { + php_error(E_WARNING, NOT_EOM, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(2 TSRMLS_CC, "ss", &f, &len, &v, &len) == SUCCESS) { + if (smfi_addheader(MG(ctx), f, v) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool smfi_chgheader(string headerf, string headerv) + Changes a header's value for the current message. */ +PHP_FUNCTION(smfi_chgheader) +{ + char *f, *v; + long idx; + int len; + + /* valid only in milter_eom */ + if (MG(state) != MLFI_EOM) { + php_error(E_WARNING, NOT_EOM, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(3 TSRMLS_CC, "sls", &f, &len, &idx, &v, &len) == SUCCESS) { + if (smfi_chgheader(MG(ctx), f, idx, v) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool smfi_addrcpt(string rcpt) + Add a recipient to the message envelope. */ +PHP_FUNCTION(smfi_addrcpt) +{ + char *rcpt; + int len; + + /* valid only in milter_eom */ + if (MG(state) != MLFI_EOM) { + php_error(E_WARNING, NOT_EOM, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "s", &rcpt, &len) == SUCCESS) { + if (smfi_addrcpt(MG(ctx), rcpt) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool smfi_delrcpt(string rcpt) + Removes the named recipient from the current message's envelope. */ +PHP_FUNCTION(smfi_delrcpt) +{ + char *rcpt; + int len; + + /* valid only in milter_eom */ + if (MG(state) != MLFI_EOM) { + php_error(E_WARNING, NOT_EOM, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "s", &rcpt, &len) == SUCCESS) { + if (smfi_delrcpt(MG(ctx), rcpt) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool smfi_replacebody(string body) + Replaces the body of the current message. If called more than once, + subsequent calls result in data being appended to the new body. */ +PHP_FUNCTION(smfi_replacebody) +{ + char *body; + int len; + + /* valid only in milter_eom */ + if (MG(state) != MLFI_EOM) { + php_error(E_WARNING, NOT_EOM, get_active_function_name(TSRMLS_C)); + } else if (zend_parse_parameters(1 TSRMLS_CC, "s", &body, &len) == SUCCESS) { + if (smfi_replacebody(MG(ctx), (u_char*)body, len) == MI_SUCCESS) { + RETURN_TRUE; + } + } + + RETURN_FALSE; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(milter) +{ + REGISTER_LONG_CONSTANT("SMFIS_CONTINUE", SMFIS_CONTINUE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIS_REJECT", SMFIS_REJECT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIS_DISCARD", SMFIS_DISCARD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIS_ACCEPT", SMFIS_ACCEPT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIS_TEMPFAIL", SMFIS_TEMPFAIL, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SMFIF_ADDHDRS", SMFIF_ADDHDRS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIF_CHGHDRS", SMFIF_CHGHDRS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIF_CHGBODY", SMFIF_CHGBODY, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIF_ADDRCPT", SMFIF_ADDRCPT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SMFIF_DELRCPT", SMFIF_DELRCPT, CONST_CS | CONST_PERSISTENT); + + ZEND_INIT_MODULE_GLOBALS(milter, NULL, NULL); + + MG(state) = MLFI_NONE; + MG(initialized) = 0; + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(milter) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Milter support", "enabled"); + php_info_print_table_end(); +} +/* }}} */ +/* }}} */ + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_setflags, 0, 0, 1) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_settimeout, 0, 0, 1) + ZEND_ARG_INFO(0, timeout) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_getsymval, 0, 0, 1) + ZEND_ARG_INFO(0, macro) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_setreply, 0, 0, 3) + ZEND_ARG_INFO(0, rcode) + ZEND_ARG_INFO(0, xcode) + ZEND_ARG_INFO(0, message) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_addheader, 0, 0, 2) + ZEND_ARG_INFO(0, headerf) + ZEND_ARG_INFO(0, headerv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_chgheader, 0, 0, 2) + ZEND_ARG_INFO(0, headerf) + ZEND_ARG_INFO(0, headerv) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_addrcpt, 0, 0, 1) + ZEND_ARG_INFO(0, rcpt) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_delrcpt, 0, 0, 1) + ZEND_ARG_INFO(0, rcpt) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_smfi_replacebody, 0, 0, 1) + ZEND_ARG_INFO(0, body) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ milter_functions[] +*/ +const static zend_function_entry milter_functions[] = { + PHP_FE(smfi_setflags, arginfo_smfi_setflags) + PHP_FE(smfi_settimeout, arginfo_smfi_settimeout) + PHP_FE(smfi_getsymval, arginfo_smfi_getsymval) + PHP_FE(smfi_setreply, arginfo_smfi_setreply) + PHP_FE(smfi_addheader, arginfo_smfi_addheader) + PHP_FE(smfi_chgheader, arginfo_smfi_chgheader) + PHP_FE(smfi_addrcpt, arginfo_smfi_addrcpt) + PHP_FE(smfi_delrcpt, arginfo_smfi_delrcpt) + PHP_FE(smfi_replacebody, arginfo_smfi_replacebody) + PHP_FE_END +}; +/* }}} */ + +/* {{{ Zend module entry +*/ +static zend_module_entry php_milter_module = { + STANDARD_MODULE_HEADER, + "Milter", + milter_functions, + PHP_MINIT(milter), + NULL, + NULL, + NULL, + PHP_MINFO(milter), + "0.1.0", + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ Milter SAPI +*/ +static int sapi_milter_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + return str_length; +} + +static void sapi_milter_flush(void *server_context) +{ +} + +static void sapi_milter_register_variables(zval *track_vars_array TSRMLS_DC) +{ + php_register_variable ("SERVER_SOFTWARE", "Sendmail Milter", track_vars_array TSRMLS_CC); +} + +static int sapi_milter_post_read(char *buf, uint count_bytes TSRMLS_DC) +{ + return 0; +} + +static char* sapi_milter_read_cookies(TSRMLS_D) +{ + return NULL; +} + +static int sapi_milter_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int php_milter_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_milter_module, 1) == FAILURE) { + return FAILURE; + } + return SUCCESS; +} +/* }}} */ + +/* {{{ sapi_module_struct milter_sapi_module +*/ +static sapi_module_struct milter_sapi_module = { + "milter", /* name */ + "Sendmail Milter SAPI", /* pretty name */ + + php_milter_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_milter_ub_write, /* unbuffered write */ + sapi_milter_flush, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + sapi_milter_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_milter_post_read, /* read POST data */ + sapi_milter_read_cookies, /* read Cookies */ + + sapi_milter_register_variables, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; +/* }}} */ + +/**** +* ripped from cli, has to be cleaned up ! +*/ + +/* {{{ php_milter_usage +*/ +static void php_milter_usage(char *argv0) +{ + char *prog; + + prog = strrchr(argv0, '/'); + if (prog) { + prog++; + } else { + prog = "php-milter"; + } + + printf( "Usage: %s [options] [-f] <file> [args...]\n" + " %s [options] [-- args...]\n" + " -a Run interactively\n" + " -c <path>|<file> Look for php.ini file in this directory\n" + " -n No php.ini file will be used\n" + " -d foo[=bar] Define INI entry foo with value 'bar'\n" + " -D run as daemon\n" + " -e Generate extended information for debugger/profiler\n" + " -f <file> Parse <file>.\n" + " -h This help\n" + " -p <socket> path to create socket\n" + " -v Version number\n" + " -V <n> set debug level to n (1 or 2).\n" + " -z <file> Load Zend extension <file>.\n" + " args... Arguments passed to script. Use -- args when first argument \n" + " starts with - or script is read from stdin\n" + , prog, prog); +} +/* }}} */ + +static void define_command_line_ini_entry(char *arg) /* {{{ */ +{ + char *name, *value; + + name = arg; + value = strchr(arg, '='); + if (value) { + *value = 0; + value++; + } else { + value = "1"; + } + zend_alter_ini_entry(name, strlen(name)+1, value, strlen(value), PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); +} +/* }}} */ + +/* {{{ main +*/ +int main(int argc, char *argv[]) +{ + char *sock = NULL; + int dofork = 0; + + int exit_status = SUCCESS; + int c; +/* temporary locals */ + int orig_optind=ap_php_optind; + char *orig_optarg=ap_php_optarg; + int interactive=0; + char *param_error=NULL; +/* end of temporary locals */ + + void ***tsrm_ls; + +#ifdef HAVE_SIGNAL_H +#if defined(SIGPIPE) && defined(SIG_IGN) + signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so + that sockets created via fsockopen() + don't kill PHP if the remote site + closes it. in apache|apxs mode apache + does that for us! thies@thieso.net + 20000419 */ +#endif +#endif + + + tsrm_startup(1, 1, 0, NULL); + sapi_startup(&milter_sapi_module); + + while ((c=ap_php_getopt(argc, argv, OPTSTRING))!=-1) { + switch (c) { + case 'c': + milter_sapi_module.php_ini_path_override = strdup(ap_php_optarg); + break; + case 'n': + milter_sapi_module.php_ini_ignore = 1; + break; + } + } + ap_php_optind = orig_optind; + ap_php_optarg = orig_optarg; + + milter_sapi_module.executable_location = argv[0]; + + tsrm_ls = ts_resource(0); + + sapi_module.startup(&milter_sapi_module); + + zend_first_try { + while ((c=ap_php_getopt(argc, argv, OPTSTRING))!=-1) { + switch (c) { + case '?': + php_output_tearup(); + SG(headers_sent) = 1; + php_milter_usage(argv[0]); + php_output_teardown(); + exit(1); + break; + } + } + ap_php_optind = orig_optind; + ap_php_optarg = orig_optarg; + + /* Set some CLI defaults */ + SG(options) |= SAPI_OPTION_NO_CHDIR; + zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + + zend_uv.html_errors = 0; /* tell the engine we're in non-html mode */ + + while ((c = ap_php_getopt(argc, argv, OPTSTRING)) != -1) { + switch (c) { + + case 'a': /* interactive mode */ + printf("Interactive mode enabled\n\n"); + interactive=1; + break; + + case 'C': /* don't chdir to the script directory */ + /* This is default so NOP */ + break; + case 'd': /* define ini entries on command line */ + define_command_line_ini_entry(ap_php_optarg); + break; + + case 'D': /* daemon */ + dofork = 1; + break; + + case 'e': /* enable extended info output */ + CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO; + break; + + case 'f': /* parse file */ + filename = ap_php_optarg; + break; + + case 'h': /* help & quit */ + case '?': + php_output_tearup(); + SG(headers_sent) = 1; + php_milter_usage(argv[0]); + php_output_teardown(); + exit(1); + break; + + case 'p': /* socket */ + sock = strdup(ap_php_optarg); + break; + + case 'v': /* show php version & quit */ + if (php_request_startup(TSRMLS_C)==FAILURE) { + zend_ini_deactivate(TSRMLS_C); + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); + tsrm_shutdown(); + + exit(1); + } + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2013 The PHP Group\n%s", PHP_VERSION, sapi_module.name, __DATE__, __TIME__, get_zend_version()); + php_output_teardown(); + exit(1); + break; + + case 'V': /* verbose */ + flag_debug = atoi(ap_php_optarg); + break; + + case 'z': /* load extension file */ + zend_load_extension(ap_php_optarg); + break; + + default: + break; + } + } + + if (param_error) { + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; + PUTS(param_error); + exit(1); + } + + CG(interactive) = interactive; + + /* only set script_file if not set already and not in direct mode and not at end of parameter list */ + if (argc > ap_php_optind && !filename) { + filename=argv[ap_php_optind]; + ap_php_optind++; + } + + /* check if file exists, exit else */ + + if (dofork) { + switch(fork()) { + case -1: /* Uh-oh, we have a problem forking. */ + fprintf(stderr, "Uh-oh, couldn't fork!\n"); + exit(errno); + break; + case 0: /* Child */ + break; + default: /* Parent */ + exit(0); + } + } + + if (sock) { + struct stat junk; + if (stat(sock,&junk) == 0) unlink(sock); + } + + openlog("php-milter", LOG_PID, LOG_MAIL); + + if ((exit_status = mlfi_init())) { + syslog(1, "mlfi_init failed."); + closelog(); + goto err; + } + + smfi_setconn(sock); + if (smfi_register(smfilter) == MI_FAILURE) { + syslog(1, "smfi_register failed."); + fprintf(stderr, "smfi_register failed\n"); + closelog(); + goto err; + } else { + exit_status = smfi_main(); + } + + closelog(); + + if (milter_sapi_module.php_ini_path_override) { + free(milter_sapi_module.php_ini_path_override); + } + + } zend_catch { + exit_status = EG(exit_status); + } zend_end_try(); + +err: + php_module_shutdown(TSRMLS_C); + sapi_shutdown(); + tsrm_shutdown(); + + exit(exit_status); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/milter/php_milter.h b/sapi/milter/php_milter.h new file mode 100644 index 0000000..72d7ac5 --- /dev/null +++ b/sapi/milter/php_milter.h @@ -0,0 +1,31 @@ +#ifndef PHP_MILTER_H +#define PHP_MILTER_H + +#include "libmilter/mfapi.h" + +#define MLFI_NONE 0 +#define MLFI_CONNECT 1 +#define MLFI_HELO 2 +#define MLFI_ENVFROM 3 +#define MLFI_ENVRCPT 4 +#define MLFI_HEADER 5 +#define MLFI_EOH 6 +#define MLFI_BODY 7 +#define MLFI_EOM 8 +#define MLFI_ABORT 9 +#define MLFI_CLOSE 10 +#define MLFI_INIT 11 + +#define MG(v) TSRMG(milter_globals_id, zend_milter_globals *, v) + +typedef struct { + pthread_t thread; + MUTEX_T receiver; + MUTEX_T sender; + SMFICTX *ctx; + sfsistat retval; + int message; + void **args; +} worker_thread; + +#endif diff --git a/sapi/nsapi/CREDITS b/sapi/nsapi/CREDITS new file mode 100644 index 0000000..2a05919 --- /dev/null +++ b/sapi/nsapi/CREDITS @@ -0,0 +1,2 @@ +NSAPI +Jayakumar Muthukumarasamy, Uwe Schindler diff --git a/sapi/nsapi/config.m4 b/sapi/nsapi/config.m4 new file mode 100644 index 0000000..8923f53 --- /dev/null +++ b/sapi/nsapi/config.m4 @@ -0,0 +1,39 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(nsapi, for NSAPI support, +[ --with-nsapi=DIR Build PHP as NSAPI module for Netscape/iPlanet/Sun Webserver], no, no) + +if test "$PHP_NSAPI" != "no"; then + if test ! -d $PHP_NSAPI/bin ; then + AC_MSG_ERROR(Please specify the path to the root of your Netscape/iPlanet/Sun Webserver using --with-nsapi=DIR) + fi + AC_MSG_CHECKING([for NSAPI include files]) + if test -d $PHP_NSAPI/include ; then + NSAPI_INC_DIR="$PHP_NSAPI/include" + AC_MSG_RESULT([Netscape 3.x / Sun 7.x style]) + AC_CHECK_HEADERS([$NSAPI_INC_DIR/nsapi.h]) + NSAPI_INCLUDE="-I$NSAPI_INC_DIR" + fi + if test -d $PHP_NSAPI/plugins/include ; then + NSAPI_INC_DIR="$PHP_NSAPI/plugins/include" + AC_MSG_RESULT([iPlanet 4.x / Sun 6.x style]) + AC_CHECK_HEADERS([$NSAPI_INC_DIR/nsapi.h]) + NSAPI_INCLUDE="$NSAPI_INCLUDE -I$NSAPI_INC_DIR" + fi + if test -z "$NSAPI_INCLUDE"; then + AC_MSG_ERROR([Please check you have nsapi.h in either $PHP_NSAPI/include or $PHP_NSAPI/plugins/include]) + fi + + PHP_EVAL_INCLINE($NSAPI_INCLUDE) + PHP_BUILD_THREAD_SAFE + AC_DEFINE(HAVE_NSAPI, 1, [Whether you have a Netscape/iPlanet/Sun Webserver]) + PHP_SELECT_SAPI(nsapi, shared, nsapi.c) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$PHP_NSAPI/bin/" +fi + + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/nsapi/config.w32 b/sapi/nsapi/config.w32 new file mode 100644 index 0000000..17b86d2 --- /dev/null +++ b/sapi/nsapi/config.w32 @@ -0,0 +1,20 @@ +// vim:ft=javascript +// $Id$ + +ARG_ENABLE('nsapi', 'Build NSAPI for Netscape/iPlanet/SunONE webservers', 'no'); + +ARG_WITH('nsapi-includes', 'Where to find NSAPI headers', null); +ARG_WITH('nsapi-libs', 'Where to find NSAPI libraries', null); + +if (PHP_NSAPI != "no") { + if (PHP_ZTS == "no") { + WARNING("NSAPI module requires an --enable-zts build of PHP"); + PHP_NSAPI = "no" + } else if (CHECK_HEADER_ADD_INCLUDE("nsapi.h", "CFLAGS_NSAPI", + PHP_NSAPI + ';' + PHP_NSAPI_INCLUDES) && + CHECK_LIB("ns-httpd*.lib", "nsapi", PHP_NSAPI + ";" + PHP_NSAPI_LIBS)) { + SAPI('nsapi', 'nsapi.c', 'php' + PHP_VERSION + 'nsapi.dll', '/D XP_WIN32 '); + } else { + WARNING("Could not find NSAPI headers/libraries"); + } +} diff --git a/sapi/nsapi/nsapi-readme.txt b/sapi/nsapi/nsapi-readme.txt new file mode 100644 index 0000000..54980bf --- /dev/null +++ b/sapi/nsapi/nsapi-readme.txt @@ -0,0 +1,154 @@ +Configuration of your Netscape/iPlanet/Sun Webserver for PHP5 +----------------------------------------------------------------- + +These instructions are targetted at Netscape Enterprise Web Server and +SUN/Netscape Alliance iPlanet Web Server and the new Sun Java System Webserver. +On other web servers your milage may vary. + +Firstly you may need to add some paths to the LD_LIBRARY_PATH +environment for Netscape to find all the shared libs. This is best done +in the start script for your Netscape server. Windows users can +probably skip this step. The start script is located in: + + <path-to-netscape-server>/https-servername/start + + +Netscape/iPlanet/Sun config files are located in: + + <path-to-server>/https-servername/config + + +Add the following line to mime.types (you can do that by the administration server): + + type=magnus-internal/x-httpd-php exts=php + + +Place the following two lines after mime.types init in +<path-to-server>/https-servername/config/obj.conf (for servers < 6) or +for iPlanet/Sun Webserver 6.0 and above however at the end of the +<path-to-server>/https-servername/config/magnus.conf file: + + Init fn="load-modules" funcs="php5_init,php5_execute,php5_auth_trans" shlib="/path/to/phplibrary" + Init fn=php5_init errorString="Failed to initialize PHP!" [php_ini="/path/to/php.ini"] + +The "shlib" will vary depending on your OS: + + Unix: "<path-to-server>/bin/libphp5.so". + Windows: "c:/path/to/php5/php5nsapi.dll" + + +In obj.conf (for virtual server classes [Sun 6.0+] in their vserver.obj.conf): + + <Object name="default"> + . + . + . + # NOTE this next line should happen after all 'ObjectType' and before + # all 'AddLog' lines + # You can modify some entries in php.ini request specific by adding it to the Service + # directive, e.g. doc_root="/path" + # For boolean ini-keys please use 0/1 as value, NOT "On","Off",... (this will not work + # correctly), e.g. zlib.output_compression=1 instead of zlib.output_compression="On" + + Service fn="php5_execute" type="magnus-internal/x-httpd-php" [inikey=value ...] + . + . + . + </Object> + +This is only needed if you want to configure a directory that only consists of +PHP scripts (same like a cgi-bin directory): + + <Object name="x-httpd-php"> + ObjectType fn="force-type" type="magnus-internal/x-httpd-php" + Service fn="php5_execute" [inikey=value ...] + </Object> + +After that you can configure a directory in the Administration server and assign it +the style "x-httpd-php". All files in it will get executed as PHP. This is nice to +hide PHP usage by renaming files to .html + +Note: The stacksize that PHP uses depends on the configuration of the webserver. If you get +crashes with very large PHP scripts, it is recommended to raise it with the Admin Server +(in the section "MAGNUS EDITOR"). + + +Authentication configuration +---------------------------- + +PHP authentication cannot be used with any other authentication. ALL +AUTHENTICATION IS PASSED TO YOUR PHP SCRIPT. To configure PHP +Authentication for the entire server, add the following line: + + <Object name="default"> + AuthTrans fn=php5_auth_trans + . + . + . + . + </Object> + + +To use PHP Authentication on a single directory, add the following: + + <Object ppath="d:\path\to\authenticated\dir\*"> + AuthTrans fn=php5_auth_trans + </Object> + + +Special use for error pages or self-made directory listings +----------------------------------------------------------- + +You can use PHP to generate the error pages for "404 Not Found" +or similar. Add the following line to the object in obj.conf for +every error page you want to overwrite: + + Error fn="php5_execute" code=XXX script="/path/to/script.php" [inikey=value inikey=value...] + +where XXX ist the HTTP error code. Please delete any other Error +directives which could interfere with yours. +If you want to place a page for all errors that could exist, leave +the "code" parameter out. Your script can get the HTTP status code +with $_SERVER['ERROR_TYPE']. + +Another posibility is to generate self-made directory listings. +Just generate a PHP script which displays a directory listing and +replace the corresponding default Service line for +type="magnus-internal/directory" in obj.conf with the following: + + Service fn="php5_execute" type="magnus-internal/directory" script="/path/to/script.php" [inikey=value inikey=value...] + +For both error and directory listing pages the original URI and +translated URI are in the variables $_SERVER['PATH_INFO'] and +$_SERVER['PATH_TRANSLATED']. + + +Note about nsapi_virtual() and subrequests +------------------------------------------ + +The NSAPI module now supports the nsapi_virtual() function (alias: virtual()) +to make subrequests on the webserver and insert the result in the webpage. +The problem is, that this function uses some undocumented features from +the NSAPI library. + +Under Unix this is not a problem, because the module automatically looks +for the needed functions and uses them if available. If not, nsapi_virtual() +is disabled. + +Under Windows limitations in the DLL handling need the use of a automatic +detection of the most recent ns-httpdXX.dll file. This is tested for servers +till version 6.1. If a newer version of the Sun server is used, the detection +fails and nsapi_virtual() is disabled. + +If this is the case, try the following: +Add the following parameter to php5_init in magnus.conf: + + Init fn=php5_init ... server_lib="ns-httpdXX.dll" + +where XX is the correct DLL version number. To get it, look in the server-root +for the correct DLL name. The DLL with the biggest filesize is the right one. + +But be warned: SUPPORT FOR nsapi_virtual() IS EXPERIMENTAL !!! + + +$Id$ diff --git a/sapi/nsapi/nsapi.c b/sapi/nsapi/nsapi.c new file mode 100644 index 0000000..66260e7 --- /dev/null +++ b/sapi/nsapi/nsapi.c @@ -0,0 +1,1104 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Jayakumar Muthukumarasamy <jk@kasenna.com> | + | Uwe Schindler <uwe@thetaphi.de> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: 1e6a680c91a69e9b9e4f91782d2582c3fa16939d $ */ + +/* + * PHP includes + */ +#define NSAPI 1 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_variables.h" +#include "ext/standard/info.h" +#include "php_ini.h" +#include "php_globals.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_version.h" +#include "TSRM.h" +#include "ext/standard/php_standard.h" +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +/* + * If neither XP_UNIX not XP_WIN32 is defined use PHP_WIN32 + */ +#if !defined(XP_UNIX) && !defined(XP_WIN32) +#ifdef PHP_WIN32 +#define XP_WIN32 +#else +#define XP_UNIX +#endif +#endif + +/* + * The manual define of HPUX is to fix bug #46020, nsapi.h needs this to detect HPUX + */ +#ifdef __hpux +#define HPUX +#endif + +/* + * NSAPI includes + */ +#include "nsapi.h" + +/* fix for gcc4 visibility issue */ +#ifndef PHP_WIN32 +# undef NSAPI_PUBLIC +# define NSAPI_PUBLIC PHPAPI +#endif + +#define NSLS_D struct nsapi_request_context *request_context +#define NSLS_DC , NSLS_D +#define NSLS_C request_context +#define NSLS_CC , NSLS_C +#define NSG(v) (request_context->v) + +/* + * ZTS needs to be defined for NSAPI to work + */ +#if !defined(ZTS) +#error "NSAPI module needs ZTS to be defined" +#endif + +/* + * Structure to encapsulate the NSAPI request in SAPI + */ +typedef struct nsapi_request_context { + pblock *pb; + Session *sn; + Request *rq; + int read_post_bytes; + char *path_info; + int fixed_script; /* 0 if script is from URI, 1 if script is from "script" parameter */ + short http_error; /* 0 in normal mode; for errors the HTTP error code */ +} nsapi_request_context; + +/* + * Mappings between NSAPI names and environment variables. This + * mapping was obtained from the sample programs at the iplanet + * website. + */ +typedef struct nsapi_equiv { + const char *env_var; + const char *nsapi_eq; +} nsapi_equiv; + +static nsapi_equiv nsapi_reqpb[] = { + { "QUERY_STRING", "query" }, + { "REQUEST_LINE", "clf-request" }, + { "REQUEST_METHOD", "method" }, + { "PHP_SELF", "uri" }, + { "SERVER_PROTOCOL", "protocol" } +}; +static size_t nsapi_reqpb_size = sizeof(nsapi_reqpb)/sizeof(nsapi_reqpb[0]); + +static nsapi_equiv nsapi_vars[] = { + { "AUTH_TYPE", "auth-type" }, + { "CLIENT_CERT", "auth-cert" }, + { "REMOTE_USER", "auth-user" } +}; +static size_t nsapi_vars_size = sizeof(nsapi_vars)/sizeof(nsapi_vars[0]); + +static nsapi_equiv nsapi_client[] = { + { "HTTPS_KEYSIZE", "keysize" }, + { "HTTPS_SECRETSIZE", "secret-keysize" }, + { "REMOTE_ADDR", "ip" }, + { "REMOTE_HOST", "ip" } +}; +static size_t nsapi_client_size = sizeof(nsapi_client)/sizeof(nsapi_client[0]); + +/* this parameters to "Service"/"Error" are NSAPI ones which should not be php.ini keys and are excluded */ +static char *nsapi_exclude_from_ini_entries[] = { "fn", "type", "method", "directive", "code", "reason", "script", "bucket", NULL }; + +static void nsapi_free(void *addr) +{ + if (addr != NULL) { + FREE(addr); + } +} + + +/*******************/ +/* PHP module part */ +/*******************/ + +PHP_MINIT_FUNCTION(nsapi); +PHP_MSHUTDOWN_FUNCTION(nsapi); +PHP_RINIT_FUNCTION(nsapi); +PHP_RSHUTDOWN_FUNCTION(nsapi); +PHP_MINFO_FUNCTION(nsapi); + +PHP_FUNCTION(nsapi_virtual); +PHP_FUNCTION(nsapi_request_headers); +PHP_FUNCTION(nsapi_response_headers); + +ZEND_BEGIN_MODULE_GLOBALS(nsapi) + long read_timeout; +ZEND_END_MODULE_GLOBALS(nsapi) + +ZEND_DECLARE_MODULE_GLOBALS(nsapi) + +#define NSAPI_G(v) TSRMG(nsapi_globals_id, zend_nsapi_globals *, v) + + +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_nsapi_virtual, 0, 0, 1) + ZEND_ARG_INFO(0, uri) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_nsapi_request_headers, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_nsapi_response_headers, 0) +ZEND_END_ARG_INFO() +/* }}} */ + +/* {{{ nsapi_functions[] + * + * Every user visible function must have an entry in nsapi_functions[]. + */ +const zend_function_entry nsapi_functions[] = { + PHP_FE(nsapi_virtual, arginfo_nsapi_virtual) /* Make subrequest */ + PHP_FALIAS(virtual, nsapi_virtual, arginfo_nsapi_virtual) /* compatibility */ + PHP_FE(nsapi_request_headers, arginfo_nsapi_request_headers) /* get request headers */ + PHP_FALIAS(getallheaders, nsapi_request_headers, arginfo_nsapi_request_headers) /* compatibility */ + PHP_FALIAS(apache_request_headers, nsapi_request_headers, arginfo_nsapi_request_headers) /* compatibility */ + PHP_FE(nsapi_response_headers, arginfo_nsapi_response_headers) /* get response headers */ + PHP_FALIAS(apache_response_headers, nsapi_response_headers, arginfo_nsapi_response_headers) /* compatibility */ + {NULL, NULL, NULL} +}; +/* }}} */ + +/* {{{ nsapi_module_entry + */ +zend_module_entry nsapi_module_entry = { + STANDARD_MODULE_HEADER, + "nsapi", + nsapi_functions, + PHP_MINIT(nsapi), + PHP_MSHUTDOWN(nsapi), + NULL, + NULL, + PHP_MINFO(nsapi), + NO_VERSION_YET, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +/* {{{ PHP_INI + */ +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("nsapi.read_timeout", "60", PHP_INI_ALL, OnUpdateLong, read_timeout, zend_nsapi_globals, nsapi_globals) +PHP_INI_END() +/* }}} */ + +/* newer servers hide this functions from the programmer so redefine the functions dynamically + thanks to Chris Elving from Sun for the function declarations */ +typedef int (*nsapi_servact_prototype)(Session *sn, Request *rq); +nsapi_servact_prototype nsapi_servact_uri2path = NULL; +nsapi_servact_prototype nsapi_servact_pathchecks = NULL; +nsapi_servact_prototype nsapi_servact_fileinfo = NULL; +nsapi_servact_prototype nsapi_servact_service = NULL; + +#ifdef PHP_WIN32 +/* The following dll-names for nsapi are in use at this time. The undocumented + * servact_* functions are always in the newest one, older ones are supported by + * the server only by wrapping the function table nothing else. So choose + * the newest one found in process space for dynamic linking */ +static char *nsapi_dlls[] = { "ns-httpd40.dll", "ns-httpd36.dll", "ns-httpd35.dll", "ns-httpd30.dll", NULL }; +/* if user specifies an other dll name by server_lib parameter + * it is placed in the following variable and only this DLL is + * checked for the servact_* functions */ +char *nsapi_dll = NULL; +#endif + +/* {{{ php_nsapi_init_dynamic_symbols + */ +static void php_nsapi_init_dynamic_symbols(void) +{ + /* find address of internal NSAPI functions */ +#ifdef PHP_WIN32 + register int i; + DL_HANDLE module = NULL; + if (nsapi_dll) { + /* try user specified server_lib */ + module = GetModuleHandle(nsapi_dll); + if (!module) { + log_error(LOG_WARN, "php5_init", NULL, NULL, "Cannot find DLL specified by server_lib parameter: %s", nsapi_dll); + } + } else { + /* find a LOADED dll module from nsapi_dlls */ + for (i=0; nsapi_dlls[i]; i++) { + if (module = GetModuleHandle(nsapi_dlls[i])) { + break; + } + } + } + if (!module) return; +#else + DL_HANDLE module = RTLD_DEFAULT; +#endif + nsapi_servact_uri2path = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_uri2path"); + nsapi_servact_pathchecks = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_pathchecks"); + nsapi_servact_fileinfo = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_fileinfo"); + nsapi_servact_service = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_service"); + if (!(nsapi_servact_uri2path && nsapi_servact_pathchecks && nsapi_servact_fileinfo && nsapi_servact_service)) { + /* not found - could be cause they are undocumented */ + nsapi_servact_uri2path = NULL; + nsapi_servact_pathchecks = NULL; + nsapi_servact_fileinfo = NULL; + nsapi_servact_service = NULL; + } +} +/* }}} */ + +/* {{{ php_nsapi_init_globals + */ +static void php_nsapi_init_globals(zend_nsapi_globals *nsapi_globals) +{ + nsapi_globals->read_timeout = 60; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION + */ +PHP_MINIT_FUNCTION(nsapi) +{ + php_nsapi_init_dynamic_symbols(); + ZEND_INIT_MODULE_GLOBALS(nsapi, php_nsapi_init_globals, NULL); + REGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +PHP_MSHUTDOWN_FUNCTION(nsapi) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(nsapi) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "NSAPI Module Revision", "$Id: 1e6a680c91a69e9b9e4f91782d2582c3fa16939d $"); + php_info_print_table_row(2, "Server Software", system_version()); + php_info_print_table_row(2, "Sub-requests with nsapi_virtual()", + (nsapi_servact_service)?((zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0))?"not supported with zlib.output_compression":"enabled"):"not supported on this platform" ); + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ proto bool nsapi_virtual(string uri) + Perform an NSAPI sub-request */ +/* This function is equivalent to <!--#include virtual...--> + * in SSI. It does an NSAPI sub-request. It is useful + * for including CGI scripts or .shtml files, or anything else + * that you'd parse through webserver. + */ +PHP_FUNCTION(nsapi_virtual) +{ + int uri_len,rv; + char *uri,*value; + Request *rq; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &uri, &uri_len) == FAILURE) { + return; + } + + if (!nsapi_servact_service) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests not supported on this platform", uri); + RETURN_FALSE; + } else if (zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests do not work with zlib.output_compression", uri); + RETURN_FALSE; + } else { + php_output_end_all(TSRMLS_C); + php_header(TSRMLS_C); + + /* do the sub-request */ + /* thanks to Chris Elving from Sun for this code sniplet */ + if ((rq = request_restart_internal(uri, NULL)) == NULL) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Internal request creation failed", uri); + RETURN_FALSE; + } + + /* insert host of current request to get page from same vhost */ + param_free(pblock_remove("host", rq->headers)); + if (value = pblock_findval("host", rc->rq->headers)) { + pblock_nvinsert("host", value, rq->headers); + } + + /* go through the normal request stages as given in obj.conf, + but leave out the logging/error section */ + do { + rv = (*nsapi_servact_uri2path)(rc->sn, rq); + if (rv != REQ_PROCEED) { + continue; + } + + rv = (*nsapi_servact_pathchecks)(rc->sn, rq); + if (rv != REQ_PROCEED) { + continue; + } + + rv = (*nsapi_servact_fileinfo)(rc->sn, rq); + if (rv != REQ_PROCEED) { + continue; + } + + rv = (*nsapi_servact_service)(rc->sn, rq); + } while (rv == REQ_RESTART); + + if (rq->status_num != 200) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - HTTP status code %d during subrequest", uri, rq->status_num); + request_free(rq); + RETURN_FALSE; + } + + request_free(rq); + + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ proto array nsapi_request_headers(void) + Get all headers from the request */ +PHP_FUNCTION(nsapi_request_headers) +{ + register int i; + struct pb_entry *entry; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + for (i=0; i < rc->rq->headers->hsize; i++) { + entry=rc->rq->headers->ht[i]; + while (entry) { + add_assoc_string(return_value, entry->param->name, entry->param->value, 1); + entry=entry->next; + } + } +} +/* }}} */ + +/* {{{ proto array nsapi_response_headers(void) + Get all headers from the response */ +PHP_FUNCTION(nsapi_response_headers) +{ + register int i; + struct pb_entry *entry; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + array_init(return_value); + + for (i=0; i < rc->rq->srvhdrs->hsize; i++) { + entry=rc->rq->srvhdrs->ht[i]; + while (entry) { + add_assoc_string(return_value, entry->param->name, entry->param->value, 1); + entry=entry->next; + } + } +} +/* }}} */ + + +/*************/ +/* SAPI part */ +/*************/ + +static int sapi_nsapi_ub_write(const char *str, unsigned int str_length TSRMLS_DC) +{ + int retval; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (!SG(headers_sent)) { + sapi_send_headers(TSRMLS_C); + } + + retval = net_write(rc->sn->csd, (char *)str, str_length); + if (retval == IO_ERROR /* -1 */ || retval == IO_EOF /* 0 */) { + php_handle_aborted_connection(); + } + return retval; +} + +/* modified version of apache2 */ +static void sapi_nsapi_flush(void *server_context) +{ + nsapi_request_context *rc = (nsapi_request_context *)server_context; + TSRMLS_FETCH(); + + if (!rc) { + /* we have no context, so no flushing needed. This fixes a SIGSEGV on shutdown */ + return; + } + + if (!SG(headers_sent)) { + sapi_send_headers(TSRMLS_C); + } + + /* flushing is only supported in iPlanet servers from version 6.1 on, make it conditional */ +#if NSAPI_VERSION >= 302 + if (net_flush(rc->sn->csd) < 0) { + php_handle_aborted_connection(); + } +#endif +} + +/* callback for zend_llist_apply on SAPI_HEADER_DELETE_ALL operation */ +static int php_nsapi_remove_header(sapi_header_struct *sapi_header TSRMLS_DC) +{ + char *header_name, *p; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + /* copy the header, because NSAPI needs reformatting and we do not want to change the parameter */ + header_name = pool_strdup(rc->sn->pool, sapi_header->header); + + /* extract name, this works, if only the header without ':' is given, too */ + if (p = strchr(header_name, ':')) { + *p = 0; + } + + /* header_name to lower case because NSAPI reformats the headers and wants lowercase */ + for (p=header_name; *p; p++) { + *p=tolower(*p); + } + + /* remove the header */ + param_free(pblock_remove(header_name, rc->rq->srvhdrs)); + pool_free(rc->sn->pool, header_name); + + return ZEND_HASH_APPLY_KEEP; +} + +static int sapi_nsapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + switch(op) { + case SAPI_HEADER_DELETE_ALL: + /* this only deletes headers set or overwritten by PHP, headers previously set by NSAPI are left intact */ + zend_llist_apply(&sapi_headers->headers, (llist_apply_func_t) php_nsapi_remove_header TSRMLS_CC); + return 0; + + case SAPI_HEADER_DELETE: + /* reuse the zend_llist_apply callback function for this, too */ + php_nsapi_remove_header(sapi_header TSRMLS_CC); + return 0; + + case SAPI_HEADER_ADD: + case SAPI_HEADER_REPLACE: + /* copy the header, because NSAPI needs reformatting and we do not want to change the parameter */ + header_name = pool_strdup(rc->sn->pool, sapi_header->header); + + /* split header and align pointer for content */ + header_content = strchr(header_name, ':'); + if (header_content) { + *header_content = 0; + do { + header_content++; + } while (*header_content==' '); + + /* header_name to lower case because NSAPI reformats the headers and wants lowercase */ + for (p=header_name; *p; p++) { + *p=tolower(*p); + } + + /* if REPLACE, remove first. "Content-type" is always removed, as SAPI has a bug according to this */ + if (op==SAPI_HEADER_REPLACE || strcmp(header_name, "content-type")==0) { + param_free(pblock_remove(header_name, rc->rq->srvhdrs)); + } + /* ADD header to nsapi table */ + pblock_nvinsert(header_name, header_content, rc->rq->srvhdrs); + } + + pool_free(rc->sn->pool, header_name); + return SAPI_HEADER_ADD; + + default: + return 0; + } +} + +static int sapi_nsapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + int retval; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (SG(sapi_headers).send_default_content_type) { + char *hd; + param_free(pblock_remove("content-type", rc->rq->srvhdrs)); + hd = sapi_get_default_content_type(TSRMLS_C); + pblock_nvinsert("content-type", hd, rc->rq->srvhdrs); + efree(hd); + } + + protocol_status(rc->sn, rc->rq, SG(sapi_headers).http_response_code, NULL); + retval = protocol_start_response(rc->sn, rc->rq); + + if (retval == REQ_PROCEED || retval == REQ_NOACTION) { + return SAPI_HEADER_SENT_SUCCESSFULLY; + } else { + return SAPI_HEADER_SEND_FAILED; + } +} + +static int sapi_nsapi_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + char *read_ptr = buffer, *content_length_str = NULL; + uint bytes_read = 0; + int length, content_length = 0; + netbuf *nbuf = rc->sn->inbuf; + + /* + * Yesss! + */ + count_bytes = MIN(count_bytes, SG(request_info).content_length-rc->read_post_bytes); + content_length = SG(request_info).content_length; + + if (content_length <= 0) { + return 0; + } + + /* + * Gobble any pending data in the netbuf. + */ + length = nbuf->cursize - nbuf->pos; + length = MIN(count_bytes, length); + if (length > 0) { + memcpy(read_ptr, nbuf->inbuf + nbuf->pos, length); + bytes_read += length; + read_ptr += length; + content_length -= length; + nbuf->pos += length; + } + + /* + * Read the remaining from the socket. + */ + while (content_length > 0 && bytes_read < count_bytes) { + int bytes_to_read = count_bytes - bytes_read; + + if (content_length < bytes_to_read) { + bytes_to_read = content_length; + } + + length = net_read(rc->sn->csd, read_ptr, bytes_to_read, NSAPI_G(read_timeout)); + + if (length == IO_ERROR || length == IO_EOF) { + break; + } + + bytes_read += length; + read_ptr += length; + content_length -= length; + } + + if ( bytes_read > 0 ) { + rc->read_post_bytes += bytes_read; + } + return bytes_read; +} + +static char *sapi_nsapi_read_cookies(TSRMLS_D) +{ + char *cookie_string; + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + cookie_string = pblock_findval("cookie", rc->rq->headers); + return cookie_string; +} + +static void sapi_nsapi_register_server_variables(zval *track_vars_array TSRMLS_DC) +{ + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + register size_t i; + int pos; + char *value,*p; + char buf[32]; + struct pb_entry *entry; + + for (i = 0; i < nsapi_reqpb_size; i++) { + value = pblock_findval(nsapi_reqpb[i].nsapi_eq, rc->rq->reqpb); + if (value) { + php_register_variable((char *)nsapi_reqpb[i].env_var, value, track_vars_array TSRMLS_CC); + } + } + + for (i=0; i < rc->rq->headers->hsize; i++) { + entry=rc->rq->headers->ht[i]; + while (entry) { + if (strcasecmp(entry->param->name, "content-length")==0 || strcasecmp(entry->param->name, "content-type")==0) { + value=estrdup(entry->param->name); + pos = 0; + } else { + spprintf(&value, 0, "HTTP_%s", entry->param->name); + pos = 5; + } + if (value) { + for(p = value + pos; *p; p++) { + *p = toupper(*p); + if (!isalnum(*p)) { + *p = '_'; + } + } + php_register_variable(value, entry->param->value, track_vars_array TSRMLS_CC); + efree(value); + } + entry=entry->next; + } + } + + for (i = 0; i < nsapi_vars_size; i++) { + value = pblock_findval(nsapi_vars[i].nsapi_eq, rc->rq->vars); + if (value) { + php_register_variable((char *)nsapi_vars[i].env_var, value, track_vars_array TSRMLS_CC); + } + } + + for (i = 0; i < nsapi_client_size; i++) { + value = pblock_findval(nsapi_client[i].nsapi_eq, rc->sn->client); + if (value) { + php_register_variable((char *)nsapi_client[i].env_var, value, track_vars_array TSRMLS_CC); + } + } + + if (value = session_dns(rc->sn)) { + php_register_variable("REMOTE_HOST", value, track_vars_array TSRMLS_CC); + nsapi_free(value); + } + + slprintf(buf, sizeof(buf), "%d", conf_getglobals()->Vport); + php_register_variable("SERVER_PORT", buf, track_vars_array TSRMLS_CC); + php_register_variable("SERVER_NAME", conf_getglobals()->Vserver_hostname, track_vars_array TSRMLS_CC); + + value = http_uri2url_dynamic("", "", rc->sn, rc->rq); + php_register_variable("SERVER_URL", value, track_vars_array TSRMLS_CC); + nsapi_free(value); + + php_register_variable("SERVER_SOFTWARE", system_version(), track_vars_array TSRMLS_CC); + if (security_active) { + php_register_variable("HTTPS", "ON", track_vars_array TSRMLS_CC); + } + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC); + + /* DOCUMENT_ROOT */ + if (value = request_translate_uri("/", rc->sn)) { + pos = strlen(value); + php_register_variable_safe("DOCUMENT_ROOT", value, pos-1, track_vars_array TSRMLS_CC); + nsapi_free(value); + } + + /* PATH_INFO / PATH_TRANSLATED */ + if (rc->path_info) { + if (value = request_translate_uri(rc->path_info, rc->sn)) { + php_register_variable("PATH_TRANSLATED", value, track_vars_array TSRMLS_CC); + nsapi_free(value); + } + php_register_variable("PATH_INFO", rc->path_info, track_vars_array TSRMLS_CC); + } + + /* Create full Request-URI & Script-Name */ + if (SG(request_info).request_uri) { + pos = strlen(SG(request_info).request_uri); + + if (SG(request_info).query_string) { + spprintf(&value, 0, "%s?%s", SG(request_info).request_uri, SG(request_info).query_string); + if (value) { + php_register_variable("REQUEST_URI", value, track_vars_array TSRMLS_CC); + efree(value); + } + } else { + php_register_variable_safe("REQUEST_URI", SG(request_info).request_uri, pos, track_vars_array TSRMLS_CC); + } + + if (rc->path_info) { + pos -= strlen(rc->path_info); + if (pos<0) { + pos = 0; + } + } + php_register_variable_safe("SCRIPT_NAME", SG(request_info).request_uri, pos, track_vars_array TSRMLS_CC); + } + php_register_variable("SCRIPT_FILENAME", SG(request_info).path_translated, track_vars_array TSRMLS_CC); + + /* special variables in error mode */ + if (rc->http_error) { + slprintf(buf, sizeof(buf), "%d", rc->http_error); + php_register_variable("ERROR_TYPE", buf, track_vars_array TSRMLS_CC); + } +} + +static void nsapi_log_message(char *message TSRMLS_DC) +{ + nsapi_request_context *rc = (nsapi_request_context *)SG(server_context); + + if (rc) { + log_error(LOG_INFORM, pblock_findval("fn", rc->pb), rc->sn, rc->rq, "%s", message); + } else { + log_error(LOG_INFORM, "php5", NULL, NULL, "%s", message); + } +} + +static double sapi_nsapi_get_request_time(TSRMLS_D) +{ + return REQ_TIME( ((nsapi_request_context *)SG(server_context))->rq ); +} + +static int php_nsapi_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &nsapi_module_entry, 1)==FAILURE) { + return FAILURE; + } + return SUCCESS; +} + +static struct stat* sapi_nsapi_get_stat(TSRMLS_D) +{ + return request_stat_path( + SG(request_info).path_translated, + ((nsapi_request_context *)SG(server_context))->rq + ); +} + +static sapi_module_struct nsapi_sapi_module = { + "nsapi", /* name */ + "NSAPI", /* pretty name */ + + php_nsapi_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_nsapi_ub_write, /* unbuffered write */ + sapi_nsapi_flush, /* flush */ + sapi_nsapi_get_stat, /* get uid/stat */ + NULL, /* getenv */ + + php_error, /* error handler */ + + sapi_nsapi_header_handler, /* header handler */ + sapi_nsapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + sapi_nsapi_read_post, /* read POST data */ + sapi_nsapi_read_cookies, /* read Cookies */ + + sapi_nsapi_register_server_variables, /* register server variables */ + nsapi_log_message, /* Log message */ + sapi_nsapi_get_request_time, /* Get request time */ + NULL, /* Child terminate */ + + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static void nsapi_php_ini_entries(NSLS_D TSRMLS_DC) +{ + struct pb_entry *entry; + register int i,j,ok; + + for (i=0; i < NSG(pb)->hsize; i++) { + entry=NSG(pb)->ht[i]; + while (entry) { + /* exclude standard entries given to "Service" which should not go into ini entries */ + ok=1; + for (j=0; nsapi_exclude_from_ini_entries[j]; j++) { + ok&=(strcasecmp(entry->param->name, nsapi_exclude_from_ini_entries[j])!=0); + } + + if (ok) { + /* change the ini entry */ + if (zend_alter_ini_entry(entry->param->name, strlen(entry->param->name)+1, + entry->param->value, strlen(entry->param->value), + PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE)==FAILURE) { + log_error(LOG_WARN, pblock_findval("fn", NSG(pb)), NSG(sn), NSG(rq), "Cannot change php.ini key \"%s\" to \"%s\"", entry->param->name, entry->param->value); + } + } + entry=entry->next; + } + } +} + +void NSAPI_PUBLIC php5_close(void *vparam) +{ + if (nsapi_sapi_module.shutdown) { + nsapi_sapi_module.shutdown(&nsapi_sapi_module); + } + + if (nsapi_sapi_module.php_ini_path_override) { + free(nsapi_sapi_module.php_ini_path_override); + } + +#ifdef PHP_WIN32 + if (nsapi_dll) { + free(nsapi_dll); + nsapi_dll = NULL; + } +#endif + + sapi_shutdown(); + tsrm_shutdown(); + + log_error(LOG_INFORM, "php5_close", NULL, NULL, "Shutdown PHP Module"); +} + +/********************************************************* +/ init SAF +/ +/ Init fn="php5_init" [php_ini="/path/to/php.ini"] [server_lib="ns-httpdXX.dll"] +/ Initialize the NSAPI module in magnus.conf +/ +/ php_ini: gives path to php.ini file +/ server_lib: (only Win32) gives name of DLL (without path) to look for +/ servact_* functions +/ +/*********************************************************/ +int NSAPI_PUBLIC php5_init(pblock *pb, Session *sn, Request *rq) +{ + php_core_globals *core_globals; + char *strval; + int threads=128; /* default for server */ + + /* fetch max threads from NSAPI and initialize TSRM with it */ + threads=conf_getglobals()->Vpool_maxthreads; + if (threads<1) { + threads=128; /* default for server */ + } + tsrm_startup(threads, 1, 0, NULL); + + core_globals = ts_resource(core_globals_id); + + /* look if php_ini parameter is given to php5_init */ + if (strval = pblock_findval("php_ini", pb)) { + nsapi_sapi_module.php_ini_path_override = strdup(strval); + } + +#ifdef PHP_WIN32 + /* look if server_lib parameter is given to php5_init + * (this disables the automatic search for the newest ns-httpdXX.dll) */ + if (strval = pblock_findval("server_lib", pb)) { + nsapi_dll = strdup(strval); + } +#endif + + /* start SAPI */ + sapi_startup(&nsapi_sapi_module); + nsapi_sapi_module.startup(&nsapi_sapi_module); + + daemon_atrestart(&php5_close, NULL); + + log_error(LOG_INFORM, pblock_findval("fn", pb), sn, rq, "Initialized PHP Module (%d threads expected)", threads); + return REQ_PROCEED; +} + +/********************************************************* +/ normal use in Service directive: +/ +/ Service fn="php5_execute" type=... method=... [inikey=inivalue inikey=inivalue...] +/ +/ use in Service for a directory to supply a php-made directory listing instead of server default: +/ +/ Service fn="php5_execute" type="magnus-internal/directory" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...] +/ +/ use in Error SAF to display php script as error page: +/ +/ Error fn="php5_execute" code=XXX script="/path/to/script.php" [inikey=inivalue inikey=inivalue...] +/ Error fn="php5_execute" reason="Reason" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...] +/ +/*********************************************************/ +int NSAPI_PUBLIC php5_execute(pblock *pb, Session *sn, Request *rq) +{ + int retval; + nsapi_request_context *request_context; + zend_file_handle file_handle = {0}; + struct stat *fst; + + char *path_info; + char *query_string = pblock_findval("query", rq->reqpb); + char *uri = pblock_findval("uri", rq->reqpb); + char *request_method = pblock_findval("method", rq->reqpb); + char *content_type = pblock_findval("content-type", rq->headers); + char *content_length = pblock_findval("content-length", rq->headers); + char *directive = pblock_findval("Directive", pb); + int error_directive = (directive && !strcasecmp(directive, "error")); + int fixed_script = 1; + + /* try to use script parameter -> Error or Service for directory listing */ + char *path_translated = pblock_findval("script", pb); + + TSRMLS_FETCH(); + + /* if script parameter is missing: normal use as Service SAF */ + if (!path_translated) { + path_translated = pblock_findval("path", rq->vars); + path_info = pblock_findval("path-info", rq->vars); + fixed_script = 0; + if (error_directive) { + /* go to next error directive if script parameter is missing */ + log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Missing 'script' parameter"); + return REQ_NOACTION; + } + } else { + /* in error the path_info is the uri to the requested page */ + path_info = pblock_findval("uri", rq->reqpb); + } + + /* check if this uri was included in an other PHP script with nsapi_virtual() + by looking for a request context in the current thread */ + if (SG(server_context)) { + /* send 500 internal server error */ + log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot make nesting PHP requests with nsapi_virtual()"); + if (error_directive) { + return REQ_NOACTION; + } else { + protocol_status(sn, rq, 500, NULL); + return REQ_ABORTED; + } + } + + request_context = (nsapi_request_context *)pool_malloc(sn->pool, sizeof(nsapi_request_context)); + if (!request_context) { + log_error(LOG_CATASTROPHE, pblock_findval("fn", pb), sn, rq, "Insufficient memory to process PHP request!"); + return REQ_ABORTED; + } + request_context->pb = pb; + request_context->sn = sn; + request_context->rq = rq; + request_context->read_post_bytes = 0; + request_context->fixed_script = fixed_script; + request_context->http_error = (error_directive) ? rq->status_num : 0; + request_context->path_info = path_info; + + SG(server_context) = request_context; + SG(request_info).query_string = query_string; + SG(request_info).request_uri = uri; + SG(request_info).request_method = request_method; + SG(request_info).path_translated = path_translated; + SG(request_info).content_type = content_type; + SG(request_info).content_length = (content_length == NULL) ? 0 : strtoul(content_length, 0, 0); + SG(sapi_headers).http_response_code = (error_directive) ? rq->status_num : 200; + + nsapi_php_ini_entries(NSLS_C TSRMLS_CC); + + php_handle_auth_data(pblock_findval("authorization", rq->headers) TSRMLS_CC); + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + fst = request_stat_path(SG(request_info).path_translated, rq); + if (fst && S_ISREG(fst->st_mode)) { + if (php_request_startup(TSRMLS_C) == SUCCESS) { + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + retval=REQ_PROCEED; + } else { + /* send 500 internal server error */ + log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot prepare PHP engine!"); + if (error_directive) { + retval=REQ_NOACTION; + } else { + protocol_status(sn, rq, 500, NULL); + retval=REQ_ABORTED; + } + } + } else { + /* send 404 because file not found */ + log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot execute PHP script: %s (File not found)", SG(request_info).path_translated); + if (error_directive) { + retval=REQ_NOACTION; + } else { + protocol_status(sn, rq, 404, NULL); + retval=REQ_ABORTED; + } + } + + pool_free(sn->pool, request_context); + SG(server_context) = NULL; + + return retval; +} + +/********************************************************* +/ authentication +/ +/ we have to make a 'fake' authenticator for netscape so it +/ will pass authentication through to php, and allow us to +/ check authentication with our scripts. +/ +/ php5_auth_trans +/ main function called from netscape server to authenticate +/ a line in obj.conf: +/ funcs=php5_auth_trans shlib="path/to/this/phpnsapi.dll" +/ and: +/ <Object ppath="path/to/be/authenticated/by/php/*"> +/ AuthTrans fn="php5_auth_trans" +/*********************************************************/ +int NSAPI_PUBLIC php5_auth_trans(pblock * pb, Session * sn, Request * rq) +{ + /* This is a DO NOTHING function that allows authentication + * information + * to be passed through to PHP scripts. + */ + return REQ_PROCEED; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/nsapi/php5nsapi.dsp b/sapi/nsapi/php5nsapi.dsp new file mode 100644 index 0000000..6cd0079 --- /dev/null +++ b/sapi/nsapi/php5nsapi.dsp @@ -0,0 +1,135 @@ +# Microsoft Developer Studio Project File - Name="php5nsapi" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5nsapi - 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 "php5nsapi.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 "php5nsapi.mak" CFG="php5nsapi - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5nsapi - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5nsapi - Win32 Release_TS_inline" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5nsapi - Win32 Debug_TS" (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)" == "php5nsapi - Win32 Release_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "php5nsapi___Win32_Release_TS"
+# PROP BASE Intermediate_Dir "php5nsapi___Win32_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 "php5nsapi_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D ZEND_DEBUG=0 /D "NDEBUG" /D "XP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "php5nsapi_EXPORTS" /D "WIN32" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 ns-httpd30.lib php5ts.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 /base:"0x62000000" /version:4.0 /dll /machine:I386 /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Release_TS" /libpath:"..\..\TSRM\Release_TS" /libpath:"..\..\Zend\Release_TS"
+
+!ELSEIF "$(CFG)" == "php5nsapi - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "php5nsapi___Win32_Release_TS_inline"
+# PROP BASE Intermediate_Dir "php5nsapi___Win32_Release_TS_inline"
+# 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 /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "php5nsapi_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "NDEBUG" /D "XP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "php5nsapi_EXPORTS" /D "WIN32" /D "_MBCS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 ns-httpd30.lib php5ts.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 /base:"0x62000000" /version:4.0 /dll /machine:I386 /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\TSRM\Release_TS_inline" /libpath:"..\..\Zend\Release_TS_inline"
+
+!ELSEIF "$(CFG)" == "php5nsapi - Win32 Debug_TS"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "php5nsapi___Win32_Debug_TS"
+# PROP BASE Intermediate_Dir "php5nsapi___Win32_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 "php5nsapi_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "." /I "..\..\..\php_build\nsapi30\include\\" /I "..\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\..\bindlib_w32" /I "..\..\main" /I "..\..\tsrm" /D "_Debug_TS" /D ZEND_DEBUG=1 /D "_DEBUG" /D "XP_WIN32" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "_WINDOWS" /D "_USRDLL" /D "php5nsapi_EXPORTS" /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 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /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 ns-httpd30.lib php5ts_debug.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 /base:"0x62000000" /version:4.0 /dll /debug /machine:I386 /pdbtype:sept /libpath:"..\..\..\php_build\nsapi30\lib\\" /libpath:"..\..\Debug_TS" /libpath:"..\..\TSRM\Debug_TS" /libpath:"..\..\Zend\Debug_TS"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5nsapi - Win32 Release_TS"
+# Name "php5nsapi - Win32 Release_TS_inline"
+# Name "php5nsapi - Win32 Debug_TS"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\nsapi.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/sapi/phttpd/CREDITS b/sapi/phttpd/CREDITS new file mode 100644 index 0000000..134cc54 --- /dev/null +++ b/sapi/phttpd/CREDITS @@ -0,0 +1,2 @@ +phttpd +Thies C. Arntzen diff --git a/sapi/phttpd/README b/sapi/phttpd/README new file mode 100644 index 0000000..cdb6f7c --- /dev/null +++ b/sapi/phttpd/README @@ -0,0 +1,5 @@ +phttpd sapi module. + +THIS IS BY NO MEANS COMPLETE NOR USABLE RIGHT NOW! + +thies@thieso.net 03.01.2000 diff --git a/sapi/phttpd/config.m4 b/sapi/phttpd/config.m4 new file mode 100644 index 0000000..91339a5 --- /dev/null +++ b/sapi/phttpd/config.m4 @@ -0,0 +1,21 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(phttpd, for PHTTPD support, +[ --with-phttpd=DIR Build PHP as phttpd module], no, no) + +if test "$PHP_PHTTPD" != "no"; then + if test ! -d $PHP_PHTTPD ; then + AC_MSG_ERROR([You did not specify a directory]) + fi + PHP_BUILD_THREAD_SAFE + PHP_ADD_INCLUDE($PHP_PHTTPD/include) + AC_DEFINE(HAVE_PHTTPD, 1, [Whether you have phttpd]) + PHP_SELECT_SAPI(phttpd, shared, phttpd.c) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$PHP_PHTTPD/modules/" +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/phttpd/php.sym b/sapi/phttpd/php.sym new file mode 100644 index 0000000..f10b883 --- /dev/null +++ b/sapi/phttpd/php.sym @@ -0,0 +1,4 @@ +pm_init +pm_exit +pm_request + diff --git a/sapi/phttpd/php_phttpd.h b/sapi/phttpd/php_phttpd.h new file mode 100644 index 0000000..49089cf --- /dev/null +++ b/sapi/phttpd/php_phttpd.h @@ -0,0 +1,24 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Thies C. Arntzen <thies@thieso.net> | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PHTTPD_H +#define PHP_PHTTPD_H + +#include <phttpd.h> + +#endif diff --git a/sapi/phttpd/phttpd.c b/sapi/phttpd/phttpd.c new file mode 100644 index 0000000..c2e9172 --- /dev/null +++ b/sapi/phttpd/phttpd.c @@ -0,0 +1,301 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Thies C. Arntzen <thies@thieso.net> | + | Based on aolserver SAPI by Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" + +#ifdef HAVE_PHTTPD + +#include "ext/standard/info.h" + +#ifndef ZTS +#error PHTTPD module is only useable in thread-safe mode +#endif + +#include "php_phttpd.h" + +typedef struct { + struct connectioninfo *cip; + struct stat sb; +} phttpd_globals_struct; + +static int ph_globals_id; + +#define PHG(v) TSRMG(ph_globals_id, phttpd_globals_struct *, v) + +static int +php_phttpd_startup(sapi_module_struct *sapi_module) +{ + fprintf(stderr,"***php_phttpd_startup\n"); + + if (php_module_startup(sapi_module, NULL, 0)) { + return FAILURE; + } else { + return SUCCESS; + } +} + +static int +php_phttpd_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int sent_bytes; + + sent_bytes = fd_write(PHG(cip)->fd, str, str_length); + + if (sent_bytes == -1) { + php_handle_aborted_connection(); + } + + return sent_bytes; +} + +static int +php_phttpd_sapi_header_handler(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content; + char *p; + + http_sendheaders(PHG(cip)->fd, PHG(cip), SG(sapi_headers).http_response_code, NULL); + + header_name = sapi_header->header; + header_content = p = strchr(header_name, ':'); + + if (p) { + *p = '\0'; + do { + header_content++; + } while (*header_content == ' '); + + fd_printf(PHG(cip)->fd,"%s: %s\n", header_name, header_content); + + *p = ':'; + } + + sapi_free_header(sapi_header); + + return 0; +} + +static int +php_phttpd_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + if (SG(sapi_headers).send_default_content_type) { + fd_printf(PHG(cip)->fd,"Content-Type: text/html\n"); + } + + fd_putc('\n', PHG(cip)->fd); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static char * +php_phttpd_sapi_read_cookies(TSRMLS_D) +{ + +/* + int i; + char *http_cookie = NULL; + + i = Ns_SetIFind(NSG(conn->headers), "cookie"); + if(i != -1) { + http_cookie = Ns_SetValue(NSG(conn->headers), i); + } + + return http_cookie; +*/ + fprintf(stderr,"***php_phttpd_sapi_read_cookies\n"); + + return 0; +} + +static int +php_phttpd_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ +/* + uint max_read; + uint total_read = 0; + + max_read = MIN(NSG(data_avail), count_bytes); + + total_read = Ns_ConnRead(NSG(conn), buf, max_read); + + if(total_read == NS_ERROR) { + total_read = -1; + } else { + NSG(data_avail) -= total_read; + } + + return total_read; +*/ + fprintf(stderr,"***php_phttpd_sapi_read_post\n"); + return 0; +} + +static sapi_module_struct phttpd_sapi_module = { + "phttpd", + "PHTTPD", + + php_phttpd_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + php_phttpd_sapi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + php_phttpd_sapi_header_handler, /* header handler */ + php_phttpd_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + php_phttpd_sapi_read_post, /* read POST data */ + php_phttpd_sapi_read_cookies, /* read Cookies */ + + NULL, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static void +php_phttpd_request_ctor(TSRMLS_D TSRMLS_DC) +{ + memset(&SG(request_info), 0, sizeof(sapi_globals_struct)); /* pfusch! */ + + SG(request_info).query_string = PHG(cip)->hip->request; + SG(request_info).request_method = PHG(cip)->hip->method; + SG(request_info).path_translated = malloc(MAXPATHLEN); + SG(sapi_headers).http_response_code = 200; + if (url_expand(PHG(cip)->hip->url, SG(request_info).path_translated, MAXPATHLEN, &PHG(sb), NULL, NULL) == NULL) { + /* handle error */ + } + +#if 0 + char *server; + Ns_DString ds; + char *root; + int index; + char *tmp; + + server = Ns_ConnServer(NSG(conn)); + + Ns_DStringInit(&ds); + Ns_UrlToFile(&ds, server, NSG(conn->request->url)); + + /* path_translated is the absolute path to the file */ + SG(request_info).path_translated = strdup(Ns_DStringValue(&ds)); + Ns_DStringFree(&ds); + root = Ns_PageRoot(server); + SG(request_info).request_uri = SG(request_info).path_translated + strlen(root); + SG(request_info).content_length = Ns_ConnContentLength(NSG(conn)); + index = Ns_SetIFind(NSG(conn)->headers, "content-type"); + SG(request_info).content_type = index == -1 ? NULL : + Ns_SetValue(NSG(conn)->headers, index); + + tmp = Ns_ConnAuthUser(NSG(conn)); + if(tmp) { + tmp = estrdup(tmp); + } + SG(request_info).auth_user = tmp; + + tmp = Ns_ConnAuthPasswd(NSG(conn)); + if(tmp) { + tmp = estrdup(tmp); + } + SG(request_info).auth_password = tmp; + + NSG(data_avail) = SG(request_info).content_length; +#endif +} + +static void +php_phttpd_request_dtor(TSRMLS_D TSRMLS_DC) +{ + free(SG(request_info).path_translated); +} + + +int php_doit(TSRMLS_D) +{ + struct stat sb; + zend_file_handle file_handle; + struct httpinfo *hip = PHG(cip)->hip; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return -1; + } + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + +/* + php_phttpd_hash_environment(TSRMLS_C); +*/ + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + + return SG(sapi_headers).http_response_code; +} + +int pm_init(const char **argv) +{ + tsrm_startup(1, 1, 0, NULL); + sapi_startup(&phttpd_sapi_module); + phttpd_sapi_module.startup(&phttpd_sapi_module); + + ts_allocate_id(&ph_globals_id, sizeof(phttpd_globals_struct), NULL, NULL); + + return 0; +} + +void pm_exit(void) +{ + fprintf(stderr,"***pm_exit\n"); +} + +int pm_request(struct connectioninfo *cip) +{ + struct httpinfo *hip = cip->hip; + int status; + TSRMLS_FETCH(); + + if (strcasecmp(hip->method, "GET") == 0 || + strcasecmp(hip->method, "HEAD") == 0 || + strcasecmp(hip->method, "POST") == 0) { + PHG(cip) = cip; + + php_phttpd_request_ctor(TSRMLS_C); + status = php_doit(TSRMLS_C); + php_phttpd_request_dtor(TSRMLS_C); + + return status; + } else { + return -2; + } +} + +#endif diff --git a/sapi/pi3web/CREDITS b/sapi/pi3web/CREDITS new file mode 100644 index 0000000..c4541f8 --- /dev/null +++ b/sapi/pi3web/CREDITS @@ -0,0 +1,2 @@ +pi3web +Holger Zimmermann diff --git a/sapi/pi3web/README b/sapi/pi3web/README new file mode 100644 index 0000000..e3e523e --- /dev/null +++ b/sapi/pi3web/README @@ -0,0 +1,50 @@ +PHP5 Module +========== +This module requires PHP5 as thread safe shared library. Have a look +into the INSTALL file which accompanies that distribution. + +If you distribute this software bundled with the PHP software in source +or binary form, then you must adhere to the PHP copyright conditions - +the terms are reasonable. + +You should have checked out and built the PHP5 source package from the +PHP CVS tree into the Pi3Web source directory called 'PHP5' first. Then +build PHP5 as Pi3Web module and after that build the Pi3Web PHP5 wrapper: + +1. Checkout PHP5 +================ +cvs -d :pserver:cvsread@cvs.php.net:/repository login +The required password is phpfi + +cvs -z3 -d :pserver:cvsread@cvs.php.net:/repository co php5 + +You must also checkout the TSRM and the ZEND module from the ZEND cvs tree +into the PHP5 root directory + +cvs -d :pserver:cvsread@cvs.zend.com:/repository login +The required password is zend + +cvs -z3 -d :pserver:cvsread@cvs.zend.com:/repository co Zend TSRM + +2. Build PHP5 +============= +2.1 POSIX +--------- +cd ./php5 +./buildconf +./configure --with-pi3web +make + +2.2 Win32 +--------- +other required downloads from the php website + - bison 1.25 + - bindlib32 + - number4.tar.gz +nmake php5dllts.mak + +3. Build Pi3Web PHP5 wrapper +============================ +Run make in the Pi3Web /Source/PHP5 directory. + +For further information refer to http://www.php.net/version4/ diff --git a/sapi/pi3web/config.m4 b/sapi/pi3web/config.m4 new file mode 100644 index 0000000..7859481 --- /dev/null +++ b/sapi/pi3web/config.m4 @@ -0,0 +1,27 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(pi3web, for Pi3Web support, +[ --with-pi3web[=DIR] Build PHP as Pi3Web module], no, no) + +if test "$PHP_PI3WEB" != "no"; then + if test "$PHP_PI3WEB" = "yes"; then + PI3PATH=../.. # the default + else + PI3PATH=$PHP_PI3WEB + fi + test -f "$PI3PATH/PiAPI/PiAPI.h" || AC_MSG_ERROR([Unable to find PiAPI.h in $PI3PATH/PiAPI]) + PHP_BUILD_THREAD_SAFE + AC_DEFINE(WITH_PI3WEB, 1, [whether you want Pi3Web support]) + PHP_ADD_INCLUDE($PI3PATH/PiAPI) + PHP_ADD_INCLUDE($PI3PATH/Pi2API) + PHP_ADD_INCLUDE($PI3PATH/Pi3API) + PHP_ADD_INCLUDE($PI3PATH/PHP5) + PHP_SELECT_SAPI(pi3web, shared, pi3web_sapi.c) + INSTALL_IT="\$(SHELL) \$(srcdir)/install-sh -m 0755 $SAPI_SHARED \$(INSTALL_ROOT)$PI3PATH/bin/" +fi + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/pi3web/config.w32 b/sapi/pi3web/config.w32 new file mode 100644 index 0000000..a5393e6 --- /dev/null +++ b/sapi/pi3web/config.w32 @@ -0,0 +1,16 @@ +// vim:ft=javascript +// $Id$ + +ARG_WITH('pi3web', 'Pi3Web', 'no'); + +if (PHP_PI3WEB != "no") { + if (CHECK_HEADER_ADD_INCLUDE('PiAPI.h', 'CFLAGS_PI3WEB', PHP_PHP_BUILD + "\\Pi3Web\\include;" + PHP_PI3WEB) && + CHECK_LIB('piapi.lib', 'pi3web', PHP_PHP_BUILD + "\\Pi3Web\\lib;" + PHP_PI3WEB) && + CHECK_LIB('pi2api.lib', 'pi3web', PHP_PHP_BUILD + "\\Pi3Web\\lib;" + PHP_PI3WEB) && + CHECK_LIB('pi3api.lib', 'pi3web', PHP_PHP_BUILD + "\\Pi3Web\\lib;" + PHP_PI3WEB)) { + SAPI('pi3web', 'pi3web_sapi.c', 'php' + PHP_VERSION + 'pi3web.dll', '/D PHP5PI3WEB_EXPORTS'); + AC_DEFINE('WITH_PI3WEB', 1); + } else { + WARNING('Pi3Web not enabled; headers/libraries not found'); + } +} diff --git a/sapi/pi3web/php.sym b/sapi/pi3web/php.sym new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sapi/pi3web/php.sym diff --git a/sapi/pi3web/php5pi3web.dsp b/sapi/pi3web/php5pi3web.dsp new file mode 100644 index 0000000..bb5a248 --- /dev/null +++ b/sapi/pi3web/php5pi3web.dsp @@ -0,0 +1,136 @@ +# Microsoft Developer Studio Project File - Name="php5pi3web" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=php5pi3web - 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 "php5pi3web.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 "php5pi3web.mak" CFG="php5pi3web - Win32 Debug_TS"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "php5pi3web - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5pi3web - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "php5pi3web - Win32 Release_TS_inline" (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)" == "php5pi3web - 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 /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "php5pi3web_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /GX /ZI /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /I "..\..\..\..\PIAPI" /I "..\..\..\..\PI2API" /I "..\..\..\..\PI3API" /D "_DEBUG" /D ZEND_DEBUG=1 /D "_WINDOWS" /D "_USRDLL" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /D "PHP5PI3WEB_EXPORTS" /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 php5ts_debug.lib 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 PiAPI.lib Pi2API.lib Pi3API.lib /nologo /version:4.0 /dll /debug /machine:I386 /nodefaultlib:"libcmt" /nodefaultlib:"libc" /pdbtype:sept /libpath:"..\..\Debug_TS" /libpath:"..\..\..\..\PIAPI" /libpath:"..\..\..\..\PI2API" /libpath:"..\..\..\..\PI3API"
+
+!ELSEIF "$(CFG)" == "php5pi3web - 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 "php5pi3web_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /I "..\..\..\..\PIAPI" /I "..\..\..\..\PI2API" /I "..\..\..\..\PI3API" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /D "PHP5PI3WEB_EXPORTS" /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 0x409 /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 php5ts.lib 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 PiAPI.lib Pi2API.lib Pi3API.lib /nologo /version:4.0 /dll /machine:I386 /nodefaultlib:"libc.lib" /nodefaultlib:"libcmt.lib" /libpath:"..\..\Release_TS" /libpath:"..\..\..\..\PIAPI" /libpath:"..\..\..\..\PI2API" /libpath:"..\..\..\..\PI3API"
+
+!ELSEIF "$(CFG)" == "php5pi3web - Win32 Release_TS_inline"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "php5pi3web___Win32_Release_TS_inline"
+# PROP BASE Intermediate_Dir "php5pi3web___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 ".." /I "..\main" /I "..\regex" /I "..\..\bindlib_w32" /I "..\Zend" /I "..\TSRM" /I "..\ext\mysql\libmysql" /I "..\..\..\PiAPI" /I "..\..\..\Pi2API" /I "..\..\..\Pi3API" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP5DLLTS_EXPORTS" /D "PHP_EXPORTS" /D "LIBZEND_EXPORTS" /D "TSRM_EXPORTS" /D "SAPI_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 /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /I "..\..\..\..\PIAPI" /I "..\..\..\..\PI2API" /I "..\..\..\..\PI3API" /D "NDEBUG" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "_WINDOWS" /D "_USRDLL" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /D "PHP5PI3WEB_EXPORTS" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /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 ZendTS.lib TSRM.lib resolv.lib libmysql.lib PiAPI.lib Pi2API.lib Pi3API.lib /nologo /version:4.0 /dll /machine:I386 /nodefaultlib:"libc.lib" /nodefaultlib:"libcmt.lib" /out:"..\Release_TS\php5ts.dll" /libpath:"..\TSRM\Release_TS" /libpath:"..\Zend\Release_TS" /libpath:"..\..\bindlib_w32\Release" /libpath:"..\ext\mysql\libmysql\Release_TS" /libpath:"Release_TS" /libpath:"..\..\..\PiAPI" /libpath:"..\..\..\Pi2API" /libpath:"..\..\..\Pi3API"
+# ADD LINK32 php5ts.lib 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 PiAPI.lib Pi2API.lib Pi3API.lib /nologo /version:4.0 /dll /machine:I386 /nodefaultlib:"libc.lib" /nodefaultlib:"libcmt.lib" /libpath:"..\..\Release_TS_inline" /libpath:"..\..\..\..\PIAPI" /libpath:"..\..\..\..\PI2API" /libpath:"..\..\..\..\PI3API"
+
+!ENDIF
+
+# Begin Target
+
+# Name "php5pi3web - Win32 Debug_TS"
+# Name "php5pi3web - Win32 Release_TS"
+# Name "php5pi3web - Win32 Release_TS_inline"
+# Begin Group "Source Files"
+
+# PROP Default_Filter ".c"
+# Begin Source File
+
+SOURCE=.\pi3web_sapi.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter ".h"
+# Begin Source File
+
+SOURCE=.\pi3web_sapi.h
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/sapi/pi3web/pi3web_sapi.c b/sapi/pi3web/pi3web_sapi.c new file mode 100644 index 0000000..64eb2a6 --- /dev/null +++ b/sapi/pi3web/pi3web_sapi.c @@ -0,0 +1,439 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Pi3Web version 2.0 | + +----------------------------------------------------------------------+ + | This file is committed by the Pi3 development group. | + | (pi3web.sourceforge.net) | + | | + | Author: Holger Zimmermann (zimpel@users.sourceforge.net) | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS + +#include "php.h" +#include "php_main.h" +#include "php_variables.h" +#include "SAPI.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "zend_highlight.h" +#include "zend_indent.h" +#include "zend_alloc.h" +#include "ext/standard/basic_functions.h" +#include "TSRM/TSRM.h" +#include "PiAPI.h" +#include "Pi3API.h" + +#include "pi3web_sapi.h" + +#define PI3WEB_SERVER_VAR_BUF_SIZE 1024 + +int IWasLoaded=0; + + +static void php_info_pi3web(ZEND_MODULE_INFO_FUNC_ARGS) +{ + char variable_buf[PI3WEB_SERVER_VAR_BUF_SIZE]; + DWORD variable_len; + LPCONTROL_BLOCK lpCB = (LPCONTROL_BLOCK) SG(server_context); + PIDB *pDB = (PIDB *)lpCB->GetVariableNames(lpCB->ConnID); + PIDBIterator *pIter = PIDB_getIterator( pDB, PIDBTYPE_STRING, 0, 0 ); + + PUTS("<table border=0 cellpadding=3 cellspacing=1 width=600 align=center>\n"); + PUTS("<tr><th colspan=2 bgcolor=\"" PHP_HEADER_COLOR "\">Pi3Web Server Information</th></tr>\n"); + php_info_print_table_header(2, "Information Field", "Value"); + php_info_print_table_row(2, "Pi3Web SAPI module version", "$Id$"); + php_info_print_table_row(2, "Server Name Stamp", HTTPCore_getServerStamp()); + snprintf(variable_buf, 511, "%d", HTTPCore_debugEnabled()); + php_info_print_table_row(2, "Debug Enabled", variable_buf); + PIPlatform_getCurrentDirectory( variable_buf, PI3WEB_SERVER_VAR_BUF_SIZE); + php_info_print_table_row(2, "Current Path", variable_buf); + if (lpCB->GetServerVariable(lpCB->ConnID, "SERVER_NAME", variable_buf, &variable_len) + && variable_buf[0]) { + php_info_print_table_row(2, "Main Virtual Hostname", variable_buf); + }; + snprintf(variable_buf, 511, "%d", PIPlatform_getProcessId()); + php_info_print_table_row(2, "Server PID", variable_buf); + php_info_print_table_row(2, "Server Platform", PIPlatform_getDescription()); + + PUTS("</table><br />"); + + PUTS("<table border=0 cellpadding=3 cellspacing=1 width=600 align=center>\n"); + PUTS("<tr><th colspan=2 bgcolor=\"" PHP_HEADER_COLOR "\">HTTP Request Information</th></tr>\n"); + php_info_print_table_row(2, "HTTP Request Line", lpCB->lpszReq); + PUTS("<tr><th colspan=2 bgcolor=\"" PHP_HEADER_COLOR "\">HTTP Headers</th></tr>\n"); + php_info_print_table_header(2, "Server Variable", "Value"); + + /* --- loop over all registered server variables --- */ + for(; pIter && PIDBIterator_atValidElement( pIter ); PIDBIterator_next( pIter ) ) + { + PCHAR pKey; + PIDBIterator_current( pIter, &pKey ); + if ( !pKey ) { /* sanity */ continue; }; + + variable_len = PI3WEB_SERVER_VAR_BUF_SIZE; + if (lpCB->GetServerVariable(lpCB->ConnID, pKey, variable_buf, &variable_len) + && variable_buf[0]) { + php_info_print_table_row(2, pKey, variable_buf); + } else if (PIPlatform_getLastError() == PIAPI_EINVAL) { + char *tmp_variable_buf; + + tmp_variable_buf = (char *) emalloc(variable_len); + if (lpCB->GetServerVariable(lpCB->ConnID, pKey, tmp_variable_buf, &variable_len) + && variable_buf[0]) { + php_info_print_table_row(2, pKey, tmp_variable_buf); + } + efree(tmp_variable_buf); + } + } + + PUTS("</table>"); +} + + +static zend_module_entry php_pi3web_module = { + STANDARD_MODULE_HEADER, + "PI3WEB", + NULL, + NULL, + NULL, + NULL, + NULL, + php_info_pi3web, + NULL, + STANDARD_MODULE_PROPERTIES +}; + + +static int zend_pi3web_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + DWORD num_bytes = str_length; + LPCONTROL_BLOCK cb; + + cb = (LPCONTROL_BLOCK) SG(server_context); + + if ( !IWasLoaded ) return 0; + cb->WriteClient(cb->ConnID, (char *) str, &num_bytes, 0 ); + + if (num_bytes != str_length) + php_handle_aborted_connection(); + return num_bytes; +} + + +static int sapi_pi3web_header_handler(sapi_header_struct *sapi_header, 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_pi3web_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; + LPCONTROL_BLOCK lpCB = (LPCONTROL_BLOCK) SG(server_context); + sapi_header_struct default_content_type; + + if ( !IWasLoaded ) return SAPI_HEADER_SENT_SUCCESSFULLY; + + + 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; + + lpCB->dwHttpStatusCode = SG(sapi_headers).http_response_code; + lpCB->SendHeaderFunction(lpCB->ConnID, &total_length, (LPDWORD) combined_headers); + + efree(combined_headers); + if (SG(sapi_headers).http_status_line) { + efree(SG(sapi_headers).http_status_line); + SG(sapi_headers).http_status_line = 0; + } + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + + +static int php_pi3web_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, &php_pi3web_module, 1)==FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + + +static int sapi_pi3web_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + LPCONTROL_BLOCK lpCB = (LPCONTROL_BLOCK) SG(server_context); + DWORD read_from_buf=0; + DWORD read_from_input=0; + DWORD total_read=0; + + if ((DWORD)SG(read_post_bytes) < lpCB->cbAvailable) { + read_from_buf = MIN(lpCB->cbAvailable-SG(read_post_bytes), count_bytes); + memcpy(buffer, lpCB->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) < lpCB->cbTotalBytes) { + DWORD cbRead=0, cbSize; + + read_from_input = MIN(count_bytes-read_from_buf, lpCB->cbTotalBytes-SG(read_post_bytes)-read_from_buf); + while (cbRead < read_from_input) { + cbSize = read_from_input - cbRead; + if (!lpCB->ReadClient(lpCB->ConnID, buffer+read_from_buf+cbRead, &cbSize) || cbSize==0) { + break; + } + cbRead += cbSize; + } + total_read += cbRead; + } + + /* removed after re-testing POST with Pi3Web 2.0.2 */ + /* SG(read_post_bytes) += total_read; */ + return total_read; +} + + +static char *sapi_pi3web_read_cookies(TSRMLS_D) +{ + LPCONTROL_BLOCK lpCB = (LPCONTROL_BLOCK) SG(server_context); + char variable_buf[PI3WEB_SERVER_VAR_BUF_SIZE]; + DWORD variable_len = PI3WEB_SERVER_VAR_BUF_SIZE; + + if (lpCB->GetServerVariable(lpCB->ConnID, "HTTP_COOKIE", variable_buf, &variable_len)) { + return estrndup(variable_buf, variable_len); + } else if (PIPlatform_getLastError()==PIAPI_EINVAL) { + char *tmp_variable_buf = (char *) emalloc(variable_len+1); + + if (lpCB->GetServerVariable(lpCB->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 init_request_info(LPCONTROL_BLOCK lpCB TSRMLS_DC) +{ + SG(server_context) = lpCB; + SG(request_info).request_method = lpCB->lpszMethod; + SG(request_info).query_string = lpCB->lpszQueryString; + SG(request_info).path_translated = lpCB->lpszPathTranslated; + SG(request_info).request_uri = lpCB->lpszUri; + SG(request_info).content_type = lpCB->lpszContentType; + SG(request_info).content_length = lpCB->cbTotalBytes; + SG(request_info).auth_user = (lpCB->lpszUser) ? (char *)estrdup((const char *)(lpCB->lpszUser)) : 0; + SG(request_info).auth_password = (lpCB->lpszPassword) ? (char *)estrdup((const char *)(lpCB->lpszPassword)) : 0; + SG(sapi_headers).http_response_code = 200; +} + +static void sapi_pi3web_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char static_variable_buf[PI3WEB_SERVER_VAR_BUF_SIZE]; + char *variable_buf; + DWORD variable_len = PI3WEB_SERVER_VAR_BUF_SIZE; + LPCONTROL_BLOCK lpCB = (LPCONTROL_BLOCK) SG(server_context); + PIDB *pDB = (PIDB *)lpCB->GetVariableNames(lpCB->ConnID); + PIDBIterator *pIter = PIDB_getIterator( pDB, PIDBTYPE_STRING, 0, 0 ); + + /* --- loop over all registered server variables --- */ + for(; pIter && PIDBIterator_atValidElement( pIter ); PIDBIterator_next( pIter ) ) + { + PCHAR pKey; + PIDBIterator_current( pIter, &pKey ); + if ( !pKey ) { /* sanity */ continue; }; + + variable_len = PI3WEB_SERVER_VAR_BUF_SIZE; + if (lpCB->GetServerVariable(lpCB->ConnID, pKey, static_variable_buf, &variable_len) + && (variable_len > 1)) { + php_register_variable(pKey, static_variable_buf, track_vars_array TSRMLS_CC); + } else if (PIPlatform_getLastError()==PIAPI_EINVAL) { + variable_buf = (char *) emalloc(variable_len); + if (lpCB->GetServerVariable(lpCB->ConnID, pKey, variable_buf, &variable_len)) { + php_register_variable(pKey, variable_buf, track_vars_array TSRMLS_CC); + } + efree(variable_buf); + } + + } + + + /* PHP_SELF support */ + variable_len = PI3WEB_SERVER_VAR_BUF_SIZE; + if (lpCB->GetServerVariable(lpCB->ConnID, "SCRIPT_NAME", static_variable_buf, &variable_len) + && (variable_len > 1)) { + php_register_variable("PHP_SELF", static_variable_buf, track_vars_array TSRMLS_CC); + } +} + +static sapi_module_struct pi3web_sapi_module = { + "pi3web", /* name */ + "PI3WEB", /* pretty name */ + + php_pi3web_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + NULL, /* activate */ + NULL, /* deactivate */ + zend_pi3web_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + php_error, /* error handler */ + sapi_pi3web_header_handler, /* header handler */ + sapi_pi3web_send_headers, /* send headers handler */ + NULL, /* send header handler */ + sapi_pi3web_read_post, /* read POST data */ + sapi_pi3web_read_cookies, /* read Cookies */ + sapi_pi3web_register_variables, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +MODULE_API DWORD PHP5_wrapper(LPCONTROL_BLOCK lpCB) +{ + zend_file_handle file_handle = {0}; + int iRet = PIAPI_COMPLETED; + TSRMLS_FETCH(); + + zend_first_try { + file_handle.filename = lpCB->lpszFileName; + file_handle.free_filename = 0; + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.opened_path = NULL; + + init_request_info(lpCB TSRMLS_CC); + php_request_startup(TSRMLS_C); + + switch ( lpCB->dwBehavior ) { + case PHP_MODE_STANDARD: + iRet = ( php_execute_script( &file_handle TSRMLS_CC ) ) ? + PIAPI_COMPLETED : PIAPI_ERROR; + break; + case PHP_MODE_HIGHLIGHT: { + zend_syntax_highlighter_ini syntax_highlighter_ini; + if ( open_file_for_scanning( &file_handle TSRMLS_CC ) == SUCCESS ) + { + php_get_highlight_struct( &syntax_highlighter_ini ); + zend_highlight( &syntax_highlighter_ini TSRMLS_CC ); + } + else + { + iRet = PIAPI_ERROR; + }; + }; + break; + case PHP_MODE_INDENT: { + sapi_header_line ctr = {0}; + + ctr.line = "Content-Type: text/plain"; + ctr.line_len = strlen(ctr.line); + + sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC); + } + if ( open_file_for_scanning( &file_handle TSRMLS_CC ) == SUCCESS ) + { + zend_indent(); + } + else + { + iRet = PIAPI_ERROR; + }; + break; + case PHP_MODE_LINT: + iRet = (php_lint_script(&file_handle TSRMLS_CC) == SUCCESS) ? + PIAPI_COMPLETED : PIAPI_ERROR; + break; + default: + iRet = PIAPI_ERROR;; + } + + if (SG(request_info).cookie_data) { + efree(SG(request_info).cookie_data); + }; + + php_request_shutdown(NULL); + } zend_catch { + iRet = PIAPI_ERROR; + } zend_end_try(); + return iRet; +} + +MODULE_API BOOL PHP5_startup() { + tsrm_startup(1, 1, 0, NULL); + sapi_startup(&pi3web_sapi_module); + if (pi3web_sapi_module.startup) { + pi3web_sapi_module.startup(&pi3web_sapi_module); + }; + IWasLoaded = 1; + return IWasLoaded; +}; + +MODULE_API BOOL PHP5_shutdown() { + if (pi3web_sapi_module.shutdown) { + pi3web_sapi_module.shutdown(&pi3web_sapi_module); + }; + sapi_shutdown(); + tsrm_shutdown(); + IWasLoaded = 0; + return !IWasLoaded; +}; + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/sapi/pi3web/pi3web_sapi.h b/sapi/pi3web/pi3web_sapi.h new file mode 100644 index 0000000..d229fec --- /dev/null +++ b/sapi/pi3web/pi3web_sapi.h @@ -0,0 +1,102 @@ +#ifndef _PI3WEB_SAPI_H_ +#define _PI3WEB_SAPI_H_ + +#ifdef PHP_WIN32 +# include <windows.h> +# ifdef PHP5PI3WEB_EXPORTS +# define MODULE_API __declspec(dllexport) +# else +# define MODULE_API __declspec(dllimport) +# endif +#else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define MODULE_API __attribute__ ((visibility("default"))) +# else +# define MODULE_API +# endif +# define far + + typedef int BOOL; + typedef void far *LPVOID; + typedef unsigned long DWORD; + typedef DWORD far *LPDWORD; + typedef char CHAR; + typedef CHAR *LPSTR; + typedef unsigned char BYTE; + typedef BYTE far *LPBYTE; +#endif + + typedef LPVOID HCONN; + +#ifdef __cplusplus +extern "C" { +#endif + +#define PHP_MODE_STANDARD 1 +#define PHP_MODE_HIGHLIGHT 2 +#define PHP_MODE_INDENT 3 +#define PHP_MODE_LINT 4 + +// +// passed to the procedure on a new request +// +typedef struct _CONTROL_BLOCK { + DWORD cbSize; // size of this struct. + HCONN ConnID; // Context number not to be modified! + DWORD dwHttpStatusCode; // HTTP Status code + CHAR lpszLogData[80]; // null terminated log info + + LPSTR lpszMethod; // REQUEST_METHOD + LPSTR lpszQueryString; // QUERY_STRING + LPSTR lpszPathInfo; // PATH_INFO + LPSTR lpszPathTranslated; // PATH_TRANSLATED + LPSTR lpszFileName; // FileName to PHP3 physical file + LPSTR lpszUri; // The request URI + LPSTR lpszReq; // The whole HTTP request line + LPSTR lpszUser; // The authenticated user + LPSTR lpszPassword; // The authenticated password + + DWORD cbTotalBytes; // Total bytes indicated from client + DWORD cbAvailable; // Available number of bytes + LPBYTE lpbData; // pointer to cbAvailable bytes + + LPSTR lpszContentType; // Content type of client data + DWORD dwBehavior; // PHP behavior (standard, highlight, intend + + + LPVOID (* GetVariableNames) (HCONN hConn); + + BOOL (* GetServerVariable) ( HCONN hConn, + LPSTR lpszVariableName, + LPVOID lpvBuffer, + LPDWORD lpdwSize ); + + BOOL (* WriteClient) ( HCONN hConn, + LPVOID lpvBuffer, + LPDWORD lpdwBytes, + DWORD dwReserved ); + + BOOL (* ReadClient) ( HCONN hConn, + LPVOID lpvBuffer, + LPDWORD lpdwSize ); + + BOOL (* SendHeaderFunction)( HCONN hConn, + LPDWORD lpdwSize, + LPDWORD lpdwDataType ); + +} CONTROL_BLOCK, *LPCONTROL_BLOCK; + +MODULE_API DWORD PHP5_wrapper(LPCONTROL_BLOCK lpCB); +MODULE_API BOOL PHP5_startup(); +MODULE_API BOOL PHP5_shutdown(); + +// the following type declaration is for the server side +typedef DWORD ( * PFN_WRAPPERFUNC )( CONTROL_BLOCK *pCB ); + + + +#ifdef __cplusplus +} +#endif + +#endif // end definition _PI3WEB_SAPI_H_ diff --git a/sapi/roxen/README b/sapi/roxen/README new file mode 100644 index 0000000..d834a00 --- /dev/null +++ b/sapi/roxen/README @@ -0,0 +1,18 @@ +Roxen PHP support. Early version. Don't expect to be able to get it to +work. Requires Pike 0.7.79 and Roxen 1.4. Anything less won't work. + +The module is now thread safe, in a couple of different modes. First +mode, the default, uses a process global PHP lock in the Roxen +module. This means that all PHP-requests are serialized (ie only one +script is executed at any one time). The second option is using ZTS +(Zend Thread Safe mode). Unless --enable-roxen-zts is specified, this +won't be used. + +This solution now works fine and is recommended. Multiple PHP5 +requests will be run in parallell. The maximum number of parallell +PHP5-execution is limited to the number of handle threads Roxen is +started with. + +Support for this module is lacking. Please contact Roxen Internet +Software for support and help. See http://www.roxen.com/company/contact/ +for contact information. diff --git a/sapi/roxen/TODO b/sapi/roxen/TODO new file mode 100644 index 0000000..248f36f --- /dev/null +++ b/sapi/roxen/TODO @@ -0,0 +1,33 @@ +BUGS: + +- fix backtraces +- exit in PHP exits Roxen +- POST newline added? +- Rewriting header handling so that more than one header with the same + name can be set (most importantly, cookies). +- Recursive mutex lock problem: + + And another error (when trying to include a class) + + Recursive mutex locks! + /Usr/local/pike/7.0.54/lib/modules/PHP5.so.Interpreter: + run("/home/www/www.tx.pl/news/test.php",mapping[3],modules/scripting/php5.pike.PHPScript(),modules/scripting/php5.pike.PHPScript.done) + modules/scripting/php5.pike:169: run() + base_server/roxen.pike:569: handler_thread(3). + + And after this every access to any php script (on other virtual sites + also) ends (of course there is no proper output) with this error: + + Php4.Interpreter->run: Tried to run a PHP-script from a PHP + callback!/usr/local/pike/7.0.54/lib/modules/PHP5.so.Interpreter: + run("/home/www/biall.com.pl/index.php3",mapping[2],modules/scripting/php5.pike.PHPScript(),modules/scripting/php5.pike.PHPScript.done) + modules/scripting/php5.pike:169: run() + base_server/roxen.pike:569: handler_thread(3). + + +ADDITIONS: + +- use th_farm +- change cwd in single threaded mode +- per-virtual-server configuration + diff --git a/sapi/roxen/config.m4 b/sapi/roxen/config.m4 new file mode 100644 index 0000000..9b0bb90 --- /dev/null +++ b/sapi/roxen/config.m4 @@ -0,0 +1,55 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(roxen,, +[ --with-roxen=DIR Build PHP as a Pike module. DIR is the base Roxen + directory, normally /usr/local/roxen/server], no, no) + +PHP_ARG_ENABLE(roxen-zts, whether Roxen module is build using ZTS, +[ --enable-roxen-zts ROXEN: Build the Roxen module using Zend Thread Safety], no, no) + +RESULT= +AC_MSG_CHECKING([for Roxen/Pike support]) +if test "$PHP_ROXEN" != "no"; then + if test ! -d $PHP_ROXEN ; then + AC_MSG_ERROR([You did not specify a directory]) + fi + if test -f $PHP_ROXEN/bin/roxen; then + PIKE=$PHP_ROXEN/bin/roxen + elif test -f $PHP_ROXEN/bin/pike; then + PIKE=$PHP_ROXEN/bin/pike + else + AC_MSG_ERROR([Could not find a pike in $PHP_ROXEN/bin/]) + fi + + if $PIKE -e 'float v; catch(v = __VERSION__ + (__BUILD__/10000.0)); if(v < 0.7079) exit(1); exit(0);'; then + PIKE_MODULE_DIR=`$PIKE --show-paths 2>&1| grep '^Module' | sed -e 's/.*: //'` + PIKE_INCLUDE_DIR=`echo $PIKE_MODULE_DIR | sed -e 's,lib/pike/modules,include/pike,' -e 's,lib/modules,include/pike,'` + if test -z "$PIKE_INCLUDE_DIR" || test -z "$PIKE_MODULE_DIR"; then + AC_MSG_ERROR([Failed to figure out Pike module and include directories]) + fi + else + AC_MSG_ERROR([Roxen/PHP requires Pike 0.7.79 or newer]) + fi + + PHP_ADD_INCLUDE($PIKE_INCLUDE_DIR) + AC_DEFINE(HAVE_ROXEN, 1, [Whether you use Roxen]) + PHP_SELECT_SAPI(roxen, shared, roxen.c) + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED $PIKE_MODULE_DIR/PHP5.so" + RESULT="yes + Pike binary used: $PIKE + Pike include dir: $PIKE_INCLUDE_DIR + Pike module directory: $PIKE_MODULE_DIR" + PIKE_INCLUDE_DIR=" -I$PIKE_INCLUDE_DIR " + + if test "$PHP_ROXEN_ZTS" != "no"; then + PHP_BUILD_THREAD_SAFE + AC_DEFINE(ROXEN_USE_ZTS, 1, [Whether to use Roxen in ZTS mode]) + fi +fi +AC_MSG_RESULT([$RESULT]) + +dnl ## Local Variables: +dnl ## tab-width: 4 +dnl ## End: diff --git a/sapi/roxen/roxen.c b/sapi/roxen/roxen.c new file mode 100644 index 0000000..c897ef6 --- /dev/null +++ b/sapi/roxen/roxen.c @@ -0,0 +1,727 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: David Hedbor <neotron@php.net> | + | Based on aolserver SAPI by Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include "php.h" +#ifdef HAVE_ROXEN + +#include "php_ini.h" +#include "php_globals.h" +#include "SAPI.h" +#include "php_main.h" +#include "ext/standard/info.h" + +#include "php_version.h" + +#ifndef ZTS +/* Only valid if thread safety is enabled. */ +#undef ROXEN_USE_ZTS +#endif + + +/* Pike Include Files + * + * conflicts with pike avoided by only using long names. Requires a new + * Pike 0.7 since it was implemented for this interface only. + * + */ +#define NO_PIKE_SHORTHAND + +#include <fdlib.h> +#include <program.h> +#include <pike_types.h> +#include <interpret.h> +#include <module_support.h> +#include <error.h> +#include <array.h> +#include <backend.h> +#include <stralloc.h> +#include <mapping.h> +#include <object.h> +#include <threads.h> +#include <builtin_functions.h> +#include <operators.h> + +#undef HIDE_GLOBAL_VARIABLES +#undef REVEAL_GLOBAL_VARIABLES +#define HIDE_GLOBAL_VARIABLES() +#define REVEAL_GLOBAL_VARIABLES() + +/* php_roxen_request is per-request object storage */ + +typedef struct +{ + struct mapping *request_data; + struct object *my_fd_obj; + int my_fd; + char *filename; +} php_roxen_request; + + +/* Defines to get to the data supplied when the script is started. */ + +#ifdef ROXEN_USE_ZTS + +/* ZTS does work now, but it seems like it's faster using the "serialization" + * method I previously used. Thus it's not used unless ROXEN_USE_ZTS is defined. + */ + +/* Per thread storage area id... */ +static int roxen_globals_id; + +# define GET_THIS() php_roxen_request *_request = ts_resource(roxen_globals_id) +# define THIS _request +#else +static php_roxen_request *current_request = NULL; + +# define GET_THIS() current_request = ((php_roxen_request *)Pike_fp->current_storage) +# define THIS current_request +#endif + +/* File descriptor integer. Used to write directly to the FD without + * passing Pike + */ +#define MY_FD (THIS->my_fd) + +/* FD object. Really a PHPScript object from Pike which implements a couple + * of functions to handle headers, writing and buffering. + */ +#define MY_FD_OBJ ((struct object *)(THIS->my_fd_obj)) + +/* Mapping with data supplied from the calling Roxen module. Contains + * a mapping with headers, an FD object etc. + */ +#define REQUEST_DATA ((struct mapping *)(THIS->request_data)) + + +#if defined(_REENTRANT) && !defined(ROXEN_USE_ZTS) +/* Lock used to serialize the PHP execution. If ROXEN_USE_ZTS is defined, we + * are using the PHP thread safe mechanism instead. + */ +static PIKE_MUTEX_T roxen_php_execution_lock; +# define PHP_INIT_LOCK() mt_init(&roxen_php_execution_lock) +# define PHP_LOCK(X) THREADS_ALLOW();mt_lock(&roxen_php_execution_lock);THREADS_DISALLOW() +# define PHP_UNLOCK(X) mt_unlock(&roxen_php_execution_lock); +# define PHP_DESTROY() mt_destroy(&roxen_php_execution_lock) +#else /* !_REENTRANT */ +# define PHP_INIT_LOCK() +# define PHP_LOCK(X) +# define PHP_UNLOCK(X) +# define PHP_DESTROY() +#endif /* _REENTRANT */ + +extern int fd_from_object(struct object *o); +static unsigned char roxen_php_initialized; + +/* This allows calling of pike functions from the PHP callbacks, + * which requires the Pike interpreter to be locked. + */ +#define THREAD_SAFE_RUN(COMMAND, what) do {\ + struct thread_state *state;\ + if((state = thread_state_for_id(th_self()))!=NULL) {\ + if(!state->swapped) {\ + COMMAND;\ + } else {\ + mt_lock(&interpreter_lock);\ + SWAP_IN_THREAD(state);\ + COMMAND;\ + SWAP_OUT_THREAD(state);\ + mt_unlock(&interpreter_lock);\ + }\ + }\ +} while(0) + +struct program *php_program; + + +/* To avoid executing a PHP script from a PHP callback, which would + * create a deadlock, a global thread id is used. If the thread calling the + * php-script is the same as the current thread, it fails. + */ +static int current_thread = -1; + + +/* Low level header lookup. Basically looks for the named header in the mapping + * headers in the supplied options mapping. + */ + +static INLINE struct svalue *lookup_header(char *headername) +{ + struct svalue *headers, *value; + struct pike_string *sind; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + sind = make_shared_string("env"); + headers = low_mapping_string_lookup(REQUEST_DATA, sind); + free_string(sind); + if(!headers || headers->type != PIKE_T_MAPPING) return NULL; + sind = make_shared_string(headername); + value = low_mapping_string_lookup(headers->u.mapping, sind); + free_string(sind); + if(!value) return NULL; + return value; +} + +/* Lookup a header in the mapping and return the value as a string, or + * return the default if it's missing + */ +INLINE static char *lookup_string_header(char *headername, char *default_value) +{ + struct svalue *head = NULL; + THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup"); + if(!head || head->type != PIKE_T_STRING) + return default_value; + return head->u.string->str; +} + +/* Lookup a header in the mapping and return the value as if it's an integer + * and otherwise return the default. + */ +INLINE static int lookup_integer_header(char *headername, int default_value) +{ + struct svalue *head = NULL; + THREAD_SAFE_RUN(head = lookup_header(headername), "header lookup"); + if(!head || head->type != PIKE_T_INT) + return default_value; + return head->u.integer; +} + +/* + * php_roxen_low_ub_write() writes data to the client connection. Might be + * rewritten to do more direct IO to save CPU and the need to lock the * + * interpreter for better threading. + */ + +static int +php_roxen_low_ub_write(const char *str, uint str_length TSRMLS_DC) { + int sent_bytes = 0; + struct pike_string *to_write = NULL; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + + if(!MY_FD_OBJ->prog) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return -1; + } + to_write = make_shared_binary_string(str, str_length); + push_string(to_write); + safe_apply(MY_FD_OBJ, "write", 1); + if(Pike_sp[-1].type == PIKE_T_INT) + sent_bytes = Pike_sp[-1].u.integer; + pop_stack(); + if(sent_bytes != str_length) { + /* This means the connection is closed. Dead. Gone. *sniff* */ + php_handle_aborted_connection(); + } + return sent_bytes; +} + +/* + * php_roxen_sapi_ub_write() calls php_roxen_low_ub_write in a Pike thread + * safe manner. + */ + +static int +php_roxen_sapi_ub_write(const char *str, uint str_length TSRMLS_DC) +{ +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + + int sent_bytes = 0, fd = MY_FD; + if(fd) + { + for(sent_bytes=0;sent_bytes < str_length;) + { + int written; + written = fd_write(fd, str + sent_bytes, str_length - sent_bytes); + if(written < 0) + { + switch(errno) + { + default: + /* This means the connection is closed. Dead. Gone. *sniff* */ + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return sent_bytes; + case EINTR: + case EWOULDBLOCK: + continue; + } + + } else { + sent_bytes += written; + } + } + } else { + THREAD_SAFE_RUN(sent_bytes = php_roxen_low_ub_write(str, str_length TSRMLS_CC), + "write"); + } + return sent_bytes; +} + +/* php_roxen_set_header() sets a header in the header mapping. Called in a + * thread safe manner from php_roxen_sapi_header_handler. + */ +static void php_roxen_set_header(char *header_name, char *value, char *p) +{ + struct svalue hsval; + struct pike_string *hval, *ind, *hind; + struct mapping *headermap; + struct svalue *s_headermap; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + hval = make_shared_string(value); + ind = make_shared_string(" _headers"); + hind = make_shared_binary_string(header_name, + (int)(p - header_name)); + + s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind); + if(!s_headermap) + { + struct svalue mappie; + mappie.type = PIKE_T_MAPPING; + headermap = allocate_mapping(1); + mappie.u.mapping = headermap; + mapping_string_insert(REQUEST_DATA, ind, &mappie); + free_mapping(headermap); + } else + headermap = s_headermap->u.mapping; + + hsval.type = PIKE_T_STRING; + hsval.u.string = hval; + mapping_string_insert(headermap, hind, &hsval); + + free_string(hval); + free_string(ind); + free_string(hind); +} + +/* + * php_roxen_sapi_header_handler() sets a HTTP reply header to be + * sent to the client. + */ +static int +php_roxen_sapi_header_handler(sapi_header_struct *sapi_header, + sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char *header_name, *header_content, *p; + header_name = sapi_header->header; + header_content = p = strchr(header_name, ':'); + + if(p) { + do { + header_content++; + } while(*header_content == ' '); + THREAD_SAFE_RUN(php_roxen_set_header(header_name, header_content, p), "header handler"); + } + sapi_free_header(sapi_header); + return 0; +} + +/* + * php_roxen_sapi_send_headers() flushes the headers to the client. + * Called before real content is sent by PHP. + */ + +static int +php_roxen_low_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + struct pike_string *ind; + struct svalue *s_headermap; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + + if(!MY_FD_OBJ->prog) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return SAPI_HEADER_SEND_FAILED; + } + ind = make_shared_string(" _headers"); + s_headermap = low_mapping_string_lookup(REQUEST_DATA, ind); + free_string(ind); + + push_int(SG(sapi_headers).http_response_code); + if(s_headermap && s_headermap->type == PIKE_T_MAPPING) + ref_push_mapping(s_headermap->u.mapping); + else + push_int(0); + safe_apply(MY_FD_OBJ, "send_headers", 2); + pop_stack(); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int +php_roxen_sapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + int res = 0; + THREAD_SAFE_RUN(res = php_roxen_low_send_headers(sapi_headers TSRMLS_CC), "send headers"); + return res; +} + +/* + * php_roxen_sapi_read_post() reads a specified number of bytes from + * the client. Used for POST/PUT requests. + */ + +INLINE static int php_roxen_low_read_post(char *buf, uint count_bytes) +{ + uint total_read = 0; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + TSRMLS_FETCH(); + + if(!MY_FD_OBJ->prog) + { + PG(connection_status) = PHP_CONNECTION_ABORTED; + zend_bailout(); + return -1; + } + push_int(count_bytes); + safe_apply(MY_FD_OBJ, "read_post", 1); + if(Pike_sp[-1].type == PIKE_T_STRING) { + MEMCPY(buf, Pike_sp[-1].u.string->str, + (total_read = Pike_sp[-1].u.string->len)); + buf[total_read] = '\0'; + } else + total_read = 0; + pop_stack(); + return total_read; +} + +static int +php_roxen_sapi_read_post(char *buf, uint count_bytes TSRMLS_DC) +{ + uint total_read = 0; + THREAD_SAFE_RUN(total_read = php_roxen_low_read_post(buf, count_bytes), "read post"); + return total_read; +} + +/* + * php_roxen_sapi_read_cookies() returns the Cookie header from + * the HTTP request header + */ + +static char * +php_roxen_sapi_read_cookies(TSRMLS_D) +{ + char *cookies; + cookies = lookup_string_header("HTTP_COOKIE", NULL); + return cookies; +} + +static void php_info_roxen(ZEND_MODULE_INFO_FUNC_ARGS) +{ + /* char buf[512]; */ + php_info_print_table_start(); + php_info_print_table_row(2, "SAPI module version", "$Id$"); + /* php_info_print_table_row(2, "Build date", Ns_InfoBuildDate()); + php_info_print_table_row(2, "Config file path", Ns_InfoConfigFile()); + php_info_print_table_row(2, "Error Log path", Ns_InfoErrorLog()); + php_info_print_table_row(2, "Installation path", Ns_InfoHomePath()); + php_info_print_table_row(2, "Hostname of server", Ns_InfoHostname()); + php_info_print_table_row(2, "Source code label", Ns_InfoLabel()); + php_info_print_table_row(2, "Server platform", Ns_InfoPlatform()); + snprintf(buf, 511, "%s/%s", Ns_InfoServerName(), Ns_InfoServerVersion()); + php_info_print_table_row(2, "Server version", buf); + snprintf(buf, 511, "%d day(s), %02d:%02d:%02d", + uptime / 86400, + (uptime / 3600) % 24, + (uptime / 60) % 60, + uptime % 60); + php_info_print_table_row(2, "Server uptime", buf); + */ + php_info_print_table_end(); +} + +static zend_module_entry php_roxen_module = { + STANDARD_MODULE_HEADER, + "Roxen", + NULL, + NULL, + NULL, + NULL, + NULL, + php_info_roxen, + NULL, + STANDARD_MODULE_PROPERTIES +}; + +static int php_roxen_startup(sapi_module_struct *sapi_module) +{ + if(php_module_startup(sapi_module, &php_roxen_module, 1) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + +/* this structure is static (as in "it does not change") */ + +static sapi_module_struct roxen_sapi_module = { + "roxen", + "Roxen", + php_roxen_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + NULL, /* activate */ + NULL, /* deactivate */ + php_roxen_sapi_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + php_error, /* error handler */ + php_roxen_sapi_header_handler, /* header handler */ + php_roxen_sapi_send_headers, /* send headers handler */ + NULL, /* send header handler */ + php_roxen_sapi_read_post, /* read POST data */ + php_roxen_sapi_read_cookies, /* read Cookies */ + NULL, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +/* + * php_roxen_hash_environment() populates the php script environment + * with a number of variables. HTTP_* variables are created for + * the HTTP header data, so that a script can access these. + */ +#define ADD_STRING(name) \ + MAKE_STD_ZVAL(zvalue); \ + zvalue->type = IS_STRING; \ + zvalue->value.str.len = strlen(buf); \ + zvalue->value.str.val = estrndup(buf, zvalue->value.str.len); \ + zend_hash_update(&EG(symbol_table), name, sizeof(name), \ + &zvalue, sizeof(zval *), NULL) + +static void +php_roxen_hash_environment(TSRMLS_D) +{ + int i; + char buf[512]; + zval *zvalue; + struct svalue *headers; + struct pike_string *sind; + struct array *indices; + struct svalue *ind, *val; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + sind = make_shared_string("env"); + headers = low_mapping_string_lookup(REQUEST_DATA, sind); + free_string(sind); + if(headers && headers->type == PIKE_T_MAPPING) { + indices = mapping_indices(headers->u.mapping); + for(i = 0; i < indices->size; i++) { + ind = &indices->item[i]; + val = low_mapping_lookup(headers->u.mapping, ind); + if(ind && ind->type == PIKE_T_STRING && + val && val->type == PIKE_T_STRING) { + int buf_len; + buf_len = MIN(511, ind->u.string->len); + strncpy(buf, ind->u.string->str, buf_len); + buf[buf_len] = '\0'; /* Terminate correctly */ + MAKE_STD_ZVAL(zvalue); + zvalue->type = IS_STRING; + zvalue->value.str.len = val->u.string->len; + zvalue->value.str.val = estrndup(val->u.string->str, zvalue->value.str.len); + + zend_hash_update(&EG(symbol_table), buf, buf_len + 1, &zvalue, sizeof(zval *), NULL); + } + } + free_array(indices); + } + + /* + MAKE_STD_ZVAL(zvalue); + zvalue->type = IS_LONG; + zvalue->value.lval = Ns_InfoBootTime(); + zend_hash_update(&EG(symbol_table), "SERVER_BOOTTIME", sizeof("SERVER_BOOTTIME"), &zvalue, sizeof(zval *), NULL); + */ +} + +/* + * php_roxen_module_main() is called by the per-request handler and + * "executes" the script + */ + +static int php_roxen_module_main(TSRMLS_D) +{ + int res, len; + char *dir; + zend_file_handle file_handle; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = THIS->filename; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + THREADS_ALLOW(); + res = php_request_startup(TSRMLS_C); + THREADS_DISALLOW(); + if(res == FAILURE) { + return 0; + } + php_roxen_hash_environment(TSRMLS_C); + THREADS_ALLOW(); + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); + THREADS_DISALLOW(); + return 1; +} + +/* + * The php_roxen_request_handler() is called per request and handles + * everything for one request. + */ + +void f_php_roxen_request_handler(INT32 args) +{ + struct object *my_fd_obj; + struct mapping *request_data; + struct svalue *done_callback, *raw_fd; + struct pike_string *script, *ind; + int status = 1; +#ifdef ROXEN_USE_ZTS + GET_THIS(); +#endif + TSRMLS_FETCH(); + + if(current_thread == th_self()) + php_error(E_WARNING, "PHP5.Interpreter->run: Tried to run a PHP-script from a PHP " + "callback!"); + get_all_args("PHP5.Interpreter->run", args, "%S%m%O%*", &script, + &request_data, &my_fd_obj, &done_callback); + if(done_callback->type != PIKE_T_FUNCTION) + php_error(E_WARNING, "PHP5.Interpreter->run: Bad argument 4, expected function.\n"); + PHP_LOCK(THIS); /* Need to lock here or reusing the same object might cause + * problems in changing stuff in that object */ +#ifndef ROXEN_USE_ZTS + GET_THIS(); +#endif + THIS->request_data = request_data; + THIS->my_fd_obj = my_fd_obj; + THIS->filename = script->str; + current_thread = th_self(); + SG(request_info).query_string = lookup_string_header("QUERY_STRING", 0); + SG(server_context) = (void *)1; /* avoid server_context == NULL */ + + /* path_translated is apparently the absolute path to the file, not + the translated PATH_INFO + */ + SG(request_info).path_translated = + lookup_string_header("SCRIPT_FILENAME", NULL); + SG(request_info).request_uri = lookup_string_header("DOCUMENT_URI", NULL); + if(!SG(request_info).request_uri) + SG(request_info).request_uri = lookup_string_header("SCRIPT_NAME", NULL); + SG(request_info).request_method = lookup_string_header("REQUEST_METHOD", "GET"); + SG(request_info).content_length = lookup_integer_header("HTTP_CONTENT_LENGTH", 0); + SG(request_info).content_type = lookup_string_header("HTTP_CONTENT_TYPE", NULL); + SG(sapi_headers).http_response_code = 200; + + /* FIXME: Check for auth stuff needs to be fixed... */ + SG(request_info).auth_user = NULL; + SG(request_info).auth_password = NULL; + + ind = make_shared_binary_string("my_fd", 5); + raw_fd = low_mapping_string_lookup(THIS->request_data, ind); + if(raw_fd && raw_fd->type == PIKE_T_OBJECT) + { + int fd = fd_from_object(raw_fd->u.object); + if(fd == -1) + php_error(E_WARNING, "PHP5.Interpreter->run: my_fd object not open or not an FD.\n"); + THIS->my_fd = fd; + } else + THIS->my_fd = 0; + + status = php_roxen_module_main(TSRMLS_C); + current_thread = -1; + + apply_svalue(done_callback, 0); + pop_stack(); + pop_n_elems(args); + push_int(status); + PHP_UNLOCK(THIS); +} + + +/* Clear the object global struct */ +static void clear_struct(struct object *o) +{ + MEMSET(Pike_fp->current_storage, 0, sizeof(php_roxen_request)); +} + + +/* + * pike_module_init() is called by Pike once at startup + * + * This functions allocates basic structures + */ + +void pike_module_init( void ) +{ + if (!roxen_php_initialized) { +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); +#ifdef ROXEN_USE_ZTS + ts_allocate_id(&roxen_globals_id, sizeof(php_roxen_request), NULL, NULL); +#endif +#endif + sapi_startup(&roxen_sapi_module); + /*php_roxen_startup(&roxen_sapi_module); removed - should be called from SAPI activation*/ + roxen_php_initialized = 1; + PHP_INIT_LOCK(); + } + start_new_program(); /* Text */ + ADD_STORAGE(php_roxen_request); + set_init_callback(clear_struct); + pike_add_function("run", f_php_roxen_request_handler, + "function(string, mapping, object, function:int)", 0); + add_program_constant("Interpreter", (php_program = end_program()), 0); +} + +/* + * pike_module_exit() performs the last steps before the + * server exists. Shutdowns basic services and frees memory + */ + +void pike_module_exit(void) +{ + roxen_php_initialized = 0; + roxen_sapi_module.shutdown(&roxen_sapi_module); + if(php_program) free_program(php_program); +#ifdef ZTS + tsrm_shutdown(); +#endif + PHP_DESTROY(); +} +#endif diff --git a/sapi/tests/test001.phpt b/sapi/tests/test001.phpt new file mode 100644 index 0000000..a964393 --- /dev/null +++ b/sapi/tests/test001.phpt @@ -0,0 +1,16 @@ +--TEST-- +IIS style CGI missing SCRIPT_FILENAME +--DESCRIPTION-- +This would be similar to what IIS produces for a simple query. +--ENV-- +return <<<END +PATH_TRANSLATED=$filename +PATH_INFO=$scriptname +SCRIPT_NAME=$scriptname +END; +--FILE-- +<?php + echo "HELLO"; +?> +--EXPECT-- +HELLO
\ No newline at end of file diff --git a/sapi/tests/test002.phpt b/sapi/tests/test002.phpt new file mode 100644 index 0000000..42ade3d --- /dev/null +++ b/sapi/tests/test002.phpt @@ -0,0 +1,22 @@ +--TEST-- +Apache style CGI +--DESCRIPTION-- +Apache likes to set SCRIPT_FILENAME to the php executable +if you use ScriptAlias configurations, and the proper +path is in PATH_TRANSLATED. SCRIPT_NAME in this is faked, +but that is ok, Apache sets SCRIPT_NAME to the ScriptAlias +of the executable. +--ENV-- +return <<<END +REDIRECT_URL=$scriptname +PATH_TRANSLATED=$filename +PATH_INFO=$scriptname +SCRIPT_NAME=/scriptalias/php +SCRIPT_FILENAME=$this->conf['TEST_PHP_EXECUTABLE'] +END; +--FILE-- +<?php + echo "HELLO"; +?> +--EXPECT-- +HELLO
\ No newline at end of file diff --git a/sapi/tests/test003.phpt b/sapi/tests/test003.phpt new file mode 100644 index 0000000..5cabe66 --- /dev/null +++ b/sapi/tests/test003.phpt @@ -0,0 +1,21 @@ +--TEST-- +IIS style CGI missing SCRIPT_FILENAME, has PATH_INFO +--DESCRIPTION-- +This would be similar to what IIS produces for a simple query +that also has PATH_INFO. +--REQUEST-- +return <<<END +PATH_INFO=/path/info +END; +--ENV-- +return <<<END +PATH_TRANSLATED=/path/bla +PATH_INFO=/path/info +SCRIPT_NAME=path +END; +--FILE-- +<?php + echo $_SERVER['PATH_INFO']; +?> +--EXPECT-- +/path/info
\ No newline at end of file diff --git a/sapi/tests/test004.phpt b/sapi/tests/test004.phpt new file mode 100644 index 0000000..ef43774 --- /dev/null +++ b/sapi/tests/test004.phpt @@ -0,0 +1,26 @@ +--TEST-- +Apache style CGI with PATH_INFO +--DESCRIPTION-- +Apache likes to set SCRIPT_FILENAME to the php executable +if you use ScriptAlias configurations, and the proper +path is in PATH_TRANSLATED. SCRIPT_NAME in this is faked, +but that is ok, Apache sets SCRIPT_NAME to the ScriptAlias +of the executable. +--REQUEST-- +return <<<END +PATH_INFO=/path/info +END; +--ENV-- +return <<<END +REDIRECT_URL=/path +PATH_TRANSLATED=/path/info/fpp +PATH_INFO=/path/info +SCRIPT_NAME=/scriptalias/php +SCRIPT_FILENAME=$this->conf['TEST_PHP_EXECUTABLE'] +END; +--FILE-- +<?php + echo $_SERVER['PATH_INFO']; +?> +--EXPECT-- +/path/info
\ No newline at end of file diff --git a/sapi/tests/test005.phpt b/sapi/tests/test005.phpt new file mode 100644 index 0000000..7415b66 --- /dev/null +++ b/sapi/tests/test005.phpt @@ -0,0 +1,27 @@ +--TEST-- +QUERY_STRING Security Bug +--DESCRIPTION-- +This bug was present in PHP 4.3.0 only. +A failure should print HELLO. +--REQUEST-- +return <<<END +SCRIPT_NAME=/nothing.php +QUERY_STRING=$filename +END; +--ENV-- +return <<<END +REDIRECT_URL=$scriptname +PATH_TRANSLATED=c:\apache\1.3.27\htdocs\nothing.php +QUERY_STRING=$filename +PATH_INFO=/nothing.php +SCRIPT_NAME=/phpexe/php.exe/nothing.php +SCRIPT_FILENAME=c:\apache\1.3.27\htdocs\nothing.php +END; +--FILE-- +<?php + echo "HELLO"; +?> +--EXPECTHEADERS-- +Status: 404 +--EXPECT-- +No input file specified.
\ No newline at end of file diff --git a/sapi/tests/test006.phpt b/sapi/tests/test006.phpt new file mode 100644 index 0000000..45e3781 --- /dev/null +++ b/sapi/tests/test006.phpt @@ -0,0 +1,73 @@ +--TEST-- +Multipart Form POST Data +--HEADERS-- +return <<<END +Content-Type=multipart/form-data; boundary=---------------------------240723202011929 +Content-Length=862 +END; +--ENV-- +return <<<END +CONTENT_TYPE=multipart/form-data; boundary=---------------------------240723202011929 +CONTENT_LENGTH=862 +END; +--POST-- +-----------------------------240723202011929 +Content-Disposition: form-data; name="entry" + +entry box +-----------------------------240723202011929 +Content-Disposition: form-data; name="password" + +password box +-----------------------------240723202011929 +Content-Disposition: form-data; name="radio1" + +test 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="checkbox1" + +test 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="choices" + +Choice 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="choices" + +Choice 2 +-----------------------------240723202011929 +Content-Disposition: form-data; name="file"; filename="info.php" +Content-Type: application/octet-stream + +<?php +phpinfo(); +?> +-----------------------------240723202011929-- + +--FILE-- +<?php +error_reporting(0); +print_r($_POST); +print_r($_FILES); +?> +--EXPECTF-- +Array +( + [entry] => entry box + [password] => password box + [radio1] => test 1 + [checkbox1] => test 1 + [choices] => Choice 2 +) +Array +( + [file] => Array + ( + [name] => info.php + [type] => application/octet-stream + [tmp_name] => %s + [error] => 0 + [size] => 21 + ) + +) diff --git a/sapi/tests/test007.phpt b/sapi/tests/test007.phpt new file mode 100644 index 0000000..8c50e4b --- /dev/null +++ b/sapi/tests/test007.phpt @@ -0,0 +1,46 @@ +--TEST-- +Multipart Form POST Data, incorrect content length +--HEADERS-- +return <<<END +Content-Type=multipart/form-data; boundary=---------------------------240723202011929 +Content-Length=100 +END; +--POST-- +-----------------------------240723202011929 +Content-Disposition: form-data; name="entry" + +entry box +-----------------------------240723202011929 +Content-Disposition: form-data; name="password" + +password box +-----------------------------240723202011929 +Content-Disposition: form-data; name="radio1" + +test 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="checkbox1" + +test 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="choices" + +Choice 1 +-----------------------------240723202011929 +Content-Disposition: form-data; name="choices" + +Choice 2 +-----------------------------240723202011929 +Content-Disposition: form-data; name="file"; filename="info.php" +Content-Type: application/octet-stream + +<?php +phpinfo(); +?> +-----------------------------240723202011929-- + +--FILE-- +<?php +print @$_POST['choices']; +?> +--EXPECT-- diff --git a/sapi/thttpd/CREDITS b/sapi/thttpd/CREDITS new file mode 100644 index 0000000..8f02f36 --- /dev/null +++ b/sapi/thttpd/CREDITS @@ -0,0 +1,2 @@ +thttpd +Sascha Schumann diff --git a/sapi/thttpd/README b/sapi/thttpd/README new file mode 100644 index 0000000..1e80a01 --- /dev/null +++ b/sapi/thttpd/README @@ -0,0 +1,85 @@ +README FOR THTTPD MODULE (by Sascha Schumann) +($Date$) + + This is a SAPI module for PHP 4.x supporting thttpd, the tiny, + turbo, throttling HTTP server by Jef Poskanzer. + + NOTE: All HTTP requests will be serialized. That means, one long running + script will block all other requests. Choose another web server, + if you want to execute arbitrarily long running scripts. + + The module contains a patch against version 2.21b of thttpd. The patch + fixes a number of bugs and adds some functionality: + + - HTTP/1.1 Persistent Connection/Pipeline Support + - PHP Scripting (**.php by default) + - Highlighting PHP Scripts (**.phps by default) + - Fast Accept Loop (unique to PHP) + - Periodic Connection Expiring (unique to PHP) + - Log to stdout (logfile=-) + - Fixes the Host: header vulnerability (affects vhosts only) + - Asynchronous request body handling (e.g. for POSTs) + - Accept filter for Linux + - Fix for non-blocking sending of thttpd-generated responses + + You can configure the filename extensions by creating a config file for + thttpd and setting these entries: + + phppat=PATTERN + phpspat=PATTERN + + The PATTERN has the same format as defined here: + + http://acme.com/software/thttpd/options.html#CGI_PATTERN + + "**.php" means: match any file ending in .php in any directory. + Setting the pattern from the command line is not supported. + + NOTE: This version supports *only* thttpd 2.21b, no prior or later + version. + + This is a functional and stable module (it runs a large application + like IMP 2.2.0 without any problems). Its original intention was to + demonstrate the ability of PHP to work in every web server environment. + +REQUIRED DOWNLOADS + + 1. thttpd 2.21b (2.20 or +2.22beta will _not_ work) + + Full Distribution: + http://www.acme.com/software/thttpd/thttpd-2.21b.tar.gz + + 2. PHP 4.x + + Download: + http://www.php.net/ + + Snapshots from CVS: + http://snaps.php.net/ + + +BUILD INSTRUCTIONS + + 1. Extract software packages + + $ gunzip -c thttpd-2.xx.tar.gz | tar xf - + $ gunzip -c php-*.tar.gz | tar xf - + + 2. Prepare PHP + + $ cd php-* + $ ./configure \ + --with-thttpd=../thttpd-2.xx \ + <further PHP options> + $ make install + $ cd .. + + You can see the list of valid PHP options by executing + + $ ./configure --help + + 3. Configure, compile, install thttpd + + Now follow the thttpd instructions. The Makefile template of + thttpd was changed to automatically use the components + required by PHP. diff --git a/sapi/thttpd/config.m4 b/sapi/thttpd/config.m4 new file mode 100644 index 0000000..371edae --- /dev/null +++ b/sapi/thttpd/config.m4 @@ -0,0 +1,39 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(thttpd,, +[ --with-thttpd=SRCDIR Build PHP as thttpd module], no, no) + +AC_MSG_CHECKING([for thttpd]) + +if test "$PHP_THTTPD" != "no"; then + if test ! -d $PHP_THTTPD; then + AC_MSG_RESULT(thttpd directory does not exist ($PHP_THTTPD)) + fi + + PHP_EXPAND_PATH($PHP_THTTPD, THTTPD) + + if grep thttpd.2.21b $PHP_THTTPD/version.h >/dev/null; then + patch="test -f $THTTPD/php_patched || \ + (cd $THTTPD && patch -p1 < $abs_srcdir/sapi/thttpd/thttpd_patch && touch php_patched)" + + elif grep Premium $PHP_THTTPD/version.h >/dev/null; then + patch= + else + AC_MSG_ERROR([This version only supports thttpd-2.21b and Premium thttpd]) + fi + PHP_TARGET_RDYNAMIC + INSTALL_IT="\ + echo 'PHP_LIBS = -L. -lphp5 \$(PHP_LIBS) \$(EXTRA_LIBS)' > $THTTPD/php_makefile; \ + echo 'PHP_LDFLAGS = \$(NATIVE_RPATHS) \$(PHP_LDFLAGS)' >> $THTTPD/php_makefile; \ + echo 'PHP_CFLAGS = \$(COMMON_FLAGS) \$(CFLAGS_CLEAN) \$(CPPFLAGS) \$(EXTRA_CFLAGS)' >> $THTTPD/php_makefile; \ + rm -f $THTTPD/php_thttpd.c $THTTPD/php_thttpd.h $THTTPD/libphp5.a; \ + \$(LN_S) $abs_srcdir/sapi/thttpd/thttpd.c $THTTPD/php_thttpd.c; \ + \$(LN_S) $abs_srcdir/sapi/thttpd/php_thttpd.h $abs_builddir/$SAPI_STATIC $THTTPD/;\ + $patch" + PHP_THTTPD="yes, using $THTTPD" + PHP_ADD_INCLUDE($THTTPD) + PHP_SELECT_SAPI(thttpd, static) +fi +AC_MSG_RESULT($PHP_THTTPD) diff --git a/sapi/thttpd/php.sym b/sapi/thttpd/php.sym new file mode 100644 index 0000000..2214d39 --- /dev/null +++ b/sapi/thttpd/php.sym @@ -0,0 +1,3 @@ +thttpd_php_request +thttpd_php_init +thttpd_php_shutdown diff --git a/sapi/thttpd/php_thttpd.h b/sapi/thttpd/php_thttpd.h new file mode 100644 index 0000000..40d63d2 --- /dev/null +++ b/sapi/thttpd/php_thttpd.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_THTTPD_H +#define PHP_THTTPD_H + +#include <sys/types.h> +#include <sys/stat.h> +#include <libhttpd.h> + +void thttpd_php_shutdown(void); +void thttpd_php_init(void); +off_t thttpd_php_request(httpd_conn *hc, int show_source); + +void thttpd_register_on_close(void (*)(int)); +void thttpd_closed_conn(int fd); +int thttpd_get_fd(void); +void thttpd_set_dont_close(void); + +#endif diff --git a/sapi/thttpd/stub.c b/sapi/thttpd/stub.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sapi/thttpd/stub.c diff --git a/sapi/thttpd/thttpd.c b/sapi/thttpd/thttpd.c new file mode 100644 index 0000000..1a1baa7 --- /dev/null +++ b/sapi/thttpd/thttpd.c @@ -0,0 +1,772 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_thttpd.h" +#include "php_variables.h" +#include "version.h" +#include "php_ini.h" +#include "zend_highlight.h" + +#include "ext/standard/php_smart_str.h" + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_GETNAMEINFO +#include <sys/socket.h> +#include <netdb.h> +#endif + +typedef struct { + httpd_conn *hc; + void (*on_close)(int); + + size_t unconsumed_length; + smart_str sbuf; + int seen_cl; + int seen_cn; +} php_thttpd_globals; + +#define PHP_SYS_CALL(x) do { x } while (n == -1 && errno == EINTR) + +#ifdef PREMIUM_THTTPD +# define do_keep_alive persistent +#endif + +#ifdef ZTS +static int thttpd_globals_id; +#define TG(v) TSRMG(thttpd_globals_id, php_thttpd_globals *, v) +#else +static php_thttpd_globals thttpd_globals; +#define TG(v) (thttpd_globals.v) +#endif + +static int sapi_thttpd_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int n; + uint sent = 0; + + if (TG(sbuf).c != 0) { + smart_str_appendl_ex(&TG(sbuf), str, str_length, 1); + return str_length; + } + + while (str_length > 0) { + PHP_SYS_CALL(n = send(TG(hc)->conn_fd, str, str_length, 0);); + + if (n == -1) { + if (errno == EAGAIN) { + smart_str_appendl_ex(&TG(sbuf), str, str_length, 1); + + return sent + str_length; + } else + php_handle_aborted_connection(); + } + + TG(hc)->bytes_sent += n; + str += n; + sent += n; + str_length -= n; + } + + return sent; +} + +#define COMBINE_HEADERS 64 + +#if defined(IOV_MAX) +# if IOV_MAX - 64 <= 0 +# define SERIALIZE_HEADERS +# endif +#endif + +static int do_writev(struct iovec *vec, int nvec, int len TSRMLS_DC) +{ + int n; + + assert(nvec <= IOV_MAX); + + if (TG(sbuf).c == 0) { + PHP_SYS_CALL(n = writev(TG(hc)->conn_fd, vec, nvec);); + + if (n == -1) { + if (errno == EAGAIN) { + n = 0; + } else { + php_handle_aborted_connection(); + } + } + + + TG(hc)->bytes_sent += n; + } else { + n = 0; + } + + if (n < len) { + int i; + + /* merge all unwritten data into sbuf */ + for (i = 0; i < nvec; vec++, i++) { + /* has this vector been written completely? */ + if (n >= vec->iov_len) { + /* yes, proceed */ + n -= vec->iov_len; + continue; + } + + if (n > 0) { + /* adjust vector */ + vec->iov_base = (char *) vec->iov_base + n; + vec->iov_len -= n; + n = 0; + } + + smart_str_appendl_ex(&TG(sbuf), vec->iov_base, vec->iov_len, 1); + } + } + + return 0; +} + +#ifdef SERIALIZE_HEADERS +# define ADD_VEC(str,l) smart_str_appendl(&vec_str, (str), (l)) +# define VEC_BASE() smart_str vec_str = {0} +# define VEC_FREE() smart_str_free(&vec_str) +#else +# define ADD_VEC(str,l) vec[n].iov_base=str;len += (vec[n].iov_len=l); n++ +# define VEC_BASE() struct iovec vec[COMBINE_HEADERS] +# define VEC_FREE() do {} while (0) +#endif + +#define ADD_VEC_S(str) ADD_VEC((str), sizeof(str)-1) + +#define CL_TOKEN "Content-length: " +#define CN_TOKEN "Connection: " +#define KA_DO "Connection: keep-alive\r\n" +#define KA_NO "Connection: close\r\n" +#define DEF_CT "Content-Type: text/html\r\n" + +static int sapi_thttpd_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char buf[1024], *p; + VEC_BASE(); + int n = 0; + zend_llist_position pos; + sapi_header_struct *h; + size_t len = 0; + + if (!SG(sapi_headers).http_status_line) { + ADD_VEC_S("HTTP/1.1 "); + p = smart_str_print_long(buf+sizeof(buf)-1, + SG(sapi_headers).http_response_code); + ADD_VEC(p, strlen(p)); + ADD_VEC_S(" HTTP\r\n"); + } else { + ADD_VEC(SG(sapi_headers).http_status_line, + strlen(SG(sapi_headers).http_status_line)); + ADD_VEC("\r\n", 2); + } + TG(hc)->status = SG(sapi_headers).http_response_code; + + if (SG(sapi_headers).send_default_content_type) { + ADD_VEC(DEF_CT, strlen(DEF_CT)); + } + + h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + + switch (h->header[0]) { + case 'c': case 'C': + if (!TG(seen_cl) && strncasecmp(h->header, CL_TOKEN, sizeof(CL_TOKEN)-1) == 0) { + TG(seen_cl) = 1; + } else if (!TG(seen_cn) && strncasecmp(h->header, CN_TOKEN, sizeof(CN_TOKEN)-1) == 0) { + TG(seen_cn) = 1; + } + } + + ADD_VEC(h->header, h->header_len); +#ifndef SERIALIZE_HEADERS + if (n >= COMBINE_HEADERS - 1) { + len = do_writev(vec, n, len TSRMLS_CC); + n = 0; + } +#endif + ADD_VEC("\r\n", 2); + + h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + + if (TG(seen_cl) && !TG(seen_cn) && TG(hc)->do_keep_alive) { + ADD_VEC(KA_DO, sizeof(KA_DO)-1); + } else { + TG(hc)->do_keep_alive = 0; + ADD_VEC(KA_NO, sizeof(KA_NO)-1); + } + + ADD_VEC("\r\n", 2); + +#ifdef SERIALIZE_HEADERS + sapi_thttpd_ub_write(vec_str.c, vec_str.len TSRMLS_CC); +#else + do_writev(vec, n, len TSRMLS_CC); +#endif + + VEC_FREE(); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +/* to understand this, read cgi_interpose_input() in libhttpd.c */ +#define SIZEOF_UNCONSUMED_BYTES() (TG(hc)->read_idx - TG(hc)->checked_idx) +#define CONSUME_BYTES(n) do { TG(hc)->checked_idx += (n); } while (0) + + +static int sapi_thttpd_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + size_t read_bytes = 0; + + if (TG(unconsumed_length) > 0) { + read_bytes = MIN(TG(unconsumed_length), count_bytes); + memcpy(buffer, TG(hc)->read_buf + TG(hc)->checked_idx, read_bytes); + TG(unconsumed_length) -= read_bytes; + CONSUME_BYTES(read_bytes); + } + + return read_bytes; +} + +static char *sapi_thttpd_read_cookies(TSRMLS_D) +{ + return TG(hc)->cookie; +} + +#define BUF_SIZE 512 +#define ADD_STRING_EX(name,buf) \ + php_register_variable(name, buf, track_vars_array TSRMLS_CC) +#define ADD_STRING(name) ADD_STRING_EX((name), buf) + +static void sapi_thttpd_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char buf[BUF_SIZE + 1]; + char *p; + + php_register_variable("PHP_SELF", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("SERVER_SOFTWARE", SERVER_SOFTWARE, track_vars_array TSRMLS_CC); + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_METHOD", (char *) SG(request_info).request_method, track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_URI", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", SG(request_info).path_translated, track_vars_array TSRMLS_CC); + + if (TG(hc)->one_one) { + php_register_variable("SERVER_PROTOCOL", "HTTP/1.1", track_vars_array TSRMLS_CC); + } else { + php_register_variable("SERVER_PROTOCOL", "HTTP/1.0", track_vars_array TSRMLS_CC); + } + + p = httpd_ntoa(&TG(hc)->client_addr); + + ADD_STRING_EX("REMOTE_ADDR", p); + ADD_STRING_EX("REMOTE_HOST", p); + + ADD_STRING_EX("SERVER_PORT", + smart_str_print_long(buf + sizeof(buf) - 1, + TG(hc)->hs->port)); + + buf[0] = '/'; + memcpy(buf + 1, TG(hc)->pathinfo, strlen(TG(hc)->pathinfo) + 1); + ADD_STRING("PATH_INFO"); + + buf[0] = '/'; + memcpy(buf + 1, TG(hc)->origfilename, strlen(TG(hc)->origfilename) + 1); + ADD_STRING("SCRIPT_NAME"); + +#define CONDADD(name, field) \ + if (TG(hc)->field[0]) { \ + php_register_variable(#name, TG(hc)->field, track_vars_array TSRMLS_CC); \ + } + + CONDADD(QUERY_STRING, query); + CONDADD(HTTP_HOST, hdrhost); + CONDADD(HTTP_REFERER, referer); + CONDADD(HTTP_USER_AGENT, useragent); + CONDADD(HTTP_ACCEPT, accept); + CONDADD(HTTP_ACCEPT_LANGUAGE, acceptl); + CONDADD(HTTP_ACCEPT_ENCODING, accepte); + CONDADD(HTTP_COOKIE, cookie); + CONDADD(CONTENT_TYPE, contenttype); + CONDADD(REMOTE_USER, remoteuser); + CONDADD(SERVER_PROTOCOL, protocol); + + if (TG(hc)->contentlength != -1) { + ADD_STRING_EX("CONTENT_LENGTH", + smart_str_print_long(buf + sizeof(buf) - 1, + TG(hc)->contentlength)); + } + + if (TG(hc)->authorization[0]) + php_register_variable("AUTH_TYPE", "Basic", track_vars_array TSRMLS_CC); +} + +static PHP_MINIT_FUNCTION(thttpd) +{ + return SUCCESS; +} + +static zend_module_entry php_thttpd_module = { + STANDARD_MODULE_HEADER, + "thttpd", + NULL, + PHP_MINIT(thttpd), + NULL, + NULL, + NULL, + NULL, /* info */ + NULL, + STANDARD_MODULE_PROPERTIES +}; + +static int php_thttpd_startup(sapi_module_struct *sapi_module) +{ +#if PHP_API_VERSION >= 20020918 + if (php_module_startup(sapi_module, &php_thttpd_module, 1) == FAILURE) { +#else + if (php_module_startup(sapi_module) == FAILURE + || zend_startup_module(&php_thttpd_module) == FAILURE) { +#endif + return FAILURE; + } + return SUCCESS; +} + +static int sapi_thttpd_get_fd(int *nfd TSRMLS_DC) +{ + if (nfd) *nfd = TG(hc)->conn_fd; + return SUCCESS; +} + +static sapi_module_struct thttpd_sapi_module = { + "thttpd", + "thttpd", + + php_thttpd_startup, + php_module_shutdown_wrapper, + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_thttpd_ub_write, + NULL, + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, + + NULL, + sapi_thttpd_send_headers, + NULL, + sapi_thttpd_read_post, + sapi_thttpd_read_cookies, + + sapi_thttpd_register_variables, + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + NULL, /* php.ini path override */ + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + NULL, + NULL, + NULL, + 0, + sapi_thttpd_get_fd +}; + +static void thttpd_module_main(int show_source TSRMLS_DC) +{ + zend_file_handle file_handle; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return; + } + + if (show_source) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC); + } else { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + } + + php_request_shutdown(NULL); +} + +static void thttpd_request_ctor(TSRMLS_D) +{ + smart_str s = {0}; + + TG(seen_cl) = 0; + TG(seen_cn) = 0; + TG(sbuf).c = 0; + SG(request_info).query_string = TG(hc)->query?strdup(TG(hc)->query):NULL; + + smart_str_appends_ex(&s, TG(hc)->hs->cwd, 1); + smart_str_appends_ex(&s, TG(hc)->expnfilename, 1); + smart_str_0(&s); + SG(request_info).path_translated = s.c; + + s.c = NULL; + smart_str_appendc_ex(&s, '/', 1); + smart_str_appends_ex(&s, TG(hc)->origfilename, 1); + smart_str_0(&s); + SG(request_info).request_uri = s.c; + SG(request_info).request_method = httpd_method_str(TG(hc)->method); + if (TG(hc)->one_one) SG(request_info).proto_num = 1001; + else SG(request_info).proto_num = 1000; + SG(sapi_headers).http_response_code = 200; + if (TG(hc)->contenttype) + SG(request_info).content_type = strdup(TG(hc)->contenttype); + SG(request_info).content_length = TG(hc)->contentlength == -1 ? 0 + : TG(hc)->contentlength; + + TG(unconsumed_length) = SG(request_info).content_length; + + php_handle_auth_data(TG(hc)->authorization TSRMLS_CC); +} + +static void thttpd_request_dtor(TSRMLS_D) +{ + smart_str_free_ex(&TG(sbuf), 1); + if (SG(request_info).query_string) + free(SG(request_info).query_string); + free(SG(request_info).request_uri); + free(SG(request_info).path_translated); + if (SG(request_info).content_type) + free(SG(request_info).content_type); +} + +#ifdef ZTS + +#ifdef TSRM_ST +#define thread_create_simple_detached(n) st_thread_create(n, NULL, 0, 0) +#define thread_usleep(n) st_usleep(n) +#define thread_exit() st_thread_exit(NULL) +/* No preemption, simple operations are safe */ +#define thread_atomic_inc(n) (++n) +#define thread_atomic_dec(n) (--n) +#else +#error No thread primitives available +#endif + +/* We might want to replace this with a STAILQ */ +typedef struct qreq { + httpd_conn *hc; + struct qreq *next; +} qreq_t; + +static MUTEX_T qr_lock; +static qreq_t *queued_requests; +static qreq_t *last_qr; +static int nr_free_threads; +static int nr_threads; +static int max_threads = 50; + +#define HANDLE_STRINGS() { \ + HANDLE_STR(encodedurl); \ + HANDLE_STR(decodedurl); \ + HANDLE_STR(origfilename); \ + HANDLE_STR(expnfilename); \ + HANDLE_STR(pathinfo); \ + HANDLE_STR(query); \ + HANDLE_STR(referer); \ + HANDLE_STR(useragent); \ + HANDLE_STR(accept); \ + HANDLE_STR(accepte); \ + HANDLE_STR(acceptl); \ + HANDLE_STR(cookie); \ + HANDLE_STR(contenttype); \ + HANDLE_STR(authorization); \ + HANDLE_STR(remoteuser); \ + } + +static httpd_conn *duplicate_conn(httpd_conn *hc, httpd_conn *nhc) +{ + memcpy(nhc, hc, sizeof(*nhc)); + +#define HANDLE_STR(m) nhc->m = nhc->m ? strdup(nhc->m) : NULL + HANDLE_STRINGS(); +#undef HANDLE_STR + + return nhc; +} + +static void destroy_conn(httpd_conn *hc) +{ +#define HANDLE_STR(m) if (hc->m) free(hc->m) + HANDLE_STRINGS(); +#undef HANDLE_STR +} + +static httpd_conn *dequeue_request(void) +{ + httpd_conn *ret = NULL; + qreq_t *m; + + tsrm_mutex_lock(qr_lock); + if (queued_requests) { + m = queued_requests; + ret = m->hc; + if (!(queued_requests = m->next)) + last_qr = NULL; + free(m); + } + tsrm_mutex_unlock(qr_lock); + + return ret; +} + +static void *worker_thread(void *); + +static void queue_request(httpd_conn *hc) +{ + qreq_t *m; + httpd_conn *nhc; + + /* Mark as long-running request */ + hc->file_address = (char *) 1; + + /* + * We cannot synchronously revoke accesses to hc in the worker + * thread, so we need to pass a copy of hc to the worker thread. + */ + nhc = malloc(sizeof *nhc); + duplicate_conn(hc, nhc); + + /* Allocate request queue container */ + m = malloc(sizeof *m); + m->hc = nhc; + m->next = NULL; + + tsrm_mutex_lock(qr_lock); + /* Create new threads when reaching a certain threshhold */ + if (nr_threads < max_threads && nr_free_threads < 2) { + nr_threads++; /* protected by qr_lock */ + + thread_atomic_inc(nr_free_threads); + thread_create_simple_detached(worker_thread); + } + /* Insert container into request queue */ + if (queued_requests) + last_qr->next = m; + else + queued_requests = m; + last_qr = m; + tsrm_mutex_unlock(qr_lock); +} + +static off_t thttpd_real_php_request(httpd_conn *hc, int TSRMLS_DC); + +static void *worker_thread(void *dummy) +{ + int do_work = 50; + httpd_conn *hc; + + while (do_work) { + hc = dequeue_request(); + + if (!hc) { +/* do_work--; */ + thread_usleep(500000); + continue; + } +/* do_work = 50; */ + + thread_atomic_dec(nr_free_threads); + + thttpd_real_php_request(hc, 0 TSRMLS_CC); + shutdown(hc->conn_fd, 0); + destroy_conn(hc); + free(hc); + + thread_atomic_inc(nr_free_threads); + } + thread_atomic_dec(nr_free_threads); + thread_atomic_dec(nr_threads); + thread_exit(); +} + +static void remove_dead_conn(int fd) +{ + qreq_t *m, *prev = NULL; + + tsrm_mutex_lock(qr_lock); + m = queued_requests; + while (m) { + if (m->hc->conn_fd == fd) { + if (prev) + if (!(prev->next = m->next)) + last_qr = prev; + else + if (!(queued_requests = m->next)) + last_qr = NULL; + destroy_conn(m->hc); + free(m->hc); + free(m); + break; + } + prev = m; + m = m->next; + } + tsrm_mutex_unlock(qr_lock); +} + +#endif + +static off_t thttpd_real_php_request(httpd_conn *hc, int show_source TSRMLS_DC) +{ + TG(hc) = hc; + hc->bytes_sent = 0; + + if (hc->contentlength != -1) { + hc->should_linger = 1; + hc->do_keep_alive = 0; + } + + if (hc->contentlength != -1 + && SIZEOF_UNCONSUMED_BYTES() < hc->contentlength) { + hc->read_body_into_mem = 1; + return 0; + } + + thttpd_request_ctor(TSRMLS_C); + + thttpd_module_main(show_source TSRMLS_CC); + + /* disable kl, if no content-length was seen or Connection: was set */ + if (TG(seen_cl) == 0 || TG(seen_cn) == 1) { + TG(hc)->do_keep_alive = 0; + } + + if (TG(sbuf).c != 0) { + if (TG(hc)->response) + free(TG(hc)->response); + + TG(hc)->response = TG(sbuf).c; + TG(hc)->responselen = TG(sbuf).len; + TG(hc)->maxresponse = TG(sbuf).a; + + TG(sbuf).c = 0; + TG(sbuf).len = 0; + TG(sbuf).a = 0; + } + + thttpd_request_dtor(TSRMLS_C); + + return 0; +} + +off_t thttpd_php_request(httpd_conn *hc, int show_source) +{ +#ifdef ZTS + queue_request(hc); +#else + TSRMLS_FETCH(); + return thttpd_real_php_request(hc, show_source TSRMLS_CC); +#endif +} + +void thttpd_register_on_close(void (*arg)(int)) +{ + TSRMLS_FETCH(); + TG(on_close) = arg; +} + +void thttpd_closed_conn(int fd) +{ + TSRMLS_FETCH(); + if (TG(on_close)) TG(on_close)(fd); +} + +int thttpd_get_fd(void) +{ + TSRMLS_FETCH(); + return TG(hc)->conn_fd; +} + +void thttpd_set_dont_close(void) +{ + TSRMLS_FETCH(); +#ifndef PREMIUM_THTTPD + TG(hc)->file_address = (char *) 1; +#endif +} + + +void thttpd_php_init(void) +{ + char *ini; + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + ts_allocate_id(&thttpd_globals_id, sizeof(php_thttpd_globals), NULL, NULL); + qr_lock = tsrm_mutex_alloc(); + thttpd_register_on_close(remove_dead_conn); +#endif + + if ((ini = getenv("PHP_INI_PATH"))) { + thttpd_sapi_module.php_ini_path_override = ini; + } + + sapi_startup(&thttpd_sapi_module); + thttpd_sapi_module.startup(&thttpd_sapi_module); + + { + TSRMLS_FETCH(); + + SG(server_context) = (void *) 1; + } +} + +void thttpd_php_shutdown(void) +{ + TSRMLS_FETCH(); + + if (SG(server_context) != NULL) { + thttpd_sapi_module.shutdown(&thttpd_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + } +} diff --git a/sapi/thttpd/thttpd_patch b/sapi/thttpd/thttpd_patch new file mode 100644 index 0000000..33de921 --- /dev/null +++ b/sapi/thttpd/thttpd_patch @@ -0,0 +1,2377 @@ +diff -ur thttpd-2.21b/Makefile.in thttpd-2.21b-cool/Makefile.in +--- thttpd-2.21b/Makefile.in Thu Mar 29 20:36:21 2001 ++++ thttpd-2.21b-cool/Makefile.in Sat Sep 20 14:43:20 2003 +@@ -46,13 +46,15 @@ + + # You shouldn't need to edit anything below here. + ++include php_makefile ++ + CC = @CC@ + CCOPT = @V_CCOPT@ + DEFS = @DEFS@ +-INCLS = -I. ++INCLS = -I. $(PHP_CFLAGS) + CFLAGS = $(CCOPT) $(DEFS) $(INCLS) +-LDFLAGS = @LDFLAGS@ +-LIBS = @LIBS@ ++LDFLAGS = @LDFLAGS@ $(PHP_LDFLAGS) ++LIBS = @LIBS@ $(PHP_LIBS) + NETLIBS = @V_NETLIBS@ + INSTALL = @INSTALL@ + +@@ -62,7 +64,7 @@ + @rm -f $@ + $(CC) $(CFLAGS) -c $*.c + +-SRC = thttpd.c libhttpd.c fdwatch.c mmc.c timers.c match.c tdate_parse.c syslog.c ++SRC = thttpd.c libhttpd.c fdwatch.c mmc.c timers.c match.c tdate_parse.c syslog.c php_thttpd.c + + OBJ = $(SRC:.c=.o) @LIBOBJS@ + +@@ -77,7 +79,7 @@ + all: this subdirs + this: $(ALL) + +-thttpd: $(OBJ) ++thttpd: $(OBJ) libphp5.a + @rm -f $@ + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) $(LIBS) $(NETLIBS) + +diff -ur thttpd-2.21b/config.h thttpd-2.21b-cool/config.h +--- thttpd-2.21b/config.h Mon Apr 9 23:57:36 2001 ++++ thttpd-2.21b-cool/config.h Sat Sep 20 14:43:20 2003 +@@ -82,6 +82,11 @@ + */ + #define IDLE_READ_TIMELIMIT 60 + ++/* CONFIGURE: How many seconds to allow for reading the subsequent requests ++** on a keep-alive connection. Should be simiar to LINGER_TIME ++*/ ++#define IDLE_KEEPALIVE_TIMELIMIT 2 ++ + /* CONFIGURE: How many seconds before an idle connection gets closed. + */ + #define IDLE_SEND_TIMELIMIT 300 +@@ -316,7 +321,7 @@ + /* CONFIGURE: A list of index filenames to check. The files are searched + ** for in this order. + */ +-#define INDEX_NAMES "index.html", "index.htm", "Default.htm", "index.cgi" ++#define INDEX_NAMES "index.php", "index.html", "index.htm", "Default.htm", "index.cgi" + + /* CONFIGURE: If this is defined then thttpd will automatically generate + ** index pages for directories that don't have an explicit index file. +diff -ur thttpd-2.21b/configure thttpd-2.21b-cool/configure +--- thttpd-2.21b/configure Sat Apr 21 02:07:14 2001 ++++ thttpd-2.21b-cool/configure Sat Sep 20 14:43:20 2003 +@@ -1021,7 +1021,7 @@ + fi + echo "$ac_t""$CPP" 1>&6 + +-for ac_hdr in fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/event.h osreldate.h ++for ac_hdr in fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/event.h osreldate.h netinet/tcp.h + do + ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'` + echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6 +diff -ur thttpd-2.21b/configure.in thttpd-2.21b-cool/configure.in +--- thttpd-2.21b/configure.in Sat Apr 21 02:06:23 2001 ++++ thttpd-2.21b-cool/configure.in Sat Sep 20 14:43:20 2003 +@@ -64,7 +64,7 @@ + AC_MSG_RESULT(no) + fi + +-AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/event.h osreldate.h) ++AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/event.h osreldate.h netinet/tcp.h) + AC_HEADER_TIME + AC_HEADER_DIRENT + +diff -ur thttpd-2.21b/fdwatch.c thttpd-2.21b-cool/fdwatch.c +--- thttpd-2.21b/fdwatch.c Fri Apr 13 07:36:08 2001 ++++ thttpd-2.21b-cool/fdwatch.c Sat Sep 20 14:43:20 2003 +@@ -419,6 +419,7 @@ + if ( pollfds == (struct pollfd*) 0 || poll_fdidx == (int*) 0 || + poll_rfdidx == (int*) 0 ) + return -1; ++ memset(pollfds, 0, sizeof(struct pollfd) * nfiles); + return 0; + } + +@@ -460,7 +461,7 @@ + + ridx = 0; + for ( i = 0; i < npollfds; ++i ) +- if ( pollfds[i].revents & ( POLLIN | POLLOUT ) ) ++ if ( pollfds[i].revents & ( POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL ) ) + poll_rfdidx[ridx++] = pollfds[i].fd; + + return r; +@@ -472,8 +473,8 @@ + { + switch ( fd_rw[fd] ) + { +- case FDW_READ: return pollfds[poll_fdidx[fd]].revents & POLLIN; +- case FDW_WRITE: return pollfds[poll_fdidx[fd]].revents & POLLOUT; ++ case FDW_READ: return pollfds[poll_fdidx[fd]].revents & ( POLLIN | POLLERR | POLLHUP | POLLNVAL ); ++ case FDW_WRITE: return pollfds[poll_fdidx[fd]].revents & ( POLLOUT | POLLERR | POLLHUP | POLLNVAL ); + default: return 0; + } + } +diff -ur thttpd-2.21b/libhttpd.c thttpd-2.21b-cool/libhttpd.c +--- thttpd-2.21b/libhttpd.c Tue Apr 24 00:42:40 2001 ++++ thttpd-2.21b-cool/libhttpd.c Sat Sep 20 14:43:29 2003 +@@ -56,6 +56,10 @@ + #include <unistd.h> + #include <stdarg.h> + ++#ifdef HAVE_NETINET_TCP_H ++#include <netinet/tcp.h> ++#endif ++ + #ifdef HAVE_OSRELDATE_H + #include <osreldate.h> + #endif /* HAVE_OSRELDATE_H */ +@@ -85,6 +89,12 @@ + #include "match.h" + #include "tdate_parse.h" + ++#include "php_thttpd.h" ++ ++#ifdef __CYGWIN__ ++# define timezone _timezone ++#endif ++ + #ifndef STDIN_FILENO + #define STDIN_FILENO 0 + #endif +@@ -111,7 +121,7 @@ + static int initialize_listen_socket( httpd_sockaddr* saP ); + static void unlisten( httpd_server* hs ); + static void add_response( httpd_conn* hc, char* str ); +-static void send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, int length, time_t mod ); ++static void send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, int length, time_t mod, const char *, size_t ); + static void send_response( httpd_conn* hc, int status, char* title, char* extraheads, char* form, char* arg ); + static void send_response_tail( httpd_conn* hc ); + static void defang( char* str, char* dfstr, int dfsize ); +@@ -242,6 +252,10 @@ + free( (void*) hs->cwd ); + if ( hs->cgi_pattern != (char*) 0 ) + free( (void*) hs->cgi_pattern ); ++ if ( hs->php_pattern != (char*) 0 ) ++ free( (void*) hs->php_pattern ); ++ if ( hs->phps_pattern != (char*) 0 ) ++ free( (void*) hs->phps_pattern ); + if ( hs->charset != (char*) 0 ) + free( (void*) hs->charset ); + if ( hs->url_pattern != (char*) 0 ) +@@ -249,6 +263,7 @@ + if ( hs->local_pattern != (char*) 0 ) + free( (void*) hs->local_pattern ); + free( (void*) hs ); ++ thttpd_php_shutdown(); + } + + +@@ -257,7 +272,8 @@ + char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P, int port, + char* cgi_pattern, char* charset, char* cwd, int no_log, FILE* logfp, + int no_symlink, int vhost, int global_passwd, char* url_pattern, +- char* local_pattern, int no_empty_referers ) ++ char* local_pattern, int no_empty_referers, char* php_pattern, ++ char* phps_pattern ) + { + httpd_server* hs; + static char ghnbuf[256]; +@@ -312,6 +328,8 @@ + } + + hs->port = port; ++ hs->php_pattern = strdup(php_pattern); ++ hs->phps_pattern = strdup(phps_pattern); + if ( cgi_pattern == (char*) 0 ) + hs->cgi_pattern = (char*) 0; + else +@@ -329,7 +347,7 @@ + while ( ( cp = strstr( hs->cgi_pattern, "|/" ) ) != (char*) 0 ) + (void) strcpy( cp + 1, cp + 2 ); + } +- hs->charset = strdup( charset ); ++ hs->charset = strdup( charset ); + hs->cwd = strdup( cwd ); + if ( hs->cwd == (char*) 0 ) + { +@@ -385,6 +403,8 @@ + return (httpd_server*) 0; + } + ++ thttpd_php_init(); ++ + /* Done initializing. */ + if ( hs->binding_hostname == (char*) 0 ) + syslog( LOG_INFO, "%.80s starting on port %d", SERVER_SOFTWARE, hs->port ); +@@ -418,6 +438,11 @@ + } + (void) fcntl( listen_fd, F_SETFD, 1 ); + ++#if defined(TCP_DEFER_ACCEPT) && defined(SOL_TCP) ++ on = 30; /* give clients 30s to send first data packet */ ++ setsockopt(listen_fd, SOL_TCP, TCP_DEFER_ACCEPT, &on, sizeof(on)); ++#endif ++ + /* Allow reuse of local addresses. */ + on = 1; + if ( setsockopt( +@@ -582,6 +607,9 @@ + /* And send it, if necessary. */ + if ( hc->responselen > 0 ) + { ++/* ++printf("**RESPONSE [%d]** len = %d\n%*.*s\n", hc->conn_fd, hc->responselen, hc->responselen, hc->responselen, hc->response); ++*/ + (void) write( hc->conn_fd, hc->response, hc->responselen ); + hc->responselen = 0; + } +@@ -619,18 +647,22 @@ + } + } + ++extern time_t httpd_time_now; ++extern char httpd_now_buf[]; ++ ++#define SMART_STR_USE_REALLOC ++ ++#include "ext/standard/php_smart_str.h" + + static void +-send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, int length, time_t mod ) ++send_mime( httpd_conn* hc, int status, char* title, char* encodings, char* extraheads, char* type, int length, time_t mod, const char *last_modified, size_t last_modified_len) + { +- time_t now; + const char* rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; +- char nowbuf[100]; + char modbuf[100]; +- char fixed_type[500]; +- char buf[1000]; + int partial_content; +- ++ smart_str s = {0}; ++ int type_len; ++ + hc->status = status; + hc->bytes_to_send = length; + if ( hc->mime_flag ) +@@ -649,41 +681,89 @@ + else + partial_content = 0; + +- now = time( (time_t*) 0 ); + if ( mod == (time_t) 0 ) +- mod = now; +- (void) strftime( nowbuf, sizeof(nowbuf), rfc1123fmt, gmtime( &now ) ); +- (void) strftime( modbuf, sizeof(modbuf), rfc1123fmt, gmtime( &mod ) ); +- (void) my_snprintf( +- fixed_type, sizeof(fixed_type), type, hc->hs->charset ); +- (void) my_snprintf( buf, sizeof(buf), +- "%.20s %d %s\r\nServer: %s\r\nContent-Type: %s\r\nDate: %s\r\nLast-Modified: %s\r\nAccept-Ranges: bytes\r\nConnection: close\r\n", +- hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE, fixed_type, +- nowbuf, modbuf ); +- add_response( hc, buf ); ++ mod = httpd_time_now; ++ ++ if (last_modified == 0) { ++ (void) strftime( modbuf, sizeof(modbuf), rfc1123fmt, gmtime( &mod ) ); ++ last_modified = modbuf; ++ last_modified_len = strlen(modbuf); ++ } ++ ++ type_len = strlen(type); ++ ++ if (hc->response) { ++ s.c = hc->response; ++ s.len = 0; ++ s.a = hc->maxresponse; ++ hc->response = 0; ++ hc->maxresponse = 0; ++ } ++ ++ smart_str_appends(&s, "HTTP/1.1 "); ++ smart_str_append_long(&s, status); ++ smart_str_appends(&s, " HTTP\r\nServer: " EXPOSED_SERVER_SOFTWARE "\r\n" ++ "Content-Type: "); ++ ++ if (type[type_len-2] == '%' && type[type_len-1] == 's') { ++ smart_str_appendl(&s, type, type_len - 2); ++ smart_str_appends(&s, hc->hs->charset); ++ } else { ++ smart_str_appendl(&s, type, type_len); ++ } ++ ++ ++ smart_str_appends(&s, "\r\nDate: "); ++ smart_str_appends(&s, httpd_now_buf); ++ smart_str_appends(&s, "\r\nLast-Modified: "); ++ smart_str_appendl(&s, last_modified, last_modified_len); ++ smart_str_appends(&s, "\r\nAccept-Ranges: bytes\r\n"); ++ + if ( encodings[0] != '\0' ) + { +- (void) my_snprintf( buf, sizeof(buf), +- "Content-Encoding: %s\r\n", encodings ); +- add_response( hc, buf ); ++ smart_str_appends(&s, "Content-Encoding: "); ++ smart_str_appends(&s, encodings); ++ smart_str_appends(&s, "\r\n"); + } + if ( partial_content ) + { +- (void) my_snprintf( buf, sizeof(buf), +- "Content-Range: bytes %ld-%ld/%d\r\nContent-Length: %ld\r\n", +- (long) hc->init_byte_loc, (long) hc->end_byte_loc, length, +- (long) ( hc->end_byte_loc - hc->init_byte_loc + 1 ) ); +- add_response( hc, buf ); ++ ++ smart_str_appends(&s, "Content-Range: bytes "); ++ smart_str_append_long(&s, hc->init_byte_loc); ++ smart_str_appendc(&s, '-'); ++ smart_str_append_long(&s, hc->end_byte_loc); ++ smart_str_appendc(&s, '/'); ++ smart_str_append_long(&s, length); ++ smart_str_appends(&s, "\r\nContent-Length: "); ++ smart_str_append_long(&s, hc->end_byte_loc - hc->init_byte_loc + 1); ++ smart_str_appends(&s, "\r\n"); ++ + } + else if ( length >= 0 ) + { +- (void) my_snprintf( buf, sizeof(buf), +- "Content-Length: %d\r\n", length ); +- add_response( hc, buf ); ++ smart_str_appends(&s, "Content-Length: "); ++ smart_str_append_long(&s, length); ++ smart_str_appends(&s, "\r\n"); + } ++ else { ++ hc->do_keep_alive = 0; ++ } + if ( extraheads[0] != '\0' ) +- add_response( hc, extraheads ); +- add_response( hc, "\r\n" ); ++ smart_str_appends(&s, extraheads); ++ if (hc->do_keep_alive) { ++ smart_str_appends(&s, "Connection: keep-alive\r\n\r\n" ); ++ } else { ++ smart_str_appends(&s, "Connection: close\r\n\r\n" ); ++ } ++ smart_str_0(&s); ++ ++ if (hc->response) { ++ free(hc->response); ++ } ++ hc->response = s.c; ++ hc->maxresponse = s.a; ++ hc->responselen = s.len; ++ + } + } + +@@ -725,7 +805,7 @@ + { + char defanged_arg[1000], buf[2000]; + +- send_mime( hc, status, title, "", extraheads, "text/html", -1, 0 ); ++ send_mime( hc, status, title, "", extraheads, "text/html", -1, 0, 0, 0 ); + (void) my_snprintf( buf, sizeof(buf), + "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n<BODY BGCOLOR=\"#cc9999\"><H2>%d %s</H2>\n", + status, title, status, title ); +@@ -764,7 +844,7 @@ + char* cp2; + + for ( cp1 = str, cp2 = dfstr; +- *cp1 != '\0' && cp2 - dfstr < dfsize - 1; ++ *cp1 != '\0' && cp2 - dfstr < dfsize - 5; + ++cp1, ++cp2 ) + { + switch ( *cp1 ) +@@ -834,7 +914,7 @@ + fp = fopen( filename, "r" ); + if ( fp == (FILE*) 0 ) + return 0; +- send_mime( hc, status, title, "", extraheads, "text/html", -1, 0 ); ++ send_mime( hc, status, title, "", extraheads, "text/html", -1, 0, 0, 0 ); + for (;;) + { + r = fread( buf, 1, sizeof(buf) - 1, fp ); +@@ -1336,6 +1416,9 @@ + if ( hc->tildemapped ) + return 1; + ++ if ( hc->hostname[0] == '.' || strchr( hc->hostname, '/' ) != (char*) 0 ) ++ return 0; ++ + /* Figure out the host directory. */ + #ifdef VHOST_DIRLEVELS + httpd_realloc_str( +@@ -1436,7 +1519,7 @@ + restlen = strlen( path ); + httpd_realloc_str( &rest, &maxrest, restlen ); + (void) strcpy( rest, path ); +- if ( rest[restlen - 1] == '/' ) ++ if ( restlen > 0 && rest[restlen - 1] == '/' ) + rest[--restlen] = '\0'; /* trim trailing slash */ + if ( ! tildemapped ) + /* Remove any leading slashes. */ +@@ -1603,6 +1686,70 @@ + + + int ++httpd_request_reset(httpd_conn* hc, int preserve_read_buf ) ++{ ++ if (!preserve_read_buf) { ++ hc->read_idx = 0; ++ hc->checked_idx = 0; ++ } ++ ++ if (hc->read_buf_is_mmap) { ++ hc->read_buf_is_mmap = 0; ++ munmap(hc->read_buf, hc->read_size); ++ hc->read_buf = NULL; ++ hc->read_size = 0; ++ httpd_realloc_str( &hc->read_buf, &hc->read_size, 500 ); ++ } ++ hc->checked_state = CHST_FIRSTWORD; ++ hc->method = METHOD_UNKNOWN; ++ hc->status = 0; ++ hc->bytes_to_send = 0; ++ hc->bytes_sent = 0; ++ hc->encodedurl = ""; ++ hc->decodedurl[0] = '\0'; ++ hc->protocol = "UNKNOWN"; ++ hc->origfilename[0] = '\0'; ++ hc->expnfilename[0] = '\0'; ++ hc->encodings[0] = '\0'; ++ hc->pathinfo[0] = '\0'; ++ hc->query[0] = '\0'; ++ hc->referer = ""; ++ hc->useragent = ""; ++ hc->accept[0] = '\0'; ++ hc->accepte[0] = '\0'; ++ hc->acceptl = ""; ++ hc->cookie = ""; ++ hc->contenttype = ""; ++ hc->reqhost[0] = '\0'; ++ hc->hdrhost = ""; ++ hc->hostdir[0] = '\0'; ++ hc->authorization = ""; ++ hc->remoteuser[0] = '\0'; ++ hc->response[0] = '\0'; ++#ifdef TILDE_MAP_2 ++ hc->altdir[0] = '\0'; ++#endif /* TILDE_MAP_2 */ ++ hc->responselen = 0; ++ hc->if_modified_since = (time_t) -1; ++ hc->range_if = (time_t) -1; ++ hc->contentlength = -1; ++ hc->type = ""; ++ hc->hostname = (char*) 0; ++ hc->mime_flag = 1; ++ hc->one_one = 0; ++ hc->got_range = 0; ++ hc->tildemapped = 0; ++ hc->init_byte_loc = 0; ++ hc->end_byte_loc = -1; ++ hc->keep_alive = 0; ++ hc->do_keep_alive = 0; ++ hc->should_linger = 0; ++ hc->file_address = (char*) 0; ++ hc->read_body_into_mem = 0; ++ return GC_OK; ++} ++ ++int + httpd_get_conn( httpd_server* hs, int listen_fd, httpd_conn* hc ) + { + httpd_sockaddr sa; +@@ -1612,6 +1759,7 @@ + { + hc->read_size = 0; + httpd_realloc_str( &hc->read_buf, &hc->read_size, 500 ); ++ hc->read_buf_is_mmap = 0; + hc->maxdecodedurl = + hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings = + hc->maxpathinfo = hc->maxquery = hc->maxaccept = +@@ -1631,12 +1779,19 @@ + httpd_realloc_str( &hc->reqhost, &hc->maxreqhost, 0 ); + httpd_realloc_str( &hc->hostdir, &hc->maxhostdir, 0 ); + httpd_realloc_str( &hc->remoteuser, &hc->maxremoteuser, 0 ); +- httpd_realloc_str( &hc->response, &hc->maxresponse, 0 ); ++ httpd_realloc_str( &hc->response, &hc->maxresponse, 350 ); + #ifdef TILDE_MAP_2 + httpd_realloc_str( &hc->altdir, &hc->maxaltdir, 0 ); + #endif /* TILDE_MAP_2 */ + hc->initialized = 1; + } ++ if (hc->read_buf_is_mmap) { ++ hc->read_buf_is_mmap = 0; ++ munmap(hc->read_buf, hc->read_size); ++ hc->read_buf = NULL; ++ hc->read_size = 0; ++ httpd_realloc_str( &hc->read_buf, &hc->read_size, 500 ); ++ } + + /* Accept the new connection. */ + sz = sizeof(sa); +@@ -1657,53 +1812,12 @@ + hc->hs = hs; + memset( &hc->client_addr, 0, sizeof(hc->client_addr) ); + memcpy( &hc->client_addr, &sa, sockaddr_len( &sa ) ); +- hc->read_idx = 0; +- hc->checked_idx = 0; +- hc->checked_state = CHST_FIRSTWORD; +- hc->method = METHOD_UNKNOWN; +- hc->status = 0; +- hc->bytes_to_send = 0; +- hc->bytes_sent = 0; +- hc->encodedurl = ""; +- hc->decodedurl[0] = '\0'; +- hc->protocol = "UNKNOWN"; +- hc->origfilename[0] = '\0'; +- hc->expnfilename[0] = '\0'; +- hc->encodings[0] = '\0'; +- hc->pathinfo[0] = '\0'; +- hc->query[0] = '\0'; +- hc->referer = ""; +- hc->useragent = ""; +- hc->accept[0] = '\0'; +- hc->accepte[0] = '\0'; +- hc->acceptl = ""; +- hc->cookie = ""; +- hc->contenttype = ""; +- hc->reqhost[0] = '\0'; +- hc->hdrhost = ""; +- hc->hostdir[0] = '\0'; +- hc->authorization = ""; +- hc->remoteuser[0] = '\0'; +- hc->response[0] = '\0'; +-#ifdef TILDE_MAP_2 +- hc->altdir[0] = '\0'; +-#endif /* TILDE_MAP_2 */ +- hc->responselen = 0; +- hc->if_modified_since = (time_t) -1; +- hc->range_if = (time_t) -1; +- hc->contentlength = -1; +- hc->type = ""; +- hc->hostname = (char*) 0; +- hc->mime_flag = 1; +- hc->one_one = 0; +- hc->got_range = 0; +- hc->tildemapped = 0; +- hc->init_byte_loc = 0; +- hc->end_byte_loc = -1; +- hc->keep_alive = 0; +- hc->should_linger = 0; +- hc->file_address = (char*) 0; +- return GC_OK; ++ ++/* ++printf("doing httpd_get_con(%d)\n", hc->conn_fd); ++*/ ++ ++ return httpd_request_reset(hc, 0); + } + + +@@ -1720,6 +1834,9 @@ + { + char c; + ++/* ++printf("**REQUEST [%d]**\n%*.*s\n", hc->conn_fd, hc->read_idx, hc->read_idx, hc->read_buf); ++*/ + for ( ; hc->checked_idx < hc->read_idx; ++hc->checked_idx ) + { + c = hc->read_buf[hc->checked_idx]; +@@ -1912,8 +2029,11 @@ + eol = strpbrk( protocol, " \t\n\r" ); + if ( eol != (char*) 0 ) + *eol = '\0'; +- if ( strcasecmp( protocol, "HTTP/1.0" ) != 0 ) ++ if ( strcasecmp( protocol, "HTTP/1.0" ) != 0 ) { + hc->one_one = 1; ++ hc->keep_alive = 1; ++ hc->do_keep_alive = 1; ++ } + } + } + /* Check for HTTP/1.1 absolute URL. */ +@@ -2129,6 +2249,7 @@ + cp = &buf[11]; + cp += strspn( cp, " \t" ); + if ( strcasecmp( cp, "keep-alive" ) == 0 ) ++ hc->do_keep_alive = 1; + hc->keep_alive = 1; + } + #ifdef LOG_UNKNOWN_HEADERS +@@ -2168,6 +2289,9 @@ + } + } + ++/* ++printf("one_one = %d keep_alive = %d\n", hc->one_one, hc->keep_alive); ++*/ + if ( hc->one_one ) + { + /* Check that HTTP/1.1 requests specify a host, as required. */ +@@ -2177,14 +2301,14 @@ + return -1; + } + +- /* If the client wants to do keep-alives, it might also be doing +- ** pipelining. There's no way for us to tell. Since we don't +- ** implement keep-alives yet, if we close such a connection there +- ** might be unread pipelined requests waiting. So, we have to +- ** do a lingering close. ++ /* ++ ** Disable keep alive support for bad browsers, ++ ** list taken from Apache 1.3.19 + */ +- if ( hc->keep_alive ) +- hc->should_linger = 1; ++ if ( hc->do_keep_alive && ++ ( strstr(hc->useragent, "Mozilla/2") != NULL || ++ strstr(hc->useragent, "MSIE 4.0b2;") != NULL)) ++ hc->do_keep_alive = 0; + } + + /* Ok, the request has been parsed. Now we resolve stuff that +@@ -2349,15 +2473,24 @@ + + + void +-httpd_close_conn( httpd_conn* hc, struct timeval* nowP ) +- { +- make_log_entry( hc, nowP ); ++httpd_complete_request( httpd_conn* hc, struct timeval* nowP) ++{ ++ if (hc->method != METHOD_UNKNOWN) ++ make_log_entry( hc, nowP ); + +- if ( hc->file_address != (char*) 0 ) ++ if ( hc->file_address == (char*) 1 ) ++ { ++ thttpd_closed_conn(hc->conn_fd); ++ } else if ( hc->file_address != (char*) 0 ) + { + mmc_unmap( hc->file_address, &(hc->sb), nowP ); + hc->file_address = (char*) 0; + } ++ } ++ ++void ++httpd_close_conn( httpd_conn* hc, struct timeval* nowP ) ++{ + if ( hc->conn_fd >= 0 ) + { + (void) close( hc->conn_fd ); +@@ -2370,7 +2503,12 @@ + { + if ( hc->initialized ) + { +- free( (void*) hc->read_buf ); ++ ++ if ( hc->read_buf_is_mmap ) { ++ munmap( hc->read_buf, hc->read_size ); ++ } else { ++ free( (void*) hc->read_buf ); ++ } + free( (void*) hc->decodedurl ); + free( (void*) hc->origfilename ); + free( (void*) hc->expnfilename ); +@@ -2556,7 +2694,7 @@ + return -1; + } + +- send_mime( hc, 200, ok200title, "", "", "text/html", -1, hc->sb.st_mtime ); ++ send_mime( hc, 200, ok200title, "", "", "text/html", -1, hc->sb.st_mtime, 0, 0 ); + if ( hc->method == METHOD_HEAD ) + closedir( dirp ); + else if ( hc->method == METHOD_GET ) +@@ -3026,11 +3164,9 @@ + post_post_garbage_hack( httpd_conn* hc ) + { + char buf[2]; +- int r; + +- r = recv( hc->conn_fd, buf, sizeof(buf), MSG_PEEK ); +- if ( r > 0 ) +- (void) read( hc->conn_fd, buf, r ); ++ fcntl(hc->conn_fd, F_SETFL, O_NONBLOCK); ++ (void) read( hc->conn_fd, buf, 2 ); + } + + +@@ -3313,6 +3449,11 @@ + int r; + ClientData client_data; + ++ /* ++ ** We are not going to leave the socket open after a CGI... too hard ++ */ ++ hc->do_keep_alive = 0; ++ + if ( hc->method == METHOD_GET || hc->method == METHOD_POST ) + { + httpd_clear_ndelay( hc->conn_fd ); +@@ -3369,6 +3510,7 @@ + int expnlen, indxlen; + char* cp; + char* pi; ++ int nocache = 0; + + expnlen = strlen( hc->expnfilename ); + +@@ -3561,6 +3703,16 @@ + match( hc->hs->cgi_pattern, hc->expnfilename ) ) + return cgi( hc ); + ++ if ( hc->hs->php_pattern != (char*) 0 && ++ match( hc->hs->php_pattern, hc->expnfilename)) { ++ return thttpd_php_request( hc, 0 ); ++ } ++ ++ if ( hc->hs->phps_pattern != (char*) 0 && ++ match( hc->hs->phps_pattern, hc->expnfilename)) { ++ return thttpd_php_request( hc, 1 ); ++ } ++ + /* It's not CGI. If it's executable or there's pathinfo, someone's + ** trying to either serve or run a non-CGI file as CGI. Either case + ** is prohibited. +@@ -3594,32 +3746,46 @@ + hc->end_byte_loc = hc->sb.st_size - 1; + + figure_mime( hc ); ++ if ( strncmp(hc->decodedurl, "/nocache/", sizeof("/nocache/") - 1 ) == 0 ) ++ nocache = 1; + + if ( hc->method == METHOD_HEAD ) + { + send_mime( + hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size, +- hc->sb.st_mtime ); ++ hc->sb.st_mtime, 0, 0 ); + } +- else if ( hc->if_modified_since != (time_t) -1 && ++ else if ( !nocache && hc->if_modified_since != (time_t) -1 && + hc->if_modified_since >= hc->sb.st_mtime ) + { +- hc->method = METHOD_HEAD; + send_mime( +- hc, 304, err304title, hc->encodings, "", hc->type, hc->sb.st_size, +- hc->sb.st_mtime ); ++ hc, 304, err304title, hc->encodings, "", hc->type, -1, ++ hc->sb.st_mtime, 0, 0 ); + } + else + { +- hc->file_address = mmc_map( hc->expnfilename, &(hc->sb), nowP ); ++ char *extraheads = ""; ++ char *lm; ++ size_t lml; ++ ++ if ( nocache ) ++ { ++ extraheads = "Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n" ++ "Cache-Control: no-store, no-cache, must-revalidate, " ++ "post-check=0, pre-check=0\r\n" ++ "Pragma: no-cache\r\n"; ++ } ++ ++ hc->file_address = mmc_map( hc->expnfilename, &(hc->sb), nowP, nocache, &lm, &lml ); + if ( hc->file_address == (char*) 0 ) + { + httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl ); + return -1; + } ++ + send_mime( +- hc, 200, ok200title, hc->encodings, "", hc->type, hc->sb.st_size, +- hc->sb.st_mtime ); ++ hc, 200, ok200title, hc->encodings, extraheads, hc->type, hc->sb.st_size, ++ hc->sb.st_mtime, lm, lml ); + } + + return 0; +@@ -3638,6 +3804,9 @@ + return r; + } + ++#define smart_str_append_const(a,b) smart_str_appendl(a,b,sizeof(b)-1) ++ ++static smart_str bentries; + + static void + make_log_entry( httpd_conn* hc, struct timeval* nowP ) +@@ -3648,88 +3817,62 @@ + + if ( hc->hs->no_log ) + return; +- +- /* This is straight CERN Combined Log Format - the only tweak +- ** being that if we're using syslog() we leave out the date, because +- ** syslogd puts it in. The included syslogtocern script turns the +- ** results into true CERN format. +- */ +- + /* Format remote user. */ + if ( hc->remoteuser[0] != '\0' ) +- ru = hc->remoteuser; ++ ru = hc->remoteuser; + else +- ru = "-"; ++ ru = "-"; + /* If we're vhosting, prepend the hostname to the url. This is + ** a little weird, perhaps writing separate log files for + ** each vhost would make more sense. + */ +- if ( hc->hs->vhost && ! hc->tildemapped ) +- (void) my_snprintf( url, sizeof(url), +- "/%.100s%.200s", +- hc->hostname == (char*) 0 ? hc->hs->server_hostname : hc->hostname, +- hc->encodedurl ); +- else +- (void) my_snprintf( url, sizeof(url), +- "%.200s", hc->encodedurl ); +- /* Format the bytes. */ +- if ( (long) hc->bytes_sent >= 0 ) +- (void) my_snprintf( bytes, sizeof(bytes), +- "%ld", (long) hc->bytes_sent ); +- else +- (void) strcpy( bytes, "-" ); + + /* Logfile or syslog? */ + if ( hc->hs->logfp != (FILE*) 0 ) +- { +- time_t now; +- struct tm* t; +- const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; +- char date_nozone[100]; +- int zone; +- char sign; +- char date[100]; +- +- /* Get the current time, if necessary. */ +- if ( nowP != (struct timeval*) 0 ) +- now = nowP->tv_sec; +- else +- now = time( (time_t*) 0 ); +- /* Format the time, forcing a numeric timezone (some log analyzers +- ** are stoooopid about this). +- */ +- t = localtime( &now ); +- (void) strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); +-#ifdef HAVE_TM_GMTOFF +- zone = t->tm_gmtoff / 60L; +-#else +- zone = -timezone / 60L; +- /* Probably have to add something about daylight time here. */ +-#endif +- if ( zone >= 0 ) +- sign = '+'; +- else +- { +- sign = '-'; +- zone = -zone; +- } +- zone = ( zone / 60 ) * 100 + zone % 60; +- (void) my_snprintf( date, sizeof(date), +- "%s %c%04d", date_nozone, sign, zone ); +- /* And write the log entry. */ +- (void) fprintf( hc->hs->logfp, +- "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.80s\"\n", +- httpd_ntoa( &hc->client_addr ), ru, date, +- httpd_method_str( hc->method ), url, hc->protocol, +- hc->status, bytes, hc->referer, hc->useragent ); +- (void) fflush( hc->hs->logfp ); /* don't need to flush every time */ +- } +- else +- syslog( LOG_INFO, +- "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.80s\"", +- httpd_ntoa( &hc->client_addr ), ru, +- httpd_method_str( hc->method ), url, hc->protocol, +- hc->status, bytes, hc->referer, hc->useragent ); ++ { ++ /* XXXXXXX */ ++ ++ smart_str_appends(&bentries, httpd_ntoa(&hc->client_addr)); ++ smart_str_append_const(&bentries, " - "); ++ smart_str_appends(&bentries, ru); ++ smart_str_append_const(&bentries, " ["); ++ smart_str_appendl(&bentries, hc->hs->log_date, hc->hs->log_date_len); ++ smart_str_append_const(&bentries, "] \""); ++ smart_str_appends(&bentries, httpd_method_str(hc->method)); ++ smart_str_appendc(&bentries, ' '); ++ ++ if (hc->hs->vhost && ! hc->tildemapped) { ++ smart_str_appendc(&bentries, '/'); ++ if (hc->hostname) ++ smart_str_appends(&bentries, hc->hostname); ++ else ++ smart_str_appends(&bentries, hc->hs->server_hostname); ++ } ++ smart_str_appends(&bentries, hc->encodedurl); ++ ++ smart_str_appendc(&bentries, ' '); ++ smart_str_appends(&bentries, hc->protocol); ++ smart_str_append_const(&bentries, "\" "); ++ smart_str_append_long(&bentries, hc->status); ++ if (hc->bytes_sent >= 0) { ++ smart_str_appendc(&bentries, ' '); ++ smart_str_append_long(&bentries, hc->bytes_sent); ++ smart_str_append_const(&bentries, " \""); ++ } else { ++ smart_str_append_const(&bentries, " - \""); ++ } ++ smart_str_appends(&bentries, hc->referer); ++ smart_str_append_const(&bentries, "\" \""); ++ smart_str_appends(&bentries, hc->useragent); ++ smart_str_append_const(&bentries, "\"\n"); ++ ++ if (bentries.len > 16384) { ++ int fd = fileno(hc->hs->logfp); ++ write(fd, bentries.c, bentries.len); ++ bentries.len = 0; ++ } ++ } ++ + } + + +@@ -3840,7 +3983,24 @@ + { + #ifdef HAVE_GETNAMEINFO + static char str[200]; ++ static smart_str httpd_ntoa_buf; ++ ++ if (saP->sa_in.sin_family == AF_INET) { ++ unsigned long n = ntohl(saP->sa_in.sin_addr.s_addr); + ++ httpd_ntoa_buf.len = 0; ++ smart_str_append_long(&httpd_ntoa_buf, (n >> 24)); ++ smart_str_appendc(&httpd_ntoa_buf, '.'); ++ smart_str_append_long(&httpd_ntoa_buf, (n >> 16) & 255); ++ smart_str_appendc(&httpd_ntoa_buf, '.'); ++ smart_str_append_long(&httpd_ntoa_buf, (n >> 8) & 255); ++ smart_str_appendc(&httpd_ntoa_buf, '.'); ++ smart_str_append_long(&httpd_ntoa_buf, (n >> 0) & 255); ++ smart_str_0(&httpd_ntoa_buf); ++ ++ return httpd_ntoa_buf.c; ++ } ++ + if ( getnameinfo( &saP->sa, sockaddr_len( saP ), str, sizeof(str), 0, 0, NI_NUMERICHOST ) != 0 ) + { + str[0] = '?'; +diff -ur thttpd-2.21b/libhttpd.h thttpd-2.21b-cool/libhttpd.h +--- thttpd-2.21b/libhttpd.h Tue Apr 24 00:36:50 2001 ++++ thttpd-2.21b-cool/libhttpd.h Sat Sep 20 14:43:20 2003 +@@ -69,6 +69,8 @@ + char* server_hostname; + int port; + char* cgi_pattern; ++ char* php_pattern; ++ char* phps_pattern; + char* charset; + char* cwd; + int listen4_fd, listen6_fd; +@@ -80,6 +82,8 @@ + char* url_pattern; + char* local_pattern; + int no_empty_referers; ++ size_t log_date_len; ++ char log_date[100]; + } httpd_server; + + /* A connection. */ +@@ -88,6 +92,7 @@ + httpd_server* hs; + httpd_sockaddr client_addr; + char* read_buf; ++ char read_buf_is_mmap; + int read_size, read_idx, checked_idx; + int checked_state; + int method; +@@ -132,11 +137,12 @@ + int got_range; + int tildemapped; /* this connection got tilde-mapped */ + off_t init_byte_loc, end_byte_loc; +- int keep_alive; ++ int keep_alive, do_keep_alive; + int should_linger; + struct stat sb; + int conn_fd; + char* file_address; ++ char read_body_into_mem; + } httpd_conn; + + /* Methods. */ +@@ -168,7 +174,8 @@ + char* hostname, httpd_sockaddr* sa4P, httpd_sockaddr* sa6P, int port, + char* cgi_pattern, char* charset, char* cwd, int no_log, FILE* logfp, + int no_symlink, int vhost, int global_passwd, char* url_pattern, +- char* local_pattern, int no_empty_referers ); ++ char* local_pattern, int no_empty_referers, char* php_pattern, ++ char* phps_pattern ); + + /* Change the log file. */ + extern void httpd_set_logfp( httpd_server* hs, FILE* logfp ); +@@ -229,6 +236,8 @@ + ** If you don't have a current timeval handy just pass in 0. + */ + extern void httpd_close_conn( httpd_conn* hc, struct timeval* nowP ); ++void httpd_complete_request( httpd_conn* hc, struct timeval* nowP); ++int httpd_request_reset(httpd_conn* hc,int ); + + /* Call this to de-initialize a connection struct and *really* free the + ** mallocced strings. +diff -ur thttpd-2.21b/mime_encodings.txt thttpd-2.21b-cool/mime_encodings.txt +--- thttpd-2.21b/mime_encodings.txt Wed May 10 03:22:28 2000 ++++ thttpd-2.21b-cool/mime_encodings.txt Sat Sep 20 14:43:20 2003 +@@ -3,6 +3,6 @@ + # A list of file extensions followed by the corresponding MIME encoding. + # Extensions not found in the table proceed to the mime_types table. + +-Z x-compress +-gz x-gzip ++Z compress ++gz gzip + uu x-uuencode +diff -ur thttpd-2.21b/mime_types.txt thttpd-2.21b-cool/mime_types.txt +--- thttpd-2.21b/mime_types.txt Sat Apr 14 04:53:30 2001 ++++ thttpd-2.21b-cool/mime_types.txt Sat Sep 20 14:43:20 2003 +@@ -1,135 +1,138 @@ +-# mime_types.txt +-# +-# A list of file extensions followed by the corresponding MIME type. +-# Extensions not found in the table are returned as text/plain. +- +-html text/html; charset=%s +-htm text/html; charset=%s +-txt text/plain; charset=%s +-rtx text/richtext +-etx text/x-setext +-tsv text/tab-separated-values +-css text/css +-xml text/xml +-dtd text/xml +- +-gif image/gif +-jpg image/jpeg +-jpeg image/jpeg +-jpe image/jpeg +-jfif image/jpeg +-tif image/tiff +-tiff image/tiff +-pbm image/x-portable-bitmap +-pgm image/x-portable-graymap +-ppm image/x-portable-pixmap +-pnm image/x-portable-anymap +-xbm image/x-xbitmap +-xpm image/x-xpixmap +-xwd image/x-xwindowdump +-ief image/ief +-png image/png +- +-au audio/basic +-snd audio/basic +-aif audio/x-aiff +-aiff audio/x-aiff +-aifc audio/x-aiff +-ra audio/x-pn-realaudio +-ram audio/x-pn-realaudio +-rm audio/x-pn-realaudio +-rpm audio/x-pn-realaudio-plugin +-wav audio/wav +-mid audio/midi +-midi audio/midi +-kar audio/midi +-mpga audio/mpeg +-mp2 audio/mpeg +-mp3 audio/mpeg +- +-mpeg video/mpeg +-mpg video/mpeg +-mpe video/mpeg +-qt video/quicktime +-mov video/quicktime +-avi video/x-msvideo +-movie video/x-sgi-movie +-mv video/x-sgi-movie +-vx video/x-rad-screenplay +- +-a application/octet-stream ++ez application/andrew-inset ++hqx application/mac-binhex40 ++cpt application/mac-compactpro ++doc application/msword + bin application/octet-stream ++dms application/octet-stream ++lha application/octet-stream ++lzh application/octet-stream + exe application/octet-stream +-dump application/octet-stream +-o application/octet-stream +-class application/java +-js application/x-javascript ++class application/octet-stream ++so application/octet-stream ++dll application/octet-stream ++oda application/oda ++pdf application/pdf + ai application/postscript + eps application/postscript + ps application/postscript +-dir application/x-director ++smi application/smil ++smil application/smil ++mif application/vnd.mif ++xls application/vnd.ms-excel ++ppt application/vnd.ms-powerpoint ++wbxml application/vnd.wap.wbxml ++wmlc application/vnd.wap.wmlc ++wmlsc application/vnd.wap.wmlscriptc ++bcpio application/x-bcpio ++vcd application/x-cdlink ++pgn application/x-chess-pgn ++cpio application/x-cpio ++csh application/x-csh + dcr application/x-director ++dir application/x-director + dxr application/x-director +-fgd application/x-director +-aam application/x-authorware-map +-aas application/x-authorware-seg +-aab application/x-authorware-bin +-fh4 image/x-freehand +-fh7 image/x-freehand +-fh5 image/x-freehand +-fhc image/x-freehand +-fh image/x-freehand +-spl application/futuresplash +-swf application/x-shockwave-flash + dvi application/x-dvi ++spl application/x-futuresplash + gtar application/x-gtar + hdf application/x-hdf +-hqx application/mac-binhex40 +-iv application/x-inventor ++js application/x-javascript ++skp application/x-koan ++skd application/x-koan ++skt application/x-koan ++skm application/x-koan + latex application/x-latex +-man application/x-troff-man +-me application/x-troff-me +-mif application/x-mif +-ms application/x-troff-ms +-oda application/oda +-pdf application/pdf +-rtf application/rtf +-bcpio application/x-bcpio +-cpio application/x-cpio +-sv4cpio application/x-sv4cpio +-sv4crc application/x-sv4crc +-sh application/x-shar ++nc application/x-netcdf ++cdf application/x-netcdf ++sh application/x-sh + shar application/x-shar ++swf application/x-shockwave-flash + sit application/x-stuffit ++sv4cpio application/x-sv4cpio ++sv4crc application/x-sv4crc + tar application/x-tar ++tcl application/x-tcl + tex application/x-tex +-texi application/x-texinfo + texinfo application/x-texinfo ++texi application/x-texinfo ++t application/x-troff + tr application/x-troff + roff application/x-troff + man application/x-troff-man + me application/x-troff-me + ms application/x-troff-ms +-zip application/x-zip-compressed +-tsp application/dsptype +-wsrc application/x-wais-source + ustar application/x-ustar +-cdf application/x-netcdf +-nc application/x-netcdf +-doc application/msword +-ppt application/powerpoint +- +-crt application/x-x509-ca-cert +-crl application/x-pkcs7-crl +- ++src application/x-wais-source ++xhtml application/xhtml+xml ++xht application/xhtml+xml ++zip application/zip ++au audio/basic ++snd audio/basic ++mid audio/midi ++midi audio/midi ++kar audio/midi ++mpga audio/mpeg ++mp2 audio/mpeg ++mp3 audio/mpeg ++aif audio/x-aiff ++aiff audio/x-aiff ++aifc audio/x-aiff ++m3u audio/x-mpegurl ++ram audio/x-pn-realaudio ++rm audio/x-pn-realaudio ++rpm audio/x-pn-realaudio-plugin ++ra audio/x-realaudio ++wav audio/x-wav ++pdb chemical/x-pdb ++xyz chemical/x-xyz ++bmp image/bmp ++gif image/gif ++ief image/ief ++jpeg image/jpeg ++jpg image/jpeg ++jpe image/jpeg ++png image/png ++tiff image/tiff ++tif image/tiff ++djvu image/vnd.djvu ++djv image/vnd.djvu ++wbmp image/vnd.wap.wbmp ++ras image/x-cmu-raster ++pnm image/x-portable-anymap ++pbm image/x-portable-bitmap ++pgm image/x-portable-graymap ++ppm image/x-portable-pixmap ++rgb image/x-rgb ++xbm image/x-xbitmap ++xpm image/x-xpixmap ++xwd image/x-xwindowdump ++igs model/iges ++iges model/iges ++msh model/mesh ++mesh model/mesh ++silo model/mesh + wrl model/vrml + vrml model/vrml +-mime message/rfc822 +- +-pac application/x-ns-proxy-autoconfig +- ++css text/css ++html text/html; charset=%s ++htm text/html; charset=%s ++asc text/plain; charset=%s ++txt text/plain; charset=%s ++rtx text/richtext ++rtf text/rtf ++sgml text/sgml ++sgm text/sgml ++tsv text/tab-separated-values + wml text/vnd.wap.wml +-wmlc application/vnd.wap.wmlc + wmls text/vnd.wap.wmlscript +-wmlsc application/vnd.wap.wmlscriptc +-wbmp image/vnd.wap.wbmp ++etx text/x-setext ++xml text/xml ++xsl text/xml ++mpeg video/mpeg ++mpg video/mpeg ++mpe video/mpeg ++qt video/quicktime ++mov video/quicktime ++mxu video/vnd.mpegurl ++avi video/x-msvideo ++movie video/x-sgi-movie ++ice x-conference/x-cooltalk +diff -ur thttpd-2.21b/mmc.c thttpd-2.21b-cool/mmc.c +--- thttpd-2.21b/mmc.c Fri Apr 13 23:02:15 2001 ++++ thttpd-2.21b-cool/mmc.c Sat Sep 20 14:43:20 2003 +@@ -70,6 +70,9 @@ + unsigned int hash; + int hash_idx; + struct MapStruct* next; ++ char nocache; ++ size_t last_modified_len; ++ char last_modified[100]; + } Map; + + +@@ -93,12 +96,13 @@ + + + void* +-mmc_map( char* filename, struct stat* sbP, struct timeval* nowP ) ++mmc_map( char* filename, struct stat* sbP, struct timeval* nowP, int nocache, char **last_modified, size_t *last_modified_len ) + { + time_t now; + struct stat sb; + Map* m; + int fd; ++ const char* rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; + + /* Stat the file, if necessary. */ + if ( sbP != (struct stat*) 0 ) +@@ -130,7 +134,7 @@ + /* Yep. Just return the existing map */ + ++m->refcount; + m->reftime = now; +- return m->addr; ++ goto done; + } + + /* Open the file. */ +@@ -167,12 +171,13 @@ + m->ctime = sb.st_ctime; + m->refcount = 1; + m->reftime = now; ++ m->nocache = (char) nocache; + + /* Avoid doing anything for zero-length files; some systems don't like + ** to mmap them, other systems dislike mallocing zero bytes. + */ + if ( m->size == 0 ) +- m->addr = (void*) 1; /* arbitrary non-NULL address */ ++ m->addr = (void*) 5; /* arbitrary non-NULL address */ + else + { + #ifdef HAVE_MMAP +@@ -223,6 +228,13 @@ + maps = m; + ++map_count; + ++ strftime( m->last_modified, sizeof(m->last_modified), rfc1123fmt, gmtime( &sb.st_mtime ) ); ++ m->last_modified_len = strlen(m->last_modified); ++ ++done: ++ *last_modified = m->last_modified; ++ *last_modified_len = m->last_modified_len; ++ + /* And return the address. */ + return m->addr; + } +@@ -231,27 +243,32 @@ + void + mmc_unmap( void* addr, struct stat* sbP, struct timeval* nowP ) + { +- Map* m = (Map*) 0; ++ Map* m = (Map*) 0, **mm = &maps; + + /* Find the Map entry for this address. First try a hash. */ + if ( sbP != (struct stat*) 0 ) + { + m = find_hash( sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime ); +- if ( m != (Map*) 0 && m->addr != addr ) ++ if ( m != (Map*) 0 && ( m->addr != addr || m->nocache == 1 ) ) + m = (Map*) 0; + } + /* If that didn't work, try a full search. */ + if ( m == (Map*) 0 ) +- for ( m = maps; m != (Map*) 0; m = m->next ) ++ for ( m = maps; m != (Map*) 0; m = m->next ) { + if ( m->addr == addr ) + break; ++ mm = &m->next; ++ } + if ( m == (Map*) 0 ) + syslog( LOG_ERR, "mmc_unmap failed to find entry!" ); + else if ( m->refcount <= 0 ) + syslog( LOG_ERR, "mmc_unmap found zero or negative refcount!" ); + else + { +- --m->refcount; ++ if ( --m->refcount == 0 && m->nocache == 1 ) { ++ really_unmap( mm ); ++ return; ++ } + if ( nowP != (struct timeval*) 0 ) + m->reftime = nowP->tv_sec; + else +diff -ur thttpd-2.21b/mmc.h thttpd-2.21b-cool/mmc.h +--- thttpd-2.21b/mmc.h Fri Apr 13 07:36:54 2001 ++++ thttpd-2.21b-cool/mmc.h Sat Sep 20 14:43:20 2003 +@@ -31,8 +31,9 @@ + /* Returns an mmap()ed area for the given file, or (void*) 0 on errors. + ** If you have a stat buffer on the file, pass it in, otherwise pass 0. + ** Same for the current time. ++** Set nocache to 1, if this entry is supposed to be removed quickly. + */ +-extern void* mmc_map( char* filename, struct stat* sbP, struct timeval* nowP ); ++extern void* mmc_map( char* filename, struct stat* sbP, struct timeval* nowP, int nocache, char **last_modified, size_t *last_modified_len ); + + /* Done with an mmap()ed area that was returned by mmc_map(). + ** If you have a stat buffer on the file, pass it in, otherwise pass 0. +diff -ur thttpd-2.21b/thttpd.c thttpd-2.21b-cool/thttpd.c +--- thttpd-2.21b/thttpd.c Tue Apr 24 00:41:57 2001 ++++ thttpd-2.21b-cool/thttpd.c Sat Sep 20 14:43:20 2003 +@@ -53,6 +53,10 @@ + #endif + #include <unistd.h> + ++#include <sys/mman.h> ++ ++#include <limits.h> ++ + #include "fdwatch.h" + #include "libhttpd.h" + #include "mmc.h" +@@ -66,6 +70,8 @@ + static char* dir; + static int do_chroot, no_log, no_symlink, do_vhost, do_global_passwd; + static char* cgi_pattern; ++static char* php_pattern; ++static char* phps_pattern; + static char* url_pattern; + static int no_empty_referers; + static char* local_pattern; +@@ -95,10 +101,10 @@ + httpd_conn* hc; + int tnums[MAXTHROTTLENUMS]; /* throttle indexes */ + int numtnums; ++ int keep_alive; + long limit; + time_t started_at; +- Timer* idle_read_timer; +- Timer* idle_send_timer; ++ time_t last_io; + Timer* wakeup_timer; + Timer* linger_timer; + long wouldblock_delay; +@@ -106,17 +112,22 @@ + off_t bytes_sent; + off_t bytes_to_send; + } connecttab; +-static connecttab* connects; ++static connecttab* connects, **free_connects; ++static int next_free_connect; + static int numconnects, maxconnects; + static int httpd_conn_count; + + /* The connection states. */ +-#define CNST_FREE 0 +-#define CNST_READING 1 +-#define CNST_SENDING 2 +-#define CNST_PAUSING 3 +-#define CNST_LINGERING 4 +- ++enum { ++ CNST_FREE = 0, ++ CNST_READING, ++ CNST_SENDING, ++ CNST_PAUSING, ++ CNST_LINGERING, ++ CNST_SENDING_RESP, ++ CNST_READING_BODY, ++ CNST_TOTAL_NR ++}; + + static httpd_server* hs = (httpd_server*) 0; + int terminate = 0; +@@ -140,23 +151,32 @@ + static int handle_newconnect( struct timeval* tvP, int listen_fd ); + static void handle_read( connecttab* c, struct timeval* tvP ); + static void handle_send( connecttab* c, struct timeval* tvP ); ++static void handle_send_resp( connecttab* c, struct timeval* tvP ); ++static void handle_read_body( connecttab* c, struct timeval* tvP ); + static void handle_linger( connecttab* c, struct timeval* tvP ); + static int check_throttles( connecttab* c ); ++static void timeout_conns( ClientData client_data, struct timeval* nowP ); + static void clear_throttles( connecttab* c, struct timeval* tvP ); + static void update_throttles( ClientData client_data, struct timeval* nowP ); +-static void clear_connection( connecttab* c, struct timeval* tvP ); ++static void clear_connection( connecttab* c, struct timeval* tvP, int ); + static void really_clear_connection( connecttab* c, struct timeval* tvP ); +-static void idle_read_connection( ClientData client_data, struct timeval* nowP ); +-static void idle_send_connection( ClientData client_data, struct timeval* nowP ); + static void wakeup_connection( ClientData client_data, struct timeval* nowP ); + static void linger_clear_connection( ClientData client_data, struct timeval* nowP ); + static void occasional( ClientData client_data, struct timeval* nowP ); ++static void periodic_jobs( ClientData client_data, struct timeval* nowP ); + #ifdef STATS_TIME + static void show_stats( ClientData client_data, struct timeval* nowP ); + #endif /* STATS_TIME */ + static void logstats( struct timeval* nowP ); + static void thttpd_logstats( long secs ); ++static void boot_request(connecttab *c, struct timeval *tvP); ++ ++typedef void (*handler_func)(connecttab*, struct timeval *); ++ ++handler_func handler_array[CNST_TOTAL_NR] = ++{NULL, handle_read, handle_send, NULL, handle_linger, handle_send_resp, handle_read_body}; + ++#define RUN_HANDLER(type, c) if (handler_array[type]) handler_array[type](c, &tv) + + static void + handle_term( int sig ) +@@ -177,7 +197,7 @@ + return; + + /* Re-open the log file. */ +- if ( logfile != (char*) 0 ) ++ if ( logfile != (char*) 0 && strcmp(logfile, "-") != 0) + { + logfp = fopen( logfile, "a" ); + if ( logfp == (FILE*) 0 ) +@@ -198,6 +218,8 @@ + } + + ++time_t httpd_time_now; ++ + static void + handle_usr2( int sig ) + { +@@ -217,7 +239,6 @@ + int num_ready; + int cnum, ridx; + connecttab* c; +- httpd_conn* hc; + httpd_sockaddr sa4; + httpd_sockaddr sa6; + int gotv4, gotv6; +@@ -270,7 +291,9 @@ + no_log = 1; + logfp = (FILE*) 0; + } +- else ++ else if (strcmp(logfile, "-") == 0) { ++ logfp = stdout; ++ } else + { + logfp = fopen( logfile, "a" ); + if ( logfp == (FILE*) 0 ) +@@ -420,7 +443,8 @@ + hostname, + gotv4 ? &sa4 : (httpd_sockaddr*) 0, gotv6 ? &sa6 : (httpd_sockaddr*) 0, + port, cgi_pattern, charset, cwd, no_log, logfp, no_symlink, do_vhost, +- do_global_passwd, url_pattern, local_pattern, no_empty_referers ); ++ do_global_passwd, url_pattern, local_pattern, no_empty_referers, ++ php_pattern, phps_pattern); + if ( hs == (httpd_server*) 0 ) + exit( 1 ); + +@@ -430,6 +454,12 @@ + syslog( LOG_CRIT, "tmr_create(occasional) failed" ); + exit( 1 ); + } ++ ++ if (tmr_create(0, timeout_conns, JunkClientData, 30 * 1000, 1) == 0) { ++ syslog(LOG_CRIT, "tmr_create(timeout_conns) failed"); ++ exit(1); ++ } ++ + if ( numthrottles > 0 ) + { + /* Set up the throttles timer. */ +@@ -439,6 +469,12 @@ + exit( 1 ); + } + } ++ ++ if (tmr_create(0, periodic_jobs, JunkClientData, 2000, 1) == 0) { ++ syslog(LOG_CRIT, "tmr_create failed"); ++ exit(1); ++ } ++ + #ifdef STATS_TIME + /* Set up the stats timer. */ + if ( tmr_create( (struct timeval*) 0, show_stats, JunkClientData, STATS_TIME * 1000L, 1 ) == (Timer*) 0 ) +@@ -454,12 +490,14 @@ + /* If we're root, try to become someone else. */ + if ( getuid() == 0 ) + { ++#ifndef __CYGWIN__ + /* Set aux groups to null. */ + if ( setgroups( 0, (const gid_t*) 0 ) < 0 ) + { + syslog( LOG_CRIT, "setgroups - %m" ); + exit( 1 ); + } ++#endif + /* Set primary group. */ + if ( setgid( gid ) < 0 ) + { +@@ -495,13 +533,17 @@ + } + maxconnects -= SPARE_FDS; + connects = NEW( connecttab, maxconnects ); ++ free_connects = malloc(sizeof(connecttab *) * maxconnects); ++ next_free_connect = maxconnects; + if ( connects == (connecttab*) 0 ) + { + syslog( LOG_CRIT, "out of memory allocating a connecttab" ); + exit( 1 ); + } ++ + for ( cnum = 0; cnum < maxconnects; ++cnum ) + { ++ free_connects[cnum] = &connects[maxconnects - cnum - 1]; + connects[cnum].conn_state = CNST_FREE; + connects[cnum].hc = (httpd_conn*) 0; + } +@@ -518,6 +560,9 @@ + + /* Main loop. */ + (void) gettimeofday( &tv, (struct timezone*) 0 ); ++ httpd_time_now = tv.tv_sec; ++ periodic_jobs(JunkClientData, &tv); ++ + while ( ( ! terminate ) || numconnects > 0 ) + { + /* Do the fd watch. */ +@@ -530,6 +575,7 @@ + exit( 1 ); + } + (void) gettimeofday( &tv, (struct timezone*) 0 ); ++ httpd_time_now = tv.tv_sec; + if ( num_ready == 0 ) + { + /* No fd's are ready - run the timers. */ +@@ -565,16 +611,10 @@ + c = (connecttab*) fdwatch_get_client_data( ridx ); + if ( c == (connecttab*) 0 ) + continue; +- hc = c->hc; +- if ( c->conn_state == CNST_READING && +- fdwatch_check_fd( hc->conn_fd ) ) +- handle_read( c, &tv ); +- else if ( c->conn_state == CNST_SENDING && +- fdwatch_check_fd( hc->conn_fd ) ) +- handle_send( c, &tv ); +- else if ( c->conn_state == CNST_LINGERING && +- fdwatch_check_fd( hc->conn_fd ) ) +- handle_linger( c, &tv ); ++#if DO_UNNECESSARY_CHECK_FD ++ fdwatch_check_fd(c->hc->conn_fd); ++#endif ++ RUN_HANDLER(c->conn_state, c); + } + tmr_run( &tv ); + +@@ -627,6 +667,8 @@ + #else /* CGI_PATTERN */ + cgi_pattern = (char*) 0; + #endif /* CGI_PATTERN */ ++ php_pattern = "**.php"; ++ phps_pattern = "**.phps"; + url_pattern = (char*) 0; + no_empty_referers = 0; + local_pattern = (char*) 0; +@@ -833,6 +875,16 @@ + value_required( name, value ); + cgi_pattern = e_strdup( value ); + } ++ else if ( strcasecmp( name, "phppat" ) == 0 ) ++ { ++ value_required( name, value ); ++ php_pattern = e_strdup( value ); ++ } ++ else if ( strcasecmp( name, "phpspat" ) == 0 ) ++ { ++ value_required( name, value ); ++ phps_pattern = e_strdup( value ); ++ } + else if ( strcasecmp( name, "urlpat" ) == 0 ) + { + value_required( name, value ); +@@ -1196,8 +1248,10 @@ + logstats( &tv ); + for ( cnum = 0; cnum < maxconnects; ++cnum ) + { +- if ( connects[cnum].conn_state != CNST_FREE ) ++ if ( connects[cnum].conn_state != CNST_FREE ) { ++ httpd_complete_request( connects[cnum].hc, &tv ); + httpd_close_conn( connects[cnum].hc, &tv ); ++ } + if ( connects[cnum].hc != (httpd_conn*) 0 ) + { + httpd_destroy_conn( connects[cnum].hc ); +@@ -1214,6 +1268,7 @@ + } + mmc_destroy(); + tmr_destroy(); ++ free( (void*) free_connects ); + free( (void*) connects ); + if ( throttles != (throttletab*) 0 ) + free( (void*) throttles ); +@@ -1234,7 +1289,7 @@ + for (;;) + { + /* Is there room in the connection table? */ +- if ( numconnects >= maxconnects ) ++ if ( numconnects >= maxconnects || next_free_connect == 0 ) + { + /* Out of connection slots. Run the timers, then the + ** existing connections, and maybe we'll free up a slot +@@ -1245,10 +1300,10 @@ + return 0; + } + /* Find a free connection entry. */ +- for ( cnum = 0; cnum < maxconnects; ++cnum ) +- if ( connects[cnum].conn_state == CNST_FREE ) +- break; +- c = &connects[cnum]; ++ ++ c = free_connects[--next_free_connect]; ++ free_connects[next_free_connect] = NULL; ++ + /* Make the httpd_conn if necessary. */ + if ( c->hc == (httpd_conn*) 0 ) + { +@@ -1267,24 +1322,18 @@ + { + case GC_FAIL: + case GC_NO_MORE: ++ free_connects[next_free_connect++] = c; + return 1; + } + c->conn_state = CNST_READING; + ++numconnects; + client_data.p = c; +- c->idle_read_timer = tmr_create( +- tvP, idle_read_connection, client_data, IDLE_READ_TIMELIMIT * 1000L, +- 0 ); +- if ( c->idle_read_timer == (Timer*) 0 ) +- { +- syslog( LOG_CRIT, "tmr_create(idle_read_connection) failed" ); +- exit( 1 ); +- } +- c->idle_send_timer = (Timer*) 0; + c->wakeup_timer = (Timer*) 0; + c->linger_timer = (Timer*) 0; + c->bytes_sent = 0; + c->numtnums = 0; ++ c->keep_alive = 0; ++ c->last_io = httpd_time_now; + + /* Set the connection file descriptor to no-delay mode. */ + httpd_set_ndelay( c->hc->conn_fd ); +@@ -1298,11 +1347,100 @@ + } + + ++#define FIXUP(x) if (hc->x >= oldptr && hc->x < pe) hc->x += d ++ ++static void ++realign_hc(httpd_conn *hc, char *oldptr) ++{ ++ int d = hc->read_buf - oldptr; ++ char *pe = oldptr + hc->checked_idx; ++ ++ FIXUP(encodedurl); ++ FIXUP(protocol); ++ FIXUP(referer); ++ FIXUP(useragent); ++ FIXUP(acceptl); ++ FIXUP(cookie); ++ FIXUP(contenttype); ++ FIXUP(hdrhost); ++ FIXUP(authorization); ++} ++ ++#undef FIXUP ++ ++static void ++setup_read_body(connecttab *c, struct timeval *tvP) ++{ ++ httpd_conn *hc = c->hc; ++ int already, missing; ++ char *oldptr = hc->read_buf; ++ ++ c->conn_state = CNST_READING_BODY; ++ ++ hc->read_body_into_mem = 0; ++ ++ already = hc->read_idx - hc->checked_idx; ++ missing = hc->contentlength - already; ++ ++ if (missing > 16384) { ++ char filename[] = "/tmp/thttpd.upload.XXXXXX"; ++ int tmp = mkstemp(filename); ++ ++ if (tmp >= 0) { ++ void *p; ++ size_t sz = hc->contentlength + hc->checked_idx + 10; ++ ++ unlink(filename); ++ ++ ftruncate(tmp, sz); ++ p = mmap(NULL, sz, ++ PROT_READ|PROT_WRITE, MAP_PRIVATE, tmp, 0); ++ ++ if (p != MAP_FAILED) { ++ memcpy(p, hc->read_buf, hc->read_idx); ++ free(hc->read_buf); ++ hc->read_size = sz; ++ hc->read_buf = p; ++ hc->read_buf_is_mmap = 1; ++ } ++ close(tmp); ++ } ++ ++ if (!hc->read_buf_is_mmap) { ++ clear_connection( c, tvP, 0 ); ++ return; ++ } ++ } else if (missing > 0) { ++ httpd_realloc_str(&hc->read_buf, &hc->read_size, hc->checked_idx + hc->contentlength + 10); ++ } ++ if (oldptr != hc->read_buf) realign_hc(hc, oldptr); ++ ++ fdwatch_del_fd( hc->conn_fd ); ++ fdwatch_add_fd( hc->conn_fd, c, FDW_READ ); ++} ++ ++static void ++setup_sending(connecttab *c, int state, struct timeval *tvP) ++{ ++ httpd_conn *hc = c->hc; ++ ClientData client_data; ++ ++ c->conn_state = state; ++ c->started_at = tvP->tv_sec; ++ c->wouldblock_delay = 0; ++ client_data.p = c; ++ ++ fdwatch_del_fd( hc->conn_fd ); ++ fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE ); ++} ++ ++static void handle_request( connecttab *c, struct timeval *tvP); ++ ++ + static void + handle_read( connecttab* c, struct timeval* tvP ) + { + int sz; +- ClientData client_data; + httpd_conn* hc = c->hc; + + /* Is there room in our buffer to read more bytes? */ +@@ -1311,7 +1449,7 @@ + if ( hc->read_size > 5000 ) + { + httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } + httpd_realloc_str( +@@ -1327,14 +1465,53 @@ + ** EWOULDBLOCK; however, this apparently can happen if a packet gets + ** garbled. + */ +- if ( sz == 0 || ( sz < 0 && ( errno != EWOULDBLOCK ) ) ) +- { +- httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); +- clear_connection( c, tvP ); ++ if ( sz == 0 ) { ++ if (! c->keep_alive) { ++ httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); ++ } ++ clear_connection( c, tvP, 0 ); ++ return; ++ } else if ( sz < 0 ) { ++ if (errno != EWOULDBLOCK) { ++ clear_connection( c, tvP, 0 ); ++ } + return; ++ } ++ ++ /* If this is a persistent PHP connection, we must not receive ++ ** any further requests on this connection. Some broken HTTP/1.1 ++ ** implementations (e.g. Mozilla 1.0.1) are known to do ++ ** pipelining on a connection, although a prior response included ++ ** Connection: close ++ */ ++ if (c->hc->file_address == (char *) 1) { ++ return; ++ } ++ ++ c->last_io = httpd_time_now; ++ if (sz > 0) hc->read_idx += sz; ++ ++ /* ++ ** if we start getting new data on this socket, "promote" it ++ ** to read timeout ++ */ ++ if ( hc->keep_alive ) { ++ ClientData client_data; ++ ++ ++ client_data.p = c; ++ ++ hc->keep_alive = 0; ++ } ++ handle_request(c, tvP); + } +- hc->read_idx += sz; + ++ ++static void ++handle_request( connecttab *c, struct timeval *tvP) ++{ ++ httpd_conn* hc = c->hc; ++ + /* Do we have a complete request yet? */ + switch ( httpd_got_request( hc ) ) + { +@@ -1342,14 +1519,14 @@ + return; + case GR_BAD_REQUEST: + httpd_send_err( hc, 400, httpd_err400title, "", httpd_err400form, "" ); +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } + + /* Yes. Try parsing and resolving it. */ + if ( httpd_parse_request( hc ) < 0 ) + { +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } + +@@ -1358,18 +1535,28 @@ + { + httpd_send_err( + hc, 503, httpd_err503title, "", httpd_err503form, hc->encodedurl ); +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } ++ boot_request(c, tvP); ++} + ++static void boot_request(connecttab *c, struct timeval *tvP) ++{ ++ httpd_conn *hc = c->hc; + /* Start the connection going. */ + if ( httpd_start_request( hc, tvP ) < 0 ) + { + /* Something went wrong. Close down the connection. */ +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } + ++ if ( hc->read_body_into_mem ) { ++ setup_read_body( c, tvP ); ++ return; ++ } ++ + /* Fill in bytes_to_send. */ + if ( hc->got_range ) + { +@@ -1384,37 +1571,25 @@ + { + /* No file address means someone else is handling it. */ + c->bytes_sent = hc->bytes_sent; +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 1 ); + return; + } ++ if (hc->file_address == (char *) 1) { ++ c->last_io = (time_t) LONG_MAX; ++ c->wouldblock_delay = 0; ++ return; ++ } + if ( c->bytes_sent >= c->bytes_to_send ) + { + /* There's nothing to send. */ +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 1 ); + return; + } + + /* Cool, we have a valid connection and a file to send to it. */ +- c->conn_state = CNST_SENDING; +- c->started_at = tvP->tv_sec; +- c->wouldblock_delay = 0; +- client_data.p = c; +- tmr_cancel( c->idle_read_timer ); +- c->idle_read_timer = (Timer*) 0; +- c->idle_send_timer = tmr_create( +- tvP, idle_send_connection, client_data, IDLE_SEND_TIMELIMIT * 1000L, +- 0 ); +- if ( c->idle_send_timer == (Timer*) 0 ) +- { +- syslog( LOG_CRIT, "tmr_create(idle_send_connection) failed" ); +- exit( 1 ); +- } +- +- fdwatch_del_fd( hc->conn_fd ); +- fdwatch_add_fd( hc->conn_fd, c, FDW_WRITE ); ++ setup_sending(c, CNST_SENDING, tvP); + } + +- + static void + handle_send( connecttab* c, struct timeval* tvP ) + { +@@ -1443,6 +1618,9 @@ + iv[1].iov_base = &(hc->file_address[c->bytes_sent]); + iv[1].iov_len = MIN( c->bytes_to_send - c->bytes_sent, c->limit ); + sz = writev( hc->conn_fd, iv, 2 ); ++/* ++printf("**RESPONSE2 [%d]** len = %d\n%*.*s\n", hc->conn_fd, hc->responselen, hc->responselen, hc->responselen, hc->response); ++*/ + } + + if ( sz == 0 || +@@ -1486,12 +1664,12 @@ + */ + if ( errno != EPIPE && errno != EINVAL && errno != ECONNRESET ) + syslog( LOG_ERR, "write - %m sending %.80s", hc->encodedurl ); +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 0 ); + return; + } + + /* Ok, we wrote something. */ +- tmr_reset( tvP, c->idle_send_timer ); ++ c->last_io = httpd_time_now; + /* Was this a headers + file writev()? */ + if ( hc->responselen > 0 ) + { +@@ -1500,7 +1678,7 @@ + { + /* Yes; move the unwritten part to the front of the buffer. */ + int newlen = hc->responselen - sz; +- (void) memcpy( hc->response, &(hc->response[sz]), newlen ); ++ (void) memmove( hc->response, &(hc->response[sz]), newlen ); + hc->responselen = newlen; + sz = 0; + } +@@ -1519,7 +1697,7 @@ + if ( c->bytes_sent >= c->bytes_to_send ) + { + /* This conection is finished! */ +- clear_connection( c, tvP ); ++ clear_connection( c, tvP, 1 ); + return; + } + +@@ -1560,6 +1738,9 @@ + char buf[1024]; + int r; + ++/* ++printf("*LINGER read\n"); ++*/ + /* In lingering-close mode we just read and ignore bytes. An error + ** or EOF ends things, otherwise we go until a timeout. + */ +@@ -1569,6 +1750,63 @@ + } + + ++static void ++handle_read_body(connecttab *c, struct timeval *tvP) ++{ ++ httpd_conn *hc = c->hc; ++ int n; ++ ++ n = read(hc->conn_fd, hc->read_buf + hc->read_idx, ++ hc->contentlength - (hc->read_idx - hc->checked_idx)); ++ ++ if (n <= 0) { ++ if (errno == EAGAIN) ++ return; ++ clear_connection(c, tvP, 0); ++ return; ++ } ++ ++ c->last_io = httpd_time_now; ++ ++ hc->read_idx += n; ++ ++ if (hc->contentlength == hc->read_idx - hc->checked_idx) { ++ boot_request(c, tvP); ++ return; ++ } ++} ++ ++static void ++handle_send_resp(connecttab *c, struct timeval *tvP) ++{ ++ httpd_conn* hc = c->hc; ++ int n = send(hc->conn_fd, hc->response, hc->responselen, 0); ++ int dokeep = 1; ++ ++ if (n < 0) { ++ if (errno == EAGAIN) ++ return; ++ ++ dokeep = 0; ++ goto clear; ++ } ++ ++ c->last_io = httpd_time_now; ++ ++ if (n == hc->responselen) { ++clear: ++ hc->response = realloc(hc->response, hc->maxresponse + 1); ++ hc->responselen = 0; ++ ++ clear_connection(c, tvP, dokeep); ++ return; ++ } ++ ++ hc->responselen -= n; ++ ++ memmove(hc->response, hc->response + n, hc->responselen); ++} ++ + static int + check_throttles( connecttab* c ) + { +@@ -1635,23 +1873,18 @@ + + + static void +-clear_connection( connecttab* c, struct timeval* tvP ) ++clear_connection( connecttab* c, struct timeval* tvP, int doKeep ) + { + ClientData client_data; ++ int linger; + + /* If we haven't actually sent the buffered response yet, do so now. */ +- httpd_write_response( c->hc ); ++ if (c->hc->responselen && c->conn_state != CNST_SENDING_RESP) { ++ setup_sending(c, CNST_SENDING_RESP, tvP); + +- if ( c->idle_read_timer != (Timer*) 0 ) +- { +- tmr_cancel( c->idle_read_timer ); +- c->idle_read_timer = 0; +- } +- if ( c->idle_send_timer != (Timer*) 0 ) +- { +- tmr_cancel( c->idle_send_timer ); +- c->idle_send_timer = 0; ++ return; + } ++ + if ( c->wakeup_timer != (Timer*) 0 ) + { + tmr_cancel( c->wakeup_timer ); +@@ -1669,13 +1902,36 @@ + ** circumstances that make a lingering close necessary. If the flag + ** isn't set we do the real close now. + */ +- if ( c->hc->should_linger ) ++ ++ if ( c->hc->do_keep_alive && doKeep) + { +- c->conn_state = CNST_LINGERING; ++ httpd_conn *hc = c->hc; ++ c->conn_state = CNST_READING; ++ ++ client_data.p = c; ++ c->bytes_sent = 0; ++ c->numtnums = 0; ++ c->keep_alive = 1; ++ ++ httpd_complete_request( c->hc, tvP ); ++ + fdwatch_del_fd( c->hc->conn_fd ); + fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); ++ ++ httpd_request_reset( c->hc, 1 ); ++ ++ hc->read_idx -= hc->checked_idx; ++ memmove(hc->read_buf, hc->read_buf + hc->checked_idx, hc->read_idx); ++ hc->checked_idx = 0; ++ + /* Make sure we are still in no-delay mode. */ + httpd_set_ndelay( c->hc->conn_fd ); ++ handle_request(c, tvP); ++ } ++ else if ( c->hc->should_linger ) ++ { ++ c->conn_state = CNST_LINGERING; ++ + client_data.p = c; + c->linger_timer = tmr_create( + tvP, linger_clear_connection, client_data, LINGER_TIME * 1000L, 0 ); +@@ -1684,9 +1940,19 @@ + syslog( LOG_CRIT, "tmr_create(linger_clear_connection) failed" ); + exit( 1 ); + } ++ ++ httpd_complete_request( c->hc, tvP ); ++ ++ fdwatch_del_fd( c->hc->conn_fd ); ++ fdwatch_add_fd( c->hc->conn_fd, c, FDW_READ ); ++ /* Make sure we are still in no-delay mode. */ ++ httpd_set_ndelay( c->hc->conn_fd ); + } +- else ++ else ++ { ++ httpd_complete_request( c->hc, tvP ); + really_clear_connection( c, tvP ); ++ } + } + + +@@ -1702,45 +1968,12 @@ + tmr_cancel( c->linger_timer ); + c->linger_timer = 0; + } ++ free_connects[next_free_connect++] = c; + c->conn_state = CNST_FREE; + --numconnects; + } + + +-static void +-idle_read_connection( ClientData client_data, struct timeval* nowP ) +- { +- connecttab* c; +- +- c = (connecttab*) client_data.p; +- c->idle_read_timer = (Timer*) 0; +- if ( c->conn_state != CNST_FREE ) +- { +- syslog( LOG_INFO, +- "%.80s connection timed out reading", +- httpd_ntoa( &c->hc->client_addr ) ); +- httpd_send_err( c->hc, 408, httpd_err408title, "", httpd_err408form, "" ); +- clear_connection( c, nowP ); +- } +- } +- +- +-static void +-idle_send_connection( ClientData client_data, struct timeval* nowP ) +- { +- connecttab* c; +- +- c = (connecttab*) client_data.p; +- c->idle_send_timer = (Timer*) 0; +- if ( c->conn_state != CNST_FREE ) +- { +- syslog( LOG_INFO, +- "%.80s connection timed out sending", +- httpd_ntoa( &c->hc->client_addr ) ); +- clear_connection( c, nowP ); +- } +- } +- + + static void + wakeup_connection( ClientData client_data, struct timeval* nowP ) +@@ -1783,6 +2016,43 @@ + } + #endif /* STATS_TIME */ + ++char httpd_now_buf[100]; ++ ++ ++ ++static void ++periodic_jobs( ClientData client_data, struct timeval* nowP ) ++{ ++ const char* rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; ++ struct tm *t; ++ char date_nozone[100]; ++ const char* cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; ++ char data[100]; ++ int zone; ++ char sign; ++ ++ strftime( httpd_now_buf, sizeof(httpd_now_buf), rfc1123fmt, gmtime( &nowP->tv_sec ) ); ++ ++ t = localtime(&nowP->tv_sec); ++ strftime( date_nozone, sizeof(date_nozone), cernfmt_nozone, t ); ++#ifdef HAVE_TM_GMTOFF ++ zone = t->tm_gmtoff / 60L; ++#else ++ zone = -timezone / 60L; ++ /* Probably have to add something about daylight time here. */ ++#endif ++ if ( zone >= 0 ) ++ sign = '+'; ++ else ++ { ++ sign = '-'; ++ zone = -zone; ++ } ++ zone = ( zone / 60 ) * 100 + zone % 60; ++ hs->log_date_len = sprintf( hs->log_date, "%s %c%04d", date_nozone, sign, ++ zone ); ++} ++ + + /* Generate debugging statistics syslog messages for all packages. */ + static void +@@ -1826,3 +2096,42 @@ + stats_connections = stats_bytes = 0L; + stats_simultaneous = 0; + } ++ ++static void ++timeout_conns(ClientData client_data, struct timeval *nowP) ++{ ++ connecttab *c = connects, *ce = c + maxconnects; ++ time_t now = nowP->tv_sec; ++ int r = 0, w = 0; ++ int checked = 0; ++ ++ while (c < ce) { ++ switch (c->conn_state) { ++ case CNST_SENDING: ++ case CNST_SENDING_RESP: ++ checked++; ++ if ((now - c->last_io) > IDLE_SEND_TIMELIMIT) { ++ clear_connection( c, nowP, 0 ); ++ w++; ++ } ++ break; ++ case CNST_READING: ++ case CNST_READING_BODY: ++ checked++; ++ if ((now - c->last_io) > IDLE_READ_TIMELIMIT) { ++ clear_connection( c, nowP, 0 ); ++ r++; ++ } ++ break; ++ case CNST_FREE: break; ++ default: checked++; break; ++ } ++ c++; ++ if (checked >= numconnects) break; ++ } ++ ++ if (r > 0 || w > 0) { ++ syslog(LOG_INFO, "Expired %d/%d connections in read/write state", r, w); ++ } ++} ++ +diff -ur thttpd-2.21b/version.h thttpd-2.21b-cool/version.h +--- thttpd-2.21b/version.h Tue Apr 24 04:05:23 2001 ++++ thttpd-2.21b-cool/version.h Sat Sep 20 14:43:20 2003 +@@ -3,7 +3,7 @@ + #ifndef _VERSION_H_ + #define _VERSION_H_ + +-#define SERVER_SOFTWARE "thttpd/2.21b 23apr2001" ++#define SERVER_SOFTWARE "thttpd/2.21b PHP/20030920" + #define SERVER_ADDRESS "http://www.acme.com/software/thttpd/" + + #endif /* _VERSION_H_ */ diff --git a/sapi/tux/CREDITS b/sapi/tux/CREDITS new file mode 100644 index 0000000..3b7aa70 --- /dev/null +++ b/sapi/tux/CREDITS @@ -0,0 +1,2 @@ +tux +Sascha Schumann diff --git a/sapi/tux/README b/sapi/tux/README new file mode 100644 index 0000000..92c0211 --- /dev/null +++ b/sapi/tux/README @@ -0,0 +1,86 @@ +README FOR THE TUX MODULE (by Sascha Schumann) +($Date$) + + This is a SAPI module for the TUX web-server by Ingo Molnar. + + The special thing about TUX is that it is integrated into the Linux + kernel and thus provides high-speed serving of static files. + + The web-server provides a user-space API which allows arbitrary + plug-ins to be made available. + + All requests to the PHP userspace module are currently serialized. + + This module is of alpha quality. Due to incomplete APIs, HTTP + authentication and handling of POST requests has not been + implemented yet. + + SECURITY NOTE: PHP will happily run everything under the + web-root through the parser; so be careful what you put + there. + + Note that requests are served in a chroot'ed environment. + The initialization of PHP does not take place in the chroot'ed + environment, so that e.g. /usr/local/lib/php.ini is treated + as usual. + +REQUIRED DOWNLOADS + + 1. TUX + + http://people.redhat.com/~mingo/TUX-patches/QuickStart-TUX.txt + + 2. PHP 4.0.x + + Download: + http://www.php.net/ + + Snapshots from CVS: + http://snaps.php.net/ + + +BUILD INSTRUCTIONS + + 1. Install TUX as outlined in the QuickStart text. + Create /tux-modules where modules will reside. + + 2. Prepare PHP + + $ cd php-* + $ ./configure \ + --with-tux=/tux-modules \ + <further PHP options> + # make install + + You can see the list of valid PHP options by executing + + $ ./configure --help + + 3. Touch a file in your web-root 'php5.tux'. This will + cause requests to '/php5.tux' to be redirected to the + userspace module php5.tux. + + 4. Start TUX with something like + + # tux -d -t 8 -r /www -m /tux-modules php5.tux + + (daemon mode, eight threads, web-root /www, modules in + /tux-modules, load php5.tux) + + BEFORE running this command, the kernel side of TUX has to + be properly setup. + + 5. Try to access + + http://yourserver/php5.tux?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000 + + It should display the PHP credits page. + + To access a script /foo/bar.php, use + + http://yourserver/php5.tux?/foo/bar.php + + Parameters can be appended: + + http://yourserver/php5.tux?/foo/bar.php&var=value + diff --git a/sapi/tux/config.m4 b/sapi/tux/config.m4 new file mode 100644 index 0000000..06788be --- /dev/null +++ b/sapi/tux/config.m4 @@ -0,0 +1,16 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(tux,, +[ --with-tux=MODULEDIR Build PHP as a TUX module (Linux only)], no, no) + +AC_MSG_CHECKING([for TUX]) +if test "$PHP_TUX" != "no"; then + INSTALL_IT="\$(INSTALL) -m 0755 $SAPI_SHARED $PHP_TUX/php5.tux.so" + AC_CHECK_HEADERS(tuxmodule.h,[:],[AC_MSG_ERROR([Cannot find tuxmodule.h])]) + PHP_SELECT_SAPI(tux, shared, php_tux.c) + AC_MSG_RESULT([$PHP_TUX]) +else + AC_MSG_RESULT(no) +fi diff --git a/sapi/tux/php.sym b/sapi/tux/php.sym new file mode 100644 index 0000000..b968c5f --- /dev/null +++ b/sapi/tux/php.sym @@ -0,0 +1,2 @@ +TUXAPI_handle_events +TUXAPI_init diff --git a/sapi/tux/php_tux.c b/sapi/tux/php_tux.c new file mode 100644 index 0000000..968dd9e --- /dev/null +++ b/sapi/tux/php_tux.c @@ -0,0 +1,457 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_variables.h" + +#include "ext/standard/php_smart_str.h" + +#include "tuxmodule.h" + +#include <sys/uio.h> + +#if 0 +#include <pthread.h> +#endif + +void tux_closed_conn(int fd); + +enum { + PHP_TUX_BACKGROUND_CONN = 1 +}; + +typedef struct { + user_req_t *req; + void (*on_close)(int); + int tux_action; + struct iovec *header_vec; + int number_vec; +} php_tux_globals; + +static php_tux_globals tux_globals; + +#define TG(v) (tux_globals.v) + +static int sapi_tux_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int n; + int m; + const char *estr; + + /* combine headers and body */ + if (TG(number_vec)) { + struct iovec *vec = TG(header_vec); + + n = TG(number_vec); + vec[n].iov_base = (void *) str; + vec[n++].iov_len = str_length; + + /* XXX: this might need more complete error handling */ + if ((m = writev(TG(req)->sock, vec, n)) == -1 && errno == EPIPE) + php_handle_aborted_connection(); + + if (m > 0) + TG(req)->bytes_sent += str_length; + + TG(number_vec) = 0; + return str_length; + } + + estr = str + str_length; + + while (str < estr) { + n = send(TG(req)->sock, str, estr - str, 0); + + if (n == -1 && errno == EPIPE) + php_handle_aborted_connection(); + if (n == -1 && errno == EAGAIN) + continue; + if (n <= 0) + return n; + + str += n; + } + + n = str_length - (estr - str); + + TG(req)->bytes_sent += n; + + return n; +} + +static int sapi_tux_send_headers(sapi_headers_struct *sapi_headers) +{ + char buf[1024]; + struct iovec *vec; + int n; + int max_headers; + zend_llist_position pos; + sapi_header_struct *h; + size_t len; + char *status_line; + int locate_cl; + TSRMLS_FETCH(); + + max_headers = 30; + n = 1; + + vec = malloc(sizeof(struct iovec) * max_headers); + status_line = malloc(30); + + /* safe sprintf use */ + len = slprintf(status_line, 30, "HTTP/1.1 %d NA\r\n", SG(sapi_headers).http_response_code); + + vec[0].iov_base = status_line; + vec[0].iov_len = len; + + TG(req)->http_status = SG(sapi_headers).http_response_code; + + if (TG(tux_action) == TUX_ACTION_FINISH_CLOSE_REQ && TG(req)->http_version == HTTP_1_1) + locate_cl = 1; + else + locate_cl = 0; + + h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + if (locate_cl + && strncasecmp(h->header, "Content-length:", sizeof("Content-length:")-1) == 0) { + TG(tux_action) = TUX_ACTION_FINISH_REQ; + locate_cl = 0; + } + + vec[n].iov_base = h->header; + vec[n++].iov_len = h->header_len; + if (n >= max_headers - 3) { + max_headers *= 2; + vec = realloc(vec, sizeof(struct iovec) * max_headers); + } + vec[n].iov_base = "\r\n"; + vec[n++].iov_len = 2; + + h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + + vec[n].iov_base = "\r\n"; + vec[n++].iov_len = 2; + + TG(number_vec) = n; + TG(header_vec) = vec; + + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +static int sapi_tux_read_post(char *buffer, uint count_bytes) +{ +#if 0 + int amount = 0; + TSRMLS_FETCH(); + + TG(req)->objectlen = count_bytes; + TG(req)->object_addr = buffer; + if (tux(TUX_ACTION_READ_POST_DATA, TG(req))) + return 0; + + TG(read_post_data) = 1; + + return TG(req)->objectlen; +#else + return 0; +#endif +} + +static char *sapi_tux_read_cookies(void) +{ + TSRMLS_FETCH(); + + return TG(req)->cookies; +} + +#define BUF_SIZE 512 +#define ADD_STRING(name) \ + php_register_variable(name, buf, track_vars_array TSRMLS_CC) + +static void sapi_tux_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char buf[BUF_SIZE + 1]; + char *p; + sapi_header_line ctr = {0}; + + ctr.line = buf; + ctr.line_len = slprintf(buf, sizeof(buf), "Server: %s", TUXAPI_version); + sapi_header_op(SAPI_HEADER_REPLACE, &ctr TSRMLS_CC); + + php_register_variable("PHP_SELF", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("SERVER_SOFTWARE", TUXAPI_version, track_vars_array TSRMLS_CC); + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_METHOD", (char *) SG(request_info).request_method, track_vars_array TSRMLS_CC); + php_register_variable("DOCUMENT_ROOT", TUXAPI_docroot, track_vars_array TSRMLS_CC); + php_register_variable("SERVER_NAME", TUXAPI_servername, track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_URI", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", SG(request_info).path_translated, track_vars_array TSRMLS_CC); + + p = inet_ntoa(TG(req)->client_host); + /* string representation of IPs are never larger than 512 bytes */ + if (p) { + memcpy(buf, p, strlen(p) + 1); + ADD_STRING("REMOTE_ADDR"); + ADD_STRING("REMOTE_HOST"); + } + + snprintf(buf, sizeof(buf), "%d", CGI_SERVER_PORT(TG(req))); + ADD_STRING("SERVER_PORT"); + +#if 0 + snprintf(buf, BUF_SIZE, "/%s", TG(hc)->pathinfo); + ADD_STRING("PATH_INFO"); + + snprintf(buf, BUF_SIZE, "/%s", TG(hc)->origfilename); + ADD_STRING("SCRIPT_NAME"); +#endif + +#define CONDADD(name, field) \ + if (TG(req)->field[0]) { \ + php_register_variable(#name, TG(req)->field, track_vars_array TSRMLS_CC); \ + } + + CONDADD(HTTP_REFERER, referer); + CONDADD(HTTP_USER_AGENT, user_agent); + CONDADD(HTTP_ACCEPT, accept); + CONDADD(HTTP_ACCEPT_ENCODING, accept_encoding); + CONDADD(HTTP_ACCEPT_LANGUAGE, accept_language); + CONDADD(HTTP_COOKIE, cookies); + CONDADD(CONTENT_TYPE, content_type); + +#if 0 + if (TG(hc)->contentlength != -1) { + snprintf(buf, sizeof(buf), "%ld", (long) TG(hc)->contentlength); + ADD_STRING("CONTENT_LENGTH"); + } +#endif + +#if 0 + if (TG(hc)->authorization[0]) + php_register_variable("AUTH_TYPE", "Basic", track_vars_array TSRMLS_CC); +#endif +} + + +static int php_tux_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module, NULL, 0)==FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + +static sapi_module_struct tux_sapi_module = { + "tux", + "tux", + + php_tux_startup, + php_module_shutdown_wrapper, + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_tux_ub_write, + NULL, + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, + + NULL, + sapi_tux_send_headers, + NULL, + sapi_tux_read_post, + sapi_tux_read_cookies, + + sapi_tux_register_variables, + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +static void tux_module_main(TSRMLS_D) +{ + zend_file_handle file_handle; + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return; + } + + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); +} + +static void tux_request_ctor(TSRMLS_D) +{ + char buf[1024]; + int offset; + size_t filename_len; + size_t cwd_len; + smart_str s = {0}; + char *p; + + TG(number_vec) = 0; + TG(header_vec) = NULL; + SG(request_info).query_string = strdup(TG(req)->query); + + smart_str_appends_ex(&s, "/", 1); + smart_str_appends_ex(&s, TG(req)->query, 1); + smart_str_0(&s); + p = strchr(s.c, '&'); + if (p) + *p = '\0'; + SG(request_info).path_translated = s.c; + + s.c = NULL; + smart_str_appendc_ex(&s, '/', 1); + smart_str_appends_ex(&s, TG(req)->objectname, 1); + smart_str_0(&s); + SG(request_info).request_uri = s.c; + SG(request_info).request_method = CGI_REQUEST_METHOD(TG(req)); + if(TG(req)->http_version == HTTP_1_1) SG(request_info).proto_num = 1001; + else SG(request_info).proto_num = 1000; + SG(sapi_headers).http_response_code = 200; + SG(request_info).content_type = TG(req)->content_type; + SG(request_info).content_length = 0; /* TG(req)->contentlength; */ + +#if 0 + php_handle_auth_data(TG(hc)->authorization TSRMLS_CC); +#endif +} + +static void tux_request_dtor(TSRMLS_D) +{ + if (TG(header_vec)) { + /* free status_line */ + free(TG(header_vec)[0].iov_base); + free(TG(header_vec)); + } + if (SG(request_info).query_string) + free(SG(request_info).query_string); + free(SG(request_info).request_uri); + free(SG(request_info).path_translated); +} + +#if 0 +static void *separate_thread(void *bla) +{ + int fd; + int i = 0; + + fd = (int) bla; + + while (i++ < 5) { + send(fd, "test<br />\n", 9, 0); + sleep(1); + } + + tux(TUX_ACTION_CONTINUE_REQ, (user_req_t *) fd); + /* We HAVE to trigger some event on the fd. Otherwise + fast_thread won't wake up, so that the eventloop + won't be entered -> TUX hangs */ + shutdown(fd, 2); + pthread_exit(NULL); +} +#endif + +int TUXAPI_handle_events(user_req_t *req) +{ + TSRMLS_FETCH(); + + if (req->event == PHP_TUX_BACKGROUND_CONN) { + tux_closed_conn(req->sock); + return tux(TUX_ACTION_FINISH_CLOSE_REQ, req); + } + + TG(req) = req; + TG(tux_action) = TUX_ACTION_FINISH_CLOSE_REQ; + + tux_request_ctor(TSRMLS_C); + + tux_module_main(TSRMLS_C); + + tux_request_dtor(TSRMLS_C); + + return tux(TG(tux_action), req); +} + +void tux_register_on_close(void (*arg)(int)) +{ + TG(on_close) = arg; +} + +void tux_closed_conn(int fd) +{ + TSRMLS_FETCH(); + + if (TG(on_close)) TG(on_close)(fd); +} + +int tux_get_fd(void) +{ + TSRMLS_FETCH(); + + return TG(req)->sock; +} + +void tux_set_dont_close(void) +{ + TSRMLS_FETCH(); + + TG(req)->event = PHP_TUX_BACKGROUND_CONN; + tux(TUX_ACTION_POSTPONE_REQ, TG(req)); + TG(tux_action) = TUX_ACTION_EVENTLOOP; +} + +void TUXAPI_init(void) +{ + sapi_startup(&tux_sapi_module); + tux_sapi_module.startup(&tux_sapi_module); + SG(server_context) = (void *) 1; +} + +void doesnotmatter_fini(void) +{ + if (SG(server_context) != NULL) { + tux_sapi_module.shutdown(&tux_sapi_module); + sapi_shutdown(); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/webjames/CREDITS b/sapi/webjames/CREDITS new file mode 100644 index 0000000..73a7983 --- /dev/null +++ b/sapi/webjames/CREDITS @@ -0,0 +1,2 @@ +WebJames +Alex Waugh diff --git a/sapi/webjames/README b/sapi/webjames/README new file mode 100644 index 0000000..746a776 --- /dev/null +++ b/sapi/webjames/README @@ -0,0 +1,28 @@ +README for WebJames SAPI module +by Alex Waugh <alex@alexwaugh.com> + +This is a SAPI module for the WebJames HTTP server, which runs on the +RISC OS operating system. + + +DOWNLOADS + +A recent (February 2002 or later) version of the GCCSDK cross compiler +http://www.hard-mofo.dsvr.net/ + +WebJames 0.35 or later +http://www.webjames.alexwaugh.com/ + + +BUILDING + +$ cd php5 +$ ./configure \ + --host=arm-riscos-aof \ + --with-webjames=../webjames/src \ + --with-config-file-path=/Choices: \ + other PHP options +$ make install +$ cd ../webjames +$ ./configure --enable-php +$ make diff --git a/sapi/webjames/config.m4 b/sapi/webjames/config.m4 new file mode 100644 index 0000000..78c8a19 --- /dev/null +++ b/sapi/webjames/config.m4 @@ -0,0 +1,21 @@ +dnl +dnl $Id$ +dnl + +PHP_ARG_WITH(webjames,, +[ --with-webjames=SRCDIR Build PHP as a WebJames module (RISC OS only)], no, no) + +AC_MSG_CHECKING([for webjames]) + +if test "$PHP_WEBJAMES" != "no"; then + PHP_EXPAND_PATH($PHP_WEBJAMES, PHP_WEBJAMES) + INSTALL_IT="\ + echo 'PHP_LIBS = -l$abs_srcdir/$SAPI_STATIC \$(PHP_LIBS) \$(EXTRA_LIBS)' > $PHP_WEBJAMES/build/php; \ + echo 'PHP_LDFLAGS = \$(NATIVE_RPATHS) \$(PHP_LDFLAGS)' >> $PHP_WEBJAMES/build/php; \ + echo 'PHP_CFLAGS = -DPHP \$(COMMON_FLAGS) \$(EXTRA_CFLAGS) -I$abs_srcdir/sapi/webjames' >> $PHP_WEBJAMES/build/php;" + PHP_ADD_INCLUDE($PHP_WEBJAMES) + PHP_SELECT_SAPI(webjames, static, webjames.c) + AC_MSG_RESULT([yes, using $PHP_WEBJAMES]) +else + AC_MSG_RESULT(no) +fi diff --git a/sapi/webjames/php_webjames.h b/sapi/webjames/php_webjames.h new file mode 100644 index 0000000..f9903d1 --- /dev/null +++ b/sapi/webjames/php_webjames.h @@ -0,0 +1,28 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Alex Waugh <alex@alexwaugh.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_WEBJAMES_H +#define PHP_WEBJAMES_H + +#include "webjames.h" + +void webjames_php_shutdown(void); +int webjames_php_init(void); +void webjames_php_request(struct connection *conn); + +#endif diff --git a/sapi/webjames/webjames.c b/sapi/webjames/webjames.c new file mode 100644 index 0000000..9237ac7 --- /dev/null +++ b/sapi/webjames/webjames.c @@ -0,0 +1,330 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | Author: Alex Waugh <alex@alexwaugh.com> | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_variables.h" + +#define WEBJAMES_PHP_ONLY +#include "php_webjames.h" + +#include <unixlib/local.h> + +#define WEBJAMES_SAPI_VERSION "1.0.2" + +typedef struct { + struct connection *conn; /*structure holding all the details of the current request*/ + int bodyread; /*amount of POST body read*/ + closefn oldclose; /*function to call to close the connection*/ +} php_webjames_globals; + +static php_webjames_globals webjames_globals; + +#define WG(v) (webjames_globals.v) + +static int sapi_webjames_ub_write(const char *str, uint str_length TSRMLS_DC) +/*unbuffered write - send data straight out to socket*/ +{ + int totalbytes = 0; + + do { + int bytes; + bytes = webjames_writebuffer(WG(conn),str,str_length); + if (bytes<0) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + if (!PG(ignore_user_abort)) { + zend_bailout(); + } + return bytes; + } + str += bytes; + str_length -= bytes; + totalbytes += bytes; + } while (str_length); + return totalbytes; +} + +static void sapi_webjames_send_header(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC) +/*send an HTTP header*/ +{ + char *header = sapi_header->header; + int len = sapi_header->header_len; + if (WG(conn)->flags.outputheaders) { + while (sapi_header && len > 0) { + int bytes; + bytes = webjames_writebuffer(WG(conn), header, len); + if (bytes<0) { + PG(connection_status) = PHP_CONNECTION_ABORTED; + if (!PG(ignore_user_abort)) { + zend_bailout(); + } + return; + } + header += bytes; + len -= bytes; + } + webjames_writestring(WG(conn), "\r\n"); + } +} + +static int sapi_webjames_read_post(char *buffer, uint count_bytes TSRMLS_DC) +/*read some of the post data*/ +{ + if (WG(conn)->body==NULL) return 0; + if (count_bytes+WG(bodyread)>WG(conn)->bodysize) count_bytes=WG(conn)->bodysize-WG(bodyread); + memcpy(buffer, WG(conn)->body+WG(bodyread), count_bytes); + WG(bodyread)+=count_bytes; + return count_bytes; +} + +static char *sapi_webjames_read_cookies(TSRMLS_D) +{ + return WG(conn)->cookie; +} + +#define BUF_SIZE 512 +#define ADD_STRING(name,string)\ + php_register_variable(name, string, track_vars_array TSRMLS_CC) + +#define ADD_NUM(name,field) {\ + snprintf(buf, BUF_SIZE, "%d", WG(conn)->field);\ + php_register_variable(name, buf, track_vars_array TSRMLS_CC);\ +} + +#define ADD_FIELD(name, field) \ + if (WG(conn)->field) { \ + php_register_variable(name, WG(conn)->field, track_vars_array TSRMLS_CC); \ + } + +static void sapi_webjames_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char buf[BUF_SIZE + 1]; + char *docroot; + + buf[BUF_SIZE] = '\0'; + + ADD_STRING("SERVER_SOFTWARE", configuration.server); + ADD_STRING("SERVER_NAME", configuration.serverip); + ADD_FIELD("SERVER_PROTOCOL", protocol); + ADD_NUM("SERVER_PORT", port); + ADD_STRING("SERVER_ADMIN",configuration.webmaster); + ADD_STRING("GATEWAY_INTERFACE", "CGI/1.1"); + + docroot = __unixify(WG(conn)->homedir,0,NULL,1024,0); + if (docroot) ADD_STRING("DOCUMENT_ROOT", docroot); + + ADD_FIELD("REQUEST_METHOD", methodstr); + ADD_FIELD("REQUEST_URI", requesturi); + ADD_STRING("PATH_TRANSLATED", SG(request_info).path_translated); + ADD_FIELD("SCRIPT_NAME", uri); + ADD_FIELD("PHP_SELF", uri); + ADD_FIELD("QUERY_STRING", args); + + + snprintf(buf, BUF_SIZE, "%d.%d.%d.%d", WG(conn)->ipaddr[0], WG(conn)->ipaddr[1], WG(conn)->ipaddr[2], WG(conn)->ipaddr[3]); + ADD_STRING("REMOTE_ADDR", buf); + if (WG(conn)->dnsstatus == DNS_OK) ADD_FIELD("REMOTE_HOST", host); + + if ((WG(conn)->method == METHOD_POST) || (WG(conn)->method == METHOD_PUT)) { + ADD_NUM("CONTENT_LENGTH", bodysize); + ADD_FIELD("CONTENT_TYPE", type); + } + + if ((WG(conn)->method == METHOD_PUT) || (WG(conn)->method == METHOD_DELETE)) ADD_FIELD("ENTITY_PATH", requesturi); + + if (WG(conn)->pwd) { + ADD_STRING("AUTH_TYPE", "basic"); + ADD_FIELD("REMOTE_USER", authorization); + } + + ADD_FIELD("HTTP_COOKIE", cookie); + ADD_FIELD("HTTP_USER_AGENT", useragent); + ADD_FIELD("HTTP_REFERER", referer); + ADD_FIELD("HTTP_ACCEPT", accept); + ADD_FIELD("HTTP_ACCEPT_LANGUAGE", acceptlanguage); + ADD_FIELD("HTTP_ACCEPT_CHARSET", acceptcharset); + ADD_FIELD("HTTP_ACCEPT_ENCODING", acceptencoding); +} + +static void webjames_module_main(TSRMLS_D) +{ + zend_file_handle file_handle; + FILE *fp=NULL; + char *path; + + /* Convert filename to Unix format*/ + __riscosify_control|=__RISCOSIFY_STRICT_UNIX_SPECS; + path = __unixify(WG(conn)->filename,0,NULL,1024,0); + if (path) SG(request_info).path_translated = estrdup(path); + + SG(request_info).query_string = WG(conn)->args; + SG(request_info).request_uri = WG(conn)->requesturi; + SG(request_info).request_method = WG(conn)->methodstr; + if (WG(conn)->method==METHOD_HEAD) { + SG(request_info).headers_only = 1; + } else { + SG(request_info).headers_only = 0; + } + SG(sapi_headers).http_response_code = 200; + SG(request_info).content_type = WG(conn)->type; + SG(request_info).content_length = WG(conn)->bodysize; + + SG(request_info).auth_user = NULL; + SG(request_info).auth_password = NULL; + if (WG(conn)->authorization) { + char *colon=strchr(WG(conn)->authorization,':'); + if (colon) { + SG(request_info).auth_user = emalloc(colon-WG(conn)->authorization+1); + if (SG(request_info).auth_user) { + memcpy(SG(request_info).auth_user,WG(conn)->authorization,colon-WG(conn)->authorization); + SG(request_info).auth_user[colon-WG(conn)->authorization]='\0'; + SG(request_info).auth_password = estrdup(colon+1); + } + } + } + + /*ensure that syslog calls get logged separately from WebJames' main log */ + openlog("PHP",0,0); + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return; + } + + php_execute_script(&file_handle TSRMLS_CC); + php_request_shutdown(NULL); +} + +static void webjames_php_close(struct connection *conn, int force) +/*called by webjames if it wants to close the connection*/ +{ + TSRMLS_FETCH(); + + php_request_shutdown(NULL); + WG(oldclose)(conn,force); +} + +void webjames_php_request(struct connection *conn) +/*called by WebJames to start handler*/ +{ + TSRMLS_FETCH(); + + WG(conn) = conn; + WG(bodyread) = 0; + WG(oldclose) = conn->close; + conn->close=webjames_php_close; + + webjames_module_main(TSRMLS_C); + + WG(oldclose)(WG(conn), 0); +} + +static void php_info_webjames(ZEND_MODULE_INFO_FUNC_ARGS) +{ + php_info_print_table_start(); + php_info_print_table_row(2, "SAPI module version", WEBJAMES_SAPI_VERSION); + php_info_print_table_row(2, "WebJames version", WEBJAMES_VERSION " (" WEBJAMES_DATE ")"); + php_info_print_table_end(); +} + +static zend_module_entry php_webjames_module = { +#if ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif + "WebJames", + NULL, + NULL, + NULL, + NULL, + NULL, + php_info_webjames, +#if ZEND_MODULE_API_NO >= 20010901 + WEBJAMES_SAPI_VERSION, +#endif + STANDARD_MODULE_PROPERTIES +}; + + +static int php_webjames_startup(sapi_module_struct *sapi_module) +{ + if(php_module_startup(sapi_module, &php_webjames_module, 1) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + +static sapi_module_struct sapi_module = { + "webjames", /* name */ + "WebJames", /* pretty name */ + + php_webjames_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_webjames_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, /* error handler */ + + NULL, /* header handler */ + NULL, /* send headers handler */ + sapi_webjames_send_header, /* send header handler */ + sapi_webjames_read_post, /* read POST data */ + sapi_webjames_read_cookies, /* read Cookies */ + + sapi_webjames_register_variables, /* register server variables */ + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +int webjames_php_init(void) +/*called when WebJames initialises*/ +{ + TSRMLS_FETCH(); + if (strcmp(configuration.webjames_h_revision,WEBJAMES_H_REVISION)!=0) { + /*This file was compiled against a different revision of + webjames.h than webjames was, which could be bad news*/ + webjames_writelog(0,"PHP module is compiled for WebJames (%s) and was linked with a different version (%s)",WEBJAMES_H_REVISION,configuration.webjames_h_revision); + return 0; /*failed to initialise*/ + } + sapi_startup(&sapi_module); + sapi_module.startup(&sapi_module); + SG(server_context) = (void *) 1; + return 1; /*initialised correctly*/ +} + +void webjames_php_shutdown(void) +/*called when WebJames is about to quit*/ +{ + sapi_module.shutdown(&sapi_module); + sapi_shutdown(); +} |