14 #include "inet_ntop_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
35 # include <sys/wait.h>
38 #ifdef HAVE_SYS_FILIO_H
39 # include <sys/filio.h>
45 /* The newest modified time of included files for include statement */
46 static volatile time_t include_file_last_mtime
= 0;
48 /* init the plugin data */
49 INIT_FUNC(mod_ssi_init
) {
52 p
= calloc(1, sizeof(*p
));
54 p
->timefmt
= buffer_init();
55 p
->stat_fn
= buffer_init();
57 p
->ssi_vars
= array_init();
58 p
->ssi_cgi_env
= array_init();
63 /* detroy the plugin data */
64 FREE_FUNC(mod_ssi_free
) {
68 if (!p
) return HANDLER_GO_ON
;
70 if (p
->config_storage
) {
72 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
73 plugin_config
*s
= p
->config_storage
[i
];
75 if (NULL
== s
) continue;
77 array_free(s
->ssi_extension
);
78 buffer_free(s
->content_type
);
82 free(p
->config_storage
);
85 array_free(p
->ssi_vars
);
86 array_free(p
->ssi_cgi_env
);
87 buffer_free(p
->timefmt
);
88 buffer_free(p
->stat_fn
);
95 /* handle plugin config and check values */
97 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
101 config_values_t cv
[] = {
102 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
103 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
104 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
105 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
106 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
109 if (!p
) return HANDLER_ERROR
;
111 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
113 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
114 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
117 s
= calloc(1, sizeof(plugin_config
));
118 s
->ssi_extension
= array_init();
119 s
->content_type
= buffer_init();
120 s
->conditional_requests
= 0;
123 cv
[0].destination
= s
->ssi_extension
;
124 cv
[1].destination
= s
->content_type
;
125 cv
[2].destination
= &(s
->conditional_requests
);
126 cv
[3].destination
= &(s
->ssi_exec
);
128 p
->config_storage
[i
] = s
;
130 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
131 return HANDLER_ERROR
;
135 return HANDLER_GO_ON
;
139 static int ssi_env_add(array
*env
, const char *key
, const char *val
) {
142 if (NULL
== (ds
= (data_string
*)array_get_unused_element(env
, TYPE_STRING
))) {
143 ds
= data_string_init();
145 buffer_copy_string(ds
->key
, key
);
146 buffer_copy_string(ds
->value
, val
);
148 array_insert_unique(env
, (data_unset
*)ds
);
155 * the next two functions are take from fcgi.c
159 static int ssi_env_add_request_headers(server
*srv
, connection
*con
, plugin_data
*p
) {
162 for (i
= 0; i
< con
->request
.headers
->used
; i
++) {
165 ds
= (data_string
*)con
->request
.headers
->data
[i
];
167 if (!buffer_is_empty(ds
->value
) && !buffer_is_empty(ds
->key
)) {
168 /* don't forward the Authorization: Header */
169 if (0 == strcasecmp(ds
->key
->ptr
, "AUTHORIZATION")) {
173 buffer_copy_string_encoded_cgi_varnames(srv
->tmp_buf
, CONST_BUF_LEN(ds
->key
), 1);
175 ssi_env_add(p
->ssi_cgi_env
, srv
->tmp_buf
->ptr
, ds
->value
->ptr
);
179 for (i
= 0; i
< con
->environment
->used
; i
++) {
182 ds
= (data_string
*)con
->environment
->data
[i
];
184 if (!buffer_is_empty(ds
->value
) && !buffer_is_empty(ds
->key
)) {
185 buffer_copy_string_encoded_cgi_varnames(srv
->tmp_buf
, CONST_BUF_LEN(ds
->key
), 0);
187 ssi_env_add(p
->ssi_cgi_env
, srv
->tmp_buf
->ptr
, ds
->value
->ptr
);
194 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, plugin_data
*p
) {
195 char buf
[LI_ITOSTRING_LENGTH
];
197 server_socket
*srv_sock
= con
->srv_socket
;
200 char b2
[INET6_ADDRSTRLEN
+ 1];
203 #define CONST_STRING(x) \
206 array_reset(p
->ssi_cgi_env
);
208 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_SOFTWARE"), PACKAGE_DESC
);
209 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_NAME"),
211 inet_ntop(srv_sock
->addr
.plain
.sa_family
,
212 srv_sock
->addr
.plain
.sa_family
== AF_INET6
?
213 (const void *) &(srv_sock
->addr
.ipv6
.sin6_addr
) :
214 (const void *) &(srv_sock
->addr
.ipv4
.sin_addr
),
217 inet_ntoa(srv_sock
->addr
.ipv4
.sin_addr
)
220 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
222 li_utostrn(buf
, sizeof(buf
),
224 ntohs(srv_sock
->addr
.plain
.sa_family
? srv_sock
->addr
.ipv6
.sin6_port
: srv_sock
->addr
.ipv4
.sin_port
)
226 ntohs(srv_sock
->addr
.ipv4
.sin_port
)
230 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_PORT"), buf
);
232 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REMOTE_ADDR"),
233 inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
235 if (con
->request
.content_length
> 0) {
236 /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
238 li_itostrn(buf
, sizeof(buf
), con
->request
.content_length
);
239 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("CONTENT_LENGTH"), buf
);
243 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
244 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
245 * (6.1.14, 6.1.6, 6.1.7)
248 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SCRIPT_NAME"), con
->uri
.path
->ptr
);
249 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("PATH_INFO"), "");
252 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
253 * http://www.php.net/manual/en/reserved.variables.php
254 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
255 * TODO: this code should be checked against cgi.fix_pathinfo php
259 if (!buffer_string_is_empty(con
->request
.pathinfo
)) {
260 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("PATH_INFO"), con
->request
.pathinfo
->ptr
);
263 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SCRIPT_FILENAME"), con
->physical
.path
->ptr
);
264 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("DOCUMENT_ROOT"), con
->physical
.basedir
->ptr
);
266 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_URI"), con
->request
.uri
->ptr
);
268 if (!buffer_string_is_empty(con
->uri
.scheme
)) {
269 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_SCHEME"), con
->uri
.scheme
->ptr
);
272 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("QUERY_STRING"), buffer_is_empty(con
->uri
.query
) ? "" : con
->uri
.query
->ptr
);
273 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con
->request
.http_method
));
274 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con
->request
.http_version
));
275 /* set REDIRECT_STATUS for php compiled with --force-redirect
276 * (if REDIRECT_STATUS has not already been set by error handler) */
277 if (0 == con
->error_handler_saved_status
) {
278 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REDIRECT_STATUS"), "200");
281 ssi_env_add_request_headers(srv
, con
, p
);
286 static int process_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, const char **l
, size_t n
, struct stat
*st
) {
289 * <!--#element attribute=value attribute=value ... -->
297 * encoding -- missing
333 * The current date in Greenwich Mean Time.
335 * The current date in the local time zone.
337 * The filename (excluding directories) of the document requested by the user.
339 * 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.
341 * The last modification date of the document requested by the user.
343 * Contains the owner of the file which included it.
347 size_t i
, ssicmd
= 0;
353 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
354 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
355 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
} type
;
357 { "echo", SSI_ECHO
},
358 { "include", SSI_INCLUDE
},
359 { "flastmod", SSI_FLASTMOD
},
360 { "fsize", SSI_FSIZE
},
361 { "config", SSI_CONFIG
},
362 { "printenv", SSI_PRINTENV
},
365 { "elif", SSI_ELIF
},
366 { "endif", SSI_ENDIF
},
367 { "else", SSI_ELSE
},
368 { "exec", SSI_EXEC
},
373 for (i
= 0; ssicmds
[i
].var
; i
++) {
374 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
375 ssicmd
= ssicmds
[i
].type
;
385 const char *var_val
= NULL
;
393 SSI_ECHO_DOCUMENT_NAME
,
394 SSI_ECHO_DOCUMENT_URI
,
395 SSI_ECHO_LAST_MODIFIED
,
401 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
402 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
403 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
404 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
405 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
406 { "USER_NAME", SSI_ECHO_USER_NAME
},
407 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
408 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
410 { NULL
, SSI_ECHO_UNSET
}
416 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
418 { "url", SSI_ENC_URL },
419 { "none", SSI_ENC_NONE },
420 { "entity", SSI_ENC_ENTITY },
422 { NULL, SSI_ENC_UNSET }
426 for (i
= 2; i
< n
; i
+= 2) {
427 if (0 == strcmp(l
[i
], "var")) {
432 for (j
= 0; echovars
[j
].var
; j
++) {
433 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
434 var
= echovars
[j
].type
;
438 } else if (0 == strcmp(l
[i
], "encoding")) {
442 for (j = 0; encvars[j].var; j++) {
443 if (0 == strcmp(l[i+1], encvars[j].var)) {
444 enc = encvars[j].type;
450 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
451 "ssi: unknown attribute for ",
456 if (p
->if_is_false
) break;
459 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
461 l
[1], "var is missing");
466 case SSI_ECHO_USER_NAME
: {
471 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
472 buffer_copy_int(b
, st
->st_uid
);
474 buffer_copy_string(b
, pw
->pw_name
);
477 buffer_copy_int(b
, st
->st_uid
);
479 chunkqueue_append_buffer(con
->write_queue
, b
);
483 case SSI_ECHO_LAST_MODIFIED
: {
484 time_t t
= st
->st_mtime
;
486 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
487 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
489 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
493 case SSI_ECHO_DATE_LOCAL
: {
494 time_t t
= time(NULL
);
496 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
497 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
499 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
503 case SSI_ECHO_DATE_GMT
: {
504 time_t t
= time(NULL
);
506 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
507 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
509 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
513 case SSI_ECHO_DOCUMENT_NAME
: {
516 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
517 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
519 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
523 case SSI_ECHO_DOCUMENT_URI
: {
524 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
527 case SSI_ECHO_SCRIPT_URI
: {
528 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
529 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
530 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
531 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
532 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
533 if (!buffer_string_is_empty(con
->uri
.query
)) {
534 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
535 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
540 case SSI_ECHO_SCRIPT_URL
: {
541 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
542 if (!buffer_string_is_empty(con
->uri
.query
)) {
543 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
544 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
550 /* check if it is a cgi-var or a ssi-var */
552 if (NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_cgi_env
, var_val
)) ||
553 NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_vars
, var_val
))) {
554 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
556 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
567 const char * file_path
= NULL
, *virt_path
= NULL
;
571 for (i
= 2; i
< n
; i
+= 2) {
572 if (0 == strcmp(l
[i
], "file")) {
574 } else if (0 == strcmp(l
[i
], "virtual")) {
577 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
578 "ssi: unknown attribute for ",
583 if (!file_path
&& !virt_path
) {
584 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
586 l
[1], "file or virtual are missing");
590 if (file_path
&& virt_path
) {
591 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
593 l
[1], "only one of file and virtual is allowed here");
598 if (p
->if_is_false
) break;
601 /* current doc-root */
602 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
603 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
605 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
608 buffer_copy_string(srv
->tmp_buf
, file_path
);
609 buffer_urldecode_path(srv
->tmp_buf
);
610 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
611 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
616 if (virt_path
[0] == '/') {
617 buffer_copy_string(p
->stat_fn
, virt_path
);
619 /* there is always a / */
620 sl
= strrchr(con
->uri
.path
->ptr
, '/');
622 buffer_copy_string_len(p
->stat_fn
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
623 buffer_append_string(p
->stat_fn
, virt_path
);
626 buffer_urldecode_path(p
->stat_fn
);
627 buffer_path_simplify(srv
->tmp_buf
, p
->stat_fn
);
631 /* Destination physical path (similar to code in mod_webdav.c)
632 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
633 * (but neither modifies con->physical.rel_path)
634 * Find matching prefix to support relative paths to current physical path.
635 * Aliasing of paths underneath current con->physical.basedir might not work.
636 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
637 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
638 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
639 * (Related, use mod_auth to protect this basedir, but avoid attempting to
640 * use mod_auth on paths underneath this basedir, as target path is not
641 * validated with mod_auth)
644 /* find matching URI prefix
645 * check if remaining con->physical.rel_path matches suffix
646 * of con->physical.basedir so that we can use it to
647 * remap Destination physical path */
649 const char *sep
, *sep2
;
650 sep
= con
->uri
.path
->ptr
;
651 sep2
= srv
->tmp_buf
->ptr
;
652 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
653 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
655 if (con
->conf
.force_lowercase_filenames
) {
656 buffer_to_lower(srv
->tmp_buf
);
658 remain
= buffer_string_length(con
->uri
.path
) - i
;
659 if (!con
->conf
.force_lowercase_filenames
660 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
661 :(buffer_string_length(con
->physical
.path
) >= remain
662 && 0 == strncasecmp(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
663 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
664 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
666 /* unable to perform physical path remap here;
667 * assume doc_root/rel_path and no remapping */
668 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
669 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
673 if (0 == stat(p
->stat_fn
->ptr
, &stb
)) {
674 time_t t
= stb
.st_mtime
;
681 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
683 off_t s
= stb
.st_size
;
685 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
687 buffer_copy_int(b
, s
);
688 buffer_append_string(b
, abr
[j
]);
690 buffer_copy_int(b
, stb
.st_size
);
692 chunkqueue_append_buffer(con
->write_queue
, b
);
696 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
697 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
699 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
703 chunkqueue_append_file(con
->write_queue
, p
->stat_fn
, 0, stb
.st_size
);
705 /* Keep the newest mtime of included files */
706 if (stb
.st_mtime
> include_file_last_mtime
)
707 include_file_last_mtime
= stb
.st_mtime
;
712 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
713 "ssi: stating failed ",
714 p
->stat_fn
, strerror(errno
));
719 const char *key
= NULL
, *val
= NULL
;
720 for (i
= 2; i
< n
; i
+= 2) {
721 if (0 == strcmp(l
[i
], "var")) {
723 } else if (0 == strcmp(l
[i
], "value")) {
726 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
727 "ssi: unknown attribute for ",
732 if (p
->if_is_false
) break;
737 if (NULL
== (ds
= (data_string
*)array_get_unused_element(p
->ssi_vars
, TYPE_STRING
))) {
738 ds
= data_string_init();
740 buffer_copy_string(ds
->key
, key
);
741 buffer_copy_string(ds
->value
, val
);
743 array_insert_unique(p
->ssi_vars
, (data_unset
*)ds
);
744 } else if (key
|| val
) {
745 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
746 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
748 log_error_write(srv
, __FILE__
, __LINE__
, "s",
749 "ssi: var and value have to be set in <!--#set var=... value=... -->");
754 if (p
->if_is_false
) break;
756 for (i
= 2; i
< n
; i
+= 2) {
757 if (0 == strcmp(l
[i
], "timefmt")) {
758 buffer_copy_string(p
->timefmt
, l
[i
+1]);
759 } else if (0 == strcmp(l
[i
], "sizefmt")) {
760 if (0 == strcmp(l
[i
+1], "abbrev")) {
762 } else if (0 == strcmp(l
[i
+1], "abbrev")) {
765 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
766 "ssi: unknown value for attribute '",
772 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
773 "ssi: unknown attribute for ",
779 if (p
->if_is_false
) break;
782 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
783 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
785 buffer_append_string_buffer(b
, ds
->key
);
786 buffer_append_string_len(b
, CONST_STR_LEN("="));
787 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
788 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
790 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
791 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
793 buffer_append_string_buffer(b
, ds
->key
);
794 buffer_append_string_len(b
, CONST_STR_LEN("="));
795 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
796 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
798 chunkqueue_append_buffer(con
->write_queue
, b
);
803 const char *cmd
= NULL
;
805 int from_exec_fds
[2];
807 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
811 for (i
= 2; i
< n
; i
+= 2) {
812 if (0 == strcmp(l
[i
], "cmd")) {
815 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
816 "ssi: unknown attribute for ",
821 if (p
->if_is_false
) break;
823 /* create a return pipe and send output to the html-page
825 * as exec is assumed evil it is implemented synchronously
830 if (pipe(from_exec_fds
)) {
831 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
832 "pipe failed: ", strerror(errno
));
837 switch (pid
= fork()) {
839 /* move stdout to from_rrdtool_fd[1] */
840 close(STDOUT_FILENO
);
841 dup2(from_exec_fds
[1], STDOUT_FILENO
);
842 close(from_exec_fds
[1]);
844 close(from_exec_fds
[0]);
849 execl("/bin/sh", "sh", "-c", cmd
, (char *)NULL
);
851 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawing exec failed:", strerror(errno
), cmd
);
859 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fork failed:", strerror(errno
));
865 int was_interrupted
= 0;
867 close(from_exec_fds
[1]);
869 /* wait for the client to end */
872 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
875 if (-1 == waitpid(pid
, &status
, 0)) {
876 if (errno
== EINTR
) {
880 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
882 } else if (WIFEXITED(status
)) {
884 /* read everything from client and paste it into the output */
888 if (ioctl(from_exec_fds
[0], FIONREAD
, &toread
)) {
889 log_error_write(srv
, __FILE__
, __LINE__
, "s",
890 "unexpected end-of-file (perhaps the ssi-exec process died)");
898 chunkqueue_get_memory(con
->write_queue
, &mem
, &mem_len
, 0, toread
);
899 r
= read(from_exec_fds
[0], mem
, mem_len
);
900 chunkqueue_use_memory(con
->write_queue
, r
> 0 ? r
: 0);
902 if (r
< 0) break; /* read failed */
909 log_error_write(srv
, __FILE__
, __LINE__
, "s", "process exited abnormally");
911 } while (was_interrupted
> 0 && was_interrupted
< 4); /* if waitpid() gets interrupted, retry, but max 4 times */
913 close(from_exec_fds
[0]);
926 const char *expr
= NULL
;
928 for (i
= 2; i
< n
; i
+= 2) {
929 if (0 == strcmp(l
[i
], "expr")) {
932 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
933 "ssi: unknown attribute for ",
939 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
941 l
[1], "expr missing");
945 if ((!p
->if_is_false
) &&
946 ((p
->if_is_false_level
== 0) ||
947 (p
->if_level
< p
->if_is_false_level
))) {
948 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
952 p
->if_is_false_level
= p
->if_level
;
967 if (p
->if_is_false
) {
968 if ((p
->if_level
== p
->if_is_false_level
) &&
969 (p
->if_is_false_endif
== 0)) {
975 p
->if_is_false_level
= p
->if_level
;
981 const char *expr
= NULL
;
982 for (i
= 2; i
< n
; i
+= 2) {
983 if (0 == strcmp(l
[i
], "expr")) {
986 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
987 "ssi: unknown attribute for ",
993 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
995 l
[1], "expr missing");
1001 if (p
->if_level
== p
->if_is_false_level
) {
1002 if ((p
->if_is_false
) &&
1003 (p
->if_is_false_endif
== 0)) {
1004 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
1008 p
->if_is_false_level
= p
->if_level
;
1016 p
->if_is_false_level
= p
->if_level
;
1017 p
->if_is_false_endif
= 1;
1028 if (p
->if_level
== p
->if_is_false_level
) {
1030 p
->if_is_false_endif
= 0;
1035 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
1036 "ssi: unknown ssi-command:",
1045 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
1047 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
1049 for (n
= 1; n
< len
; ++n
) {
1050 if (s
[n
] == c
) return n
+1;
1052 if (n
+1 == len
) return 0; /* invalid */
1056 return 0; /* invalid */
1058 for (n
= 0; n
< len
; ++n
) {
1059 if (isspace(s
[n
])) return n
;
1061 if (n
+1 == len
) return 0; /* invalid */
1069 static int mod_ssi_parse_ssi_stmt_offlen(int o
[10], const char * const s
, const int len
) {
1072 * <!--#element attribute=value attribute=value ... -->
1075 /* s must begin "<!--#" and must end with "-->" */
1078 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
1080 if (0 == o
[1]) return -1; /* empty token */
1082 if (n
+3 == len
) return 2; /* token only; no params */
1083 if (!isspace(s
[n
])) return -1;
1084 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1085 if (n
+3 == len
) return 2; /* token only; no params */
1088 for (; light_isalpha(s
[n
]); ++n
) ;
1090 if (0 == o
[3] || s
[n
++] != '=') return -1;
1093 o
[5] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1094 if (0 == o
[5]) return -1; /* empty or invalid token */
1097 if (n
+3 == len
) return 6; /* token and one param */
1098 if (!isspace(s
[n
])) return -1;
1099 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1100 if (n
+3 == len
) return 6; /* token and one param */
1103 for (; light_isalpha(s
[n
]); ++n
) ;
1105 if (0 == o
[7] || s
[n
++] != '=') return -1;
1108 o
[9] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1109 if (0 == o
[9]) return -1; /* empty or invalid token */
1112 if (n
+3 == len
) return 10; /* token and two params */
1113 if (!isspace(s
[n
])) return -1;
1114 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1115 if (n
+3 == len
) return 10; /* token and two params */
1119 static void mod_ssi_parse_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, char *s
, int len
, struct stat
*st
) {
1122 * <!--#element attribute=value attribute=value ... -->
1127 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1128 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1130 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1131 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1136 /* dup s and then modify s */
1137 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1138 l
[0] = malloc((size_t)(len
+1));
1139 memcpy(l
[0], s
, (size_t)len
);
1143 /* modify s in-place to split string into arg tokens */
1144 for (m
= 0; m
< n
; m
+= 2) {
1148 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1149 default: ptr
[o
[m
+1]] = '\0'; break;
1152 if (m
== 4 || m
== 8) {
1153 /* XXX: removing '\\' escapes from param value would be
1154 * the right thing to do, but would potentially change
1155 * current behavior, e.g. <!--#exec cmd=... --> */
1159 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1166 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1167 /* s must begin "<!--#" */
1168 int n
, sq
= 0, dq
= 0, bs
= 0;
1169 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1174 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1177 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
; break;
1179 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
; break;
1181 if (sq
|| dq
) bs
= !bs
; break;
1184 return 0; /* incomplete directive "<!--#...-->" */
1187 static void mod_ssi_read_fd(server
*srv
, connection
*con
, plugin_data
*p
, int fd
, struct stat
*st
) {
1189 size_t offset
, pretag
;
1194 while (0 < (rd
= read(fd
, buf
+offset
, sizeof(buf
)-offset
))) {
1196 size_t prelen
= 0, len
;
1197 offset
+= (size_t)rd
;
1198 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1200 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1201 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1203 if (prelen
- pretag
&& !p
->if_is_false
) {
1204 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1207 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1208 if (len
) { /* num of chars to be consumed */
1209 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1210 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1211 pretag
= prelen
+ 1;
1212 if (pretag
== offset
) {
1213 offset
= pretag
= 0;
1216 } else if (0 == prelen
&& offset
== sizeof(buf
)) { /*(full buf)*/
1217 /* SSI statement is way too long
1218 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1219 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1220 /* check if buf ends with "-" or "--" which might be part of "-->"
1221 * (buf contains at least 5 chars for "<!--#") */
1222 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1223 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1224 } else if (buf
[offset
-1] == '-') {
1225 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1227 offset
= pretag
= 0;
1229 } else { /* incomplete directive "<!--#...-->" */
1230 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1234 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1235 if (prelen
- pretag
&& !p
->if_is_false
) {
1236 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1238 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1242 /* loop to look for next '<' */
1244 if (offset
== sizeof(buf
)) {
1245 if (!p
->if_is_false
) {
1246 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1248 offset
= pretag
= 0;
1253 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1256 if (offset
- pretag
) {
1257 /* copy remaining data in buf */
1258 if (!p
->if_is_false
) {
1259 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1265 /* don't want to block when open()ing a fifo */
1266 #if defined(O_NONBLOCK)
1267 # define FIFO_NONBLOCK O_NONBLOCK
1269 # define FIFO_NONBLOCK 0
1272 static int mod_ssi_handle_request(server
*srv
, connection
*con
, plugin_data
*p
) {
1276 /* get a stream to the file */
1278 array_reset(p
->ssi_vars
);
1279 array_reset(p
->ssi_cgi_env
);
1280 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1282 build_ssi_cgi_vars(srv
, con
, p
);
1285 /* Reset the modified time of included files */
1286 include_file_last_mtime
= 0;
1288 if (-1 == (fd
= open(con
->physical
.path
->ptr
, O_RDONLY
| FIFO_NONBLOCK
))) {
1289 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
1290 "open: ", con
->physical
.path
);
1294 if (0 != fstat(fd
, &st
)) {
1295 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "fstat failed: ", con
->physical
.path
);
1300 mod_ssi_read_fd(srv
, con
, p
, fd
, &st
);
1303 con
->file_started
= 1;
1304 con
->file_finished
= 1;
1307 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1308 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1310 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1313 if (p
->conf
.conditional_requests
) {
1314 /* Generate "ETag" & "Last-Modified" headers */
1315 buffer
*mtime
= NULL
;
1317 /* use most recently modified include file for ETag and Last-Modified */
1318 if (st
.st_mtime
< include_file_last_mtime
)
1319 st
.st_mtime
= include_file_last_mtime
;
1321 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1322 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1324 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1325 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1327 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1328 /* ok, the client already has our content,
1329 * no need to send it again */
1331 chunkqueue_reset(con
->write_queue
);
1335 /* Reset the modified time of included files */
1336 include_file_last_mtime
= 0;
1338 /* reset physical.path */
1339 buffer_reset(con
->physical
.path
);
1346 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1348 plugin_config
*s
= p
->config_storage
[0];
1350 PATCH(ssi_extension
);
1351 PATCH(content_type
);
1352 PATCH(conditional_requests
);
1355 /* skip the first, the global context */
1356 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1357 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1358 s
= p
->config_storage
[i
];
1360 /* condition didn't match */
1361 if (!config_check_cond(srv
, con
, dc
)) continue;
1364 for (j
= 0; j
< dc
->value
->used
; j
++) {
1365 data_unset
*du
= dc
->value
->data
[j
];
1367 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1368 PATCH(ssi_extension
);
1369 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1370 PATCH(content_type
);
1371 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1372 PATCH(conditional_requests
);
1373 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1383 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1384 plugin_data
*p
= p_d
;
1387 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1389 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1391 mod_ssi_patch_connection(srv
, con
, p
);
1393 for (k
= 0; k
< p
->conf
.ssi_extension
->used
; k
++) {
1394 data_string
*ds
= (data_string
*)p
->conf
.ssi_extension
->data
[k
];
1396 if (buffer_is_empty(ds
->value
)) continue;
1398 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
1399 /* handle ssi-request */
1401 if (mod_ssi_handle_request(srv
, con
, p
)) {
1403 con
->http_status
= 500;
1407 return HANDLER_FINISHED
;
1412 return HANDLER_GO_ON
;
1415 /* this function is called at dlopen() time and inits the callbacks */
1417 int mod_ssi_plugin_init(plugin
*p
);
1418 int mod_ssi_plugin_init(plugin
*p
) {
1419 p
->version
= LIGHTTPD_VERSION_ID
;
1420 p
->name
= buffer_init_string("ssi");
1422 p
->init
= mod_ssi_init
;
1423 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1424 p
->set_defaults
= mod_ssi_set_defaults
;
1425 p
->cleanup
= mod_ssi_free
;