14 #include "inet_ntop_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
35 # include <sys/wait.h>
38 #ifdef HAVE_SYS_FILIO_H
39 # include <sys/filio.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
) {
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();
62 /* detroy the plugin data */
63 FREE_FUNC(mod_ssi_free
) {
67 if (!p
) return HANDLER_GO_ON
;
69 if (p
->config_storage
) {
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
);
81 free(p
->config_storage
);
84 array_free(p
->ssi_vars
);
85 array_free(p
->ssi_cgi_env
);
86 buffer_free(p
->timefmt
);
87 buffer_free(p
->stat_fn
);
94 /* handle plugin config and check values */
96 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
100 config_values_t cv
[] = {
101 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
102 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
103 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
104 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
105 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
108 if (!p
) return HANDLER_ERROR
;
110 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
112 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
113 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
116 s
= calloc(1, sizeof(plugin_config
));
117 s
->ssi_extension
= array_init();
118 s
->content_type
= buffer_init();
119 s
->conditional_requests
= 0;
122 cv
[0].destination
= s
->ssi_extension
;
123 cv
[1].destination
= s
->content_type
;
124 cv
[2].destination
= &(s
->conditional_requests
);
125 cv
[3].destination
= &(s
->ssi_exec
);
127 p
->config_storage
[i
] = s
;
129 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
130 return HANDLER_ERROR
;
134 return HANDLER_GO_ON
;
138 static int ssi_env_add(void *venv
, const char *key
, size_t klen
, const char *val
, size_t vlen
) {
142 /* array_set_key_value() w/o extra lookup to see if key already exists */
143 if (NULL
== (ds
= (data_string
*)array_get_unused_element(env
, TYPE_STRING
))) {
144 ds
= data_string_init();
146 buffer_copy_string_len(ds
->key
, key
, klen
);
147 buffer_copy_string_len(ds
->value
, val
, vlen
);
149 array_insert_unique(env
, (data_unset
*)ds
);
154 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, plugin_data
*p
) {
155 http_cgi_opts opts
= { 0, 0, NULL
, NULL
};
156 /* temporarily remove Authorization from request headers
157 * so that Authorization does not end up in SSI environment */
158 data_string
*ds_auth
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
159 buffer
*b_auth
= NULL
;
161 b_auth
= ds_auth
->value
;
162 ds_auth
->value
= NULL
;
165 array_reset(p
->ssi_cgi_env
);
167 if (0 != http_cgi_headers(srv
, con
, &opts
, ssi_env_add
, p
->ssi_cgi_env
)) {
168 con
->http_status
= 400;
173 ds_auth
->value
= b_auth
;
179 static int process_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, const char **l
, size_t n
, struct stat
*st
) {
182 * <!--#element attribute=value attribute=value ... -->
190 * encoding -- missing
226 * The current date in Greenwich Mean Time.
228 * The current date in the local time zone.
230 * The filename (excluding directories) of the document requested by the user.
232 * 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.
234 * The last modification date of the document requested by the user.
236 * Contains the owner of the file which included it.
240 size_t i
, ssicmd
= 0;
246 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
247 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
248 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
} type
;
250 { "echo", SSI_ECHO
},
251 { "include", SSI_INCLUDE
},
252 { "flastmod", SSI_FLASTMOD
},
253 { "fsize", SSI_FSIZE
},
254 { "config", SSI_CONFIG
},
255 { "printenv", SSI_PRINTENV
},
258 { "elif", SSI_ELIF
},
259 { "endif", SSI_ENDIF
},
260 { "else", SSI_ELSE
},
261 { "exec", SSI_EXEC
},
266 for (i
= 0; ssicmds
[i
].var
; i
++) {
267 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
268 ssicmd
= ssicmds
[i
].type
;
278 const char *var_val
= NULL
;
286 SSI_ECHO_DOCUMENT_NAME
,
287 SSI_ECHO_DOCUMENT_URI
,
288 SSI_ECHO_LAST_MODIFIED
,
294 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
295 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
296 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
297 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
298 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
299 { "USER_NAME", SSI_ECHO_USER_NAME
},
300 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
301 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
303 { NULL
, SSI_ECHO_UNSET
}
309 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
311 { "url", SSI_ENC_URL },
312 { "none", SSI_ENC_NONE },
313 { "entity", SSI_ENC_ENTITY },
315 { NULL, SSI_ENC_UNSET }
319 for (i
= 2; i
< n
; i
+= 2) {
320 if (0 == strcmp(l
[i
], "var")) {
325 for (j
= 0; echovars
[j
].var
; j
++) {
326 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
327 var
= echovars
[j
].type
;
331 } else if (0 == strcmp(l
[i
], "encoding")) {
335 for (j = 0; encvars[j].var; j++) {
336 if (0 == strcmp(l[i+1], encvars[j].var)) {
337 enc = encvars[j].type;
343 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
344 "ssi: unknown attribute for ",
349 if (p
->if_is_false
) break;
352 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
354 l
[1], "var is missing");
359 case SSI_ECHO_USER_NAME
: {
364 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
365 buffer_copy_int(b
, st
->st_uid
);
367 buffer_copy_string(b
, pw
->pw_name
);
370 buffer_copy_int(b
, st
->st_uid
);
372 chunkqueue_append_buffer(con
->write_queue
, b
);
376 case SSI_ECHO_LAST_MODIFIED
: {
377 time_t t
= st
->st_mtime
;
379 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
380 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
382 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
386 case SSI_ECHO_DATE_LOCAL
: {
387 time_t t
= time(NULL
);
389 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
390 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
392 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
396 case SSI_ECHO_DATE_GMT
: {
397 time_t t
= time(NULL
);
399 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
400 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
402 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
406 case SSI_ECHO_DOCUMENT_NAME
: {
409 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
410 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
412 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
416 case SSI_ECHO_DOCUMENT_URI
: {
417 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
420 case SSI_ECHO_SCRIPT_URI
: {
421 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
422 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
423 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
424 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
425 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
426 if (!buffer_string_is_empty(con
->uri
.query
)) {
427 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
428 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
433 case SSI_ECHO_SCRIPT_URL
: {
434 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
435 if (!buffer_string_is_empty(con
->uri
.query
)) {
436 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
437 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
443 /* check if it is a cgi-var or a ssi-var */
445 if (NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_cgi_env
, var_val
)) ||
446 NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_vars
, var_val
))) {
447 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
449 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
460 const char * file_path
= NULL
, *virt_path
= NULL
;
464 for (i
= 2; i
< n
; i
+= 2) {
465 if (0 == strcmp(l
[i
], "file")) {
467 } else if (0 == strcmp(l
[i
], "virtual")) {
470 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
471 "ssi: unknown attribute for ",
476 if (!file_path
&& !virt_path
) {
477 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
479 l
[1], "file or virtual are missing");
483 if (file_path
&& virt_path
) {
484 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
486 l
[1], "only one of file and virtual is allowed here");
491 if (p
->if_is_false
) break;
494 /* current doc-root */
495 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
496 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
498 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
501 buffer_copy_string(srv
->tmp_buf
, file_path
);
502 buffer_urldecode_path(srv
->tmp_buf
);
503 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
504 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
509 if (virt_path
[0] == '/') {
510 buffer_copy_string(p
->stat_fn
, virt_path
);
512 /* there is always a / */
513 sl
= strrchr(con
->uri
.path
->ptr
, '/');
515 buffer_copy_string_len(p
->stat_fn
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
516 buffer_append_string(p
->stat_fn
, virt_path
);
519 buffer_urldecode_path(p
->stat_fn
);
520 buffer_path_simplify(srv
->tmp_buf
, p
->stat_fn
);
524 /* Destination physical path (similar to code in mod_webdav.c)
525 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
526 * (but neither modifies con->physical.rel_path)
527 * Find matching prefix to support relative paths to current physical path.
528 * Aliasing of paths underneath current con->physical.basedir might not work.
529 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
530 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
531 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
532 * (Related, use mod_auth to protect this basedir, but avoid attempting to
533 * use mod_auth on paths underneath this basedir, as target path is not
534 * validated with mod_auth)
537 /* find matching URI prefix
538 * check if remaining con->physical.rel_path matches suffix
539 * of con->physical.basedir so that we can use it to
540 * remap Destination physical path */
542 const char *sep
, *sep2
;
543 sep
= con
->uri
.path
->ptr
;
544 sep2
= srv
->tmp_buf
->ptr
;
545 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
546 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
548 if (con
->conf
.force_lowercase_filenames
) {
549 buffer_to_lower(srv
->tmp_buf
);
551 remain
= buffer_string_length(con
->uri
.path
) - i
;
552 if (!con
->conf
.force_lowercase_filenames
553 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
554 :(buffer_string_length(con
->physical
.path
) >= remain
555 && 0 == strncasecmp(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
556 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
557 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
559 /* unable to perform physical path remap here;
560 * assume doc_root/rel_path and no remapping */
561 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
562 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
566 if (0 == stat(p
->stat_fn
->ptr
, &stb
)) {
567 time_t t
= stb
.st_mtime
;
574 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
576 off_t s
= stb
.st_size
;
578 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
580 buffer_copy_int(b
, s
);
581 buffer_append_string(b
, abr
[j
]);
583 buffer_copy_int(b
, stb
.st_size
);
585 chunkqueue_append_buffer(con
->write_queue
, b
);
589 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
590 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
592 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
596 chunkqueue_append_file(con
->write_queue
, p
->stat_fn
, 0, stb
.st_size
);
598 /* Keep the newest mtime of included files */
599 if (stb
.st_mtime
> include_file_last_mtime
)
600 include_file_last_mtime
= stb
.st_mtime
;
605 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
606 "ssi: stating failed ",
607 p
->stat_fn
, strerror(errno
));
612 const char *key
= NULL
, *val
= NULL
;
613 for (i
= 2; i
< n
; i
+= 2) {
614 if (0 == strcmp(l
[i
], "var")) {
616 } else if (0 == strcmp(l
[i
], "value")) {
619 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
620 "ssi: unknown attribute for ",
625 if (p
->if_is_false
) break;
630 if (NULL
== (ds
= (data_string
*)array_get_unused_element(p
->ssi_vars
, TYPE_STRING
))) {
631 ds
= data_string_init();
633 buffer_copy_string(ds
->key
, key
);
634 buffer_copy_string(ds
->value
, val
);
636 array_insert_unique(p
->ssi_vars
, (data_unset
*)ds
);
637 } else if (key
|| val
) {
638 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
639 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
641 log_error_write(srv
, __FILE__
, __LINE__
, "s",
642 "ssi: var and value have to be set in <!--#set var=... value=... -->");
647 if (p
->if_is_false
) break;
649 for (i
= 2; i
< n
; i
+= 2) {
650 if (0 == strcmp(l
[i
], "timefmt")) {
651 buffer_copy_string(p
->timefmt
, l
[i
+1]);
652 } else if (0 == strcmp(l
[i
], "sizefmt")) {
653 if (0 == strcmp(l
[i
+1], "abbrev")) {
655 } else if (0 == strcmp(l
[i
+1], "bytes")) {
658 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
659 "ssi: unknown value for attribute '",
665 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
666 "ssi: unknown attribute for ",
672 if (p
->if_is_false
) break;
675 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
676 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
678 buffer_append_string_buffer(b
, ds
->key
);
679 buffer_append_string_len(b
, CONST_STR_LEN("="));
680 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
681 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
683 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
684 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
686 buffer_append_string_buffer(b
, ds
->key
);
687 buffer_append_string_len(b
, CONST_STR_LEN("="));
688 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
689 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
691 chunkqueue_append_buffer(con
->write_queue
, b
);
696 const char *cmd
= NULL
;
698 int from_exec_fds
[2];
700 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
704 for (i
= 2; i
< n
; i
+= 2) {
705 if (0 == strcmp(l
[i
], "cmd")) {
708 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
709 "ssi: unknown attribute for ",
714 if (p
->if_is_false
) break;
716 /* create a return pipe and send output to the html-page
718 * as exec is assumed evil it is implemented synchronously
723 if (pipe(from_exec_fds
)) {
724 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
725 "pipe failed: ", strerror(errno
));
730 switch (pid
= fork()) {
732 /* move stdout to from_rrdtool_fd[1] */
733 close(STDOUT_FILENO
);
734 dup2(from_exec_fds
[1], STDOUT_FILENO
);
735 close(from_exec_fds
[1]);
737 close(from_exec_fds
[0]);
742 execl("/bin/sh", "sh", "-c", cmd
, (char *)NULL
);
744 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawing exec failed:", strerror(errno
), cmd
);
752 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fork failed:", strerror(errno
));
758 int was_interrupted
= 0;
760 close(from_exec_fds
[1]);
762 /* wait for the client to end */
765 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
768 if (-1 == waitpid(pid
, &status
, 0)) {
769 if (errno
== EINTR
) {
773 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
775 } else if (WIFEXITED(status
)) {
777 /* read everything from client and paste it into the output */
781 if (ioctl(from_exec_fds
[0], FIONREAD
, &toread
)) {
782 log_error_write(srv
, __FILE__
, __LINE__
, "s",
783 "unexpected end-of-file (perhaps the ssi-exec process died)");
791 chunkqueue_get_memory(con
->write_queue
, &mem
, &mem_len
, 0, toread
);
792 r
= read(from_exec_fds
[0], mem
, mem_len
);
793 chunkqueue_use_memory(con
->write_queue
, r
> 0 ? r
: 0);
795 if (r
< 0) break; /* read failed */
802 log_error_write(srv
, __FILE__
, __LINE__
, "s", "process exited abnormally");
804 } while (was_interrupted
> 0 && was_interrupted
< 4); /* if waitpid() gets interrupted, retry, but max 4 times */
806 close(from_exec_fds
[0]);
819 const char *expr
= NULL
;
821 for (i
= 2; i
< n
; i
+= 2) {
822 if (0 == strcmp(l
[i
], "expr")) {
825 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
826 "ssi: unknown attribute for ",
832 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
834 l
[1], "expr missing");
838 if ((!p
->if_is_false
) &&
839 ((p
->if_is_false_level
== 0) ||
840 (p
->if_level
< p
->if_is_false_level
))) {
841 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
845 p
->if_is_false_level
= p
->if_level
;
860 if (p
->if_is_false
) {
861 if ((p
->if_level
== p
->if_is_false_level
) &&
862 (p
->if_is_false_endif
== 0)) {
868 p
->if_is_false_level
= p
->if_level
;
874 const char *expr
= NULL
;
875 for (i
= 2; i
< n
; i
+= 2) {
876 if (0 == strcmp(l
[i
], "expr")) {
879 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
880 "ssi: unknown attribute for ",
886 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
888 l
[1], "expr missing");
894 if (p
->if_level
== p
->if_is_false_level
) {
895 if ((p
->if_is_false
) &&
896 (p
->if_is_false_endif
== 0)) {
897 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
901 p
->if_is_false_level
= p
->if_level
;
909 p
->if_is_false_level
= p
->if_level
;
910 p
->if_is_false_endif
= 1;
921 if (p
->if_level
== p
->if_is_false_level
) {
923 p
->if_is_false_endif
= 0;
928 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
929 "ssi: unknown ssi-command:",
938 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
940 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
942 for (n
= 1; n
< len
; ++n
) {
943 if (s
[n
] == c
) return n
+1;
945 if (n
+1 == len
) return 0; /* invalid */
949 return 0; /* invalid */
951 for (n
= 0; n
< len
; ++n
) {
952 if (isspace(s
[n
])) return n
;
954 if (n
+1 == len
) return 0; /* invalid */
962 static int mod_ssi_parse_ssi_stmt_offlen(int o
[10], const char * const s
, const int len
) {
965 * <!--#element attribute=value attribute=value ... -->
968 /* s must begin "<!--#" and must end with "-->" */
971 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
973 if (0 == o
[1]) return -1; /* empty token */
975 if (n
+3 == len
) return 2; /* token only; no params */
976 if (!isspace(s
[n
])) return -1;
977 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
978 if (n
+3 == len
) return 2; /* token only; no params */
981 for (; light_isalpha(s
[n
]); ++n
) ;
983 if (0 == o
[3] || s
[n
++] != '=') return -1;
986 o
[5] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
987 if (0 == o
[5]) return -1; /* empty or invalid token */
990 if (n
+3 == len
) return 6; /* token and one param */
991 if (!isspace(s
[n
])) return -1;
992 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
993 if (n
+3 == len
) return 6; /* token and one param */
996 for (; light_isalpha(s
[n
]); ++n
) ;
998 if (0 == o
[7] || s
[n
++] != '=') return -1;
1001 o
[9] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1002 if (0 == o
[9]) return -1; /* empty or invalid token */
1005 if (n
+3 == len
) return 10; /* token and two params */
1006 if (!isspace(s
[n
])) return -1;
1007 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1008 if (n
+3 == len
) return 10; /* token and two params */
1012 static void mod_ssi_parse_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, char *s
, int len
, struct stat
*st
) {
1015 * <!--#element attribute=value attribute=value ... -->
1020 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1021 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1023 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1024 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1029 /* dup s and then modify s */
1030 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1031 l
[0] = malloc((size_t)(len
+1));
1032 memcpy(l
[0], s
, (size_t)len
);
1036 /* modify s in-place to split string into arg tokens */
1037 for (m
= 0; m
< n
; m
+= 2) {
1041 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1042 default: ptr
[o
[m
+1]] = '\0'; break;
1045 if (m
== 4 || m
== 8) {
1046 /* XXX: removing '\\' escapes from param value would be
1047 * the right thing to do, but would potentially change
1048 * current behavior, e.g. <!--#exec cmd=... --> */
1052 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1059 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1060 /* s must begin "<!--#" */
1061 int n
, sq
= 0, dq
= 0, bs
= 0;
1062 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1067 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1070 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1073 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1076 if (sq
|| dq
) bs
= !bs
;
1080 return 0; /* incomplete directive "<!--#...-->" */
1083 static void mod_ssi_read_fd(server
*srv
, connection
*con
, plugin_data
*p
, int fd
, struct stat
*st
) {
1085 size_t offset
, pretag
;
1090 while (0 < (rd
= read(fd
, buf
+offset
, sizeof(buf
)-offset
))) {
1092 size_t prelen
= 0, len
;
1093 offset
+= (size_t)rd
;
1094 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1096 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1097 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1099 if (prelen
- pretag
&& !p
->if_is_false
) {
1100 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1103 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1104 if (len
) { /* num of chars to be consumed */
1105 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1106 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1107 pretag
= prelen
+ 1;
1108 if (pretag
== offset
) {
1109 offset
= pretag
= 0;
1112 } else if (0 == prelen
&& offset
== sizeof(buf
)) { /*(full buf)*/
1113 /* SSI statement is way too long
1114 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1115 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1116 /* check if buf ends with "-" or "--" which might be part of "-->"
1117 * (buf contains at least 5 chars for "<!--#") */
1118 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1119 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1120 } else if (buf
[offset
-1] == '-') {
1121 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1123 offset
= pretag
= 0;
1125 } else { /* incomplete directive "<!--#...-->" */
1126 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1130 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1131 if (prelen
- pretag
&& !p
->if_is_false
) {
1132 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1134 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1138 /* loop to look for next '<' */
1140 if (offset
== sizeof(buf
)) {
1141 if (!p
->if_is_false
) {
1142 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1144 offset
= pretag
= 0;
1149 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1152 if (offset
- pretag
) {
1153 /* copy remaining data in buf */
1154 if (!p
->if_is_false
) {
1155 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1161 /* don't want to block when open()ing a fifo */
1162 #if defined(O_NONBLOCK)
1163 # define FIFO_NONBLOCK O_NONBLOCK
1165 # define FIFO_NONBLOCK 0
1168 static int mod_ssi_handle_request(server
*srv
, connection
*con
, plugin_data
*p
) {
1172 /* get a stream to the file */
1174 array_reset(p
->ssi_vars
);
1175 array_reset(p
->ssi_cgi_env
);
1176 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1178 build_ssi_cgi_vars(srv
, con
, p
);
1181 /* Reset the modified time of included files */
1182 include_file_last_mtime
= 0;
1184 if (-1 == (fd
= open(con
->physical
.path
->ptr
, O_RDONLY
| FIFO_NONBLOCK
))) {
1185 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
1186 "open: ", con
->physical
.path
);
1190 if (0 != fstat(fd
, &st
)) {
1191 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "fstat failed: ", con
->physical
.path
);
1196 mod_ssi_read_fd(srv
, con
, p
, fd
, &st
);
1199 con
->file_started
= 1;
1200 con
->file_finished
= 1;
1203 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1204 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1206 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1209 if (p
->conf
.conditional_requests
) {
1210 /* Generate "ETag" & "Last-Modified" headers */
1211 buffer
*mtime
= NULL
;
1213 /* use most recently modified include file for ETag and Last-Modified */
1214 if (st
.st_mtime
< include_file_last_mtime
)
1215 st
.st_mtime
= include_file_last_mtime
;
1217 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1218 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1220 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1221 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1223 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1224 /* ok, the client already has our content,
1225 * no need to send it again */
1227 chunkqueue_reset(con
->write_queue
);
1231 /* Reset the modified time of included files */
1232 include_file_last_mtime
= 0;
1234 /* reset physical.path */
1235 buffer_reset(con
->physical
.path
);
1242 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1244 plugin_config
*s
= p
->config_storage
[0];
1246 PATCH(ssi_extension
);
1247 PATCH(content_type
);
1248 PATCH(conditional_requests
);
1251 /* skip the first, the global context */
1252 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1253 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1254 s
= p
->config_storage
[i
];
1256 /* condition didn't match */
1257 if (!config_check_cond(srv
, con
, dc
)) continue;
1260 for (j
= 0; j
< dc
->value
->used
; j
++) {
1261 data_unset
*du
= dc
->value
->data
[j
];
1263 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1264 PATCH(ssi_extension
);
1265 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1266 PATCH(content_type
);
1267 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1268 PATCH(conditional_requests
);
1269 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1279 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1280 plugin_data
*p
= p_d
;
1283 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1285 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1287 mod_ssi_patch_connection(srv
, con
, p
);
1289 for (k
= 0; k
< p
->conf
.ssi_extension
->used
; k
++) {
1290 data_string
*ds
= (data_string
*)p
->conf
.ssi_extension
->data
[k
];
1292 if (buffer_is_empty(ds
->value
)) continue;
1294 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
1295 /* handle ssi-request */
1297 if (mod_ssi_handle_request(srv
, con
, p
)) {
1299 con
->http_status
= 500;
1303 return HANDLER_FINISHED
;
1308 return HANDLER_GO_ON
;
1311 /* this function is called at dlopen() time and inits the callbacks */
1313 int mod_ssi_plugin_init(plugin
*p
);
1314 int mod_ssi_plugin_init(plugin
*p
) {
1315 p
->version
= LIGHTTPD_VERSION_ID
;
1316 p
->name
= buffer_init_string("ssi");
1318 p
->init
= mod_ssi_init
;
1319 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1320 p
->set_defaults
= mod_ssi_set_defaults
;
1321 p
->cleanup
= mod_ssi_free
;