2 * Copyright (c) 2006,2007 Steven Johnson <sjohnson@sakuraindustries.com>
3 * Copyright (c) 2007 Sergey Lyubka <valenok@gmail.com>
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.
14 #define CMDBUFSIZ 512 /* SSI command buffer size */
15 #define NEST_MAX 6 /* Maximum nesting level */
21 shttpd_callback_t func;
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 */
35 struct conn *conn; /* Connection we belong to */
36 int nest; /* Current nesting level */
37 struct ssi_inc incs[NEST_MAX]; /* Nested includes */
40 enum { SSI_PASS, SSI_BUF, SSI_EXEC, SSI_CALL };
41 enum { SSI_GO, SSI_STOP }; /* Conditional states */
43 static const struct vec st = {"<!--#", 5};
46 shttpd_register_ssi_func(struct shttpd_ctx *ctx, const char *name,
47 shttpd_callback_t func, void *user_data)
51 if ((e = malloc(sizeof(*e))) != NULL) {
52 e->name = my_strdup(name);
54 e->user_data = user_data;
55 LL_TAIL(&ctx->ssi_funcs, &e->link);
60 ssi_func_destructor(struct llhead *lp)
62 struct ssi_func *e = LL_ENTRY(lp, struct ssi_func, link);
68 static const struct ssi_func *
69 find_ssi_func(struct ssi *ssi, const char *name)
74 LL_FOREACH(&ssi->conn->ctx->ssi_funcs, lp) {
75 e = LL_ENTRY(lp, struct ssi_func, link);
76 if (!strcmp(name, e->name))
84 call(struct ssi *ssi, const char *name,
85 struct shttpd_arg *arg, char *buf, int len)
87 const struct ssi_func *ssi_func;
89 (void) memset(arg, 0, sizeof(*arg));
92 * SSI function may be called with parameters. These parameters
93 * are passed as arg->in.buf, arg->in.len vector.
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);
101 if ((ssi_func = find_ssi_func(ssi, name)) != NULL) {
102 arg->priv = ssi->conn;
103 arg->user_data = ssi_func->user_data;
111 evaluate(struct ssi *ssi, const char *name)
113 struct shttpd_arg arg;
115 call(ssi, name, &arg, NULL, 0);
117 return (arg.flags & SHTTPD_SSI_EVAL_TRUE);
121 pass(struct ssi_inc *inc, void *buf, int *n)
123 if (inc->cond == SSI_GO) {
124 (void) memcpy(buf, inc->buf, inc->nbuf);
128 inc->state = SSI_PASS;
132 get_path(struct conn *conn, const char *src,
133 int src_len, char *dst, int dst_len)
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 */
142 const char *p, *root = conn->ctx->options[OPT_ROOT];
145 for (vec = accepted; vec->len > 0; vec++)
146 if (src_len > vec->len && !memcmp(src, vec->ptr, vec->len)) {
149 if ((p = memchr(src, '"', src_len)) == NULL)
152 len = my_snprintf(dst, dst_len, "%s%c%s",
153 root, DIRSEP, conn->uri);
154 while (len > 0 && dst[len] != '/')
158 } else if (vec->len == 9) {
159 len = my_snprintf(dst, dst_len, "%s%c",
164 url_decode(src, p - src, dst, dst_len);
172 do_include(struct ssi *ssi)
174 struct ssi_inc *inc = ssi->incs + ssi->nest;
175 char buf[FILENAME_MAX];
178 assert(inc->nbuf >= 13);
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));
194 ssi->incs[ssi->nest].fp = fp;
195 ssi->incs[ssi->nest].nbuf = 0;
196 ssi->incs[ssi->nest].cond = SSI_GO;
201 trim_spaces(struct ssi_inc *inc)
203 char *p = inc->buf + inc->nbuf - 2;
205 /* Trim spaces from the right */
207 while (isspace(* (unsigned char *) p))
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++;
218 do_if(struct ssi *ssi)
220 struct ssi_inc *inc = ssi->incs + ssi->nest;
221 char *name = trim_spaces(inc);
223 inc->cond = evaluate(ssi, name) ? SSI_GO : SSI_STOP;
227 do_elif(struct ssi *ssi)
229 struct ssi_inc *inc = ssi->incs + ssi->nest;
230 char *name = trim_spaces(inc);
232 if (inc->cond == SSI_STOP && evaluate(ssi, name))
235 inc->cond = SSI_STOP;
238 do_endif(struct ssi *ssi)
240 ssi->incs[ssi->nest].cond = SSI_GO;
244 do_else(struct ssi *ssi)
246 struct ssi_inc *inc = ssi->incs + ssi->nest;
248 inc->cond = inc->cond == SSI_GO ? SSI_STOP : SSI_GO;
252 do_call2(struct ssi *ssi, char *buf, int len, int *n)
254 struct ssi_inc *inc = ssi->incs + ssi->nest;
255 struct shttpd_arg arg;
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;
264 do_call(struct ssi *ssi, char *buf, int len, int *n)
266 struct ssi_inc *inc = ssi->incs + ssi->nest;
267 char *name = trim_spaces(inc);
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);
277 do_exec2(struct ssi *ssi, char *buf, int len, int *n)
279 struct ssi_inc *inc = ssi->incs + ssi->nest;
282 for (i = 0; i < len; i++) {
283 if ((ch = fgetc(inc->pipe)) == EOF) {
284 inc->state = SSI_PASS;
285 (void) pclose(inc->pipe);
295 do_exec(struct ssi *ssi, char *buf, int len, int *n)
297 struct ssi_inc *inc = ssi->incs + ssi->nest;
298 char cmd[sizeof(inc->buf)], *e, *p;
300 p = trim_spaces(inc);
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);
311 inc->state = SSI_EXEC;
312 do_exec2(ssi, buf, len, n);
316 static const struct ssi_cmd {
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 },
331 do_command(struct ssi *ssi, char *buf, size_t len, int *n)
333 struct ssi_inc *inc = ssi->incs + ssi->nest;
334 const struct ssi_cmd *cmd;
337 assert(inc->nbuf <= len);
338 inc->state = SSI_PASS;
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);
347 if (cmd->func == NULL)
354 read_ssi(struct stream *stream, void *vbuf, size_t len)
356 struct ssi *ssi = stream->conn->ssi;
357 struct ssi_inc *inc = ssi->incs + ssi->nest;
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);
368 while (n + inc->nbuf < len && (ch = fgetc(inc->fp)) != EOF)
370 switch (inc->state) {
375 inc->buf[inc->nbuf++] = ch;
376 inc->state = SSI_BUF;
377 } else if (inc->cond == SSI_GO) {
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.
388 * 1. The command must fit in CMDBUFSIZ
389 * 2. HTML comments inside the command ? Not sure about this.
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;
399 inc->buf[inc->nbuf++] = ch;
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);
418 if (ssi->nest > 0 && n + inc->nbuf < len && ch == EOF) {
419 (void) fclose(inc->fp);
430 close_ssi(struct stream *stream)
432 struct ssi *ssi = stream->conn->ssi;
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);
446 do_ssi(struct conn *c)
451 (void) strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT",
452 localtime(¤t_time));
454 c->loc.io.head = c->loc.headers_len = my_snprintf(c->loc.io.buf,
456 "HTTP/1.1 200 OK\r\n"
458 "Content-Type: text/html\r\n"
459 "Connection: close\r\n\r\n",
463 c->loc.io_class = &io_ssi;
464 c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
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");
471 ssi->incs[0].fp = fdopen(c->loc.chan.fd, "r");
477 const struct io_class io_ssi = {