xref: /openbsd/usr.bin/mandoc/cgi.c (revision f211c215)
1*f211c215Sschwarze /*	$OpenBSD: cgi.c,v 1.61 2016/04/14 23:48:06 schwarze Exp $ */
2c6c22f12Sschwarze /*
3c6c22f12Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4abf19dc9Sschwarze  * Copyright (c) 2014, 2015, 2016 Ingo Schwarze <schwarze@usta.de>
5c6c22f12Sschwarze  *
6c6c22f12Sschwarze  * Permission to use, copy, modify, and distribute this software for any
7c6c22f12Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8c6c22f12Sschwarze  * copyright notice and this permission notice appear in all copies.
9c6c22f12Sschwarze  *
104de77decSschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11c6c22f12Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124de77decSschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13c6c22f12Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14c6c22f12Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15c6c22f12Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16c6c22f12Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17c6c22f12Sschwarze  */
18136c26b8Sschwarze #include <sys/types.h>
19136c26b8Sschwarze #include <sys/time.h>
20136c26b8Sschwarze 
21c6c22f12Sschwarze #include <ctype.h>
22c6c22f12Sschwarze #include <errno.h>
23c6c22f12Sschwarze #include <fcntl.h>
24c6c22f12Sschwarze #include <limits.h>
25c2235d37Sschwarze #include <stdint.h>
26c6c22f12Sschwarze #include <stdio.h>
27c6c22f12Sschwarze #include <stdlib.h>
28c6c22f12Sschwarze #include <string.h>
29c6c22f12Sschwarze #include <unistd.h>
30c6c22f12Sschwarze 
31c6c22f12Sschwarze #include "mandoc_aux.h"
32f2d5c709Sschwarze #include "mandoc.h"
33f2d5c709Sschwarze #include "roff.h"
34396853b5Sschwarze #include "mdoc.h"
35fec2846bSschwarze #include "man.h"
36c6c22f12Sschwarze #include "main.h"
374de77decSschwarze #include "manconf.h"
38c6c22f12Sschwarze #include "mansearch.h"
396fdade3eSschwarze #include "cgi.h"
40c6c22f12Sschwarze 
41c6c22f12Sschwarze /*
42c6c22f12Sschwarze  * A query as passed to the search function.
43c6c22f12Sschwarze  */
44c6c22f12Sschwarze struct	query {
4531e689c3Sschwarze 	char		*manpath; /* desired manual directory */
4631e689c3Sschwarze 	char		*arch; /* architecture */
4731e689c3Sschwarze 	char		*sec; /* manual section */
48e89321abSschwarze 	char		*query; /* unparsed query expression */
494477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
50c6c22f12Sschwarze };
51c6c22f12Sschwarze 
52c6c22f12Sschwarze struct	req {
53c6c22f12Sschwarze 	struct query	  q;
54c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
55c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
5652b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
57c6c22f12Sschwarze };
58c6c22f12Sschwarze 
59c6c22f12Sschwarze static	void		 catman(const struct req *, const char *);
60c6c22f12Sschwarze static	void		 format(const struct req *, const char *);
61c6c22f12Sschwarze static	void		 html_print(const char *);
62c6c22f12Sschwarze static	void		 html_putchar(char);
63c6c22f12Sschwarze static	int		 http_decode(char *);
6431e689c3Sschwarze static	void		 http_parse(struct req *, const char *);
65c6c22f12Sschwarze static	void		 pathgen(struct req *);
6602b1b494Sschwarze static	void		 path_parse(struct req *req, const char *path);
67facea411Sschwarze static	void		 pg_error_badrequest(const char *);
68facea411Sschwarze static	void		 pg_error_internal(void);
69facea411Sschwarze static	void		 pg_index(const struct req *);
70facea411Sschwarze static	void		 pg_noresult(const struct req *, const char *);
7157482ef4Sschwarze static	void		 pg_search(const struct req *);
72facea411Sschwarze static	void		 pg_searchres(const struct req *,
73facea411Sschwarze 				struct manpage *, size_t);
7481060b1aSschwarze static	void		 pg_show(struct req *, const char *);
75c6c22f12Sschwarze static	void		 resp_begin_html(int, const char *);
76c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
77711661c7Sschwarze static	void		 resp_copy(const char *);
78c6c22f12Sschwarze static	void		 resp_end_html(void);
79c6c22f12Sschwarze static	void		 resp_searchform(const struct req *);
8046723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
81e89321abSschwarze static	void		 set_query_attr(char **, char **);
82e89321abSschwarze static	int		 validate_filename(const char *);
83e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
84e89321abSschwarze static	int		 validate_urifrag(const char *);
85c6c22f12Sschwarze 
863b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
87c6c22f12Sschwarze 
8846723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
8928e449d6Sschwarze static	const char *const sec_numbers[] = {
9028e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
9128e449d6Sschwarze };
9228e449d6Sschwarze static	const char *const sec_names[] = {
9328e449d6Sschwarze     "All Sections",
9428e449d6Sschwarze     "1 - General Commands",
9528e449d6Sschwarze     "2 - System Calls",
964e6618fdSschwarze     "3 - Library Functions",
974e6618fdSschwarze     "3p - Perl Library",
984e6618fdSschwarze     "4 - Device Drivers",
9928e449d6Sschwarze     "5 - File Formats",
10028e449d6Sschwarze     "6 - Games",
1014e6618fdSschwarze     "7 - Miscellaneous Information",
1024e6618fdSschwarze     "8 - System Manager\'s Manual",
1034e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
10428e449d6Sschwarze };
10528e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
10628e449d6Sschwarze 
10728e449d6Sschwarze static	const char *const arch_names[] = {
10828e449d6Sschwarze     "amd64",       "alpha",       "armish",      "armv7",
10928e449d6Sschwarze     "aviion",      "hppa",        "hppa64",      "i386",
11028e449d6Sschwarze     "ia64",        "landisk",     "loongson",    "luna88k",
11128e449d6Sschwarze     "macppc",      "mips64",      "octeon",      "sgi",
11228e449d6Sschwarze     "socppc",      "solbourne",   "sparc",       "sparc64",
11328e449d6Sschwarze     "vax",         "zaurus",
11428e449d6Sschwarze     "amiga",       "arc",         "arm32",       "atari",
11528e449d6Sschwarze     "beagle",      "cats",        "hp300",       "mac68k",
11628e449d6Sschwarze     "mvme68k",     "mvme88k",     "mvmeppc",     "palm",
11728e449d6Sschwarze     "pc532",       "pegasos",     "pmax",        "powerpc",
11828e449d6Sschwarze     "sun3",        "wgrisc",      "x68k"
11928e449d6Sschwarze };
12028e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
12128e449d6Sschwarze 
122c6c22f12Sschwarze /*
123c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
124c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
125c6c22f12Sschwarze  */
126c6c22f12Sschwarze static void
127c6c22f12Sschwarze html_putchar(char c)
128c6c22f12Sschwarze {
129c6c22f12Sschwarze 
130c6c22f12Sschwarze 	switch (c) {
131c6c22f12Sschwarze 	case ('"'):
132c6c22f12Sschwarze 		printf("&quote;");
133c6c22f12Sschwarze 		break;
134c6c22f12Sschwarze 	case ('&'):
135c6c22f12Sschwarze 		printf("&amp;");
136c6c22f12Sschwarze 		break;
137c6c22f12Sschwarze 	case ('>'):
138c6c22f12Sschwarze 		printf("&gt;");
139c6c22f12Sschwarze 		break;
140c6c22f12Sschwarze 	case ('<'):
141c6c22f12Sschwarze 		printf("&lt;");
142c6c22f12Sschwarze 		break;
143c6c22f12Sschwarze 	default:
144c6c22f12Sschwarze 		putchar((unsigned char)c);
145c6c22f12Sschwarze 		break;
146c6c22f12Sschwarze 	}
147c6c22f12Sschwarze }
148c6c22f12Sschwarze 
149c6c22f12Sschwarze /*
150c6c22f12Sschwarze  * Call through to html_putchar().
151c6c22f12Sschwarze  * Accepts NULL strings.
152c6c22f12Sschwarze  */
153c6c22f12Sschwarze static void
154c6c22f12Sschwarze html_print(const char *p)
155c6c22f12Sschwarze {
156c6c22f12Sschwarze 
157c6c22f12Sschwarze 	if (NULL == p)
158c6c22f12Sschwarze 		return;
159c6c22f12Sschwarze 	while ('\0' != *p)
160c6c22f12Sschwarze 		html_putchar(*p++);
161c6c22f12Sschwarze }
162c6c22f12Sschwarze 
163c6c22f12Sschwarze /*
16431e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
16531e689c3Sschwarze  * to the query structure.
166c6c22f12Sschwarze  */
167c6c22f12Sschwarze static void
16831e689c3Sschwarze set_query_attr(char **attr, char **val)
16931e689c3Sschwarze {
17031e689c3Sschwarze 
17131e689c3Sschwarze 	free(*attr);
17231e689c3Sschwarze 	if (**val == '\0') {
17331e689c3Sschwarze 		*attr = NULL;
17431e689c3Sschwarze 		free(*val);
17531e689c3Sschwarze 	} else
17631e689c3Sschwarze 		*attr = *val;
17731e689c3Sschwarze 	*val = NULL;
17831e689c3Sschwarze }
17931e689c3Sschwarze 
18031e689c3Sschwarze /*
18131e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
18231e689c3Sschwarze  * and store the values into the query structure.
18331e689c3Sschwarze  */
18431e689c3Sschwarze static void
18531e689c3Sschwarze http_parse(struct req *req, const char *qs)
186c6c22f12Sschwarze {
187c6c22f12Sschwarze 	char		*key, *val;
18831e689c3Sschwarze 	size_t		 keysz, valsz;
189c6c22f12Sschwarze 
19052b413c1Sschwarze 	req->isquery	= 1;
19131e689c3Sschwarze 	req->q.manpath	= NULL;
19231e689c3Sschwarze 	req->q.arch	= NULL;
19331e689c3Sschwarze 	req->q.sec	= NULL;
194e89321abSschwarze 	req->q.query	= NULL;
1954477fbfaSschwarze 	req->q.equal	= 1;
196c6c22f12Sschwarze 
19731e689c3Sschwarze 	key = val = NULL;
19831e689c3Sschwarze 	while (*qs != '\0') {
199c6c22f12Sschwarze 
20031e689c3Sschwarze 		/* Parse one key. */
201c6c22f12Sschwarze 
20231e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
20331e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
20431e689c3Sschwarze 		qs += keysz;
20531e689c3Sschwarze 		if (*qs != '=')
20631e689c3Sschwarze 			goto next;
207c6c22f12Sschwarze 
20831e689c3Sschwarze 		/* Parse one value. */
209c6c22f12Sschwarze 
21031e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
21131e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
21231e689c3Sschwarze 		qs += valsz;
213c6c22f12Sschwarze 
21431e689c3Sschwarze 		/* Decode and catch encoding errors. */
21531e689c3Sschwarze 
21631e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
21731e689c3Sschwarze 			goto next;
21831e689c3Sschwarze 
21931e689c3Sschwarze 		/* Handle key-value pairs. */
22031e689c3Sschwarze 
22131e689c3Sschwarze 		if ( ! strcmp(key, "query"))
222e89321abSschwarze 			set_query_attr(&req->q.query, &val);
22331e689c3Sschwarze 
22431e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
22531e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
22631e689c3Sschwarze 
22731e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
228aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
22931e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
230aabcc9a1Sschwarze 				val[7] = '-';
231aabcc9a1Sschwarze 				if ('C' == val[8])
232aabcc9a1Sschwarze 					val[8] = 'c';
233aabcc9a1Sschwarze 			}
234aabcc9a1Sschwarze #endif
23531e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
23631e689c3Sschwarze 		}
23731e689c3Sschwarze 
23831e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
239aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24031e689c3Sschwarze 		    && strcmp(key, "sektion")
241aabcc9a1Sschwarze #endif
24231e689c3Sschwarze 		    )) {
24331e689c3Sschwarze 			if ( ! strcmp(val, "0"))
24431e689c3Sschwarze 				*val = '\0';
24531e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
246c6c22f12Sschwarze 		}
24731e689c3Sschwarze 
24831e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
24931e689c3Sschwarze 			if ( ! strcmp(val, "default"))
25031e689c3Sschwarze 				*val = '\0';
25131e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2524477fbfaSschwarze 		}
25331e689c3Sschwarze 
25431e689c3Sschwarze 		/*
25531e689c3Sschwarze 		 * The key must be freed in any case.
25631e689c3Sschwarze 		 * The val may have been handed over to the query
25731e689c3Sschwarze 		 * structure, in which case it is now NULL.
25831e689c3Sschwarze 		 */
25931e689c3Sschwarze next:
26031e689c3Sschwarze 		free(key);
26131e689c3Sschwarze 		key = NULL;
26231e689c3Sschwarze 		free(val);
26331e689c3Sschwarze 		val = NULL;
26431e689c3Sschwarze 
26531e689c3Sschwarze 		if (*qs != '\0')
26631e689c3Sschwarze 			qs++;
26731e689c3Sschwarze 	}
268c6c22f12Sschwarze }
269c6c22f12Sschwarze 
270c6c22f12Sschwarze /*
271c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
272c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
273c6c22f12Sschwarze  * over the allocated string.
274c6c22f12Sschwarze  */
275c6c22f12Sschwarze static int
276c6c22f12Sschwarze http_decode(char *p)
277c6c22f12Sschwarze {
278c6c22f12Sschwarze 	char             hex[3];
2791f69f32bStedu 	char		*q;
280c6c22f12Sschwarze 	int              c;
281c6c22f12Sschwarze 
282c6c22f12Sschwarze 	hex[2] = '\0';
283c6c22f12Sschwarze 
2841f69f32bStedu 	q = p;
2851f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
286c6c22f12Sschwarze 		if ('%' == *p) {
287c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
288526e306bSschwarze 				return 0;
289c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
290526e306bSschwarze 				return 0;
291c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
292526e306bSschwarze 				return 0;
293c6c22f12Sschwarze 			if ('\0' == c)
294526e306bSschwarze 				return 0;
295c6c22f12Sschwarze 
2961f69f32bStedu 			*q = (char)c;
2971f69f32bStedu 			p += 2;
298c6c22f12Sschwarze 		} else
2991f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
300c6c22f12Sschwarze 	}
301c6c22f12Sschwarze 
3021f69f32bStedu 	*q = '\0';
303526e306bSschwarze 	return 1;
304c6c22f12Sschwarze }
305c6c22f12Sschwarze 
306c6c22f12Sschwarze static void
307c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
308c6c22f12Sschwarze {
309c6c22f12Sschwarze 
310c6c22f12Sschwarze 	if (200 != code)
311fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
312c6c22f12Sschwarze 
313fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
314fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
315fa9c540aStedu 	     "Pragma: no-cache\r\n"
316fa9c540aStedu 	     "\r\n");
317c6c22f12Sschwarze 
318c6c22f12Sschwarze 	fflush(stdout);
319c6c22f12Sschwarze }
320c6c22f12Sschwarze 
321c6c22f12Sschwarze static void
322711661c7Sschwarze resp_copy(const char *filename)
323711661c7Sschwarze {
324711661c7Sschwarze 	char	 buf[4096];
325711661c7Sschwarze 	ssize_t	 sz;
326711661c7Sschwarze 	int	 fd;
327711661c7Sschwarze 
328711661c7Sschwarze 	if ((fd = open(filename, O_RDONLY)) != -1) {
329711661c7Sschwarze 		fflush(stdout);
330711661c7Sschwarze 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
331711661c7Sschwarze 			write(STDOUT_FILENO, buf, sz);
332711661c7Sschwarze 	}
333711661c7Sschwarze }
334711661c7Sschwarze 
335711661c7Sschwarze static void
336c6c22f12Sschwarze resp_begin_html(int code, const char *msg)
337c6c22f12Sschwarze {
338c6c22f12Sschwarze 
339c6c22f12Sschwarze 	resp_begin_http(code, msg);
340c6c22f12Sschwarze 
341d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
342c6c22f12Sschwarze 	       "<HTML>\n"
343c6c22f12Sschwarze 	       "<HEAD>\n"
344d649d931Sschwarze 	       "<META CHARSET=\"UTF-8\" />\n"
34506bcd913Sschwarze 	       "<LINK REL=\"stylesheet\" HREF=\"%s/mandoc.css\""
346c6c22f12Sschwarze 	       " TYPE=\"text/css\" media=\"all\">\n"
3476fdade3eSschwarze 	       "<TITLE>%s</TITLE>\n"
348c6c22f12Sschwarze 	       "</HEAD>\n"
349c6c22f12Sschwarze 	       "<BODY>\n"
350c6c22f12Sschwarze 	       "<!-- Begin page content. //-->\n",
35106bcd913Sschwarze 	       CSS_DIR, CUSTOMIZE_TITLE);
352711661c7Sschwarze 
353711661c7Sschwarze 	resp_copy(MAN_DIR "/header.html");
354c6c22f12Sschwarze }
355c6c22f12Sschwarze 
356c6c22f12Sschwarze static void
357c6c22f12Sschwarze resp_end_html(void)
358c6c22f12Sschwarze {
359c6c22f12Sschwarze 
360711661c7Sschwarze 	resp_copy(MAN_DIR "/footer.html");
361711661c7Sschwarze 
362c6c22f12Sschwarze 	puts("</BODY>\n"
363c6c22f12Sschwarze 	     "</HTML>");
364c6c22f12Sschwarze }
365c6c22f12Sschwarze 
366c6c22f12Sschwarze static void
367c6c22f12Sschwarze resp_searchform(const struct req *req)
368c6c22f12Sschwarze {
369c6c22f12Sschwarze 	int		 i;
370c6c22f12Sschwarze 
371c6c22f12Sschwarze 	puts("<!-- Begin search form. //-->");
372c6c22f12Sschwarze 	printf("<DIV ID=\"mancgi\">\n"
3733b9cfc6fSschwarze 	       "<FORM ACTION=\"/%s\" METHOD=\"get\">\n"
374c6c22f12Sschwarze 	       "<FIELDSET>\n"
37528e449d6Sschwarze 	       "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
376c6c22f12Sschwarze 	       scriptname);
37728e449d6Sschwarze 
37828e449d6Sschwarze 	/* Write query input box. */
37928e449d6Sschwarze 
38028e449d6Sschwarze 	printf(	"<TABLE><TR><TD>\n"
3814477fbfaSschwarze 		"<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
382e89321abSschwarze 	if (NULL != req->q.query)
383e89321abSschwarze 		html_print(req->q.query);
38428e449d6Sschwarze 	puts("\" SIZE=\"40\">");
38528e449d6Sschwarze 
38628e449d6Sschwarze 	/* Write submission and reset buttons. */
38728e449d6Sschwarze 
38828e449d6Sschwarze 	printf(	"<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
38928e449d6Sschwarze 		"<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
39028e449d6Sschwarze 
39128e449d6Sschwarze 	/* Write show radio button */
39228e449d6Sschwarze 
39328e449d6Sschwarze 	printf(	"</TD><TD>\n"
39428e449d6Sschwarze 		"<INPUT TYPE=\"radio\" ");
39528e449d6Sschwarze 	if (req->q.equal)
396d56ca219Sschwarze 		printf("CHECKED=\"checked\" ");
39728e449d6Sschwarze 	printf(	"NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
39828e449d6Sschwarze 		"<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
39928e449d6Sschwarze 
40028e449d6Sschwarze 	/* Write section selector. */
40128e449d6Sschwarze 
402d56ca219Sschwarze 	puts(	"</TD></TR><TR><TD>\n"
40328e449d6Sschwarze 		"<SELECT NAME=\"sec\">");
40428e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
40528e449d6Sschwarze 		printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
40628e449d6Sschwarze 		if (NULL != req->q.sec &&
40728e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
408d56ca219Sschwarze 			printf(" SELECTED=\"selected\"");
40928e449d6Sschwarze 		printf(">%s</OPTION>\n", sec_names[i]);
41028e449d6Sschwarze 	}
41128e449d6Sschwarze 	puts("</SELECT>");
41228e449d6Sschwarze 
41328e449d6Sschwarze 	/* Write architecture selector. */
41428e449d6Sschwarze 
415be14a32aSschwarze 	printf(	"<SELECT NAME=\"arch\">\n"
416be14a32aSschwarze 		"<OPTION VALUE=\"default\"");
417be14a32aSschwarze 	if (NULL == req->q.arch)
418d56ca219Sschwarze 		printf(" SELECTED=\"selected\"");
419be14a32aSschwarze 	puts(">All Architectures</OPTION>");
42028e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
42128e449d6Sschwarze 		printf("<OPTION VALUE=\"%s\"", arch_names[i]);
42228e449d6Sschwarze 		if (NULL != req->q.arch &&
42328e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
424d56ca219Sschwarze 			printf(" SELECTED=\"selected\"");
42528e449d6Sschwarze 		printf(">%s</OPTION>\n", arch_names[i]);
42628e449d6Sschwarze 	}
42728e449d6Sschwarze 	puts("</SELECT>");
42828e449d6Sschwarze 
42928e449d6Sschwarze 	/* Write manpath selector. */
43028e449d6Sschwarze 
431c6c22f12Sschwarze 	if (req->psz > 1) {
43228e449d6Sschwarze 		puts("<SELECT NAME=\"manpath\">");
433c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
434c6c22f12Sschwarze 			printf("<OPTION ");
43550eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
436d56ca219Sschwarze 				printf("SELECTED=\"selected\" ");
437c6c22f12Sschwarze 			printf("VALUE=\"");
438c6c22f12Sschwarze 			html_print(req->p[i]);
439c6c22f12Sschwarze 			printf("\">");
440c6c22f12Sschwarze 			html_print(req->p[i]);
441c6c22f12Sschwarze 			puts("</OPTION>");
442c6c22f12Sschwarze 		}
443c6c22f12Sschwarze 		puts("</SELECT>");
444c6c22f12Sschwarze 	}
44528e449d6Sschwarze 
44628e449d6Sschwarze 	/* Write search radio button */
44728e449d6Sschwarze 
44828e449d6Sschwarze 	printf(	"</TD><TD>\n"
44928e449d6Sschwarze 		"<INPUT TYPE=\"radio\" ");
45028e449d6Sschwarze 	if (0 == req->q.equal)
451d56ca219Sschwarze 		printf("CHECKED=\"checked\" ");
45228e449d6Sschwarze 	printf(	"NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
45328e449d6Sschwarze 		"<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
45428e449d6Sschwarze 
45528e449d6Sschwarze 	puts("</TD></TR></TABLE>\n"
456c6c22f12Sschwarze 	     "</FIELDSET>\n"
457c6c22f12Sschwarze 	     "</FORM>\n"
458c6c22f12Sschwarze 	     "</DIV>");
459c6c22f12Sschwarze 	puts("<!-- End search form. //-->");
460c6c22f12Sschwarze }
461c6c22f12Sschwarze 
46281475784Sschwarze static int
463cf3a545cSschwarze validate_urifrag(const char *frag)
464cf3a545cSschwarze {
465cf3a545cSschwarze 
466cf3a545cSschwarze 	while ('\0' != *frag) {
467cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
468cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
469cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
470526e306bSschwarze 			return 0;
471cf3a545cSschwarze 		frag++;
472cf3a545cSschwarze 	}
473526e306bSschwarze 	return 1;
474cf3a545cSschwarze }
475cf3a545cSschwarze 
476cf3a545cSschwarze static int
477631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
478631ce2c6Sschwarze {
479631ce2c6Sschwarze 	size_t	 i;
480631ce2c6Sschwarze 
481631ce2c6Sschwarze 	if ( ! strcmp(manpath, "mandoc"))
482526e306bSschwarze 		return 1;
483631ce2c6Sschwarze 
484631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
485631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
486526e306bSschwarze 			return 1;
487631ce2c6Sschwarze 
488526e306bSschwarze 	return 0;
489631ce2c6Sschwarze }
490631ce2c6Sschwarze 
491631ce2c6Sschwarze static int
49281475784Sschwarze validate_filename(const char *file)
49381475784Sschwarze {
49481475784Sschwarze 
49581475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
49681475784Sschwarze 		file += 2;
49781475784Sschwarze 
498526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
499526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
50081475784Sschwarze }
50181475784Sschwarze 
502c6c22f12Sschwarze static void
503facea411Sschwarze pg_index(const struct req *req)
504c6c22f12Sschwarze {
505c6c22f12Sschwarze 
506c6c22f12Sschwarze 	resp_begin_html(200, NULL);
507c6c22f12Sschwarze 	resp_searchform(req);
508f267f4feSschwarze 	printf("<P>\n"
509d56ca219Sschwarze 	       "This web interface is documented in the\n"
5103b9cfc6fSschwarze 	       "<A HREF=\"/%s%smandoc/man8/man.cgi.8\">man.cgi</A>\n"
511d56ca219Sschwarze 	       "manual, and the\n"
5123b9cfc6fSschwarze 	       "<A HREF=\"/%s%smandoc/man1/apropos.1\">apropos</A>\n"
5132a43838fSschwarze 	       "manual explains the query syntax.\n"
514f267f4feSschwarze 	       "</P>\n",
5153b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5163b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
517c6c22f12Sschwarze 	resp_end_html();
518c6c22f12Sschwarze }
519c6c22f12Sschwarze 
520c6c22f12Sschwarze static void
521facea411Sschwarze pg_noresult(const struct req *req, const char *msg)
522c6c22f12Sschwarze {
523c6c22f12Sschwarze 	resp_begin_html(200, NULL);
524c6c22f12Sschwarze 	resp_searchform(req);
525c6c22f12Sschwarze 	puts("<P>");
526c6c22f12Sschwarze 	puts(msg);
527c6c22f12Sschwarze 	puts("</P>");
528c6c22f12Sschwarze 	resp_end_html();
529c6c22f12Sschwarze }
530c6c22f12Sschwarze 
531c6c22f12Sschwarze static void
532facea411Sschwarze pg_error_badrequest(const char *msg)
533c6c22f12Sschwarze {
534c6c22f12Sschwarze 
535c6c22f12Sschwarze 	resp_begin_html(400, "Bad Request");
536c6c22f12Sschwarze 	puts("<H1>Bad Request</H1>\n"
537c6c22f12Sschwarze 	     "<P>\n");
538c6c22f12Sschwarze 	puts(msg);
539c6c22f12Sschwarze 	printf("Try again from the\n"
5403b9cfc6fSschwarze 	       "<A HREF=\"/%s\">main page</A>.\n"
541c6c22f12Sschwarze 	       "</P>", scriptname);
542c6c22f12Sschwarze 	resp_end_html();
543c6c22f12Sschwarze }
544c6c22f12Sschwarze 
545c6c22f12Sschwarze static void
546facea411Sschwarze pg_error_internal(void)
547c6c22f12Sschwarze {
548c6c22f12Sschwarze 	resp_begin_html(500, "Internal Server Error");
549c6c22f12Sschwarze 	puts("<P>Internal Server Error</P>");
550c6c22f12Sschwarze 	resp_end_html();
551c6c22f12Sschwarze }
552c6c22f12Sschwarze 
553c6c22f12Sschwarze static void
554facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
555c6c22f12Sschwarze {
556be14a32aSschwarze 	char		*arch, *archend;
557db26164eSschwarze 	const char	*sec;
558db26164eSschwarze 	size_t		 i, iuse;
559be14a32aSschwarze 	int		 archprio, archpriouse;
56046723f19Sschwarze 	int		 prio, priouse;
561c6c22f12Sschwarze 
56281475784Sschwarze 	for (i = 0; i < sz; i++) {
56381475784Sschwarze 		if (validate_filename(r[i].file))
56481475784Sschwarze 			continue;
56581475784Sschwarze 		fprintf(stderr, "invalid filename %s in %s database\n",
56681475784Sschwarze 		    r[i].file, req->q.manpath);
56781475784Sschwarze 		pg_error_internal();
56881475784Sschwarze 		return;
56981475784Sschwarze 	}
57081475784Sschwarze 
57152b413c1Sschwarze 	if (req->isquery && sz == 1) {
572c6c22f12Sschwarze 		/*
573c6c22f12Sschwarze 		 * If we have just one result, then jump there now
574c6c22f12Sschwarze 		 * without any delay.
575c6c22f12Sschwarze 		 */
576fa9c540aStedu 		printf("Status: 303 See Other\r\n");
5773b9cfc6fSschwarze 		printf("Location: http://%s/%s%s%s/%s",
5783b9cfc6fSschwarze 		    HTTP_HOST, scriptname,
5793b9cfc6fSschwarze 		    *scriptname == '\0' ? "" : "/",
5803b9cfc6fSschwarze 		    req->q.manpath, r[0].file);
581fa9c540aStedu 		printf("\r\n"
582fa9c540aStedu 		     "Content-Type: text/html; charset=utf-8\r\n"
583fa9c540aStedu 		     "\r\n");
584c6c22f12Sschwarze 		return;
585c6c22f12Sschwarze 	}
586c6c22f12Sschwarze 
587c6c22f12Sschwarze 	resp_begin_html(200, NULL);
588c6c22f12Sschwarze 	resp_searchform(req);
589c6c22f12Sschwarze 	puts("<DIV CLASS=\"results\">");
590c6c22f12Sschwarze 	puts("<TABLE>");
591c6c22f12Sschwarze 
592c6c22f12Sschwarze 	for (i = 0; i < sz; i++) {
593c6c22f12Sschwarze 		printf("<TR>\n"
594c6c22f12Sschwarze 		       "<TD CLASS=\"title\">\n"
5953b9cfc6fSschwarze 		       "<A HREF=\"/%s%s%s/%s",
5963b9cfc6fSschwarze 		    scriptname, *scriptname == '\0' ? "" : "/",
5973b9cfc6fSschwarze 		    req->q.manpath, r[i].file);
598c6c22f12Sschwarze 		printf("\">");
599c6c22f12Sschwarze 		html_print(r[i].names);
600c6c22f12Sschwarze 		printf("</A>\n"
601c6c22f12Sschwarze 		       "</TD>\n"
602c6c22f12Sschwarze 		       "<TD CLASS=\"desc\">");
603c6c22f12Sschwarze 		html_print(r[i].output);
604c6c22f12Sschwarze 		puts("</TD>\n"
605c6c22f12Sschwarze 		     "</TR>");
606c6c22f12Sschwarze 	}
607c6c22f12Sschwarze 
608c6c22f12Sschwarze 	puts("</TABLE>\n"
609c6c22f12Sschwarze 	     "</DIV>");
61046723f19Sschwarze 
61146723f19Sschwarze 	/*
61246723f19Sschwarze 	 * In man(1) mode, show one of the pages
61346723f19Sschwarze 	 * even if more than one is found.
61446723f19Sschwarze 	 */
61546723f19Sschwarze 
61646723f19Sschwarze 	if (req->q.equal) {
61746723f19Sschwarze 		puts("<HR>");
61846723f19Sschwarze 		iuse = 0;
619db26164eSschwarze 		priouse = 20;
620be14a32aSschwarze 		archpriouse = 3;
62146723f19Sschwarze 		for (i = 0; i < sz; i++) {
622db26164eSschwarze 			sec = r[i].file;
623db26164eSschwarze 			sec += strcspn(sec, "123456789");
624db26164eSschwarze 			if (sec[0] == '\0')
62546723f19Sschwarze 				continue;
626db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
627db26164eSschwarze 			if (sec[1] != '/')
628db26164eSschwarze 				prio += 10;
629db26164eSschwarze 			if (req->q.arch == NULL) {
630be14a32aSschwarze 				archprio =
631db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
632db26164eSschwarze 					== NULL) ? 3 :
633db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
634db26164eSschwarze 					== NULL) ? 0 :
635be14a32aSschwarze 				    strncmp(arch, "amd64/",
636be14a32aSschwarze 					archend - arch) ? 2 : 1;
637be14a32aSschwarze 				if (archprio < archpriouse) {
638be14a32aSschwarze 					archpriouse = archprio;
639be14a32aSschwarze 					priouse = prio;
640be14a32aSschwarze 					iuse = i;
641be14a32aSschwarze 					continue;
642be14a32aSschwarze 				}
643be14a32aSschwarze 				if (archprio > archpriouse)
644be14a32aSschwarze 					continue;
645be14a32aSschwarze 			}
64646723f19Sschwarze 			if (prio >= priouse)
64746723f19Sschwarze 				continue;
64846723f19Sschwarze 			priouse = prio;
64946723f19Sschwarze 			iuse = i;
65046723f19Sschwarze 		}
65146723f19Sschwarze 		resp_show(req, r[iuse].file);
65246723f19Sschwarze 	}
65346723f19Sschwarze 
654c6c22f12Sschwarze 	resp_end_html();
655c6c22f12Sschwarze }
656c6c22f12Sschwarze 
657c6c22f12Sschwarze static void
658c6c22f12Sschwarze catman(const struct req *req, const char *file)
659c6c22f12Sschwarze {
660c6c22f12Sschwarze 	FILE		*f;
661c6c22f12Sschwarze 	char		*p;
66231f93c25Sschwarze 	size_t		 sz;
66331f93c25Sschwarze 	ssize_t		 len;
66431f93c25Sschwarze 	int		 i;
665c6c22f12Sschwarze 	int		 italic, bold;
666c6c22f12Sschwarze 
66731f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
66846723f19Sschwarze 		puts("<P>You specified an invalid manual file.</P>");
669c6c22f12Sschwarze 		return;
670c6c22f12Sschwarze 	}
671c6c22f12Sschwarze 
672c6c22f12Sschwarze 	puts("<DIV CLASS=\"catman\">\n"
673c6c22f12Sschwarze 	     "<PRE>");
674c6c22f12Sschwarze 
67531f93c25Sschwarze 	p = NULL;
67631f93c25Sschwarze 	sz = 0;
67731f93c25Sschwarze 
67831f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
679c6c22f12Sschwarze 		bold = italic = 0;
68031f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
681c6c22f12Sschwarze 			/*
682c6c22f12Sschwarze 			 * This means that the catpage is out of state.
683c6c22f12Sschwarze 			 * Ignore it and keep going (although the
684c6c22f12Sschwarze 			 * catpage is bogus).
685c6c22f12Sschwarze 			 */
686c6c22f12Sschwarze 
687c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
688c6c22f12Sschwarze 				continue;
689c6c22f12Sschwarze 
690c6c22f12Sschwarze 			/*
691c6c22f12Sschwarze 			 * Print a regular character.
692c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
693c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
694c6c22f12Sschwarze 			 * have something to enter when we backspace.
695c6c22f12Sschwarze 			 */
696c6c22f12Sschwarze 
697c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
698c6c22f12Sschwarze 				if (italic)
699c6c22f12Sschwarze 					printf("</I>");
700c6c22f12Sschwarze 				if (bold)
701c6c22f12Sschwarze 					printf("</B>");
702c6c22f12Sschwarze 				italic = bold = 0;
703c6c22f12Sschwarze 				html_putchar(p[i]);
704c6c22f12Sschwarze 				continue;
70531f93c25Sschwarze 			} else if (i + 2 >= len)
706c6c22f12Sschwarze 				continue;
707c6c22f12Sschwarze 
708c6c22f12Sschwarze 			/* Italic mode. */
709c6c22f12Sschwarze 
710c6c22f12Sschwarze 			if ('_' == p[i]) {
711c6c22f12Sschwarze 				if (bold)
712c6c22f12Sschwarze 					printf("</B>");
713c6c22f12Sschwarze 				if ( ! italic)
714c6c22f12Sschwarze 					printf("<I>");
715c6c22f12Sschwarze 				bold = 0;
716c6c22f12Sschwarze 				italic = 1;
717c6c22f12Sschwarze 				i += 2;
718c6c22f12Sschwarze 				html_putchar(p[i]);
719c6c22f12Sschwarze 				continue;
720c6c22f12Sschwarze 			}
721c6c22f12Sschwarze 
722c6c22f12Sschwarze 			/*
723c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
724c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
725c6c22f12Sschwarze 			 */
726c6c22f12Sschwarze 
727c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
728c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
729c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
730c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
731c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
732c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
733c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
734c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
735c6c22f12Sschwarze 				if (italic)
736c6c22f12Sschwarze 					printf("</I>");
737c6c22f12Sschwarze 				if (bold)
738c6c22f12Sschwarze 					printf("</B>");
739c6c22f12Sschwarze 				italic = bold = 0;
740c6c22f12Sschwarze 				putchar('*');
741c6c22f12Sschwarze 				i += 2;
742c6c22f12Sschwarze 				continue;
743c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
744c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
745c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
746c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
747c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
748c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
749c6c22f12Sschwarze 				if (italic)
750c6c22f12Sschwarze 					printf("</I>");
751c6c22f12Sschwarze 				if (bold)
752c6c22f12Sschwarze 					printf("</B>");
753c6c22f12Sschwarze 				italic = bold = 0;
754c6c22f12Sschwarze 				putchar('+');
755c6c22f12Sschwarze 				i += 2;
756c6c22f12Sschwarze 				continue;
757c6c22f12Sschwarze 			}
758c6c22f12Sschwarze 
759c6c22f12Sschwarze 			/* Bold mode. */
760c6c22f12Sschwarze 
761c6c22f12Sschwarze 			if (italic)
762c6c22f12Sschwarze 				printf("</I>");
763c6c22f12Sschwarze 			if ( ! bold)
764c6c22f12Sschwarze 				printf("<B>");
765c6c22f12Sschwarze 			bold = 1;
766c6c22f12Sschwarze 			italic = 0;
767c6c22f12Sschwarze 			i += 2;
768c6c22f12Sschwarze 			html_putchar(p[i]);
769c6c22f12Sschwarze 		}
770c6c22f12Sschwarze 
771c6c22f12Sschwarze 		/*
772c6c22f12Sschwarze 		 * Clean up the last character.
773c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
774c6c22f12Sschwarze 		 */
775c6c22f12Sschwarze 
776c6c22f12Sschwarze 		if (italic)
777c6c22f12Sschwarze 			printf("</I>");
778c6c22f12Sschwarze 		if (bold)
779c6c22f12Sschwarze 			printf("</B>");
780c6c22f12Sschwarze 
78131f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
782c6c22f12Sschwarze 			html_putchar(p[i]);
783c6c22f12Sschwarze 
784c6c22f12Sschwarze 		putchar('\n');
785c6c22f12Sschwarze 	}
78631f93c25Sschwarze 	free(p);
787c6c22f12Sschwarze 
788c6c22f12Sschwarze 	puts("</PRE>\n"
78946723f19Sschwarze 	     "</DIV>");
790c6c22f12Sschwarze 
791c6c22f12Sschwarze 	fclose(f);
792c6c22f12Sschwarze }
793c6c22f12Sschwarze 
794c6c22f12Sschwarze static void
795c6c22f12Sschwarze format(const struct req *req, const char *file)
796c6c22f12Sschwarze {
7972ccd0917Sschwarze 	struct manoutput conf;
798c6c22f12Sschwarze 	struct mparse	*mp;
799ede1b9d0Sschwarze 	struct roff_man	*man;
800c6c22f12Sschwarze 	void		*vp;
801f74d674aSschwarze 	int		 fd;
802f74d674aSschwarze 	int		 usepath;
803c6c22f12Sschwarze 
804c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
80546723f19Sschwarze 		puts("<P>You specified an invalid manual file.</P>");
806c6c22f12Sschwarze 		return;
807c6c22f12Sschwarze 	}
808c6c22f12Sschwarze 
80916536faaSschwarze 	mchars_alloc();
81016536faaSschwarze 	mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL, req->q.manpath);
811df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
812c6c22f12Sschwarze 	close(fd);
813c6c22f12Sschwarze 
8142ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
8152ccd0917Sschwarze 	conf.fragment = 1;
816f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
817*f211c215Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%%N.%%S",
818*f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
819c6c22f12Sschwarze 
820f2d5c709Sschwarze 	mparse_result(mp, &man, NULL);
821f2d5c709Sschwarze 	if (man == NULL) {
822c6c22f12Sschwarze 		fprintf(stderr, "fatal mandoc error: %s/%s\n",
823c6c22f12Sschwarze 		    req->q.manpath, file);
824facea411Sschwarze 		pg_error_internal();
825c6c22f12Sschwarze 		mparse_free(mp);
82616536faaSschwarze 		mchars_free();
827c6c22f12Sschwarze 		return;
828c6c22f12Sschwarze 	}
829c6c22f12Sschwarze 
83016536faaSschwarze 	vp = html_alloc(&conf);
831c6c22f12Sschwarze 
832396853b5Sschwarze 	if (man->macroset == MACROSET_MDOC) {
833396853b5Sschwarze 		mdoc_validate(man);
834f2d5c709Sschwarze 		html_mdoc(vp, man);
835fec2846bSschwarze 	} else {
836fec2846bSschwarze 		man_validate(man);
837c6c22f12Sschwarze 		html_man(vp, man);
838fec2846bSschwarze 	}
839c6c22f12Sschwarze 
840c6c22f12Sschwarze 	html_free(vp);
841c6c22f12Sschwarze 	mparse_free(mp);
84216536faaSschwarze 	mchars_free();
8432ccd0917Sschwarze 	free(conf.man);
844c6c22f12Sschwarze }
845c6c22f12Sschwarze 
846c6c22f12Sschwarze static void
84746723f19Sschwarze resp_show(const struct req *req, const char *file)
84846723f19Sschwarze {
84981475784Sschwarze 
85081475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
8512f7bef27Sschwarze 		file += 2;
85246723f19Sschwarze 
85346723f19Sschwarze 	if ('c' == *file)
85446723f19Sschwarze 		catman(req, file);
85546723f19Sschwarze 	else
85646723f19Sschwarze 		format(req, file);
85746723f19Sschwarze }
85846723f19Sschwarze 
85946723f19Sschwarze static void
860b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
861c6c22f12Sschwarze {
862b53c14c3Sschwarze 	char		*manpath;
863b53c14c3Sschwarze 	const char	*file;
864c6c22f12Sschwarze 
865b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
866facea411Sschwarze 		pg_error_badrequest(
867c6c22f12Sschwarze 		    "You did not specify a page to show.");
868c6c22f12Sschwarze 		return;
869c6c22f12Sschwarze 	}
870b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
871b53c14c3Sschwarze 	file++;
872c6c22f12Sschwarze 
873b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
874631ce2c6Sschwarze 		pg_error_badrequest(
875631ce2c6Sschwarze 		    "You specified an invalid manpath.");
876b53c14c3Sschwarze 		free(manpath);
877631ce2c6Sschwarze 		return;
878631ce2c6Sschwarze 	}
879631ce2c6Sschwarze 
880c6c22f12Sschwarze 	/*
881c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
882c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
883c6c22f12Sschwarze 	 * relative to the manpath root.
884c6c22f12Sschwarze 	 */
885c6c22f12Sschwarze 
886b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
887631ce2c6Sschwarze 		fprintf(stderr, "chdir %s: %s\n",
888b53c14c3Sschwarze 		    manpath, strerror(errno));
889631ce2c6Sschwarze 		pg_error_internal();
890b53c14c3Sschwarze 		free(manpath);
891c6c22f12Sschwarze 		return;
892c6c22f12Sschwarze 	}
893c6c22f12Sschwarze 
894b53c14c3Sschwarze 	if (strcmp(manpath, "mandoc")) {
895b53c14c3Sschwarze 		free(req->q.manpath);
896b53c14c3Sschwarze 		req->q.manpath = manpath;
897b53c14c3Sschwarze 	} else
898b53c14c3Sschwarze 		free(manpath);
899b53c14c3Sschwarze 
900b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
90181475784Sschwarze 		pg_error_badrequest(
90281475784Sschwarze 		    "You specified an invalid manual file.");
90381475784Sschwarze 		return;
90481475784Sschwarze 	}
90581475784Sschwarze 
90646723f19Sschwarze 	resp_begin_html(200, NULL);
90746723f19Sschwarze 	resp_searchform(req);
908b53c14c3Sschwarze 	resp_show(req, file);
90946723f19Sschwarze 	resp_end_html();
910c6c22f12Sschwarze }
911c6c22f12Sschwarze 
912c6c22f12Sschwarze static void
91357482ef4Sschwarze pg_search(const struct req *req)
914c6c22f12Sschwarze {
915c6c22f12Sschwarze 	struct mansearch	  search;
916c6c22f12Sschwarze 	struct manpaths		  paths;
917c6c22f12Sschwarze 	struct manpage		 *res;
918fbeeb774Sschwarze 	char			**argv;
919fbeeb774Sschwarze 	char			 *query, *rp, *wp;
920c6c22f12Sschwarze 	size_t			  ressz;
921fbeeb774Sschwarze 	int			  argc;
922c6c22f12Sschwarze 
923c6c22f12Sschwarze 	/*
924c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
925c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
926c6c22f12Sschwarze 	 * relative to the manpath root.
927c6c22f12Sschwarze 	 */
928c6c22f12Sschwarze 
929c6c22f12Sschwarze 	if (-1 == (chdir(req->q.manpath))) {
930631ce2c6Sschwarze 		fprintf(stderr, "chdir %s: %s\n",
931631ce2c6Sschwarze 		    req->q.manpath, strerror(errno));
932631ce2c6Sschwarze 		pg_error_internal();
933c6c22f12Sschwarze 		return;
934c6c22f12Sschwarze 	}
935c6c22f12Sschwarze 
936c6c22f12Sschwarze 	search.arch = req->q.arch;
937c6c22f12Sschwarze 	search.sec = req->q.sec;
9380f10154cSschwarze 	search.outkey = "Nd";
9390f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
940fea71919Sschwarze 	search.firstmatch = 1;
941c6c22f12Sschwarze 
942c6c22f12Sschwarze 	paths.sz = 1;
943c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
944c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
945c6c22f12Sschwarze 
946c6c22f12Sschwarze 	/*
947fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
948c6c22f12Sschwarze 	 */
949c6c22f12Sschwarze 
950fbeeb774Sschwarze 	argc = 0;
951fbeeb774Sschwarze 	argv = NULL;
952fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
953fbeeb774Sschwarze 	for (;;) {
954fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
955fbeeb774Sschwarze 			rp++;
956fbeeb774Sschwarze 		if (*rp == '\0')
957fbeeb774Sschwarze 			break;
958fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
959fbeeb774Sschwarze 		argv[argc++] = wp = rp;
960fbeeb774Sschwarze 		for (;;) {
961fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
962fbeeb774Sschwarze 				*wp = '\0';
963fbeeb774Sschwarze 				rp++;
964fbeeb774Sschwarze 				break;
965fbeeb774Sschwarze 			}
966fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
967fbeeb774Sschwarze 				rp++;
968fbeeb774Sschwarze 			if (wp != rp)
969fbeeb774Sschwarze 				*wp = *rp;
970fbeeb774Sschwarze 			if (*rp == '\0')
971fbeeb774Sschwarze 				break;
972fbeeb774Sschwarze 			wp++;
973fbeeb774Sschwarze 			rp++;
974fbeeb774Sschwarze 		}
975c6c22f12Sschwarze 	}
976c6c22f12Sschwarze 
977fbeeb774Sschwarze 	if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
978facea411Sschwarze 		pg_noresult(req, "You entered an invalid query.");
979c6c22f12Sschwarze 	else if (0 == ressz)
980facea411Sschwarze 		pg_noresult(req, "No results found.");
981c6c22f12Sschwarze 	else
982facea411Sschwarze 		pg_searchres(req, res, ressz);
983c6c22f12Sschwarze 
984fbeeb774Sschwarze 	free(query);
985fbeeb774Sschwarze 	mansearch_free(res, ressz);
986c6c22f12Sschwarze 	free(paths.paths[0]);
987c6c22f12Sschwarze 	free(paths.paths);
988c6c22f12Sschwarze }
989c6c22f12Sschwarze 
990c6c22f12Sschwarze int
991c6c22f12Sschwarze main(void)
992c6c22f12Sschwarze {
993c6c22f12Sschwarze 	struct req	 req;
994136c26b8Sschwarze 	struct itimerval itimer;
99557482ef4Sschwarze 	const char	*path;
99631e689c3Sschwarze 	const char	*querystring;
99757482ef4Sschwarze 	int		 i;
998c6c22f12Sschwarze 
999136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1000136c26b8Sschwarze 
10012935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1002136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
10032935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1004136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1005136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1006136c26b8Sschwarze 		fprintf(stderr, "setitimer: %s\n", strerror(errno));
1007136c26b8Sschwarze 		pg_error_internal();
1008526e306bSschwarze 		return EXIT_FAILURE;
1009136c26b8Sschwarze 	}
1010136c26b8Sschwarze 
1011c6c22f12Sschwarze 	/*
10126fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1013c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1014c6c22f12Sschwarze 	 * relative to the same position.
1015c6c22f12Sschwarze 	 */
1016c6c22f12Sschwarze 
10176fdade3eSschwarze 	if (-1 == chdir(MAN_DIR)) {
1018c6c22f12Sschwarze 		fprintf(stderr, "MAN_DIR: %s: %s\n",
10196fdade3eSschwarze 		    MAN_DIR, strerror(errno));
1020facea411Sschwarze 		pg_error_internal();
1021526e306bSschwarze 		return EXIT_FAILURE;
1022c6c22f12Sschwarze 	}
1023c6c22f12Sschwarze 
1024c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1025abf19dc9Sschwarze 	req.q.equal = 1;
1026c6c22f12Sschwarze 	pathgen(&req);
1027c6c22f12Sschwarze 
102802b1b494Sschwarze 	/* Parse the path info and the query string. */
1029c6c22f12Sschwarze 
103002b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
103102b1b494Sschwarze 		path = "";
103202b1b494Sschwarze 	else if (*path == '/')
103302b1b494Sschwarze 		path++;
103402b1b494Sschwarze 
103502b1b494Sschwarze 	if (*path != '\0' && access(path, F_OK) == -1) {
103602b1b494Sschwarze 		path_parse(&req, path);
103702b1b494Sschwarze 		path = "";
103802b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1039c6c22f12Sschwarze 		http_parse(&req, querystring);
1040c6c22f12Sschwarze 
104102b1b494Sschwarze 	/* Validate parsed data and add defaults. */
104202b1b494Sschwarze 
104350eaed2bSschwarze 	if (req.q.manpath == NULL)
104450eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
104550eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1046631ce2c6Sschwarze 		pg_error_badrequest(
1047631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1048526e306bSschwarze 		return EXIT_FAILURE;
1049631ce2c6Sschwarze 	}
1050631ce2c6Sschwarze 
1051cf3a545cSschwarze 	if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1052cf3a545cSschwarze 		pg_error_badrequest(
1053cf3a545cSschwarze 		    "You specified an invalid architecture.");
1054526e306bSschwarze 		return EXIT_FAILURE;
1055cf3a545cSschwarze 	}
1056cf3a545cSschwarze 
105757482ef4Sschwarze 	/* Dispatch to the three different pages. */
1058c6c22f12Sschwarze 
105957482ef4Sschwarze 	if ('\0' != *path)
106057482ef4Sschwarze 		pg_show(&req, path);
1061e89321abSschwarze 	else if (NULL != req.q.query)
106257482ef4Sschwarze 		pg_search(&req);
106357482ef4Sschwarze 	else
1064facea411Sschwarze 		pg_index(&req);
1065c6c22f12Sschwarze 
106631e689c3Sschwarze 	free(req.q.manpath);
106731e689c3Sschwarze 	free(req.q.arch);
106831e689c3Sschwarze 	free(req.q.sec);
1069e89321abSschwarze 	free(req.q.query);
1070c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1071c6c22f12Sschwarze 		free(req.p[i]);
1072c6c22f12Sschwarze 	free(req.p);
1073526e306bSschwarze 	return EXIT_SUCCESS;
1074c6c22f12Sschwarze }
1075c6c22f12Sschwarze 
1076c6c22f12Sschwarze /*
107702b1b494Sschwarze  * If PATH_INFO is not a file name, translate it to a query.
107802b1b494Sschwarze  */
107902b1b494Sschwarze static void
108002b1b494Sschwarze path_parse(struct req *req, const char *path)
108102b1b494Sschwarze {
108202b1b494Sschwarze 	int	 dir_done;
108302b1b494Sschwarze 
108452b413c1Sschwarze 	req->isquery = 0;
108502b1b494Sschwarze 	req->q.equal = 1;
108602b1b494Sschwarze 	req->q.manpath = mandoc_strdup(path);
108702b1b494Sschwarze 
108802b1b494Sschwarze 	/* Mandatory manual page name. */
108902b1b494Sschwarze 	if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) {
109002b1b494Sschwarze 		req->q.query = req->q.manpath;
109102b1b494Sschwarze 		req->q.manpath = NULL;
109202b1b494Sschwarze 	} else
109302b1b494Sschwarze 		*req->q.query++ = '\0';
109402b1b494Sschwarze 
109502b1b494Sschwarze 	/* Optional trailing section. */
109602b1b494Sschwarze 	if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) {
109702b1b494Sschwarze 		if(isdigit((unsigned char)req->q.sec[1])) {
109802b1b494Sschwarze 			*req->q.sec++ = '\0';
109902b1b494Sschwarze 			req->q.sec = mandoc_strdup(req->q.sec);
110002b1b494Sschwarze 		} else
110102b1b494Sschwarze 			req->q.sec = NULL;
110202b1b494Sschwarze 	}
110302b1b494Sschwarze 
110402b1b494Sschwarze 	/* Handle the case of name[.section] only. */
110502b1b494Sschwarze 	if (req->q.manpath == NULL) {
110602b1b494Sschwarze 		req->q.arch = NULL;
110702b1b494Sschwarze 		return;
110802b1b494Sschwarze 	}
110902b1b494Sschwarze 	req->q.query = mandoc_strdup(req->q.query);
111002b1b494Sschwarze 
111102b1b494Sschwarze 	/* Optional architecture. */
111202b1b494Sschwarze 	dir_done = 0;
111302b1b494Sschwarze 	for (;;) {
111402b1b494Sschwarze 		if ((req->q.arch = strrchr(req->q.manpath, '/')) == NULL)
111502b1b494Sschwarze 			break;
111602b1b494Sschwarze 		*req->q.arch++ = '\0';
111702b1b494Sschwarze 		if (dir_done || strncmp(req->q.arch, "man", 3)) {
111802b1b494Sschwarze 			req->q.arch = mandoc_strdup(req->q.arch);
111902b1b494Sschwarze 			break;
112002b1b494Sschwarze 		}
112102b1b494Sschwarze 
112202b1b494Sschwarze 		/* Optional directory name. */
112302b1b494Sschwarze 		req->q.arch += 3;
112402b1b494Sschwarze 		if (*req->q.arch != '\0') {
112502b1b494Sschwarze 			free(req->q.sec);
112602b1b494Sschwarze 			req->q.sec = mandoc_strdup(req->q.arch);
112702b1b494Sschwarze 		}
112802b1b494Sschwarze 		dir_done = 1;
112902b1b494Sschwarze 	}
113002b1b494Sschwarze }
113102b1b494Sschwarze 
113202b1b494Sschwarze /*
1133c6c22f12Sschwarze  * Scan for indexable paths.
1134c6c22f12Sschwarze  */
1135c6c22f12Sschwarze static void
1136c6c22f12Sschwarze pathgen(struct req *req)
1137c6c22f12Sschwarze {
1138c6c22f12Sschwarze 	FILE	*fp;
1139c6c22f12Sschwarze 	char	*dp;
1140c6c22f12Sschwarze 	size_t	 dpsz;
114131f93c25Sschwarze 	ssize_t	 len;
1142c6c22f12Sschwarze 
1143de651747Sschwarze 	if (NULL == (fp = fopen("manpath.conf", "r"))) {
1144de651747Sschwarze 		fprintf(stderr, "%s/manpath.conf: %s\n",
1145de651747Sschwarze 			MAN_DIR, strerror(errno));
1146de651747Sschwarze 		pg_error_internal();
1147de651747Sschwarze 		exit(EXIT_FAILURE);
1148de651747Sschwarze 	}
1149c6c22f12Sschwarze 
115031f93c25Sschwarze 	dp = NULL;
115131f93c25Sschwarze 	dpsz = 0;
115231f93c25Sschwarze 
115331f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
115431f93c25Sschwarze 		if (dp[len - 1] == '\n')
115531f93c25Sschwarze 			dp[--len] = '\0';
1156c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1157c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1158cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1159cf3a545cSschwarze 			fprintf(stderr, "%s/manpath.conf contains "
1160cf3a545cSschwarze 			    "unsafe path \"%s\"\n", MAN_DIR, dp);
1161cf3a545cSschwarze 			pg_error_internal();
1162cf3a545cSschwarze 			exit(EXIT_FAILURE);
1163cf3a545cSschwarze 		}
1164cf3a545cSschwarze 		if (NULL != strchr(dp, '/')) {
1165cf3a545cSschwarze 			fprintf(stderr, "%s/manpath.conf contains "
1166cf3a545cSschwarze 			    "path with slash \"%s\"\n", MAN_DIR, dp);
1167cf3a545cSschwarze 			pg_error_internal();
1168cf3a545cSschwarze 			exit(EXIT_FAILURE);
1169cf3a545cSschwarze 		}
1170cf3a545cSschwarze 		req->p[req->psz++] = dp;
117131f93c25Sschwarze 		dp = NULL;
117231f93c25Sschwarze 		dpsz = 0;
1173c6c22f12Sschwarze 	}
117431f93c25Sschwarze 	free(dp);
1175de651747Sschwarze 
1176de651747Sschwarze 	if ( req->p == NULL ) {
1177de651747Sschwarze 		fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1178de651747Sschwarze 		pg_error_internal();
1179de651747Sschwarze 		exit(EXIT_FAILURE);
1180de651747Sschwarze 	}
1181c6c22f12Sschwarze }
1182