xref: /openbsd/usr.bin/mandoc/cgi.c (revision f5938fa6)
1*f5938fa6Sschwarze /*	$OpenBSD: cgi.c,v 1.99 2018/10/19 21:10:00 schwarze Exp $ */
2c6c22f12Sschwarze /*
3c6c22f12Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
432f0ba5fSschwarze  * Copyright (c) 2014, 2015, 2016, 2017, 2018 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>
22c976f0e2Sschwarze #include <err.h>
23c6c22f12Sschwarze #include <errno.h>
24c6c22f12Sschwarze #include <fcntl.h>
25c6c22f12Sschwarze #include <limits.h>
26c2235d37Sschwarze #include <stdint.h>
27c6c22f12Sschwarze #include <stdio.h>
28c6c22f12Sschwarze #include <stdlib.h>
29c6c22f12Sschwarze #include <string.h>
30c6c22f12Sschwarze #include <unistd.h>
31c6c22f12Sschwarze 
32c6c22f12Sschwarze #include "mandoc_aux.h"
33f2d5c709Sschwarze #include "mandoc.h"
34f2d5c709Sschwarze #include "roff.h"
35396853b5Sschwarze #include "mdoc.h"
36fec2846bSschwarze #include "man.h"
37c6c22f12Sschwarze #include "main.h"
384de77decSschwarze #include "manconf.h"
39c6c22f12Sschwarze #include "mansearch.h"
406fdade3eSschwarze #include "cgi.h"
41c6c22f12Sschwarze 
42c6c22f12Sschwarze /*
43c6c22f12Sschwarze  * A query as passed to the search function.
44c6c22f12Sschwarze  */
45c6c22f12Sschwarze struct	query {
4631e689c3Sschwarze 	char		*manpath; /* desired manual directory */
4731e689c3Sschwarze 	char		*arch; /* architecture */
4831e689c3Sschwarze 	char		*sec; /* manual section */
49e89321abSschwarze 	char		*query; /* unparsed query expression */
504477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
51c6c22f12Sschwarze };
52c6c22f12Sschwarze 
53c6c22f12Sschwarze struct	req {
54c6c22f12Sschwarze 	struct query	  q;
55c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
56c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
5752b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
58c6c22f12Sschwarze };
59c6c22f12Sschwarze 
6084f05c93Sschwarze enum	focus {
6184f05c93Sschwarze 	FOCUS_NONE = 0,
6284f05c93Sschwarze 	FOCUS_QUERY
6384f05c93Sschwarze };
6484f05c93Sschwarze 
65c6c22f12Sschwarze static	void		 html_print(const char *);
66c6c22f12Sschwarze static	void		 html_putchar(char);
67c6c22f12Sschwarze static	int		 http_decode(char *);
68f7a12365Sschwarze static	void		 http_encode(const char *p);
69941df026Sschwarze static	void		 parse_manpath_conf(struct req *);
70941df026Sschwarze static	void		 parse_path_info(struct req *req, const char *path);
71941df026Sschwarze static	void		 parse_query_string(struct req *, const char *);
72facea411Sschwarze static	void		 pg_error_badrequest(const char *);
73facea411Sschwarze static	void		 pg_error_internal(void);
74facea411Sschwarze static	void		 pg_index(const struct req *);
75facea411Sschwarze static	void		 pg_noresult(const struct req *, const char *);
76e1beff2aSschwarze static	void		 pg_redirect(const struct req *, const char *);
7757482ef4Sschwarze static	void		 pg_search(const struct req *);
78facea411Sschwarze static	void		 pg_searchres(const struct req *,
79facea411Sschwarze 				struct manpage *, size_t);
8081060b1aSschwarze static	void		 pg_show(struct req *, const char *);
81fdef72b0Sschwarze static	void		 resp_begin_html(int, const char *, const char *);
82c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
83941df026Sschwarze static	void		 resp_catman(const struct req *, const char *);
84711661c7Sschwarze static	void		 resp_copy(const char *);
85c6c22f12Sschwarze static	void		 resp_end_html(void);
86941df026Sschwarze static	void		 resp_format(const struct req *, const char *);
8784f05c93Sschwarze static	void		 resp_searchform(const struct req *, enum focus);
8846723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
89e89321abSschwarze static	void		 set_query_attr(char **, char **);
90f7a12365Sschwarze static	int		 validate_arch(const char *);
91e89321abSschwarze static	int		 validate_filename(const char *);
92e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
93e89321abSschwarze static	int		 validate_urifrag(const char *);
94c6c22f12Sschwarze 
953b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
96c6c22f12Sschwarze 
9746723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
9828e449d6Sschwarze static	const char *const sec_numbers[] = {
9928e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10028e449d6Sschwarze };
10128e449d6Sschwarze static	const char *const sec_names[] = {
10228e449d6Sschwarze     "All Sections",
10328e449d6Sschwarze     "1 - General Commands",
10428e449d6Sschwarze     "2 - System Calls",
1054e6618fdSschwarze     "3 - Library Functions",
1064e6618fdSschwarze     "3p - Perl Library",
1074e6618fdSschwarze     "4 - Device Drivers",
10828e449d6Sschwarze     "5 - File Formats",
10928e449d6Sschwarze     "6 - Games",
1104e6618fdSschwarze     "7 - Miscellaneous Information",
1114e6618fdSschwarze     "8 - System Manager\'s Manual",
1124e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
11328e449d6Sschwarze };
11428e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
11528e449d6Sschwarze 
11628e449d6Sschwarze static	const char *const arch_names[] = {
11793d3e4e1Sderaadt     "amd64",       "alpha",       "armv7",	"arm64",
118766ef059Sschwarze     "hppa",        "i386",        "landisk",
1193bcd4815Sschwarze     "loongson",    "luna88k",     "macppc",      "mips64",
1206250a715Sschwarze     "octeon",      "sgi",         "socppc",      "sparc64",
121766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
122766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
123766ef059Sschwarze     "hppa64",      "hp300",
1243bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1253bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
1260c245db5Sschwarze     "pmax",        "powerpc",     "solbourne",   "sparc",
1276250a715Sschwarze     "sun3",        "vax",         "wgrisc",      "x68k",
1286250a715Sschwarze     "zaurus"
12928e449d6Sschwarze };
13028e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
13128e449d6Sschwarze 
132c6c22f12Sschwarze /*
133c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
134c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
135c6c22f12Sschwarze  */
136c6c22f12Sschwarze static void
137c6c22f12Sschwarze html_putchar(char c)
138c6c22f12Sschwarze {
139c6c22f12Sschwarze 
140c6c22f12Sschwarze 	switch (c) {
141e74fa2aeSschwarze 	case '"':
142f765a656Sbentley 		printf("&quot;");
143c6c22f12Sschwarze 		break;
144e74fa2aeSschwarze 	case '&':
145c6c22f12Sschwarze 		printf("&amp;");
146c6c22f12Sschwarze 		break;
147e74fa2aeSschwarze 	case '>':
148c6c22f12Sschwarze 		printf("&gt;");
149c6c22f12Sschwarze 		break;
150e74fa2aeSschwarze 	case '<':
151c6c22f12Sschwarze 		printf("&lt;");
152c6c22f12Sschwarze 		break;
153c6c22f12Sschwarze 	default:
154c6c22f12Sschwarze 		putchar((unsigned char)c);
155c6c22f12Sschwarze 		break;
156c6c22f12Sschwarze 	}
157c6c22f12Sschwarze }
158c6c22f12Sschwarze 
159c6c22f12Sschwarze /*
160c6c22f12Sschwarze  * Call through to html_putchar().
161c6c22f12Sschwarze  * Accepts NULL strings.
162c6c22f12Sschwarze  */
163c6c22f12Sschwarze static void
164c6c22f12Sschwarze html_print(const char *p)
165c6c22f12Sschwarze {
166c6c22f12Sschwarze 
167c6c22f12Sschwarze 	if (NULL == p)
168c6c22f12Sschwarze 		return;
169c6c22f12Sschwarze 	while ('\0' != *p)
170c6c22f12Sschwarze 		html_putchar(*p++);
171c6c22f12Sschwarze }
172c6c22f12Sschwarze 
173c6c22f12Sschwarze /*
17431e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
17531e689c3Sschwarze  * to the query structure.
176c6c22f12Sschwarze  */
177c6c22f12Sschwarze static void
17831e689c3Sschwarze set_query_attr(char **attr, char **val)
17931e689c3Sschwarze {
18031e689c3Sschwarze 
18131e689c3Sschwarze 	free(*attr);
18231e689c3Sschwarze 	if (**val == '\0') {
18331e689c3Sschwarze 		*attr = NULL;
18431e689c3Sschwarze 		free(*val);
18531e689c3Sschwarze 	} else
18631e689c3Sschwarze 		*attr = *val;
18731e689c3Sschwarze 	*val = NULL;
18831e689c3Sschwarze }
18931e689c3Sschwarze 
19031e689c3Sschwarze /*
19131e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
19231e689c3Sschwarze  * and store the values into the query structure.
19331e689c3Sschwarze  */
19431e689c3Sschwarze static void
195941df026Sschwarze parse_query_string(struct req *req, const char *qs)
196c6c22f12Sschwarze {
197c6c22f12Sschwarze 	char		*key, *val;
19831e689c3Sschwarze 	size_t		 keysz, valsz;
199c6c22f12Sschwarze 
20052b413c1Sschwarze 	req->isquery	= 1;
20131e689c3Sschwarze 	req->q.manpath	= NULL;
20231e689c3Sschwarze 	req->q.arch	= NULL;
20331e689c3Sschwarze 	req->q.sec	= NULL;
204e89321abSschwarze 	req->q.query	= NULL;
2054477fbfaSschwarze 	req->q.equal	= 1;
206c6c22f12Sschwarze 
20731e689c3Sschwarze 	key = val = NULL;
20831e689c3Sschwarze 	while (*qs != '\0') {
209c6c22f12Sschwarze 
21031e689c3Sschwarze 		/* Parse one key. */
211c6c22f12Sschwarze 
21231e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
21331e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
21431e689c3Sschwarze 		qs += keysz;
21531e689c3Sschwarze 		if (*qs != '=')
21631e689c3Sschwarze 			goto next;
217c6c22f12Sschwarze 
21831e689c3Sschwarze 		/* Parse one value. */
219c6c22f12Sschwarze 
22031e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
22131e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
22231e689c3Sschwarze 		qs += valsz;
223c6c22f12Sschwarze 
22431e689c3Sschwarze 		/* Decode and catch encoding errors. */
22531e689c3Sschwarze 
22631e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
22731e689c3Sschwarze 			goto next;
22831e689c3Sschwarze 
22931e689c3Sschwarze 		/* Handle key-value pairs. */
23031e689c3Sschwarze 
23131e689c3Sschwarze 		if ( ! strcmp(key, "query"))
232e89321abSschwarze 			set_query_attr(&req->q.query, &val);
23331e689c3Sschwarze 
23431e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
23531e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
23631e689c3Sschwarze 
23731e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
238aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
23931e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
240aabcc9a1Sschwarze 				val[7] = '-';
241aabcc9a1Sschwarze 				if ('C' == val[8])
242aabcc9a1Sschwarze 					val[8] = 'c';
243aabcc9a1Sschwarze 			}
244aabcc9a1Sschwarze #endif
24531e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
24631e689c3Sschwarze 		}
24731e689c3Sschwarze 
24831e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
249aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
25031e689c3Sschwarze 		    && strcmp(key, "sektion")
251aabcc9a1Sschwarze #endif
25231e689c3Sschwarze 		    )) {
25331e689c3Sschwarze 			if ( ! strcmp(val, "0"))
25431e689c3Sschwarze 				*val = '\0';
25531e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
256c6c22f12Sschwarze 		}
25731e689c3Sschwarze 
25831e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
25931e689c3Sschwarze 			if ( ! strcmp(val, "default"))
26031e689c3Sschwarze 				*val = '\0';
26131e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2624477fbfaSschwarze 		}
26331e689c3Sschwarze 
26431e689c3Sschwarze 		/*
26531e689c3Sschwarze 		 * The key must be freed in any case.
26631e689c3Sschwarze 		 * The val may have been handed over to the query
26731e689c3Sschwarze 		 * structure, in which case it is now NULL.
26831e689c3Sschwarze 		 */
26931e689c3Sschwarze next:
27031e689c3Sschwarze 		free(key);
27131e689c3Sschwarze 		key = NULL;
27231e689c3Sschwarze 		free(val);
27331e689c3Sschwarze 		val = NULL;
27431e689c3Sschwarze 
27531e689c3Sschwarze 		if (*qs != '\0')
27631e689c3Sschwarze 			qs++;
27731e689c3Sschwarze 	}
278c6c22f12Sschwarze }
279c6c22f12Sschwarze 
280c6c22f12Sschwarze /*
281c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
282c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
283c6c22f12Sschwarze  * over the allocated string.
284c6c22f12Sschwarze  */
285c6c22f12Sschwarze static int
286c6c22f12Sschwarze http_decode(char *p)
287c6c22f12Sschwarze {
288c6c22f12Sschwarze 	char             hex[3];
2891f69f32bStedu 	char		*q;
290c6c22f12Sschwarze 	int              c;
291c6c22f12Sschwarze 
292c6c22f12Sschwarze 	hex[2] = '\0';
293c6c22f12Sschwarze 
2941f69f32bStedu 	q = p;
2951f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
296c6c22f12Sschwarze 		if ('%' == *p) {
297c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
298526e306bSschwarze 				return 0;
299c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
300526e306bSschwarze 				return 0;
301c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
302526e306bSschwarze 				return 0;
303c6c22f12Sschwarze 			if ('\0' == c)
304526e306bSschwarze 				return 0;
305c6c22f12Sschwarze 
3061f69f32bStedu 			*q = (char)c;
3071f69f32bStedu 			p += 2;
308c6c22f12Sschwarze 		} else
3091f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
310c6c22f12Sschwarze 	}
311c6c22f12Sschwarze 
3121f69f32bStedu 	*q = '\0';
313526e306bSschwarze 	return 1;
314c6c22f12Sschwarze }
315c6c22f12Sschwarze 
316c6c22f12Sschwarze static void
317f7a12365Sschwarze http_encode(const char *p)
318f7a12365Sschwarze {
319f7a12365Sschwarze 	for (; *p != '\0'; p++) {
320f7a12365Sschwarze 		if (isalnum((unsigned char)*p) == 0 &&
321f7a12365Sschwarze 		    strchr("-._~", *p) == NULL)
322f7a12365Sschwarze 			printf("%%%02.2X", (unsigned char)*p);
323f7a12365Sschwarze 		else
324f7a12365Sschwarze 			putchar(*p);
325f7a12365Sschwarze 	}
326f7a12365Sschwarze }
327f7a12365Sschwarze 
328f7a12365Sschwarze static void
329c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
330c6c22f12Sschwarze {
331c6c22f12Sschwarze 
332c6c22f12Sschwarze 	if (200 != code)
333fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
334c6c22f12Sschwarze 
335fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
336fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
337fa9c540aStedu 	     "Pragma: no-cache\r\n"
338fa9c540aStedu 	     "\r\n");
339c6c22f12Sschwarze 
340c6c22f12Sschwarze 	fflush(stdout);
341c6c22f12Sschwarze }
342c6c22f12Sschwarze 
343c6c22f12Sschwarze static void
344711661c7Sschwarze resp_copy(const char *filename)
345711661c7Sschwarze {
346711661c7Sschwarze 	char	 buf[4096];
347711661c7Sschwarze 	ssize_t	 sz;
348711661c7Sschwarze 	int	 fd;
349711661c7Sschwarze 
350711661c7Sschwarze 	if ((fd = open(filename, O_RDONLY)) != -1) {
351711661c7Sschwarze 		fflush(stdout);
352711661c7Sschwarze 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
353711661c7Sschwarze 			write(STDOUT_FILENO, buf, sz);
354fd3cdd86Sjsg 		close(fd);
355711661c7Sschwarze 	}
356711661c7Sschwarze }
357711661c7Sschwarze 
358711661c7Sschwarze static void
359fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file)
360c6c22f12Sschwarze {
361fdef72b0Sschwarze 	char	*cp;
362c6c22f12Sschwarze 
363c6c22f12Sschwarze 	resp_begin_http(code, msg);
364c6c22f12Sschwarze 
365d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
366735516bdSschwarze 	       "<html>\n"
367735516bdSschwarze 	       "<head>\n"
368735516bdSschwarze 	       "  <meta charset=\"UTF-8\"/>\n"
369661a42d8Sschwarze 	       "  <meta name=\"viewport\""
370661a42d8Sschwarze 		      " content=\"width=device-width, initial-scale=1.0\">\n"
371735516bdSschwarze 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
372735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
373fdef72b0Sschwarze 	       "  <title>",
374fdef72b0Sschwarze 	       CSS_DIR);
375fdef72b0Sschwarze 	if (file != NULL) {
376fdef72b0Sschwarze 		if ((cp = strrchr(file, '/')) != NULL)
377fdef72b0Sschwarze 			file = cp + 1;
378fdef72b0Sschwarze 		if ((cp = strrchr(file, '.')) != NULL) {
379fdef72b0Sschwarze 			printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
380fdef72b0Sschwarze 		} else
381fdef72b0Sschwarze 			printf("%s - ", file);
382fdef72b0Sschwarze 	}
383fdef72b0Sschwarze 	printf("%s</title>\n"
384735516bdSschwarze 	       "</head>\n"
385ce781f36Sschwarze 	       "<body>\n",
386fdef72b0Sschwarze 	       CUSTOMIZE_TITLE);
387711661c7Sschwarze 
388711661c7Sschwarze 	resp_copy(MAN_DIR "/header.html");
389c6c22f12Sschwarze }
390c6c22f12Sschwarze 
391c6c22f12Sschwarze static void
392c6c22f12Sschwarze resp_end_html(void)
393c6c22f12Sschwarze {
394c6c22f12Sschwarze 
395711661c7Sschwarze 	resp_copy(MAN_DIR "/footer.html");
396711661c7Sschwarze 
397735516bdSschwarze 	puts("</body>\n"
398735516bdSschwarze 	     "</html>");
399c6c22f12Sschwarze }
400c6c22f12Sschwarze 
401c6c22f12Sschwarze static void
40284f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
403c6c22f12Sschwarze {
404c6c22f12Sschwarze 	int		 i;
405c6c22f12Sschwarze 
406ce781f36Sschwarze 	printf("<form action=\"/%s\" method=\"get\">\n"
407735516bdSschwarze 	       "  <fieldset>\n"
408735516bdSschwarze 	       "    <legend>Manual Page Search Parameters</legend>\n",
409c6c22f12Sschwarze 	       scriptname);
41028e449d6Sschwarze 
41128e449d6Sschwarze 	/* Write query input box. */
41228e449d6Sschwarze 
4136b824e3bSschwarze 	printf("    <input type=\"search\" name=\"query\" value=\"");
41484f05c93Sschwarze 	if (req->q.query != NULL)
415e89321abSschwarze 		html_print(req->q.query);
41684f05c93Sschwarze 	printf( "\" size=\"40\"");
41784f05c93Sschwarze 	if (focus == FOCUS_QUERY)
41884f05c93Sschwarze 		printf(" autofocus");
41984f05c93Sschwarze 	puts(">");
42028e449d6Sschwarze 
421784a63d6Sschwarze 	/* Write submission buttons. */
42228e449d6Sschwarze 
423784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
424784a63d6Sschwarze 		"man</button>\n"
425784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
426542ee4bfSschwarze 		"apropos</button>\n"
427542ee4bfSschwarze 		"    <br/>\n");
42828e449d6Sschwarze 
42928e449d6Sschwarze 	/* Write section selector. */
43028e449d6Sschwarze 
431784a63d6Sschwarze 	puts("    <select name=\"sec\">");
43228e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
433735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
43428e449d6Sschwarze 		if (NULL != req->q.sec &&
43528e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
436735516bdSschwarze 			printf(" selected=\"selected\"");
437735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
43828e449d6Sschwarze 	}
439735516bdSschwarze 	puts("    </select>");
44028e449d6Sschwarze 
44128e449d6Sschwarze 	/* Write architecture selector. */
44228e449d6Sschwarze 
443735516bdSschwarze 	printf(	"    <select name=\"arch\">\n"
444735516bdSschwarze 		"      <option value=\"default\"");
445be14a32aSschwarze 	if (NULL == req->q.arch)
446735516bdSschwarze 		printf(" selected=\"selected\"");
447735516bdSschwarze 	puts(">All Architectures</option>");
44828e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
4496b824e3bSschwarze 		printf("      <option");
45028e449d6Sschwarze 		if (NULL != req->q.arch &&
45128e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
452735516bdSschwarze 			printf(" selected=\"selected\"");
453735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
45428e449d6Sschwarze 	}
455735516bdSschwarze 	puts("    </select>");
45628e449d6Sschwarze 
45728e449d6Sschwarze 	/* Write manpath selector. */
45828e449d6Sschwarze 
459c6c22f12Sschwarze 	if (req->psz > 1) {
460735516bdSschwarze 		puts("    <select name=\"manpath\">");
461c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
462735516bdSschwarze 			printf("      <option");
46350eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
464735516bdSschwarze 				printf(" selected=\"selected\"");
4656b824e3bSschwarze 			printf(">");
466c6c22f12Sschwarze 			html_print(req->p[i]);
467735516bdSschwarze 			puts("</option>");
468c6c22f12Sschwarze 		}
469735516bdSschwarze 		puts("    </select>");
470c6c22f12Sschwarze 	}
47128e449d6Sschwarze 
472784a63d6Sschwarze 	puts("  </fieldset>\n"
473ce781f36Sschwarze 	     "</form>");
474c6c22f12Sschwarze }
475c6c22f12Sschwarze 
47681475784Sschwarze static int
477cf3a545cSschwarze validate_urifrag(const char *frag)
478cf3a545cSschwarze {
479cf3a545cSschwarze 
480cf3a545cSschwarze 	while ('\0' != *frag) {
481cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
482cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
483cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
484526e306bSschwarze 			return 0;
485cf3a545cSschwarze 		frag++;
486cf3a545cSschwarze 	}
487526e306bSschwarze 	return 1;
488cf3a545cSschwarze }
489cf3a545cSschwarze 
490cf3a545cSschwarze static int
491631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
492631ce2c6Sschwarze {
493631ce2c6Sschwarze 	size_t	 i;
494631ce2c6Sschwarze 
495631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
496631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
497526e306bSschwarze 			return 1;
498631ce2c6Sschwarze 
499526e306bSschwarze 	return 0;
500631ce2c6Sschwarze }
501631ce2c6Sschwarze 
502631ce2c6Sschwarze static int
503f7a12365Sschwarze validate_arch(const char *arch)
504f7a12365Sschwarze {
505f7a12365Sschwarze 	int	 i;
506f7a12365Sschwarze 
507f7a12365Sschwarze 	for (i = 0; i < arch_MAX; i++)
508f7a12365Sschwarze 		if (strcmp(arch, arch_names[i]) == 0)
509f7a12365Sschwarze 			return 1;
510f7a12365Sschwarze 
511f7a12365Sschwarze 	return 0;
512f7a12365Sschwarze }
513f7a12365Sschwarze 
514f7a12365Sschwarze static int
51581475784Sschwarze validate_filename(const char *file)
51681475784Sschwarze {
51781475784Sschwarze 
51881475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
51981475784Sschwarze 		file += 2;
52081475784Sschwarze 
521526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
522526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
52381475784Sschwarze }
52481475784Sschwarze 
525c6c22f12Sschwarze static void
526facea411Sschwarze pg_index(const struct req *req)
527c6c22f12Sschwarze {
528c6c22f12Sschwarze 
529fdef72b0Sschwarze 	resp_begin_html(200, NULL, NULL);
53084f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
531735516bdSschwarze 	printf("<p>\n"
532d56ca219Sschwarze 	       "This web interface is documented in the\n"
5330a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
534d56ca219Sschwarze 	       "manual, and the\n"
5350a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
5362a43838fSschwarze 	       "manual explains the query syntax.\n"
537735516bdSschwarze 	       "</p>\n",
5383b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5393b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
540c6c22f12Sschwarze 	resp_end_html();
541c6c22f12Sschwarze }
542c6c22f12Sschwarze 
543c6c22f12Sschwarze static void
544facea411Sschwarze pg_noresult(const struct req *req, const char *msg)
545c6c22f12Sschwarze {
546fdef72b0Sschwarze 	resp_begin_html(200, NULL, NULL);
54784f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
548735516bdSschwarze 	puts("<p>");
549c6c22f12Sschwarze 	puts(msg);
550735516bdSschwarze 	puts("</p>");
551c6c22f12Sschwarze 	resp_end_html();
552c6c22f12Sschwarze }
553c6c22f12Sschwarze 
554c6c22f12Sschwarze static void
555facea411Sschwarze pg_error_badrequest(const char *msg)
556c6c22f12Sschwarze {
557c6c22f12Sschwarze 
558fdef72b0Sschwarze 	resp_begin_html(400, "Bad Request", NULL);
559735516bdSschwarze 	puts("<h1>Bad Request</h1>\n"
560735516bdSschwarze 	     "<p>\n");
561c6c22f12Sschwarze 	puts(msg);
562c6c22f12Sschwarze 	printf("Try again from the\n"
563735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
564735516bdSschwarze 	       "</p>", scriptname);
565c6c22f12Sschwarze 	resp_end_html();
566c6c22f12Sschwarze }
567c6c22f12Sschwarze 
568c6c22f12Sschwarze static void
569facea411Sschwarze pg_error_internal(void)
570c6c22f12Sschwarze {
571fdef72b0Sschwarze 	resp_begin_html(500, "Internal Server Error", NULL);
572735516bdSschwarze 	puts("<p>Internal Server Error</p>");
573c6c22f12Sschwarze 	resp_end_html();
574c6c22f12Sschwarze }
575c6c22f12Sschwarze 
576c6c22f12Sschwarze static void
577e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
578e1beff2aSschwarze {
579e0d9a108Sschwarze 	printf("Status: 303 See Other\r\n"
580e0d9a108Sschwarze 	    "Location: /");
581e1beff2aSschwarze 	if (*scriptname != '\0')
582e1beff2aSschwarze 		printf("%s/", scriptname);
583e1beff2aSschwarze 	if (strcmp(req->q.manpath, req->p[0]))
584e1beff2aSschwarze 		printf("%s/", req->q.manpath);
585e1beff2aSschwarze 	if (req->q.arch != NULL)
586e1beff2aSschwarze 		printf("%s/", req->q.arch);
587f7a12365Sschwarze 	http_encode(name);
588f7a12365Sschwarze 	if (req->q.sec != NULL) {
589f7a12365Sschwarze 		putchar('.');
590f7a12365Sschwarze 		http_encode(req->q.sec);
591f7a12365Sschwarze 	}
592e1beff2aSschwarze 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
593e1beff2aSschwarze }
594e1beff2aSschwarze 
595e1beff2aSschwarze static void
596facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
597c6c22f12Sschwarze {
598be14a32aSschwarze 	char		*arch, *archend;
599db26164eSschwarze 	const char	*sec;
600db26164eSschwarze 	size_t		 i, iuse;
601be14a32aSschwarze 	int		 archprio, archpriouse;
60246723f19Sschwarze 	int		 prio, priouse;
603c6c22f12Sschwarze 
60481475784Sschwarze 	for (i = 0; i < sz; i++) {
60581475784Sschwarze 		if (validate_filename(r[i].file))
60681475784Sschwarze 			continue;
607c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
60881475784Sschwarze 		    r[i].file, req->q.manpath);
60981475784Sschwarze 		pg_error_internal();
61081475784Sschwarze 		return;
61181475784Sschwarze 	}
61281475784Sschwarze 
61352b413c1Sschwarze 	if (req->isquery && sz == 1) {
614c6c22f12Sschwarze 		/*
615c6c22f12Sschwarze 		 * If we have just one result, then jump there now
616c6c22f12Sschwarze 		 * without any delay.
617c6c22f12Sschwarze 		 */
618e0d9a108Sschwarze 		printf("Status: 303 See Other\r\n"
619e0d9a108Sschwarze 		    "Location: /");
620e0d9a108Sschwarze 		if (*scriptname != '\0')
621e0d9a108Sschwarze 			printf("%s/", scriptname);
622e0d9a108Sschwarze 		if (strcmp(req->q.manpath, req->p[0]))
623e0d9a108Sschwarze 			printf("%s/", req->q.manpath);
624e0d9a108Sschwarze 		printf("%s\r\n"
625e0d9a108Sschwarze 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
626e0d9a108Sschwarze 		    r[0].file);
627c6c22f12Sschwarze 		return;
628c6c22f12Sschwarze 	}
629c6c22f12Sschwarze 
63046723f19Sschwarze 	/*
63146723f19Sschwarze 	 * In man(1) mode, show one of the pages
63246723f19Sschwarze 	 * even if more than one is found.
63346723f19Sschwarze 	 */
63446723f19Sschwarze 
63546723f19Sschwarze 	iuse = 0;
636fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
637db26164eSschwarze 		priouse = 20;
638be14a32aSschwarze 		archpriouse = 3;
63946723f19Sschwarze 		for (i = 0; i < sz; i++) {
640db26164eSschwarze 			sec = r[i].file;
641db26164eSschwarze 			sec += strcspn(sec, "123456789");
642db26164eSschwarze 			if (sec[0] == '\0')
64346723f19Sschwarze 				continue;
644db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
645db26164eSschwarze 			if (sec[1] != '/')
646db26164eSschwarze 				prio += 10;
647db26164eSschwarze 			if (req->q.arch == NULL) {
648be14a32aSschwarze 				archprio =
649db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
650db26164eSschwarze 					== NULL) ? 3 :
651db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
652db26164eSschwarze 					== NULL) ? 0 :
653be14a32aSschwarze 				    strncmp(arch, "amd64/",
654be14a32aSschwarze 					archend - arch) ? 2 : 1;
655be14a32aSschwarze 				if (archprio < archpriouse) {
656be14a32aSschwarze 					archpriouse = archprio;
657be14a32aSschwarze 					priouse = prio;
658be14a32aSschwarze 					iuse = i;
659be14a32aSschwarze 					continue;
660be14a32aSschwarze 				}
661be14a32aSschwarze 				if (archprio > archpriouse)
662be14a32aSschwarze 					continue;
663be14a32aSschwarze 			}
66446723f19Sschwarze 			if (prio >= priouse)
66546723f19Sschwarze 				continue;
66646723f19Sschwarze 			priouse = prio;
66746723f19Sschwarze 			iuse = i;
66846723f19Sschwarze 		}
669fdef72b0Sschwarze 		resp_begin_html(200, NULL, r[iuse].file);
670fdef72b0Sschwarze 	} else
671fdef72b0Sschwarze 		resp_begin_html(200, NULL, NULL);
672fdef72b0Sschwarze 
673fdef72b0Sschwarze 	resp_searchform(req,
674fdef72b0Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
675fdef72b0Sschwarze 
676fdef72b0Sschwarze 	if (sz > 1) {
677fdef72b0Sschwarze 		puts("<table class=\"results\">");
678fdef72b0Sschwarze 		for (i = 0; i < sz; i++) {
679fdef72b0Sschwarze 			printf("  <tr>\n"
680fdef72b0Sschwarze 			       "    <td>"
6817b3dbe16Sschwarze 			       "<a class=\"Xr\" href=\"/");
6827b3dbe16Sschwarze 			if (*scriptname != '\0')
6837b3dbe16Sschwarze 				printf("%s/", scriptname);
6847b3dbe16Sschwarze 			if (strcmp(req->q.manpath, req->p[0]))
6857b3dbe16Sschwarze 				printf("%s/", req->q.manpath);
6867b3dbe16Sschwarze 			printf("%s\">", r[i].file);
687fdef72b0Sschwarze 			html_print(r[i].names);
688fdef72b0Sschwarze 			printf("</a></td>\n"
689fdef72b0Sschwarze 			       "    <td><span class=\"Nd\">");
690fdef72b0Sschwarze 			html_print(r[i].output);
691fdef72b0Sschwarze 			puts("</span></td>\n"
692fdef72b0Sschwarze 			     "  </tr>");
693fdef72b0Sschwarze 		}
694fdef72b0Sschwarze 		puts("</table>");
695fdef72b0Sschwarze 	}
696fdef72b0Sschwarze 
697fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
698fdef72b0Sschwarze 		puts("<hr>");
69946723f19Sschwarze 		resp_show(req, r[iuse].file);
70046723f19Sschwarze 	}
70146723f19Sschwarze 
702c6c22f12Sschwarze 	resp_end_html();
703c6c22f12Sschwarze }
704c6c22f12Sschwarze 
705c6c22f12Sschwarze static void
706941df026Sschwarze resp_catman(const struct req *req, const char *file)
707c6c22f12Sschwarze {
708c6c22f12Sschwarze 	FILE		*f;
709c6c22f12Sschwarze 	char		*p;
71031f93c25Sschwarze 	size_t		 sz;
71131f93c25Sschwarze 	ssize_t		 len;
71231f93c25Sschwarze 	int		 i;
713c6c22f12Sschwarze 	int		 italic, bold;
714c6c22f12Sschwarze 
71531f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
716735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
717c6c22f12Sschwarze 		return;
718c6c22f12Sschwarze 	}
719c6c22f12Sschwarze 
720735516bdSschwarze 	puts("<div class=\"catman\">\n"
721735516bdSschwarze 	     "<pre>");
722c6c22f12Sschwarze 
72331f93c25Sschwarze 	p = NULL;
72431f93c25Sschwarze 	sz = 0;
72531f93c25Sschwarze 
72631f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
727c6c22f12Sschwarze 		bold = italic = 0;
72831f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
729c6c22f12Sschwarze 			/*
730c6c22f12Sschwarze 			 * This means that the catpage is out of state.
731c6c22f12Sschwarze 			 * Ignore it and keep going (although the
732c6c22f12Sschwarze 			 * catpage is bogus).
733c6c22f12Sschwarze 			 */
734c6c22f12Sschwarze 
735c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
736c6c22f12Sschwarze 				continue;
737c6c22f12Sschwarze 
738c6c22f12Sschwarze 			/*
739c6c22f12Sschwarze 			 * Print a regular character.
740c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
741c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
742c6c22f12Sschwarze 			 * have something to enter when we backspace.
743c6c22f12Sschwarze 			 */
744c6c22f12Sschwarze 
745c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
746c6c22f12Sschwarze 				if (italic)
747735516bdSschwarze 					printf("</i>");
748c6c22f12Sschwarze 				if (bold)
749735516bdSschwarze 					printf("</b>");
750c6c22f12Sschwarze 				italic = bold = 0;
751c6c22f12Sschwarze 				html_putchar(p[i]);
752c6c22f12Sschwarze 				continue;
75331f93c25Sschwarze 			} else if (i + 2 >= len)
754c6c22f12Sschwarze 				continue;
755c6c22f12Sschwarze 
756c6c22f12Sschwarze 			/* Italic mode. */
757c6c22f12Sschwarze 
758c6c22f12Sschwarze 			if ('_' == p[i]) {
759c6c22f12Sschwarze 				if (bold)
760735516bdSschwarze 					printf("</b>");
761c6c22f12Sschwarze 				if ( ! italic)
762735516bdSschwarze 					printf("<i>");
763c6c22f12Sschwarze 				bold = 0;
764c6c22f12Sschwarze 				italic = 1;
765c6c22f12Sschwarze 				i += 2;
766c6c22f12Sschwarze 				html_putchar(p[i]);
767c6c22f12Sschwarze 				continue;
768c6c22f12Sschwarze 			}
769c6c22f12Sschwarze 
770c6c22f12Sschwarze 			/*
771c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
772c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
773c6c22f12Sschwarze 			 */
774c6c22f12Sschwarze 
775c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
776c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
777c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
778c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
779c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
780c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
781c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
782c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
783c6c22f12Sschwarze 				if (italic)
784735516bdSschwarze 					printf("</i>");
785c6c22f12Sschwarze 				if (bold)
786735516bdSschwarze 					printf("</b>");
787c6c22f12Sschwarze 				italic = bold = 0;
788c6c22f12Sschwarze 				putchar('*');
789c6c22f12Sschwarze 				i += 2;
790c6c22f12Sschwarze 				continue;
791c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
792c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
793c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
794c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
795c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
796c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
797c6c22f12Sschwarze 				if (italic)
798735516bdSschwarze 					printf("</i>");
799c6c22f12Sschwarze 				if (bold)
800735516bdSschwarze 					printf("</b>");
801c6c22f12Sschwarze 				italic = bold = 0;
802c6c22f12Sschwarze 				putchar('+');
803c6c22f12Sschwarze 				i += 2;
804c6c22f12Sschwarze 				continue;
805c6c22f12Sschwarze 			}
806c6c22f12Sschwarze 
807c6c22f12Sschwarze 			/* Bold mode. */
808c6c22f12Sschwarze 
809c6c22f12Sschwarze 			if (italic)
810735516bdSschwarze 				printf("</i>");
811c6c22f12Sschwarze 			if ( ! bold)
812735516bdSschwarze 				printf("<b>");
813c6c22f12Sschwarze 			bold = 1;
814c6c22f12Sschwarze 			italic = 0;
815c6c22f12Sschwarze 			i += 2;
816c6c22f12Sschwarze 			html_putchar(p[i]);
817c6c22f12Sschwarze 		}
818c6c22f12Sschwarze 
819c6c22f12Sschwarze 		/*
820c6c22f12Sschwarze 		 * Clean up the last character.
821c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
822c6c22f12Sschwarze 		 */
823c6c22f12Sschwarze 
824c6c22f12Sschwarze 		if (italic)
825735516bdSschwarze 			printf("</i>");
826c6c22f12Sschwarze 		if (bold)
827735516bdSschwarze 			printf("</b>");
828c6c22f12Sschwarze 
82931f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
830c6c22f12Sschwarze 			html_putchar(p[i]);
831c6c22f12Sschwarze 
832c6c22f12Sschwarze 		putchar('\n');
833c6c22f12Sschwarze 	}
83431f93c25Sschwarze 	free(p);
835c6c22f12Sschwarze 
836735516bdSschwarze 	puts("</pre>\n"
837735516bdSschwarze 	     "</div>");
838c6c22f12Sschwarze 
839c6c22f12Sschwarze 	fclose(f);
840c6c22f12Sschwarze }
841c6c22f12Sschwarze 
842c6c22f12Sschwarze static void
843941df026Sschwarze resp_format(const struct req *req, const char *file)
844c6c22f12Sschwarze {
8452ccd0917Sschwarze 	struct manoutput conf;
846c6c22f12Sschwarze 	struct mparse	*mp;
847ede1b9d0Sschwarze 	struct roff_man	*man;
848c6c22f12Sschwarze 	void		*vp;
849f74d674aSschwarze 	int		 fd;
850f74d674aSschwarze 	int		 usepath;
851c6c22f12Sschwarze 
852c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
853735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
854c6c22f12Sschwarze 		return;
855c6c22f12Sschwarze 	}
856c6c22f12Sschwarze 
85716536faaSschwarze 	mchars_alloc();
858f139d5f6Sschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
859f3476b07Sschwarze 	    MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, req->q.manpath);
860df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
861c6c22f12Sschwarze 	close(fd);
862c6c22f12Sschwarze 
8632ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
8642ccd0917Sschwarze 	conf.fragment = 1;
8657c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
86632f0ba5fSschwarze 	conf.toc = 1;
867f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
868ac2abdb8Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
869ac2abdb8Sschwarze 	    scriptname, *scriptname == '\0' ? "" : "/",
870f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
871c6c22f12Sschwarze 
872f2d5c709Sschwarze 	mparse_result(mp, &man, NULL);
873f2d5c709Sschwarze 	if (man == NULL) {
874c976f0e2Sschwarze 		warnx("fatal mandoc error: %s/%s", req->q.manpath, file);
875facea411Sschwarze 		pg_error_internal();
876c6c22f12Sschwarze 		mparse_free(mp);
87716536faaSschwarze 		mchars_free();
878c6c22f12Sschwarze 		return;
879c6c22f12Sschwarze 	}
880c6c22f12Sschwarze 
88116536faaSschwarze 	vp = html_alloc(&conf);
882c6c22f12Sschwarze 
883396853b5Sschwarze 	if (man->macroset == MACROSET_MDOC) {
884396853b5Sschwarze 		mdoc_validate(man);
885f2d5c709Sschwarze 		html_mdoc(vp, man);
886fec2846bSschwarze 	} else {
887fec2846bSschwarze 		man_validate(man);
888c6c22f12Sschwarze 		html_man(vp, man);
889fec2846bSschwarze 	}
890c6c22f12Sschwarze 
891c6c22f12Sschwarze 	html_free(vp);
892c6c22f12Sschwarze 	mparse_free(mp);
89316536faaSschwarze 	mchars_free();
8942ccd0917Sschwarze 	free(conf.man);
8957c6e1b3aSschwarze 	free(conf.style);
896c6c22f12Sschwarze }
897c6c22f12Sschwarze 
898c6c22f12Sschwarze static void
89946723f19Sschwarze resp_show(const struct req *req, const char *file)
90046723f19Sschwarze {
90181475784Sschwarze 
90281475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
9032f7bef27Sschwarze 		file += 2;
90446723f19Sschwarze 
90546723f19Sschwarze 	if ('c' == *file)
906941df026Sschwarze 		resp_catman(req, file);
90746723f19Sschwarze 	else
908941df026Sschwarze 		resp_format(req, file);
90946723f19Sschwarze }
91046723f19Sschwarze 
91146723f19Sschwarze static void
912b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
913c6c22f12Sschwarze {
914b53c14c3Sschwarze 	char		*manpath;
915b53c14c3Sschwarze 	const char	*file;
916c6c22f12Sschwarze 
917b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
918facea411Sschwarze 		pg_error_badrequest(
919c6c22f12Sschwarze 		    "You did not specify a page to show.");
920c6c22f12Sschwarze 		return;
921c6c22f12Sschwarze 	}
922b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
923b53c14c3Sschwarze 	file++;
924c6c22f12Sschwarze 
925b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
926631ce2c6Sschwarze 		pg_error_badrequest(
927631ce2c6Sschwarze 		    "You specified an invalid manpath.");
928b53c14c3Sschwarze 		free(manpath);
929631ce2c6Sschwarze 		return;
930631ce2c6Sschwarze 	}
931631ce2c6Sschwarze 
932c6c22f12Sschwarze 	/*
933c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
934c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
935c6c22f12Sschwarze 	 * relative to the manpath root.
936c6c22f12Sschwarze 	 */
937c6c22f12Sschwarze 
938b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
939c976f0e2Sschwarze 		warn("chdir %s", manpath);
940631ce2c6Sschwarze 		pg_error_internal();
941b53c14c3Sschwarze 		free(manpath);
942c6c22f12Sschwarze 		return;
943c6c22f12Sschwarze 	}
944b53c14c3Sschwarze 	free(manpath);
945b53c14c3Sschwarze 
946b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
94781475784Sschwarze 		pg_error_badrequest(
94881475784Sschwarze 		    "You specified an invalid manual file.");
94981475784Sschwarze 		return;
95081475784Sschwarze 	}
95181475784Sschwarze 
952fdef72b0Sschwarze 	resp_begin_html(200, NULL, file);
95384f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
954b53c14c3Sschwarze 	resp_show(req, file);
95546723f19Sschwarze 	resp_end_html();
956c6c22f12Sschwarze }
957c6c22f12Sschwarze 
958c6c22f12Sschwarze static void
95957482ef4Sschwarze pg_search(const struct req *req)
960c6c22f12Sschwarze {
961c6c22f12Sschwarze 	struct mansearch	  search;
962c6c22f12Sschwarze 	struct manpaths		  paths;
963c6c22f12Sschwarze 	struct manpage		 *res;
964fbeeb774Sschwarze 	char			**argv;
965fbeeb774Sschwarze 	char			 *query, *rp, *wp;
966c6c22f12Sschwarze 	size_t			  ressz;
967fbeeb774Sschwarze 	int			  argc;
968c6c22f12Sschwarze 
969c6c22f12Sschwarze 	/*
970c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
971c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
972c6c22f12Sschwarze 	 * relative to the manpath root.
973c6c22f12Sschwarze 	 */
974c6c22f12Sschwarze 
975c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
976c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
977631ce2c6Sschwarze 		pg_error_internal();
978c6c22f12Sschwarze 		return;
979c6c22f12Sschwarze 	}
980c6c22f12Sschwarze 
981c6c22f12Sschwarze 	search.arch = req->q.arch;
982c6c22f12Sschwarze 	search.sec = req->q.sec;
9830f10154cSschwarze 	search.outkey = "Nd";
9840f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
985fea71919Sschwarze 	search.firstmatch = 1;
986c6c22f12Sschwarze 
987c6c22f12Sschwarze 	paths.sz = 1;
988c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
989c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
990c6c22f12Sschwarze 
991c6c22f12Sschwarze 	/*
992fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
993c6c22f12Sschwarze 	 */
994c6c22f12Sschwarze 
995fbeeb774Sschwarze 	argc = 0;
996fbeeb774Sschwarze 	argv = NULL;
997fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
998fbeeb774Sschwarze 	for (;;) {
999fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
1000fbeeb774Sschwarze 			rp++;
1001fbeeb774Sschwarze 		if (*rp == '\0')
1002fbeeb774Sschwarze 			break;
1003fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
1004fbeeb774Sschwarze 		argv[argc++] = wp = rp;
1005fbeeb774Sschwarze 		for (;;) {
1006fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
1007fbeeb774Sschwarze 				*wp = '\0';
1008fbeeb774Sschwarze 				rp++;
1009fbeeb774Sschwarze 				break;
1010fbeeb774Sschwarze 			}
1011fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
1012fbeeb774Sschwarze 				rp++;
1013fbeeb774Sschwarze 			if (wp != rp)
1014fbeeb774Sschwarze 				*wp = *rp;
1015fbeeb774Sschwarze 			if (*rp == '\0')
1016fbeeb774Sschwarze 				break;
1017fbeeb774Sschwarze 			wp++;
1018fbeeb774Sschwarze 			rp++;
1019fbeeb774Sschwarze 		}
1020c6c22f12Sschwarze 	}
1021c6c22f12Sschwarze 
1022e1beff2aSschwarze 	res = NULL;
1023e1beff2aSschwarze 	ressz = 0;
1024e1beff2aSschwarze 	if (req->isquery && req->q.equal && argc == 1)
1025e1beff2aSschwarze 		pg_redirect(req, argv[0]);
1026e1beff2aSschwarze 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1027facea411Sschwarze 		pg_noresult(req, "You entered an invalid query.");
1028e1beff2aSschwarze 	else if (ressz == 0)
1029facea411Sschwarze 		pg_noresult(req, "No results found.");
1030c6c22f12Sschwarze 	else
1031facea411Sschwarze 		pg_searchres(req, res, ressz);
1032c6c22f12Sschwarze 
1033fbeeb774Sschwarze 	free(query);
1034fbeeb774Sschwarze 	mansearch_free(res, ressz);
1035c6c22f12Sschwarze 	free(paths.paths[0]);
1036c6c22f12Sschwarze 	free(paths.paths);
1037c6c22f12Sschwarze }
1038c6c22f12Sschwarze 
1039c6c22f12Sschwarze int
1040c6c22f12Sschwarze main(void)
1041c6c22f12Sschwarze {
1042c6c22f12Sschwarze 	struct req	 req;
1043136c26b8Sschwarze 	struct itimerval itimer;
104457482ef4Sschwarze 	const char	*path;
104531e689c3Sschwarze 	const char	*querystring;
104657482ef4Sschwarze 	int		 i;
1047c6c22f12Sschwarze 
1048f80eb964Sschwarze 	/*
1049f80eb964Sschwarze 	 * The "rpath" pledge could be revoked after mparse_readfd()
1050f80eb964Sschwarze 	 * if the file desciptor to "/footer.html" would be opened
1051f80eb964Sschwarze 	 * up front, but it's probably not worth the complication
1052f80eb964Sschwarze 	 * of the code it would cause: it would require scattering
1053f80eb964Sschwarze 	 * pledge() calls in multiple low-level resp_*() functions.
1054f80eb964Sschwarze 	 */
1055f80eb964Sschwarze 
1056f80eb964Sschwarze 	if (pledge("stdio rpath", NULL) == -1) {
1057f80eb964Sschwarze 		warn("pledge");
1058f80eb964Sschwarze 		pg_error_internal();
1059f80eb964Sschwarze 		return EXIT_FAILURE;
1060f80eb964Sschwarze 	}
1061f80eb964Sschwarze 
1062136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1063136c26b8Sschwarze 
10642935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1065136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
10662935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1067136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1068136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1069c976f0e2Sschwarze 		warn("setitimer");
1070136c26b8Sschwarze 		pg_error_internal();
1071526e306bSschwarze 		return EXIT_FAILURE;
1072136c26b8Sschwarze 	}
1073136c26b8Sschwarze 
1074c6c22f12Sschwarze 	/*
10756fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1076c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1077c6c22f12Sschwarze 	 * relative to the same position.
1078c6c22f12Sschwarze 	 */
1079c6c22f12Sschwarze 
1080c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1081c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1082facea411Sschwarze 		pg_error_internal();
1083526e306bSschwarze 		return EXIT_FAILURE;
1084c6c22f12Sschwarze 	}
1085c6c22f12Sschwarze 
1086c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1087abf19dc9Sschwarze 	req.q.equal = 1;
1088941df026Sschwarze 	parse_manpath_conf(&req);
1089c6c22f12Sschwarze 
109002b1b494Sschwarze 	/* Parse the path info and the query string. */
1091c6c22f12Sschwarze 
109202b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
109302b1b494Sschwarze 		path = "";
109402b1b494Sschwarze 	else if (*path == '/')
109502b1b494Sschwarze 		path++;
109602b1b494Sschwarze 
1097aa16a3a8Sschwarze 	if (*path != '\0') {
1098941df026Sschwarze 		parse_path_info(&req, path);
1099a9941855Sschwarze 		if (req.q.manpath == NULL || req.q.sec == NULL ||
1100a9941855Sschwarze 		    *req.q.query == '\0' || access(path, F_OK) == -1)
110102b1b494Sschwarze 			path = "";
110202b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1103941df026Sschwarze 		parse_query_string(&req, querystring);
1104c6c22f12Sschwarze 
110502b1b494Sschwarze 	/* Validate parsed data and add defaults. */
110602b1b494Sschwarze 
110750eaed2bSschwarze 	if (req.q.manpath == NULL)
110850eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
110950eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1110631ce2c6Sschwarze 		pg_error_badrequest(
1111631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1112526e306bSschwarze 		return EXIT_FAILURE;
1113631ce2c6Sschwarze 	}
1114631ce2c6Sschwarze 
1115f7a12365Sschwarze 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1116cf3a545cSschwarze 		pg_error_badrequest(
1117cf3a545cSschwarze 		    "You specified an invalid architecture.");
1118526e306bSschwarze 		return EXIT_FAILURE;
1119cf3a545cSschwarze 	}
1120cf3a545cSschwarze 
112157482ef4Sschwarze 	/* Dispatch to the three different pages. */
1122c6c22f12Sschwarze 
112357482ef4Sschwarze 	if ('\0' != *path)
112457482ef4Sschwarze 		pg_show(&req, path);
1125e89321abSschwarze 	else if (NULL != req.q.query)
112657482ef4Sschwarze 		pg_search(&req);
112757482ef4Sschwarze 	else
1128facea411Sschwarze 		pg_index(&req);
1129c6c22f12Sschwarze 
113031e689c3Sschwarze 	free(req.q.manpath);
113131e689c3Sschwarze 	free(req.q.arch);
113231e689c3Sschwarze 	free(req.q.sec);
1133e89321abSschwarze 	free(req.q.query);
1134c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1135c6c22f12Sschwarze 		free(req.p[i]);
1136c6c22f12Sschwarze 	free(req.p);
1137526e306bSschwarze 	return EXIT_SUCCESS;
1138c6c22f12Sschwarze }
1139c6c22f12Sschwarze 
1140c6c22f12Sschwarze /*
1141*f5938fa6Sschwarze  * Translate PATH_INFO to a query.
114202b1b494Sschwarze  */
114302b1b494Sschwarze static void
1144941df026Sschwarze parse_path_info(struct req *req, const char *path)
114502b1b494Sschwarze {
1146*f5938fa6Sschwarze 	const char	*name, *sec, *end;
114702b1b494Sschwarze 
114852b413c1Sschwarze 	req->isquery = 0;
114902b1b494Sschwarze 	req->q.equal = 1;
1150*f5938fa6Sschwarze 	req->q.manpath = NULL;
1151daf4c292Sschwarze 	req->q.arch = NULL;
115202b1b494Sschwarze 
115302b1b494Sschwarze 	/* Mandatory manual page name. */
1154*f5938fa6Sschwarze 	if ((name = strrchr(path, '/')) == NULL)
1155*f5938fa6Sschwarze 		name = path;
1156*f5938fa6Sschwarze 	else
1157*f5938fa6Sschwarze 		name++;
115802b1b494Sschwarze 
115902b1b494Sschwarze 	/* Optional trailing section. */
1160*f5938fa6Sschwarze 	sec = strrchr(name, '.');
1161*f5938fa6Sschwarze 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
1162*f5938fa6Sschwarze 		req->q.query = mandoc_strndup(name, sec - name - 1);
1163*f5938fa6Sschwarze 		req->q.sec = mandoc_strdup(sec);
1164*f5938fa6Sschwarze 	} else {
1165*f5938fa6Sschwarze 		req->q.query = mandoc_strdup(name);
116602b1b494Sschwarze 		req->q.sec = NULL;
116702b1b494Sschwarze 	}
116802b1b494Sschwarze 
116902b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1170*f5938fa6Sschwarze 	if (name == path)
117102b1b494Sschwarze 		return;
117202b1b494Sschwarze 
1173*f5938fa6Sschwarze 	/* Optional manpath. */
1174*f5938fa6Sschwarze 	end = strchr(path, '/');
1175*f5938fa6Sschwarze 	req->q.manpath = mandoc_strndup(path, end - path);
1176*f5938fa6Sschwarze 	if (validate_manpath(req, req->q.manpath)) {
1177*f5938fa6Sschwarze 		path = end + 1;
1178*f5938fa6Sschwarze 		if (name == path)
1179*f5938fa6Sschwarze 			return;
1180*f5938fa6Sschwarze 	} else {
1181*f5938fa6Sschwarze 		free(req->q.manpath);
1182*f5938fa6Sschwarze 		req->q.manpath = NULL;
1183*f5938fa6Sschwarze 	}
1184*f5938fa6Sschwarze 
1185*f5938fa6Sschwarze 	/* Optional section. */
1186*f5938fa6Sschwarze 	if (strncmp(path, "man", 3) == 0) {
1187*f5938fa6Sschwarze 		path += 3;
1188*f5938fa6Sschwarze 		end = strchr(path, '/');
1189*f5938fa6Sschwarze 		free(req->q.sec);
1190*f5938fa6Sschwarze 		req->q.sec = mandoc_strndup(path, end - path);
1191*f5938fa6Sschwarze 		path = end + 1;
1192*f5938fa6Sschwarze 		if (name == path)
1193*f5938fa6Sschwarze 			return;
1194*f5938fa6Sschwarze 	}
1195*f5938fa6Sschwarze 
1196*f5938fa6Sschwarze 	/* Optional architecture. */
1197*f5938fa6Sschwarze 	end = strchr(path, '/');
1198*f5938fa6Sschwarze 	if (end + 1 != name) {
1199daf4c292Sschwarze 		pg_error_badrequest(
1200daf4c292Sschwarze 		    "You specified too many directory components.");
1201daf4c292Sschwarze 		exit(EXIT_FAILURE);
120202b1b494Sschwarze 	}
1203*f5938fa6Sschwarze 	req->q.arch = mandoc_strndup(path, end - path);
1204*f5938fa6Sschwarze 	if (validate_arch(req->q.arch) == 0) {
1205daf4c292Sschwarze 		pg_error_badrequest(
1206daf4c292Sschwarze 		    "You specified an invalid directory component.");
1207daf4c292Sschwarze 		exit(EXIT_FAILURE);
1208daf4c292Sschwarze 	}
120902b1b494Sschwarze }
121002b1b494Sschwarze 
121102b1b494Sschwarze /*
1212c6c22f12Sschwarze  * Scan for indexable paths.
1213c6c22f12Sschwarze  */
1214c6c22f12Sschwarze static void
1215941df026Sschwarze parse_manpath_conf(struct req *req)
1216c6c22f12Sschwarze {
1217c6c22f12Sschwarze 	FILE	*fp;
1218c6c22f12Sschwarze 	char	*dp;
1219c6c22f12Sschwarze 	size_t	 dpsz;
122031f93c25Sschwarze 	ssize_t	 len;
1221c6c22f12Sschwarze 
1222c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1223c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1224de651747Sschwarze 		pg_error_internal();
1225de651747Sschwarze 		exit(EXIT_FAILURE);
1226de651747Sschwarze 	}
1227c6c22f12Sschwarze 
122831f93c25Sschwarze 	dp = NULL;
122931f93c25Sschwarze 	dpsz = 0;
123031f93c25Sschwarze 
123131f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
123231f93c25Sschwarze 		if (dp[len - 1] == '\n')
123331f93c25Sschwarze 			dp[--len] = '\0';
1234c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1235c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1236cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1237c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1238c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1239cf3a545cSschwarze 			pg_error_internal();
1240cf3a545cSschwarze 			exit(EXIT_FAILURE);
1241cf3a545cSschwarze 		}
1242c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1243c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1244c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1245cf3a545cSschwarze 			pg_error_internal();
1246cf3a545cSschwarze 			exit(EXIT_FAILURE);
1247cf3a545cSschwarze 		}
1248cf3a545cSschwarze 		req->p[req->psz++] = dp;
124931f93c25Sschwarze 		dp = NULL;
125031f93c25Sschwarze 		dpsz = 0;
1251c6c22f12Sschwarze 	}
125231f93c25Sschwarze 	free(dp);
1253de651747Sschwarze 
1254de651747Sschwarze 	if (req->p == NULL) {
1255c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1256de651747Sschwarze 		pg_error_internal();
1257de651747Sschwarze 		exit(EXIT_FAILURE);
1258de651747Sschwarze 	}
1259c6c22f12Sschwarze }
1260