16f8c987bapt/*	$Id: cgi.c,v 1.167 2019/07/10 12:49:20 schwarze Exp $ */
2c2dfbbduqs/*
3c2dfbbduqs * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
472da6efbapt * Copyright (c) 2014, 2015, 2016, 2017, 2018 Ingo Schwarze <schwarze@usta.de>
5c2dfbbduqs *
6c2dfbbduqs * Permission to use, copy, modify, and distribute this software for any
7c2dfbbduqs * purpose with or without fee is hereby granted, provided that the above
8c2dfbbduqs * copyright notice and this permission notice appear in all copies.
9c2dfbbduqs *
10877b18fbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11c2dfbbduqs * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12877b18fbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13c2dfbbduqs * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c2dfbbduqs * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15c2dfbbduqs * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16c2dfbbduqs * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c2dfbbduqs */
18c2dfbbduqs#include "config.h"
19e00686fbapt
20e00686fbapt#include <sys/types.h>
21e00686fbapt#include <sys/time.h>
22c2dfbbduqs
23c2dfbbduqs#include <ctype.h>
24cd7178dbapt#if HAVE_ERR
2536a85ccbapt#include <err.h>
26cd7178dbapt#endif
27c2dfbbduqs#include <errno.h>
28c2dfbbduqs#include <fcntl.h>
29c2dfbbduqs#include <limits.h>
30c2dfbbduqs#include <stdint.h>
31eb03421bapt#include <stdio.h>
32c2dfbbduqs#include <stdlib.h>
33c2dfbbduqs#include <string.h>
34c2dfbbduqs#include <unistd.h>
35c2dfbbduqs
36eb03421bapt#include "mandoc_aux.h"
37877b18fbapt#include "mandoc.h"
38877b18fbapt#include "roff.h"
39877b18fbapt#include "mdoc.h"
40877b18fbapt#include "man.h"
4172da6efbapt#include "mandoc_parse.h"
42c2dfbbduqs#include "main.h"
43877b18fbapt#include "manconf.h"
44eb03421bapt#include "mansearch.h"
45eb03421bapt#include "cgi.h"
46c2dfbbduqs
47c2dfbbduqs/*
48c2dfbbduqs * A query as passed to the search function.
49c2dfbbduqs */
50c2dfbbduqsstruct	query {
51eb03421bapt	char		*manpath; /* desired manual directory */
52eb03421bapt	char		*arch; /* architecture */
53eb03421bapt	char		*sec; /* manual section */
54eb03421bapt	char		*query; /* unparsed query expression */
55eb03421bapt	int		 equal; /* match whole names, not substrings */
56c2dfbbduqs};
57c2dfbbduqs
58c2dfbbduqsstruct	req {
59eb03421bapt	struct query	  q;
60eb03421bapt	char		**p; /* array of available manpaths */
61eb03421bapt	size_t		  psz; /* number of available manpaths */
6236a85ccbapt	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
6336a85ccbapt};
6436a85ccbapt
6536a85ccbaptenum	focus {
6636a85ccbapt	FOCUS_NONE = 0,
6736a85ccbapt	FOCUS_QUERY
68c2dfbbduqs};
69c2dfbbduqs
70c2dfbbduqsstatic	void		 html_print(const char *);
71c2dfbbduqsstatic	void		 html_putchar(char);
72ecbd8afbaptstatic	int		 http_decode(char *);
7372da6efbaptstatic	void		 http_encode(const char *p);
7436a85ccbaptstatic	void		 parse_manpath_conf(struct req *);
7536a85ccbaptstatic	void		 parse_path_info(struct req *req, const char *path);
7636a85ccbaptstatic	void		 parse_query_string(struct req *, const char *);
77eb03421baptstatic	void		 pg_error_badrequest(const char *);
78eb03421baptstatic	void		 pg_error_internal(void);
79eb03421baptstatic	void		 pg_index(const struct req *);
80eb03421baptstatic	void		 pg_noresult(const struct req *, const char *);
8146b1dcfbaptstatic	void		 pg_redirect(const struct req *, const char *);
82eb03421baptstatic	void		 pg_search(const struct req *);
83eb03421baptstatic	void		 pg_searchres(const struct req *,
84eb03421bapt				struct manpage *, size_t);
85eb03421baptstatic	void		 pg_show(struct req *, const char *);
8646b1dcfbaptstatic	void		 resp_begin_html(int, const char *, const char *);
87c2dfbbduqsstatic	void		 resp_begin_http(int, const char *);
8836a85ccbaptstatic	void		 resp_catman(const struct req *, const char *);
89877b18fbaptstatic	void		 resp_copy(const char *);
90c2dfbbduqsstatic	void		 resp_end_html(void);
9136a85ccbaptstatic	void		 resp_format(const struct req *, const char *);
9236a85ccbaptstatic	void		 resp_searchform(const struct req *, enum focus);
93eb03421baptstatic	void		 resp_show(const struct req *, const char *);
94eb03421baptstatic	void		 set_query_attr(char **, char **);
9572da6efbaptstatic	int		 validate_arch(const char *);
96eb03421baptstatic	int		 validate_filename(const char *);
97eb03421baptstatic	int		 validate_manpath(const struct req *, const char *);
98eb03421baptstatic	int		 validate_urifrag(const char *);
99c2dfbbduqs
10036a85ccbaptstatic	const char	 *scriptname = SCRIPT_NAME;
101c2dfbbduqs
102eb03421baptstatic	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
103eb03421baptstatic	const char *const sec_numbers[] = {
104eb03421bapt    "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
105c2dfbbduqs};
106eb03421baptstatic	const char *const sec_names[] = {
107eb03421bapt    "All Sections",
108eb03421bapt    "1 - General Commands",
109eb03421bapt    "2 - System Calls",
110e00686fbapt    "3 - Library Functions",
111e00686fbapt    "3p - Perl Library",
112e00686fbapt    "4 - Device Drivers",
113eb03421bapt    "5 - File Formats",
114eb03421bapt    "6 - Games",
115e00686fbapt    "7 - Miscellaneous Information",
116e00686fbapt    "8 - System Manager\'s Manual",
117e00686fbapt    "9 - Kernel Developer\'s Manual"
118eb03421bapt};
119eb03421baptstatic	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
120eb03421bapt
121eb03421baptstatic	const char *const arch_names[] = {
122cd7178dbapt    "amd64",       "alpha",       "armv7",	"arm64",
123cb61028bapt    "hppa",        "i386",        "landisk",
12436a85ccbapt    "loongson",    "luna88k",     "macppc",      "mips64",
125cb61028bapt    "octeon",      "sgi",         "socppc",      "sparc64",
126cb61028bapt    "amiga",       "arc",         "armish",      "arm32",
127cb61028bapt    "atari",       "aviion",      "beagle",      "cats",
128cb61028bapt    "hppa64",      "hp300",
12936a85ccbapt    "ia64",        "mac68k",      "mvme68k",     "mvme88k",
13036a85ccbapt    "mvmeppc",     "palm",        "pc532",       "pegasos",
131cb61028bapt    "pmax",        "powerpc",     "solbourne",   "sparc",
132cb61028bapt    "sun3",        "vax",         "wgrisc",      "x68k",
133cb61028bapt    "zaurus"
134eb03421bapt};
135eb03421baptstatic	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
136c2dfbbduqs
137c2dfbbduqs/*
138c2dfbbduqs * Print a character, escaping HTML along the way.
139c2dfbbduqs * This will pass non-ASCII straight to output: be warned!
140c2dfbbduqs */
141c2dfbbduqsstatic void
142c2dfbbduqshtml_putchar(char c)
143c2dfbbduqs{
144c2dfbbduqs
145c2dfbbduqs	switch (c) {
146302d56fbapt	case '"':
147cb61028bapt		printf("&quot;");
148c2dfbbduqs		break;
149302d56fbapt	case '&':
150c2dfbbduqs		printf("&amp;");
151c2dfbbduqs		break;
152302d56fbapt	case '>':
153c2dfbbduqs		printf("&gt;");
154c2dfbbduqs		break;
155302d56fbapt	case '<':
156c2dfbbduqs		printf("&lt;");
157c2dfbbduqs		break;
158c2dfbbduqs	default:
159c2dfbbduqs		putchar((unsigned char)c);
160c2dfbbduqs		break;
161c2dfbbduqs	}
162c2dfbbduqs}
163c2dfbbduqs
164c2dfbbduqs/*
165c2dfbbduqs * Call through to html_putchar().
166c2dfbbduqs * Accepts NULL strings.
167c2dfbbduqs */
168c2dfbbduqsstatic void
169c2dfbbduqshtml_print(const char *p)
170c2dfbbduqs{
171ecbd8afbapt
172c2dfbbduqs	if (NULL == p)
173c2dfbbduqs		return;
174c2dfbbduqs	while ('\0' != *p)
175c2dfbbduqs		html_putchar(*p++);
176c2dfbbduqs}
177c2dfbbduqs
178c2dfbbduqs/*
179eb03421bapt * Transfer the responsibility for the allocated string *val
180eb03421bapt * to the query structure.
181eb03421bapt */
182eb03421baptstatic void
183eb03421baptset_query_attr(char **attr, char **val)
184eb03421bapt{
185eb03421bapt
186eb03421bapt	free(*attr);
187eb03421bapt	if (**val == '\0') {
188eb03421bapt		*attr = NULL;
189eb03421bapt		free(*val);
190eb03421bapt	} else
191eb03421bapt		*attr = *val;
192eb03421bapt	*val = NULL;
193eb03421bapt}
194eb03421bapt
195eb03421bapt/*
196eb03421bapt * Parse the QUERY_STRING for key-value pairs
197eb03421bapt * and store the values into the query structure.
198c2dfbbduqs */
199c2dfbbduqsstatic void
20036a85ccbaptparse_query_string(struct req *req, const char *qs)
201c2dfbbduqs{
202eb03421bapt	char		*key, *val;
203eb03421bapt	size_t		 keysz, valsz;
204c2dfbbduqs
20536a85ccbapt	req->isquery	= 1;
206eb03421bapt	req->q.manpath	= NULL;
207eb03421bapt	req->q.arch	= NULL;
208eb03421bapt	req->q.sec	= NULL;
209eb03421bapt	req->q.query	= NULL;
210eb03421bapt	req->q.equal	= 1;
211c2dfbbduqs
212eb03421bapt	key = val = NULL;
213eb03421bapt	while (*qs != '\0') {
214c2dfbbduqs
215eb03421bapt		/* Parse one key. */
216c2dfbbduqs
217eb03421bapt		keysz = strcspn(qs, "=;&");
218eb03421bapt		key = mandoc_strndup(qs, keysz);
219eb03421bapt		qs += keysz;
220eb03421bapt		if (*qs != '=')
221eb03421bapt			goto next;
222c2dfbbduqs
223eb03421bapt		/* Parse one value. */
224c2dfbbduqs
225eb03421bapt		valsz = strcspn(++qs, ";&");
226eb03421bapt		val = mandoc_strndup(qs, valsz);
227eb03421bapt		qs += valsz;
228c2dfbbduqs
229eb03421bapt		/* Decode and catch encoding errors. */
230c2dfbbduqs
231eb03421bapt		if ( ! (http_decode(key) && http_decode(val)))
232eb03421bapt			goto next;
233c2dfbbduqs
234eb03421bapt		/* Handle key-value pairs. */
235eb03421bapt
236eb03421bapt		if ( ! strcmp(key, "query"))
237eb03421bapt			set_query_attr(&req->q.query, &val);
238eb03421bapt
239eb03421bapt		else if ( ! strcmp(key, "apropos"))
240eb03421bapt			req->q.equal = !strcmp(val, "0");
241eb03421bapt
242eb03421bapt		else if ( ! strcmp(key, "manpath")) {
243eb03421bapt#ifdef COMPAT_OLDURI
244eb03421bapt			if ( ! strncmp(val, "OpenBSD ", 8)) {
245eb03421bapt				val[7] = '-';
246eb03421bapt				if ('C' == val[8])
247eb03421bapt					val[8] = 'c';
248eb03421bapt			}
249eb03421bapt#endif
250eb03421bapt			set_query_attr(&req->q.manpath, &val);
251eb03421bapt		}
252eb03421bapt
253eb03421bapt		else if ( ! (strcmp(key, "sec")
254eb03421bapt#ifdef COMPAT_OLDURI
255eb03421bapt		    && strcmp(key, "sektion")
256eb03421bapt#endif
257eb03421bapt		    )) {
258eb03421bapt			if ( ! strcmp(val, "0"))
259eb03421bapt				*val = '\0';
260eb03421bapt			set_query_attr(&req->q.sec, &val);
261eb03421bapt		}
262c2dfbbduqs
263eb03421bapt		else if ( ! strcmp(key, "arch")) {
264eb03421bapt			if ( ! strcmp(val, "default"))
265eb03421bapt				*val = '\0';
266eb03421bapt			set_query_attr(&req->q.arch, &val);
267eb03421bapt		}
268c2dfbbduqs
269eb03421bapt		/*
270eb03421bapt		 * The key must be freed in any case.
271eb03421bapt		 * The val may have been handed over to the query
272eb03421bapt		 * structure, in which case it is now NULL.
273eb03421bapt		 */
274eb03421baptnext:
275eb03421bapt		free(key);
276eb03421bapt		key = NULL;
277eb03421bapt		free(val);
278eb03421bapt		val = NULL;
279c2dfbbduqs
280eb03421bapt		if (*qs != '\0')
281eb03421bapt			qs++;
282c2dfbbduqs	}
283c2dfbbduqs}
284c2dfbbduqs
285c2dfbbduqs/*
286c2dfbbduqs * HTTP-decode a string.  The standard explanation is that this turns
287c2dfbbduqs * "%4e+foo" into "n foo" in the regular way.  This is done in-place
288c2dfbbduqs * over the allocated string.
289c2dfbbduqs */
290c2dfbbduqsstatic int
291c2dfbbduqshttp_decode(char *p)
292c2dfbbduqs{
293c2dfbbduqs	char             hex[3];
294eb03421bapt	char		*q;
295c2dfbbduqs	int              c;
296c2dfbbduqs
297c2dfbbduqs	hex[2] = '\0';
298c2dfbbduqs
299eb03421bapt	q = p;
300eb03421bapt	for ( ; '\0' != *p; p++, q++) {
301c2dfbbduqs		if ('%' == *p) {
302c2dfbbduqs			if ('\0' == (hex[0] = *(p + 1)))
303877b18fbapt				return 0;
304c2dfbbduqs			if ('\0' == (hex[1] = *(p + 2)))
305877b18fbapt				return 0;
306c2dfbbduqs			if (1 != sscanf(hex, "%x", &c))
307877b18fbapt				return 0;
308c2dfbbduqs			if ('\0' == c)
309877b18fbapt				return 0;
310c2dfbbduqs
311eb03421bapt			*q = (char)c;
312eb03421bapt			p += 2;
313c2dfbbduqs		} else
314eb03421bapt			*q = '+' == *p ? ' ' : *p;
315c2dfbbduqs	}
316c2dfbbduqs
317eb03421bapt	*q = '\0';
318877b18fbapt	return 1;
319c2dfbbduqs}
320c2dfbbduqs
321c2dfbbduqsstatic void
32272da6efbapthttp_encode(const char *p)
32372da6efbapt{
32472da6efbapt	for (; *p != '\0'; p++) {
32572da6efbapt		if (isalnum((unsigned char)*p) == 0 &&
32672da6efbapt		    strchr("-._~", *p) == NULL)
32772da6efbapt			printf("%%%2.2X", (unsigned char)*p);
32872da6efbapt		else
32972da6efbapt			putchar(*p);
33072da6efbapt	}
33172da6efbapt}
33272da6efbapt
33372da6efbaptstatic void
334c2dfbbduqsresp_begin_http(int code, const char *msg)
335c2dfbbduqs{
336c2dfbbduqs
337c2dfbbduqs	if (200 != code)
338eb03421bapt		printf("Status: %d %s\r\n", code, msg);
339c2dfbbduqs
340eb03421bapt	printf("Content-Type: text/html; charset=utf-8\r\n"
341eb03421bapt	     "Cache-Control: no-cache\r\n"
342eb03421bapt	     "Pragma: no-cache\r\n"
343eb03421bapt	     "\r\n");
344c2dfbbduqs
345c2dfbbduqs	fflush(stdout);
346c2dfbbduqs}
347c2dfbbduqs
348c2dfbbduqsstatic void
349877b18fbaptresp_copy(const char *filename)
350877b18fbapt{
351877b18fbapt	char	 buf[4096];
352877b18fbapt	ssize_t	 sz;
353877b18fbapt	int	 fd;
354877b18fbapt
355877b18fbapt	if ((fd = open(filename, O_RDONLY)) != -1) {
356877b18fbapt		fflush(stdout);
357877b18fbapt		while ((sz = read(fd, buf, sizeof(buf))) > 0)
358877b18fbapt			write(STDOUT_FILENO, buf, sz);
359cb61028bapt		close(fd);
360877b18fbapt	}
361877b18fbapt}
362877b18fbapt
363877b18fbaptstatic void
36446b1dcfbaptresp_begin_html(int code, const char *msg, const char *file)
365c2dfbbduqs{
36646b1dcfbapt	char	*cp;
367c2dfbbduqs
368c2dfbbduqs	resp_begin_http(code, msg);
369c2dfbbduqs
370e00686fbapt	printf("<!DOCTYPE html>\n"
37136a85ccbapt	       "<html>\n"
37236a85ccbapt	       "<head>\n"
373cb61028bapt	       "  <meta charset=\"UTF-8\"/>\n"
374bb683e4bapt	       "  <meta name=\"viewport\""
375bb683e4bapt		      " content=\"width=device-width, initial-scale=1.0\">\n"
376cb61028bapt	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
37736a85ccbapt	       " type=\"text/css\" media=\"all\">\n"
37846b1dcfbapt	       "  <title>",
37946b1dcfbapt	       CSS_DIR);
38046b1dcfbapt	if (file != NULL) {
38146b1dcfbapt		if ((cp = strrchr(file, '/')) != NULL)
38246b1dcfbapt			file = cp + 1;
38346b1dcfbapt		if ((cp = strrchr(file, '.')) != NULL) {
38446b1dcfbapt			printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
38546b1dcfbapt		} else
38646b1dcfbapt			printf("%s - ", file);
38746b1dcfbapt	}
38846b1dcfbapt	printf("%s</title>\n"
38936a85ccbapt	       "</head>\n"
390cb61028bapt	       "<body>\n",
39146b1dcfbapt	       CUSTOMIZE_TITLE);
392877b18fbapt
393877b18fbapt	resp_copy(MAN_DIR "/header.html");
394c2dfbbduqs}
395c2dfbbduqs
396c2dfbbduqsstatic void
397c2dfbbduqsresp_end_html(void)
398c2dfbbduqs{
399c2dfbbduqs
400877b18fbapt	resp_copy(MAN_DIR "/footer.html");
401877b18fbapt
40236a85ccbapt	puts("</body>\n"
40336a85ccbapt	     "</html>");
404c2dfbbduqs}
405c2dfbbduqs
406c2dfbbduqsstatic void
40736a85ccbaptresp_searchform(const struct req *req, enum focus focus)
408c2dfbbduqs{
409c2dfbbduqs	int		 i;
410c2dfbbduqs
411cb61028bapt	printf("<form action=\"/%s\" method=\"get\">\n"
412cb61028bapt	       "  <fieldset>\n"
413cb61028bapt	       "    <legend>Manual Page Search Parameters</legend>\n",
414eb03421bapt	       scriptname);
415eb03421bapt
416eb03421bapt	/* Write query input box. */
417eb03421bapt
418bb683e4bapt	printf("    <input type=\"search\" name=\"query\" value=\"");
41936a85ccbapt	if (req->q.query != NULL)
420eb03421bapt		html_print(req->q.query);
42136a85ccbapt	printf( "\" size=\"40\"");
42236a85ccbapt	if (focus == FOCUS_QUERY)
42336a85ccbapt		printf(" autofocus");
42436a85ccbapt	puts(">");
425eb03421bapt
42636a85ccbapt	/* Write submission buttons. */
427eb03421bapt
428cb61028bapt	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
42936a85ccbapt		"man</button>\n"
430cb61028bapt		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
431cb61028bapt		"apropos</button>\n"
432cb61028bapt		"    <br/>\n");
433eb03421bapt
434eb03421bapt	/* Write section selector. */
435eb03421bapt
436cb61028bapt	puts("    <select name=\"sec\">");
437eb03421bapt	for (i = 0; i < sec_MAX; i++) {
438cb61028bapt		printf("      <option value=\"%s\"", sec_numbers[i]);
439eb03421bapt		if (NULL != req->q.sec &&
440eb03421bapt		    0 == strcmp(sec_numbers[i], req->q.sec))
44136a85ccbapt			printf(" selected=\"selected\"");
44236a85ccbapt		printf(">%s</option>\n", sec_names[i]);
443eb03421bapt	}
444cb61028bapt	puts("    </select>");
445eb03421bapt
446eb03421bapt	/* Write architecture selector. */
447eb03421bapt
448cb61028bapt	printf(	"    <select name=\"arch\">\n"
449cb61028bapt		"      <option value=\"default\"");
450eb03421bapt	if (NULL == req->q.arch)
45136a85ccbapt		printf(" selected=\"selected\"");
45236a85ccbapt	puts(">All Architectures</option>");
453eb03421bapt	for (i = 0; i < arch_MAX; i++) {
454bb683e4bapt		printf("      <option");
455eb03421bapt		if (NULL != req->q.arch &&
456eb03421bapt		    0 == strcmp(arch_names[i], req->q.arch))
45736a85ccbapt			printf(" selected=\"selected\"");
45836a85ccbapt		printf(">%s</option>\n", arch_names[i]);
459eb03421bapt	}
460cb61028bapt	puts("    </select>");
461eb03421bapt
462eb03421bapt	/* Write manpath selector. */
463eb03421bapt
464c2dfbbduqs	if (req->psz > 1) {
465cb61028bapt		puts("    <select name=\"manpath\">");
466c2dfbbduqs		for (i = 0; i < (int)req->psz; i++) {
467bb683e4bapt			printf("      <option");
468e00686fbapt			if (strcmp(req->q.manpath, req->p[i]) == 0)
469bb683e4bapt				printf(" selected=\"selected\"");
470bb683e4bapt			printf(">");
471eb03421bapt			html_print(req->p[i]);
47236a85ccbapt			puts("</option>");
473c2dfbbduqs		}
474cb61028bapt		puts("    </select>");
475c2dfbbduqs	}
476eb03421bapt
477cb61028bapt	puts("  </fieldset>\n"
478cb61028bapt	     "</form>");
479c2dfbbduqs}
480c2dfbbduqs
481eb03421baptstatic int
482eb03421baptvalidate_urifrag(const char *frag)
483c2dfbbduqs{
484c2dfbbduqs
485eb03421bapt	while ('\0' != *frag) {
486eb03421bapt		if ( ! (isalnum((unsigned char)*frag) ||
487eb03421bapt		    '-' == *frag || '.' == *frag ||
488eb03421bapt		    '/' == *frag || '_' == *frag))
489877b18fbapt			return 0;
490eb03421bapt		frag++;
491eb03421bapt	}
492877b18fbapt	return 1;
493c2dfbbduqs}
494c2dfbbduqs
495eb03421baptstatic int
496eb03421baptvalidate_manpath(const struct req *req, const char* manpath)
497c2dfbbduqs{
498eb03421bapt	size_t	 i;
499c2dfbbduqs
500eb03421bapt	for (i = 0; i < req->psz; i++)
501eb03421bapt		if ( ! strcmp(manpath, req->p[i]))
502877b18fbapt			return 1;
503eb03421bapt
504877b18fbapt	return 0;
505eb03421bapt}
506eb03421bapt
507eb03421baptstatic int
50872da6efbaptvalidate_arch(const char *arch)
50972da6efbapt{
51072da6efbapt	int	 i;
51172da6efbapt
51272da6efbapt	for (i = 0; i < arch_MAX; i++)
51372da6efbapt		if (strcmp(arch, arch_names[i]) == 0)
51472da6efbapt			return 1;
51572da6efbapt
51672da6efbapt	return 0;
51772da6efbapt}
51872da6efbapt
51972da6efbaptstatic int
520eb03421baptvalidate_filename(const char *file)
521eb03421bapt{
522eb03421bapt
523eb03421bapt	if ('.' == file[0] && '/' == file[1])
524eb03421bapt		file += 2;
525eb03421bapt
526877b18fbapt	return ! (strstr(file, "../") || strstr(file, "/..") ||
527877b18fbapt	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
528c2dfbbduqs}
529c2dfbbduqs
530c2dfbbduqsstatic void
531eb03421baptpg_index(const struct req *req)
532c2dfbbduqs{
533c2dfbbduqs
53446b1dcfbapt	resp_begin_html(200, NULL, NULL);
53536a85ccbapt	resp_searchform(req, FOCUS_QUERY);
53636a85ccbapt	printf("<p>\n"
537eb03421bapt	       "This web interface is documented in the\n"
538cb61028bapt	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
539eb03421bapt	       "manual, and the\n"
540cb61028bapt	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
541eb03421bapt	       "manual explains the query syntax.\n"
54236a85ccbapt	       "</p>\n",
54336a85ccbapt	       scriptname, *scriptname == '\0' ? "" : "/",
54436a85ccbapt	       scriptname, *scriptname == '\0' ? "" : "/");
545c2dfbbduqs	resp_end_html();
546c2dfbbduqs}
547c2dfbbduqs
548c2dfbbduqsstatic void
549eb03421baptpg_noresult(const struct req *req, const char *msg)
550c2dfbbduqs{
55146b1dcfbapt	resp_begin_html(200, NULL, NULL);
55236a85ccbapt	resp_searchform(req, FOCUS_QUERY);
55336a85ccbapt	puts("<p>");
554eb03421bapt	puts(msg);
55536a85ccbapt	puts("</p>");
556c2dfbbduqs	resp_end_html();
557c2dfbbduqs}
558c2dfbbduqs
559c2dfbbduqsstatic void
560eb03421baptpg_error_badrequest(const char *msg)
561c2dfbbduqs{
562c2dfbbduqs
56346b1dcfbapt	resp_begin_html(400, "Bad Request", NULL);
56436a85ccbapt	puts("<h1>Bad Request</h1>\n"
56536a85ccbapt	     "<p>\n");
566eb03421bapt	puts(msg);
567eb03421bapt	printf("Try again from the\n"
56836a85ccbapt	       "<a href=\"/%s\">main page</a>.\n"
56936a85ccbapt	       "</p>", scriptname);
570c2dfbbduqs	resp_end_html();
571c2dfbbduqs}
572c2dfbbduqs
573c2dfbbduqsstatic void
574eb03421baptpg_error_internal(void)
575c2dfbbduqs{
57646b1dcfbapt	resp_begin_html(500, "Internal Server Error", NULL);
57736a85ccbapt	puts("<p>Internal Server Error</p>");
578eb03421bapt	resp_end_html();
579eb03421bapt}
580c2dfbbduqs
581eb03421baptstatic void
58246b1dcfbaptpg_redirect(const struct req *req, const char *name)
58346b1dcfbapt{
58446b1dcfbapt	printf("Status: 303 See Other\r\n"
58546b1dcfbapt	    "Location: /");
58646b1dcfbapt	if (*scriptname != '\0')
58746b1dcfbapt		printf("%s/", scriptname);
58846b1dcfbapt	if (strcmp(req->q.manpath, req->p[0]))
58946b1dcfbapt		printf("%s/", req->q.manpath);
59046b1dcfbapt	if (req->q.arch != NULL)
59146b1dcfbapt		printf("%s/", req->q.arch);
59272da6efbapt	http_encode(name);
59372da6efbapt	if (req->q.sec != NULL) {
59472da6efbapt		putchar('.');
59572da6efbapt		http_encode(req->q.sec);
59672da6efbapt	}
59746b1dcfbapt	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
59846b1dcfbapt}
59946b1dcfbapt
60046b1dcfbaptstatic void
601eb03421baptpg_searchres(const struct req *req, struct manpage *r, size_t sz)
602eb03421bapt{
603eb03421bapt	char		*arch, *archend;
60436a85ccbapt	const char	*sec;
60536a85ccbapt	size_t		 i, iuse;
606eb03421bapt	int		 archprio, archpriouse;
607eb03421bapt	int		 prio, priouse;
608c2dfbbduqs
609eb03421bapt	for (i = 0; i < sz; i++) {
610eb03421bapt		if (validate_filename(r[i].file))
611eb03421bapt			continue;
61236a85ccbapt		warnx("invalid filename %s in %s database",
613eb03421bapt		    r[i].file, req->q.manpath);
614eb03421bapt		pg_error_internal();
615eb03421bapt		return;
616eb03421bapt	}
617c2dfbbduqs
61836a85ccbapt	if (req->isquery && sz == 1) {
619c2dfbbduqs		/*
620c2dfbbduqs		 * If we have just one result, then jump there now
621c2dfbbduqs		 * without any delay.
622c2dfbbduqs		 */
62346b1dcfbapt		printf("Status: 303 See Other\r\n"
62446b1dcfbapt		    "Location: /");
62546b1dcfbapt		if (*scriptname != '\0')
62646b1dcfbapt			printf("%s/", scriptname);
62746b1dcfbapt		if (strcmp(req->q.manpath, req->p[0]))
62846b1dcfbapt			printf("%s/", req->q.manpath);
62946b1dcfbapt		printf("%s\r\n"
63046b1dcfbapt		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
63146b1dcfbapt		    r[0].file);
632c2dfbbduqs		return;
633c2dfbbduqs	}
634c2dfbbduqs
635eb03421bapt	/*
636eb03421bapt	 * In man(1) mode, show one of the pages
637eb03421bapt	 * even if more than one is found.
638eb03421bapt	 */
639c2dfbbduqs
64046b1dcfbapt	iuse = 0;
64136a85ccbapt	if (req->q.equal || sz == 1) {
64236a85ccbapt		priouse = 20;
643eb03421bapt		archpriouse = 3;
644eb03421bapt		for (i = 0; i < sz; i++) {
64536a85ccbapt			sec = r[i].file;
64636a85ccbapt			sec += strcspn(sec, "123456789");
64736a85ccbapt			if (sec[0] == '\0')
648eb03421bapt				continue;
64936a85ccbapt			prio = sec_prios[sec[0] - '1'];
65036a85ccbapt			if (sec[1] != '/')
65136a85ccbapt				prio += 10;
65236a85ccbapt			if (req->q.arch == NULL) {
653eb03421bapt				archprio =
65436a85ccbapt				    ((arch = strchr(sec + 1, '/'))
65536a85ccbapt					== NULL) ? 3 :
65636a85ccbapt				    ((archend = strchr(arch + 1, '/'))
65736a85ccbapt					== NULL) ? 0 :
658eb03421bapt				    strncmp(arch, "amd64/",
659eb03421bapt					archend - arch) ? 2 : 1;
660eb03421bapt				if (archprio < archpriouse) {
661eb03421bapt					archpriouse = archprio;
662eb03421bapt					priouse = prio;
663eb03421bapt					iuse = i;
664eb03421bapt					continue;
665eb03421bapt				}
666eb03421bapt				if (archprio > archpriouse)
667eb03421bapt					continue;
668eb03421bapt			}
669eb03421bapt			if (prio >= priouse)
670eb03421bapt				continue;
671eb03421bapt			priouse = prio;
672eb03421bapt			iuse = i;
673eb03421bapt		}
67446b1dcfbapt		resp_begin_html(200, NULL, r[iuse].file);
67546b1dcfbapt	} else
67646b1dcfbapt		resp_begin_html(200, NULL, NULL);
67746b1dcfbapt
67846b1dcfbapt	resp_searchform(req,
67946b1dcfbapt	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
68046b1dcfbapt
68146b1dcfbapt	if (sz > 1) {
68246b1dcfbapt		puts("<table class=\"results\">");
68346b1dcfbapt		for (i = 0; i < sz; i++) {
68446b1dcfbapt			printf("  <tr>\n"
68546b1dcfbapt			       "    <td>"
68646b1dcfbapt			       "<a class=\"Xr\" href=\"/");
68746b1dcfbapt			if (*scriptname != '\0')
68846b1dcfbapt				printf("%s/", scriptname);
68946b1dcfbapt			if (strcmp(req->q.manpath, req->p[0]))
69046b1dcfbapt				printf("%s/", req->q.manpath);
69146b1dcfbapt			printf("%s\">", r[i].file);
69246b1dcfbapt			html_print(r[i].names);
69346b1dcfbapt			printf("</a></td>\n"
69446b1dcfbapt			       "    <td><span class=\"Nd\">");
69546b1dcfbapt			html_print(r[i].output);
69646b1dcfbapt			puts("</span></td>\n"
69746b1dcfbapt			     "  </tr>");
69846b1dcfbapt		}
69946b1dcfbapt		puts("</table>");
70046b1dcfbapt	}
70146b1dcfbapt
70246b1dcfbapt	if (req->q.equal || sz == 1) {
70346b1dcfbapt		puts("<hr>");
704eb03421bapt		resp_show(req, r[iuse].file);
705eb03421bapt	}
706eb03421bapt
707eb03421bapt	resp_end_html();
708c2dfbbduqs}
709c2dfbbduqs
710c2dfbbduqsstatic void
71136a85ccbaptresp_catman(const struct req *req, const char *file)
712c2dfbbduqs{
713c2dfbbduqs	FILE		*f;
714c2dfbbduqs	char		*p;
715877b18fbapt	size_t		 sz;
716877b18fbapt	ssize_t		 len;
717877b18fbapt	int		 i;
718c2dfbbduqs	int		 italic, bold;
719c2dfbbduqs
720877b18fbapt	if ((f = fopen(file, "r")) == NULL) {
72136a85ccbapt		puts("<p>You specified an invalid manual file.</p>");
722c2dfbbduqs		return;
723c2dfbbduqs	}
724c2dfbbduqs
72536a85ccbapt	puts("<div class=\"catman\">\n"
72636a85ccbapt	     "<pre>");
727c2dfbbduqs
728877b18fbapt	p = NULL;
729877b18fbapt	sz = 0;
730877b18fbapt
731877b18fbapt	while ((len = getline(&p, &sz, f)) != -1) {
732c2dfbbduqs		bold = italic = 0;
733877b18fbapt		for (i = 0; i < len - 1; i++) {
734ecbd8afbapt			/*
735c2dfbbduqs			 * This means that the catpage is out of state.
736c2dfbbduqs			 * Ignore it and keep going (although the
737c2dfbbduqs			 * catpage is bogus).
738c2dfbbduqs			 */
739c2dfbbduqs
740c2dfbbduqs			if ('\b' == p[i] || '\n' == p[i])
741c2dfbbduqs				continue;
742c2dfbbduqs
743c2dfbbduqs			/*
744c2dfbbduqs			 * Print a regular character.
745c2dfbbduqs			 * Close out any bold/italic scopes.
746c2dfbbduqs			 * If we're in back-space mode, make sure we'll
747c2dfbbduqs			 * have something to enter when we backspace.
748c2dfbbduqs			 */
749c2dfbbduqs
750c2dfbbduqs			if ('\b' != p[i + 1]) {
751c2dfbbduqs				if (italic)
75236a85ccbapt					printf("</i>");
753c2dfbbduqs				if (bold)
75436a85ccbapt					printf("</b>");
755c2dfbbduqs				italic = bold = 0;
756c2dfbbduqs				html_putchar(p[i]);
757c2dfbbduqs				continue;
758877b18fbapt			} else if (i + 2 >= len)
759c2dfbbduqs				continue;
760c2dfbbduqs
761c2dfbbduqs			/* Italic mode. */
762c2dfbbduqs
763c2dfbbduqs			if ('_' == p[i]) {
764c2dfbbduqs				if (bold)
76536a85ccbapt					printf("</b>");
766c2dfbbduqs				if ( ! italic)
76736a85ccbapt					printf("<i>");
768c2dfbbduqs				bold = 0;
769c2dfbbduqs				italic = 1;
770c2dfbbduqs				i += 2;
771c2dfbbduqs				html_putchar(p[i]);
772c2dfbbduqs				continue;
773c2dfbbduqs			}
774c2dfbbduqs
775ecbd8afbapt			/*
776c2dfbbduqs			 * Handle funny behaviour troff-isms.
777c2dfbbduqs			 * These grok'd from the original man2html.c.
778c2dfbbduqs			 */
779c2dfbbduqs
780c2dfbbduqs			if (('+' == p[i] && 'o' == p[i + 2]) ||
781c2dfbbduqs					('o' == p[i] && '+' == p[i + 2]) ||
782c2dfbbduqs					('|' == p[i] && '=' == p[i + 2]) ||
783c2dfbbduqs					('=' == p[i] && '|' == p[i + 2]) ||
784c2dfbbduqs					('*' == p[i] && '=' == p[i + 2]) ||
785c2dfbbduqs					('=' == p[i] && '*' == p[i + 2]) ||
786c2dfbbduqs					('*' == p[i] && '|' == p[i + 2]) ||
787c2dfbbduqs					('|' == p[i] && '*' == p[i + 2]))  {
788c2dfbbduqs				if (italic)
78936a85ccbapt					printf("</i>");
790c2dfbbduqs				if (bold)
79136a85ccbapt					printf("</b>");
792c2dfbbduqs				italic = bold = 0;
793c2dfbbduqs				putchar('*');
794c2dfbbduqs				i += 2;
795c2dfbbduqs				continue;
796c2dfbbduqs			} else if (('|' == p[i] && '-' == p[i + 2]) ||
797c2dfbbduqs					('-' == p[i] && '|' == p[i + 1]) ||
798c2dfbbduqs					('+' == p[i] && '-' == p[i + 1]) ||
799c2dfbbduqs					('-' == p[i] && '+' == p[i + 1]) ||
800c2dfbbduqs					('+' == p[i] && '|' == p[i + 1]) ||
801c2dfbbduqs					('|' == p[i] && '+' == p[i + 1]))  {
802c2dfbbduqs				if (italic)
80336a85ccbapt					printf("</i>");
804c2dfbbduqs				if (bold)
80536a85ccbapt					printf("</b>");
806c2dfbbduqs				italic = bold = 0;
807c2dfbbduqs				putchar('+');
808c2dfbbduqs				i += 2;
809c2dfbbduqs				continue;
810c2dfbbduqs			}
811c2dfbbduqs
812c2dfbbduqs			/* Bold mode. */
813ecbd8afbapt
814c2dfbbduqs			if (italic)
81536a85ccbapt				printf("</i>");
816c2dfbbduqs			if ( ! bold)
81736a85ccbapt				printf("<b>");
818c2dfbbduqs			bold = 1;
819c2dfbbduqs			italic = 0;
820c2dfbbduqs			i += 2;
821c2dfbbduqs			html_putchar(p[i]);
822c2dfbbduqs		}
823c2dfbbduqs
824ecbd8afbapt		/*
825c2dfbbduqs		 * Clean up the last character.
826ecbd8afbapt		 * We can get to a newline; don't print that.
827c2dfbbduqs		 */
828c2dfbbduqs
829c2dfbbduqs		if (italic)
83036a85ccbapt			printf("</i>");
831c2dfbbduqs		if (bold)
83236a85ccbapt			printf("</b>");
833c2dfbbduqs
834877b18fbapt		if (i == len - 1 && p[i] != '\n')
835c2dfbbduqs			html_putchar(p[i]);
836c2dfbbduqs
837c2dfbbduqs		putchar('\n');
838c2dfbbduqs	}
839877b18fbapt	free(p);
840c2dfbbduqs
84136a85ccbapt	puts("</pre>\n"
84236a85ccbapt	     "</div>");
843c2dfbbduqs
844c2dfbbduqs	fclose(f);
845c2dfbbduqs}
846c2dfbbduqs
847c2dfbbduqsstatic void
84836a85ccbaptresp_format(const struct req *req, const char *file)
849c2dfbbduqs{
850877b18fbapt	struct manoutput conf;
851c2dfbbduqs	struct mparse	*mp;
85272da6efbapt	struct roff_meta *meta;
853c2dfbbduqs	void		*vp;
854eb03421bapt	int		 fd;
855eb03421bapt	int		 usepath;
856c2dfbbduqs
857c2dfbbduqs	if (-1 == (fd = open(file, O_RDONLY, 0))) {
85836a85ccbapt		puts("<p>You specified an invalid manual file.</p>");
859c2dfbbduqs		return;
860c2dfbbduqs	}
861c2dfbbduqs
862877b18fbapt	mchars_alloc();
86372da6efbapt	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
86472da6efbapt	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
865ecbd8afbapt	mparse_readfd(mp, fd, file);
866c2dfbbduqs	close(fd);
86772da6efbapt	meta = mparse_result(mp);
868c2dfbbduqs
869877b18fbapt	memset(&conf, 0, sizeof(conf));
870877b18fbapt	conf.fragment = 1;
871cd7178dbapt	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
872eb03421bapt	usepath = strcmp(req->q.manpath, req->p[0]);
87346b1dcfbapt	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
87446b1dcfbapt	    scriptname, *scriptname == '\0' ? "" : "/",
87536a85ccbapt	    usepath ? req->q.manpath : "", usepath ? "/" : "");
876c2dfbbduqs
877877b18fbapt	vp = html_alloc(&conf);
87872da6efbapt	if (meta->macroset == MACROSET_MDOC)
87972da6efbapt		html_mdoc(vp, meta);
88072da6efbapt	else
88172da6efbapt		html_man(vp, meta);
882c2dfbbduqs
883c2dfbbduqs	html_free(vp);
884c2dfbbduqs	mparse_free(mp);
885877b18fbapt	mchars_free();
886877b18fbapt	free(conf.man);
887cd7178dbapt	free(conf.style);
888eb03421bapt}
889eb03421bapt
890eb03421baptstatic void
891eb03421baptresp_show(const struct req *req, const char *file)
892eb03421bapt{
893eb03421bapt
894eb03421bapt	if ('.' == file[0] && '/' == file[1])
895eb03421bapt		file += 2;
896eb03421bapt
897eb03421bapt	if ('c' == *file)
89836a85ccbapt		resp_catman(req, file);
899eb03421bapt	else
90036a85ccbapt		resp_format(req, file);
901c2dfbbduqs}
902c2dfbbduqs
903c2dfbbduqsstatic void
904eb03421baptpg_show(struct req *req, const char *fullpath)
905c2dfbbduqs{
906eb03421bapt	char		*manpath;
907eb03421bapt	const char	*file;
908eb03421bapt
909eb03421bapt	if ((file = strchr(fullpath, '/')) == NULL) {
910eb03421bapt		pg_error_badrequest(
911eb03421bapt		    "You did not specify a page to show.");
912c2dfbbduqs		return;
913ecbd8afbapt	}
914eb03421bapt	manpath = mandoc_strndup(fullpath, file - fullpath);
915eb03421bapt	file++;
916eb03421bapt
917eb03421bapt	if ( ! validate_manpath(req, manpath)) {
918eb03421bapt		pg_error_badrequest(
919eb03421bapt		    "You specified an invalid manpath.");
920eb03421bapt		free(manpath);
921c2dfbbduqs		return;
922c2dfbbduqs	}
923c2dfbbduqs
924c2dfbbduqs	/*
925eb03421bapt	 * Begin by chdir()ing into the manpath.
926c2dfbbduqs	 * This way we can pick up the database files, which are
927c2dfbbduqs	 * relative to the manpath root.
928c2dfbbduqs	 */
929c2dfbbduqs
930eb03421bapt	if (chdir(manpath) == -1) {
93136a85ccbapt		warn("chdir %s", manpath);
932eb03421bapt		pg_error_internal();
933eb03421bapt		free(manpath);
934c2dfbbduqs		return;
935c2dfbbduqs	}
93636a85ccbapt	free(manpath);
937c2dfbbduqs
938eb03421bapt	if ( ! validate_filename(file)) {
939eb03421bapt		pg_error_badrequest(
940eb03421bapt		    "You specified an invalid manual file.");
941eb03421bapt		return;
942c2dfbbduqs	}
943c2dfbbduqs
94446b1dcfbapt	resp_begin_html(200, NULL, file);
94536a85ccbapt	resp_searchform(req, FOCUS_NONE);
946eb03421bapt	resp_show(req, file);
947eb03421bapt	resp_end_html();
948c2dfbbduqs}
949c2dfbbduqs
950c2dfbbduqsstatic void
951eb03421baptpg_search(const struct req *req)
952c2dfbbduqs{
953eb03421bapt	struct mansearch	  search;
954eb03421bapt	struct manpaths		  paths;
955eb03421bapt	struct manpage		 *res;
956e00686fbapt	char			**argv;
957e00686fbapt	char			 *query, *rp, *wp;
958eb03421bapt	size_t			  ressz;
959e00686fbapt	int			  argc;
960c2dfbbduqs
961c2dfbbduqs	/*
962c2dfbbduqs	 * Begin by chdir()ing into the root of the manpath.
963c2dfbbduqs	 * This way we can pick up the database files, which are
964c2dfbbduqs	 * relative to the manpath root.
965c2dfbbduqs	 */
966c2dfbbduqs
96736a85ccbapt	if (chdir(req->q.manpath) == -1) {
96836a85ccbapt		warn("chdir %s", req->q.manpath);
969eb03421bapt		pg_error_internal();
970c2dfbbduqs		return;
971c2dfbbduqs	}
972c2dfbbduqs
973eb03421bapt	search.arch = req->q.arch;
974eb03421bapt	search.sec = req->q.sec;
975e00686fbapt	search.outkey = "Nd";
976e00686fbapt	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
977e00686fbapt	search.firstmatch = 1;
978eb03421bapt
979eb03421bapt	paths.sz = 1;
980eb03421bapt	paths.paths = mandoc_malloc(sizeof(char *));
981eb03421bapt	paths.paths[0] = mandoc_strdup(".");
982c2dfbbduqs
983c2dfbbduqs	/*
984e00686fbapt	 * Break apart at spaces with backslash-escaping.
985c2dfbbduqs	 */
986c2dfbbduqs
987e00686fbapt	argc = 0;
988e00686fbapt	argv = NULL;
989e00686fbapt	rp = query = mandoc_strdup(req->q.query);
990e00686fbapt	for (;;) {
991e00686fbapt		while (isspace((unsigned char)*rp))
992e00686fbapt			rp++;
993e00686fbapt		if (*rp == '\0')
994e00686fbapt			break;
995e00686fbapt		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
996e00686fbapt		argv[argc++] = wp = rp;
997e00686fbapt		for (;;) {
998e00686fbapt			if (isspace((unsigned char)*rp)) {
999e00686fbapt				*wp = '\0';
1000e00686fbapt				rp++;
1001e00686fbapt				break;
1002e00686fbapt			}
1003e00686fbapt			if (rp[0] == '\\' && rp[1] != '\0')
1004e00686fbapt				rp++;
1005e00686fbapt			if (wp != rp)
1006e00686fbapt				*wp = *rp;
1007e00686fbapt			if (*rp == '\0')
1008e00686fbapt				break;
1009e00686fbapt			wp++;
1010e00686fbapt			rp++;
1011e00686fbapt		}
1012c2dfbbduqs	}
1013c2dfbbduqs
101446b1dcfbapt	res = NULL;
101546b1dcfbapt	ressz = 0;
101646b1dcfbapt	if (req->isquery && req->q.equal && argc == 1)
101746b1dcfbapt		pg_redirect(req, argv[0]);
101846b1dcfbapt	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1019eb03421bapt		pg_noresult(req, "You entered an invalid query.");
102046b1dcfbapt	else if (ressz == 0)
1021eb03421bapt		pg_noresult(req, "No results found.");
1022eb03421bapt	else
1023eb03421bapt		pg_searchres(req, res, ressz);
1024c2dfbbduqs
1025e00686fbapt	free(query);
1026e00686fbapt	mansearch_free(res, ressz);
1027eb03421bapt	free(paths.paths[0]);
1028eb03421bapt	free(paths.paths);
1029c2dfbbduqs}
1030c2dfbbduqs
1031c2dfbbduqsint
1032c2dfbbduqsmain(void)
1033c2dfbbduqs{
1034c2dfbbduqs	struct req	 req;
1035e00686fbapt	struct itimerval itimer;
1036eb03421bapt	const char	*path;
1037eb03421bapt	const char	*querystring;
1038eb03421bapt	int		 i;
1039c2dfbbduqs
104046b1dcfbapt#if HAVE_PLEDGE
104146b1dcfbapt	/*
104246b1dcfbapt	 * The "rpath" pledge could be revoked after mparse_readfd()
104346b1dcfbapt	 * if the file desciptor to "/footer.html" would be opened
104446b1dcfbapt	 * up front, but it's probably not worth the complication
104546b1dcfbapt	 * of the code it would cause: it would require scattering
104646b1dcfbapt	 * pledge() calls in multiple low-level resp_*() functions.
104746b1dcfbapt	 */
104846b1dcfbapt
104946b1dcfbapt	if (pledge("stdio rpath", NULL) == -1) {
105046b1dcfbapt		warn("pledge");
105146b1dcfbapt		pg_error_internal();
105246b1dcfbapt		return EXIT_FAILURE;
105346b1dcfbapt	}
105446b1dcfbapt#endif
105546b1dcfbapt
1056e00686fbapt	/* Poor man's ReDoS mitigation. */
1057e00686fbapt
1058e00686fbapt	itimer.it_value.tv_sec = 2;
1059e00686fbapt	itimer.it_value.tv_usec = 0;
1060e00686fbapt	itimer.it_interval.tv_sec = 2;
1061e00686fbapt	itimer.it_interval.tv_usec = 0;
1062e00686fbapt	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
106336a85ccbapt		warn("setitimer");
1064eb03421bapt		pg_error_internal();
1065877b18fbapt		return EXIT_FAILURE;
1066eb03421bapt	}
1067c2dfbbduqs
1068c2dfbbduqs	/*
1069eb03421bapt	 * First we change directory into the MAN_DIR so that
1070c2dfbbduqs	 * subsequent scanning for manpath directories is rooted
1071c2dfbbduqs	 * relative to the same position.
1072c2dfbbduqs	 */
1073c2dfbbduqs
107436a85ccbapt	if (chdir(MAN_DIR) == -1) {
107536a85ccbapt		warn("MAN_DIR: %s", MAN_DIR);
1076eb03421bapt		pg_error_internal();
1077877b18fbapt		return EXIT_FAILURE;
1078ecbd8afbapt	}
1079c2dfbbduqs
1080c2dfbbduqs	memset(&req, 0, sizeof(struct req));
108136a85ccbapt	req.q.equal = 1;
108236a85ccbapt	parse_manpath_conf(&req);
1083c2dfbbduqs
108436a85ccbapt	/* Parse the path info and the query string. */
1085c2dfbbduqs
108636a85ccbapt	if ((path = getenv("PATH_INFO")) == NULL)
108736a85ccbapt		path = "";
108836a85ccbapt	else if (*path == '/')
108936a85ccbapt		path++;
109036a85ccbapt
109136a85ccbapt	if (*path != '\0') {
109236a85ccbapt		parse_path_info(&req, path);
109346b1dcfbapt		if (req.q.manpath == NULL || req.q.sec == NULL ||
109446b1dcfbapt		    *req.q.query == '\0' || access(path, F_OK) == -1)
109536a85ccbapt			path = "";
109636a85ccbapt	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
109736a85ccbapt		parse_query_string(&req, querystring);
109836a85ccbapt
109936a85ccbapt	/* Validate parsed data and add defaults. */
1100c2dfbbduqs
1101e00686fbapt	if (req.q.manpath == NULL)
1102e00686fbapt		req.q.manpath = mandoc_strdup(req.p[0]);
1103e00686fbapt	else if ( ! validate_manpath(&req, req.q.manpath)) {
1104eb03421bapt		pg_error_badrequest(
1105eb03421bapt		    "You specified an invalid manpath.");
1106877b18fbapt		return EXIT_FAILURE;
1107c2dfbbduqs	}
1108c2dfbbduqs
110972da6efbapt	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1110eb03421bapt		pg_error_badrequest(
1111eb03421bapt		    "You specified an invalid architecture.");
1112877b18fbapt		return EXIT_FAILURE;
1113c2dfbbduqs	}
1114c2dfbbduqs
1115eb03421bapt	/* Dispatch to the three different pages. */
1116c2dfbbduqs
1117eb03421bapt	if ('\0' != *path)
1118eb03421bapt		pg_show(&req, path);
1119eb03421bapt	else if (NULL != req.q.query)
1120eb03421bapt		pg_search(&req);
1121eb03421bapt	else
1122eb03421bapt		pg_index(&req);
1123eb03421bapt
1124eb03421bapt	free(req.q.manpath);
1125eb03421bapt	free(req.q.arch);
1126eb03421bapt	free(req.q.sec);
1127eb03421bapt	free(req.q.query);
1128eb03421bapt	for (i = 0; i < (int)req.psz; i++)
1129eb03421bapt		free(req.p[i]);
1130eb03421bapt	free(req.p);
1131877b18fbapt	return EXIT_SUCCESS;
1132c2dfbbduqs}
1133c2dfbbduqs
1134c2dfbbduqs/*
113572da6efbapt * Translate PATH_INFO to a query.
113636a85ccbapt */
113736a85ccbaptstatic void
113836a85ccbaptparse_path_info(struct req *req, const char *path)
113936a85ccbapt{
114072da6efbapt	const char	*name, *sec, *end;
114136a85ccbapt
114236a85ccbapt	req->isquery = 0;
114336a85ccbapt	req->q.equal = 1;
114472da6efbapt	req->q.manpath = NULL;
114536a85ccbapt	req->q.arch = NULL;
114636a85ccbapt
114736a85ccbapt	/* Mandatory manual page name. */
114872da6efbapt	if ((name = strrchr(path, '/')) == NULL)
114972da6efbapt		name = path;
115072da6efbapt	else
115172da6efbapt		name++;
115236a85ccbapt
115336a85ccbapt	/* Optional trailing section. */
115472da6efbapt	sec = strrchr(name, '.');
115572da6efbapt	if (sec != NULL && isdigit((unsigned char)*++sec)) {
115672da6efbapt		req->q.query = mandoc_strndup(name, sec - name - 1);
115772da6efbapt		req->q.sec = mandoc_strdup(sec);
115872da6efbapt	} else {
115972da6efbapt		req->q.query = mandoc_strdup(name);
116072da6efbapt		req->q.sec = NULL;
116136a85ccbapt	}
116236a85ccbapt
116336a85ccbapt	/* Handle the case of name[.section] only. */
116472da6efbapt	if (name == path)
116536a85ccbapt		return;
116636a85ccbapt
116736a85ccbapt	/* Optional manpath. */
116872da6efbapt	end = strchr(path, '/');
116972da6efbapt	req->q.manpath = mandoc_strndup(path, end - path);
117072da6efbapt	if (validate_manpath(req, req->q.manpath)) {
117172da6efbapt		path = end + 1;
117272da6efbapt		if (name == path)
117372da6efbapt			return;
117472da6efbapt	} else {
117572da6efbapt		free(req->q.manpath);
117636a85ccbapt		req->q.manpath = NULL;
117772da6efbapt	}
117836a85ccbapt
117936a85ccbapt	/* Optional section. */
118072da6efbapt	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
118172da6efbapt		path += 3;
118272da6efbapt		end = strchr(path, '/');
118336a85ccbapt		free(req->q.sec);
118472da6efbapt		req->q.sec = mandoc_strndup(path, end - path);
118572da6efbapt		path = end + 1;
118672da6efbapt		if (name == path)
118772da6efbapt			return;
118836a85ccbapt	}
118972da6efbapt
119072da6efbapt	/* Optional architecture. */
119172da6efbapt	end = strchr(path, '/');
119272da6efbapt	if (end + 1 != name) {
119372da6efbapt		pg_error_badrequest(
119472da6efbapt		    "You specified too many directory components.");
119572da6efbapt		exit(EXIT_FAILURE);
119636a85ccbapt	}
119772da6efbapt	req->q.arch = mandoc_strndup(path, end - path);
119872da6efbapt	if (validate_arch(req->q.arch) == 0) {
119936a85ccbapt		pg_error_badrequest(
120036a85ccbapt		    "You specified an invalid directory component.");
120136a85ccbapt		exit(EXIT_FAILURE);
120236a85ccbapt	}
120336a85ccbapt}
120436a85ccbapt
120536a85ccbapt/*
1206c2dfbbduqs * Scan for indexable paths.
1207c2dfbbduqs */
1208c2dfbbduqsstatic void
120936a85ccbaptparse_manpath_conf(struct req *req)
1210c2dfbbduqs{
1211eb03421bapt	FILE	*fp;
1212eb03421bapt	char	*dp;
1213eb03421bapt	size_t	 dpsz;
1214877b18fbapt	ssize_t	 len;
1215eb03421bapt
121636a85ccbapt	if ((fp = fopen("manpath.conf", "r")) == NULL) {
121736a85ccbapt		warn("%s/manpath.conf", MAN_DIR);
1218eb03421bapt		pg_error_internal();
1219eb03421bapt		exit(EXIT_FAILURE);
1220c2dfbbduqs	}
1221c2dfbbduqs
1222877b18fbapt	dp = NULL;
1223877b18fbapt	dpsz = 0;
1224877b18fbapt
1225877b18fbapt	while ((len = getline(&dp, &dpsz, fp)) != -1) {
1226877b18fbapt		if (dp[len - 1] == '\n')
1227877b18fbapt			dp[--len] = '\0';
1228eb03421bapt		req->p = mandoc_realloc(req->p,
1229eb03421bapt		    (req->psz + 1) * sizeof(char *));
1230eb03421bapt		if ( ! validate_urifrag(dp)) {
123136a85ccbapt			warnx("%s/manpath.conf contains "
123236a85ccbapt			    "unsafe path \"%s\"", MAN_DIR, dp);
1233eb03421bapt			pg_error_internal();
1234eb03421bapt			exit(EXIT_FAILURE);
1235eb03421bapt		}
123636a85ccbapt		if (strchr(dp, '/') != NULL) {
123736a85ccbapt			warnx("%s/manpath.conf contains "
123836a85ccbapt			    "path with slash \"%s\"", MAN_DIR, dp);
1239eb03421bapt			pg_error_internal();
1240eb03421bapt			exit(EXIT_FAILURE);
1241c2dfbbduqs		}
1242eb03421bapt		req->p[req->psz++] = dp;
1243877b18fbapt		dp = NULL;
1244877b18fbapt		dpsz = 0;
1245eb03421bapt	}
1246877b18fbapt	free(dp);
1247c2dfbbduqs
124836a85ccbapt	if (req->p == NULL) {
124936a85ccbapt		warnx("%s/manpath.conf is empty", MAN_DIR);
1250eb03421bapt		pg_error_internal();
1251eb03421bapt		exit(EXIT_FAILURE);
1252c2dfbbduqs	}
1253c2dfbbduqs}
1254