1 /*
2 * Copyright (c) 2006,2007 Steven Johnson <sjohnson@sakuraindustries.com>
3 * Copyright (c) 2007 Sergey Lyubka <valenok@gmail.com>
4 * All rights reserved
5 *
6 * "THE BEER-WARE LICENSE" (Revision 42):
7 * Sergey Lyubka wrote this file. As long as you retain this notice you
8 * can do whatever you want with this stuff. If we meet some day, and you think
9 * this stuff is worth it, you can buy me a beer in return.
10 */
11
12 #include "defs.h"
13
14 #if !defined(NO_SSI)
15
16 #define CMDBUFSIZ 512 /* SSI command buffer size */
17 #define NEST_MAX 6 /* Maximum nesting level */
18
19 struct ssi_func {
20 struct llhead link;
21 void *user_data;
22 char *name;
23 shttpd_callback_t func;
24 };
25
26 struct ssi_inc {
27 int state; /* Buffering state */
28 int cond; /* Conditional state */
29 FILE *fp; /* Icluded file stream */
30 char buf[CMDBUFSIZ]; /* SSI command buffer */
31 size_t nbuf; /* Bytes in a command buffer */
32 FILE *pipe; /* #exec stream */
33 struct ssi_func func; /* #call function */
34 };
35
36 struct ssi {
37 struct conn *conn; /* Connection we belong to */
38 int nest; /* Current nesting level */
39 struct ssi_inc incs[NEST_MAX]; /* Nested includes */
40 };
41
42 enum { SSI_PASS, SSI_BUF, SSI_EXEC, SSI_CALL };
43 enum { SSI_GO, SSI_STOP }; /* Conditional states */
44
45 static const struct vec st = {"<!--#", 5};
46
47 void
shttpd_register_ssi_func(struct shttpd_ctx * ctx,const char * name,shttpd_callback_t func,void * user_data)48 shttpd_register_ssi_func(struct shttpd_ctx *ctx, const char *name,
49 shttpd_callback_t func, void *user_data)
50 {
51 struct ssi_func *e;
52
53 if ((e = malloc(sizeof(*e))) != NULL) {
54 e->name = _shttpd_strdup(name);
55 e->func = func;
56 e->user_data = user_data;
57 LL_TAIL(&ctx->ssi_funcs, &e->link);
58 }
59 }
60
61 void
_shttpd_ssi_func_destructor(struct llhead * lp)62 _shttpd_ssi_func_destructor(struct llhead *lp)
63 {
64 struct ssi_func *e = LL_ENTRY(lp, struct ssi_func, link);
65
66 free(e->name);
67 free(e);
68 }
69
70 static const struct ssi_func *
find_ssi_func(struct ssi * ssi,const char * name)71 find_ssi_func(struct ssi *ssi, const char *name)
72 {
73 struct ssi_func *e;
74 struct llhead *lp;
75
76 LL_FOREACH(&ssi->conn->ctx->ssi_funcs, lp) {
77 e = LL_ENTRY(lp, struct ssi_func, link);
78 if (!strcmp(name, e->name))
79 return (e);
80 }
81
82 return (NULL);
83 }
84
85 static void
call(struct ssi * ssi,const char * name,struct shttpd_arg * arg,char * buf,int len)86 call(struct ssi *ssi, const char *name,
87 struct shttpd_arg *arg, char *buf, int len)
88 {
89 const struct ssi_func *ssi_func;
90
91 (void) memset(arg, 0, sizeof(*arg));
92
93 /*
94 * SSI function may be called with parameters. These parameters
95 * are passed as arg->in.buf, arg->in.len vector.
96 */
97 arg->in.buf = strchr(name, ' ');
98 if (arg->in.buf != NULL) {
99 *arg->in.buf++ = '\0';
100 arg->in.len = strlen(arg->in.buf);
101 }
102
103 if ((ssi_func = find_ssi_func(ssi, name)) != NULL) {
104 arg->priv = ssi->conn;
105 arg->user_data = ssi_func->user_data;
106 arg->out.buf = buf;
107 arg->out.len = len;
108 ssi_func->func(arg);
109 }
110 }
111
112 static int
evaluate(struct ssi * ssi,const char * name)113 evaluate(struct ssi *ssi, const char *name)
114 {
115 struct shttpd_arg arg;
116
117 call(ssi, name, &arg, NULL, 0);
118
119 return (arg.flags & SHTTPD_SSI_EVAL_TRUE);
120 }
121
122 static void
pass(struct ssi_inc * inc,void * buf,int * n)123 pass(struct ssi_inc *inc, void *buf, int *n)
124 {
125 if (inc->cond == SSI_GO) {
126 (void) memcpy(buf, inc->buf, inc->nbuf);
127 (*n) += inc->nbuf;
128 }
129 inc->nbuf = 0;
130 inc->state = SSI_PASS;
131 }
132
133 static int
get_path(struct conn * conn,const char * src,int src_len,char * dst,int dst_len)134 get_path(struct conn *conn, const char *src,
135 int src_len, char *dst, int dst_len)
136 {
137 static struct vec accepted[] = {
138 {"\"", 1}, /* Relative to webserver CWD */
139 {"file=\"", 6}, /* Relative to current URI */
140 {"virtual=\"", 9}, /* Relative to document root */
141 {NULL, 0},
142 };
143 struct vec *vec;
144 const char *p, *root = conn->ctx->options[OPT_ROOT];
145 int len;
146
147 for (vec = accepted; vec->len > 0; vec++)
148 if (src_len > vec->len && !memcmp(src, vec->ptr, vec->len)) {
149 src += vec->len;
150 src_len -= vec->len;
151 if ((p = memchr(src, '"', src_len)) == NULL)
152 break;
153 if (vec->len == 6) {
154 len = _shttpd_snprintf(dst, dst_len, "%s%c%s",
155 root, DIRSEP, conn->uri);
156 while (len > 0 && dst[len] != '/')
157 len--;
158 dst += len;
159 dst_len -= len;
160 } else if (vec->len == 9) {
161 len = _shttpd_snprintf(dst, dst_len, "%s%c",
162 root, DIRSEP);
163 dst += len;
164 dst_len -= len;
165 }
166 _shttpd_url_decode(src, p - src, dst, dst_len);
167 return (1);
168 }
169
170 return (0);
171 }
172
173 static void
do_include(struct ssi * ssi)174 do_include(struct ssi *ssi)
175 {
176 struct ssi_inc *inc = ssi->incs + ssi->nest;
177 char buf[FILENAME_MAX];
178 FILE *fp;
179
180 assert(inc->nbuf >= 13);
181
182 if (inc->cond == SSI_STOP) {
183 /* Do nothing - conditional FALSE */
184 } else if (ssi->nest >= (int) NELEMS(ssi->incs) - 1) {
185 _shttpd_elog(E_LOG, ssi->conn,
186 "ssi: #include: maximum nested level reached");
187 } else if (!get_path(ssi->conn,
188 inc->buf + 13, inc->nbuf - 13, buf, sizeof(buf))) {
189 _shttpd_elog(E_LOG, ssi->conn, "ssi: bad #include: [%.*s]",
190 inc->nbuf, inc->buf);
191 } else if ((fp = fopen(buf, "r")) == NULL) {
192 _shttpd_elog(E_LOG, ssi->conn,
193 "ssi: fopen(%s): %s", buf, strerror(errno));
194 } else {
195 ssi->nest++;
196 ssi->incs[ssi->nest].fp = fp;
197 ssi->incs[ssi->nest].nbuf = 0;
198 ssi->incs[ssi->nest].cond = SSI_GO;
199 }
200 }
201
202 static char *
trim_spaces(struct ssi_inc * inc)203 trim_spaces(struct ssi_inc *inc)
204 {
205 char *p = inc->buf + inc->nbuf - 2;
206
207 /* Trim spaces from the right */
208 *p-- = '\0';
209 while (isspace(* (unsigned char *) p))
210 *p-- = '\0';
211
212 /* Shift pointer to the start of attributes */
213 for (p = inc->buf; !isspace(* (unsigned char *) p); p++);
214 while (*p && isspace(* (unsigned char *) p)) p++;
215
216 return (p);
217 }
218
219 static void
do_if(struct ssi * ssi)220 do_if(struct ssi *ssi)
221 {
222 struct ssi_inc *inc = ssi->incs + ssi->nest;
223 char *name = trim_spaces(inc);
224
225 inc->cond = evaluate(ssi, name) ? SSI_GO : SSI_STOP;
226 }
227
228 static void
do_elif(struct ssi * ssi)229 do_elif(struct ssi *ssi)
230 {
231 struct ssi_inc *inc = ssi->incs + ssi->nest;
232 char *name = trim_spaces(inc);
233
234 if (inc->cond == SSI_STOP && evaluate(ssi, name))
235 inc->cond = SSI_GO;
236 else
237 inc->cond = SSI_STOP;
238 }
239 static void
do_endif(struct ssi * ssi)240 do_endif(struct ssi *ssi)
241 {
242 ssi->incs[ssi->nest].cond = SSI_GO;
243 }
244
245 static void
do_else(struct ssi * ssi)246 do_else(struct ssi *ssi)
247 {
248 struct ssi_inc *inc = ssi->incs + ssi->nest;
249
250 inc->cond = inc->cond == SSI_GO ? SSI_STOP : SSI_GO;
251 }
252
253 static void
do_call2(struct ssi * ssi,char * buf,int len,int * n)254 do_call2(struct ssi *ssi, char *buf, int len, int *n)
255 {
256 struct ssi_inc *inc = ssi->incs + ssi->nest;
257 struct shttpd_arg arg;
258
259 call(ssi, inc->buf, &arg, buf, len);
260 (*n) += arg.out.num_bytes;
261 if (arg.flags & SHTTPD_END_OF_OUTPUT)
262 inc->state = SSI_PASS;
263 }
264
265 static void
do_call(struct ssi * ssi,char * buf,int len,int * n)266 do_call(struct ssi *ssi, char *buf, int len, int *n)
267 {
268 struct ssi_inc *inc = ssi->incs + ssi->nest;
269 char *name = trim_spaces(inc);
270
271 if (inc->cond == SSI_GO) {
272 (void) memmove(inc->buf, name, strlen(name) + 1);
273 inc->state = SSI_CALL;
274 do_call2(ssi, buf, len, n);
275 }
276 }
277
278 static void
do_exec2(struct ssi * ssi,char * buf,int len,int * n)279 do_exec2(struct ssi *ssi, char *buf, int len, int *n)
280 {
281 struct ssi_inc *inc = ssi->incs + ssi->nest;
282 int i, ch;
283
284 for (i = 0; i < len; i++) {
285 if ((ch = fgetc(inc->pipe)) == EOF) {
286 inc->state = SSI_PASS;
287 (void) pclose(inc->pipe);
288 inc->pipe = NULL;
289 break;
290 }
291 *buf++ = ch;
292 (*n)++;
293 }
294 }
295
296 static void
do_exec(struct ssi * ssi,char * buf,int len,int * n)297 do_exec(struct ssi *ssi, char *buf, int len, int *n)
298 {
299 struct ssi_inc *inc = ssi->incs + ssi->nest;
300 char cmd[sizeof(inc->buf)], *e, *p;
301
302 p = trim_spaces(inc);
303
304 if (inc->cond == SSI_STOP) {
305 /* Do nothing - conditional FALSE */
306 } else if (*p != '"' || (e = strchr(p + 1, '"')) == NULL) {
307 _shttpd_elog(E_LOG, ssi->conn, "ssi: bad exec(%s)", p);
308 } else if (!_shttpd_url_decode(p + 1, e - p - 1, cmd, sizeof(cmd))) {
309 _shttpd_elog(E_LOG, ssi->conn,
310 "ssi: cannot url_decode: exec(%s)", p);
311 } else if ((inc->pipe = popen(cmd, "r")) == NULL) {
312 _shttpd_elog(E_LOG, ssi->conn, "ssi: popen(%s)", cmd);
313 } else {
314 inc->state = SSI_EXEC;
315 do_exec2(ssi, buf, len, n);
316 }
317 }
318
319 static const struct ssi_cmd {
320 struct vec vec;
321 void (*func)();
322 } known_ssi_commands [] = {
323 {{"include ", 8}, do_include },
324 {{"if ", 3}, do_if },
325 {{"elif ", 5}, do_elif },
326 {{"else", 4}, do_else },
327 {{"endif", 5}, do_endif },
328 {{"call ", 5}, do_call },
329 {{"exec ", 5}, do_exec },
330 {{NULL, 0}, NULL }
331 };
332
333 static void
do_command(struct ssi * ssi,char * buf,size_t len,int * n)334 do_command(struct ssi *ssi, char *buf, size_t len, int *n)
335 {
336 struct ssi_inc *inc = ssi->incs + ssi->nest;
337 const struct ssi_cmd *cmd;
338
339 assert(len > 0);
340 assert(inc->nbuf <= len);
341 inc->state = SSI_PASS;
342
343 for (cmd = known_ssi_commands; cmd->func != NULL; cmd++)
344 if (inc->nbuf > (size_t) st.len + cmd->vec.len &&
345 !memcmp(inc->buf + st.len, cmd->vec.ptr, cmd->vec.len)) {
346 cmd->func(ssi, buf, len, n);
347 break;
348 }
349
350 if (cmd->func == NULL)
351 pass(inc, buf, n);
352
353 inc->nbuf = 0;
354 }
355
356 static int
read_ssi(struct stream * stream,void * vbuf,size_t len)357 read_ssi(struct stream *stream, void *vbuf, size_t len)
358 {
359 struct ssi *ssi = stream->conn->ssi;
360 struct ssi_inc *inc = ssi->incs + ssi->nest;
361 char *buf = vbuf;
362 int ch = EOF, n = 0;
363
364 again:
365
366 if (inc->state == SSI_CALL)
367 do_call2(ssi, buf, len, &n);
368 else if (inc->state == SSI_EXEC)
369 do_exec2(ssi, buf, len, &n);
370
371 while (n + inc->nbuf < len && (ch = fgetc(inc->fp)) != EOF)
372
373 switch (inc->state) {
374
375 case SSI_PASS:
376 if (ch == '<') {
377 inc->nbuf = 0;
378 inc->buf[inc->nbuf++] = ch;
379 inc->state = SSI_BUF;
380 } else if (inc->cond == SSI_GO) {
381 buf[n++] = ch;
382 }
383 break;
384
385 /*
386 * We are buffering whole SSI command, until closing "-->".
387 * That means that when do_command() is called, we can rely
388 * on that full command with arguments is buffered in and
389 * there is no need for streaming.
390 * Restrictions:
391 * 1. The command must fit in CMDBUFSIZ
392 * 2. HTML comments inside the command ? Not sure about this.
393 */
394 case SSI_BUF:
395 if (inc->nbuf >= sizeof(inc->buf) - 1) {
396 pass(inc, buf + n, &n);
397 } else if (ch == '>' &&
398 !memcmp(inc->buf + inc->nbuf - 2, "--", 2)) {
399 do_command(ssi, buf + n, len - n, &n);
400 inc = ssi->incs + ssi->nest;
401 } else {
402 inc->buf[inc->nbuf++] = ch;
403
404 /* If not SSI tag, pass it */
405 if (inc->nbuf <= (size_t) st.len &&
406 memcmp(inc->buf, st.ptr, inc->nbuf) != 0)
407 pass(inc, buf + n, &n);
408 }
409 break;
410
411 case SSI_EXEC:
412 case SSI_CALL:
413 break;
414
415 default:
416 /* Never happens */
417 abort();
418 break;
419 }
420
421 if (ssi->nest > 0 && n + inc->nbuf < len && ch == EOF) {
422 (void) fclose(inc->fp);
423 inc->fp = NULL;
424 ssi->nest--;
425 inc--;
426 goto again;
427 }
428
429 return (n);
430 }
431
432 static void
close_ssi(struct stream * stream)433 close_ssi(struct stream *stream)
434 {
435 struct ssi *ssi = stream->conn->ssi;
436 size_t i;
437
438 for (i = 0; i < NELEMS(ssi->incs); i++) {
439 if (ssi->incs[i].fp != NULL)
440 (void) fclose(ssi->incs[i].fp);
441 if (ssi->incs[i].pipe != NULL)
442 (void) pclose(ssi->incs[i].pipe);
443 }
444
445 free(ssi);
446 }
447
448 void
_shttpd_do_ssi(struct conn * c)449 _shttpd_do_ssi(struct conn *c)
450 {
451 char date[64];
452 struct ssi *ssi;
453
454 (void) strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT",
455 localtime(&_shttpd_current_time));
456
457 c->loc.io.head = c->loc.headers_len = _shttpd_snprintf(c->loc.io.buf,
458 c->loc.io.size,
459 "HTTP/1.1 200 OK\r\n"
460 "Date: %s\r\n"
461 "Content-Type: text/html\r\n"
462 "Connection: close\r\n\r\n",
463 date);
464
465 c->status = 200;
466 c->loc.io_class = &_shttpd_io_ssi;
467 c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
468
469 if (c->method == METHOD_HEAD) {
470 _shttpd_stop_stream(&c->loc);
471 } else if ((ssi = calloc(1, sizeof(struct ssi))) == NULL) {
472 _shttpd_send_server_error(c, 500,
473 "Cannot allocate SSI descriptor");
474 } else {
475 ssi->incs[0].fp = fdopen(c->loc.chan.fd, "r");
476 ssi->conn = c;
477 c->ssi = ssi;
478 }
479 }
480
481 const struct io_class _shttpd_io_ssi = {
482 "ssi",
483 read_ssi,
484 NULL,
485 close_ssi
486 };
487
488 #endif /* !NO_SSI */
489