xref: /freebsd/contrib/mandoc/cgi.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
1*c1c95addSBrooks Davis /* $Id: cgi.c,v 1.181 2023/04/28 19:11:03 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*c1c95addSBrooks Davis  * Copyright (c) 2014-2019, 2021, 2022 Ingo Schwarze <schwarze@usta.de>
461d06d6bSBaptiste Daroussin  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5*c1c95addSBrooks Davis  * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
661d06d6bSBaptiste Daroussin  *
761d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
861d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
961d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
1061d06d6bSBaptiste Daroussin  *
1161d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1261d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1361d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1461d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1561d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1661d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1761d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
186d38604fSBaptiste Daroussin  *
196d38604fSBaptiste Daroussin  * Implementation of the man.cgi(8) program.
2061d06d6bSBaptiste Daroussin  */
2161d06d6bSBaptiste Daroussin #include "config.h"
2261d06d6bSBaptiste Daroussin 
2361d06d6bSBaptiste Daroussin #include <sys/types.h>
2461d06d6bSBaptiste Daroussin #include <sys/time.h>
2561d06d6bSBaptiste Daroussin 
2661d06d6bSBaptiste Daroussin #include <ctype.h>
2761d06d6bSBaptiste Daroussin #if HAVE_ERR
2861d06d6bSBaptiste Daroussin #include <err.h>
2961d06d6bSBaptiste Daroussin #endif
3061d06d6bSBaptiste Daroussin #include <errno.h>
3161d06d6bSBaptiste Daroussin #include <fcntl.h>
3261d06d6bSBaptiste Daroussin #include <limits.h>
3361d06d6bSBaptiste Daroussin #include <stdint.h>
3461d06d6bSBaptiste Daroussin #include <stdio.h>
3561d06d6bSBaptiste Daroussin #include <stdlib.h>
3661d06d6bSBaptiste Daroussin #include <string.h>
3761d06d6bSBaptiste Daroussin #include <unistd.h>
3861d06d6bSBaptiste Daroussin 
3961d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
4061d06d6bSBaptiste Daroussin #include "mandoc.h"
4161d06d6bSBaptiste Daroussin #include "roff.h"
4261d06d6bSBaptiste Daroussin #include "mdoc.h"
4361d06d6bSBaptiste Daroussin #include "man.h"
447295610fSBaptiste Daroussin #include "mandoc_parse.h"
4561d06d6bSBaptiste Daroussin #include "main.h"
4661d06d6bSBaptiste Daroussin #include "manconf.h"
4761d06d6bSBaptiste Daroussin #include "mansearch.h"
4861d06d6bSBaptiste Daroussin #include "cgi.h"
4961d06d6bSBaptiste Daroussin 
5061d06d6bSBaptiste Daroussin /*
5161d06d6bSBaptiste Daroussin  * A query as passed to the search function.
5261d06d6bSBaptiste Daroussin  */
5361d06d6bSBaptiste Daroussin struct	query {
5461d06d6bSBaptiste Daroussin 	char		*manpath; /* desired manual directory */
5561d06d6bSBaptiste Daroussin 	char		*arch; /* architecture */
5661d06d6bSBaptiste Daroussin 	char		*sec; /* manual section */
5761d06d6bSBaptiste Daroussin 	char		*query; /* unparsed query expression */
5861d06d6bSBaptiste Daroussin 	int		 equal; /* match whole names, not substrings */
5961d06d6bSBaptiste Daroussin };
6061d06d6bSBaptiste Daroussin 
6161d06d6bSBaptiste Daroussin struct	req {
6261d06d6bSBaptiste Daroussin 	struct query	  q;
6361d06d6bSBaptiste Daroussin 	char		**p; /* array of available manpaths */
6461d06d6bSBaptiste Daroussin 	size_t		  psz; /* number of available manpaths */
6561d06d6bSBaptiste Daroussin 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
6661d06d6bSBaptiste Daroussin };
6761d06d6bSBaptiste Daroussin 
6861d06d6bSBaptiste Daroussin enum	focus {
6961d06d6bSBaptiste Daroussin 	FOCUS_NONE = 0,
7061d06d6bSBaptiste Daroussin 	FOCUS_QUERY
7161d06d6bSBaptiste Daroussin };
7261d06d6bSBaptiste Daroussin 
7361d06d6bSBaptiste Daroussin static	void		 html_print(const char *);
7461d06d6bSBaptiste Daroussin static	void		 html_putchar(char);
7561d06d6bSBaptiste Daroussin static	int		 http_decode(char *);
766d38604fSBaptiste Daroussin static	void		 http_encode(const char *);
7761d06d6bSBaptiste Daroussin static	void		 parse_manpath_conf(struct req *);
786d38604fSBaptiste Daroussin static	void		 parse_path_info(struct req *, const char *);
7961d06d6bSBaptiste Daroussin static	void		 parse_query_string(struct req *, const char *);
8061d06d6bSBaptiste Daroussin static	void		 pg_error_badrequest(const char *);
8161d06d6bSBaptiste Daroussin static	void		 pg_error_internal(void);
8261d06d6bSBaptiste Daroussin static	void		 pg_index(const struct req *);
836d38604fSBaptiste Daroussin static	void		 pg_noresult(const struct req *, int, const char *,
846d38604fSBaptiste Daroussin 				const char *);
8561d06d6bSBaptiste Daroussin static	void		 pg_redirect(const struct req *, const char *);
8661d06d6bSBaptiste Daroussin static	void		 pg_search(const struct req *);
8761d06d6bSBaptiste Daroussin static	void		 pg_searchres(const struct req *,
8861d06d6bSBaptiste Daroussin 				struct manpage *, size_t);
8961d06d6bSBaptiste Daroussin static	void		 pg_show(struct req *, const char *);
90*c1c95addSBrooks Davis static	int		 resp_begin_html(int, const char *, const char *);
9161d06d6bSBaptiste Daroussin static	void		 resp_begin_http(int, const char *);
9261d06d6bSBaptiste Daroussin static	void		 resp_catman(const struct req *, const char *);
93*c1c95addSBrooks Davis static	int		 resp_copy(const char *, const char *);
9461d06d6bSBaptiste Daroussin static	void		 resp_end_html(void);
9561d06d6bSBaptiste Daroussin static	void		 resp_format(const struct req *, const char *);
9661d06d6bSBaptiste Daroussin static	void		 resp_searchform(const struct req *, enum focus);
9761d06d6bSBaptiste Daroussin static	void		 resp_show(const struct req *, const char *);
9861d06d6bSBaptiste Daroussin static	void		 set_query_attr(char **, char **);
997295610fSBaptiste Daroussin static	int		 validate_arch(const char *);
10061d06d6bSBaptiste Daroussin static	int		 validate_filename(const char *);
10161d06d6bSBaptiste Daroussin static	int		 validate_manpath(const struct req *, const char *);
10261d06d6bSBaptiste Daroussin static	int		 validate_urifrag(const char *);
10361d06d6bSBaptiste Daroussin 
10461d06d6bSBaptiste Daroussin static	const char	 *scriptname = SCRIPT_NAME;
10561d06d6bSBaptiste Daroussin 
10661d06d6bSBaptiste Daroussin static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
10761d06d6bSBaptiste Daroussin static	const char *const sec_numbers[] = {
10861d06d6bSBaptiste Daroussin     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10961d06d6bSBaptiste Daroussin };
11061d06d6bSBaptiste Daroussin static	const char *const sec_names[] = {
11161d06d6bSBaptiste Daroussin     "All Sections",
11261d06d6bSBaptiste Daroussin     "1 - General Commands",
11361d06d6bSBaptiste Daroussin     "2 - System Calls",
11461d06d6bSBaptiste Daroussin     "3 - Library Functions",
11561d06d6bSBaptiste Daroussin     "3p - Perl Library",
11661d06d6bSBaptiste Daroussin     "4 - Device Drivers",
11761d06d6bSBaptiste Daroussin     "5 - File Formats",
11861d06d6bSBaptiste Daroussin     "6 - Games",
11961d06d6bSBaptiste Daroussin     "7 - Miscellaneous Information",
12061d06d6bSBaptiste Daroussin     "8 - System Manager\'s Manual",
12161d06d6bSBaptiste Daroussin     "9 - Kernel Developer\'s Manual"
12261d06d6bSBaptiste Daroussin };
12361d06d6bSBaptiste Daroussin static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
12461d06d6bSBaptiste Daroussin 
12561d06d6bSBaptiste Daroussin static	const char *const arch_names[] = {
12661d06d6bSBaptiste Daroussin     "amd64",       "alpha",       "armv7",       "arm64",
1276d38604fSBaptiste Daroussin     "hppa",        "i386",        "landisk",     "loongson",
1286d38604fSBaptiste Daroussin     "luna88k",     "macppc",      "mips64",      "octeon",
1296d38604fSBaptiste Daroussin     "powerpc64",   "riscv64",     "sparc64",
1306d38604fSBaptiste Daroussin 
13161d06d6bSBaptiste Daroussin     "amiga",       "arc",         "armish",      "arm32",
13261d06d6bSBaptiste Daroussin     "atari",       "aviion",      "beagle",      "cats",
13361d06d6bSBaptiste Daroussin     "hppa64",      "hp300",
13461d06d6bSBaptiste Daroussin     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
13561d06d6bSBaptiste Daroussin     "mvmeppc",     "palm",        "pc532",       "pegasos",
1366d38604fSBaptiste Daroussin     "pmax",        "powerpc",     "sgi",         "socppc",
1376d38604fSBaptiste Daroussin     "solbourne",   "sparc",
13861d06d6bSBaptiste Daroussin     "sun3",        "vax",         "wgrisc",      "x68k",
13961d06d6bSBaptiste Daroussin     "zaurus"
14061d06d6bSBaptiste Daroussin };
14161d06d6bSBaptiste Daroussin static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
14261d06d6bSBaptiste Daroussin 
14361d06d6bSBaptiste Daroussin /*
14461d06d6bSBaptiste Daroussin  * Print a character, escaping HTML along the way.
14561d06d6bSBaptiste Daroussin  * This will pass non-ASCII straight to output: be warned!
14661d06d6bSBaptiste Daroussin  */
14761d06d6bSBaptiste Daroussin static void
html_putchar(char c)14861d06d6bSBaptiste Daroussin html_putchar(char c)
14961d06d6bSBaptiste Daroussin {
15061d06d6bSBaptiste Daroussin 
15161d06d6bSBaptiste Daroussin 	switch (c) {
15261d06d6bSBaptiste Daroussin 	case '"':
15361d06d6bSBaptiste Daroussin 		printf("&quot;");
15461d06d6bSBaptiste Daroussin 		break;
15561d06d6bSBaptiste Daroussin 	case '&':
15661d06d6bSBaptiste Daroussin 		printf("&amp;");
15761d06d6bSBaptiste Daroussin 		break;
15861d06d6bSBaptiste Daroussin 	case '>':
15961d06d6bSBaptiste Daroussin 		printf("&gt;");
16061d06d6bSBaptiste Daroussin 		break;
16161d06d6bSBaptiste Daroussin 	case '<':
16261d06d6bSBaptiste Daroussin 		printf("&lt;");
16361d06d6bSBaptiste Daroussin 		break;
16461d06d6bSBaptiste Daroussin 	default:
16561d06d6bSBaptiste Daroussin 		putchar((unsigned char)c);
16661d06d6bSBaptiste Daroussin 		break;
16761d06d6bSBaptiste Daroussin 	}
16861d06d6bSBaptiste Daroussin }
16961d06d6bSBaptiste Daroussin 
17061d06d6bSBaptiste Daroussin /*
17161d06d6bSBaptiste Daroussin  * Call through to html_putchar().
17261d06d6bSBaptiste Daroussin  * Accepts NULL strings.
17361d06d6bSBaptiste Daroussin  */
17461d06d6bSBaptiste Daroussin static void
html_print(const char * p)17561d06d6bSBaptiste Daroussin html_print(const char *p)
17661d06d6bSBaptiste Daroussin {
17761d06d6bSBaptiste Daroussin 
17861d06d6bSBaptiste Daroussin 	if (NULL == p)
17961d06d6bSBaptiste Daroussin 		return;
18061d06d6bSBaptiste Daroussin 	while ('\0' != *p)
18161d06d6bSBaptiste Daroussin 		html_putchar(*p++);
18261d06d6bSBaptiste Daroussin }
18361d06d6bSBaptiste Daroussin 
18461d06d6bSBaptiste Daroussin /*
18561d06d6bSBaptiste Daroussin  * Transfer the responsibility for the allocated string *val
18661d06d6bSBaptiste Daroussin  * to the query structure.
18761d06d6bSBaptiste Daroussin  */
18861d06d6bSBaptiste Daroussin static void
set_query_attr(char ** attr,char ** val)18961d06d6bSBaptiste Daroussin set_query_attr(char **attr, char **val)
19061d06d6bSBaptiste Daroussin {
19161d06d6bSBaptiste Daroussin 
19261d06d6bSBaptiste Daroussin 	free(*attr);
19361d06d6bSBaptiste Daroussin 	if (**val == '\0') {
19461d06d6bSBaptiste Daroussin 		*attr = NULL;
19561d06d6bSBaptiste Daroussin 		free(*val);
19661d06d6bSBaptiste Daroussin 	} else
19761d06d6bSBaptiste Daroussin 		*attr = *val;
19861d06d6bSBaptiste Daroussin 	*val = NULL;
19961d06d6bSBaptiste Daroussin }
20061d06d6bSBaptiste Daroussin 
20161d06d6bSBaptiste Daroussin /*
20261d06d6bSBaptiste Daroussin  * Parse the QUERY_STRING for key-value pairs
20361d06d6bSBaptiste Daroussin  * and store the values into the query structure.
20461d06d6bSBaptiste Daroussin  */
20561d06d6bSBaptiste Daroussin static void
parse_query_string(struct req * req,const char * qs)20661d06d6bSBaptiste Daroussin parse_query_string(struct req *req, const char *qs)
20761d06d6bSBaptiste Daroussin {
20861d06d6bSBaptiste Daroussin 	char		*key, *val;
20961d06d6bSBaptiste Daroussin 	size_t		 keysz, valsz;
21061d06d6bSBaptiste Daroussin 
21161d06d6bSBaptiste Daroussin 	req->isquery	= 1;
21261d06d6bSBaptiste Daroussin 	req->q.manpath	= NULL;
21361d06d6bSBaptiste Daroussin 	req->q.arch	= NULL;
21461d06d6bSBaptiste Daroussin 	req->q.sec	= NULL;
21561d06d6bSBaptiste Daroussin 	req->q.query	= NULL;
21661d06d6bSBaptiste Daroussin 	req->q.equal	= 1;
21761d06d6bSBaptiste Daroussin 
21861d06d6bSBaptiste Daroussin 	key = val = NULL;
21961d06d6bSBaptiste Daroussin 	while (*qs != '\0') {
22061d06d6bSBaptiste Daroussin 
22161d06d6bSBaptiste Daroussin 		/* Parse one key. */
22261d06d6bSBaptiste Daroussin 
22361d06d6bSBaptiste Daroussin 		keysz = strcspn(qs, "=;&");
22461d06d6bSBaptiste Daroussin 		key = mandoc_strndup(qs, keysz);
22561d06d6bSBaptiste Daroussin 		qs += keysz;
22661d06d6bSBaptiste Daroussin 		if (*qs != '=')
22761d06d6bSBaptiste Daroussin 			goto next;
22861d06d6bSBaptiste Daroussin 
22961d06d6bSBaptiste Daroussin 		/* Parse one value. */
23061d06d6bSBaptiste Daroussin 
23161d06d6bSBaptiste Daroussin 		valsz = strcspn(++qs, ";&");
23261d06d6bSBaptiste Daroussin 		val = mandoc_strndup(qs, valsz);
23361d06d6bSBaptiste Daroussin 		qs += valsz;
23461d06d6bSBaptiste Daroussin 
23561d06d6bSBaptiste Daroussin 		/* Decode and catch encoding errors. */
23661d06d6bSBaptiste Daroussin 
23761d06d6bSBaptiste Daroussin 		if ( ! (http_decode(key) && http_decode(val)))
23861d06d6bSBaptiste Daroussin 			goto next;
23961d06d6bSBaptiste Daroussin 
24061d06d6bSBaptiste Daroussin 		/* Handle key-value pairs. */
24161d06d6bSBaptiste Daroussin 
24261d06d6bSBaptiste Daroussin 		if ( ! strcmp(key, "query"))
24361d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.query, &val);
24461d06d6bSBaptiste Daroussin 
24561d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "apropos"))
24661d06d6bSBaptiste Daroussin 			req->q.equal = !strcmp(val, "0");
24761d06d6bSBaptiste Daroussin 
24861d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "manpath")) {
24961d06d6bSBaptiste Daroussin #ifdef COMPAT_OLDURI
25061d06d6bSBaptiste Daroussin 			if ( ! strncmp(val, "OpenBSD ", 8)) {
25161d06d6bSBaptiste Daroussin 				val[7] = '-';
25261d06d6bSBaptiste Daroussin 				if ('C' == val[8])
25361d06d6bSBaptiste Daroussin 					val[8] = 'c';
25461d06d6bSBaptiste Daroussin 			}
25561d06d6bSBaptiste Daroussin #endif
25661d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.manpath, &val);
25761d06d6bSBaptiste Daroussin 		}
25861d06d6bSBaptiste Daroussin 
25961d06d6bSBaptiste Daroussin 		else if ( ! (strcmp(key, "sec")
26061d06d6bSBaptiste Daroussin #ifdef COMPAT_OLDURI
26161d06d6bSBaptiste Daroussin 		    && strcmp(key, "sektion")
26261d06d6bSBaptiste Daroussin #endif
26361d06d6bSBaptiste Daroussin 		    )) {
26461d06d6bSBaptiste Daroussin 			if ( ! strcmp(val, "0"))
26561d06d6bSBaptiste Daroussin 				*val = '\0';
26661d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.sec, &val);
26761d06d6bSBaptiste Daroussin 		}
26861d06d6bSBaptiste Daroussin 
26961d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "arch")) {
27061d06d6bSBaptiste Daroussin 			if ( ! strcmp(val, "default"))
27161d06d6bSBaptiste Daroussin 				*val = '\0';
27261d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.arch, &val);
27361d06d6bSBaptiste Daroussin 		}
27461d06d6bSBaptiste Daroussin 
27561d06d6bSBaptiste Daroussin 		/*
27661d06d6bSBaptiste Daroussin 		 * The key must be freed in any case.
27761d06d6bSBaptiste Daroussin 		 * The val may have been handed over to the query
27861d06d6bSBaptiste Daroussin 		 * structure, in which case it is now NULL.
27961d06d6bSBaptiste Daroussin 		 */
28061d06d6bSBaptiste Daroussin next:
28161d06d6bSBaptiste Daroussin 		free(key);
28261d06d6bSBaptiste Daroussin 		key = NULL;
28361d06d6bSBaptiste Daroussin 		free(val);
28461d06d6bSBaptiste Daroussin 		val = NULL;
28561d06d6bSBaptiste Daroussin 
28661d06d6bSBaptiste Daroussin 		if (*qs != '\0')
28761d06d6bSBaptiste Daroussin 			qs++;
28861d06d6bSBaptiste Daroussin 	}
28961d06d6bSBaptiste Daroussin }
29061d06d6bSBaptiste Daroussin 
29161d06d6bSBaptiste Daroussin /*
29261d06d6bSBaptiste Daroussin  * HTTP-decode a string.  The standard explanation is that this turns
29361d06d6bSBaptiste Daroussin  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
29461d06d6bSBaptiste Daroussin  * over the allocated string.
29561d06d6bSBaptiste Daroussin  */
29661d06d6bSBaptiste Daroussin static int
http_decode(char * p)29761d06d6bSBaptiste Daroussin http_decode(char *p)
29861d06d6bSBaptiste Daroussin {
29961d06d6bSBaptiste Daroussin 	char             hex[3];
30061d06d6bSBaptiste Daroussin 	char		*q;
30161d06d6bSBaptiste Daroussin 	int              c;
30261d06d6bSBaptiste Daroussin 
30361d06d6bSBaptiste Daroussin 	hex[2] = '\0';
30461d06d6bSBaptiste Daroussin 
30561d06d6bSBaptiste Daroussin 	q = p;
30661d06d6bSBaptiste Daroussin 	for ( ; '\0' != *p; p++, q++) {
30761d06d6bSBaptiste Daroussin 		if ('%' == *p) {
30861d06d6bSBaptiste Daroussin 			if ('\0' == (hex[0] = *(p + 1)))
30961d06d6bSBaptiste Daroussin 				return 0;
31061d06d6bSBaptiste Daroussin 			if ('\0' == (hex[1] = *(p + 2)))
31161d06d6bSBaptiste Daroussin 				return 0;
31261d06d6bSBaptiste Daroussin 			if (1 != sscanf(hex, "%x", &c))
31361d06d6bSBaptiste Daroussin 				return 0;
31461d06d6bSBaptiste Daroussin 			if ('\0' == c)
31561d06d6bSBaptiste Daroussin 				return 0;
31661d06d6bSBaptiste Daroussin 
31761d06d6bSBaptiste Daroussin 			*q = (char)c;
31861d06d6bSBaptiste Daroussin 			p += 2;
31961d06d6bSBaptiste Daroussin 		} else
32061d06d6bSBaptiste Daroussin 			*q = '+' == *p ? ' ' : *p;
32161d06d6bSBaptiste Daroussin 	}
32261d06d6bSBaptiste Daroussin 
32361d06d6bSBaptiste Daroussin 	*q = '\0';
32461d06d6bSBaptiste Daroussin 	return 1;
32561d06d6bSBaptiste Daroussin }
32661d06d6bSBaptiste Daroussin 
32761d06d6bSBaptiste Daroussin static void
http_encode(const char * p)3287295610fSBaptiste Daroussin http_encode(const char *p)
3297295610fSBaptiste Daroussin {
3307295610fSBaptiste Daroussin 	for (; *p != '\0'; p++) {
3317295610fSBaptiste Daroussin 		if (isalnum((unsigned char)*p) == 0 &&
3327295610fSBaptiste Daroussin 		    strchr("-._~", *p) == NULL)
3337295610fSBaptiste Daroussin 			printf("%%%2.2X", (unsigned char)*p);
3347295610fSBaptiste Daroussin 		else
3357295610fSBaptiste Daroussin 			putchar(*p);
3367295610fSBaptiste Daroussin 	}
3377295610fSBaptiste Daroussin }
3387295610fSBaptiste Daroussin 
3397295610fSBaptiste Daroussin static void
resp_begin_http(int code,const char * msg)34061d06d6bSBaptiste Daroussin resp_begin_http(int code, const char *msg)
34161d06d6bSBaptiste Daroussin {
34261d06d6bSBaptiste Daroussin 
34361d06d6bSBaptiste Daroussin 	if (200 != code)
34461d06d6bSBaptiste Daroussin 		printf("Status: %d %s\r\n", code, msg);
34561d06d6bSBaptiste Daroussin 
34661d06d6bSBaptiste Daroussin 	printf("Content-Type: text/html; charset=utf-8\r\n"
34761d06d6bSBaptiste Daroussin 	     "Cache-Control: no-cache\r\n"
3486d38604fSBaptiste Daroussin 	     "Content-Security-Policy: default-src 'none'; "
3496d38604fSBaptiste Daroussin 	     "style-src 'self' 'unsafe-inline'\r\n"
35061d06d6bSBaptiste Daroussin 	     "Pragma: no-cache\r\n"
35161d06d6bSBaptiste Daroussin 	     "\r\n");
35261d06d6bSBaptiste Daroussin 
35361d06d6bSBaptiste Daroussin 	fflush(stdout);
35461d06d6bSBaptiste Daroussin }
35561d06d6bSBaptiste Daroussin 
356*c1c95addSBrooks Davis static int
resp_copy(const char * element,const char * filename)357*c1c95addSBrooks Davis resp_copy(const char *element, const char *filename)
35861d06d6bSBaptiste Daroussin {
35961d06d6bSBaptiste Daroussin 	char	 buf[4096];
36061d06d6bSBaptiste Daroussin 	ssize_t	 sz;
36161d06d6bSBaptiste Daroussin 	int	 fd;
36261d06d6bSBaptiste Daroussin 
363*c1c95addSBrooks Davis 	if ((fd = open(filename, O_RDONLY)) == -1)
364*c1c95addSBrooks Davis 		return 0;
365*c1c95addSBrooks Davis 
366*c1c95addSBrooks Davis 	if (element != NULL)
367*c1c95addSBrooks Davis 		printf("<%s>\n", element);
36861d06d6bSBaptiste Daroussin 	fflush(stdout);
36961d06d6bSBaptiste Daroussin 	while ((sz = read(fd, buf, sizeof(buf))) > 0)
37061d06d6bSBaptiste Daroussin 		write(STDOUT_FILENO, buf, sz);
37161d06d6bSBaptiste Daroussin 	close(fd);
372*c1c95addSBrooks Davis 	return 1;
37361d06d6bSBaptiste Daroussin }
37461d06d6bSBaptiste Daroussin 
375*c1c95addSBrooks Davis static int
resp_begin_html(int code,const char * msg,const char * file)37661d06d6bSBaptiste Daroussin resp_begin_html(int code, const char *msg, const char *file)
37761d06d6bSBaptiste Daroussin {
3786d38604fSBaptiste Daroussin 	const char	*name, *sec, *cp;
3796d38604fSBaptiste Daroussin 	int		 namesz, secsz;
38061d06d6bSBaptiste Daroussin 
38161d06d6bSBaptiste Daroussin 	resp_begin_http(code, msg);
38261d06d6bSBaptiste Daroussin 
38361d06d6bSBaptiste Daroussin 	printf("<!DOCTYPE html>\n"
38461d06d6bSBaptiste Daroussin 	       "<html>\n"
38561d06d6bSBaptiste Daroussin 	       "<head>\n"
38661d06d6bSBaptiste Daroussin 	       "  <meta charset=\"UTF-8\"/>\n"
38761d06d6bSBaptiste Daroussin 	       "  <meta name=\"viewport\""
38861d06d6bSBaptiste Daroussin 		      " content=\"width=device-width, initial-scale=1.0\">\n"
38961d06d6bSBaptiste Daroussin 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
39061d06d6bSBaptiste Daroussin 	       " type=\"text/css\" media=\"all\">\n"
39161d06d6bSBaptiste Daroussin 	       "  <title>",
39261d06d6bSBaptiste Daroussin 	       CSS_DIR);
39361d06d6bSBaptiste Daroussin 	if (file != NULL) {
3946d38604fSBaptiste Daroussin 		cp = strrchr(file, '/');
3956d38604fSBaptiste Daroussin 		name = cp == NULL ? file : cp + 1;
3966d38604fSBaptiste Daroussin 		cp = strrchr(name, '.');
3976d38604fSBaptiste Daroussin 		namesz = cp == NULL ? strlen(name) : cp - name;
3986d38604fSBaptiste Daroussin 		sec = NULL;
3996d38604fSBaptiste Daroussin 		if (cp != NULL && cp[1] != '0') {
4006d38604fSBaptiste Daroussin 			sec = cp + 1;
4016d38604fSBaptiste Daroussin 			secsz = strlen(sec);
4026d38604fSBaptiste Daroussin 		} else if (name - file > 1) {
4036d38604fSBaptiste Daroussin 			for (cp = name - 2; cp >= file; cp--) {
4046d38604fSBaptiste Daroussin 				if (*cp < '1' || *cp > '9')
4056d38604fSBaptiste Daroussin 					continue;
4066d38604fSBaptiste Daroussin 				sec = cp;
4076d38604fSBaptiste Daroussin 				secsz = name - cp - 1;
4086d38604fSBaptiste Daroussin 				break;
4096d38604fSBaptiste Daroussin 			}
4106d38604fSBaptiste Daroussin 		}
4116d38604fSBaptiste Daroussin 		printf("%.*s", namesz, name);
4126d38604fSBaptiste Daroussin 		if (sec != NULL)
4136d38604fSBaptiste Daroussin 			printf("(%.*s)", secsz, sec);
4146d38604fSBaptiste Daroussin 		fputs(" - ", stdout);
41561d06d6bSBaptiste Daroussin 	}
41661d06d6bSBaptiste Daroussin 	printf("%s</title>\n"
41761d06d6bSBaptiste Daroussin 	       "</head>\n"
41861d06d6bSBaptiste Daroussin 	       "<body>\n",
41961d06d6bSBaptiste Daroussin 	       CUSTOMIZE_TITLE);
42061d06d6bSBaptiste Daroussin 
421*c1c95addSBrooks Davis 	return resp_copy("header", MAN_DIR "/header.html");
42261d06d6bSBaptiste Daroussin }
42361d06d6bSBaptiste Daroussin 
42461d06d6bSBaptiste Daroussin static void
resp_end_html(void)42561d06d6bSBaptiste Daroussin resp_end_html(void)
42661d06d6bSBaptiste Daroussin {
427*c1c95addSBrooks Davis 	if (resp_copy("footer", MAN_DIR "/footer.html"))
428*c1c95addSBrooks Davis 		puts("</footer>");
42961d06d6bSBaptiste Daroussin 
43061d06d6bSBaptiste Daroussin 	puts("</body>\n"
43161d06d6bSBaptiste Daroussin 	     "</html>");
43261d06d6bSBaptiste Daroussin }
43361d06d6bSBaptiste Daroussin 
43461d06d6bSBaptiste Daroussin static void
resp_searchform(const struct req * req,enum focus focus)43561d06d6bSBaptiste Daroussin resp_searchform(const struct req *req, enum focus focus)
43661d06d6bSBaptiste Daroussin {
43761d06d6bSBaptiste Daroussin 	int		 i;
43861d06d6bSBaptiste Daroussin 
439*c1c95addSBrooks Davis 	printf("<form role=\"search\" action=\"/%s\" method=\"get\" "
4406d38604fSBaptiste Daroussin 	       "autocomplete=\"off\" autocapitalize=\"none\">\n"
44161d06d6bSBaptiste Daroussin 	       "  <fieldset>\n"
44261d06d6bSBaptiste Daroussin 	       "    <legend>Manual Page Search Parameters</legend>\n",
44361d06d6bSBaptiste Daroussin 	       scriptname);
44461d06d6bSBaptiste Daroussin 
44561d06d6bSBaptiste Daroussin 	/* Write query input box. */
44661d06d6bSBaptiste Daroussin 
447*c1c95addSBrooks Davis 	printf("    <label>Search query:\n"
448*c1c95addSBrooks Davis 	       "      <input type=\"search\" name=\"query\" value=\"");
44961d06d6bSBaptiste Daroussin 	if (req->q.query != NULL)
45061d06d6bSBaptiste Daroussin 		html_print(req->q.query);
45161d06d6bSBaptiste Daroussin 	printf("\" size=\"40\"");
45261d06d6bSBaptiste Daroussin 	if (focus == FOCUS_QUERY)
45361d06d6bSBaptiste Daroussin 		printf(" autofocus");
454*c1c95addSBrooks Davis 	puts(">\n    </label>");
45561d06d6bSBaptiste Daroussin 
45661d06d6bSBaptiste Daroussin 	/* Write submission buttons. */
45761d06d6bSBaptiste Daroussin 
45861d06d6bSBaptiste Daroussin 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
45961d06d6bSBaptiste Daroussin 		"man</button>\n"
46061d06d6bSBaptiste Daroussin 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
46161d06d6bSBaptiste Daroussin 		"apropos</button>\n"
46261d06d6bSBaptiste Daroussin 		"    <br/>\n");
46361d06d6bSBaptiste Daroussin 
46461d06d6bSBaptiste Daroussin 	/* Write section selector. */
46561d06d6bSBaptiste Daroussin 
466*c1c95addSBrooks Davis 	puts("    <select name=\"sec\" aria-label=\"Manual section\">");
46761d06d6bSBaptiste Daroussin 	for (i = 0; i < sec_MAX; i++) {
46861d06d6bSBaptiste Daroussin 		printf("      <option value=\"%s\"", sec_numbers[i]);
46961d06d6bSBaptiste Daroussin 		if (NULL != req->q.sec &&
47061d06d6bSBaptiste Daroussin 		    0 == strcmp(sec_numbers[i], req->q.sec))
47161d06d6bSBaptiste Daroussin 			printf(" selected=\"selected\"");
47261d06d6bSBaptiste Daroussin 		printf(">%s</option>\n", sec_names[i]);
47361d06d6bSBaptiste Daroussin 	}
47461d06d6bSBaptiste Daroussin 	puts("    </select>");
47561d06d6bSBaptiste Daroussin 
47661d06d6bSBaptiste Daroussin 	/* Write architecture selector. */
47761d06d6bSBaptiste Daroussin 
478*c1c95addSBrooks Davis 	printf(	"    <select name=\"arch\" aria-label=\"CPU architecture\">\n"
47961d06d6bSBaptiste Daroussin 		"      <option value=\"default\"");
48061d06d6bSBaptiste Daroussin 	if (NULL == req->q.arch)
48161d06d6bSBaptiste Daroussin 		printf(" selected=\"selected\"");
48261d06d6bSBaptiste Daroussin 	puts(">All Architectures</option>");
48361d06d6bSBaptiste Daroussin 	for (i = 0; i < arch_MAX; i++) {
48461d06d6bSBaptiste Daroussin 		printf("      <option");
48561d06d6bSBaptiste Daroussin 		if (NULL != req->q.arch &&
48661d06d6bSBaptiste Daroussin 		    0 == strcmp(arch_names[i], req->q.arch))
48761d06d6bSBaptiste Daroussin 			printf(" selected=\"selected\"");
48861d06d6bSBaptiste Daroussin 		printf(">%s</option>\n", arch_names[i]);
48961d06d6bSBaptiste Daroussin 	}
49061d06d6bSBaptiste Daroussin 	puts("    </select>");
49161d06d6bSBaptiste Daroussin 
49261d06d6bSBaptiste Daroussin 	/* Write manpath selector. */
49361d06d6bSBaptiste Daroussin 
49461d06d6bSBaptiste Daroussin 	if (req->psz > 1) {
495*c1c95addSBrooks Davis 		puts("    <select name=\"manpath\""
496*c1c95addSBrooks Davis 		     " aria-label=\"Manual path\">");
49761d06d6bSBaptiste Daroussin 		for (i = 0; i < (int)req->psz; i++) {
49861d06d6bSBaptiste Daroussin 			printf("      <option");
49961d06d6bSBaptiste Daroussin 			if (strcmp(req->q.manpath, req->p[i]) == 0)
50061d06d6bSBaptiste Daroussin 				printf(" selected=\"selected\"");
50161d06d6bSBaptiste Daroussin 			printf(">");
50261d06d6bSBaptiste Daroussin 			html_print(req->p[i]);
50361d06d6bSBaptiste Daroussin 			puts("</option>");
50461d06d6bSBaptiste Daroussin 		}
50561d06d6bSBaptiste Daroussin 		puts("    </select>");
50661d06d6bSBaptiste Daroussin 	}
50761d06d6bSBaptiste Daroussin 
50861d06d6bSBaptiste Daroussin 	puts("  </fieldset>\n"
50961d06d6bSBaptiste Daroussin 	     "</form>");
51061d06d6bSBaptiste Daroussin }
51161d06d6bSBaptiste Daroussin 
51261d06d6bSBaptiste Daroussin static int
validate_urifrag(const char * frag)51361d06d6bSBaptiste Daroussin validate_urifrag(const char *frag)
51461d06d6bSBaptiste Daroussin {
51561d06d6bSBaptiste Daroussin 
51661d06d6bSBaptiste Daroussin 	while ('\0' != *frag) {
51761d06d6bSBaptiste Daroussin 		if ( ! (isalnum((unsigned char)*frag) ||
51861d06d6bSBaptiste Daroussin 		    '-' == *frag || '.' == *frag ||
51961d06d6bSBaptiste Daroussin 		    '/' == *frag || '_' == *frag))
52061d06d6bSBaptiste Daroussin 			return 0;
52161d06d6bSBaptiste Daroussin 		frag++;
52261d06d6bSBaptiste Daroussin 	}
52361d06d6bSBaptiste Daroussin 	return 1;
52461d06d6bSBaptiste Daroussin }
52561d06d6bSBaptiste Daroussin 
52661d06d6bSBaptiste Daroussin static int
validate_manpath(const struct req * req,const char * manpath)52761d06d6bSBaptiste Daroussin validate_manpath(const struct req *req, const char* manpath)
52861d06d6bSBaptiste Daroussin {
52961d06d6bSBaptiste Daroussin 	size_t	 i;
53061d06d6bSBaptiste Daroussin 
53161d06d6bSBaptiste Daroussin 	for (i = 0; i < req->psz; i++)
53261d06d6bSBaptiste Daroussin 		if ( ! strcmp(manpath, req->p[i]))
53361d06d6bSBaptiste Daroussin 			return 1;
53461d06d6bSBaptiste Daroussin 
53561d06d6bSBaptiste Daroussin 	return 0;
53661d06d6bSBaptiste Daroussin }
53761d06d6bSBaptiste Daroussin 
53861d06d6bSBaptiste Daroussin static int
validate_arch(const char * arch)5397295610fSBaptiste Daroussin validate_arch(const char *arch)
5407295610fSBaptiste Daroussin {
5417295610fSBaptiste Daroussin 	int	 i;
5427295610fSBaptiste Daroussin 
5437295610fSBaptiste Daroussin 	for (i = 0; i < arch_MAX; i++)
5447295610fSBaptiste Daroussin 		if (strcmp(arch, arch_names[i]) == 0)
5457295610fSBaptiste Daroussin 			return 1;
5467295610fSBaptiste Daroussin 
5477295610fSBaptiste Daroussin 	return 0;
5487295610fSBaptiste Daroussin }
5497295610fSBaptiste Daroussin 
5507295610fSBaptiste Daroussin static int
validate_filename(const char * file)55161d06d6bSBaptiste Daroussin validate_filename(const char *file)
55261d06d6bSBaptiste Daroussin {
55361d06d6bSBaptiste Daroussin 
55461d06d6bSBaptiste Daroussin 	if ('.' == file[0] && '/' == file[1])
55561d06d6bSBaptiste Daroussin 		file += 2;
55661d06d6bSBaptiste Daroussin 
55761d06d6bSBaptiste Daroussin 	return ! (strstr(file, "../") || strstr(file, "/..") ||
55861d06d6bSBaptiste Daroussin 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
55961d06d6bSBaptiste Daroussin }
56061d06d6bSBaptiste Daroussin 
56161d06d6bSBaptiste Daroussin static void
pg_index(const struct req * req)56261d06d6bSBaptiste Daroussin pg_index(const struct req *req)
56361d06d6bSBaptiste Daroussin {
564*c1c95addSBrooks Davis 	if (resp_begin_html(200, NULL, NULL) == 0)
565*c1c95addSBrooks Davis 		puts("<header>");
56661d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_QUERY);
567*c1c95addSBrooks Davis 	printf("</header>\n"
568*c1c95addSBrooks Davis 	       "<main>\n"
569*c1c95addSBrooks Davis 	       "<p role=\"doc-notice\" aria-label=\"Usage\">\n"
57061d06d6bSBaptiste Daroussin 	       "This web interface is documented in the\n"
571*c1c95addSBrooks Davis 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
572*c1c95addSBrooks Davis 	       " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
57361d06d6bSBaptiste Daroussin 	       "manual, and the\n"
574*c1c95addSBrooks Davis 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\""
575*c1c95addSBrooks Davis 	       " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
57661d06d6bSBaptiste Daroussin 	       "manual explains the query syntax.\n"
577*c1c95addSBrooks Davis 	       "</p>\n"
578*c1c95addSBrooks Davis 	       "</main>\n",
57961d06d6bSBaptiste Daroussin 	       scriptname, *scriptname == '\0' ? "" : "/",
58061d06d6bSBaptiste Daroussin 	       scriptname, *scriptname == '\0' ? "" : "/");
58161d06d6bSBaptiste Daroussin 	resp_end_html();
58261d06d6bSBaptiste Daroussin }
58361d06d6bSBaptiste Daroussin 
58461d06d6bSBaptiste Daroussin static void
pg_noresult(const struct req * req,int code,const char * http_msg,const char * user_msg)5856d38604fSBaptiste Daroussin pg_noresult(const struct req *req, int code, const char *http_msg,
5866d38604fSBaptiste Daroussin     const char *user_msg)
58761d06d6bSBaptiste Daroussin {
588*c1c95addSBrooks Davis 	if (resp_begin_html(code, http_msg, NULL) == 0)
589*c1c95addSBrooks Davis 		puts("<header>");
59061d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_QUERY);
591*c1c95addSBrooks Davis 	puts("</header>");
592*c1c95addSBrooks Davis 	puts("<main>");
593*c1c95addSBrooks Davis 	puts("<p role=\"doc-notice\" aria-label=\"No result\">");
5946d38604fSBaptiste Daroussin 	puts(user_msg);
59561d06d6bSBaptiste Daroussin 	puts("</p>");
596*c1c95addSBrooks Davis 	puts("</main>");
59761d06d6bSBaptiste Daroussin 	resp_end_html();
59861d06d6bSBaptiste Daroussin }
59961d06d6bSBaptiste Daroussin 
60061d06d6bSBaptiste Daroussin static void
pg_error_badrequest(const char * msg)60161d06d6bSBaptiste Daroussin pg_error_badrequest(const char *msg)
60261d06d6bSBaptiste Daroussin {
603*c1c95addSBrooks Davis 	if (resp_begin_html(400, "Bad Request", NULL))
604*c1c95addSBrooks Davis 		puts("</header>");
605*c1c95addSBrooks Davis 	puts("<main>\n"
606*c1c95addSBrooks Davis 	     "<h1>Bad Request</h1>\n"
607*c1c95addSBrooks Davis 	     "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
60861d06d6bSBaptiste Daroussin 	puts(msg);
60961d06d6bSBaptiste Daroussin 	printf("Try again from the\n"
61061d06d6bSBaptiste Daroussin 	       "<a href=\"/%s\">main page</a>.\n"
611*c1c95addSBrooks Davis 	       "</p>\n"
612*c1c95addSBrooks Davis 	       "</main>\n", scriptname);
61361d06d6bSBaptiste Daroussin 	resp_end_html();
61461d06d6bSBaptiste Daroussin }
61561d06d6bSBaptiste Daroussin 
61661d06d6bSBaptiste Daroussin static void
pg_error_internal(void)61761d06d6bSBaptiste Daroussin pg_error_internal(void)
61861d06d6bSBaptiste Daroussin {
619*c1c95addSBrooks Davis 	if (resp_begin_html(500, "Internal Server Error", NULL))
620*c1c95addSBrooks Davis 		puts("</header>");
621*c1c95addSBrooks Davis 	puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
62261d06d6bSBaptiste Daroussin 	resp_end_html();
62361d06d6bSBaptiste Daroussin }
62461d06d6bSBaptiste Daroussin 
62561d06d6bSBaptiste Daroussin static void
pg_redirect(const struct req * req,const char * name)62661d06d6bSBaptiste Daroussin pg_redirect(const struct req *req, const char *name)
62761d06d6bSBaptiste Daroussin {
62861d06d6bSBaptiste Daroussin 	printf("Status: 303 See Other\r\n"
62961d06d6bSBaptiste Daroussin 	    "Location: /");
63061d06d6bSBaptiste Daroussin 	if (*scriptname != '\0')
63161d06d6bSBaptiste Daroussin 		printf("%s/", scriptname);
63261d06d6bSBaptiste Daroussin 	if (strcmp(req->q.manpath, req->p[0]))
63361d06d6bSBaptiste Daroussin 		printf("%s/", req->q.manpath);
63461d06d6bSBaptiste Daroussin 	if (req->q.arch != NULL)
63561d06d6bSBaptiste Daroussin 		printf("%s/", req->q.arch);
6367295610fSBaptiste Daroussin 	http_encode(name);
6377295610fSBaptiste Daroussin 	if (req->q.sec != NULL) {
6387295610fSBaptiste Daroussin 		putchar('.');
6397295610fSBaptiste Daroussin 		http_encode(req->q.sec);
6407295610fSBaptiste Daroussin 	}
64161d06d6bSBaptiste Daroussin 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
64261d06d6bSBaptiste Daroussin }
64361d06d6bSBaptiste Daroussin 
64461d06d6bSBaptiste Daroussin static void
pg_searchres(const struct req * req,struct manpage * r,size_t sz)64561d06d6bSBaptiste Daroussin pg_searchres(const struct req *req, struct manpage *r, size_t sz)
64661d06d6bSBaptiste Daroussin {
64761d06d6bSBaptiste Daroussin 	char		*arch, *archend;
64861d06d6bSBaptiste Daroussin 	const char	*sec;
64961d06d6bSBaptiste Daroussin 	size_t		 i, iuse;
65061d06d6bSBaptiste Daroussin 	int		 archprio, archpriouse;
65161d06d6bSBaptiste Daroussin 	int		 prio, priouse;
652*c1c95addSBrooks Davis 	int		 have_header;
65361d06d6bSBaptiste Daroussin 
65461d06d6bSBaptiste Daroussin 	for (i = 0; i < sz; i++) {
65561d06d6bSBaptiste Daroussin 		if (validate_filename(r[i].file))
65661d06d6bSBaptiste Daroussin 			continue;
65761d06d6bSBaptiste Daroussin 		warnx("invalid filename %s in %s database",
65861d06d6bSBaptiste Daroussin 		    r[i].file, req->q.manpath);
65961d06d6bSBaptiste Daroussin 		pg_error_internal();
66061d06d6bSBaptiste Daroussin 		return;
66161d06d6bSBaptiste Daroussin 	}
66261d06d6bSBaptiste Daroussin 
66361d06d6bSBaptiste Daroussin 	if (req->isquery && sz == 1) {
66461d06d6bSBaptiste Daroussin 		/*
66561d06d6bSBaptiste Daroussin 		 * If we have just one result, then jump there now
66661d06d6bSBaptiste Daroussin 		 * without any delay.
66761d06d6bSBaptiste Daroussin 		 */
66861d06d6bSBaptiste Daroussin 		printf("Status: 303 See Other\r\n"
66961d06d6bSBaptiste Daroussin 		    "Location: /");
67061d06d6bSBaptiste Daroussin 		if (*scriptname != '\0')
67161d06d6bSBaptiste Daroussin 			printf("%s/", scriptname);
67261d06d6bSBaptiste Daroussin 		if (strcmp(req->q.manpath, req->p[0]))
67361d06d6bSBaptiste Daroussin 			printf("%s/", req->q.manpath);
67461d06d6bSBaptiste Daroussin 		printf("%s\r\n"
67561d06d6bSBaptiste Daroussin 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
67661d06d6bSBaptiste Daroussin 		    r[0].file);
67761d06d6bSBaptiste Daroussin 		return;
67861d06d6bSBaptiste Daroussin 	}
67961d06d6bSBaptiste Daroussin 
68061d06d6bSBaptiste Daroussin 	/*
68161d06d6bSBaptiste Daroussin 	 * In man(1) mode, show one of the pages
68261d06d6bSBaptiste Daroussin 	 * even if more than one is found.
68361d06d6bSBaptiste Daroussin 	 */
68461d06d6bSBaptiste Daroussin 
68561d06d6bSBaptiste Daroussin 	iuse = 0;
68661d06d6bSBaptiste Daroussin 	if (req->q.equal || sz == 1) {
68761d06d6bSBaptiste Daroussin 		priouse = 20;
68861d06d6bSBaptiste Daroussin 		archpriouse = 3;
68961d06d6bSBaptiste Daroussin 		for (i = 0; i < sz; i++) {
69061d06d6bSBaptiste Daroussin 			sec = r[i].file;
69161d06d6bSBaptiste Daroussin 			sec += strcspn(sec, "123456789");
69261d06d6bSBaptiste Daroussin 			if (sec[0] == '\0')
69361d06d6bSBaptiste Daroussin 				continue;
69461d06d6bSBaptiste Daroussin 			prio = sec_prios[sec[0] - '1'];
69561d06d6bSBaptiste Daroussin 			if (sec[1] != '/')
69661d06d6bSBaptiste Daroussin 				prio += 10;
69761d06d6bSBaptiste Daroussin 			if (req->q.arch == NULL) {
69861d06d6bSBaptiste Daroussin 				archprio =
69961d06d6bSBaptiste Daroussin 				    ((arch = strchr(sec + 1, '/'))
70061d06d6bSBaptiste Daroussin 					== NULL) ? 3 :
70161d06d6bSBaptiste Daroussin 				    ((archend = strchr(arch + 1, '/'))
70261d06d6bSBaptiste Daroussin 					== NULL) ? 0 :
70361d06d6bSBaptiste Daroussin 				    strncmp(arch, "amd64/",
70461d06d6bSBaptiste Daroussin 					archend - arch) ? 2 : 1;
70561d06d6bSBaptiste Daroussin 				if (archprio < archpriouse) {
70661d06d6bSBaptiste Daroussin 					archpriouse = archprio;
70761d06d6bSBaptiste Daroussin 					priouse = prio;
70861d06d6bSBaptiste Daroussin 					iuse = i;
70961d06d6bSBaptiste Daroussin 					continue;
71061d06d6bSBaptiste Daroussin 				}
71161d06d6bSBaptiste Daroussin 				if (archprio > archpriouse)
71261d06d6bSBaptiste Daroussin 					continue;
71361d06d6bSBaptiste Daroussin 			}
71461d06d6bSBaptiste Daroussin 			if (prio >= priouse)
71561d06d6bSBaptiste Daroussin 				continue;
71661d06d6bSBaptiste Daroussin 			priouse = prio;
71761d06d6bSBaptiste Daroussin 			iuse = i;
71861d06d6bSBaptiste Daroussin 		}
719*c1c95addSBrooks Davis 		have_header = resp_begin_html(200, NULL, r[iuse].file);
72061d06d6bSBaptiste Daroussin 	} else
721*c1c95addSBrooks Davis 		have_header = resp_begin_html(200, NULL, NULL);
72261d06d6bSBaptiste Daroussin 
723*c1c95addSBrooks Davis 	if (have_header == 0)
724*c1c95addSBrooks Davis 		puts("<header>");
72561d06d6bSBaptiste Daroussin 	resp_searchform(req,
72661d06d6bSBaptiste Daroussin 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
727*c1c95addSBrooks Davis 	puts("</header>");
72861d06d6bSBaptiste Daroussin 
72961d06d6bSBaptiste Daroussin 	if (sz > 1) {
730*c1c95addSBrooks Davis 		puts("<nav>");
73161d06d6bSBaptiste Daroussin 		puts("<table class=\"results\">");
73261d06d6bSBaptiste Daroussin 		for (i = 0; i < sz; i++) {
73361d06d6bSBaptiste Daroussin 			printf("  <tr>\n"
73461d06d6bSBaptiste Daroussin 			       "    <td>"
73561d06d6bSBaptiste Daroussin 			       "<a class=\"Xr\" href=\"/");
73661d06d6bSBaptiste Daroussin 			if (*scriptname != '\0')
73761d06d6bSBaptiste Daroussin 				printf("%s/", scriptname);
73861d06d6bSBaptiste Daroussin 			if (strcmp(req->q.manpath, req->p[0]))
73961d06d6bSBaptiste Daroussin 				printf("%s/", req->q.manpath);
74061d06d6bSBaptiste Daroussin 			printf("%s\">", r[i].file);
74161d06d6bSBaptiste Daroussin 			html_print(r[i].names);
74261d06d6bSBaptiste Daroussin 			printf("</a></td>\n"
74361d06d6bSBaptiste Daroussin 			       "    <td><span class=\"Nd\">");
74461d06d6bSBaptiste Daroussin 			html_print(r[i].output);
74561d06d6bSBaptiste Daroussin 			puts("</span></td>\n"
74661d06d6bSBaptiste Daroussin 			     "  </tr>");
74761d06d6bSBaptiste Daroussin 		}
74861d06d6bSBaptiste Daroussin 		puts("</table>");
749*c1c95addSBrooks Davis 		puts("</nav>");
75061d06d6bSBaptiste Daroussin 	}
75161d06d6bSBaptiste Daroussin 
75261d06d6bSBaptiste Daroussin 	if (req->q.equal || sz == 1) {
75361d06d6bSBaptiste Daroussin 		puts("<hr>");
75461d06d6bSBaptiste Daroussin 		resp_show(req, r[iuse].file);
75561d06d6bSBaptiste Daroussin 	}
75661d06d6bSBaptiste Daroussin 
75761d06d6bSBaptiste Daroussin 	resp_end_html();
75861d06d6bSBaptiste Daroussin }
75961d06d6bSBaptiste Daroussin 
76061d06d6bSBaptiste Daroussin static void
resp_catman(const struct req * req,const char * file)76161d06d6bSBaptiste Daroussin resp_catman(const struct req *req, const char *file)
76261d06d6bSBaptiste Daroussin {
76361d06d6bSBaptiste Daroussin 	FILE		*f;
76461d06d6bSBaptiste Daroussin 	char		*p;
76561d06d6bSBaptiste Daroussin 	size_t		 sz;
76661d06d6bSBaptiste Daroussin 	ssize_t		 len;
76761d06d6bSBaptiste Daroussin 	int		 i;
76861d06d6bSBaptiste Daroussin 	int		 italic, bold;
76961d06d6bSBaptiste Daroussin 
77061d06d6bSBaptiste Daroussin 	if ((f = fopen(file, "r")) == NULL) {
771*c1c95addSBrooks Davis 		puts("<p role=\"doc-notice\">\n"
772*c1c95addSBrooks Davis 		     "  You specified an invalid manual file.\n"
773*c1c95addSBrooks Davis 		     "</p>");
77461d06d6bSBaptiste Daroussin 		return;
77561d06d6bSBaptiste Daroussin 	}
77661d06d6bSBaptiste Daroussin 
77761d06d6bSBaptiste Daroussin 	puts("<div class=\"catman\">\n"
77861d06d6bSBaptiste Daroussin 	     "<pre>");
77961d06d6bSBaptiste Daroussin 
78061d06d6bSBaptiste Daroussin 	p = NULL;
78161d06d6bSBaptiste Daroussin 	sz = 0;
78261d06d6bSBaptiste Daroussin 
78361d06d6bSBaptiste Daroussin 	while ((len = getline(&p, &sz, f)) != -1) {
78461d06d6bSBaptiste Daroussin 		bold = italic = 0;
78561d06d6bSBaptiste Daroussin 		for (i = 0; i < len - 1; i++) {
78661d06d6bSBaptiste Daroussin 			/*
78761d06d6bSBaptiste Daroussin 			 * This means that the catpage is out of state.
78861d06d6bSBaptiste Daroussin 			 * Ignore it and keep going (although the
78961d06d6bSBaptiste Daroussin 			 * catpage is bogus).
79061d06d6bSBaptiste Daroussin 			 */
79161d06d6bSBaptiste Daroussin 
79261d06d6bSBaptiste Daroussin 			if ('\b' == p[i] || '\n' == p[i])
79361d06d6bSBaptiste Daroussin 				continue;
79461d06d6bSBaptiste Daroussin 
79561d06d6bSBaptiste Daroussin 			/*
79661d06d6bSBaptiste Daroussin 			 * Print a regular character.
79761d06d6bSBaptiste Daroussin 			 * Close out any bold/italic scopes.
79861d06d6bSBaptiste Daroussin 			 * If we're in back-space mode, make sure we'll
79961d06d6bSBaptiste Daroussin 			 * have something to enter when we backspace.
80061d06d6bSBaptiste Daroussin 			 */
80161d06d6bSBaptiste Daroussin 
80261d06d6bSBaptiste Daroussin 			if ('\b' != p[i + 1]) {
80361d06d6bSBaptiste Daroussin 				if (italic)
80461d06d6bSBaptiste Daroussin 					printf("</i>");
80561d06d6bSBaptiste Daroussin 				if (bold)
80661d06d6bSBaptiste Daroussin 					printf("</b>");
80761d06d6bSBaptiste Daroussin 				italic = bold = 0;
80861d06d6bSBaptiste Daroussin 				html_putchar(p[i]);
80961d06d6bSBaptiste Daroussin 				continue;
81061d06d6bSBaptiste Daroussin 			} else if (i + 2 >= len)
81161d06d6bSBaptiste Daroussin 				continue;
81261d06d6bSBaptiste Daroussin 
81361d06d6bSBaptiste Daroussin 			/* Italic mode. */
81461d06d6bSBaptiste Daroussin 
81561d06d6bSBaptiste Daroussin 			if ('_' == p[i]) {
81661d06d6bSBaptiste Daroussin 				if (bold)
81761d06d6bSBaptiste Daroussin 					printf("</b>");
81861d06d6bSBaptiste Daroussin 				if ( ! italic)
81961d06d6bSBaptiste Daroussin 					printf("<i>");
82061d06d6bSBaptiste Daroussin 				bold = 0;
82161d06d6bSBaptiste Daroussin 				italic = 1;
82261d06d6bSBaptiste Daroussin 				i += 2;
82361d06d6bSBaptiste Daroussin 				html_putchar(p[i]);
82461d06d6bSBaptiste Daroussin 				continue;
82561d06d6bSBaptiste Daroussin 			}
82661d06d6bSBaptiste Daroussin 
82761d06d6bSBaptiste Daroussin 			/*
82861d06d6bSBaptiste Daroussin 			 * Handle funny behaviour troff-isms.
82961d06d6bSBaptiste Daroussin 			 * These grok'd from the original man2html.c.
83061d06d6bSBaptiste Daroussin 			 */
83161d06d6bSBaptiste Daroussin 
83261d06d6bSBaptiste Daroussin 			if (('+' == p[i] && 'o' == p[i + 2]) ||
83361d06d6bSBaptiste Daroussin 					('o' == p[i] && '+' == p[i + 2]) ||
83461d06d6bSBaptiste Daroussin 					('|' == p[i] && '=' == p[i + 2]) ||
83561d06d6bSBaptiste Daroussin 					('=' == p[i] && '|' == p[i + 2]) ||
83661d06d6bSBaptiste Daroussin 					('*' == p[i] && '=' == p[i + 2]) ||
83761d06d6bSBaptiste Daroussin 					('=' == p[i] && '*' == p[i + 2]) ||
83861d06d6bSBaptiste Daroussin 					('*' == p[i] && '|' == p[i + 2]) ||
83961d06d6bSBaptiste Daroussin 					('|' == p[i] && '*' == p[i + 2]))  {
84061d06d6bSBaptiste Daroussin 				if (italic)
84161d06d6bSBaptiste Daroussin 					printf("</i>");
84261d06d6bSBaptiste Daroussin 				if (bold)
84361d06d6bSBaptiste Daroussin 					printf("</b>");
84461d06d6bSBaptiste Daroussin 				italic = bold = 0;
84561d06d6bSBaptiste Daroussin 				putchar('*');
84661d06d6bSBaptiste Daroussin 				i += 2;
84761d06d6bSBaptiste Daroussin 				continue;
84861d06d6bSBaptiste Daroussin 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
84961d06d6bSBaptiste Daroussin 					('-' == p[i] && '|' == p[i + 1]) ||
85061d06d6bSBaptiste Daroussin 					('+' == p[i] && '-' == p[i + 1]) ||
85161d06d6bSBaptiste Daroussin 					('-' == p[i] && '+' == p[i + 1]) ||
85261d06d6bSBaptiste Daroussin 					('+' == p[i] && '|' == p[i + 1]) ||
85361d06d6bSBaptiste Daroussin 					('|' == p[i] && '+' == p[i + 1]))  {
85461d06d6bSBaptiste Daroussin 				if (italic)
85561d06d6bSBaptiste Daroussin 					printf("</i>");
85661d06d6bSBaptiste Daroussin 				if (bold)
85761d06d6bSBaptiste Daroussin 					printf("</b>");
85861d06d6bSBaptiste Daroussin 				italic = bold = 0;
85961d06d6bSBaptiste Daroussin 				putchar('+');
86061d06d6bSBaptiste Daroussin 				i += 2;
86161d06d6bSBaptiste Daroussin 				continue;
86261d06d6bSBaptiste Daroussin 			}
86361d06d6bSBaptiste Daroussin 
86461d06d6bSBaptiste Daroussin 			/* Bold mode. */
86561d06d6bSBaptiste Daroussin 
86661d06d6bSBaptiste Daroussin 			if (italic)
86761d06d6bSBaptiste Daroussin 				printf("</i>");
86861d06d6bSBaptiste Daroussin 			if ( ! bold)
86961d06d6bSBaptiste Daroussin 				printf("<b>");
87061d06d6bSBaptiste Daroussin 			bold = 1;
87161d06d6bSBaptiste Daroussin 			italic = 0;
87261d06d6bSBaptiste Daroussin 			i += 2;
87361d06d6bSBaptiste Daroussin 			html_putchar(p[i]);
87461d06d6bSBaptiste Daroussin 		}
87561d06d6bSBaptiste Daroussin 
87661d06d6bSBaptiste Daroussin 		/*
87761d06d6bSBaptiste Daroussin 		 * Clean up the last character.
87861d06d6bSBaptiste Daroussin 		 * We can get to a newline; don't print that.
87961d06d6bSBaptiste Daroussin 		 */
88061d06d6bSBaptiste Daroussin 
88161d06d6bSBaptiste Daroussin 		if (italic)
88261d06d6bSBaptiste Daroussin 			printf("</i>");
88361d06d6bSBaptiste Daroussin 		if (bold)
88461d06d6bSBaptiste Daroussin 			printf("</b>");
88561d06d6bSBaptiste Daroussin 
88661d06d6bSBaptiste Daroussin 		if (i == len - 1 && p[i] != '\n')
88761d06d6bSBaptiste Daroussin 			html_putchar(p[i]);
88861d06d6bSBaptiste Daroussin 
88961d06d6bSBaptiste Daroussin 		putchar('\n');
89061d06d6bSBaptiste Daroussin 	}
89161d06d6bSBaptiste Daroussin 	free(p);
89261d06d6bSBaptiste Daroussin 
89361d06d6bSBaptiste Daroussin 	puts("</pre>\n"
89461d06d6bSBaptiste Daroussin 	     "</div>");
89561d06d6bSBaptiste Daroussin 
89661d06d6bSBaptiste Daroussin 	fclose(f);
89761d06d6bSBaptiste Daroussin }
89861d06d6bSBaptiste Daroussin 
89961d06d6bSBaptiste Daroussin static void
resp_format(const struct req * req,const char * file)90061d06d6bSBaptiste Daroussin resp_format(const struct req *req, const char *file)
90161d06d6bSBaptiste Daroussin {
90261d06d6bSBaptiste Daroussin 	struct manoutput conf;
90361d06d6bSBaptiste Daroussin 	struct mparse	*mp;
9047295610fSBaptiste Daroussin 	struct roff_meta *meta;
90561d06d6bSBaptiste Daroussin 	void		*vp;
90661d06d6bSBaptiste Daroussin 	int		 fd;
90761d06d6bSBaptiste Daroussin 	int		 usepath;
90861d06d6bSBaptiste Daroussin 
909*c1c95addSBrooks Davis 	if (-1 == (fd = open(file, O_RDONLY))) {
910*c1c95addSBrooks Davis 		puts("<p role=\"doc-notice\">\n"
911*c1c95addSBrooks Davis 		     "  You specified an invalid manual file.\n"
912*c1c95addSBrooks Davis 		     "</p>");
91361d06d6bSBaptiste Daroussin 		return;
91461d06d6bSBaptiste Daroussin 	}
91561d06d6bSBaptiste Daroussin 
91661d06d6bSBaptiste Daroussin 	mchars_alloc();
9177295610fSBaptiste Daroussin 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
9187295610fSBaptiste Daroussin 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
91961d06d6bSBaptiste Daroussin 	mparse_readfd(mp, fd, file);
92061d06d6bSBaptiste Daroussin 	close(fd);
9217295610fSBaptiste Daroussin 	meta = mparse_result(mp);
92261d06d6bSBaptiste Daroussin 
92361d06d6bSBaptiste Daroussin 	memset(&conf, 0, sizeof(conf));
92461d06d6bSBaptiste Daroussin 	conf.fragment = 1;
92561d06d6bSBaptiste Daroussin 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
92661d06d6bSBaptiste Daroussin 	usepath = strcmp(req->q.manpath, req->p[0]);
92761d06d6bSBaptiste Daroussin 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
92861d06d6bSBaptiste Daroussin 	    scriptname, *scriptname == '\0' ? "" : "/",
92961d06d6bSBaptiste Daroussin 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
93061d06d6bSBaptiste Daroussin 
93161d06d6bSBaptiste Daroussin 	vp = html_alloc(&conf);
9327295610fSBaptiste Daroussin 	if (meta->macroset == MACROSET_MDOC)
9337295610fSBaptiste Daroussin 		html_mdoc(vp, meta);
9347295610fSBaptiste Daroussin 	else
9357295610fSBaptiste Daroussin 		html_man(vp, meta);
93661d06d6bSBaptiste Daroussin 
93761d06d6bSBaptiste Daroussin 	html_free(vp);
93861d06d6bSBaptiste Daroussin 	mparse_free(mp);
93961d06d6bSBaptiste Daroussin 	mchars_free();
94061d06d6bSBaptiste Daroussin 	free(conf.man);
94161d06d6bSBaptiste Daroussin 	free(conf.style);
94261d06d6bSBaptiste Daroussin }
94361d06d6bSBaptiste Daroussin 
94461d06d6bSBaptiste Daroussin static void
resp_show(const struct req * req,const char * file)94561d06d6bSBaptiste Daroussin resp_show(const struct req *req, const char *file)
94661d06d6bSBaptiste Daroussin {
94761d06d6bSBaptiste Daroussin 
94861d06d6bSBaptiste Daroussin 	if ('.' == file[0] && '/' == file[1])
94961d06d6bSBaptiste Daroussin 		file += 2;
95061d06d6bSBaptiste Daroussin 
95161d06d6bSBaptiste Daroussin 	if ('c' == *file)
95261d06d6bSBaptiste Daroussin 		resp_catman(req, file);
95361d06d6bSBaptiste Daroussin 	else
95461d06d6bSBaptiste Daroussin 		resp_format(req, file);
95561d06d6bSBaptiste Daroussin }
95661d06d6bSBaptiste Daroussin 
95761d06d6bSBaptiste Daroussin static void
pg_show(struct req * req,const char * fullpath)95861d06d6bSBaptiste Daroussin pg_show(struct req *req, const char *fullpath)
95961d06d6bSBaptiste Daroussin {
96061d06d6bSBaptiste Daroussin 	char		*manpath;
96161d06d6bSBaptiste Daroussin 	const char	*file;
96261d06d6bSBaptiste Daroussin 
96361d06d6bSBaptiste Daroussin 	if ((file = strchr(fullpath, '/')) == NULL) {
96461d06d6bSBaptiste Daroussin 		pg_error_badrequest(
96561d06d6bSBaptiste Daroussin 		    "You did not specify a page to show.");
96661d06d6bSBaptiste Daroussin 		return;
96761d06d6bSBaptiste Daroussin 	}
96861d06d6bSBaptiste Daroussin 	manpath = mandoc_strndup(fullpath, file - fullpath);
96961d06d6bSBaptiste Daroussin 	file++;
97061d06d6bSBaptiste Daroussin 
97161d06d6bSBaptiste Daroussin 	if ( ! validate_manpath(req, manpath)) {
97261d06d6bSBaptiste Daroussin 		pg_error_badrequest(
97361d06d6bSBaptiste Daroussin 		    "You specified an invalid manpath.");
97461d06d6bSBaptiste Daroussin 		free(manpath);
97561d06d6bSBaptiste Daroussin 		return;
97661d06d6bSBaptiste Daroussin 	}
97761d06d6bSBaptiste Daroussin 
97861d06d6bSBaptiste Daroussin 	/*
97961d06d6bSBaptiste Daroussin 	 * Begin by chdir()ing into the manpath.
98061d06d6bSBaptiste Daroussin 	 * This way we can pick up the database files, which are
98161d06d6bSBaptiste Daroussin 	 * relative to the manpath root.
98261d06d6bSBaptiste Daroussin 	 */
98361d06d6bSBaptiste Daroussin 
98461d06d6bSBaptiste Daroussin 	if (chdir(manpath) == -1) {
98561d06d6bSBaptiste Daroussin 		warn("chdir %s", manpath);
98661d06d6bSBaptiste Daroussin 		pg_error_internal();
98761d06d6bSBaptiste Daroussin 		free(manpath);
98861d06d6bSBaptiste Daroussin 		return;
98961d06d6bSBaptiste Daroussin 	}
99061d06d6bSBaptiste Daroussin 	free(manpath);
99161d06d6bSBaptiste Daroussin 
99261d06d6bSBaptiste Daroussin 	if ( ! validate_filename(file)) {
99361d06d6bSBaptiste Daroussin 		pg_error_badrequest(
99461d06d6bSBaptiste Daroussin 		    "You specified an invalid manual file.");
99561d06d6bSBaptiste Daroussin 		return;
99661d06d6bSBaptiste Daroussin 	}
99761d06d6bSBaptiste Daroussin 
998*c1c95addSBrooks Davis 	if (resp_begin_html(200, NULL, file) == 0)
999*c1c95addSBrooks Davis 		puts("<header>");
100061d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_NONE);
1001*c1c95addSBrooks Davis 	puts("</header>");
100261d06d6bSBaptiste Daroussin 	resp_show(req, file);
100361d06d6bSBaptiste Daroussin 	resp_end_html();
100461d06d6bSBaptiste Daroussin }
100561d06d6bSBaptiste Daroussin 
100661d06d6bSBaptiste Daroussin static void
pg_search(const struct req * req)100761d06d6bSBaptiste Daroussin pg_search(const struct req *req)
100861d06d6bSBaptiste Daroussin {
100961d06d6bSBaptiste Daroussin 	struct mansearch	  search;
101061d06d6bSBaptiste Daroussin 	struct manpaths		  paths;
101161d06d6bSBaptiste Daroussin 	struct manpage		 *res;
101261d06d6bSBaptiste Daroussin 	char			**argv;
101361d06d6bSBaptiste Daroussin 	char			 *query, *rp, *wp;
101461d06d6bSBaptiste Daroussin 	size_t			  ressz;
101561d06d6bSBaptiste Daroussin 	int			  argc;
101661d06d6bSBaptiste Daroussin 
101761d06d6bSBaptiste Daroussin 	/*
101861d06d6bSBaptiste Daroussin 	 * Begin by chdir()ing into the root of the manpath.
101961d06d6bSBaptiste Daroussin 	 * This way we can pick up the database files, which are
102061d06d6bSBaptiste Daroussin 	 * relative to the manpath root.
102161d06d6bSBaptiste Daroussin 	 */
102261d06d6bSBaptiste Daroussin 
102361d06d6bSBaptiste Daroussin 	if (chdir(req->q.manpath) == -1) {
102461d06d6bSBaptiste Daroussin 		warn("chdir %s", req->q.manpath);
102561d06d6bSBaptiste Daroussin 		pg_error_internal();
102661d06d6bSBaptiste Daroussin 		return;
102761d06d6bSBaptiste Daroussin 	}
102861d06d6bSBaptiste Daroussin 
102961d06d6bSBaptiste Daroussin 	search.arch = req->q.arch;
103061d06d6bSBaptiste Daroussin 	search.sec = req->q.sec;
103161d06d6bSBaptiste Daroussin 	search.outkey = "Nd";
103261d06d6bSBaptiste Daroussin 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
103361d06d6bSBaptiste Daroussin 	search.firstmatch = 1;
103461d06d6bSBaptiste Daroussin 
103561d06d6bSBaptiste Daroussin 	paths.sz = 1;
103661d06d6bSBaptiste Daroussin 	paths.paths = mandoc_malloc(sizeof(char *));
103761d06d6bSBaptiste Daroussin 	paths.paths[0] = mandoc_strdup(".");
103861d06d6bSBaptiste Daroussin 
103961d06d6bSBaptiste Daroussin 	/*
104061d06d6bSBaptiste Daroussin 	 * Break apart at spaces with backslash-escaping.
104161d06d6bSBaptiste Daroussin 	 */
104261d06d6bSBaptiste Daroussin 
104361d06d6bSBaptiste Daroussin 	argc = 0;
104461d06d6bSBaptiste Daroussin 	argv = NULL;
104561d06d6bSBaptiste Daroussin 	rp = query = mandoc_strdup(req->q.query);
104661d06d6bSBaptiste Daroussin 	for (;;) {
104761d06d6bSBaptiste Daroussin 		while (isspace((unsigned char)*rp))
104861d06d6bSBaptiste Daroussin 			rp++;
104961d06d6bSBaptiste Daroussin 		if (*rp == '\0')
105061d06d6bSBaptiste Daroussin 			break;
105161d06d6bSBaptiste Daroussin 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
105261d06d6bSBaptiste Daroussin 		argv[argc++] = wp = rp;
105361d06d6bSBaptiste Daroussin 		for (;;) {
105461d06d6bSBaptiste Daroussin 			if (isspace((unsigned char)*rp)) {
105561d06d6bSBaptiste Daroussin 				*wp = '\0';
105661d06d6bSBaptiste Daroussin 				rp++;
105761d06d6bSBaptiste Daroussin 				break;
105861d06d6bSBaptiste Daroussin 			}
105961d06d6bSBaptiste Daroussin 			if (rp[0] == '\\' && rp[1] != '\0')
106061d06d6bSBaptiste Daroussin 				rp++;
106161d06d6bSBaptiste Daroussin 			if (wp != rp)
106261d06d6bSBaptiste Daroussin 				*wp = *rp;
106361d06d6bSBaptiste Daroussin 			if (*rp == '\0')
106461d06d6bSBaptiste Daroussin 				break;
106561d06d6bSBaptiste Daroussin 			wp++;
106661d06d6bSBaptiste Daroussin 			rp++;
106761d06d6bSBaptiste Daroussin 		}
106861d06d6bSBaptiste Daroussin 	}
106961d06d6bSBaptiste Daroussin 
107061d06d6bSBaptiste Daroussin 	res = NULL;
107161d06d6bSBaptiste Daroussin 	ressz = 0;
107261d06d6bSBaptiste Daroussin 	if (req->isquery && req->q.equal && argc == 1)
107361d06d6bSBaptiste Daroussin 		pg_redirect(req, argv[0]);
107461d06d6bSBaptiste Daroussin 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
10756d38604fSBaptiste Daroussin 		pg_noresult(req, 400, "Bad Request",
10766d38604fSBaptiste Daroussin 		    "You entered an invalid query.");
107761d06d6bSBaptiste Daroussin 	else if (ressz == 0)
10786d38604fSBaptiste Daroussin 		pg_noresult(req, 404, "Not Found", "No results found.");
107961d06d6bSBaptiste Daroussin 	else
108061d06d6bSBaptiste Daroussin 		pg_searchres(req, res, ressz);
108161d06d6bSBaptiste Daroussin 
108261d06d6bSBaptiste Daroussin 	free(query);
108361d06d6bSBaptiste Daroussin 	mansearch_free(res, ressz);
108461d06d6bSBaptiste Daroussin 	free(paths.paths[0]);
108561d06d6bSBaptiste Daroussin 	free(paths.paths);
108661d06d6bSBaptiste Daroussin }
108761d06d6bSBaptiste Daroussin 
108861d06d6bSBaptiste Daroussin int
main(void)108961d06d6bSBaptiste Daroussin main(void)
109061d06d6bSBaptiste Daroussin {
109161d06d6bSBaptiste Daroussin 	struct req	 req;
109261d06d6bSBaptiste Daroussin 	struct itimerval itimer;
109361d06d6bSBaptiste Daroussin 	const char	*path;
109461d06d6bSBaptiste Daroussin 	const char	*querystring;
109561d06d6bSBaptiste Daroussin 	int		 i;
109661d06d6bSBaptiste Daroussin 
109761d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
109861d06d6bSBaptiste Daroussin 	/*
109961d06d6bSBaptiste Daroussin 	 * The "rpath" pledge could be revoked after mparse_readfd()
1100*c1c95addSBrooks Davis 	 * if the file descriptor to "/footer.html" would be opened
110161d06d6bSBaptiste Daroussin 	 * up front, but it's probably not worth the complication
110261d06d6bSBaptiste Daroussin 	 * of the code it would cause: it would require scattering
110361d06d6bSBaptiste Daroussin 	 * pledge() calls in multiple low-level resp_*() functions.
110461d06d6bSBaptiste Daroussin 	 */
110561d06d6bSBaptiste Daroussin 
110661d06d6bSBaptiste Daroussin 	if (pledge("stdio rpath", NULL) == -1) {
110761d06d6bSBaptiste Daroussin 		warn("pledge");
110861d06d6bSBaptiste Daroussin 		pg_error_internal();
110961d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
111061d06d6bSBaptiste Daroussin 	}
111161d06d6bSBaptiste Daroussin #endif
111261d06d6bSBaptiste Daroussin 
111361d06d6bSBaptiste Daroussin 	/* Poor man's ReDoS mitigation. */
111461d06d6bSBaptiste Daroussin 
111561d06d6bSBaptiste Daroussin 	itimer.it_value.tv_sec = 2;
111661d06d6bSBaptiste Daroussin 	itimer.it_value.tv_usec = 0;
111761d06d6bSBaptiste Daroussin 	itimer.it_interval.tv_sec = 2;
111861d06d6bSBaptiste Daroussin 	itimer.it_interval.tv_usec = 0;
111961d06d6bSBaptiste Daroussin 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
112061d06d6bSBaptiste Daroussin 		warn("setitimer");
112161d06d6bSBaptiste Daroussin 		pg_error_internal();
112261d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
112361d06d6bSBaptiste Daroussin 	}
112461d06d6bSBaptiste Daroussin 
112561d06d6bSBaptiste Daroussin 	/*
112661d06d6bSBaptiste Daroussin 	 * First we change directory into the MAN_DIR so that
112761d06d6bSBaptiste Daroussin 	 * subsequent scanning for manpath directories is rooted
112861d06d6bSBaptiste Daroussin 	 * relative to the same position.
112961d06d6bSBaptiste Daroussin 	 */
113061d06d6bSBaptiste Daroussin 
113161d06d6bSBaptiste Daroussin 	if (chdir(MAN_DIR) == -1) {
113261d06d6bSBaptiste Daroussin 		warn("MAN_DIR: %s", MAN_DIR);
113361d06d6bSBaptiste Daroussin 		pg_error_internal();
113461d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
113561d06d6bSBaptiste Daroussin 	}
113661d06d6bSBaptiste Daroussin 
113761d06d6bSBaptiste Daroussin 	memset(&req, 0, sizeof(struct req));
113861d06d6bSBaptiste Daroussin 	req.q.equal = 1;
113961d06d6bSBaptiste Daroussin 	parse_manpath_conf(&req);
114061d06d6bSBaptiste Daroussin 
114161d06d6bSBaptiste Daroussin 	/* Parse the path info and the query string. */
114261d06d6bSBaptiste Daroussin 
114361d06d6bSBaptiste Daroussin 	if ((path = getenv("PATH_INFO")) == NULL)
114461d06d6bSBaptiste Daroussin 		path = "";
114561d06d6bSBaptiste Daroussin 	else if (*path == '/')
114661d06d6bSBaptiste Daroussin 		path++;
114761d06d6bSBaptiste Daroussin 
114861d06d6bSBaptiste Daroussin 	if (*path != '\0') {
114961d06d6bSBaptiste Daroussin 		parse_path_info(&req, path);
115061d06d6bSBaptiste Daroussin 		if (req.q.manpath == NULL || req.q.sec == NULL ||
115161d06d6bSBaptiste Daroussin 		    *req.q.query == '\0' || access(path, F_OK) == -1)
115261d06d6bSBaptiste Daroussin 			path = "";
115361d06d6bSBaptiste Daroussin 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
115461d06d6bSBaptiste Daroussin 		parse_query_string(&req, querystring);
115561d06d6bSBaptiste Daroussin 
115661d06d6bSBaptiste Daroussin 	/* Validate parsed data and add defaults. */
115761d06d6bSBaptiste Daroussin 
115861d06d6bSBaptiste Daroussin 	if (req.q.manpath == NULL)
115961d06d6bSBaptiste Daroussin 		req.q.manpath = mandoc_strdup(req.p[0]);
116061d06d6bSBaptiste Daroussin 	else if ( ! validate_manpath(&req, req.q.manpath)) {
116161d06d6bSBaptiste Daroussin 		pg_error_badrequest(
116261d06d6bSBaptiste Daroussin 		    "You specified an invalid manpath.");
116361d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
116461d06d6bSBaptiste Daroussin 	}
116561d06d6bSBaptiste Daroussin 
11667295610fSBaptiste Daroussin 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
116761d06d6bSBaptiste Daroussin 		pg_error_badrequest(
116861d06d6bSBaptiste Daroussin 		    "You specified an invalid architecture.");
116961d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
117061d06d6bSBaptiste Daroussin 	}
117161d06d6bSBaptiste Daroussin 
117261d06d6bSBaptiste Daroussin 	/* Dispatch to the three different pages. */
117361d06d6bSBaptiste Daroussin 
117461d06d6bSBaptiste Daroussin 	if ('\0' != *path)
117561d06d6bSBaptiste Daroussin 		pg_show(&req, path);
117661d06d6bSBaptiste Daroussin 	else if (NULL != req.q.query)
117761d06d6bSBaptiste Daroussin 		pg_search(&req);
117861d06d6bSBaptiste Daroussin 	else
117961d06d6bSBaptiste Daroussin 		pg_index(&req);
118061d06d6bSBaptiste Daroussin 
118161d06d6bSBaptiste Daroussin 	free(req.q.manpath);
118261d06d6bSBaptiste Daroussin 	free(req.q.arch);
118361d06d6bSBaptiste Daroussin 	free(req.q.sec);
118461d06d6bSBaptiste Daroussin 	free(req.q.query);
118561d06d6bSBaptiste Daroussin 	for (i = 0; i < (int)req.psz; i++)
118661d06d6bSBaptiste Daroussin 		free(req.p[i]);
118761d06d6bSBaptiste Daroussin 	free(req.p);
118861d06d6bSBaptiste Daroussin 	return EXIT_SUCCESS;
118961d06d6bSBaptiste Daroussin }
119061d06d6bSBaptiste Daroussin 
119161d06d6bSBaptiste Daroussin /*
11927295610fSBaptiste Daroussin  * Translate PATH_INFO to a query.
119361d06d6bSBaptiste Daroussin  */
119461d06d6bSBaptiste Daroussin static void
parse_path_info(struct req * req,const char * path)119561d06d6bSBaptiste Daroussin parse_path_info(struct req *req, const char *path)
119661d06d6bSBaptiste Daroussin {
11977295610fSBaptiste Daroussin 	const char	*name, *sec, *end;
119861d06d6bSBaptiste Daroussin 
119961d06d6bSBaptiste Daroussin 	req->isquery = 0;
120061d06d6bSBaptiste Daroussin 	req->q.equal = 1;
12017295610fSBaptiste Daroussin 	req->q.manpath = NULL;
120261d06d6bSBaptiste Daroussin 	req->q.arch = NULL;
120361d06d6bSBaptiste Daroussin 
120461d06d6bSBaptiste Daroussin 	/* Mandatory manual page name. */
12057295610fSBaptiste Daroussin 	if ((name = strrchr(path, '/')) == NULL)
12067295610fSBaptiste Daroussin 		name = path;
12077295610fSBaptiste Daroussin 	else
12087295610fSBaptiste Daroussin 		name++;
120961d06d6bSBaptiste Daroussin 
121061d06d6bSBaptiste Daroussin 	/* Optional trailing section. */
12117295610fSBaptiste Daroussin 	sec = strrchr(name, '.');
12127295610fSBaptiste Daroussin 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
12137295610fSBaptiste Daroussin 		req->q.query = mandoc_strndup(name, sec - name - 1);
12147295610fSBaptiste Daroussin 		req->q.sec = mandoc_strdup(sec);
12157295610fSBaptiste Daroussin 	} else {
12167295610fSBaptiste Daroussin 		req->q.query = mandoc_strdup(name);
121761d06d6bSBaptiste Daroussin 		req->q.sec = NULL;
121861d06d6bSBaptiste Daroussin 	}
121961d06d6bSBaptiste Daroussin 
122061d06d6bSBaptiste Daroussin 	/* Handle the case of name[.section] only. */
12217295610fSBaptiste Daroussin 	if (name == path)
122261d06d6bSBaptiste Daroussin 		return;
122361d06d6bSBaptiste Daroussin 
12247295610fSBaptiste Daroussin 	/* Optional manpath. */
12257295610fSBaptiste Daroussin 	end = strchr(path, '/');
12267295610fSBaptiste Daroussin 	req->q.manpath = mandoc_strndup(path, end - path);
12277295610fSBaptiste Daroussin 	if (validate_manpath(req, req->q.manpath)) {
12287295610fSBaptiste Daroussin 		path = end + 1;
12297295610fSBaptiste Daroussin 		if (name == path)
12307295610fSBaptiste Daroussin 			return;
12317295610fSBaptiste Daroussin 	} else {
12327295610fSBaptiste Daroussin 		free(req->q.manpath);
12337295610fSBaptiste Daroussin 		req->q.manpath = NULL;
12347295610fSBaptiste Daroussin 	}
12357295610fSBaptiste Daroussin 
12367295610fSBaptiste Daroussin 	/* Optional section. */
12377295610fSBaptiste Daroussin 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
12387295610fSBaptiste Daroussin 		path += 3;
12397295610fSBaptiste Daroussin 		end = strchr(path, '/');
12407295610fSBaptiste Daroussin 		free(req->q.sec);
12417295610fSBaptiste Daroussin 		req->q.sec = mandoc_strndup(path, end - path);
12427295610fSBaptiste Daroussin 		path = end + 1;
12437295610fSBaptiste Daroussin 		if (name == path)
12447295610fSBaptiste Daroussin 			return;
12457295610fSBaptiste Daroussin 	}
12467295610fSBaptiste Daroussin 
12477295610fSBaptiste Daroussin 	/* Optional architecture. */
12487295610fSBaptiste Daroussin 	end = strchr(path, '/');
12497295610fSBaptiste Daroussin 	if (end + 1 != name) {
125061d06d6bSBaptiste Daroussin 		pg_error_badrequest(
125161d06d6bSBaptiste Daroussin 		    "You specified too many directory components.");
125261d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
125361d06d6bSBaptiste Daroussin 	}
12547295610fSBaptiste Daroussin 	req->q.arch = mandoc_strndup(path, end - path);
12557295610fSBaptiste Daroussin 	if (validate_arch(req->q.arch) == 0) {
125661d06d6bSBaptiste Daroussin 		pg_error_badrequest(
125761d06d6bSBaptiste Daroussin 		    "You specified an invalid directory component.");
125861d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
125961d06d6bSBaptiste Daroussin 	}
126061d06d6bSBaptiste Daroussin }
126161d06d6bSBaptiste Daroussin 
126261d06d6bSBaptiste Daroussin /*
126361d06d6bSBaptiste Daroussin  * Scan for indexable paths.
126461d06d6bSBaptiste Daroussin  */
126561d06d6bSBaptiste Daroussin static void
parse_manpath_conf(struct req * req)126661d06d6bSBaptiste Daroussin parse_manpath_conf(struct req *req)
126761d06d6bSBaptiste Daroussin {
126861d06d6bSBaptiste Daroussin 	FILE	*fp;
126961d06d6bSBaptiste Daroussin 	char	*dp;
127061d06d6bSBaptiste Daroussin 	size_t	 dpsz;
127161d06d6bSBaptiste Daroussin 	ssize_t	 len;
127261d06d6bSBaptiste Daroussin 
127361d06d6bSBaptiste Daroussin 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
127461d06d6bSBaptiste Daroussin 		warn("%s/manpath.conf", MAN_DIR);
127561d06d6bSBaptiste Daroussin 		pg_error_internal();
127661d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
127761d06d6bSBaptiste Daroussin 	}
127861d06d6bSBaptiste Daroussin 
127961d06d6bSBaptiste Daroussin 	dp = NULL;
128061d06d6bSBaptiste Daroussin 	dpsz = 0;
128161d06d6bSBaptiste Daroussin 
128261d06d6bSBaptiste Daroussin 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
128361d06d6bSBaptiste Daroussin 		if (dp[len - 1] == '\n')
128461d06d6bSBaptiste Daroussin 			dp[--len] = '\0';
128561d06d6bSBaptiste Daroussin 		req->p = mandoc_realloc(req->p,
128661d06d6bSBaptiste Daroussin 		    (req->psz + 1) * sizeof(char *));
128761d06d6bSBaptiste Daroussin 		if ( ! validate_urifrag(dp)) {
128861d06d6bSBaptiste Daroussin 			warnx("%s/manpath.conf contains "
128961d06d6bSBaptiste Daroussin 			    "unsafe path \"%s\"", MAN_DIR, dp);
129061d06d6bSBaptiste Daroussin 			pg_error_internal();
129161d06d6bSBaptiste Daroussin 			exit(EXIT_FAILURE);
129261d06d6bSBaptiste Daroussin 		}
129361d06d6bSBaptiste Daroussin 		if (strchr(dp, '/') != NULL) {
129461d06d6bSBaptiste Daroussin 			warnx("%s/manpath.conf contains "
129561d06d6bSBaptiste Daroussin 			    "path with slash \"%s\"", MAN_DIR, dp);
129661d06d6bSBaptiste Daroussin 			pg_error_internal();
129761d06d6bSBaptiste Daroussin 			exit(EXIT_FAILURE);
129861d06d6bSBaptiste Daroussin 		}
129961d06d6bSBaptiste Daroussin 		req->p[req->psz++] = dp;
130061d06d6bSBaptiste Daroussin 		dp = NULL;
130161d06d6bSBaptiste Daroussin 		dpsz = 0;
130261d06d6bSBaptiste Daroussin 	}
130361d06d6bSBaptiste Daroussin 	free(dp);
130461d06d6bSBaptiste Daroussin 
130561d06d6bSBaptiste Daroussin 	if (req->p == NULL) {
130661d06d6bSBaptiste Daroussin 		warnx("%s/manpath.conf is empty", MAN_DIR);
130761d06d6bSBaptiste Daroussin 		pg_error_internal();
130861d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
130961d06d6bSBaptiste Daroussin 	}
131061d06d6bSBaptiste Daroussin }
1311