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