[core] use buffer_eq_icase* funcs
[lighttpd.git] / src / mod_ssi.c
blobeefd2944cb9a5f124e0bce7140812c75c17e8f81
1 #include "first.h"
3 #include "base.h"
4 #include "fdevent.h"
5 #include "log.h"
6 #include "buffer.h"
7 #include "http_header.h"
8 #include "stat_cache.h"
10 #include "plugin.h"
12 #include "response.h"
14 #include "mod_ssi.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include "sys-strings.h"
21 #include <sys/wait.h>
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <time.h>
29 #include <unistd.h>
31 #ifdef HAVE_PWD_H
32 # include <pwd.h>
33 #endif
35 #ifdef HAVE_SYS_FILIO_H
36 # include <sys/filio.h>
37 #endif
39 #include "etag.h"
41 static handler_ctx * handler_ctx_init(plugin_data *p) {
42 handler_ctx *hctx = calloc(1, sizeof(*hctx));
43 force_assert(hctx);
44 hctx->timefmt = p->timefmt;
45 hctx->stat_fn = p->stat_fn;
46 hctx->ssi_vars = p->ssi_vars;
47 hctx->ssi_cgi_env = p->ssi_cgi_env;
48 memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
49 return hctx;
52 static void handler_ctx_free(handler_ctx *hctx) {
53 free(hctx);
56 /* The newest modified time of included files for include statement */
57 static volatile time_t include_file_last_mtime = 0;
59 /* init the plugin data */
60 INIT_FUNC(mod_ssi_init) {
61 plugin_data *p;
63 p = calloc(1, sizeof(*p));
65 p->timefmt = buffer_init();
66 p->stat_fn = buffer_init();
68 p->ssi_vars = array_init();
69 p->ssi_cgi_env = array_init();
71 return p;
74 /* detroy the plugin data */
75 FREE_FUNC(mod_ssi_free) {
76 plugin_data *p = p_d;
77 UNUSED(srv);
79 if (!p) return HANDLER_GO_ON;
81 if (p->config_storage) {
82 size_t i;
83 for (i = 0; i < srv->config_context->used; i++) {
84 plugin_config *s = p->config_storage[i];
86 if (NULL == s) continue;
88 array_free(s->ssi_extension);
89 buffer_free(s->content_type);
91 free(s);
93 free(p->config_storage);
96 array_free(p->ssi_vars);
97 array_free(p->ssi_cgi_env);
98 buffer_free(p->timefmt);
99 buffer_free(p->stat_fn);
101 free(p);
103 return HANDLER_GO_ON;
106 /* handle plugin config and check values */
108 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
109 plugin_data *p = p_d;
110 size_t i = 0;
112 config_values_t cv[] = {
113 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
114 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
115 { "ssi.conditional-requests", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
116 { "ssi.exec", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
117 { "ssi.recursion-max", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
118 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
121 if (!p) return HANDLER_ERROR;
123 p->config_storage = calloc(srv->config_context->used, sizeof(plugin_config *));
125 for (i = 0; i < srv->config_context->used; i++) {
126 data_config const* config = (data_config const*)srv->config_context->data[i];
127 plugin_config *s;
129 s = calloc(1, sizeof(plugin_config));
130 s->ssi_extension = array_init();
131 s->content_type = buffer_init();
132 s->conditional_requests = 0;
133 s->ssi_exec = 1;
134 s->ssi_recursion_max = 0;
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);
140 cv[4].destination = &(s->ssi_recursion_max);
142 p->config_storage[i] = s;
144 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
145 return HANDLER_ERROR;
148 if (!array_is_vlist(s->ssi_extension)) {
149 log_error_write(srv, __FILE__, __LINE__, "s",
150 "unexpected value for ssi.extension; expected list of \"ext\"");
151 return HANDLER_ERROR;
155 return HANDLER_GO_ON;
159 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
160 array_insert_key_value((array *)venv, key, klen, val, vlen);
161 return 0;
164 static int build_ssi_cgi_vars(server *srv, connection *con, handler_ctx *p) {
165 http_cgi_opts opts = { 0, 0, NULL, NULL };
166 /* temporarily remove Authorization from request headers
167 * so that Authorization does not end up in SSI environment */
168 buffer *vb_auth = http_header_request_get(con, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
169 buffer b_auth;
170 if (vb_auth) {
171 memcpy(&b_auth, vb_auth, sizeof(buffer));
172 memset(vb_auth, 0, sizeof(buffer));
175 array_reset_data_strings(p->ssi_cgi_env);
177 if (0 != http_cgi_headers(srv, con, &opts, ssi_env_add, p->ssi_cgi_env)) {
178 con->http_status = 400;
179 return -1;
182 if (vb_auth) {
183 memcpy(vb_auth, &b_auth, sizeof(buffer));
186 return 0;
189 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st);
191 static int process_ssi_stmt(server *srv, connection *con, handler_ctx *p, const char **l, size_t n, struct stat *st) {
194 * <!--#element attribute=value attribute=value ... -->
196 * config DONE
197 * errmsg -- missing
198 * sizefmt DONE
199 * timefmt DONE
200 * echo DONE
201 * var DONE
202 * encoding -- missing
203 * exec DONE
204 * cgi -- never
205 * cmd DONE
206 * fsize DONE
207 * file DONE
208 * virtual DONE
209 * flastmod DONE
210 * file DONE
211 * virtual DONE
212 * include DONE
213 * file DONE
214 * virtual DONE
215 * printenv DONE
216 * set DONE
217 * var DONE
218 * value DONE
220 * if DONE
221 * elif DONE
222 * else DONE
223 * endif DONE
226 * expressions
227 * AND, OR DONE
228 * comp DONE
229 * ${...} -- missing
230 * $... DONE
231 * '...' DONE
232 * ( ... ) DONE
236 * ** all DONE **
237 * DATE_GMT
238 * The current date in Greenwich Mean Time.
239 * DATE_LOCAL
240 * The current date in the local time zone.
241 * DOCUMENT_NAME
242 * The filename (excluding directories) of the document requested by the user.
243 * DOCUMENT_URI
244 * 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.
245 * LAST_MODIFIED
246 * The last modification date of the document requested by the user.
247 * USER_NAME
248 * Contains the owner of the file which included it.
252 size_t i, ssicmd = 0;
253 char buf[255];
254 buffer *b = NULL;
256 static const struct {
257 const char *var;
258 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
259 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
260 SSI_ELSE, SSI_ENDIF, SSI_EXEC, SSI_COMMENT } type;
261 } ssicmds[] = {
262 { "echo", SSI_ECHO },
263 { "include", SSI_INCLUDE },
264 { "flastmod", SSI_FLASTMOD },
265 { "fsize", SSI_FSIZE },
266 { "config", SSI_CONFIG },
267 { "printenv", SSI_PRINTENV },
268 { "set", SSI_SET },
269 { "if", SSI_IF },
270 { "elif", SSI_ELIF },
271 { "endif", SSI_ENDIF },
272 { "else", SSI_ELSE },
273 { "exec", SSI_EXEC },
274 { "comment", SSI_COMMENT },
276 { NULL, SSI_UNSET }
279 for (i = 0; ssicmds[i].var; i++) {
280 if (0 == strcmp(l[1], ssicmds[i].var)) {
281 ssicmd = ssicmds[i].type;
282 break;
286 switch(ssicmd) {
287 case SSI_ECHO: {
288 /* echo */
289 int var = 0;
290 /* int enc = 0; */
291 const char *var_val = NULL;
293 static const struct {
294 const char *var;
295 enum {
296 SSI_ECHO_UNSET,
297 SSI_ECHO_DATE_GMT,
298 SSI_ECHO_DATE_LOCAL,
299 SSI_ECHO_DOCUMENT_NAME,
300 SSI_ECHO_DOCUMENT_URI,
301 SSI_ECHO_LAST_MODIFIED,
302 SSI_ECHO_USER_NAME,
303 SSI_ECHO_SCRIPT_URI,
304 SSI_ECHO_SCRIPT_URL,
305 } type;
306 } echovars[] = {
307 { "DATE_GMT", SSI_ECHO_DATE_GMT },
308 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
309 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
310 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
311 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
312 { "USER_NAME", SSI_ECHO_USER_NAME },
313 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
314 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
316 { NULL, SSI_ECHO_UNSET }
320 static const struct {
321 const char *var;
322 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
323 } encvars[] = {
324 { "url", SSI_ENC_URL },
325 { "none", SSI_ENC_NONE },
326 { "entity", SSI_ENC_ENTITY },
328 { NULL, SSI_ENC_UNSET }
332 for (i = 2; i < n; i += 2) {
333 if (0 == strcmp(l[i], "var")) {
334 int j;
336 var_val = l[i+1];
338 for (j = 0; echovars[j].var; j++) {
339 if (0 == strcmp(l[i+1], echovars[j].var)) {
340 var = echovars[j].type;
341 break;
344 } else if (0 == strcmp(l[i], "encoding")) {
346 int j;
348 for (j = 0; encvars[j].var; j++) {
349 if (0 == strcmp(l[i+1], encvars[j].var)) {
350 enc = encvars[j].type;
351 break;
355 } else {
356 log_error_write(srv, __FILE__, __LINE__, "sss",
357 "ssi: unknown attribute for ",
358 l[1], l[i]);
362 if (p->if_is_false) break;
364 if (!var_val) {
365 log_error_write(srv, __FILE__, __LINE__, "sss",
366 "ssi: ",
367 l[1], "var is missing");
368 break;
371 switch(var) {
372 case SSI_ECHO_USER_NAME: {
373 struct passwd *pw;
375 b = srv->tmp_buf;
376 #ifdef HAVE_PWD_H
377 if (NULL == (pw = getpwuid(st->st_uid))) {
378 buffer_copy_int(b, st->st_uid);
379 } else {
380 buffer_copy_string(b, pw->pw_name);
382 #else
383 buffer_copy_int(b, st->st_uid);
384 #endif
385 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b));
386 break;
388 case SSI_ECHO_LAST_MODIFIED: {
389 time_t t = st->st_mtime;
391 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
392 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
393 } else {
394 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
396 break;
398 case SSI_ECHO_DATE_LOCAL: {
399 time_t t = time(NULL);
401 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
402 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
403 } else {
404 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
406 break;
408 case SSI_ECHO_DATE_GMT: {
409 time_t t = time(NULL);
411 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
412 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
413 } else {
414 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
416 break;
418 case SSI_ECHO_DOCUMENT_NAME: {
419 char *sl;
421 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
422 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
423 } else {
424 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
426 break;
428 case SSI_ECHO_DOCUMENT_URI: {
429 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
430 break;
432 case SSI_ECHO_SCRIPT_URI: {
433 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
434 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
435 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
436 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
437 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
438 if (!buffer_string_is_empty(con->uri.query)) {
439 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
440 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
443 break;
445 case SSI_ECHO_SCRIPT_URL: {
446 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
447 if (!buffer_string_is_empty(con->uri.query)) {
448 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
449 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
451 break;
453 default: {
454 data_string *ds;
455 /* check if it is a cgi-var or a ssi-var */
457 if (NULL != (ds = (data_string *)array_get_element_klen(p->ssi_cgi_env, var_val, strlen(var_val))) ||
458 NULL != (ds = (data_string *)array_get_element_klen(p->ssi_vars, var_val, strlen(var_val)))) {
459 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
460 } else {
461 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
464 break;
467 break;
469 case SSI_INCLUDE:
470 case SSI_FLASTMOD:
471 case SSI_FSIZE: {
472 const char * file_path = NULL, *virt_path = NULL;
473 struct stat stb;
474 char *sl;
476 for (i = 2; i < n; i += 2) {
477 if (0 == strcmp(l[i], "file")) {
478 file_path = l[i+1];
479 } else if (0 == strcmp(l[i], "virtual")) {
480 virt_path = l[i+1];
481 } else {
482 log_error_write(srv, __FILE__, __LINE__, "sss",
483 "ssi: unknown attribute for ",
484 l[1], l[i]);
488 if (!file_path && !virt_path) {
489 log_error_write(srv, __FILE__, __LINE__, "sss",
490 "ssi: ",
491 l[1], "file or virtual are missing");
492 break;
495 if (file_path && virt_path) {
496 log_error_write(srv, __FILE__, __LINE__, "sss",
497 "ssi: ",
498 l[1], "only one of file and virtual is allowed here");
499 break;
503 if (p->if_is_false) break;
505 if (file_path) {
506 /* current doc-root */
507 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
508 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
509 } else {
510 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
513 buffer_copy_string(srv->tmp_buf, file_path);
514 buffer_urldecode_path(srv->tmp_buf);
515 if (!buffer_is_valid_UTF8(srv->tmp_buf)) {
516 log_error_write(srv, __FILE__, __LINE__, "sb",
517 "SSI invalid UTF-8 after url-decode:", srv->tmp_buf);
518 break;
520 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
521 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
522 } else {
523 /* virtual */
524 size_t remain;
526 if (virt_path[0] == '/') {
527 buffer_copy_string(srv->tmp_buf, virt_path);
528 } else {
529 /* there is always a / */
530 sl = strrchr(con->uri.path->ptr, '/');
532 buffer_copy_string_len(srv->tmp_buf, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
533 buffer_append_string(srv->tmp_buf, virt_path);
536 buffer_urldecode_path(srv->tmp_buf);
537 if (!buffer_is_valid_UTF8(srv->tmp_buf)) {
538 log_error_write(srv, __FILE__, __LINE__, "sb",
539 "SSI invalid UTF-8 after url-decode:", srv->tmp_buf);
540 break;
542 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
544 /* we have an uri */
546 /* Destination physical path (similar to code in mod_webdav.c)
547 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
548 * (but neither modifies con->physical.rel_path)
549 * Find matching prefix to support relative paths to current physical path.
550 * Aliasing of paths underneath current con->physical.basedir might not work.
551 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
552 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
553 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
554 * (Related, use mod_auth to protect this basedir, but avoid attempting to
555 * use mod_auth on paths underneath this basedir, as target path is not
556 * validated with mod_auth)
559 /* find matching URI prefix
560 * check if remaining con->physical.rel_path matches suffix
561 * of con->physical.basedir so that we can use it to
562 * remap Destination physical path */
564 const char *sep, *sep2;
565 sep = con->uri.path->ptr;
566 sep2 = srv->tmp_buf->ptr;
567 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
568 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
570 if (con->conf.force_lowercase_filenames) {
571 buffer_to_lower(srv->tmp_buf);
573 remain = buffer_string_length(con->uri.path) - i;
574 if (!con->conf.force_lowercase_filenames
575 ? buffer_is_equal_right_len(con->physical.path, con->physical.rel_path, remain)
576 :(buffer_string_length(con->physical.path) >= remain
577 && 0 == strncasecmp(con->physical.path->ptr+buffer_string_length(con->physical.path)-remain, con->physical.rel_path->ptr+i, remain))) {
578 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, buffer_string_length(con->physical.path)-remain);
579 buffer_append_string_len(p->stat_fn, srv->tmp_buf->ptr+i, buffer_string_length(srv->tmp_buf)-i);
580 } else {
581 /* unable to perform physical path remap here;
582 * assume doc_root/rel_path and no remapping */
583 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
584 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
588 if (!con->conf.follow_symlink
589 && 0 != stat_cache_path_contains_symlink(srv, p->stat_fn)) {
590 break;
593 int fd = stat_cache_open_rdonly_fstat(p->stat_fn, &stb, con->conf.follow_symlink);
594 if (fd > 0) {
595 time_t t = stb.st_mtime;
597 switch (ssicmd) {
598 case SSI_FSIZE:
599 b = srv->tmp_buf;
600 if (p->sizefmt) {
601 int j = 0;
602 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
604 off_t s = stb.st_size;
606 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
608 buffer_copy_int(b, s);
609 buffer_append_string(b, abr[j]);
610 } else {
611 buffer_copy_int(b, stb.st_size);
613 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b));
614 break;
615 case SSI_FLASTMOD:
616 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
617 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
618 } else {
619 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
621 break;
622 case SSI_INCLUDE:
623 /* Keep the newest mtime of included files */
624 if (stb.st_mtime > include_file_last_mtime)
625 include_file_last_mtime = stb.st_mtime;
627 if (file_path || 0 == p->conf.ssi_recursion_max) {
628 /* don't process if #include file="..." is used */
629 chunkqueue_append_file_fd(con->write_queue, p->stat_fn, fd, 0, stb.st_size);
630 fd = -1;
631 } else {
632 buffer *upsave, *ppsave, *prpsave;
634 /* only allow predefined recursion depth */
635 if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) {
636 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
637 break;
640 /* prevents simple infinite loop */
641 if (buffer_is_equal(con->physical.path, p->stat_fn)) {
642 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(error: include directives create an infinite loop)"));
643 break;
646 /* save and restore con->physical.path, con->physical.rel_path, and con->uri.path around include
648 * srv->tmp_buf contains url-decoded, path-simplified, and lowercased (if con->conf.force_lowercase) uri path of target.
649 * con->uri.path and con->physical.rel_path are set to the same since we only operate on filenames here,
650 * not full re-run of all modules for subrequest */
651 upsave = con->uri.path;
652 ppsave = con->physical.path;
653 prpsave = con->physical.rel_path;
655 con->physical.path = p->stat_fn;
656 p->stat_fn = buffer_init();
658 con->uri.path = con->physical.rel_path = buffer_init_buffer(srv->tmp_buf);
660 close(fd);
661 fd = -1;
663 /*(ignore return value; muddle along as best we can if error occurs)*/
664 ++p->ssi_recursion_depth;
665 mod_ssi_process_file(srv, con, p, &stb);
666 --p->ssi_recursion_depth;
668 buffer_free(con->uri.path);
669 con->uri.path = upsave;
670 con->physical.rel_path = prpsave;
672 buffer_free(p->stat_fn);
673 p->stat_fn = con->physical.path;
674 con->physical.path = ppsave;
677 break;
680 if (fd > 0) close(fd);
681 } else {
682 log_error_write(srv, __FILE__, __LINE__, "sbs",
683 "ssi: stating failed ",
684 p->stat_fn, strerror(errno));
686 break;
688 case SSI_SET: {
689 const char *key = NULL, *val = NULL;
690 for (i = 2; i < n; i += 2) {
691 if (0 == strcmp(l[i], "var")) {
692 key = l[i+1];
693 } else if (0 == strcmp(l[i], "value")) {
694 val = l[i+1];
695 } else {
696 log_error_write(srv, __FILE__, __LINE__, "sss",
697 "ssi: unknown attribute for ",
698 l[1], l[i]);
702 if (p->if_is_false) break;
704 if (key && val) {
705 array_insert_key_value(p->ssi_vars, key, strlen(key), val, strlen(val));
706 } else if (key || val) {
707 log_error_write(srv, __FILE__, __LINE__, "sSSss",
708 "ssi: var and value have to be set in <!--#set", l[1], "=", l[2], "-->");
709 } else {
710 log_error_write(srv, __FILE__, __LINE__, "s",
711 "ssi: var and value have to be set in <!--#set var=... value=... -->");
713 break;
715 case SSI_CONFIG:
716 if (p->if_is_false) break;
718 for (i = 2; i < n; i += 2) {
719 if (0 == strcmp(l[i], "timefmt")) {
720 buffer_copy_string(p->timefmt, l[i+1]);
721 } else if (0 == strcmp(l[i], "sizefmt")) {
722 if (0 == strcmp(l[i+1], "abbrev")) {
723 p->sizefmt = 1;
724 } else if (0 == strcmp(l[i+1], "bytes")) {
725 p->sizefmt = 0;
726 } else {
727 log_error_write(srv, __FILE__, __LINE__, "sssss",
728 "ssi: unknown value for attribute '",
729 l[i],
730 "' for ",
731 l[1], l[i+1]);
733 } else {
734 log_error_write(srv, __FILE__, __LINE__, "sss",
735 "ssi: unknown attribute for ",
736 l[1], l[i]);
739 break;
740 case SSI_PRINTENV:
741 if (p->if_is_false) break;
743 b = srv->tmp_buf;
744 buffer_clear(b);
745 for (i = 0; i < p->ssi_vars->used; i++) {
746 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
748 buffer_append_string_buffer(b, ds->key);
749 buffer_append_string_len(b, CONST_STR_LEN("="));
750 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
751 buffer_append_string_len(b, CONST_STR_LEN("\n"));
753 for (i = 0; i < p->ssi_cgi_env->used; i++) {
754 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
756 buffer_append_string_buffer(b, ds->key);
757 buffer_append_string_len(b, CONST_STR_LEN("="));
758 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
759 buffer_append_string_len(b, CONST_STR_LEN("\n"));
761 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b));
762 break;
763 case SSI_EXEC: {
764 const char *cmd = NULL;
765 pid_t pid;
766 chunk *c;
767 char *args[4];
769 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
770 break;
773 for (i = 2; i < n; i += 2) {
774 if (0 == strcmp(l[i], "cmd")) {
775 cmd = l[i+1];
776 } else {
777 log_error_write(srv, __FILE__, __LINE__, "sss",
778 "ssi: unknown attribute for ",
779 l[1], l[i]);
783 if (p->if_is_false) break;
786 * as exec is assumed evil it is implemented synchronously
789 if (!cmd) break;
791 /* send cmd output to a temporary file */
792 if (0 != chunkqueue_append_mem_to_tempfile(srv, con->write_queue, "", 0)) break;
793 c = con->write_queue->last;
795 *(const char **)&args[0] = "/bin/sh";
796 *(const char **)&args[1] = "-c";
797 *(const char **)&args[2] = cmd;
798 args[3] = NULL;
800 /*(expects STDIN_FILENO open to /dev/null)*/
801 pid = fdevent_fork_execve(args[0], args, NULL, -1, c->file.fd, -1, -1);
802 if (-1 == pid) {
803 log_error_write(srv, __FILE__, __LINE__, "sss", "spawning exec failed:", strerror(errno), cmd);
804 } else {
805 struct stat stb;
806 int status;
808 /* wait for the client to end */
809 /* NOTE: synchronous; blocks entire lighttpd server */
812 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
814 while (-1 == waitpid(pid, &status, 0)) {
815 if (errno != EINTR) {
816 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
817 break;
820 if (!WIFEXITED(status)) {
821 log_error_write(srv, __FILE__, __LINE__, "ss", "process exited abnormally:", cmd);
823 if (0 == fstat(c->file.fd, &stb)) {
824 c->file.length = stb.st_size;
828 break;
830 case SSI_IF: {
831 const char *expr = NULL;
833 for (i = 2; i < n; i += 2) {
834 if (0 == strcmp(l[i], "expr")) {
835 expr = l[i+1];
836 } else {
837 log_error_write(srv, __FILE__, __LINE__, "sss",
838 "ssi: unknown attribute for ",
839 l[1], l[i]);
843 if (!expr) {
844 log_error_write(srv, __FILE__, __LINE__, "sss",
845 "ssi: ",
846 l[1], "expr missing");
847 break;
850 if ((!p->if_is_false) &&
851 ((p->if_is_false_level == 0) ||
852 (p->if_level < p->if_is_false_level))) {
853 switch (ssi_eval_expr(srv, con, p, expr)) {
854 case -1:
855 case 0:
856 p->if_is_false = 1;
857 p->if_is_false_level = p->if_level;
858 break;
859 case 1:
860 p->if_is_false = 0;
861 break;
865 p->if_level++;
867 break;
869 case SSI_ELSE:
870 p->if_level--;
872 if (p->if_is_false) {
873 if ((p->if_level == p->if_is_false_level) &&
874 (p->if_is_false_endif == 0)) {
875 p->if_is_false = 0;
877 } else {
878 p->if_is_false = 1;
880 p->if_is_false_level = p->if_level;
882 p->if_level++;
884 break;
885 case SSI_ELIF: {
886 const char *expr = NULL;
887 for (i = 2; i < n; i += 2) {
888 if (0 == strcmp(l[i], "expr")) {
889 expr = l[i+1];
890 } else {
891 log_error_write(srv, __FILE__, __LINE__, "sss",
892 "ssi: unknown attribute for ",
893 l[1], l[i]);
897 if (!expr) {
898 log_error_write(srv, __FILE__, __LINE__, "sss",
899 "ssi: ",
900 l[1], "expr missing");
901 break;
904 p->if_level--;
906 if (p->if_level == p->if_is_false_level) {
907 if ((p->if_is_false) &&
908 (p->if_is_false_endif == 0)) {
909 switch (ssi_eval_expr(srv, con, p, expr)) {
910 case -1:
911 case 0:
912 p->if_is_false = 1;
913 p->if_is_false_level = p->if_level;
914 break;
915 case 1:
916 p->if_is_false = 0;
917 break;
919 } else {
920 p->if_is_false = 1;
921 p->if_is_false_level = p->if_level;
922 p->if_is_false_endif = 1;
926 p->if_level++;
928 break;
930 case SSI_ENDIF:
931 p->if_level--;
933 if (p->if_level == p->if_is_false_level) {
934 p->if_is_false = 0;
935 p->if_is_false_endif = 0;
938 break;
939 case SSI_COMMENT:
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 /* ignore <!--#comment ... --> */
1038 if (len >= 16
1039 && 0 == memcmp(s+5, "comment", sizeof("comment")-1)
1040 && (s[12] == ' ' || s[12] == '\t'))
1041 return;
1042 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1043 chunkqueue_append_mem(con->write_queue, s, len); /* append stmt as-is */
1044 return;
1047 #if 0
1048 /* dup s and then modify s */
1049 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1050 l[0] = malloc((size_t)(len+1));
1051 memcpy(l[0], s, (size_t)len);
1052 (l[0])[len] = '\0';
1053 #endif
1055 /* modify s in-place to split string into arg tokens */
1056 for (m = 0; m < n; m += 2) {
1057 char *ptr = s+o[m];
1058 switch (*ptr) {
1059 case '"':
1060 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1061 default: ptr[o[m+1]] = '\0'; break;
1063 l[1+(m>>1)] = ptr;
1064 if (m == 4 || m == 8) {
1065 /* XXX: removing '\\' escapes from param value would be
1066 * the right thing to do, but would potentially change
1067 * current behavior, e.g. <!--#exec cmd=... --> */
1071 process_ssi_stmt(srv, con, p, (const char **)l, 1+(n>>1), st);
1073 #if 0
1074 free(l[0]);
1075 #endif
1078 static int mod_ssi_stmt_len(const char *s, const int len) {
1079 /* s must begin "<!--#" */
1080 int n, sq = 0, dq = 0, bs = 0;
1081 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1082 switch (s[n]) {
1083 default:
1084 break;
1085 case '-':
1086 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1087 break;
1088 case '"':
1089 if (!sq && (!dq || !bs)) dq = !dq;
1090 break;
1091 case '\'':
1092 if (!dq && (!sq || !bs)) sq = !sq;
1093 break;
1094 case '\\':
1095 if (sq || dq) bs = !bs;
1096 break;
1099 return 0; /* incomplete directive "<!--#...-->" */
1102 static void mod_ssi_read_fd(server *srv, connection *con, handler_ctx *p, struct stat *st, int fd) {
1103 ssize_t rd;
1104 size_t offset, pretag;
1105 size_t bufsz = 8192;
1106 char *buf = malloc(bufsz); /* allocate to reduce chance of stack exhaustion upon deep recursion */
1107 force_assert(buf);
1109 offset = 0;
1110 pretag = 0;
1111 while (0 < (rd = read(fd, buf+offset, bufsz-offset))) {
1112 char *s;
1113 size_t prelen = 0, len;
1114 offset += (size_t)rd;
1115 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1116 prelen = s - buf;
1117 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1118 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1120 if (prelen - pretag && !p->if_is_false) {
1121 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1124 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1125 if (len) { /* num of chars to be consumed */
1126 mod_ssi_parse_ssi_stmt(srv, con, p, buf+prelen, len, st);
1127 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1128 pretag = prelen + 1;
1129 if (pretag == offset) {
1130 offset = pretag = 0;
1131 break;
1133 } else if (0 == prelen && offset == bufsz) { /*(full buf)*/
1134 /* SSI statement is way too long
1135 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1136 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1137 /* check if buf ends with "-" or "--" which might be part of "-->"
1138 * (buf contains at least 5 chars for "<!--#") */
1139 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1140 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("--"));
1141 } else if (buf[offset-1] == '-') {
1142 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("-"));
1144 offset = pretag = 0;
1145 break;
1146 } else { /* incomplete directive "<!--#...-->" */
1147 memmove(buf, buf+prelen, (offset -= prelen));
1148 pretag = 0;
1149 break;
1151 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1152 if (prelen - pretag && !p->if_is_false) {
1153 chunkqueue_append_mem(con->write_queue, buf+pretag, prelen-pretag);
1155 memcpy(buf, buf+prelen, (offset -= prelen));
1156 pretag = 0;
1157 break;
1159 /* loop to look for next '<' */
1161 if (offset == bufsz) {
1162 if (!p->if_is_false) {
1163 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1165 offset = pretag = 0;
1169 if (0 != rd) {
1170 log_error_write(srv, __FILE__, __LINE__, "SsB", "read(): ", strerror(errno), con->physical.path);
1173 if (offset - pretag) {
1174 /* copy remaining data in buf */
1175 if (!p->if_is_false) {
1176 chunkqueue_append_mem(con->write_queue, buf+pretag, offset-pretag);
1180 free(buf);
1184 static int mod_ssi_process_file(server *srv, connection *con, handler_ctx *p, struct stat *st) {
1185 int fd = fdevent_open_cloexec(con->physical.path->ptr, con->conf.follow_symlink, O_RDONLY, 0);
1186 if (-1 == fd) {
1187 log_error_write(srv, __FILE__, __LINE__, "SsB", "open(): ",
1188 strerror(errno), con->physical.path);
1189 return -1;
1192 if (0 != fstat(fd, st)) {
1193 log_error_write(srv, __FILE__, __LINE__, "SsB", "fstat(): ",
1194 strerror(errno), con->physical.path);
1195 close(fd);
1196 return -1;
1199 mod_ssi_read_fd(srv, con, p, st, fd);
1201 close(fd);
1202 return 0;
1206 static int mod_ssi_handle_request(server *srv, connection *con, handler_ctx *p) {
1207 struct stat st;
1209 /* get a stream to the file */
1211 array_reset_data_strings(p->ssi_vars);
1212 array_reset_data_strings(p->ssi_cgi_env);
1213 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1214 build_ssi_cgi_vars(srv, con, p);
1216 /* Reset the modified time of included files */
1217 include_file_last_mtime = 0;
1219 if (mod_ssi_process_file(srv, con, p, &st)) return -1;
1221 con->file_started = 1;
1222 con->file_finished = 1;
1224 if (buffer_string_is_empty(p->conf.content_type)) {
1225 http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1226 } else {
1227 http_header_response_set(con, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1230 if (p->conf.conditional_requests) {
1231 /* Generate "ETag" & "Last-Modified" headers */
1232 buffer *mtime = NULL;
1234 /* use most recently modified include file for ETag and Last-Modified */
1235 if (st.st_mtime < include_file_last_mtime)
1236 st.st_mtime = include_file_last_mtime;
1238 etag_create(con->physical.etag, &st, con->etag_flags);
1239 etag_mutate(con->physical.etag, con->physical.etag);
1240 http_header_response_set(con, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1242 mtime = strftime_cache_get(srv, st.st_mtime);
1243 http_header_response_set(con, HTTP_HEADER_LAST_MODIFIED, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1245 if (HANDLER_FINISHED == http_response_handle_cachable(srv, con, mtime)) {
1246 /* ok, the client already has our content,
1247 * no need to send it again */
1249 chunkqueue_reset(con->write_queue);
1253 /* Reset the modified time of included files */
1254 include_file_last_mtime = 0;
1256 /* reset physical.path */
1257 buffer_reset(con->physical.path);
1259 return 0;
1262 #define PATCH(x) \
1263 p->conf.x = s->x;
1264 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1265 size_t i, j;
1266 plugin_config *s = p->config_storage[0];
1268 PATCH(ssi_extension);
1269 PATCH(content_type);
1270 PATCH(conditional_requests);
1271 PATCH(ssi_exec);
1272 PATCH(ssi_recursion_max);
1274 /* skip the first, the global context */
1275 for (i = 1; i < srv->config_context->used; i++) {
1276 data_config *dc = (data_config *)srv->config_context->data[i];
1277 s = p->config_storage[i];
1279 /* condition didn't match */
1280 if (!config_check_cond(srv, con, dc)) continue;
1282 /* merge config */
1283 for (j = 0; j < dc->value->used; j++) {
1284 data_unset *du = dc->value->data[j];
1286 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1287 PATCH(ssi_extension);
1288 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1289 PATCH(content_type);
1290 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.conditional-requests"))) {
1291 PATCH(conditional_requests);
1292 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.exec"))) {
1293 PATCH(ssi_exec);
1294 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.recursion-max"))) {
1295 PATCH(ssi_recursion_max);
1300 return 0;
1302 #undef PATCH
1304 URIHANDLER_FUNC(mod_ssi_physical_path) {
1305 plugin_data *p = p_d;
1307 if (con->mode != DIRECT) return HANDLER_GO_ON;
1308 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1310 mod_ssi_patch_connection(srv, con, p);
1312 if (array_match_value_suffix(p->conf.ssi_extension, con->physical.path)) {
1313 con->plugin_ctx[p->id] = handler_ctx_init(p);
1314 con->mode = p->id;
1317 return HANDLER_GO_ON;
1320 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) {
1321 plugin_data *p = p_d;
1322 handler_ctx *hctx = con->plugin_ctx[p->id];
1323 if (NULL == hctx) return HANDLER_GO_ON;
1324 if (con->mode != p->id) return HANDLER_GO_ON; /* not my job */
1326 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1327 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1328 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1331 /* handle ssi-request */
1333 if (mod_ssi_handle_request(srv, con, hctx)) {
1334 /* on error */
1335 con->http_status = 500;
1336 con->mode = DIRECT;
1339 return HANDLER_FINISHED;
1342 static handler_t mod_ssi_connection_reset(server *srv, connection *con, void *p_d) {
1343 plugin_data *p = p_d;
1344 handler_ctx *hctx = con->plugin_ctx[p->id];
1345 if (hctx) {
1346 handler_ctx_free(hctx);
1347 con->plugin_ctx[p->id] = NULL;
1350 UNUSED(srv);
1351 return HANDLER_GO_ON;
1354 /* this function is called at dlopen() time and inits the callbacks */
1356 int mod_ssi_plugin_init(plugin *p);
1357 int mod_ssi_plugin_init(plugin *p) {
1358 p->version = LIGHTTPD_VERSION_ID;
1359 p->name = buffer_init_string("ssi");
1361 p->init = mod_ssi_init;
1362 p->handle_subrequest_start = mod_ssi_physical_path;
1363 p->handle_subrequest = mod_ssi_handle_subrequest;
1364 p->connection_reset = mod_ssi_connection_reset;
1365 p->set_defaults = mod_ssi_set_defaults;
1366 p->cleanup = mod_ssi_free;
1368 p->data = NULL;
1370 return 0;