lighttpd1.4/src/mod_ssi.c

1385 lines
37 KiB
C
Raw Normal View History

#include "first.h"
#include "base.h"
#include "fdevent.h"
#include "log.h"
#include "buffer.h"
#include "plugin.h"
#include "response.h"
#include "mod_ssi.h"
#include "sys-socket.h"
#include <sys/types.h>
#include "sys-strings.h"
#include <sys/wait.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
2016-04-18 03:37:40 +00:00
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#include "etag.h"
static handler_ctx * handler_ctx_init(plugin_data *p) {
handler_ctx *hctx = calloc(1, sizeof(*hctx));
force_assert(hctx);
hctx->timefmt = p->timefmt;
hctx->stat_fn = p->stat_fn;
hctx->ssi_vars = p->ssi_vars;
hctx->ssi_cgi_env = p->ssi_cgi_env;
memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
return hctx;
}
static void handler_ctx_free(handler_ctx *hctx) {
free(hctx);
}
/* The newest modified time of included files for include statement */
static volatile time_t include_file_last_mtime = 0;
/* init the plugin data */
INIT_FUNC(mod_ssi_init) {
plugin_data *p;
p = calloc(1, sizeof(*p));
p->timefmt = buffer_init();
p->stat_fn = buffer_init();
p->ssi_vars = array_init();
p->ssi_cgi_env = array_init();
return p;
}
/* detroy the plugin data */
FREE_FUNC(mod_ssi_free) {
plugin_data *p = p_d;
UNUSED(srv);
if (!p) return HANDLER_GO_ON;
if (p->config_storage) {
size_t i;
for (i = 0; i < srv->config_context->used; i++) {
plugin_config *s = p->config_storage[i];
if (NULL == s) continue;
array_free(s->ssi_extension);
buffer_free(s->content_type);
free(s);
}
free(p->config_storage);
}
array_free(p->ssi_vars);
array_free(p->ssi_cgi_env);
buffer_free(p->timefmt);
buffer_free(p->stat_fn);
free(p);
return HANDLER_GO_ON;
}
/* handle plugin config and check values */
SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
plugin_data *p = p_d;
size_t i = 0;
config_values_t cv[] = {
{ "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
{ "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
[mod_ssi] config ssi.conditional-requests Summary: A new SSI directive, "ssi.conditional-requests", allows to inform lighttpd which SSI pages should be considered as cacheable and which should not. In particular, the "ETag" & "Last-Modified" headers will only be sent for those SSI pages for which the directive is enabled. Long description: "ETag" and "Last-Modified" headers were being sent for all SSI pages, regardless of whether they were cacheable or not. And yet, there was no cache validation at all for any SSI page. This commit fixes these two minor issues by adding a new directive, "ssi.conditional-requests", which allows to specify which SSI pages are cacheable and which are not, and by adding cache validation to those SSI pages which are cacheable. And since sending ETags for non-cacheable documents is not appropriate, they are no longuer computed nor sent for those SSI pages which are not cacheable. Regarding the "Last-Modified" header for non-cacheable documents, the standards allow to either send the current date and time for that header or to simply skip it. The approach chosen is to not send it for non-cacheable SSI pages. "ETag" and "Last-Modified" headers are therefore only sent for an SSI page if ssi.conditional-requests is enabled for that page. The ssi.conditional-requests directive can be enabled or disabled globally and/or in any context. It is disabled by default. An index.shtml which only includes deterministic SSI commands such as: <!--#echo var="LAST_MODIFIED"--> is a trivial example of a dynamic SSI page that is cacheable.
2016-04-13 12:27:47 +00:00
{ "ssi.conditional-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
{ "ssi.exec", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
{ "ssi.recursion-max", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
{ NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
};
if (!p) return HANDLER_ERROR;
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;
s = calloc(1, sizeof(plugin_config));
s->ssi_extension = array_init();
s->content_type = buffer_init();
[mod_ssi] config ssi.conditional-requests Summary: A new SSI directive, "ssi.conditional-requests", allows to inform lighttpd which SSI pages should be considered as cacheable and which should not. In particular, the "ETag" & "Last-Modified" headers will only be sent for those SSI pages for which the directive is enabled. Long description: "ETag" and "Last-Modified" headers were being sent for all SSI pages, regardless of whether they were cacheable or not. And yet, there was no cache validation at all for any SSI page. This commit fixes these two minor issues by adding a new directive, "ssi.conditional-requests", which allows to specify which SSI pages are cacheable and which are not, and by adding cache validation to those SSI pages which are cacheable. And since sending ETags for non-cacheable documents is not appropriate, they are no longuer computed nor sent for those SSI pages which are not cacheable. Regarding the "Last-Modified" header for non-cacheable documents, the standards allow to either send the current date and time for that header or to simply skip it. The approach chosen is to not send it for non-cacheable SSI pages. "ETag" and "Last-Modified" headers are therefore only sent for an SSI page if ssi.conditional-requests is enabled for that page. The ssi.conditional-requests directive can be enabled or disabled globally and/or in any context. It is disabled by default. An index.shtml which only includes deterministic SSI commands such as: <!--#echo var="LAST_MODIFIED"--> is a trivial example of a dynamic SSI page that is cacheable.
2016-04-13 12:27:47 +00:00
s->conditional_requests = 0;
s->ssi_exec = 1;
s->ssi_recursion_max = 0;
cv[0].destination = s->ssi_extension;
cv[1].destination = s->content_type;
[mod_ssi] config ssi.conditional-requests Summary: A new SSI directive, "ssi.conditional-requests", allows to inform lighttpd which SSI pages should be considered as cacheable and which should not. In particular, the "ETag" & "Last-Modified" headers will only be sent for those SSI pages for which the directive is enabled. Long description: "ETag" and "Last-Modified" headers were being sent for all SSI pages, regardless of whether they were cacheable or not. And yet, there was no cache validation at all for any SSI page. This commit fixes these two minor issues by adding a new directive, "ssi.conditional-requests", which allows to specify which SSI pages are cacheable and which are not, and by adding cache validation to those SSI pages which are cacheable. And since sending ETags for non-cacheable documents is not appropriate, they are no longuer computed nor sent for those SSI pages which are not cacheable. Regarding the "Last-Modified" header for non-cacheable documents, the standards allow to either send the current date and time for that header or to simply skip it. The approach chosen is to not send it for non-cacheable SSI pages. "ETag" and "Last-Modified" headers are therefore only sent for an SSI page if ssi.conditional-requests is enabled for that page. The ssi.conditional-requests directive can be enabled or disabled globally and/or in any context. It is disabled by default. An index.shtml which only includes deterministic SSI commands such as: <!--#echo var="LAST_MODIFIED"--> is a trivial example of a dynamic SSI page that is cacheable.
2016-04-13 12:27:47 +00:00
cv[2].destination = &(s->conditional_requests);
cv[3].destination = &(s->ssi_exec);
cv[4].destination = &(s->ssi_recursion_max);
p->config_storage[i] = s;
if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
return HANDLER_ERROR;
}
if (!array_is_vlist(s->ssi_extension)) {
log_error_write(srv, __FILE__, __LINE__, "s",
"unexpected value for ssi.extension; expected list of \"ext\"");
return HANDLER_ERROR;
}
}
return HANDLER_GO_ON;
}
2016-04-18 03:37:40 +00:00
static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
array *env = venv;
data_string *ds;
/* array_set_key_value() w/o extra lookup to see if key already exists */
if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
ds = data_string_init();
}
buffer_copy_string_len(ds->key, key, klen);
buffer_copy_string_len(ds->value, val, vlen);
array_insert_unique(env, (data_unset *)ds);
return 0;
}
static int build_ssi_cgi_vars(server *srv, connection *con, handler_ctx *p) {
http_cgi_opts opts = { 0, 0, NULL, NULL };
/* temporarily remove Authorization from request headers
* so that Authorization does not end up in SSI environment */
data_string *ds_auth = (data_string *)array_get_element(con->request.headers, "Authorization");
buffer *b_auth = NULL;
if (ds_auth) {
b_auth = ds_auth->value;
ds_auth->value = NULL;
}
array_reset(p->ssi_cgi_env);
if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) {
con->http_status = 400;
return -1;
}
if (ds_auth) {
ds_auth->value = b_auth;
}
return 0;
}
static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st);
static int process_ssi_stmt(server *srv, connection *con, handler_ctx *p, const char **l, size_t n, struct stat *st) {
2016-04-18 03:37:40 +00:00
/**
* <!--#element attribute=value attribute=value ... -->
*
* config DONE
* errmsg -- missing
* sizefmt DONE
* timefmt DONE
* echo DONE
* var DONE
* encoding -- missing
* exec DONE
* cgi -- never
* cmd DONE
* fsize DONE
* file DONE
* virtual DONE
* flastmod DONE
* file DONE
* virtual DONE
* include DONE
* file DONE
* virtual DONE
* printenv DONE
* set DONE
* var DONE
* value DONE
*
* if DONE
* elif DONE
* else DONE
* endif DONE
*
*
* expressions
* AND, OR DONE
* comp DONE
* ${...} -- missing
* $... DONE
* '...' DONE
* ( ... ) DONE
*
*
*
* ** all DONE **
* DATE_GMT
* The current date in Greenwich Mean Time.
* DATE_LOCAL
* The current date in the local time zone.
* DOCUMENT_NAME
* The filename (excluding directories) of the document requested by the user.
* DOCUMENT_URI
* The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
* LAST_MODIFIED
* The last modification date of the document requested by the user.
* USER_NAME
* Contains the owner of the file which included it.
*
*/
size_t i, ssicmd = 0;
char buf[255];
buffer *b = NULL;
static const struct {
const char *var;
enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
SSI_ELSE, SSI_ENDIF, SSI_EXEC, SSI_COMMENT } type;
} ssicmds[] = {
{ "echo", SSI_ECHO },
{ "include", SSI_INCLUDE },
{ "flastmod", SSI_FLASTMOD },
{ "fsize", SSI_FSIZE },
{ "config", SSI_CONFIG },
{ "printenv", SSI_PRINTENV },
{ "set", SSI_SET },
{ "if", SSI_IF },
{ "elif", SSI_ELIF },
{ "endif", SSI_ENDIF },
{ "else", SSI_ELSE },
{ "exec", SSI_EXEC },
{ "comment", SSI_COMMENT },
{ NULL, SSI_UNSET }
};
for (i = 0; ssicmds[i].var; i++) {
if (0 == strcmp(l[1], ssicmds[i].var)) {
ssicmd = ssicmds[i].type;
break;
}
}
switch(ssicmd) {
case SSI_ECHO: {
/* echo */
int var = 0;
/* int enc = 0; */
const char *var_val = NULL;
static const struct {
const char *var;
enum {
SSI_ECHO_UNSET,
SSI_ECHO_DATE_GMT,
SSI_ECHO_DATE_LOCAL,
SSI_ECHO_DOCUMENT_NAME,
SSI_ECHO_DOCUMENT_URI,
SSI_ECHO_LAST_MODIFIED,
SSI_ECHO_USER_NAME,
SSI_ECHO_SCRIPT_URI,
SSI_ECHO_SCRIPT_URL,
} type;
} echovars[] = {
{ "DATE_GMT", SSI_ECHO_DATE_GMT },
{ "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
{ "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
{ "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
{ "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
{ "USER_NAME", SSI_ECHO_USER_NAME },
{ "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
{ "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
{ NULL, SSI_ECHO_UNSET }
};
/*
static const struct {
const char *var;
enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
} encvars[] = {
{ "url", SSI_ENC_URL },
{ "none", SSI_ENC_NONE },
{ "entity", SSI_ENC_ENTITY },
{ NULL, SSI_ENC_UNSET }
};
*/
for (i = 2; i < n; i += 2) {
if (0 == strcmp(l[i], "var")) {
int j;
var_val = l[i+1];
for (j = 0; echovars[j].var; j++) {
if (0 == strcmp(l[i+1], echovars[j].var)) {
var = echovars[j].type;
break;
}
}
} else if (0 == strcmp(l[i], "encoding")) {
/*
int j;
for (j = 0; encvars[j].var; j++) {
if (0 == strcmp(l[i+1], encvars[j].var)) {
enc = encvars[j].type;
break;
}
}
*/
} else {
log_error_write(srv, __FILE__, __LINE__, "sss",
"ssi: unknown attribute for ",
l[1], l[i]);
}
}
if (p->if_is_false) break;
if (!var_val) {
log_error_write(srv, __FILE__, __LINE__, "sss",
"ssi: ",
l[1], "var is missing");
break;
}
switch(var) {
case SSI_ECHO_USER_NAME: {
struct passwd *pw;
b = buffer_init();
#ifdef HAVE_PWD_H
2016-04-18 03:37:40 +00:00
if (NULL == (pw = getpwuid(st->st_uid))) {
buffer_copy_int(b, st->st_uid);
} else {
buffer_copy_string(b, pw->pw_name);
}
#else
2016-04-18 03:37:40 +00:00
buffer_copy_int(b, st->st_uid);
#endif
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
break;
}
case SSI_ECHO_LAST_MODIFIED: {
2016-04-18 03:37:40 +00:00
time_t t = st->st_mtime;
if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
} else {
chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
}
break;
}
case SSI_ECHO_DATE_LOCAL: {
time_t t = time(NULL);
if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
} else {
chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
}
break;
}
case SSI_ECHO_DATE_GMT: {
time_t t = time(NULL);
if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
} else {
chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
}
break;
}
case SSI_ECHO_DOCUMENT_NAME: {
char *sl;
if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
} else {
chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
}
break;
}
case SSI_ECHO_DOCUMENT_URI: {
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
break;
}
case SSI_ECHO_SCRIPT_URI: {
if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
if (!buffer_string_is_empty(con->uri.query)) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
}
}
break;
}
case SSI_ECHO_SCRIPT_URL: {
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
if (!buffer_string_is_empty(con->uri.query)) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
}
break;
}
default: {
data_string *ds;
/* check if it is a cgi-var or a ssi-var */
if (NULL != (ds = (data_string *)array_get_element_klen(p->ssi_cgi_env, var_val, strlen(var_val))) ||
NULL != (ds = (data_string *)array_get_element_klen(p->ssi_vars, var_val, strlen(var_val)))) {
chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
} else {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
}
break;
}
}
break;
}
case SSI_INCLUDE:
case SSI_FLASTMOD:
case SSI_FSIZE: {
const char * file_path = NULL, *virt_path = NULL;
2016-04-18 03:37:40 +00:00
struct stat stb;
char *sl;
for (i = 2; i < n; i += 2) {
if (0 == strcmp(l[i], "file")) {
file_path = l[i+1];
} else if (0 == strcmp(l[i], "virtual")) {
virt_path = l[i+1];
} else {
log_error_write(srv, __FILE__, __LINE__, "sss",
"ssi: unknown attribute for ",
l[1], l[i]);
}
}
if (!file_path && !virt_path) {
log_error_write(srv, __FILE__, __LINE__, "sss",
"ssi: ",
l[1], "file or virtual are missing");
break;
}
if (file_path && virt_path) {
log_error_write(srv, __FILE__, __LINE__, "sss",
"ssi: ",
l[1], "only one of file and virtual is allowed here");
break;
}
if (p->if_is_false) break;
if (file_path) {
/* current doc-root */
if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
} else {
buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
}
buffer_copy_string(srv->tmp_buf, file_path);
buffer_urldecode_path(srv->tmp_buf);
buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
} else {
/* virtual */
size_t remain;
if (virt_path[0] == '/') {
buffer_copy_string(p->stat_fn, virt_path);
} else {
/* there is always a / */
sl = strrchr(con->uri.path->ptr, '/');
buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
buffer_append_string(p->stat_fn, virt_path);
}
buffer_urldecode_path(p->stat_fn);
buffer_path_simplify(srv->tmp_buf, p->stat_fn);
/* we have an uri */
/* Destination physical path (similar to code in mod_webdav.c)
* src con->physical.path might have been remapped with mod_alias, mod_userdir.
* (but neither modifies con->physical.rel_path)
* Find matching prefix to support relative paths to current physical path.
* Aliasing of paths underneath current con->physical.basedir might not work.
* Likewise, mod_rewrite URL rewriting might thwart this comparison.
* Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
* Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
* (Related, use mod_auth to protect this basedir, but avoid attempting to
* use mod_auth on paths underneath this basedir, as target path is not
* validated with mod_auth)
*/
/* find matching URI prefix
* check if remaining con->physical.rel_path matches suffix
* of con->physical.basedir so that we can use it to
* remap Destination physical path */
{
const char *sep, *sep2;
sep = con->uri.path->ptr;
sep2 = srv->tmp_buf->ptr;
for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
}
if (con->conf.force_lowercase_filenames) {
buffer_to_lower(srv->tmp_buf);
}
remain = buffer_string_length(con->uri.path) - i;
if (!con->conf.force_lowercase_filenames
? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
:(buffer_string_length(con->physical.path) >= remain
&& 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
} else {
/* unable to perform physical path remap here;
* assume doc_root/rel_path and no remapping */
buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
}
}
2016-04-18 03:37:40 +00:00
if (0 == stat(p->stat_fn->ptr, &stb)) {
time_t t = stb.st_mtime;
switch (ssicmd) {
case SSI_FSIZE:
b = buffer_init();
if (p->sizefmt) {
int j = 0;
const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
2016-04-18 03:37:40 +00:00
off_t s = stb.st_size;
for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
fix buffer, chunk and http_chunk API * remove unused structs and functions (buffer_array, read_buffer) * change return type from int to void for many functions, as the return value (indicating error/success) was never checked, and the function would only fail on programming errors and not on invalid input; changed functions to use force_assert instead of returning an error. * all "len" parameters now are the real size of the memory to be read. the length of strings is given always without the terminating 0. * the "buffer" struct still counts the terminating 0 in ->used, provide buffer_string_length() to get the length of a string in a buffer. unset config "strings" have used == 0, which is used in some places to distinguish unset values from "" (empty string) values. * most buffer usages should now use it as string container. * optimise some buffer copying by "moving" data to other buffers * use (u)intmax_t for generic int-to-string functions * remove unused enum values: UNUSED_CHUNK, ENCODING_UNSET * converted BUFFER_APPEND_SLASH to inline function (no macro feature needed) * refactor: create chunkqueue_steal: moving (partial) chunks into another queue * http_chunk: added separate function to terminate chunked body instead of magic handling in http_chunk_append_mem(). http_chunk_append_* now handle empty chunks, and never terminate the chunked body. From: Stefan Bühler <stbuehler@web.de> git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@2975 152afb58-edef-0310-8abb-c4023f1b3aa9
2015-02-08 12:37:10 +00:00
buffer_copy_int(b, s);
buffer_append_string(b, abr[j]);
} else {
2016-04-18 03:37:40 +00:00
buffer_copy_int(b, stb.st_size);
}
chunkqueue_append_buffer(con->write_queue, b);
buffer_free(b);
break;
case SSI_FLASTMOD:
if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
} else {
chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
}
break;
case SSI_INCLUDE:
/* Keep the newest mtime of included files */
2016-04-18 03:37:40 +00:00
if (stb.st_mtime > include_file_last_mtime)
include_file_last_mtime = stb.st_mtime;
if (file_path || 0 == p->conf.ssi_recursion_max) {
/* don't process if #include file="..." is used */
chunkqueue_append_file(con->write_queue, p->stat_fn, 0, stb.st_size);
} else {
buffer *upsave, *ppsave, *prpsave;
/* only allow predefined recursion depth */
if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) {
chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
break;
}