--- embedaddon/lighttpd/src/mod_fastcgi.c 2014/06/15 20:20:06 1.1.1.2 +++ embedaddon/lighttpd/src/mod_fastcgi.c 2016/11/02 10:35:00 1.1.1.3 @@ -1,3 +1,5 @@ +#include "first.h" + #include "buffer.h" #include "server.h" #include "keyvalue.h" @@ -37,10 +39,6 @@ #include -#ifdef HAVE_SYS_FILIO_H -# include -#endif - #include "sys-socket.h" #ifdef HAVE_SYS_UIO_H @@ -50,15 +48,6 @@ #include #endif -#include "version.h" - -#define FCGI_ENV_ADD_CHECK(ret, con) \ - if (ret == -1) { \ - con->http_status = 400; \ - con->file_finished = 1; \ - return -1; \ - }; - /* * * TODO: @@ -157,6 +146,7 @@ typedef struct { */ buffer *host; unsigned short port; + sa_family_t family; /* * Unix Domain Socket @@ -235,11 +225,12 @@ typedef struct { unsigned short fix_root_path_name; /* - * If the backend includes X-LIGHTTPD-send-file in the response + * If the backend includes X-Sendfile in the response * we use the value as filename and ignore the content. * */ - unsigned short allow_xsendfile; + unsigned short xsendfile_allow; + array *xsendfile_docroot; ssize_t load; /* replace by host->load */ @@ -255,6 +246,9 @@ typedef struct { applications prefer SIGUSR1 while the rest of the world would use SIGTERM *sigh* */ + + int listen_backlog; + int refcount; } fcgi_extension_host; /* @@ -318,7 +312,6 @@ typedef struct { buffer *fcgi_env; buffer *path; - buffer *parse_response; buffer *statuskey; @@ -329,7 +322,6 @@ typedef struct { /* connection specific data */ typedef enum { - FCGI_STATE_UNSET, FCGI_STATE_INIT, FCGI_STATE_CONNECT_DELAYED, FCGI_STATE_PREPARE_WRITE, @@ -345,20 +337,20 @@ typedef struct { fcgi_connection_state_t state; time_t state_timestamp; - int reconnects; /* number of reconnect attempts */ - chunkqueue *rb; /* read queue */ chunkqueue *wb; /* write queue */ + off_t wb_reqlen; buffer *response_header; - size_t request_id; int fd; /* fd to the fastcgi process */ int fde_ndx; /* index into the fd-event buffer */ pid_t pid; int got_proc; + int reconnects; /* number of reconnect attempts */ + int request_id; int send_content_body; plugin_config conf; @@ -391,7 +383,7 @@ static void fastcgi_status_copy_procname(buffer *b, fc buffer_append_string_buffer(b, host->id); if (proc) { buffer_append_string_len(b, CONST_STR_LEN(".")); - buffer_append_long(b, proc->id); + buffer_append_int(b, proc->id); } } @@ -504,6 +496,7 @@ static handler_ctx * handler_ctx_init(void) { hctx->rb = chunkqueue_init(); hctx->wb = chunkqueue_init(); + hctx->wb_reqlen = 0; return hctx; } @@ -558,12 +551,17 @@ static fcgi_extension_host *fastcgi_host_init(void) { f->bin_env = array_init(); f->bin_env_copy = array_init(); f->strip_request_uri = buffer_init(); + f->xsendfile_docroot = array_init(); return f; } static void fastcgi_host_free(fcgi_extension_host *h) { if (!h) return; + if (h->refcount) { + --h->refcount; + return; + } buffer_free(h->id); buffer_free(h->host); @@ -573,6 +571,7 @@ static void fastcgi_host_free(fcgi_extension_host *h) buffer_free(h->strip_request_uri); array_free(h->bin_env); array_free(h->bin_env_copy); + array_free(h->xsendfile_docroot); fastcgi_process_free(h->first); fastcgi_process_free(h->unused_procs); @@ -637,7 +636,7 @@ static int fastcgi_extension_insert(fcgi_exts *ext, bu force_assert(fe); fe->key = buffer_init(); fe->last_used_ndx = -1; - buffer_copy_string_buffer(fe->key, key); + buffer_copy_buffer(fe->key, key); /* */ @@ -679,7 +678,6 @@ INIT_FUNC(mod_fastcgi_init) { p->fcgi_env = buffer_init(); p->path = buffer_init(); - p->parse_response = buffer_init(); p->statuskey = buffer_init(); @@ -694,7 +692,6 @@ FREE_FUNC(mod_fastcgi_free) { buffer_free(p->fcgi_env); buffer_free(p->path); - buffer_free(p->parse_response); buffer_free(p->statuskey); if (p->config_storage) { @@ -703,7 +700,7 @@ FREE_FUNC(mod_fastcgi_free) { plugin_config *s = p->config_storage[i]; fcgi_exts *exts; - if (!s) continue; + if (NULL == s) continue; exts = s->exts; @@ -724,7 +721,7 @@ FREE_FUNC(mod_fastcgi_free) { } if (proc->is_local && - !buffer_is_empty(proc->unixsocket)) { + !buffer_string_is_empty(proc->unixsocket)) { unlink(proc->unixsocket->ptr); } } @@ -734,7 +731,7 @@ FREE_FUNC(mod_fastcgi_free) { kill(proc->pid, host->kill_signal); } if (proc->is_local && - !buffer_is_empty(proc->unixsocket)) { + !buffer_string_is_empty(proc->unixsocket)) { unlink(proc->unixsocket->ptr); } } @@ -794,7 +791,7 @@ static int parse_binpath(char_array *env, buffer *b) { /* search for spaces */ start = b->ptr; - for (i = 0; i < b->used - 1; i++) { + for (i = 0; i < buffer_string_length(b); i++) { switch(b->ptr[i]) { case ' ': case '\t': @@ -844,50 +841,61 @@ static int parse_binpath(char_array *env, buffer *b) { return 0; } +#if !defined(HAVE_FORK) static int fcgi_spawn_connection(server *srv, - plugin_data *p, - fcgi_extension_host *host, - fcgi_proc *proc) { + plugin_data *p, + fcgi_extension_host *host, + fcgi_proc *proc) { + UNUSED(srv); + UNUSED(p); + UNUSED(host); + UNUSED(proc); + return -1; +} + +#else /* -> defined(HAVE_FORK) */ + +static int fcgi_spawn_connection(server *srv, + plugin_data *p, + fcgi_extension_host *host, + fcgi_proc *proc) { int fcgi_fd; - int socket_type, status; + int status; struct timeval tv = { 0, 100 * 1000 }; #ifdef HAVE_SYS_UN_H struct sockaddr_un fcgi_addr_un; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + struct sockaddr_in6 fcgi_addr_in6; +#endif struct sockaddr_in fcgi_addr_in; struct sockaddr *fcgi_addr; socklen_t servlen; -#ifndef HAVE_FORK - return -1; -#endif - if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sdb", "new proc, socket:", proc->port, proc->unixsocket); } - if (!buffer_is_empty(proc->unixsocket)) { - memset(&fcgi_addr, 0, sizeof(fcgi_addr)); - + if (!buffer_string_is_empty(proc->unixsocket)) { #ifdef HAVE_SYS_UN_H + memset(&fcgi_addr_un, 0, sizeof(fcgi_addr_un)); fcgi_addr_un.sun_family = AF_UNIX; - if (proc->unixsocket->used > sizeof(fcgi_addr_un.sun_path)) { + if (buffer_string_length(proc->unixsocket) + 1 > sizeof(fcgi_addr_un.sun_path)) { log_error_write(srv, __FILE__, __LINE__, "sB", "ERROR: Unix Domain socket filename too long:", proc->unixsocket); return -1; } - memcpy(fcgi_addr_un.sun_path, proc->unixsocket->ptr, proc->unixsocket->used); + memcpy(fcgi_addr_un.sun_path, proc->unixsocket->ptr, buffer_string_length(proc->unixsocket) + 1); #ifdef SUN_LEN servlen = SUN_LEN(&fcgi_addr_un); #else /* stevens says: */ - servlen = proc->unixsocket->used + sizeof(fcgi_addr_un.sun_family); + servlen = buffer_string_length(proc->unixsocket) + 1 + sizeof(fcgi_addr_un.sun_family); #endif - socket_type = AF_UNIX; fcgi_addr = (struct sockaddr *) &fcgi_addr_un; buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("unix:")); @@ -898,10 +906,20 @@ static int fcgi_spawn_connection(server *srv, "ERROR: Unix Domain sockets are not supported."); return -1; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + } else if (host->family == AF_INET6 && !buffer_string_is_empty(host->host)) { + memset(&fcgi_addr_in6, 0, sizeof(fcgi_addr_in6)); + fcgi_addr_in6.sin6_family = AF_INET6; + inet_pton(AF_INET6, host->host->ptr, (char *) &fcgi_addr_in6.sin6_addr); + fcgi_addr_in6.sin6_port = htons(proc->port); + servlen = sizeof(fcgi_addr_in6); + fcgi_addr = (struct sockaddr *) &fcgi_addr_in6; +#endif } else { + memset(&fcgi_addr_in, 0, sizeof(fcgi_addr_in)); fcgi_addr_in.sin_family = AF_INET; - if (buffer_is_empty(host->host)) { + if (buffer_string_is_empty(host->host)) { fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); } else { struct hostent *he; @@ -933,20 +951,21 @@ static int fcgi_spawn_connection(server *srv, fcgi_addr_in.sin_port = htons(proc->port); servlen = sizeof(fcgi_addr_in); - socket_type = AF_INET; fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } + if (buffer_string_is_empty(proc->unixsocket)) { buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("tcp:")); - if (!buffer_is_empty(host->host)) { + if (!buffer_string_is_empty(host->host)) { buffer_append_string_buffer(proc->connection_name, host->host); } else { buffer_append_string_len(proc->connection_name, CONST_STR_LEN("localhost")); } buffer_append_string_len(proc->connection_name, CONST_STR_LEN(":")); - buffer_append_long(proc->connection_name, proc->port); + buffer_append_int(proc->connection_name, proc->port); } - if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + if (-1 == (fcgi_fd = socket(fcgi_addr->sa_family, SOCK_STREAM, 0))) { log_error_write(srv, __FILE__, __LINE__, "ss", "failed:", strerror(errno)); return -1; @@ -958,14 +977,14 @@ static int fcgi_spawn_connection(server *srv, int val; if (errno != ENOENT && - !buffer_is_empty(proc->unixsocket)) { + !buffer_string_is_empty(proc->unixsocket)) { unlink(proc->unixsocket->ptr); } close(fcgi_fd); /* reopen socket */ - if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + if (-1 == (fcgi_fd = socket(fcgi_addr->sa_family, SOCK_STREAM, 0))) { log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno)); return -1; @@ -989,14 +1008,13 @@ static int fcgi_spawn_connection(server *srv, return -1; } - if (-1 == listen(fcgi_fd, 1024)) { + if (-1 == listen(fcgi_fd, host->listen_backlog)) { log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed:", strerror(errno)); close(fcgi_fd); return -1; } -#ifdef HAVE_FORK switch ((child = fork())) { case 0: { size_t i = 0; @@ -1035,11 +1053,12 @@ static int fcgi_spawn_connection(server *srv, } } } else { - for (i = 0; environ[i]; i++) { + char ** const e = environ; + for (i = 0; e[i]; ++i) { char *eq; - if (NULL != (eq = strchr(environ[i], '='))) { - env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1)); + if (NULL != (eq = strchr(e[i], '='))) { + env_add(&env, e[i], eq - e[i], eq+1, strlen(eq+1)); } } } @@ -1086,15 +1105,17 @@ static int fcgi_spawn_connection(server *srv, /* log_error_write(srv, __FILE__, __LINE__, "sbs", "execve failed for:", host->bin_path, strerror(errno)); */ - exit(errno); + _exit(errno); break; } case -1: /* error */ + close(fcgi_fd); break; default: /* father */ + close(fcgi_fd); /* wait */ select(0, NULL, NULL, NULL, &tv); @@ -1144,8 +1165,8 @@ static int fcgi_spawn_connection(server *srv, break; } -#endif } else { + close(fcgi_fd); proc->is_local = 0; proc->pid = 0; @@ -1159,12 +1180,30 @@ static int fcgi_spawn_connection(server *srv, proc->state = PROC_STATE_RUNNING; host->active_procs++; - close(fcgi_fd); - return 0; } +#endif /* HAVE_FORK */ +static fcgi_extension_host * unixsocket_is_dup(plugin_data *p, size_t used, buffer *unixsocket) { + size_t i, j, n; + for (i = 0; i < used; ++i) { + fcgi_exts *exts = p->config_storage[i]->exts; + for (j = 0; j < exts->used; ++j) { + fcgi_extension *ex = exts->exts[j]; + for (n = 0; n < ex->used; ++n) { + fcgi_extension_host *host = ex->hosts[n]; + if (!buffer_string_is_empty(host->unixsocket) + && buffer_is_equal(host->unixsocket, unixsocket) + && !buffer_string_is_empty(host->bin_path)) + return host; + } + } + } + + return NULL; +} + SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { plugin_data *p = p_d; data_unset *du; @@ -1182,8 +1221,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); for (i = 0; i < srv->config_context->used; i++) { + data_config const* config = (data_config const*)srv->config_context->data[i]; plugin_config *s; - array *ca; s = malloc(sizeof(plugin_config)); s->exts = fastcgi_extensions_init(); @@ -1195,9 +1234,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { cv[2].destination = s->ext_mapping; p->config_storage[i] = s; - ca = ((data_config *)srv->config_context->data[i])->value; - if (0 != config_insert_values_global(srv, ca, cv)) { + if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { goto error; } @@ -1205,13 +1243,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { * = ( ... ) */ - if (NULL != (du = array_get_element(ca, "fastcgi.server"))) { + if (NULL != (du = array_get_element(config->value, "fastcgi.server"))) { size_t j; data_array *da = (data_array *)du; if (du->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "sss", - "unexpected type for key: ", "fastcgi.server", "array of strings"); + "unexpected type for key: ", "fastcgi.server", "expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -1229,7 +1267,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { if (da->value->data[j]->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "fastcgi.server", - "[", da->value->data[j]->key, "](string)"); + "[", da->value->data[j]->key, "](string); expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -1269,6 +1307,9 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { { "strip-request-uri", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ { "kill-signal", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ { "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ + { "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -1277,7 +1318,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { log_error_write(srv, __FILE__, __LINE__, "ssSBS", "unexpected type for key:", "fastcgi.server", - "[", da_host->key, "](string)"); + "[", da_host->key, "](string); expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -1285,16 +1326,18 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { host = fastcgi_host_init(); buffer_reset(fcgi_mode); - buffer_copy_string_buffer(host->id, da_host->key); + buffer_copy_buffer(host->id, da_host->key); host->check_local = 1; host->max_procs = 4; host->mode = FCGI_RESPONDER; host->disable_time = 1; host->break_scriptfilename_for_php = 0; - host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ + host->xsendfile_allow = 0; host->kill_signal = SIGTERM; host->fix_root_path_name = 0; + host->listen_backlog = 1024; + host->refcount = 0; fcv[0].destination = host->host; fcv[1].destination = host->docroot; @@ -1310,17 +1353,20 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { fcv[9].destination = host->bin_env; fcv[10].destination = host->bin_env_copy; fcv[11].destination = &(host->break_scriptfilename_for_php); - fcv[12].destination = &(host->allow_xsendfile); + fcv[12].destination = &(host->xsendfile_allow); fcv[13].destination = host->strip_request_uri; fcv[14].destination = &(host->kill_signal); fcv[15].destination = &(host->fix_root_path_name); + fcv[16].destination = &(host->listen_backlog); + fcv[17].destination = &(host->xsendfile_allow); + fcv[18].destination = host->xsendfile_docroot; - if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { + if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) { goto error; } - if ((!buffer_is_empty(host->host) || host->port) && - !buffer_is_empty(host->unixsocket)) { + if ((!buffer_string_is_empty(host->host) || host->port) && + !buffer_string_is_empty(host->unixsocket)) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "either host/port or socket have to be set in:", da->key, "= (", @@ -1330,11 +1376,11 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { goto error; } - if (!buffer_is_empty(host->unixsocket)) { + if (!buffer_string_is_empty(host->unixsocket)) { /* unix domain socket */ struct sockaddr_un un; - if (host->unixsocket->used > sizeof(un.sun_path) - 2) { + if (buffer_string_length(host->unixsocket) + 1 > sizeof(un.sun_path) - 2) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "unixsocket is too long in:", da->key, "= (", @@ -1343,11 +1389,28 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { goto error; } + + if (!buffer_string_is_empty(host->bin_path)) { + fcgi_extension_host *duplicate = unixsocket_is_dup(p, i+1, host->unixsocket); + if (NULL != duplicate) { + if (!buffer_is_equal(host->bin_path, duplicate->bin_path)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "duplicate unixsocket path:", + host->unixsocket); + goto error; + } + fastcgi_host_free(host); + host = duplicate; + ++host->refcount; + } + } + + host->family = AF_UNIX; } else { /* tcp/ip */ - if (buffer_is_empty(host->host) && - buffer_is_empty(host->bin_path)) { + if (buffer_string_is_empty(host->host) && + buffer_string_is_empty(host->bin_path)) { log_error_write(srv, __FILE__, __LINE__, "sbsbsbs", "host or binpath have to be set in:", da->key, "= (", @@ -1364,9 +1427,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { goto error; } + + host->family = (!buffer_string_is_empty(host->host) && NULL != strchr(host->host->ptr, ':')) ? AF_INET6 : AF_INET; } - if (!buffer_is_empty(host->bin_path)) { + if (host->refcount) { + /* already init'd; skip spawning */ + } else if (!buffer_string_is_empty(host->bin_path)) { /* a local socket + self spawning */ size_t pno; @@ -1386,12 +1453,12 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { proc->id = host->num_procs++; host->max_id++; - if (buffer_is_empty(host->unixsocket)) { + if (buffer_string_is_empty(host->unixsocket)) { proc->port = host->port + pno; } else { - buffer_copy_string_buffer(proc->unixsocket, host->unixsocket); + buffer_copy_buffer(proc->unixsocket, host->unixsocket); buffer_append_string_len(proc->unixsocket, CONST_STR_LEN("-")); - buffer_append_long(proc->unixsocket, pno); + buffer_append_int(proc->unixsocket, pno); } if (s->debug) { @@ -1402,7 +1469,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { "\n\tcurrent:", pno, "/", host->max_procs); } - if (fcgi_spawn_connection(srv, p, host, proc)) { + if (!srv->srvconf.preflight_check + && fcgi_spawn_connection(srv, p, host, proc)) { log_error_write(srv, __FILE__, __LINE__, "s", "[ERROR]: spawning fcgi failed."); fastcgi_process_free(proc); @@ -1425,10 +1493,10 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { host->active_procs++; proc->state = PROC_STATE_RUNNING; - if (buffer_is_empty(host->unixsocket)) { + if (buffer_string_is_empty(host->unixsocket)) { proc->port = host->port; } else { - buffer_copy_string_buffer(proc->unixsocket, host->unixsocket); + buffer_copy_buffer(proc->unixsocket, host->unixsocket); } fastcgi_status_init(srv, p->statuskey, host, proc); @@ -1438,12 +1506,12 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { host->max_procs = 1; } - if (!buffer_is_empty(fcgi_mode)) { + if (!buffer_string_is_empty(fcgi_mode)) { if (strcmp(fcgi_mode->ptr, "responder") == 0) { host->mode = FCGI_RESPONDER; } else if (strcmp(fcgi_mode->ptr, "authorizer") == 0) { host->mode = FCGI_AUTHORIZER; - if (buffer_is_empty(host->docroot)) { + if (buffer_string_is_empty(host->docroot)) { log_error_write(srv, __FILE__, __LINE__, "s", "ERROR: docroot is required for authorizer mode."); goto error; @@ -1455,6 +1523,25 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { } } + if (host->xsendfile_docroot->used) { + size_t k; + for (k = 0; k < host->xsendfile_docroot->used; ++k) { + data_string *ds = (data_string *)host->xsendfile_docroot->data[k]; + if (ds->type != TYPE_STRING) { + log_error_write(srv, __FILE__, __LINE__, "s", + "unexpected type for x-sendfile-docroot; expected: \"x-sendfile-docroot\" => ( \"/allowed/path\", ... )"); + goto error; + } + if (ds->value->ptr[0] != '/') { + log_error_write(srv, __FILE__, __LINE__, "SBs", + "x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\""); + goto error; + } + buffer_path_simplify(ds->value, ds->value); + buffer_append_slash(ds->value); + } + } + /* if extension already exists, take it */ fastcgi_extension_insert(s->exts, da_ext->key, host); host = NULL; @@ -1484,8 +1571,6 @@ static void fcgi_connection_close(server *srv, handler plugin_data *p; connection *con; - if (NULL == hctx) return; - p = hctx->plugin_data; con = hctx->remote_conn; @@ -1514,6 +1599,11 @@ static void fcgi_connection_close(server *srv, handler handler_ctx_free(srv, hctx); con->plugin_ctx[p->id] = NULL; + + /* finish response (if not already con->file_started, con->file_finished) */ + if (con->mode == p->id) { + http_response_backend_done(srv, con); + } } static int fcgi_reconnect(server *srv, handler_ctx *hctx) { @@ -1576,15 +1666,17 @@ static int fcgi_reconnect(server *srv, handler_ctx *hc static handler_t fcgi_connection_reset(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; + handler_ctx *hctx = con->plugin_ctx[p->id]; + if (hctx) fcgi_connection_close(srv, hctx); - fcgi_connection_close(srv, con->plugin_ctx[p->id]); - return HANDLER_GO_ON; } static int fcgi_env_add(buffer *env, const char *key, size_t key_len, const char *val, size_t val_len) { size_t len; + char len_enc[8]; + size_t len_enc_len = 0; if (!key || !val) return -1; @@ -1593,7 +1685,7 @@ static int fcgi_env_add(buffer *env, const char *key, len += key_len > 127 ? 4 : 1; len += val_len > 127 ? 4 : 1; - if (env->used + len >= FCGI_MAX_LENGTH) { + if (buffer_string_length(env) + len >= FCGI_MAX_LENGTH) { /** * we can't append more headers, ignore it */ @@ -1605,38 +1697,37 @@ static int fcgi_env_add(buffer *env, const char *key, * * HINT: this can't happen as FCGI_MAX_LENGTH is only 16bit */ - if (key_len > 0x7fffffff) key_len = 0x7fffffff; - if (val_len > 0x7fffffff) val_len = 0x7fffffff; + force_assert(key_len < 0x7fffffffu); + force_assert(val_len < 0x7fffffffu); - buffer_prepare_append(env, len); + buffer_string_prepare_append(env, len); if (key_len > 127) { - env->ptr[env->used++] = ((key_len >> 24) & 0xff) | 0x80; - env->ptr[env->used++] = (key_len >> 16) & 0xff; - env->ptr[env->used++] = (key_len >> 8) & 0xff; - env->ptr[env->used++] = (key_len >> 0) & 0xff; + len_enc[len_enc_len++] = ((key_len >> 24) & 0xff) | 0x80; + len_enc[len_enc_len++] = (key_len >> 16) & 0xff; + len_enc[len_enc_len++] = (key_len >> 8) & 0xff; + len_enc[len_enc_len++] = (key_len >> 0) & 0xff; } else { - env->ptr[env->used++] = (key_len >> 0) & 0xff; + len_enc[len_enc_len++] = (key_len >> 0) & 0xff; } if (val_len > 127) { - env->ptr[env->used++] = ((val_len >> 24) & 0xff) | 0x80; - env->ptr[env->used++] = (val_len >> 16) & 0xff; - env->ptr[env->used++] = (val_len >> 8) & 0xff; - env->ptr[env->used++] = (val_len >> 0) & 0xff; + len_enc[len_enc_len++] = ((val_len >> 24) & 0xff) | 0x80; + len_enc[len_enc_len++] = (val_len >> 16) & 0xff; + len_enc[len_enc_len++] = (val_len >> 8) & 0xff; + len_enc[len_enc_len++] = (val_len >> 0) & 0xff; } else { - env->ptr[env->used++] = (val_len >> 0) & 0xff; + len_enc[len_enc_len++] = (val_len >> 0) & 0xff; } - memcpy(env->ptr + env->used, key, key_len); - env->used += key_len; - memcpy(env->ptr + env->used, val, val_len); - env->used += val_len; + buffer_append_string_len(env, len_enc, len_enc_len); + buffer_append_string_len(env, key, key_len); + buffer_append_string_len(env, val, val_len); return 0; } -static int fcgi_header(FCGI_Header * header, unsigned char type, size_t request_id, int contentLength, unsigned char paddingLength) { +static int fcgi_header(FCGI_Header * header, unsigned char type, int request_id, int contentLength, unsigned char paddingLength) { force_assert(contentLength <= FCGI_MAX_LENGTH); header->version = FCGI_VERSION_1; @@ -1661,6 +1752,9 @@ typedef enum { static connection_result_t fcgi_establish_connection(server *srv, handler_ctx *hctx) { struct sockaddr *fcgi_addr; struct sockaddr_in fcgi_addr_in; +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + struct sockaddr_in6 fcgi_addr_in6; +#endif #ifdef HAVE_SYS_UN_H struct sockaddr_un fcgi_addr_un; #endif @@ -1670,29 +1764,28 @@ static connection_result_t fcgi_establish_connection(s fcgi_proc *proc = hctx->proc; int fcgi_fd = hctx->fd; - memset(&fcgi_addr, 0, sizeof(fcgi_addr)); - - if (!buffer_is_empty(proc->unixsocket)) { + if (!buffer_string_is_empty(proc->unixsocket)) { #ifdef HAVE_SYS_UN_H /* use the unix domain socket */ + memset(&fcgi_addr_un, 0, sizeof(fcgi_addr_un)); fcgi_addr_un.sun_family = AF_UNIX; - if (proc->unixsocket->used > sizeof(fcgi_addr_un.sun_path)) { + if (buffer_string_length(proc->unixsocket) + 1 > sizeof(fcgi_addr_un.sun_path)) { log_error_write(srv, __FILE__, __LINE__, "sB", "ERROR: Unix Domain socket filename too long:", proc->unixsocket); return -1; } - memcpy(fcgi_addr_un.sun_path, proc->unixsocket->ptr, proc->unixsocket->used); + memcpy(fcgi_addr_un.sun_path, proc->unixsocket->ptr, buffer_string_length(proc->unixsocket) + 1); #ifdef SUN_LEN servlen = SUN_LEN(&fcgi_addr_un); #else /* stevens says: */ - servlen = proc->unixsocket->used + sizeof(fcgi_addr_un.sun_family); + servlen = buffer_string_length(proc->unixsocket) + 1 + sizeof(fcgi_addr_un.sun_family); #endif fcgi_addr = (struct sockaddr *) &fcgi_addr_un; - if (buffer_is_empty(proc->connection_name)) { + if (buffer_string_is_empty(proc->connection_name)) { /* on remote spawing we have to set the connection-name now */ buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("unix:")); buffer_append_string_buffer(proc->connection_name, proc->unixsocket); @@ -1700,9 +1793,19 @@ static connection_result_t fcgi_establish_connection(s #else return CONNECTION_DEAD; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + } else if (host->family == AF_INET6 && !buffer_string_is_empty(host->host)) { + memset(&fcgi_addr_in6, 0, sizeof(fcgi_addr_in6)); + fcgi_addr_in6.sin6_family = AF_INET6; + inet_pton(AF_INET6, host->host->ptr, (char *) &fcgi_addr_in6.sin6_addr); + fcgi_addr_in6.sin6_port = htons(proc->port); + servlen = sizeof(fcgi_addr_in6); + fcgi_addr = (struct sockaddr *) &fcgi_addr_in6; +#endif } else { + memset(&fcgi_addr_in, 0, sizeof(fcgi_addr_in)); fcgi_addr_in.sin_family = AF_INET; - if (!buffer_is_empty(host->host)) { + if (!buffer_string_is_empty(host->host)) { if (0 == inet_aton(host->host->ptr, &(fcgi_addr_in.sin_addr))) { log_error_write(srv, __FILE__, __LINE__, "sbs", "converting IP address failed for", host->host, @@ -1717,17 +1820,19 @@ static connection_result_t fcgi_establish_connection(s servlen = sizeof(fcgi_addr_in); fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } - if (buffer_is_empty(proc->connection_name)) { + if (buffer_string_is_empty(proc->unixsocket)) { + if (buffer_string_is_empty(proc->connection_name)) { /* on remote spawing we have to set the connection-name now */ buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("tcp:")); - if (!buffer_is_empty(host->host)) { + if (!buffer_string_is_empty(host->host)) { buffer_append_string_buffer(proc->connection_name, host->host); } else { buffer_append_string_len(proc->connection_name, CONST_STR_LEN("localhost")); } buffer_append_string_len(proc->connection_name, CONST_STR_LEN(":")); - buffer_append_long(proc->connection_name, proc->port); + buffer_append_int(proc->connection_name, proc->port); } } @@ -1769,6 +1874,11 @@ static connection_result_t fcgi_establish_connection(s return CONNECTION_OK; } +#define FCGI_ENV_ADD_CHECK(ret, con) \ + if (ret == -1) { \ + con->http_status = 400; \ + return -1; \ + }; static int fcgi_env_add_request_headers(server *srv, connection *con, plugin_data *p) { size_t i; @@ -1777,28 +1887,15 @@ static int fcgi_env_add_request_headers(server *srv, c ds = (data_string *)con->request.headers->data[i]; - if (ds->value->used && ds->key->used) { - size_t j; - buffer_reset(srv->tmp_buf); - - if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { - buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("HTTP_")); - srv->tmp_buf->used--; + if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) { + /* Do not emit HTTP_PROXY in environment. + * Some executables use HTTP_PROXY to configure + * outgoing proxy. See also https://httpoxy.org/ */ + if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Proxy"))) { + continue; } - buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); - for (j = 0; j < ds->key->used - 1; j++) { - char c = '_'; - if (light_isalpha(ds->key->ptr[j])) { - /* upper-case */ - c = ds->key->ptr[j] & ~32; - } else if (light_isdigit(ds->key->ptr[j])) { - /* copy */ - c = ds->key->ptr[j]; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; + buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 1); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)),con); } @@ -1809,24 +1906,9 @@ static int fcgi_env_add_request_headers(server *srv, c ds = (data_string *)con->environment->data[i]; - if (ds->value->used && ds->key->used) { - size_t j; - buffer_reset(srv->tmp_buf); + if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) { + buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 0); - buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); - for (j = 0; j < ds->key->used - 1; j++) { - char c = '_'; - if (light_isalpha(ds->key->ptr[j])) { - /* upper-case */ - c = ds->key->ptr[j] & ~32; - } else if (light_isdigit(ds->key->ptr[j])) { - /* copy */ - c = ds->key->ptr[j]; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = c; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)), con); } } @@ -1834,13 +1916,46 @@ static int fcgi_env_add_request_headers(server *srv, c return 0; } +static void fcgi_stdin_append(server *srv, connection *con, handler_ctx *hctx, int request_id) { + FCGI_Header header; + chunkqueue *req_cq = con->request_content_queue; + plugin_data *p = hctx->plugin_data; + off_t offset, weWant; + const off_t req_cqlen = req_cq->bytes_in - req_cq->bytes_out; -static int fcgi_create_env(server *srv, handler_ctx *hctx, size_t request_id) { + /* something to send ? */ + for (offset = 0; offset != req_cqlen; offset += weWant) { + weWant = req_cqlen - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cqlen - offset; + + /* we announce toWrite octets + * now take all request_content chunks available + * */ + + fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); + chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); + hctx->wb_reqlen += sizeof(header); + + if (p->conf.debug > 10) { + log_error_write(srv, __FILE__, __LINE__, "soso", "tosend:", offset, "/", req_cqlen); + } + + chunkqueue_steal(hctx->wb, req_cq, weWant); + /*(hctx->wb_reqlen already includes content_length)*/ + } + + if (hctx->wb->bytes_in == hctx->wb_reqlen) { + /* terminate STDIN */ + fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); + chunkqueue_append_mem(hctx->wb, (const char *)&header, sizeof(header)); + hctx->wb_reqlen += (int)sizeof(header); + } +} + +static int fcgi_create_env(server *srv, handler_ctx *hctx, int request_id) { FCGI_BeginRequestRecord beginRecord; FCGI_Header header; - buffer *b; - char buf[32]; + char buf[LI_ITOSTRING_LENGTH]; const char *s; #ifdef HAVE_IPV6 char b2[INET6_ADDRSTRLEN + 1]; @@ -1850,6 +1965,7 @@ static int fcgi_create_env(server *srv, handler_ctx *h fcgi_extension_host *host= hctx->host; connection *con = hctx->remote_conn; + buffer * const req_uri = con->request.orig_uri; server_socket *srv_sock = con->srv_socket; sock_addr our_addr; @@ -1863,23 +1979,14 @@ static int fcgi_create_env(server *srv, handler_ctx *h beginRecord.body.flags = 0; memset(beginRecord.body.reserved, 0, sizeof(beginRecord.body.reserved)); - b = chunkqueue_get_append_buffer(hctx->wb); - - buffer_copy_memory(b, (const char *)&beginRecord, sizeof(beginRecord)); - /* send FCGI_PARAMS */ - buffer_prepare_copy(p->fcgi_env, 1024); + buffer_string_prepare_copy(p->fcgi_env, 1023); + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_BUF_LEN(con->conf.server_tag)),con) - if (buffer_is_empty(con->conf.server_tag)) { - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_DESC)),con) - } else { - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_BUF_LEN(con->conf.server_tag)),con) - } + if (!buffer_is_empty(con->server_name)) { + size_t len = buffer_string_length(con->server_name); - if (con->server_name->used) { - size_t len = con->server_name->used - 1; - if (con->server_name->ptr[0] == '[') { const char *colon = strstr(con->server_name->ptr, "]:"); if (colon) len = (colon + 1) - con->server_name->ptr; @@ -1904,7 +2011,7 @@ static int fcgi_create_env(server *srv, handler_ctx *h FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")),con) - LI_ltostr(buf, + li_utostrn(buf, sizeof(buf), #ifdef HAVE_IPV6 ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) #else @@ -1917,14 +2024,15 @@ static int fcgi_create_env(server *srv, handler_ctx *h /* get the server-side of the connection to the client */ our_addr_len = sizeof(our_addr); - if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) { + if (-1 == getsockname(con->fd, (struct sockaddr *)&our_addr, &our_addr_len) + || our_addr_len > (socklen_t)sizeof(our_addr)) { s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr)); } else { s = inet_ntop_cache_get_ip(srv, &(our_addr)); } FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)),con) - LI_ltostr(buf, + li_utostrn(buf, sizeof(buf), #ifdef HAVE_IPV6 ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) #else @@ -1940,8 +2048,7 @@ static int fcgi_create_env(server *srv, handler_ctx *h if (con->request.content_length > 0 && host->mode != FCGI_AUTHORIZER) { /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */ - /* request.content_length < SSIZE_MAX, see request.c */ - LI_ltostr(buf, con->request.content_length); + li_itostrn(buf, sizeof(buf), con->request.content_length); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)),con) } @@ -1955,15 +2062,15 @@ static int fcgi_create_env(server *srv, handler_ctx *h FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)),con) - if (!buffer_is_empty(con->request.pathinfo)) { + if (!buffer_string_is_empty(con->request.pathinfo)) { FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)),con) /* PATH_TRANSLATED is only defined if PATH_INFO is set */ - if (!buffer_is_empty(host->docroot)) { - buffer_copy_string_buffer(p->path, host->docroot); + if (!buffer_string_is_empty(host->docroot)) { + buffer_copy_buffer(p->path, host->docroot); } else { - buffer_copy_string_buffer(p->path, con->physical.basedir); + buffer_copy_buffer(p->path, con->physical.basedir); } buffer_append_string_buffer(p->path, con->request.pathinfo); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path)),con) @@ -1980,19 +2087,19 @@ static int fcgi_create_env(server *srv, handler_ctx *h * parameter. */ - if (!buffer_is_empty(host->docroot)) { + if (!buffer_string_is_empty(host->docroot)) { /* * rewrite SCRIPT_FILENAME * */ - buffer_copy_string_buffer(p->path, host->docroot); + buffer_copy_buffer(p->path, host->docroot); buffer_append_string_buffer(p->path, con->uri.path); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)),con) FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot)),con) } else { - buffer_copy_string_buffer(p->path, con->physical.path); + buffer_copy_buffer(p->path, con->physical.path); /* cgi.fix_pathinfo need a broken SCRIPT_FILENAME to find out what PATH_INFO is itself * @@ -2006,7 +2113,7 @@ static int fcgi_create_env(server *srv, handler_ctx *h FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.basedir)),con) } - if (host->strip_request_uri->used > 1) { + if (!buffer_string_is_empty(host->strip_request_uri)) { /* we need at least one char to strip off */ /** * /app1/index/list @@ -2016,37 +2123,44 @@ static int fcgi_create_env(server *srv, handler_ctx *h * /index/list * */ - if ('/' != host->strip_request_uri->ptr[host->strip_request_uri->used - 2]) { + + if ('/' != host->strip_request_uri->ptr[buffer_string_length(host->strip_request_uri) - 1]) { /* fix the user-input to have / as last char */ buffer_append_string_len(host->strip_request_uri, CONST_STR_LEN("/")); } - if (con->request.orig_uri->used >= host->strip_request_uri->used && - 0 == strncmp(con->request.orig_uri->ptr, host->strip_request_uri->ptr, host->strip_request_uri->used - 1)) { + if (buffer_string_length(req_uri) >= buffer_string_length(host->strip_request_uri) && + 0 == strncmp(req_uri->ptr, host->strip_request_uri->ptr, buffer_string_length(host->strip_request_uri))) { /* the left is the same */ - fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), - con->request.orig_uri->ptr + (host->strip_request_uri->used - 2), - con->request.orig_uri->used - (host->strip_request_uri->used - 2) - 1); + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), + req_uri->ptr + (buffer_string_length(host->strip_request_uri) - 1), + buffer_string_length(req_uri) - (buffer_string_length(host->strip_request_uri) - 1)), con) } else { - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)),con) + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(req_uri)),con) } } else { - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)),con) + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(req_uri)),con) } if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) { - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri)),con) + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri)),con); } - if (!buffer_is_empty(con->uri.query)) { + if (!buffer_string_is_empty(con->uri.query)) { FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)),con) } else { FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN("")),con) } s = get_http_method_name(con->request.http_method); + force_assert(s); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)),con) - FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")),con) /* if php is compiled with --force-redirect */ + /* set REDIRECT_STATUS for php compiled with --force-redirect + * (if REDIRECT_STATUS has not already been set by error handler) */ + if (0 == con->error_handler_saved_status) { + FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")), con); + } s = get_http_version_name(con->request.http_version); + force_assert(s); FCGI_ENV_ADD_CHECK(fcgi_env_add(p->fcgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)),con) if (buffer_is_equal_caseless_string(con->uri.scheme, CONST_STR_LEN("https"))) { @@ -2055,139 +2169,26 @@ static int fcgi_create_env(server *srv, handler_ctx *h FCGI_ENV_ADD_CHECK(fcgi_env_add_request_headers(srv, con, p), con); - fcgi_header(&(header), FCGI_PARAMS, request_id, p->fcgi_env->used, 0); - buffer_append_memory(b, (const char *)&header, sizeof(header)); - buffer_append_memory(b, (const char *)p->fcgi_env->ptr, p->fcgi_env->used); + { + buffer *b = buffer_init(); - fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); - buffer_append_memory(b, (const char *)&header, sizeof(header)); + buffer_copy_string_len(b, (const char *)&beginRecord, sizeof(beginRecord)); - b->used++; /* add virtual \0 */ - hctx->wb->bytes_in += b->used - 1; + fcgi_header(&(header), FCGI_PARAMS, request_id, buffer_string_length(p->fcgi_env), 0); + buffer_append_string_len(b, (const char *)&header, sizeof(header)); + buffer_append_string_buffer(b, p->fcgi_env); - if (con->request.content_length) { - chunkqueue *req_cq = con->request_content_queue; - chunk *req_c; - off_t offset; + fcgi_header(&(header), FCGI_PARAMS, request_id, 0, 0); + buffer_append_string_len(b, (const char *)&header, sizeof(header)); - /* something to send ? */ - for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; ) { - off_t weWant = req_cq->bytes_in - offset > FCGI_MAX_LENGTH ? FCGI_MAX_LENGTH : req_cq->bytes_in - offset; - off_t written = 0; - off_t weHave = 0; - - /* we announce toWrite octets - * now take all the request_content chunks that we need to fill this request - * */ - - b = chunkqueue_get_append_buffer(hctx->wb); - fcgi_header(&(header), FCGI_STDIN, request_id, weWant, 0); - buffer_copy_memory(b, (const char *)&header, sizeof(header)); - hctx->wb->bytes_in += sizeof(header); - - if (p->conf.debug > 10) { - log_error_write(srv, __FILE__, __LINE__, "soso", "tosend:", offset, "/", req_cq->bytes_in); - } - - for (written = 0; written != weWant; ) { - if (p->conf.debug > 10) { - log_error_write(srv, __FILE__, __LINE__, "soso", "chunk:", written, "/", weWant); - } - - switch (req_c->type) { - case FILE_CHUNK: - weHave = req_c->file.length - req_c->offset; - - if (weHave > weWant - written) weHave = weWant - written; - - if (p->conf.debug > 10) { - log_error_write(srv, __FILE__, __LINE__, "soSosOsb", - "sending", weHave, "bytes from (", - req_c->offset, "/", req_c->file.length, ")", - req_c->file.name); - } - - force_assert(weHave != 0); - - chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); - - req_c->offset += weHave; - req_cq->bytes_out += weHave; - written += weHave; - - hctx->wb->bytes_in += weHave; - - /* steal the tempfile - * - * This is tricky: - * - we reference the tempfile from the request-content-queue several times - * if the req_c is larger than FCGI_MAX_LENGTH - * - we can't simply cleanup the request-content-queue as soon as possible - * as it would remove the tempfiles - * - the idea is to 'steal' the tempfiles and attach the is_temp flag to the last - * referencing chunk of the fastcgi-write-queue - * - * */ - - if (req_c->offset == req_c->file.length) { - chunk *c; - - if (p->conf.debug > 10) { - log_error_write(srv, __FILE__, __LINE__, "s", "next chunk"); - } - c = hctx->wb->last; - - force_assert(c->type == FILE_CHUNK); - force_assert(req_c->file.is_temp == 1); - - c->file.is_temp = 1; - req_c->file.is_temp = 0; - - chunkqueue_remove_finished_chunks(req_cq); - - req_c = req_cq->first; - } - - break; - case MEM_CHUNK: - /* append to the buffer */ - weHave = req_c->mem->used - 1 - req_c->offset; - - if (weHave > weWant - written) weHave = weWant - written; - - buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); - - req_c->offset += weHave; - req_cq->bytes_out += weHave; - written += weHave; - - hctx->wb->bytes_in += weHave; - - if (req_c->offset == (off_t) req_c->mem->used - 1) { - chunkqueue_remove_finished_chunks(req_cq); - - req_c = req_cq->first; - } - - break; - default: - break; - } - } - - b->used++; /* add virtual \0 */ - offset += weWant; - } + hctx->wb_reqlen = buffer_string_length(b); + chunkqueue_append_buffer(hctx->wb, b); + buffer_free(b); } - b = chunkqueue_get_append_buffer(hctx->wb); - /* terminate STDIN */ - fcgi_header(&(header), FCGI_STDIN, request_id, 0, 0); - buffer_copy_memory(b, (const char *)&header, sizeof(header)); - b->used++; /* add virtual \0 */ + hctx->wb_reqlen += con->request.content_length;/* (eventual) (minimal) total request size, not necessarily including all fcgi_headers around content length yet */ + fcgi_stdin_append(srv, con, hctx, request_id); - hctx->wb->bytes_in += sizeof(header); - return 0; } @@ -2201,17 +2202,14 @@ static int fcgi_response_parse(server *srv, connection UNUSED(srv); - buffer_copy_string_buffer(p->parse_response, in); - /* search for \n */ - for (s = p->parse_response->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) { + for (s = in->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) { char *key, *value; int key_len; - data_string *ds = NULL; /* a good day. Someone has read the specs and is sending a \r\n to us */ - if (ns > p->parse_response->ptr && + if (ns > in->ptr && *(ns-1) == '\r') { *(ns-1) = '\0'; } @@ -2237,6 +2235,7 @@ static int fcgi_response_parse(server *srv, connection /* don't forward Status: */ if (0 != strncasecmp(key, "Status", key_len)) { + data_string *ds; if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { ds = data_response_init(); } @@ -2255,8 +2254,13 @@ static int fcgi_response_parse(server *srv, connection break; case 6: if (0 == strncasecmp(key, "Status", key_len)) { - con->http_status = strtol(value, NULL, 10); - con->parsed_response |= HTTP_STATUS; + int status = strtol(value, NULL, 10); + if (status >= 100 && status < 1000) { + con->http_status = status; + con->parsed_response |= HTTP_STATUS; + } else { + con->http_status = 502; + } } break; case 8: @@ -2271,7 +2275,7 @@ static int fcgi_response_parse(server *srv, connection } break; case 11: - if (host->allow_xsendfile && 0 == strncasecmp(key, "X-Sendfile2", key_len)&& hctx->send_content_body) { + if (host->xsendfile_allow && 0 == strncasecmp(key, "X-Sendfile2", key_len) && hctx->send_content_body) { char *pos = value; have_sendfile2 = 1; @@ -2289,7 +2293,7 @@ static int fcgi_response_parse(server *srv, connection if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't find range after filename:", filename); } - return 1; + return 502; } buffer_copy_string_len(srv->tmp_buf, filename, range - filename); @@ -2297,20 +2301,44 @@ static int fcgi_response_parse(server *srv, connection for (pos = ++range; *pos && *pos != ' ' && *pos != ','; pos++) ; buffer_urldecode_path(srv->tmp_buf); + buffer_path_simplify(srv->tmp_buf, srv->tmp_buf); + if (con->conf.force_lowercase_filenames) { + buffer_to_lower(srv->tmp_buf); + } + if (host->xsendfile_docroot->used) { + size_t i, xlen = buffer_string_length(srv->tmp_buf); + for (i = 0; i < host->xsendfile_docroot->used; ++i) { + data_string *ds = (data_string *)host->xsendfile_docroot->data[i]; + size_t dlen = buffer_string_length(ds->value); + if (dlen <= xlen + && (!con->conf.force_lowercase_filenames + ? 0 == memcmp(srv->tmp_buf->ptr, ds->value->ptr, dlen) + : 0 == strncasecmp(srv->tmp_buf->ptr, ds->value->ptr, dlen))) { + break; + } + } + if (i == host->xsendfile_docroot->used) { + log_error_write(srv, __FILE__, __LINE__, "SBs", + "X-Sendfile2 (", srv->tmp_buf, + ") not under configured x-sendfile-docroot(s)"); + return 403; + } + } + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, srv->tmp_buf, &sce)) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sb", "send-file error: couldn't get stat_cache entry for X-Sendfile2:", srv->tmp_buf); } - return 1; + return 404; } else if (!S_ISREG(sce->st.st_mode)) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sb", "send-file error: wrong filetype for X-Sendfile2:", srv->tmp_buf); } - return 1; + return 502; } /* found the file */ @@ -2335,7 +2363,7 @@ range_failed: if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "ss", "Couldn't decode range after filename:", filename); } - return 1; + return 502; range_success: ; } @@ -2343,12 +2371,14 @@ range_success: ; /* no parameters accepted */ while (*pos == ' ') pos++; - if (*pos != '\0' && *pos != ',') return 1; + if (*pos != '\0' && *pos != ',') return 502; range_len = end_range - begin_range + 1; - if (range_len < 0) return 1; + if (range_len < 0) return 502; if (range_len != 0) { - http_chunk_append_file(srv, con, srv->tmp_buf, begin_range, range_len); + if (0 != http_chunk_append_file_range(srv, con, srv->tmp_buf, begin_range, range_len)) { + return 502; + } } sendfile2_content_length += range_len; @@ -2358,7 +2388,7 @@ range_success: ; break; case 14: if (0 == strncasecmp(key, "Content-Length", key_len)) { - con->response.content_length = strtol(value, NULL, 10); + con->response.content_length = strtoul(value, NULL, 10); con->parsed_response |= HTTP_CONTENT_LENGTH; if (con->response.content_length < 0) con->response.content_length = 0; @@ -2372,21 +2402,18 @@ range_success: ; if (have_sendfile2) { data_string *dcls; - hctx->send_content_body = 0; - joblist_append(srv, con); - /* fix content-length */ if (NULL == (dcls = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { dcls = data_response_init(); } buffer_copy_string_len(dcls->key, "Content-Length", sizeof("Content-Length")-1); - buffer_copy_off_t(dcls->value, sendfile2_content_length); - dcls = (data_string*) array_replace(con->response.headers, (data_unset *)dcls); - if (dcls) dcls->free((data_unset*)dcls); + buffer_copy_int(dcls->value, sendfile2_content_length); + array_replace(con->response.headers, (data_unset *)dcls); con->parsed_response |= HTTP_CONTENT_LENGTH; con->response.content_length = sendfile2_content_length; + return 200; } /* CGI/1.1 rev 03 - 7.2.1.2 */ @@ -2400,14 +2427,14 @@ range_success: ; typedef struct { buffer *b; - size_t len; + unsigned int len; int type; int padding; - size_t request_id; + int request_id; } fastcgi_response_packet; static int fastcgi_get_packet(server *srv, handler_ctx *hctx, fastcgi_response_packet *packet) { - chunk * c; + chunk *c; size_t offset; size_t toread; FCGI_Header *header; @@ -2423,26 +2450,21 @@ static int fastcgi_get_packet(server *srv, handler_ctx offset = 0; toread = 8; /* get at least the FastCGI header */ for (c = hctx->rb->first; c; c = c->next) { - size_t weHave = c->mem->used - c->offset - 1; + size_t weHave = buffer_string_length(c->mem) - c->offset; if (weHave > toread) weHave = toread; - if (packet->b->used == 0) { - buffer_copy_string_len(packet->b, c->mem->ptr + c->offset, weHave); - } else { - buffer_append_string_len(packet->b, c->mem->ptr + c->offset, weHave); - } + buffer_append_string_len(packet->b, c->mem->ptr + c->offset, weHave); toread -= weHave; offset = weHave; /* skip offset bytes in chunk for "real" data */ if (0 == toread) break; } - if ((packet->b->used == 0) || - (packet->b->used - 1 < sizeof(FCGI_Header))) { + if (buffer_string_length(packet->b) < sizeof(FCGI_Header)) { /* no header */ if (hctx->plugin_data->conf.debug) { - log_error_write(srv, __FILE__, __LINE__, "sdsds", "FastCGI: header too small:", packet->b->used, "bytes <", sizeof(FCGI_Header), "bytes, waiting for more data"); + log_error_write(srv, __FILE__, __LINE__, "sdsds", "FastCGI: header too small:", buffer_string_length(packet->b), "bytes <", sizeof(FCGI_Header), "bytes, waiting for more data"); } buffer_free(packet->b); @@ -2459,13 +2481,13 @@ static int fastcgi_get_packet(server *srv, handler_ctx packet->padding = header->paddingLength; /* ->b should only be the content */ - buffer_copy_string_len(packet->b, CONST_STR_LEN("")); /* used == 1 */ + buffer_string_set_length(packet->b, 0); if (packet->len) { /* copy the content */ - for (; c && (packet->b->used < packet->len + 1); c = c->next) { - size_t weWant = packet->len - (packet->b->used - 1); - size_t weHave = c->mem->used - c->offset - offset - 1; + for (; c && (buffer_string_length(packet->b) < packet->len); c = c->next) { + size_t weWant = packet->len - buffer_string_length(packet->b); + size_t weHave = buffer_string_length(c->mem) - c->offset - offset; if (weHave > weWant) weHave = weWant; @@ -2475,39 +2497,25 @@ static int fastcgi_get_packet(server *srv, handler_ctx offset = 0; } - if (packet->b->used < packet->len + 1) { + if (buffer_string_length(packet->b) < packet->len) { /* we didn't get the full packet */ buffer_free(packet->b); return -1; } - packet->b->used -= packet->padding; - packet->b->ptr[packet->b->used - 1] = '\0'; + buffer_string_set_length(packet->b, buffer_string_length(packet->b) - packet->padding); } - /* tag the chunks as read */ - toread = packet->len + sizeof(FCGI_Header); - for (c = hctx->rb->first; c && toread; c = c->next) { - if (c->mem->used - c->offset - 1 <= toread) { - /* we read this whole buffer, move it to unused */ - toread -= c->mem->used - c->offset - 1; - c->offset = c->mem->used - 1; /* everthing has been written */ - } else { - c->offset += toread; - toread = 0; - } - } + chunkqueue_mark_written(hctx->rb, packet->len + sizeof(FCGI_Header)); - chunkqueue_remove_finished_chunks(hctx->rb); - return 0; } static int fcgi_demux_response(server *srv, handler_ctx *hctx) { int fin = 0; - int toread; - ssize_t r; + int toread, ret; + ssize_t r = 0; plugin_data *p = hctx->plugin_data; connection *con = hctx->remote_conn; @@ -2518,33 +2526,43 @@ static int fcgi_demux_response(server *srv, handler_ct /* * check how much we have to read */ + #if !defined(_WIN32) && !defined(__CYGWIN__) if (ioctl(hctx->fd, FIONREAD, &toread)) { - if (errno == EAGAIN) return 0; + if (errno == EAGAIN) { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + return 0; + } log_error_write(srv, __FILE__, __LINE__, "sd", "unexpected end-of-file (perhaps the fastcgi process died):", fcgi_fd); return -1; } + #else + toread = 4096; + #endif - /* init read-buffer */ - if (toread > 0) { - buffer *b; - chunk *cq_first = hctx->rb->first; - chunk *cq_last = hctx->rb->last; + char *mem; + size_t mem_len; - b = chunkqueue_get_append_buffer(hctx->rb); - buffer_prepare_copy(b, toread + 1); + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)) { + off_t cqlen = chunkqueue_length(hctx->rb); + if (cqlen + toread > 65536 + (int)sizeof(FCGI_Header)) { /*(max size of FastCGI packet + 1)*/ + if (cqlen < 65536 + (int)sizeof(FCGI_Header)) { + toread = 65536 + (int)sizeof(FCGI_Header) - cqlen; + } else { /* should not happen */ + toread = toread < 1024 ? toread : 1024; + } + } + } - /* append to read-buffer */ - if (-1 == (r = read(hctx->fd, b->ptr, toread))) { + chunkqueue_get_memory(hctx->rb, &mem, &mem_len, 0, toread); + r = read(hctx->fd, mem, mem_len); + chunkqueue_use_memory(hctx->rb, r > 0 ? r : 0); + + if (-1 == r) { if (errno == EAGAIN) { - /* roll back the last chunk allocation, - and continue on next iteration */ - buffer_free(hctx->rb->last->mem); - free(hctx->rb->last); - hctx->rb->first = cq_first; - hctx->rb->last = cq_last; + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return 0; } log_error_write(srv, __FILE__, __LINE__, "sds", @@ -2552,13 +2570,8 @@ static int fcgi_demux_response(server *srv, handler_ct fcgi_fd, strerror(errno)); return -1; } - - /* this should be catched by the b > 0 above */ - force_assert(r); - - b->used = r + 1; /* one extra for the fake \0 */ - b->ptr[b->used - 1] = '\0'; - } else { + } + if (0 == r) { log_error_write(srv, __FILE__, __LINE__, "ssdsb", "unexpected end-of-file (perhaps the fastcgi process died):", "pid:", proc->pid, @@ -2587,7 +2600,6 @@ static int fcgi_demux_response(server *srv, handler_ct /* is the header already finished */ if (0 == con->file_started) { char *c; - size_t blen; data_string *ds; /* search for header terminator @@ -2598,30 +2610,39 @@ static int fcgi_demux_response(server *srv, handler_ct * search for \n\n */ - if (hctx->response_header->used == 0) { - buffer_copy_string_buffer(hctx->response_header, packet.b); - } else { - buffer_append_string_buffer(hctx->response_header, packet.b); - } + buffer_append_string_buffer(hctx->response_header, packet.b); if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\r\n\r\n")))) { - blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 4; - hctx->response_header->used = (c - hctx->response_header->ptr) + 3; - c += 4; /* point the the start of the response */ + char *hend = c + 4; /* header end == body start */ + size_t hlen = hend - hctx->response_header->ptr; + buffer_copy_string_len(packet.b, hend, buffer_string_length(hctx->response_header) - hlen); + buffer_string_set_length(hctx->response_header, hlen); } else if (NULL != (c = buffer_search_string_len(hctx->response_header, CONST_STR_LEN("\n\n")))) { - blen = hctx->response_header->used - (c - hctx->response_header->ptr) - 2; - hctx->response_header->used = c - hctx->response_header->ptr + 2; - c += 2; /* point the the start of the response */ + char *hend = c + 2; /* header end == body start */ + size_t hlen = hend - hctx->response_header->ptr; + buffer_copy_string_len(packet.b, hend, buffer_string_length(hctx->response_header) - hlen); + buffer_string_set_length(hctx->response_header, hlen); } else { /* no luck, no header found */ + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (buffer_string_length(hctx->response_header) > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + fin = 1; + } break; } /* parse the response header */ - if (fcgi_response_parse(srv, con, p, hctx->response_header)) { - con->http_status = 502; - hctx->send_content_body = 0; + if ((ret = fcgi_response_parse(srv, con, p, hctx->response_header))) { + if (200 != ret) { /*(200 returned for X-Sendfile2 handled)*/ + con->http_status = ret; + con->mode = DIRECT; + } con->file_started = 1; + hctx->send_content_body = 0; + fin = 1; break; } @@ -2634,59 +2655,37 @@ static int fcgi_demux_response(server *srv, handler_ct hctx->send_content_body = 0; } - if (host->allow_xsendfile && hctx->send_content_body && + if (host->xsendfile_allow && hctx->send_content_body && (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file")) || NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile")))) { - stat_cache_entry *sce; - - if (HANDLER_ERROR != stat_cache_get_entry(srv, con, ds->value, &sce)) { - data_string *dcls; - if (NULL == (dcls = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { - dcls = data_response_init(); - } - /* found */ - http_chunk_append_file(srv, con, ds->value, 0, sce->st.st_size); - hctx->send_content_body = 0; /* ignore the content */ - joblist_append(srv, con); - - buffer_copy_string_len(dcls->key, "Content-Length", sizeof("Content-Length")-1); - buffer_copy_off_t(dcls->value, sce->st.st_size); - dcls = (data_string*) array_replace(con->response.headers, (data_unset *)dcls); - if (dcls) dcls->free((data_unset*)dcls); - - con->parsed_response |= HTTP_CONTENT_LENGTH; - con->response.content_length = sce->st.st_size; - } else { - log_error_write(srv, __FILE__, __LINE__, "sb", - "send-file error: couldn't get stat_cache entry for:", - ds->value); - con->http_status = 502; - hctx->send_content_body = 0; - con->file_started = 1; - break; + http_response_xsendfile(srv, con, ds->value, host->xsendfile_docroot); + if (con->mode == DIRECT) { + fin = 1; } + + hctx->send_content_body = 0; /* ignore the content */ + break; } + } - - if (hctx->send_content_body && blen > 1) { - /* enable chunked-transfer-encoding */ - if (con->request.http_version == HTTP_VERSION_1_1 && - !(con->parsed_response & HTTP_CONTENT_LENGTH)) { - con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + if (hctx->send_content_body && !buffer_string_is_empty(packet.b)) { + if (0 != http_chunk_append_buffer(srv, con, packet.b)) { + /* error writing to tempfile; + * truncate response or send 500 if nothing sent yet */ + fin = 1; + break; + } + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && chunkqueue_length(con->write_queue) > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_fastcgi_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); } - - http_chunk_append_mem(srv, con, c, blen); - joblist_append(srv, con); } - } else if (hctx->send_content_body && packet.b->used > 1) { - if (con->request.http_version == HTTP_VERSION_1_1 && - !(con->parsed_response & HTTP_CONTENT_LENGTH)) { - /* enable chunked-transfer-encoding */ - con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; - } - - http_chunk_append_mem(srv, con, packet.b->ptr, packet.b->used); - joblist_append(srv, con); } break; case FCGI_STDERR: @@ -2697,16 +2696,6 @@ static int fcgi_demux_response(server *srv, handler_ct break; case FCGI_END_REQUEST: - con->file_finished = 1; - - if (host->mode != FCGI_AUTHORIZER || - !(con->http_status == 0 || - con->http_status == 200)) { - /* send chunk-end if necessary */ - http_chunk_append_mem(srv, con, NULL, 0); - joblist_append(srv, con); - } - fin = 1; break; default: @@ -2820,7 +2809,7 @@ static int fcgi_restart_dead_procs(server *srv, plugin /* local procs get restarted by us, * remote ones hopefully by the admin */ - if (!buffer_is_empty(host->bin_path)) { + if (!buffer_string_is_empty(host->bin_path)) { /* we still have connections bound to this proc, * let them terminate first */ if (proc->load != 0) break; @@ -2874,7 +2863,7 @@ static handler_t fcgi_write_request(server *srv, handl log_error_write(srv, __FILE__, __LINE__, "s", "fatal error: host = NULL"); return HANDLER_ERROR; } - if ((!host->port && !host->unixsocket->used)) { + if ((!host->port && buffer_string_is_empty(host->unixsocket))) { log_error_write(srv, __FILE__, __LINE__, "s", "fatal error: neither host->port nor host->unixsocket is set"); return HANDLER_ERROR; } @@ -2923,7 +2912,7 @@ static handler_t fcgi_write_request(server *srv, handl switch(hctx->state) { case FCGI_STATE_CONNECT_DELAYED: /* should never happen */ - break; + return HANDLER_WAIT_FOR_EVENT; case FCGI_STATE_INIT: /* do we have a running process for this host (max-procs) ? */ hctx->proc = NULL; @@ -2947,9 +2936,7 @@ static handler_t fcgi_write_request(server *srv, handl if (proc->load < hctx->proc->load) hctx->proc = proc; } - ret = host->unixsocket->used ? AF_UNIX : AF_INET; - - if (-1 == (hctx->fd = socket(ret, SOCK_STREAM, 0))) { + if (-1 == (hctx->fd = socket(host->family, SOCK_STREAM, 0))) { if (errno == EMFILE || errno == EINTR) { log_error_write(srv, __FILE__, __LINE__, "sd", @@ -3066,8 +3053,9 @@ static handler_t fcgi_write_request(server *srv, handl "fcgi-request is already in use:", hctx->request_id); } - /* fall through */ if (-1 == fcgi_create_env(srv, hctx, hctx->request_id)) return HANDLER_ERROR; + + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); fcgi_set_state(srv, hctx, FCGI_STATE_WRITE); /* fall through */ case FCGI_STATE_WRITE: @@ -3098,43 +3086,42 @@ static handler_t fcgi_write_request(server *srv, handl } } - if (hctx->wb->bytes_out == hctx->wb->bytes_in) { - /* we don't need the out event anymore */ - fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + if (hctx->wb->bytes_out == hctx->wb_reqlen) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); fcgi_set_state(srv, hctx, FCGI_STATE_READ); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); - - return HANDLER_WAIT_FOR_EVENT; + off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; + if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 65536 - 16384) { + /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ + if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { + con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; + con->is_readable = 1; /* trigger optimistic read from client */ + } + } + if (0 == wblen) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + } } - break; + return HANDLER_WAIT_FOR_EVENT; case FCGI_STATE_READ: /* waiting for a response */ - break; + return HANDLER_WAIT_FOR_EVENT; default: log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); return HANDLER_ERROR; } - - return HANDLER_WAIT_FOR_EVENT; } /* might be called on fdevent after a connect() is delay too * */ -SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { - plugin_data *p = p_d; - - handler_ctx *hctx = con->plugin_ctx[p->id]; +static handler_t fcgi_send_request(server *srv, handler_ctx *hctx) { fcgi_extension_host *host; + handler_t rc; - if (NULL == hctx) return HANDLER_GO_ON; - - /* not my job */ - if (con->mode != p->id) return HANDLER_GO_ON; - /* we don't have a host yet, choose one * -> this happens in the first round * and when the host died and we have to select a new one */ @@ -3168,9 +3155,6 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { fcgi_connection_close(srv, hctx); - con->http_status = 500; - con->mode = DIRECT; - return HANDLER_FINISHED; } @@ -3194,8 +3178,13 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { } /* ok, create the request */ - switch(fcgi_write_request(srv, hctx)) { - case HANDLER_ERROR: + rc = fcgi_write_request(srv, hctx); + if (HANDLER_ERROR != rc) { + return rc; + } else { + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; + if (hctx->state == FCGI_STATE_INIT || hctx->state == FCGI_STATE_CONNECT_DELAYED) { fcgi_restart_dead_procs(srv, p, host); @@ -3203,53 +3192,85 @@ SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { /* cleanup this request and let the request handler start this request again */ if (hctx->reconnects < 5) { fcgi_reconnect(srv, hctx); - joblist_append(srv, con); /* in case we come from the event-handler */ - return HANDLER_WAIT_FOR_FD; + return HANDLER_COMEBACK; } else { fcgi_connection_close(srv, hctx); - - buffer_reset(con->physical.path); - con->mode = DIRECT; con->http_status = 503; - joblist_append(srv, con); /* in case we come from the event-handler */ return HANDLER_FINISHED; } } else { + int status = con->http_status; fcgi_connection_close(srv, hctx); + con->http_status = (status == 400) ? 400 : 503; /* see FCGI_ENV_ADD_CHECK() for 400 error */ - buffer_reset(con->physical.path); - con->mode = DIRECT; - if (con->http_status != 400) con->http_status = 503; - joblist_append(srv, con); /* really ? */ - return HANDLER_FINISHED; } - case HANDLER_WAIT_FOR_EVENT: - if (con->file_started == 1) { - return HANDLER_FINISHED; + } +} + + +static handler_t fcgi_recv_response(server *srv, handler_ctx *hctx); + + +SUBREQUEST_FUNC(mod_fastcgi_handle_subrequest) { + plugin_data *p = p_d; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + + if (NULL == hctx) return HANDLER_GO_ON; + + /* not my job */ + if (con->mode != p->id) return HANDLER_GO_ON; + + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && con->file_started) { + if (chunkqueue_length(con->write_queue) > 65536 - 4096) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) { + /* optimistic read from backend, which might re-enable FDEVENT_IN */ + handler_t rc = fcgi_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } + + if (0 == hctx->wb->bytes_in + ? con->state == CON_STATE_READ_POST + : hctx->wb->bytes_in < hctx->wb_reqlen) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT; } else { - return HANDLER_WAIT_FOR_EVENT; + handler_t r = connection_handle_read_post_state(srv, con); + chunkqueue *req_cq = con->request_content_queue; + if (0 != hctx->wb->bytes_in && !chunkqueue_is_empty(req_cq)) { + fcgi_stdin_append(srv, con, hctx, hctx->request_id); + if (fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; } - case HANDLER_WAIT_FOR_FD: - return HANDLER_WAIT_FOR_FD; - default: - log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default"); - return HANDLER_ERROR; } + + return ((0 == hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb)) + && hctx->state != FCGI_STATE_CONNECT_DELAYED) + ? fcgi_send_request(srv, hctx) + : HANDLER_WAIT_FOR_EVENT; } -static handler_t fcgi_handle_fdevent(server *srv, void *ctx, int revents) { - handler_ctx *hctx = ctx; + +static handler_t fcgi_recv_response(server *srv, handler_ctx *hctx) { connection *con = hctx->remote_conn; plugin_data *p = hctx->plugin_data; fcgi_proc *proc = hctx->proc; fcgi_extension_host *host= hctx->host; - if ((revents & FDEVENT_IN) && - hctx->state == FCGI_STATE_READ) { switch (fcgi_demux_response(srv, hctx)) { case 0: break; @@ -3264,14 +3285,14 @@ static handler_t fcgi_handle_fdevent(server *srv, void * now to handle authorized request. */ - buffer_copy_string_buffer(con->physical.doc_root, host->docroot); - buffer_copy_string_buffer(con->physical.basedir, host->docroot); + buffer_copy_buffer(con->physical.doc_root, host->docroot); + buffer_copy_buffer(con->physical.basedir, host->docroot); - buffer_copy_string_buffer(con->physical.path, host->docroot); + buffer_copy_buffer(con->physical.path, host->docroot); buffer_append_string_buffer(con->physical.path, con->uri.path); - fcgi_connection_close(srv, hctx); - con->mode = DIRECT; + con->mode = DIRECT;/*(avoid changing con->state, con->http_status)*/ + fcgi_connection_close(srv, hctx); con->http_status = 0; con->file_started = 1; /* fcgi_extension won't touch the request afterwards */ } else { @@ -3279,7 +3300,6 @@ static handler_t fcgi_handle_fdevent(server *srv, void fcgi_connection_close(srv, hctx); } - joblist_append(srv, con); return HANDLER_FINISHED; case -1: if (proc->pid && proc->state != PROC_STATE_DIED) { @@ -3333,61 +3353,50 @@ static handler_t fcgi_handle_fdevent(server *srv, void if (hctx->wb->bytes_out == 0 && hctx->reconnects < 5) { - fcgi_reconnect(srv, hctx); log_error_write(srv, __FILE__, __LINE__, "ssbsBSBs", "response not received, request not sent", "on socket:", proc->connection_name, "for", con->uri.path, "?", con->uri.query, ", reconnecting"); - return HANDLER_WAIT_FOR_FD; + fcgi_reconnect(srv, hctx); + + return HANDLER_COMEBACK; } log_error_write(srv, __FILE__, __LINE__, "sosbsBSBs", "response not received, request sent:", hctx->wb->bytes_out, "on socket:", proc->connection_name, "for", con->uri.path, "?", con->uri.query, ", closing connection"); - - fcgi_connection_close(srv, hctx); - - connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); - buffer_reset(con->physical.path); - con->http_status = 500; - con->mode = DIRECT; } else { - /* response might have been already started, kill the connection */ - fcgi_connection_close(srv, hctx); - log_error_write(srv, __FILE__, __LINE__, "ssbsBSBs", "response already sent out, but backend returned error", "on socket:", proc->connection_name, "for", con->uri.path, "?", con->uri.query, ", terminating connection"); - - connection_set_state(srv, con, CON_STATE_ERROR); } - /* */ - - - joblist_append(srv, con); + http_response_backend_error(srv, con); + fcgi_connection_close(srv, hctx); return HANDLER_FINISHED; } + + return HANDLER_GO_ON; +} + + +static handler_t fcgi_handle_fdevent(server *srv, void *ctx, int revents) { + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; + + joblist_append(srv, con); + + if (revents & FDEVENT_IN) { + handler_t rc = fcgi_recv_response(srv, hctx);/*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ } if (revents & FDEVENT_OUT) { - if (hctx->state == FCGI_STATE_CONNECT_DELAYED || - hctx->state == FCGI_STATE_WRITE) { - /* we are allowed to send something out - * - * 1. in an unfinished connect() call - * 2. in an unfinished write() call (long POST request) - */ - return mod_fastcgi_handle_subrequest(srv, con, p); - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "got a FDEVENT_OUT and didn't know why:", - hctx->state); - } + return fcgi_send_request(srv, hctx); /*(might invalidate hctx)*/ } /* perhaps this issue is already handled */ @@ -3402,38 +3411,39 @@ static handler_t fcgi_handle_fdevent(server *srv, void * FIXME: as it is a bit ugly. * */ - return mod_fastcgi_handle_subrequest(srv, con, p); - } else if (hctx->state == FCGI_STATE_READ && - hctx->proc->port == 0) { - /* FIXME: - * - * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket - * even if the FCGI_FIN packet is not received yet - */ + fcgi_send_request(srv, hctx); + } else if (con->file_started) { + /* drain any remaining data from kernel pipe buffers + * even if (con->conf.stream_response_body + * & FDEVENT_STREAM_RESPONSE_BUFMIN) + * since event loop will spin on fd FDEVENT_HUP event + * until unregistered. */ + handler_t rc; + do { + rc = fcgi_recv_response(srv,hctx);/*(might invalidate hctx)*/ + } while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/ + return rc; /* HANDLER_FINISHED or HANDLER_ERROR */ } else { + fcgi_proc *proc = hctx->proc; log_error_write(srv, __FILE__, __LINE__, "sBSbsbsd", "error: unexpected close of fastcgi connection for", con->uri.path, "?", con->uri.query, "(no fastcgi process on socket:", proc->connection_name, "?)", hctx->state); - connection_set_state(srv, con, CON_STATE_ERROR); fcgi_connection_close(srv, hctx); - joblist_append(srv, con); } } else if (revents & FDEVENT_ERR) { log_error_write(srv, __FILE__, __LINE__, "s", "fcgi: got a FDEVENT_ERR. Don't know why."); - /* kill all connections to the fastcgi process */ - - connection_set_state(srv, con, CON_STATE_ERROR); + http_response_backend_error(srv, con); fcgi_connection_close(srv, hctx); - joblist_append(srv, con); } return HANDLER_FINISHED; } + #define PATCH(x) \ p->conf.x = s->x; static int fcgi_patch_connection(server *srv, connection *con, plugin_data *p) { @@ -3486,9 +3496,9 @@ static handler_t fcgi_check_extension(server *srv, con fn = uri_path_handler ? con->uri.path : con->physical.path; - if (buffer_is_empty(fn)) return HANDLER_GO_ON; + if (buffer_string_is_empty(fn)) return HANDLER_GO_ON; - s_len = fn->used - 1; + s_len = buffer_string_length(fn); fcgi_patch_connection(srv, con, p); @@ -3505,9 +3515,9 @@ static handler_t fcgi_check_extension(server *srv, con data_string *ds = (data_string *)p->conf.ext_mapping->data[k]; size_t ct_len; /* length of the config entry */ - if (ds->key->used == 0) continue; + if (buffer_is_empty(ds->key)) continue; - ct_len = ds->key->used - 1; + ct_len = buffer_string_length(ds->key); if (s_len < ct_len) continue; @@ -3533,18 +3543,20 @@ static handler_t fcgi_check_extension(server *srv, con } if (extension == NULL) { + size_t uri_path_len = buffer_string_length(con->uri.path); + /* check if extension matches */ for (k = 0; k < p->conf.exts->used; k++) { size_t ct_len; /* length of the config entry */ fcgi_extension *ext = p->conf.exts->exts[k]; - if (ext->key->used == 0) continue; + if (buffer_is_empty(ext->key)) continue; - ct_len = ext->key->used - 1; + ct_len = buffer_string_length(ext->key); /* check _url_ in the form "/fcgi_pattern" */ if (ext->key->ptr[0] == '/') { - if ((ct_len <= con->uri.path->used -1) && + if ((ct_len <= uri_path_len) && (strncmp(con->uri.path->ptr, ext->key->ptr, ct_len) == 0)) { extension = ext; break; @@ -3577,8 +3589,8 @@ static handler_t fcgi_check_extension(server *srv, con if (!host) { /* sorry, we don't have a server alive for this ext */ - buffer_reset(con->physical.path); con->http_status = 500; + con->mode = DIRECT; /* only send the 'no handler' once */ if (!extension->note_is_sent) { @@ -3659,17 +3671,14 @@ static handler_t fcgi_check_extension(server *srv, con /* the rewrite is only done for /prefix/? matches */ if (host->fix_root_path_name && extension->key->ptr[0] == '/' && extension->key->ptr[1] == '\0') { buffer_copy_string(con->request.pathinfo, con->uri.path->ptr); - con->uri.path->used = 1; - con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + buffer_string_set_length(con->uri.path, 0); } else if (extension->key->ptr[0] == '/' && - con->uri.path->used > extension->key->used && - NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { + buffer_string_length(con->uri.path) > buffer_string_length(extension->key) && + NULL != (pathinfo = strchr(con->uri.path->ptr + buffer_string_length(extension->key), '/'))) { /* rewrite uri.path and pathinfo */ buffer_copy_string(con->request.pathinfo, pathinfo); - - con->uri.path->used -= con->request.pathinfo->used - 1; - con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + buffer_string_set_length(con->uri.path, buffer_string_length(con->uri.path) - buffer_string_length(con->request.pathinfo)); } } } @@ -3707,44 +3716,7 @@ static handler_t fcgi_check_extension_2(server *srv, c return fcgi_check_extension(srv, con, p_d, 0); } -JOBLIST_FUNC(mod_fastcgi_handle_joblist) { - plugin_data *p = p_d; - handler_ctx *hctx = con->plugin_ctx[p->id]; - if (hctx == NULL) return HANDLER_GO_ON; - - if (hctx->fd != -1) { - switch (hctx->state) { - case FCGI_STATE_READ: - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); - - break; - case FCGI_STATE_CONNECT_DELAYED: - case FCGI_STATE_WRITE: - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); - - break; - case FCGI_STATE_INIT: - /* at reconnect */ - break; - default: - log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state); - break; - } - } - - return HANDLER_GO_ON; -} - - -static handler_t fcgi_connection_close_callback(server *srv, connection *con, void *p_d) { - plugin_data *p = p_d; - - fcgi_connection_close(srv, con->plugin_ctx[p->id]); - - return HANDLER_GO_ON; -} - TRIGGER_FUNC(mod_fastcgi_handle_trigger) { plugin_data *p = p_d; size_t i, j, n; @@ -3849,11 +3821,10 @@ int mod_fastcgi_plugin_init(plugin *p) { p->cleanup = mod_fastcgi_free; p->set_defaults = mod_fastcgi_set_defaults; p->connection_reset = fcgi_connection_reset; - p->handle_connection_close = fcgi_connection_close_callback; + p->handle_connection_close = fcgi_connection_reset; p->handle_uri_clean = fcgi_check_extension_1; p->handle_subrequest_start = fcgi_check_extension_2; p->handle_subrequest = mod_fastcgi_handle_subrequest; - p->handle_joblist = mod_fastcgi_handle_joblist; p->handle_trigger = mod_fastcgi_handle_trigger; p->data = NULL;