xref: /openbsd/usr.bin/mandoc/cgi.c (revision 93d3e4e1)
1*93d3e4e1Sderaadt /*	$OpenBSD: cgi.c,v 1.85 2017/01/25 03:19:56 deraadt Exp $ */
2c6c22f12Sschwarze /*
3c6c22f12Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4ce781f36Sschwarze  * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze <schwarze@usta.de>
5c6c22f12Sschwarze  *
6c6c22f12Sschwarze  * Permission to use, copy, modify, and distribute this software for any
7c6c22f12Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8c6c22f12Sschwarze  * copyright notice and this permission notice appear in all copies.
9c6c22f12Sschwarze  *
104de77decSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11c6c22f12Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124de77decSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13c6c22f12Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c6c22f12Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15c6c22f12Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16c6c22f12Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c6c22f12Sschwarze  */
18136c26b8Sschwarze #include <sys/types.h>
19136c26b8Sschwarze #include <sys/time.h>
20136c26b8Sschwarze 
21c6c22f12Sschwarze #include <ctype.h>
22c976f0e2Sschwarze #include <err.h>
23c6c22f12Sschwarze #include <errno.h>
24c6c22f12Sschwarze #include <fcntl.h>
25c6c22f12Sschwarze #include <limits.h>
26c2235d37Sschwarze #include <stdint.h>
27c6c22f12Sschwarze #include <stdio.h>
28c6c22f12Sschwarze #include <stdlib.h>
29c6c22f12Sschwarze #include <string.h>
30c6c22f12Sschwarze #include <unistd.h>
31c6c22f12Sschwarze 
32c6c22f12Sschwarze #include "mandoc_aux.h"
33f2d5c709Sschwarze #include "mandoc.h"
34f2d5c709Sschwarze #include "roff.h"
35396853b5Sschwarze #include "mdoc.h"
36fec2846bSschwarze #include "man.h"
37c6c22f12Sschwarze #include "main.h"
384de77decSschwarze #include "manconf.h"
39c6c22f12Sschwarze #include "mansearch.h"
406fdade3eSschwarze #include "cgi.h"
41c6c22f12Sschwarze 
42c6c22f12Sschwarze /*
43c6c22f12Sschwarze  * A query as passed to the search function.
44c6c22f12Sschwarze  */
45c6c22f12Sschwarze struct	query {
4631e689c3Sschwarze 	char		*manpath; /* desired manual directory */
4731e689c3Sschwarze 	char		*arch; /* architecture */
4831e689c3Sschwarze 	char		*sec; /* manual section */
49e89321abSschwarze 	char		*query; /* unparsed query expression */
504477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
51c6c22f12Sschwarze };
52c6c22f12Sschwarze 
53c6c22f12Sschwarze struct	req {
54c6c22f12Sschwarze 	struct query	  q;
55c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
56c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
5752b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
58c6c22f12Sschwarze };
59c6c22f12Sschwarze 
6084f05c93Sschwarze enum	focus {
6184f05c93Sschwarze 	FOCUS_NONE = 0,
6284f05c93Sschwarze 	FOCUS_QUERY
6384f05c93Sschwarze };
6484f05c93Sschwarze 
65c6c22f12Sschwarze static	void		 html_print(const char *);
66c6c22f12Sschwarze static	void		 html_putchar(char);
67c6c22f12Sschwarze static	int		 http_decode(char *);
68941df026Sschwarze static	void		 parse_manpath_conf(struct req *);
69941df026Sschwarze static	void		 parse_path_info(struct req *req, const char *path);
70941df026Sschwarze static	void		 parse_query_string(struct req *, const char *);
71facea411Sschwarze static	void		 pg_error_badrequest(const char *);
72facea411Sschwarze static	void		 pg_error_internal(void);
73facea411Sschwarze static	void		 pg_index(const struct req *);
74facea411Sschwarze static	void		 pg_noresult(const struct req *, const char *);
7557482ef4Sschwarze static	void		 pg_search(const struct req *);
76facea411Sschwarze static	void		 pg_searchres(const struct req *,
77facea411Sschwarze 				struct manpage *, size_t);
7881060b1aSschwarze static	void		 pg_show(struct req *, const char *);
79c6c22f12Sschwarze static	void		 resp_begin_html(int, const char *);
80c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
81941df026Sschwarze static	void		 resp_catman(const struct req *, const char *);
82711661c7Sschwarze static	void		 resp_copy(const char *);
83c6c22f12Sschwarze static	void		 resp_end_html(void);
84941df026Sschwarze static	void		 resp_format(const struct req *, const char *);
8584f05c93Sschwarze static	void		 resp_searchform(const struct req *, enum focus);
8646723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
87e89321abSschwarze static	void		 set_query_attr(char **, char **);
88e89321abSschwarze static	int		 validate_filename(const char *);
89e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
90e89321abSschwarze static	int		 validate_urifrag(const char *);
91c6c22f12Sschwarze 
923b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
93c6c22f12Sschwarze 
9446723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
9528e449d6Sschwarze static	const char *const sec_numbers[] = {
9628e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
9728e449d6Sschwarze };
9828e449d6Sschwarze static	const char *const sec_names[] = {
9928e449d6Sschwarze     "All Sections",
10028e449d6Sschwarze     "1 - General Commands",
10128e449d6Sschwarze     "2 - System Calls",
1024e6618fdSschwarze     "3 - Library Functions",
1034e6618fdSschwarze     "3p - Perl Library",
1044e6618fdSschwarze     "4 - Device Drivers",
10528e449d6Sschwarze     "5 - File Formats",
10628e449d6Sschwarze     "6 - Games",
1074e6618fdSschwarze     "7 - Miscellaneous Information",
1084e6618fdSschwarze     "8 - System Manager\'s Manual",
1094e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
11028e449d6Sschwarze };
11128e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
11228e449d6Sschwarze 
11328e449d6Sschwarze static	const char *const arch_names[] = {
114*93d3e4e1Sderaadt     "amd64",       "alpha",       "armv7",	"arm64",
115766ef059Sschwarze     "hppa",        "i386",        "landisk",
1163bcd4815Sschwarze     "loongson",    "luna88k",     "macppc",      "mips64",
1176250a715Sschwarze     "octeon",      "sgi",         "socppc",      "sparc64",
118766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
119766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
120766ef059Sschwarze     "hppa64",      "hp300",
1213bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1223bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
1230c245db5Sschwarze     "pmax",        "powerpc",     "solbourne",   "sparc",
1246250a715Sschwarze     "sun3",        "vax",         "wgrisc",      "x68k",
1256250a715Sschwarze     "zaurus"
12628e449d6Sschwarze };
12728e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
12828e449d6Sschwarze 
129c6c22f12Sschwarze /*
130c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
131c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
132c6c22f12Sschwarze  */
133c6c22f12Sschwarze static void
134c6c22f12Sschwarze html_putchar(char c)
135c6c22f12Sschwarze {
136c6c22f12Sschwarze 
137c6c22f12Sschwarze 	switch (c) {
138c6c22f12Sschwarze 	case ('"'):
139f765a656Sbentley 		printf("&quot;");
140c6c22f12Sschwarze 		break;
141c6c22f12Sschwarze 	case ('&'):
142c6c22f12Sschwarze 		printf("&amp;");
143c6c22f12Sschwarze 		break;
144c6c22f12Sschwarze 	case ('>'):
145c6c22f12Sschwarze 		printf("&gt;");
146c6c22f12Sschwarze 		break;
147c6c22f12Sschwarze 	case ('<'):
148c6c22f12Sschwarze 		printf("&lt;");
149c6c22f12Sschwarze 		break;
150c6c22f12Sschwarze 	default:
151c6c22f12Sschwarze 		putchar((unsigned char)c);
152c6c22f12Sschwarze 		break;
153c6c22f12Sschwarze 	}
154c6c22f12Sschwarze }
155c6c22f12Sschwarze 
156c6c22f12Sschwarze /*
157c6c22f12Sschwarze  * Call through to html_putchar().
158c6c22f12Sschwarze  * Accepts NULL strings.
159c6c22f12Sschwarze  */
160c6c22f12Sschwarze static void
161c6c22f12Sschwarze html_print(const char *p)
162c6c22f12Sschwarze {
163c6c22f12Sschwarze 
164c6c22f12Sschwarze 	if (NULL == p)
165c6c22f12Sschwarze 		return;
166c6c22f12Sschwarze 	while ('\0' != *p)
167c6c22f12Sschwarze 		html_putchar(*p++);
168c6c22f12Sschwarze }
169c6c22f12Sschwarze 
170c6c22f12Sschwarze /*
17131e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
17231e689c3Sschwarze  * to the query structure.
173c6c22f12Sschwarze  */
174c6c22f12Sschwarze static void
17531e689c3Sschwarze set_query_attr(char **attr, char **val)
17631e689c3Sschwarze {
17731e689c3Sschwarze 
17831e689c3Sschwarze 	free(*attr);
17931e689c3Sschwarze 	if (**val == '\0') {
18031e689c3Sschwarze 		*attr = NULL;
18131e689c3Sschwarze 		free(*val);
18231e689c3Sschwarze 	} else
18331e689c3Sschwarze 		*attr = *val;
18431e689c3Sschwarze 	*val = NULL;
18531e689c3Sschwarze }
18631e689c3Sschwarze 
18731e689c3Sschwarze /*
18831e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
18931e689c3Sschwarze  * and store the values into the query structure.
19031e689c3Sschwarze  */
19131e689c3Sschwarze static void
192941df026Sschwarze parse_query_string(struct req *req, const char *qs)
193c6c22f12Sschwarze {
194c6c22f12Sschwarze 	char		*key, *val;
19531e689c3Sschwarze 	size_t		 keysz, valsz;
196c6c22f12Sschwarze 
19752b413c1Sschwarze 	req->isquery	= 1;
19831e689c3Sschwarze 	req->q.manpath	= NULL;
19931e689c3Sschwarze 	req->q.arch	= NULL;
20031e689c3Sschwarze 	req->q.sec	= NULL;
201e89321abSschwarze 	req->q.query	= NULL;
2024477fbfaSschwarze 	req->q.equal	= 1;
203c6c22f12Sschwarze 
20431e689c3Sschwarze 	key = val = NULL;
20531e689c3Sschwarze 	while (*qs != '\0') {
206c6c22f12Sschwarze 
20731e689c3Sschwarze 		/* Parse one key. */
208c6c22f12Sschwarze 
20931e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
21031e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
21131e689c3Sschwarze 		qs += keysz;
21231e689c3Sschwarze 		if (*qs != '=')
21331e689c3Sschwarze 			goto next;
214c6c22f12Sschwarze 
21531e689c3Sschwarze 		/* Parse one value. */
216c6c22f12Sschwarze 
21731e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
21831e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
21931e689c3Sschwarze 		qs += valsz;
220c6c22f12Sschwarze 
22131e689c3Sschwarze 		/* Decode and catch encoding errors. */
22231e689c3Sschwarze 
22331e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
22431e689c3Sschwarze 			goto next;
22531e689c3Sschwarze 
22631e689c3Sschwarze 		/* Handle key-value pairs. */
22731e689c3Sschwarze 
22831e689c3Sschwarze 		if ( ! strcmp(key, "query"))
229e89321abSschwarze 			set_query_attr(&req->q.query, &val);
23031e689c3Sschwarze 
23131e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
23231e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
23331e689c3Sschwarze 
23431e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
235aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
23631e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
237aabcc9a1Sschwarze 				val[7] = '-';
238aabcc9a1Sschwarze 				if ('C' == val[8])
239aabcc9a1Sschwarze 					val[8] = 'c';
240aabcc9a1Sschwarze 			}
241aabcc9a1Sschwarze #endif
24231e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
24331e689c3Sschwarze 		}
24431e689c3Sschwarze 
24531e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
246aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24731e689c3Sschwarze 		    && strcmp(key, "sektion")
248aabcc9a1Sschwarze #endif
24931e689c3Sschwarze 		    )) {
25031e689c3Sschwarze 			if ( ! strcmp(val, "0"))
25131e689c3Sschwarze 				*val = '\0';
25231e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
253c6c22f12Sschwarze 		}
25431e689c3Sschwarze 
25531e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
25631e689c3Sschwarze 			if ( ! strcmp(val, "default"))
25731e689c3Sschwarze 				*val = '\0';
25831e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2594477fbfaSschwarze 		}
26031e689c3Sschwarze 
26131e689c3Sschwarze 		/*
26231e689c3Sschwarze 		 * The key must be freed in any case.
26331e689c3Sschwarze 		 * The val may have been handed over to the query
26431e689c3Sschwarze 		 * structure, in which case it is now NULL.
26531e689c3Sschwarze 		 */
26631e689c3Sschwarze next:
26731e689c3Sschwarze 		free(key);
26831e689c3Sschwarze 		key = NULL;
26931e689c3Sschwarze 		free(val);
27031e689c3Sschwarze 		val = NULL;
27131e689c3Sschwarze 
27231e689c3Sschwarze 		if (*qs != '\0')
27331e689c3Sschwarze 			qs++;
27431e689c3Sschwarze 	}
275c6c22f12Sschwarze }
276c6c22f12Sschwarze 
277c6c22f12Sschwarze /*
278c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
279c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
280c6c22f12Sschwarze  * over the allocated string.
281c6c22f12Sschwarze  */
282c6c22f12Sschwarze static int
283c6c22f12Sschwarze http_decode(char *p)
284c6c22f12Sschwarze {
285c6c22f12Sschwarze 	char             hex[3];
2861f69f32bStedu 	char		*q;
287c6c22f12Sschwarze 	int              c;
288c6c22f12Sschwarze 
289c6c22f12Sschwarze 	hex[2] = '\0';
290c6c22f12Sschwarze 
2911f69f32bStedu 	q = p;
2921f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
293c6c22f12Sschwarze 		if ('%' == *p) {
294c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
295526e306bSschwarze 				return 0;
296c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
297526e306bSschwarze 				return 0;
298c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
299526e306bSschwarze 				return 0;
300c6c22f12Sschwarze 			if ('\0' == c)
301526e306bSschwarze 				return 0;
302c6c22f12Sschwarze 
3031f69f32bStedu 			*q = (char)c;
3041f69f32bStedu 			p += 2;
305c6c22f12Sschwarze 		} else
3061f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
307c6c22f12Sschwarze 	}
308c6c22f12Sschwarze 
3091f69f32bStedu 	*q = '\0';
310526e306bSschwarze 	return 1;
311c6c22f12Sschwarze }
312c6c22f12Sschwarze 
313c6c22f12Sschwarze static void
314c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
315c6c22f12Sschwarze {
316c6c22f12Sschwarze 
317c6c22f12Sschwarze 	if (200 != code)
318fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
319c6c22f12Sschwarze 
320fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
321fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
322fa9c540aStedu 	     "Pragma: no-cache\r\n"
323fa9c540aStedu 	     "\r\n");
324c6c22f12Sschwarze 
325c6c22f12Sschwarze 	fflush(stdout);
326c6c22f12Sschwarze }
327c6c22f12Sschwarze 
328c6c22f12Sschwarze static void
329711661c7Sschwarze resp_copy(const char *filename)
330711661c7Sschwarze {
331711661c7Sschwarze 	char	 buf[4096];
332711661c7Sschwarze 	ssize_t	 sz;
333711661c7Sschwarze 	int	 fd;
334711661c7Sschwarze 
335711661c7Sschwarze 	if ((fd = open(filename, O_RDONLY)) != -1) {
336711661c7Sschwarze 		fflush(stdout);
337711661c7Sschwarze 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
338711661c7Sschwarze 			write(STDOUT_FILENO, buf, sz);
339fd3cdd86Sjsg 		close(fd);
340711661c7Sschwarze 	}
341711661c7Sschwarze }
342711661c7Sschwarze 
343711661c7Sschwarze static void
344c6c22f12Sschwarze resp_begin_html(int code, const char *msg)
345c6c22f12Sschwarze {
346c6c22f12Sschwarze 
347c6c22f12Sschwarze 	resp_begin_http(code, msg);
348c6c22f12Sschwarze 
349d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
350735516bdSschwarze 	       "<html>\n"
351735516bdSschwarze 	       "<head>\n"
352735516bdSschwarze 	       "  <meta charset=\"UTF-8\"/>\n"
353735516bdSschwarze 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
354735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
355735516bdSschwarze 	       "  <title>%s</title>\n"
356735516bdSschwarze 	       "</head>\n"
357ce781f36Sschwarze 	       "<body>\n",
35806bcd913Sschwarze 	       CSS_DIR, CUSTOMIZE_TITLE);
359711661c7Sschwarze 
360711661c7Sschwarze 	resp_copy(MAN_DIR "/header.html");
361c6c22f12Sschwarze }
362c6c22f12Sschwarze 
363c6c22f12Sschwarze static void
364c6c22f12Sschwarze resp_end_html(void)
365c6c22f12Sschwarze {
366c6c22f12Sschwarze 
367711661c7Sschwarze 	resp_copy(MAN_DIR "/footer.html");
368711661c7Sschwarze 
369735516bdSschwarze 	puts("</body>\n"
370735516bdSschwarze 	     "</html>");
371c6c22f12Sschwarze }
372c6c22f12Sschwarze 
373c6c22f12Sschwarze static void
37484f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
375c6c22f12Sschwarze {
376c6c22f12Sschwarze 	int		 i;
377c6c22f12Sschwarze 
378ce781f36Sschwarze 	printf("<form action=\"/%s\" method=\"get\">\n"
379735516bdSschwarze 	       "  <fieldset>\n"
380735516bdSschwarze 	       "    <legend>Manual Page Search Parameters</legend>\n",
381c6c22f12Sschwarze 	       scriptname);
38228e449d6Sschwarze 
38328e449d6Sschwarze 	/* Write query input box. */
38428e449d6Sschwarze 
385784a63d6Sschwarze 	printf("    <input type=\"text\" name=\"query\" value=\"");
38684f05c93Sschwarze 	if (req->q.query != NULL)
387e89321abSschwarze 		html_print(req->q.query);
38884f05c93Sschwarze 	printf( "\" size=\"40\"");
38984f05c93Sschwarze 	if (focus == FOCUS_QUERY)
39084f05c93Sschwarze 		printf(" autofocus");
39184f05c93Sschwarze 	puts(">");
39228e449d6Sschwarze 
393784a63d6Sschwarze 	/* Write submission buttons. */
39428e449d6Sschwarze 
395784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
396784a63d6Sschwarze 		"man</button>\n"
397784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
398542ee4bfSschwarze 		"apropos</button>\n"
399542ee4bfSschwarze 		"    <br/>\n");
40028e449d6Sschwarze 
40128e449d6Sschwarze 	/* Write section selector. */
40228e449d6Sschwarze 
403784a63d6Sschwarze 	puts("    <select name=\"sec\">");
40428e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
405735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
40628e449d6Sschwarze 		if (NULL != req->q.sec &&
40728e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
408735516bdSschwarze 			printf(" selected=\"selected\"");
409735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
41028e449d6Sschwarze 	}
411735516bdSschwarze 	puts("    </select>");
41228e449d6Sschwarze 
41328e449d6Sschwarze 	/* Write architecture selector. */
41428e449d6Sschwarze 
415735516bdSschwarze 	printf(	"    <select name=\"arch\">\n"
416735516bdSschwarze 		"      <option value=\"default\"");
417be14a32aSschwarze 	if (NULL == req->q.arch)
418735516bdSschwarze 		printf(" selected=\"selected\"");
419735516bdSschwarze 	puts(">All Architectures</option>");
42028e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
421735516bdSschwarze 		printf("      <option value=\"%s\"", arch_names[i]);
42228e449d6Sschwarze 		if (NULL != req->q.arch &&
42328e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
424735516bdSschwarze 			printf(" selected=\"selected\"");
425735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
42628e449d6Sschwarze 	}
427735516bdSschwarze 	puts("    </select>");
42828e449d6Sschwarze 
42928e449d6Sschwarze 	/* Write manpath selector. */
43028e449d6Sschwarze 
431c6c22f12Sschwarze 	if (req->psz > 1) {
432735516bdSschwarze 		puts("    <select name=\"manpath\">");
433c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
434735516bdSschwarze 			printf("      <option ");
43550eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
436735516bdSschwarze 				printf("selected=\"selected\" ");
437735516bdSschwarze 			printf("value=\"");
438c6c22f12Sschwarze 			html_print(req->p[i]);
439c6c22f12Sschwarze 			printf("\">");
440c6c22f12Sschwarze 			html_print(req->p[i]);
441735516bdSschwarze 			puts("</option>");
442c6c22f12Sschwarze 		}
443735516bdSschwarze 		puts("    </select>");
444c6c22f12Sschwarze 	}
44528e449d6Sschwarze 
446784a63d6Sschwarze 	puts("  </fieldset>\n"
447ce781f36Sschwarze 	     "</form>");
448c6c22f12Sschwarze }
449c6c22f12Sschwarze 
45081475784Sschwarze static int
451cf3a545cSschwarze validate_urifrag(const char *frag)
452cf3a545cSschwarze {
453cf3a545cSschwarze 
454cf3a545cSschwarze 	while ('\0' != *frag) {
455cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
456cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
457cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
458526e306bSschwarze 			return 0;
459cf3a545cSschwarze 		frag++;
460cf3a545cSschwarze 	}
461526e306bSschwarze 	return 1;
462cf3a545cSschwarze }
463cf3a545cSschwarze 
464cf3a545cSschwarze static int
465631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
466631ce2c6Sschwarze {
467631ce2c6Sschwarze 	size_t	 i;
468631ce2c6Sschwarze 
469631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
470631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
471526e306bSschwarze 			return 1;
472631ce2c6Sschwarze 
473526e306bSschwarze 	return 0;
474631ce2c6Sschwarze }
475631ce2c6Sschwarze 
476631ce2c6Sschwarze static int
47781475784Sschwarze validate_filename(const char *file)
47881475784Sschwarze {
47981475784Sschwarze 
48081475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
48181475784Sschwarze 		file += 2;
48281475784Sschwarze 
483526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
484526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
48581475784Sschwarze }
48681475784Sschwarze 
487c6c22f12Sschwarze static void
488facea411Sschwarze pg_index(const struct req *req)
489c6c22f12Sschwarze {
490c6c22f12Sschwarze 
491c6c22f12Sschwarze 	resp_begin_html(200, NULL);
49284f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
493735516bdSschwarze 	printf("<p>\n"
494d56ca219Sschwarze 	       "This web interface is documented in the\n"
4950a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
496d56ca219Sschwarze 	       "manual, and the\n"
4970a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
4982a43838fSschwarze 	       "manual explains the query syntax.\n"
499735516bdSschwarze 	       "</p>\n",
5003b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5013b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
502c6c22f12Sschwarze 	resp_end_html();
503c6c22f12Sschwarze }
504c6c22f12Sschwarze 
505c6c22f12Sschwarze static void
506facea411Sschwarze pg_noresult(const struct req *req, const char *msg)
507c6c22f12Sschwarze {
508c6c22f12Sschwarze 	resp_begin_html(200, NULL);
50984f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
510735516bdSschwarze 	puts("<p>");
511c6c22f12Sschwarze 	puts(msg);
512735516bdSschwarze 	puts("</p>");
513c6c22f12Sschwarze 	resp_end_html();
514c6c22f12Sschwarze }
515c6c22f12Sschwarze 
516c6c22f12Sschwarze static void
517facea411Sschwarze pg_error_badrequest(const char *msg)
518c6c22f12Sschwarze {
519c6c22f12Sschwarze 
520c6c22f12Sschwarze 	resp_begin_html(400, "Bad Request");
521735516bdSschwarze 	puts("<h1>Bad Request</h1>\n"
522735516bdSschwarze 	     "<p>\n");
523c6c22f12Sschwarze 	puts(msg);
524c6c22f12Sschwarze 	printf("Try again from the\n"
525735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
526735516bdSschwarze 	       "</p>", scriptname);
527c6c22f12Sschwarze 	resp_end_html();
528c6c22f12Sschwarze }
529c6c22f12Sschwarze 
530c6c22f12Sschwarze static void
531facea411Sschwarze pg_error_internal(void)
532c6c22f12Sschwarze {
533c6c22f12Sschwarze 	resp_begin_html(500, "Internal Server Error");
534735516bdSschwarze 	puts("<p>Internal Server Error</p>");
535c6c22f12Sschwarze 	resp_end_html();
536c6c22f12Sschwarze }
537c6c22f12Sschwarze 
538c6c22f12Sschwarze static void
539facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
540c6c22f12Sschwarze {
541be14a32aSschwarze 	char		*arch, *archend;
542db26164eSschwarze 	const char	*sec;
543db26164eSschwarze 	size_t		 i, iuse;
544be14a32aSschwarze 	int		 archprio, archpriouse;
54546723f19Sschwarze 	int		 prio, priouse;
546c6c22f12Sschwarze 
54781475784Sschwarze 	for (i = 0; i < sz; i++) {
54881475784Sschwarze 		if (validate_filename(r[i].file))
54981475784Sschwarze 			continue;
550c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
55181475784Sschwarze 		    r[i].file, req->q.manpath);
55281475784Sschwarze 		pg_error_internal();
55381475784Sschwarze 		return;
55481475784Sschwarze 	}
55581475784Sschwarze 
55652b413c1Sschwarze 	if (req->isquery && sz == 1) {
557c6c22f12Sschwarze 		/*
558c6c22f12Sschwarze 		 * If we have just one result, then jump there now
559c6c22f12Sschwarze 		 * without any delay.
560c6c22f12Sschwarze 		 */
561fa9c540aStedu 		printf("Status: 303 See Other\r\n");
5623b9cfc6fSschwarze 		printf("Location: http://%s/%s%s%s/%s",
5633b9cfc6fSschwarze 		    HTTP_HOST, scriptname,
5643b9cfc6fSschwarze 		    *scriptname == '\0' ? "" : "/",
5653b9cfc6fSschwarze 		    req->q.manpath, r[0].file);
566fa9c540aStedu 		printf("\r\n"
567fa9c540aStedu 		     "Content-Type: text/html; charset=utf-8\r\n"
568fa9c540aStedu 		     "\r\n");
569c6c22f12Sschwarze 		return;
570c6c22f12Sschwarze 	}
571c6c22f12Sschwarze 
572c6c22f12Sschwarze 	resp_begin_html(200, NULL);
57384f05c93Sschwarze 	resp_searchform(req,
57484f05c93Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
5754592dbf1Sschwarze 
5764592dbf1Sschwarze 	if (sz > 1) {
5770a282dffSschwarze 		puts("<table class=\"results\">");
578c6c22f12Sschwarze 		for (i = 0; i < sz; i++) {
579735516bdSschwarze 			printf("  <tr>\n"
5800a282dffSschwarze 			       "    <td>"
5810a282dffSschwarze 			       "<a class=\"Xr\" href=\"/%s%s%s/%s\">",
5823b9cfc6fSschwarze 			    scriptname, *scriptname == '\0' ? "" : "/",
5833b9cfc6fSschwarze 			    req->q.manpath, r[i].file);
584c6c22f12Sschwarze 			html_print(r[i].names);
585542ee4bfSschwarze 			printf("</a></td>\n"
5860a282dffSschwarze 			       "    <td><span class=\"Nd\">");
587c6c22f12Sschwarze 			html_print(r[i].output);
5880a282dffSschwarze 			puts("</span></td>\n"
589735516bdSschwarze 			     "  </tr>");
590c6c22f12Sschwarze 		}
5910a282dffSschwarze 		puts("</table>");
5924592dbf1Sschwarze 	}
59346723f19Sschwarze 
59446723f19Sschwarze 	/*
59546723f19Sschwarze 	 * In man(1) mode, show one of the pages
59646723f19Sschwarze 	 * even if more than one is found.
59746723f19Sschwarze 	 */
59846723f19Sschwarze 
5994592dbf1Sschwarze 	if (req->q.equal || sz == 1) {
600735516bdSschwarze 		puts("<hr>");
60146723f19Sschwarze 		iuse = 0;
602db26164eSschwarze 		priouse = 20;
603be14a32aSschwarze 		archpriouse = 3;
60446723f19Sschwarze 		for (i = 0; i < sz; i++) {
605db26164eSschwarze 			sec = r[i].file;
606db26164eSschwarze 			sec += strcspn(sec, "123456789");
607db26164eSschwarze 			if (sec[0] == '\0')
60846723f19Sschwarze 				continue;
609db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
610db26164eSschwarze 			if (sec[1] != '/')
611db26164eSschwarze 				prio += 10;
612db26164eSschwarze 			if (req->q.arch == NULL) {
613be14a32aSschwarze 				archprio =
614db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
615db26164eSschwarze 					== NULL) ? 3 :
616db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
617db26164eSschwarze 					== NULL) ? 0 :
618be14a32aSschwarze 				    strncmp(arch, "amd64/",
619be14a32aSschwarze 					archend - arch) ? 2 : 1;
620be14a32aSschwarze 				if (archprio < archpriouse) {
621be14a32aSschwarze 					archpriouse = archprio;
622be14a32aSschwarze 					priouse = prio;
623be14a32aSschwarze 					iuse = i;
624be14a32aSschwarze 					continue;
625be14a32aSschwarze 				}
626be14a32aSschwarze 				if (archprio > archpriouse)
627be14a32aSschwarze 					continue;
628be14a32aSschwarze 			}
62946723f19Sschwarze 			if (prio >= priouse)
63046723f19Sschwarze 				continue;
63146723f19Sschwarze 			priouse = prio;
63246723f19Sschwarze 			iuse = i;
63346723f19Sschwarze 		}
63446723f19Sschwarze 		resp_show(req, r[iuse].file);
63546723f19Sschwarze 	}
63646723f19Sschwarze 
637c6c22f12Sschwarze 	resp_end_html();
638c6c22f12Sschwarze }
639c6c22f12Sschwarze 
640c6c22f12Sschwarze static void
641941df026Sschwarze resp_catman(const struct req *req, const char *file)
642c6c22f12Sschwarze {
643c6c22f12Sschwarze 	FILE		*f;
644c6c22f12Sschwarze 	char		*p;
64531f93c25Sschwarze 	size_t		 sz;
64631f93c25Sschwarze 	ssize_t		 len;
64731f93c25Sschwarze 	int		 i;
648c6c22f12Sschwarze 	int		 italic, bold;
649c6c22f12Sschwarze 
65031f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
651735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
652c6c22f12Sschwarze 		return;
653c6c22f12Sschwarze 	}
654c6c22f12Sschwarze 
655735516bdSschwarze 	puts("<div class=\"catman\">\n"
656735516bdSschwarze 	     "<pre>");
657c6c22f12Sschwarze 
65831f93c25Sschwarze 	p = NULL;
65931f93c25Sschwarze 	sz = 0;
66031f93c25Sschwarze 
66131f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
662c6c22f12Sschwarze 		bold = italic = 0;
66331f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
664c6c22f12Sschwarze 			/*
665c6c22f12Sschwarze 			 * This means that the catpage is out of state.
666c6c22f12Sschwarze 			 * Ignore it and keep going (although the
667c6c22f12Sschwarze 			 * catpage is bogus).
668c6c22f12Sschwarze 			 */
669c6c22f12Sschwarze 
670c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
671c6c22f12Sschwarze 				continue;
672c6c22f12Sschwarze 
673c6c22f12Sschwarze 			/*
674c6c22f12Sschwarze 			 * Print a regular character.
675c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
676c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
677c6c22f12Sschwarze 			 * have something to enter when we backspace.
678c6c22f12Sschwarze 			 */
679c6c22f12Sschwarze 
680c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
681c6c22f12Sschwarze 				if (italic)
682735516bdSschwarze 					printf("</i>");
683c6c22f12Sschwarze 				if (bold)
684735516bdSschwarze 					printf("</b>");
685c6c22f12Sschwarze 				italic = bold = 0;
686c6c22f12Sschwarze 				html_putchar(p[i]);
687c6c22f12Sschwarze 				continue;
68831f93c25Sschwarze 			} else if (i + 2 >= len)
689c6c22f12Sschwarze 				continue;
690c6c22f12Sschwarze 
691c6c22f12Sschwarze 			/* Italic mode. */
692c6c22f12Sschwarze 
693c6c22f12Sschwarze 			if ('_' == p[i]) {
694c6c22f12Sschwarze 				if (bold)
695735516bdSschwarze 					printf("</b>");
696c6c22f12Sschwarze 				if ( ! italic)
697735516bdSschwarze 					printf("<i>");
698c6c22f12Sschwarze 				bold = 0;
699c6c22f12Sschwarze 				italic = 1;
700c6c22f12Sschwarze 				i += 2;
701c6c22f12Sschwarze 				html_putchar(p[i]);
702c6c22f12Sschwarze 				continue;
703c6c22f12Sschwarze 			}
704c6c22f12Sschwarze 
705c6c22f12Sschwarze 			/*
706c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
707c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
708c6c22f12Sschwarze 			 */
709c6c22f12Sschwarze 
710c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
711c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
712c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
713c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
714c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
715c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
716c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
717c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
718c6c22f12Sschwarze 				if (italic)
719735516bdSschwarze 					printf("</i>");
720c6c22f12Sschwarze 				if (bold)
721735516bdSschwarze 					printf("</b>");
722c6c22f12Sschwarze 				italic = bold = 0;
723c6c22f12Sschwarze 				putchar('*');
724c6c22f12Sschwarze 				i += 2;
725c6c22f12Sschwarze 				continue;
726c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
727c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
728c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
729c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
730c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
731c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
732c6c22f12Sschwarze 				if (italic)
733735516bdSschwarze 					printf("</i>");
734c6c22f12Sschwarze 				if (bold)
735735516bdSschwarze 					printf("</b>");
736c6c22f12Sschwarze 				italic = bold = 0;
737c6c22f12Sschwarze 				putchar('+');
738c6c22f12Sschwarze 				i += 2;
739c6c22f12Sschwarze 				continue;
740c6c22f12Sschwarze 			}
741c6c22f12Sschwarze 
742c6c22f12Sschwarze 			/* Bold mode. */
743c6c22f12Sschwarze 
744c6c22f12Sschwarze 			if (italic)
745735516bdSschwarze 				printf("</i>");
746c6c22f12Sschwarze 			if ( ! bold)
747735516bdSschwarze 				printf("<b>");
748c6c22f12Sschwarze 			bold = 1;
749c6c22f12Sschwarze 			italic = 0;
750c6c22f12Sschwarze 			i += 2;
751c6c22f12Sschwarze 			html_putchar(p[i]);
752c6c22f12Sschwarze 		}
753c6c22f12Sschwarze 
754c6c22f12Sschwarze 		/*
755c6c22f12Sschwarze 		 * Clean up the last character.
756c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
757c6c22f12Sschwarze 		 */
758c6c22f12Sschwarze 
759c6c22f12Sschwarze 		if (italic)
760735516bdSschwarze 			printf("</i>");
761c6c22f12Sschwarze 		if (bold)
762735516bdSschwarze 			printf("</b>");
763c6c22f12Sschwarze 
76431f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
765c6c22f12Sschwarze 			html_putchar(p[i]);
766c6c22f12Sschwarze 
767c6c22f12Sschwarze 		putchar('\n');
768c6c22f12Sschwarze 	}
76931f93c25Sschwarze 	free(p);
770c6c22f12Sschwarze 
771735516bdSschwarze 	puts("</pre>\n"
772735516bdSschwarze 	     "</div>");
773c6c22f12Sschwarze 
774c6c22f12Sschwarze 	fclose(f);
775c6c22f12Sschwarze }
776c6c22f12Sschwarze 
777c6c22f12Sschwarze static void
778941df026Sschwarze resp_format(const struct req *req, const char *file)
779c6c22f12Sschwarze {
7802ccd0917Sschwarze 	struct manoutput conf;
781c6c22f12Sschwarze 	struct mparse	*mp;
782ede1b9d0Sschwarze 	struct roff_man	*man;
783c6c22f12Sschwarze 	void		*vp;
784f74d674aSschwarze 	int		 fd;
785f74d674aSschwarze 	int		 usepath;
786c6c22f12Sschwarze 
787c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
788735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
789c6c22f12Sschwarze 		return;
790c6c22f12Sschwarze 	}
791c6c22f12Sschwarze 
79216536faaSschwarze 	mchars_alloc();
793f139d5f6Sschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
794f139d5f6Sschwarze 	    MANDOCLEVEL_BADARG, NULL, req->q.manpath);
795df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
796c6c22f12Sschwarze 	close(fd);
797c6c22f12Sschwarze 
7982ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
7992ccd0917Sschwarze 	conf.fragment = 1;
8007c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
801f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
802f211c215Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%%N.%%S",
803f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
804c6c22f12Sschwarze 
805f2d5c709Sschwarze 	mparse_result(mp, &man, NULL);
806f2d5c709Sschwarze 	if (man == NULL) {
807c976f0e2Sschwarze 		warnx("fatal mandoc error: %s/%s", req->q.manpath, file);
808facea411Sschwarze 		pg_error_internal();
809c6c22f12Sschwarze 		mparse_free(mp);
81016536faaSschwarze 		mchars_free();
811c6c22f12Sschwarze 		return;
812c6c22f12Sschwarze 	}
813c6c22f12Sschwarze 
81416536faaSschwarze 	vp = html_alloc(&conf);
815c6c22f12Sschwarze 
816396853b5Sschwarze 	if (man->macroset == MACROSET_MDOC) {
817396853b5Sschwarze 		mdoc_validate(man);
818f2d5c709Sschwarze 		html_mdoc(vp, man);
819fec2846bSschwarze 	} else {
820fec2846bSschwarze 		man_validate(man);
821c6c22f12Sschwarze 		html_man(vp, man);
822fec2846bSschwarze 	}
823c6c22f12Sschwarze 
824c6c22f12Sschwarze 	html_free(vp);
825c6c22f12Sschwarze 	mparse_free(mp);
82616536faaSschwarze 	mchars_free();
8272ccd0917Sschwarze 	free(conf.man);
8287c6e1b3aSschwarze 	free(conf.style);
829c6c22f12Sschwarze }
830c6c22f12Sschwarze 
831c6c22f12Sschwarze static void
83246723f19Sschwarze resp_show(const struct req *req, const char *file)
83346723f19Sschwarze {
83481475784Sschwarze 
83581475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
8362f7bef27Sschwarze 		file += 2;
83746723f19Sschwarze 
83846723f19Sschwarze 	if ('c' == *file)
839941df026Sschwarze 		resp_catman(req, file);
84046723f19Sschwarze 	else
841941df026Sschwarze 		resp_format(req, file);
84246723f19Sschwarze }
84346723f19Sschwarze 
84446723f19Sschwarze static void
845b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
846c6c22f12Sschwarze {
847b53c14c3Sschwarze 	char		*manpath;
848b53c14c3Sschwarze 	const char	*file;
849c6c22f12Sschwarze 
850b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
851facea411Sschwarze 		pg_error_badrequest(
852c6c22f12Sschwarze 		    "You did not specify a page to show.");
853c6c22f12Sschwarze 		return;
854c6c22f12Sschwarze 	}
855b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
856b53c14c3Sschwarze 	file++;
857c6c22f12Sschwarze 
858b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
859631ce2c6Sschwarze 		pg_error_badrequest(
860631ce2c6Sschwarze 		    "You specified an invalid manpath.");
861b53c14c3Sschwarze 		free(manpath);
862631ce2c6Sschwarze 		return;
863631ce2c6Sschwarze 	}
864631ce2c6Sschwarze 
865c6c22f12Sschwarze 	/*
866c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
867c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
868c6c22f12Sschwarze 	 * relative to the manpath root.
869c6c22f12Sschwarze 	 */
870c6c22f12Sschwarze 
871b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
872c976f0e2Sschwarze 		warn("chdir %s", manpath);
873631ce2c6Sschwarze 		pg_error_internal();
874b53c14c3Sschwarze 		free(manpath);
875c6c22f12Sschwarze 		return;
876c6c22f12Sschwarze 	}
877b53c14c3Sschwarze 	free(manpath);
878b53c14c3Sschwarze 
879b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
88081475784Sschwarze 		pg_error_badrequest(
88181475784Sschwarze 		    "You specified an invalid manual file.");
88281475784Sschwarze 		return;
88381475784Sschwarze 	}
88481475784Sschwarze 
88546723f19Sschwarze 	resp_begin_html(200, NULL);
88684f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
887b53c14c3Sschwarze 	resp_show(req, file);
88846723f19Sschwarze 	resp_end_html();
889c6c22f12Sschwarze }
890c6c22f12Sschwarze 
891c6c22f12Sschwarze static void
89257482ef4Sschwarze pg_search(const struct req *req)
893c6c22f12Sschwarze {
894c6c22f12Sschwarze 	struct mansearch	  search;
895c6c22f12Sschwarze 	struct manpaths		  paths;
896c6c22f12Sschwarze 	struct manpage		 *res;
897fbeeb774Sschwarze 	char			**argv;
898fbeeb774Sschwarze 	char			 *query, *rp, *wp;
899c6c22f12Sschwarze 	size_t			  ressz;
900fbeeb774Sschwarze 	int			  argc;
901c6c22f12Sschwarze 
902c6c22f12Sschwarze 	/*
903c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
904c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
905c6c22f12Sschwarze 	 * relative to the manpath root.
906c6c22f12Sschwarze 	 */
907c6c22f12Sschwarze 
908c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
909c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
910631ce2c6Sschwarze 		pg_error_internal();
911c6c22f12Sschwarze 		return;
912c6c22f12Sschwarze 	}
913c6c22f12Sschwarze 
914c6c22f12Sschwarze 	search.arch = req->q.arch;
915c6c22f12Sschwarze 	search.sec = req->q.sec;
9160f10154cSschwarze 	search.outkey = "Nd";
9170f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
918fea71919Sschwarze 	search.firstmatch = 1;
919c6c22f12Sschwarze 
920c6c22f12Sschwarze 	paths.sz = 1;
921c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
922c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
923c6c22f12Sschwarze 
924c6c22f12Sschwarze 	/*
925fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
926c6c22f12Sschwarze 	 */
927c6c22f12Sschwarze 
928fbeeb774Sschwarze 	argc = 0;
929fbeeb774Sschwarze 	argv = NULL;
930fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
931fbeeb774Sschwarze 	for (;;) {
932fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
933fbeeb774Sschwarze 			rp++;
934fbeeb774Sschwarze 		if (*rp == '\0')
935fbeeb774Sschwarze 			break;
936fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
937fbeeb774Sschwarze 		argv[argc++] = wp = rp;
938fbeeb774Sschwarze 		for (;;) {
939fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
940fbeeb774Sschwarze 				*wp = '\0';
941fbeeb774Sschwarze 				rp++;
942fbeeb774Sschwarze 				break;
943fbeeb774Sschwarze 			}
944fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
945fbeeb774Sschwarze 				rp++;
946fbeeb774Sschwarze 			if (wp != rp)
947fbeeb774Sschwarze 				*wp = *rp;
948fbeeb774Sschwarze 			if (*rp == '\0')
949fbeeb774Sschwarze 				break;
950fbeeb774Sschwarze 			wp++;
951fbeeb774Sschwarze 			rp++;
952fbeeb774Sschwarze 		}
953c6c22f12Sschwarze 	}
954c6c22f12Sschwarze 
955fbeeb774Sschwarze 	if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
956facea411Sschwarze 		pg_noresult(req, "You entered an invalid query.");
957c6c22f12Sschwarze 	else if (0 == ressz)
958facea411Sschwarze 		pg_noresult(req, "No results found.");
959c6c22f12Sschwarze 	else
960facea411Sschwarze 		pg_searchres(req, res, ressz);
961c6c22f12Sschwarze 
962fbeeb774Sschwarze 	free(query);
963fbeeb774Sschwarze 	mansearch_free(res, ressz);
964c6c22f12Sschwarze 	free(paths.paths[0]);
965c6c22f12Sschwarze 	free(paths.paths);
966c6c22f12Sschwarze }
967c6c22f12Sschwarze 
968c6c22f12Sschwarze int
969c6c22f12Sschwarze main(void)
970c6c22f12Sschwarze {
971c6c22f12Sschwarze 	struct req	 req;
972136c26b8Sschwarze 	struct itimerval itimer;
97357482ef4Sschwarze 	const char	*path;
97431e689c3Sschwarze 	const char	*querystring;
97557482ef4Sschwarze 	int		 i;
976c6c22f12Sschwarze 
977136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
978136c26b8Sschwarze 
9792935aafcSschwarze 	itimer.it_value.tv_sec = 2;
980136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
9812935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
982136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
983136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
984c976f0e2Sschwarze 		warn("setitimer");
985136c26b8Sschwarze 		pg_error_internal();
986526e306bSschwarze 		return EXIT_FAILURE;
987136c26b8Sschwarze 	}
988136c26b8Sschwarze 
989c6c22f12Sschwarze 	/*
9906fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
991c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
992c6c22f12Sschwarze 	 * relative to the same position.
993c6c22f12Sschwarze 	 */
994c6c22f12Sschwarze 
995c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
996c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
997facea411Sschwarze 		pg_error_internal();
998526e306bSschwarze 		return EXIT_FAILURE;
999c6c22f12Sschwarze 	}
1000c6c22f12Sschwarze 
1001c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1002abf19dc9Sschwarze 	req.q.equal = 1;
1003941df026Sschwarze 	parse_manpath_conf(&req);
1004c6c22f12Sschwarze 
100502b1b494Sschwarze 	/* Parse the path info and the query string. */
1006c6c22f12Sschwarze 
100702b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
100802b1b494Sschwarze 		path = "";
100902b1b494Sschwarze 	else if (*path == '/')
101002b1b494Sschwarze 		path++;
101102b1b494Sschwarze 
1012aa16a3a8Sschwarze 	if (*path != '\0') {
1013941df026Sschwarze 		parse_path_info(&req, path);
101486f4328bSschwarze 		if (req.q.manpath == NULL || access(path, F_OK) == -1)
101502b1b494Sschwarze 			path = "";
101602b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1017941df026Sschwarze 		parse_query_string(&req, querystring);
1018c6c22f12Sschwarze 
101902b1b494Sschwarze 	/* Validate parsed data and add defaults. */
102002b1b494Sschwarze 
102150eaed2bSschwarze 	if (req.q.manpath == NULL)
102250eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
102350eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1024631ce2c6Sschwarze 		pg_error_badrequest(
1025631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1026526e306bSschwarze 		return EXIT_FAILURE;
1027631ce2c6Sschwarze 	}
1028631ce2c6Sschwarze 
1029cf3a545cSschwarze 	if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1030cf3a545cSschwarze 		pg_error_badrequest(
1031cf3a545cSschwarze 		    "You specified an invalid architecture.");
1032526e306bSschwarze 		return EXIT_FAILURE;
1033cf3a545cSschwarze 	}
1034cf3a545cSschwarze 
103557482ef4Sschwarze 	/* Dispatch to the three different pages. */
1036c6c22f12Sschwarze 
103757482ef4Sschwarze 	if ('\0' != *path)
103857482ef4Sschwarze 		pg_show(&req, path);
1039e89321abSschwarze 	else if (NULL != req.q.query)
104057482ef4Sschwarze 		pg_search(&req);
104157482ef4Sschwarze 	else
1042facea411Sschwarze 		pg_index(&req);
1043c6c22f12Sschwarze 
104431e689c3Sschwarze 	free(req.q.manpath);
104531e689c3Sschwarze 	free(req.q.arch);
104631e689c3Sschwarze 	free(req.q.sec);
1047e89321abSschwarze 	free(req.q.query);
1048c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1049c6c22f12Sschwarze 		free(req.p[i]);
1050c6c22f12Sschwarze 	free(req.p);
1051526e306bSschwarze 	return EXIT_SUCCESS;
1052c6c22f12Sschwarze }
1053c6c22f12Sschwarze 
1054c6c22f12Sschwarze /*
105502b1b494Sschwarze  * If PATH_INFO is not a file name, translate it to a query.
105602b1b494Sschwarze  */
105702b1b494Sschwarze static void
1058941df026Sschwarze parse_path_info(struct req *req, const char *path)
105902b1b494Sschwarze {
1060daf4c292Sschwarze 	char	*dir[4];
1061daf4c292Sschwarze 	int	 i;
106202b1b494Sschwarze 
106352b413c1Sschwarze 	req->isquery = 0;
106402b1b494Sschwarze 	req->q.equal = 1;
106502b1b494Sschwarze 	req->q.manpath = mandoc_strdup(path);
1066daf4c292Sschwarze 	req->q.arch = NULL;
106702b1b494Sschwarze 
106802b1b494Sschwarze 	/* Mandatory manual page name. */
106902b1b494Sschwarze 	if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) {
107002b1b494Sschwarze 		req->q.query = req->q.manpath;
107102b1b494Sschwarze 		req->q.manpath = NULL;
107202b1b494Sschwarze 	} else
107302b1b494Sschwarze 		*req->q.query++ = '\0';
107402b1b494Sschwarze 
107502b1b494Sschwarze 	/* Optional trailing section. */
107602b1b494Sschwarze 	if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) {
107702b1b494Sschwarze 		if(isdigit((unsigned char)req->q.sec[1])) {
107802b1b494Sschwarze 			*req->q.sec++ = '\0';
107902b1b494Sschwarze 			req->q.sec = mandoc_strdup(req->q.sec);
108002b1b494Sschwarze 		} else
108102b1b494Sschwarze 			req->q.sec = NULL;
108202b1b494Sschwarze 	}
108302b1b494Sschwarze 
108402b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1085daf4c292Sschwarze 	if (req->q.manpath == NULL)
108602b1b494Sschwarze 		return;
108702b1b494Sschwarze 	req->q.query = mandoc_strdup(req->q.query);
108802b1b494Sschwarze 
1089daf4c292Sschwarze 	/* Split directory components. */
1090daf4c292Sschwarze 	dir[i = 0] = req->q.manpath;
1091daf4c292Sschwarze 	while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) {
1092daf4c292Sschwarze 		if (++i == 3) {
1093daf4c292Sschwarze 			pg_error_badrequest(
1094daf4c292Sschwarze 			    "You specified too many directory components.");
1095daf4c292Sschwarze 			exit(EXIT_FAILURE);
109602b1b494Sschwarze 		}
1097daf4c292Sschwarze 		*dir[i]++ = '\0';
1098daf4c292Sschwarze 	}
1099daf4c292Sschwarze 
1100daf4c292Sschwarze 	/* Optional manpath. */
1101daf4c292Sschwarze 	if ((i = validate_manpath(req, req->q.manpath)) == 0)
1102daf4c292Sschwarze 		req->q.manpath = NULL;
1103daf4c292Sschwarze 	else if (dir[1] == NULL)
1104daf4c292Sschwarze 		return;
1105daf4c292Sschwarze 
1106daf4c292Sschwarze 	/* Optional section. */
1107daf4c292Sschwarze 	if (strncmp(dir[i], "man", 3) == 0) {
1108daf4c292Sschwarze 		free(req->q.sec);
1109daf4c292Sschwarze 		req->q.sec = mandoc_strdup(dir[i++] + 3);
1110daf4c292Sschwarze 	}
1111daf4c292Sschwarze 	if (dir[i] == NULL) {
1112daf4c292Sschwarze 		if (req->q.manpath == NULL)
1113daf4c292Sschwarze 			free(dir[0]);
1114daf4c292Sschwarze 		return;
1115daf4c292Sschwarze 	}
1116daf4c292Sschwarze 	if (dir[i + 1] != NULL) {
1117daf4c292Sschwarze 		pg_error_badrequest(
1118daf4c292Sschwarze 		    "You specified an invalid directory component.");
1119daf4c292Sschwarze 		exit(EXIT_FAILURE);
1120daf4c292Sschwarze 	}
1121daf4c292Sschwarze 
1122daf4c292Sschwarze 	/* Optional architecture. */
1123daf4c292Sschwarze 	if (i) {
1124daf4c292Sschwarze 		req->q.arch = mandoc_strdup(dir[i]);
1125daf4c292Sschwarze 		if (req->q.manpath == NULL)
1126daf4c292Sschwarze 			free(dir[0]);
1127daf4c292Sschwarze 	} else
1128daf4c292Sschwarze 		req->q.arch = dir[0];
112902b1b494Sschwarze }
113002b1b494Sschwarze 
113102b1b494Sschwarze /*
1132c6c22f12Sschwarze  * Scan for indexable paths.
1133c6c22f12Sschwarze  */
1134c6c22f12Sschwarze static void
1135941df026Sschwarze parse_manpath_conf(struct req *req)
1136c6c22f12Sschwarze {
1137c6c22f12Sschwarze 	FILE	*fp;
1138c6c22f12Sschwarze 	char	*dp;
1139c6c22f12Sschwarze 	size_t	 dpsz;
114031f93c25Sschwarze 	ssize_t	 len;
1141c6c22f12Sschwarze 
1142c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1143c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1144de651747Sschwarze 		pg_error_internal();
1145de651747Sschwarze 		exit(EXIT_FAILURE);
1146de651747Sschwarze 	}
1147c6c22f12Sschwarze 
114831f93c25Sschwarze 	dp = NULL;
114931f93c25Sschwarze 	dpsz = 0;
115031f93c25Sschwarze 
115131f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
115231f93c25Sschwarze 		if (dp[len - 1] == '\n')
115331f93c25Sschwarze 			dp[--len] = '\0';
1154c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1155c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1156cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1157c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1158c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1159cf3a545cSschwarze 			pg_error_internal();
1160cf3a545cSschwarze 			exit(EXIT_FAILURE);
1161cf3a545cSschwarze 		}
1162c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1163c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1164c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1165cf3a545cSschwarze 			pg_error_internal();
1166cf3a545cSschwarze 			exit(EXIT_FAILURE);
1167cf3a545cSschwarze 		}
1168cf3a545cSschwarze 		req->p[req->psz++] = dp;
116931f93c25Sschwarze 		dp = NULL;
117031f93c25Sschwarze 		dpsz = 0;
1171c6c22f12Sschwarze 	}
117231f93c25Sschwarze 	free(dp);
1173de651747Sschwarze 
1174de651747Sschwarze 	if (req->p == NULL) {
1175c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1176de651747Sschwarze 		pg_error_internal();
1177de651747Sschwarze 		exit(EXIT_FAILURE);
1178de651747Sschwarze 	}
1179c6c22f12Sschwarze }
1180