xref: /openbsd/usr.bin/mandoc/cgi.c (revision 766ef059)
1*766ef059Sschwarze /*	$OpenBSD: cgi.c,v 1.76 2016/08/10 18:39:04 schwarze Exp $ */
2c6c22f12Sschwarze /*
3c6c22f12Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4abf19dc9Sschwarze  * Copyright (c) 2014, 2015, 2016 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*766ef059Sschwarze     "amd64",       "alpha",       "armv7",
115*766ef059Sschwarze     "hppa",        "i386",        "landisk",
1163bcd4815Sschwarze     "loongson",    "luna88k",     "macppc",      "mips64",
1173bcd4815Sschwarze     "octeon",      "sgi",         "socppc",      "sparc",
1183bcd4815Sschwarze     "sparc64",     "zaurus",
119*766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
120*766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
121*766ef059Sschwarze     "hppa64",      "hp300",
1223bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1233bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
1243bcd4815Sschwarze     "pmax",        "powerpc",     "solbourne",   "sun3",
1253bcd4815Sschwarze     "vax",         "wgrisc",      "x68k"
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 ('"'):
139c6c22f12Sschwarze 		printf("&quote;");
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);
339711661c7Sschwarze 	}
340711661c7Sschwarze }
341711661c7Sschwarze 
342711661c7Sschwarze static void
343c6c22f12Sschwarze resp_begin_html(int code, const char *msg)
344c6c22f12Sschwarze {
345c6c22f12Sschwarze 
346c6c22f12Sschwarze 	resp_begin_http(code, msg);
347c6c22f12Sschwarze 
348d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
349735516bdSschwarze 	       "<html>\n"
350735516bdSschwarze 	       "<head>\n"
351735516bdSschwarze 	       "<meta charset=\"UTF-8\"/>\n"
352735516bdSschwarze 	       "<link rel=\"stylesheet\" href=\"%s/mandoc.css\""
353735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
354735516bdSschwarze 	       "<title>%s</title>\n"
355735516bdSschwarze 	       "</head>\n"
356735516bdSschwarze 	       "<body>\n"
357c6c22f12Sschwarze 	       "<!-- Begin page content. //-->\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 
378c6c22f12Sschwarze 	puts("<!-- Begin search form. //-->");
379735516bdSschwarze 	printf("<div id=\"mancgi\">\n"
380735516bdSschwarze 	       "<form action=\"/%s\" method=\"get\">\n"
381735516bdSschwarze 	       "<fieldset>\n"
382735516bdSschwarze 	       "<legend>Manual Page Search Parameters</legend>\n",
383c6c22f12Sschwarze 	       scriptname);
38428e449d6Sschwarze 
38528e449d6Sschwarze 	/* Write query input box. */
38628e449d6Sschwarze 
387784a63d6Sschwarze 	printf("<input type=\"text\" name=\"query\" value=\"");
38884f05c93Sschwarze 	if (req->q.query != NULL)
389e89321abSschwarze 		html_print(req->q.query);
39084f05c93Sschwarze 	printf( "\" size=\"40\"");
39184f05c93Sschwarze 	if (focus == FOCUS_QUERY)
39284f05c93Sschwarze 		printf(" autofocus");
39384f05c93Sschwarze 	puts(">");
39428e449d6Sschwarze 
395784a63d6Sschwarze 	/* Write submission buttons. */
39628e449d6Sschwarze 
397784a63d6Sschwarze 	printf(	"<button type=\"submit\" name=\"apropos\" value=\"0\">"
398784a63d6Sschwarze 		"man</button>\n"
399784a63d6Sschwarze 		"<button type=\"submit\" name=\"apropos\" value=\"1\">"
400784a63d6Sschwarze 		"apropos</button>\n<br/>\n");
40128e449d6Sschwarze 
40228e449d6Sschwarze 	/* Write section selector. */
40328e449d6Sschwarze 
404784a63d6Sschwarze 	puts("<select name=\"sec\">");
40528e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
406735516bdSschwarze 		printf("<option value=\"%s\"", sec_numbers[i]);
40728e449d6Sschwarze 		if (NULL != req->q.sec &&
40828e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
409735516bdSschwarze 			printf(" selected=\"selected\"");
410735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
41128e449d6Sschwarze 	}
412735516bdSschwarze 	puts("</select>");
41328e449d6Sschwarze 
41428e449d6Sschwarze 	/* Write architecture selector. */
41528e449d6Sschwarze 
416735516bdSschwarze 	printf(	"<select name=\"arch\">\n"
417735516bdSschwarze 		"<option value=\"default\"");
418be14a32aSschwarze 	if (NULL == req->q.arch)
419735516bdSschwarze 		printf(" selected=\"selected\"");
420735516bdSschwarze 	puts(">All Architectures</option>");
42128e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
422735516bdSschwarze 		printf("<option value=\"%s\"", arch_names[i]);
42328e449d6Sschwarze 		if (NULL != req->q.arch &&
42428e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
425735516bdSschwarze 			printf(" selected=\"selected\"");
426735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
42728e449d6Sschwarze 	}
428735516bdSschwarze 	puts("</select>");
42928e449d6Sschwarze 
43028e449d6Sschwarze 	/* Write manpath selector. */
43128e449d6Sschwarze 
432c6c22f12Sschwarze 	if (req->psz > 1) {
433735516bdSschwarze 		puts("<select name=\"manpath\">");
434c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
435735516bdSschwarze 			printf("<option ");
43650eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
437735516bdSschwarze 				printf("selected=\"selected\" ");
438735516bdSschwarze 			printf("value=\"");
439c6c22f12Sschwarze 			html_print(req->p[i]);
440c6c22f12Sschwarze 			printf("\">");
441c6c22f12Sschwarze 			html_print(req->p[i]);
442735516bdSschwarze 			puts("</option>");
443c6c22f12Sschwarze 		}
444735516bdSschwarze 		puts("</select>");
445c6c22f12Sschwarze 	}
44628e449d6Sschwarze 
447784a63d6Sschwarze 	puts("</fieldset>\n"
448735516bdSschwarze 	     "</form>\n"
449735516bdSschwarze 	     "</div>");
450c6c22f12Sschwarze 	puts("<!-- End search form. //-->");
451c6c22f12Sschwarze }
452c6c22f12Sschwarze 
45381475784Sschwarze static int
454cf3a545cSschwarze validate_urifrag(const char *frag)
455cf3a545cSschwarze {
456cf3a545cSschwarze 
457cf3a545cSschwarze 	while ('\0' != *frag) {
458cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
459cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
460cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
461526e306bSschwarze 			return 0;
462cf3a545cSschwarze 		frag++;
463cf3a545cSschwarze 	}
464526e306bSschwarze 	return 1;
465cf3a545cSschwarze }
466cf3a545cSschwarze 
467cf3a545cSschwarze static int
468631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
469631ce2c6Sschwarze {
470631ce2c6Sschwarze 	size_t	 i;
471631ce2c6Sschwarze 
472631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
473631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
474526e306bSschwarze 			return 1;
475631ce2c6Sschwarze 
476526e306bSschwarze 	return 0;
477631ce2c6Sschwarze }
478631ce2c6Sschwarze 
479631ce2c6Sschwarze static int
48081475784Sschwarze validate_filename(const char *file)
48181475784Sschwarze {
48281475784Sschwarze 
48381475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
48481475784Sschwarze 		file += 2;
48581475784Sschwarze 
486526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
487526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
48881475784Sschwarze }
48981475784Sschwarze 
490c6c22f12Sschwarze static void
491facea411Sschwarze pg_index(const struct req *req)
492c6c22f12Sschwarze {
493c6c22f12Sschwarze 
494c6c22f12Sschwarze 	resp_begin_html(200, NULL);
49584f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
496735516bdSschwarze 	printf("<p>\n"
497d56ca219Sschwarze 	       "This web interface is documented in the\n"
4988361bf54Sschwarze 	       "<a href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
499d56ca219Sschwarze 	       "manual, and the\n"
5008361bf54Sschwarze 	       "<a href=\"/%s%sapropos.1\">apropos(1)</a>\n"
5012a43838fSschwarze 	       "manual explains the query syntax.\n"
502735516bdSschwarze 	       "</p>\n",
5033b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5043b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
505c6c22f12Sschwarze 	resp_end_html();
506c6c22f12Sschwarze }
507c6c22f12Sschwarze 
508c6c22f12Sschwarze static void
509facea411Sschwarze pg_noresult(const struct req *req, const char *msg)
510c6c22f12Sschwarze {
511c6c22f12Sschwarze 	resp_begin_html(200, NULL);
51284f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
513735516bdSschwarze 	puts("<p>");
514c6c22f12Sschwarze 	puts(msg);
515735516bdSschwarze 	puts("</p>");
516c6c22f12Sschwarze 	resp_end_html();
517c6c22f12Sschwarze }
518c6c22f12Sschwarze 
519c6c22f12Sschwarze static void
520facea411Sschwarze pg_error_badrequest(const char *msg)
521c6c22f12Sschwarze {
522c6c22f12Sschwarze 
523c6c22f12Sschwarze 	resp_begin_html(400, "Bad Request");
524735516bdSschwarze 	puts("<h1>Bad Request</h1>\n"
525735516bdSschwarze 	     "<p>\n");
526c6c22f12Sschwarze 	puts(msg);
527c6c22f12Sschwarze 	printf("Try again from the\n"
528735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
529735516bdSschwarze 	       "</p>", scriptname);
530c6c22f12Sschwarze 	resp_end_html();
531c6c22f12Sschwarze }
532c6c22f12Sschwarze 
533c6c22f12Sschwarze static void
534facea411Sschwarze pg_error_internal(void)
535c6c22f12Sschwarze {
536c6c22f12Sschwarze 	resp_begin_html(500, "Internal Server Error");
537735516bdSschwarze 	puts("<p>Internal Server Error</p>");
538c6c22f12Sschwarze 	resp_end_html();
539c6c22f12Sschwarze }
540c6c22f12Sschwarze 
541c6c22f12Sschwarze static void
542facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
543c6c22f12Sschwarze {
544be14a32aSschwarze 	char		*arch, *archend;
545db26164eSschwarze 	const char	*sec;
546db26164eSschwarze 	size_t		 i, iuse;
547be14a32aSschwarze 	int		 archprio, archpriouse;
54846723f19Sschwarze 	int		 prio, priouse;
549c6c22f12Sschwarze 
55081475784Sschwarze 	for (i = 0; i < sz; i++) {
55181475784Sschwarze 		if (validate_filename(r[i].file))
55281475784Sschwarze 			continue;
553c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
55481475784Sschwarze 		    r[i].file, req->q.manpath);
55581475784Sschwarze 		pg_error_internal();
55681475784Sschwarze 		return;
55781475784Sschwarze 	}
55881475784Sschwarze 
55952b413c1Sschwarze 	if (req->isquery && sz == 1) {
560c6c22f12Sschwarze 		/*
561c6c22f12Sschwarze 		 * If we have just one result, then jump there now
562c6c22f12Sschwarze 		 * without any delay.
563c6c22f12Sschwarze 		 */
564fa9c540aStedu 		printf("Status: 303 See Other\r\n");
5653b9cfc6fSschwarze 		printf("Location: http://%s/%s%s%s/%s",
5663b9cfc6fSschwarze 		    HTTP_HOST, scriptname,
5673b9cfc6fSschwarze 		    *scriptname == '\0' ? "" : "/",
5683b9cfc6fSschwarze 		    req->q.manpath, r[0].file);
569fa9c540aStedu 		printf("\r\n"
570fa9c540aStedu 		     "Content-Type: text/html; charset=utf-8\r\n"
571fa9c540aStedu 		     "\r\n");
572c6c22f12Sschwarze 		return;
573c6c22f12Sschwarze 	}
574c6c22f12Sschwarze 
575c6c22f12Sschwarze 	resp_begin_html(200, NULL);
57684f05c93Sschwarze 	resp_searchform(req,
57784f05c93Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
5784592dbf1Sschwarze 
5794592dbf1Sschwarze 	if (sz > 1) {
580735516bdSschwarze 		puts("<div class=\"results\">");
581735516bdSschwarze 		puts("<table>");
582c6c22f12Sschwarze 
583c6c22f12Sschwarze 		for (i = 0; i < sz; i++) {
584735516bdSschwarze 			printf("<tr>\n"
585735516bdSschwarze 			       "<td class=\"title\">\n"
586735516bdSschwarze 			       "<a href=\"/%s%s%s/%s",
5873b9cfc6fSschwarze 			    scriptname, *scriptname == '\0' ? "" : "/",
5883b9cfc6fSschwarze 			    req->q.manpath, r[i].file);
589c6c22f12Sschwarze 			printf("\">");
590c6c22f12Sschwarze 			html_print(r[i].names);
591735516bdSschwarze 			printf("</a>\n"
592735516bdSschwarze 			       "</td>\n"
593735516bdSschwarze 			       "<td class=\"desc\">");
594c6c22f12Sschwarze 			html_print(r[i].output);
595735516bdSschwarze 			puts("</td>\n"
596735516bdSschwarze 			     "</tr>");
597c6c22f12Sschwarze 		}
598c6c22f12Sschwarze 
599735516bdSschwarze 		puts("</table>\n"
600735516bdSschwarze 		     "</div>");
6014592dbf1Sschwarze 	}
60246723f19Sschwarze 
60346723f19Sschwarze 	/*
60446723f19Sschwarze 	 * In man(1) mode, show one of the pages
60546723f19Sschwarze 	 * even if more than one is found.
60646723f19Sschwarze 	 */
60746723f19Sschwarze 
6084592dbf1Sschwarze 	if (req->q.equal || sz == 1) {
609735516bdSschwarze 		puts("<hr>");
61046723f19Sschwarze 		iuse = 0;
611db26164eSschwarze 		priouse = 20;
612be14a32aSschwarze 		archpriouse = 3;
61346723f19Sschwarze 		for (i = 0; i < sz; i++) {
614db26164eSschwarze 			sec = r[i].file;
615db26164eSschwarze 			sec += strcspn(sec, "123456789");
616db26164eSschwarze 			if (sec[0] == '\0')
61746723f19Sschwarze 				continue;
618db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
619db26164eSschwarze 			if (sec[1] != '/')
620db26164eSschwarze 				prio += 10;
621db26164eSschwarze 			if (req->q.arch == NULL) {
622be14a32aSschwarze 				archprio =
623db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
624db26164eSschwarze 					== NULL) ? 3 :
625db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
626db26164eSschwarze 					== NULL) ? 0 :
627be14a32aSschwarze 				    strncmp(arch, "amd64/",
628be14a32aSschwarze 					archend - arch) ? 2 : 1;
629be14a32aSschwarze 				if (archprio < archpriouse) {
630be14a32aSschwarze 					archpriouse = archprio;
631be14a32aSschwarze 					priouse = prio;
632be14a32aSschwarze 					iuse = i;
633be14a32aSschwarze 					continue;
634be14a32aSschwarze 				}
635be14a32aSschwarze 				if (archprio > archpriouse)
636be14a32aSschwarze 					continue;
637be14a32aSschwarze 			}
63846723f19Sschwarze 			if (prio >= priouse)
63946723f19Sschwarze 				continue;
64046723f19Sschwarze 			priouse = prio;
64146723f19Sschwarze 			iuse = i;
64246723f19Sschwarze 		}
64346723f19Sschwarze 		resp_show(req, r[iuse].file);
64446723f19Sschwarze 	}
64546723f19Sschwarze 
646c6c22f12Sschwarze 	resp_end_html();
647c6c22f12Sschwarze }
648c6c22f12Sschwarze 
649c6c22f12Sschwarze static void
650941df026Sschwarze resp_catman(const struct req *req, const char *file)
651c6c22f12Sschwarze {
652c6c22f12Sschwarze 	FILE		*f;
653c6c22f12Sschwarze 	char		*p;
65431f93c25Sschwarze 	size_t		 sz;
65531f93c25Sschwarze 	ssize_t		 len;
65631f93c25Sschwarze 	int		 i;
657c6c22f12Sschwarze 	int		 italic, bold;
658c6c22f12Sschwarze 
65931f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
660735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
661c6c22f12Sschwarze 		return;
662c6c22f12Sschwarze 	}
663c6c22f12Sschwarze 
664735516bdSschwarze 	puts("<div class=\"catman\">\n"
665735516bdSschwarze 	     "<pre>");
666c6c22f12Sschwarze 
66731f93c25Sschwarze 	p = NULL;
66831f93c25Sschwarze 	sz = 0;
66931f93c25Sschwarze 
67031f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
671c6c22f12Sschwarze 		bold = italic = 0;
67231f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
673c6c22f12Sschwarze 			/*
674c6c22f12Sschwarze 			 * This means that the catpage is out of state.
675c6c22f12Sschwarze 			 * Ignore it and keep going (although the
676c6c22f12Sschwarze 			 * catpage is bogus).
677c6c22f12Sschwarze 			 */
678c6c22f12Sschwarze 
679c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
680c6c22f12Sschwarze 				continue;
681c6c22f12Sschwarze 
682c6c22f12Sschwarze 			/*
683c6c22f12Sschwarze 			 * Print a regular character.
684c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
685c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
686c6c22f12Sschwarze 			 * have something to enter when we backspace.
687c6c22f12Sschwarze 			 */
688c6c22f12Sschwarze 
689c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
690c6c22f12Sschwarze 				if (italic)
691735516bdSschwarze 					printf("</i>");
692c6c22f12Sschwarze 				if (bold)
693735516bdSschwarze 					printf("</b>");
694c6c22f12Sschwarze 				italic = bold = 0;
695c6c22f12Sschwarze 				html_putchar(p[i]);
696c6c22f12Sschwarze 				continue;
69731f93c25Sschwarze 			} else if (i + 2 >= len)
698c6c22f12Sschwarze 				continue;
699c6c22f12Sschwarze 
700c6c22f12Sschwarze 			/* Italic mode. */
701c6c22f12Sschwarze 
702c6c22f12Sschwarze 			if ('_' == p[i]) {
703c6c22f12Sschwarze 				if (bold)
704735516bdSschwarze 					printf("</b>");
705c6c22f12Sschwarze 				if ( ! italic)
706735516bdSschwarze 					printf("<i>");
707c6c22f12Sschwarze 				bold = 0;
708c6c22f12Sschwarze 				italic = 1;
709c6c22f12Sschwarze 				i += 2;
710c6c22f12Sschwarze 				html_putchar(p[i]);
711c6c22f12Sschwarze 				continue;
712c6c22f12Sschwarze 			}
713c6c22f12Sschwarze 
714c6c22f12Sschwarze 			/*
715c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
716c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
717c6c22f12Sschwarze 			 */
718c6c22f12Sschwarze 
719c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
720c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
721c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
722c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
723c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
724c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
725c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
726c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
727c6c22f12Sschwarze 				if (italic)
728735516bdSschwarze 					printf("</i>");
729c6c22f12Sschwarze 				if (bold)
730735516bdSschwarze 					printf("</b>");
731c6c22f12Sschwarze 				italic = bold = 0;
732c6c22f12Sschwarze 				putchar('*');
733c6c22f12Sschwarze 				i += 2;
734c6c22f12Sschwarze 				continue;
735c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
736c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
737c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
738c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
739c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
740c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
741c6c22f12Sschwarze 				if (italic)
742735516bdSschwarze 					printf("</i>");
743c6c22f12Sschwarze 				if (bold)
744735516bdSschwarze 					printf("</b>");
745c6c22f12Sschwarze 				italic = bold = 0;
746c6c22f12Sschwarze 				putchar('+');
747c6c22f12Sschwarze 				i += 2;
748c6c22f12Sschwarze 				continue;
749c6c22f12Sschwarze 			}
750c6c22f12Sschwarze 
751c6c22f12Sschwarze 			/* Bold mode. */
752c6c22f12Sschwarze 
753c6c22f12Sschwarze 			if (italic)
754735516bdSschwarze 				printf("</i>");
755c6c22f12Sschwarze 			if ( ! bold)
756735516bdSschwarze 				printf("<b>");
757c6c22f12Sschwarze 			bold = 1;
758c6c22f12Sschwarze 			italic = 0;
759c6c22f12Sschwarze 			i += 2;
760c6c22f12Sschwarze 			html_putchar(p[i]);
761c6c22f12Sschwarze 		}
762c6c22f12Sschwarze 
763c6c22f12Sschwarze 		/*
764c6c22f12Sschwarze 		 * Clean up the last character.
765c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
766c6c22f12Sschwarze 		 */
767c6c22f12Sschwarze 
768c6c22f12Sschwarze 		if (italic)
769735516bdSschwarze 			printf("</i>");
770c6c22f12Sschwarze 		if (bold)
771735516bdSschwarze 			printf("</b>");
772c6c22f12Sschwarze 
77331f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
774c6c22f12Sschwarze 			html_putchar(p[i]);
775c6c22f12Sschwarze 
776c6c22f12Sschwarze 		putchar('\n');
777c6c22f12Sschwarze 	}
77831f93c25Sschwarze 	free(p);
779c6c22f12Sschwarze 
780735516bdSschwarze 	puts("</pre>\n"
781735516bdSschwarze 	     "</div>");
782c6c22f12Sschwarze 
783c6c22f12Sschwarze 	fclose(f);
784c6c22f12Sschwarze }
785c6c22f12Sschwarze 
786c6c22f12Sschwarze static void
787941df026Sschwarze resp_format(const struct req *req, const char *file)
788c6c22f12Sschwarze {
7892ccd0917Sschwarze 	struct manoutput conf;
790c6c22f12Sschwarze 	struct mparse	*mp;
791ede1b9d0Sschwarze 	struct roff_man	*man;
792c6c22f12Sschwarze 	void		*vp;
793f74d674aSschwarze 	int		 fd;
794f74d674aSschwarze 	int		 usepath;
795c6c22f12Sschwarze 
796c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
797735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
798c6c22f12Sschwarze 		return;
799c6c22f12Sschwarze 	}
800c6c22f12Sschwarze 
80116536faaSschwarze 	mchars_alloc();
802f139d5f6Sschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
803f139d5f6Sschwarze 	    MANDOCLEVEL_BADARG, NULL, req->q.manpath);
804df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
805c6c22f12Sschwarze 	close(fd);
806c6c22f12Sschwarze 
8072ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
8082ccd0917Sschwarze 	conf.fragment = 1;
809f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
810f211c215Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%%N.%%S",
811f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
812c6c22f12Sschwarze 
813f2d5c709Sschwarze 	mparse_result(mp, &man, NULL);
814f2d5c709Sschwarze 	if (man == NULL) {
815c976f0e2Sschwarze 		warnx("fatal mandoc error: %s/%s", req->q.manpath, file);
816facea411Sschwarze 		pg_error_internal();
817c6c22f12Sschwarze 		mparse_free(mp);
81816536faaSschwarze 		mchars_free();
819c6c22f12Sschwarze 		return;
820c6c22f12Sschwarze 	}
821c6c22f12Sschwarze 
82216536faaSschwarze 	vp = html_alloc(&conf);
823c6c22f12Sschwarze 
824396853b5Sschwarze 	if (man->macroset == MACROSET_MDOC) {
825396853b5Sschwarze 		mdoc_validate(man);
826f2d5c709Sschwarze 		html_mdoc(vp, man);
827fec2846bSschwarze 	} else {
828fec2846bSschwarze 		man_validate(man);
829c6c22f12Sschwarze 		html_man(vp, man);
830fec2846bSschwarze 	}
831c6c22f12Sschwarze 
832c6c22f12Sschwarze 	html_free(vp);
833c6c22f12Sschwarze 	mparse_free(mp);
83416536faaSschwarze 	mchars_free();
8352ccd0917Sschwarze 	free(conf.man);
836c6c22f12Sschwarze }
837c6c22f12Sschwarze 
838c6c22f12Sschwarze static void
83946723f19Sschwarze resp_show(const struct req *req, const char *file)
84046723f19Sschwarze {
84181475784Sschwarze 
84281475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
8432f7bef27Sschwarze 		file += 2;
84446723f19Sschwarze 
84546723f19Sschwarze 	if ('c' == *file)
846941df026Sschwarze 		resp_catman(req, file);
84746723f19Sschwarze 	else
848941df026Sschwarze 		resp_format(req, file);
84946723f19Sschwarze }
85046723f19Sschwarze 
85146723f19Sschwarze static void
852b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
853c6c22f12Sschwarze {
854b53c14c3Sschwarze 	char		*manpath;
855b53c14c3Sschwarze 	const char	*file;
856c6c22f12Sschwarze 
857b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
858facea411Sschwarze 		pg_error_badrequest(
859c6c22f12Sschwarze 		    "You did not specify a page to show.");
860c6c22f12Sschwarze 		return;
861c6c22f12Sschwarze 	}
862b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
863b53c14c3Sschwarze 	file++;
864c6c22f12Sschwarze 
865b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
866631ce2c6Sschwarze 		pg_error_badrequest(
867631ce2c6Sschwarze 		    "You specified an invalid manpath.");
868b53c14c3Sschwarze 		free(manpath);
869631ce2c6Sschwarze 		return;
870631ce2c6Sschwarze 	}
871631ce2c6Sschwarze 
872c6c22f12Sschwarze 	/*
873c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
874c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
875c6c22f12Sschwarze 	 * relative to the manpath root.
876c6c22f12Sschwarze 	 */
877c6c22f12Sschwarze 
878b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
879c976f0e2Sschwarze 		warn("chdir %s", manpath);
880631ce2c6Sschwarze 		pg_error_internal();
881b53c14c3Sschwarze 		free(manpath);
882c6c22f12Sschwarze 		return;
883c6c22f12Sschwarze 	}
884b53c14c3Sschwarze 	free(manpath);
885b53c14c3Sschwarze 
886b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
88781475784Sschwarze 		pg_error_badrequest(
88881475784Sschwarze 		    "You specified an invalid manual file.");
88981475784Sschwarze 		return;
89081475784Sschwarze 	}
89181475784Sschwarze 
89246723f19Sschwarze 	resp_begin_html(200, NULL);
89384f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
894b53c14c3Sschwarze 	resp_show(req, file);
89546723f19Sschwarze 	resp_end_html();
896c6c22f12Sschwarze }
897c6c22f12Sschwarze 
898c6c22f12Sschwarze static void
89957482ef4Sschwarze pg_search(const struct req *req)
900c6c22f12Sschwarze {
901c6c22f12Sschwarze 	struct mansearch	  search;
902c6c22f12Sschwarze 	struct manpaths		  paths;
903c6c22f12Sschwarze 	struct manpage		 *res;
904fbeeb774Sschwarze 	char			**argv;
905fbeeb774Sschwarze 	char			 *query, *rp, *wp;
906c6c22f12Sschwarze 	size_t			  ressz;
907fbeeb774Sschwarze 	int			  argc;
908c6c22f12Sschwarze 
909c6c22f12Sschwarze 	/*
910c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
911c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
912c6c22f12Sschwarze 	 * relative to the manpath root.
913c6c22f12Sschwarze 	 */
914c6c22f12Sschwarze 
915c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
916c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
917631ce2c6Sschwarze 		pg_error_internal();
918c6c22f12Sschwarze 		return;
919c6c22f12Sschwarze 	}
920c6c22f12Sschwarze 
921c6c22f12Sschwarze 	search.arch = req->q.arch;
922c6c22f12Sschwarze 	search.sec = req->q.sec;
9230f10154cSschwarze 	search.outkey = "Nd";
9240f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
925fea71919Sschwarze 	search.firstmatch = 1;
926c6c22f12Sschwarze 
927c6c22f12Sschwarze 	paths.sz = 1;
928c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
929c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
930c6c22f12Sschwarze 
931c6c22f12Sschwarze 	/*
932fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
933c6c22f12Sschwarze 	 */
934c6c22f12Sschwarze 
935fbeeb774Sschwarze 	argc = 0;
936fbeeb774Sschwarze 	argv = NULL;
937fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
938fbeeb774Sschwarze 	for (;;) {
939fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
940fbeeb774Sschwarze 			rp++;
941fbeeb774Sschwarze 		if (*rp == '\0')
942fbeeb774Sschwarze 			break;
943fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
944fbeeb774Sschwarze 		argv[argc++] = wp = rp;
945fbeeb774Sschwarze 		for (;;) {
946fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
947fbeeb774Sschwarze 				*wp = '\0';
948fbeeb774Sschwarze 				rp++;
949fbeeb774Sschwarze 				break;
950fbeeb774Sschwarze 			}
951fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
952fbeeb774Sschwarze 				rp++;
953fbeeb774Sschwarze 			if (wp != rp)
954fbeeb774Sschwarze 				*wp = *rp;
955fbeeb774Sschwarze 			if (*rp == '\0')
956fbeeb774Sschwarze 				break;
957fbeeb774Sschwarze 			wp++;
958fbeeb774Sschwarze 			rp++;
959fbeeb774Sschwarze 		}
960c6c22f12Sschwarze 	}
961c6c22f12Sschwarze 
962fbeeb774Sschwarze 	if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
963facea411Sschwarze 		pg_noresult(req, "You entered an invalid query.");
964c6c22f12Sschwarze 	else if (0 == ressz)
965facea411Sschwarze 		pg_noresult(req, "No results found.");
966c6c22f12Sschwarze 	else
967facea411Sschwarze 		pg_searchres(req, res, ressz);
968c6c22f12Sschwarze 
969fbeeb774Sschwarze 	free(query);
970fbeeb774Sschwarze 	mansearch_free(res, ressz);
971c6c22f12Sschwarze 	free(paths.paths[0]);
972c6c22f12Sschwarze 	free(paths.paths);
973c6c22f12Sschwarze }
974c6c22f12Sschwarze 
975c6c22f12Sschwarze int
976c6c22f12Sschwarze main(void)
977c6c22f12Sschwarze {
978c6c22f12Sschwarze 	struct req	 req;
979136c26b8Sschwarze 	struct itimerval itimer;
98057482ef4Sschwarze 	const char	*path;
98131e689c3Sschwarze 	const char	*querystring;
98257482ef4Sschwarze 	int		 i;
983c6c22f12Sschwarze 
984136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
985136c26b8Sschwarze 
9862935aafcSschwarze 	itimer.it_value.tv_sec = 2;
987136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
9882935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
989136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
990136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
991c976f0e2Sschwarze 		warn("setitimer");
992136c26b8Sschwarze 		pg_error_internal();
993526e306bSschwarze 		return EXIT_FAILURE;
994136c26b8Sschwarze 	}
995136c26b8Sschwarze 
996c6c22f12Sschwarze 	/*
9976fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
998c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
999c6c22f12Sschwarze 	 * relative to the same position.
1000c6c22f12Sschwarze 	 */
1001c6c22f12Sschwarze 
1002c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1003c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1004facea411Sschwarze 		pg_error_internal();
1005526e306bSschwarze 		return EXIT_FAILURE;
1006c6c22f12Sschwarze 	}
1007c6c22f12Sschwarze 
1008c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1009abf19dc9Sschwarze 	req.q.equal = 1;
1010941df026Sschwarze 	parse_manpath_conf(&req);
1011c6c22f12Sschwarze 
101202b1b494Sschwarze 	/* Parse the path info and the query string. */
1013c6c22f12Sschwarze 
101402b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
101502b1b494Sschwarze 		path = "";
101602b1b494Sschwarze 	else if (*path == '/')
101702b1b494Sschwarze 		path++;
101802b1b494Sschwarze 
1019aa16a3a8Sschwarze 	if (*path != '\0') {
1020941df026Sschwarze 		parse_path_info(&req, path);
102186f4328bSschwarze 		if (req.q.manpath == NULL || access(path, F_OK) == -1)
102202b1b494Sschwarze 			path = "";
102302b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1024941df026Sschwarze 		parse_query_string(&req, querystring);
1025c6c22f12Sschwarze 
102602b1b494Sschwarze 	/* Validate parsed data and add defaults. */
102702b1b494Sschwarze 
102850eaed2bSschwarze 	if (req.q.manpath == NULL)
102950eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
103050eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1031631ce2c6Sschwarze 		pg_error_badrequest(
1032631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1033526e306bSschwarze 		return EXIT_FAILURE;
1034631ce2c6Sschwarze 	}
1035631ce2c6Sschwarze 
1036cf3a545cSschwarze 	if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1037cf3a545cSschwarze 		pg_error_badrequest(
1038cf3a545cSschwarze 		    "You specified an invalid architecture.");
1039526e306bSschwarze 		return EXIT_FAILURE;
1040cf3a545cSschwarze 	}
1041cf3a545cSschwarze 
104257482ef4Sschwarze 	/* Dispatch to the three different pages. */
1043c6c22f12Sschwarze 
104457482ef4Sschwarze 	if ('\0' != *path)
104557482ef4Sschwarze 		pg_show(&req, path);
1046e89321abSschwarze 	else if (NULL != req.q.query)
104757482ef4Sschwarze 		pg_search(&req);
104857482ef4Sschwarze 	else
1049facea411Sschwarze 		pg_index(&req);
1050c6c22f12Sschwarze 
105131e689c3Sschwarze 	free(req.q.manpath);
105231e689c3Sschwarze 	free(req.q.arch);
105331e689c3Sschwarze 	free(req.q.sec);
1054e89321abSschwarze 	free(req.q.query);
1055c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1056c6c22f12Sschwarze 		free(req.p[i]);
1057c6c22f12Sschwarze 	free(req.p);
1058526e306bSschwarze 	return EXIT_SUCCESS;
1059c6c22f12Sschwarze }
1060c6c22f12Sschwarze 
1061c6c22f12Sschwarze /*
106202b1b494Sschwarze  * If PATH_INFO is not a file name, translate it to a query.
106302b1b494Sschwarze  */
106402b1b494Sschwarze static void
1065941df026Sschwarze parse_path_info(struct req *req, const char *path)
106602b1b494Sschwarze {
1067daf4c292Sschwarze 	char	*dir[4];
1068daf4c292Sschwarze 	int	 i;
106902b1b494Sschwarze 
107052b413c1Sschwarze 	req->isquery = 0;
107102b1b494Sschwarze 	req->q.equal = 1;
107202b1b494Sschwarze 	req->q.manpath = mandoc_strdup(path);
1073daf4c292Sschwarze 	req->q.arch = NULL;
107402b1b494Sschwarze 
107502b1b494Sschwarze 	/* Mandatory manual page name. */
107602b1b494Sschwarze 	if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) {
107702b1b494Sschwarze 		req->q.query = req->q.manpath;
107802b1b494Sschwarze 		req->q.manpath = NULL;
107902b1b494Sschwarze 	} else
108002b1b494Sschwarze 		*req->q.query++ = '\0';
108102b1b494Sschwarze 
108202b1b494Sschwarze 	/* Optional trailing section. */
108302b1b494Sschwarze 	if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) {
108402b1b494Sschwarze 		if(isdigit((unsigned char)req->q.sec[1])) {
108502b1b494Sschwarze 			*req->q.sec++ = '\0';
108602b1b494Sschwarze 			req->q.sec = mandoc_strdup(req->q.sec);
108702b1b494Sschwarze 		} else
108802b1b494Sschwarze 			req->q.sec = NULL;
108902b1b494Sschwarze 	}
109002b1b494Sschwarze 
109102b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1092daf4c292Sschwarze 	if (req->q.manpath == NULL)
109302b1b494Sschwarze 		return;
109402b1b494Sschwarze 	req->q.query = mandoc_strdup(req->q.query);
109502b1b494Sschwarze 
1096daf4c292Sschwarze 	/* Split directory components. */
1097daf4c292Sschwarze 	dir[i = 0] = req->q.manpath;
1098daf4c292Sschwarze 	while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) {
1099daf4c292Sschwarze 		if (++i == 3) {
1100daf4c292Sschwarze 			pg_error_badrequest(
1101daf4c292Sschwarze 			    "You specified too many directory components.");
1102daf4c292Sschwarze 			exit(EXIT_FAILURE);
110302b1b494Sschwarze 		}
1104daf4c292Sschwarze 		*dir[i]++ = '\0';
1105daf4c292Sschwarze 	}
1106daf4c292Sschwarze 
1107daf4c292Sschwarze 	/* Optional manpath. */
1108daf4c292Sschwarze 	if ((i = validate_manpath(req, req->q.manpath)) == 0)
1109daf4c292Sschwarze 		req->q.manpath = NULL;
1110daf4c292Sschwarze 	else if (dir[1] == NULL)
1111daf4c292Sschwarze 		return;
1112daf4c292Sschwarze 
1113daf4c292Sschwarze 	/* Optional section. */
1114daf4c292Sschwarze 	if (strncmp(dir[i], "man", 3) == 0) {
1115daf4c292Sschwarze 		free(req->q.sec);
1116daf4c292Sschwarze 		req->q.sec = mandoc_strdup(dir[i++] + 3);
1117daf4c292Sschwarze 	}
1118daf4c292Sschwarze 	if (dir[i] == NULL) {
1119daf4c292Sschwarze 		if (req->q.manpath == NULL)
1120daf4c292Sschwarze 			free(dir[0]);
1121daf4c292Sschwarze 		return;
1122daf4c292Sschwarze 	}
1123daf4c292Sschwarze 	if (dir[i + 1] != NULL) {
1124daf4c292Sschwarze 		pg_error_badrequest(
1125daf4c292Sschwarze 		    "You specified an invalid directory component.");
1126daf4c292Sschwarze 		exit(EXIT_FAILURE);
1127daf4c292Sschwarze 	}
1128daf4c292Sschwarze 
1129daf4c292Sschwarze 	/* Optional architecture. */
1130daf4c292Sschwarze 	if (i) {
1131daf4c292Sschwarze 		req->q.arch = mandoc_strdup(dir[i]);
1132daf4c292Sschwarze 		if (req->q.manpath == NULL)
1133daf4c292Sschwarze 			free(dir[0]);
1134daf4c292Sschwarze 	} else
1135daf4c292Sschwarze 		req->q.arch = dir[0];
113602b1b494Sschwarze }
113702b1b494Sschwarze 
113802b1b494Sschwarze /*
1139c6c22f12Sschwarze  * Scan for indexable paths.
1140c6c22f12Sschwarze  */
1141c6c22f12Sschwarze static void
1142941df026Sschwarze parse_manpath_conf(struct req *req)
1143c6c22f12Sschwarze {
1144c6c22f12Sschwarze 	FILE	*fp;
1145c6c22f12Sschwarze 	char	*dp;
1146c6c22f12Sschwarze 	size_t	 dpsz;
114731f93c25Sschwarze 	ssize_t	 len;
1148c6c22f12Sschwarze 
1149c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1150c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1151de651747Sschwarze 		pg_error_internal();
1152de651747Sschwarze 		exit(EXIT_FAILURE);
1153de651747Sschwarze 	}
1154c6c22f12Sschwarze 
115531f93c25Sschwarze 	dp = NULL;
115631f93c25Sschwarze 	dpsz = 0;
115731f93c25Sschwarze 
115831f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
115931f93c25Sschwarze 		if (dp[len - 1] == '\n')
116031f93c25Sschwarze 			dp[--len] = '\0';
1161c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1162c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1163cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1164c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1165c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1166cf3a545cSschwarze 			pg_error_internal();
1167cf3a545cSschwarze 			exit(EXIT_FAILURE);
1168cf3a545cSschwarze 		}
1169c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1170c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1171c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1172cf3a545cSschwarze 			pg_error_internal();
1173cf3a545cSschwarze 			exit(EXIT_FAILURE);
1174cf3a545cSschwarze 		}
1175cf3a545cSschwarze 		req->p[req->psz++] = dp;
117631f93c25Sschwarze 		dp = NULL;
117731f93c25Sschwarze 		dpsz = 0;
1178c6c22f12Sschwarze 	}
117931f93c25Sschwarze 	free(dp);
1180de651747Sschwarze 
1181de651747Sschwarze 	if (req->p == NULL) {
1182c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1183de651747Sschwarze 		pg_error_internal();
1184de651747Sschwarze 		exit(EXIT_FAILURE);
1185de651747Sschwarze 	}
1186c6c22f12Sschwarze }
1187