xref: /freebsd/contrib/mandoc/cgi.c (revision 6d38604f)
1*6d38604fSBaptiste Daroussin /* $Id: cgi.c,v 1.175 2021/08/19 15:23:36 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*6d38604fSBaptiste Daroussin  * Copyright (c) 2014-2019, 2021 Ingo Schwarze <schwarze@usta.de>
461d06d6bSBaptiste Daroussin  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
561d06d6bSBaptiste Daroussin  *
661d06d6bSBaptiste Daroussin  * Permission to use, copy, modify, and distribute this software for any
761d06d6bSBaptiste Daroussin  * purpose with or without fee is hereby granted, provided that the above
861d06d6bSBaptiste Daroussin  * copyright notice and this permission notice appear in all copies.
961d06d6bSBaptiste Daroussin  *
1061d06d6bSBaptiste Daroussin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1161d06d6bSBaptiste Daroussin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1261d06d6bSBaptiste Daroussin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1361d06d6bSBaptiste Daroussin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1461d06d6bSBaptiste Daroussin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1561d06d6bSBaptiste Daroussin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1661d06d6bSBaptiste Daroussin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*6d38604fSBaptiste Daroussin  *
18*6d38604fSBaptiste Daroussin  * Implementation of the man.cgi(8) program.
1961d06d6bSBaptiste Daroussin  */
2061d06d6bSBaptiste Daroussin #include "config.h"
2161d06d6bSBaptiste Daroussin 
2261d06d6bSBaptiste Daroussin #include <sys/types.h>
2361d06d6bSBaptiste Daroussin #include <sys/time.h>
2461d06d6bSBaptiste Daroussin 
2561d06d6bSBaptiste Daroussin #include <ctype.h>
2661d06d6bSBaptiste Daroussin #if HAVE_ERR
2761d06d6bSBaptiste Daroussin #include <err.h>
2861d06d6bSBaptiste Daroussin #endif
2961d06d6bSBaptiste Daroussin #include <errno.h>
3061d06d6bSBaptiste Daroussin #include <fcntl.h>
3161d06d6bSBaptiste Daroussin #include <limits.h>
3261d06d6bSBaptiste Daroussin #include <stdint.h>
3361d06d6bSBaptiste Daroussin #include <stdio.h>
3461d06d6bSBaptiste Daroussin #include <stdlib.h>
3561d06d6bSBaptiste Daroussin #include <string.h>
3661d06d6bSBaptiste Daroussin #include <unistd.h>
3761d06d6bSBaptiste Daroussin 
3861d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3961d06d6bSBaptiste Daroussin #include "mandoc.h"
4061d06d6bSBaptiste Daroussin #include "roff.h"
4161d06d6bSBaptiste Daroussin #include "mdoc.h"
4261d06d6bSBaptiste Daroussin #include "man.h"
437295610fSBaptiste Daroussin #include "mandoc_parse.h"
4461d06d6bSBaptiste Daroussin #include "main.h"
4561d06d6bSBaptiste Daroussin #include "manconf.h"
4661d06d6bSBaptiste Daroussin #include "mansearch.h"
4761d06d6bSBaptiste Daroussin #include "cgi.h"
4861d06d6bSBaptiste Daroussin 
4961d06d6bSBaptiste Daroussin /*
5061d06d6bSBaptiste Daroussin  * A query as passed to the search function.
5161d06d6bSBaptiste Daroussin  */
5261d06d6bSBaptiste Daroussin struct	query {
5361d06d6bSBaptiste Daroussin 	char		*manpath; /* desired manual directory */
5461d06d6bSBaptiste Daroussin 	char		*arch; /* architecture */
5561d06d6bSBaptiste Daroussin 	char		*sec; /* manual section */
5661d06d6bSBaptiste Daroussin 	char		*query; /* unparsed query expression */
5761d06d6bSBaptiste Daroussin 	int		 equal; /* match whole names, not substrings */
5861d06d6bSBaptiste Daroussin };
5961d06d6bSBaptiste Daroussin 
6061d06d6bSBaptiste Daroussin struct	req {
6161d06d6bSBaptiste Daroussin 	struct query	  q;
6261d06d6bSBaptiste Daroussin 	char		**p; /* array of available manpaths */
6361d06d6bSBaptiste Daroussin 	size_t		  psz; /* number of available manpaths */
6461d06d6bSBaptiste Daroussin 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
6561d06d6bSBaptiste Daroussin };
6661d06d6bSBaptiste Daroussin 
6761d06d6bSBaptiste Daroussin enum	focus {
6861d06d6bSBaptiste Daroussin 	FOCUS_NONE = 0,
6961d06d6bSBaptiste Daroussin 	FOCUS_QUERY
7061d06d6bSBaptiste Daroussin };
7161d06d6bSBaptiste Daroussin 
7261d06d6bSBaptiste Daroussin static	void		 html_print(const char *);
7361d06d6bSBaptiste Daroussin static	void		 html_putchar(char);
7461d06d6bSBaptiste Daroussin static	int		 http_decode(char *);
75*6d38604fSBaptiste Daroussin static	void		 http_encode(const char *);
7661d06d6bSBaptiste Daroussin static	void		 parse_manpath_conf(struct req *);
77*6d38604fSBaptiste Daroussin static	void		 parse_path_info(struct req *, const char *);
7861d06d6bSBaptiste Daroussin static	void		 parse_query_string(struct req *, const char *);
7961d06d6bSBaptiste Daroussin static	void		 pg_error_badrequest(const char *);
8061d06d6bSBaptiste Daroussin static	void		 pg_error_internal(void);
8161d06d6bSBaptiste Daroussin static	void		 pg_index(const struct req *);
82*6d38604fSBaptiste Daroussin static	void		 pg_noresult(const struct req *, int, const char *,
83*6d38604fSBaptiste Daroussin 				const char *);
8461d06d6bSBaptiste Daroussin static	void		 pg_redirect(const struct req *, const char *);
8561d06d6bSBaptiste Daroussin static	void		 pg_search(const struct req *);
8661d06d6bSBaptiste Daroussin static	void		 pg_searchres(const struct req *,
8761d06d6bSBaptiste Daroussin 				struct manpage *, size_t);
8861d06d6bSBaptiste Daroussin static	void		 pg_show(struct req *, const char *);
8961d06d6bSBaptiste Daroussin static	void		 resp_begin_html(int, const char *, const char *);
9061d06d6bSBaptiste Daroussin static	void		 resp_begin_http(int, const char *);
9161d06d6bSBaptiste Daroussin static	void		 resp_catman(const struct req *, const char *);
9261d06d6bSBaptiste Daroussin static	void		 resp_copy(const char *);
9361d06d6bSBaptiste Daroussin static	void		 resp_end_html(void);
9461d06d6bSBaptiste Daroussin static	void		 resp_format(const struct req *, const char *);
9561d06d6bSBaptiste Daroussin static	void		 resp_searchform(const struct req *, enum focus);
9661d06d6bSBaptiste Daroussin static	void		 resp_show(const struct req *, const char *);
9761d06d6bSBaptiste Daroussin static	void		 set_query_attr(char **, char **);
987295610fSBaptiste Daroussin static	int		 validate_arch(const char *);
9961d06d6bSBaptiste Daroussin static	int		 validate_filename(const char *);
10061d06d6bSBaptiste Daroussin static	int		 validate_manpath(const struct req *, const char *);
10161d06d6bSBaptiste Daroussin static	int		 validate_urifrag(const char *);
10261d06d6bSBaptiste Daroussin 
10361d06d6bSBaptiste Daroussin static	const char	 *scriptname = SCRIPT_NAME;
10461d06d6bSBaptiste Daroussin 
10561d06d6bSBaptiste Daroussin static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
10661d06d6bSBaptiste Daroussin static	const char *const sec_numbers[] = {
10761d06d6bSBaptiste Daroussin     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10861d06d6bSBaptiste Daroussin };
10961d06d6bSBaptiste Daroussin static	const char *const sec_names[] = {
11061d06d6bSBaptiste Daroussin     "All Sections",
11161d06d6bSBaptiste Daroussin     "1 - General Commands",
11261d06d6bSBaptiste Daroussin     "2 - System Calls",
11361d06d6bSBaptiste Daroussin     "3 - Library Functions",
11461d06d6bSBaptiste Daroussin     "3p - Perl Library",
11561d06d6bSBaptiste Daroussin     "4 - Device Drivers",
11661d06d6bSBaptiste Daroussin     "5 - File Formats",
11761d06d6bSBaptiste Daroussin     "6 - Games",
11861d06d6bSBaptiste Daroussin     "7 - Miscellaneous Information",
11961d06d6bSBaptiste Daroussin     "8 - System Manager\'s Manual",
12061d06d6bSBaptiste Daroussin     "9 - Kernel Developer\'s Manual"
12161d06d6bSBaptiste Daroussin };
12261d06d6bSBaptiste Daroussin static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
12361d06d6bSBaptiste Daroussin 
12461d06d6bSBaptiste Daroussin static	const char *const arch_names[] = {
12561d06d6bSBaptiste Daroussin     "amd64",       "alpha",       "armv7",       "arm64",
126*6d38604fSBaptiste Daroussin     "hppa",        "i386",        "landisk",     "loongson",
127*6d38604fSBaptiste Daroussin     "luna88k",     "macppc",      "mips64",      "octeon",
128*6d38604fSBaptiste Daroussin     "powerpc64",   "riscv64",     "sparc64",
129*6d38604fSBaptiste Daroussin 
13061d06d6bSBaptiste Daroussin     "amiga",       "arc",         "armish",      "arm32",
13161d06d6bSBaptiste Daroussin     "atari",       "aviion",      "beagle",      "cats",
13261d06d6bSBaptiste Daroussin     "hppa64",      "hp300",
13361d06d6bSBaptiste Daroussin     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
13461d06d6bSBaptiste Daroussin     "mvmeppc",     "palm",        "pc532",       "pegasos",
135*6d38604fSBaptiste Daroussin     "pmax",        "powerpc",     "sgi",         "socppc",
136*6d38604fSBaptiste Daroussin     "solbourne",   "sparc",
13761d06d6bSBaptiste Daroussin     "sun3",        "vax",         "wgrisc",      "x68k",
13861d06d6bSBaptiste Daroussin     "zaurus"
13961d06d6bSBaptiste Daroussin };
14061d06d6bSBaptiste Daroussin static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
14161d06d6bSBaptiste Daroussin 
14261d06d6bSBaptiste Daroussin /*
14361d06d6bSBaptiste Daroussin  * Print a character, escaping HTML along the way.
14461d06d6bSBaptiste Daroussin  * This will pass non-ASCII straight to output: be warned!
14561d06d6bSBaptiste Daroussin  */
14661d06d6bSBaptiste Daroussin static void
html_putchar(char c)14761d06d6bSBaptiste Daroussin html_putchar(char c)
14861d06d6bSBaptiste Daroussin {
14961d06d6bSBaptiste Daroussin 
15061d06d6bSBaptiste Daroussin 	switch (c) {
15161d06d6bSBaptiste Daroussin 	case '"':
15261d06d6bSBaptiste Daroussin 		printf("&quot;");
15361d06d6bSBaptiste Daroussin 		break;
15461d06d6bSBaptiste Daroussin 	case '&':
15561d06d6bSBaptiste Daroussin 		printf("&amp;");
15661d06d6bSBaptiste Daroussin 		break;
15761d06d6bSBaptiste Daroussin 	case '>':
15861d06d6bSBaptiste Daroussin 		printf("&gt;");
15961d06d6bSBaptiste Daroussin 		break;
16061d06d6bSBaptiste Daroussin 	case '<':
16161d06d6bSBaptiste Daroussin 		printf("&lt;");
16261d06d6bSBaptiste Daroussin 		break;
16361d06d6bSBaptiste Daroussin 	default:
16461d06d6bSBaptiste Daroussin 		putchar((unsigned char)c);
16561d06d6bSBaptiste Daroussin 		break;
16661d06d6bSBaptiste Daroussin 	}
16761d06d6bSBaptiste Daroussin }
16861d06d6bSBaptiste Daroussin 
16961d06d6bSBaptiste Daroussin /*
17061d06d6bSBaptiste Daroussin  * Call through to html_putchar().
17161d06d6bSBaptiste Daroussin  * Accepts NULL strings.
17261d06d6bSBaptiste Daroussin  */
17361d06d6bSBaptiste Daroussin static void
html_print(const char * p)17461d06d6bSBaptiste Daroussin html_print(const char *p)
17561d06d6bSBaptiste Daroussin {
17661d06d6bSBaptiste Daroussin 
17761d06d6bSBaptiste Daroussin 	if (NULL == p)
17861d06d6bSBaptiste Daroussin 		return;
17961d06d6bSBaptiste Daroussin 	while ('\0' != *p)
18061d06d6bSBaptiste Daroussin 		html_putchar(*p++);
18161d06d6bSBaptiste Daroussin }
18261d06d6bSBaptiste Daroussin 
18361d06d6bSBaptiste Daroussin /*
18461d06d6bSBaptiste Daroussin  * Transfer the responsibility for the allocated string *val
18561d06d6bSBaptiste Daroussin  * to the query structure.
18661d06d6bSBaptiste Daroussin  */
18761d06d6bSBaptiste Daroussin static void
set_query_attr(char ** attr,char ** val)18861d06d6bSBaptiste Daroussin set_query_attr(char **attr, char **val)
18961d06d6bSBaptiste Daroussin {
19061d06d6bSBaptiste Daroussin 
19161d06d6bSBaptiste Daroussin 	free(*attr);
19261d06d6bSBaptiste Daroussin 	if (**val == '\0') {
19361d06d6bSBaptiste Daroussin 		*attr = NULL;
19461d06d6bSBaptiste Daroussin 		free(*val);
19561d06d6bSBaptiste Daroussin 	} else
19661d06d6bSBaptiste Daroussin 		*attr = *val;
19761d06d6bSBaptiste Daroussin 	*val = NULL;
19861d06d6bSBaptiste Daroussin }
19961d06d6bSBaptiste Daroussin 
20061d06d6bSBaptiste Daroussin /*
20161d06d6bSBaptiste Daroussin  * Parse the QUERY_STRING for key-value pairs
20261d06d6bSBaptiste Daroussin  * and store the values into the query structure.
20361d06d6bSBaptiste Daroussin  */
20461d06d6bSBaptiste Daroussin static void
parse_query_string(struct req * req,const char * qs)20561d06d6bSBaptiste Daroussin parse_query_string(struct req *req, const char *qs)
20661d06d6bSBaptiste Daroussin {
20761d06d6bSBaptiste Daroussin 	char		*key, *val;
20861d06d6bSBaptiste Daroussin 	size_t		 keysz, valsz;
20961d06d6bSBaptiste Daroussin 
21061d06d6bSBaptiste Daroussin 	req->isquery	= 1;
21161d06d6bSBaptiste Daroussin 	req->q.manpath	= NULL;
21261d06d6bSBaptiste Daroussin 	req->q.arch	= NULL;
21361d06d6bSBaptiste Daroussin 	req->q.sec	= NULL;
21461d06d6bSBaptiste Daroussin 	req->q.query	= NULL;
21561d06d6bSBaptiste Daroussin 	req->q.equal	= 1;
21661d06d6bSBaptiste Daroussin 
21761d06d6bSBaptiste Daroussin 	key = val = NULL;
21861d06d6bSBaptiste Daroussin 	while (*qs != '\0') {
21961d06d6bSBaptiste Daroussin 
22061d06d6bSBaptiste Daroussin 		/* Parse one key. */
22161d06d6bSBaptiste Daroussin 
22261d06d6bSBaptiste Daroussin 		keysz = strcspn(qs, "=;&");
22361d06d6bSBaptiste Daroussin 		key = mandoc_strndup(qs, keysz);
22461d06d6bSBaptiste Daroussin 		qs += keysz;
22561d06d6bSBaptiste Daroussin 		if (*qs != '=')
22661d06d6bSBaptiste Daroussin 			goto next;
22761d06d6bSBaptiste Daroussin 
22861d06d6bSBaptiste Daroussin 		/* Parse one value. */
22961d06d6bSBaptiste Daroussin 
23061d06d6bSBaptiste Daroussin 		valsz = strcspn(++qs, ";&");
23161d06d6bSBaptiste Daroussin 		val = mandoc_strndup(qs, valsz);
23261d06d6bSBaptiste Daroussin 		qs += valsz;
23361d06d6bSBaptiste Daroussin 
23461d06d6bSBaptiste Daroussin 		/* Decode and catch encoding errors. */
23561d06d6bSBaptiste Daroussin 
23661d06d6bSBaptiste Daroussin 		if ( ! (http_decode(key) && http_decode(val)))
23761d06d6bSBaptiste Daroussin 			goto next;
23861d06d6bSBaptiste Daroussin 
23961d06d6bSBaptiste Daroussin 		/* Handle key-value pairs. */
24061d06d6bSBaptiste Daroussin 
24161d06d6bSBaptiste Daroussin 		if ( ! strcmp(key, "query"))
24261d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.query, &val);
24361d06d6bSBaptiste Daroussin 
24461d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "apropos"))
24561d06d6bSBaptiste Daroussin 			req->q.equal = !strcmp(val, "0");
24661d06d6bSBaptiste Daroussin 
24761d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "manpath")) {
24861d06d6bSBaptiste Daroussin #ifdef COMPAT_OLDURI
24961d06d6bSBaptiste Daroussin 			if ( ! strncmp(val, "OpenBSD ", 8)) {
25061d06d6bSBaptiste Daroussin 				val[7] = '-';
25161d06d6bSBaptiste Daroussin 				if ('C' == val[8])
25261d06d6bSBaptiste Daroussin 					val[8] = 'c';
25361d06d6bSBaptiste Daroussin 			}
25461d06d6bSBaptiste Daroussin #endif
25561d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.manpath, &val);
25661d06d6bSBaptiste Daroussin 		}
25761d06d6bSBaptiste Daroussin 
25861d06d6bSBaptiste Daroussin 		else if ( ! (strcmp(key, "sec")
25961d06d6bSBaptiste Daroussin #ifdef COMPAT_OLDURI
26061d06d6bSBaptiste Daroussin 		    && strcmp(key, "sektion")
26161d06d6bSBaptiste Daroussin #endif
26261d06d6bSBaptiste Daroussin 		    )) {
26361d06d6bSBaptiste Daroussin 			if ( ! strcmp(val, "0"))
26461d06d6bSBaptiste Daroussin 				*val = '\0';
26561d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.sec, &val);
26661d06d6bSBaptiste Daroussin 		}
26761d06d6bSBaptiste Daroussin 
26861d06d6bSBaptiste Daroussin 		else if ( ! strcmp(key, "arch")) {
26961d06d6bSBaptiste Daroussin 			if ( ! strcmp(val, "default"))
27061d06d6bSBaptiste Daroussin 				*val = '\0';
27161d06d6bSBaptiste Daroussin 			set_query_attr(&req->q.arch, &val);
27261d06d6bSBaptiste Daroussin 		}
27361d06d6bSBaptiste Daroussin 
27461d06d6bSBaptiste Daroussin 		/*
27561d06d6bSBaptiste Daroussin 		 * The key must be freed in any case.
27661d06d6bSBaptiste Daroussin 		 * The val may have been handed over to the query
27761d06d6bSBaptiste Daroussin 		 * structure, in which case it is now NULL.
27861d06d6bSBaptiste Daroussin 		 */
27961d06d6bSBaptiste Daroussin next:
28061d06d6bSBaptiste Daroussin 		free(key);
28161d06d6bSBaptiste Daroussin 		key = NULL;
28261d06d6bSBaptiste Daroussin 		free(val);
28361d06d6bSBaptiste Daroussin 		val = NULL;
28461d06d6bSBaptiste Daroussin 
28561d06d6bSBaptiste Daroussin 		if (*qs != '\0')
28661d06d6bSBaptiste Daroussin 			qs++;
28761d06d6bSBaptiste Daroussin 	}
28861d06d6bSBaptiste Daroussin }
28961d06d6bSBaptiste Daroussin 
29061d06d6bSBaptiste Daroussin /*
29161d06d6bSBaptiste Daroussin  * HTTP-decode a string.  The standard explanation is that this turns
29261d06d6bSBaptiste Daroussin  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
29361d06d6bSBaptiste Daroussin  * over the allocated string.
29461d06d6bSBaptiste Daroussin  */
29561d06d6bSBaptiste Daroussin static int
http_decode(char * p)29661d06d6bSBaptiste Daroussin http_decode(char *p)
29761d06d6bSBaptiste Daroussin {
29861d06d6bSBaptiste Daroussin 	char             hex[3];
29961d06d6bSBaptiste Daroussin 	char		*q;
30061d06d6bSBaptiste Daroussin 	int              c;
30161d06d6bSBaptiste Daroussin 
30261d06d6bSBaptiste Daroussin 	hex[2] = '\0';
30361d06d6bSBaptiste Daroussin 
30461d06d6bSBaptiste Daroussin 	q = p;
30561d06d6bSBaptiste Daroussin 	for ( ; '\0' != *p; p++, q++) {
30661d06d6bSBaptiste Daroussin 		if ('%' == *p) {
30761d06d6bSBaptiste Daroussin 			if ('\0' == (hex[0] = *(p + 1)))
30861d06d6bSBaptiste Daroussin 				return 0;
30961d06d6bSBaptiste Daroussin 			if ('\0' == (hex[1] = *(p + 2)))
31061d06d6bSBaptiste Daroussin 				return 0;
31161d06d6bSBaptiste Daroussin 			if (1 != sscanf(hex, "%x", &c))
31261d06d6bSBaptiste Daroussin 				return 0;
31361d06d6bSBaptiste Daroussin 			if ('\0' == c)
31461d06d6bSBaptiste Daroussin 				return 0;
31561d06d6bSBaptiste Daroussin 
31661d06d6bSBaptiste Daroussin 			*q = (char)c;
31761d06d6bSBaptiste Daroussin 			p += 2;
31861d06d6bSBaptiste Daroussin 		} else
31961d06d6bSBaptiste Daroussin 			*q = '+' == *p ? ' ' : *p;
32061d06d6bSBaptiste Daroussin 	}
32161d06d6bSBaptiste Daroussin 
32261d06d6bSBaptiste Daroussin 	*q = '\0';
32361d06d6bSBaptiste Daroussin 	return 1;
32461d06d6bSBaptiste Daroussin }
32561d06d6bSBaptiste Daroussin 
32661d06d6bSBaptiste Daroussin static void
http_encode(const char * p)3277295610fSBaptiste Daroussin http_encode(const char *p)
3287295610fSBaptiste Daroussin {
3297295610fSBaptiste Daroussin 	for (; *p != '\0'; p++) {
3307295610fSBaptiste Daroussin 		if (isalnum((unsigned char)*p) == 0 &&
3317295610fSBaptiste Daroussin 		    strchr("-._~", *p) == NULL)
3327295610fSBaptiste Daroussin 			printf("%%%2.2X", (unsigned char)*p);
3337295610fSBaptiste Daroussin 		else
3347295610fSBaptiste Daroussin 			putchar(*p);
3357295610fSBaptiste Daroussin 	}
3367295610fSBaptiste Daroussin }
3377295610fSBaptiste Daroussin 
3387295610fSBaptiste Daroussin static void
resp_begin_http(int code,const char * msg)33961d06d6bSBaptiste Daroussin resp_begin_http(int code, const char *msg)
34061d06d6bSBaptiste Daroussin {
34161d06d6bSBaptiste Daroussin 
34261d06d6bSBaptiste Daroussin 	if (200 != code)
34361d06d6bSBaptiste Daroussin 		printf("Status: %d %s\r\n", code, msg);
34461d06d6bSBaptiste Daroussin 
34561d06d6bSBaptiste Daroussin 	printf("Content-Type: text/html; charset=utf-8\r\n"
34661d06d6bSBaptiste Daroussin 	     "Cache-Control: no-cache\r\n"
347*6d38604fSBaptiste Daroussin 	     "Content-Security-Policy: default-src 'none'; "
348*6d38604fSBaptiste Daroussin 	     "style-src 'self' 'unsafe-inline'\r\n"
34961d06d6bSBaptiste Daroussin 	     "Pragma: no-cache\r\n"
35061d06d6bSBaptiste Daroussin 	     "\r\n");
35161d06d6bSBaptiste Daroussin 
35261d06d6bSBaptiste Daroussin 	fflush(stdout);
35361d06d6bSBaptiste Daroussin }
35461d06d6bSBaptiste Daroussin 
35561d06d6bSBaptiste Daroussin static void
resp_copy(const char * filename)35661d06d6bSBaptiste Daroussin resp_copy(const char *filename)
35761d06d6bSBaptiste Daroussin {
35861d06d6bSBaptiste Daroussin 	char	 buf[4096];
35961d06d6bSBaptiste Daroussin 	ssize_t	 sz;
36061d06d6bSBaptiste Daroussin 	int	 fd;
36161d06d6bSBaptiste Daroussin 
36261d06d6bSBaptiste Daroussin 	if ((fd = open(filename, O_RDONLY)) != -1) {
36361d06d6bSBaptiste Daroussin 		fflush(stdout);
36461d06d6bSBaptiste Daroussin 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
36561d06d6bSBaptiste Daroussin 			write(STDOUT_FILENO, buf, sz);
36661d06d6bSBaptiste Daroussin 		close(fd);
36761d06d6bSBaptiste Daroussin 	}
36861d06d6bSBaptiste Daroussin }
36961d06d6bSBaptiste Daroussin 
37061d06d6bSBaptiste Daroussin static void
resp_begin_html(int code,const char * msg,const char * file)37161d06d6bSBaptiste Daroussin resp_begin_html(int code, const char *msg, const char *file)
37261d06d6bSBaptiste Daroussin {
373*6d38604fSBaptiste Daroussin 	const char	*name, *sec, *cp;
374*6d38604fSBaptiste Daroussin 	int		 namesz, secsz;
37561d06d6bSBaptiste Daroussin 
37661d06d6bSBaptiste Daroussin 	resp_begin_http(code, msg);
37761d06d6bSBaptiste Daroussin 
37861d06d6bSBaptiste Daroussin 	printf("<!DOCTYPE html>\n"
37961d06d6bSBaptiste Daroussin 	       "<html>\n"
38061d06d6bSBaptiste Daroussin 	       "<head>\n"
38161d06d6bSBaptiste Daroussin 	       "  <meta charset=\"UTF-8\"/>\n"
38261d06d6bSBaptiste Daroussin 	       "  <meta name=\"viewport\""
38361d06d6bSBaptiste Daroussin 		      " content=\"width=device-width, initial-scale=1.0\">\n"
38461d06d6bSBaptiste Daroussin 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
38561d06d6bSBaptiste Daroussin 	       " type=\"text/css\" media=\"all\">\n"
38661d06d6bSBaptiste Daroussin 	       "  <title>",
38761d06d6bSBaptiste Daroussin 	       CSS_DIR);
38861d06d6bSBaptiste Daroussin 	if (file != NULL) {
389*6d38604fSBaptiste Daroussin 		cp = strrchr(file, '/');
390*6d38604fSBaptiste Daroussin 		name = cp == NULL ? file : cp + 1;
391*6d38604fSBaptiste Daroussin 		cp = strrchr(name, '.');
392*6d38604fSBaptiste Daroussin 		namesz = cp == NULL ? strlen(name) : cp - name;
393*6d38604fSBaptiste Daroussin 		sec = NULL;
394*6d38604fSBaptiste Daroussin 		if (cp != NULL && cp[1] != '0') {
395*6d38604fSBaptiste Daroussin 			sec = cp + 1;
396*6d38604fSBaptiste Daroussin 			secsz = strlen(sec);
397*6d38604fSBaptiste Daroussin 		} else if (name - file > 1) {
398*6d38604fSBaptiste Daroussin 			for (cp = name - 2; cp >= file; cp--) {
399*6d38604fSBaptiste Daroussin 				if (*cp < '1' || *cp > '9')
400*6d38604fSBaptiste Daroussin 					continue;
401*6d38604fSBaptiste Daroussin 				sec = cp;
402*6d38604fSBaptiste Daroussin 				secsz = name - cp - 1;
403*6d38604fSBaptiste Daroussin 				break;
404*6d38604fSBaptiste Daroussin 			}
405*6d38604fSBaptiste Daroussin 		}
406*6d38604fSBaptiste Daroussin 		printf("%.*s", namesz, name);
407*6d38604fSBaptiste Daroussin 		if (sec != NULL)
408*6d38604fSBaptiste Daroussin 			printf("(%.*s)", secsz, sec);
409*6d38604fSBaptiste Daroussin 		fputs(" - ", stdout);
41061d06d6bSBaptiste Daroussin 	}
41161d06d6bSBaptiste Daroussin 	printf("%s</title>\n"
41261d06d6bSBaptiste Daroussin 	       "</head>\n"
41361d06d6bSBaptiste Daroussin 	       "<body>\n",
41461d06d6bSBaptiste Daroussin 	       CUSTOMIZE_TITLE);
41561d06d6bSBaptiste Daroussin 
41661d06d6bSBaptiste Daroussin 	resp_copy(MAN_DIR "/header.html");
41761d06d6bSBaptiste Daroussin }
41861d06d6bSBaptiste Daroussin 
41961d06d6bSBaptiste Daroussin static void
resp_end_html(void)42061d06d6bSBaptiste Daroussin resp_end_html(void)
42161d06d6bSBaptiste Daroussin {
42261d06d6bSBaptiste Daroussin 
42361d06d6bSBaptiste Daroussin 	resp_copy(MAN_DIR "/footer.html");
42461d06d6bSBaptiste Daroussin 
42561d06d6bSBaptiste Daroussin 	puts("</body>\n"
42661d06d6bSBaptiste Daroussin 	     "</html>");
42761d06d6bSBaptiste Daroussin }
42861d06d6bSBaptiste Daroussin 
42961d06d6bSBaptiste Daroussin static void
resp_searchform(const struct req * req,enum focus focus)43061d06d6bSBaptiste Daroussin resp_searchform(const struct req *req, enum focus focus)
43161d06d6bSBaptiste Daroussin {
43261d06d6bSBaptiste Daroussin 	int		 i;
43361d06d6bSBaptiste Daroussin 
434*6d38604fSBaptiste Daroussin 	printf("<form action=\"/%s\" method=\"get\" "
435*6d38604fSBaptiste Daroussin 	       "autocomplete=\"off\" autocapitalize=\"none\">\n"
43661d06d6bSBaptiste Daroussin 	       "  <fieldset>\n"
43761d06d6bSBaptiste Daroussin 	       "    <legend>Manual Page Search Parameters</legend>\n",
43861d06d6bSBaptiste Daroussin 	       scriptname);
43961d06d6bSBaptiste Daroussin 
44061d06d6bSBaptiste Daroussin 	/* Write query input box. */
44161d06d6bSBaptiste Daroussin 
44261d06d6bSBaptiste Daroussin 	printf("    <input type=\"search\" name=\"query\" value=\"");
44361d06d6bSBaptiste Daroussin 	if (req->q.query != NULL)
44461d06d6bSBaptiste Daroussin 		html_print(req->q.query);
44561d06d6bSBaptiste Daroussin 	printf( "\" size=\"40\"");
44661d06d6bSBaptiste Daroussin 	if (focus == FOCUS_QUERY)
44761d06d6bSBaptiste Daroussin 		printf(" autofocus");
44861d06d6bSBaptiste Daroussin 	puts(">");
44961d06d6bSBaptiste Daroussin 
45061d06d6bSBaptiste Daroussin 	/* Write submission buttons. */
45161d06d6bSBaptiste Daroussin 
45261d06d6bSBaptiste Daroussin 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
45361d06d6bSBaptiste Daroussin 		"man</button>\n"
45461d06d6bSBaptiste Daroussin 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
45561d06d6bSBaptiste Daroussin 		"apropos</button>\n"
45661d06d6bSBaptiste Daroussin 		"    <br/>\n");
45761d06d6bSBaptiste Daroussin 
45861d06d6bSBaptiste Daroussin 	/* Write section selector. */
45961d06d6bSBaptiste Daroussin 
46061d06d6bSBaptiste Daroussin 	puts("    <select name=\"sec\">");
46161d06d6bSBaptiste Daroussin 	for (i = 0; i < sec_MAX; i++) {
46261d06d6bSBaptiste Daroussin 		printf("      <option value=\"%s\"", sec_numbers[i]);
46361d06d6bSBaptiste Daroussin 		if (NULL != req->q.sec &&
46461d06d6bSBaptiste Daroussin 		    0 == strcmp(sec_numbers[i], req->q.sec))
46561d06d6bSBaptiste Daroussin 			printf(" selected=\"selected\"");
46661d06d6bSBaptiste Daroussin 		printf(">%s</option>\n", sec_names[i]);
46761d06d6bSBaptiste Daroussin 	}
46861d06d6bSBaptiste Daroussin 	puts("    </select>");
46961d06d6bSBaptiste Daroussin 
47061d06d6bSBaptiste Daroussin 	/* Write architecture selector. */
47161d06d6bSBaptiste Daroussin 
47261d06d6bSBaptiste Daroussin 	printf(	"    <select name=\"arch\">\n"
47361d06d6bSBaptiste Daroussin 		"      <option value=\"default\"");
47461d06d6bSBaptiste Daroussin 	if (NULL == req->q.arch)
47561d06d6bSBaptiste Daroussin 		printf(" selected=\"selected\"");
47661d06d6bSBaptiste Daroussin 	puts(">All Architectures</option>");
47761d06d6bSBaptiste Daroussin 	for (i = 0; i < arch_MAX; i++) {
47861d06d6bSBaptiste Daroussin 		printf("      <option");
47961d06d6bSBaptiste Daroussin 		if (NULL != req->q.arch &&
48061d06d6bSBaptiste Daroussin 		    0 == strcmp(arch_names[i], req->q.arch))
48161d06d6bSBaptiste Daroussin 			printf(" selected=\"selected\"");
48261d06d6bSBaptiste Daroussin 		printf(">%s</option>\n", arch_names[i]);
48361d06d6bSBaptiste Daroussin 	}
48461d06d6bSBaptiste Daroussin 	puts("    </select>");
48561d06d6bSBaptiste Daroussin 
48661d06d6bSBaptiste Daroussin 	/* Write manpath selector. */
48761d06d6bSBaptiste Daroussin 
48861d06d6bSBaptiste Daroussin 	if (req->psz > 1) {
48961d06d6bSBaptiste Daroussin 		puts("    <select name=\"manpath\">");
49061d06d6bSBaptiste Daroussin 		for (i = 0; i < (int)req->psz; i++) {
49161d06d6bSBaptiste Daroussin 			printf("      <option");
49261d06d6bSBaptiste Daroussin 			if (strcmp(req->q.manpath, req->p[i]) == 0)
49361d06d6bSBaptiste Daroussin 				printf(" selected=\"selected\"");
49461d06d6bSBaptiste Daroussin 			printf(">");
49561d06d6bSBaptiste Daroussin 			html_print(req->p[i]);
49661d06d6bSBaptiste Daroussin 			puts("</option>");
49761d06d6bSBaptiste Daroussin 		}
49861d06d6bSBaptiste Daroussin 		puts("    </select>");
49961d06d6bSBaptiste Daroussin 	}
50061d06d6bSBaptiste Daroussin 
50161d06d6bSBaptiste Daroussin 	puts("  </fieldset>\n"
50261d06d6bSBaptiste Daroussin 	     "</form>");
50361d06d6bSBaptiste Daroussin }
50461d06d6bSBaptiste Daroussin 
50561d06d6bSBaptiste Daroussin static int
validate_urifrag(const char * frag)50661d06d6bSBaptiste Daroussin validate_urifrag(const char *frag)
50761d06d6bSBaptiste Daroussin {
50861d06d6bSBaptiste Daroussin 
50961d06d6bSBaptiste Daroussin 	while ('\0' != *frag) {
51061d06d6bSBaptiste Daroussin 		if ( ! (isalnum((unsigned char)*frag) ||
51161d06d6bSBaptiste Daroussin 		    '-' == *frag || '.' == *frag ||
51261d06d6bSBaptiste Daroussin 		    '/' == *frag || '_' == *frag))
51361d06d6bSBaptiste Daroussin 			return 0;
51461d06d6bSBaptiste Daroussin 		frag++;
51561d06d6bSBaptiste Daroussin 	}
51661d06d6bSBaptiste Daroussin 	return 1;
51761d06d6bSBaptiste Daroussin }
51861d06d6bSBaptiste Daroussin 
51961d06d6bSBaptiste Daroussin static int
validate_manpath(const struct req * req,const char * manpath)52061d06d6bSBaptiste Daroussin validate_manpath(const struct req *req, const char* manpath)
52161d06d6bSBaptiste Daroussin {
52261d06d6bSBaptiste Daroussin 	size_t	 i;
52361d06d6bSBaptiste Daroussin 
52461d06d6bSBaptiste Daroussin 	for (i = 0; i < req->psz; i++)
52561d06d6bSBaptiste Daroussin 		if ( ! strcmp(manpath, req->p[i]))
52661d06d6bSBaptiste Daroussin 			return 1;
52761d06d6bSBaptiste Daroussin 
52861d06d6bSBaptiste Daroussin 	return 0;
52961d06d6bSBaptiste Daroussin }
53061d06d6bSBaptiste Daroussin 
53161d06d6bSBaptiste Daroussin static int
validate_arch(const char * arch)5327295610fSBaptiste Daroussin validate_arch(const char *arch)
5337295610fSBaptiste Daroussin {
5347295610fSBaptiste Daroussin 	int	 i;
5357295610fSBaptiste Daroussin 
5367295610fSBaptiste Daroussin 	for (i = 0; i < arch_MAX; i++)
5377295610fSBaptiste Daroussin 		if (strcmp(arch, arch_names[i]) == 0)
5387295610fSBaptiste Daroussin 			return 1;
5397295610fSBaptiste Daroussin 
5407295610fSBaptiste Daroussin 	return 0;
5417295610fSBaptiste Daroussin }
5427295610fSBaptiste Daroussin 
5437295610fSBaptiste Daroussin static int
validate_filename(const char * file)54461d06d6bSBaptiste Daroussin validate_filename(const char *file)
54561d06d6bSBaptiste Daroussin {
54661d06d6bSBaptiste Daroussin 
54761d06d6bSBaptiste Daroussin 	if ('.' == file[0] && '/' == file[1])
54861d06d6bSBaptiste Daroussin 		file += 2;
54961d06d6bSBaptiste Daroussin 
55061d06d6bSBaptiste Daroussin 	return ! (strstr(file, "../") || strstr(file, "/..") ||
55161d06d6bSBaptiste Daroussin 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
55261d06d6bSBaptiste Daroussin }
55361d06d6bSBaptiste Daroussin 
55461d06d6bSBaptiste Daroussin static void
pg_index(const struct req * req)55561d06d6bSBaptiste Daroussin pg_index(const struct req *req)
55661d06d6bSBaptiste Daroussin {
55761d06d6bSBaptiste Daroussin 
55861d06d6bSBaptiste Daroussin 	resp_begin_html(200, NULL, NULL);
55961d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_QUERY);
56061d06d6bSBaptiste Daroussin 	printf("<p>\n"
56161d06d6bSBaptiste Daroussin 	       "This web interface is documented in the\n"
56261d06d6bSBaptiste Daroussin 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
56361d06d6bSBaptiste Daroussin 	       "manual, and the\n"
56461d06d6bSBaptiste Daroussin 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
56561d06d6bSBaptiste Daroussin 	       "manual explains the query syntax.\n"
56661d06d6bSBaptiste Daroussin 	       "</p>\n",
56761d06d6bSBaptiste Daroussin 	       scriptname, *scriptname == '\0' ? "" : "/",
56861d06d6bSBaptiste Daroussin 	       scriptname, *scriptname == '\0' ? "" : "/");
56961d06d6bSBaptiste Daroussin 	resp_end_html();
57061d06d6bSBaptiste Daroussin }
57161d06d6bSBaptiste Daroussin 
57261d06d6bSBaptiste Daroussin static void
pg_noresult(const struct req * req,int code,const char * http_msg,const char * user_msg)573*6d38604fSBaptiste Daroussin pg_noresult(const struct req *req, int code, const char *http_msg,
574*6d38604fSBaptiste Daroussin     const char *user_msg)
57561d06d6bSBaptiste Daroussin {
576*6d38604fSBaptiste Daroussin 	resp_begin_html(code, http_msg, NULL);
57761d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_QUERY);
57861d06d6bSBaptiste Daroussin 	puts("<p>");
579*6d38604fSBaptiste Daroussin 	puts(user_msg);
58061d06d6bSBaptiste Daroussin 	puts("</p>");
58161d06d6bSBaptiste Daroussin 	resp_end_html();
58261d06d6bSBaptiste Daroussin }
58361d06d6bSBaptiste Daroussin 
58461d06d6bSBaptiste Daroussin static void
pg_error_badrequest(const char * msg)58561d06d6bSBaptiste Daroussin pg_error_badrequest(const char *msg)
58661d06d6bSBaptiste Daroussin {
58761d06d6bSBaptiste Daroussin 
58861d06d6bSBaptiste Daroussin 	resp_begin_html(400, "Bad Request", NULL);
58961d06d6bSBaptiste Daroussin 	puts("<h1>Bad Request</h1>\n"
59061d06d6bSBaptiste Daroussin 	     "<p>\n");
59161d06d6bSBaptiste Daroussin 	puts(msg);
59261d06d6bSBaptiste Daroussin 	printf("Try again from the\n"
59361d06d6bSBaptiste Daroussin 	       "<a href=\"/%s\">main page</a>.\n"
59461d06d6bSBaptiste Daroussin 	       "</p>", scriptname);
59561d06d6bSBaptiste Daroussin 	resp_end_html();
59661d06d6bSBaptiste Daroussin }
59761d06d6bSBaptiste Daroussin 
59861d06d6bSBaptiste Daroussin static void
pg_error_internal(void)59961d06d6bSBaptiste Daroussin pg_error_internal(void)
60061d06d6bSBaptiste Daroussin {
60161d06d6bSBaptiste Daroussin 	resp_begin_html(500, "Internal Server Error", NULL);
60261d06d6bSBaptiste Daroussin 	puts("<p>Internal Server Error</p>");
60361d06d6bSBaptiste Daroussin 	resp_end_html();
60461d06d6bSBaptiste Daroussin }
60561d06d6bSBaptiste Daroussin 
60661d06d6bSBaptiste Daroussin static void
pg_redirect(const struct req * req,const char * name)60761d06d6bSBaptiste Daroussin pg_redirect(const struct req *req, const char *name)
60861d06d6bSBaptiste Daroussin {
60961d06d6bSBaptiste Daroussin 	printf("Status: 303 See Other\r\n"
61061d06d6bSBaptiste Daroussin 	    "Location: /");
61161d06d6bSBaptiste Daroussin 	if (*scriptname != '\0')
61261d06d6bSBaptiste Daroussin 		printf("%s/", scriptname);
61361d06d6bSBaptiste Daroussin 	if (strcmp(req->q.manpath, req->p[0]))
61461d06d6bSBaptiste Daroussin 		printf("%s/", req->q.manpath);
61561d06d6bSBaptiste Daroussin 	if (req->q.arch != NULL)
61661d06d6bSBaptiste Daroussin 		printf("%s/", req->q.arch);
6177295610fSBaptiste Daroussin 	http_encode(name);
6187295610fSBaptiste Daroussin 	if (req->q.sec != NULL) {
6197295610fSBaptiste Daroussin 		putchar('.');
6207295610fSBaptiste Daroussin 		http_encode(req->q.sec);
6217295610fSBaptiste Daroussin 	}
62261d06d6bSBaptiste Daroussin 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
62361d06d6bSBaptiste Daroussin }
62461d06d6bSBaptiste Daroussin 
62561d06d6bSBaptiste Daroussin static void
pg_searchres(const struct req * req,struct manpage * r,size_t sz)62661d06d6bSBaptiste Daroussin pg_searchres(const struct req *req, struct manpage *r, size_t sz)
62761d06d6bSBaptiste Daroussin {
62861d06d6bSBaptiste Daroussin 	char		*arch, *archend;
62961d06d6bSBaptiste Daroussin 	const char	*sec;
63061d06d6bSBaptiste Daroussin 	size_t		 i, iuse;
63161d06d6bSBaptiste Daroussin 	int		 archprio, archpriouse;
63261d06d6bSBaptiste Daroussin 	int		 prio, priouse;
63361d06d6bSBaptiste Daroussin 
63461d06d6bSBaptiste Daroussin 	for (i = 0; i < sz; i++) {
63561d06d6bSBaptiste Daroussin 		if (validate_filename(r[i].file))
63661d06d6bSBaptiste Daroussin 			continue;
63761d06d6bSBaptiste Daroussin 		warnx("invalid filename %s in %s database",
63861d06d6bSBaptiste Daroussin 		    r[i].file, req->q.manpath);
63961d06d6bSBaptiste Daroussin 		pg_error_internal();
64061d06d6bSBaptiste Daroussin 		return;
64161d06d6bSBaptiste Daroussin 	}
64261d06d6bSBaptiste Daroussin 
64361d06d6bSBaptiste Daroussin 	if (req->isquery && sz == 1) {
64461d06d6bSBaptiste Daroussin 		/*
64561d06d6bSBaptiste Daroussin 		 * If we have just one result, then jump there now
64661d06d6bSBaptiste Daroussin 		 * without any delay.
64761d06d6bSBaptiste Daroussin 		 */
64861d06d6bSBaptiste Daroussin 		printf("Status: 303 See Other\r\n"
64961d06d6bSBaptiste Daroussin 		    "Location: /");
65061d06d6bSBaptiste Daroussin 		if (*scriptname != '\0')
65161d06d6bSBaptiste Daroussin 			printf("%s/", scriptname);
65261d06d6bSBaptiste Daroussin 		if (strcmp(req->q.manpath, req->p[0]))
65361d06d6bSBaptiste Daroussin 			printf("%s/", req->q.manpath);
65461d06d6bSBaptiste Daroussin 		printf("%s\r\n"
65561d06d6bSBaptiste Daroussin 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
65661d06d6bSBaptiste Daroussin 		    r[0].file);
65761d06d6bSBaptiste Daroussin 		return;
65861d06d6bSBaptiste Daroussin 	}
65961d06d6bSBaptiste Daroussin 
66061d06d6bSBaptiste Daroussin 	/*
66161d06d6bSBaptiste Daroussin 	 * In man(1) mode, show one of the pages
66261d06d6bSBaptiste Daroussin 	 * even if more than one is found.
66361d06d6bSBaptiste Daroussin 	 */
66461d06d6bSBaptiste Daroussin 
66561d06d6bSBaptiste Daroussin 	iuse = 0;
66661d06d6bSBaptiste Daroussin 	if (req->q.equal || sz == 1) {
66761d06d6bSBaptiste Daroussin 		priouse = 20;
66861d06d6bSBaptiste Daroussin 		archpriouse = 3;
66961d06d6bSBaptiste Daroussin 		for (i = 0; i < sz; i++) {
67061d06d6bSBaptiste Daroussin 			sec = r[i].file;
67161d06d6bSBaptiste Daroussin 			sec += strcspn(sec, "123456789");
67261d06d6bSBaptiste Daroussin 			if (sec[0] == '\0')
67361d06d6bSBaptiste Daroussin 				continue;
67461d06d6bSBaptiste Daroussin 			prio = sec_prios[sec[0] - '1'];
67561d06d6bSBaptiste Daroussin 			if (sec[1] != '/')
67661d06d6bSBaptiste Daroussin 				prio += 10;
67761d06d6bSBaptiste Daroussin 			if (req->q.arch == NULL) {
67861d06d6bSBaptiste Daroussin 				archprio =
67961d06d6bSBaptiste Daroussin 				    ((arch = strchr(sec + 1, '/'))
68061d06d6bSBaptiste Daroussin 					== NULL) ? 3 :
68161d06d6bSBaptiste Daroussin 				    ((archend = strchr(arch + 1, '/'))
68261d06d6bSBaptiste Daroussin 					== NULL) ? 0 :
68361d06d6bSBaptiste Daroussin 				    strncmp(arch, "amd64/",
68461d06d6bSBaptiste Daroussin 					archend - arch) ? 2 : 1;
68561d06d6bSBaptiste Daroussin 				if (archprio < archpriouse) {
68661d06d6bSBaptiste Daroussin 					archpriouse = archprio;
68761d06d6bSBaptiste Daroussin 					priouse = prio;
68861d06d6bSBaptiste Daroussin 					iuse = i;
68961d06d6bSBaptiste Daroussin 					continue;
69061d06d6bSBaptiste Daroussin 				}
69161d06d6bSBaptiste Daroussin 				if (archprio > archpriouse)
69261d06d6bSBaptiste Daroussin 					continue;
69361d06d6bSBaptiste Daroussin 			}
69461d06d6bSBaptiste Daroussin 			if (prio >= priouse)
69561d06d6bSBaptiste Daroussin 				continue;
69661d06d6bSBaptiste Daroussin 			priouse = prio;
69761d06d6bSBaptiste Daroussin 			iuse = i;
69861d06d6bSBaptiste Daroussin 		}
69961d06d6bSBaptiste Daroussin 		resp_begin_html(200, NULL, r[iuse].file);
70061d06d6bSBaptiste Daroussin 	} else
70161d06d6bSBaptiste Daroussin 		resp_begin_html(200, NULL, NULL);
70261d06d6bSBaptiste Daroussin 
70361d06d6bSBaptiste Daroussin 	resp_searchform(req,
70461d06d6bSBaptiste Daroussin 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
70561d06d6bSBaptiste Daroussin 
70661d06d6bSBaptiste Daroussin 	if (sz > 1) {
70761d06d6bSBaptiste Daroussin 		puts("<table class=\"results\">");
70861d06d6bSBaptiste Daroussin 		for (i = 0; i < sz; i++) {
70961d06d6bSBaptiste Daroussin 			printf("  <tr>\n"
71061d06d6bSBaptiste Daroussin 			       "    <td>"
71161d06d6bSBaptiste Daroussin 			       "<a class=\"Xr\" href=\"/");
71261d06d6bSBaptiste Daroussin 			if (*scriptname != '\0')
71361d06d6bSBaptiste Daroussin 				printf("%s/", scriptname);
71461d06d6bSBaptiste Daroussin 			if (strcmp(req->q.manpath, req->p[0]))
71561d06d6bSBaptiste Daroussin 				printf("%s/", req->q.manpath);
71661d06d6bSBaptiste Daroussin 			printf("%s\">", r[i].file);
71761d06d6bSBaptiste Daroussin 			html_print(r[i].names);
71861d06d6bSBaptiste Daroussin 			printf("</a></td>\n"
71961d06d6bSBaptiste Daroussin 			       "    <td><span class=\"Nd\">");
72061d06d6bSBaptiste Daroussin 			html_print(r[i].output);
72161d06d6bSBaptiste Daroussin 			puts("</span></td>\n"
72261d06d6bSBaptiste Daroussin 			     "  </tr>");
72361d06d6bSBaptiste Daroussin 		}
72461d06d6bSBaptiste Daroussin 		puts("</table>");
72561d06d6bSBaptiste Daroussin 	}
72661d06d6bSBaptiste Daroussin 
72761d06d6bSBaptiste Daroussin 	if (req->q.equal || sz == 1) {
72861d06d6bSBaptiste Daroussin 		puts("<hr>");
72961d06d6bSBaptiste Daroussin 		resp_show(req, r[iuse].file);
73061d06d6bSBaptiste Daroussin 	}
73161d06d6bSBaptiste Daroussin 
73261d06d6bSBaptiste Daroussin 	resp_end_html();
73361d06d6bSBaptiste Daroussin }
73461d06d6bSBaptiste Daroussin 
73561d06d6bSBaptiste Daroussin static void
resp_catman(const struct req * req,const char * file)73661d06d6bSBaptiste Daroussin resp_catman(const struct req *req, const char *file)
73761d06d6bSBaptiste Daroussin {
73861d06d6bSBaptiste Daroussin 	FILE		*f;
73961d06d6bSBaptiste Daroussin 	char		*p;
74061d06d6bSBaptiste Daroussin 	size_t		 sz;
74161d06d6bSBaptiste Daroussin 	ssize_t		 len;
74261d06d6bSBaptiste Daroussin 	int		 i;
74361d06d6bSBaptiste Daroussin 	int		 italic, bold;
74461d06d6bSBaptiste Daroussin 
74561d06d6bSBaptiste Daroussin 	if ((f = fopen(file, "r")) == NULL) {
74661d06d6bSBaptiste Daroussin 		puts("<p>You specified an invalid manual file.</p>");
74761d06d6bSBaptiste Daroussin 		return;
74861d06d6bSBaptiste Daroussin 	}
74961d06d6bSBaptiste Daroussin 
75061d06d6bSBaptiste Daroussin 	puts("<div class=\"catman\">\n"
75161d06d6bSBaptiste Daroussin 	     "<pre>");
75261d06d6bSBaptiste Daroussin 
75361d06d6bSBaptiste Daroussin 	p = NULL;
75461d06d6bSBaptiste Daroussin 	sz = 0;
75561d06d6bSBaptiste Daroussin 
75661d06d6bSBaptiste Daroussin 	while ((len = getline(&p, &sz, f)) != -1) {
75761d06d6bSBaptiste Daroussin 		bold = italic = 0;
75861d06d6bSBaptiste Daroussin 		for (i = 0; i < len - 1; i++) {
75961d06d6bSBaptiste Daroussin 			/*
76061d06d6bSBaptiste Daroussin 			 * This means that the catpage is out of state.
76161d06d6bSBaptiste Daroussin 			 * Ignore it and keep going (although the
76261d06d6bSBaptiste Daroussin 			 * catpage is bogus).
76361d06d6bSBaptiste Daroussin 			 */
76461d06d6bSBaptiste Daroussin 
76561d06d6bSBaptiste Daroussin 			if ('\b' == p[i] || '\n' == p[i])
76661d06d6bSBaptiste Daroussin 				continue;
76761d06d6bSBaptiste Daroussin 
76861d06d6bSBaptiste Daroussin 			/*
76961d06d6bSBaptiste Daroussin 			 * Print a regular character.
77061d06d6bSBaptiste Daroussin 			 * Close out any bold/italic scopes.
77161d06d6bSBaptiste Daroussin 			 * If we're in back-space mode, make sure we'll
77261d06d6bSBaptiste Daroussin 			 * have something to enter when we backspace.
77361d06d6bSBaptiste Daroussin 			 */
77461d06d6bSBaptiste Daroussin 
77561d06d6bSBaptiste Daroussin 			if ('\b' != p[i + 1]) {
77661d06d6bSBaptiste Daroussin 				if (italic)
77761d06d6bSBaptiste Daroussin 					printf("</i>");
77861d06d6bSBaptiste Daroussin 				if (bold)
77961d06d6bSBaptiste Daroussin 					printf("</b>");
78061d06d6bSBaptiste Daroussin 				italic = bold = 0;
78161d06d6bSBaptiste Daroussin 				html_putchar(p[i]);
78261d06d6bSBaptiste Daroussin 				continue;
78361d06d6bSBaptiste Daroussin 			} else if (i + 2 >= len)
78461d06d6bSBaptiste Daroussin 				continue;
78561d06d6bSBaptiste Daroussin 
78661d06d6bSBaptiste Daroussin 			/* Italic mode. */
78761d06d6bSBaptiste Daroussin 
78861d06d6bSBaptiste Daroussin 			if ('_' == p[i]) {
78961d06d6bSBaptiste Daroussin 				if (bold)
79061d06d6bSBaptiste Daroussin 					printf("</b>");
79161d06d6bSBaptiste Daroussin 				if ( ! italic)
79261d06d6bSBaptiste Daroussin 					printf("<i>");
79361d06d6bSBaptiste Daroussin 				bold = 0;
79461d06d6bSBaptiste Daroussin 				italic = 1;
79561d06d6bSBaptiste Daroussin 				i += 2;
79661d06d6bSBaptiste Daroussin 				html_putchar(p[i]);
79761d06d6bSBaptiste Daroussin 				continue;
79861d06d6bSBaptiste Daroussin 			}
79961d06d6bSBaptiste Daroussin 
80061d06d6bSBaptiste Daroussin 			/*
80161d06d6bSBaptiste Daroussin 			 * Handle funny behaviour troff-isms.
80261d06d6bSBaptiste Daroussin 			 * These grok'd from the original man2html.c.
80361d06d6bSBaptiste Daroussin 			 */
80461d06d6bSBaptiste Daroussin 
80561d06d6bSBaptiste Daroussin 			if (('+' == p[i] && 'o' == p[i + 2]) ||
80661d06d6bSBaptiste Daroussin 					('o' == p[i] && '+' == p[i + 2]) ||
80761d06d6bSBaptiste Daroussin 					('|' == p[i] && '=' == p[i + 2]) ||
80861d06d6bSBaptiste Daroussin 					('=' == p[i] && '|' == p[i + 2]) ||
80961d06d6bSBaptiste Daroussin 					('*' == p[i] && '=' == p[i + 2]) ||
81061d06d6bSBaptiste Daroussin 					('=' == p[i] && '*' == p[i + 2]) ||
81161d06d6bSBaptiste Daroussin 					('*' == p[i] && '|' == p[i + 2]) ||
81261d06d6bSBaptiste Daroussin 					('|' == p[i] && '*' == p[i + 2]))  {
81361d06d6bSBaptiste Daroussin 				if (italic)
81461d06d6bSBaptiste Daroussin 					printf("</i>");
81561d06d6bSBaptiste Daroussin 				if (bold)
81661d06d6bSBaptiste Daroussin 					printf("</b>");
81761d06d6bSBaptiste Daroussin 				italic = bold = 0;
81861d06d6bSBaptiste Daroussin 				putchar('*');
81961d06d6bSBaptiste Daroussin 				i += 2;
82061d06d6bSBaptiste Daroussin 				continue;
82161d06d6bSBaptiste Daroussin 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
82261d06d6bSBaptiste Daroussin 					('-' == p[i] && '|' == p[i + 1]) ||
82361d06d6bSBaptiste Daroussin 					('+' == p[i] && '-' == p[i + 1]) ||
82461d06d6bSBaptiste Daroussin 					('-' == p[i] && '+' == p[i + 1]) ||
82561d06d6bSBaptiste Daroussin 					('+' == p[i] && '|' == p[i + 1]) ||
82661d06d6bSBaptiste Daroussin 					('|' == p[i] && '+' == p[i + 1]))  {
82761d06d6bSBaptiste Daroussin 				if (italic)
82861d06d6bSBaptiste Daroussin 					printf("</i>");
82961d06d6bSBaptiste Daroussin 				if (bold)
83061d06d6bSBaptiste Daroussin 					printf("</b>");
83161d06d6bSBaptiste Daroussin 				italic = bold = 0;
83261d06d6bSBaptiste Daroussin 				putchar('+');
83361d06d6bSBaptiste Daroussin 				i += 2;
83461d06d6bSBaptiste Daroussin 				continue;
83561d06d6bSBaptiste Daroussin 			}
83661d06d6bSBaptiste Daroussin 
83761d06d6bSBaptiste Daroussin 			/* Bold mode. */
83861d06d6bSBaptiste Daroussin 
83961d06d6bSBaptiste Daroussin 			if (italic)
84061d06d6bSBaptiste Daroussin 				printf("</i>");
84161d06d6bSBaptiste Daroussin 			if ( ! bold)
84261d06d6bSBaptiste Daroussin 				printf("<b>");
84361d06d6bSBaptiste Daroussin 			bold = 1;
84461d06d6bSBaptiste Daroussin 			italic = 0;
84561d06d6bSBaptiste Daroussin 			i += 2;
84661d06d6bSBaptiste Daroussin 			html_putchar(p[i]);
84761d06d6bSBaptiste Daroussin 		}
84861d06d6bSBaptiste Daroussin 
84961d06d6bSBaptiste Daroussin 		/*
85061d06d6bSBaptiste Daroussin 		 * Clean up the last character.
85161d06d6bSBaptiste Daroussin 		 * We can get to a newline; don't print that.
85261d06d6bSBaptiste Daroussin 		 */
85361d06d6bSBaptiste Daroussin 
85461d06d6bSBaptiste Daroussin 		if (italic)
85561d06d6bSBaptiste Daroussin 			printf("</i>");
85661d06d6bSBaptiste Daroussin 		if (bold)
85761d06d6bSBaptiste Daroussin 			printf("</b>");
85861d06d6bSBaptiste Daroussin 
85961d06d6bSBaptiste Daroussin 		if (i == len - 1 && p[i] != '\n')
86061d06d6bSBaptiste Daroussin 			html_putchar(p[i]);
86161d06d6bSBaptiste Daroussin 
86261d06d6bSBaptiste Daroussin 		putchar('\n');
86361d06d6bSBaptiste Daroussin 	}
86461d06d6bSBaptiste Daroussin 	free(p);
86561d06d6bSBaptiste Daroussin 
86661d06d6bSBaptiste Daroussin 	puts("</pre>\n"
86761d06d6bSBaptiste Daroussin 	     "</div>");
86861d06d6bSBaptiste Daroussin 
86961d06d6bSBaptiste Daroussin 	fclose(f);
87061d06d6bSBaptiste Daroussin }
87161d06d6bSBaptiste Daroussin 
87261d06d6bSBaptiste Daroussin static void
resp_format(const struct req * req,const char * file)87361d06d6bSBaptiste Daroussin resp_format(const struct req *req, const char *file)
87461d06d6bSBaptiste Daroussin {
87561d06d6bSBaptiste Daroussin 	struct manoutput conf;
87661d06d6bSBaptiste Daroussin 	struct mparse	*mp;
8777295610fSBaptiste Daroussin 	struct roff_meta *meta;
87861d06d6bSBaptiste Daroussin 	void		*vp;
87961d06d6bSBaptiste Daroussin 	int		 fd;
88061d06d6bSBaptiste Daroussin 	int		 usepath;
88161d06d6bSBaptiste Daroussin 
88261d06d6bSBaptiste Daroussin 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
88361d06d6bSBaptiste Daroussin 		puts("<p>You specified an invalid manual file.</p>");
88461d06d6bSBaptiste Daroussin 		return;
88561d06d6bSBaptiste Daroussin 	}
88661d06d6bSBaptiste Daroussin 
88761d06d6bSBaptiste Daroussin 	mchars_alloc();
8887295610fSBaptiste Daroussin 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
8897295610fSBaptiste Daroussin 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
89061d06d6bSBaptiste Daroussin 	mparse_readfd(mp, fd, file);
89161d06d6bSBaptiste Daroussin 	close(fd);
8927295610fSBaptiste Daroussin 	meta = mparse_result(mp);
89361d06d6bSBaptiste Daroussin 
89461d06d6bSBaptiste Daroussin 	memset(&conf, 0, sizeof(conf));
89561d06d6bSBaptiste Daroussin 	conf.fragment = 1;
89661d06d6bSBaptiste Daroussin 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
89761d06d6bSBaptiste Daroussin 	usepath = strcmp(req->q.manpath, req->p[0]);
89861d06d6bSBaptiste Daroussin 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
89961d06d6bSBaptiste Daroussin 	    scriptname, *scriptname == '\0' ? "" : "/",
90061d06d6bSBaptiste Daroussin 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
90161d06d6bSBaptiste Daroussin 
90261d06d6bSBaptiste Daroussin 	vp = html_alloc(&conf);
9037295610fSBaptiste Daroussin 	if (meta->macroset == MACROSET_MDOC)
9047295610fSBaptiste Daroussin 		html_mdoc(vp, meta);
9057295610fSBaptiste Daroussin 	else
9067295610fSBaptiste Daroussin 		html_man(vp, meta);
90761d06d6bSBaptiste Daroussin 
90861d06d6bSBaptiste Daroussin 	html_free(vp);
90961d06d6bSBaptiste Daroussin 	mparse_free(mp);
91061d06d6bSBaptiste Daroussin 	mchars_free();
91161d06d6bSBaptiste Daroussin 	free(conf.man);
91261d06d6bSBaptiste Daroussin 	free(conf.style);
91361d06d6bSBaptiste Daroussin }
91461d06d6bSBaptiste Daroussin 
91561d06d6bSBaptiste Daroussin static void
resp_show(const struct req * req,const char * file)91661d06d6bSBaptiste Daroussin resp_show(const struct req *req, const char *file)
91761d06d6bSBaptiste Daroussin {
91861d06d6bSBaptiste Daroussin 
91961d06d6bSBaptiste Daroussin 	if ('.' == file[0] && '/' == file[1])
92061d06d6bSBaptiste Daroussin 		file += 2;
92161d06d6bSBaptiste Daroussin 
92261d06d6bSBaptiste Daroussin 	if ('c' == *file)
92361d06d6bSBaptiste Daroussin 		resp_catman(req, file);
92461d06d6bSBaptiste Daroussin 	else
92561d06d6bSBaptiste Daroussin 		resp_format(req, file);
92661d06d6bSBaptiste Daroussin }
92761d06d6bSBaptiste Daroussin 
92861d06d6bSBaptiste Daroussin static void
pg_show(struct req * req,const char * fullpath)92961d06d6bSBaptiste Daroussin pg_show(struct req *req, const char *fullpath)
93061d06d6bSBaptiste Daroussin {
93161d06d6bSBaptiste Daroussin 	char		*manpath;
93261d06d6bSBaptiste Daroussin 	const char	*file;
93361d06d6bSBaptiste Daroussin 
93461d06d6bSBaptiste Daroussin 	if ((file = strchr(fullpath, '/')) == NULL) {
93561d06d6bSBaptiste Daroussin 		pg_error_badrequest(
93661d06d6bSBaptiste Daroussin 		    "You did not specify a page to show.");
93761d06d6bSBaptiste Daroussin 		return;
93861d06d6bSBaptiste Daroussin 	}
93961d06d6bSBaptiste Daroussin 	manpath = mandoc_strndup(fullpath, file - fullpath);
94061d06d6bSBaptiste Daroussin 	file++;
94161d06d6bSBaptiste Daroussin 
94261d06d6bSBaptiste Daroussin 	if ( ! validate_manpath(req, manpath)) {
94361d06d6bSBaptiste Daroussin 		pg_error_badrequest(
94461d06d6bSBaptiste Daroussin 		    "You specified an invalid manpath.");
94561d06d6bSBaptiste Daroussin 		free(manpath);
94661d06d6bSBaptiste Daroussin 		return;
94761d06d6bSBaptiste Daroussin 	}
94861d06d6bSBaptiste Daroussin 
94961d06d6bSBaptiste Daroussin 	/*
95061d06d6bSBaptiste Daroussin 	 * Begin by chdir()ing into the manpath.
95161d06d6bSBaptiste Daroussin 	 * This way we can pick up the database files, which are
95261d06d6bSBaptiste Daroussin 	 * relative to the manpath root.
95361d06d6bSBaptiste Daroussin 	 */
95461d06d6bSBaptiste Daroussin 
95561d06d6bSBaptiste Daroussin 	if (chdir(manpath) == -1) {
95661d06d6bSBaptiste Daroussin 		warn("chdir %s", manpath);
95761d06d6bSBaptiste Daroussin 		pg_error_internal();
95861d06d6bSBaptiste Daroussin 		free(manpath);
95961d06d6bSBaptiste Daroussin 		return;
96061d06d6bSBaptiste Daroussin 	}
96161d06d6bSBaptiste Daroussin 	free(manpath);
96261d06d6bSBaptiste Daroussin 
96361d06d6bSBaptiste Daroussin 	if ( ! validate_filename(file)) {
96461d06d6bSBaptiste Daroussin 		pg_error_badrequest(
96561d06d6bSBaptiste Daroussin 		    "You specified an invalid manual file.");
96661d06d6bSBaptiste Daroussin 		return;
96761d06d6bSBaptiste Daroussin 	}
96861d06d6bSBaptiste Daroussin 
96961d06d6bSBaptiste Daroussin 	resp_begin_html(200, NULL, file);
97061d06d6bSBaptiste Daroussin 	resp_searchform(req, FOCUS_NONE);
97161d06d6bSBaptiste Daroussin 	resp_show(req, file);
97261d06d6bSBaptiste Daroussin 	resp_end_html();
97361d06d6bSBaptiste Daroussin }
97461d06d6bSBaptiste Daroussin 
97561d06d6bSBaptiste Daroussin static void
pg_search(const struct req * req)97661d06d6bSBaptiste Daroussin pg_search(const struct req *req)
97761d06d6bSBaptiste Daroussin {
97861d06d6bSBaptiste Daroussin 	struct mansearch	  search;
97961d06d6bSBaptiste Daroussin 	struct manpaths		  paths;
98061d06d6bSBaptiste Daroussin 	struct manpage		 *res;
98161d06d6bSBaptiste Daroussin 	char			**argv;
98261d06d6bSBaptiste Daroussin 	char			 *query, *rp, *wp;
98361d06d6bSBaptiste Daroussin 	size_t			  ressz;
98461d06d6bSBaptiste Daroussin 	int			  argc;
98561d06d6bSBaptiste Daroussin 
98661d06d6bSBaptiste Daroussin 	/*
98761d06d6bSBaptiste Daroussin 	 * Begin by chdir()ing into the root of the manpath.
98861d06d6bSBaptiste Daroussin 	 * This way we can pick up the database files, which are
98961d06d6bSBaptiste Daroussin 	 * relative to the manpath root.
99061d06d6bSBaptiste Daroussin 	 */
99161d06d6bSBaptiste Daroussin 
99261d06d6bSBaptiste Daroussin 	if (chdir(req->q.manpath) == -1) {
99361d06d6bSBaptiste Daroussin 		warn("chdir %s", req->q.manpath);
99461d06d6bSBaptiste Daroussin 		pg_error_internal();
99561d06d6bSBaptiste Daroussin 		return;
99661d06d6bSBaptiste Daroussin 	}
99761d06d6bSBaptiste Daroussin 
99861d06d6bSBaptiste Daroussin 	search.arch = req->q.arch;
99961d06d6bSBaptiste Daroussin 	search.sec = req->q.sec;
100061d06d6bSBaptiste Daroussin 	search.outkey = "Nd";
100161d06d6bSBaptiste Daroussin 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
100261d06d6bSBaptiste Daroussin 	search.firstmatch = 1;
100361d06d6bSBaptiste Daroussin 
100461d06d6bSBaptiste Daroussin 	paths.sz = 1;
100561d06d6bSBaptiste Daroussin 	paths.paths = mandoc_malloc(sizeof(char *));
100661d06d6bSBaptiste Daroussin 	paths.paths[0] = mandoc_strdup(".");
100761d06d6bSBaptiste Daroussin 
100861d06d6bSBaptiste Daroussin 	/*
100961d06d6bSBaptiste Daroussin 	 * Break apart at spaces with backslash-escaping.
101061d06d6bSBaptiste Daroussin 	 */
101161d06d6bSBaptiste Daroussin 
101261d06d6bSBaptiste Daroussin 	argc = 0;
101361d06d6bSBaptiste Daroussin 	argv = NULL;
101461d06d6bSBaptiste Daroussin 	rp = query = mandoc_strdup(req->q.query);
101561d06d6bSBaptiste Daroussin 	for (;;) {
101661d06d6bSBaptiste Daroussin 		while (isspace((unsigned char)*rp))
101761d06d6bSBaptiste Daroussin 			rp++;
101861d06d6bSBaptiste Daroussin 		if (*rp == '\0')
101961d06d6bSBaptiste Daroussin 			break;
102061d06d6bSBaptiste Daroussin 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
102161d06d6bSBaptiste Daroussin 		argv[argc++] = wp = rp;
102261d06d6bSBaptiste Daroussin 		for (;;) {
102361d06d6bSBaptiste Daroussin 			if (isspace((unsigned char)*rp)) {
102461d06d6bSBaptiste Daroussin 				*wp = '\0';
102561d06d6bSBaptiste Daroussin 				rp++;
102661d06d6bSBaptiste Daroussin 				break;
102761d06d6bSBaptiste Daroussin 			}
102861d06d6bSBaptiste Daroussin 			if (rp[0] == '\\' && rp[1] != '\0')
102961d06d6bSBaptiste Daroussin 				rp++;
103061d06d6bSBaptiste Daroussin 			if (wp != rp)
103161d06d6bSBaptiste Daroussin 				*wp = *rp;
103261d06d6bSBaptiste Daroussin 			if (*rp == '\0')
103361d06d6bSBaptiste Daroussin 				break;
103461d06d6bSBaptiste Daroussin 			wp++;
103561d06d6bSBaptiste Daroussin 			rp++;
103661d06d6bSBaptiste Daroussin 		}
103761d06d6bSBaptiste Daroussin 	}
103861d06d6bSBaptiste Daroussin 
103961d06d6bSBaptiste Daroussin 	res = NULL;
104061d06d6bSBaptiste Daroussin 	ressz = 0;
104161d06d6bSBaptiste Daroussin 	if (req->isquery && req->q.equal && argc == 1)
104261d06d6bSBaptiste Daroussin 		pg_redirect(req, argv[0]);
104361d06d6bSBaptiste Daroussin 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1044*6d38604fSBaptiste Daroussin 		pg_noresult(req, 400, "Bad Request",
1045*6d38604fSBaptiste Daroussin 		    "You entered an invalid query.");
104661d06d6bSBaptiste Daroussin 	else if (ressz == 0)
1047*6d38604fSBaptiste Daroussin 		pg_noresult(req, 404, "Not Found", "No results found.");
104861d06d6bSBaptiste Daroussin 	else
104961d06d6bSBaptiste Daroussin 		pg_searchres(req, res, ressz);
105061d06d6bSBaptiste Daroussin 
105161d06d6bSBaptiste Daroussin 	free(query);
105261d06d6bSBaptiste Daroussin 	mansearch_free(res, ressz);
105361d06d6bSBaptiste Daroussin 	free(paths.paths[0]);
105461d06d6bSBaptiste Daroussin 	free(paths.paths);
105561d06d6bSBaptiste Daroussin }
105661d06d6bSBaptiste Daroussin 
105761d06d6bSBaptiste Daroussin int
main(void)105861d06d6bSBaptiste Daroussin main(void)
105961d06d6bSBaptiste Daroussin {
106061d06d6bSBaptiste Daroussin 	struct req	 req;
106161d06d6bSBaptiste Daroussin 	struct itimerval itimer;
106261d06d6bSBaptiste Daroussin 	const char	*path;
106361d06d6bSBaptiste Daroussin 	const char	*querystring;
106461d06d6bSBaptiste Daroussin 	int		 i;
106561d06d6bSBaptiste Daroussin 
106661d06d6bSBaptiste Daroussin #if HAVE_PLEDGE
106761d06d6bSBaptiste Daroussin 	/*
106861d06d6bSBaptiste Daroussin 	 * The "rpath" pledge could be revoked after mparse_readfd()
106961d06d6bSBaptiste Daroussin 	 * if the file desciptor to "/footer.html" would be opened
107061d06d6bSBaptiste Daroussin 	 * up front, but it's probably not worth the complication
107161d06d6bSBaptiste Daroussin 	 * of the code it would cause: it would require scattering
107261d06d6bSBaptiste Daroussin 	 * pledge() calls in multiple low-level resp_*() functions.
107361d06d6bSBaptiste Daroussin 	 */
107461d06d6bSBaptiste Daroussin 
107561d06d6bSBaptiste Daroussin 	if (pledge("stdio rpath", NULL) == -1) {
107661d06d6bSBaptiste Daroussin 		warn("pledge");
107761d06d6bSBaptiste Daroussin 		pg_error_internal();
107861d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
107961d06d6bSBaptiste Daroussin 	}
108061d06d6bSBaptiste Daroussin #endif
108161d06d6bSBaptiste Daroussin 
108261d06d6bSBaptiste Daroussin 	/* Poor man's ReDoS mitigation. */
108361d06d6bSBaptiste Daroussin 
108461d06d6bSBaptiste Daroussin 	itimer.it_value.tv_sec = 2;
108561d06d6bSBaptiste Daroussin 	itimer.it_value.tv_usec = 0;
108661d06d6bSBaptiste Daroussin 	itimer.it_interval.tv_sec = 2;
108761d06d6bSBaptiste Daroussin 	itimer.it_interval.tv_usec = 0;
108861d06d6bSBaptiste Daroussin 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
108961d06d6bSBaptiste Daroussin 		warn("setitimer");
109061d06d6bSBaptiste Daroussin 		pg_error_internal();
109161d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
109261d06d6bSBaptiste Daroussin 	}
109361d06d6bSBaptiste Daroussin 
109461d06d6bSBaptiste Daroussin 	/*
109561d06d6bSBaptiste Daroussin 	 * First we change directory into the MAN_DIR so that
109661d06d6bSBaptiste Daroussin 	 * subsequent scanning for manpath directories is rooted
109761d06d6bSBaptiste Daroussin 	 * relative to the same position.
109861d06d6bSBaptiste Daroussin 	 */
109961d06d6bSBaptiste Daroussin 
110061d06d6bSBaptiste Daroussin 	if (chdir(MAN_DIR) == -1) {
110161d06d6bSBaptiste Daroussin 		warn("MAN_DIR: %s", MAN_DIR);
110261d06d6bSBaptiste Daroussin 		pg_error_internal();
110361d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
110461d06d6bSBaptiste Daroussin 	}
110561d06d6bSBaptiste Daroussin 
110661d06d6bSBaptiste Daroussin 	memset(&req, 0, sizeof(struct req));
110761d06d6bSBaptiste Daroussin 	req.q.equal = 1;
110861d06d6bSBaptiste Daroussin 	parse_manpath_conf(&req);
110961d06d6bSBaptiste Daroussin 
111061d06d6bSBaptiste Daroussin 	/* Parse the path info and the query string. */
111161d06d6bSBaptiste Daroussin 
111261d06d6bSBaptiste Daroussin 	if ((path = getenv("PATH_INFO")) == NULL)
111361d06d6bSBaptiste Daroussin 		path = "";
111461d06d6bSBaptiste Daroussin 	else if (*path == '/')
111561d06d6bSBaptiste Daroussin 		path++;
111661d06d6bSBaptiste Daroussin 
111761d06d6bSBaptiste Daroussin 	if (*path != '\0') {
111861d06d6bSBaptiste Daroussin 		parse_path_info(&req, path);
111961d06d6bSBaptiste Daroussin 		if (req.q.manpath == NULL || req.q.sec == NULL ||
112061d06d6bSBaptiste Daroussin 		    *req.q.query == '\0' || access(path, F_OK) == -1)
112161d06d6bSBaptiste Daroussin 			path = "";
112261d06d6bSBaptiste Daroussin 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
112361d06d6bSBaptiste Daroussin 		parse_query_string(&req, querystring);
112461d06d6bSBaptiste Daroussin 
112561d06d6bSBaptiste Daroussin 	/* Validate parsed data and add defaults. */
112661d06d6bSBaptiste Daroussin 
112761d06d6bSBaptiste Daroussin 	if (req.q.manpath == NULL)
112861d06d6bSBaptiste Daroussin 		req.q.manpath = mandoc_strdup(req.p[0]);
112961d06d6bSBaptiste Daroussin 	else if ( ! validate_manpath(&req, req.q.manpath)) {
113061d06d6bSBaptiste Daroussin 		pg_error_badrequest(
113161d06d6bSBaptiste Daroussin 		    "You specified an invalid manpath.");
113261d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
113361d06d6bSBaptiste Daroussin 	}
113461d06d6bSBaptiste Daroussin 
11357295610fSBaptiste Daroussin 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
113661d06d6bSBaptiste Daroussin 		pg_error_badrequest(
113761d06d6bSBaptiste Daroussin 		    "You specified an invalid architecture.");
113861d06d6bSBaptiste Daroussin 		return EXIT_FAILURE;
113961d06d6bSBaptiste Daroussin 	}
114061d06d6bSBaptiste Daroussin 
114161d06d6bSBaptiste Daroussin 	/* Dispatch to the three different pages. */
114261d06d6bSBaptiste Daroussin 
114361d06d6bSBaptiste Daroussin 	if ('\0' != *path)
114461d06d6bSBaptiste Daroussin 		pg_show(&req, path);
114561d06d6bSBaptiste Daroussin 	else if (NULL != req.q.query)
114661d06d6bSBaptiste Daroussin 		pg_search(&req);
114761d06d6bSBaptiste Daroussin 	else
114861d06d6bSBaptiste Daroussin 		pg_index(&req);
114961d06d6bSBaptiste Daroussin 
115061d06d6bSBaptiste Daroussin 	free(req.q.manpath);
115161d06d6bSBaptiste Daroussin 	free(req.q.arch);
115261d06d6bSBaptiste Daroussin 	free(req.q.sec);
115361d06d6bSBaptiste Daroussin 	free(req.q.query);
115461d06d6bSBaptiste Daroussin 	for (i = 0; i < (int)req.psz; i++)
115561d06d6bSBaptiste Daroussin 		free(req.p[i]);
115661d06d6bSBaptiste Daroussin 	free(req.p);
115761d06d6bSBaptiste Daroussin 	return EXIT_SUCCESS;
115861d06d6bSBaptiste Daroussin }
115961d06d6bSBaptiste Daroussin 
116061d06d6bSBaptiste Daroussin /*
11617295610fSBaptiste Daroussin  * Translate PATH_INFO to a query.
116261d06d6bSBaptiste Daroussin  */
116361d06d6bSBaptiste Daroussin static void
parse_path_info(struct req * req,const char * path)116461d06d6bSBaptiste Daroussin parse_path_info(struct req *req, const char *path)
116561d06d6bSBaptiste Daroussin {
11667295610fSBaptiste Daroussin 	const char	*name, *sec, *end;
116761d06d6bSBaptiste Daroussin 
116861d06d6bSBaptiste Daroussin 	req->isquery = 0;
116961d06d6bSBaptiste Daroussin 	req->q.equal = 1;
11707295610fSBaptiste Daroussin 	req->q.manpath = NULL;
117161d06d6bSBaptiste Daroussin 	req->q.arch = NULL;
117261d06d6bSBaptiste Daroussin 
117361d06d6bSBaptiste Daroussin 	/* Mandatory manual page name. */
11747295610fSBaptiste Daroussin 	if ((name = strrchr(path, '/')) == NULL)
11757295610fSBaptiste Daroussin 		name = path;
11767295610fSBaptiste Daroussin 	else
11777295610fSBaptiste Daroussin 		name++;
117861d06d6bSBaptiste Daroussin 
117961d06d6bSBaptiste Daroussin 	/* Optional trailing section. */
11807295610fSBaptiste Daroussin 	sec = strrchr(name, '.');
11817295610fSBaptiste Daroussin 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
11827295610fSBaptiste Daroussin 		req->q.query = mandoc_strndup(name, sec - name - 1);
11837295610fSBaptiste Daroussin 		req->q.sec = mandoc_strdup(sec);
11847295610fSBaptiste Daroussin 	} else {
11857295610fSBaptiste Daroussin 		req->q.query = mandoc_strdup(name);
118661d06d6bSBaptiste Daroussin 		req->q.sec = NULL;
118761d06d6bSBaptiste Daroussin 	}
118861d06d6bSBaptiste Daroussin 
118961d06d6bSBaptiste Daroussin 	/* Handle the case of name[.section] only. */
11907295610fSBaptiste Daroussin 	if (name == path)
119161d06d6bSBaptiste Daroussin 		return;
119261d06d6bSBaptiste Daroussin 
11937295610fSBaptiste Daroussin 	/* Optional manpath. */
11947295610fSBaptiste Daroussin 	end = strchr(path, '/');
11957295610fSBaptiste Daroussin 	req->q.manpath = mandoc_strndup(path, end - path);
11967295610fSBaptiste Daroussin 	if (validate_manpath(req, req->q.manpath)) {
11977295610fSBaptiste Daroussin 		path = end + 1;
11987295610fSBaptiste Daroussin 		if (name == path)
11997295610fSBaptiste Daroussin 			return;
12007295610fSBaptiste Daroussin 	} else {
12017295610fSBaptiste Daroussin 		free(req->q.manpath);
12027295610fSBaptiste Daroussin 		req->q.manpath = NULL;
12037295610fSBaptiste Daroussin 	}
12047295610fSBaptiste Daroussin 
12057295610fSBaptiste Daroussin 	/* Optional section. */
12067295610fSBaptiste Daroussin 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
12077295610fSBaptiste Daroussin 		path += 3;
12087295610fSBaptiste Daroussin 		end = strchr(path, '/');
12097295610fSBaptiste Daroussin 		free(req->q.sec);
12107295610fSBaptiste Daroussin 		req->q.sec = mandoc_strndup(path, end - path);
12117295610fSBaptiste Daroussin 		path = end + 1;
12127295610fSBaptiste Daroussin 		if (name == path)
12137295610fSBaptiste Daroussin 			return;
12147295610fSBaptiste Daroussin 	}
12157295610fSBaptiste Daroussin 
12167295610fSBaptiste Daroussin 	/* Optional architecture. */
12177295610fSBaptiste Daroussin 	end = strchr(path, '/');
12187295610fSBaptiste Daroussin 	if (end + 1 != name) {
121961d06d6bSBaptiste Daroussin 		pg_error_badrequest(
122061d06d6bSBaptiste Daroussin 		    "You specified too many directory components.");
122161d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
122261d06d6bSBaptiste Daroussin 	}
12237295610fSBaptiste Daroussin 	req->q.arch = mandoc_strndup(path, end - path);
12247295610fSBaptiste Daroussin 	if (validate_arch(req->q.arch) == 0) {
122561d06d6bSBaptiste Daroussin 		pg_error_badrequest(
122661d06d6bSBaptiste Daroussin 		    "You specified an invalid directory component.");
122761d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
122861d06d6bSBaptiste Daroussin 	}
122961d06d6bSBaptiste Daroussin }
123061d06d6bSBaptiste Daroussin 
123161d06d6bSBaptiste Daroussin /*
123261d06d6bSBaptiste Daroussin  * Scan for indexable paths.
123361d06d6bSBaptiste Daroussin  */
123461d06d6bSBaptiste Daroussin static void
parse_manpath_conf(struct req * req)123561d06d6bSBaptiste Daroussin parse_manpath_conf(struct req *req)
123661d06d6bSBaptiste Daroussin {
123761d06d6bSBaptiste Daroussin 	FILE	*fp;
123861d06d6bSBaptiste Daroussin 	char	*dp;
123961d06d6bSBaptiste Daroussin 	size_t	 dpsz;
124061d06d6bSBaptiste Daroussin 	ssize_t	 len;
124161d06d6bSBaptiste Daroussin 
124261d06d6bSBaptiste Daroussin 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
124361d06d6bSBaptiste Daroussin 		warn("%s/manpath.conf", MAN_DIR);
124461d06d6bSBaptiste Daroussin 		pg_error_internal();
124561d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
124661d06d6bSBaptiste Daroussin 	}
124761d06d6bSBaptiste Daroussin 
124861d06d6bSBaptiste Daroussin 	dp = NULL;
124961d06d6bSBaptiste Daroussin 	dpsz = 0;
125061d06d6bSBaptiste Daroussin 
125161d06d6bSBaptiste Daroussin 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
125261d06d6bSBaptiste Daroussin 		if (dp[len - 1] == '\n')
125361d06d6bSBaptiste Daroussin 			dp[--len] = '\0';
125461d06d6bSBaptiste Daroussin 		req->p = mandoc_realloc(req->p,
125561d06d6bSBaptiste Daroussin 		    (req->psz + 1) * sizeof(char *));
125661d06d6bSBaptiste Daroussin 		if ( ! validate_urifrag(dp)) {
125761d06d6bSBaptiste Daroussin 			warnx("%s/manpath.conf contains "
125861d06d6bSBaptiste Daroussin 			    "unsafe path \"%s\"", MAN_DIR, dp);
125961d06d6bSBaptiste Daroussin 			pg_error_internal();
126061d06d6bSBaptiste Daroussin 			exit(EXIT_FAILURE);
126161d06d6bSBaptiste Daroussin 		}
126261d06d6bSBaptiste Daroussin 		if (strchr(dp, '/') != NULL) {
126361d06d6bSBaptiste Daroussin 			warnx("%s/manpath.conf contains "
126461d06d6bSBaptiste Daroussin 			    "path with slash \"%s\"", MAN_DIR, dp);
126561d06d6bSBaptiste Daroussin 			pg_error_internal();
126661d06d6bSBaptiste Daroussin 			exit(EXIT_FAILURE);
126761d06d6bSBaptiste Daroussin 		}
126861d06d6bSBaptiste Daroussin 		req->p[req->psz++] = dp;
126961d06d6bSBaptiste Daroussin 		dp = NULL;
127061d06d6bSBaptiste Daroussin 		dpsz = 0;
127161d06d6bSBaptiste Daroussin 	}
127261d06d6bSBaptiste Daroussin 	free(dp);
127361d06d6bSBaptiste Daroussin 
127461d06d6bSBaptiste Daroussin 	if (req->p == NULL) {
127561d06d6bSBaptiste Daroussin 		warnx("%s/manpath.conf is empty", MAN_DIR);
127661d06d6bSBaptiste Daroussin 		pg_error_internal();
127761d06d6bSBaptiste Daroussin 		exit(EXIT_FAILURE);
127861d06d6bSBaptiste Daroussin 	}
127961d06d6bSBaptiste Daroussin }
1280