14 #include "inet_ntop_cache.h"
16 #include "sys-socket.h"
18 #include <sys/types.h>
35 # include <sys/wait.h>
38 #ifdef HAVE_SYS_FILIO_H
39 # include <sys/filio.h>
44 /* The newest modified time of included files for include statement */
45 static volatile time_t include_file_last_mtime
= 0;
47 /* init the plugin data */
48 INIT_FUNC(mod_ssi_init
) {
51 p
= calloc(1, sizeof(*p
));
53 p
->timefmt
= buffer_init();
54 p
->stat_fn
= buffer_init();
56 p
->ssi_vars
= array_init();
57 p
->ssi_cgi_env
= array_init();
62 /* detroy the plugin data */
63 FREE_FUNC(mod_ssi_free
) {
67 if (!p
) return HANDLER_GO_ON
;
69 if (p
->config_storage
) {
71 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
72 plugin_config
*s
= p
->config_storage
[i
];
74 if (NULL
== s
) continue;
76 array_free(s
->ssi_extension
);
77 buffer_free(s
->content_type
);
81 free(p
->config_storage
);
84 array_free(p
->ssi_vars
);
85 array_free(p
->ssi_cgi_env
);
86 buffer_free(p
->timefmt
);
87 buffer_free(p
->stat_fn
);
94 /* handle plugin config and check values */
96 SETDEFAULTS_FUNC(mod_ssi_set_defaults
) {
100 config_values_t cv
[] = {
101 { "ssi.extension", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
}, /* 0 */
102 { "ssi.content-type", NULL
, T_CONFIG_STRING
, T_CONFIG_SCOPE_CONNECTION
}, /* 1 */
103 { "ssi.conditional-requests", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 2 */
104 { "ssi.exec", NULL
, T_CONFIG_BOOLEAN
, T_CONFIG_SCOPE_CONNECTION
}, /* 3 */
105 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
108 if (!p
) return HANDLER_ERROR
;
110 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
112 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
113 data_config
const* config
= (data_config
const*)srv
->config_context
->data
[i
];
116 s
= calloc(1, sizeof(plugin_config
));
117 s
->ssi_extension
= array_init();
118 s
->content_type
= buffer_init();
119 s
->conditional_requests
= 0;
122 cv
[0].destination
= s
->ssi_extension
;
123 cv
[1].destination
= s
->content_type
;
124 cv
[2].destination
= &(s
->conditional_requests
);
125 cv
[3].destination
= &(s
->ssi_exec
);
127 p
->config_storage
[i
] = s
;
129 if (0 != config_insert_values_global(srv
, config
->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
130 return HANDLER_ERROR
;
134 return HANDLER_GO_ON
;
138 static int ssi_env_add(array
*env
, const char *key
, const char *val
) {
141 if (NULL
== (ds
= (data_string
*)array_get_unused_element(env
, TYPE_STRING
))) {
142 ds
= data_string_init();
144 buffer_copy_string(ds
->key
, key
);
145 buffer_copy_string(ds
->value
, val
);
147 array_insert_unique(env
, (data_unset
*)ds
);
154 * the next two functions are take from fcgi.c
158 static int ssi_env_add_request_headers(server
*srv
, connection
*con
, plugin_data
*p
) {
161 for (i
= 0; i
< con
->request
.headers
->used
; i
++) {
164 ds
= (data_string
*)con
->request
.headers
->data
[i
];
166 if (!buffer_is_empty(ds
->value
) && !buffer_is_empty(ds
->key
)) {
167 /* don't forward the Authorization: Header */
168 if (0 == strcasecmp(ds
->key
->ptr
, "AUTHORIZATION")) {
172 buffer_copy_string_encoded_cgi_varnames(srv
->tmp_buf
, CONST_BUF_LEN(ds
->key
), 1);
174 ssi_env_add(p
->ssi_cgi_env
, srv
->tmp_buf
->ptr
, ds
->value
->ptr
);
178 for (i
= 0; i
< con
->environment
->used
; i
++) {
181 ds
= (data_string
*)con
->environment
->data
[i
];
183 if (!buffer_is_empty(ds
->value
) && !buffer_is_empty(ds
->key
)) {
184 buffer_copy_string_encoded_cgi_varnames(srv
->tmp_buf
, CONST_BUF_LEN(ds
->key
), 0);
186 ssi_env_add(p
->ssi_cgi_env
, srv
->tmp_buf
->ptr
, ds
->value
->ptr
);
193 static int build_ssi_cgi_vars(server
*srv
, connection
*con
, plugin_data
*p
) {
194 char buf
[LI_ITOSTRING_LENGTH
];
196 server_socket
*srv_sock
= con
->srv_socket
;
199 char b2
[INET6_ADDRSTRLEN
+ 1];
202 #define CONST_STRING(x) \
205 array_reset(p
->ssi_cgi_env
);
207 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_SOFTWARE"), con
->conf
.server_tag
->ptr
);
208 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_NAME"),
210 inet_ntop(srv_sock
->addr
.plain
.sa_family
,
211 srv_sock
->addr
.plain
.sa_family
== AF_INET6
?
212 (const void *) &(srv_sock
->addr
.ipv6
.sin6_addr
) :
213 (const void *) &(srv_sock
->addr
.ipv4
.sin_addr
),
216 inet_ntoa(srv_sock
->addr
.ipv4
.sin_addr
)
219 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("GATEWAY_INTERFACE"), "CGI/1.1");
221 li_utostrn(buf
, sizeof(buf
),
223 ntohs(srv_sock
->addr
.plain
.sa_family
? srv_sock
->addr
.ipv6
.sin6_port
: srv_sock
->addr
.ipv4
.sin_port
)
225 ntohs(srv_sock
->addr
.ipv4
.sin_port
)
229 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_PORT"), buf
);
231 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REMOTE_ADDR"),
232 inet_ntop_cache_get_ip(srv
, &(con
->dst_addr
)));
234 if (con
->request
.content_length
> 0) {
235 /* CGI-SPEC 6.1.2 and FastCGI spec 6.3 */
237 li_itostrn(buf
, sizeof(buf
), con
->request
.content_length
);
238 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("CONTENT_LENGTH"), buf
);
242 * SCRIPT_NAME, PATH_INFO and PATH_TRANSLATED according to
243 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html
244 * (6.1.14, 6.1.6, 6.1.7)
247 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SCRIPT_NAME"), con
->uri
.path
->ptr
);
248 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("PATH_INFO"), "");
251 * SCRIPT_FILENAME and DOCUMENT_ROOT for php. The PHP manual
252 * http://www.php.net/manual/en/reserved.variables.php
253 * treatment of PATH_TRANSLATED is different from the one of CGI specs.
254 * TODO: this code should be checked against cgi.fix_pathinfo php
258 if (!buffer_string_is_empty(con
->request
.pathinfo
)) {
259 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("PATH_INFO"), con
->request
.pathinfo
->ptr
);
262 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SCRIPT_FILENAME"), con
->physical
.path
->ptr
);
263 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("DOCUMENT_ROOT"), con
->physical
.basedir
->ptr
);
265 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_URI"), con
->request
.uri
->ptr
);
267 if (!buffer_string_is_empty(con
->uri
.scheme
)) {
268 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_SCHEME"), con
->uri
.scheme
->ptr
);
271 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("QUERY_STRING"), buffer_is_empty(con
->uri
.query
) ? "" : con
->uri
.query
->ptr
);
272 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REQUEST_METHOD"), get_http_method_name(con
->request
.http_method
));
273 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("SERVER_PROTOCOL"), get_http_version_name(con
->request
.http_version
));
274 /* set REDIRECT_STATUS for php compiled with --force-redirect
275 * (if REDIRECT_STATUS has not already been set by error handler) */
276 if (0 == con
->error_handler_saved_status
) {
277 ssi_env_add(p
->ssi_cgi_env
, CONST_STRING("REDIRECT_STATUS"), "200");
280 ssi_env_add_request_headers(srv
, con
, p
);
285 static int process_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, const char **l
, size_t n
, struct stat
*st
) {
288 * <!--#element attribute=value attribute=value ... -->
296 * encoding -- missing
332 * The current date in Greenwich Mean Time.
334 * The current date in the local time zone.
336 * The filename (excluding directories) of the document requested by the user.
338 * 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.
340 * The last modification date of the document requested by the user.
342 * Contains the owner of the file which included it.
346 size_t i
, ssicmd
= 0;
352 enum { SSI_UNSET
, SSI_ECHO
, SSI_FSIZE
, SSI_INCLUDE
, SSI_FLASTMOD
,
353 SSI_CONFIG
, SSI_PRINTENV
, SSI_SET
, SSI_IF
, SSI_ELIF
,
354 SSI_ELSE
, SSI_ENDIF
, SSI_EXEC
} type
;
356 { "echo", SSI_ECHO
},
357 { "include", SSI_INCLUDE
},
358 { "flastmod", SSI_FLASTMOD
},
359 { "fsize", SSI_FSIZE
},
360 { "config", SSI_CONFIG
},
361 { "printenv", SSI_PRINTENV
},
364 { "elif", SSI_ELIF
},
365 { "endif", SSI_ENDIF
},
366 { "else", SSI_ELSE
},
367 { "exec", SSI_EXEC
},
372 for (i
= 0; ssicmds
[i
].var
; i
++) {
373 if (0 == strcmp(l
[1], ssicmds
[i
].var
)) {
374 ssicmd
= ssicmds
[i
].type
;
384 const char *var_val
= NULL
;
392 SSI_ECHO_DOCUMENT_NAME
,
393 SSI_ECHO_DOCUMENT_URI
,
394 SSI_ECHO_LAST_MODIFIED
,
400 { "DATE_GMT", SSI_ECHO_DATE_GMT
},
401 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL
},
402 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME
},
403 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI
},
404 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED
},
405 { "USER_NAME", SSI_ECHO_USER_NAME
},
406 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI
},
407 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL
},
409 { NULL
, SSI_ECHO_UNSET
}
415 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
417 { "url", SSI_ENC_URL },
418 { "none", SSI_ENC_NONE },
419 { "entity", SSI_ENC_ENTITY },
421 { NULL, SSI_ENC_UNSET }
425 for (i
= 2; i
< n
; i
+= 2) {
426 if (0 == strcmp(l
[i
], "var")) {
431 for (j
= 0; echovars
[j
].var
; j
++) {
432 if (0 == strcmp(l
[i
+1], echovars
[j
].var
)) {
433 var
= echovars
[j
].type
;
437 } else if (0 == strcmp(l
[i
], "encoding")) {
441 for (j = 0; encvars[j].var; j++) {
442 if (0 == strcmp(l[i+1], encvars[j].var)) {
443 enc = encvars[j].type;
449 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
450 "ssi: unknown attribute for ",
455 if (p
->if_is_false
) break;
458 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
460 l
[1], "var is missing");
465 case SSI_ECHO_USER_NAME
: {
470 if (NULL
== (pw
= getpwuid(st
->st_uid
))) {
471 buffer_copy_int(b
, st
->st_uid
);
473 buffer_copy_string(b
, pw
->pw_name
);
476 buffer_copy_int(b
, st
->st_uid
);
478 chunkqueue_append_buffer(con
->write_queue
, b
);
482 case SSI_ECHO_LAST_MODIFIED
: {
483 time_t t
= st
->st_mtime
;
485 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
486 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
488 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
492 case SSI_ECHO_DATE_LOCAL
: {
493 time_t t
= time(NULL
);
495 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
496 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
498 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
502 case SSI_ECHO_DATE_GMT
: {
503 time_t t
= time(NULL
);
505 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, gmtime(&t
))) {
506 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
508 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
512 case SSI_ECHO_DOCUMENT_NAME
: {
515 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
516 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->physical
.path
));
518 chunkqueue_append_mem(con
->write_queue
, sl
+ 1, strlen(sl
+ 1));
522 case SSI_ECHO_DOCUMENT_URI
: {
523 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.path
));
526 case SSI_ECHO_SCRIPT_URI
: {
527 if (!buffer_string_is_empty(con
->uri
.scheme
) && !buffer_string_is_empty(con
->uri
.authority
)) {
528 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.scheme
));
529 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("://"));
530 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.authority
));
531 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
532 if (!buffer_string_is_empty(con
->uri
.query
)) {
533 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
534 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
539 case SSI_ECHO_SCRIPT_URL
: {
540 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->request
.uri
));
541 if (!buffer_string_is_empty(con
->uri
.query
)) {
542 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("?"));
543 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(con
->uri
.query
));
549 /* check if it is a cgi-var or a ssi-var */
551 if (NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_cgi_env
, var_val
)) ||
552 NULL
!= (ds
= (data_string
*)array_get_element(p
->ssi_vars
, var_val
))) {
553 chunkqueue_append_mem(con
->write_queue
, CONST_BUF_LEN(ds
->value
));
555 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
566 const char * file_path
= NULL
, *virt_path
= NULL
;
570 for (i
= 2; i
< n
; i
+= 2) {
571 if (0 == strcmp(l
[i
], "file")) {
573 } else if (0 == strcmp(l
[i
], "virtual")) {
576 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
577 "ssi: unknown attribute for ",
582 if (!file_path
&& !virt_path
) {
583 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
585 l
[1], "file or virtual are missing");
589 if (file_path
&& virt_path
) {
590 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
592 l
[1], "only one of file and virtual is allowed here");
597 if (p
->if_is_false
) break;
600 /* current doc-root */
601 if (NULL
== (sl
= strrchr(con
->physical
.path
->ptr
, '/'))) {
602 buffer_copy_string_len(p
->stat_fn
, CONST_STR_LEN("/"));
604 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, sl
- con
->physical
.path
->ptr
+ 1);
607 buffer_copy_string(srv
->tmp_buf
, file_path
);
608 buffer_urldecode_path(srv
->tmp_buf
);
609 buffer_path_simplify(srv
->tmp_buf
, srv
->tmp_buf
);
610 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
615 if (virt_path
[0] == '/') {
616 buffer_copy_string(p
->stat_fn
, virt_path
);
618 /* there is always a / */
619 sl
= strrchr(con
->uri
.path
->ptr
, '/');
621 buffer_copy_string_len(p
->stat_fn
, con
->uri
.path
->ptr
, sl
- con
->uri
.path
->ptr
+ 1);
622 buffer_append_string(p
->stat_fn
, virt_path
);
625 buffer_urldecode_path(p
->stat_fn
);
626 buffer_path_simplify(srv
->tmp_buf
, p
->stat_fn
);
630 /* Destination physical path (similar to code in mod_webdav.c)
631 * src con->physical.path might have been remapped with mod_alias, mod_userdir.
632 * (but neither modifies con->physical.rel_path)
633 * Find matching prefix to support relative paths to current physical path.
634 * Aliasing of paths underneath current con->physical.basedir might not work.
635 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
636 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
637 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
638 * (Related, use mod_auth to protect this basedir, but avoid attempting to
639 * use mod_auth on paths underneath this basedir, as target path is not
640 * validated with mod_auth)
643 /* find matching URI prefix
644 * check if remaining con->physical.rel_path matches suffix
645 * of con->physical.basedir so that we can use it to
646 * remap Destination physical path */
648 const char *sep
, *sep2
;
649 sep
= con
->uri
.path
->ptr
;
650 sep2
= srv
->tmp_buf
->ptr
;
651 for (i
= 0; sep
[i
] && sep
[i
] == sep2
[i
]; ++i
) ;
652 while (i
!= 0 && sep
[--i
] != '/') ; /* find matching directory path */
654 if (con
->conf
.force_lowercase_filenames
) {
655 buffer_to_lower(srv
->tmp_buf
);
657 remain
= buffer_string_length(con
->uri
.path
) - i
;
658 if (!con
->conf
.force_lowercase_filenames
659 ? buffer_is_equal_right_len(con
->physical
.path
, con
->physical
.rel_path
, remain
)
660 :(buffer_string_length(con
->physical
.path
) >= remain
661 && 0 == strncasecmp(con
->physical
.path
->ptr
+buffer_string_length(con
->physical
.path
)-remain
, con
->physical
.rel_path
->ptr
+i
, remain
))) {
662 buffer_copy_string_len(p
->stat_fn
, con
->physical
.path
->ptr
, buffer_string_length(con
->physical
.path
)-remain
);
663 buffer_append_string_len(p
->stat_fn
, srv
->tmp_buf
->ptr
+i
, buffer_string_length(srv
->tmp_buf
)-i
);
665 /* unable to perform physical path remap here;
666 * assume doc_root/rel_path and no remapping */
667 buffer_copy_buffer(p
->stat_fn
, con
->physical
.doc_root
);
668 buffer_append_string_buffer(p
->stat_fn
, srv
->tmp_buf
);
672 if (0 == stat(p
->stat_fn
->ptr
, &stb
)) {
673 time_t t
= stb
.st_mtime
;
680 const char *abr
[] = { " B", " kB", " MB", " GB", " TB", NULL
};
682 off_t s
= stb
.st_size
;
684 for (j
= 0; s
> 1024 && abr
[j
+1]; s
/= 1024, j
++);
686 buffer_copy_int(b
, s
);
687 buffer_append_string(b
, abr
[j
]);
689 buffer_copy_int(b
, stb
.st_size
);
691 chunkqueue_append_buffer(con
->write_queue
, b
);
695 if (0 == strftime(buf
, sizeof(buf
), p
->timefmt
->ptr
, localtime(&t
))) {
696 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("(none)"));
698 chunkqueue_append_mem(con
->write_queue
, buf
, strlen(buf
));
702 chunkqueue_append_file(con
->write_queue
, p
->stat_fn
, 0, stb
.st_size
);
704 /* Keep the newest mtime of included files */
705 if (stb
.st_mtime
> include_file_last_mtime
)
706 include_file_last_mtime
= stb
.st_mtime
;
711 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
712 "ssi: stating failed ",
713 p
->stat_fn
, strerror(errno
));
718 const char *key
= NULL
, *val
= NULL
;
719 for (i
= 2; i
< n
; i
+= 2) {
720 if (0 == strcmp(l
[i
], "var")) {
722 } else if (0 == strcmp(l
[i
], "value")) {
725 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
726 "ssi: unknown attribute for ",
731 if (p
->if_is_false
) break;
736 if (NULL
== (ds
= (data_string
*)array_get_unused_element(p
->ssi_vars
, TYPE_STRING
))) {
737 ds
= data_string_init();
739 buffer_copy_string(ds
->key
, key
);
740 buffer_copy_string(ds
->value
, val
);
742 array_insert_unique(p
->ssi_vars
, (data_unset
*)ds
);
743 } else if (key
|| val
) {
744 log_error_write(srv
, __FILE__
, __LINE__
, "sSSss",
745 "ssi: var and value have to be set in <!--#set", l
[1], "=", l
[2], "-->");
747 log_error_write(srv
, __FILE__
, __LINE__
, "s",
748 "ssi: var and value have to be set in <!--#set var=... value=... -->");
753 if (p
->if_is_false
) break;
755 for (i
= 2; i
< n
; i
+= 2) {
756 if (0 == strcmp(l
[i
], "timefmt")) {
757 buffer_copy_string(p
->timefmt
, l
[i
+1]);
758 } else if (0 == strcmp(l
[i
], "sizefmt")) {
759 if (0 == strcmp(l
[i
+1], "abbrev")) {
761 } else if (0 == strcmp(l
[i
+1], "bytes")) {
764 log_error_write(srv
, __FILE__
, __LINE__
, "sssss",
765 "ssi: unknown value for attribute '",
771 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
772 "ssi: unknown attribute for ",
778 if (p
->if_is_false
) break;
781 for (i
= 0; i
< p
->ssi_vars
->used
; i
++) {
782 data_string
*ds
= (data_string
*)p
->ssi_vars
->data
[p
->ssi_vars
->sorted
[i
]];
784 buffer_append_string_buffer(b
, ds
->key
);
785 buffer_append_string_len(b
, CONST_STR_LEN("="));
786 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
787 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
789 for (i
= 0; i
< p
->ssi_cgi_env
->used
; i
++) {
790 data_string
*ds
= (data_string
*)p
->ssi_cgi_env
->data
[p
->ssi_cgi_env
->sorted
[i
]];
792 buffer_append_string_buffer(b
, ds
->key
);
793 buffer_append_string_len(b
, CONST_STR_LEN("="));
794 buffer_append_string_encoded(b
, CONST_BUF_LEN(ds
->value
), ENCODING_MINIMAL_XML
);
795 buffer_append_string_len(b
, CONST_STR_LEN("\n"));
797 chunkqueue_append_buffer(con
->write_queue
, b
);
802 const char *cmd
= NULL
;
804 int from_exec_fds
[2];
806 if (!p
->conf
.ssi_exec
) { /* <!--#exec ... --> disabled by config */
810 for (i
= 2; i
< n
; i
+= 2) {
811 if (0 == strcmp(l
[i
], "cmd")) {
814 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
815 "ssi: unknown attribute for ",
820 if (p
->if_is_false
) break;
822 /* create a return pipe and send output to the html-page
824 * as exec is assumed evil it is implemented synchronously
829 if (pipe(from_exec_fds
)) {
830 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
831 "pipe failed: ", strerror(errno
));
836 switch (pid
= fork()) {
838 /* move stdout to from_rrdtool_fd[1] */
839 close(STDOUT_FILENO
);
840 dup2(from_exec_fds
[1], STDOUT_FILENO
);
841 close(from_exec_fds
[1]);
843 close(from_exec_fds
[0]);
848 execl("/bin/sh", "sh", "-c", cmd
, (char *)NULL
);
850 log_error_write(srv
, __FILE__
, __LINE__
, "sss", "spawing exec failed:", strerror(errno
), cmd
);
858 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "fork failed:", strerror(errno
));
864 int was_interrupted
= 0;
866 close(from_exec_fds
[1]);
868 /* wait for the client to end */
871 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
874 if (-1 == waitpid(pid
, &status
, 0)) {
875 if (errno
== EINTR
) {
879 log_error_write(srv
, __FILE__
, __LINE__
, "ss", "waitpid failed:", strerror(errno
));
881 } else if (WIFEXITED(status
)) {
883 /* read everything from client and paste it into the output */
887 if (ioctl(from_exec_fds
[0], FIONREAD
, &toread
)) {
888 log_error_write(srv
, __FILE__
, __LINE__
, "s",
889 "unexpected end-of-file (perhaps the ssi-exec process died)");
897 chunkqueue_get_memory(con
->write_queue
, &mem
, &mem_len
, 0, toread
);
898 r
= read(from_exec_fds
[0], mem
, mem_len
);
899 chunkqueue_use_memory(con
->write_queue
, r
> 0 ? r
: 0);
901 if (r
< 0) break; /* read failed */
908 log_error_write(srv
, __FILE__
, __LINE__
, "s", "process exited abnormally");
910 } while (was_interrupted
> 0 && was_interrupted
< 4); /* if waitpid() gets interrupted, retry, but max 4 times */
912 close(from_exec_fds
[0]);
925 const char *expr
= NULL
;
927 for (i
= 2; i
< n
; i
+= 2) {
928 if (0 == strcmp(l
[i
], "expr")) {
931 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
932 "ssi: unknown attribute for ",
938 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
940 l
[1], "expr missing");
944 if ((!p
->if_is_false
) &&
945 ((p
->if_is_false_level
== 0) ||
946 (p
->if_level
< p
->if_is_false_level
))) {
947 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
951 p
->if_is_false_level
= p
->if_level
;
966 if (p
->if_is_false
) {
967 if ((p
->if_level
== p
->if_is_false_level
) &&
968 (p
->if_is_false_endif
== 0)) {
974 p
->if_is_false_level
= p
->if_level
;
980 const char *expr
= NULL
;
981 for (i
= 2; i
< n
; i
+= 2) {
982 if (0 == strcmp(l
[i
], "expr")) {
985 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
986 "ssi: unknown attribute for ",
992 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
994 l
[1], "expr missing");
1000 if (p
->if_level
== p
->if_is_false_level
) {
1001 if ((p
->if_is_false
) &&
1002 (p
->if_is_false_endif
== 0)) {
1003 switch (ssi_eval_expr(srv
, con
, p
, expr
)) {
1007 p
->if_is_false_level
= p
->if_level
;
1015 p
->if_is_false_level
= p
->if_level
;
1016 p
->if_is_false_endif
= 1;
1027 if (p
->if_level
== p
->if_is_false_level
) {
1029 p
->if_is_false_endif
= 0;
1034 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
1035 "ssi: unknown ssi-command:",
1044 static int mod_ssi_parse_ssi_stmt_value(const char * const s
, const int len
) {
1046 const int c
= (s
[0] == '"' ? '"' : s
[0] == '\'' ? '\'' : 0);
1048 for (n
= 1; n
< len
; ++n
) {
1049 if (s
[n
] == c
) return n
+1;
1051 if (n
+1 == len
) return 0; /* invalid */
1055 return 0; /* invalid */
1057 for (n
= 0; n
< len
; ++n
) {
1058 if (isspace(s
[n
])) return n
;
1060 if (n
+1 == len
) return 0; /* invalid */
1068 static int mod_ssi_parse_ssi_stmt_offlen(int o
[10], const char * const s
, const int len
) {
1071 * <!--#element attribute=value attribute=value ... -->
1074 /* s must begin "<!--#" and must end with "-->" */
1077 for (; light_isalpha(s
[n
]); ++n
) ; /*(n = 5 to begin after "<!--#")*/
1079 if (0 == o
[1]) return -1; /* empty token */
1081 if (n
+3 == len
) return 2; /* token only; no params */
1082 if (!isspace(s
[n
])) return -1;
1083 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1084 if (n
+3 == len
) return 2; /* token only; no params */
1087 for (; light_isalpha(s
[n
]); ++n
) ;
1089 if (0 == o
[3] || s
[n
++] != '=') return -1;
1092 o
[5] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1093 if (0 == o
[5]) return -1; /* empty or invalid token */
1096 if (n
+3 == len
) return 6; /* token and one param */
1097 if (!isspace(s
[n
])) return -1;
1098 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1099 if (n
+3 == len
) return 6; /* token and one param */
1102 for (; light_isalpha(s
[n
]); ++n
) ;
1104 if (0 == o
[7] || s
[n
++] != '=') return -1;
1107 o
[9] = mod_ssi_parse_ssi_stmt_value(s
+n
, len
-n
-3);
1108 if (0 == o
[9]) return -1; /* empty or invalid token */
1111 if (n
+3 == len
) return 10; /* token and two params */
1112 if (!isspace(s
[n
])) return -1;
1113 do { ++n
; } while (isspace(s
[n
])); /* string ends "-->", so n < len */
1114 if (n
+3 == len
) return 10; /* token and two params */
1118 static void mod_ssi_parse_ssi_stmt(server
*srv
, connection
*con
, plugin_data
*p
, char *s
, int len
, struct stat
*st
) {
1121 * <!--#element attribute=value attribute=value ... -->
1126 const int n
= mod_ssi_parse_ssi_stmt_offlen(o
, s
, len
);
1127 char *l
[6] = { s
, NULL
, NULL
, NULL
, NULL
, NULL
};
1129 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1130 chunkqueue_append_mem(con
->write_queue
, s
, len
); /* append stmt as-is */
1135 /* dup s and then modify s */
1136 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1137 l
[0] = malloc((size_t)(len
+1));
1138 memcpy(l
[0], s
, (size_t)len
);
1142 /* modify s in-place to split string into arg tokens */
1143 for (m
= 0; m
< n
; m
+= 2) {
1147 case '\'': (++ptr
)[o
[m
+1]-2] = '\0'; break;
1148 default: ptr
[o
[m
+1]] = '\0'; break;
1151 if (m
== 4 || m
== 8) {
1152 /* XXX: removing '\\' escapes from param value would be
1153 * the right thing to do, but would potentially change
1154 * current behavior, e.g. <!--#exec cmd=... --> */
1158 process_ssi_stmt(srv
, con
, p
, (const char **)l
, 1+(n
>>1), st
);
1165 static int mod_ssi_stmt_len(const char *s
, const int len
) {
1166 /* s must begin "<!--#" */
1167 int n
, sq
= 0, dq
= 0, bs
= 0;
1168 for (n
= 5; n
< len
; ++n
) { /*(n = 5 to begin after "<!--#")*/
1173 if (!sq
&& !dq
&& n
+2 < len
&& s
[n
+1] == '-' && s
[n
+2] == '>') return n
+3; /* found end of stmt */
1176 if (!sq
&& (!dq
|| !bs
)) dq
= !dq
;
1179 if (!dq
&& (!sq
|| !bs
)) sq
= !sq
;
1182 if (sq
|| dq
) bs
= !bs
;
1186 return 0; /* incomplete directive "<!--#...-->" */
1189 static void mod_ssi_read_fd(server
*srv
, connection
*con
, plugin_data
*p
, int fd
, struct stat
*st
) {
1191 size_t offset
, pretag
;
1196 while (0 < (rd
= read(fd
, buf
+offset
, sizeof(buf
)-offset
))) {
1198 size_t prelen
= 0, len
;
1199 offset
+= (size_t)rd
;
1200 for (; (s
= memchr(buf
+prelen
, '<', offset
-prelen
)); ++prelen
) {
1202 if (prelen
+ 5 <= offset
) { /*("<!--#" is 5 chars)*/
1203 if (0 != memcmp(s
+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1205 if (prelen
- pretag
&& !p
->if_is_false
) {
1206 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1209 len
= mod_ssi_stmt_len(buf
+prelen
, offset
-prelen
);
1210 if (len
) { /* num of chars to be consumed */
1211 mod_ssi_parse_ssi_stmt(srv
, con
, p
, buf
+prelen
, len
, st
);
1212 prelen
+= (len
- 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1213 pretag
= prelen
+ 1;
1214 if (pretag
== offset
) {
1215 offset
= pretag
= 0;
1218 } else if (0 == prelen
&& offset
== sizeof(buf
)) { /*(full buf)*/
1219 /* SSI statement is way too long
1220 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1221 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1222 /* check if buf ends with "-" or "--" which might be part of "-->"
1223 * (buf contains at least 5 chars for "<!--#") */
1224 if (buf
[offset
-2] == '-' && buf
[offset
-1] == '-') {
1225 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("--"));
1226 } else if (buf
[offset
-1] == '-') {
1227 chunkqueue_append_mem(con
->write_queue
, CONST_STR_LEN("-"));
1229 offset
= pretag
= 0;
1231 } else { /* incomplete directive "<!--#...-->" */
1232 memmove(buf
, buf
+prelen
, (offset
-= prelen
));
1236 } else if (prelen
+ 1 == offset
|| 0 == memcmp(s
+1, "!--", offset
- prelen
- 1)) {
1237 if (prelen
- pretag
&& !p
->if_is_false
) {
1238 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, prelen
-pretag
);
1240 memcpy(buf
, buf
+prelen
, (offset
-= prelen
));
1244 /* loop to look for next '<' */
1246 if (offset
== sizeof(buf
)) {
1247 if (!p
->if_is_false
) {
1248 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1250 offset
= pretag
= 0;
1255 log_error_write(srv
, __FILE__
, __LINE__
, "SsB", "read(): ", strerror(errno
), con
->physical
.path
);
1258 if (offset
- pretag
) {
1259 /* copy remaining data in buf */
1260 if (!p
->if_is_false
) {
1261 chunkqueue_append_mem(con
->write_queue
, buf
+pretag
, offset
-pretag
);
1267 /* don't want to block when open()ing a fifo */
1268 #if defined(O_NONBLOCK)
1269 # define FIFO_NONBLOCK O_NONBLOCK
1271 # define FIFO_NONBLOCK 0
1274 static int mod_ssi_handle_request(server
*srv
, connection
*con
, plugin_data
*p
) {
1278 /* get a stream to the file */
1280 array_reset(p
->ssi_vars
);
1281 array_reset(p
->ssi_cgi_env
);
1282 buffer_copy_string_len(p
->timefmt
, CONST_STR_LEN("%a, %d %b %Y %H:%M:%S %Z"));
1284 build_ssi_cgi_vars(srv
, con
, p
);
1287 /* Reset the modified time of included files */
1288 include_file_last_mtime
= 0;
1290 if (-1 == (fd
= open(con
->physical
.path
->ptr
, O_RDONLY
| FIFO_NONBLOCK
))) {
1291 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
1292 "open: ", con
->physical
.path
);
1296 if (0 != fstat(fd
, &st
)) {
1297 log_error_write(srv
, __FILE__
, __LINE__
, "SB", "fstat failed: ", con
->physical
.path
);
1302 mod_ssi_read_fd(srv
, con
, p
, fd
, &st
);
1305 con
->file_started
= 1;
1306 con
->file_finished
= 1;
1309 if (buffer_string_is_empty(p
->conf
.content_type
)) {
1310 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1312 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Type"), CONST_BUF_LEN(p
->conf
.content_type
));
1315 if (p
->conf
.conditional_requests
) {
1316 /* Generate "ETag" & "Last-Modified" headers */
1317 buffer
*mtime
= NULL
;
1319 /* use most recently modified include file for ETag and Last-Modified */
1320 if (st
.st_mtime
< include_file_last_mtime
)
1321 st
.st_mtime
= include_file_last_mtime
;
1323 etag_create(con
->physical
.etag
, &st
, con
->etag_flags
);
1324 response_header_overwrite(srv
, con
, CONST_STR_LEN("ETag"), CONST_BUF_LEN(con
->physical
.etag
));
1326 mtime
= strftime_cache_get(srv
, st
.st_mtime
);
1327 response_header_overwrite(srv
, con
, CONST_STR_LEN("Last-Modified"), CONST_BUF_LEN(mtime
));
1329 if (HANDLER_FINISHED
== http_response_handle_cachable(srv
, con
, mtime
)) {
1330 /* ok, the client already has our content,
1331 * no need to send it again */
1333 chunkqueue_reset(con
->write_queue
);
1337 /* Reset the modified time of included files */
1338 include_file_last_mtime
= 0;
1340 /* reset physical.path */
1341 buffer_reset(con
->physical
.path
);
1348 static int mod_ssi_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
1350 plugin_config
*s
= p
->config_storage
[0];
1352 PATCH(ssi_extension
);
1353 PATCH(content_type
);
1354 PATCH(conditional_requests
);
1357 /* skip the first, the global context */
1358 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
1359 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
1360 s
= p
->config_storage
[i
];
1362 /* condition didn't match */
1363 if (!config_check_cond(srv
, con
, dc
)) continue;
1366 for (j
= 0; j
< dc
->value
->used
; j
++) {
1367 data_unset
*du
= dc
->value
->data
[j
];
1369 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.extension"))) {
1370 PATCH(ssi_extension
);
1371 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.content-type"))) {
1372 PATCH(content_type
);
1373 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.conditional-requests"))) {
1374 PATCH(conditional_requests
);
1375 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("ssi.exec"))) {
1385 URIHANDLER_FUNC(mod_ssi_physical_path
) {
1386 plugin_data
*p
= p_d
;
1389 if (con
->mode
!= DIRECT
) return HANDLER_GO_ON
;
1391 if (buffer_is_empty(con
->physical
.path
)) return HANDLER_GO_ON
;
1393 mod_ssi_patch_connection(srv
, con
, p
);
1395 for (k
= 0; k
< p
->conf
.ssi_extension
->used
; k
++) {
1396 data_string
*ds
= (data_string
*)p
->conf
.ssi_extension
->data
[k
];
1398 if (buffer_is_empty(ds
->value
)) continue;
1400 if (buffer_is_equal_right_len(con
->physical
.path
, ds
->value
, buffer_string_length(ds
->value
))) {
1401 /* handle ssi-request */
1403 if (mod_ssi_handle_request(srv
, con
, p
)) {
1405 con
->http_status
= 500;
1409 return HANDLER_FINISHED
;
1414 return HANDLER_GO_ON
;
1417 /* this function is called at dlopen() time and inits the callbacks */
1419 int mod_ssi_plugin_init(plugin
*p
);
1420 int mod_ssi_plugin_init(plugin
*p
) {
1421 p
->version
= LIGHTTPD_VERSION_ID
;
1422 p
->name
= buffer_init_string("ssi");
1424 p
->init
= mod_ssi_init
;
1425 p
->handle_subrequest_start
= mod_ssi_physical_path
;
1426 p
->set_defaults
= mod_ssi_set_defaults
;
1427 p
->cleanup
= mod_ssi_free
;