1 #include "first.h"
2
3 #include "fdevent.h"
4 #include "fdlog.h"
5 #include "log.h"
6 #include "array.h"
7 #include "buffer.h"
8 #include "chunk.h"
9 #include "http_cgi.h"
10 #include "http_chunk.h"
11 #include "http_etag.h"
12 #include "http_header.h"
13 #include "request.h"
14 #include "stat_cache.h"
15
16 #include "plugin.h"
17
18 #include "response.h"
19
20 #include "sys-socket.h"
21 #include "sys-time.h"
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #ifdef HAVE_SYS_WAIT_H
26 #include <sys/wait.h>
27 #endif
28
29 #include <ctype.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35
36 #ifdef HAVE_PWD_H
37 # include <pwd.h>
38 #endif
39
40 typedef struct {
41 const array *ssi_extension;
42 const buffer *content_type;
43 unsigned short conditional_requests;
44 unsigned short ssi_exec;
45 unsigned short ssi_recursion_max;
46 } plugin_config;
47
48 typedef struct {
49 PLUGIN_DATA;
50 plugin_config defaults;
51 plugin_config conf;
52 array *ssi_vars;
53 array *ssi_cgi_env;
54 buffer stat_fn;
55 buffer timefmt;
56 } plugin_data;
57
58 typedef struct {
59 array *ssi_vars;
60 array *ssi_cgi_env;
61 buffer *stat_fn;
62 buffer *timefmt;
63 int sizefmt;
64
65 int if_level, if_is_false_level, if_is_false, if_is_false_endif;
66 unsigned short ssi_recursion_depth;
67
68 chunkqueue wq;
69 log_error_st *errh;
70 plugin_config conf;
71 } handler_ctx;
72
handler_ctx_init(plugin_data * p,log_error_st * errh)73 static handler_ctx * handler_ctx_init(plugin_data *p, log_error_st *errh) {
74 handler_ctx *hctx = ck_calloc(1, sizeof(*hctx));
75 hctx->errh = errh;
76 hctx->timefmt = &p->timefmt;
77 hctx->stat_fn = &p->stat_fn;
78 hctx->ssi_vars = p->ssi_vars;
79 hctx->ssi_cgi_env = p->ssi_cgi_env;
80 memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
81 return hctx;
82 }
83
handler_ctx_free(handler_ctx * hctx)84 static void handler_ctx_free(handler_ctx *hctx) {
85 chunkqueue_reset(&hctx->wq);
86 free(hctx);
87 }
88
89 /* The newest modified time of included files for include statement */
90 static volatile unix_time64_t include_file_last_mtime = 0;
91
INIT_FUNC(mod_ssi_init)92 INIT_FUNC(mod_ssi_init) {
93 plugin_data * const p = ck_calloc(1, sizeof(*p));
94 p->ssi_vars = array_init(8);
95 p->ssi_cgi_env = array_init(32);
96 return p;
97 }
98
FREE_FUNC(mod_ssi_free)99 FREE_FUNC(mod_ssi_free) {
100 plugin_data *p = p_d;
101 array_free(p->ssi_vars);
102 array_free(p->ssi_cgi_env);
103 free(p->timefmt.ptr);
104 free(p->stat_fn.ptr);
105 }
106
mod_ssi_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)107 static void mod_ssi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
108 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
109 case 0: /* ssi.extension */
110 pconf->ssi_extension = cpv->v.a;
111 break;
112 case 1: /* ssi.content-type */
113 pconf->content_type = cpv->v.b;
114 break;
115 case 2: /* ssi.conditional-requests */
116 pconf->conditional_requests = cpv->v.u;
117 break;
118 case 3: /* ssi.exec */
119 pconf->ssi_exec = cpv->v.u;
120 break;
121 case 4: /* ssi.recursion-max */
122 pconf->ssi_recursion_max = cpv->v.shrt;
123 break;
124 default:/* should not happen */
125 return;
126 }
127 }
128
mod_ssi_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)129 static void mod_ssi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
130 do {
131 mod_ssi_merge_config_cpv(pconf, cpv);
132 } while ((++cpv)->k_id != -1);
133 }
134
mod_ssi_patch_config(request_st * const r,plugin_data * const p)135 static void mod_ssi_patch_config(request_st * const r, plugin_data * const p) {
136 memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
137 for (int i = 1, used = p->nconfig; i < used; ++i) {
138 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
139 mod_ssi_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
140 }
141 }
142
SETDEFAULTS_FUNC(mod_ssi_set_defaults)143 SETDEFAULTS_FUNC(mod_ssi_set_defaults) {
144 static const config_plugin_keys_t cpk[] = {
145 { CONST_STR_LEN("ssi.extension"),
146 T_CONFIG_ARRAY_VLIST,
147 T_CONFIG_SCOPE_CONNECTION }
148 ,{ CONST_STR_LEN("ssi.content-type"),
149 T_CONFIG_STRING,
150 T_CONFIG_SCOPE_CONNECTION }
151 ,{ CONST_STR_LEN("ssi.conditional-requests"),
152 T_CONFIG_BOOL,
153 T_CONFIG_SCOPE_CONNECTION }
154 ,{ CONST_STR_LEN("ssi.exec"),
155 T_CONFIG_BOOL,
156 T_CONFIG_SCOPE_CONNECTION }
157 ,{ CONST_STR_LEN("ssi.recursion-max"),
158 T_CONFIG_SHORT,
159 T_CONFIG_SCOPE_CONNECTION }
160 ,{ NULL, 0,
161 T_CONFIG_UNSET,
162 T_CONFIG_SCOPE_UNSET }
163 };
164
165 plugin_data * const p = p_d;
166 if (!config_plugin_values_init(srv, p, cpk, "mod_ssi"))
167 return HANDLER_ERROR;
168
169 /* process and validate config directives
170 * (init i to 0 if global context; to 1 to skip empty global context) */
171 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
172 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
173 for (; -1 != cpv->k_id; ++cpv) {
174 switch (cpv->k_id) {
175 case 0: /* ssi.extension */
176 break;
177 case 1: /* ssi.content-type */
178 if (buffer_is_blank(cpv->v.b))
179 cpv->v.b = NULL;
180 break;
181 case 2: /* ssi.conditional-requests */
182 case 3: /* ssi.exec */
183 case 4: /* ssi.recursion-max */
184 break;
185 default:/* should not happen */
186 break;
187 }
188 }
189 }
190
191 p->defaults.ssi_exec = 1;
192
193 /* initialize p->defaults from global config context */
194 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
195 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
196 if (-1 != cpv->k_id)
197 mod_ssi_merge_config(&p->defaults, cpv);
198 }
199
200 return HANDLER_GO_ON;
201 }
202
203
204
205
206 #define TK_AND 1
207 #define TK_OR 2
208 #define TK_EQ 3
209 #define TK_NE 4
210 #define TK_GT 5
211 #define TK_GE 6
212 #define TK_LT 7
213 #define TK_LE 8
214 #define TK_NOT 9
215 #define TK_LPARAN 10
216 #define TK_RPARAN 11
217 #define TK_VALUE 12
218
219 typedef struct {
220 const char *input;
221 size_t offset;
222 size_t size;
223 int in_brace;
224 int depth;
225 handler_ctx *p;
226 } ssi_tokenizer_t;
227
228 typedef struct {
229 buffer str;
230 enum { SSI_TYPE_UNSET, SSI_TYPE_BOOL, SSI_TYPE_STRING } type;
231 int bo;
232 } ssi_val_t;
233
234 __attribute_pure__
ssi_val_tobool(const ssi_val_t * B)235 static int ssi_val_tobool(const ssi_val_t *B) {
236 return B->type == SSI_TYPE_BOOL ? B->bo : !buffer_is_blank(&B->str);
237 }
238
239 __attribute_pure__
ssi_eval_expr_cmp(const ssi_val_t * const v1,const ssi_val_t * const v2,const int cond)240 static int ssi_eval_expr_cmp(const ssi_val_t * const v1, const ssi_val_t * const v2, const int cond) {
241 int cmp = (v1->type != SSI_TYPE_BOOL && v2->type != SSI_TYPE_BOOL)
242 ? strcmp(v1->str.ptr ? v1->str.ptr : "",
243 v2->str.ptr ? v2->str.ptr : "")
244 : ssi_val_tobool(v1) - ssi_val_tobool(v2);
245 switch (cond) {
246 case TK_EQ: return (cmp == 0);
247 case TK_NE: return (cmp != 0);
248 case TK_GE: return (cmp >= 0);
249 case TK_GT: return (cmp > 0);
250 case TK_LE: return (cmp <= 0);
251 case TK_LT: return (cmp < 0);
252 default: return 0;/*(should not happen)*/
253 }
254 }
255
256 __attribute_pure__
ssi_eval_expr_cmp_bool(const ssi_val_t * const v1,const ssi_val_t * const v2,const int cond)257 static int ssi_eval_expr_cmp_bool(const ssi_val_t * const v1, const ssi_val_t * const v2, const int cond) {
258 return (cond == TK_OR)
259 ? ssi_val_tobool(v1) || ssi_val_tobool(v2) /* TK_OR */
260 : ssi_val_tobool(v1) && ssi_val_tobool(v2); /* TK_AND */
261 }
262
ssi_eval_expr_append_val(buffer * const b,const char * s,const size_t slen)263 static void ssi_eval_expr_append_val(buffer * const b, const char *s, const size_t slen) {
264 if (buffer_is_blank(b))
265 buffer_append_string_len(b, s, slen);
266 else if (slen)
267 buffer_append_str2(b, CONST_STR_LEN(" "), s, slen);
268 }
269
ssi_expr_tokenizer(ssi_tokenizer_t * const t,buffer * const token)270 static int ssi_expr_tokenizer(ssi_tokenizer_t * const t, buffer * const token) {
271 size_t i;
272
273 while (t->offset < t->size
274 && (t->input[t->offset] == ' ' || t->input[t->offset] == '\t')) {
275 ++t->offset;
276 }
277 if (t->offset >= t->size)
278 return 0;
279 if (t->input[t->offset] == '\0') {
280 log_error(t->p->errh, __FILE__, __LINE__,
281 "pos: %zu foobar", t->offset+1);
282 return -1;
283 }
284
285 switch (t->input[t->offset]) {
286 case '=':
287 #if 0 /*(maybe accept "==", too)*/
288 if (t->input[t->offset + 1] == '=')
289 ++t->offset;
290 #endif
291 t->offset++;
292 return TK_EQ;
293 case '>':
294 if (t->input[t->offset + 1] == '=') {
295 t->offset += 2;
296 return TK_GE;
297 }
298 else {
299 t->offset += 1;
300 return TK_GT;
301 }
302 case '<':
303 if (t->input[t->offset + 1] == '=') {
304 t->offset += 2;
305 return TK_LE;
306 }
307 else {
308 t->offset += 1;
309 return TK_LT;
310 }
311 case '!':
312 if (t->input[t->offset + 1] == '=') {
313 t->offset += 2;
314 return TK_NE;
315 }
316 else {
317 t->offset += 1;
318 return TK_NOT;
319 }
320 case '&':
321 if (t->input[t->offset + 1] == '&') {
322 t->offset += 2;
323 return TK_AND;
324 }
325 else {
326 log_error(t->p->errh, __FILE__, __LINE__,
327 "pos: %zu missing second &", t->offset+1);
328 return -1;
329 }
330 case '|':
331 if (t->input[t->offset + 1] == '|') {
332 t->offset += 2;
333 return TK_OR;
334 }
335 else {
336 log_error(t->p->errh, __FILE__, __LINE__,
337 "pos: %zu missing second |", t->offset+1);
338 return -1;
339 }
340 case '(':
341 t->offset++;
342 t->in_brace++;
343 return TK_LPARAN;
344 case ')':
345 t->offset++;
346 t->in_brace--;
347 return TK_RPARAN;
348 case '\'':
349 /* search for the terminating "'" */
350 i = 1;
351 while (t->input[t->offset + i] && t->input[t->offset + i] != '\'')
352 ++i;
353 if (t->input[t->offset + i]) {
354 ssi_eval_expr_append_val(token, t->input + t->offset + 1, i-1);
355 t->offset += i + 1;
356 return TK_VALUE;
357 }
358 else {
359 log_error(t->p->errh, __FILE__, __LINE__,
360 "pos: %zu missing closing quote", t->offset+1);
361 return -1;
362 }
363 case '$': {
364 const char *var;
365 size_t varlen;
366 if (t->input[t->offset + 1] == '{') {
367 i = 2;
368 while (t->input[t->offset + i] && t->input[t->offset + i] != '}')
369 ++i;
370 if (t->input[t->offset + i] != '}') {
371 log_error(t->p->errh, __FILE__, __LINE__,
372 "pos: %zu missing closing curly-brace", t->offset+1);
373 return -1;
374 }
375 ++i; /* step past '}' */
376 var = t->input + t->offset + 2;
377 varlen = i-3;
378 }
379 else {
380 for (i = 1; light_isalpha(t->input[t->offset + i]) ||
381 t->input[t->offset + i] == '_' ||
382 ((i > 1) && light_isdigit(t->input[t->offset + i])); ++i) ;
383 var = t->input + t->offset + 1;
384 varlen = i-1;
385 }
386
387 const data_string *ds;
388 if ((ds = (const data_string *)
389 array_get_element_klen(t->p->ssi_cgi_env, var, varlen))
390 || (ds = (const data_string *)
391 array_get_element_klen(t->p->ssi_vars, var, varlen)))
392 ssi_eval_expr_append_val(token, BUF_PTR_LEN(&ds->value));
393 t->offset += i;
394 return TK_VALUE;
395 }
396 default:
397 for (i = 0; isgraph(((unsigned char *)t->input)[t->offset + i]); ++i) {
398 char d = t->input[t->offset + i];
399 switch(d) {
400 default: continue;
401 case ' ':
402 case '\t':
403 case ')':
404 case '(':
405 case '\'':
406 case '=':
407 case '!':
408 case '<':
409 case '>':
410 case '&':
411 case '|':
412 break;
413 }
414 break;
415 }
416 ssi_eval_expr_append_val(token, t->input + t->offset, i);
417 t->offset += i;
418 return TK_VALUE;
419 }
420 }
421
422 static int ssi_eval_expr_loop(ssi_tokenizer_t * const t, ssi_val_t * const v);
423
ssi_eval_expr_step(ssi_tokenizer_t * const t,ssi_val_t * const v)424 static int ssi_eval_expr_step(ssi_tokenizer_t * const t, ssi_val_t * const v) {
425 buffer_clear(&v->str);
426 v->type = SSI_TYPE_UNSET; /*(not SSI_TYPE_BOOL)*/
427 int next;
428 const int level = t->in_brace;
429 switch ((next = ssi_expr_tokenizer(t, &v->str))) {
430 case TK_VALUE:
431 do { next = ssi_expr_tokenizer(t, &v->str); } while (next == TK_VALUE);
432 return next;
433 case TK_LPARAN:
434 if (t->in_brace > 16) return -1; /*(arbitrary limit)*/
435 next = ssi_eval_expr_loop(t, v);
436 if (next == TK_RPARAN && level == t->in_brace) {
437 int result = ssi_val_tobool(v);
438 next = ssi_eval_expr_step(t, v); /*(resets v)*/
439 v->bo = result;
440 v->type = SSI_TYPE_BOOL;
441 return (next==TK_AND || next==TK_OR || next==TK_RPARAN || 0==next)
442 ? next
443 : -1;
444 }
445 else
446 return -1;
447 case TK_RPARAN:
448 return t->in_brace >= 0 ? TK_RPARAN : -1;
449 case TK_NOT:
450 if (++t->depth > 16) return -1; /*(arbitrary limit)*/
451 next = ssi_eval_expr_step(t, v);
452 --t->depth;
453 if (-1 == next) return next;
454 v->bo = !ssi_val_tobool(v);
455 v->type = SSI_TYPE_BOOL;
456 return next;
457 default:
458 return next;
459 }
460 }
461
ssi_eval_expr_loop_cmp(ssi_tokenizer_t * const t,ssi_val_t * const v1,int cond)462 static int ssi_eval_expr_loop_cmp(ssi_tokenizer_t * const t, ssi_val_t * const v1, int cond) {
463 ssi_val_t v2 = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 };
464 int next = ssi_eval_expr_step(t, &v2);
465 if (-1 != next) {
466 v1->bo = ssi_eval_expr_cmp(v1, &v2, cond);
467 v1->type = SSI_TYPE_BOOL;
468 }
469 buffer_free_ptr(&v2.str);
470 return next;
471 }
472
ssi_eval_expr_loop(ssi_tokenizer_t * const t,ssi_val_t * const v1)473 static int ssi_eval_expr_loop(ssi_tokenizer_t * const t, ssi_val_t * const v1) {
474 int next = ssi_eval_expr_step(t, v1);
475 switch (next) {
476 case TK_AND: case TK_OR:
477 break;
478 case TK_EQ: case TK_NE:
479 case TK_GT: case TK_GE:
480 case TK_LT: case TK_LE:
481 next = ssi_eval_expr_loop_cmp(t, v1, next);
482 if (next == TK_RPARAN || 0 == next) return next;
483 if (next != TK_AND && next != TK_OR) {
484 log_error(t->p->errh, __FILE__, __LINE__,
485 "pos: %zu parser failed somehow near here", t->offset+1);
486 return -1;
487 }
488 break;
489 default:
490 return next;
491 }
492
493 /*(Note: '&&' and '||' evaluations are not short-circuited)*/
494 ssi_val_t v2 = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 };
495 do {
496 int cond = next;
497 next = ssi_eval_expr_step(t, &v2);
498 switch (next) {
499 case TK_AND: case TK_OR: case 0:
500 break;
501 case TK_EQ: case TK_NE:
502 case TK_GT: case TK_GE:
503 case TK_LT: case TK_LE:
504 next = ssi_eval_expr_loop_cmp(t, &v2, next);
505 if (-1 == next) continue;
506 break;
507 case TK_RPARAN:
508 break;
509 default:
510 continue;
511 }
512 v1->bo = ssi_eval_expr_cmp_bool(v1, &v2, cond);
513 v1->type = SSI_TYPE_BOOL;
514 } while (next == TK_AND || next == TK_OR);
515 buffer_free_ptr(&v2.str);
516 return next;
517 }
518
ssi_eval_expr(handler_ctx * p,const char * expr)519 static int ssi_eval_expr(handler_ctx *p, const char *expr) {
520 ssi_tokenizer_t t;
521 t.input = expr;
522 t.offset = 0;
523 t.size = strlen(expr);
524 t.in_brace = 0;
525 t.depth = 0;
526 t.p = p;
527
528 ssi_val_t v = { { NULL, 0, 0 }, SSI_TYPE_UNSET, 0 };
529 int rc = ssi_eval_expr_loop(&t, &v);
530 rc = (0 == rc && 0 == t.in_brace && 0 == t.depth)
531 ? ssi_val_tobool(&v)
532 : -1;
533 buffer_free_ptr(&v.str);
534
535 return rc;
536 }
537
538
539
540
ssi_env_add(void * venv,const char * key,size_t klen,const char * val,size_t vlen)541 static int ssi_env_add(void *venv, const char *key, size_t klen, const char *val, size_t vlen) {
542 array_set_key_value((array *)venv, key, klen, val, vlen);
543 return 0;
544 }
545
build_ssi_cgi_vars(request_st * const r,handler_ctx * const p)546 static int build_ssi_cgi_vars(request_st * const r, handler_ctx * const p) {
547 http_cgi_opts opts = { 0, 0, NULL, NULL };
548 /* temporarily remove Authorization from request headers
549 * so that Authorization does not end up in SSI environment */
550 buffer *vb_auth = http_header_request_get(r, HTTP_HEADER_AUTHORIZATION, CONST_STR_LEN("Authorization"));
551 buffer b_auth;
552 if (vb_auth) {
553 memcpy(&b_auth, vb_auth, sizeof(buffer));
554 memset(vb_auth, 0, sizeof(buffer));
555 }
556
557 array_reset_data_strings(p->ssi_cgi_env);
558
559 if (0 != http_cgi_headers(r, &opts, ssi_env_add, p->ssi_cgi_env)) {
560 r->http_status = 400;
561 return -1;
562 }
563
564 if (vb_auth) {
565 memcpy(vb_auth, &b_auth, sizeof(buffer));
566 }
567
568 return 0;
569 }
570
mod_ssi_timefmt(buffer * const b,buffer * timefmtb,unix_time64_t t,int localtm)571 static void mod_ssi_timefmt (buffer * const b, buffer *timefmtb, unix_time64_t t, int localtm) {
572 struct tm tm;
573 const char * const timefmt = buffer_is_blank(timefmtb)
574 ? "%a, %d %b %Y %T %Z"
575 : timefmtb->ptr;
576 buffer_append_strftime(b, timefmt, localtm
577 ? localtime64_r(&t, &tm)
578 : gmtime64_r(&t, &tm));
579 if (buffer_is_blank(b))
580 buffer_copy_string_len(b, CONST_STR_LEN("(none)"));
581 }
582
583 static int mod_ssi_process_file(request_st *r, handler_ctx *p, struct stat *st);
584
process_ssi_stmt(request_st * const r,handler_ctx * const p,const char ** const l,size_t n,struct stat * const st)585 static int process_ssi_stmt(request_st * const r, handler_ctx * const p, const char ** const l, size_t n, struct stat * const st) {
586
587 /**
588 * <!--#element attribute=value attribute=value ... -->
589 *
590 * config DONE
591 * errmsg -- missing
592 * sizefmt DONE
593 * timefmt DONE
594 * echo DONE
595 * var DONE
596 * encoding -- missing
597 * exec DONE
598 * cgi -- never
599 * cmd DONE
600 * fsize DONE
601 * file DONE
602 * virtual DONE
603 * flastmod DONE
604 * file DONE
605 * virtual DONE
606 * include DONE
607 * file DONE
608 * virtual DONE
609 * printenv DONE
610 * set DONE
611 * var DONE
612 * value DONE
613 *
614 * if DONE
615 * elif DONE
616 * else DONE
617 * endif DONE
618 *
619 *
620 * expressions
621 * AND, OR DONE
622 * comp DONE
623 * ${...} -- missing
624 * $... DONE
625 * '...' DONE
626 * ( ... ) DONE
627 *
628 *
629 *
630 * ** all DONE **
631 * DATE_GMT
632 * The current date in Greenwich Mean Time.
633 * DATE_LOCAL
634 * The current date in the local time zone.
635 * DOCUMENT_NAME
636 * The filename (excluding directories) of the document requested by the user.
637 * DOCUMENT_URI
638 * 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.
639 * LAST_MODIFIED
640 * The last modification date of the document requested by the user.
641 * USER_NAME
642 * Contains the owner of the file which included it.
643 *
644 */
645
646 size_t i, ssicmd = 0;
647 buffer *tb = NULL;
648
649 static const struct {
650 const char *var;
651 enum { SSI_UNSET, SSI_ECHO, SSI_FSIZE, SSI_INCLUDE, SSI_FLASTMOD,
652 SSI_CONFIG, SSI_PRINTENV, SSI_SET, SSI_IF, SSI_ELIF,
653 SSI_ELSE, SSI_ENDIF, SSI_EXEC, SSI_COMMENT } type;
654 } ssicmds[] = {
655 { "echo", SSI_ECHO },
656 { "include", SSI_INCLUDE },
657 { "flastmod", SSI_FLASTMOD },
658 { "fsize", SSI_FSIZE },
659 { "config", SSI_CONFIG },
660 { "printenv", SSI_PRINTENV },
661 { "set", SSI_SET },
662 { "if", SSI_IF },
663 { "elif", SSI_ELIF },
664 { "endif", SSI_ENDIF },
665 { "else", SSI_ELSE },
666 { "exec", SSI_EXEC },
667 { "comment", SSI_COMMENT },
668
669 { NULL, SSI_UNSET }
670 };
671
672 for (i = 0; ssicmds[i].var; i++) {
673 if (0 == strcmp(l[1], ssicmds[i].var)) {
674 ssicmd = ssicmds[i].type;
675 break;
676 }
677 }
678
679 chunkqueue * const cq = &p->wq;
680
681 switch(ssicmd) {
682 case SSI_ECHO: {
683 /* echo */
684 int var = 0;
685 /* int enc = 0; */
686 const char *var_val = NULL;
687
688 static const struct {
689 const char *var;
690 enum {
691 SSI_ECHO_UNSET,
692 SSI_ECHO_DATE_GMT,
693 SSI_ECHO_DATE_LOCAL,
694 SSI_ECHO_DOCUMENT_NAME,
695 SSI_ECHO_DOCUMENT_URI,
696 SSI_ECHO_LAST_MODIFIED,
697 SSI_ECHO_USER_NAME,
698 SSI_ECHO_SCRIPT_URI,
699 SSI_ECHO_SCRIPT_URL,
700 } type;
701 } echovars[] = {
702 { "DATE_GMT", SSI_ECHO_DATE_GMT },
703 { "DATE_LOCAL", SSI_ECHO_DATE_LOCAL },
704 { "DOCUMENT_NAME", SSI_ECHO_DOCUMENT_NAME },
705 { "DOCUMENT_URI", SSI_ECHO_DOCUMENT_URI },
706 { "LAST_MODIFIED", SSI_ECHO_LAST_MODIFIED },
707 { "USER_NAME", SSI_ECHO_USER_NAME },
708 { "SCRIPT_URI", SSI_ECHO_SCRIPT_URI },
709 { "SCRIPT_URL", SSI_ECHO_SCRIPT_URL },
710
711 { NULL, SSI_ECHO_UNSET }
712 };
713
714 /*
715 static const struct {
716 const char *var;
717 enum { SSI_ENC_UNSET, SSI_ENC_URL, SSI_ENC_NONE, SSI_ENC_ENTITY } type;
718 } encvars[] = {
719 { "url", SSI_ENC_URL },
720 { "none", SSI_ENC_NONE },
721 { "entity", SSI_ENC_ENTITY },
722
723 { NULL, SSI_ENC_UNSET }
724 };
725 */
726
727 for (i = 2; i < n; i += 2) {
728 if (0 == strcmp(l[i], "var")) {
729 int j;
730
731 var_val = l[i+1];
732
733 for (j = 0; echovars[j].var; j++) {
734 if (0 == strcmp(l[i+1], echovars[j].var)) {
735 var = echovars[j].type;
736 break;
737 }
738 }
739 } else if (0 == strcmp(l[i], "encoding")) {
740 /*
741 int j;
742
743 for (j = 0; encvars[j].var; j++) {
744 if (0 == strcmp(l[i+1], encvars[j].var)) {
745 enc = encvars[j].type;
746 break;
747 }
748 }
749 */
750 } else {
751 log_error(r->conf.errh, __FILE__, __LINE__,
752 "ssi: unknown attribute for %s %s", l[1], l[i]);
753 }
754 }
755
756 if (p->if_is_false) break;
757
758 if (!var_val) {
759 log_error(r->conf.errh, __FILE__, __LINE__,
760 "ssi: %s var is missing", l[1]);
761 break;
762 }
763
764 switch(var) {
765 case SSI_ECHO_USER_NAME: {
766 tb = r->tmp_buf;
767 buffer_clear(tb);
768 #ifdef HAVE_PWD_H
769 struct passwd *pw;
770 if (NULL == (pw = getpwuid(st->st_uid))) {
771 buffer_append_int(tb, st->st_uid);
772 } else {
773 buffer_copy_string(tb, pw->pw_name);
774 }
775 #else
776 buffer_append_int(tb, st->st_uid);
777 #endif
778 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
779 break;
780 }
781 case SSI_ECHO_LAST_MODIFIED:
782 case SSI_ECHO_DATE_LOCAL:
783 case SSI_ECHO_DATE_GMT:
784 tb = r->tmp_buf;
785 buffer_clear(tb);
786 mod_ssi_timefmt(tb, p->timefmt,
787 (var == SSI_ECHO_LAST_MODIFIED)
788 ? st->st_mtime
789 : log_epoch_secs,
790 (var != SSI_ECHO_DATE_GMT));
791 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
792 break;
793 case SSI_ECHO_DOCUMENT_NAME: {
794 char *sl;
795
796 if (NULL == (sl = strrchr(r->physical.path.ptr, '/'))) {
797 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->physical.path));
798 } else {
799 chunkqueue_append_mem(cq, sl + 1, strlen(sl + 1));
800 }
801 break;
802 }
803 case SSI_ECHO_DOCUMENT_URI: {
804 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.path));
805 break;
806 }
807 case SSI_ECHO_SCRIPT_URI: {
808 if (!buffer_is_blank(&r->uri.scheme) && !buffer_is_blank(&r->uri.authority)) {
809 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.scheme));
810 chunkqueue_append_mem(cq, CONST_STR_LEN("://"));
811 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.authority));
812 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->target));
813 if (!buffer_is_blank(&r->uri.query)) {
814 chunkqueue_append_mem(cq, CONST_STR_LEN("?"));
815 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.query));
816 }
817 }
818 break;
819 }
820 case SSI_ECHO_SCRIPT_URL: {
821 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->target));
822 if (!buffer_is_blank(&r->uri.query)) {
823 chunkqueue_append_mem(cq, CONST_STR_LEN("?"));
824 chunkqueue_append_mem(cq, BUF_PTR_LEN(&r->uri.query));
825 }
826 break;
827 }
828 default: {
829 const data_string *ds;
830 /* check if it is a cgi-var or a ssi-var */
831
832 if (NULL != (ds = (const data_string *)array_get_element_klen(p->ssi_cgi_env, var_val, strlen(var_val))) ||
833 NULL != (ds = (const data_string *)array_get_element_klen(p->ssi_vars, var_val, strlen(var_val)))) {
834 chunkqueue_append_mem(cq, BUF_PTR_LEN(&ds->value));
835 } else {
836 chunkqueue_append_mem(cq, CONST_STR_LEN("(none)"));
837 }
838
839 break;
840 }
841 }
842 break;
843 }
844 case SSI_INCLUDE:
845 case SSI_FLASTMOD:
846 case SSI_FSIZE: {
847 const char * file_path = NULL, *virt_path = NULL;
848 struct stat stb;
849
850 for (i = 2; i < n; i += 2) {
851 if (0 == strcmp(l[i], "file")) {
852 file_path = l[i+1];
853 } else if (0 == strcmp(l[i], "virtual")) {
854 virt_path = l[i+1];
855 } else {
856 log_error(r->conf.errh, __FILE__, __LINE__,
857 "ssi: unknown attribute for %s %s", l[1], l[i]);
858 }
859 }
860
861 if (!file_path && !virt_path) {
862 log_error(r->conf.errh, __FILE__, __LINE__,
863 "ssi: %s file or virtual is missing", l[1]);
864 break;
865 }
866
867 if (file_path && virt_path) {
868 log_error(r->conf.errh, __FILE__, __LINE__,
869 "ssi: %s only one of file and virtual is allowed here", l[1]);
870 break;
871 }
872
873
874 if (p->if_is_false) break;
875
876 tb = r->tmp_buf;
877
878 if (file_path) {
879 /* current doc-root */
880 buffer_copy_string(tb, file_path);
881 buffer_urldecode_path(tb);
882 if (!buffer_is_valid_UTF8(tb)) {
883 log_error(r->conf.errh, __FILE__, __LINE__,
884 "SSI invalid UTF-8 after url-decode: %s", tb->ptr);
885 break;
886 }
887 buffer_path_simplify(tb);
888 char *sl = strrchr(r->physical.path.ptr, '/');
889 if (NULL == sl) break; /*(not expected)*/
890 buffer_copy_path_len2(p->stat_fn,
891 r->physical.path.ptr,
892 sl - r->physical.path.ptr + 1,
893 BUF_PTR_LEN(tb));
894 } else {
895 /* virtual */
896
897 buffer_clear(tb);
898 if (virt_path[0] != '/') {
899 /* there is always a / */
900 const char * const sl = strrchr(r->uri.path.ptr, '/');
901 buffer_copy_string_len(tb, r->uri.path.ptr, sl - r->uri.path.ptr + 1);
902 }
903 buffer_append_string(tb, virt_path);
904
905 buffer_urldecode_path(tb);
906 if (!buffer_is_valid_UTF8(tb)) {
907 log_error(r->conf.errh, __FILE__, __LINE__,
908 "SSI invalid UTF-8 after url-decode: %s", tb->ptr);
909 break;
910 }
911 buffer_path_simplify(tb);
912
913 /* we have an uri */
914
915 /* Destination physical path (similar to code in mod_webdav.c)
916 * src r->physical.path might have been remapped with mod_alias, mod_userdir.
917 * (but neither modifies r->physical.rel_path)
918 * Find matching prefix to support relative paths to current physical path.
919 * Aliasing of paths underneath current r->physical.basedir might not work.
920 * Likewise, mod_rewrite URL rewriting might thwart this comparison.
921 * Use mod_redirect instead of mod_alias to remap paths *under* this basedir.
922 * Use mod_redirect instead of mod_rewrite on *any* parts of path to basedir.
923 * (Related, use mod_auth to protect this basedir, but avoid attempting to
924 * use mod_auth on paths underneath this basedir, as target path is not
925 * validated with mod_auth)
926 */
927
928 /* find matching URI prefix
929 * check if remaining r->physical.rel_path matches suffix
930 * of r->physical.basedir so that we can use it to
931 * remap Destination physical path */
932 {
933 const char *sep, *sep2;
934 sep = r->uri.path.ptr;
935 sep2 = tb->ptr;
936 for (i = 0; sep[i] && sep[i] == sep2[i]; ++i) ;
937 while (i != 0 && sep[--i] != '/') ; /* find matching directory path */
938 }
939 if (r->conf.force_lowercase_filenames) {
940 buffer_to_lower(tb);
941 }
942 uint32_t remain = buffer_clen(&r->uri.path) - i;
943 uint32_t plen = buffer_clen(&r->physical.path);
944 if (plen >= remain
945 && (!r->conf.force_lowercase_filenames
946 ? 0 == memcmp(r->physical.path.ptr+plen-remain, r->physical.rel_path.ptr+i, remain)
947 : buffer_eq_icase_ssn(r->physical.path.ptr+plen-remain, r->physical.rel_path.ptr+i, remain))) {
948 buffer_copy_path_len2(p->stat_fn,
949 r->physical.path.ptr,
950 plen-remain,
951 tb->ptr+i,
952 buffer_clen(tb)-i);
953 } else {
954 /* unable to perform physical path remap here;
955 * assume doc_root/rel_path and no remapping */
956 buffer_copy_path_len2(p->stat_fn,
957 BUF_PTR_LEN(&r->physical.doc_root),
958 BUF_PTR_LEN(tb));
959 }
960 }
961
962 if (!r->conf.follow_symlink
963 && 0 != stat_cache_path_contains_symlink(p->stat_fn, r->conf.errh)) {
964 break;
965 }
966
967 int fd = stat_cache_open_rdonly_fstat(p->stat_fn, &stb, r->conf.follow_symlink);
968 if (fd >= 0) {
969 switch (ssicmd) {
970 case SSI_FSIZE:
971 buffer_clear(tb);
972 if (p->sizefmt) {
973 int j = 0;
974 const char *abr[] = { " B", " kB", " MB", " GB", " TB", NULL };
975
976 off_t s = stb.st_size;
977
978 for (j = 0; s > 1024 && abr[j+1]; s /= 1024, j++);
979
980 buffer_append_int(tb, s);
981 buffer_append_string_len(tb, abr[j], j ? 3 : 2);
982 } else {
983 buffer_append_int(tb, stb.st_size);
984 }
985 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
986 break;
987 case SSI_FLASTMOD:
988 buffer_clear(tb);
989 mod_ssi_timefmt(tb, p->timefmt, stb.st_mtime, 1);
990 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
991 break;
992 case SSI_INCLUDE:
993 /* Keep the newest mtime of included files */
994 if (include_file_last_mtime < TIME64_CAST(stb.st_mtime))
995 include_file_last_mtime = TIME64_CAST(stb.st_mtime);
996
997 if (file_path || 0 == p->conf.ssi_recursion_max) {
998 /* don't process if #include file="..." is used */
999 chunkqueue_append_file_fd(cq, p->stat_fn, fd, 0, stb.st_size);
1000 fd = -1;
1001 } else {
1002 buffer upsave, ppsave, prpsave;
1003
1004 /* only allow predefined recursion depth */
1005 if (p->ssi_recursion_depth >= p->conf.ssi_recursion_max) {
1006 chunkqueue_append_mem(cq, CONST_STR_LEN("(error: include directives recurse deeper than pre-defined ssi.recursion-max)"));
1007 break;
1008 }
1009
1010 /* prevents simple infinite loop */
1011 if (buffer_is_equal(&r->physical.path, p->stat_fn)) {
1012 chunkqueue_append_mem(cq, CONST_STR_LEN("(error: include directives create an infinite loop)"));
1013 break;
1014 }
1015
1016 /* save and restore r->physical.path, r->physical.rel_path, and r->uri.path around include
1017 *
1018 * tb contains url-decoded, path-simplified, and lowercased (if r->conf.force_lowercase) uri path of target.
1019 * r->uri.path and r->physical.rel_path are set to the same since we only operate on filenames here,
1020 * not full re-run of all modules for subrequest */
1021 upsave = r->uri.path;
1022 ppsave = r->physical.path;
1023 prpsave = r->physical.rel_path;
1024
1025 r->physical.path = *p->stat_fn;
1026 memset(p->stat_fn, 0, sizeof(buffer));
1027
1028 memset(&r->uri.path, 0, sizeof(buffer));
1029 buffer_copy_buffer(&r->uri.path, tb);
1030 r->physical.rel_path = r->uri.path;
1031
1032 close(fd);
1033 fd = -1;
1034
1035 /*(ignore return value; muddle along as best we can if error occurs)*/
1036 ++p->ssi_recursion_depth;
1037 mod_ssi_process_file(r, p, &stb);
1038 --p->ssi_recursion_depth;
1039
1040 free(r->uri.path.ptr);
1041 r->uri.path = upsave;
1042 r->physical.rel_path = prpsave;
1043
1044 free(p->stat_fn->ptr);
1045 *p->stat_fn = r->physical.path;
1046 r->physical.path = ppsave;
1047 }
1048
1049 break;
1050 }
1051
1052 if (fd >= 0) close(fd);
1053 } else {
1054 log_perror(r->conf.errh, __FILE__, __LINE__,
1055 "ssi: stating %s failed", p->stat_fn->ptr);
1056 }
1057 break;
1058 }
1059 case SSI_SET: {
1060 const char *key = NULL, *val = NULL;
1061 for (i = 2; i < n; i += 2) {
1062 if (0 == strcmp(l[i], "var")) {
1063 key = l[i+1];
1064 } else if (0 == strcmp(l[i], "value")) {
1065 val = l[i+1];
1066 } else {
1067 log_error(r->conf.errh, __FILE__, __LINE__,
1068 "ssi: unknown attribute for %s %s", l[1], l[i]);
1069 }
1070 }
1071
1072 if (p->if_is_false) break;
1073
1074 if (key && val) {
1075 array_set_key_value(p->ssi_vars, key, strlen(key), val, strlen(val));
1076 } else if (key || val) {
1077 log_error(r->conf.errh, __FILE__, __LINE__,
1078 "ssi: var and value have to be set in <!--#set %s=%s -->", l[1], l[2]);
1079 } else {
1080 log_error(r->conf.errh, __FILE__, __LINE__,
1081 "ssi: var and value have to be set in <!--#set var=... value=... -->");
1082 }
1083 break;
1084 }
1085 case SSI_CONFIG:
1086 if (p->if_is_false) break;
1087
1088 for (i = 2; i < n; i += 2) {
1089 if (0 == strcmp(l[i], "timefmt")) {
1090 buffer_copy_string(p->timefmt, l[i+1]);
1091 } else if (0 == strcmp(l[i], "sizefmt")) {
1092 if (0 == strcmp(l[i+1], "abbrev")) {
1093 p->sizefmt = 1;
1094 } else if (0 == strcmp(l[i+1], "bytes")) {
1095 p->sizefmt = 0;
1096 } else {
1097 log_error(r->conf.errh, __FILE__, __LINE__,
1098 "ssi: unknown value for attribute '%s' for %s %s",
1099 l[i], l[1], l[i+1]);
1100 }
1101 } else {
1102 log_error(r->conf.errh, __FILE__, __LINE__,
1103 "ssi: unknown attribute for %s %s", l[1], l[i]);
1104 }
1105 }
1106 break;
1107 case SSI_PRINTENV:
1108 if (p->if_is_false) break;
1109
1110 tb = r->tmp_buf;
1111 buffer_clear(tb);
1112 for (i = 0; i < p->ssi_vars->used; i++) {
1113 data_string *ds = (data_string *)p->ssi_vars->sorted[i];
1114
1115 buffer_append_str2(tb, BUF_PTR_LEN(&ds->key), CONST_STR_LEN("="));
1116 buffer_append_string_encoded(tb, BUF_PTR_LEN(&ds->value), ENCODING_MINIMAL_XML);
1117 buffer_append_char(tb, '\n');
1118 }
1119 for (i = 0; i < p->ssi_cgi_env->used; i++) {
1120 data_string *ds = (data_string *)p->ssi_cgi_env->sorted[i];
1121
1122 buffer_append_str2(tb, BUF_PTR_LEN(&ds->key), CONST_STR_LEN("="));
1123 buffer_append_string_encoded(tb, BUF_PTR_LEN(&ds->value), ENCODING_MINIMAL_XML);
1124 buffer_append_char(tb, '\n');
1125 }
1126 chunkqueue_append_mem(cq, BUF_PTR_LEN(tb));
1127 break;
1128 case SSI_EXEC: {
1129 const char *cmd = NULL;
1130 pid_t pid;
1131 chunk *c;
1132 char *args[4];
1133 log_error_st *errh = p->errh;
1134
1135 if (!p->conf.ssi_exec) { /* <!--#exec ... --> disabled by config */
1136 break;
1137 }
1138
1139 for (i = 2; i < n; i += 2) {
1140 if (0 == strcmp(l[i], "cmd")) {
1141 cmd = l[i+1];
1142 } else {
1143 log_error(errh, __FILE__, __LINE__,
1144 "ssi: unknown attribute for %s %s", l[1], l[i]);
1145 }
1146 }
1147
1148 if (p->if_is_false) break;
1149
1150 /*
1151 * as exec is assumed evil it is implemented synchronously
1152 */
1153
1154 if (!cmd) break;
1155
1156 /* send cmd output to a temporary file */
1157 if (0 != chunkqueue_append_mem_to_tempfile(cq, "", 0, errh)) break;
1158 c = cq->last;
1159
1160 *(const char **)&args[0] = "/bin/sh";
1161 *(const char **)&args[1] = "-c";
1162 *(const char **)&args[2] = cmd;
1163 args[3] = NULL;
1164
1165 int status = 0;
1166 struct stat stb;
1167 stb.st_size = 0;
1168 /*(expects STDIN_FILENO open to /dev/null)*/
1169 int serrh_fd = r->conf.serrh ? r->conf.serrh->fd : -1;
1170 pid = fdevent_fork_execve(args[0], args, NULL, -1, c->file.fd, serrh_fd, -1);
1171 if (-1 == pid) {
1172 log_perror(errh, __FILE__, __LINE__, "spawning exec failed: %s", cmd);
1173 } else if (fdevent_waitpid(pid, &status, 0) < 0) {
1174 log_perror(errh, __FILE__, __LINE__, "waitpid failed");
1175 } else {
1176 /* wait for the client to end */
1177 /* NOTE: synchronous; blocks entire lighttpd server */
1178
1179 /*
1180 * OpenBSD and Solaris send a EINTR on SIGCHILD even if we ignore it
1181 */
1182 if (!WIFEXITED(status)) {
1183 log_error(errh, __FILE__, __LINE__, "process exited abnormally: %s", cmd);
1184 }
1185 if (0 == fstat(c->file.fd, &stb)) {
1186 }
1187 }
1188 chunkqueue_update_file(cq, c, stb.st_size);
1189 break;
1190 }
1191 case SSI_IF: {
1192 const char *expr = NULL;
1193
1194 for (i = 2; i < n; i += 2) {
1195 if (0 == strcmp(l[i], "expr")) {
1196 expr = l[i+1];
1197 } else {
1198 log_error(r->conf.errh, __FILE__, __LINE__,
1199 "ssi: unknown attribute for %s %s", l[1], l[i]);
1200 }
1201 }
1202
1203 if (!expr) {
1204 log_error(r->conf.errh, __FILE__, __LINE__,
1205 "ssi: %s expr missing", l[1]);
1206 break;
1207 }
1208
1209 if ((!p->if_is_false) &&
1210 ((p->if_is_false_level == 0) ||
1211 (p->if_level < p->if_is_false_level))) {
1212 switch (ssi_eval_expr(p, expr)) {
1213 case -1:
1214 case 0:
1215 p->if_is_false = 1;
1216 p->if_is_false_level = p->if_level;
1217 break;
1218 case 1:
1219 p->if_is_false = 0;
1220 break;
1221 }
1222 }
1223
1224 p->if_level++;
1225
1226 break;
1227 }
1228 case SSI_ELSE:
1229 p->if_level--;
1230
1231 if (p->if_is_false) {
1232 if ((p->if_level == p->if_is_false_level) &&
1233 (p->if_is_false_endif == 0)) {
1234 p->if_is_false = 0;
1235 }
1236 } else {
1237 p->if_is_false = 1;
1238
1239 p->if_is_false_level = p->if_level;
1240 }
1241 p->if_level++;
1242
1243 break;
1244 case SSI_ELIF: {
1245 const char *expr = NULL;
1246 for (i = 2; i < n; i += 2) {
1247 if (0 == strcmp(l[i], "expr")) {
1248 expr = l[i+1];
1249 } else {
1250 log_error(r->conf.errh, __FILE__, __LINE__,
1251 "ssi: unknown attribute for %s %s", l[1], l[i]);
1252 }
1253 }
1254
1255 if (!expr) {
1256 log_error(r->conf.errh, __FILE__, __LINE__,
1257 "ssi: %s expr missing", l[1]);
1258 break;
1259 }
1260
1261 p->if_level--;
1262
1263 if (p->if_level == p->if_is_false_level) {
1264 if ((p->if_is_false) &&
1265 (p->if_is_false_endif == 0)) {
1266 switch (ssi_eval_expr(p, expr)) {
1267 case -1:
1268 case 0:
1269 p->if_is_false = 1;
1270 p->if_is_false_level = p->if_level;
1271 break;
1272 case 1:
1273 p->if_is_false = 0;
1274 break;
1275 }
1276 } else {
1277 p->if_is_false = 1;
1278 p->if_is_false_level = p->if_level;
1279 p->if_is_false_endif = 1;
1280 }
1281 }
1282
1283 p->if_level++;
1284
1285 break;
1286 }
1287 case SSI_ENDIF:
1288 p->if_level--;
1289
1290 if (p->if_level == p->if_is_false_level) {
1291 p->if_is_false = 0;
1292 p->if_is_false_endif = 0;
1293 }
1294
1295 break;
1296 case SSI_COMMENT:
1297 break;
1298 default:
1299 log_error(r->conf.errh, __FILE__, __LINE__,
1300 "ssi: unknown ssi-command: %s", l[1]);
1301 break;
1302 }
1303
1304 return 0;
1305
1306 }
1307
1308 __attribute_pure__
mod_ssi_parse_ssi_stmt_value(const unsigned char * const s,const int len)1309 static int mod_ssi_parse_ssi_stmt_value(const unsigned char * const s, const int len) {
1310 int n;
1311 const int c = (s[0] == '"' ? '"' : s[0] == '\'' ? '\'' : 0);
1312 if (0 != c) {
1313 for (n = 1; n < len; ++n) {
1314 if (s[n] == c) return n+1;
1315 if (s[n] == '\\') {
1316 if (n+1 == len) return 0; /* invalid */
1317 ++n;
1318 }
1319 }
1320 return 0; /* invalid */
1321 } else {
1322 for (n = 0; n < len; ++n) {
1323 if (isspace(s[n])) return n;
1324 if (s[n] == '\\') {
1325 if (n+1 == len) return 0; /* invalid */
1326 ++n;
1327 }
1328 }
1329 return n;
1330 }
1331 }
1332
mod_ssi_parse_ssi_stmt_offlen(int o[10],const unsigned char * const s,const int len)1333 static int mod_ssi_parse_ssi_stmt_offlen(int o[10], const unsigned char * const s, const int len) {
1334
1335 /**
1336 * <!--#element attribute=value attribute=value ... -->
1337 */
1338
1339 /* s must begin "<!--#" and must end with "-->" */
1340 int n = 5;
1341 o[0] = n;
1342 for (; light_isalpha(s[n]); ++n) ; /*(n = 5 to begin after "<!--#")*/
1343 o[1] = n - o[0];
1344 if (0 == o[1]) return -1; /* empty token */
1345
1346 if (n+3 == len) return 2; /* token only; no params */
1347 if (!isspace(s[n])) return -1;
1348 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1349 if (n+3 == len) return 2; /* token only; no params */
1350
1351 o[2] = n;
1352 for (; light_isalpha(s[n]); ++n) ;
1353 o[3] = n - o[2];
1354 if (0 == o[3] || s[n++] != '=') return -1;
1355
1356 o[4] = n;
1357 o[5] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1358 if (0 == o[5]) return -1; /* empty or invalid token */
1359 n += o[5];
1360
1361 if (n+3 == len) return 6; /* token and one param */
1362 if (!isspace(s[n])) return -1;
1363 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1364 if (n+3 == len) return 6; /* token and one param */
1365
1366 o[6] = n;
1367 for (; light_isalpha(s[n]); ++n) ;
1368 o[7] = n - o[6];
1369 if (0 == o[7] || s[n++] != '=') return -1;
1370
1371 o[8] = n;
1372 o[9] = mod_ssi_parse_ssi_stmt_value(s+n, len-n-3);
1373 if (0 == o[9]) return -1; /* empty or invalid token */
1374 n += o[9];
1375
1376 if (n+3 == len) return 10; /* token and two params */
1377 if (!isspace(s[n])) return -1;
1378 do { ++n; } while (isspace(s[n])); /* string ends "-->", so n < len */
1379 if (n+3 == len) return 10; /* token and two params */
1380 return -1;
1381 }
1382
mod_ssi_parse_ssi_stmt(request_st * const r,handler_ctx * const p,char * const s,int len,struct stat * const st)1383 static void mod_ssi_parse_ssi_stmt(request_st * const r, handler_ctx * const p, char * const s, int len, struct stat * const st) {
1384
1385 /**
1386 * <!--#element attribute=value attribute=value ... -->
1387 */
1388
1389 int o[10];
1390 int m;
1391 const int n = mod_ssi_parse_ssi_stmt_offlen(o, (unsigned char *)s, len);
1392 char *l[6] = { s, NULL, NULL, NULL, NULL, NULL };
1393 if (-1 == n) {
1394 /* ignore <!--#comment ... --> */
1395 if (len >= 16
1396 && 0 == memcmp(s+5, "comment", sizeof("comment")-1)
1397 && (s[12] == ' ' || s[12] == '\t'))
1398 return;
1399 /* XXX: perhaps emit error comment instead of invalid <!--#...--> code to client */
1400 chunkqueue_append_mem(&p->wq, s, len); /* append stmt as-is */
1401 return;
1402 }
1403
1404 #if 0
1405 /* dup s and then modify s */
1406 /*(l[0] is no longer used; was previously used in only one place for error reporting)*/
1407 l[0] = ck_malloc((size_t)(len+1));
1408 memcpy(l[0], s, (size_t)len);
1409 (l[0])[len] = '\0';
1410 #endif
1411
1412 /* modify s in-place to split string into arg tokens */
1413 for (m = 0; m < n; m += 2) {
1414 char *ptr = s+o[m];
1415 switch (*ptr) {
1416 case '"':
1417 case '\'': (++ptr)[o[m+1]-2] = '\0'; break;
1418 default: ptr[o[m+1]] = '\0'; break;
1419 }
1420 l[1+(m>>1)] = ptr;
1421 if (m == 4 || m == 8) {
1422 /* XXX: removing '\\' escapes from param value would be
1423 * the right thing to do, but would potentially change
1424 * current behavior, e.g. <!--#exec cmd=... --> */
1425 }
1426 }
1427
1428 process_ssi_stmt(r, p, (const char **)l, 1+(n>>1), st);
1429
1430 #if 0
1431 free(l[0]);
1432 #endif
1433 }
1434
mod_ssi_stmt_len(const char * s,const int len)1435 static int mod_ssi_stmt_len(const char *s, const int len) {
1436 /* s must begin "<!--#" */
1437 int n, sq = 0, dq = 0, bs = 0;
1438 for (n = 5; n < len; ++n) { /*(n = 5 to begin after "<!--#")*/
1439 switch (s[n]) {
1440 default:
1441 break;
1442 case '-':
1443 if (!sq && !dq && n+2 < len && s[n+1] == '-' && s[n+2] == '>') return n+3; /* found end of stmt */
1444 break;
1445 case '"':
1446 if (!sq && (!dq || !bs)) dq = !dq;
1447 break;
1448 case '\'':
1449 if (!dq && (!sq || !bs)) sq = !sq;
1450 break;
1451 case '\\':
1452 if (sq || dq) bs = !bs;
1453 break;
1454 }
1455 }
1456 return 0; /* incomplete directive "<!--#...-->" */
1457 }
1458
mod_ssi_read_fd(request_st * const r,handler_ctx * const p,struct stat * const st,int fd)1459 static void mod_ssi_read_fd(request_st * const r, handler_ctx * const p, struct stat * const st, int fd) {
1460 ssize_t rd;
1461 size_t offset, pretag;
1462 /* allocate to reduce chance of stack exhaustion upon deep recursion */
1463 buffer * const b = chunk_buffer_acquire();
1464 chunkqueue * const cq = &p->wq;
1465 const size_t bufsz = 8192;
1466 chunk_buffer_prepare_append(b, bufsz-1);
1467 char * const buf = b->ptr;
1468
1469 offset = 0;
1470 pretag = 0;
1471 while (0 < (rd = read(fd, buf+offset, bufsz-offset))) {
1472 char *s;
1473 size_t prelen = 0, len;
1474 offset += (size_t)rd;
1475 for (; (s = memchr(buf+prelen, '<', offset-prelen)); ++prelen) {
1476 prelen = s - buf;
1477 if (prelen + 5 <= offset) { /*("<!--#" is 5 chars)*/
1478 if (0 != memcmp(s+1, CONST_STR_LEN("!--#"))) continue; /* loop to loop for next '<' */
1479
1480 if (prelen - pretag && !p->if_is_false) {
1481 chunkqueue_append_mem(cq, buf+pretag, prelen-pretag);
1482 }
1483
1484 len = mod_ssi_stmt_len(buf+prelen, offset-prelen);
1485 if (len) { /* num of chars to be consumed */
1486 mod_ssi_parse_ssi_stmt(r, p, buf+prelen, len, st);
1487 prelen += (len - 1); /* offset to '>' at end of SSI directive; incremented at top of loop */
1488 pretag = prelen + 1;
1489 if (pretag == offset) {
1490 offset = pretag = 0;
1491 break;
1492 }
1493 } else if (0 == prelen && offset == bufsz) { /*(full buf)*/
1494 /* SSI statement is way too long
1495 * NOTE: skipping this buf will expose *the rest* of this SSI statement */
1496 chunkqueue_append_mem(cq, CONST_STR_LEN("<!-- [an error occurred: directive too long] "));
1497 /* check if buf ends with "-" or "--" which might be part of "-->"
1498 * (buf contains at least 5 chars for "<!--#") */
1499 if (buf[offset-2] == '-' && buf[offset-1] == '-') {
1500 chunkqueue_append_mem(cq, CONST_STR_LEN("--"));
1501 } else if (buf[offset-1] == '-') {
1502 chunkqueue_append_mem(cq, CONST_STR_LEN("-"));
1503 }
1504 offset = pretag = 0;
1505 break;
1506 } else { /* incomplete directive "<!--#...-->" */
1507 memmove(buf, buf+prelen, (offset -= prelen));
1508 pretag = 0;
1509 break;
1510 }
1511 } else if (prelen + 1 == offset || 0 == memcmp(s+1, "!--", offset - prelen - 1)) {
1512 if (prelen - pretag && !p->if_is_false) {
1513 chunkqueue_append_mem(cq, buf+pretag, prelen-pretag);
1514 }
1515 memcpy(buf, buf+prelen, (offset -= prelen));
1516 pretag = 0;
1517 break;
1518 }
1519 /* loop to look for next '<' */
1520 }
1521 if (offset == bufsz) {
1522 if (!p->if_is_false) {
1523 chunkqueue_append_mem(cq, buf+pretag, offset-pretag);
1524 }
1525 offset = pretag = 0;
1526 }
1527 /* flush intermediate cq to r->write_queue (and possibly to
1528 * temporary file) if last MEM_CHUNK has less than 1k-1 avail
1529 * (reduce occurrence of copying to reallocate larger chunk) */
1530 if (cq->last && cq->last->type == MEM_CHUNK
1531 && buffer_string_space(cq->last->mem) < 1023)
1532 if (0 != http_chunk_transfer_cqlen(r, cq, chunkqueue_length(cq)))
1533 chunkqueue_remove_empty_chunks(&r->write_queue);
1534 /*(likely unrecoverable error if r->resp_send_chunked)*/
1535 }
1536
1537 if (0 != rd) {
1538 log_perror(r->conf.errh, __FILE__, __LINE__,
1539 "read(): %s", r->physical.path.ptr);
1540 }
1541
1542 if (offset - pretag) {
1543 /* copy remaining data in buf */
1544 if (!p->if_is_false) {
1545 chunkqueue_append_mem(cq, buf+pretag, offset-pretag);
1546 }
1547 }
1548
1549 chunk_buffer_release(b);
1550 if (0 != http_chunk_transfer_cqlen(r, cq, chunkqueue_length(cq)))
1551 chunkqueue_remove_empty_chunks(&r->write_queue);
1552 /*(likely error unrecoverable if r->resp_send_chunked)*/
1553 }
1554
1555
mod_ssi_process_file(request_st * const r,handler_ctx * const p,struct stat * const st)1556 static int mod_ssi_process_file(request_st * const r, handler_ctx * const p, struct stat * const st) {
1557 int fd = stat_cache_open_rdonly_fstat(&r->physical.path, st, r->conf.follow_symlink);
1558 if (-1 == fd) {
1559 log_perror(r->conf.errh, __FILE__, __LINE__,
1560 "open(): %s", r->physical.path.ptr);
1561 return -1;
1562 }
1563
1564 mod_ssi_read_fd(r, p, st, fd);
1565
1566 close(fd);
1567 return 0;
1568 }
1569
1570
mod_ssi_handle_request(request_st * const r,handler_ctx * const p)1571 static int mod_ssi_handle_request(request_st * const r, handler_ctx * const p) {
1572 struct stat st;
1573
1574 /* get a stream to the file */
1575
1576 buffer_clear(p->timefmt);
1577 array_reset_data_strings(p->ssi_vars);
1578 array_reset_data_strings(p->ssi_cgi_env);
1579 build_ssi_cgi_vars(r, p);
1580
1581 /* Reset the modified time of included files */
1582 include_file_last_mtime = 0;
1583
1584 if (mod_ssi_process_file(r, p, &st)) return -1;
1585
1586 r->resp_body_started = 1;
1587 r->resp_body_finished = 1;
1588
1589 if (!p->conf.content_type) {
1590 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
1591 } else {
1592 http_header_response_set(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type"), BUF_PTR_LEN(p->conf.content_type));
1593 }
1594
1595 if (p->conf.conditional_requests) {
1596 /* Generate "ETag" & "Last-Modified" headers */
1597
1598 /* use most recently modified include file for ETag and Last-Modified */
1599 if (TIME64_CAST(st.st_mtime) < include_file_last_mtime)
1600 st.st_mtime = include_file_last_mtime;
1601
1602 http_etag_create(r->tmp_buf, &st, r->conf.etag_flags);
1603 http_header_response_set(r, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"), BUF_PTR_LEN(r->tmp_buf));
1604
1605 const buffer * const mtime = http_response_set_last_modified(r, st.st_mtime);
1606 if (HANDLER_FINISHED == http_response_handle_cachable(r, mtime, st.st_mtime)) {
1607 /* ok, the client already has our content,
1608 * no need to send it again */
1609
1610 chunkqueue_reset(&r->write_queue);
1611 }
1612 }
1613
1614 /* Reset the modified time of included files */
1615 include_file_last_mtime = 0;
1616
1617 return 0;
1618 }
1619
URIHANDLER_FUNC(mod_ssi_physical_path)1620 URIHANDLER_FUNC(mod_ssi_physical_path) {
1621 plugin_data *p = p_d;
1622
1623 if (NULL != r->handler_module) return HANDLER_GO_ON;
1624 /* r->physical.path is non-empty for handle_subrequest_start */
1625 /*if (buffer_is_blank(&r->physical.path)) return HANDLER_GO_ON;*/
1626
1627 mod_ssi_patch_config(r, p);
1628 if (NULL == p->conf.ssi_extension) return HANDLER_GO_ON;
1629
1630 if (array_match_value_suffix(p->conf.ssi_extension, &r->physical.path)) {
1631 r->plugin_ctx[p->id] = handler_ctx_init(p, r->conf.errh);
1632 r->handler_module = p->self;
1633 }
1634
1635 return HANDLER_GO_ON;
1636 }
1637
SUBREQUEST_FUNC(mod_ssi_handle_subrequest)1638 SUBREQUEST_FUNC(mod_ssi_handle_subrequest) {
1639 plugin_data *p = p_d;
1640 handler_ctx *hctx = r->plugin_ctx[p->id];
1641 if (NULL == hctx) return HANDLER_GO_ON;
1642 /*
1643 * NOTE: if mod_ssi modified to use fdevents, HANDLER_WAIT_FOR_EVENT,
1644 * instead of blocking to completion, then hctx->timefmt, hctx->ssi_vars,
1645 * and hctx->ssi_cgi_env should be allocated and cleaned up per request.
1646 */
1647
1648 /* handle ssi-request */
1649
1650 if (mod_ssi_handle_request(r, hctx)) {
1651 /* on error */
1652 r->http_status = 500;
1653 r->handler_module = NULL;
1654 }
1655
1656 return HANDLER_FINISHED;
1657 }
1658
mod_ssi_handle_request_reset(request_st * const r,void * p_d)1659 static handler_t mod_ssi_handle_request_reset(request_st * const r, void *p_d) {
1660 plugin_data *p = p_d;
1661 handler_ctx *hctx = r->plugin_ctx[p->id];
1662 if (hctx) {
1663 handler_ctx_free(hctx);
1664 r->plugin_ctx[p->id] = NULL;
1665 }
1666
1667 return HANDLER_GO_ON;
1668 }
1669
1670
1671 __attribute_cold__
1672 int mod_ssi_plugin_init(plugin *p);
mod_ssi_plugin_init(plugin * p)1673 int mod_ssi_plugin_init(plugin *p) {
1674 p->version = LIGHTTPD_VERSION_ID;
1675 p->name = "ssi";
1676
1677 p->init = mod_ssi_init;
1678 p->handle_subrequest_start = mod_ssi_physical_path;
1679 p->handle_subrequest = mod_ssi_handle_subrequest;
1680 p->handle_request_reset = mod_ssi_handle_request_reset;
1681 p->set_defaults = mod_ssi_set_defaults;
1682 p->cleanup = mod_ssi_free;
1683
1684 return 0;
1685 }
1686