xref: /openbsd/usr.bin/mandoc/cgi.c (revision d9a51c35)
1*d9a51c35Sjmc /* $OpenBSD: cgi.c,v 1.120 2022/12/26 19:16:02 jmc 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
html_putchar(char c)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
html_print(const char * p)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
set_query_attr(char ** attr,char ** val)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
parse_query_string(struct req * req,const char * qs)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
http_decode(char * p)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
http_encode(const char * p)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
resp_begin_http(int code,const char * msg)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
resp_copy(const char * element,const char * filename)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
resp_begin_html(int code,const char * msg,const char * file)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
resp_end_html(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
resp_searchform(const struct req * req,enum focus focus)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 
443df46652dSschwarze 	printf("    <label>Search query:\n"
444df46652dSschwarze 	       "      <input type=\"search\" name=\"query\" value=\"");
44584f05c93Sschwarze 	if (req->q.query != NULL)
446e89321abSschwarze 		html_print(req->q.query);
44784f05c93Sschwarze 	printf("\" size=\"40\"");
44884f05c93Sschwarze 	if (focus == FOCUS_QUERY)
44984f05c93Sschwarze 		printf(" autofocus");
450df46652dSschwarze 	puts(">\n    </label>");
45128e449d6Sschwarze 
452784a63d6Sschwarze 	/* Write submission buttons. */
45328e449d6Sschwarze 
454784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
455784a63d6Sschwarze 		"man</button>\n"
456784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
457542ee4bfSschwarze 		"apropos</button>\n"
458542ee4bfSschwarze 		"    <br/>\n");
45928e449d6Sschwarze 
46028e449d6Sschwarze 	/* Write section selector. */
46128e449d6Sschwarze 
462f0f927fcSschwarze 	puts("    <select name=\"sec\" aria-label=\"Manual section\">");
46328e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
464735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
46528e449d6Sschwarze 		if (NULL != req->q.sec &&
46628e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
467735516bdSschwarze 			printf(" selected=\"selected\"");
468735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
46928e449d6Sschwarze 	}
470735516bdSschwarze 	puts("    </select>");
47128e449d6Sschwarze 
47228e449d6Sschwarze 	/* Write architecture selector. */
47328e449d6Sschwarze 
474b2d1ada6Sschwarze 	printf(	"    <select name=\"arch\" aria-label=\"CPU architecture\">\n"
475735516bdSschwarze 		"      <option value=\"default\"");
476be14a32aSschwarze 	if (NULL == req->q.arch)
477735516bdSschwarze 		printf(" selected=\"selected\"");
478735516bdSschwarze 	puts(">All Architectures</option>");
47928e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
4806b824e3bSschwarze 		printf("      <option");
48128e449d6Sschwarze 		if (NULL != req->q.arch &&
48228e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
483735516bdSschwarze 			printf(" selected=\"selected\"");
484735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
48528e449d6Sschwarze 	}
486735516bdSschwarze 	puts("    </select>");
48728e449d6Sschwarze 
48828e449d6Sschwarze 	/* Write manpath selector. */
48928e449d6Sschwarze 
490c6c22f12Sschwarze 	if (req->psz > 1) {
491df46652dSschwarze 		puts("    <select name=\"manpath\""
492df46652dSschwarze 		     " aria-label=\"Manual path\">");
493c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
494735516bdSschwarze 			printf("      <option");
49550eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
496735516bdSschwarze 				printf(" selected=\"selected\"");
4976b824e3bSschwarze 			printf(">");
498c6c22f12Sschwarze 			html_print(req->p[i]);
499735516bdSschwarze 			puts("</option>");
500c6c22f12Sschwarze 		}
501735516bdSschwarze 		puts("    </select>");
502c6c22f12Sschwarze 	}
50328e449d6Sschwarze 
504784a63d6Sschwarze 	puts("  </fieldset>\n"
505a43df5a0Sschwarze 	     "</form>");
506c6c22f12Sschwarze }
507c6c22f12Sschwarze 
50881475784Sschwarze static int
validate_urifrag(const char * frag)509cf3a545cSschwarze validate_urifrag(const char *frag)
510cf3a545cSschwarze {
511cf3a545cSschwarze 
512cf3a545cSschwarze 	while ('\0' != *frag) {
513cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
514cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
515cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
516526e306bSschwarze 			return 0;
517cf3a545cSschwarze 		frag++;
518cf3a545cSschwarze 	}
519526e306bSschwarze 	return 1;
520cf3a545cSschwarze }
521cf3a545cSschwarze 
522cf3a545cSschwarze static int
validate_manpath(const struct req * req,const char * manpath)523631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
524631ce2c6Sschwarze {
525631ce2c6Sschwarze 	size_t	 i;
526631ce2c6Sschwarze 
527631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
528631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
529526e306bSschwarze 			return 1;
530631ce2c6Sschwarze 
531526e306bSschwarze 	return 0;
532631ce2c6Sschwarze }
533631ce2c6Sschwarze 
534631ce2c6Sschwarze static int
validate_arch(const char * arch)535f7a12365Sschwarze validate_arch(const char *arch)
536f7a12365Sschwarze {
537f7a12365Sschwarze 	int	 i;
538f7a12365Sschwarze 
539f7a12365Sschwarze 	for (i = 0; i < arch_MAX; i++)
540f7a12365Sschwarze 		if (strcmp(arch, arch_names[i]) == 0)
541f7a12365Sschwarze 			return 1;
542f7a12365Sschwarze 
543f7a12365Sschwarze 	return 0;
544f7a12365Sschwarze }
545f7a12365Sschwarze 
546f7a12365Sschwarze static int
validate_filename(const char * file)54781475784Sschwarze validate_filename(const char *file)
54881475784Sschwarze {
54981475784Sschwarze 
55081475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
55181475784Sschwarze 		file += 2;
55281475784Sschwarze 
553526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
554526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
55581475784Sschwarze }
55681475784Sschwarze 
557c6c22f12Sschwarze static void
pg_index(const struct req * req)558facea411Sschwarze pg_index(const struct req *req)
559c6c22f12Sschwarze {
560a43df5a0Sschwarze 	if (resp_begin_html(200, NULL, NULL) == 0)
561a43df5a0Sschwarze 		puts("<header>");
56284f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
563a43df5a0Sschwarze 	printf("</header>\n"
564a43df5a0Sschwarze 	       "<main>\n"
565f0f927fcSschwarze 	       "<p role=\"doc-notice\" aria-label=\"Usage\">\n"
566d56ca219Sschwarze 	       "This web interface is documented in the\n"
567b2d1ada6Sschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
568b2d1ada6Sschwarze 	       " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
569d56ca219Sschwarze 	       "manual, and the\n"
570b2d1ada6Sschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\""
571b2d1ada6Sschwarze 	       " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
5722a43838fSschwarze 	       "manual explains the query syntax.\n"
573b2d1ada6Sschwarze 	       "</p>\n"
574b2d1ada6Sschwarze 	       "</main>\n",
5753b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5763b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
577c6c22f12Sschwarze 	resp_end_html();
578c6c22f12Sschwarze }
579c6c22f12Sschwarze 
580c6c22f12Sschwarze static void
pg_noresult(const struct req * req,int code,const char * http_msg,const char * user_msg)58118ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg,
58218ccf011Sschwarze     const char *user_msg)
583c6c22f12Sschwarze {
584a43df5a0Sschwarze 	if (resp_begin_html(code, http_msg, NULL) == 0)
585a43df5a0Sschwarze 		puts("<header>");
58684f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
587a43df5a0Sschwarze 	puts("</header>");
588b2d1ada6Sschwarze 	puts("<main>");
589f0f927fcSschwarze 	puts("<p role=\"doc-notice\" aria-label=\"No result\">");
59018ccf011Sschwarze 	puts(user_msg);
591735516bdSschwarze 	puts("</p>");
592b2d1ada6Sschwarze 	puts("</main>");
593c6c22f12Sschwarze 	resp_end_html();
594c6c22f12Sschwarze }
595c6c22f12Sschwarze 
596c6c22f12Sschwarze static void
pg_error_badrequest(const char * msg)597facea411Sschwarze pg_error_badrequest(const char *msg)
598c6c22f12Sschwarze {
599a43df5a0Sschwarze 	if (resp_begin_html(400, "Bad Request", NULL))
600a43df5a0Sschwarze 		puts("</header>");
601b2d1ada6Sschwarze 	puts("<main>\n"
602b2d1ada6Sschwarze 	     "<h1>Bad Request</h1>\n"
603b2d1ada6Sschwarze 	     "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
604c6c22f12Sschwarze 	puts(msg);
605c6c22f12Sschwarze 	printf("Try again from the\n"
606735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
607b2d1ada6Sschwarze 	       "</p>\n"
608a43df5a0Sschwarze 	       "</main>\n", scriptname);
609c6c22f12Sschwarze 	resp_end_html();
610c6c22f12Sschwarze }
611c6c22f12Sschwarze 
612c6c22f12Sschwarze static void
pg_error_internal(void)613facea411Sschwarze pg_error_internal(void)
614c6c22f12Sschwarze {
615a43df5a0Sschwarze 	if (resp_begin_html(500, "Internal Server Error", NULL))
616a43df5a0Sschwarze 		puts("</header>");
617b2d1ada6Sschwarze 	puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
618c6c22f12Sschwarze 	resp_end_html();
619c6c22f12Sschwarze }
620c6c22f12Sschwarze 
621c6c22f12Sschwarze static void
pg_redirect(const struct req * req,const char * name)622e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
623e1beff2aSschwarze {
624e0d9a108Sschwarze 	printf("Status: 303 See Other\r\n"
625e0d9a108Sschwarze 	    "Location: /");
626e1beff2aSschwarze 	if (*scriptname != '\0')
627e1beff2aSschwarze 		printf("%s/", scriptname);
628e1beff2aSschwarze 	if (strcmp(req->q.manpath, req->p[0]))
629e1beff2aSschwarze 		printf("%s/", req->q.manpath);
630e1beff2aSschwarze 	if (req->q.arch != NULL)
631e1beff2aSschwarze 		printf("%s/", req->q.arch);
632f7a12365Sschwarze 	http_encode(name);
633f7a12365Sschwarze 	if (req->q.sec != NULL) {
634f7a12365Sschwarze 		putchar('.');
635f7a12365Sschwarze 		http_encode(req->q.sec);
636f7a12365Sschwarze 	}
637e1beff2aSschwarze 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
638e1beff2aSschwarze }
639e1beff2aSschwarze 
640e1beff2aSschwarze static void
pg_searchres(const struct req * req,struct manpage * r,size_t sz)641facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
642c6c22f12Sschwarze {
643be14a32aSschwarze 	char		*arch, *archend;
644db26164eSschwarze 	const char	*sec;
645db26164eSschwarze 	size_t		 i, iuse;
646be14a32aSschwarze 	int		 archprio, archpriouse;
64746723f19Sschwarze 	int		 prio, priouse;
648a43df5a0Sschwarze 	int		 have_header;
649c6c22f12Sschwarze 
65081475784Sschwarze 	for (i = 0; i < sz; i++) {
65181475784Sschwarze 		if (validate_filename(r[i].file))
65281475784Sschwarze 			continue;
653c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
65481475784Sschwarze 		    r[i].file, req->q.manpath);
65581475784Sschwarze 		pg_error_internal();
65681475784Sschwarze 		return;
65781475784Sschwarze 	}
65881475784Sschwarze 
65952b413c1Sschwarze 	if (req->isquery && sz == 1) {
660c6c22f12Sschwarze 		/*
661c6c22f12Sschwarze 		 * If we have just one result, then jump there now
662c6c22f12Sschwarze 		 * without any delay.
663c6c22f12Sschwarze 		 */
664e0d9a108Sschwarze 		printf("Status: 303 See Other\r\n"
665e0d9a108Sschwarze 		    "Location: /");
666e0d9a108Sschwarze 		if (*scriptname != '\0')
667e0d9a108Sschwarze 			printf("%s/", scriptname);
668e0d9a108Sschwarze 		if (strcmp(req->q.manpath, req->p[0]))
669e0d9a108Sschwarze 			printf("%s/", req->q.manpath);
670e0d9a108Sschwarze 		printf("%s\r\n"
671e0d9a108Sschwarze 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
672e0d9a108Sschwarze 		    r[0].file);
673c6c22f12Sschwarze 		return;
674c6c22f12Sschwarze 	}
675c6c22f12Sschwarze 
67646723f19Sschwarze 	/*
67746723f19Sschwarze 	 * In man(1) mode, show one of the pages
67846723f19Sschwarze 	 * even if more than one is found.
67946723f19Sschwarze 	 */
68046723f19Sschwarze 
68146723f19Sschwarze 	iuse = 0;
682fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
683db26164eSschwarze 		priouse = 20;
684be14a32aSschwarze 		archpriouse = 3;
68546723f19Sschwarze 		for (i = 0; i < sz; i++) {
686db26164eSschwarze 			sec = r[i].file;
687db26164eSschwarze 			sec += strcspn(sec, "123456789");
688db26164eSschwarze 			if (sec[0] == '\0')
68946723f19Sschwarze 				continue;
690db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
691db26164eSschwarze 			if (sec[1] != '/')
692db26164eSschwarze 				prio += 10;
693db26164eSschwarze 			if (req->q.arch == NULL) {
694be14a32aSschwarze 				archprio =
695db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
696db26164eSschwarze 					== NULL) ? 3 :
697db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
698db26164eSschwarze 					== NULL) ? 0 :
699be14a32aSschwarze 				    strncmp(arch, "amd64/",
700be14a32aSschwarze 					archend - arch) ? 2 : 1;
701be14a32aSschwarze 				if (archprio < archpriouse) {
702be14a32aSschwarze 					archpriouse = archprio;
703be14a32aSschwarze 					priouse = prio;
704be14a32aSschwarze 					iuse = i;
705be14a32aSschwarze 					continue;
706be14a32aSschwarze 				}
707be14a32aSschwarze 				if (archprio > archpriouse)
708be14a32aSschwarze 					continue;
709be14a32aSschwarze 			}
71046723f19Sschwarze 			if (prio >= priouse)
71146723f19Sschwarze 				continue;
71246723f19Sschwarze 			priouse = prio;
71346723f19Sschwarze 			iuse = i;
71446723f19Sschwarze 		}
715a43df5a0Sschwarze 		have_header = resp_begin_html(200, NULL, r[iuse].file);
716fdef72b0Sschwarze 	} else
717a43df5a0Sschwarze 		have_header = resp_begin_html(200, NULL, NULL);
718fdef72b0Sschwarze 
719a43df5a0Sschwarze 	if (have_header == 0)
720a43df5a0Sschwarze 		puts("<header>");
721fdef72b0Sschwarze 	resp_searchform(req,
722fdef72b0Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
723a43df5a0Sschwarze 	puts("</header>");
724fdef72b0Sschwarze 
725fdef72b0Sschwarze 	if (sz > 1) {
726b2d1ada6Sschwarze 		puts("<nav>");
727fdef72b0Sschwarze 		puts("<table class=\"results\">");
728fdef72b0Sschwarze 		for (i = 0; i < sz; i++) {
729fdef72b0Sschwarze 			printf("  <tr>\n"
730fdef72b0Sschwarze 			       "    <td>"
7317b3dbe16Sschwarze 			       "<a class=\"Xr\" href=\"/");
7327b3dbe16Sschwarze 			if (*scriptname != '\0')
7337b3dbe16Sschwarze 				printf("%s/", scriptname);
7347b3dbe16Sschwarze 			if (strcmp(req->q.manpath, req->p[0]))
7357b3dbe16Sschwarze 				printf("%s/", req->q.manpath);
7367b3dbe16Sschwarze 			printf("%s\">", r[i].file);
737fdef72b0Sschwarze 			html_print(r[i].names);
738fdef72b0Sschwarze 			printf("</a></td>\n"
739fdef72b0Sschwarze 			       "    <td><span class=\"Nd\">");
740fdef72b0Sschwarze 			html_print(r[i].output);
741fdef72b0Sschwarze 			puts("</span></td>\n"
742fdef72b0Sschwarze 			     "  </tr>");
743fdef72b0Sschwarze 		}
744fdef72b0Sschwarze 		puts("</table>");
745b2d1ada6Sschwarze 		puts("</nav>");
746fdef72b0Sschwarze 	}
747fdef72b0Sschwarze 
748fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
749fdef72b0Sschwarze 		puts("<hr>");
75046723f19Sschwarze 		resp_show(req, r[iuse].file);
75146723f19Sschwarze 	}
75246723f19Sschwarze 
753c6c22f12Sschwarze 	resp_end_html();
754c6c22f12Sschwarze }
755c6c22f12Sschwarze 
756c6c22f12Sschwarze static void
resp_catman(const struct req * req,const char * file)757941df026Sschwarze resp_catman(const struct req *req, const char *file)
758c6c22f12Sschwarze {
759c6c22f12Sschwarze 	FILE		*f;
760c6c22f12Sschwarze 	char		*p;
76131f93c25Sschwarze 	size_t		 sz;
76231f93c25Sschwarze 	ssize_t		 len;
76331f93c25Sschwarze 	int		 i;
764c6c22f12Sschwarze 	int		 italic, bold;
765c6c22f12Sschwarze 
76631f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
767b2d1ada6Sschwarze 		puts("<p role=\"doc-notice\">\n"
768b2d1ada6Sschwarze 		     "  You specified an invalid manual file.\n"
769b2d1ada6Sschwarze 		     "</p>");
770c6c22f12Sschwarze 		return;
771c6c22f12Sschwarze 	}
772c6c22f12Sschwarze 
773735516bdSschwarze 	puts("<div class=\"catman\">\n"
774735516bdSschwarze 	     "<pre>");
775c6c22f12Sschwarze 
77631f93c25Sschwarze 	p = NULL;
77731f93c25Sschwarze 	sz = 0;
77831f93c25Sschwarze 
77931f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
780c6c22f12Sschwarze 		bold = italic = 0;
78131f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
782c6c22f12Sschwarze 			/*
783c6c22f12Sschwarze 			 * This means that the catpage is out of state.
784c6c22f12Sschwarze 			 * Ignore it and keep going (although the
785c6c22f12Sschwarze 			 * catpage is bogus).
786c6c22f12Sschwarze 			 */
787c6c22f12Sschwarze 
788c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
789c6c22f12Sschwarze 				continue;
790c6c22f12Sschwarze 
791c6c22f12Sschwarze 			/*
792c6c22f12Sschwarze 			 * Print a regular character.
793c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
794c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
795c6c22f12Sschwarze 			 * have something to enter when we backspace.
796c6c22f12Sschwarze 			 */
797c6c22f12Sschwarze 
798c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
799c6c22f12Sschwarze 				if (italic)
800735516bdSschwarze 					printf("</i>");
801c6c22f12Sschwarze 				if (bold)
802735516bdSschwarze 					printf("</b>");
803c6c22f12Sschwarze 				italic = bold = 0;
804c6c22f12Sschwarze 				html_putchar(p[i]);
805c6c22f12Sschwarze 				continue;
80631f93c25Sschwarze 			} else if (i + 2 >= len)
807c6c22f12Sschwarze 				continue;
808c6c22f12Sschwarze 
809c6c22f12Sschwarze 			/* Italic mode. */
810c6c22f12Sschwarze 
811c6c22f12Sschwarze 			if ('_' == p[i]) {
812c6c22f12Sschwarze 				if (bold)
813735516bdSschwarze 					printf("</b>");
814c6c22f12Sschwarze 				if ( ! italic)
815735516bdSschwarze 					printf("<i>");
816c6c22f12Sschwarze 				bold = 0;
817c6c22f12Sschwarze 				italic = 1;
818c6c22f12Sschwarze 				i += 2;
819c6c22f12Sschwarze 				html_putchar(p[i]);
820c6c22f12Sschwarze 				continue;
821c6c22f12Sschwarze 			}
822c6c22f12Sschwarze 
823c6c22f12Sschwarze 			/*
824c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
825c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
826c6c22f12Sschwarze 			 */
827c6c22f12Sschwarze 
828c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
829c6c22f12Sschwarze 					('o' == 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 					('*' == p[i] && '|' == p[i + 2]) ||
835c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
836c6c22f12Sschwarze 				if (italic)
837735516bdSschwarze 					printf("</i>");
838c6c22f12Sschwarze 				if (bold)
839735516bdSschwarze 					printf("</b>");
840c6c22f12Sschwarze 				italic = bold = 0;
841c6c22f12Sschwarze 				putchar('*');
842c6c22f12Sschwarze 				i += 2;
843c6c22f12Sschwarze 				continue;
844c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
845c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
846c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
847c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
848c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
849c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
850c6c22f12Sschwarze 				if (italic)
851735516bdSschwarze 					printf("</i>");
852c6c22f12Sschwarze 				if (bold)
853735516bdSschwarze 					printf("</b>");
854c6c22f12Sschwarze 				italic = bold = 0;
855c6c22f12Sschwarze 				putchar('+');
856c6c22f12Sschwarze 				i += 2;
857c6c22f12Sschwarze 				continue;
858c6c22f12Sschwarze 			}
859c6c22f12Sschwarze 
860c6c22f12Sschwarze 			/* Bold mode. */
861c6c22f12Sschwarze 
862c6c22f12Sschwarze 			if (italic)
863735516bdSschwarze 				printf("</i>");
864c6c22f12Sschwarze 			if ( ! bold)
865735516bdSschwarze 				printf("<b>");
866c6c22f12Sschwarze 			bold = 1;
867c6c22f12Sschwarze 			italic = 0;
868c6c22f12Sschwarze 			i += 2;
869c6c22f12Sschwarze 			html_putchar(p[i]);
870c6c22f12Sschwarze 		}
871c6c22f12Sschwarze 
872c6c22f12Sschwarze 		/*
873c6c22f12Sschwarze 		 * Clean up the last character.
874c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
875c6c22f12Sschwarze 		 */
876c6c22f12Sschwarze 
877c6c22f12Sschwarze 		if (italic)
878735516bdSschwarze 			printf("</i>");
879c6c22f12Sschwarze 		if (bold)
880735516bdSschwarze 			printf("</b>");
881c6c22f12Sschwarze 
88231f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
883c6c22f12Sschwarze 			html_putchar(p[i]);
884c6c22f12Sschwarze 
885c6c22f12Sschwarze 		putchar('\n');
886c6c22f12Sschwarze 	}
88731f93c25Sschwarze 	free(p);
888c6c22f12Sschwarze 
889735516bdSschwarze 	puts("</pre>\n"
890735516bdSschwarze 	     "</div>");
891c6c22f12Sschwarze 
892c6c22f12Sschwarze 	fclose(f);
893c6c22f12Sschwarze }
894c6c22f12Sschwarze 
895c6c22f12Sschwarze static void
resp_format(const struct req * req,const char * file)896941df026Sschwarze resp_format(const struct req *req, const char *file)
897c6c22f12Sschwarze {
8982ccd0917Sschwarze 	struct manoutput conf;
899c6c22f12Sschwarze 	struct mparse	*mp;
9006b86842eSschwarze 	struct roff_meta *meta;
901c6c22f12Sschwarze 	void		*vp;
902f74d674aSschwarze 	int		 fd;
903f74d674aSschwarze 	int		 usepath;
904c6c22f12Sschwarze 
905b7041c07Sderaadt 	if (-1 == (fd = open(file, O_RDONLY))) {
906b2d1ada6Sschwarze 		puts("<p role=\"doc-notice\">\n"
907b2d1ada6Sschwarze 		     "  You specified an invalid manual file.\n"
908b2d1ada6Sschwarze 		     "</p>");
909c6c22f12Sschwarze 		return;
910c6c22f12Sschwarze 	}
911c6c22f12Sschwarze 
91216536faaSschwarze 	mchars_alloc();
9136b86842eSschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
9146b86842eSschwarze 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
915df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
916c6c22f12Sschwarze 	close(fd);
9176b86842eSschwarze 	meta = mparse_result(mp);
918c6c22f12Sschwarze 
9192ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
9202ccd0917Sschwarze 	conf.fragment = 1;
9217c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
922f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
923ac2abdb8Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
924ac2abdb8Sschwarze 	    scriptname, *scriptname == '\0' ? "" : "/",
925f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
926c6c22f12Sschwarze 
92716536faaSschwarze 	vp = html_alloc(&conf);
9286b86842eSschwarze 	if (meta->macroset == MACROSET_MDOC)
9296b86842eSschwarze 		html_mdoc(vp, meta);
9306b86842eSschwarze 	else
9316b86842eSschwarze 		html_man(vp, meta);
932c6c22f12Sschwarze 
933c6c22f12Sschwarze 	html_free(vp);
934c6c22f12Sschwarze 	mparse_free(mp);
93516536faaSschwarze 	mchars_free();
9362ccd0917Sschwarze 	free(conf.man);
9377c6e1b3aSschwarze 	free(conf.style);
938c6c22f12Sschwarze }
939c6c22f12Sschwarze 
940c6c22f12Sschwarze static void
resp_show(const struct req * req,const char * file)94146723f19Sschwarze resp_show(const struct req *req, const char *file)
94246723f19Sschwarze {
94381475784Sschwarze 
94481475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
9452f7bef27Sschwarze 		file += 2;
94646723f19Sschwarze 
94746723f19Sschwarze 	if ('c' == *file)
948941df026Sschwarze 		resp_catman(req, file);
94946723f19Sschwarze 	else
950941df026Sschwarze 		resp_format(req, file);
95146723f19Sschwarze }
95246723f19Sschwarze 
95346723f19Sschwarze static void
pg_show(struct req * req,const char * fullpath)954b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
955c6c22f12Sschwarze {
956b53c14c3Sschwarze 	char		*manpath;
957b53c14c3Sschwarze 	const char	*file;
958c6c22f12Sschwarze 
959b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
960facea411Sschwarze 		pg_error_badrequest(
961c6c22f12Sschwarze 		    "You did not specify a page to show.");
962c6c22f12Sschwarze 		return;
963c6c22f12Sschwarze 	}
964b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
965b53c14c3Sschwarze 	file++;
966c6c22f12Sschwarze 
967b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
968631ce2c6Sschwarze 		pg_error_badrequest(
969631ce2c6Sschwarze 		    "You specified an invalid manpath.");
970b53c14c3Sschwarze 		free(manpath);
971631ce2c6Sschwarze 		return;
972631ce2c6Sschwarze 	}
973631ce2c6Sschwarze 
974c6c22f12Sschwarze 	/*
975c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
976c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
977c6c22f12Sschwarze 	 * relative to the manpath root.
978c6c22f12Sschwarze 	 */
979c6c22f12Sschwarze 
980b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
981c976f0e2Sschwarze 		warn("chdir %s", manpath);
982631ce2c6Sschwarze 		pg_error_internal();
983b53c14c3Sschwarze 		free(manpath);
984c6c22f12Sschwarze 		return;
985c6c22f12Sschwarze 	}
986b53c14c3Sschwarze 	free(manpath);
987b53c14c3Sschwarze 
988b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
98981475784Sschwarze 		pg_error_badrequest(
99081475784Sschwarze 		    "You specified an invalid manual file.");
99181475784Sschwarze 		return;
99281475784Sschwarze 	}
99381475784Sschwarze 
994a43df5a0Sschwarze 	if (resp_begin_html(200, NULL, file) == 0)
995a43df5a0Sschwarze 		puts("<header>");
99684f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
997a43df5a0Sschwarze 	puts("</header>");
998b53c14c3Sschwarze 	resp_show(req, file);
99946723f19Sschwarze 	resp_end_html();
1000c6c22f12Sschwarze }
1001c6c22f12Sschwarze 
1002c6c22f12Sschwarze static void
pg_search(const struct req * req)100357482ef4Sschwarze pg_search(const struct req *req)
1004c6c22f12Sschwarze {
1005c6c22f12Sschwarze 	struct mansearch	  search;
1006c6c22f12Sschwarze 	struct manpaths		  paths;
1007c6c22f12Sschwarze 	struct manpage		 *res;
1008fbeeb774Sschwarze 	char			**argv;
1009fbeeb774Sschwarze 	char			 *query, *rp, *wp;
1010c6c22f12Sschwarze 	size_t			  ressz;
1011fbeeb774Sschwarze 	int			  argc;
1012c6c22f12Sschwarze 
1013c6c22f12Sschwarze 	/*
1014c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
1015c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
1016c6c22f12Sschwarze 	 * relative to the manpath root.
1017c6c22f12Sschwarze 	 */
1018c6c22f12Sschwarze 
1019c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
1020c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
1021631ce2c6Sschwarze 		pg_error_internal();
1022c6c22f12Sschwarze 		return;
1023c6c22f12Sschwarze 	}
1024c6c22f12Sschwarze 
1025c6c22f12Sschwarze 	search.arch = req->q.arch;
1026c6c22f12Sschwarze 	search.sec = req->q.sec;
10270f10154cSschwarze 	search.outkey = "Nd";
10280f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
1029fea71919Sschwarze 	search.firstmatch = 1;
1030c6c22f12Sschwarze 
1031c6c22f12Sschwarze 	paths.sz = 1;
1032c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
1033c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
1034c6c22f12Sschwarze 
1035c6c22f12Sschwarze 	/*
1036fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
1037c6c22f12Sschwarze 	 */
1038c6c22f12Sschwarze 
1039fbeeb774Sschwarze 	argc = 0;
1040fbeeb774Sschwarze 	argv = NULL;
1041fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
1042fbeeb774Sschwarze 	for (;;) {
1043fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
1044fbeeb774Sschwarze 			rp++;
1045fbeeb774Sschwarze 		if (*rp == '\0')
1046fbeeb774Sschwarze 			break;
1047fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
1048fbeeb774Sschwarze 		argv[argc++] = wp = rp;
1049fbeeb774Sschwarze 		for (;;) {
1050fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
1051fbeeb774Sschwarze 				*wp = '\0';
1052fbeeb774Sschwarze 				rp++;
1053fbeeb774Sschwarze 				break;
1054fbeeb774Sschwarze 			}
1055fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
1056fbeeb774Sschwarze 				rp++;
1057fbeeb774Sschwarze 			if (wp != rp)
1058fbeeb774Sschwarze 				*wp = *rp;
1059fbeeb774Sschwarze 			if (*rp == '\0')
1060fbeeb774Sschwarze 				break;
1061fbeeb774Sschwarze 			wp++;
1062fbeeb774Sschwarze 			rp++;
1063fbeeb774Sschwarze 		}
1064c6c22f12Sschwarze 	}
1065c6c22f12Sschwarze 
1066e1beff2aSschwarze 	res = NULL;
1067e1beff2aSschwarze 	ressz = 0;
1068e1beff2aSschwarze 	if (req->isquery && req->q.equal && argc == 1)
1069e1beff2aSschwarze 		pg_redirect(req, argv[0]);
1070e1beff2aSschwarze 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
107118ccf011Sschwarze 		pg_noresult(req, 400, "Bad Request",
107218ccf011Sschwarze 		    "You entered an invalid query.");
1073e1beff2aSschwarze 	else if (ressz == 0)
107418ccf011Sschwarze 		pg_noresult(req, 404, "Not Found", "No results found.");
1075c6c22f12Sschwarze 	else
1076facea411Sschwarze 		pg_searchres(req, res, ressz);
1077c6c22f12Sschwarze 
1078fbeeb774Sschwarze 	free(query);
1079fbeeb774Sschwarze 	mansearch_free(res, ressz);
1080c6c22f12Sschwarze 	free(paths.paths[0]);
1081c6c22f12Sschwarze 	free(paths.paths);
1082c6c22f12Sschwarze }
1083c6c22f12Sschwarze 
1084c6c22f12Sschwarze int
main(void)1085c6c22f12Sschwarze main(void)
1086c6c22f12Sschwarze {
1087c6c22f12Sschwarze 	struct req	 req;
1088136c26b8Sschwarze 	struct itimerval itimer;
108957482ef4Sschwarze 	const char	*path;
109031e689c3Sschwarze 	const char	*querystring;
109157482ef4Sschwarze 	int		 i;
1092c6c22f12Sschwarze 
1093f80eb964Sschwarze 	/*
1094f80eb964Sschwarze 	 * The "rpath" pledge could be revoked after mparse_readfd()
1095*d9a51c35Sjmc 	 * if the file descriptor to "/footer.html" would be opened
1096f80eb964Sschwarze 	 * up front, but it's probably not worth the complication
1097f80eb964Sschwarze 	 * of the code it would cause: it would require scattering
1098f80eb964Sschwarze 	 * pledge() calls in multiple low-level resp_*() functions.
1099f80eb964Sschwarze 	 */
1100f80eb964Sschwarze 
1101f80eb964Sschwarze 	if (pledge("stdio rpath", NULL) == -1) {
1102f80eb964Sschwarze 		warn("pledge");
1103f80eb964Sschwarze 		pg_error_internal();
1104f80eb964Sschwarze 		return EXIT_FAILURE;
1105f80eb964Sschwarze 	}
1106f80eb964Sschwarze 
1107136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1108136c26b8Sschwarze 
11092935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1110136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
11112935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1112136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1113136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1114c976f0e2Sschwarze 		warn("setitimer");
1115136c26b8Sschwarze 		pg_error_internal();
1116526e306bSschwarze 		return EXIT_FAILURE;
1117136c26b8Sschwarze 	}
1118136c26b8Sschwarze 
1119c6c22f12Sschwarze 	/*
11206fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1121c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1122c6c22f12Sschwarze 	 * relative to the same position.
1123c6c22f12Sschwarze 	 */
1124c6c22f12Sschwarze 
1125c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1126c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1127facea411Sschwarze 		pg_error_internal();
1128526e306bSschwarze 		return EXIT_FAILURE;
1129c6c22f12Sschwarze 	}
1130c6c22f12Sschwarze 
1131c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1132abf19dc9Sschwarze 	req.q.equal = 1;
1133941df026Sschwarze 	parse_manpath_conf(&req);
1134c6c22f12Sschwarze 
113502b1b494Sschwarze 	/* Parse the path info and the query string. */
1136c6c22f12Sschwarze 
113702b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
113802b1b494Sschwarze 		path = "";
113902b1b494Sschwarze 	else if (*path == '/')
114002b1b494Sschwarze 		path++;
114102b1b494Sschwarze 
1142aa16a3a8Sschwarze 	if (*path != '\0') {
1143941df026Sschwarze 		parse_path_info(&req, path);
1144a9941855Sschwarze 		if (req.q.manpath == NULL || req.q.sec == NULL ||
1145a9941855Sschwarze 		    *req.q.query == '\0' || access(path, F_OK) == -1)
114602b1b494Sschwarze 			path = "";
114702b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1148941df026Sschwarze 		parse_query_string(&req, querystring);
1149c6c22f12Sschwarze 
115002b1b494Sschwarze 	/* Validate parsed data and add defaults. */
115102b1b494Sschwarze 
115250eaed2bSschwarze 	if (req.q.manpath == NULL)
115350eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
115450eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1155631ce2c6Sschwarze 		pg_error_badrequest(
1156631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1157526e306bSschwarze 		return EXIT_FAILURE;
1158631ce2c6Sschwarze 	}
1159631ce2c6Sschwarze 
1160f7a12365Sschwarze 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1161cf3a545cSschwarze 		pg_error_badrequest(
1162cf3a545cSschwarze 		    "You specified an invalid architecture.");
1163526e306bSschwarze 		return EXIT_FAILURE;
1164cf3a545cSschwarze 	}
1165cf3a545cSschwarze 
116657482ef4Sschwarze 	/* Dispatch to the three different pages. */
1167c6c22f12Sschwarze 
116857482ef4Sschwarze 	if ('\0' != *path)
116957482ef4Sschwarze 		pg_show(&req, path);
1170e89321abSschwarze 	else if (NULL != req.q.query)
117157482ef4Sschwarze 		pg_search(&req);
117257482ef4Sschwarze 	else
1173facea411Sschwarze 		pg_index(&req);
1174c6c22f12Sschwarze 
117531e689c3Sschwarze 	free(req.q.manpath);
117631e689c3Sschwarze 	free(req.q.arch);
117731e689c3Sschwarze 	free(req.q.sec);
1178e89321abSschwarze 	free(req.q.query);
1179c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1180c6c22f12Sschwarze 		free(req.p[i]);
1181c6c22f12Sschwarze 	free(req.p);
1182526e306bSschwarze 	return EXIT_SUCCESS;
1183c6c22f12Sschwarze }
1184c6c22f12Sschwarze 
1185c6c22f12Sschwarze /*
1186f5938fa6Sschwarze  * Translate PATH_INFO to a query.
118702b1b494Sschwarze  */
118802b1b494Sschwarze static void
parse_path_info(struct req * req,const char * path)1189941df026Sschwarze parse_path_info(struct req *req, const char *path)
119002b1b494Sschwarze {
1191f5938fa6Sschwarze 	const char	*name, *sec, *end;
119202b1b494Sschwarze 
119352b413c1Sschwarze 	req->isquery = 0;
119402b1b494Sschwarze 	req->q.equal = 1;
1195f5938fa6Sschwarze 	req->q.manpath = NULL;
1196daf4c292Sschwarze 	req->q.arch = NULL;
119702b1b494Sschwarze 
119802b1b494Sschwarze 	/* Mandatory manual page name. */
1199f5938fa6Sschwarze 	if ((name = strrchr(path, '/')) == NULL)
1200f5938fa6Sschwarze 		name = path;
1201f5938fa6Sschwarze 	else
1202f5938fa6Sschwarze 		name++;
120302b1b494Sschwarze 
120402b1b494Sschwarze 	/* Optional trailing section. */
1205f5938fa6Sschwarze 	sec = strrchr(name, '.');
1206f5938fa6Sschwarze 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
1207f5938fa6Sschwarze 		req->q.query = mandoc_strndup(name, sec - name - 1);
1208f5938fa6Sschwarze 		req->q.sec = mandoc_strdup(sec);
1209f5938fa6Sschwarze 	} else {
1210f5938fa6Sschwarze 		req->q.query = mandoc_strdup(name);
121102b1b494Sschwarze 		req->q.sec = NULL;
121202b1b494Sschwarze 	}
121302b1b494Sschwarze 
121402b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1215f5938fa6Sschwarze 	if (name == path)
121602b1b494Sschwarze 		return;
121702b1b494Sschwarze 
1218f5938fa6Sschwarze 	/* Optional manpath. */
1219f5938fa6Sschwarze 	end = strchr(path, '/');
1220f5938fa6Sschwarze 	req->q.manpath = mandoc_strndup(path, end - path);
1221f5938fa6Sschwarze 	if (validate_manpath(req, req->q.manpath)) {
1222f5938fa6Sschwarze 		path = end + 1;
1223f5938fa6Sschwarze 		if (name == path)
1224f5938fa6Sschwarze 			return;
1225f5938fa6Sschwarze 	} else {
1226f5938fa6Sschwarze 		free(req->q.manpath);
1227f5938fa6Sschwarze 		req->q.manpath = NULL;
1228f5938fa6Sschwarze 	}
1229f5938fa6Sschwarze 
1230f5938fa6Sschwarze 	/* Optional section. */
1231955967fcSschwarze 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
1232f5938fa6Sschwarze 		path += 3;
1233f5938fa6Sschwarze 		end = strchr(path, '/');
1234f5938fa6Sschwarze 		free(req->q.sec);
1235f5938fa6Sschwarze 		req->q.sec = mandoc_strndup(path, end - path);
1236f5938fa6Sschwarze 		path = end + 1;
1237f5938fa6Sschwarze 		if (name == path)
1238f5938fa6Sschwarze 			return;
1239f5938fa6Sschwarze 	}
1240f5938fa6Sschwarze 
1241f5938fa6Sschwarze 	/* Optional architecture. */
1242f5938fa6Sschwarze 	end = strchr(path, '/');
1243f5938fa6Sschwarze 	if (end + 1 != name) {
1244daf4c292Sschwarze 		pg_error_badrequest(
1245daf4c292Sschwarze 		    "You specified too many directory components.");
1246daf4c292Sschwarze 		exit(EXIT_FAILURE);
124702b1b494Sschwarze 	}
1248f5938fa6Sschwarze 	req->q.arch = mandoc_strndup(path, end - path);
1249f5938fa6Sschwarze 	if (validate_arch(req->q.arch) == 0) {
1250daf4c292Sschwarze 		pg_error_badrequest(
1251daf4c292Sschwarze 		    "You specified an invalid directory component.");
1252daf4c292Sschwarze 		exit(EXIT_FAILURE);
1253daf4c292Sschwarze 	}
125402b1b494Sschwarze }
125502b1b494Sschwarze 
125602b1b494Sschwarze /*
1257c6c22f12Sschwarze  * Scan for indexable paths.
1258c6c22f12Sschwarze  */
1259c6c22f12Sschwarze static void
parse_manpath_conf(struct req * req)1260941df026Sschwarze parse_manpath_conf(struct req *req)
1261c6c22f12Sschwarze {
1262c6c22f12Sschwarze 	FILE	*fp;
1263c6c22f12Sschwarze 	char	*dp;
1264c6c22f12Sschwarze 	size_t	 dpsz;
126531f93c25Sschwarze 	ssize_t	 len;
1266c6c22f12Sschwarze 
1267c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1268c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1269de651747Sschwarze 		pg_error_internal();
1270de651747Sschwarze 		exit(EXIT_FAILURE);
1271de651747Sschwarze 	}
1272c6c22f12Sschwarze 
127331f93c25Sschwarze 	dp = NULL;
127431f93c25Sschwarze 	dpsz = 0;
127531f93c25Sschwarze 
127631f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
127731f93c25Sschwarze 		if (dp[len - 1] == '\n')
127831f93c25Sschwarze 			dp[--len] = '\0';
1279c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1280c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1281cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1282c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1283c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1284cf3a545cSschwarze 			pg_error_internal();
1285cf3a545cSschwarze 			exit(EXIT_FAILURE);
1286cf3a545cSschwarze 		}
1287c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1288c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1289c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1290cf3a545cSschwarze 			pg_error_internal();
1291cf3a545cSschwarze 			exit(EXIT_FAILURE);
1292cf3a545cSschwarze 		}
1293cf3a545cSschwarze 		req->p[req->psz++] = dp;
129431f93c25Sschwarze 		dp = NULL;
129531f93c25Sschwarze 		dpsz = 0;
1296c6c22f12Sschwarze 	}
129731f93c25Sschwarze 	free(dp);
1298de651747Sschwarze 
1299de651747Sschwarze 	if (req->p == NULL) {
1300c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1301de651747Sschwarze 		pg_error_internal();
1302de651747Sschwarze 		exit(EXIT_FAILURE);
1303de651747Sschwarze 	}
1304c6c22f12Sschwarze }
1305