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