add embeded http library so we can test remote scenarios
[platform/upstream/libzypp.git] / vendor / 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         = _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
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 *
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
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
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
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
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
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 *
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
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
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
240 do_endif(struct ssi *ssi)
241 {
242         ssi->incs[ssi->nest].cond = SSI_GO;
243 }
244
245 static void
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
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
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
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
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
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
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
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
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 */