lighttpd 1.4.x https://www.lighttpd.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1203 lines
37 KiB

#include "first.h"
#include "base.h"
#include "stat_cache.h"
#include "http_kv.h"
#include "fdlog.h"
#include "log.h"
#include "response.h"
#include "http_cgi.h"
#include "http_chunk.h"
#include "http_header.h"
#include "plugin.h"
#include <sys/types.h>
#include "sys-socket.h"
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fdevent.h>
#include <fcntl.h>
#include <signal.h>
static int pipe_cloexec(int pipefd[2]) {
#ifdef HAVE_PIPE2
if (0 == pipe2(pipefd, O_CLOEXEC)) return 0;
#endif
return 0 == pipe(pipefd)
#ifdef FD_CLOEXEC
&& 0 == fcntl(pipefd[0], F_SETFD, FD_CLOEXEC)
&& 0 == fcntl(pipefd[1], F_SETFD, FD_CLOEXEC)
#endif
? 0
: -1;
}
typedef struct {
uintptr_t *offsets;
size_t osize;
size_t oused;
buffer *b;
buffer *boffsets;
buffer *ld_preload;
buffer *ld_library_path;
#ifdef __CYGWIN__
buffer *systemroot;
#endif
} env_accum;
typedef struct {
unix_time64_t read_timeout;
unix_time64_t write_timeout;
int signal_fin;
} cgi_limits;
typedef struct {
const array *cgi;
const cgi_limits *limits;
unsigned short execute_x_only;
unsigned short local_redir;
unsigned short xsendfile_allow;
unsigned short upgrade;
const array *xsendfile_docroot;
} plugin_config;
struct cgi_pid_t;
typedef struct {
PLUGIN_DATA;
plugin_config defaults;
plugin_config conf;
int tempfile_accum;
struct cgi_pid_t *cgi_pid;
env_accum env;
} plugin_data;
typedef struct {
struct cgi_pid_t *cgi_pid;
int fd;
int fdtocgi;
fdnode *fdn;
fdnode *fdntocgi;
request_st *r;
struct fdevents *ev; /* dumb pointer */
plugin_data *plugin_data; /* dumb pointer */
buffer *response;
unix_time64_t read_ts;
unix_time64_t write_ts;
buffer *cgi_handler; /* dumb pointer */
http_response_opts opts;
plugin_config conf;
} handler_ctx;
typedef struct cgi_pid_t {
pid_t pid;
int signal_sent;
handler_ctx *hctx;
struct cgi_pid_t *next;
struct cgi_pid_t *prev;
} cgi_pid_t;
static handler_ctx * cgi_handler_ctx_init(void) {
handler_ctx *hctx = calloc(1, sizeof(*hctx));
force_assert(hctx);
hctx->response = chunk_buffer_acquire();
hctx->fd = -1;
hctx->fdtocgi = -1;
return hctx;
}
static void cgi_handler_ctx_free(handler_ctx *hctx) {
chunk_buffer_release(hctx->response);
free(hctx);
}
INIT_FUNC(mod_cgi_init) {
plugin_data *p;
const char *s;
p = calloc(1, sizeof(*p));
force_assert(p);
/* for valgrind */
s = getenv("LD_PRELOAD");
if (s) p->env.ld_preload = buffer_init_string(s);
s = getenv("LD_LIBRARY_PATH");
if (s) p->env.ld_library_path = buffer_init_string(s);
#ifdef __CYGWIN__
/* CYGWIN needs SYSTEMROOT */
s = getenv("SYSTEMROOT");
if (s) p->env.systemroot = buffer_init_string(s);
#endif
return p;
}
FREE_FUNC(mod_cgi_free) {
plugin_data *p = p_d;
buffer_free(p->env.ld_preload);
buffer_free(p->env.ld_library_path);
#ifdef __CYGWIN__
buffer_free(p->env.systemroot);
#endif
for (cgi_pid_t *cgi_pid = p->cgi_pid, *next; cgi_pid; cgi_pid = next) {
next = cgi_pid->next;
free(cgi_pid);
}
if (NULL == p->cvlist) return;
/* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
switch (cpv->k_id) {
case 6: /* cgi.limits */
free(cpv->v.v);
break;
default:
break;
}
}
}
}
static void mod_cgi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
case 0: /* cgi.assign */
pconf->cgi = cpv->v.a;
break;
case 1: /* cgi.execute-x-only */
pconf->execute_x_only = (unsigned short)cpv->v.u;
break;
case 2: /* cgi.x-sendfile */
pconf->xsendfile_allow = (unsigned short)cpv->v.u;
break;
case 3: /* cgi.x-sendfile-docroot */
pconf->xsendfile_docroot = cpv->v.a;
break;
case 4: /* cgi.local-redir */
pconf->local_redir = (unsigned short)cpv->v.u;
break;
case 5: /* cgi.upgrade */
pconf->upgrade = (unsigned short)cpv->v.u;
break;
case 6: /* cgi.limits */
if (cpv->vtype != T_CONFIG_LOCAL) break;
pconf->limits = cpv->v.v;
break;
default:/* should not happen */
return;
}
}
static void mod_cgi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
do {
mod_cgi_merge_config_cpv(pconf, cpv);
} while ((++cpv)->k_id != -1);
}
static void mod_cgi_patch_config(request_st * const r, plugin_data * const p) {
p->conf = p->defaults; /* copy small struct instead of memcpy() */
/*memcpy(&p->conf, &p->defaults, sizeof(plugin_config));*/
for (int i = 1, used = p->nconfig; i < used; ++i) {
if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
mod_cgi_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
}
}
__attribute_cold__
__attribute_pure__
static int mod_cgi_str_to_signal (const char *s, int default_sig) {
static const struct { const char *name; int sig; } sigs[] = {
{ "HUP", SIGHUP }
,{ "INT", SIGINT }
,{ "QUIT", SIGQUIT }
,{ "ILL", SIGILL }
,{ "TRAP", SIGTRAP }
,{ "ABRT", SIGABRT }
#ifdef SIGBUS
,{ "BUS", SIGBUS }
#endif
,{ "FPE", SIGFPE }
,{ "KILL", SIGKILL }
#ifdef SIGUSR1
,{ "USR1", SIGUSR1 }
#endif
,{ "SEGV", SIGSEGV }
#ifdef SIGUSR2
,{ "USR2", SIGUSR2 }
#endif
,{ "PIPE", SIGPIPE }
,{ "ALRM", SIGALRM }
,{ "TERM", SIGTERM }
#ifdef SIGCHLD
,{ "CHLD", SIGCHLD }
#endif
#ifdef SIGCONT
,{ "CONT", SIGCONT }
#endif
#ifdef SIGURG
,{ "URG", SIGURG }
#endif
#ifdef SIGXCPU
,{ "XCPU", SIGXCPU }
#endif
#ifdef SIGXFSZ
,{ "XFSZ", SIGXFSZ }
#endif
#ifdef SIGWINCH
,{ "WINCH",SIGWINCH}
#endif
#ifdef SIGPOLL
,{ "POLL", SIGPOLL }
#endif
#ifdef SIGIO
,{ "IO", SIGIO }
#endif
};
if (s[0] == 'S' && s[1] == 'I' && s[2] == 'G') s += 3; /*("SIG" prefix)*/
for (uint32_t i = 0; i < sizeof(sigs)/sizeof(*sigs); ++i) {
if (0 == strcmp(s, sigs[i].name)) return sigs[i].sig;
}
return default_sig;
}
static cgi_limits * mod_cgi_parse_limits(const array * const a, log_error_st * const errh) {
cgi_limits * const limits = calloc(1, sizeof(cgi_limits));
force_assert(limits);
for (uint32_t i = 0; i < a->used; ++i) {
const data_unset * const du = a->data[i];
int32_t v = config_plugin_value_to_int32(du, -1);
if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("read-timeout"))) {
limits->read_timeout = (unix_time64_t)v;
continue;
}
if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("write-timeout"))) {
limits->write_timeout = (unix_time64_t)v;
continue;
}
if (buffer_eq_icase_slen(&du->key, CONST_STR_LEN("tcp-fin-propagate"))) {
if (-1 == v) {
v = SIGTERM;
if (du->type == TYPE_STRING) {
buffer * const vstr = &((data_string *)du)->value;
buffer_to_upper(vstr);
v = mod_cgi_str_to_signal(vstr->ptr, SIGTERM);
}
}
limits->signal_fin = v;
continue;
}
log_error(errh, __FILE__, __LINE__,
"unrecognized cgi.limits param: %s", du->key.ptr);
}
return limits;
}
SETDEFAULTS_FUNC(mod_cgi_set_defaults) {
static const config_plugin_keys_t cpk[] = {
{ CONST_STR_LEN("cgi.assign"),
T_CONFIG_ARRAY_KVSTRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.execute-x-only"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.x-sendfile"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.x-sendfile-docroot"),
T_CONFIG_ARRAY_VLIST,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.local-redir"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.upgrade"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("cgi.limits"),
T_CONFIG_ARRAY_KVANY,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
};
plugin_data * const p = p_d;
if (!config_plugin_values_init(srv, p, cpk, "mod_cgi"))
return HANDLER_ERROR;
/* process and validate config directives
* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
switch (cpv->k_id) {
case 0: /* cgi.assign */
case 1: /* cgi.execute-x-only */
case 2: /* cgi.x-sendfile */
break;
case 3: /* cgi.x-sendfile-docroot */
for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
data_string *ds = (data_string *)cpv->v.a->data[j];
if (ds->value.ptr[0] != '/') {
log_error(srv->errh, __FILE__, __LINE__,
"%s paths must begin with '/'; invalid: \"%s\"",
cpk[cpv->k_id].k, ds->value.ptr);
return HANDLER_ERROR;
}
buffer_path_simplify(&ds->value);
buffer_append_slash(&ds->value);
}
break;
case 4: /* cgi.local-redir */
case 5: /* cgi.upgrade */
break;
case 6: /* cgi.limits */
cpv->v.v = mod_cgi_parse_limits(cpv->v.a, srv->errh);
if (NULL == cpv->v.v) return HANDLER_ERROR;
cpv->vtype = T_CONFIG_LOCAL;
break;
default:/* should not happen */
break;
}
}
}
/* initialize p->defaults from global config context */
if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
if (-1 != cpv->k_id)
mod_cgi_merge_config(&p->defaults, cpv);
}
p->tempfile_accum =
!srv->srvconf.feature_flags
|| config_plugin_value_tobool(
array_get_element_klen(srv->srvconf.feature_flags,
CONST_STR_LEN("cgi.tempfile-accum")), 1);
return HANDLER_GO_ON;
}
static cgi_pid_t * cgi_pid_add(plugin_data *p, pid_t pid, handler_ctx *hctx) {
cgi_pid_t *cgi_pid = malloc(sizeof(cgi_pid_t));
force_assert(cgi_pid);
cgi_pid->pid = pid;
cgi_pid->signal_sent = 0;
cgi_pid->hctx = hctx;
cgi_pid->prev = NULL;
cgi_pid->next = p->cgi_pid;
p->cgi_pid = cgi_pid;
return cgi_pid;
}
static void cgi_pid_kill(cgi_pid_t *cgi_pid, int sig) {
cgi_pid->signal_sent = sig; /*(save last signal sent)*/
kill(cgi_pid->pid, sig);
}
static void cgi_pid_del(plugin_data *p, cgi_pid_t *cgi_pid) {
if (cgi_pid->prev)
cgi_pid->prev->next = cgi_pid->next;
else
p->cgi_pid = cgi_pid->next;
if (cgi_pid->next)
cgi_pid->next->prev = cgi_pid->prev;
free(cgi_pid);
}
static void cgi_connection_close_fdtocgi(handler_ctx *hctx) {
/*(closes only hctx->fdtocgi)*/
struct fdevents * const ev = hctx->ev;
fdevent_fdnode_event_del(ev, hctx->fdntocgi);
/*fdevent_unregister(ev, hctx->fdtocgi);*//*(handled below)*/
fdevent_sched_close(ev, hctx->fdtocgi, 0);
hctx->fdntocgi = NULL;
hctx->fdtocgi = -1;
}
static void cgi_connection_close(handler_ctx *hctx) {
/* the connection to the browser went away, but we still have a connection
* to the CGI script
*
* close cgi-connection
*/
if (hctx->fd != -1) {
struct fdevents * const ev = hctx->ev;
/* close connection to the cgi-script */
fdevent_fdnode_event_del(ev, hctx->fdn);
/*fdevent_unregister(ev, hctx->fd);*//*(handled below)*/
fdevent_sched_close(ev, hctx->fd, 0);
hctx->fdn = NULL;
}
if (hctx->fdtocgi != -1) {
cgi_connection_close_fdtocgi(hctx); /*(closes only hctx->fdtocgi)*/
}
plugin_data * const p = hctx->plugin_data;
request_st * const r = hctx->r;
r->plugin_ctx[p->id] = NULL;
if (hctx->cgi_pid) {
cgi_pid_kill(hctx->cgi_pid, SIGTERM);
hctx->cgi_pid->hctx = NULL;
}
cgi_handler_ctx_free(hctx);
/* finish response (if not already r->resp_body_started, r->resp_body_finished) */
if (r->handler_module == p->self) {
http_response_backend_done(r);
}
}
static handler_t cgi_connection_close_callback(request_st * const r, void *p_d) {
handler_ctx *hctx = r->plugin_ctx[((plugin_data *)p_d)->id];
if (hctx) {
chunkqueue_set_tempdirs(&r->reqbody_queue, /* reset sz */
r->reqbody_queue.tempdirs, 0);
cgi_connection_close(hctx);
}
return HANDLER_GO_ON;
}
static int cgi_write_request(handler_ctx *hctx, int fd);
static handler_t cgi_handle_fdevent_send (void *ctx, int revents) {
handler_ctx *hctx = ctx;
request_st * const r = hctx->r;
/*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/
joblist_append(r->con);
if (revents & FDEVENT_OUT) {
if (0 != cgi_write_request(hctx, hctx->fdtocgi)) {
cgi_connection_close(hctx);
return HANDLER_ERROR;
}
/* more request body to be sent to CGI */
}
if (revents & FDEVENT_HUP) {
/* skip sending remaining data to CGI */
if (r->reqbody_length) {
chunkqueue *cq = &r->reqbody_queue;
chunkqueue_mark_written(cq, chunkqueue_length(cq));
if (cq->bytes_in != (off_t)r->reqbody_length) {
r->keep_alive = 0;
}
}
cgi_connection_close_fdtocgi(hctx); /*(closes only hctx->fdtocgi)*/
} else if (revents & FDEVENT_ERR) {
/* kill all connections to the cgi process */
#if 1
log_error(r->conf.errh, __FILE__, __LINE__, "cgi-FDEVENT_ERR");
#endif
cgi_connection_close(hctx);
return HANDLER_ERROR;
}
return HANDLER_FINISHED;
}
static handler_t cgi_response_headers(request_st * const r, struct http_response_opts_t *opts) {
/* response headers just completed */
[core] shared code for socket backends common codebase for socket backends, based off mod_fastcgi with some features added for mod_proxy (mostly intended to reduce code duplication and enhance code isolation) mod_fastcgi and mod_scgi can now use fastcgi.balance and scgi.balance for similar behavior as proxy.balance, but the balancing is per-host and not per-proc. proxy.balance is also per-host and not per-proc. mod_proxy and mod_scgi can now use proxy.map-extensions and scgi.map-extensions, similar to fastcgi.map-extensions. mod_fastcgi behavior change (affects only mod_status): - statistics tags have been renamed from "fastcgi.*" to "gw.*" "fastcgi.backend.*" -> "gw.backend.*" "fastcgi.active-requests" -> "gw.active-requests" ("fastcgi.requests" remains "fastcgi.requests") ("proxy.requests" is new) ("scgi.requests" is new) mod_scgi behavior change (likely minor): - removed scgi_proclist_sort_down() and scgi_proclist_sort_up(). procs now chosen based on load as measured by num socket connnections Note: modules using gw_backend.[ch] are currently still independent modules. If it had been written as a single module with fastcgi, scgi, proxy implementations, then there would have been a chance of breaking some existing user configurations where module ordering made a difference for which module handled a given request, though for most people, this would have made no difference. Details about mod_fastcgi code transformations: unsigned int debug -> int debug fastcgi_env member removed from plugin_config renamed "fcgi" and "fastcgi" to "gw", and "FCGI" to "GW" reorganize routines for high-level and lower-level interfaces some lower-level internal interfaces changed to use host,proc,debug args rather than knowing about higher-level (app) hctx and plugin_data tabs->spaces and reformatting
5 years ago
handler_ctx *hctx = (handler_ctx *)opts->pdata;
if (light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)) {
if (hctx->conf.upgrade && r->http_status == 101) {
/* 101 Switching Protocols; transition to transparent proxy */
http_response_upgrade_read_body_unknown(r);
}
else {
light_bclr(r->resp_htags, HTTP_HEADER_UPGRADE);
#if 0
/* preserve prior questionable behavior; likely broken behavior
* anyway if backend thinks connection is being upgraded but client
* does not receive Connection: upgrade */
http_header_response_unset(r, HTTP_HEADER_UPGRADE,
CONST_STR_LEN("Upgrade"));
#endif
}
}
if (hctx->conf.upgrade
&& !light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)) {
chunkqueue *cq = &r->reqbody_queue;
hctx->conf.upgrade = 0;
if (cq->bytes_out == (off_t)r->reqbody_length) {
cgi_connection_close_fdtocgi(hctx); /*(closes hctx->fdtocgi)*/
}
}
[core] shared code for socket backends common codebase for socket backends, based off mod_fastcgi with some features added for mod_proxy (mostly intended to reduce code duplication and enhance code isolation) mod_fastcgi and mod_scgi can now use fastcgi.balance and scgi.balance for similar behavior as proxy.balance, but the balancing is per-host and not per-proc. proxy.balance is also per-host and not per-proc. mod_proxy and mod_scgi can now use proxy.map-extensions and scgi.map-extensions, similar to fastcgi.map-extensions. mod_fastcgi behavior change (affects only mod_status): - statistics tags have been renamed from "fastcgi.*" to "gw.*" "fastcgi.backend.*" -> "gw.backend.*" "fastcgi.active-requests" -> "gw.active-requests" ("fastcgi.requests" remains "fastcgi.requests") ("proxy.requests" is new) ("scgi.requests" is new) mod_scgi behavior change (likely minor): - removed scgi_proclist_sort_down() and scgi_proclist_sort_up(). procs now chosen based on load as measured by num socket connnections Note: modules using gw_backend.[ch] are currently still independent modules. If it had been written as a single module with fastcgi, scgi, proxy implementations, then there would have been a chance of breaking some existing user configurations where module ordering made a difference for which module handled a given request, though for most people, this would have made no difference. Details about mod_fastcgi code transformations: unsigned int debug -> int debug fastcgi_env member removed from plugin_config renamed "fcgi" and "fastcgi" to "gw", and "FCGI" to "GW" reorganize routines for high-level and lower-level interfaces some lower-level internal interfaces changed to use host,proc,debug args rather than knowing about higher-level (app) hctx and plugin_data tabs->spaces and reformatting
5 years ago
return HANDLER_GO_ON;
}
static int cgi_recv_response(request_st * const r, handler_ctx * const hctx) {
const off_t bytes_in = r->write_queue.bytes_in;
switch (http_response_read(r, &hctx->opts,
hctx->response, hctx->fdn)) {
default:
if (r->write_queue.bytes_in > bytes_in)
hctx->read_ts = log_monotonic_secs;
return HANDLER_GO_ON;
case HANDLER_ERROR:
http_response_backend_error(r);
__attribute_fallthrough__
case HANDLER_FINISHED:
cgi_connection_close(hctx);
return HANDLER_FINISHED;
case HANDLER_COMEBACK:
/* flag for mod_cgi_handle_subrequest() */
hctx->conf.local_redir = 2;
buffer_clear(hctx->response);
return HANDLER_COMEBACK;
}
}
static handler_t cgi_handle_fdevent(void *ctx, int revents) {
handler_ctx *hctx = ctx;
request_st * const r = hctx->r;
joblist_append(r->con);
if (revents & FDEVENT_IN) {
handler_t rc = cgi_recv_response(r, hctx); /*(might invalidate hctx)*/
if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
}
/* perhaps this issue is already handled */
if (revents & (FDEVENT_HUP|FDEVENT_RDHUP)) {
if (r->resp_body_started) {
/* drain any remaining data from kernel pipe buffers
* even if (r->conf.stream_response_body
* & FDEVENT_STREAM_RESPONSE_BUFMIN)
* since event loop will spin on fd FDEVENT_HUP event
* until unregistered. */
handler_t rc;
const unsigned short flags = r->conf.stream_response_body;
r->conf.stream_response_body &= ~FDEVENT_STREAM_RESPONSE_BUFMIN;
r->conf.stream_response_body |= FDEVENT_STREAM_RESPONSE_POLLRDHUP;
do {
rc = cgi_recv_response(r,hctx); /*(might invalidate hctx)*/
} while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/
r->conf.stream_response_body = flags;
return rc; /* HANDLER_FINISHED or HANDLER_COMEBACK or HANDLER_ERROR */
[multiple] reduce redundant NULL buffer checks This commit is a large set of code changes and results in removal of hundreds, perhaps thousands, of CPU instructions, a portion of which are on hot code paths. Most (buffer *) used by lighttpd are not NULL, especially since buffers were inlined into numerous larger structs such as request_st and chunk. In the small number of instances where that is not the case, a NULL check is often performed earlier in a function where that buffer is later used with a buffer_* func. In the handful of cases that remained, a NULL check was added, e.g. with r->http_host and r->conf.server_tag. - check for empty strings at config time and set value to NULL if blank string will be ignored at runtime; at runtime, simple pointer check for NULL can be used to check for a value that has been set and is not blank ("") - use buffer_is_blank() instead of buffer_string_is_empty(), and use buffer_is_unset() instead of buffer_is_empty(), where buffer is known not to be NULL so that NULL check can be skipped - use buffer_clen() instead of buffer_string_length() when buffer is known not to be NULL (to avoid NULL check at runtime) - use buffer_truncate() instead of buffer_string_set_length() to truncate string, and use buffer_extend() to extend Examples where buffer known not to be NULL: - cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL (though we might set it to NULL if buffer_is_blank(cpv->v.b)) - address of buffer is arg (&foo) (compiler optimizer detects this in most, but not all, cases) - buffer is checked for NULL earlier in func - buffer is accessed in same scope without a NULL check (e.g. b->ptr) internal behavior change: callers must not pass a NULL buffer to some funcs. - buffer_init_buffer() requires non-null args - buffer_copy_buffer() requires non-null args - buffer_append_string_buffer() requires non-null args - buffer_string_space() requires non-null arg
8 months ago
} else if (!buffer_is_blank(hctx->response)) {
/* unfinished header package which is a body in reality */
r->resp_body_started = 1;
if (0 != http_chunk_append_buffer(r, hctx->response)) {
cgi_connection_close(hctx);
return HANDLER_ERROR;
}
if (0 == r->http_status) r->http_status = 200; /* OK */
}
cgi_connection_close(hctx);
} else if (revents & FDEVENT_ERR) {
/* kill all connections to the cgi process */
cgi_connection_close(hctx);
return HANDLER_ERROR;
}
return HANDLER_FINISHED;
}
__attribute_cold__
__attribute_noinline__
static void cgi_env_offset_resize(env_accum *env) {
chunk_buffer_prepare_append(env->boffsets, env->boffsets->size*2);
env->offsets = (uintptr_t *)(void *)env->boffsets->ptr;
env->osize = env->boffsets->size/sizeof(*env->offsets);