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