xref: /openbsd/usr.bin/mandoc/cgi.c (revision 955967fc)
1*955967fcSschwarze /*	$OpenBSD: cgi.c,v 1.103 2019/01/31 23:00:18 schwarze Exp $ */
2c6c22f12Sschwarze /*
3c6c22f12Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
432f0ba5fSschwarze  * Copyright (c) 2014, 2015, 2016, 2017, 2018 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"
3799acaf1eSschwarze #include "mandoc_parse.h"
38c6c22f12Sschwarze #include "main.h"
394de77decSschwarze #include "manconf.h"
40c6c22f12Sschwarze #include "mansearch.h"
416fdade3eSschwarze #include "cgi.h"
42c6c22f12Sschwarze 
43c6c22f12Sschwarze /*
44c6c22f12Sschwarze  * A query as passed to the search function.
45c6c22f12Sschwarze  */
46c6c22f12Sschwarze struct	query {
4731e689c3Sschwarze 	char		*manpath; /* desired manual directory */
4831e689c3Sschwarze 	char		*arch; /* architecture */
4931e689c3Sschwarze 	char		*sec; /* manual section */
50e89321abSschwarze 	char		*query; /* unparsed query expression */
514477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
52c6c22f12Sschwarze };
53c6c22f12Sschwarze 
54c6c22f12Sschwarze struct	req {
55c6c22f12Sschwarze 	struct query	  q;
56c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
57c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
5852b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
59c6c22f12Sschwarze };
60c6c22f12Sschwarze 
6184f05c93Sschwarze enum	focus {
6284f05c93Sschwarze 	FOCUS_NONE = 0,
6384f05c93Sschwarze 	FOCUS_QUERY
6484f05c93Sschwarze };
6584f05c93Sschwarze 
66c6c22f12Sschwarze static	void		 html_print(const char *);
67c6c22f12Sschwarze static	void		 html_putchar(char);
68c6c22f12Sschwarze static	int		 http_decode(char *);
69f7a12365Sschwarze static	void		 http_encode(const char *p);
70941df026Sschwarze static	void		 parse_manpath_conf(struct req *);
71941df026Sschwarze static	void		 parse_path_info(struct req *req, const char *path);
72941df026Sschwarze static	void		 parse_query_string(struct req *, const char *);
73facea411Sschwarze static	void		 pg_error_badrequest(const char *);
74facea411Sschwarze static	void		 pg_error_internal(void);
75facea411Sschwarze static	void		 pg_index(const struct req *);
76facea411Sschwarze static	void		 pg_noresult(const struct req *, const char *);
77e1beff2aSschwarze static	void		 pg_redirect(const struct req *, const char *);
7857482ef4Sschwarze static	void		 pg_search(const struct req *);
79facea411Sschwarze static	void		 pg_searchres(const struct req *,
80facea411Sschwarze 				struct manpage *, size_t);
8181060b1aSschwarze static	void		 pg_show(struct req *, const char *);
82fdef72b0Sschwarze static	void		 resp_begin_html(int, const char *, const char *);
83c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
84941df026Sschwarze static	void		 resp_catman(const struct req *, const char *);
85711661c7Sschwarze static	void		 resp_copy(const char *);
86c6c22f12Sschwarze static	void		 resp_end_html(void);
87941df026Sschwarze static	void		 resp_format(const struct req *, const char *);
8884f05c93Sschwarze static	void		 resp_searchform(const struct req *, enum focus);
8946723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
90e89321abSschwarze static	void		 set_query_attr(char **, char **);
91f7a12365Sschwarze static	int		 validate_arch(const char *);
92e89321abSschwarze static	int		 validate_filename(const char *);
93e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
94e89321abSschwarze static	int		 validate_urifrag(const char *);
95c6c22f12Sschwarze 
963b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
97c6c22f12Sschwarze 
9846723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
9928e449d6Sschwarze static	const char *const sec_numbers[] = {
10028e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10128e449d6Sschwarze };
10228e449d6Sschwarze static	const char *const sec_names[] = {
10328e449d6Sschwarze     "All Sections",
10428e449d6Sschwarze     "1 - General Commands",
10528e449d6Sschwarze     "2 - System Calls",
1064e6618fdSschwarze     "3 - Library Functions",
1074e6618fdSschwarze     "3p - Perl Library",
1084e6618fdSschwarze     "4 - Device Drivers",
10928e449d6Sschwarze     "5 - File Formats",
11028e449d6Sschwarze     "6 - Games",
1114e6618fdSschwarze     "7 - Miscellaneous Information",
1124e6618fdSschwarze     "8 - System Manager\'s Manual",
1134e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
11428e449d6Sschwarze };
11528e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
11628e449d6Sschwarze 
11728e449d6Sschwarze static	const char *const arch_names[] = {
11893d3e4e1Sderaadt     "amd64",       "alpha",       "armv7",	"arm64",
119766ef059Sschwarze     "hppa",        "i386",        "landisk",
1203bcd4815Sschwarze     "loongson",    "luna88k",     "macppc",      "mips64",
1216250a715Sschwarze     "octeon",      "sgi",         "socppc",      "sparc64",
122766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
123766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
124766ef059Sschwarze     "hppa64",      "hp300",
1253bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1263bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
1270c245db5Sschwarze     "pmax",        "powerpc",     "solbourne",   "sparc",
1286250a715Sschwarze     "sun3",        "vax",         "wgrisc",      "x68k",
1296250a715Sschwarze     "zaurus"
13028e449d6Sschwarze };
13128e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
13228e449d6Sschwarze 
133c6c22f12Sschwarze /*
134c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
135c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
136c6c22f12Sschwarze  */
137c6c22f12Sschwarze static void
138c6c22f12Sschwarze html_putchar(char c)
139c6c22f12Sschwarze {
140c6c22f12Sschwarze 
141c6c22f12Sschwarze 	switch (c) {
142e74fa2aeSschwarze 	case '"':
143f765a656Sbentley 		printf("&quot;");
144c6c22f12Sschwarze 		break;
145e74fa2aeSschwarze 	case '&':
146c6c22f12Sschwarze 		printf("&amp;");
147c6c22f12Sschwarze 		break;
148e74fa2aeSschwarze 	case '>':
149c6c22f12Sschwarze 		printf("&gt;");
150c6c22f12Sschwarze 		break;
151e74fa2aeSschwarze 	case '<':
152c6c22f12Sschwarze 		printf("&lt;");
153c6c22f12Sschwarze 		break;
154c6c22f12Sschwarze 	default:
155c6c22f12Sschwarze 		putchar((unsigned char)c);
156c6c22f12Sschwarze 		break;
157c6c22f12Sschwarze 	}
158c6c22f12Sschwarze }
159c6c22f12Sschwarze 
160c6c22f12Sschwarze /*
161c6c22f12Sschwarze  * Call through to html_putchar().
162c6c22f12Sschwarze  * Accepts NULL strings.
163c6c22f12Sschwarze  */
164c6c22f12Sschwarze static void
165c6c22f12Sschwarze html_print(const char *p)
166c6c22f12Sschwarze {
167c6c22f12Sschwarze 
168c6c22f12Sschwarze 	if (NULL == p)
169c6c22f12Sschwarze 		return;
170c6c22f12Sschwarze 	while ('\0' != *p)
171c6c22f12Sschwarze 		html_putchar(*p++);
172c6c22f12Sschwarze }
173c6c22f12Sschwarze 
174c6c22f12Sschwarze /*
17531e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
17631e689c3Sschwarze  * to the query structure.
177c6c22f12Sschwarze  */
178c6c22f12Sschwarze static void
17931e689c3Sschwarze set_query_attr(char **attr, char **val)
18031e689c3Sschwarze {
18131e689c3Sschwarze 
18231e689c3Sschwarze 	free(*attr);
18331e689c3Sschwarze 	if (**val == '\0') {
18431e689c3Sschwarze 		*attr = NULL;
18531e689c3Sschwarze 		free(*val);
18631e689c3Sschwarze 	} else
18731e689c3Sschwarze 		*attr = *val;
18831e689c3Sschwarze 	*val = NULL;
18931e689c3Sschwarze }
19031e689c3Sschwarze 
19131e689c3Sschwarze /*
19231e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
19331e689c3Sschwarze  * and store the values into the query structure.
19431e689c3Sschwarze  */
19531e689c3Sschwarze static void
196941df026Sschwarze parse_query_string(struct req *req, const char *qs)
197c6c22f12Sschwarze {
198c6c22f12Sschwarze 	char		*key, *val;
19931e689c3Sschwarze 	size_t		 keysz, valsz;
200c6c22f12Sschwarze 
20152b413c1Sschwarze 	req->isquery	= 1;
20231e689c3Sschwarze 	req->q.manpath	= NULL;
20331e689c3Sschwarze 	req->q.arch	= NULL;
20431e689c3Sschwarze 	req->q.sec	= NULL;
205e89321abSschwarze 	req->q.query	= NULL;
2064477fbfaSschwarze 	req->q.equal	= 1;
207c6c22f12Sschwarze 
20831e689c3Sschwarze 	key = val = NULL;
20931e689c3Sschwarze 	while (*qs != '\0') {
210c6c22f12Sschwarze 
21131e689c3Sschwarze 		/* Parse one key. */
212c6c22f12Sschwarze 
21331e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
21431e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
21531e689c3Sschwarze 		qs += keysz;
21631e689c3Sschwarze 		if (*qs != '=')
21731e689c3Sschwarze 			goto next;
218c6c22f12Sschwarze 
21931e689c3Sschwarze 		/* Parse one value. */
220c6c22f12Sschwarze 
22131e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
22231e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
22331e689c3Sschwarze 		qs += valsz;
224c6c22f12Sschwarze 
22531e689c3Sschwarze 		/* Decode and catch encoding errors. */
22631e689c3Sschwarze 
22731e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
22831e689c3Sschwarze 			goto next;
22931e689c3Sschwarze 
23031e689c3Sschwarze 		/* Handle key-value pairs. */
23131e689c3Sschwarze 
23231e689c3Sschwarze 		if ( ! strcmp(key, "query"))
233e89321abSschwarze 			set_query_attr(&req->q.query, &val);
23431e689c3Sschwarze 
23531e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
23631e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
23731e689c3Sschwarze 
23831e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
239aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24031e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
241aabcc9a1Sschwarze 				val[7] = '-';
242aabcc9a1Sschwarze 				if ('C' == val[8])
243aabcc9a1Sschwarze 					val[8] = 'c';
244aabcc9a1Sschwarze 			}
245aabcc9a1Sschwarze #endif
24631e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
24731e689c3Sschwarze 		}
24831e689c3Sschwarze 
24931e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
250aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
25131e689c3Sschwarze 		    && strcmp(key, "sektion")
252aabcc9a1Sschwarze #endif
25331e689c3Sschwarze 		    )) {
25431e689c3Sschwarze 			if ( ! strcmp(val, "0"))
25531e689c3Sschwarze 				*val = '\0';
25631e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
257c6c22f12Sschwarze 		}
25831e689c3Sschwarze 
25931e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
26031e689c3Sschwarze 			if ( ! strcmp(val, "default"))
26131e689c3Sschwarze 				*val = '\0';
26231e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2634477fbfaSschwarze 		}
26431e689c3Sschwarze 
26531e689c3Sschwarze 		/*
26631e689c3Sschwarze 		 * The key must be freed in any case.
26731e689c3Sschwarze 		 * The val may have been handed over to the query
26831e689c3Sschwarze 		 * structure, in which case it is now NULL.
26931e689c3Sschwarze 		 */
27031e689c3Sschwarze next:
27131e689c3Sschwarze 		free(key);
27231e689c3Sschwarze 		key = NULL;
27331e689c3Sschwarze 		free(val);
27431e689c3Sschwarze 		val = NULL;
27531e689c3Sschwarze 
27631e689c3Sschwarze 		if (*qs != '\0')
27731e689c3Sschwarze 			qs++;
27831e689c3Sschwarze 	}
279c6c22f12Sschwarze }
280c6c22f12Sschwarze 
281c6c22f12Sschwarze /*
282c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
283c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
284c6c22f12Sschwarze  * over the allocated string.
285c6c22f12Sschwarze  */
286c6c22f12Sschwarze static int
287c6c22f12Sschwarze http_decode(char *p)
288c6c22f12Sschwarze {
289c6c22f12Sschwarze 	char             hex[3];
2901f69f32bStedu 	char		*q;
291c6c22f12Sschwarze 	int              c;
292c6c22f12Sschwarze 
293c6c22f12Sschwarze 	hex[2] = '\0';
294c6c22f12Sschwarze 
2951f69f32bStedu 	q = p;
2961f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
297c6c22f12Sschwarze 		if ('%' == *p) {
298c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
299526e306bSschwarze 				return 0;
300c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
301526e306bSschwarze 				return 0;
302c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
303526e306bSschwarze 				return 0;
304c6c22f12Sschwarze 			if ('\0' == c)
305526e306bSschwarze 				return 0;
306c6c22f12Sschwarze 
3071f69f32bStedu 			*q = (char)c;
3081f69f32bStedu 			p += 2;
309c6c22f12Sschwarze 		} else
3101f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
311c6c22f12Sschwarze 	}
312c6c22f12Sschwarze 
3131f69f32bStedu 	*q = '\0';
314526e306bSschwarze 	return 1;
315c6c22f12Sschwarze }
316c6c22f12Sschwarze 
317c6c22f12Sschwarze static void
318f7a12365Sschwarze http_encode(const char *p)
319f7a12365Sschwarze {
320f7a12365Sschwarze 	for (; *p != '\0'; p++) {
321f7a12365Sschwarze 		if (isalnum((unsigned char)*p) == 0 &&
322f7a12365Sschwarze 		    strchr("-._~", *p) == NULL)
323f7a12365Sschwarze 			printf("%%%02.2X", (unsigned char)*p);
324f7a12365Sschwarze 		else
325f7a12365Sschwarze 			putchar(*p);
326f7a12365Sschwarze 	}
327f7a12365Sschwarze }
328f7a12365Sschwarze 
329f7a12365Sschwarze static void
330c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
331c6c22f12Sschwarze {
332c6c22f12Sschwarze 
333c6c22f12Sschwarze 	if (200 != code)
334fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
335c6c22f12Sschwarze 
336fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
337fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
338fa9c540aStedu 	     "Pragma: no-cache\r\n"
339fa9c540aStedu 	     "\r\n");
340c6c22f12Sschwarze 
341c6c22f12Sschwarze 	fflush(stdout);
342c6c22f12Sschwarze }
343c6c22f12Sschwarze 
344c6c22f12Sschwarze static void
345711661c7Sschwarze resp_copy(const char *filename)
346711661c7Sschwarze {
347711661c7Sschwarze 	char	 buf[4096];
348711661c7Sschwarze 	ssize_t	 sz;
349711661c7Sschwarze 	int	 fd;
350711661c7Sschwarze 
351711661c7Sschwarze 	if ((fd = open(filename, O_RDONLY)) != -1) {
352711661c7Sschwarze 		fflush(stdout);
353711661c7Sschwarze 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
354711661c7Sschwarze 			write(STDOUT_FILENO, buf, sz);
355fd3cdd86Sjsg 		close(fd);
356711661c7Sschwarze 	}
357711661c7Sschwarze }
358711661c7Sschwarze 
359711661c7Sschwarze static void
360fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file)
361c6c22f12Sschwarze {
362fdef72b0Sschwarze 	char	*cp;
363c6c22f12Sschwarze 
364c6c22f12Sschwarze 	resp_begin_http(code, msg);
365c6c22f12Sschwarze 
366d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
367735516bdSschwarze 	       "<html>\n"
368735516bdSschwarze 	       "<head>\n"
369735516bdSschwarze 	       "  <meta charset=\"UTF-8\"/>\n"
370661a42d8Sschwarze 	       "  <meta name=\"viewport\""
371661a42d8Sschwarze 		      " content=\"width=device-width, initial-scale=1.0\">\n"
372735516bdSschwarze 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
373735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
374fdef72b0Sschwarze 	       "  <title>",
375fdef72b0Sschwarze 	       CSS_DIR);
376fdef72b0Sschwarze 	if (file != NULL) {
377fdef72b0Sschwarze 		if ((cp = strrchr(file, '/')) != NULL)
378fdef72b0Sschwarze 			file = cp + 1;
379fdef72b0Sschwarze 		if ((cp = strrchr(file, '.')) != NULL) {
380fdef72b0Sschwarze 			printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
381fdef72b0Sschwarze 		} else
382fdef72b0Sschwarze 			printf("%s - ", file);
383fdef72b0Sschwarze 	}
384fdef72b0Sschwarze 	printf("%s</title>\n"
385735516bdSschwarze 	       "</head>\n"
386ce781f36Sschwarze 	       "<body>\n",
387fdef72b0Sschwarze 	       CUSTOMIZE_TITLE);
388711661c7Sschwarze 
389711661c7Sschwarze 	resp_copy(MAN_DIR "/header.html");
390c6c22f12Sschwarze }
391c6c22f12Sschwarze 
392c6c22f12Sschwarze static void
393c6c22f12Sschwarze resp_end_html(void)
394c6c22f12Sschwarze {
395c6c22f12Sschwarze 
396711661c7Sschwarze 	resp_copy(MAN_DIR "/footer.html");
397711661c7Sschwarze 
398735516bdSschwarze 	puts("</body>\n"
399735516bdSschwarze 	     "</html>");
400c6c22f12Sschwarze }
401c6c22f12Sschwarze 
402c6c22f12Sschwarze static void
40384f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
404c6c22f12Sschwarze {
405c6c22f12Sschwarze 	int		 i;
406c6c22f12Sschwarze 
407ce781f36Sschwarze 	printf("<form action=\"/%s\" method=\"get\">\n"
408735516bdSschwarze 	       "  <fieldset>\n"
409735516bdSschwarze 	       "    <legend>Manual Page Search Parameters</legend>\n",
410c6c22f12Sschwarze 	       scriptname);
41128e449d6Sschwarze 
41228e449d6Sschwarze 	/* Write query input box. */
41328e449d6Sschwarze 
4146b824e3bSschwarze 	printf("    <input type=\"search\" name=\"query\" value=\"");
41584f05c93Sschwarze 	if (req->q.query != NULL)
416e89321abSschwarze 		html_print(req->q.query);
41784f05c93Sschwarze 	printf( "\" size=\"40\"");
41884f05c93Sschwarze 	if (focus == FOCUS_QUERY)
41984f05c93Sschwarze 		printf(" autofocus");
42084f05c93Sschwarze 	puts(">");
42128e449d6Sschwarze 
422784a63d6Sschwarze 	/* Write submission buttons. */
42328e449d6Sschwarze 
424784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
425784a63d6Sschwarze 		"man</button>\n"
426784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
427542ee4bfSschwarze 		"apropos</button>\n"
428542ee4bfSschwarze 		"    <br/>\n");
42928e449d6Sschwarze 
43028e449d6Sschwarze 	/* Write section selector. */
43128e449d6Sschwarze 
432784a63d6Sschwarze 	puts("    <select name=\"sec\">");
43328e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
434735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
43528e449d6Sschwarze 		if (NULL != req->q.sec &&
43628e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
437735516bdSschwarze 			printf(" selected=\"selected\"");
438735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
43928e449d6Sschwarze 	}
440735516bdSschwarze 	puts("    </select>");
44128e449d6Sschwarze 
44228e449d6Sschwarze 	/* Write architecture selector. */
44328e449d6Sschwarze 
444735516bdSschwarze 	printf(	"    <select name=\"arch\">\n"
445735516bdSschwarze 		"      <option value=\"default\"");
446be14a32aSschwarze 	if (NULL == req->q.arch)
447735516bdSschwarze 		printf(" selected=\"selected\"");
448735516bdSschwarze 	puts(">All Architectures</option>");
44928e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
4506b824e3bSschwarze 		printf("      <option");
45128e449d6Sschwarze 		if (NULL != req->q.arch &&
45228e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
453735516bdSschwarze 			printf(" selected=\"selected\"");
454735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
45528e449d6Sschwarze 	}
456735516bdSschwarze 	puts("    </select>");
45728e449d6Sschwarze 
45828e449d6Sschwarze 	/* Write manpath selector. */
45928e449d6Sschwarze 
460c6c22f12Sschwarze 	if (req->psz > 1) {
461735516bdSschwarze 		puts("    <select name=\"manpath\">");
462c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
463735516bdSschwarze 			printf("      <option");
46450eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
465735516bdSschwarze 				printf(" selected=\"selected\"");
4666b824e3bSschwarze 			printf(">");
467c6c22f12Sschwarze 			html_print(req->p[i]);
468735516bdSschwarze 			puts("</option>");
469c6c22f12Sschwarze 		}
470735516bdSschwarze 		puts("    </select>");
471c6c22f12Sschwarze 	}
47228e449d6Sschwarze 
473784a63d6Sschwarze 	puts("  </fieldset>\n"
474ce781f36Sschwarze 	     "</form>");
475c6c22f12Sschwarze }
476c6c22f12Sschwarze 
47781475784Sschwarze static int
478cf3a545cSschwarze validate_urifrag(const char *frag)
479cf3a545cSschwarze {
480cf3a545cSschwarze 
481cf3a545cSschwarze 	while ('\0' != *frag) {
482cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
483cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
484cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
485526e306bSschwarze 			return 0;
486cf3a545cSschwarze 		frag++;
487cf3a545cSschwarze 	}
488526e306bSschwarze 	return 1;
489cf3a545cSschwarze }
490cf3a545cSschwarze 
491cf3a545cSschwarze static int
492631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
493631ce2c6Sschwarze {
494631ce2c6Sschwarze 	size_t	 i;
495631ce2c6Sschwarze 
496631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
497631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
498526e306bSschwarze 			return 1;
499631ce2c6Sschwarze 
500526e306bSschwarze 	return 0;
501631ce2c6Sschwarze }
502631ce2c6Sschwarze 
503631ce2c6Sschwarze static int
504f7a12365Sschwarze validate_arch(const char *arch)
505f7a12365Sschwarze {
506f7a12365Sschwarze 	int	 i;
507f7a12365Sschwarze 
508f7a12365Sschwarze 	for (i = 0; i < arch_MAX; i++)
509f7a12365Sschwarze 		if (strcmp(arch, arch_names[i]) == 0)
510f7a12365Sschwarze 			return 1;
511f7a12365Sschwarze 
512f7a12365Sschwarze 	return 0;
513f7a12365Sschwarze }
514f7a12365Sschwarze 
515f7a12365Sschwarze static int
51681475784Sschwarze validate_filename(const char *file)
51781475784Sschwarze {
51881475784Sschwarze 
51981475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
52081475784Sschwarze 		file += 2;
52181475784Sschwarze 
522526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
523526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
52481475784Sschwarze }
52581475784Sschwarze 
526c6c22f12Sschwarze static void
527facea411Sschwarze pg_index(const struct req *req)
528c6c22f12Sschwarze {
529c6c22f12Sschwarze 
530fdef72b0Sschwarze 	resp_begin_html(200, NULL, NULL);
53184f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
532735516bdSschwarze 	printf("<p>\n"
533d56ca219Sschwarze 	       "This web interface is documented in the\n"
5340a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
535d56ca219Sschwarze 	       "manual, and the\n"
5360a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
5372a43838fSschwarze 	       "manual explains the query syntax.\n"
538735516bdSschwarze 	       "</p>\n",
5393b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5403b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
541c6c22f12Sschwarze 	resp_end_html();
542c6c22f12Sschwarze }
543c6c22f12Sschwarze 
544c6c22f12Sschwarze static void
545facea411Sschwarze pg_noresult(const struct req *req, const char *msg)
546c6c22f12Sschwarze {
547fdef72b0Sschwarze 	resp_begin_html(200, NULL, NULL);
54884f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
549735516bdSschwarze 	puts("<p>");
550c6c22f12Sschwarze 	puts(msg);
551735516bdSschwarze 	puts("</p>");
552c6c22f12Sschwarze 	resp_end_html();
553c6c22f12Sschwarze }
554c6c22f12Sschwarze 
555c6c22f12Sschwarze static void
556facea411Sschwarze pg_error_badrequest(const char *msg)
557c6c22f12Sschwarze {
558c6c22f12Sschwarze 
559fdef72b0Sschwarze 	resp_begin_html(400, "Bad Request", NULL);
560735516bdSschwarze 	puts("<h1>Bad Request</h1>\n"
561735516bdSschwarze 	     "<p>\n");
562c6c22f12Sschwarze 	puts(msg);
563c6c22f12Sschwarze 	printf("Try again from the\n"
564735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
565735516bdSschwarze 	       "</p>", scriptname);
566c6c22f12Sschwarze 	resp_end_html();
567c6c22f12Sschwarze }
568c6c22f12Sschwarze 
569c6c22f12Sschwarze static void
570facea411Sschwarze pg_error_internal(void)
571c6c22f12Sschwarze {
572fdef72b0Sschwarze 	resp_begin_html(500, "Internal Server Error", NULL);
573735516bdSschwarze 	puts("<p>Internal Server Error</p>");
574c6c22f12Sschwarze 	resp_end_html();
575c6c22f12Sschwarze }
576c6c22f12Sschwarze 
577c6c22f12Sschwarze static void
578e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
579e1beff2aSschwarze {
580e0d9a108Sschwarze 	printf("Status: 303 See Other\r\n"
581e0d9a108Sschwarze 	    "Location: /");
582e1beff2aSschwarze 	if (*scriptname != '\0')
583e1beff2aSschwarze 		printf("%s/", scriptname);
584e1beff2aSschwarze 	if (strcmp(req->q.manpath, req->p[0]))
585e1beff2aSschwarze 		printf("%s/", req->q.manpath);
586e1beff2aSschwarze 	if (req->q.arch != NULL)
587e1beff2aSschwarze 		printf("%s/", req->q.arch);
588f7a12365Sschwarze 	http_encode(name);
589f7a12365Sschwarze 	if (req->q.sec != NULL) {
590f7a12365Sschwarze 		putchar('.');
591f7a12365Sschwarze 		http_encode(req->q.sec);
592f7a12365Sschwarze 	}
593e1beff2aSschwarze 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
594e1beff2aSschwarze }
595e1beff2aSschwarze 
596e1beff2aSschwarze static void
597facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
598c6c22f12Sschwarze {
599be14a32aSschwarze 	char		*arch, *archend;
600db26164eSschwarze 	const char	*sec;
601db26164eSschwarze 	size_t		 i, iuse;
602be14a32aSschwarze 	int		 archprio, archpriouse;
60346723f19Sschwarze 	int		 prio, priouse;
604c6c22f12Sschwarze 
60581475784Sschwarze 	for (i = 0; i < sz; i++) {
60681475784Sschwarze 		if (validate_filename(r[i].file))
60781475784Sschwarze 			continue;
608c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
60981475784Sschwarze 		    r[i].file, req->q.manpath);
61081475784Sschwarze 		pg_error_internal();
61181475784Sschwarze 		return;
61281475784Sschwarze 	}
61381475784Sschwarze 
61452b413c1Sschwarze 	if (req->isquery && sz == 1) {
615c6c22f12Sschwarze 		/*
616c6c22f12Sschwarze 		 * If we have just one result, then jump there now
617c6c22f12Sschwarze 		 * without any delay.
618c6c22f12Sschwarze 		 */
619e0d9a108Sschwarze 		printf("Status: 303 See Other\r\n"
620e0d9a108Sschwarze 		    "Location: /");
621e0d9a108Sschwarze 		if (*scriptname != '\0')
622e0d9a108Sschwarze 			printf("%s/", scriptname);
623e0d9a108Sschwarze 		if (strcmp(req->q.manpath, req->p[0]))
624e0d9a108Sschwarze 			printf("%s/", req->q.manpath);
625e0d9a108Sschwarze 		printf("%s\r\n"
626e0d9a108Sschwarze 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
627e0d9a108Sschwarze 		    r[0].file);
628c6c22f12Sschwarze 		return;
629c6c22f12Sschwarze 	}
630c6c22f12Sschwarze 
63146723f19Sschwarze 	/*
63246723f19Sschwarze 	 * In man(1) mode, show one of the pages
63346723f19Sschwarze 	 * even if more than one is found.
63446723f19Sschwarze 	 */
63546723f19Sschwarze 
63646723f19Sschwarze 	iuse = 0;
637fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
638db26164eSschwarze 		priouse = 20;
639be14a32aSschwarze 		archpriouse = 3;
64046723f19Sschwarze 		for (i = 0; i < sz; i++) {
641db26164eSschwarze 			sec = r[i].file;
642db26164eSschwarze 			sec += strcspn(sec, "123456789");
643db26164eSschwarze 			if (sec[0] == '\0')
64446723f19Sschwarze 				continue;
645db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
646db26164eSschwarze 			if (sec[1] != '/')
647db26164eSschwarze 				prio += 10;
648db26164eSschwarze 			if (req->q.arch == NULL) {
649be14a32aSschwarze 				archprio =
650db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
651db26164eSschwarze 					== NULL) ? 3 :
652db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
653db26164eSschwarze 					== NULL) ? 0 :
654be14a32aSschwarze 				    strncmp(arch, "amd64/",
655be14a32aSschwarze 					archend - arch) ? 2 : 1;
656be14a32aSschwarze 				if (archprio < archpriouse) {
657be14a32aSschwarze 					archpriouse = archprio;
658be14a32aSschwarze 					priouse = prio;
659be14a32aSschwarze 					iuse = i;
660be14a32aSschwarze 					continue;
661be14a32aSschwarze 				}
662be14a32aSschwarze 				if (archprio > archpriouse)
663be14a32aSschwarze 					continue;
664be14a32aSschwarze 			}
66546723f19Sschwarze 			if (prio >= priouse)
66646723f19Sschwarze 				continue;
66746723f19Sschwarze 			priouse = prio;
66846723f19Sschwarze 			iuse = i;
66946723f19Sschwarze 		}
670fdef72b0Sschwarze 		resp_begin_html(200, NULL, r[iuse].file);
671fdef72b0Sschwarze 	} else
672fdef72b0Sschwarze 		resp_begin_html(200, NULL, NULL);
673fdef72b0Sschwarze 
674fdef72b0Sschwarze 	resp_searchform(req,
675fdef72b0Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
676fdef72b0Sschwarze 
677fdef72b0Sschwarze 	if (sz > 1) {
678fdef72b0Sschwarze 		puts("<table class=\"results\">");
679fdef72b0Sschwarze 		for (i = 0; i < sz; i++) {
680fdef72b0Sschwarze 			printf("  <tr>\n"
681fdef72b0Sschwarze 			       "    <td>"
6827b3dbe16Sschwarze 			       "<a class=\"Xr\" href=\"/");
6837b3dbe16Sschwarze 			if (*scriptname != '\0')
6847b3dbe16Sschwarze 				printf("%s/", scriptname);
6857b3dbe16Sschwarze 			if (strcmp(req->q.manpath, req->p[0]))
6867b3dbe16Sschwarze 				printf("%s/", req->q.manpath);
6877b3dbe16Sschwarze 			printf("%s\">", r[i].file);
688fdef72b0Sschwarze 			html_print(r[i].names);
689fdef72b0Sschwarze 			printf("</a></td>\n"
690fdef72b0Sschwarze 			       "    <td><span class=\"Nd\">");
691fdef72b0Sschwarze 			html_print(r[i].output);
692fdef72b0Sschwarze 			puts("</span></td>\n"
693fdef72b0Sschwarze 			     "  </tr>");
694fdef72b0Sschwarze 		}
695fdef72b0Sschwarze 		puts("</table>");
696fdef72b0Sschwarze 	}
697fdef72b0Sschwarze 
698fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
699fdef72b0Sschwarze 		puts("<hr>");
70046723f19Sschwarze 		resp_show(req, r[iuse].file);
70146723f19Sschwarze 	}
70246723f19Sschwarze 
703c6c22f12Sschwarze 	resp_end_html();
704c6c22f12Sschwarze }
705c6c22f12Sschwarze 
706c6c22f12Sschwarze static void
707941df026Sschwarze resp_catman(const struct req *req, const char *file)
708c6c22f12Sschwarze {
709c6c22f12Sschwarze 	FILE		*f;
710c6c22f12Sschwarze 	char		*p;
71131f93c25Sschwarze 	size_t		 sz;
71231f93c25Sschwarze 	ssize_t		 len;
71331f93c25Sschwarze 	int		 i;
714c6c22f12Sschwarze 	int		 italic, bold;
715c6c22f12Sschwarze 
71631f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
717735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
718c6c22f12Sschwarze 		return;
719c6c22f12Sschwarze 	}
720c6c22f12Sschwarze 
721735516bdSschwarze 	puts("<div class=\"catman\">\n"
722735516bdSschwarze 	     "<pre>");
723c6c22f12Sschwarze 
72431f93c25Sschwarze 	p = NULL;
72531f93c25Sschwarze 	sz = 0;
72631f93c25Sschwarze 
72731f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
728c6c22f12Sschwarze 		bold = italic = 0;
72931f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
730c6c22f12Sschwarze 			/*
731c6c22f12Sschwarze 			 * This means that the catpage is out of state.
732c6c22f12Sschwarze 			 * Ignore it and keep going (although the
733c6c22f12Sschwarze 			 * catpage is bogus).
734c6c22f12Sschwarze 			 */
735c6c22f12Sschwarze 
736c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
737c6c22f12Sschwarze 				continue;
738c6c22f12Sschwarze 
739c6c22f12Sschwarze 			/*
740c6c22f12Sschwarze 			 * Print a regular character.
741c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
742c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
743c6c22f12Sschwarze 			 * have something to enter when we backspace.
744c6c22f12Sschwarze 			 */
745c6c22f12Sschwarze 
746c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
747c6c22f12Sschwarze 				if (italic)
748735516bdSschwarze 					printf("</i>");
749c6c22f12Sschwarze 				if (bold)
750735516bdSschwarze 					printf("</b>");
751c6c22f12Sschwarze 				italic = bold = 0;
752c6c22f12Sschwarze 				html_putchar(p[i]);
753c6c22f12Sschwarze 				continue;
75431f93c25Sschwarze 			} else if (i + 2 >= len)
755c6c22f12Sschwarze 				continue;
756c6c22f12Sschwarze 
757c6c22f12Sschwarze 			/* Italic mode. */
758c6c22f12Sschwarze 
759c6c22f12Sschwarze 			if ('_' == p[i]) {
760c6c22f12Sschwarze 				if (bold)
761735516bdSschwarze 					printf("</b>");
762c6c22f12Sschwarze 				if ( ! italic)
763735516bdSschwarze 					printf("<i>");
764c6c22f12Sschwarze 				bold = 0;
765c6c22f12Sschwarze 				italic = 1;
766c6c22f12Sschwarze 				i += 2;
767c6c22f12Sschwarze 				html_putchar(p[i]);
768c6c22f12Sschwarze 				continue;
769c6c22f12Sschwarze 			}
770c6c22f12Sschwarze 
771c6c22f12Sschwarze 			/*
772c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
773c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
774c6c22f12Sschwarze 			 */
775c6c22f12Sschwarze 
776c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
777c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
778c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
779c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
780c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
781c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
782c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
783c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
784c6c22f12Sschwarze 				if (italic)
785735516bdSschwarze 					printf("</i>");
786c6c22f12Sschwarze 				if (bold)
787735516bdSschwarze 					printf("</b>");
788c6c22f12Sschwarze 				italic = bold = 0;
789c6c22f12Sschwarze 				putchar('*');
790c6c22f12Sschwarze 				i += 2;
791c6c22f12Sschwarze 				continue;
792c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
793c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
794c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
795c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
796c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
797c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
798c6c22f12Sschwarze 				if (italic)
799735516bdSschwarze 					printf("</i>");
800c6c22f12Sschwarze 				if (bold)
801735516bdSschwarze 					printf("</b>");
802c6c22f12Sschwarze 				italic = bold = 0;
803c6c22f12Sschwarze 				putchar('+');
804c6c22f12Sschwarze 				i += 2;
805c6c22f12Sschwarze 				continue;
806c6c22f12Sschwarze 			}
807c6c22f12Sschwarze 
808c6c22f12Sschwarze 			/* Bold mode. */
809c6c22f12Sschwarze 
810c6c22f12Sschwarze 			if (italic)
811735516bdSschwarze 				printf("</i>");
812c6c22f12Sschwarze 			if ( ! bold)
813735516bdSschwarze 				printf("<b>");
814c6c22f12Sschwarze 			bold = 1;
815c6c22f12Sschwarze 			italic = 0;
816c6c22f12Sschwarze 			i += 2;
817c6c22f12Sschwarze 			html_putchar(p[i]);
818c6c22f12Sschwarze 		}
819c6c22f12Sschwarze 
820c6c22f12Sschwarze 		/*
821c6c22f12Sschwarze 		 * Clean up the last character.
822c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
823c6c22f12Sschwarze 		 */
824c6c22f12Sschwarze 
825c6c22f12Sschwarze 		if (italic)
826735516bdSschwarze 			printf("</i>");
827c6c22f12Sschwarze 		if (bold)
828735516bdSschwarze 			printf("</b>");
829c6c22f12Sschwarze 
83031f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
831c6c22f12Sschwarze 			html_putchar(p[i]);
832c6c22f12Sschwarze 
833c6c22f12Sschwarze 		putchar('\n');
834c6c22f12Sschwarze 	}
83531f93c25Sschwarze 	free(p);
836c6c22f12Sschwarze 
837735516bdSschwarze 	puts("</pre>\n"
838735516bdSschwarze 	     "</div>");
839c6c22f12Sschwarze 
840c6c22f12Sschwarze 	fclose(f);
841c6c22f12Sschwarze }
842c6c22f12Sschwarze 
843c6c22f12Sschwarze static void
844941df026Sschwarze resp_format(const struct req *req, const char *file)
845c6c22f12Sschwarze {
8462ccd0917Sschwarze 	struct manoutput conf;
847c6c22f12Sschwarze 	struct mparse	*mp;
8486b86842eSschwarze 	struct roff_meta *meta;
849c6c22f12Sschwarze 	void		*vp;
850f74d674aSschwarze 	int		 fd;
851f74d674aSschwarze 	int		 usepath;
852c6c22f12Sschwarze 
853c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
854735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
855c6c22f12Sschwarze 		return;
856c6c22f12Sschwarze 	}
857c6c22f12Sschwarze 
85816536faaSschwarze 	mchars_alloc();
8596b86842eSschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
8606b86842eSschwarze 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
861df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
862c6c22f12Sschwarze 	close(fd);
8636b86842eSschwarze 	meta = mparse_result(mp);
864c6c22f12Sschwarze 
8652ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
8662ccd0917Sschwarze 	conf.fragment = 1;
8677c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
86832f0ba5fSschwarze 	conf.toc = 1;
869f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
870ac2abdb8Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
871ac2abdb8Sschwarze 	    scriptname, *scriptname == '\0' ? "" : "/",
872f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
873c6c22f12Sschwarze 
87416536faaSschwarze 	vp = html_alloc(&conf);
8756b86842eSschwarze 	if (meta->macroset == MACROSET_MDOC)
8766b86842eSschwarze 		html_mdoc(vp, meta);
8776b86842eSschwarze 	else
8786b86842eSschwarze 		html_man(vp, meta);
879c6c22f12Sschwarze 
880c6c22f12Sschwarze 	html_free(vp);
881c6c22f12Sschwarze 	mparse_free(mp);
88216536faaSschwarze 	mchars_free();
8832ccd0917Sschwarze 	free(conf.man);
8847c6e1b3aSschwarze 	free(conf.style);
885c6c22f12Sschwarze }
886c6c22f12Sschwarze 
887c6c22f12Sschwarze static void
88846723f19Sschwarze resp_show(const struct req *req, const char *file)
88946723f19Sschwarze {
89081475784Sschwarze 
89181475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
8922f7bef27Sschwarze 		file += 2;
89346723f19Sschwarze 
89446723f19Sschwarze 	if ('c' == *file)
895941df026Sschwarze 		resp_catman(req, file);
89646723f19Sschwarze 	else
897941df026Sschwarze 		resp_format(req, file);
89846723f19Sschwarze }
89946723f19Sschwarze 
90046723f19Sschwarze static void
901b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
902c6c22f12Sschwarze {
903b53c14c3Sschwarze 	char		*manpath;
904b53c14c3Sschwarze 	const char	*file;
905c6c22f12Sschwarze 
906b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
907facea411Sschwarze 		pg_error_badrequest(
908c6c22f12Sschwarze 		    "You did not specify a page to show.");
909c6c22f12Sschwarze 		return;
910c6c22f12Sschwarze 	}
911b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
912b53c14c3Sschwarze 	file++;
913c6c22f12Sschwarze 
914b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
915631ce2c6Sschwarze 		pg_error_badrequest(
916631ce2c6Sschwarze 		    "You specified an invalid manpath.");
917b53c14c3Sschwarze 		free(manpath);
918631ce2c6Sschwarze 		return;
919631ce2c6Sschwarze 	}
920631ce2c6Sschwarze 
921c6c22f12Sschwarze 	/*
922c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
923c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
924c6c22f12Sschwarze 	 * relative to the manpath root.
925c6c22f12Sschwarze 	 */
926c6c22f12Sschwarze 
927b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
928c976f0e2Sschwarze 		warn("chdir %s", manpath);
929631ce2c6Sschwarze 		pg_error_internal();
930b53c14c3Sschwarze 		free(manpath);
931c6c22f12Sschwarze 		return;
932c6c22f12Sschwarze 	}
933b53c14c3Sschwarze 	free(manpath);
934b53c14c3Sschwarze 
935b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
93681475784Sschwarze 		pg_error_badrequest(
93781475784Sschwarze 		    "You specified an invalid manual file.");
93881475784Sschwarze 		return;
93981475784Sschwarze 	}
94081475784Sschwarze 
941fdef72b0Sschwarze 	resp_begin_html(200, NULL, file);
94284f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
943b53c14c3Sschwarze 	resp_show(req, file);
94446723f19Sschwarze 	resp_end_html();
945c6c22f12Sschwarze }
946c6c22f12Sschwarze 
947c6c22f12Sschwarze static void
94857482ef4Sschwarze pg_search(const struct req *req)
949c6c22f12Sschwarze {
950c6c22f12Sschwarze 	struct mansearch	  search;
951c6c22f12Sschwarze 	struct manpaths		  paths;
952c6c22f12Sschwarze 	struct manpage		 *res;
953fbeeb774Sschwarze 	char			**argv;
954fbeeb774Sschwarze 	char			 *query, *rp, *wp;
955c6c22f12Sschwarze 	size_t			  ressz;
956fbeeb774Sschwarze 	int			  argc;
957c6c22f12Sschwarze 
958c6c22f12Sschwarze 	/*
959c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
960c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
961c6c22f12Sschwarze 	 * relative to the manpath root.
962c6c22f12Sschwarze 	 */
963c6c22f12Sschwarze 
964c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
965c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
966631ce2c6Sschwarze 		pg_error_internal();
967c6c22f12Sschwarze 		return;
968c6c22f12Sschwarze 	}
969c6c22f12Sschwarze 
970c6c22f12Sschwarze 	search.arch = req->q.arch;
971c6c22f12Sschwarze 	search.sec = req->q.sec;
9720f10154cSschwarze 	search.outkey = "Nd";
9730f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
974fea71919Sschwarze 	search.firstmatch = 1;
975c6c22f12Sschwarze 
976c6c22f12Sschwarze 	paths.sz = 1;
977c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
978c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
979c6c22f12Sschwarze 
980c6c22f12Sschwarze 	/*
981fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
982c6c22f12Sschwarze 	 */
983c6c22f12Sschwarze 
984fbeeb774Sschwarze 	argc = 0;
985fbeeb774Sschwarze 	argv = NULL;
986fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
987fbeeb774Sschwarze 	for (;;) {
988fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
989fbeeb774Sschwarze 			rp++;
990fbeeb774Sschwarze 		if (*rp == '\0')
991fbeeb774Sschwarze 			break;
992fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
993fbeeb774Sschwarze 		argv[argc++] = wp = rp;
994fbeeb774Sschwarze 		for (;;) {
995fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
996fbeeb774Sschwarze 				*wp = '\0';
997fbeeb774Sschwarze 				rp++;
998fbeeb774Sschwarze 				break;
999fbeeb774Sschwarze 			}
1000fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
1001fbeeb774Sschwarze 				rp++;
1002fbeeb774Sschwarze 			if (wp != rp)
1003fbeeb774Sschwarze 				*wp = *rp;
1004fbeeb774Sschwarze 			if (*rp == '\0')
1005fbeeb774Sschwarze 				break;
1006fbeeb774Sschwarze 			wp++;
1007fbeeb774Sschwarze 			rp++;
1008fbeeb774Sschwarze 		}
1009c6c22f12Sschwarze 	}
1010c6c22f12Sschwarze 
1011e1beff2aSschwarze 	res = NULL;
1012e1beff2aSschwarze 	ressz = 0;
1013e1beff2aSschwarze 	if (req->isquery && req->q.equal && argc == 1)
1014e1beff2aSschwarze 		pg_redirect(req, argv[0]);
1015e1beff2aSschwarze 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1016facea411Sschwarze 		pg_noresult(req, "You entered an invalid query.");
1017e1beff2aSschwarze 	else if (ressz == 0)
1018facea411Sschwarze 		pg_noresult(req, "No results found.");
1019c6c22f12Sschwarze 	else
1020facea411Sschwarze 		pg_searchres(req, res, ressz);
1021c6c22f12Sschwarze 
1022fbeeb774Sschwarze 	free(query);
1023fbeeb774Sschwarze 	mansearch_free(res, ressz);
1024c6c22f12Sschwarze 	free(paths.paths[0]);
1025c6c22f12Sschwarze 	free(paths.paths);
1026c6c22f12Sschwarze }
1027c6c22f12Sschwarze 
1028c6c22f12Sschwarze int
1029c6c22f12Sschwarze main(void)
1030c6c22f12Sschwarze {
1031c6c22f12Sschwarze 	struct req	 req;
1032136c26b8Sschwarze 	struct itimerval itimer;
103357482ef4Sschwarze 	const char	*path;
103431e689c3Sschwarze 	const char	*querystring;
103557482ef4Sschwarze 	int		 i;
1036c6c22f12Sschwarze 
1037f80eb964Sschwarze 	/*
1038f80eb964Sschwarze 	 * The "rpath" pledge could be revoked after mparse_readfd()
1039f80eb964Sschwarze 	 * if the file desciptor to "/footer.html" would be opened
1040f80eb964Sschwarze 	 * up front, but it's probably not worth the complication
1041f80eb964Sschwarze 	 * of the code it would cause: it would require scattering
1042f80eb964Sschwarze 	 * pledge() calls in multiple low-level resp_*() functions.
1043f80eb964Sschwarze 	 */
1044f80eb964Sschwarze 
1045f80eb964Sschwarze 	if (pledge("stdio rpath", NULL) == -1) {
1046f80eb964Sschwarze 		warn("pledge");
1047f80eb964Sschwarze 		pg_error_internal();
1048f80eb964Sschwarze 		return EXIT_FAILURE;
1049f80eb964Sschwarze 	}
1050f80eb964Sschwarze 
1051136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1052136c26b8Sschwarze 
10532935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1054136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
10552935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1056136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1057136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1058c976f0e2Sschwarze 		warn("setitimer");
1059136c26b8Sschwarze 		pg_error_internal();
1060526e306bSschwarze 		return EXIT_FAILURE;
1061136c26b8Sschwarze 	}
1062136c26b8Sschwarze 
1063c6c22f12Sschwarze 	/*
10646fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1065c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1066c6c22f12Sschwarze 	 * relative to the same position.
1067c6c22f12Sschwarze 	 */
1068c6c22f12Sschwarze 
1069c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1070c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1071facea411Sschwarze 		pg_error_internal();
1072526e306bSschwarze 		return EXIT_FAILURE;
1073c6c22f12Sschwarze 	}
1074c6c22f12Sschwarze 
1075c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1076abf19dc9Sschwarze 	req.q.equal = 1;
1077941df026Sschwarze 	parse_manpath_conf(&req);
1078c6c22f12Sschwarze 
107902b1b494Sschwarze 	/* Parse the path info and the query string. */
1080c6c22f12Sschwarze 
108102b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
108202b1b494Sschwarze 		path = "";
108302b1b494Sschwarze 	else if (*path == '/')
108402b1b494Sschwarze 		path++;
108502b1b494Sschwarze 
1086aa16a3a8Sschwarze 	if (*path != '\0') {
1087941df026Sschwarze 		parse_path_info(&req, path);
1088a9941855Sschwarze 		if (req.q.manpath == NULL || req.q.sec == NULL ||
1089a9941855Sschwarze 		    *req.q.query == '\0' || access(path, F_OK) == -1)
109002b1b494Sschwarze 			path = "";
109102b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1092941df026Sschwarze 		parse_query_string(&req, querystring);
1093c6c22f12Sschwarze 
109402b1b494Sschwarze 	/* Validate parsed data and add defaults. */
109502b1b494Sschwarze 
109650eaed2bSschwarze 	if (req.q.manpath == NULL)
109750eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
109850eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1099631ce2c6Sschwarze 		pg_error_badrequest(
1100631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1101526e306bSschwarze 		return EXIT_FAILURE;
1102631ce2c6Sschwarze 	}
1103631ce2c6Sschwarze 
1104f7a12365Sschwarze 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1105cf3a545cSschwarze 		pg_error_badrequest(
1106cf3a545cSschwarze 		    "You specified an invalid architecture.");
1107526e306bSschwarze 		return EXIT_FAILURE;
1108cf3a545cSschwarze 	}
1109cf3a545cSschwarze 
111057482ef4Sschwarze 	/* Dispatch to the three different pages. */
1111c6c22f12Sschwarze 
111257482ef4Sschwarze 	if ('\0' != *path)
111357482ef4Sschwarze 		pg_show(&req, path);
1114e89321abSschwarze 	else if (NULL != req.q.query)
111557482ef4Sschwarze 		pg_search(&req);
111657482ef4Sschwarze 	else
1117facea411Sschwarze 		pg_index(&req);
1118c6c22f12Sschwarze 
111931e689c3Sschwarze 	free(req.q.manpath);
112031e689c3Sschwarze 	free(req.q.arch);
112131e689c3Sschwarze 	free(req.q.sec);
1122e89321abSschwarze 	free(req.q.query);
1123c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1124c6c22f12Sschwarze 		free(req.p[i]);
1125c6c22f12Sschwarze 	free(req.p);
1126526e306bSschwarze 	return EXIT_SUCCESS;
1127c6c22f12Sschwarze }
1128c6c22f12Sschwarze 
1129c6c22f12Sschwarze /*
1130f5938fa6Sschwarze  * Translate PATH_INFO to a query.
113102b1b494Sschwarze  */
113202b1b494Sschwarze static void
1133941df026Sschwarze parse_path_info(struct req *req, const char *path)
113402b1b494Sschwarze {
1135f5938fa6Sschwarze 	const char	*name, *sec, *end;
113602b1b494Sschwarze 
113752b413c1Sschwarze 	req->isquery = 0;
113802b1b494Sschwarze 	req->q.equal = 1;
1139f5938fa6Sschwarze 	req->q.manpath = NULL;
1140daf4c292Sschwarze 	req->q.arch = NULL;
114102b1b494Sschwarze 
114202b1b494Sschwarze 	/* Mandatory manual page name. */
1143f5938fa6Sschwarze 	if ((name = strrchr(path, '/')) == NULL)
1144f5938fa6Sschwarze 		name = path;
1145f5938fa6Sschwarze 	else
1146f5938fa6Sschwarze 		name++;
114702b1b494Sschwarze 
114802b1b494Sschwarze 	/* Optional trailing section. */
1149f5938fa6Sschwarze 	sec = strrchr(name, '.');
1150f5938fa6Sschwarze 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
1151f5938fa6Sschwarze 		req->q.query = mandoc_strndup(name, sec - name - 1);
1152f5938fa6Sschwarze 		req->q.sec = mandoc_strdup(sec);
1153f5938fa6Sschwarze 	} else {
1154f5938fa6Sschwarze 		req->q.query = mandoc_strdup(name);
115502b1b494Sschwarze 		req->q.sec = NULL;
115602b1b494Sschwarze 	}
115702b1b494Sschwarze 
115802b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1159f5938fa6Sschwarze 	if (name == path)
116002b1b494Sschwarze 		return;
116102b1b494Sschwarze 
1162f5938fa6Sschwarze 	/* Optional manpath. */
1163f5938fa6Sschwarze 	end = strchr(path, '/');
1164f5938fa6Sschwarze 	req->q.manpath = mandoc_strndup(path, end - path);
1165f5938fa6Sschwarze 	if (validate_manpath(req, req->q.manpath)) {
1166f5938fa6Sschwarze 		path = end + 1;
1167f5938fa6Sschwarze 		if (name == path)
1168f5938fa6Sschwarze 			return;
1169f5938fa6Sschwarze 	} else {
1170f5938fa6Sschwarze 		free(req->q.manpath);
1171f5938fa6Sschwarze 		req->q.manpath = NULL;
1172f5938fa6Sschwarze 	}
1173f5938fa6Sschwarze 
1174f5938fa6Sschwarze 	/* Optional section. */
1175*955967fcSschwarze 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
1176f5938fa6Sschwarze 		path += 3;
1177f5938fa6Sschwarze 		end = strchr(path, '/');
1178f5938fa6Sschwarze 		free(req->q.sec);
1179f5938fa6Sschwarze 		req->q.sec = mandoc_strndup(path, end - path);
1180f5938fa6Sschwarze 		path = end + 1;
1181f5938fa6Sschwarze 		if (name == path)
1182f5938fa6Sschwarze 			return;
1183f5938fa6Sschwarze 	}
1184f5938fa6Sschwarze 
1185f5938fa6Sschwarze 	/* Optional architecture. */
1186f5938fa6Sschwarze 	end = strchr(path, '/');
1187f5938fa6Sschwarze 	if (end + 1 != name) {
1188daf4c292Sschwarze 		pg_error_badrequest(
1189daf4c292Sschwarze 		    "You specified too many directory components.");
1190daf4c292Sschwarze 		exit(EXIT_FAILURE);
119102b1b494Sschwarze 	}
1192f5938fa6Sschwarze 	req->q.arch = mandoc_strndup(path, end - path);
1193f5938fa6Sschwarze 	if (validate_arch(req->q.arch) == 0) {
1194daf4c292Sschwarze 		pg_error_badrequest(
1195daf4c292Sschwarze 		    "You specified an invalid directory component.");
1196daf4c292Sschwarze 		exit(EXIT_FAILURE);
1197daf4c292Sschwarze 	}
119802b1b494Sschwarze }
119902b1b494Sschwarze 
120002b1b494Sschwarze /*
1201c6c22f12Sschwarze  * Scan for indexable paths.
1202c6c22f12Sschwarze  */
1203c6c22f12Sschwarze static void
1204941df026Sschwarze parse_manpath_conf(struct req *req)
1205c6c22f12Sschwarze {
1206c6c22f12Sschwarze 	FILE	*fp;
1207c6c22f12Sschwarze 	char	*dp;
1208c6c22f12Sschwarze 	size_t	 dpsz;
120931f93c25Sschwarze 	ssize_t	 len;
1210c6c22f12Sschwarze 
1211c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1212c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1213de651747Sschwarze 		pg_error_internal();
1214de651747Sschwarze 		exit(EXIT_FAILURE);
1215de651747Sschwarze 	}
1216c6c22f12Sschwarze 
121731f93c25Sschwarze 	dp = NULL;
121831f93c25Sschwarze 	dpsz = 0;
121931f93c25Sschwarze 
122031f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
122131f93c25Sschwarze 		if (dp[len - 1] == '\n')
122231f93c25Sschwarze 			dp[--len] = '\0';
1223c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1224c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1225cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1226c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1227c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1228cf3a545cSschwarze 			pg_error_internal();
1229cf3a545cSschwarze 			exit(EXIT_FAILURE);
1230cf3a545cSschwarze 		}
1231c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1232c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1233c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1234cf3a545cSschwarze 			pg_error_internal();
1235cf3a545cSschwarze 			exit(EXIT_FAILURE);
1236cf3a545cSschwarze 		}
1237cf3a545cSschwarze 		req->p[req->psz++] = dp;
123831f93c25Sschwarze 		dp = NULL;
123931f93c25Sschwarze 		dpsz = 0;
1240c6c22f12Sschwarze 	}
124131f93c25Sschwarze 	free(dp);
1242de651747Sschwarze 
1243de651747Sschwarze 	if (req->p == NULL) {
1244c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1245de651747Sschwarze 		pg_error_internal();
1246de651747Sschwarze 		exit(EXIT_FAILURE);
1247de651747Sschwarze 	}
1248c6c22f12Sschwarze }
1249