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/cgi | |
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/cgi')
-rwxr-xr-x | sapi/cgi/CHANGES | 34 | ||||
-rw-r--r-- | sapi/cgi/CREDITS | 2 | ||||
-rw-r--r-- | sapi/cgi/Makefile.frag | 9 | ||||
-rw-r--r-- | sapi/cgi/README.FastCGI | 151 | ||||
-rw-r--r-- | sapi/cgi/cgi_main.c | 2606 | ||||
-rw-r--r-- | sapi/cgi/config.w32 | 10 | ||||
-rw-r--r-- | sapi/cgi/config9.m4 | 76 | ||||
-rw-r--r-- | sapi/cgi/fastcgi.c | 1535 | ||||
-rw-r--r-- | sapi/cgi/fastcgi.h | 151 | ||||
-rw-r--r-- | sapi/cgi/php.sym | 0 | ||||
-rw-r--r-- | sapi/cgi/tests/001.phpt | 22 | ||||
-rw-r--r-- | sapi/cgi/tests/002.phpt | 52 | ||||
-rw-r--r-- | sapi/cgi/tests/003.phpt | 62 | ||||
-rw-r--r-- | sapi/cgi/tests/004.phpt | 43 | ||||
-rw-r--r-- | sapi/cgi/tests/005.phpt | 27 | ||||
-rw-r--r-- | sapi/cgi/tests/006.phpt | 62 | ||||
-rw-r--r-- | sapi/cgi/tests/007.phpt | 22 | ||||
-rw-r--r-- | sapi/cgi/tests/008.phpt | 54 | ||||
-rw-r--r-- | sapi/cgi/tests/009.phpt | 30 | ||||
-rw-r--r-- | sapi/cgi/tests/010.phpt | 53 | ||||
-rw-r--r-- | sapi/cgi/tests/011.phpt | 165 | ||||
-rw-r--r-- | sapi/cgi/tests/apache_request_headers.phpt | 51 | ||||
-rw-r--r-- | sapi/cgi/tests/bug61605.phpt | 34 | ||||
-rw-r--r-- | sapi/cgi/tests/include.inc | 57 | ||||
-rw-r--r-- | sapi/cgi/tests/skipif.inc | 17 |
25 files changed, 5325 insertions, 0 deletions
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"); +} + +?> |