7 #include "http_header.h"
8 #include "stat_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
34 #ifdef HAVE_SYS_FILIO_H
35 # include <sys/filio.h>
40 static handler_ctx
* handler_ctx_init(plugin_data
*p
) {
41 handler_ctx
*hctx
= calloc(1, sizeof(*hctx
));
43 hctx
->timefmt
= p
->timefmt
;
44 hctx
->stat_fn
= p
->stat_fn
;
45 hctx
->ssi_vars
= p
->ssi_vars
;
46 hctx
->ssi_cgi_env
= p
->ssi_cgi_env
;
47 memcpy(&hctx
->conf
, &p
->conf
, sizeof(plugin_config
));
51 static void handler_ctx_free(handler_ctx
*hctx
) {
55 /* The newest modified time of included files for include statement */
56 static volatile time_t include_file_last_mtime
= 0;
58 /* init the plugin data */
59 INIT_FUNC(mod_ssi_init
) {
62 p
= calloc(1, sizeof(*p
));
64 p
->timefmt
= buffer_init();
65 p
->stat_fn
= buffer_init();
67 p
->ssi_vars
= array_init();
68 p
->ssi_cgi_env
= array_init();
73 /* detroy the plugin data */
74 FREE_FUNC(mod_ssi_free
) {
78 if (!p
) return HANDLER_GO_ON
;
80 if (p
->config_storage
) {
82 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
83 plugin_config
*s
= p
->config_storage
[i
];
85 if (NULL
== s
) continue;
87 array_free(s
->ssi_extension
);
88 buffer_free(s
->content_type
);
92 free(p
->config_storage
);
95 array_free(p
->ssi_vars
);
96 array_free(p
->ssi_cgi_env
);
97 buffer_free(p
->timefmt
);
98 buffer_free(p
->stat_fn
);
102 return HANDLER_GO_ON
;
105 /* handle plugin config and check values */
107 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
108 plugin_data
*p
= p_d
;
111 config_values_t cv
[] = {
112 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
113 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
114 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
115 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
116 { "ssi.recursion-max", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
}, /* 4 */
117 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
120 if (!p
) return HANDLER_ERROR
;
122 p
->config_storage
= calloc(srv
->config_context
->used
, sizeof(plugin_config
*));
124 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
125 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
128 s
= calloc(1, sizeof(plugin_config
));
129 s
->ssi_extension
= array_init();
130 s
->content_type
= buffer_init();
131 s
->conditional_requests
= 0;
133 s
->ssi_recursion_max
= 0;
135 cv
[0].destination
= s
->ssi_extension
;
136 cv
[1].destination
= s
->content_type
;
137 cv
[2].destination
= &(s
->conditional_requests
);
138 cv
[3].destination
= &(s
->ssi_exec
);
139 cv
[4].destination
= &(s
->ssi_recursion_max
);
141 p
->config_storage
[i
] = s
;
143 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
144 return HANDLER_ERROR
;
147 if (!array_is_vlist(s
->ssi_extension
)) {
148 log_error_write(srv
, __FILE__
, __LINE__
, "s",
149 "unexpected value for ssi.extension; expected list of \"ext\"");
150 return HANDLER_ERROR
;
154 return HANDLER_GO_ON
;
158 static int ssi_env_add(void *venv
, const char *key
, size_t klen
, const char *val
, size_t vlen
) {
159 array_insert_key_value((array
*)venv
, key
, klen
, val
, vlen
);
163 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, handler_ctx
*p
) {
164 http_cgi_opts opts
= { 0, 0, NULL
, NULL
};
165 /* temporarily remove Authorization from request headers
166 * so that Authorization does not end up in SSI environment */
167 buffer
*vb_auth
= http_header_request_get(con
, HTTP_HEADER_AUTHORIZATION
, CONST_STR_LEN("Authorization"));
170 memcpy(&b_auth
, vb_auth
, sizeof(buffer
));
171 memset(vb_auth
, 0, sizeof(buffer
));
174 array_reset_data_strings(p
->ssi_cgi_env
);
176 if (0 != http_cgi_headers(srv
, con
, &opts
, ssi_env_add
, p
->ssi_cgi_env
)) {
177 con
->http_status
= 400;
182 memcpy(vb_auth
, &b_auth
, sizeof(buffer
));
188 static int mod_ssi_process_file(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
);
190 static int process_ssi_stmt(server
*srv
, connection
*con
, handler_ctx
*p
, const char **l
, size_t n
, struct stat
*st
) {
193 * <!--#element attribute=value attribute=value ... -->
201 * encoding -- missing
237 * The current date in Greenwich Mean Time.
239 * The current date in the local time zone.
241 * The filename (excluding directories) of the document requested by the user.
243 * The (%-decoded) URL path of the document requested by the user. Note that in the case of nested include files, this is not then URL for the current document.
245 * The last modification date of the document requested by the user.
247 * Contains the owner of the file which included it.
251 size_t i
, ssicmd
= 0;
255 static const struct {
257 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
258 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
259 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
, SSI_COMMENT
} type
;
261 { "echo", SSI_ECHO
},
262 { "include", SSI_INCLUDE
},
263 { "flastmod", SSI_FLASTMOD
},
264 { "fsize", SSI_FSIZE
},
265 { "config", SSI_CONFIG
},
266 { "printenv", SSI_PRINTENV
},
269 { "elif", SSI_ELIF
},
270 { "endif", SSI_ENDIF
},
271 { "else", SSI_ELSE
},
272 { "exec", SSI_EXEC
},
273 { "comment", SSI_COMMENT
},
278 for (i
= 0; ssicmds
[i
].var
; i
++) {
279 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
280 ssicmd
= ssicmds
[i
].type
;
290 const char *var_val
= NULL
;
292 static const struct {
298 SSI_ECHO_DOCUMENT_NAME
,
299 SSI_ECHO_DOCUMENT_URI
,
300 SSI_ECHO_LAST_MODIFIED
,
306 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
307 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
308 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
309 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
310 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
311 { "USER_NAME", SSI_ECHO_USER_NAME
},
312 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
313 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
315 { NULL
, SSI_ECHO_UNSET
}
319 static const struct {
321 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
323 { "url", SSI_ENC_URL },
324 { "none", SSI_ENC_NONE },
325 { "entity", SSI_ENC_ENTITY },
327 { NULL, SSI_ENC_UNSET }
331 for (i
= 2; i
< n
; i
+= 2) {
332 if (0 == strcmp(l
[i
], "var")) {
337 for (j
= 0; echovars
[j
].var
; j
++) {
338 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
339 var
= echovars
[j
].type
;
343 } else if (0 == strcmp(l
[i
], "encoding")) {
347 for (j = 0; encvars[j].var; j++) {
348 if (0 == strcmp(l[i+1], encvars[j].var)) {
349 enc = encvars[j].type;
355 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
356 "ssi: unknown attribute for ",
361 if (p
->if_is_false
) break;
364 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
366 l
[1], "var is missing");
371 case SSI_ECHO_USER_NAME
: {
376 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
377 buffer_copy_int(b
, st
->st_uid
);
379 buffer_copy_string(b
, pw
->pw_name
);
382 buffer_copy_int(b
, st
->st_uid
);
384 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(b
));
387 case SSI_ECHO_LAST_MODIFIED
: {
388 time_t t
= st
->st_mtime
;
390 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
391 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
393 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
397 case SSI_ECHO_DATE_LOCAL
: {
398 time_t t
= time(NULL
);
400 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
401 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
403 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
407 case SSI_ECHO_DATE_GMT
: {
408 time_t t
= time(NULL
);
410 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
411 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
413 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
417 case SSI_ECHO_DOCUMENT_NAME
: {
420 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
421 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
423 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
427 case SSI_ECHO_DOCUMENT_URI
: {
428 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
431 case SSI_ECHO_SCRIPT_URI
: {
432 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
433 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
434 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
435 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
436 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
437 if (!buffer_string_is_empty(con
->uri
.query
)) {
438 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
439 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
444 case SSI_ECHO_SCRIPT_URL
: {
445 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
446 if (!buffer_string_is_empty(con
->uri
.query
)) {
447 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
448 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
454 /* check if it is a cgi-var or a ssi-var */
456 if (NULL
!= (ds
= (data_string
*)array_get_element_klen(p
->ssi_cgi_env
, var_val
, strlen(var_val
))) ||
457 NULL
!= (ds
= (data_string
*)array_get_element_klen(p
->ssi_vars
, var_val
, strlen(var_val
)))) {
458 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
460 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
471 const char * file_path
= NULL
, *virt_path
= NULL
;
475 for (i
= 2; i
< n
; i
+= 2) {
476 if (0 == strcmp(l
[i
], "file")) {
478 } else if (0 == strcmp(l
[i
], "virtual")) {
481 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
482 "ssi: unknown attribute for ",
487 if (!file_path
&& !virt_path
) {
488 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
490 l
[1], "file or virtual are missing");
494 if (file_path
&& virt_path
) {
495 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
497 l
[1], "only one of file and virtual is allowed here");
502 if (p
->if_is_false
) break;
505 /* current doc-root */
506 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
507 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
509 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
512 buffer_copy_string(srv
->tmp_buf
, file_path
);
513 buffer_urldecode_path(srv
->tmp_buf
);
514 if (!buffer_is_valid_UTF8(srv
->tmp_buf
)) {
515 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
516 "SSI invalid UTF-8 after url-decode:", srv
->tmp_buf
);
519 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
520 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
525 if (virt_path
[0] == '/') {
526 buffer_copy_string(srv
->tmp_buf
, virt_path
);
528 /* there is always a / */
529 sl
= strrchr(con
->uri
.path
->ptr
, '/');
531 buffer_copy_string_len(srv
->tmp_buf
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
532 buffer_append_string(srv
->tmp_buf
, virt_path
);
535 buffer_urldecode_path(srv
->tmp_buf
);
536 if (!buffer_is_valid_UTF8(srv
->tmp_buf
)) {
537 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
538 "SSI invalid UTF-8 after url-decode:", srv
->tmp_buf
);
541 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
545 /* Destination physical path (similar to code in mod_webdav.c)
546 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
547 * (but neither modifies con->physical.rel_path)
548 * Find matching prefix to support relative paths to current physical path.
549 * Aliasing of paths underneath current con->physical.basedir might not work.
550 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
551 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
552 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
553 * (Related, use mod_auth to protect this basedir, but avoid attempting to
554 * use mod_auth on paths underneath this basedir, as target path is not
555 * validated with mod_auth)
558 /* find matching URI prefix
559 * check if remaining con->physical.rel_path matches suffix
560 * of con->physical.basedir so that we can use it to
561 * remap Destination physical path */
563 const char *sep
, *sep2
;
564 sep
= con
->uri
.path
->ptr
;
565 sep2
= srv
->tmp_buf
->ptr
;
566 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
567 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
569 if (con
->conf
.force_lowercase_filenames
) {
570 buffer_to_lower(srv
->tmp_buf
);
572 remain
= buffer_string_length(con
->uri
.path
) - i
;
573 if (!con
->conf
.force_lowercase_filenames
574 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
575 :(buffer_string_length(con
->physical
.path
) >= remain
576 && buffer_eq_icase_ssn(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
577 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
578 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
580 /* unable to perform physical path remap here;
581 * assume doc_root/rel_path and no remapping */
582 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
583 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
587 if (!con
->conf
.follow_symlink
588 && 0 != stat_cache_path_contains_symlink(srv
, p
->stat_fn
)) {
592 int fd
= stat_cache_open_rdonly_fstat(p
->stat_fn
, &stb
, con
->conf
.follow_symlink
);
594 time_t t
= stb
.st_mtime
;
601 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
603 off_t s
= stb
.st_size
;
605 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
607 buffer_copy_int(b
, s
);
608 buffer_append_string(b
, abr
[j
]);
610 buffer_copy_int(b
, stb
.st_size
);
612 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(b
));
615 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
616 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
618 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
622 /* Keep the newest mtime of included files */
623 if (stb
.st_mtime
> include_file_last_mtime
)
624 include_file_last_mtime
= stb
.st_mtime
;
626 if (file_path
|| 0 == p
->conf
.ssi_recursion_max
) {
627 /* don't process if #include file="..." is used */
628 chunkqueue_append_file_fd(con
->write_queue
, p
->stat_fn
, fd
, 0, stb
.st_size
);
631 buffer
*upsave
, *ppsave
, *prpsave
;
633 /* only allow predefined recursion depth */
634 if (p
->ssi_recursion_depth
>= p
->conf
.ssi_recursion_max
) {
635 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
639 /* prevents simple infinite loop */
640 if (buffer_is_equal(con
->physical
.path
, p
->stat_fn
)) {
641 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(error: include directives create an infinite loop)"));
645 /* save and restore con->physical.path, con->physical.rel_path, and con->uri.path around include
647 * srv->tmp_buf contains url-decoded, path-simplified, and lowercased (if con->conf.force_lowercase) uri path of target.
648 * con->uri.path and con->physical.rel_path are set to the same since we only operate on filenames here,
649 * not full re-run of all modules for subrequest */
650 upsave
= con
->uri
.path
;
651 ppsave
= con
->physical
.path
;
652 prpsave
= con
->physical
.rel_path
;
654 con
->physical
.path
= p
->stat_fn
;
655 p
->stat_fn
= buffer_init();
657 con
->uri
.path
= con
->physical
.rel_path
= buffer_init_buffer(srv
->tmp_buf
);
662 /*(ignore return value; muddle along as best we can if error occurs)*/
663 ++p
->ssi_recursion_depth
;
664 mod_ssi_process_file(srv
, con
, p
, &stb
);
665 --p
->ssi_recursion_depth
;
667 buffer_free(con
->uri
.path
);
668 con
->uri
.path
= upsave
;
669 con
->physical
.rel_path
= prpsave
;
671 buffer_free(p
->stat_fn
);
672 p
->stat_fn
= con
->physical
.path
;
673 con
->physical
.path
= ppsave
;
679 if (fd
> 0) close(fd
);
681 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
682 "ssi: stating failed ",
683 p
->stat_fn
, strerror(errno
));
688 const char *key
= NULL
, *val
= NULL
;
689 for (i
= 2; i
< n
; i
+= 2) {
690 if (0 == strcmp(l
[i
], "var")) {
692 } else if (0 == strcmp(l
[i
], "value")) {
695 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
696 "ssi: unknown attribute for ",
701 if (p
->if_is_false
) break;
704 array_insert_key_value(p
->ssi_vars
, key
, strlen(key
), val
, strlen(val
));
705 } else if (key
|| val
) {
706 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
707 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
709 log_error_write(srv
, __FILE__
, __LINE__
, "s",
710 "ssi: var and value have to be set in <!--#set var=... value=... -->");
715 if (p
->if_is_false
) break;
717 for (i
= 2; i
< n
; i
+= 2) {
718 if (0 == strcmp(l
[i
], "timefmt")) {
719 buffer_copy_string(p
->timefmt
, l
[i
+1]);
720 } else if (0 == strcmp(l
[i
], "sizefmt")) {
721 if (0 == strcmp(l
[i
+1], "abbrev")) {
723 } else if (0 == strcmp(l
[i
+1], "bytes")) {
726 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
727 "ssi: unknown value for attribute '",
733 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
734 "ssi: unknown attribute for ",
740 if (p
->if_is_false
) break;
744 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
745 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
747 buffer_append_string_buffer(b
, ds
->key
);
748 buffer_append_string_len(b
, CONST_STR_LEN("="));
749 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
750 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
752 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
753 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
755 buffer_append_string_buffer(b
, ds
->key
);
756 buffer_append_string_len(b
, CONST_STR_LEN("="));
757 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
758 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
760 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(b
));
763 const char *cmd
= NULL
;
768 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
772 for (i
= 2; i
< n
; i
+= 2) {
773 if (0 == strcmp(l
[i
], "cmd")) {
776 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
777 "ssi: unknown attribute for ",
782 if (p
->if_is_false
) break;
785 * as exec is assumed evil it is implemented synchronously
790 /* send cmd output to a temporary file */
791 if (0 != chunkqueue_append_mem_to_tempfile(srv
, con
->write_queue
, "", 0)) break;
792 c
= con
->write_queue
->last
;
794 *(const char **)&args
[0] = "/bin/sh";
795 *(const char **)&args
[1] = "-c";
796 *(const char **)&args
[2] = cmd
;
799 /*(expects STDIN_FILENO open to /dev/null)*/
800 pid
= fdevent_fork_execve(args
[0], args
, NULL
, -1, c
->file
.fd
, -1, -1);
802 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawning exec failed:", strerror(errno
), cmd
);
807 /* wait for the client to end */
808 /* NOTE: synchronous; blocks entire lighttpd server */
811 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
813 while (-1 == waitpid(pid
, &status
, 0)) {
814 if (errno
!= EINTR
) {
815 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
819 if (!WIFEXITED(status
)) {
820 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "process exited abnormally:", cmd
);
822 if (0 == fstat(c
->file
.fd
, &stb
)) {
823 c
->file
.length
= stb
.st_size
;
830 const char *expr
= NULL
;
832 for (i
= 2; i
< n
; i
+= 2) {
833 if (0 == strcmp(l
[i
], "expr")) {
836 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
837 "ssi: unknown attribute for ",
843 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
845 l
[1], "expr missing");
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
)) {
856 p
->if_is_false_level
= 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)) {
879 p
->if_is_false_level
= p
->if_level
;
885 const char *expr
= NULL
;
886 for (i
= 2; i
< n
; i
+= 2) {
887 if (0 == strcmp(l
[i
], "expr")) {
890 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
891 "ssi: unknown attribute for ",
897 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
899 l
[1], "expr missing");
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
)) {
912 p
->if_is_false_level
= p
->if_level
;
920 p
->if_is_false_level
= p
->if_level
;
921 p
->if_is_false_endif
= 1;
932 if (p
->if_level
== p
->if_is_false_level
) {
934 p
->if_is_false_endif
= 0;
941 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
942 "ssi: unknown ssi-command:",
951 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
953 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
955 for (n
= 1; n
< len
; ++n
) {
956 if (s
[n
] == c
) return n
+1;
958 if (n
+1 == len
) return 0; /* invalid */
962 return 0; /* invalid */
964 for (n
= 0; n
< len
; ++n
) {
965 if (isspace(s
[n
])) return n
;
967 if (n
+1 == len
) return 0; /* invalid */
975 static int mod_ssi_parse_ssi_stmt_offlen(int o
[10], const char * const s
, const int len
) {
978 * <!--#element attribute=value attribute=value ... -->
981 /* s must begin "<!--#" and must end with "-->" */
984 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
986 if (0 == o
[1]) return -1; /* empty token */
988 if (n
+3 == len
) return 2; /* token only; no params */
989 if (!isspace(s
[n
])) return -1;
990 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
991 if (n
+3 == len
) return 2; /* token only; no params */
994 for (; light_isalpha(s
[n
]); ++n
) ;
996 if (0 == o
[3] || s
[n
++] != '=') return -1;
999 o
[5] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1000 if (0 == o
[5]) return -1; /* empty or invalid token */
1003 if (n
+3 == len
) return 6; /* token and one param */
1004 if (!isspace(s
[n
])) return -1;
1005 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1006 if (n
+3 == len
) return 6; /* token and one param */
1009 for (; light_isalpha(s
[n
]); ++n
) ;
1011 if (0 == o
[7] || s
[n
++] != '=') return -1;
1014 o
[9] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1015 if (0 == o
[9]) return -1; /* empty or invalid token */
1018 if (n
+3 == len
) return 10; /* token and two params */
1019 if (!isspace(s
[n
])) return -1;
1020 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1021 if (n
+3 == len
) return 10; /* token and two params */
1025 static void mod_ssi_parse_ssi_stmt(server
*srv
, connection
*con
, handler_ctx
*p
, char *s
, int len
, struct stat
*st
) {
1028 * <!--#element attribute=value attribute=value ... -->
1033 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1034 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1036 /* ignore <!--#comment ... --> */
1038 && 0 == memcmp(s
+5, "comment", sizeof("comment")-1)
1039 && (s
[12] == ' ' || s
[12] == '\t'))
1041 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1042 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1047 /* dup s and then modify s */
1048 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1049 l
[0] = malloc((size_t)(len
+1));
1050 memcpy(l
[0], s
, (size_t)len
);
1054 /* modify s in-place to split string into arg tokens */
1055 for (m
= 0; m
< n
; m
+= 2) {
1059 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1060 default: ptr
[o
[m
+1]] = '\0'; break;
1063 if (m
== 4 || m
== 8) {
1064 /* XXX: removing '\\' escapes from param value would be
1065 * the right thing to do, but would potentially change
1066 * current behavior, e.g. <!--#exec cmd=... --> */
1070 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1077 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1078 /* s must begin "<!--#" */
1079 int n
, sq
= 0, dq
= 0, bs
= 0;
1080 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1085 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1088 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1091 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1094 if (sq
|| dq
) bs
= !bs
;
1098 return 0; /* incomplete directive "<!--#...-->" */
1101 static void mod_ssi_read_fd(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
, int fd
) {
1103 size_t offset
, pretag
;
1104 size_t bufsz
= 8192;
1105 char *buf
= malloc(bufsz
); /* allocate to reduce chance of stack exhaustion upon deep recursion */
1110 while (0 < (rd
= read(fd
, buf
+offset
, bufsz
-offset
))) {
1112 size_t prelen
= 0, len
;
1113 offset
+= (size_t)rd
;
1114 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1116 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1117 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1119 if (prelen
- pretag
&& !p
->if_is_false
) {
1120 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1123 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1124 if (len
) { /* num of chars to be consumed */
1125 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1126 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1127 pretag
= prelen
+ 1;
1128 if (pretag
== offset
) {
1129 offset
= pretag
= 0;
1132 } else if (0 == prelen
&& offset
== bufsz
) { /*(full buf)*/
1133 /* SSI statement is way too long
1134 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1135 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1136 /* check if buf ends with "-" or "--" which might be part of "-->"
1137 * (buf contains at least 5 chars for "<!--#") */
1138 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1139 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1140 } else if (buf
[offset
-1] == '-') {
1141 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1143 offset
= pretag
= 0;
1145 } else { /* incomplete directive "<!--#...-->" */
1146 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1150 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1151 if (prelen
- pretag
&& !p
->if_is_false
) {
1152 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1154 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1158 /* loop to look for next '<' */
1160 if (offset
== bufsz
) {
1161 if (!p
->if_is_false
) {
1162 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1164 offset
= pretag
= 0;
1169 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1172 if (offset
- pretag
) {
1173 /* copy remaining data in buf */
1174 if (!p
->if_is_false
) {
1175 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1183 static int mod_ssi_process_file(server
*srv
, connection
*con
, handler_ctx
*p
, struct stat
*st
) {
1184 int fd
= fdevent_open_cloexec(con
->physical
.path
->ptr
, con
->conf
.follow_symlink
, O_RDONLY
, 0);
1186 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "open(): ",
1187 strerror(errno
), con
->physical
.path
);
1191 if (0 != fstat(fd
, st
)) {
1192 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "fstat(): ",
1193 strerror(errno
), con
->physical
.path
);
1198 mod_ssi_read_fd(srv
, con
, p
, st
, fd
);
1205 static int mod_ssi_handle_request(server
*srv
, connection
*con
, handler_ctx
*p
) {
1208 /* get a stream to the file */
1210 array_reset_data_strings(p
->ssi_vars
);
1211 array_reset_data_strings(p
->ssi_cgi_env
);
1212 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1213 build_ssi_cgi_vars(srv
, con
, p
);
1215 /* Reset the modified time of included files */
1216 include_file_last_mtime
= 0;
1218 if (mod_ssi_process_file(srv
, con
, p
, &st
)) return -1;
1220 con
->file_started
= 1;
1221 con
->file_finished
= 1;
1223 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1224 http_header_response_set(con
, HTTP_HEADER_CONTENT_TYPE
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1226 http_header_response_set(con
, HTTP_HEADER_CONTENT_TYPE
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1229 if (p
->conf
.conditional_requests
) {
1230 /* Generate "ETag" & "Last-Modified" headers */
1231 buffer
*mtime
= NULL
;
1233 /* use most recently modified include file for ETag and Last-Modified */
1234 if (st
.st_mtime
< include_file_last_mtime
)
1235 st
.st_mtime
= include_file_last_mtime
;
1237 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1238 etag_mutate(con
->physical
.etag
, con
->physical
.etag
);
1239 http_header_response_set(con
, HTTP_HEADER_ETAG
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1241 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1242 http_header_response_set(con
, HTTP_HEADER_LAST_MODIFIED
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1244 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1245 /* ok, the client already has our content,
1246 * no need to send it again */
1248 chunkqueue_reset(con
->write_queue
);
1252 /* Reset the modified time of included files */
1253 include_file_last_mtime
= 0;
1255 /* reset physical.path */
1256 buffer_reset(con
->physical
.path
);
1263 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1265 plugin_config
*s
= p
->config_storage
[0];
1267 PATCH(ssi_extension
);
1268 PATCH(content_type
);
1269 PATCH(conditional_requests
);
1271 PATCH(ssi_recursion_max
);
1273 /* skip the first, the global context */
1274 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1275 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1276 s
= p
->config_storage
[i
];
1278 /* condition didn't match */
1279 if (!config_check_cond(srv
, con
, dc
)) continue;
1282 for (j
= 0; j
< dc
->value
->used
; j
++) {
1283 data_unset
*du
= dc
->value
->data
[j
];
1285 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1286 PATCH(ssi_extension
);
1287 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1288 PATCH(content_type
);
1289 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1290 PATCH(conditional_requests
);
1291 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1293 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.recursion-max"))) {
1294 PATCH(ssi_recursion_max
);
1303 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1304 plugin_data
*p
= p_d
;
1306 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1307 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1309 mod_ssi_patch_connection(srv
, con
, p
);
1311 if (array_match_value_suffix(p
->conf
.ssi_extension
, con
->physical
.path
)) {
1312 con
->plugin_ctx
[p
->id
] = handler_ctx_init(p
);
1316 return HANDLER_GO_ON
;
1319 SUBREQUEST_FUNC(mod_ssi_handle_subrequest
) {
1320 plugin_data
*p
= p_d
;
1321 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1322 if (NULL
== hctx
) return HANDLER_GO_ON
;
1323 if (con
->mode
!= p
->id
) return HANDLER_GO_ON
; /* not my job */
1325 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1326 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1327 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1330 /* handle ssi-request */
1332 if (mod_ssi_handle_request(srv
, con
, hctx
)) {
1334 con
->http_status
= 500;
1338 return HANDLER_FINISHED
;
1341 static handler_t
mod_ssi_connection_reset(server
*srv
, connection
*con
, void *p_d
) {
1342 plugin_data
*p
= p_d
;
1343 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1345 handler_ctx_free(hctx
);
1346 con
->plugin_ctx
[p
->id
] = NULL
;
1350 return HANDLER_GO_ON
;
1353 /* this function is called at dlopen() time and inits the callbacks */
1355 int mod_ssi_plugin_init(plugin
*p
);
1356 int mod_ssi_plugin_init(plugin
*p
) {
1357 p
->version
= LIGHTTPD_VERSION_ID
;
1358 p
->name
= buffer_init_string("ssi");
1360 p
->init
= mod_ssi_init
;
1361 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1362 p
->handle_subrequest
= mod_ssi_handle_subrequest
;
1363 p
->connection_reset
= mod_ssi_connection_reset
;
1364 p
->set_defaults
= mod_ssi_set_defaults
;
1365 p
->cleanup
= mod_ssi_free
;