xref: /openbsd/usr.bin/mandoc/cgi.c (revision 6a6803e4)
1*6a6803e4Sschwarze /* $OpenBSD: cgi.c,v 1.110 2020/04/03 11:34:19 schwarze Exp $ */
2c6c22f12Sschwarze /*
318ccf011Sschwarze  * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@usta.de>
4*6a6803e4Sschwarze  * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
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.
17*6a6803e4Sschwarze  *
18*6a6803e4Sschwarze  * Implementation of the man.cgi(8) program.
19c6c22f12Sschwarze  */
20136c26b8Sschwarze #include <sys/types.h>
21136c26b8Sschwarze #include <sys/time.h>
22136c26b8Sschwarze 
23c6c22f12Sschwarze #include <ctype.h>
24c976f0e2Sschwarze #include <err.h>
25c6c22f12Sschwarze #include <errno.h>
26c6c22f12Sschwarze #include <fcntl.h>
27c6c22f12Sschwarze #include <limits.h>
28c2235d37Sschwarze #include <stdint.h>
29c6c22f12Sschwarze #include <stdio.h>
30c6c22f12Sschwarze #include <stdlib.h>
31c6c22f12Sschwarze #include <string.h>
32c6c22f12Sschwarze #include <unistd.h>
33c6c22f12Sschwarze 
34c6c22f12Sschwarze #include "mandoc_aux.h"
35f2d5c709Sschwarze #include "mandoc.h"
36f2d5c709Sschwarze #include "roff.h"
37396853b5Sschwarze #include "mdoc.h"
38fec2846bSschwarze #include "man.h"
3999acaf1eSschwarze #include "mandoc_parse.h"
40c6c22f12Sschwarze #include "main.h"
414de77decSschwarze #include "manconf.h"
42c6c22f12Sschwarze #include "mansearch.h"
436fdade3eSschwarze #include "cgi.h"
44c6c22f12Sschwarze 
45c6c22f12Sschwarze /*
46c6c22f12Sschwarze  * A query as passed to the search function.
47c6c22f12Sschwarze  */
48c6c22f12Sschwarze struct	query {
4931e689c3Sschwarze 	char		*manpath; /* desired manual directory */
5031e689c3Sschwarze 	char		*arch; /* architecture */
5131e689c3Sschwarze 	char		*sec; /* manual section */
52e89321abSschwarze 	char		*query; /* unparsed query expression */
534477fbfaSschwarze 	int		 equal; /* match whole names, not substrings */
54c6c22f12Sschwarze };
55c6c22f12Sschwarze 
56c6c22f12Sschwarze struct	req {
57c6c22f12Sschwarze 	struct query	  q;
58c6c22f12Sschwarze 	char		**p; /* array of available manpaths */
59c6c22f12Sschwarze 	size_t		  psz; /* number of available manpaths */
6052b413c1Sschwarze 	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
61c6c22f12Sschwarze };
62c6c22f12Sschwarze 
6384f05c93Sschwarze enum	focus {
6484f05c93Sschwarze 	FOCUS_NONE = 0,
6584f05c93Sschwarze 	FOCUS_QUERY
6684f05c93Sschwarze };
6784f05c93Sschwarze 
68c6c22f12Sschwarze static	void		 html_print(const char *);
69c6c22f12Sschwarze static	void		 html_putchar(char);
70c6c22f12Sschwarze static	int		 http_decode(char *);
71*6a6803e4Sschwarze static	void		 http_encode(const char *);
72941df026Sschwarze static	void		 parse_manpath_conf(struct req *);
73*6a6803e4Sschwarze static	void		 parse_path_info(struct req *, const char *);
74941df026Sschwarze static	void		 parse_query_string(struct req *, const char *);
75facea411Sschwarze static	void		 pg_error_badrequest(const char *);
76facea411Sschwarze static	void		 pg_error_internal(void);
77facea411Sschwarze static	void		 pg_index(const struct req *);
7818ccf011Sschwarze static	void		 pg_noresult(const struct req *, int, const char *,
7918ccf011Sschwarze 				const char *);
80e1beff2aSschwarze static	void		 pg_redirect(const struct req *, const char *);
8157482ef4Sschwarze static	void		 pg_search(const struct req *);
82facea411Sschwarze static	void		 pg_searchres(const struct req *,
83facea411Sschwarze 				struct manpage *, size_t);
8481060b1aSschwarze static	void		 pg_show(struct req *, const char *);
85fdef72b0Sschwarze static	void		 resp_begin_html(int, const char *, const char *);
86c6c22f12Sschwarze static	void		 resp_begin_http(int, const char *);
87941df026Sschwarze static	void		 resp_catman(const struct req *, const char *);
88711661c7Sschwarze static	void		 resp_copy(const char *);
89c6c22f12Sschwarze static	void		 resp_end_html(void);
90941df026Sschwarze static	void		 resp_format(const struct req *, const char *);
9184f05c93Sschwarze static	void		 resp_searchform(const struct req *, enum focus);
9246723f19Sschwarze static	void		 resp_show(const struct req *, const char *);
93e89321abSschwarze static	void		 set_query_attr(char **, char **);
94f7a12365Sschwarze static	int		 validate_arch(const char *);
95e89321abSschwarze static	int		 validate_filename(const char *);
96e89321abSschwarze static	int		 validate_manpath(const struct req *, const char *);
97e89321abSschwarze static	int		 validate_urifrag(const char *);
98c6c22f12Sschwarze 
993b9cfc6fSschwarze static	const char	 *scriptname = SCRIPT_NAME;
100c6c22f12Sschwarze 
10146723f19Sschwarze static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
10228e449d6Sschwarze static	const char *const sec_numbers[] = {
10328e449d6Sschwarze     "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10428e449d6Sschwarze };
10528e449d6Sschwarze static	const char *const sec_names[] = {
10628e449d6Sschwarze     "All Sections",
10728e449d6Sschwarze     "1 - General Commands",
10828e449d6Sschwarze     "2 - System Calls",
1094e6618fdSschwarze     "3 - Library Functions",
1104e6618fdSschwarze     "3p - Perl Library",
1114e6618fdSschwarze     "4 - Device Drivers",
11228e449d6Sschwarze     "5 - File Formats",
11328e449d6Sschwarze     "6 - Games",
1144e6618fdSschwarze     "7 - Miscellaneous Information",
1154e6618fdSschwarze     "8 - System Manager\'s Manual",
1164e6618fdSschwarze     "9 - Kernel Developer\'s Manual"
11728e449d6Sschwarze };
11828e449d6Sschwarze static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
11928e449d6Sschwarze 
12028e449d6Sschwarze static	const char *const arch_names[] = {
12193d3e4e1Sderaadt     "amd64",       "alpha",       "armv7",	"arm64",
122766ef059Sschwarze     "hppa",        "i386",        "landisk",
1233bcd4815Sschwarze     "loongson",    "luna88k",     "macppc",      "mips64",
1246250a715Sschwarze     "octeon",      "sgi",         "socppc",      "sparc64",
125766ef059Sschwarze     "amiga",       "arc",         "armish",      "arm32",
126766ef059Sschwarze     "atari",       "aviion",      "beagle",      "cats",
127766ef059Sschwarze     "hppa64",      "hp300",
1283bcd4815Sschwarze     "ia64",        "mac68k",      "mvme68k",     "mvme88k",
1293bcd4815Sschwarze     "mvmeppc",     "palm",        "pc532",       "pegasos",
1300c245db5Sschwarze     "pmax",        "powerpc",     "solbourne",   "sparc",
1316250a715Sschwarze     "sun3",        "vax",         "wgrisc",      "x68k",
1326250a715Sschwarze     "zaurus"
13328e449d6Sschwarze };
13428e449d6Sschwarze static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
13528e449d6Sschwarze 
136c6c22f12Sschwarze /*
137c6c22f12Sschwarze  * Print a character, escaping HTML along the way.
138c6c22f12Sschwarze  * This will pass non-ASCII straight to output: be warned!
139c6c22f12Sschwarze  */
140c6c22f12Sschwarze static void
141c6c22f12Sschwarze html_putchar(char c)
142c6c22f12Sschwarze {
143c6c22f12Sschwarze 
144c6c22f12Sschwarze 	switch (c) {
145e74fa2aeSschwarze 	case '"':
146f765a656Sbentley 		printf("&quot;");
147c6c22f12Sschwarze 		break;
148e74fa2aeSschwarze 	case '&':
149c6c22f12Sschwarze 		printf("&amp;");
150c6c22f12Sschwarze 		break;
151e74fa2aeSschwarze 	case '>':
152c6c22f12Sschwarze 		printf("&gt;");
153c6c22f12Sschwarze 		break;
154e74fa2aeSschwarze 	case '<':
155c6c22f12Sschwarze 		printf("&lt;");
156c6c22f12Sschwarze 		break;
157c6c22f12Sschwarze 	default:
158c6c22f12Sschwarze 		putchar((unsigned char)c);
159c6c22f12Sschwarze 		break;
160c6c22f12Sschwarze 	}
161c6c22f12Sschwarze }
162c6c22f12Sschwarze 
163c6c22f12Sschwarze /*
164c6c22f12Sschwarze  * Call through to html_putchar().
165c6c22f12Sschwarze  * Accepts NULL strings.
166c6c22f12Sschwarze  */
167c6c22f12Sschwarze static void
168c6c22f12Sschwarze html_print(const char *p)
169c6c22f12Sschwarze {
170c6c22f12Sschwarze 
171c6c22f12Sschwarze 	if (NULL == p)
172c6c22f12Sschwarze 		return;
173c6c22f12Sschwarze 	while ('\0' != *p)
174c6c22f12Sschwarze 		html_putchar(*p++);
175c6c22f12Sschwarze }
176c6c22f12Sschwarze 
177c6c22f12Sschwarze /*
17831e689c3Sschwarze  * Transfer the responsibility for the allocated string *val
17931e689c3Sschwarze  * to the query structure.
180c6c22f12Sschwarze  */
181c6c22f12Sschwarze static void
18231e689c3Sschwarze set_query_attr(char **attr, char **val)
18331e689c3Sschwarze {
18431e689c3Sschwarze 
18531e689c3Sschwarze 	free(*attr);
18631e689c3Sschwarze 	if (**val == '\0') {
18731e689c3Sschwarze 		*attr = NULL;
18831e689c3Sschwarze 		free(*val);
18931e689c3Sschwarze 	} else
19031e689c3Sschwarze 		*attr = *val;
19131e689c3Sschwarze 	*val = NULL;
19231e689c3Sschwarze }
19331e689c3Sschwarze 
19431e689c3Sschwarze /*
19531e689c3Sschwarze  * Parse the QUERY_STRING for key-value pairs
19631e689c3Sschwarze  * and store the values into the query structure.
19731e689c3Sschwarze  */
19831e689c3Sschwarze static void
199941df026Sschwarze parse_query_string(struct req *req, const char *qs)
200c6c22f12Sschwarze {
201c6c22f12Sschwarze 	char		*key, *val;
20231e689c3Sschwarze 	size_t		 keysz, valsz;
203c6c22f12Sschwarze 
20452b413c1Sschwarze 	req->isquery	= 1;
20531e689c3Sschwarze 	req->q.manpath	= NULL;
20631e689c3Sschwarze 	req->q.arch	= NULL;
20731e689c3Sschwarze 	req->q.sec	= NULL;
208e89321abSschwarze 	req->q.query	= NULL;
2094477fbfaSschwarze 	req->q.equal	= 1;
210c6c22f12Sschwarze 
21131e689c3Sschwarze 	key = val = NULL;
21231e689c3Sschwarze 	while (*qs != '\0') {
213c6c22f12Sschwarze 
21431e689c3Sschwarze 		/* Parse one key. */
215c6c22f12Sschwarze 
21631e689c3Sschwarze 		keysz = strcspn(qs, "=;&");
21731e689c3Sschwarze 		key = mandoc_strndup(qs, keysz);
21831e689c3Sschwarze 		qs += keysz;
21931e689c3Sschwarze 		if (*qs != '=')
22031e689c3Sschwarze 			goto next;
221c6c22f12Sschwarze 
22231e689c3Sschwarze 		/* Parse one value. */
223c6c22f12Sschwarze 
22431e689c3Sschwarze 		valsz = strcspn(++qs, ";&");
22531e689c3Sschwarze 		val = mandoc_strndup(qs, valsz);
22631e689c3Sschwarze 		qs += valsz;
227c6c22f12Sschwarze 
22831e689c3Sschwarze 		/* Decode and catch encoding errors. */
22931e689c3Sschwarze 
23031e689c3Sschwarze 		if ( ! (http_decode(key) && http_decode(val)))
23131e689c3Sschwarze 			goto next;
23231e689c3Sschwarze 
23331e689c3Sschwarze 		/* Handle key-value pairs. */
23431e689c3Sschwarze 
23531e689c3Sschwarze 		if ( ! strcmp(key, "query"))
236e89321abSschwarze 			set_query_attr(&req->q.query, &val);
23731e689c3Sschwarze 
23831e689c3Sschwarze 		else if ( ! strcmp(key, "apropos"))
23931e689c3Sschwarze 			req->q.equal = !strcmp(val, "0");
24031e689c3Sschwarze 
24131e689c3Sschwarze 		else if ( ! strcmp(key, "manpath")) {
242aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24331e689c3Sschwarze 			if ( ! strncmp(val, "OpenBSD ", 8)) {
244aabcc9a1Sschwarze 				val[7] = '-';
245aabcc9a1Sschwarze 				if ('C' == val[8])
246aabcc9a1Sschwarze 					val[8] = 'c';
247aabcc9a1Sschwarze 			}
248aabcc9a1Sschwarze #endif
24931e689c3Sschwarze 			set_query_attr(&req->q.manpath, &val);
25031e689c3Sschwarze 		}
25131e689c3Sschwarze 
25231e689c3Sschwarze 		else if ( ! (strcmp(key, "sec")
253aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
25431e689c3Sschwarze 		    && strcmp(key, "sektion")
255aabcc9a1Sschwarze #endif
25631e689c3Sschwarze 		    )) {
25731e689c3Sschwarze 			if ( ! strcmp(val, "0"))
25831e689c3Sschwarze 				*val = '\0';
25931e689c3Sschwarze 			set_query_attr(&req->q.sec, &val);
260c6c22f12Sschwarze 		}
26131e689c3Sschwarze 
26231e689c3Sschwarze 		else if ( ! strcmp(key, "arch")) {
26331e689c3Sschwarze 			if ( ! strcmp(val, "default"))
26431e689c3Sschwarze 				*val = '\0';
26531e689c3Sschwarze 			set_query_attr(&req->q.arch, &val);
2664477fbfaSschwarze 		}
26731e689c3Sschwarze 
26831e689c3Sschwarze 		/*
26931e689c3Sschwarze 		 * The key must be freed in any case.
27031e689c3Sschwarze 		 * The val may have been handed over to the query
27131e689c3Sschwarze 		 * structure, in which case it is now NULL.
27231e689c3Sschwarze 		 */
27331e689c3Sschwarze next:
27431e689c3Sschwarze 		free(key);
27531e689c3Sschwarze 		key = NULL;
27631e689c3Sschwarze 		free(val);
27731e689c3Sschwarze 		val = NULL;
27831e689c3Sschwarze 
27931e689c3Sschwarze 		if (*qs != '\0')
28031e689c3Sschwarze 			qs++;
28131e689c3Sschwarze 	}
282c6c22f12Sschwarze }
283c6c22f12Sschwarze 
284c6c22f12Sschwarze /*
285c6c22f12Sschwarze  * HTTP-decode a string.  The standard explanation is that this turns
286c6c22f12Sschwarze  * "%4e+foo" into "n foo" in the regular way.  This is done in-place
287c6c22f12Sschwarze  * over the allocated string.
288c6c22f12Sschwarze  */
289c6c22f12Sschwarze static int
290c6c22f12Sschwarze http_decode(char *p)
291c6c22f12Sschwarze {
292c6c22f12Sschwarze 	char             hex[3];
2931f69f32bStedu 	char		*q;
294c6c22f12Sschwarze 	int              c;
295c6c22f12Sschwarze 
296c6c22f12Sschwarze 	hex[2] = '\0';
297c6c22f12Sschwarze 
2981f69f32bStedu 	q = p;
2991f69f32bStedu 	for ( ; '\0' != *p; p++, q++) {
300c6c22f12Sschwarze 		if ('%' == *p) {
301c6c22f12Sschwarze 			if ('\0' == (hex[0] = *(p + 1)))
302526e306bSschwarze 				return 0;
303c6c22f12Sschwarze 			if ('\0' == (hex[1] = *(p + 2)))
304526e306bSschwarze 				return 0;
305c6c22f12Sschwarze 			if (1 != sscanf(hex, "%x", &c))
306526e306bSschwarze 				return 0;
307c6c22f12Sschwarze 			if ('\0' == c)
308526e306bSschwarze 				return 0;
309c6c22f12Sschwarze 
3101f69f32bStedu 			*q = (char)c;
3111f69f32bStedu 			p += 2;
312c6c22f12Sschwarze 		} else
3131f69f32bStedu 			*q = '+' == *p ? ' ' : *p;
314c6c22f12Sschwarze 	}
315c6c22f12Sschwarze 
3161f69f32bStedu 	*q = '\0';
317526e306bSschwarze 	return 1;
318c6c22f12Sschwarze }
319c6c22f12Sschwarze 
320c6c22f12Sschwarze static void
321f7a12365Sschwarze http_encode(const char *p)
322f7a12365Sschwarze {
323f7a12365Sschwarze 	for (; *p != '\0'; p++) {
324f7a12365Sschwarze 		if (isalnum((unsigned char)*p) == 0 &&
325f7a12365Sschwarze 		    strchr("-._~", *p) == NULL)
3261ecf8c4fSschwarze 			printf("%%%2.2X", (unsigned char)*p);
327f7a12365Sschwarze 		else
328f7a12365Sschwarze 			putchar(*p);
329f7a12365Sschwarze 	}
330f7a12365Sschwarze }
331f7a12365Sschwarze 
332f7a12365Sschwarze static void
333c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
334c6c22f12Sschwarze {
335c6c22f12Sschwarze 
336c6c22f12Sschwarze 	if (200 != code)
337fa9c540aStedu 		printf("Status: %d %s\r\n", code, msg);
338c6c22f12Sschwarze 
339fa9c540aStedu 	printf("Content-Type: text/html; charset=utf-8\r\n"
340fa9c540aStedu 	     "Cache-Control: no-cache\r\n"
34153459fdfSbentley 	     "Content-Security-Policy: default-src 'none'; "
34253459fdfSbentley 	     "style-src 'self' 'unsafe-inline'\r\n"
343fa9c540aStedu 	     "Pragma: no-cache\r\n"
344fa9c540aStedu 	     "\r\n");
345c6c22f12Sschwarze 
346c6c22f12Sschwarze 	fflush(stdout);
347c6c22f12Sschwarze }
348c6c22f12Sschwarze 
349c6c22f12Sschwarze static void
350711661c7Sschwarze resp_copy(const char *filename)
351711661c7Sschwarze {
352711661c7Sschwarze 	char	 buf[4096];
353711661c7Sschwarze 	ssize_t	 sz;
354711661c7Sschwarze 	int	 fd;
355711661c7Sschwarze 
356711661c7Sschwarze 	if ((fd = open(filename, O_RDONLY)) != -1) {
357711661c7Sschwarze 		fflush(stdout);
358711661c7Sschwarze 		while ((sz = read(fd, buf, sizeof(buf))) > 0)
359711661c7Sschwarze 			write(STDOUT_FILENO, buf, sz);
360fd3cdd86Sjsg 		close(fd);
361711661c7Sschwarze 	}
362711661c7Sschwarze }
363711661c7Sschwarze 
364711661c7Sschwarze static void
365fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file)
366c6c22f12Sschwarze {
367fdef72b0Sschwarze 	char	*cp;
368c6c22f12Sschwarze 
369c6c22f12Sschwarze 	resp_begin_http(code, msg);
370c6c22f12Sschwarze 
371d649d931Sschwarze 	printf("<!DOCTYPE html>\n"
372735516bdSschwarze 	       "<html>\n"
373735516bdSschwarze 	       "<head>\n"
374735516bdSschwarze 	       "  <meta charset=\"UTF-8\"/>\n"
375661a42d8Sschwarze 	       "  <meta name=\"viewport\""
376661a42d8Sschwarze 		      " content=\"width=device-width, initial-scale=1.0\">\n"
377735516bdSschwarze 	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
378735516bdSschwarze 	       " type=\"text/css\" media=\"all\">\n"
379fdef72b0Sschwarze 	       "  <title>",
380fdef72b0Sschwarze 	       CSS_DIR);
381fdef72b0Sschwarze 	if (file != NULL) {
382fdef72b0Sschwarze 		if ((cp = strrchr(file, '/')) != NULL)
383fdef72b0Sschwarze 			file = cp + 1;
384fdef72b0Sschwarze 		if ((cp = strrchr(file, '.')) != NULL) {
385fdef72b0Sschwarze 			printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
386fdef72b0Sschwarze 		} else
387fdef72b0Sschwarze 			printf("%s - ", file);
388fdef72b0Sschwarze 	}
389fdef72b0Sschwarze 	printf("%s</title>\n"
390735516bdSschwarze 	       "</head>\n"
391ce781f36Sschwarze 	       "<body>\n",
392fdef72b0Sschwarze 	       CUSTOMIZE_TITLE);
393711661c7Sschwarze 
394711661c7Sschwarze 	resp_copy(MAN_DIR "/header.html");
395c6c22f12Sschwarze }
396c6c22f12Sschwarze 
397c6c22f12Sschwarze static void
398c6c22f12Sschwarze resp_end_html(void)
399c6c22f12Sschwarze {
400c6c22f12Sschwarze 
401711661c7Sschwarze 	resp_copy(MAN_DIR "/footer.html");
402711661c7Sschwarze 
403735516bdSschwarze 	puts("</body>\n"
404735516bdSschwarze 	     "</html>");
405c6c22f12Sschwarze }
406c6c22f12Sschwarze 
407c6c22f12Sschwarze static void
40884f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
409c6c22f12Sschwarze {
410c6c22f12Sschwarze 	int		 i;
411c6c22f12Sschwarze 
4127411869fSschwarze 	printf("<form action=\"/%s\" method=\"get\" "
4137411869fSschwarze 	       "autocomplete=\"off\" autocapitalize=\"none\">\n"
414735516bdSschwarze 	       "  <fieldset>\n"
415735516bdSschwarze 	       "    <legend>Manual Page Search Parameters</legend>\n",
416c6c22f12Sschwarze 	       scriptname);
41728e449d6Sschwarze 
41828e449d6Sschwarze 	/* Write query input box. */
41928e449d6Sschwarze 
4206b824e3bSschwarze 	printf("    <input type=\"search\" name=\"query\" value=\"");
42184f05c93Sschwarze 	if (req->q.query != NULL)
422e89321abSschwarze 		html_print(req->q.query);
42384f05c93Sschwarze 	printf( "\" size=\"40\"");
42484f05c93Sschwarze 	if (focus == FOCUS_QUERY)
42584f05c93Sschwarze 		printf(" autofocus");
42684f05c93Sschwarze 	puts(">");
42728e449d6Sschwarze 
428784a63d6Sschwarze 	/* Write submission buttons. */
42928e449d6Sschwarze 
430784a63d6Sschwarze 	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
431784a63d6Sschwarze 		"man</button>\n"
432784a63d6Sschwarze 		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
433542ee4bfSschwarze 		"apropos</button>\n"
434542ee4bfSschwarze 		"    <br/>\n");
43528e449d6Sschwarze 
43628e449d6Sschwarze 	/* Write section selector. */
43728e449d6Sschwarze 
438784a63d6Sschwarze 	puts("    <select name=\"sec\">");
43928e449d6Sschwarze 	for (i = 0; i < sec_MAX; i++) {
440735516bdSschwarze 		printf("      <option value=\"%s\"", sec_numbers[i]);
44128e449d6Sschwarze 		if (NULL != req->q.sec &&
44228e449d6Sschwarze 		    0 == strcmp(sec_numbers[i], req->q.sec))
443735516bdSschwarze 			printf(" selected=\"selected\"");
444735516bdSschwarze 		printf(">%s</option>\n", sec_names[i]);
44528e449d6Sschwarze 	}
446735516bdSschwarze 	puts("    </select>");
44728e449d6Sschwarze 
44828e449d6Sschwarze 	/* Write architecture selector. */
44928e449d6Sschwarze 
450735516bdSschwarze 	printf(	"    <select name=\"arch\">\n"
451735516bdSschwarze 		"      <option value=\"default\"");
452be14a32aSschwarze 	if (NULL == req->q.arch)
453735516bdSschwarze 		printf(" selected=\"selected\"");
454735516bdSschwarze 	puts(">All Architectures</option>");
45528e449d6Sschwarze 	for (i = 0; i < arch_MAX; i++) {
4566b824e3bSschwarze 		printf("      <option");
45728e449d6Sschwarze 		if (NULL != req->q.arch &&
45828e449d6Sschwarze 		    0 == strcmp(arch_names[i], req->q.arch))
459735516bdSschwarze 			printf(" selected=\"selected\"");
460735516bdSschwarze 		printf(">%s</option>\n", arch_names[i]);
46128e449d6Sschwarze 	}
462735516bdSschwarze 	puts("    </select>");
46328e449d6Sschwarze 
46428e449d6Sschwarze 	/* Write manpath selector. */
46528e449d6Sschwarze 
466c6c22f12Sschwarze 	if (req->psz > 1) {
467735516bdSschwarze 		puts("    <select name=\"manpath\">");
468c6c22f12Sschwarze 		for (i = 0; i < (int)req->psz; i++) {
469735516bdSschwarze 			printf("      <option");
47050eaed2bSschwarze 			if (strcmp(req->q.manpath, req->p[i]) == 0)
471735516bdSschwarze 				printf(" selected=\"selected\"");
4726b824e3bSschwarze 			printf(">");
473c6c22f12Sschwarze 			html_print(req->p[i]);
474735516bdSschwarze 			puts("</option>");
475c6c22f12Sschwarze 		}
476735516bdSschwarze 		puts("    </select>");
477c6c22f12Sschwarze 	}
47828e449d6Sschwarze 
479784a63d6Sschwarze 	puts("  </fieldset>\n"
480ce781f36Sschwarze 	     "</form>");
481c6c22f12Sschwarze }
482c6c22f12Sschwarze 
48381475784Sschwarze static int
484cf3a545cSschwarze validate_urifrag(const char *frag)
485cf3a545cSschwarze {
486cf3a545cSschwarze 
487cf3a545cSschwarze 	while ('\0' != *frag) {
488cf3a545cSschwarze 		if ( ! (isalnum((unsigned char)*frag) ||
489cf3a545cSschwarze 		    '-' == *frag || '.' == *frag ||
490cf3a545cSschwarze 		    '/' == *frag || '_' == *frag))
491526e306bSschwarze 			return 0;
492cf3a545cSschwarze 		frag++;
493cf3a545cSschwarze 	}
494526e306bSschwarze 	return 1;
495cf3a545cSschwarze }
496cf3a545cSschwarze 
497cf3a545cSschwarze static int
498631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
499631ce2c6Sschwarze {
500631ce2c6Sschwarze 	size_t	 i;
501631ce2c6Sschwarze 
502631ce2c6Sschwarze 	for (i = 0; i < req->psz; i++)
503631ce2c6Sschwarze 		if ( ! strcmp(manpath, req->p[i]))
504526e306bSschwarze 			return 1;
505631ce2c6Sschwarze 
506526e306bSschwarze 	return 0;
507631ce2c6Sschwarze }
508631ce2c6Sschwarze 
509631ce2c6Sschwarze static int
510f7a12365Sschwarze validate_arch(const char *arch)
511f7a12365Sschwarze {
512f7a12365Sschwarze 	int	 i;
513f7a12365Sschwarze 
514f7a12365Sschwarze 	for (i = 0; i < arch_MAX; i++)
515f7a12365Sschwarze 		if (strcmp(arch, arch_names[i]) == 0)
516f7a12365Sschwarze 			return 1;
517f7a12365Sschwarze 
518f7a12365Sschwarze 	return 0;
519f7a12365Sschwarze }
520f7a12365Sschwarze 
521f7a12365Sschwarze static int
52281475784Sschwarze validate_filename(const char *file)
52381475784Sschwarze {
52481475784Sschwarze 
52581475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
52681475784Sschwarze 		file += 2;
52781475784Sschwarze 
528526e306bSschwarze 	return ! (strstr(file, "../") || strstr(file, "/..") ||
529526e306bSschwarze 	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
53081475784Sschwarze }
53181475784Sschwarze 
532c6c22f12Sschwarze static void
533facea411Sschwarze pg_index(const struct req *req)
534c6c22f12Sschwarze {
535c6c22f12Sschwarze 
536fdef72b0Sschwarze 	resp_begin_html(200, NULL, NULL);
53784f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
538735516bdSschwarze 	printf("<p>\n"
539d56ca219Sschwarze 	       "This web interface is documented in the\n"
5400a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
541d56ca219Sschwarze 	       "manual, and the\n"
5420a282dffSschwarze 	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
5432a43838fSschwarze 	       "manual explains the query syntax.\n"
544735516bdSschwarze 	       "</p>\n",
5453b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/",
5463b9cfc6fSschwarze 	       scriptname, *scriptname == '\0' ? "" : "/");
547c6c22f12Sschwarze 	resp_end_html();
548c6c22f12Sschwarze }
549c6c22f12Sschwarze 
550c6c22f12Sschwarze static void
55118ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg,
55218ccf011Sschwarze     const char *user_msg)
553c6c22f12Sschwarze {
55418ccf011Sschwarze 	resp_begin_html(code, http_msg, NULL);
55584f05c93Sschwarze 	resp_searchform(req, FOCUS_QUERY);
556735516bdSschwarze 	puts("<p>");
55718ccf011Sschwarze 	puts(user_msg);
558735516bdSschwarze 	puts("</p>");
559c6c22f12Sschwarze 	resp_end_html();
560c6c22f12Sschwarze }
561c6c22f12Sschwarze 
562c6c22f12Sschwarze static void
563facea411Sschwarze pg_error_badrequest(const char *msg)
564c6c22f12Sschwarze {
565c6c22f12Sschwarze 
566fdef72b0Sschwarze 	resp_begin_html(400, "Bad Request", NULL);
567735516bdSschwarze 	puts("<h1>Bad Request</h1>\n"
568735516bdSschwarze 	     "<p>\n");
569c6c22f12Sschwarze 	puts(msg);
570c6c22f12Sschwarze 	printf("Try again from the\n"
571735516bdSschwarze 	       "<a href=\"/%s\">main page</a>.\n"
572735516bdSschwarze 	       "</p>", scriptname);
573c6c22f12Sschwarze 	resp_end_html();
574c6c22f12Sschwarze }
575c6c22f12Sschwarze 
576c6c22f12Sschwarze static void
577facea411Sschwarze pg_error_internal(void)
578c6c22f12Sschwarze {
579fdef72b0Sschwarze 	resp_begin_html(500, "Internal Server Error", NULL);
580735516bdSschwarze 	puts("<p>Internal Server Error</p>");
581c6c22f12Sschwarze 	resp_end_html();
582c6c22f12Sschwarze }
583c6c22f12Sschwarze 
584c6c22f12Sschwarze static void
585e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
586e1beff2aSschwarze {
587e0d9a108Sschwarze 	printf("Status: 303 See Other\r\n"
588e0d9a108Sschwarze 	    "Location: /");
589e1beff2aSschwarze 	if (*scriptname != '\0')
590e1beff2aSschwarze 		printf("%s/", scriptname);
591e1beff2aSschwarze 	if (strcmp(req->q.manpath, req->p[0]))
592e1beff2aSschwarze 		printf("%s/", req->q.manpath);
593e1beff2aSschwarze 	if (req->q.arch != NULL)
594e1beff2aSschwarze 		printf("%s/", req->q.arch);
595f7a12365Sschwarze 	http_encode(name);
596f7a12365Sschwarze 	if (req->q.sec != NULL) {
597f7a12365Sschwarze 		putchar('.');
598f7a12365Sschwarze 		http_encode(req->q.sec);
599f7a12365Sschwarze 	}
600e1beff2aSschwarze 	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
601e1beff2aSschwarze }
602e1beff2aSschwarze 
603e1beff2aSschwarze static void
604facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
605c6c22f12Sschwarze {
606be14a32aSschwarze 	char		*arch, *archend;
607db26164eSschwarze 	const char	*sec;
608db26164eSschwarze 	size_t		 i, iuse;
609be14a32aSschwarze 	int		 archprio, archpriouse;
61046723f19Sschwarze 	int		 prio, priouse;
611c6c22f12Sschwarze 
61281475784Sschwarze 	for (i = 0; i < sz; i++) {
61381475784Sschwarze 		if (validate_filename(r[i].file))
61481475784Sschwarze 			continue;
615c976f0e2Sschwarze 		warnx("invalid filename %s in %s database",
61681475784Sschwarze 		    r[i].file, req->q.manpath);
61781475784Sschwarze 		pg_error_internal();
61881475784Sschwarze 		return;
61981475784Sschwarze 	}
62081475784Sschwarze 
62152b413c1Sschwarze 	if (req->isquery && sz == 1) {
622c6c22f12Sschwarze 		/*
623c6c22f12Sschwarze 		 * If we have just one result, then jump there now
624c6c22f12Sschwarze 		 * without any delay.
625c6c22f12Sschwarze 		 */
626e0d9a108Sschwarze 		printf("Status: 303 See Other\r\n"
627e0d9a108Sschwarze 		    "Location: /");
628e0d9a108Sschwarze 		if (*scriptname != '\0')
629e0d9a108Sschwarze 			printf("%s/", scriptname);
630e0d9a108Sschwarze 		if (strcmp(req->q.manpath, req->p[0]))
631e0d9a108Sschwarze 			printf("%s/", req->q.manpath);
632e0d9a108Sschwarze 		printf("%s\r\n"
633e0d9a108Sschwarze 		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
634e0d9a108Sschwarze 		    r[0].file);
635c6c22f12Sschwarze 		return;
636c6c22f12Sschwarze 	}
637c6c22f12Sschwarze 
63846723f19Sschwarze 	/*
63946723f19Sschwarze 	 * In man(1) mode, show one of the pages
64046723f19Sschwarze 	 * even if more than one is found.
64146723f19Sschwarze 	 */
64246723f19Sschwarze 
64346723f19Sschwarze 	iuse = 0;
644fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
645db26164eSschwarze 		priouse = 20;
646be14a32aSschwarze 		archpriouse = 3;
64746723f19Sschwarze 		for (i = 0; i < sz; i++) {
648db26164eSschwarze 			sec = r[i].file;
649db26164eSschwarze 			sec += strcspn(sec, "123456789");
650db26164eSschwarze 			if (sec[0] == '\0')
65146723f19Sschwarze 				continue;
652db26164eSschwarze 			prio = sec_prios[sec[0] - '1'];
653db26164eSschwarze 			if (sec[1] != '/')
654db26164eSschwarze 				prio += 10;
655db26164eSschwarze 			if (req->q.arch == NULL) {
656be14a32aSschwarze 				archprio =
657db26164eSschwarze 				    ((arch = strchr(sec + 1, '/'))
658db26164eSschwarze 					== NULL) ? 3 :
659db26164eSschwarze 				    ((archend = strchr(arch + 1, '/'))
660db26164eSschwarze 					== NULL) ? 0 :
661be14a32aSschwarze 				    strncmp(arch, "amd64/",
662be14a32aSschwarze 					archend - arch) ? 2 : 1;
663be14a32aSschwarze 				if (archprio < archpriouse) {
664be14a32aSschwarze 					archpriouse = archprio;
665be14a32aSschwarze 					priouse = prio;
666be14a32aSschwarze 					iuse = i;
667be14a32aSschwarze 					continue;
668be14a32aSschwarze 				}
669be14a32aSschwarze 				if (archprio > archpriouse)
670be14a32aSschwarze 					continue;
671be14a32aSschwarze 			}
67246723f19Sschwarze 			if (prio >= priouse)
67346723f19Sschwarze 				continue;
67446723f19Sschwarze 			priouse = prio;
67546723f19Sschwarze 			iuse = i;
67646723f19Sschwarze 		}
677fdef72b0Sschwarze 		resp_begin_html(200, NULL, r[iuse].file);
678fdef72b0Sschwarze 	} else
679fdef72b0Sschwarze 		resp_begin_html(200, NULL, NULL);
680fdef72b0Sschwarze 
681fdef72b0Sschwarze 	resp_searchform(req,
682fdef72b0Sschwarze 	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
683fdef72b0Sschwarze 
684fdef72b0Sschwarze 	if (sz > 1) {
685fdef72b0Sschwarze 		puts("<table class=\"results\">");
686fdef72b0Sschwarze 		for (i = 0; i < sz; i++) {
687fdef72b0Sschwarze 			printf("  <tr>\n"
688fdef72b0Sschwarze 			       "    <td>"
6897b3dbe16Sschwarze 			       "<a class=\"Xr\" href=\"/");
6907b3dbe16Sschwarze 			if (*scriptname != '\0')
6917b3dbe16Sschwarze 				printf("%s/", scriptname);
6927b3dbe16Sschwarze 			if (strcmp(req->q.manpath, req->p[0]))
6937b3dbe16Sschwarze 				printf("%s/", req->q.manpath);
6947b3dbe16Sschwarze 			printf("%s\">", r[i].file);
695fdef72b0Sschwarze 			html_print(r[i].names);
696fdef72b0Sschwarze 			printf("</a></td>\n"
697fdef72b0Sschwarze 			       "    <td><span class=\"Nd\">");
698fdef72b0Sschwarze 			html_print(r[i].output);
699fdef72b0Sschwarze 			puts("</span></td>\n"
700fdef72b0Sschwarze 			     "  </tr>");
701fdef72b0Sschwarze 		}
702fdef72b0Sschwarze 		puts("</table>");
703fdef72b0Sschwarze 	}
704fdef72b0Sschwarze 
705fdef72b0Sschwarze 	if (req->q.equal || sz == 1) {
706fdef72b0Sschwarze 		puts("<hr>");
70746723f19Sschwarze 		resp_show(req, r[iuse].file);
70846723f19Sschwarze 	}
70946723f19Sschwarze 
710c6c22f12Sschwarze 	resp_end_html();
711c6c22f12Sschwarze }
712c6c22f12Sschwarze 
713c6c22f12Sschwarze static void
714941df026Sschwarze resp_catman(const struct req *req, const char *file)
715c6c22f12Sschwarze {
716c6c22f12Sschwarze 	FILE		*f;
717c6c22f12Sschwarze 	char		*p;
71831f93c25Sschwarze 	size_t		 sz;
71931f93c25Sschwarze 	ssize_t		 len;
72031f93c25Sschwarze 	int		 i;
721c6c22f12Sschwarze 	int		 italic, bold;
722c6c22f12Sschwarze 
72331f93c25Sschwarze 	if ((f = fopen(file, "r")) == NULL) {
724735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
725c6c22f12Sschwarze 		return;
726c6c22f12Sschwarze 	}
727c6c22f12Sschwarze 
728735516bdSschwarze 	puts("<div class=\"catman\">\n"
729735516bdSschwarze 	     "<pre>");
730c6c22f12Sschwarze 
73131f93c25Sschwarze 	p = NULL;
73231f93c25Sschwarze 	sz = 0;
73331f93c25Sschwarze 
73431f93c25Sschwarze 	while ((len = getline(&p, &sz, f)) != -1) {
735c6c22f12Sschwarze 		bold = italic = 0;
73631f93c25Sschwarze 		for (i = 0; i < len - 1; i++) {
737c6c22f12Sschwarze 			/*
738c6c22f12Sschwarze 			 * This means that the catpage is out of state.
739c6c22f12Sschwarze 			 * Ignore it and keep going (although the
740c6c22f12Sschwarze 			 * catpage is bogus).
741c6c22f12Sschwarze 			 */
742c6c22f12Sschwarze 
743c6c22f12Sschwarze 			if ('\b' == p[i] || '\n' == p[i])
744c6c22f12Sschwarze 				continue;
745c6c22f12Sschwarze 
746c6c22f12Sschwarze 			/*
747c6c22f12Sschwarze 			 * Print a regular character.
748c6c22f12Sschwarze 			 * Close out any bold/italic scopes.
749c6c22f12Sschwarze 			 * If we're in back-space mode, make sure we'll
750c6c22f12Sschwarze 			 * have something to enter when we backspace.
751c6c22f12Sschwarze 			 */
752c6c22f12Sschwarze 
753c6c22f12Sschwarze 			if ('\b' != p[i + 1]) {
754c6c22f12Sschwarze 				if (italic)
755735516bdSschwarze 					printf("</i>");
756c6c22f12Sschwarze 				if (bold)
757735516bdSschwarze 					printf("</b>");
758c6c22f12Sschwarze 				italic = bold = 0;
759c6c22f12Sschwarze 				html_putchar(p[i]);
760c6c22f12Sschwarze 				continue;
76131f93c25Sschwarze 			} else if (i + 2 >= len)
762c6c22f12Sschwarze 				continue;
763c6c22f12Sschwarze 
764c6c22f12Sschwarze 			/* Italic mode. */
765c6c22f12Sschwarze 
766c6c22f12Sschwarze 			if ('_' == p[i]) {
767c6c22f12Sschwarze 				if (bold)
768735516bdSschwarze 					printf("</b>");
769c6c22f12Sschwarze 				if ( ! italic)
770735516bdSschwarze 					printf("<i>");
771c6c22f12Sschwarze 				bold = 0;
772c6c22f12Sschwarze 				italic = 1;
773c6c22f12Sschwarze 				i += 2;
774c6c22f12Sschwarze 				html_putchar(p[i]);
775c6c22f12Sschwarze 				continue;
776c6c22f12Sschwarze 			}
777c6c22f12Sschwarze 
778c6c22f12Sschwarze 			/*
779c6c22f12Sschwarze 			 * Handle funny behaviour troff-isms.
780c6c22f12Sschwarze 			 * These grok'd from the original man2html.c.
781c6c22f12Sschwarze 			 */
782c6c22f12Sschwarze 
783c6c22f12Sschwarze 			if (('+' == p[i] && 'o' == p[i + 2]) ||
784c6c22f12Sschwarze 					('o' == p[i] && '+' == p[i + 2]) ||
785c6c22f12Sschwarze 					('|' == p[i] && '=' == p[i + 2]) ||
786c6c22f12Sschwarze 					('=' == p[i] && '|' == p[i + 2]) ||
787c6c22f12Sschwarze 					('*' == p[i] && '=' == p[i + 2]) ||
788c6c22f12Sschwarze 					('=' == p[i] && '*' == p[i + 2]) ||
789c6c22f12Sschwarze 					('*' == p[i] && '|' == p[i + 2]) ||
790c6c22f12Sschwarze 					('|' == p[i] && '*' == p[i + 2]))  {
791c6c22f12Sschwarze 				if (italic)
792735516bdSschwarze 					printf("</i>");
793c6c22f12Sschwarze 				if (bold)
794735516bdSschwarze 					printf("</b>");
795c6c22f12Sschwarze 				italic = bold = 0;
796c6c22f12Sschwarze 				putchar('*');
797c6c22f12Sschwarze 				i += 2;
798c6c22f12Sschwarze 				continue;
799c6c22f12Sschwarze 			} else if (('|' == p[i] && '-' == p[i + 2]) ||
800c6c22f12Sschwarze 					('-' == p[i] && '|' == p[i + 1]) ||
801c6c22f12Sschwarze 					('+' == p[i] && '-' == p[i + 1]) ||
802c6c22f12Sschwarze 					('-' == p[i] && '+' == p[i + 1]) ||
803c6c22f12Sschwarze 					('+' == p[i] && '|' == p[i + 1]) ||
804c6c22f12Sschwarze 					('|' == p[i] && '+' == p[i + 1]))  {
805c6c22f12Sschwarze 				if (italic)
806735516bdSschwarze 					printf("</i>");
807c6c22f12Sschwarze 				if (bold)
808735516bdSschwarze 					printf("</b>");
809c6c22f12Sschwarze 				italic = bold = 0;
810c6c22f12Sschwarze 				putchar('+');
811c6c22f12Sschwarze 				i += 2;
812c6c22f12Sschwarze 				continue;
813c6c22f12Sschwarze 			}
814c6c22f12Sschwarze 
815c6c22f12Sschwarze 			/* Bold mode. */
816c6c22f12Sschwarze 
817c6c22f12Sschwarze 			if (italic)
818735516bdSschwarze 				printf("</i>");
819c6c22f12Sschwarze 			if ( ! bold)
820735516bdSschwarze 				printf("<b>");
821c6c22f12Sschwarze 			bold = 1;
822c6c22f12Sschwarze 			italic = 0;
823c6c22f12Sschwarze 			i += 2;
824c6c22f12Sschwarze 			html_putchar(p[i]);
825c6c22f12Sschwarze 		}
826c6c22f12Sschwarze 
827c6c22f12Sschwarze 		/*
828c6c22f12Sschwarze 		 * Clean up the last character.
829c6c22f12Sschwarze 		 * We can get to a newline; don't print that.
830c6c22f12Sschwarze 		 */
831c6c22f12Sschwarze 
832c6c22f12Sschwarze 		if (italic)
833735516bdSschwarze 			printf("</i>");
834c6c22f12Sschwarze 		if (bold)
835735516bdSschwarze 			printf("</b>");
836c6c22f12Sschwarze 
83731f93c25Sschwarze 		if (i == len - 1 && p[i] != '\n')
838c6c22f12Sschwarze 			html_putchar(p[i]);
839c6c22f12Sschwarze 
840c6c22f12Sschwarze 		putchar('\n');
841c6c22f12Sschwarze 	}
84231f93c25Sschwarze 	free(p);
843c6c22f12Sschwarze 
844735516bdSschwarze 	puts("</pre>\n"
845735516bdSschwarze 	     "</div>");
846c6c22f12Sschwarze 
847c6c22f12Sschwarze 	fclose(f);
848c6c22f12Sschwarze }
849c6c22f12Sschwarze 
850c6c22f12Sschwarze static void
851941df026Sschwarze resp_format(const struct req *req, const char *file)
852c6c22f12Sschwarze {
8532ccd0917Sschwarze 	struct manoutput conf;
854c6c22f12Sschwarze 	struct mparse	*mp;
8556b86842eSschwarze 	struct roff_meta *meta;
856c6c22f12Sschwarze 	void		*vp;
857f74d674aSschwarze 	int		 fd;
858f74d674aSschwarze 	int		 usepath;
859c6c22f12Sschwarze 
860c6c22f12Sschwarze 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
861735516bdSschwarze 		puts("<p>You specified an invalid manual file.</p>");
862c6c22f12Sschwarze 		return;
863c6c22f12Sschwarze 	}
864c6c22f12Sschwarze 
86516536faaSschwarze 	mchars_alloc();
8666b86842eSschwarze 	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
8676b86842eSschwarze 	    MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
868df927bb6Sschwarze 	mparse_readfd(mp, fd, file);
869c6c22f12Sschwarze 	close(fd);
8706b86842eSschwarze 	meta = mparse_result(mp);
871c6c22f12Sschwarze 
8722ccd0917Sschwarze 	memset(&conf, 0, sizeof(conf));
8732ccd0917Sschwarze 	conf.fragment = 1;
8747c6e1b3aSschwarze 	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
875f74d674aSschwarze 	usepath = strcmp(req->q.manpath, req->p[0]);
876ac2abdb8Sschwarze 	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
877ac2abdb8Sschwarze 	    scriptname, *scriptname == '\0' ? "" : "/",
878f211c215Sschwarze 	    usepath ? req->q.manpath : "", usepath ? "/" : "");
879c6c22f12Sschwarze 
88016536faaSschwarze 	vp = html_alloc(&conf);
8816b86842eSschwarze 	if (meta->macroset == MACROSET_MDOC)
8826b86842eSschwarze 		html_mdoc(vp, meta);
8836b86842eSschwarze 	else
8846b86842eSschwarze 		html_man(vp, meta);
885c6c22f12Sschwarze 
886c6c22f12Sschwarze 	html_free(vp);
887c6c22f12Sschwarze 	mparse_free(mp);
88816536faaSschwarze 	mchars_free();
8892ccd0917Sschwarze 	free(conf.man);
8907c6e1b3aSschwarze 	free(conf.style);
891c6c22f12Sschwarze }
892c6c22f12Sschwarze 
893c6c22f12Sschwarze static void
89446723f19Sschwarze resp_show(const struct req *req, const char *file)
89546723f19Sschwarze {
89681475784Sschwarze 
89781475784Sschwarze 	if ('.' == file[0] && '/' == file[1])
8982f7bef27Sschwarze 		file += 2;
89946723f19Sschwarze 
90046723f19Sschwarze 	if ('c' == *file)
901941df026Sschwarze 		resp_catman(req, file);
90246723f19Sschwarze 	else
903941df026Sschwarze 		resp_format(req, file);
90446723f19Sschwarze }
90546723f19Sschwarze 
90646723f19Sschwarze static void
907b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
908c6c22f12Sschwarze {
909b53c14c3Sschwarze 	char		*manpath;
910b53c14c3Sschwarze 	const char	*file;
911c6c22f12Sschwarze 
912b53c14c3Sschwarze 	if ((file = strchr(fullpath, '/')) == NULL) {
913facea411Sschwarze 		pg_error_badrequest(
914c6c22f12Sschwarze 		    "You did not specify a page to show.");
915c6c22f12Sschwarze 		return;
916c6c22f12Sschwarze 	}
917b53c14c3Sschwarze 	manpath = mandoc_strndup(fullpath, file - fullpath);
918b53c14c3Sschwarze 	file++;
919c6c22f12Sschwarze 
920b53c14c3Sschwarze 	if ( ! validate_manpath(req, manpath)) {
921631ce2c6Sschwarze 		pg_error_badrequest(
922631ce2c6Sschwarze 		    "You specified an invalid manpath.");
923b53c14c3Sschwarze 		free(manpath);
924631ce2c6Sschwarze 		return;
925631ce2c6Sschwarze 	}
926631ce2c6Sschwarze 
927c6c22f12Sschwarze 	/*
928c6c22f12Sschwarze 	 * Begin by chdir()ing into the manpath.
929c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
930c6c22f12Sschwarze 	 * relative to the manpath root.
931c6c22f12Sschwarze 	 */
932c6c22f12Sschwarze 
933b53c14c3Sschwarze 	if (chdir(manpath) == -1) {
934c976f0e2Sschwarze 		warn("chdir %s", manpath);
935631ce2c6Sschwarze 		pg_error_internal();
936b53c14c3Sschwarze 		free(manpath);
937c6c22f12Sschwarze 		return;
938c6c22f12Sschwarze 	}
939b53c14c3Sschwarze 	free(manpath);
940b53c14c3Sschwarze 
941b53c14c3Sschwarze 	if ( ! validate_filename(file)) {
94281475784Sschwarze 		pg_error_badrequest(
94381475784Sschwarze 		    "You specified an invalid manual file.");
94481475784Sschwarze 		return;
94581475784Sschwarze 	}
94681475784Sschwarze 
947fdef72b0Sschwarze 	resp_begin_html(200, NULL, file);
94884f05c93Sschwarze 	resp_searchform(req, FOCUS_NONE);
949b53c14c3Sschwarze 	resp_show(req, file);
95046723f19Sschwarze 	resp_end_html();
951c6c22f12Sschwarze }
952c6c22f12Sschwarze 
953c6c22f12Sschwarze static void
95457482ef4Sschwarze pg_search(const struct req *req)
955c6c22f12Sschwarze {
956c6c22f12Sschwarze 	struct mansearch	  search;
957c6c22f12Sschwarze 	struct manpaths		  paths;
958c6c22f12Sschwarze 	struct manpage		 *res;
959fbeeb774Sschwarze 	char			**argv;
960fbeeb774Sschwarze 	char			 *query, *rp, *wp;
961c6c22f12Sschwarze 	size_t			  ressz;
962fbeeb774Sschwarze 	int			  argc;
963c6c22f12Sschwarze 
964c6c22f12Sschwarze 	/*
965c6c22f12Sschwarze 	 * Begin by chdir()ing into the root of the manpath.
966c6c22f12Sschwarze 	 * This way we can pick up the database files, which are
967c6c22f12Sschwarze 	 * relative to the manpath root.
968c6c22f12Sschwarze 	 */
969c6c22f12Sschwarze 
970c976f0e2Sschwarze 	if (chdir(req->q.manpath) == -1) {
971c976f0e2Sschwarze 		warn("chdir %s", req->q.manpath);
972631ce2c6Sschwarze 		pg_error_internal();
973c6c22f12Sschwarze 		return;
974c6c22f12Sschwarze 	}
975c6c22f12Sschwarze 
976c6c22f12Sschwarze 	search.arch = req->q.arch;
977c6c22f12Sschwarze 	search.sec = req->q.sec;
9780f10154cSschwarze 	search.outkey = "Nd";
9790f10154cSschwarze 	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
980fea71919Sschwarze 	search.firstmatch = 1;
981c6c22f12Sschwarze 
982c6c22f12Sschwarze 	paths.sz = 1;
983c6c22f12Sschwarze 	paths.paths = mandoc_malloc(sizeof(char *));
984c6c22f12Sschwarze 	paths.paths[0] = mandoc_strdup(".");
985c6c22f12Sschwarze 
986c6c22f12Sschwarze 	/*
987fbeeb774Sschwarze 	 * Break apart at spaces with backslash-escaping.
988c6c22f12Sschwarze 	 */
989c6c22f12Sschwarze 
990fbeeb774Sschwarze 	argc = 0;
991fbeeb774Sschwarze 	argv = NULL;
992fbeeb774Sschwarze 	rp = query = mandoc_strdup(req->q.query);
993fbeeb774Sschwarze 	for (;;) {
994fbeeb774Sschwarze 		while (isspace((unsigned char)*rp))
995fbeeb774Sschwarze 			rp++;
996fbeeb774Sschwarze 		if (*rp == '\0')
997fbeeb774Sschwarze 			break;
998fbeeb774Sschwarze 		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
999fbeeb774Sschwarze 		argv[argc++] = wp = rp;
1000fbeeb774Sschwarze 		for (;;) {
1001fbeeb774Sschwarze 			if (isspace((unsigned char)*rp)) {
1002fbeeb774Sschwarze 				*wp = '\0';
1003fbeeb774Sschwarze 				rp++;
1004fbeeb774Sschwarze 				break;
1005fbeeb774Sschwarze 			}
1006fbeeb774Sschwarze 			if (rp[0] == '\\' && rp[1] != '\0')
1007fbeeb774Sschwarze 				rp++;
1008fbeeb774Sschwarze 			if (wp != rp)
1009fbeeb774Sschwarze 				*wp = *rp;
1010fbeeb774Sschwarze 			if (*rp == '\0')
1011fbeeb774Sschwarze 				break;
1012fbeeb774Sschwarze 			wp++;
1013fbeeb774Sschwarze 			rp++;
1014fbeeb774Sschwarze 		}
1015c6c22f12Sschwarze 	}
1016c6c22f12Sschwarze 
1017e1beff2aSschwarze 	res = NULL;
1018e1beff2aSschwarze 	ressz = 0;
1019e1beff2aSschwarze 	if (req->isquery && req->q.equal && argc == 1)
1020e1beff2aSschwarze 		pg_redirect(req, argv[0]);
1021e1beff2aSschwarze 	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
102218ccf011Sschwarze 		pg_noresult(req, 400, "Bad Request",
102318ccf011Sschwarze 		    "You entered an invalid query.");
1024e1beff2aSschwarze 	else if (ressz == 0)
102518ccf011Sschwarze 		pg_noresult(req, 404, "Not Found", "No results found.");
1026c6c22f12Sschwarze 	else
1027facea411Sschwarze 		pg_searchres(req, res, ressz);
1028c6c22f12Sschwarze 
1029fbeeb774Sschwarze 	free(query);
1030fbeeb774Sschwarze 	mansearch_free(res, ressz);
1031c6c22f12Sschwarze 	free(paths.paths[0]);
1032c6c22f12Sschwarze 	free(paths.paths);
1033c6c22f12Sschwarze }
1034c6c22f12Sschwarze 
1035c6c22f12Sschwarze int
1036c6c22f12Sschwarze main(void)
1037c6c22f12Sschwarze {
1038c6c22f12Sschwarze 	struct req	 req;
1039136c26b8Sschwarze 	struct itimerval itimer;
104057482ef4Sschwarze 	const char	*path;
104131e689c3Sschwarze 	const char	*querystring;
104257482ef4Sschwarze 	int		 i;
1043c6c22f12Sschwarze 
1044f80eb964Sschwarze 	/*
1045f80eb964Sschwarze 	 * The "rpath" pledge could be revoked after mparse_readfd()
1046f80eb964Sschwarze 	 * if the file desciptor to "/footer.html" would be opened
1047f80eb964Sschwarze 	 * up front, but it's probably not worth the complication
1048f80eb964Sschwarze 	 * of the code it would cause: it would require scattering
1049f80eb964Sschwarze 	 * pledge() calls in multiple low-level resp_*() functions.
1050f80eb964Sschwarze 	 */
1051f80eb964Sschwarze 
1052f80eb964Sschwarze 	if (pledge("stdio rpath", NULL) == -1) {
1053f80eb964Sschwarze 		warn("pledge");
1054f80eb964Sschwarze 		pg_error_internal();
1055f80eb964Sschwarze 		return EXIT_FAILURE;
1056f80eb964Sschwarze 	}
1057f80eb964Sschwarze 
1058136c26b8Sschwarze 	/* Poor man's ReDoS mitigation. */
1059136c26b8Sschwarze 
10602935aafcSschwarze 	itimer.it_value.tv_sec = 2;
1061136c26b8Sschwarze 	itimer.it_value.tv_usec = 0;
10622935aafcSschwarze 	itimer.it_interval.tv_sec = 2;
1063136c26b8Sschwarze 	itimer.it_interval.tv_usec = 0;
1064136c26b8Sschwarze 	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1065c976f0e2Sschwarze 		warn("setitimer");
1066136c26b8Sschwarze 		pg_error_internal();
1067526e306bSschwarze 		return EXIT_FAILURE;
1068136c26b8Sschwarze 	}
1069136c26b8Sschwarze 
1070c6c22f12Sschwarze 	/*
10716fdade3eSschwarze 	 * First we change directory into the MAN_DIR so that
1072c6c22f12Sschwarze 	 * subsequent scanning for manpath directories is rooted
1073c6c22f12Sschwarze 	 * relative to the same position.
1074c6c22f12Sschwarze 	 */
1075c6c22f12Sschwarze 
1076c976f0e2Sschwarze 	if (chdir(MAN_DIR) == -1) {
1077c976f0e2Sschwarze 		warn("MAN_DIR: %s", MAN_DIR);
1078facea411Sschwarze 		pg_error_internal();
1079526e306bSschwarze 		return EXIT_FAILURE;
1080c6c22f12Sschwarze 	}
1081c6c22f12Sschwarze 
1082c6c22f12Sschwarze 	memset(&req, 0, sizeof(struct req));
1083abf19dc9Sschwarze 	req.q.equal = 1;
1084941df026Sschwarze 	parse_manpath_conf(&req);
1085c6c22f12Sschwarze 
108602b1b494Sschwarze 	/* Parse the path info and the query string. */
1087c6c22f12Sschwarze 
108802b1b494Sschwarze 	if ((path = getenv("PATH_INFO")) == NULL)
108902b1b494Sschwarze 		path = "";
109002b1b494Sschwarze 	else if (*path == '/')
109102b1b494Sschwarze 		path++;
109202b1b494Sschwarze 
1093aa16a3a8Sschwarze 	if (*path != '\0') {
1094941df026Sschwarze 		parse_path_info(&req, path);
1095a9941855Sschwarze 		if (req.q.manpath == NULL || req.q.sec == NULL ||
1096a9941855Sschwarze 		    *req.q.query == '\0' || access(path, F_OK) == -1)
109702b1b494Sschwarze 			path = "";
109802b1b494Sschwarze 	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1099941df026Sschwarze 		parse_query_string(&req, querystring);
1100c6c22f12Sschwarze 
110102b1b494Sschwarze 	/* Validate parsed data and add defaults. */
110202b1b494Sschwarze 
110350eaed2bSschwarze 	if (req.q.manpath == NULL)
110450eaed2bSschwarze 		req.q.manpath = mandoc_strdup(req.p[0]);
110550eaed2bSschwarze 	else if ( ! validate_manpath(&req, req.q.manpath)) {
1106631ce2c6Sschwarze 		pg_error_badrequest(
1107631ce2c6Sschwarze 		    "You specified an invalid manpath.");
1108526e306bSschwarze 		return EXIT_FAILURE;
1109631ce2c6Sschwarze 	}
1110631ce2c6Sschwarze 
1111f7a12365Sschwarze 	if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1112cf3a545cSschwarze 		pg_error_badrequest(
1113cf3a545cSschwarze 		    "You specified an invalid architecture.");
1114526e306bSschwarze 		return EXIT_FAILURE;
1115cf3a545cSschwarze 	}
1116cf3a545cSschwarze 
111757482ef4Sschwarze 	/* Dispatch to the three different pages. */
1118c6c22f12Sschwarze 
111957482ef4Sschwarze 	if ('\0' != *path)
112057482ef4Sschwarze 		pg_show(&req, path);
1121e89321abSschwarze 	else if (NULL != req.q.query)
112257482ef4Sschwarze 		pg_search(&req);
112357482ef4Sschwarze 	else
1124facea411Sschwarze 		pg_index(&req);
1125c6c22f12Sschwarze 
112631e689c3Sschwarze 	free(req.q.manpath);
112731e689c3Sschwarze 	free(req.q.arch);
112831e689c3Sschwarze 	free(req.q.sec);
1129e89321abSschwarze 	free(req.q.query);
1130c6c22f12Sschwarze 	for (i = 0; i < (int)req.psz; i++)
1131c6c22f12Sschwarze 		free(req.p[i]);
1132c6c22f12Sschwarze 	free(req.p);
1133526e306bSschwarze 	return EXIT_SUCCESS;
1134c6c22f12Sschwarze }
1135c6c22f12Sschwarze 
1136c6c22f12Sschwarze /*
1137f5938fa6Sschwarze  * Translate PATH_INFO to a query.
113802b1b494Sschwarze  */
113902b1b494Sschwarze static void
1140941df026Sschwarze parse_path_info(struct req *req, const char *path)
114102b1b494Sschwarze {
1142f5938fa6Sschwarze 	const char	*name, *sec, *end;
114302b1b494Sschwarze 
114452b413c1Sschwarze 	req->isquery = 0;
114502b1b494Sschwarze 	req->q.equal = 1;
1146f5938fa6Sschwarze 	req->q.manpath = NULL;
1147daf4c292Sschwarze 	req->q.arch = NULL;
114802b1b494Sschwarze 
114902b1b494Sschwarze 	/* Mandatory manual page name. */
1150f5938fa6Sschwarze 	if ((name = strrchr(path, '/')) == NULL)
1151f5938fa6Sschwarze 		name = path;
1152f5938fa6Sschwarze 	else
1153f5938fa6Sschwarze 		name++;
115402b1b494Sschwarze 
115502b1b494Sschwarze 	/* Optional trailing section. */
1156f5938fa6Sschwarze 	sec = strrchr(name, '.');
1157f5938fa6Sschwarze 	if (sec != NULL && isdigit((unsigned char)*++sec)) {
1158f5938fa6Sschwarze 		req->q.query = mandoc_strndup(name, sec - name - 1);
1159f5938fa6Sschwarze 		req->q.sec = mandoc_strdup(sec);
1160f5938fa6Sschwarze 	} else {
1161f5938fa6Sschwarze 		req->q.query = mandoc_strdup(name);
116202b1b494Sschwarze 		req->q.sec = NULL;
116302b1b494Sschwarze 	}
116402b1b494Sschwarze 
116502b1b494Sschwarze 	/* Handle the case of name[.section] only. */
1166f5938fa6Sschwarze 	if (name == path)
116702b1b494Sschwarze 		return;
116802b1b494Sschwarze 
1169f5938fa6Sschwarze 	/* Optional manpath. */
1170f5938fa6Sschwarze 	end = strchr(path, '/');
1171f5938fa6Sschwarze 	req->q.manpath = mandoc_strndup(path, end - path);
1172f5938fa6Sschwarze 	if (validate_manpath(req, req->q.manpath)) {
1173f5938fa6Sschwarze 		path = end + 1;
1174f5938fa6Sschwarze 		if (name == path)
1175f5938fa6Sschwarze 			return;
1176f5938fa6Sschwarze 	} else {
1177f5938fa6Sschwarze 		free(req->q.manpath);
1178f5938fa6Sschwarze 		req->q.manpath = NULL;
1179f5938fa6Sschwarze 	}
1180f5938fa6Sschwarze 
1181f5938fa6Sschwarze 	/* Optional section. */
1182955967fcSschwarze 	if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
1183f5938fa6Sschwarze 		path += 3;
1184f5938fa6Sschwarze 		end = strchr(path, '/');
1185f5938fa6Sschwarze 		free(req->q.sec);
1186f5938fa6Sschwarze 		req->q.sec = mandoc_strndup(path, end - path);
1187f5938fa6Sschwarze 		path = end + 1;
1188f5938fa6Sschwarze 		if (name == path)
1189f5938fa6Sschwarze 			return;
1190f5938fa6Sschwarze 	}
1191f5938fa6Sschwarze 
1192f5938fa6Sschwarze 	/* Optional architecture. */
1193f5938fa6Sschwarze 	end = strchr(path, '/');
1194f5938fa6Sschwarze 	if (end + 1 != name) {
1195daf4c292Sschwarze 		pg_error_badrequest(
1196daf4c292Sschwarze 		    "You specified too many directory components.");
1197daf4c292Sschwarze 		exit(EXIT_FAILURE);
119802b1b494Sschwarze 	}
1199f5938fa6Sschwarze 	req->q.arch = mandoc_strndup(path, end - path);
1200f5938fa6Sschwarze 	if (validate_arch(req->q.arch) == 0) {
1201daf4c292Sschwarze 		pg_error_badrequest(
1202daf4c292Sschwarze 		    "You specified an invalid directory component.");
1203daf4c292Sschwarze 		exit(EXIT_FAILURE);
1204daf4c292Sschwarze 	}
120502b1b494Sschwarze }
120602b1b494Sschwarze 
120702b1b494Sschwarze /*
1208c6c22f12Sschwarze  * Scan for indexable paths.
1209c6c22f12Sschwarze  */
1210c6c22f12Sschwarze static void
1211941df026Sschwarze parse_manpath_conf(struct req *req)
1212c6c22f12Sschwarze {
1213c6c22f12Sschwarze 	FILE	*fp;
1214c6c22f12Sschwarze 	char	*dp;
1215c6c22f12Sschwarze 	size_t	 dpsz;
121631f93c25Sschwarze 	ssize_t	 len;
1217c6c22f12Sschwarze 
1218c976f0e2Sschwarze 	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1219c976f0e2Sschwarze 		warn("%s/manpath.conf", MAN_DIR);
1220de651747Sschwarze 		pg_error_internal();
1221de651747Sschwarze 		exit(EXIT_FAILURE);
1222de651747Sschwarze 	}
1223c6c22f12Sschwarze 
122431f93c25Sschwarze 	dp = NULL;
122531f93c25Sschwarze 	dpsz = 0;
122631f93c25Sschwarze 
122731f93c25Sschwarze 	while ((len = getline(&dp, &dpsz, fp)) != -1) {
122831f93c25Sschwarze 		if (dp[len - 1] == '\n')
122931f93c25Sschwarze 			dp[--len] = '\0';
1230c6c22f12Sschwarze 		req->p = mandoc_realloc(req->p,
1231c6c22f12Sschwarze 		    (req->psz + 1) * sizeof(char *));
1232cf3a545cSschwarze 		if ( ! validate_urifrag(dp)) {
1233c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1234c976f0e2Sschwarze 			    "unsafe path \"%s\"", MAN_DIR, dp);
1235cf3a545cSschwarze 			pg_error_internal();
1236cf3a545cSschwarze 			exit(EXIT_FAILURE);
1237cf3a545cSschwarze 		}
1238c976f0e2Sschwarze 		if (strchr(dp, '/') != NULL) {
1239c976f0e2Sschwarze 			warnx("%s/manpath.conf contains "
1240c976f0e2Sschwarze 			    "path with slash \"%s\"", MAN_DIR, dp);
1241cf3a545cSschwarze 			pg_error_internal();
1242cf3a545cSschwarze 			exit(EXIT_FAILURE);
1243cf3a545cSschwarze 		}
1244cf3a545cSschwarze 		req->p[req->psz++] = dp;
124531f93c25Sschwarze 		dp = NULL;
124631f93c25Sschwarze 		dpsz = 0;
1247c6c22f12Sschwarze 	}
124831f93c25Sschwarze 	free(dp);
1249de651747Sschwarze 
1250de651747Sschwarze 	if (req->p == NULL) {
1251c976f0e2Sschwarze 		warnx("%s/manpath.conf is empty", MAN_DIR);
1252de651747Sschwarze 		pg_error_internal();
1253de651747Sschwarze 		exit(EXIT_FAILURE);
1254de651747Sschwarze 	}
1255c6c22f12Sschwarze }
1256