13 #include "inet_ntop_cache.h"
15 #include "sys-socket.h"
17 #include <sys/types.h>
34 # include <sys/wait.h>
37 #ifdef HAVE_SYS_FILIO_H
38 # include <sys/filio.h>
43 static handler_ctx
* handler_ctx_init(plugin_data
*p
) {
44 handler_ctx
*hctx
= calloc(1, sizeof(*hctx
));
46 hctx
->timefmt
= p
->timefmt
;
47 hctx
->stat_fn
= p
->stat_fn
;
48 hctx
->ssi_vars
= p
->ssi_vars
;
49 hctx
->ssi_cgi_env
= p
->ssi_cgi_env
;
50 memcpy(&hctx
->conf
, &p
->conf
, sizeof(plugin_config
));
54 static void handler_ctx_free(handler_ctx
*hctx
) {
58 /* The newest modified time of included files for include statement */
59 static volatile time_t include_file_last_mtime
= 0;
61 /* init the plugin data */
62 INIT_FUNC(mod_ssi_init
) {
65 p
= calloc(1, sizeof(*p
));
67 p
->timefmt
= buffer_init();
68 p
->stat_fn
= buffer_init();
70 p
->ssi_vars
= array_init();
71 p
->ssi_cgi_env
= array_init();
76 /* detroy the plugin data */
77 FREE_FUNC(mod_ssi_free
) {
81 if (!p
) return HANDLER_GO_ON
;
83 if (p
->config_storage
) {
85 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
86 plugin_config
*s
= p
->config_storage
[i
];
88 if (NULL
== s
) continue;
90 array_free(s
->ssi_extension
);
91 buffer_free(s
->content_type
);
95 free(p
->config_storage
);
98 array_free(p
->ssi_vars
);
99 array_free(p
->ssi_cgi_env
);
100 buffer_free(p
->timefmt
);
101 buffer_free(p
->stat_fn
);
105 return HANDLER_GO_ON
;
108 /* handle plugin config and check values */
110 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
111 plugin_data
*p
= p_d
;
114 config_values_t cv
[] = {
115 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
116 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
117 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
118 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
119 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
122 if (!p
) return HANDLER_ERROR
;
124 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
126 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
127 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
130 s
= calloc(1, sizeof(plugin_config
));
131 s
->ssi_extension
= array_init();
132 s
->content_type
= buffer_init();
133 s
->conditional_requests
= 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
);
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
;
148 return HANDLER_GO_ON
;
152 static int ssi_env_add(void *venv
, const char *key
, size_t klen
, const char *val
, size_t vlen
) {
156 /* array_set_key_value() w/o extra lookup to see if key already exists */
157 if (NULL
== (ds
= (data_string
*)array_get_unused_element(env
, TYPE_STRING
))) {
158 ds
= data_string_init();
160 buffer_copy_string_len(ds
->key
, key
, klen
);
161 buffer_copy_string_len(ds
->value
, val
, vlen
);
163 array_insert_unique(env
, (data_unset
*)ds
);
168 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, handler_ctx
*p
) {
169 http_cgi_opts opts
= { 0, 0, NULL
, NULL
};
170 /* temporarily remove Authorization from request headers
171 * so that Authorization does not end up in SSI environment */
172 data_string
*ds_auth
= (data_string
*)array_get_element(con
->request
.headers
, "Authorization");
173 buffer
*b_auth
= NULL
;
175 b_auth
= ds_auth
->value
;
176 ds_auth
->value
= NULL
;
179 array_reset(p
->ssi_cgi_env
);
181 if (0 != http_cgi_headers(srv
, con
, &opts
, ssi_env_add
, p
->ssi_cgi_env
)) {
182 con
->http_status
= 400;
187 ds_auth
->value
= b_auth
;
193 static int process_ssi_stmt(server
*srv
, connection
*con
, handler_ctx
*p
, const char **l
, size_t n
, struct stat
*st
) {
196 * <!--#element attribute=value attribute=value ... -->
204 * encoding -- missing
240 * The current date in Greenwich Mean Time.
242 * The current date in the local time zone.
244 * The filename (excluding directories) of the document requested by the user.
246 * 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.
248 * The last modification date of the document requested by the user.
250 * Contains the owner of the file which included it.
254 size_t i
, ssicmd
= 0;
260 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
261 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
262 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
} type
;
264 { "echo", SSI_ECHO
},
265 { "include", SSI_INCLUDE
},
266 { "flastmod", SSI_FLASTMOD
},
267 { "fsize", SSI_FSIZE
},
268 { "config", SSI_CONFIG
},
269 { "printenv", SSI_PRINTENV
},
272 { "elif", SSI_ELIF
},
273 { "endif", SSI_ENDIF
},
274 { "else", SSI_ELSE
},
275 { "exec", SSI_EXEC
},
280 for (i
= 0; ssicmds
[i
].var
; i
++) {
281 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
282 ssicmd
= ssicmds
[i
].type
;
292 const char *var_val
= NULL
;
300 SSI_ECHO_DOCUMENT_NAME
,
301 SSI_ECHO_DOCUMENT_URI
,
302 SSI_ECHO_LAST_MODIFIED
,
308 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
309 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
310 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
311 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
312 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
313 { "USER_NAME", SSI_ECHO_USER_NAME
},
314 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
315 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
317 { NULL
, SSI_ECHO_UNSET
}
323 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
325 { "url", SSI_ENC_URL },
326 { "none", SSI_ENC_NONE },
327 { "entity", SSI_ENC_ENTITY },
329 { NULL, SSI_ENC_UNSET }
333 for (i
= 2; i
< n
; i
+= 2) {
334 if (0 == strcmp(l
[i
], "var")) {
339 for (j
= 0; echovars
[j
].var
; j
++) {
340 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
341 var
= echovars
[j
].type
;
345 } else if (0 == strcmp(l
[i
], "encoding")) {
349 for (j = 0; encvars[j].var; j++) {
350 if (0 == strcmp(l[i+1], encvars[j].var)) {
351 enc = encvars[j].type;
357 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
358 "ssi: unknown attribute for ",
363 if (p
->if_is_false
) break;
366 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
368 l
[1], "var is missing");
373 case SSI_ECHO_USER_NAME
: {
378 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
379 buffer_copy_int(b
, st
->st_uid
);
381 buffer_copy_string(b
, pw
->pw_name
);
384 buffer_copy_int(b
, st
->st_uid
);
386 chunkqueue_append_buffer(con
->write_queue
, b
);
390 case SSI_ECHO_LAST_MODIFIED
: {
391 time_t t
= st
->st_mtime
;
393 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
394 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
396 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
400 case SSI_ECHO_DATE_LOCAL
: {
401 time_t t
= time(NULL
);
403 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
404 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
406 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
410 case SSI_ECHO_DATE_GMT
: {
411 time_t t
= time(NULL
);
413 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
414 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
416 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
420 case SSI_ECHO_DOCUMENT_NAME
: {
423 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
424 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
426 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
430 case SSI_ECHO_DOCUMENT_URI
: {
431 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
434 case SSI_ECHO_SCRIPT_URI
: {
435 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
436 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
437 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
438 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
439 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
440 if (!buffer_string_is_empty(con
->uri
.query
)) {
441 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
442 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
447 case SSI_ECHO_SCRIPT_URL
: {
448 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
449 if (!buffer_string_is_empty(con
->uri
.query
)) {
450 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
451 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
457 /* check if it is a cgi-var or a ssi-var */
459 if (NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_cgi_env
, var_val
)) ||
460 NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_vars
, var_val
))) {
461 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
463 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
474 const char * file_path
= NULL
, *virt_path
= NULL
;
478 for (i
= 2; i
< n
; i
+= 2) {
479 if (0 == strcmp(l
[i
], "file")) {
481 } else if (0 == strcmp(l
[i
], "virtual")) {
484 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
485 "ssi: unknown attribute for ",
490 if (!file_path
&& !virt_path
) {
491 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
493 l
[1], "file or virtual are missing");
497 if (file_path
&& virt_path
) {
498 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
500 l
[1], "only one of file and virtual is allowed here");
505 if (p
->if_is_false
) break;
508 /* current doc-root */
509 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
510 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
512 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
515 buffer_copy_string(srv
->tmp_buf
, file_path
);
516 buffer_urldecode_path(srv
->tmp_buf
);
517 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
518 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
523 if (virt_path
[0] == '/') {
524 buffer_copy_string(p
->stat_fn
, virt_path
);
526 /* there is always a / */
527 sl
= strrchr(con
->uri
.path
->ptr
, '/');
529 buffer_copy_string_len(p
->stat_fn
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
530 buffer_append_string(p
->stat_fn
, virt_path
);
533 buffer_urldecode_path(p
->stat_fn
);
534 buffer_path_simplify(srv
->tmp_buf
, p
->stat_fn
);
538 /* Destination physical path (similar to code in mod_webdav.c)
539 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
540 * (but neither modifies con->physical.rel_path)
541 * Find matching prefix to support relative paths to current physical path.
542 * Aliasing of paths underneath current con->physical.basedir might not work.
543 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
544 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
545 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
546 * (Related, use mod_auth to protect this basedir, but avoid attempting to
547 * use mod_auth on paths underneath this basedir, as target path is not
548 * validated with mod_auth)
551 /* find matching URI prefix
552 * check if remaining con->physical.rel_path matches suffix
553 * of con->physical.basedir so that we can use it to
554 * remap Destination physical path */
556 const char *sep
, *sep2
;
557 sep
= con
->uri
.path
->ptr
;
558 sep2
= srv
->tmp_buf
->ptr
;
559 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
560 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
562 if (con
->conf
.force_lowercase_filenames
) {
563 buffer_to_lower(srv
->tmp_buf
);
565 remain
= buffer_string_length(con
->uri
.path
) - i
;
566 if (!con
->conf
.force_lowercase_filenames
567 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
568 :(buffer_string_length(con
->physical
.path
) >= remain
569 && 0 == strncasecmp(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
570 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
571 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
573 /* unable to perform physical path remap here;
574 * assume doc_root/rel_path and no remapping */
575 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
576 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
580 if (0 == stat(p
->stat_fn
->ptr
, &stb
)) {
581 time_t t
= stb
.st_mtime
;
588 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
590 off_t s
= stb
.st_size
;
592 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
594 buffer_copy_int(b
, s
);
595 buffer_append_string(b
, abr
[j
]);
597 buffer_copy_int(b
, stb
.st_size
);
599 chunkqueue_append_buffer(con
->write_queue
, b
);
603 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
604 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
606 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
610 chunkqueue_append_file(con
->write_queue
, p
->stat_fn
, 0, stb
.st_size
);
612 /* Keep the newest mtime of included files */
613 if (stb
.st_mtime
> include_file_last_mtime
)
614 include_file_last_mtime
= stb
.st_mtime
;
619 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
620 "ssi: stating failed ",
621 p
->stat_fn
, strerror(errno
));
626 const char *key
= NULL
, *val
= NULL
;
627 for (i
= 2; i
< n
; i
+= 2) {
628 if (0 == strcmp(l
[i
], "var")) {
630 } else if (0 == strcmp(l
[i
], "value")) {
633 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
634 "ssi: unknown attribute for ",
639 if (p
->if_is_false
) break;
644 if (NULL
== (ds
= (data_string
*)array_get_unused_element(p
->ssi_vars
, TYPE_STRING
))) {
645 ds
= data_string_init();
647 buffer_copy_string(ds
->key
, key
);
648 buffer_copy_string(ds
->value
, val
);
650 array_insert_unique(p
->ssi_vars
, (data_unset
*)ds
);
651 } else if (key
|| val
) {
652 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
653 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
655 log_error_write(srv
, __FILE__
, __LINE__
, "s",
656 "ssi: var and value have to be set in <!--#set var=... value=... -->");
661 if (p
->if_is_false
) break;
663 for (i
= 2; i
< n
; i
+= 2) {
664 if (0 == strcmp(l
[i
], "timefmt")) {
665 buffer_copy_string(p
->timefmt
, l
[i
+1]);
666 } else if (0 == strcmp(l
[i
], "sizefmt")) {
667 if (0 == strcmp(l
[i
+1], "abbrev")) {
669 } else if (0 == strcmp(l
[i
+1], "bytes")) {
672 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
673 "ssi: unknown value for attribute '",
679 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
680 "ssi: unknown attribute for ",
686 if (p
->if_is_false
) break;
689 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
690 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
692 buffer_append_string_buffer(b
, ds
->key
);
693 buffer_append_string_len(b
, CONST_STR_LEN("="));
694 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
695 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
697 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
698 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
700 buffer_append_string_buffer(b
, ds
->key
);
701 buffer_append_string_len(b
, CONST_STR_LEN("="));
702 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
703 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
705 chunkqueue_append_buffer(con
->write_queue
, b
);
710 const char *cmd
= NULL
;
712 int from_exec_fds
[2];
714 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
718 for (i
= 2; i
< n
; i
+= 2) {
719 if (0 == strcmp(l
[i
], "cmd")) {
722 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
723 "ssi: unknown attribute for ",
728 if (p
->if_is_false
) break;
730 /* create a return pipe and send output to the html-page
732 * as exec is assumed evil it is implemented synchronously
737 if (pipe(from_exec_fds
)) {
738 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
739 "pipe failed: ", strerror(errno
));
744 switch (pid
= fork()) {
746 /* move stdout to from_rrdtool_fd[1] */
747 close(STDOUT_FILENO
);
748 dup2(from_exec_fds
[1], STDOUT_FILENO
);
749 close(from_exec_fds
[1]);
751 close(from_exec_fds
[0]);
756 execl("/bin/sh", "sh", "-c", cmd
, (char *)NULL
);
758 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawing exec failed:", strerror(errno
), cmd
);
766 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fork failed:", strerror(errno
));
772 int was_interrupted
= 0;
774 close(from_exec_fds
[1]);
776 /* wait for the client to end */
779 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
782 if (-1 == waitpid(pid
, &status
, 0)) {
783 if (errno
== EINTR
) {
787 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
789 } else if (WIFEXITED(status
)) {
791 /* read everything from client and paste it into the output */
795 if (ioctl(from_exec_fds
[0], FIONREAD
, &toread
)) {
796 log_error_write(srv
, __FILE__
, __LINE__
, "s",
797 "unexpected end-of-file (perhaps the ssi-exec process died)");
805 chunkqueue_get_memory(con
->write_queue
, &mem
, &mem_len
, 0, toread
);
806 r
= read(from_exec_fds
[0], mem
, mem_len
);
807 chunkqueue_use_memory(con
->write_queue
, r
> 0 ? r
: 0);
809 if (r
< 0) break; /* read failed */
816 log_error_write(srv
, __FILE__
, __LINE__
, "s", "process exited abnormally");
818 } while (was_interrupted
> 0 && was_interrupted
< 4); /* if waitpid() gets interrupted, retry, but max 4 times */
820 close(from_exec_fds
[0]);
833 const char *expr
= NULL
;
835 for (i
= 2; i
< n
; i
+= 2) {
836 if (0 == strcmp(l
[i
], "expr")) {
839 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
840 "ssi: unknown attribute for ",
846 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
848 l
[1], "expr missing");
852 if ((!p
->if_is_false
) &&
853 ((p
->if_is_false_level
== 0) ||
854 (p
->if_level
< p
->if_is_false_level
))) {
855 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
859 p
->if_is_false_level
= p
->if_level
;
874 if (p
->if_is_false
) {
875 if ((p
->if_level
== p
->if_is_false_level
) &&
876 (p
->if_is_false_endif
== 0)) {
882 p
->if_is_false_level
= p
->if_level
;
888 const char *expr
= NULL
;
889 for (i
= 2; i
< n
; i
+= 2) {
890 if (0 == strcmp(l
[i
], "expr")) {
893 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
894 "ssi: unknown attribute for ",
900 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
902 l
[1], "expr missing");
908 if (p
->if_level
== p
->if_is_false_level
) {
909 if ((p
->if_is_false
) &&
910 (p
->if_is_false_endif
== 0)) {
911 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
915 p
->if_is_false_level
= p
->if_level
;
923 p
->if_is_false_level
= p
->if_level
;
924 p
->if_is_false_endif
= 1;
935 if (p
->if_level
== p
->if_is_false_level
) {
937 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 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1038 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1043 /* dup s and then modify s */
1044 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1045 l
[0] = malloc((size_t)(len
+1));
1046 memcpy(l
[0], s
, (size_t)len
);
1050 /* modify s in-place to split string into arg tokens */
1051 for (m
= 0; m
< n
; m
+= 2) {
1055 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1056 default: ptr
[o
[m
+1]] = '\0'; break;
1059 if (m
== 4 || m
== 8) {
1060 /* XXX: removing '\\' escapes from param value would be
1061 * the right thing to do, but would potentially change
1062 * current behavior, e.g. <!--#exec cmd=... --> */
1066 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1073 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1074 /* s must begin "<!--#" */
1075 int n
, sq
= 0, dq
= 0, bs
= 0;
1076 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1081 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1084 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1087 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1090 if (sq
|| dq
) bs
= !bs
;
1094 return 0; /* incomplete directive "<!--#...-->" */
1097 static void mod_ssi_read_fd(server
*srv
, connection
*con
, handler_ctx
*p
, int fd
, struct stat
*st
) {
1099 size_t offset
, pretag
;
1104 while (0 < (rd
= read(fd
, buf
+offset
, sizeof(buf
)-offset
))) {
1106 size_t prelen
= 0, len
;
1107 offset
+= (size_t)rd
;
1108 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1110 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1111 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1113 if (prelen
- pretag
&& !p
->if_is_false
) {
1114 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1117 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1118 if (len
) { /* num of chars to be consumed */
1119 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1120 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1121 pretag
= prelen
+ 1;
1122 if (pretag
== offset
) {
1123 offset
= pretag
= 0;
1126 } else if (0 == prelen
&& offset
== sizeof(buf
)) { /*(full buf)*/
1127 /* SSI statement is way too long
1128 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1129 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1130 /* check if buf ends with "-" or "--" which might be part of "-->"
1131 * (buf contains at least 5 chars for "<!--#") */
1132 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1133 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1134 } else if (buf
[offset
-1] == '-') {
1135 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1137 offset
= pretag
= 0;
1139 } else { /* incomplete directive "<!--#...-->" */
1140 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1144 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1145 if (prelen
- pretag
&& !p
->if_is_false
) {
1146 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1148 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1152 /* loop to look for next '<' */
1154 if (offset
== sizeof(buf
)) {
1155 if (!p
->if_is_false
) {
1156 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1158 offset
= pretag
= 0;
1163 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1166 if (offset
- pretag
) {
1167 /* copy remaining data in buf */
1168 if (!p
->if_is_false
) {
1169 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1175 /* don't want to block when open()ing a fifo */
1176 #if defined(O_NONBLOCK)
1177 # define FIFO_NONBLOCK O_NONBLOCK
1179 # define FIFO_NONBLOCK 0
1182 static int mod_ssi_handle_request(server
*srv
, connection
*con
, handler_ctx
*p
) {
1186 /* get a stream to the file */
1188 array_reset(p
->ssi_vars
);
1189 array_reset(p
->ssi_cgi_env
);
1190 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1191 build_ssi_cgi_vars(srv
, con
, p
);
1193 /* Reset the modified time of included files */
1194 include_file_last_mtime
= 0;
1196 if (-1 == (fd
= open(con
->physical
.path
->ptr
, O_RDONLY
| FIFO_NONBLOCK
))) {
1197 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
1198 "open: ", con
->physical
.path
);
1202 if (0 != fstat(fd
, &st
)) {
1203 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "fstat failed: ", con
->physical
.path
);
1208 mod_ssi_read_fd(srv
, con
, p
, fd
, &st
);
1211 con
->file_started
= 1;
1212 con
->file_finished
= 1;
1214 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1215 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1217 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1220 if (p
->conf
.conditional_requests
) {
1221 /* Generate "ETag" & "Last-Modified" headers */
1222 buffer
*mtime
= NULL
;
1224 /* use most recently modified include file for ETag and Last-Modified */
1225 if (st
.st_mtime
< include_file_last_mtime
)
1226 st
.st_mtime
= include_file_last_mtime
;
1228 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1229 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1231 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1232 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1234 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1235 /* ok, the client already has our content,
1236 * no need to send it again */
1238 chunkqueue_reset(con
->write_queue
);
1242 /* Reset the modified time of included files */
1243 include_file_last_mtime
= 0;
1245 /* reset physical.path */
1246 buffer_reset(con
->physical
.path
);
1253 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1255 plugin_config
*s
= p
->config_storage
[0];
1257 PATCH(ssi_extension
);
1258 PATCH(content_type
);
1259 PATCH(conditional_requests
);
1262 /* skip the first, the global context */
1263 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1264 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1265 s
= p
->config_storage
[i
];
1267 /* condition didn't match */
1268 if (!config_check_cond(srv
, con
, dc
)) continue;
1271 for (j
= 0; j
< dc
->value
->used
; j
++) {
1272 data_unset
*du
= dc
->value
->data
[j
];
1274 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1275 PATCH(ssi_extension
);
1276 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1277 PATCH(content_type
);
1278 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1279 PATCH(conditional_requests
);
1280 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1290 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1291 plugin_data
*p
= p_d
;
1294 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1296 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1298 mod_ssi_patch_connection(srv
, con
, p
);
1300 for (k
= 0; k
< p
->conf
.ssi_extension
->used
; k
++) {
1301 data_string
*ds
= (data_string
*)p
->conf
.ssi_extension
->data
[k
];
1303 if (buffer_is_empty(ds
->value
)) continue;
1305 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
1306 con
->plugin_ctx
[p
->id
] = handler_ctx_init(p
);
1312 return HANDLER_GO_ON
;
1315 SUBREQUEST_FUNC(mod_ssi_handle_subrequest
) {
1316 plugin_data
*p
= p_d
;
1317 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1318 if (NULL
== hctx
) return HANDLER_GO_ON
;
1319 if (con
->mode
!= p
->id
) return HANDLER_GO_ON
; /* not my job */
1321 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1322 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1323 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1326 /* handle ssi-request */
1328 if (mod_ssi_handle_request(srv
, con
, hctx
)) {
1330 con
->http_status
= 500;
1334 return HANDLER_FINISHED
;
1337 static handler_t
mod_ssi_connection_reset(server
*srv
, connection
*con
, void *p_d
) {
1338 plugin_data
*p
= p_d
;
1339 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1341 handler_ctx_free(hctx
);
1342 con
->plugin_ctx
[p
->id
] = NULL
;
1346 return HANDLER_GO_ON
;
1349 /* this function is called at dlopen() time and inits the callbacks */
1351 int mod_ssi_plugin_init(plugin
*p
);
1352 int mod_ssi_plugin_init(plugin
*p
) {
1353 p
->version
= LIGHTTPD_VERSION_ID
;
1354 p
->name
= buffer_init_string("ssi");
1356 p
->init
= mod_ssi_init
;
1357 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1358 p
->handle_subrequest
= mod_ssi_handle_subrequest
;
1359 p
->connection_reset
= mod_ssi_connection_reset
;
1360 p
->set_defaults
= mod_ssi_set_defaults
;
1361 p
->cleanup
= mod_ssi_free
;