[mod_ssi] produce content in subrequest hook
[lighttpd.git] / src / mod_ssi.c
blob246e704330226da9dfbdcdf0ae41cf654938ccc4
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 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
122 if (!p) return HANDLER_ERROR;
124 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
126 for (i = 0; i < srv->config_context->used; i++) {
127 data_config const* config = (data_config const*)srv->config_context->data[i];
128 plugin_config *s;
130 s = calloc(1, sizeof(plugin_config));
131 s->ssi_extension = array_init();
132 s->content_type = buffer_init();
133 s->conditional_requests = 0;
134 s->ssi_exec = 1;
136 cv[0].destination = s->ssi_extension;
137 cv[1].destination = s->content_type;
138 cv[2].destination = &(s->conditional_requests);
139 cv[3].destination = &(s->ssi_exec);
141 p->config_storage[i] = s;
143 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
144 return HANDLER_ERROR;
148 return HANDLER_GO_ON;
152 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
153 array *env = venv;
154 data_string *ds;
156 /* array_set_key_value() w/o extra lookup to see if key already exists */
157 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
158 ds = data_string_init();
160 buffer_copy_string_len(ds->key, key, klen);
161 buffer_copy_string_len(ds->value, val, vlen);
163 array_insert_unique(env, (data_unset *)ds);
165 return 0;
168 static int build_ssi_cgi_vars(server *srv, connection *con, handler_ctx *p) {
169 http_cgi_opts opts = { 0, 0, NULL, NULL };
170 /* temporarily remove Authorization from request headers
171 * so that Authorization does not end up in SSI environment */
172 data_string *ds_auth = (data_string *)array_get_element(con->request.headers, "Authorization");
173 buffer *b_auth = NULL;
174 if (ds_auth) {
175 b_auth = ds_auth->value;
176 ds_auth->value = NULL;
179 array_reset(p->ssi_cgi_env);
181 if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) {
182 con->http_status = 400;
183 return -1;
186 if (ds_auth) {
187 ds_auth->value = b_auth;
190 return 0;
193 static int process_ssi_stmt(server *srv, connection *con, handler_ctx *p, const char **l, size_t n, struct stat *st) {
196 * <!--#element attribute=value attribute=value ... -->
198 * config DONE
199 * errmsg -- missing
200 * sizefmt DONE
201 * timefmt DONE
202 * echo DONE
203 * var DONE
204 * encoding -- missing
205 * exec DONE
206 * cgi -- never
207 * cmd DONE
208 * fsize DONE
209 * file DONE
210 * virtual DONE
211 * flastmod DONE
212 * file DONE
213 * virtual DONE
214 * include DONE
215 * file DONE
216 * virtual DONE
217 * printenv DONE
218 * set DONE
219 * var DONE
220 * value DONE
222 * if DONE
223 * elif DONE
224 * else DONE
225 * endif DONE
228 * expressions
229 * AND, OR DONE
230 * comp DONE
231 * ${...} -- missing
232 * $... DONE
233 * '...' DONE
234 * ( ... ) DONE
238 * ** all DONE **
239 * DATE_GMT
240 * The current date in Greenwich Mean Time.
241 * DATE_LOCAL
242 * The current date in the local time zone.
243 * DOCUMENT_NAME
244 * The filename (excluding directories) of the document requested by the user.
245 * DOCUMENT_URI
246 * 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.
247 * LAST_MODIFIED
248 * The last modification date of the document requested by the user.
249 * USER_NAME
250 * Contains the owner of the file which included it.
254 size_t i, ssicmd = 0;
255 char buf[255];
256 buffer *b = NULL;
258 struct {
259 const char *var;
260 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
261 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
262 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
263 } ssicmds[] = {
264 { "echo", SSI_ECHO },
265 { "include", SSI_INCLUDE },
266 { "flastmod", SSI_FLASTMOD },
267 { "fsize", SSI_FSIZE },
268 { "config", SSI_CONFIG },
269 { "printenv", SSI_PRINTENV },
270 { "set", SSI_SET },
271 { "if", SSI_IF },
272 { "elif", SSI_ELIF },
273 { "endif", SSI_ENDIF },
274 { "else", SSI_ELSE },
275 { "exec", SSI_EXEC },
277 { NULL, SSI_UNSET }
280 for (i = 0; ssicmds[i].var; i++) {
281 if (0 == strcmp(l[1], ssicmds[i].var)) {
282 ssicmd = ssicmds[i].type;
283 break;
287 switch(ssicmd) {
288 case SSI_ECHO: {
289 /* echo */
290 int var = 0;
291 /* int enc = 0; */
292 const char *var_val = NULL;
294 struct {
295 const char *var;
296 enum {
297 SSI_ECHO_UNSET,
298 SSI_ECHO_DATE_GMT,
299 SSI_ECHO_DATE_LOCAL,
300 SSI_ECHO_DOCUMENT_NAME,
301 SSI_ECHO_DOCUMENT_URI,
302 SSI_ECHO_LAST_MODIFIED,
303 SSI_ECHO_USER_NAME,
304 SSI_ECHO_SCRIPT_URI,
305 SSI_ECHO_SCRIPT_URL,
306 } type;
307 } echovars[] = {
308 { "DATE_GMT", SSI_ECHO_DATE_GMT },
309 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
310 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
311 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
312 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
313 { "USER_NAME", SSI_ECHO_USER_NAME },
314 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
315 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
317 { NULL, SSI_ECHO_UNSET }
321 struct {
322 const char *var;
323 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
324 } encvars[] = {
325 { "url", SSI_ENC_URL },
326 { "none", SSI_ENC_NONE },
327 { "entity", SSI_ENC_ENTITY },
329 { NULL, SSI_ENC_UNSET }
333 for (i = 2; i < n; i += 2) {
334 if (0 == strcmp(l[i], "var")) {
335 int j;
337 var_val = l[i+1];
339 for (j = 0; echovars[j].var; j++) {
340 if (0 == strcmp(l[i+1], echovars[j].var)) {
341 var = echovars[j].type;
342 break;
345 } else if (0 == strcmp(l[i], "encoding")) {
347 int j;
349 for (j = 0; encvars[j].var; j++) {
350 if (0 == strcmp(l[i+1], encvars[j].var)) {
351 enc = encvars[j].type;
352 break;
356 } else {
357 log_error_write(srv, __FILE__, __LINE__, "sss",
358 "ssi: unknown attribute for ",
359 l[1], l[i]);
363 if (p->if_is_false) break;
365 if (!var_val) {
366 log_error_write(srv, __FILE__, __LINE__, "sss",
367 "ssi: ",
368 l[1], "var is missing");
369 break;
372 switch(var) {
373 case SSI_ECHO_USER_NAME: {
374 struct passwd *pw;
376 b = buffer_init();
377 #ifdef HAVE_PWD_H
378 if (NULL == (pw = getpwuid(st->st_uid))) {
379 buffer_copy_int(b, st->st_uid);
380 } else {
381 buffer_copy_string(b, pw->pw_name);
383 #else
384 buffer_copy_int(b, st->st_uid);
385 #endif
386 chunkqueue_append_buffer(con->write_queue, b);
387 buffer_free(b);
388 break;
390 case SSI_ECHO_LAST_MODIFIED: {
391 time_t t = st->st_mtime;
393 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
394 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
395 } else {
396 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
398 break;
400 case SSI_ECHO_DATE_LOCAL: {
401 time_t t = time(NULL);
403 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
404 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
405 } else {
406 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
408 break;
410 case SSI_ECHO_DATE_GMT: {
411 time_t t = time(NULL);
413 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
414 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
415 } else {
416 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
418 break;
420 case SSI_ECHO_DOCUMENT_NAME: {
421 char *sl;
423 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
424 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
425 } else {
426 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
428 break;
430 case SSI_ECHO_DOCUMENT_URI: {
431 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
432 break;
434 case SSI_ECHO_SCRIPT_URI: {
435 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
436 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
437 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
438 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
439 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
440 if (!buffer_string_is_empty(con->uri.query)) {
441 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
442 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
445 break;
447 case SSI_ECHO_SCRIPT_URL: {
448 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
449 if (!buffer_string_is_empty(con->uri.query)) {
450 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
451 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
453 break;
455 default: {
456 data_string *ds;
457 /* check if it is a cgi-var or a ssi-var */
459 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val)) ||
460 NULL != (ds = (data_string *)array_get_element(p->ssi_vars, var_val))) {
461 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
462 } else {
463 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
466 break;
469 break;
471 case SSI_INCLUDE:
472 case SSI_FLASTMOD:
473 case SSI_FSIZE: {
474 const char * file_path = NULL, *virt_path = NULL;
475 struct stat stb;
476 char *sl;
478 for (i = 2; i < n; i += 2) {
479 if (0 == strcmp(l[i], "file")) {
480 file_path = l[i+1];
481 } else if (0 == strcmp(l[i], "virtual")) {
482 virt_path = l[i+1];
483 } else {
484 log_error_write(srv, __FILE__, __LINE__, "sss",
485 "ssi: unknown attribute for ",
486 l[1], l[i]);
490 if (!file_path && !virt_path) {
491 log_error_write(srv, __FILE__, __LINE__, "sss",
492 "ssi: ",
493 l[1], "file or virtual are missing");
494 break;
497 if (file_path && virt_path) {
498 log_error_write(srv, __FILE__, __LINE__, "sss",
499 "ssi: ",
500 l[1], "only one of file and virtual is allowed here");
501 break;
505 if (p->if_is_false) break;
507 if (file_path) {
508 /* current doc-root */
509 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
510 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
511 } else {
512 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
515 buffer_copy_string(srv->tmp_buf, file_path);
516 buffer_urldecode_path(srv->tmp_buf);
517 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
518 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
519 } else {
520 /* virtual */
521 size_t remain;
523 if (virt_path[0] == '/') {
524 buffer_copy_string(p->stat_fn, virt_path);
525 } else {
526 /* there is always a / */
527 sl = strrchr(con->uri.path->ptr, '/');
529 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
530 buffer_append_string(p->stat_fn, virt_path);
533 buffer_urldecode_path(p->stat_fn);
534 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
536 /* we have an uri */
538 /* Destination physical path (similar to code in mod_webdav.c)
539 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
540 * (but neither modifies con->physical.rel_path)
541 * Find matching prefix to support relative paths to current physical path.
542 * Aliasing of paths underneath current con->physical.basedir might not work.
543 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
544 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
545 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
546 * (Related, use mod_auth to protect this basedir, but avoid attempting to
547 * use mod_auth on paths underneath this basedir, as target path is not
548 * validated with mod_auth)
551 /* find matching URI prefix
552 * check if remaining con->physical.rel_path matches suffix
553 * of con->physical.basedir so that we can use it to
554 * remap Destination physical path */
556 const char *sep, *sep2;
557 sep = con->uri.path->ptr;
558 sep2 = srv->tmp_buf->ptr;
559 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
560 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
562 if (con->conf.force_lowercase_filenames) {
563 buffer_to_lower(srv->tmp_buf);
565 remain = buffer_string_length(con->uri.path) - i;
566 if (!con->conf.force_lowercase_filenames
567 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
568 :(buffer_string_length(con->physical.path) >= remain
569 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
570 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
571 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
572 } else {
573 /* unable to perform physical path remap here;
574 * assume doc_root/rel_path and no remapping */
575 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
576 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
580 if (0 == stat(p->stat_fn->ptr, &stb)) {
581 time_t t = stb.st_mtime;
583 switch (ssicmd) {
584 case SSI_FSIZE:
585 b = buffer_init();
586 if (p->sizefmt) {
587 int j = 0;
588 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
590 off_t s = stb.st_size;
592 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
594 buffer_copy_int(b, s);
595 buffer_append_string(b, abr[j]);
596 } else {
597 buffer_copy_int(b, stb.st_size);
599 chunkqueue_append_buffer(con->write_queue, b);
600 buffer_free(b);
601 break;
602 case SSI_FLASTMOD:
603 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
604 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
605 } else {
606 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
608 break;
609 case SSI_INCLUDE:
610 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, stb.st_size);
612 /* Keep the newest mtime of included files */
613 if (stb.st_mtime > include_file_last_mtime)
614 include_file_last_mtime = stb.st_mtime;
616 break;
618 } else {
619 log_error_write(srv, __FILE__, __LINE__, "sbs",
620 "ssi: stating failed ",
621 p->stat_fn, strerror(errno));
623 break;
625 case SSI_SET: {
626 const char *key = NULL, *val = NULL;
627 for (i = 2; i < n; i += 2) {
628 if (0 == strcmp(l[i], "var")) {
629 key = l[i+1];
630 } else if (0 == strcmp(l[i], "value")) {
631 val = l[i+1];
632 } else {
633 log_error_write(srv, __FILE__, __LINE__, "sss",
634 "ssi: unknown attribute for ",
635 l[1], l[i]);
639 if (p->if_is_false) break;
641 if (key && val) {
642 data_string *ds;
644 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
645 ds = data_string_init();
647 buffer_copy_string(ds->key, key);
648 buffer_copy_string(ds->value, val);
650 array_insert_unique(p->ssi_vars, (data_unset *)ds);
651 } else if (key || val) {
652 log_error_write(srv, __FILE__, __LINE__, "sSSss",
653 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->");
654 } else {
655 log_error_write(srv, __FILE__, __LINE__, "s",
656 "ssi: var and value have to be set in <!--#set var=... value=... -->");
658 break;
660 case SSI_CONFIG:
661 if (p->if_is_false) break;
663 for (i = 2; i < n; i += 2) {
664 if (0 == strcmp(l[i], "timefmt")) {
665 buffer_copy_string(p->timefmt, l[i+1]);
666 } else if (0 == strcmp(l[i], "sizefmt")) {
667 if (0 == strcmp(l[i+1], "abbrev")) {
668 p->sizefmt = 1;
669 } else if (0 == strcmp(l[i+1], "bytes")) {
670 p->sizefmt = 0;
671 } else {
672 log_error_write(srv, __FILE__, __LINE__, "sssss",
673 "ssi: unknown value for attribute '",
674 l[i],
675 "' for ",
676 l[1], l[i+1]);
678 } else {
679 log_error_write(srv, __FILE__, __LINE__, "sss",
680 "ssi: unknown attribute for ",
681 l[1], l[i]);
684 break;
685 case SSI_PRINTENV:
686 if (p->if_is_false) break;
688 b = buffer_init();
689 for (i = 0; i < p->ssi_vars->used; i++) {
690 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
692 buffer_append_string_buffer(b, ds->key);
693 buffer_append_string_len(b, CONST_STR_LEN("="));
694 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
695 buffer_append_string_len(b, CONST_STR_LEN("\n"));
697 for (i = 0; i < p->ssi_cgi_env->used; i++) {
698 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
700 buffer_append_string_buffer(b, ds->key);
701 buffer_append_string_len(b, CONST_STR_LEN("="));
702 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
703 buffer_append_string_len(b, CONST_STR_LEN("\n"));
705 chunkqueue_append_buffer(con->write_queue, b);
706 buffer_free(b);
708 break;
709 case SSI_EXEC: {
710 const char *cmd = NULL;
711 pid_t pid;
712 int from_exec_fds[2];
714 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
715 break;
718 for (i = 2; i < n; i += 2) {
719 if (0 == strcmp(l[i], "cmd")) {
720 cmd = l[i+1];
721 } else {
722 log_error_write(srv, __FILE__, __LINE__, "sss",
723 "ssi: unknown attribute for ",
724 l[1], l[i]);
728 if (p->if_is_false) break;
730 /* create a return pipe and send output to the html-page
732 * as exec is assumed evil it is implemented synchronously
735 if (!cmd) break;
736 #ifdef HAVE_FORK
737 if (pipe(from_exec_fds)) {
738 log_error_write(srv, __FILE__, __LINE__, "ss",
739 "pipe failed: ", strerror(errno));
740 return -1;
743 /* fork, execve */
744 switch (pid = fork()) {
745 case 0: {
746 /* move stdout to from_rrdtool_fd[1] */
747 close(STDOUT_FILENO);
748 dup2(from_exec_fds[1], STDOUT_FILENO);
749 close(from_exec_fds[1]);
750 /* not needed */
751 close(from_exec_fds[0]);
753 /* close stdin */
754 close(STDIN_FILENO);
756 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
758 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
760 /* */
761 SEGFAULT();
762 break;
764 case -1:
765 /* error */
766 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
767 break;
768 default: {
769 /* father */
770 int status;
771 ssize_t r;
772 int was_interrupted = 0;
774 close(from_exec_fds[1]);
776 /* wait for the client to end */
779 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
781 do {
782 if (-1 == waitpid(pid, &status, 0)) {
783 if (errno == EINTR) {
784 was_interrupted++;
785 } else {
786 was_interrupted = 0;
787 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
789 } else if (WIFEXITED(status)) {
790 int toread;
791 /* read everything from client and paste it into the output */
792 was_interrupted = 0;
794 while(1) {
795 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
796 log_error_write(srv, __FILE__, __LINE__, "s",
797 "unexpected end-of-file (perhaps the ssi-exec process died)");
798 return -1;
801 if (toread > 0) {
802 char *mem;
803 size_t mem_len;
805 chunkqueue_get_memory(con->write_queue, &mem, &mem_len, 0, toread);
806 r = read(from_exec_fds[0], mem, mem_len);
807 chunkqueue_use_memory(con->write_queue, r > 0 ? r : 0);
809 if (r < 0) break; /* read failed */
810 } else {
811 break;
814 } else {
815 was_interrupted = 0;
816 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
818 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
820 close(from_exec_fds[0]);
822 break;
825 #else
827 return -1;
828 #endif
830 break;
832 case SSI_IF: {
833 const char *expr = NULL;
835 for (i = 2; i < n; i += 2) {
836 if (0 == strcmp(l[i], "expr")) {
837 expr = l[i+1];
838 } else {
839 log_error_write(srv, __FILE__, __LINE__, "sss",
840 "ssi: unknown attribute for ",
841 l[1], l[i]);
845 if (!expr) {
846 log_error_write(srv, __FILE__, __LINE__, "sss",
847 "ssi: ",
848 l[1], "expr missing");
849 break;
852 if ((!p->if_is_false) &&
853 ((p->if_is_false_level == 0) ||
854 (p->if_level < p->if_is_false_level))) {
855 switch (ssi_eval_expr(srv, con, p, expr)) {
856 case -1:
857 case 0:
858 p->if_is_false = 1;
859 p->if_is_false_level = p->if_level;
860 break;
861 case 1:
862 p->if_is_false = 0;
863 break;
867 p->if_level++;
869 break;
871 case SSI_ELSE:
872 p->if_level--;
874 if (p->if_is_false) {
875 if ((p->if_level == p->if_is_false_level) &&
876 (p->if_is_false_endif == 0)) {
877 p->if_is_false = 0;
879 } else {
880 p->if_is_false = 1;
882 p->if_is_false_level = p->if_level;
884 p->if_level++;
886 break;
887 case SSI_ELIF: {
888 const char *expr = NULL;
889 for (i = 2; i < n; i += 2) {
890 if (0 == strcmp(l[i], "expr")) {
891 expr = l[i+1];
892 } else {
893 log_error_write(srv, __FILE__, __LINE__, "sss",
894 "ssi: unknown attribute for ",
895 l[1], l[i]);
899 if (!expr) {
900 log_error_write(srv, __FILE__, __LINE__, "sss",
901 "ssi: ",
902 l[1], "expr missing");
903 break;
906 p->if_level--;
908 if (p->if_level == p->if_is_false_level) {
909 if ((p->if_is_false) &&
910 (p->if_is_false_endif == 0)) {
911 switch (ssi_eval_expr(srv, con, p, expr)) {
912 case -1:
913 case 0:
914 p->if_is_false = 1;
915 p->if_is_false_level = p->if_level;
916 break;
917 case 1:
918 p->if_is_false = 0;
919 break;
921 } else {
922 p->if_is_false = 1;
923 p->if_is_false_level = p->if_level;
924 p->if_is_false_endif = 1;
928 p->if_level++;
930 break;
932 case SSI_ENDIF:
933 p->if_level--;
935 if (p->if_level == p->if_is_false_level) {
936 p->if_is_false = 0;
937 p->if_is_false_endif = 0;
940 break;
941 default:
942 log_error_write(srv, __FILE__, __LINE__, "ss",
943 "ssi: unknown ssi-command:",
944 l[1]);
945 break;
948 return 0;
952 static int mod_ssi_parse_ssi_stmt_value(const char * const s, const int len) {
953 int n;
954 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0);
955 if (0 != c) {
956 for (n = 1; n < len; ++n) {
957 if (s[n] == c) return n+1;
958 if (s[n] == '\\') {
959 if (n+1 == len) return 0; /* invalid */
960 ++n;
963 return 0; /* invalid */
964 } else {
965 for (n = 0; n < len; ++n) {
966 if (isspace(s[n])) return n;
967 if (s[n] == '\\') {
968 if (n+1 == len) return 0; /* invalid */
969 ++n;
972 return n;
976 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const char * const s, const int len) {
979 * <!--#element attribute=value attribute=value ... -->
982 /* s must begin "<!--#" and must end with "-->" */
983 int n = 5;
984 o[0] = n;
985 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/
986 o[1] = n - o[0];
987 if (0 == o[1]) return -1; /* empty token */
989 if (n+3 == len) return 2; /* token only; no params */
990 if (!isspace(s[n])) return -1;
991 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
992 if (n+3 == len) return 2; /* token only; no params */
994 o[2] = n;
995 for (; light_isalpha(s[n]); ++n) ;
996 o[3] = n - o[2];
997 if (0 == o[3] || s[n++] != '=') return -1;
999 o[4] = n;
1000 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1001 if (0 == o[5]) return -1; /* empty or invalid token */
1002 n += o[5];
1004 if (n+3 == len) return 6; /* token and one param */
1005 if (!isspace(s[n])) return -1;
1006 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1007 if (n+3 == len) return 6; /* token and one param */
1009 o[6] = n;
1010 for (; light_isalpha(s[n]); ++n) ;
1011 o[7] = n - o[6];
1012 if (0 == o[7] || s[n++] != '=') return -1;
1014 o[8] = n;
1015 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1016 if (0 == o[9]) return -1; /* empty or invalid token */
1017 n += o[9];
1019 if (n+3 == len) return 10; /* token and two params */
1020 if (!isspace(s[n])) return -1;
1021 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1022 if (n+3 == len) return 10; /* token and two params */
1023 return -1;
1026 static void mod_ssi_parse_ssi_stmt(server *srv, connection *con, handler_ctx *p, char *s, int len, struct stat *st) {
1029 * <!--#element attribute=value attribute=value ... -->
1032 int o[10];
1033 int m;
1034 const int n = mod_ssi_parse_ssi_stmt_offlen(o, s, len);
1035 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL };
1036 if (-1 == n) {
1037 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1038 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */
1039 return;
1042 #if 0
1043 /* dup s and then modify s */
1044 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1045 l[0] = malloc((size_t)(len+1));
1046 memcpy(l[0], s, (size_t)len);
1047 (l[0])[len] = '\0';
1048 #endif
1050 /* modify s in-place to split string into arg tokens */
1051 for (m = 0; m < n; m += 2) {
1052 char *ptr = s+o[m];
1053 switch (*ptr) {
1054 case '"':
1055 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1056 default: ptr[o[m+1]] = '\0'; break;
1058 l[1+(m>>1)] = ptr;
1059 if (m == 4 || m == 8) {
1060 /* XXX: removing '\\' escapes from param value would be
1061 * the right thing to do, but would potentially change
1062 * current behavior, e.g. <!--#exec cmd=... --> */
1066 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st);
1068 #if 0
1069 free(l[0]);
1070 #endif
1073 static int mod_ssi_stmt_len(const char *s, const int len) {
1074 /* s must begin "<!--#" */
1075 int n, sq = 0, dq = 0, bs = 0;
1076 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1077 switch (s[n]) {
1078 default:
1079 break;
1080 case '-':
1081 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1082 break;
1083 case '"':
1084 if (!sq && (!dq || !bs)) dq = !dq;
1085 break;
1086 case '\'':
1087 if (!dq && (!sq || !bs)) sq = !sq;
1088 break;
1089 case '\\':
1090 if (sq || dq) bs = !bs;
1091 break;
1094 return 0; /* incomplete directive "<!--#...-->" */
1097 static void mod_ssi_read_fd(server *srv, connection *con, handler_ctx *p, int fd, struct stat *st) {
1098 ssize_t rd;
1099 size_t offset, pretag;
1100 char buf[8192];
1102 offset = 0;
1103 pretag = 0;
1104 while (0 < (rd = read(fd, buf+offset, sizeof(buf)-offset))) {
1105 char *s;
1106 size_t prelen = 0, len;
1107 offset += (size_t)rd;
1108 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1109 prelen = s - buf;
1110 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1111 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1113 if (prelen - pretag && !p->if_is_false) {
1114 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1117 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1118 if (len) { /* num of chars to be consumed */
1119 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st);
1120 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1121 pretag = prelen + 1;
1122 if (pretag == offset) {
1123 offset = pretag = 0;
1124 break;
1126 } else if (0 == prelen && offset == sizeof(buf)) { /*(full buf)*/
1127 /* SSI statement is way too long
1128 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1129 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1130 /* check if buf ends with "-" or "--" which might be part of "-->"
1131 * (buf contains at least 5 chars for "<!--#") */
1132 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1133 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--"));
1134 } else if (buf[offset-1] == '-') {
1135 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-"));
1137 offset = pretag = 0;
1138 break;
1139 } else { /* incomplete directive "<!--#...-->" */
1140 memmove(buf, buf+prelen, (offset -= prelen));
1141 pretag = 0;
1142 break;
1144 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1145 if (prelen - pretag && !p->if_is_false) {
1146 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1148 memcpy(buf, buf+prelen, (offset -= prelen));
1149 pretag = 0;
1150 break;
1152 /* loop to look for next '<' */
1154 if (offset == sizeof(buf)) {
1155 if (!p->if_is_false) {
1156 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1158 offset = pretag = 0;
1162 if (0 != rd) {
1163 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path);
1166 if (offset - pretag) {
1167 /* copy remaining data in buf */
1168 if (!p->if_is_false) {
1169 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1175 /* don't want to block when open()ing a fifo */
1176 #if defined(O_NONBLOCK)
1177 # define FIFO_NONBLOCK O_NONBLOCK
1178 #else
1179 # define FIFO_NONBLOCK 0
1180 #endif
1182 static int mod_ssi_handle_request(server *srv, connection *con, handler_ctx *p) {
1183 int fd;
1184 struct stat st;
1186 /* get a stream to the file */
1188 array_reset(p->ssi_vars);
1189 array_reset(p->ssi_cgi_env);
1190 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1191 build_ssi_cgi_vars(srv, con, p);
1193 /* Reset the modified time of included files */
1194 include_file_last_mtime = 0;
1196 if (-1 == (fd = open(con->physical.path->ptr, O_RDONLY | FIFO_NONBLOCK))) {
1197 log_error_write(srv, __FILE__, __LINE__, "sb",
1198 "open: ", con->physical.path);
1199 return -1;
1202 if (0 != fstat(fd, &st)) {
1203 log_error_write(srv, __FILE__, __LINE__, "SB", "fstat failed: ", con->physical.path);
1204 close(fd);
1205 return -1;
1208 mod_ssi_read_fd(srv, con, p, fd, &st);
1210 close(fd);
1211 con->file_started = 1;
1212 con->file_finished = 1;
1214 if (buffer_string_is_empty(p->conf.content_type)) {
1215 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1216 } else {
1217 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1220 if (p->conf.conditional_requests) {
1221 /* Generate "ETag" & "Last-Modified" headers */
1222 buffer *mtime = NULL;
1224 /* use most recently modified include file for ETag and Last-Modified */
1225 if (st.st_mtime < include_file_last_mtime)
1226 st.st_mtime = include_file_last_mtime;
1228 etag_create(con->physical.etag, &st, con->etag_flags);
1229 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1231 mtime = strftime_cache_get(srv, st.st_mtime);
1232 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1234 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
1235 /* ok, the client already has our content,
1236 * no need to send it again */
1238 chunkqueue_reset(con->write_queue);
1242 /* Reset the modified time of included files */
1243 include_file_last_mtime = 0;
1245 /* reset physical.path */
1246 buffer_reset(con->physical.path);
1248 return 0;
1251 #define PATCH(x) \
1252 p->conf.x = s->x;
1253 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1254 size_t i, j;
1255 plugin_config *s = p->config_storage[0];
1257 PATCH(ssi_extension);
1258 PATCH(content_type);
1259 PATCH(conditional_requests);
1260 PATCH(ssi_exec);
1262 /* skip the first, the global context */
1263 for (i = 1; i < srv->config_context->used; i++) {
1264 data_config *dc = (data_config *)srv->config_context->data[i];
1265 s = p->config_storage[i];
1267 /* condition didn't match */
1268 if (!config_check_cond(srv, con, dc)) continue;
1270 /* merge config */
1271 for (j = 0; j < dc->value->used; j++) {
1272 data_unset *du = dc->value->data[j];
1274 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1275 PATCH(ssi_extension);
1276 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1277 PATCH(content_type);
1278 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) {
1279 PATCH(conditional_requests);
1280 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) {
1281 PATCH(ssi_exec);
1286 return 0;
1288 #undef PATCH
1290 URIHANDLER_FUNC(mod_ssi_physical_path) {
1291 plugin_data *p = p_d;
1292 size_t k;
1294 if (con->mode != DIRECT) return HANDLER_GO_ON;
1296 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1298 mod_ssi_patch_connection(srv, con, p);
1300 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1301 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1303 if (buffer_is_empty(ds->value)) continue;
1305 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
1306 con->plugin_ctx[p->id] = handler_ctx_init(p);
1307 con->mode = p->id;
1308 break;
1312 return HANDLER_GO_ON;
1315 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) {
1316 plugin_data *p = p_d;
1317 handler_ctx *hctx = con->plugin_ctx[p->id];
1318 if (NULL == hctx) return HANDLER_GO_ON;
1319 if (con->mode != p->id) return HANDLER_GO_ON; /* not my job */
1321 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1322 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1323 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1326 /* handle ssi-request */
1328 if (mod_ssi_handle_request(srv, con, hctx)) {
1329 /* on error */
1330 con->http_status = 500;
1331 con->mode = DIRECT;
1334 return HANDLER_FINISHED;
1337 static handler_t mod_ssi_connection_reset(server *srv, connection *con, void *p_d) {
1338 plugin_data *p = p_d;
1339 handler_ctx *hctx = con->plugin_ctx[p->id];
1340 if (hctx) {
1341 handler_ctx_free(hctx);
1342 con->plugin_ctx[p->id] = NULL;
1345 UNUSED(srv);
1346 return HANDLER_GO_ON;
1349 /* this function is called at dlopen() time and inits the callbacks */
1351 int mod_ssi_plugin_init(plugin *p);
1352 int mod_ssi_plugin_init(plugin *p) {
1353 p->version = LIGHTTPD_VERSION_ID;
1354 p->name = buffer_init_string("ssi");
1356 p->init = mod_ssi_init;
1357 p->handle_subrequest_start = mod_ssi_physical_path;
1358 p->handle_subrequest = mod_ssi_handle_subrequest;
1359 p->connection_reset = mod_ssi_connection_reset;
1360 p->set_defaults = mod_ssi_set_defaults;
1361 p->cleanup = mod_ssi_free;
1363 p->data = NULL;
1365 return 0;