From: Dmitry Stogov Date: Thu, 2 Feb 2006 08:17:23 +0000 (+0000) Subject: Reimplement FastCGI interface X-Git-Tag: php-5.1.3RC1~175 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7e7fcc3a078029b06a23e7cc70cde935575df889;p=php Reimplement FastCGI interface Due to licensing restrictions that actually don't allow anybody to use the libfcgi code without prior approval from Open Market, which is impossible to obtain - we decided to reimplement this interface from scratch. The result is actually slightly faster, and more importantly, clear of any copyright issues. --- diff --git a/NEWS b/NEWS index ddc8ac14ba..1e3e8a4d79 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? 2006, PHP 5.1.3 +- Reimplemented FastCGI interface - Moved extensions to PECL: . ext/msession (Derick) - Improved cURL extension: (Ilia) diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index e30d4f59fd..ac60c08d46 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -79,9 +79,7 @@ #include "php_getopt.h" #if PHP_FASTCGI -#include "fcgi_config.h" -#include "fcgiapp.h" -/* don't want to include fcgios.h, causes conflicts */ +#include "fastcgi.h" #ifdef PHP_WIN32 extern int OS_SetImpersonate(void); #else diff --git a/sapi/cgi/config.w32 b/sapi/cgi/config.w32 index 5ef51ccd29..e1599f28d3 100644 --- a/sapi/cgi/config.w32 +++ b/sapi/cgi/config.w32 @@ -16,8 +16,7 @@ AC_DEFINE("ENABLE_PATHINFO_CHECK", PHP_PATH_INFO_CHECK == "yes" ? 1 : 0, "Pathin if (PHP_CGI == "yes") { AC_DEFINE('PHP_FASTCGI', PHP_FASTCGI == "yes" ? 1 : 0); if (PHP_FASTCGI == "yes") { - SAPI('cgi', 'cgi_main.c getopt.c', 'php-cgi.exe', '/I sapi/cgi/libfcgi/include /D FCGI_STATIC'); - ADD_SOURCES('sapi/cgi/libfcgi', 'fcgi_stdio.c fcgiapp.c os_win32.c', 'cgi'); + SAPI('cgi', 'cgi_main.c getopt.c fastcgi.c', 'php-cgi.exe', '/I sapi/cgi/libfcgi/include /D FCGI_STATIC'); ADD_FLAG('LIBS_CGI', 'ws2_32.lib kernel32.lib advapi32.lib'); } else { SAPI('cgi', 'cgi_main.c getopt.c', 'php-cgi.exe'); diff --git a/sapi/cgi/config9.m4 b/sapi/cgi/config9.m4 index abc6d31cea..61dcf4d45a 100644 --- a/sapi/cgi/config9.m4 +++ b/sapi/cgi/config9.m4 @@ -133,15 +133,12 @@ if test "$PHP_SAPI" = "default"; then exit 1 fi if test "$PHP_ENABLE_FASTCGI" = "yes"; then - PHP_ADD_BUILD_DIR($abs_builddir/sapi/cgi/libfcgi) PHP_FASTCGI=1 - PHP_FCGI_FILES="libfcgi/fcgi_stdio.c libfcgi/fcgiapp.c libfcgi/os_unix.c" - PHP_FCGI_INCLUDE="-I$PHP_LIBFCGI_DIR/include" + PHP_FCGI_FILES="fastcgi.c" PHP_FCGI_STATIC=1 else PHP_FASTCGI=0 PHP_FCGI_FILES="" - PHP_FCGI_INCLUDE="" PHP_FCGI_STATIC=0 fi AC_DEFINE_UNQUOTED(PHP_FASTCGI, $PHP_FASTCGI, [ ]) @@ -149,7 +146,7 @@ if test "$PHP_SAPI" = "default"; then AC_MSG_RESULT($PHP_ENABLE_FASTCGI) INSTALL_IT="@echo \"Installing PHP CGI into: \$(INSTALL_ROOT)\$(bindir)/\"; \$(INSTALL) -m 0755 \$(SAPI_CGI_PATH) \$(INSTALL_ROOT)\$(bindir)/\$(program_prefix)php\$(program_suffix)\$(EXEEXT)" - PHP_SELECT_SAPI(cgi, program, $PHP_FCGI_FILES cgi_main.c getopt.c, $PHP_FCGI_INCLUDE, '$(SAPI_CGI_PATH)') + PHP_SELECT_SAPI(cgi, program, $PHP_FCGI_FILES cgi_main.c getopt.c, , '$(SAPI_CGI_PATH)') case $host_alias in *aix*) diff --git a/sapi/cgi/fastcgi.c b/sapi/cgi/fastcgi.c new file mode 100644 index 0000000000..f2ba15982e --- /dev/null +++ b/sapi/cgi/fastcgi.c @@ -0,0 +1,908 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2006 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 | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "fastcgi.h" +#include "php.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include + + typedef unsigned int size_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 +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# 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; + +typedef struct _fcgi_mgmt_rec { + char* name; + char val; +} fcgi_mgmt_rec; + +static const fcgi_mgmt_rec fcgi_mgmt_vars[] = { + {"FCGI_MAX_CONNS", 1}, + {"FCGI_MAX_REQS", 1}, + {"FCGI_MPXS_CONNS", 0} +}; + + +static int is_initialized = 0; +static int is_fastcgi = 0; +static int in_shutdown = 0; + +static inline char* fcgi_strndup(const char *str, int len) +{ + char *s = malloc(len+1); + memcpy(s, str, len+1); + return s; +} + +#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) { + in_shutdown = 1; + } +} + +#endif + +int fcgi_init(void) +{ + if (!is_initialized) { +#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 + is_initialized = 1; + + 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 + sa_t sa; + socklen_t len = sizeof(sa); + + is_initialized = 1; + errno = 0; + if (getpeername(0, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) { + 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(SIGPIPE, NULL, &old_sa); + if (old_sa.sa_handler == SIG_DFL) { + sigaction(SIGPIPE, &new_sa, NULL); + } + + 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; + } +} + +int fcgi_listen(const char *path, int backlog) +{ +#ifdef _WIN32 + /* TODO: Support for manual binding on TCP sockets (php -b ) */ + return -1; +#else + char *s; + int tcp = 0; + char host[MAXPATHLEN]; + short port = 0; + int listen_socket; + sa_t sa; + socklen_t sa_len; + + 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; + } + } + + /* 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); + sa_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 { + 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); + sa_len = sizeof(sa.sa_unix.sun_family) + path_len; +#ifdef HAVE_SOCKADDR_UN_SUN_LEN + sa_len += sizeof(sa.sa_unix.sun_len) + 1; + sa.sa_unix.sun_len = sa_len; +#endif + unlink(path); + } + + /* Create, bind socket and start listen on it */ + if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 || + bind(listen_socket, (struct sockaddr *) &sa, sa_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); + } + + if (!is_initialized) { + fcgi_init(); + } + is_fastcgi = 1; + return listen_socket; +#endif +} + +void fcgi_init_request(fcgi_request *req, int listen_socket) +{ + memset(req, 0, sizeof(fcgi_request)); + req->listen_socket = listen_socket; + req->fd = -1; + req->id = -1; + + req->in_len = 0; + req->in_pad = 0; + + req->out_hdr = NULL; + req->out_pos = req->out_buf; +} + +static inline ssize_t safe_write(fcgi_request *req, const void *buf, size_t count) +{ + int ret; + size_t n = 0; + + do { + ret = write(req->fd, ((char*)buf)+n, count-n); + 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 { + ret = read(req->fd, ((char*)buf)+n, count-n); + 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; + return pad; +} + +static int fcgi_get_params(fcgi_request *req, unsigned char *p, unsigned char *end, int n) +{ + int name_len, val_len; + char *s; + + while (p < end) { + name_len = *p++; + if (name_len >= 128) { + name_len = ((name_len & 0x7f) << 24); + name_len |= (*p++ << 16); + name_len |= (*p++ << 8); + name_len |= *p++; + } + val_len = *p++; + if (val_len >= 128) { + val_len = ((val_len & 0x7f) << 24); + val_len |= (*p++ << 16); + val_len |= (*p++ << 8); + val_len |= *p++; + } + req->env[n] = s = malloc(name_len + val_len + 2); + memcpy(s, p, name_len); + p += name_len; + s[name_len] = '='; + memcpy(s+name_len+1, p, val_len); + p += val_len; + s[name_len+1+val_len] = '\0'; + n++; + if (n > sizeof(req->env)/sizeof(req->env[0])) { + /* TODO: to many environment variables */ + return n; + } + } + return n; +} + +static int fcgi_read_request(fcgi_request *req) +{ + fcgi_header hdr; + int len, padding; + int n = 1; + char *s; + unsigned char buf[FCGI_MAX_LENGTH+8]; + + req->keep = 0; + req->in_len = 0; + req->out_hdr = NULL; + req->out_pos = req->out_buf; + memset(req->env, 0, sizeof(req->env)); + + 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; + } + + 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); + switch ((((fcgi_begin_request*)buf)->roleB1 << 8) + ((fcgi_begin_request*)buf)->roleB0) { + case FCGI_RESPONDER: + req->env[0] = fcgi_strndup("FCGI_ROLE=RESPONDER", sizeof("FCGI_ROLE=RESPONDER")-1); + break; + case FCGI_AUTHORIZER: + req->env[0] = fcgi_strndup("FCGI_ROLE=AUTHORIZER", sizeof("FCGI_ROLE=AUTHORIZER")-1); + break; + case FCGI_FILTER: + req->env[0] = fcgi_strndup("FCGI_ROLE=FILTER", sizeof("FCGI_ROLE=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 (safe_read(req, buf, len+padding) != len+padding) { + req->keep = 0; + return 0; + } + n = fcgi_get_params(req, buf, buf+len, n); + + 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) { + int i, j; + int name_len; + unsigned char *p = buf + sizeof(fcgi_header); + + if (safe_read(req, buf, len+padding) != len+padding) { + return 0; + } + n = fcgi_get_params(req, buf, buf+len, 0); + for (i = 0; i < n; i++) { + if ((s = strchr(req->env[i], '=')) != NULL) { + *s = '\0'; + name_len = s - req->env[i]; + } else { + name_len = strlen(req->env[i]); + } + for (j = 0; j < sizeof(fcgi_mgmt_vars)/sizeof(fcgi_mgmt_vars[0]); j++) { + if (strncmp(req->env[i], fcgi_mgmt_vars[j].name, name_len) == 0) { + sprintf((char*)p, "%c%c%s%c", name_len, 1, fcgi_mgmt_vars[j].name, fcgi_mgmt_vars[j].val); + p += name_len+3; + } + } + } + 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) != sizeof(fcgi_header)+len) { + 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[8]; + + 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) { + char **env = req->env; + while (*env) { + free(*env++); + } + } + if ((force || !req->keep) && req->fd >= 0) { +#ifdef _WIN32 + HANDLE pipe = (HANDLE)_get_osfhandle(req->fd); + + if (!force) { + FlushFileBuffers(pipe); + } + DisconnectNamedPipe(pipe); + if (is_impersonate) { + RevertToSelf(); + } +#else + close(req->fd); +#endif + req->fd = -1; + } +} + +int fcgi_accept_request(fcgi_request *req) +{ + fcgi_finish_request(req); + + while (1) { + if (req->fd < 0) { + while (1) { +#ifdef _WIN32 + HANDLE pipe = (HANDLE)_get_osfhandle(req->listen_socket); + OVERLAPPED ov; + + 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); + if (is_impersonate && !ImpersonateNamedPipeClient(pipe)) { + DisconnectNamedPipe(pipe); + req->fd = -1; + } else { + req->fd = req->listen_socket; + } + FCGI_UNLOCK(req->listen_socket); +#else + sa_t sa; + socklen_t len = sizeof(sa); + + FCGI_LOCK(req->listen_socket); + req->fd = accept(req->listen_socket, (struct sockaddr *)&sa, &len); + FCGI_UNLOCK(req->listen_socket); +#endif + + if (in_shutdown || (req->fd < 0 && errno != EINTR)) { + return -1; + } + +#ifdef _WIN32 + break; +#else + if (req->fd >= 0) { + struct timeval tv = {1,0}; + fd_set set; + + FD_ZERO(&set); + FD_SET(req->fd, &set); +try_again: + errno = 0; + if (select(req->fd + 1, &set, NULL, NULL, &tv) >= 0 && FD_ISSET(req->fd, &set)) { + break; + } + if (errno == EINTR) goto try_again; + fcgi_close(req, 1, 0); + } +#endif + } + } + if (fcgi_read_request(req)) { + 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, 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); + } + rest = len; +#if 0 + /* Unoptinmzed, but clear version */ + while (rest > 0) { + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + + if (!req->out_hdr) { + if (limit < sizeof(fcgi_header)) { + fcgi_flush(req, 0); + } + 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; + fcgi_flush(req, 0); + } + } +#else + /* Optinmzed version */ + if (!req->out_hdr) { + rest += sizeof(fcgi_header); + } + limit = sizeof(req->out_buf) - (req->out_pos - req->out_buf); + + if (rest < limit) { + if (!req->out_hdr) { + open_packet(req, type); + } + memcpy(req->out_pos, str, len); + req->out_pos += len; + } else if (rest - limit < sizeof(req->out_buf) - sizeof(fcgi_header)) { + if (!req->out_hdr) { + open_packet(req, type); + } + memcpy(req->out_pos, str, limit); + req->out_pos += limit; + fcgi_flush(req, 0); + open_packet(req, type); + memcpy(req->out_pos, str + limit, len - limit); + req->out_pos += len - limit; + } else { + int pad = ((len + 7) & ~7) - len; + + rest = pad ? 8 - pad : 0; + + close_packet(req); + open_packet(req, type); + fcgi_make_header(req->out_hdr, type, req->id, len - rest); + req->out_hdr = NULL; + fcgi_flush(req, 0); + if (safe_write(req, str, len - rest) != len - 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) +{ + fcgi_flush(req, 1); + fcgi_close(req, 0, 1); + return 1; +} + +char* fcgi_getenv_helper(char** env, const char *name, int len) +{ + if (name && env) { + while (*env) { + if ((strncmp(name, *env, len) == 0) && ((*env)[len] == '=')) { + return *env+len+1; + } + env++; + } + } + return NULL; +} + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len) +{ + if (!req) return NULL; + return fcgi_getenv_helper(req->env, var, var_len); +} + +void fcgi_putenv(fcgi_request *req, char* var, int var_len) +{ + if (var && req) { + char **env = req->env; + char *s = strchr(var, '='); + int len; + + if (!s) return ; + len = s - var + 1; + while (*env) { + if ((strncmp(var, *env, len) == 0)) { + free(*env); + *env = fcgi_strndup(var, var_len); + return; + } + env++; + } + *env = fcgi_strndup(var, var_len); + } +} + +int FCGX_FPrintF(FCGX_Stream stream, const char *format, ...) +{ + int result; + va_list args; + char buf[4096]; + + va_start(args, format); + result = vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + + fcgi_write(stream.req, stream.type, buf, result); + return result; +} + +#ifdef _WIN32 +void OS_SetImpersonate(void) +{ + char *os_name; + + os_name = getenv("OS"); + if (os_name && stricmp(os_name, "Windows_NT") == 0) { + is_impersonate = 1; + } +} +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cgi/fastcgi.h b/sapi/cgi/fastcgi.h new file mode 100644 index 0000000000..7c910e1abb --- /dev/null +++ b/sapi/cgi/fastcgi.h @@ -0,0 +1,183 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2006 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 | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +/* FastCGI protocol */ + +#define FCGI_VERSION_1 1 + +#define FCGI_MAX_LENGTH 0xffff + +#define FCGI_KEEP_CONN 1 + +typedef enum _fcgi_role { + FCGI_RESPONDER = 1, + FCGI_AUTHORIZER = 2, + FCGI_FILTER = 3 +} fcgi_role; + +typedef enum _fcgi_request_type { + FCGI_BEGIN_REQUEST = 1, /* [in] */ + FCGI_ABORT_REQUEST = 2, /* [in] (not supported) */ + FCGI_END_REQUEST = 3, /* [out] */ + FCGI_PARAMS = 4, /* [in] environment variables */ + FCGI_STDIN = 5, /* [in] post data */ + FCGI_STDOUT = 6, /* [out] response */ + FCGI_STDERR = 7, /* [out] errors */ + FCGI_DATA = 8, /* [in] filter data (not supported) */ + FCGI_GET_VALUES = 9, /* [in] */ + FCGI_GET_VALUES_RESULT = 10 /* [out] */ +} fcgi_request_type; + +typedef enum _fcgi_protocol_status { + FCGI_REQUEST_COMPLETE = 0, + FCGI_CANT_MPX_CONN = 1, + FCGI_OVERLOADED = 2, + FCGI_UNKNOWN_ROLE = 3 +} dcgi_protocol_status; + +typedef struct _fcgi_header { + unsigned char version; + unsigned char type; + unsigned char requestIdB1; + unsigned char requestIdB0; + unsigned char contentLengthB1; + unsigned char contentLengthB0; + unsigned char paddingLength; + unsigned char reserved; +} fcgi_header; + +typedef struct _fcgi_begin_request { + unsigned char roleB1; + unsigned char roleB0; + unsigned char flags; + unsigned char reserved[5]; +} fcgi_begin_request; + +typedef struct _fcgi_begin_request_rec { + fcgi_header hdr; + fcgi_begin_request body; +} fcgi_begin_request_rec; + +typedef struct _fcgi_end_request { + unsigned char appStatusB3; + unsigned char appStatusB2; + unsigned char appStatusB1; + unsigned char appStatusB0; + unsigned char protocolStatus; + unsigned char reserved[3]; +} fcgi_end_request; + +typedef struct _fcgi_end_request_rec { + fcgi_header hdr; + fcgi_end_request body; +} fcgi_end_request_rec; + +/* FastCGI client API */ + +typedef struct _fcgi_request { + int listen_socket; + int fd; + int id; + int keep; + + 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)]; + + char *env[128]; +} fcgi_request; + +int fcgi_init(void); +int fcgi_is_fastcgi(void); +int fcgi_listen(const char *path, int backlog); +void fcgi_init_request(fcgi_request *req, int listen_socket); +int fcgi_accept_request(fcgi_request *req); +int fcgi_finish_request(fcgi_request *req); + +char* fcgi_getenv(fcgi_request *req, const char* var, int var_len); +void fcgi_putenv(fcgi_request *req, char* var, int var_len); + +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); + +/* Some defines for limited libfcgi comatibility */ + +typedef struct _FCGX_Stream { + fcgi_request *req; + fcgi_request_type type; +} FCGX_Stream; + +typedef struct _FCGX_Request { + fcgi_request req; + FCGX_Stream in; + FCGX_Stream out; + FCGX_Stream err; + char **envp; +} FCGX_Request; + +#define FCGX_Init() +#define FCGX_IsCGI() (!fcgi_is_fastcgi()) +#define FCGX_OpenSocket(path, backlog) fcgi_listen(path, backlog) + +#define FCGX_InitRequest(r, sock, flags) \ + do { \ + fcgi_init_request(&(r)->req, sock); \ + (r)->in.req = &(r)->req; \ + (r)->out.req = &(r)->req; \ + (r)->err.req = &(r)->req; \ + (r)->in.type = FCGI_STDIN; \ + (r)->out.type = FCGI_STDOUT; \ + (r)->err.type = FCGI_STDERR; \ + (r)->envp = (r)->req.env; \ + } while (0); + + +#define FCGX_Accept_r(r) fcgi_accept_request(&(r)->req) +#define FCGX_Finish_r(r) fcgi_finish_request(&(r)->req) + +#define FCGX_PutStr(str, len, stream) fcgi_write((stream).req, (stream).type, str, len) +#define FCGX_PutS(str, len, stream) fcgi_write((stream).req, (stream).type, str, len) +#define FCGX_FFlush(stream) fcgi_flush((stream).req, 0) +#define FCGX_GetStr(str, len, stream) fcgi_read((stream).req, str, len) + +#define FCGX_GetParam(var, envp) fcgi_getenv_helper(envp, var, strlen(var)); + +#define FCGX_PutEnv(r, var) fcgi_putenv(&(r)->req, var, strlen(var)); + +int FCGX_FPrintF(FCGX_Stream stream, const char *format, ...); + +/* Internal helper functions. They shouldn't be used directly. */ + +char* fcgi_getenv_helper(char** env, const char *name, int len); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */