[core] clean up srv before exiting for lighttpd -[vVh]
[lighttpd/svnmirror.git] / src / mod_ssi.c
blob16f1e1b77c8a1c317dbeced4f5916b7267777085
1 #include "first.h"
3 #include "base.h"
4 #include "log.h"
5 #include "buffer.h"
6 #include "stat_cache.h"
8 #include "plugin.h"
9 #include "stream.h"
11 #include "response.h"
13 #include "mod_ssi.h"
15 #include "inet_ntop_cache.h"
17 #include "sys-socket.h"
19 #include <sys/types.h>
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <errno.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"
42 #include "version.h"
44 /* The newest modified time of included files for include statement */
45 static volatile time_t include_file_last_mtime = 0;
47 /* init the plugin data */
48 INIT_FUNC(mod_ssi_init) {
49 plugin_data *p;
51 p = calloc(1, sizeof(*p));
53 p->timefmt = buffer_init();
54 p->stat_fn = buffer_init();
56 p->ssi_vars = array_init();
57 p->ssi_cgi_env = array_init();
59 return p;
62 /* detroy the plugin data */
63 FREE_FUNC(mod_ssi_free) {
64 plugin_data *p = p_d;
65 UNUSED(srv);
67 if (!p) return HANDLER_GO_ON;
69 if (p->config_storage) {
70 size_t i;
71 for (i = 0; i < srv->config_context->used; i++) {
72 plugin_config *s = p->config_storage[i];
74 if (NULL == s) continue;
76 array_free(s->ssi_extension);
77 buffer_free(s->content_type);
79 free(s);
81 free(p->config_storage);
84 array_free(p->ssi_vars);
85 array_free(p->ssi_cgi_env);
86 #ifdef HAVE_PCRE_H
87 pcre_free(p->ssi_regex);
88 #endif
89 buffer_free(p->timefmt);
90 buffer_free(p->stat_fn);
92 free(p);
94 return HANDLER_GO_ON;
97 /* handle plugin config and check values */
99 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
100 plugin_data *p = p_d;
101 size_t i = 0;
102 #ifdef HAVE_PCRE_H
103 const char *errptr;
104 int erroff;
105 #endif
107 config_values_t cv[] = {
108 { "ssi.extension", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
109 { "ssi.content-type", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
110 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
113 if (!p) return HANDLER_ERROR;
115 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
117 for (i = 0; i < srv->config_context->used; i++) {
118 data_config const* config = (data_config const*)srv->config_context->data[i];
119 plugin_config *s;
121 s = calloc(1, sizeof(plugin_config));
122 s->ssi_extension = array_init();
123 s->content_type = buffer_init();
125 cv[0].destination = s->ssi_extension;
126 cv[1].destination = s->content_type;
128 p->config_storage[i] = s;
130 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
131 return HANDLER_ERROR;
135 #ifdef HAVE_PCRE_H
136 /* allow 2 params */
137 if (NULL == (p->ssi_regex = pcre_compile("<!--#([a-z]+)\\s+(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?(?:([a-z]+)=\"(.*?)(?<!\\\\)\"\\s*)?-->", 0, &errptr, &erroff, NULL))) {
138 log_error_write(srv, __FILE__, __LINE__, "sds",
139 "ssi: pcre ",
140 erroff, errptr);
141 return HANDLER_ERROR;
143 #else
144 log_error_write(srv, __FILE__, __LINE__, "s",
145 "mod_ssi: pcre support is missing, please recompile with pcre support or remove mod_ssi from the list of modules");
146 return HANDLER_ERROR;
147 #endif
149 return HANDLER_GO_ON;
152 static int ssi_env_add(array *env, const char *key, const char *val) {
153 data_string *ds;
155 if (NULL == (ds = (data_string *)array_get_unused_element(env, TYPE_STRING))) {
156 ds = data_string_init();
158 buffer_copy_string(ds->key, key);
159 buffer_copy_string(ds->value, val);
161 array_insert_unique(env, (data_unset *)ds);
163 return 0;
168 * the next two functions are take from fcgi.c
172 static int ssi_env_add_request_headers(server *srv, connection *con, plugin_data *p) {
173 size_t i;
175 for (i = 0; i < con->request.headers->used; i++) {
176 data_string *ds;
178 ds = (data_string *)con->request.headers->data[i];
180 if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) {
181 /* don't forward the Authorization: Header */
182 if (0 == strcasecmp(ds->key->ptr, "AUTHORIZATION")) {
183 continue;
186 buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 1);
188 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
192 for (i = 0; i < con->environment->used; i++) {
193 data_string *ds;
195 ds = (data_string *)con->environment->data[i];
197 if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) {
198 buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 0);
200 ssi_env_add(p->ssi_cgi_env, srv->tmp_buf->ptr, ds->value->ptr);
204 return 0;
207 static int build_ssi_cgi_vars(server *srv, connection *con, plugin_data *p) {
208 char buf[LI_ITOSTRING_LENGTH];
210 server_socket *srv_sock = con->srv_socket;
212 #ifdef HAVE_IPV6
213 char b2[INET6_ADDRSTRLEN + 1];
214 #endif
216 #define CONST_STRING(x) \
219 array_reset(p->ssi_cgi_env);
221 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_DESC);
222 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_NAME"),
223 #ifdef HAVE_IPV6
224 inet_ntop(srv_sock->addr.plain.sa_family,
225 srv_sock->addr.plain.sa_family == AF_INET6 ?
226 (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
227 (const void *) &(srv_sock->addr.ipv4.sin_addr),
228 b2, sizeof(b2)-1)
229 #else
230 inet_ntoa(srv_sock->addr.ipv4.sin_addr)
231 #endif
233 ssi_env_add(p->ssi_cgi_env, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
235 li_utostrn(buf, sizeof(buf),
236 #ifdef HAVE_IPV6
237 ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
238 #else
239 ntohs(srv_sock->addr.ipv4.sin_port)
240 #endif
243 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PORT"), buf);
245 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REMOTE_ADDR"),
246 inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
248 if (con->request.content_length > 0) {
249 /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
251 li_itostrn(buf, sizeof(buf), con->request.content_length);
252 ssi_env_add(p->ssi_cgi_env, CONST_STRING("CONTENT_LENGTH"), buf);
256 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
257 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
258 * (6.1.14, 6.1.6, 6.1.7)
261 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_NAME"), con->uri.path->ptr);
262 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), "");
265 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
266 * http://www.php.net/manual/en/reserved.variables.php
267 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
268 * TODO: this code should be checked against cgi.fix_pathinfo php
269 * parameter.
272 if (!buffer_string_is_empty(con->request.pathinfo)) {
273 ssi_env_add(p->ssi_cgi_env, CONST_STRING("PATH_INFO"), con->request.pathinfo->ptr);
276 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SCRIPT_FILENAME"), con->physical.path->ptr);
277 ssi_env_add(p->ssi_cgi_env, CONST_STRING("DOCUMENT_ROOT"), con->physical.doc_root->ptr);
279 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_URI"), con->request.uri->ptr);
281 if (!buffer_string_is_empty(con->uri.scheme)) {
282 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_SCHEME"), con->uri.scheme->ptr);
285 ssi_env_add(p->ssi_cgi_env, CONST_STRING("QUERY_STRING"), buffer_is_empty(con->uri.query) ? "" : con->uri.query->ptr);
286 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con->request.http_method));
287 ssi_env_add(p->ssi_cgi_env, CONST_STRING("REDIRECT_STATUS"), "200");
288 ssi_env_add(p->ssi_cgi_env, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con->request.http_version));
290 ssi_env_add_request_headers(srv, con, p);
292 return 0;
295 static int process_ssi_stmt(server *srv, connection *con, plugin_data *p, const char **l, size_t n, stat_cache_entry *sce) {
296 size_t i, ssicmd = 0;
297 char buf[255];
298 buffer *b = NULL;
300 struct {
301 const char *var;
302 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
303 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
304 SSI_ELSE, SSI_ENDIF, SSI_EXEC } type;
305 } ssicmds[] = {
306 { "echo", SSI_ECHO },
307 { "include", SSI_INCLUDE },
308 { "flastmod", SSI_FLASTMOD },
309 { "fsize", SSI_FSIZE },
310 { "config", SSI_CONFIG },
311 { "printenv", SSI_PRINTENV },
312 { "set", SSI_SET },
313 { "if", SSI_IF },
314 { "elif", SSI_ELIF },
315 { "endif", SSI_ENDIF },
316 { "else", SSI_ELSE },
317 { "exec", SSI_EXEC },
319 { NULL, SSI_UNSET }
322 for (i = 0; ssicmds[i].var; i++) {
323 if (0 == strcmp(l[1], ssicmds[i].var)) {
324 ssicmd = ssicmds[i].type;
325 break;
329 switch(ssicmd) {
330 case SSI_ECHO: {
331 /* echo */
332 int var = 0;
333 /* int enc = 0; */
334 const char *var_val = NULL;
336 struct {
337 const char *var;
338 enum {
339 SSI_ECHO_UNSET,
340 SSI_ECHO_DATE_GMT,
341 SSI_ECHO_DATE_LOCAL,
342 SSI_ECHO_DOCUMENT_NAME,
343 SSI_ECHO_DOCUMENT_URI,
344 SSI_ECHO_LAST_MODIFIED,
345 SSI_ECHO_USER_NAME,
346 SSI_ECHO_SCRIPT_URI,
347 SSI_ECHO_SCRIPT_URL,
348 } type;
349 } echovars[] = {
350 { "DATE_GMT", SSI_ECHO_DATE_GMT },
351 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
352 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
353 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
354 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
355 { "USER_NAME", SSI_ECHO_USER_NAME },
356 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
357 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
359 { NULL, SSI_ECHO_UNSET }
363 struct {
364 const char *var;
365 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
366 } encvars[] = {
367 { "url", SSI_ENC_URL },
368 { "none", SSI_ENC_NONE },
369 { "entity", SSI_ENC_ENTITY },
371 { NULL, SSI_ENC_UNSET }
375 for (i = 2; i < n; i += 2) {
376 if (0 == strcmp(l[i], "var")) {
377 int j;
379 var_val = l[i+1];
381 for (j = 0; echovars[j].var; j++) {
382 if (0 == strcmp(l[i+1], echovars[j].var)) {
383 var = echovars[j].type;
384 break;
387 } else if (0 == strcmp(l[i], "encoding")) {
389 int j;
391 for (j = 0; encvars[j].var; j++) {
392 if (0 == strcmp(l[i+1], encvars[j].var)) {
393 enc = encvars[j].type;
394 break;
398 } else {
399 log_error_write(srv, __FILE__, __LINE__, "sss",
400 "ssi: unknow attribute for ",
401 l[1], l[i]);
405 if (p->if_is_false) break;
407 if (!var_val) {
408 log_error_write(srv, __FILE__, __LINE__, "sss",
409 "ssi: ",
410 l[1], "var is missing");
411 break;
414 switch(var) {
415 case SSI_ECHO_USER_NAME: {
416 struct passwd *pw;
418 b = buffer_init();
419 #ifdef HAVE_PWD_H
420 if (NULL == (pw = getpwuid(sce->st.st_uid))) {
421 buffer_copy_int(b, sce->st.st_uid);
422 } else {
423 buffer_copy_string(b, pw->pw_name);
425 #else
426 buffer_copy_int(b, sce->st.st_uid);
427 #endif
428 chunkqueue_append_buffer(con->write_queue, b);
429 buffer_free(b);
430 break;
432 case SSI_ECHO_LAST_MODIFIED: {
433 time_t t = sce->st.st_mtime;
435 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
436 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
437 } else {
438 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
440 break;
442 case SSI_ECHO_DATE_LOCAL: {
443 time_t t = time(NULL);
445 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
446 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
447 } else {
448 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
450 break;
452 case SSI_ECHO_DATE_GMT: {
453 time_t t = time(NULL);
455 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, gmtime(&t))) {
456 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
457 } else {
458 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
460 break;
462 case SSI_ECHO_DOCUMENT_NAME: {
463 char *sl;
465 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
466 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->physical.path));
467 } else {
468 chunkqueue_append_mem(con->write_queue, sl + 1, strlen(sl + 1));
470 break;
472 case SSI_ECHO_DOCUMENT_URI: {
473 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.path));
474 break;
476 case SSI_ECHO_SCRIPT_URI: {
477 if (!buffer_string_is_empty(con->uri.scheme) && !buffer_string_is_empty(con->uri.authority)) {
478 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.scheme));
479 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("://"));
480 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.authority));
481 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
482 if (!buffer_string_is_empty(con->uri.query)) {
483 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
484 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
487 break;
489 case SSI_ECHO_SCRIPT_URL: {
490 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->request.uri));
491 if (!buffer_string_is_empty(con->uri.query)) {
492 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("?"));
493 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(con->uri.query));
495 break;
497 default: {
498 data_string *ds;
499 /* check if it is a cgi-var or a ssi-var */
501 if (NULL != (ds = (data_string *)array_get_element(p->ssi_cgi_env, var_val)) ||
502 NULL != (ds = (data_string *)array_get_element(p->ssi_vars, var_val))) {
503 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(ds->value));
504 } else {
505 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
508 break;
511 break;
513 case SSI_INCLUDE:
514 case SSI_FLASTMOD:
515 case SSI_FSIZE: {
516 const char * file_path = NULL, *virt_path = NULL;
517 struct stat st;
518 char *sl;
520 for (i = 2; i < n; i += 2) {
521 if (0 == strcmp(l[i], "file")) {
522 file_path = l[i+1];
523 } else if (0 == strcmp(l[i], "virtual")) {
524 virt_path = l[i+1];
525 } else {
526 log_error_write(srv, __FILE__, __LINE__, "sss",
527 "ssi: unknow attribute for ",
528 l[1], l[i]);
532 if (!file_path && !virt_path) {
533 log_error_write(srv, __FILE__, __LINE__, "sss",
534 "ssi: ",
535 l[1], "file or virtual are missing");
536 break;
539 if (file_path && virt_path) {
540 log_error_write(srv, __FILE__, __LINE__, "sss",
541 "ssi: ",
542 l[1], "only one of file and virtual is allowed here");
543 break;
547 if (p->if_is_false) break;
549 if (file_path) {
550 /* current doc-root */
551 if (NULL == (sl = strrchr(con->physical.path->ptr, '/'))) {
552 buffer_copy_string_len(p->stat_fn, CONST_STR_LEN("/"));
553 } else {
554 buffer_copy_string_len(p->stat_fn, con->physical.path->ptr, sl - con->physical.path->ptr + 1);
557 buffer_copy_string(srv->tmp_buf, file_path);
558 buffer_urldecode_path(srv->tmp_buf);
559 buffer_path_simplify(srv->tmp_buf, srv->tmp_buf);
560 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
561 } else {
562 /* virtual */
564 if (virt_path[0] == '/') {
565 buffer_copy_string(p->stat_fn, virt_path);
566 } else {
567 /* there is always a / */
568 sl = strrchr(con->uri.path->ptr, '/');
570 buffer_copy_string_len(p->stat_fn, con->uri.path->ptr, sl - con->uri.path->ptr + 1);
571 buffer_append_string(p->stat_fn, virt_path);
574 buffer_urldecode_path(p->stat_fn);
575 buffer_path_simplify(srv->tmp_buf, p->stat_fn);
577 /* we have an uri */
579 buffer_copy_buffer(p->stat_fn, con->physical.doc_root);
580 buffer_append_string_buffer(p->stat_fn, srv->tmp_buf);
583 if (0 == stat(p->stat_fn->ptr, &st)) {
584 time_t t = st.st_mtime;
586 switch (ssicmd) {
587 case SSI_FSIZE:
588 b = buffer_init();
589 if (p->sizefmt) {
590 int j = 0;
591 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
593 off_t s = st.st_size;
595 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
597 buffer_copy_int(b, s);
598 buffer_append_string(b, abr[j]);
599 } else {
600 buffer_copy_int(b, st.st_size);
602 chunkqueue_append_buffer(con->write_queue, b);
603 buffer_free(b);
604 break;
605 case SSI_FLASTMOD:
606 if (0 == strftime(buf, sizeof(buf), p->timefmt->ptr, localtime(&t))) {
607 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("(none)"));
608 } else {
609 chunkqueue_append_mem(con->write_queue, buf, strlen(buf));
611 break;
612 case SSI_INCLUDE:
613 chunkqueue_append_file(con->write_queue, p->stat_fn, 0, st.st_size);
615 /* Keep the newest mtime of included files */
616 if (st.st_mtime > include_file_last_mtime)
617 include_file_last_mtime = st.st_mtime;
619 break;
621 } else {
622 log_error_write(srv, __FILE__, __LINE__, "sbs",
623 "ssi: stating failed ",
624 p->stat_fn, strerror(errno));
626 break;
628 case SSI_SET: {
629 const char *key = NULL, *val = NULL;
630 for (i = 2; i < n; i += 2) {
631 if (0 == strcmp(l[i], "var")) {
632 key = l[i+1];
633 } else if (0 == strcmp(l[i], "value")) {
634 val = l[i+1];
635 } else {
636 log_error_write(srv, __FILE__, __LINE__, "sss",
637 "ssi: unknow attribute for ",
638 l[1], l[i]);
642 if (p->if_is_false) break;
644 if (key && val) {
645 data_string *ds;
647 if (NULL == (ds = (data_string *)array_get_unused_element(p->ssi_vars, TYPE_STRING))) {
648 ds = data_string_init();
650 buffer_copy_string(ds->key, key);
651 buffer_copy_string(ds->value, val);
653 array_insert_unique(p->ssi_vars, (data_unset *)ds);
654 } else {
655 log_error_write(srv, __FILE__, __LINE__, "sss",
656 "ssi: var and value have to be set in",
657 l[0], l[1]);
659 break;
661 case SSI_CONFIG:
662 if (p->if_is_false) break;
664 for (i = 2; i < n; i += 2) {
665 if (0 == strcmp(l[i], "timefmt")) {
666 buffer_copy_string(p->timefmt, l[i+1]);
667 } else if (0 == strcmp(l[i], "sizefmt")) {
668 if (0 == strcmp(l[i+1], "abbrev")) {
669 p->sizefmt = 1;
670 } else if (0 == strcmp(l[i+1], "abbrev")) {
671 p->sizefmt = 0;
672 } else {
673 log_error_write(srv, __FILE__, __LINE__, "sssss",
674 "ssi: unknow value for attribute '",
675 l[i],
676 "' for ",
677 l[1], l[i+1]);
679 } else {
680 log_error_write(srv, __FILE__, __LINE__, "sss",
681 "ssi: unknow attribute for ",
682 l[1], l[i]);
685 break;
686 case SSI_PRINTENV:
687 if (p->if_is_false) break;
689 b = buffer_init();
690 for (i = 0; i < p->ssi_vars->used; i++) {
691 data_string *ds = (data_string *)p->ssi_vars->data[p->ssi_vars->sorted[i]];
693 buffer_append_string_buffer(b, ds->key);
694 buffer_append_string_len(b, CONST_STR_LEN("="));
695 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
696 buffer_append_string_len(b, CONST_STR_LEN("\n"));
698 for (i = 0; i < p->ssi_cgi_env->used; i++) {
699 data_string *ds = (data_string *)p->ssi_cgi_env->data[p->ssi_cgi_env->sorted[i]];
701 buffer_append_string_buffer(b, ds->key);
702 buffer_append_string_len(b, CONST_STR_LEN("="));
703 buffer_append_string_encoded(b, CONST_BUF_LEN(ds->value), ENCODING_MINIMAL_XML);
704 buffer_append_string_len(b, CONST_STR_LEN("\n"));
706 chunkqueue_append_buffer(con->write_queue, b);
707 buffer_free(b);
709 break;
710 case SSI_EXEC: {
711 const char *cmd = NULL;
712 pid_t pid;
713 int from_exec_fds[2];
715 for (i = 2; i < n; i += 2) {
716 if (0 == strcmp(l[i], "cmd")) {
717 cmd = l[i+1];
718 } else {
719 log_error_write(srv, __FILE__, __LINE__, "sss",
720 "ssi: unknow attribute for ",
721 l[1], l[i]);
725 if (p->if_is_false) break;
727 /* create a return pipe and send output to the html-page
729 * as exec is assumed evil it is implemented synchronously
732 if (!cmd) break;
733 #ifdef HAVE_FORK
734 if (pipe(from_exec_fds)) {
735 log_error_write(srv, __FILE__, __LINE__, "ss",
736 "pipe failed: ", strerror(errno));
737 return -1;
740 /* fork, execve */
741 switch (pid = fork()) {
742 case 0: {
743 /* move stdout to from_rrdtool_fd[1] */
744 close(STDOUT_FILENO);
745 dup2(from_exec_fds[1], STDOUT_FILENO);
746 close(from_exec_fds[1]);
747 /* not needed */
748 close(from_exec_fds[0]);
750 /* close stdin */
751 close(STDIN_FILENO);
753 execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
755 log_error_write(srv, __FILE__, __LINE__, "sss", "spawing exec failed:", strerror(errno), cmd);
757 /* */
758 SEGFAULT();
759 break;
761 case -1:
762 /* error */
763 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
764 break;
765 default: {
766 /* father */
767 int status;
768 ssize_t r;
769 int was_interrupted = 0;
771 close(from_exec_fds[1]);
773 /* wait for the client to end */
776 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
778 do {
779 if (-1 == waitpid(pid, &status, 0)) {
780 if (errno == EINTR) {
781 was_interrupted++;
782 } else {
783 was_interrupted = 0;
784 log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed:", strerror(errno));
786 } else if (WIFEXITED(status)) {
787 int toread;
788 /* read everything from client and paste it into the output */
789 was_interrupted = 0;
791 while(1) {
792 if (ioctl(from_exec_fds[0], FIONREAD, &toread)) {
793 log_error_write(srv, __FILE__, __LINE__, "s",
794 "unexpected end-of-file (perhaps the ssi-exec process died)");
795 return -1;
798 if (toread > 0) {
799 char *mem;
800 size_t mem_len;
802 chunkqueue_get_memory(con->write_queue, &mem, &mem_len, 0, toread);
803 r = read(from_exec_fds[0], mem, mem_len);
804 chunkqueue_use_memory(con->write_queue, r > 0 ? r : 0);
806 if (r < 0) break; /* read failed */
807 } else {
808 break;
811 } else {
812 was_interrupted = 0;
813 log_error_write(srv, __FILE__, __LINE__, "s", "process exited abnormally");
815 } while (was_interrupted > 0 && was_interrupted < 4); /* if waitpid() gets interrupted, retry, but max 4 times */
817 close(from_exec_fds[0]);
819 break;
822 #else
824 return -1;
825 #endif
827 break;
829 case SSI_IF: {
830 const char *expr = NULL;
832 for (i = 2; i < n; i += 2) {
833 if (0 == strcmp(l[i], "expr")) {
834 expr = l[i+1];
835 } else {
836 log_error_write(srv, __FILE__, __LINE__, "sss",
837 "ssi: unknow attribute for ",
838 l[1], l[i]);
842 if (!expr) {
843 log_error_write(srv, __FILE__, __LINE__, "sss",
844 "ssi: ",
845 l[1], "expr missing");
846 break;
849 if ((!p->if_is_false) &&
850 ((p->if_is_false_level == 0) ||
851 (p->if_level < p->if_is_false_level))) {
852 switch (ssi_eval_expr(srv, con, p, expr)) {
853 case -1:
854 case 0:
855 p->if_is_false = 1;
856 p->if_is_false_level = p->if_level;
857 break;
858 case 1:
859 p->if_is_false = 0;
860 break;
864 p->if_level++;
866 break;
868 case SSI_ELSE:
869 p->if_level--;
871 if (p->if_is_false) {
872 if ((p->if_level == p->if_is_false_level) &&
873 (p->if_is_false_endif == 0)) {
874 p->if_is_false = 0;
876 } else {
877 p->if_is_false = 1;
879 p->if_is_false_level = p->if_level;
881 p->if_level++;
883 break;
884 case SSI_ELIF: {
885 const char *expr = NULL;
886 for (i = 2; i < n; i += 2) {
887 if (0 == strcmp(l[i], "expr")) {
888 expr = l[i+1];
889 } else {
890 log_error_write(srv, __FILE__, __LINE__, "sss",
891 "ssi: unknow attribute for ",
892 l[1], l[i]);
896 if (!expr) {
897 log_error_write(srv, __FILE__, __LINE__, "sss",
898 "ssi: ",
899 l[1], "expr missing");
900 break;
903 p->if_level--;
905 if (p->if_level == p->if_is_false_level) {
906 if ((p->if_is_false) &&
907 (p->if_is_false_endif == 0)) {
908 switch (ssi_eval_expr(srv, con, p, expr)) {
909 case -1:
910 case 0:
911 p->if_is_false = 1;
912 p->if_is_false_level = p->if_level;
913 break;
914 case 1:
915 p->if_is_false = 0;
916 break;
918 } else {
919 p->if_is_false = 1;
920 p->if_is_false_level = p->if_level;
921 p->if_is_false_endif = 1;
925 p->if_level++;
927 break;
929 case SSI_ENDIF:
930 p->if_level--;
932 if (p->if_level == p->if_is_false_level) {
933 p->if_is_false = 0;
934 p->if_is_false_endif = 0;
937 break;
938 default:
939 log_error_write(srv, __FILE__, __LINE__, "ss",
940 "ssi: unknow ssi-command:",
941 l[1]);
942 break;
945 return 0;
949 static int mod_ssi_handle_request(server *srv, connection *con, plugin_data *p) {
950 stream s;
951 #ifdef HAVE_PCRE_H
952 int i, n;
954 #define N 10
955 int ovec[N * 3];
956 #endif
958 stat_cache_entry *sce = NULL;
961 /* get a stream to the file */
963 array_reset(p->ssi_vars);
964 array_reset(p->ssi_cgi_env);
965 buffer_copy_string_len(p->timefmt, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
966 p->sizefmt = 0;
967 build_ssi_cgi_vars(srv, con, p);
968 p->if_is_false = 0;
970 /* Reset the modified time of included files */
971 include_file_last_mtime = 0;
973 if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) {
974 log_error_write(srv, __FILE__, __LINE__, "SB", "stat_cache_get_entry failed: ", con->physical.path);
975 return -1;
978 if (-1 == stream_open(&s, con->physical.path)) {
979 log_error_write(srv, __FILE__, __LINE__, "sb",
980 "stream-open: ", con->physical.path);
981 return -1;
986 * <!--#element attribute=value attribute=value ... -->
988 * config DONE
989 * errmsg -- missing
990 * sizefmt DONE
991 * timefmt DONE
992 * echo DONE
993 * var DONE
994 * encoding -- missing
995 * exec DONE
996 * cgi -- never
997 * cmd DONE
998 * fsize DONE
999 * file DONE
1000 * virtual DONE
1001 * flastmod DONE
1002 * file DONE
1003 * virtual DONE
1004 * include DONE
1005 * file DONE
1006 * virtual DONE
1007 * printenv DONE
1008 * set DONE
1009 * var DONE
1010 * value DONE
1012 * if DONE
1013 * elif DONE
1014 * else DONE
1015 * endif DONE
1018 * expressions
1019 * AND, OR DONE
1020 * comp DONE
1021 * ${...} -- missing
1022 * $... DONE
1023 * '...' DONE
1024 * ( ... ) DONE
1028 * ** all DONE **
1029 * DATE_GMT
1030 * The current date in Greenwich Mean Time.
1031 * DATE_LOCAL
1032 * The current date in the local time zone.
1033 * DOCUMENT_NAME
1034 * The filename (excluding directories) of the document requested by the user.
1035 * DOCUMENT_URI
1036 * 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.
1037 * LAST_MODIFIED
1038 * The last modification date of the document requested by the user.
1039 * USER_NAME
1040 * Contains the owner of the file which included it.
1043 #ifdef HAVE_PCRE_H
1044 for (i = 0; (n = pcre_exec(p->ssi_regex, NULL, s.start, s.size, i, 0, ovec, N * 3)) > 0; i = ovec[1]) {
1045 const char **l;
1046 /* take everything from last offset to current match pos */
1048 if (!p->if_is_false) chunkqueue_append_file(con->write_queue, con->physical.path, i, ovec[0] - i);
1050 pcre_get_substring_list(s.start, ovec, n, &l);
1051 process_ssi_stmt(srv, con, p, l, n, sce);
1052 pcre_free_substring_list(l);
1055 switch(n) {
1056 case PCRE_ERROR_NOMATCH:
1057 /* copy everything/the rest */
1058 chunkqueue_append_file(con->write_queue, con->physical.path, i, s.size - i);
1060 break;
1061 default:
1062 log_error_write(srv, __FILE__, __LINE__, "sd",
1063 "execution error while matching: ", n);
1064 break;
1066 #endif
1069 stream_close(&s);
1071 con->file_started = 1;
1072 con->file_finished = 1;
1073 con->mode = p->id;
1075 if (buffer_string_is_empty(p->conf.content_type)) {
1076 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1077 } else {
1078 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p->conf.content_type));
1082 /* Generate "ETag" & "Last-Modified" headers */
1083 time_t lm_time = 0;
1084 buffer *mtime = NULL;
1086 etag_mutate(con->physical.etag, sce->etag);
1087 response_header_overwrite(srv, con, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con->physical.etag));
1089 if (sce->st.st_mtime > include_file_last_mtime)
1090 lm_time = sce->st.st_mtime;
1091 else
1092 lm_time = include_file_last_mtime;
1094 mtime = strftime_cache_get(srv, lm_time);
1095 response_header_overwrite(srv, con, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime));
1098 /* Reset the modified time of included files */
1099 include_file_last_mtime = 0;
1101 /* reset physical.path */
1102 buffer_reset(con->physical.path);
1104 return 0;
1107 #define PATCH(x) \
1108 p->conf.x = s->x;
1109 static int mod_ssi_patch_connection(server *srv, connection *con, plugin_data *p) {
1110 size_t i, j;
1111 plugin_config *s = p->config_storage[0];
1113 PATCH(ssi_extension);
1114 PATCH(content_type);
1116 /* skip the first, the global context */
1117 for (i = 1; i < srv->config_context->used; i++) {
1118 data_config *dc = (data_config *)srv->config_context->data[i];
1119 s = p->config_storage[i];
1121 /* condition didn't match */
1122 if (!config_check_cond(srv, con, dc)) continue;
1124 /* merge config */
1125 for (j = 0; j < dc->value->used; j++) {
1126 data_unset *du = dc->value->data[j];
1128 if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.extension"))) {
1129 PATCH(ssi_extension);
1130 } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("ssi.content-type"))) {
1131 PATCH(content_type);
1136 return 0;
1138 #undef PATCH
1140 URIHANDLER_FUNC(mod_ssi_physical_path) {
1141 plugin_data *p = p_d;
1142 size_t k;
1144 if (con->mode != DIRECT) return HANDLER_GO_ON;
1146 if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
1148 mod_ssi_patch_connection(srv, con, p);
1150 for (k = 0; k < p->conf.ssi_extension->used; k++) {
1151 data_string *ds = (data_string *)p->conf.ssi_extension->data[k];
1153 if (buffer_is_empty(ds->value)) continue;
1155 if (buffer_is_equal_right_len(con->physical.path, ds->value, buffer_string_length(ds->value))) {
1156 /* handle ssi-request */
1158 if (mod_ssi_handle_request(srv, con, p)) {
1159 /* on error */
1160 con->http_status = 500;
1161 con->mode = DIRECT;
1164 return HANDLER_FINISHED;
1168 /* not found */
1169 return HANDLER_GO_ON;
1172 /* this function is called at dlopen() time and inits the callbacks */
1174 int mod_ssi_plugin_init(plugin *p);
1175 int mod_ssi_plugin_init(plugin *p) {
1176 p->version = LIGHTTPD_VERSION_ID;
1177 p->name = buffer_init_string("ssi");
1179 p->init = mod_ssi_init;
1180 p->handle_subrequest_start = mod_ssi_physical_path;
1181 p->set_defaults = mod_ssi_set_defaults;
1182 p->cleanup = mod_ssi_free;
1184 p->data = NULL;
1186 return 0;