xref: /openbsd/usr.bin/mandoc/cgi.c (revision f0f927fc)
1*f0f927fcSschwarze /* $OpenBSD: cgi.c,v 1.118 2022/07/06 16:02:52 schwarze Exp $ */
2c6c22f12Sschwarze /*
3a43df5a0Sschwarze  * Copyright (c) 2014-2019, 2021, 2022 Ingo Schwarze <schwarze@usta.de>
46a6803e4Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5b2d1ada6Sschwarze  * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
6c6c22f12Sschwarze  *
7c6c22f12Sschwarze  * Permission to use, copy, modify, and distribute this software for any
8c6c22f12Sschwarze  * purpose with or without fee is hereby granted, provided that the above
9c6c22f12Sschwarze  * copyright notice and this permission notice appear in all copies.
10c6c22f12Sschwarze  *
114de77decSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12c6c22f12Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
134de77decSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14c6c22f12Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15c6c22f12Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16c6c22f12Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17c6c22f12Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
186a6803e4Sschwarze  *
196a6803e4Sschwarze  * Implementation of the man.cgi(8) program.
20c6c22f12Sschwarze  */
21136c26b8Sschwarze #include <sys/types.h>
22136c26b8Sschwarze #include <sys/time.h>
23136c26b8Sschwarze 
24c6c22f12Sschwarze #include <ctype.h>
25c976f0e2Sschwarze #include <err.h>
26c6c22f12Sschwarze #include <errno.h>
27c6c22f12Sschwarze #include <fcntl.h>
28c6c22f12Sschwarze #include <limits.h>
29c2235d37Sschwarze #include <stdint.h>
30c6c22f12Sschwarze #include <stdio.h>
31c6c22f12Sschwarze #include <stdlib.h>
32c6c22f12Sschwarze #include <string.h>
33c6c22f12Sschwarze #include <unistd.h>
34c6c22f12Sschwarze 
35c6c22f12Sschwarze #include "mandoc_aux.h"
36f2d5c709Sschwarze #include "mandoc.h"
37f2d5c709Sschwarze #include "roff.h"
38396853b5Sschwarze #include "mdoc.h"
39fec2846bSschwarze #include "man.h"
4099acaf1eSschwarze #include "mandoc_parse.h"
41c6c22f12Sschwarze #include "main.h"
424de77decSschwarze #include "manconf.h"
43c6c22f12Sschwarze #include "mansearch.h"
446fdade3eSschwarze #include "cgi.h"
45c6c22f12Sschwarze 
46c6c22f12Sschwarze /*
47c6c22f12Sschwarze  * A query as passed to the search function.
48c6c22f12Sschwarze  */
49c6c22f12Sschwarze struct	query {
5031e689c3Sschwarze 	char		*manpath; /* desired manual directory */
5131e689c3Sschwarze 	char		*arch; /* architecture */
5231e689c3Sschwarze 	char		*sec; /* manual section */
53e89321abSschwarze 	char		*query; /* unparsed query expression */
544477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
55c6c22f12Sschwarze };
56c6c22f12Sschwarze 
57c6c22f12Sschwarze struct	req {
58c6c22f12Sschwarze 	struct query	  q;
59c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
60c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
6152b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
62c6c22f12Sschwarze };
63c6c22f12Sschwarze 
6484f05c93Sschwarze enum	focus {
6584f05c93Sschwarze 	FOCUS_NONE = 0,
6684f05c93Sschwarze 	FOCUS_QUERY
6784f05c93Sschwarze };
6884f05c93Sschwarze 
69c6c22f12Sschwarze static	void		 html_print(const char *);
70c6c22f12Sschwarze static	void		 html_putchar(char);
71c6c22f12Sschwarze static	int		 http_decode(char *);
726a6803e4Sschwarze static	void		 http_encode(const char *);
73941df026Sschwarze static	void		 parse_manpath_conf(struct req *);
746a6803e4Sschwarze static	void		 parse_path_info(struct req *, const char *);
75941df026Sschwarze static	void		 parse_query_string(struct req *, const char *);
76facea411Sschwarze static	void		 pg_error_badrequest(const char *);
77facea411Sschwarze static	void		 pg_error_internal(void);
78facea411Sschwarze static	void		 pg_index(const struct req *);
7918ccf011Sschwarze static	void		 pg_noresult(const struct req *, int, const char *,
8018ccf011Sschwarze 				const char *);
81e1beff2aSschwarze static	void		 pg_redirect(const struct req *, const char *);
8257482ef4Sschwarze static	void		 pg_search(const struct req *);
83facea411Sschwarze static	void		 pg_searchres(const struct req *,
84facea411Sschwarze 				struct manpage *, size_t);
8581060b1aSschwarze static	void		 pg_show(struct req *, const char *);
86a43df5a0Sschwarze static	int		 resp_begin_html(int, const char *, const char *);
87c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
88941df026Sschwarze static	void		 resp_catman(const struct req *, const char *);
89a43df5a0Sschwarze static	int		 resp_copy(const char *, const char *);
90c6c22f12Sschwarze static	void		 resp_end_html(void);
91941df026Sschwarze static	void		 resp_format(const struct req *, const char *);
9284f05c93Sschwarze static	void		 resp_searchform(const struct req *, enum focus);
9346723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
94e89321abSschwarze static	void		 set_query_attr(char **, char **);
95f7a12365Sschwarze static	int		 validate_arch(const char *);
96e89321abSschwarze static	int		 validate_filename(const char *);
97e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
98e89321abSschwarze static	int		 validate_urifrag(const char *);
99c6c22f12Sschwarze 
1003b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
101c6c22f12Sschwarze 
10246723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
10328e449d6Sschwarze static	const char *const sec_numbers[] = {
10428e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10528e449d6Sschwarze };
10628e449d6Sschwarze static	const char *const sec_names[] = {
10728e449d6Sschwarze     "All Sections",
10828e449d6Sschwarze     "1 - General Commands",
10928e449d6Sschwarze     "2 - System Calls",
1104e6618fdSschwarze     "3 - Library Functions",
1114e6618fdSschwarze     "3p - Perl Library",
1124e6618fdSschwarze     "4 - Device Drivers",
11328e449d6Sschwarze     "5 - File Formats",
11428e449d6Sschwarze     "6 - Games",
1154e6618fdSschwarze     "7 - Miscellaneous Information",
1164e6618fdSschwarze     "8 - System Manager\'s Manual",
1174e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
11828e449d6Sschwarze };
11928e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
12028e449d6Sschwarze 
12128e449d6Sschwarze static	const char *const arch_names[] = {
12293d3e4e1Sderaadt     "amd64",       "alpha",       "armv7",       "arm64",
123ab2c7ff4Sschwarze     "hppa",        "i386",        "landisk",     "loongson",
124ab2c7ff4Sschwarze     "luna88k",     "macppc",      "mips64",      "octeon",
125bfc185c1Svisa     "powerpc64",   "riscv64",     "sparc64",
126ab2c7ff4Sschwarze 
127766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
128766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
129766ef059Sschwarze     "hppa64",      "hp300",
1303bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1313bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
132bfc185c1Svisa     "pmax",        "powerpc",     "sgi",         "socppc",
133bfc185c1Svisa     "solbourne",   "sparc",
1346250a715Sschwarze     "sun3",        "vax",         "wgrisc",      "x68k",
1356250a715Sschwarze     "zaurus"
13628e449d6Sschwarze };
13728e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
13828e449d6Sschwarze 
139c6c22f12Sschwarze /*
140c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
141c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
142c6c22f12Sschwarze  */
143c6c22f12Sschwarze static void
144c6c22f12Sschwarze html_putchar(char c)
145c6c22f12Sschwarze {
146c6c22f12Sschwarze 
147c6c22f12Sschwarze 	switch (c) {
148e74fa2aeSschwarze 	case '"':
149f765a656Sbentley 		printf("&quot;");
150c6c22f12Sschwarze 		break;
151e74fa2aeSschwarze 	case '&':
152c6c22f12Sschwarze 		printf("&amp;");
153c6c22f12Sschwarze 		break;
154e74fa2aeSschwarze 	case '>':
155c6c22f12Sschwarze 		printf("&gt;");
156c6c22f12Sschwarze 		break;
157e74fa2aeSschwarze 	case '<':
158c6c22f12Sschwarze 		printf("&lt;");
159c6c22f12Sschwarze 		break;
160c6c22f12Sschwarze 	default:
161c6c22f12Sschwarze 		putchar((unsigned char)c);
162c6c22f12Sschwarze 		break;
163c6c22f12Sschwarze 	}
164c6c22f12Sschwarze }
165c6c22f12Sschwarze 
166c6c22f12Sschwarze /*
167c6c22f12Sschwarze  * Call through to html_putchar().
168c6c22f12Sschwarze  * Accepts NULL strings.
169c6c22f12Sschwarze  */
170c6c22f12Sschwarze static void
171c6c22f12Sschwarze html_print(const char *p)
172c6c22f12Sschwarze {
173c6c22f12Sschwarze 
174c6c22f12Sschwarze 	if (NULL == p)
175c6c22f12Sschwarze 		return;
176c6c22f12Sschwarze 	while ('\0' != *p)
177c6c22f12Sschwarze 		html_putchar(*p++);
178c6c22f12Sschwarze }
179c6c22f12Sschwarze 
180c6c22f12Sschwarze /*
18131e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
18231e689c3Sschwarze  * to the query structure.
183c6c22f12Sschwarze  */
184c6c22f12Sschwarze static void
18531e689c3Sschwarze set_query_attr(char **attr, char **val)
18631e689c3Sschwarze {
18731e689c3Sschwarze 
18831e689c3Sschwarze 	free(*attr);
18931e689c3Sschwarze 	if (**val == '\0') {
19031e689c3Sschwarze 		*attr = NULL;
19131e689c3Sschwarze 		free(*val);
19231e689c3Sschwarze 	} else
19331e689c3Sschwarze 		*attr = *val;
19431e689c3Sschwarze 	*val = NULL;
19531e689c3Sschwarze }
19631e689c3Sschwarze 
19731e689c3Sschwarze /*
19831e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
19931e689c3Sschwarze  * and store the values into the query structure.
20031e689c3Sschwarze  */
20131e689c3Sschwarze static void
202941df026Sschwarze parse_query_string(struct req *req, const char *qs)
203c6c22f12Sschwarze {
204c6c22f12Sschwarze 	char		*key, *val;
20531e689c3Sschwarze 	size_t		 keysz, valsz;
206c6c22f12Sschwarze 
20752b413c1Sschwarze 	req->isquery	= 1;
20831e689c3Sschwarze 	req->q.manpath	= NULL;
20931e689c3Sschwarze 	req->q.arch	= NULL;
21031e689c3Sschwarze 	req->q.sec	= NULL;
211e89321abSschwarze 	req->q.query	= NULL;
2124477fbfaSschwarze 	req->q.equal	= 1;
213c6c22f12Sschwarze 
21431e689c3Sschwarze 	key = val = NULL;
21531e689c3Sschwarze 	while (*qs != '\0') {
216c6c22f12Sschwarze 
21731e689c3Sschwarze 		/* Parse one key. */
218c6c22f12Sschwarze 
21931e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
22031e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
22131e689c3Sschwarze 		qs += keysz;
22231e689c3Sschwarze 		if (*qs != '=')
22331e689c3Sschwarze 			goto next;
224c6c22f12Sschwarze 
22531e689c3Sschwarze 		/* Parse one value. */
226c6c22f12Sschwarze 
22731e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
22831e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
22931e689c3Sschwarze 		qs += valsz;
230c6c22f12Sschwarze 
23131e689c3Sschwarze 		/* Decode and catch encoding errors. */
23231e689c3Sschwarze 
23331e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
23431e689c3Sschwarze 			goto next;
23531e689c3Sschwarze 
23631e689c3Sschwarze 		/* Handle key-value pairs. */
23731e689c3Sschwarze 
23831e689c3Sschwarze 		if ( ! strcmp(key, "query"))
239e89321abSschwarze 			set_query_attr(&req->q.query, &val);
24031e689c3Sschwarze 
24131e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
24231e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
24331e689c3Sschwarze 
24431e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
245aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24631e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
247aabcc9a1Sschwarze 				val[7] = '-';
248aabcc9a1Sschwarze 				if ('C' == val[8])
249aabcc9a1Sschwarze 					val[8] = 'c';
250aabcc9a1Sschwarze 			}
251aabcc9a1Sschwarze #endif
25231e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
25331e689c3Sschwarze 		}
25431e689c3Sschwarze 
25531e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
256aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
25731e689c3Sschwarze 		    && strcmp(key, "sektion")
258aabcc9a1Sschwarze #endif
25931e689c3Sschwarze 		    )) {
26031e689c3Sschwarze 			if ( ! strcmp(val, "0"))
26131e689c3Sschwarze 				*val = '\0';
26231e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
263c6c22f12Sschwarze 		}
26431e689c3Sschwarze 
26531e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
26631e689c3Sschwarze 			if ( ! strcmp(val, "default"))
26731e689c3Sschwarze 				*val = '\0';
26831e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2694477fbfaSschwarze 		}
27031e689c3Sschwarze 
27131e689c3Sschwarze 		/*
27231e689c3Sschwarze 		 * The key must be freed in any case.
27331e689c3Sschwarze 		 * The val may have been handed over to the query
27431e689c3Sschwarze 		 * structure, in which case it is now NULL.
27531e689c3Sschwarze 		 */
27631e689c3Sschwarze next:
27731e689c3Sschwarze 		free(key);
27831e689c3Sschwarze 		key = NULL;
27931e689c3Sschwarze 		free(val);
28031e689c3Sschwarze 		val = NULL;
28131e689c3Sschwarze 
28231e689c3Sschwarze 		if (*qs != '\0')
28331e689c3Sschwarze 			qs++;
28431e689c3Sschwarze 	}
285c6c22f12Sschwarze }
286c6c22f12Sschwarze 
287c6c22f12Sschwarze /*
288c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
289c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
290c6c22f12Sschwarze  * over the allocated string.
291c6c22f12Sschwarze  */
292c6c22f12Sschwarze static int
293c6c22f12Sschwarze http_decode(char *p)
294c6c22f12Sschwarze {
295c6c22f12Sschwarze 	char             hex[3];
2961f69f32bStedu 	char		*q;
297c6c22f12Sschwarze 	int              c;
298c6c22f12Sschwarze 
299c6c22f12Sschwarze 	hex[2] = '\0';
300c6c22f12Sschwarze 
3011f69f32bStedu 	q = p;
3021f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
303c6c22f12Sschwarze 		if ('%' == *p) {
304c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
305526e306bSschwarze 				return 0;
306c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
307526e306bSschwarze 				return 0;
308c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
309526e306bSschwarze 				return 0;
310c6c22f12Sschwarze 			if ('\0' == c)
311526e306bSschwarze 				return 0;
312c6c22f12Sschwarze 
3131f69f32bStedu 			*q = (char)c;
3141f69f32bStedu 			p += 2;
315c6c22f12Sschwarze 		} else
3161f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
317c6c22f12Sschwarze 	}
318c6c22f12Sschwarze 
3191f69f32bStedu 	*q = '\0';
320526e306bSschwarze 	return 1;
321c6c22f12Sschwarze }
322c6c22f12Sschwarze 
323c6c22f12Sschwarze static void
324f7a12365Sschwarze http_encode(const char *p)
325f7a12365Sschwarze {
326f7a12365Sschwarze 	for (; *p != '\0'; p++) {
327f7a12365Sschwarze 		if (isalnum((unsigned char)*p) == 0 &&
328f7a12365Sschwarze 		    strchr("-._~", *p) == NULL)
3291ecf8c4fSschwarze 			printf("%%%2.2X", (unsigned char)*p);
330f7a12365Sschwarze 		else
331f7a12365Sschwarze 			putchar(*p);
332f7a12365Sschwarze 	}
333f7a12365Sschwarze }
334f7a12365Sschwarze 
335f7a12365Sschwarze static void
336c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
337c6c22f12Sschwarze {
338c6c22f12Sschwarze 
339c6c22f12Sschwarze 	if (200 != code)
340fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
341c6c22f12Sschwarze 
342fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
343fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
34453459fdfSbentley 	     "Content-Security-Policy: default-src 'none'; "
34553459fdfSbentley 	     "style-src 'self' 'unsafe-inline'\r\n"
346fa9c540aStedu 	     "Pragma: no-cache\r\n"
347fa9c540aStedu 	     "\r\n");
348c6c22f12Sschwarze 
349c6c22f12Sschwarze 	fflush(stdout);
350c6c22f12Sschwarze }
351c6c22f12Sschwarze 
352a43df5a0Sschwarze static int
353a43df5a0Sschwarze resp_copy(const char *element, const char *filename)
354711661c7Sschwarze {
355711661c7Sschwarze 	char	 buf[4096];
356711661c7Sschwarze 	ssize_t	 sz;
357711661c7Sschwarze 	int	 fd;
358711661c7Sschwarze 
359a43df5a0Sschwarze 	if ((fd = open(filename, O_RDONLY)) == -1)
360a43df5a0Sschwarze 		return 0;
361a43df5a0Sschwarze 
362a43df5a0Sschwarze 	if (element != NULL)
363a43df5a0Sschwarze 		printf("<%s>\n", element);
364711661c7Sschwarze 	fflush(stdout);
365711661c7Sschwarze 	while ((sz = read(fd, buf, sizeof(buf))) > 0)
366711661c7Sschwarze 		write(STDOUT_FILENO, buf, sz);
367fd3cdd86Sjsg 	close(fd);
368a43df5a0Sschwarze 	return 1;
369711661c7Sschwarze }
370711661c7Sschwarze 
371a43df5a0Sschwarze static int
372fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file)
373c6c22f12Sschwarze {
374e621f1d2Sschwarze 	const char	*name, *sec, *cp;
375e621f1d2Sschwarze 	int		 namesz, secsz;
376c6c22f12Sschwarze 
377c6c22f12Sschwarze 	resp_begin_http(code, msg);
378c6c22f12Sschwarze 
379d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
380735516bdSschwarze 	       "<html>\n"
381735516bdSschwarze 	       "<head>\n"
382735516bdSschwarze 	       "  <meta charset=\"UTF-8\"/>\n"
383661a42d8Sschwarze 	       "  <meta name=\"viewport\""
384661a42d8Sschwarze 		      " content=\"width=device-width, initial-scale=1.0\">\n"
385735516bdSschwarze 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
386735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
387fdef72b0Sschwarze 	       "  <title>",
388fdef72b0Sschwarze 	       CSS_DIR);
389fdef72b0Sschwarze 	if (file != NULL) {
390e621f1d2Sschwarze 		cp = strrchr(file, '/');
391e621f1d2Sschwarze 		name = cp == NULL ? file : cp + 1;
392e621f1d2Sschwarze 		cp = strrchr(name, '.');
393e621f1d2Sschwarze 		namesz = cp == NULL ? strlen(name) : cp - name;
394e621f1d2Sschwarze 		sec = NULL;
395e621f1d2Sschwarze 		if (cp != NULL && cp[1] != '0') {
396e621f1d2Sschwarze 			sec = cp + 1;
397e621f1d2Sschwarze 			secsz = strlen(sec);
398e621f1d2Sschwarze 		} else if (name - file > 1) {
399e621f1d2Sschwarze 			for (cp = name - 2; cp >= file; cp--) {
400e621f1d2Sschwarze 				if (*cp < '1' || *cp > '9')
401e621f1d2Sschwarze 					continue;
402e621f1d2Sschwarze 				sec = cp;
403e621f1d2Sschwarze 				secsz = name - cp - 1;
404e621f1d2Sschwarze 				break;
405e621f1d2Sschwarze 			}
406e621f1d2Sschwarze 		}
407e621f1d2Sschwarze 		printf("%.*s", namesz, name);
408e621f1d2Sschwarze 		if (sec != NULL)
409e621f1d2Sschwarze 			printf("(%.*s)", secsz, sec);
410e621f1d2Sschwarze 		fputs(" - ", stdout);
411fdef72b0Sschwarze 	}
412fdef72b0Sschwarze 	printf("%s</title>\n"
413735516bdSschwarze 	       "</head>\n"
414ce781f36Sschwarze 	       "<body>\n",
415fdef72b0Sschwarze 	       CUSTOMIZE_TITLE);
416711661c7Sschwarze 
417a43df5a0Sschwarze 	return resp_copy("header", MAN_DIR "/header.html");
418c6c22f12Sschwarze }
419c6c22f12Sschwarze 
420c6c22f12Sschwarze static void
421c6c22f12Sschwarze resp_end_html(void)
422c6c22f12Sschwarze {
423a43df5a0Sschwarze 	if (resp_copy("footer", MAN_DIR "/footer.html"))
424a43df5a0Sschwarze 		puts("</footer>");
425711661c7Sschwarze 
426735516bdSschwarze 	puts("</body>\n"
427735516bdSschwarze 	     "</html>");
428c6c22f12Sschwarze }
429c6c22f12Sschwarze 
430c6c22f12Sschwarze static void
43184f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
432c6c22f12Sschwarze {
433c6c22f12Sschwarze 	int		 i;
434c6c22f12Sschwarze 
435a43df5a0Sschwarze 	printf("<form role=\"search\" action=\"/%s\" method=\"get\" "
4367411869fSschwarze 	       "autocomplete=\"off\" autocapitalize=\"none\">\n"
437735516bdSschwarze 	       "  <fieldset>\n"
438735516bdSschwarze 	       "    <legend>Manual Page Search Parameters</legend>\n",
439c6c22f12Sschwarze 	       scriptname);
44028e449d6Sschwarze 
44128e449d6Sschwarze 	/* Write query input box. */
44228e449d6Sschwarze 
4436b824e3bSschwarze 	printf("    <input type=\"search\" name=\"query\" value=\"");
44484f05c93Sschwarze 	if (req->q.query != NULL)
445e89321abSschwarze 		html_print(req->q.query);
44684f05c93Sschwarze 	printf( "\" size=\"40\"");
44784f05c93Sschwarze 	if (focus == FOCUS_QUERY)
44884f05c93Sschwarze 		printf(" autofocus");
44984f05c93Sschwarze 	puts(">");
45028e449d6Sschwarze 
451784a63d6Sschwarze 	/* Write submission buttons. */
45228e449d6Sschwarze 
453784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
454784a63d6Sschwarze 		"man</button>\n"
455784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
456542ee4bfSschwarze 		"apropos</button>\n"
457542ee4bfSschwarze 		"    <br/>\n");
45828e449d6Sschwarze 
45928e449d6Sschwarze 	/* Write section selector. */
46028e449d6Sschwarze 
461*f0f927fcSschwarze 	puts("    <select name=\"sec\" aria-label=\"Manual section\">");
46228e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
463735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
46428e449d6Sschwarze 		if (NULL != req->q.sec &&
46528e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
466735516bdSschwarze 			printf(" selected=\"selected\"");
467735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
46828e449d6Sschwarze 	}
469735516bdSschwarze 	puts("    </select>");
47028e449d6Sschwarze 
47128e449d6Sschwarze 	/* Write architecture selector. */
47228e449d6Sschwarze 
473b2d1ada6Sschwarze 	printf(	"    <select name=\"arch\" aria-label=\"CPU architecture\">\n"
474735516bdSschwarze 		"      <option value=\"default\"");
475be14a32aSschwarze 	if (NULL == req->q.arch)
476735516bdSschwarze 		printf(" selected=\"selected\"");
477735516bdSschwarze 	puts(">All Architectures</option>");
47828e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
4796b824e3bSschwarze 		printf("      <option");
48028e449d6Sschwarze 		if (NULL != req->q.arch &&
48128e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
482735516bdSschwarze 			printf(" selected=\"selected\"");
483735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
48428e449d6Sschwarze 	}
485735516bdSschwarze 	puts("    </select>");
48628e449d6Sschwarze 
48728e449d6Sschwarze 	/* Write manpath selector. */
48828e449d6Sschwarze 
489c6c22f12Sschwarze 	if (req->psz > 1) {
490735516bdSschwarze 		puts("    <select name=\"manpath\">");
491c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
492735516bdSschwarze 			printf("      <option");
49350eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
494735516bdSschwarze 				printf(" selected=\"selected\"");
4956b824e3bSschwarze 			printf(">");
496c6c22f12Sschwarze 			html_print(req->p[i]);
497735516bdSschwarze 			puts("</option>");
498c6c22f12Sschwarze 		}
499735516bdSschwarze 		puts("    </select>");
500c6c22f12Sschwarze 	}
50128e449d6Sschwarze 
502784a63d6Sschwarze 	puts("  </fieldset>\n"
503a43df5a0Sschwarze 	     "</form>");
504c6c22f12Sschwarze }
505c6c22f12Sschwarze 
50681475784Sschwarze static int
507cf3a545cSschwarze validate_urifrag(const char *frag)
508cf3a545cSschwarze {
509cf3a545cSschwarze 
510cf3a545cSschwarze 	while ('\0' != *frag) {
511cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
512cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
513cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
514526e306bSschwarze 			return 0;
515cf3a545cSschwarze 		frag++;
516cf3a545cSschwarze 	}
517526e306bSschwarze 	return 1;
518cf3a545cSschwarze }
519cf3a545cSschwarze 
520cf3a545cSschwarze static int
521631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
522631ce2c6Sschwarze {
523631ce2c6Sschwarze 	size_t	 i;
524631ce2c6Sschwarze 
525631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
526631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
527526e306bSschwarze 			return 1;
528631ce2c6Sschwarze 
529526e306bSschwarze 	return 0;
530631ce2c6Sschwarze }
531631ce2c6Sschwarze 
532631ce2c6Sschwarze static int
533f7a12365Sschwarze validate_arch(const char *arch)
534f7a12365Sschwarze {
535f7a12365Sschwarze 	int	 i;
536f7a12365Sschwarze 
537f7a12365Sschwarze 	for (i = 0; i < arch_MAX; i++)
538f7a12365Sschwarze 		if (strcmp(arch, arch_names[i]) == 0)
539f7a12365Sschwarze 			return 1;
540f7a12365Sschwarze 
541f7a12365Sschwarze 	return 0;
542f7a12365Sschwarze }
543f7a12365Sschwarze 
544f7a12365Sschwarze static int
54581475784Sschwarze validate_filename(const char *file)
54681475784Sschwarze {
54781475784Sschwarze 
54881475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
54981475784Sschwarze 		file += 2;
55081475784Sschwarze 
551526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
552526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
55381475784Sschwarze }
55481475784Sschwarze 
555c6c22f12Sschwarze static void
556facea411Sschwarze pg_index(const struct req *req)
557c6c22f12Sschwarze {
558a43df5a0Sschwarze 	if (resp_begin_html(200, NULL, NULL) == 0)
559a43df5a0Sschwarze 		puts("<header>");
56084f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
561a43df5a0Sschwarze 	printf("</header>\n"
562a43df5a0Sschwarze 	       "<main>\n"
563*f0f927fcSschwarze 	       "<p role=\"doc-notice\" aria-label=\"Usage\">\n"
564d56ca219Sschwarze 	       "This web interface is documented in the\n"
565b2d1ada6Sschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
566b2d1ada6Sschwarze 	       " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
567d56ca219Sschwarze 	       "manual, and the\n"
568b2d1ada6Sschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\""
569b2d1ada6Sschwarze 	       " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
5702a43838fSschwarze 	       "manual explains the query syntax.\n"
571b2d1ada6Sschwarze 	       "</p>\n"
572b2d1ada6Sschwarze 	       "</main>\n",
5733b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5743b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
575c6c22f12Sschwarze 	resp_end_html();
576c6c22f12Sschwarze }
577c6c22f12Sschwarze 
578c6c22f12Sschwarze static void
57918ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg,
58018ccf011Sschwarze     const char *user_msg)
581c6c22f12Sschwarze {
582a43df5a0Sschwarze 	if (resp_begin_html(code, http_msg, NULL) == 0)
583a43df5a0Sschwarze 		puts("<header>");
58484f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
585a43df5a0Sschwarze 	puts("</header>");
586b2d1ada6Sschwarze 	puts("<main>");
587*f0f927fcSschwarze 	puts("<p role=\"doc-notice\" aria-label=\"No result\">");
58818ccf011Sschwarze 	puts(user_msg);
589735516bdSschwarze 	puts("</p>");
590b2d1ada6Sschwarze 	puts("</main>");
591c6c22f12Sschwarze 	resp_end_html();
592c6c22f12Sschwarze }
593c6c22f12Sschwarze 
594c6c22f12Sschwarze static void
595facea411Sschwarze pg_error_badrequest(const char *msg)
596c6c22f12Sschwarze {
597a43df5a0Sschwarze 	if (resp_begin_html(400, "Bad Request", NULL))
598a43df5a0Sschwarze 		puts("</header>");
599b2d1ada6Sschwarze 	puts("<main>\n"
600b2d1ada6Sschwarze 	     "<h1>Bad Request</h1>\n"
601b2d1ada6Sschwarze 	     "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
602c6c22f12Sschwarze 	puts(msg);
603c6c22f12Sschwarze 	printf("Try again from the\n"
604735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
605b2d1ada6Sschwarze 	       "</p>\n"
606a43df5a0Sschwarze 	       "</main>\n", scriptname);
607c6c22f12Sschwarze 	resp_end_html();
608c6c22f12Sschwarze }
609c6c22f12Sschwarze 
610c6c22f12Sschwarze static void
611facea411Sschwarze pg_error_internal(void)
612c6c22f12Sschwarze {
613a43df5a0Sschwarze 	if (resp_begin_html(500, "Internal Server Error", NULL))
614a43df5a0Sschwarze 		puts("</header>");
615b2d1ada6Sschwarze 	puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
616c6c22f12Sschwarze 	resp_end_html();
617c6c22f12Sschwarze }
618c6c22f12Sschwarze 
619c6c22f12Sschwarze static void
620e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
621e1beff2aSschwarze {
622e0d9a108Sschwarze 	printf("Status: 303 See Other\r\n"
623e0d9a108Sschwarze 	    "Location: /");
624e1beff2aSschwarze 	if (*scriptname != '\0')
625e1beff2aSschwarze 		printf("%s/", scriptname);
626e1beff2aSschwarze 	if (strcmp(req->q.manpath, req->p[0]))
627e1beff2aSschwarze 		printf("%s/", req->q.manpath);
628e1beff2aSschwarze 	if (req->q.arch != NULL)
629e1beff2aSschwarze 		printf("%s/", req->q.arch);
630f7a12365Sschwarze 	http_encode(name);
631f7a12365Sschwarze 	if (req->q.sec != NULL) {
632f7a12365Sschwarze 		putchar('.');
633f7a12365Sschwarze 		http_encode(req->q.sec);
634f7a12365Sschwarze 	}
635e1beff2aSschwarze 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
636e1beff2aSschwarze }
637e1beff2aSschwarze 
638e1beff2aSschwarze static void
639facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
640c6c22f12Sschwarze {
641be14a32aSschwarze 	char		*arch, *archend;
642db26164eSschwarze 	const char	*sec;
643db26164eSschwarze 	size_t		 i, iuse;
644be14a32aSschwarze 	int		 archprio, archpriouse;
64546723f19Sschwarze 	int		 prio, priouse;
646a43df5a0Sschwarze 	int		 have_header;
647c6c22f12Sschwarze 
64881475784Sschwarze 	for (i = 0; i < sz; i++) {
64981475784Sschwarze 		if (validate_filename(r[i].file))
65081475784Sschwarze 			continue;
651c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
65281475784Sschwarze 		    r[i].file, req->q.manpath);
65381475784Sschwarze 		pg_error_internal();
65481475784Sschwarze 		return;
65581475784Sschwarze 	}
65681475784Sschwarze 
65752b413c1Sschwarze 	if (req->isquery && sz == 1) {
658c6c22f12Sschwarze 		/*
659c6c22f12Sschwarze 		 * If we have just one result, then jump there now
660c6c22f12Sschwarze 		 * without any delay.
661c6c22f12Sschwarze 		 */
662e0d9a108Sschwarze 		printf("Status: 303 See Other\r\n"
663e0d9a108Sschwarze 		    "Location: /");
664e0d9a108Sschwarze 		if (*scriptname != '\0')
665e0d9a108Sschwarze 			printf("%s/", scriptname);
666e0d9a108Sschwarze 		if (strcmp(req->q.manpath, req->p[0]))
667e0d9a108Sschwarze 			printf("%s/", req->q.manpath);
668e0d9a108Sschwarze 		printf("%s\r\n"
669e0d9a108Sschwarze 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
670e0d9a108Sschwarze 		    r[0].file);
671c6c22f12Sschwarze 		return;
672c6c22f12Sschwarze 	}
673c6c22f12Sschwarze 
67446723f19Sschwarze 	/*
67546723f19Sschwarze 	 * In man(1) mode, show one of the pages
67646723f19Sschwarze 	 * even if more than one is found.
67746723f19Sschwarze 	 */
67846723f19Sschwarze 
67946723f19Sschwarze 	iuse = 0;
680fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
681db26164eSschwarze 		priouse = 20;
682be14a32aSschwarze 		archpriouse = 3;
68346723f19Sschwarze 		for (i = 0; i < sz; i++) {
684db26164eSschwarze 			sec = r[i].file;
685db26164eSschwarze 			sec += strcspn(sec, "123456789");
686db26164eSschwarze 			if (sec[0] == '\0')
68746723f19Sschwarze 				continue;
688db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
689db26164eSschwarze 			if (sec[1] != '/')
690db26164eSschwarze 				prio += 10;
691db26164eSschwarze 			if (req->q.arch == NULL) {
692be14a32aSschwarze 				archprio =
693db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
694db26164eSschwarze 					== NULL) ? 3 :
695db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
696db26164eSschwarze 					== NULL) ? 0 :
697be14a32aSschwarze 				    strncmp(arch, "amd64/",
698be14a32aSschwarze 					archend - arch) ? 2 : 1;
699be14a32aSschwarze 				if (archprio < archpriouse) {
700be14a32aSschwarze 					archpriouse = archprio;
701be14a32aSschwarze 					priouse = prio;
702be14a32aSschwarze 					iuse = i;
703be14a32aSschwarze 					continue;
704be14a32aSschwarze 				}
705be14a32aSschwarze 				if (archprio > archpriouse)
706be14a32aSschwarze 					continue;
707be14a32aSschwarze 			}
70846723f19Sschwarze 			if (prio >= priouse)
70946723f19Sschwarze 				continue;
71046723f19Sschwarze 			priouse = prio;
71146723f19Sschwarze 			iuse = i;
71246723f19Sschwarze 		}
713a43df5a0Sschwarze 		have_header = resp_begin_html(200, NULL, r[iuse].file);
714fdef72b0Sschwarze 	} else
715a43df5a0Sschwarze 		have_header = resp_begin_html(200, NULL, NULL);
716fdef72b0Sschwarze 
717a43df5a0Sschwarze 	if (have_header == 0)
718a43df5a0Sschwarze 		puts("<header>");
719fdef72b0Sschwarze 	resp_searchform(req,
720fdef72b0Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
721a43df5a0Sschwarze 	puts("</header>");
722fdef72b0Sschwarze 
723fdef72b0Sschwarze 	if (sz > 1) {
724b2d1ada6Sschwarze 		puts("<nav>");
725fdef72b0Sschwarze 		puts("<table class=\"results\">");
726fdef72b0Sschwarze 		for (i = 0; i < sz; i++) {
727fdef72b0Sschwarze 			printf("  <tr>\n"
728fdef72b0Sschwarze 			       "    <td>"
7297b3dbe16Sschwarze 			       "<a class=\"Xr\" href=\"/");
7307b3dbe16Sschwarze 			if (*scriptname != '\0')
7317b3dbe16Sschwarze 				printf("%s/", scriptname);
7327b3dbe16Sschwarze 			if (strcmp(req->q.manpath, req->p[0]))
7337b3dbe16Sschwarze 				printf("%s/", req->q.manpath);
7347b3dbe16Sschwarze 			printf("%s\">", r[i].file);
735fdef72b0Sschwarze 			html_print(r[i].names);
736fdef72b0Sschwarze 			printf("</a></td>\n"
737fdef72b0Sschwarze 			       "    <td><span class=\"Nd\">");
738fdef72b0Sschwarze 			html_print(r[i].output);
739fdef72b0Sschwarze 			puts("</span></td>\n"
740fdef72b0Sschwarze 			     "  </tr>");
741fdef72b0Sschwarze 		}
742fdef72b0Sschwarze 		puts("</table>");
743b2d1ada6Sschwarze 		puts("</nav>");
744fdef72b0Sschwarze 	}
745fdef72b0Sschwarze 
746fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
747fdef72b0Sschwarze 		puts("<hr>");
74846723f19Sschwarze 		resp_show(req, r[iuse].file);
74946723f19Sschwarze 	}
75046723f19Sschwarze 
751c6c22f12Sschwarze 	resp_end_html();
752c6c22f12Sschwarze }
753c6c22f12Sschwarze 
754c6c22f12Sschwarze static void
755941df026Sschwarze resp_catman(const struct req *req, const char *file)
756c6c22f12Sschwarze {
757c6c22f12Sschwarze 	FILE		*f;
758c6c22f12Sschwarze 	char		*p;
75931f93c25Sschwarze 	size_t		 sz;
76031f93c25Sschwarze 	ssize_t		 len;
76131f93c25Sschwarze 	int		 i;
762c6c22f12Sschwarze 	int		 italic, bold;
763c6c22f12Sschwarze 
76431f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
765b2d1ada6Sschwarze 		puts("<p role=\"doc-notice\">\n"
766b2d1ada6Sschwarze 		     "  You specified an invalid manual file.\n"
767b2d1ada6Sschwarze 		     "</p>");
768c6c22f12Sschwarze 		return;
769c6c22f12Sschwarze 	}
770c6c22f12Sschwarze 
771735516bdSschwarze 	puts("<div class=\"catman\">\n"
772735516bdSschwarze 	     "<pre>");
773c6c22f12Sschwarze 
77431f93c25Sschwarze 	p = NULL;
77531f93c25Sschwarze 	sz = 0;
77631f93c25Sschwarze 
77731f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
778c6c22f12Sschwarze 		bold = italic = 0;
77931f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
780c6c22f12Sschwarze 			/*
781c6c22f12Sschwarze 			 * This means that the catpage is out of state.
782c6c22f12Sschwarze 			 * Ignore it and keep going (although the
783c6c22f12Sschwarze 			 * catpage is bogus).
784c6c22f12Sschwarze 			 */
785c6c22f12Sschwarze 
786c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
787c6c22f12Sschwarze 				continue;
788c6c22f12Sschwarze 
789c6c22f12Sschwarze 			/*
790c6c22f12Sschwarze 			 * Print a regular character.
791c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
792c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
793c6c22f12Sschwarze 			 * have something to enter when we backspace.
794c6c22f12Sschwarze 			 */
795c6c22f12Sschwarze 
796c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
797c6c22f12Sschwarze 				if (italic)
798735516bdSschwarze 					printf("</i>");
799c6c22f12Sschwarze 				if (bold)
800735516bdSschwarze 					printf("</b>");
801c6c22f12Sschwarze 				italic = bold = 0;
802c6c22f12Sschwarze 				html_putchar(p[i]);
803c6c22f12Sschwarze 				continue;
80431f93c25Sschwarze 			} else if (i + 2 >= len)
805c6c22f12Sschwarze 				continue;
806c6c22f12Sschwarze 
807c6c22f12Sschwarze 			/* Italic mode. */
808c6c22f12Sschwarze 
809c6c22f12Sschwarze 			if ('_' == p[i]) {
810c6c22f12Sschwarze 				if (bold)
811735516bdSschwarze 					printf("</b>");
812c6c22f12Sschwarze 				if ( ! italic)
813735516bdSschwarze 					printf("<i>");
814c6c22f12Sschwarze 				bold = 0;
815c6c22f12Sschwarze 				italic = 1;
816c6c22f12Sschwarze 				i += 2;
817c6c22f12Sschwarze 				html_putchar(p[i]);
818c6c22f12Sschwarze 				continue;
819c6c22f12Sschwarze 			}
820c6c22f12Sschwarze 
821c6c22f12Sschwarze 			/*
822c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
823c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
824c6c22f12Sschwarze 			 */
825c6c22f12Sschwarze 
826c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
827c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
828c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
829c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
830c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
831c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
832c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
833c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
834c6c22f12Sschwarze 				if (italic)
835735516bdSschwarze 					printf("</i>");
836c6c22f12Sschwarze 				if (bold)
837735516bdSschwarze 					printf("</b>");
838c6c22f12Sschwarze 				italic = bold = 0;
839c6c22f12Sschwarze 				putchar('*');
840c6c22f12Sschwarze 				i += 2;
841c6c22f12Sschwarze 				continue;
842c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
843c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
844c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
845c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
846c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
847c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
848c6c22f12Sschwarze 				if (italic)
849735516bdSschwarze 					printf("</i>");
850c6c22f12Sschwarze 				if (bold)
851735516bdSschwarze 					printf("</b>");
852c6c22f12Sschwarze 				italic = bold = 0;
853c6c22f12Sschwarze 				putchar('+');
854c6c22f12Sschwarze 				i += 2;
855c6c22f12Sschwarze 				continue;
856c6c22f12Sschwarze 			}
857c6c22f12Sschwarze 
858c6c22f12Sschwarze 			/* Bold mode. */
859c6c22f12Sschwarze 
860c6c22f12Sschwarze 			if (italic)
861735516bdSschwarze 				printf("</i>");
862c6c22f12Sschwarze 			if ( ! bold)
863735516bdSschwarze 				printf("<b>");
864c6c22f12Sschwarze 			bold = 1;
865c6c22f12Sschwarze 			italic = 0;
866c6c22f12Sschwarze 			i += 2;
867c6c22f12Sschwarze 			html_putchar(p[i]);
868c6c22f12Sschwarze 		}
869c6c22f12Sschwarze 
870c6c22f12Sschwarze 		/*
871c6c22f12Sschwarze 		 * Clean up the last character.
872c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
873c6c22f12Sschwarze 		 */
874c6c22f12Sschwarze 
875c6c22f12Sschwarze 		if (italic)
876735516bdSschwarze 			printf("</i>");
877c6c22f12Sschwarze 		if (bold)
878735516bdSschwarze 			printf("</b>");
879c6c22f12Sschwarze 
88031f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
881c6c22f12Sschwarze 			html_putchar(p[i]);
882c6c22f12Sschwarze 
883c6c22f12Sschwarze 		putchar('\n');
884c6c22f12Sschwarze 	}
88531f93c25Sschwarze 	free(p);
886c6c22f12Sschwarze 
887735516bdSschwarze 	puts("</pre>\n"
888735516bdSschwarze 	     "</div>");
889c6c22f12Sschwarze 
890c6c22f12Sschwarze 	fclose(f);
891c6c22f12Sschwarze }
892c6c22f12Sschwarze 
893c6c22f12Sschwarze static void
894941df026Sschwarze resp_format(const struct req *req, const char *file)
895c6c22f12Sschwarze {
8962ccd0917Sschwarze 	struct manoutput conf;
897c6c22f12Sschwarze 	struct mparse	*mp;
8986b86842eSschwarze 	struct roff_meta *meta;
899c6c22f12Sschwarze 	void		*vp;
900f74d674aSschwarze 	int		 fd;
901f74d674aSschwarze 	int		 usepath;
902c6c22f12Sschwarze 
903b7041c07Sderaadt 	if (-1 == (fd = open(file, O_RDONLY))) {
904b2d1ada6Sschwarze 		puts("<p role=\"doc-notice\">\n"
905b2d1ada6Sschwarze 		     "  You specified an invalid manual file.\n"
906b2d1ada6Sschwarze 		     "</p>");
907c6c22f12Sschwarze 		return;
908c6c22f12Sschwarze 	}
909c6c22f12Sschwarze 
91016536faaSschwarze 	mchars_alloc();
9116b86842eSschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
9126b86842eSschwarze 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
913df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
914c6c22f12Sschwarze 	close(fd);
9156b86842eSschwarze 	meta = mparse_result(mp);
916c6c22f12Sschwarze 
9172ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
9182ccd0917Sschwarze 	conf.fragment = 1;
9197c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
920f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
921ac2abdb8Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
922ac2abdb8Sschwarze 	    scriptname, *scriptname == '\0' ? "" : "/",
923f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
924c6c22f12Sschwarze 
92516536faaSschwarze 	vp = html_alloc(&conf);
9266b86842eSschwarze 	if (meta->macroset == MACROSET_MDOC)
9276b86842eSschwarze 		html_mdoc(vp, meta);
9286b86842eSschwarze 	else
9296b86842eSschwarze 		html_man(vp, meta);
930c6c22f12Sschwarze 
931c6c22f12Sschwarze 	html_free(vp);
932c6c22f12Sschwarze 	mparse_free(mp);
93316536faaSschwarze 	mchars_free();
9342ccd0917Sschwarze 	free(conf.man);
9357c6e1b3aSschwarze 	free(conf.style);
936c6c22f12Sschwarze }
937c6c22f12Sschwarze 
938c6c22f12Sschwarze static void
93946723f19Sschwarze resp_show(const struct req *req, const char *file)
94046723f19Sschwarze {
94181475784Sschwarze 
94281475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
9432f7bef27Sschwarze 		file += 2;
94446723f19Sschwarze 
94546723f19Sschwarze 	if ('c' == *file)
946941df026Sschwarze 		resp_catman(req, file);
94746723f19Sschwarze 	else
948941df026Sschwarze 		resp_format(req, file);
94946723f19Sschwarze }
95046723f19Sschwarze 
95146723f19Sschwarze static void
952b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
953c6c22f12Sschwarze {
954b53c14c3Sschwarze 	char		*manpath;
955b53c14c3Sschwarze 	const char	*file;
956c6c22f12Sschwarze 
957b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
958facea411Sschwarze 		pg_error_badrequest(
959c6c22f12Sschwarze 		    "You did not specify a page to show.");
960c6c22f12Sschwarze 		return;
961c6c22f12Sschwarze 	}
962b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
963b53c14c3Sschwarze 	file++;
964c6c22f12Sschwarze 
965b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
966631ce2c6Sschwarze 		pg_error_badrequest(
967631ce2c6Sschwarze 		    "You specified an invalid manpath.");
968b53c14c3Sschwarze 		free(manpath);
969631ce2c6Sschwarze 		return;
970631ce2c6Sschwarze 	}
971631ce2c6Sschwarze 
972c6c22f12Sschwarze 	/*
973c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
974c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
975c6c22f12Sschwarze 	 * relative to the manpath root.
976c6c22f12Sschwarze 	 */
977c6c22f12Sschwarze 
978b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
979c976f0e2Sschwarze 		warn("chdir %s", manpath);
980631ce2c6Sschwarze 		pg_error_internal();
981b53c14c3Sschwarze 		free(manpath);
982c6c22f12Sschwarze 		return;
983c6c22f12Sschwarze 	}
984b53c14c3Sschwarze 	free(manpath);
985b53c14c3Sschwarze 
986b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
98781475784Sschwarze 		pg_error_badrequest(
98881475784Sschwarze 		    "You specified an invalid manual file.");
98981475784Sschwarze 		return;
99081475784Sschwarze 	}
99181475784Sschwarze 
992a43df5a0Sschwarze 	if (resp_begin_html(200, NULL, file) == 0)
993a43df5a0Sschwarze 		puts("<header>");
99484f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
995a43df5a0Sschwarze 	puts("</header>");
996b53c14c3Sschwarze 	resp_show(req, file);
99746723f19Sschwarze 	resp_end_html();
998c6c22f12Sschwarze }
999c6c22f12Sschwarze 
1000c6c22f12Sschwarze static void
100157482ef4Sschwarze pg_search(const struct req *req)
1002c6c22f12Sschwarze {
1003c6c22f12Sschwarze 	struct mansearch	  search;
1004c6c22f12Sschwarze 	struct manpaths		  paths;
1005c6c22f12Sschwarze 	struct manpage		 *res;
1006fbeeb774Sschwarze 	char			**argv;
1007fbeeb774Sschwarze 	char			 *query, *rp, *wp;
1008c6c22f12Sschwarze 	size_t			  ressz;
1009fbeeb774Sschwarze 	int			  argc;
1010c6c22f12Sschwarze 
1011c6c22f12Sschwarze 	/*
1012c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
1013c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
1014c6c22f12Sschwarze 	 * relative to the manpath root.
1015c6c22f12Sschwarze 	 */
1016c6c22f12Sschwarze 
1017c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
1018c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
1019631ce2c6Sschwarze 		pg_error_internal();
1020c6c22f12Sschwarze 		return;
1021c6c22f12Sschwarze 	}
1022c6c22f12Sschwarze 
1023c6c22f12Sschwarze 	search.arch = req->q.arch;
1024c6c22f12Sschwarze 	search.sec = req->q.sec;
10250f10154cSschwarze 	search.outkey = "Nd";
10260f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
1027fea71919Sschwarze 	search.firstmatch = 1;
1028c6c22f12Sschwarze 
1029c6c22f12Sschwarze 	paths.sz = 1;
1030c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
1031c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
1032c6c22f12Sschwarze 
1033c6c22f12Sschwarze 	/*
1034fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
1035c6c22f12Sschwarze 	 */
1036c6c22f12Sschwarze 
1037fbeeb774Sschwarze 	argc = 0;
1038fbeeb774Sschwarze 	argv = NULL;
1039fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
1040fbeeb774Sschwarze 	for (;;) {
1041fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
1042fbeeb774Sschwarze 			rp++;
1043fbeeb774Sschwarze 		if (*rp == '\0')
1044fbeeb774Sschwarze 			break;
1045fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
1046fbeeb774Sschwarze 		argv[argc++] = wp = rp;
1047fbeeb774Sschwarze 		for (;;) {
1048fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
1049fbeeb774Sschwarze 				*wp = '\0';
1050fbeeb774Sschwarze 				rp++;
1051fbeeb774Sschwarze 				break;
1052fbeeb774Sschwarze 			}
1053fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
1054fbeeb774Sschwarze 				rp++;
1055fbeeb774Sschwarze 			if (wp != rp)
1056fbeeb774Sschwarze 				*wp = *rp;
1057fbeeb774Sschwarze 			if (*rp == '\0')
1058fbeeb774Sschwarze 				break;
1059fbeeb774Sschwarze 			wp++;
1060fbeeb774Sschwarze 			rp++;
1061fbeeb774Sschwarze 		}
1062c6c22f12Sschwarze 	}
1063c6c22f12Sschwarze 
1064e1beff2aSschwarze 	res = NULL;
1065e1beff2aSschwarze 	ressz = 0;
1066e1beff2aSschwarze 	if (req->isquery && req->q.equal && argc == 1)
1067e1beff2aSschwarze 		pg_redirect(req, argv[0]);
1068e1beff2aSschwarze 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
106918ccf011Sschwarze 		pg_noresult(req, 400, "Bad Request",
107018ccf011Sschwarze 		    "You entered an invalid query.");
1071e1beff2aSschwarze 	else if (ressz == 0)
107218ccf011Sschwarze 		pg_noresult(req, 404, "Not Found", "No results found.");
1073c6c22f12Sschwarze 	else
1074facea411Sschwarze 		pg_searchres(req, res, ressz);
1075c6c22f12Sschwarze 
1076fbeeb774Sschwarze 	free(query);
1077fbeeb774Sschwarze 	mansearch_free(res, ressz);
1078c6c22f12Sschwarze 	free(paths.paths[0]);
1079c6c22f12Sschwarze 	free(paths.paths);
1080c6c22f12Sschwarze }
1081c6c22f12Sschwarze 
1082c6c22f12Sschwarze int
1083c6c22f12Sschwarze main(void)
1084c6c22f12Sschwarze {
1085c6c22f12Sschwarze 	struct req	 req;
1086136c26b8Sschwarze 	struct itimerval itimer;
108757482ef4Sschwarze 	const char	*path;
108831e689c3Sschwarze 	const char	*querystring;
108957482ef4Sschwarze 	int		 i;
1090c6c22f12Sschwarze 
1091f80eb964Sschwarze 	/*
1092f80eb964Sschwarze 	 * The "rpath" pledge could be revoked after mparse_readfd()
1093f80eb964Sschwarze 	 * if the file desciptor to "/footer.html" would be opened
1094f80eb964Sschwarze 	 * up front, but it's probably not worth the complication
1095f80eb964Sschwarze 	 * of the code it would cause: it would require scattering
1096f80eb964Sschwarze 	 * pledge() calls in multiple low-level resp_*() functions.
1097f80eb964Sschwarze 	 */
1098f80eb964Sschwarze 
1099f80eb964Sschwarze 	if (pledge("stdio rpath", NULL) == -1) {
1100f80eb964Sschwarze 		warn("pledge");
1101f80eb964Sschwarze 		pg_error_internal();
1102f80eb964Sschwarze 		return EXIT_FAILURE;
1103f80eb964Sschwarze 	}
1104f80eb964Sschwarze 
1105136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1106136c26b8Sschwarze 
11072935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1108136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
11092935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1110136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1111136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1112c976f0e2Sschwarze 		warn("setitimer");
1113136c26b8Sschwarze 		pg_error_internal();
1114526e306bSschwarze 		return EXIT_FAILURE;
1115136c26b8Sschwarze 	}
1116136c26b8Sschwarze 
1117c6c22f12Sschwarze 	/*
11186fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1119c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1120c6c22f12Sschwarze 	 * relative to the same position.
1121c6c22f12Sschwarze 	 */
1122c6c22f12Sschwarze 
1123c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1124c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1125facea411Sschwarze 		pg_error_internal();
1126526e306bSschwarze 		return EXIT_FAILURE;
1127c6c22f12Sschwarze 	}
1128c6c22f12Sschwarze 
1129c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1130abf19dc9Sschwarze 	req.q.equal = 1;
1131941df026Sschwarze 	parse_manpath_conf(&req);
1132c6c22f12Sschwarze 
113302b1b494Sschwarze 	/* Parse the path info and the query string. */
1134c6c22f12Sschwarze 
113502b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
113602b1b494Sschwarze 		path = "";
113702b1b494Sschwarze 	else if (*path == '/')
113802b1b494Sschwarze 		path++;
113902b1b494Sschwarze 
1140aa16a3a8Sschwarze 	if (*path != '\0') {
1141941df026Sschwarze 		parse_path_info(&req, path);
1142a9941855Sschwarze 		if (req.q.manpath == NULL || req.q.sec == NULL ||
1143a9941855Sschwarze 		    *req.q.query == '\0' || access(path, F_OK) == -1)
114402b1b494Sschwarze 			path = "";
114502b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1146941df026Sschwarze 		parse_query_string(&req, querystring);
1147c6c22f12Sschwarze 
114802b1b494Sschwarze 	/* Validate parsed data and add defaults. */
114902b1b494Sschwarze 
115050eaed2bSschwarze 	if (req.q.manpath == NULL)
115150eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
115250eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1153631ce2c6Sschwarze 		pg_error_badrequest(
1154631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1155526e306bSschwarze 		return EXIT_FAILURE;
1156631ce2c6Sschwarze 	}
1157631ce2c6Sschwarze 
1158f7a12365Sschwarze 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1159cf3a545cSschwarze 		pg_error_badrequest(
1160cf3a545cSschwarze 		    "You specified an invalid architecture.");
1161526e306bSschwarze 		return EXIT_FAILURE;
1162cf3a545cSschwarze 	}
1163cf3a545cSschwarze 
116457482ef4Sschwarze 	/* Dispatch to the three different pages. */
1165c6c22f12Sschwarze 
116657482ef4Sschwarze 	if ('\0' != *path)
116757482ef4Sschwarze 		pg_show(&req, path);
1168e89321abSschwarze 	else if (NULL != req.q.query)
116957482ef4Sschwarze 		pg_search(&req);
117057482ef4Sschwarze 	else
1171facea411Sschwarze 		pg_index(&req);
1172c6c22f12Sschwarze 
117331e689c3Sschwarze 	free(req.q.manpath);
117431e689c3Sschwarze 	free(req.q.arch);
117531e689c3Sschwarze 	free(req.q.sec);
1176e89321abSschwarze 	free(req.q.query);
1177c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1178c6c22f12Sschwarze 		free(req.p[i]);
1179c6c22f12Sschwarze 	free(req.p);
1180526e306bSschwarze 	return EXIT_SUCCESS;
1181c6c22f12Sschwarze }
1182c6c22f12Sschwarze 
1183c6c22f12Sschwarze /*
1184f5938fa6Sschwarze  * Translate PATH_INFO to a query.
118502b1b494Sschwarze  */
118602b1b494Sschwarze static void
1187941df026Sschwarze parse_path_info(struct req *req, const char *path)
118802b1b494Sschwarze {
1189f5938fa6Sschwarze 	const char	*name, *sec, *end;
119002b1b494Sschwarze 
119152b413c1Sschwarze 	req->isquery = 0;
119202b1b494Sschwarze 	req->q.equal = 1;
1193f5938fa6Sschwarze 	req->q.manpath = NULL;
1194daf4c292Sschwarze 	req->q.arch = NULL;
119502b1b494Sschwarze 
119602b1b494Sschwarze 	/* Mandatory manual page name. */
1197f5938fa6Sschwarze 	if ((name = strrchr(path, '/')) == NULL)
1198f5938fa6Sschwarze 		name = path;
1199f5938fa6Sschwarze 	else
1200f5938fa6Sschwarze 		name++;
120102b1b494Sschwarze 
120202b1b494Sschwarze 	/* Optional trailing section. */
1203f5938fa6Sschwarze 	sec = strrchr(name, '.');
1204f5938fa6Sschwarze 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
1205f5938fa6Sschwarze 		req->q.query = mandoc_strndup(name, sec - name - 1);
1206f5938fa6Sschwarze 		req->q.sec = mandoc_strdup(sec);
1207f5938fa6Sschwarze 	} else {
1208f5938fa6Sschwarze 		req->q.query = mandoc_strdup(name);
120902b1b494Sschwarze 		req->q.sec = NULL;
121002b1b494Sschwarze 	}
121102b1b494Sschwarze 
121202b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1213f5938fa6Sschwarze 	if (name == path)
121402b1b494Sschwarze 		return;
121502b1b494Sschwarze 
1216f5938fa6Sschwarze 	/* Optional manpath. */
1217f5938fa6Sschwarze 	end = strchr(path, '/');
1218f5938fa6Sschwarze 	req->q.manpath = mandoc_strndup(path, end - path);
1219f5938fa6Sschwarze 	if (validate_manpath(req, req->q.manpath)) {
1220f5938fa6Sschwarze 		path = end + 1;
1221f5938fa6Sschwarze 		if (name == path)
1222f5938fa6Sschwarze 			return;
1223f5938fa6Sschwarze 	} else {
1224f5938fa6Sschwarze 		free(req->q.manpath);
1225f5938fa6Sschwarze 		req->q.manpath = NULL;
1226f5938fa6Sschwarze 	}
1227f5938fa6Sschwarze 
1228f5938fa6Sschwarze 	/* Optional section. */
1229955967fcSschwarze 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
1230f5938fa6Sschwarze 		path += 3;
1231f5938fa6Sschwarze 		end = strchr(path, '/');
1232f5938fa6Sschwarze 		free(req->q.sec);
1233f5938fa6Sschwarze 		req->q.sec = mandoc_strndup(path, end - path);
1234f5938fa6Sschwarze 		path = end + 1;
1235f5938fa6Sschwarze 		if (name == path)
1236f5938fa6Sschwarze 			return;
1237f5938fa6Sschwarze 	}
1238f5938fa6Sschwarze 
1239f5938fa6Sschwarze 	/* Optional architecture. */
1240f5938fa6Sschwarze 	end = strchr(path, '/');
1241f5938fa6Sschwarze 	if (end + 1 != name) {
1242daf4c292Sschwarze 		pg_error_badrequest(
1243daf4c292Sschwarze 		    "You specified too many directory components.");
1244daf4c292Sschwarze 		exit(EXIT_FAILURE);
124502b1b494Sschwarze 	}
1246f5938fa6Sschwarze 	req->q.arch = mandoc_strndup(path, end - path);
1247f5938fa6Sschwarze 	if (validate_arch(req->q.arch) == 0) {
1248daf4c292Sschwarze 		pg_error_badrequest(
1249daf4c292Sschwarze 		    "You specified an invalid directory component.");
1250daf4c292Sschwarze 		exit(EXIT_FAILURE);
1251daf4c292Sschwarze 	}
125202b1b494Sschwarze }
125302b1b494Sschwarze 
125402b1b494Sschwarze /*
1255c6c22f12Sschwarze  * Scan for indexable paths.
1256c6c22f12Sschwarze  */
1257c6c22f12Sschwarze static void
1258941df026Sschwarze parse_manpath_conf(struct req *req)
1259c6c22f12Sschwarze {
1260c6c22f12Sschwarze 	FILE	*fp;
1261c6c22f12Sschwarze 	char	*dp;
1262c6c22f12Sschwarze 	size_t	 dpsz;
126331f93c25Sschwarze 	ssize_t	 len;
1264c6c22f12Sschwarze 
1265c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1266c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1267de651747Sschwarze 		pg_error_internal();
1268de651747Sschwarze 		exit(EXIT_FAILURE);
1269de651747Sschwarze 	}
1270c6c22f12Sschwarze 
127131f93c25Sschwarze 	dp = NULL;
127231f93c25Sschwarze 	dpsz = 0;
127331f93c25Sschwarze 
127431f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
127531f93c25Sschwarze 		if (dp[len - 1] == '\n')
127631f93c25Sschwarze 			dp[--len] = '\0';
1277c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1278c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1279cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1280c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1281c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1282cf3a545cSschwarze 			pg_error_internal();
1283cf3a545cSschwarze 			exit(EXIT_FAILURE);
1284cf3a545cSschwarze 		}
1285c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1286c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1287c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1288cf3a545cSschwarze 			pg_error_internal();
1289cf3a545cSschwarze 			exit(EXIT_FAILURE);
1290cf3a545cSschwarze 		}
1291cf3a545cSschwarze 		req->p[req->psz++] = dp;
129231f93c25Sschwarze 		dp = NULL;
129331f93c25Sschwarze 		dpsz = 0;
1294c6c22f12Sschwarze 	}
129531f93c25Sschwarze 	free(dp);
1296de651747Sschwarze 
1297de651747Sschwarze 	if (req->p == NULL) {
1298c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1299de651747Sschwarze 		pg_error_internal();
1300de651747Sschwarze 		exit(EXIT_FAILURE);
1301de651747Sschwarze 	}
1302c6c22f12Sschwarze }
1303