[mod_ssi] basic recursive SSI include virtual (fixes #536)
[lighttpd.git] / src / mod_ssi.c
blob0001ecfcb4127355bb0353e0023eacb1e0812b7e
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
7 #include "plugin.h"
9 #include "response.h"
11 #include "mod_ssi.h"
13 #include "inet_ntop_cache.h"
15 #include "sys-socket.h"
17 #include <sys/types.h>
19 #include <ctype.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <strings.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <time.h>
27 #include <unistd.h>
29 #ifdef HAVE_PWD_H
30 # include <pwd.h>
31 #endif
33 #ifdef HAVE_FORK
34 # include <sys/wait.h>
35 #endif
37 #ifdef HAVE_SYS_FILIO_H
38 # include <sys/filio.h>
39 #endif
41 #include "etag.h"
43 static handler_ctx * handler_ctx_init(plugin_data *p) {
44 handler_ctx *hctx = calloc(1, sizeof(*hctx));
45 force_assert(hctx);
46 hctx->timefmt = p->timefmt;
47 hctx->stat_fn = p->stat_fn;
48 hctx->ssi_vars = p->ssi_vars;
49 hctx->ssi_cgi_env = p->ssi_cgi_env;
50 memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
51 return hctx;
54 static void handler_ctx_free(handler_ctx *hctx) {
55 free(hctx);
58 /* The newest modified time of included files for include statement */
59 static volatile time_t include_file_last_mtime = 0;
61 /* init the plugin data */
62 INIT_FUNC(mod_ssi_init) {
63 plugin_data *p;
65 p = calloc(1, sizeof(*p));
67 p->timefmt = buffer_init();
68 p->stat_fn = buffer_init();
70 p->ssi_vars = array_init();
71 p->ssi_cgi_env = array_init();
73 return p;
76 /* detroy the plugin data */
77 FREE_FUNC(mod_ssi_free) {
78 plugin_data *p = p_d;
79 UNUSED(srv);
81 if (!p) return HANDLER_GO_ON;
83 if (p->config_storage) {
84 size_t i;
85 for (i = 0; i < srv->config_context->used; i++) {
86 plugin_config *s = p->config_storage[i];
88 if (NULL == s) continue;
90 array_free(s->ssi_extension);
91 buffer_free(s->content_type);
93 free(s);
95 free(p->config_storage);
98 array_free(p->ssi_vars);
99 array_free(p->ssi_cgi_env);
100 buffer_free(p->timefmt);
101 buffer_free(p->stat_fn);
103 free(p);
105 return HANDLER_GO_ON;
108 /* handle plugin config and check values */
110 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
111 plugin_data *p = p_d;
112 size_t i = 0;
114 config_values_t cv[] = {
115 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
116 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
117 { "ssi.conditional-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
118 { "ssi.exec", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
119 { "ssi.recursion-max", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
120 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
123 if (!p) return HANDLER_ERROR;
125 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
127 for (i = 0; i < srv->config_context->used; i++) {
128 data_config const* config = (data_config const*)srv->config_context->data[i];
129 plugin_config *s;
131 s = calloc(1, sizeof(plugin_config));
132 s->ssi_extension = array_init();
133 s->content_type = buffer_init();
134 s->conditional_requests = 0;
135 s->ssi_exec = 1;
136 s->ssi_recursion_max = 0;
138 cv[0].destination = s->ssi_extension;
139 cv[1].destination = s->content_type;
140 cv[2].destination = &(s->conditional_requests);
141 cv[3].destination = &(s->ssi_exec);
142 cv[4].destination = &(s->ssi_recursion_max);
144 p->config_storage[i] = s;
146 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
147 return HANDLER_ERROR;
151 return HANDLER_GO_ON;
155 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
156 array *env = venv;
157 data_string *ds;
159 /* array_set_key_value() w/o extra lookup to see if key already exists */
160 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
161 ds = data_string_init();
163 buffer_copy_string_len(ds->key, key, klen);
164 buffer_copy_string_len(ds->value, val, vlen);
166 array_insert_unique(env, (data_unset *)ds);
168 return 0;
171 static int build_ssi_cgi_vars(server *srv, connection *con, handler_ctx *p) {
172 http_cgi_opts opts = { 0, 0, NULL, NULL };
173 /* temporarily remove Authorization from request headers
174 * so that Authorization does not end up in SSI environment */
175 data_string *ds_auth = (data_string *)array_get_element(con->request.headers, "Authorization");
176 buffer *b_auth = NULL;
177 if (ds_auth) {
178 b_auth = ds_auth->value;
179 ds_auth->value = NULL;
182 array_reset(p->ssi_cgi_env);
184 if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) {
185 con->http_status = 400;
186 return -1;
189 if (ds_auth) {
190 ds_auth->value = b_auth;
193 return 0;
196 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st);
198 static int process_ssi_stmt(server *srv, connection *con, handler_ctx *p, const char **l, size_t n, struct stat *st) {
201 * <!--#element attribute=value attribute=value ... -->
203 * config DONE
204 * errmsg -- missing
205 * sizefmt DONE
206 * timefmt DONE
207 * echo DONE
208 * var DONE
209 * encoding -- missing
210 * exec DONE
211 * cgi -- never
212 * cmd DONE
213 * fsize DONE
214 * file DONE
215 * virtual DONE
216 * flastmod DONE
217 * file DONE
218 * virtual DONE
219 * include DONE
220 * file DONE
221 * virtual DONE
222 * printenv DONE
223 * set DONE
224 * var DONE
225 * value DONE
227 * if DONE
228 * elif DONE
229 * else DONE
230 * endif DONE
233 * expressions
234 * AND, OR DONE
235 * comp DONE
236 * ${...} -- missing
237 * $... DONE
238 * '...' DONE
239 * ( ... ) DONE
243 * ** all DONE **
244 * DATE_GMT
245 * The current date in Greenwich Mean Time.
246 * DATE_LOCAL
247 * The current date in the local time zone.
248 * DOCUMENT_NAME
249 * The filename (excluding directories) of the document requested by the user.
250 * DOCUMENT_URI
251 * 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.
252 * LAST_MODIFIED
253 * The last modification date of the document requested by the user.
254 * USER_NAME
255 * Contains the owner of the file which included it.
259 size_t i, ssicmd = 0;
260 char buf[255];
261 buffer *b = NULL;
263 static const struct {
264 const char *var;
265 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
266 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
267 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
268 } ssicmds[] = {
269 { "echo", SSI_ECHO },
270 { "include", SSI_INCLUDE },
271 { "flastmod", SSI_FLASTMOD },
272 { "fsize", SSI_FSIZE },
273 { "config", SSI_CONFIG },
274 { "printenv", SSI_PRINTENV },
275 { "set", SSI_SET },
276 { "if", SSI_IF },
277 { "elif", SSI_ELIF },
278 { "endif", SSI_ENDIF },
279 { "else", SSI_ELSE },
280 { "exec", SSI_EXEC },
282 { NULL, SSI_UNSET }
285 for (i = 0; ssicmds[i].var; i++) {
286 if (0 == strcmp(l[1], ssicmds[i].var)) {
287 ssicmd = ssicmds[i].type;
288 break;
292 switch(ssicmd) {
293 case SSI_ECHO: {
294 /* echo */
295 int var = 0;
296 /* int enc = 0; */
297 const char *var_val = NULL;
299 static const struct {
300 const char *var;
301 enum {
302 SSI_ECHO_UNSET,
303 SSI_ECHO_DATE_GMT,
304 SSI_ECHO_DATE_LOCAL,
305 SSI_ECHO_DOCUMENT_NAME,
306 SSI_ECHO_DOCUMENT_URI,
307 SSI_ECHO_LAST_MODIFIED,
308 SSI_ECHO_USER_NAME,
309 SSI_ECHO_SCRIPT_URI,
310 SSI_ECHO_SCRIPT_URL,
311 } type;
312 } echovars[] = {
313 { "DATE_GMT", SSI_ECHO_DATE_GMT },
314 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
315 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
316 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
317 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
318 { "USER_NAME", SSI_ECHO_USER_NAME },
319 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
320 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
322 { NULL, SSI_ECHO_UNSET }
326 static const struct {
327 const char *var;
328 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
329 } encvars[] = {
330 { "url", SSI_ENC_URL },
331 { "none", SSI_ENC_NONE },
332 { "entity", SSI_ENC_ENTITY },
334 { NULL, SSI_ENC_UNSET }
338 for (i = 2; i < n; i += 2) {
339 if (0 == strcmp(l[i], "var")) {
340 int j;
342 var_val = l[i+1];
344 for (j = 0; echovars[j].var; j++) {
345 if (0 == strcmp(l[i+1], echovars[j].var)) {
346 var = echovars[j].type;
347 break;
350 } else if (0 == strcmp(l[i], "encoding")) {
352 int j;
354 for (j = 0; encvars[j].var; j++) {
355 if (0 == strcmp(l[i+1], encvars[j].var)) {
356 enc = encvars[j].type;
357 break;
361 } else {
362 log_error_write(srv, __FILE__, __LINE__, "sss",
363 "ssi: unknown attribute for ",
364 l[1], l[i]);
368 if (p->if_is_false) break;
370 if (!var_val) {
371 log_error_write(srv, __FILE__, __LINE__, "sss",
372 "ssi: ",
373 l[1], "var is missing");
374 break;
377 switch(var) {
378 case SSI_ECHO_USER_NAME: {
379 struct passwd *pw;
381 b = buffer_init();
382 #ifdef HAVE_PWD_H
383 if (NULL == (pw = getpwuid(st->st_uid))) {
384 buffer_copy_int(b, st->st_uid);
385 } else {
386 buffer_copy_string(b, pw->pw_name);
388 #else
389 buffer_copy_int(b, st->st_uid);
390 #endif
391 chunkqueue_append_buffer(con->write_queue, b);
392 buffer_free(b);
393 break;
395 case SSI_ECHO_LAST_MODIFIED: {
396 time_t t = st->st_mtime;
398 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
399 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
400 } else {
401 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
403 break;
405 case SSI_ECHO_DATE_LOCAL: {
406 time_t t = time(NULL);
408 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
409 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
410 } else {
411 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
413 break;
415 case SSI_ECHO_DATE_GMT: {
416 time_t t = time(NULL);
418 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
419 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
420 } else {
421 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
423 break;
425 case SSI_ECHO_DOCUMENT_NAME: {
426 char *sl;
428 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
429 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
430 } else {
431 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
433 break;
435 case SSI_ECHO_DOCUMENT_URI: {
436 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
437 break;
439 case SSI_ECHO_SCRIPT_URI: {
440 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
441 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
442 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
443 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
444 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
445 if (!buffer_string_is_empty(con->uri.query)) {
446 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
447 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
450 break;
452 case SSI_ECHO_SCRIPT_URL: {
453 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
454 if (!buffer_string_is_empty(con->uri.query)) {
455 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
456 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
458 break;
460 default: {
461 data_string *ds;
462 /* check if it is a cgi-var or a ssi-var */
464 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val)) ||
465 NULL != (ds = (data_string *)array_get_element(p->ssi_vars, var_val))) {
466 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
467 } else {
468 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
471 break;
474 break;
476 case SSI_INCLUDE:
477 case SSI_FLASTMOD:
478 case SSI_FSIZE: {
479 const char * file_path = NULL, *virt_path = NULL;
480 struct stat stb;
481 char *sl;
483 for (i = 2; i < n; i += 2) {
484 if (0 == strcmp(l[i], "file")) {
485 file_path = l[i+1];
486 } else if (0 == strcmp(l[i], "virtual")) {
487 virt_path = l[i+1];
488 } else {
489 log_error_write(srv, __FILE__, __LINE__, "sss",
490 "ssi: unknown attribute for ",
491 l[1], l[i]);
495 if (!file_path && !virt_path) {
496 log_error_write(srv, __FILE__, __LINE__, "sss",
497 "ssi: ",
498 l[1], "file or virtual are missing");
499 break;
502 if (file_path && virt_path) {
503 log_error_write(srv, __FILE__, __LINE__, "sss",
504 "ssi: ",
505 l[1], "only one of file and virtual is allowed here");
506 break;
510 if (p->if_is_false) break;
512 if (file_path) {
513 /* current doc-root */
514 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
515 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
516 } else {
517 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
520 buffer_copy_string(srv->tmp_buf, file_path);
521 buffer_urldecode_path(srv->tmp_buf);
522 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
523 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
524 } else {
525 /* virtual */
526 size_t remain;
528 if (virt_path[0] == '/') {
529 buffer_copy_string(p->stat_fn, virt_path);
530 } else {
531 /* there is always a / */
532 sl = strrchr(con->uri.path->ptr, '/');
534 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
535 buffer_append_string(p->stat_fn, virt_path);
538 buffer_urldecode_path(p->stat_fn);
539 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
541 /* we have an uri */
543 /* Destination physical path (similar to code in mod_webdav.c)
544 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
545 * (but neither modifies con->physical.rel_path)
546 * Find matching prefix to support relative paths to current physical path.
547 * Aliasing of paths underneath current con->physical.basedir might not work.
548 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
549 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
550 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
551 * (Related, use mod_auth to protect this basedir, but avoid attempting to
552 * use mod_auth on paths underneath this basedir, as target path is not
553 * validated with mod_auth)
556 /* find matching URI prefix
557 * check if remaining con->physical.rel_path matches suffix
558 * of con->physical.basedir so that we can use it to
559 * remap Destination physical path */
561 const char *sep, *sep2;
562 sep = con->uri.path->ptr;
563 sep2 = srv->tmp_buf->ptr;
564 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
565 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
567 if (con->conf.force_lowercase_filenames) {
568 buffer_to_lower(srv->tmp_buf);
570 remain = buffer_string_length(con->uri.path) - i;
571 if (!con->conf.force_lowercase_filenames
572 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
573 :(buffer_string_length(con->physical.path) >= remain
574 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
575 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
576 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
577 } else {
578 /* unable to perform physical path remap here;
579 * assume doc_root/rel_path and no remapping */
580 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
581 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
585 if (0 == stat(p->stat_fn->ptr, &stb)) {
586 time_t t = stb.st_mtime;
588 switch (ssicmd) {
589 case SSI_FSIZE:
590 b = buffer_init();
591 if (p->sizefmt) {
592 int j = 0;
593 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
595 off_t s = stb.st_size;
597 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
599 buffer_copy_int(b, s);
600 buffer_append_string(b, abr[j]);
601 } else {
602 buffer_copy_int(b, stb.st_size);
604 chunkqueue_append_buffer(con->write_queue, b);
605 buffer_free(b);
606 break;
607 case SSI_FLASTMOD:
608 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
609 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
610 } else {
611 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
613 break;
614 case SSI_INCLUDE:
615 /* Keep the newest mtime of included files */
616 if (stb.st_mtime > include_file_last_mtime)
617 include_file_last_mtime = stb.st_mtime;
619 if (file_path || 0 == p->conf.ssi_recursion_max) {
620 /* don't process if #include file="..." is used */
621 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, stb.st_size);
622 } else {
623 buffer *upsave, *ppsave, *prpsave;
625 /* only allow predefined recursion depth */
626 if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) {
627 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
628 break;
631 /* prevents simple infinite loop */
632 if (buffer_is_equal(con->physical.path, p->stat_fn)) {
633 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives create an infinite loop)"));
634 break;
637 /* save and restore con->physical.path, con->physical.rel_path, and con->uri.path around include
639 * srv->tmp_buf contains url-decoded, path-simplified, and lowercased (if con->conf.force_lowercase) uri path of target.
640 * con->uri.path and con->physical.rel_path are set to the same since we only operate on filenames here,
641 * not full re-run of all modules for subrequest */
642 upsave = con->uri.path;
643 ppsave = con->physical.path;
644 prpsave = con->physical.rel_path;
646 con->physical.path = p->stat_fn;
647 p->stat_fn = buffer_init();
649 con->uri.path = con->physical.rel_path = buffer_init_buffer(srv->tmp_buf);
651 /*(ignore return value; muddle along as best we can if error occurs)*/
652 ++p->ssi_recursion_depth;
653 mod_ssi_process_file(srv, con, p, &stb);
654 --p->ssi_recursion_depth;
656 buffer_free(con->uri.path);
657 con->uri.path = upsave;
658 con->physical.rel_path = prpsave;
660 buffer_free(p->stat_fn);
661 p->stat_fn = con->physical.path;
662 con->physical.path = ppsave;
665 break;
667 } else {
668 log_error_write(srv, __FILE__, __LINE__, "sbs",
669 "ssi: stating failed ",
670 p->stat_fn, strerror(errno));
672 break;
674 case SSI_SET: {
675 const char *key = NULL, *val = NULL;
676 for (i = 2; i < n; i += 2) {
677 if (0 == strcmp(l[i], "var")) {
678 key = l[i+1];
679 } else if (0 == strcmp(l[i], "value")) {
680 val = l[i+1];
681 } else {
682 log_error_write(srv, __FILE__, __LINE__, "sss",
683 "ssi: unknown attribute for ",
684 l[1], l[i]);
688 if (p->if_is_false) break;
690 if (key && val) {
691 data_string *ds;
693 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
694 ds = data_string_init();
696 buffer_copy_string(ds->key, key);
697 buffer_copy_string(ds->value, val);
699 array_insert_unique(p->ssi_vars, (data_unset *)ds);
700 } else if (key || val) {
701 log_error_write(srv, __FILE__, __LINE__, "sSSss",
702 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->");
703 } else {
704 log_error_write(srv, __FILE__, __LINE__, "s",
705 "ssi: var and value have to be set in <!--#set var=... value=... -->");
707 break;
709 case SSI_CONFIG:
710 if (p->if_is_false) break;
712 for (i = 2; i < n; i += 2) {
713 if (0 == strcmp(l[i], "timefmt")) {
714 buffer_copy_string(p->timefmt, l[i+1]);
715 } else if (0 == strcmp(l[i], "sizefmt")) {
716 if (0 == strcmp(l[i+1], "abbrev")) {
717 p->sizefmt = 1;
718 } else if (0 == strcmp(l[i+1], "bytes")) {
719 p->sizefmt = 0;
720 } else {
721 log_error_write(srv, __FILE__, __LINE__, "sssss",
722 "ssi: unknown value for attribute '",
723 l[i],
724 "' for ",
725 l[1], l[i+1]);
727 } else {
728 log_error_write(srv, __FILE__, __LINE__, "sss",
729 "ssi: unknown attribute for ",
730 l[1], l[i]);
733 break;
734 case SSI_PRINTENV:
735 if (p->if_is_false) break;
737 b = buffer_init();
738 for (i = 0; i < p->ssi_vars->used; i++) {
739 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
741 buffer_append_string_buffer(b, ds->key);
742 buffer_append_string_len(b, CONST_STR_LEN("="));
743 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
744 buffer_append_string_len(b, CONST_STR_LEN("\n"));
746 for (i = 0; i < p->ssi_cgi_env->used; i++) {
747 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
749 buffer_append_string_buffer(b, ds->key);
750 buffer_append_string_len(b, CONST_STR_LEN("="));
751 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
752 buffer_append_string_len(b, CONST_STR_LEN("\n"));
754 chunkqueue_append_buffer(con->write_queue, b);
755 buffer_free(b);
757 break;
758 case SSI_EXEC: {
759 const char *cmd = NULL;
760 pid_t pid;
761 int from_exec_fds[2];
763 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
764 break;
767 for (i = 2; i < n; i += 2) {
768 if (0 == strcmp(l[i], "cmd")) {
769 cmd = l[i+1];
770 } else {
771 log_error_write(srv, __FILE__, __LINE__, "sss",
772 "ssi: unknown attribute for ",
773 l[1], l[i]);
777 if (p->if_is_false) break;
779 /* create a return pipe and send output to the html-page
781 * as exec is assumed evil it is implemented synchronously
784 if (!cmd) break;
785 #ifdef HAVE_FORK
786 if (pipe(from_exec_fds)) {
787 log_error_write(srv, __FILE__, __LINE__, "ss",
788 "pipe failed: ", strerror(errno));
789 return -1;
792 /* fork, execve */
793 switch (pid = fork()) {
794 case 0: {
795 /* move stdout to from_rrdtool_fd[1] */
796 close(STDOUT_FILENO);
797 dup2(from_exec_fds[1], STDOUT_FILENO);
798 close(from_exec_fds[1]);
799 /* not needed */
800 close(from_exec_fds[0]);
802 /* close stdin */
803 close(STDIN_FILENO);
805 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
807 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
809 /* */
810 SEGFAULT();
811 break;
813 case -1:
814 /* error */
815 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
816 break;
817 default: {
818 /* father */
819 int status;
820 ssize_t r;
821 int was_interrupted = 0;
823 close(from_exec_fds[1]);
825 /* wait for the client to end */
828 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
830 do {
831 if (-1 == waitpid(pid, &status, 0)) {
832 if (errno == EINTR) {
833 was_interrupted++;
834 } else {
835 was_interrupted = 0;
836 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
838 } else if (WIFEXITED(status)) {
839 int toread;
840 /* read everything from client and paste it into the output */
841 was_interrupted = 0;
843 while(1) {
844 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
845 log_error_write(srv, __FILE__, __LINE__, "s",
846 "unexpected end-of-file (perhaps the ssi-exec process died)");
847 return -1;
850 if (toread > 0) {
851 char *mem;
852 size_t mem_len;
854 chunkqueue_get_memory(con->write_queue, &mem, &mem_len, 0, toread);
855 r = read(from_exec_fds[0], mem, mem_len);
856 chunkqueue_use_memory(con->write_queue, r > 0 ? r : 0);
858 if (r < 0) break; /* read failed */
859 } else {
860 break;
863 } else {
864 was_interrupted = 0;
865 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
867 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
869 close(from_exec_fds[0]);
871 break;
874 #else
876 return -1;
877 #endif
879 break;
881 case SSI_IF: {
882 const char *expr = NULL;
884 for (i = 2; i < n; i += 2) {
885 if (0 == strcmp(l[i], "expr")) {
886 expr = l[i+1];
887 } else {
888 log_error_write(srv, __FILE__, __LINE__, "sss",
889 "ssi: unknown attribute for ",
890 l[1], l[i]);
894 if (!expr) {
895 log_error_write(srv, __FILE__, __LINE__, "sss",
896 "ssi: ",
897 l[1], "expr missing");
898 break;
901 if ((!p->if_is_false) &&
902 ((p->if_is_false_level == 0) ||
903 (p->if_level < p->if_is_false_level))) {
904 switch (ssi_eval_expr(srv, con, p, expr)) {
905 case -1:
906 case 0:
907 p->if_is_false = 1;
908 p->if_is_false_level = p->if_level;
909 break;
910 case 1:
911 p->if_is_false = 0;
912 break;
916 p->if_level++;
918 break;
920 case SSI_ELSE:
921 p->if_level--;
923 if (p->if_is_false) {
924 if ((p->if_level == p->if_is_false_level) &&
925 (p->if_is_false_endif == 0)) {
926 p->if_is_false = 0;
928 } else {
929 p->if_is_false = 1;
931 p->if_is_false_level = p->if_level;
933 p->if_level++;
935 break;
936 case SSI_ELIF: {
937 const char *expr = NULL;
938 for (i = 2; i < n; i += 2) {
939 if (0 == strcmp(l[i], "expr")) {
940 expr = l[i+1];
941 } else {
942 log_error_write(srv, __FILE__, __LINE__, "sss",
943 "ssi: unknown attribute for ",
944 l[1], l[i]);
948 if (!expr) {
949 log_error_write(srv, __FILE__, __LINE__, "sss",
950 "ssi: ",
951 l[1], "expr missing");
952 break;
955 p->if_level--;
957 if (p->if_level == p->if_is_false_level) {
958 if ((p->if_is_false) &&
959 (p->if_is_false_endif == 0)) {
960 switch (ssi_eval_expr(srv, con, p, expr)) {
961 case -1:
962 case 0:
963 p->if_is_false = 1;
964 p->if_is_false_level = p->if_level;
965 break;
966 case 1:
967 p->if_is_false = 0;
968 break;
970 } else {
971 p->if_is_false = 1;
972 p->if_is_false_level = p->if_level;
973 p->if_is_false_endif = 1;
977 p->if_level++;
979 break;
981 case SSI_ENDIF:
982 p->if_level--;
984 if (p->if_level == p->if_is_false_level) {
985 p->if_is_false = 0;
986 p->if_is_false_endif = 0;
989 break;
990 default:
991 log_error_write(srv, __FILE__, __LINE__, "ss",
992 "ssi: unknown ssi-command:",
993 l[1]);
994 break;
997 return 0;
1001 static int mod_ssi_parse_ssi_stmt_value(const char * const s, const int len) {
1002 int n;
1003 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0);
1004 if (0 != c) {
1005 for (n = 1; n < len; ++n) {
1006 if (s[n] == c) return n+1;
1007 if (s[n] == '\\') {
1008 if (n+1 == len) return 0; /* invalid */
1009 ++n;
1012 return 0; /* invalid */
1013 } else {
1014 for (n = 0; n < len; ++n) {
1015 if (isspace(s[n])) return n;
1016 if (s[n] == '\\') {
1017 if (n+1 == len) return 0; /* invalid */
1018 ++n;
1021 return n;
1025 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const char * const s, const int len) {
1028 * <!--#element attribute=value attribute=value ... -->
1031 /* s must begin "<!--#" and must end with "-->" */
1032 int n = 5;
1033 o[0] = n;
1034 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/
1035 o[1] = n - o[0];
1036 if (0 == o[1]) return -1; /* empty token */
1038 if (n+3 == len) return 2; /* token only; no params */
1039 if (!isspace(s[n])) return -1;
1040 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1041 if (n+3 == len) return 2; /* token only; no params */
1043 o[2] = n;
1044 for (; light_isalpha(s[n]); ++n) ;
1045 o[3] = n - o[2];
1046 if (0 == o[3] || s[n++] != '=') return -1;
1048 o[4] = n;
1049 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1050 if (0 == o[5]) return -1; /* empty or invalid token */
1051 n += o[5];
1053 if (n+3 == len) return 6; /* token and one param */
1054 if (!isspace(s[n])) return -1;
1055 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1056 if (n+3 == len) return 6; /* token and one param */
1058 o[6] = n;
1059 for (; light_isalpha(s[n]); ++n) ;
1060 o[7] = n - o[6];
1061 if (0 == o[7] || s[n++] != '=') return -1;
1063 o[8] = n;
1064 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1065 if (0 == o[9]) return -1; /* empty or invalid token */
1066 n += o[9];
1068 if (n+3 == len) return 10; /* token and two params */
1069 if (!isspace(s[n])) return -1;
1070 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1071 if (n+3 == len) return 10; /* token and two params */
1072 return -1;
1075 static void mod_ssi_parse_ssi_stmt(server *srv, connection *con, handler_ctx *p, char *s, int len, struct stat *st) {
1078 * <!--#element attribute=value attribute=value ... -->
1081 int o[10];
1082 int m;
1083 const int n = mod_ssi_parse_ssi_stmt_offlen(o, s, len);
1084 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL };
1085 if (-1 == n) {
1086 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1087 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */
1088 return;
1091 #if 0
1092 /* dup s and then modify s */
1093 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1094 l[0] = malloc((size_t)(len+1));
1095 memcpy(l[0], s, (size_t)len);
1096 (l[0])[len] = '\0';
1097 #endif
1099 /* modify s in-place to split string into arg tokens */
1100 for (m = 0; m < n; m += 2) {
1101 char *ptr = s+o[m];
1102 switch (*ptr) {
1103 case '"':
1104 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1105 default: ptr[o[m+1]] = '\0'; break;
1107 l[1+(m>>1)] = ptr;
1108 if (m == 4 || m == 8) {
1109 /* XXX: removing '\\' escapes from param value would be
1110 * the right thing to do, but would potentially change
1111 * current behavior, e.g. <!--#exec cmd=... --> */
1115 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st);
1117 #if 0
1118 free(l[0]);
1119 #endif
1122 static int mod_ssi_stmt_len(const char *s, const int len) {
1123 /* s must begin "<!--#" */
1124 int n, sq = 0, dq = 0, bs = 0;
1125 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1126 switch (s[n]) {
1127 default:
1128 break;
1129 case '-':
1130 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1131 break;
1132 case '"':
1133 if (!sq && (!dq || !bs)) dq = !dq;
1134 break;
1135 case '\'':
1136 if (!dq && (!sq || !bs)) sq = !sq;
1137 break;
1138 case '\\':
1139 if (sq || dq) bs = !bs;
1140 break;
1143 return 0; /* incomplete directive "<!--#...-->" */
1146 static void mod_ssi_read_fd(server *srv, connection *con, handler_ctx *p, struct stat *st, int fd) {
1147 ssize_t rd;
1148 size_t offset, pretag;
1149 size_t bufsz = 8192;
1150 char *buf = malloc(bufsz); /* allocate to reduce chance of stack exhaustion upon deep recursion */
1151 force_assert(buf);
1153 offset = 0;
1154 pretag = 0;
1155 while (0 < (rd = read(fd, buf+offset, bufsz-offset))) {
1156 char *s;
1157 size_t prelen = 0, len;
1158 offset += (size_t)rd;
1159 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1160 prelen = s - buf;
1161 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1162 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1164 if (prelen - pretag && !p->if_is_false) {
1165 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1168 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1169 if (len) { /* num of chars to be consumed */
1170 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st);
1171 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1172 pretag = prelen + 1;
1173 if (pretag == offset) {
1174 offset = pretag = 0;
1175 break;
1177 } else if (0 == prelen && offset == bufsz) { /*(full buf)*/
1178 /* SSI statement is way too long
1179 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1180 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1181 /* check if buf ends with "-" or "--" which might be part of "-->"
1182 * (buf contains at least 5 chars for "<!--#") */
1183 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1184 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--"));
1185 } else if (buf[offset-1] == '-') {
1186 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-"));
1188 offset = pretag = 0;
1189 break;
1190 } else { /* incomplete directive "<!--#...-->" */
1191 memmove(buf, buf+prelen, (offset -= prelen));
1192 pretag = 0;
1193 break;
1195 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1196 if (prelen - pretag && !p->if_is_false) {
1197 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1199 memcpy(buf, buf+prelen, (offset -= prelen));
1200 pretag = 0;
1201 break;
1203 /* loop to look for next '<' */
1205 if (offset == bufsz) {
1206 if (!p->if_is_false) {
1207 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1209 offset = pretag = 0;
1213 if (0 != rd) {
1214 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path);
1217 if (offset - pretag) {
1218 /* copy remaining data in buf */
1219 if (!p->if_is_false) {
1220 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1224 free(buf);
1228 /* don't want to block when open()ing a fifo */
1229 #if defined(O_NONBLOCK)
1230 # define FIFO_NONBLOCK O_NONBLOCK
1231 #else
1232 # define FIFO_NONBLOCK 0
1233 #endif
1235 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st) {
1236 int fd = open(con->physical.path->ptr, O_RDONLY | FIFO_NONBLOCK);
1237 if (-1 == fd) {
1238 log_error_write(srv, __FILE__, __LINE__, "SsB", "open(): ",
1239 strerror(errno), con->physical.path);
1240 return -1;
1243 if (0 != fstat(fd, st)) {
1244 log_error_write(srv, __FILE__, __LINE__, "SsB", "fstat(): ",
1245 strerror(errno), con->physical.path);
1246 close(fd);
1247 return -1;
1250 mod_ssi_read_fd(srv, con, p, st, fd);
1252 close(fd);
1253 return 0;
1257 static int mod_ssi_handle_request(server *srv, connection *con, handler_ctx *p) {
1258 struct stat st;
1260 /* get a stream to the file */
1262 array_reset(p->ssi_vars);
1263 array_reset(p->ssi_cgi_env);
1264 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1265 build_ssi_cgi_vars(srv, con, p);
1267 /* Reset the modified time of included files */
1268 include_file_last_mtime = 0;
1270 mod_ssi_process_file(srv, con, p, &st);
1272 con->file_started = 1;
1273 con->file_finished = 1;
1275 if (buffer_string_is_empty(p->conf.content_type)) {
1276 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1277 } else {
1278 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1281 if (p->conf.conditional_requests) {
1282 /* Generate "ETag" & "Last-Modified" headers */
1283 buffer *mtime = NULL;
1285 /* use most recently modified include file for ETag and Last-Modified */
1286 if (st.st_mtime < include_file_last_mtime)
1287 st.st_mtime = include_file_last_mtime;
1289 etag_create(con->physical.etag, &st, con->etag_flags);
1290 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1292 mtime = strftime_cache_get(srv, st.st_mtime);
1293 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1295 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
1296 /* ok, the client already has our content,
1297 * no need to send it again */
1299 chunkqueue_reset(con->write_queue);
1303 /* Reset the modified time of included files */
1304 include_file_last_mtime = 0;
1306 /* reset physical.path */
1307 buffer_reset(con->physical.path);
1309 return 0;
1312 #define PATCH(x) \
1313 p->conf.x = s->x;
1314 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1315 size_t i, j;
1316 plugin_config *s = p->config_storage[0];
1318 PATCH(ssi_extension);
1319 PATCH(content_type);
1320 PATCH(conditional_requests);
1321 PATCH(ssi_exec);
1322 PATCH(ssi_recursion_max);
1324 /* skip the first, the global context */
1325 for (i = 1; i < srv->config_context->used; i++) {
1326 data_config *dc = (data_config *)srv->config_context->data[i];
1327 s = p->config_storage[i];
1329 /* condition didn't match */
1330 if (!config_check_cond(srv, con, dc)) continue;
1332 /* merge config */
1333 for (j = 0; j < dc->value->used; j++) {
1334 data_unset *du = dc->value->data[j];
1336 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1337 PATCH(ssi_extension);
1338 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1339 PATCH(content_type);
1340 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) {
1341 PATCH(conditional_requests);
1342 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) {
1343 PATCH(ssi_exec);
1344 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.recursion-max"))) {
1345 PATCH(ssi_recursion_max);
1350 return 0;
1352 #undef PATCH
1354 URIHANDLER_FUNC(mod_ssi_physical_path) {
1355 plugin_data *p = p_d;
1356 size_t k;
1358 if (con->mode != DIRECT) return HANDLER_GO_ON;
1360 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1362 mod_ssi_patch_connection(srv, con, p);
1364 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1365 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1367 if (buffer_is_empty(ds->value)) continue;
1369 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
1370 con->plugin_ctx[p->id] = handler_ctx_init(p);
1371 con->mode = p->id;
1372 break;
1376 return HANDLER_GO_ON;
1379 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) {
1380 plugin_data *p = p_d;
1381 handler_ctx *hctx = con->plugin_ctx[p->id];
1382 if (NULL == hctx) return HANDLER_GO_ON;
1383 if (con->mode != p->id) return HANDLER_GO_ON; /* not my job */
1385 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1386 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1387 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1390 /* handle ssi-request */
1392 if (mod_ssi_handle_request(srv, con, hctx)) {
1393 /* on error */
1394 con->http_status = 500;
1395 con->mode = DIRECT;
1398 return HANDLER_FINISHED;
1401 static handler_t mod_ssi_connection_reset(server *srv, connection *con, void *p_d) {
1402 plugin_data *p = p_d;
1403 handler_ctx *hctx = con->plugin_ctx[p->id];
1404 if (hctx) {
1405 handler_ctx_free(hctx);
1406 con->plugin_ctx[p->id] = NULL;
1409 UNUSED(srv);
1410 return HANDLER_GO_ON;
1413 /* this function is called at dlopen() time and inits the callbacks */
1415 int mod_ssi_plugin_init(plugin *p);
1416 int mod_ssi_plugin_init(plugin *p) {
1417 p->version = LIGHTTPD_VERSION_ID;
1418 p->name = buffer_init_string("ssi");
1420 p->init = mod_ssi_init;
1421 p->handle_subrequest_start = mod_ssi_physical_path;
1422 p->handle_subrequest = mod_ssi_handle_subrequest;
1423 p->connection_reset = mod_ssi_connection_reset;
1424 p->set_defaults = mod_ssi_set_defaults;
1425 p->cleanup = mod_ssi_free;
1427 p->data = NULL;
1429 return 0;