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