xref: /lighttpd1.4/src/mod_ssi.c (revision 5e14db43)
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