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