7 #include "http_header.h"
8 #include "stat_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
20 #include "sys-strings.h"
35 #ifdef HAVE_SYS_FILIO_H
36 # include <sys/filio.h>
41 static handler_ctx
* handler_ctx_init(plugin_data
*p
) {
42 handler_ctx
*hctx
= calloc(1, sizeof(*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
));
52 static void handler_ctx_free(handler_ctx
*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
) {
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();
74 /* detroy the plugin data */
75 FREE_FUNC(mod_ssi_free
) {
79 if (!p
) return HANDLER_GO_ON
;
81 if (p
->config_storage
) {
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
);
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
);
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
;
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
];
129 s
= calloc(1, sizeof(plugin_config
));
130 s
->ssi_extension
= array_init();
131 s
->content_type
= buffer_init();
132 s
->conditional_requests
= 0;
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
);
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"));
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;
183 memcpy(vb_auth
, &b_auth
, sizeof(buffer
));
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 ... -->
202 * encoding -- missing
238 * The current date in Greenwich Mean Time.
240 * The current date in the local time zone.
242 * The filename (excluding directories) of the document requested by the user.
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.
246 * The last modification date of the document requested by the user.
248 * Contains the owner of the file which included it.
252 size_t i
, ssicmd
= 0;
256 static const struct {
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
;
262 { "echo", SSI_ECHO
},
263 { "include", SSI_INCLUDE
},
264 { "flastmod", SSI_FLASTMOD
},
265 { "fsize", SSI_FSIZE
},
266 { "config", SSI_CONFIG
},
267 { "printenv", SSI_PRINTENV
},
270 { "elif", SSI_ELIF
},
271 { "endif", SSI_ENDIF
},
272 { "else", SSI_ELSE
},
273 { "exec", SSI_EXEC
},
274 { "comment", SSI_COMMENT
},
279 for (i
= 0; ssicmds
[i
].var
; i
++) {
280 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
281 ssicmd
= ssicmds
[i
].type
;
291 const char *var_val
= NULL
;
293 static const struct {
299 SSI_ECHO_DOCUMENT_NAME
,
300 SSI_ECHO_DOCUMENT_URI
,
301 SSI_ECHO_LAST_MODIFIED
,
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 {
322 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
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")) {
338 for (j
= 0; echovars
[j
].var
; j
++) {
339 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
340 var
= echovars
[j
].type
;
344 } else if (0 == strcmp(l
[i
], "encoding")) {
348 for (j = 0; encvars[j].var; j++) {
349 if (0 == strcmp(l[i+1], encvars[j].var)) {
350 enc = encvars[j].type;
356 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
357 "ssi: unknown attribute for ",
362 if (p
->if_is_false
) break;
365 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
367 l
[1], "var is missing");
372 case SSI_ECHO_USER_NAME
: {
377 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
378 buffer_copy_int(b
, st
->st_uid
);
380 buffer_copy_string(b
, pw
->pw_name
);
383 buffer_copy_int(b
, st
->st_uid
);
385 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(b
));
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)"));
394 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
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)"));
404 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
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)"));
414 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
418 case SSI_ECHO_DOCUMENT_NAME
: {
421 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
422 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
424 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
428 case SSI_ECHO_DOCUMENT_URI
: {
429 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
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
));
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
));
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
));
461 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
472 const char * file_path
= NULL
, *virt_path
= NULL
;
476 for (i
= 2; i
< n
; i
+= 2) {
477 if (0 == strcmp(l
[i
], "file")) {
479 } else if (0 == strcmp(l
[i
], "virtual")) {
482 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
483 "ssi: unknown attribute for ",
488 if (!file_path
&& !virt_path
) {
489 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
491 l
[1], "file or virtual are missing");
495 if (file_path
&& virt_path
) {
496 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
498 l
[1], "only one of file and virtual is allowed here");
503 if (p
->if_is_false
) break;
506 /* current doc-root */
507 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
508 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
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
);
520 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
521 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
526 if (virt_path
[0] == '/') {
527 buffer_copy_string(srv
->tmp_buf
, virt_path
);
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
);
542 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
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
);
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
)) {
593 int fd
= stat_cache_open_rdonly_fstat(p
->stat_fn
, &stb
, con
->conf
.follow_symlink
);
595 time_t t
= stb
.st_mtime
;
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
]);
611 buffer_copy_int(b
, stb
.st_size
);
613 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(b
));
616 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
617 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
619 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
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
);
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)"));
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)"));
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
);
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
;
680 if (fd
> 0) close(fd
);
682 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
683 "ssi: stating failed ",
684 p
->stat_fn
, strerror(errno
));
689 const char *key
= NULL
, *val
= NULL
;
690 for (i
= 2; i
< n
; i
+= 2) {
691 if (0 == strcmp(l
[i
], "var")) {
693 } else if (0 == strcmp(l
[i
], "value")) {
696 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
697 "ssi: unknown attribute for ",
702 if (p
->if_is_false
) break;
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], "-->");
710 log_error_write(srv
, __FILE__
, __LINE__
, "s",
711 "ssi: var and value have to be set in <!--#set var=... value=... -->");
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")) {
724 } else if (0 == strcmp(l
[i
+1], "bytes")) {
727 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
728 "ssi: unknown value for attribute '",
734 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
735 "ssi: unknown attribute for ",
741 if (p
->if_is_false
) break;
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
));
764 const char *cmd
= NULL
;
769 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
773 for (i
= 2; i
< n
; i
+= 2) {
774 if (0 == strcmp(l
[i
], "cmd")) {
777 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
778 "ssi: unknown attribute for ",
783 if (p
->if_is_false
) break;
786 * as exec is assumed evil it is implemented synchronously
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
;
800 /*(expects STDIN_FILENO open to /dev/null)*/
801 pid
= fdevent_fork_execve(args
[0], args
, NULL
, -1, c
->file
.fd
, -1, -1);
803 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawning exec failed:", strerror(errno
), cmd
);
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
));
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
;
831 const char *expr
= NULL
;
833 for (i
= 2; i
< n
; i
+= 2) {
834 if (0 == strcmp(l
[i
], "expr")) {
837 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
838 "ssi: unknown attribute for ",
844 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
846 l
[1], "expr missing");
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
)) {
857 p
->if_is_false_level
= 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)) {
880 p
->if_is_false_level
= p
->if_level
;
886 const char *expr
= NULL
;
887 for (i
= 2; i
< n
; i
+= 2) {
888 if (0 == strcmp(l
[i
], "expr")) {
891 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
892 "ssi: unknown attribute for ",
898 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
900 l
[1], "expr missing");
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
)) {
913 p
->if_is_false_level
= p
->if_level
;
921 p
->if_is_false_level
= p
->if_level
;
922 p
->if_is_false_endif
= 1;
933 if (p
->if_level
== p
->if_is_false_level
) {
935 p
->if_is_false_endif
= 0;
942 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
943 "ssi: unknown ssi-command:",
952 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
954 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
956 for (n
= 1; n
< len
; ++n
) {
957 if (s
[n
] == c
) return n
+1;
959 if (n
+1 == len
) return 0; /* invalid */
963 return 0; /* invalid */
965 for (n
= 0; n
< len
; ++n
) {
966 if (isspace(s
[n
])) return n
;
968 if (n
+1 == len
) return 0; /* invalid */
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 "-->" */
985 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
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 */
995 for (; light_isalpha(s
[n
]); ++n
) ;
997 if (0 == o
[3] || s
[n
++] != '=') return -1;
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 */
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 */
1010 for (; light_isalpha(s
[n
]); ++n
) ;
1012 if (0 == o
[7] || s
[n
++] != '=') return -1;
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 */
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 */
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 ... -->
1034 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1035 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1037 /* ignore <!--#comment ... --> */
1039 && 0 == memcmp(s
+5, "comment", sizeof("comment")-1)
1040 && (s
[12] == ' ' || s
[12] == '\t'))
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 */
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
);
1055 /* modify s in-place to split string into arg tokens */
1056 for (m
= 0; m
< n
; m
+= 2) {
1060 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1061 default: ptr
[o
[m
+1]] = '\0'; break;
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
);
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 "<!--#")*/
1086 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1089 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1092 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1095 if (sq
|| dq
) bs
= !bs
;
1099 return 0; /* incomplete directive "<!--#...-->" */
1102 static void mod_ssi_read_fd(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
, int fd
) {
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 */
1111 while (0 < (rd
= read(fd
, buf
+offset
, bufsz
-offset
))) {
1113 size_t prelen
= 0, len
;
1114 offset
+= (size_t)rd
;
1115 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
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;
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;
1146 } else { /* incomplete directive "<!--#...-->" */
1147 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
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
));
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;
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
);
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);
1187 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "open(): ",
1188 strerror(errno
), con
->physical
.path
);
1192 if (0 != fstat(fd
, st
)) {
1193 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "fstat(): ",
1194 strerror(errno
), con
->physical
.path
);
1199 mod_ssi_read_fd(srv
, con
, p
, st
, fd
);
1206 static int mod_ssi_handle_request(server
*srv
, connection
*con
, handler_ctx
*p
) {
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"));
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
);
1264 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1266 plugin_config
*s
= p
->config_storage
[0];
1268 PATCH(ssi_extension
);
1269 PATCH(content_type
);
1270 PATCH(conditional_requests
);
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;
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"))) {
1294 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.recursion-max"))) {
1295 PATCH(ssi_recursion_max
);
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
);
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
)) {
1335 con
->http_status
= 500;
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
];
1346 handler_ctx_free(hctx
);
1347 con
->plugin_ctx
[p
->id
] = NULL
;
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
;