1322249Sbapt/*	$Id: cgi.c,v 1.156 2017/06/24 14:38:32 schwarze Exp $ */
2274880Sbapt/*
3274880Sbapt * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4316420Sbapt * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze <schwarze@usta.de>
5274880Sbapt *
6274880Sbapt * Permission to use, copy, modify, and distribute this software for any
7274880Sbapt * purpose with or without fee is hereby granted, provided that the above
8274880Sbapt * copyright notice and this permission notice appear in all copies.
9274880Sbapt *
10294113Sbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11274880Sbapt * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12294113Sbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13274880Sbapt * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14274880Sbapt * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15274880Sbapt * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16274880Sbapt * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17274880Sbapt */
18274880Sbapt#include "config.h"
19274880Sbapt
20275432Sbapt#include <sys/types.h>
21275432Sbapt#include <sys/time.h>
22275432Sbapt
23274880Sbapt#include <ctype.h>
24322249Sbapt#if HAVE_ERR
25307795Sbapt#include <err.h>
26322249Sbapt#endif
27274880Sbapt#include <errno.h>
28274880Sbapt#include <fcntl.h>
29274880Sbapt#include <limits.h>
30274880Sbapt#include <stdint.h>
31274880Sbapt#include <stdio.h>
32274880Sbapt#include <stdlib.h>
33274880Sbapt#include <string.h>
34274880Sbapt#include <unistd.h>
35274880Sbapt
36294113Sbapt#include "mandoc_aux.h"
37274880Sbapt#include "mandoc.h"
38294113Sbapt#include "roff.h"
39294113Sbapt#include "mdoc.h"
40294113Sbapt#include "man.h"
41274880Sbapt#include "main.h"
42294113Sbapt#include "manconf.h"
43274880Sbapt#include "mansearch.h"
44274880Sbapt#include "cgi.h"
45274880Sbapt
46274880Sbapt/*
47274880Sbapt * A query as passed to the search function.
48274880Sbapt */
49274880Sbaptstruct	query {
50274880Sbapt	char		*manpath; /* desired manual directory */
51274880Sbapt	char		*arch; /* architecture */
52274880Sbapt	char		*sec; /* manual section */
53274880Sbapt	char		*query; /* unparsed query expression */
54274880Sbapt	int		 equal; /* match whole names, not substrings */
55274880Sbapt};
56274880Sbapt
57274880Sbaptstruct	req {
58274880Sbapt	struct query	  q;
59274880Sbapt	char		**p; /* array of available manpaths */
60274880Sbapt	size_t		  psz; /* number of available manpaths */
61307795Sbapt	int		  isquery; /* QUERY_STRING used, not PATH_INFO */
62274880Sbapt};
63274880Sbapt
64307795Sbaptenum	focus {
65307795Sbapt	FOCUS_NONE = 0,
66307795Sbapt	FOCUS_QUERY
67307795Sbapt};
68307795Sbapt
69274880Sbaptstatic	void		 html_print(const char *);
70274880Sbaptstatic	void		 html_putchar(char);
71279527Sbaptstatic	int		 http_decode(char *);
72307795Sbaptstatic	void		 parse_manpath_conf(struct req *);
73307795Sbaptstatic	void		 parse_path_info(struct req *req, const char *path);
74307795Sbaptstatic	void		 parse_query_string(struct req *, const char *);
75274880Sbaptstatic	void		 pg_error_badrequest(const char *);
76274880Sbaptstatic	void		 pg_error_internal(void);
77274880Sbaptstatic	void		 pg_index(const struct req *);
78274880Sbaptstatic	void		 pg_noresult(const struct req *, const char *);
79322249Sbaptstatic	void		 pg_redirect(const struct req *, const char *);
80274880Sbaptstatic	void		 pg_search(const struct req *);
81274880Sbaptstatic	void		 pg_searchres(const struct req *,
82274880Sbapt				struct manpage *, size_t);
83274880Sbaptstatic	void		 pg_show(struct req *, const char *);
84322249Sbaptstatic	void		 resp_begin_html(int, const char *, const char *);
85274880Sbaptstatic	void		 resp_begin_http(int, const char *);
86307795Sbaptstatic	void		 resp_catman(const struct req *, const char *);
87294113Sbaptstatic	void		 resp_copy(const char *);
88274880Sbaptstatic	void		 resp_end_html(void);
89307795Sbaptstatic	void		 resp_format(const struct req *, const char *);
90307795Sbaptstatic	void		 resp_searchform(const struct req *, enum focus);
91274880Sbaptstatic	void		 resp_show(const struct req *, const char *);
92274880Sbaptstatic	void		 set_query_attr(char **, char **);
93274880Sbaptstatic	int		 validate_filename(const char *);
94274880Sbaptstatic	int		 validate_manpath(const struct req *, const char *);
95274880Sbaptstatic	int		 validate_urifrag(const char *);
96274880Sbapt
97307795Sbaptstatic	const char	 *scriptname = SCRIPT_NAME;
98274880Sbapt
99274880Sbaptstatic	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
100274880Sbaptstatic	const char *const sec_numbers[] = {
101274880Sbapt    "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
102274880Sbapt};
103274880Sbaptstatic	const char *const sec_names[] = {
104274880Sbapt    "All Sections",
105274880Sbapt    "1 - General Commands",
106274880Sbapt    "2 - System Calls",
107275432Sbapt    "3 - Library Functions",
108275432Sbapt    "3p - Perl Library",
109275432Sbapt    "4 - Device Drivers",
110274880Sbapt    "5 - File Formats",
111274880Sbapt    "6 - Games",
112275432Sbapt    "7 - Miscellaneous Information",
113275432Sbapt    "8 - System Manager\'s Manual",
114275432Sbapt    "9 - Kernel Developer\'s Manual"
115274880Sbapt};
116274880Sbaptstatic	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
117274880Sbapt
118274880Sbaptstatic	const char *const arch_names[] = {
119322249Sbapt    "amd64",       "alpha",       "armv7",	"arm64",
120316420Sbapt    "hppa",        "i386",        "landisk",
121307795Sbapt    "loongson",    "luna88k",     "macppc",      "mips64",
122316420Sbapt    "octeon",      "sgi",         "socppc",      "sparc64",
123316420Sbapt    "amiga",       "arc",         "armish",      "arm32",
124316420Sbapt    "atari",       "aviion",      "beagle",      "cats",
125316420Sbapt    "hppa64",      "hp300",
126307795Sbapt    "ia64",        "mac68k",      "mvme68k",     "mvme88k",
127307795Sbapt    "mvmeppc",     "palm",        "pc532",       "pegasos",
128316420Sbapt    "pmax",        "powerpc",     "solbourne",   "sparc",
129316420Sbapt    "sun3",        "vax",         "wgrisc",      "x68k",
130316420Sbapt    "zaurus"
131274880Sbapt};
132274880Sbaptstatic	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
133274880Sbapt
134274880Sbapt/*
135274880Sbapt * Print a character, escaping HTML along the way.
136274880Sbapt * This will pass non-ASCII straight to output: be warned!
137274880Sbapt */
138274880Sbaptstatic void
139274880Sbapthtml_putchar(char c)
140274880Sbapt{
141274880Sbapt
142274880Sbapt	switch (c) {
143322249Sbapt	case '"':
144316420Sbapt		printf("&quot;");
145274880Sbapt		break;
146322249Sbapt	case '&':
147274880Sbapt		printf("&amp;");
148274880Sbapt		break;
149322249Sbapt	case '>':
150274880Sbapt		printf("&gt;");
151274880Sbapt		break;
152322249Sbapt	case '<':
153274880Sbapt		printf("&lt;");
154274880Sbapt		break;
155274880Sbapt	default:
156274880Sbapt		putchar((unsigned char)c);
157274880Sbapt		break;
158274880Sbapt	}
159274880Sbapt}
160274880Sbapt
161274880Sbapt/*
162274880Sbapt * Call through to html_putchar().
163274880Sbapt * Accepts NULL strings.
164274880Sbapt */
165274880Sbaptstatic void
166274880Sbapthtml_print(const char *p)
167274880Sbapt{
168279527Sbapt
169274880Sbapt	if (NULL == p)
170274880Sbapt		return;
171274880Sbapt	while ('\0' != *p)
172274880Sbapt		html_putchar(*p++);
173274880Sbapt}
174274880Sbapt
175274880Sbapt/*
176274880Sbapt * Transfer the responsibility for the allocated string *val
177274880Sbapt * to the query structure.
178274880Sbapt */
179274880Sbaptstatic void
180274880Sbaptset_query_attr(char **attr, char **val)
181274880Sbapt{
182274880Sbapt
183274880Sbapt	free(*attr);
184274880Sbapt	if (**val == '\0') {
185274880Sbapt		*attr = NULL;
186274880Sbapt		free(*val);
187274880Sbapt	} else
188274880Sbapt		*attr = *val;
189274880Sbapt	*val = NULL;
190274880Sbapt}
191274880Sbapt
192274880Sbapt/*
193274880Sbapt * Parse the QUERY_STRING for key-value pairs
194274880Sbapt * and store the values into the query structure.
195274880Sbapt */
196274880Sbaptstatic void
197307795Sbaptparse_query_string(struct req *req, const char *qs)
198274880Sbapt{
199274880Sbapt	char		*key, *val;
200274880Sbapt	size_t		 keysz, valsz;
201274880Sbapt
202307795Sbapt	req->isquery	= 1;
203274880Sbapt	req->q.manpath	= NULL;
204274880Sbapt	req->q.arch	= NULL;
205274880Sbapt	req->q.sec	= NULL;
206274880Sbapt	req->q.query	= NULL;
207274880Sbapt	req->q.equal	= 1;
208274880Sbapt
209274880Sbapt	key = val = NULL;
210274880Sbapt	while (*qs != '\0') {
211274880Sbapt
212274880Sbapt		/* Parse one key. */
213274880Sbapt
214274880Sbapt		keysz = strcspn(qs, "=;&");
215274880Sbapt		key = mandoc_strndup(qs, keysz);
216274880Sbapt		qs += keysz;
217274880Sbapt		if (*qs != '=')
218274880Sbapt			goto next;
219274880Sbapt
220274880Sbapt		/* Parse one value. */
221274880Sbapt
222274880Sbapt		valsz = strcspn(++qs, ";&");
223274880Sbapt		val = mandoc_strndup(qs, valsz);
224274880Sbapt		qs += valsz;
225274880Sbapt
226274880Sbapt		/* Decode and catch encoding errors. */
227274880Sbapt
228274880Sbapt		if ( ! (http_decode(key) && http_decode(val)))
229274880Sbapt			goto next;
230274880Sbapt
231274880Sbapt		/* Handle key-value pairs. */
232274880Sbapt
233274880Sbapt		if ( ! strcmp(key, "query"))
234274880Sbapt			set_query_attr(&req->q.query, &val);
235274880Sbapt
236274880Sbapt		else if ( ! strcmp(key, "apropos"))
237274880Sbapt			req->q.equal = !strcmp(val, "0");
238274880Sbapt
239274880Sbapt		else if ( ! strcmp(key, "manpath")) {
240274880Sbapt#ifdef COMPAT_OLDURI
241274880Sbapt			if ( ! strncmp(val, "OpenBSD ", 8)) {
242274880Sbapt				val[7] = '-';
243274880Sbapt				if ('C' == val[8])
244274880Sbapt					val[8] = 'c';
245274880Sbapt			}
246274880Sbapt#endif
247274880Sbapt			set_query_attr(&req->q.manpath, &val);
248274880Sbapt		}
249274880Sbapt
250274880Sbapt		else if ( ! (strcmp(key, "sec")
251274880Sbapt#ifdef COMPAT_OLDURI
252274880Sbapt		    && strcmp(key, "sektion")
253274880Sbapt#endif
254274880Sbapt		    )) {
255274880Sbapt			if ( ! strcmp(val, "0"))
256274880Sbapt				*val = '\0';
257274880Sbapt			set_query_attr(&req->q.sec, &val);
258274880Sbapt		}
259274880Sbapt
260274880Sbapt		else if ( ! strcmp(key, "arch")) {
261274880Sbapt			if ( ! strcmp(val, "default"))
262274880Sbapt				*val = '\0';
263274880Sbapt			set_query_attr(&req->q.arch, &val);
264274880Sbapt		}
265274880Sbapt
266274880Sbapt		/*
267274880Sbapt		 * The key must be freed in any case.
268274880Sbapt		 * The val may have been handed over to the query
269274880Sbapt		 * structure, in which case it is now NULL.
270274880Sbapt		 */
271274880Sbaptnext:
272274880Sbapt		free(key);
273274880Sbapt		key = NULL;
274274880Sbapt		free(val);
275274880Sbapt		val = NULL;
276274880Sbapt
277274880Sbapt		if (*qs != '\0')
278274880Sbapt			qs++;
279274880Sbapt	}
280274880Sbapt}
281274880Sbapt
282274880Sbapt/*
283274880Sbapt * HTTP-decode a string.  The standard explanation is that this turns
284274880Sbapt * "%4e+foo" into "n foo" in the regular way.  This is done in-place
285274880Sbapt * over the allocated string.
286274880Sbapt */
287274880Sbaptstatic int
288274880Sbapthttp_decode(char *p)
289274880Sbapt{
290274880Sbapt	char             hex[3];
291274880Sbapt	char		*q;
292274880Sbapt	int              c;
293274880Sbapt
294274880Sbapt	hex[2] = '\0';
295274880Sbapt
296274880Sbapt	q = p;
297274880Sbapt	for ( ; '\0' != *p; p++, q++) {
298274880Sbapt		if ('%' == *p) {
299274880Sbapt			if ('\0' == (hex[0] = *(p + 1)))
300294113Sbapt				return 0;
301274880Sbapt			if ('\0' == (hex[1] = *(p + 2)))
302294113Sbapt				return 0;
303274880Sbapt			if (1 != sscanf(hex, "%x", &c))
304294113Sbapt				return 0;
305274880Sbapt			if ('\0' == c)
306294113Sbapt				return 0;
307274880Sbapt
308274880Sbapt			*q = (char)c;
309274880Sbapt			p += 2;
310274880Sbapt		} else
311274880Sbapt			*q = '+' == *p ? ' ' : *p;
312274880Sbapt	}
313274880Sbapt
314274880Sbapt	*q = '\0';
315294113Sbapt	return 1;
316274880Sbapt}
317274880Sbapt
318274880Sbaptstatic void
319274880Sbaptresp_begin_http(int code, const char *msg)
320274880Sbapt{
321274880Sbapt
322274880Sbapt	if (200 != code)
323274880Sbapt		printf("Status: %d %s\r\n", code, msg);
324274880Sbapt
325274880Sbapt	printf("Content-Type: text/html; charset=utf-8\r\n"
326274880Sbapt	     "Cache-Control: no-cache\r\n"
327274880Sbapt	     "Pragma: no-cache\r\n"
328274880Sbapt	     "\r\n");
329274880Sbapt
330274880Sbapt	fflush(stdout);
331274880Sbapt}
332274880Sbapt
333274880Sbaptstatic void
334294113Sbaptresp_copy(const char *filename)
335294113Sbapt{
336294113Sbapt	char	 buf[4096];
337294113Sbapt	ssize_t	 sz;
338294113Sbapt	int	 fd;
339294113Sbapt
340294113Sbapt	if ((fd = open(filename, O_RDONLY)) != -1) {
341294113Sbapt		fflush(stdout);
342294113Sbapt		while ((sz = read(fd, buf, sizeof(buf))) > 0)
343294113Sbapt			write(STDOUT_FILENO, buf, sz);
344316420Sbapt		close(fd);
345294113Sbapt	}
346294113Sbapt}
347294113Sbapt
348294113Sbaptstatic void
349322249Sbaptresp_begin_html(int code, const char *msg, const char *file)
350274880Sbapt{
351322249Sbapt	char	*cp;
352274880Sbapt
353274880Sbapt	resp_begin_http(code, msg);
354274880Sbapt
355275432Sbapt	printf("<!DOCTYPE html>\n"
356307795Sbapt	       "<html>\n"
357307795Sbapt	       "<head>\n"
358316420Sbapt	       "  <meta charset=\"UTF-8\"/>\n"
359316420Sbapt	       "  <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
360307795Sbapt	       " type=\"text/css\" media=\"all\">\n"
361322249Sbapt	       "  <title>",
362322249Sbapt	       CSS_DIR);
363322249Sbapt	if (file != NULL) {
364322249Sbapt		if ((cp = strrchr(file, '/')) != NULL)
365322249Sbapt			file = cp + 1;
366322249Sbapt		if ((cp = strrchr(file, '.')) != NULL) {
367322249Sbapt			printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
368322249Sbapt		} else
369322249Sbapt			printf("%s - ", file);
370322249Sbapt	}
371322249Sbapt	printf("%s</title>\n"
372307795Sbapt	       "</head>\n"
373316420Sbapt	       "<body>\n",
374322249Sbapt	       CUSTOMIZE_TITLE);
375294113Sbapt
376294113Sbapt	resp_copy(MAN_DIR "/header.html");
377274880Sbapt}
378274880Sbapt
379274880Sbaptstatic void
380274880Sbaptresp_end_html(void)
381274880Sbapt{
382274880Sbapt
383294113Sbapt	resp_copy(MAN_DIR "/footer.html");
384294113Sbapt
385307795Sbapt	puts("</body>\n"
386307795Sbapt	     "</html>");
387274880Sbapt}
388274880Sbapt
389274880Sbaptstatic void
390307795Sbaptresp_searchform(const struct req *req, enum focus focus)
391274880Sbapt{
392274880Sbapt	int		 i;
393274880Sbapt
394316420Sbapt	printf("<form action=\"/%s\" method=\"get\">\n"
395316420Sbapt	       "  <fieldset>\n"
396316420Sbapt	       "    <legend>Manual Page Search Parameters</legend>\n",
397274880Sbapt	       scriptname);
398274880Sbapt
399274880Sbapt	/* Write query input box. */
400274880Sbapt
401316420Sbapt	printf("    <input type=\"text\" name=\"query\" value=\"");
402307795Sbapt	if (req->q.query != NULL)
403274880Sbapt		html_print(req->q.query);
404307795Sbapt	printf( "\" size=\"40\"");
405307795Sbapt	if (focus == FOCUS_QUERY)
406307795Sbapt		printf(" autofocus");
407307795Sbapt	puts(">");
408274880Sbapt
409307795Sbapt	/* Write submission buttons. */
410274880Sbapt
411316420Sbapt	printf(	"    <button type=\"submit\" name=\"apropos\" value=\"0\">"
412307795Sbapt		"man</button>\n"
413316420Sbapt		"    <button type=\"submit\" name=\"apropos\" value=\"1\">"
414316420Sbapt		"apropos</button>\n"
415316420Sbapt		"    <br/>\n");
416274880Sbapt
417274880Sbapt	/* Write section selector. */
418274880Sbapt
419316420Sbapt	puts("    <select name=\"sec\">");
420274880Sbapt	for (i = 0; i < sec_MAX; i++) {
421316420Sbapt		printf("      <option value=\"%s\"", sec_numbers[i]);
422274880Sbapt		if (NULL != req->q.sec &&
423274880Sbapt		    0 == strcmp(sec_numbers[i], req->q.sec))
424307795Sbapt			printf(" selected=\"selected\"");
425307795Sbapt		printf(">%s</option>\n", sec_names[i]);
426274880Sbapt	}
427316420Sbapt	puts("    </select>");
428274880Sbapt
429274880Sbapt	/* Write architecture selector. */
430274880Sbapt
431316420Sbapt	printf(	"    <select name=\"arch\">\n"
432316420Sbapt		"      <option value=\"default\"");
433274880Sbapt	if (NULL == req->q.arch)
434307795Sbapt		printf(" selected=\"selected\"");
435307795Sbapt	puts(">All Architectures</option>");
436274880Sbapt	for (i = 0; i < arch_MAX; i++) {
437316420Sbapt		printf("      <option value=\"%s\"", arch_names[i]);
438274880Sbapt		if (NULL != req->q.arch &&
439274880Sbapt		    0 == strcmp(arch_names[i], req->q.arch))
440307795Sbapt			printf(" selected=\"selected\"");
441307795Sbapt		printf(">%s</option>\n", arch_names[i]);
442274880Sbapt	}
443316420Sbapt	puts("    </select>");
444274880Sbapt
445274880Sbapt	/* Write manpath selector. */
446274880Sbapt
447274880Sbapt	if (req->psz > 1) {
448316420Sbapt		puts("    <select name=\"manpath\">");
449274880Sbapt		for (i = 0; i < (int)req->psz; i++) {
450316420Sbapt			printf("      <option ");
451275432Sbapt			if (strcmp(req->q.manpath, req->p[i]) == 0)
452307795Sbapt				printf("selected=\"selected\" ");
453307795Sbapt			printf("value=\"");
454274880Sbapt			html_print(req->p[i]);
455274880Sbapt			printf("\">");
456274880Sbapt			html_print(req->p[i]);
457307795Sbapt			puts("</option>");
458274880Sbapt		}
459316420Sbapt		puts("    </select>");
460274880Sbapt	}
461274880Sbapt
462316420Sbapt	puts("  </fieldset>\n"
463316420Sbapt	     "</form>");
464274880Sbapt}
465274880Sbapt
466274880Sbaptstatic int
467274880Sbaptvalidate_urifrag(const char *frag)
468274880Sbapt{
469274880Sbapt
470274880Sbapt	while ('\0' != *frag) {
471274880Sbapt		if ( ! (isalnum((unsigned char)*frag) ||
472274880Sbapt		    '-' == *frag || '.' == *frag ||
473274880Sbapt		    '/' == *frag || '_' == *frag))
474294113Sbapt			return 0;
475274880Sbapt		frag++;
476274880Sbapt	}
477294113Sbapt	return 1;
478274880Sbapt}
479274880Sbapt
480274880Sbaptstatic int
481274880Sbaptvalidate_manpath(const struct req *req, const char* manpath)
482274880Sbapt{
483274880Sbapt	size_t	 i;
484274880Sbapt
485274880Sbapt	for (i = 0; i < req->psz; i++)
486274880Sbapt		if ( ! strcmp(manpath, req->p[i]))
487294113Sbapt			return 1;
488274880Sbapt
489294113Sbapt	return 0;
490274880Sbapt}
491274880Sbapt
492274880Sbaptstatic int
493274880Sbaptvalidate_filename(const char *file)
494274880Sbapt{
495274880Sbapt
496274880Sbapt	if ('.' == file[0] && '/' == file[1])
497274880Sbapt		file += 2;
498274880Sbapt
499294113Sbapt	return ! (strstr(file, "../") || strstr(file, "/..") ||
500294113Sbapt	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
501274880Sbapt}
502274880Sbapt
503274880Sbaptstatic void
504274880Sbaptpg_index(const struct req *req)
505274880Sbapt{
506274880Sbapt
507322249Sbapt	resp_begin_html(200, NULL, NULL);
508307795Sbapt	resp_searchform(req, FOCUS_QUERY);
509307795Sbapt	printf("<p>\n"
510274880Sbapt	       "This web interface is documented in the\n"
511316420Sbapt	       "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
512274880Sbapt	       "manual, and the\n"
513316420Sbapt	       "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
514274880Sbapt	       "manual explains the query syntax.\n"
515307795Sbapt	       "</p>\n",
516307795Sbapt	       scriptname, *scriptname == '\0' ? "" : "/",
517307795Sbapt	       scriptname, *scriptname == '\0' ? "" : "/");
518274880Sbapt	resp_end_html();
519274880Sbapt}
520274880Sbapt
521274880Sbaptstatic void
522274880Sbaptpg_noresult(const struct req *req, const char *msg)
523274880Sbapt{
524322249Sbapt	resp_begin_html(200, NULL, NULL);
525307795Sbapt	resp_searchform(req, FOCUS_QUERY);
526307795Sbapt	puts("<p>");
527274880Sbapt	puts(msg);
528307795Sbapt	puts("</p>");
529274880Sbapt	resp_end_html();
530274880Sbapt}
531274880Sbapt
532274880Sbaptstatic void
533274880Sbaptpg_error_badrequest(const char *msg)
534274880Sbapt{
535274880Sbapt
536322249Sbapt	resp_begin_html(400, "Bad Request", NULL);
537307795Sbapt	puts("<h1>Bad Request</h1>\n"
538307795Sbapt	     "<p>\n");
539274880Sbapt	puts(msg);
540274880Sbapt	printf("Try again from the\n"
541307795Sbapt	       "<a href=\"/%s\">main page</a>.\n"
542307795Sbapt	       "</p>", scriptname);
543274880Sbapt	resp_end_html();
544274880Sbapt}
545274880Sbapt
546274880Sbaptstatic void
547274880Sbaptpg_error_internal(void)
548274880Sbapt{
549322249Sbapt	resp_begin_html(500, "Internal Server Error", NULL);
550307795Sbapt	puts("<p>Internal Server Error</p>");
551274880Sbapt	resp_end_html();
552274880Sbapt}
553274880Sbapt
554274880Sbaptstatic void
555322249Sbaptpg_redirect(const struct req *req, const char *name)
556322249Sbapt{
557322249Sbapt	printf("Status: 303 See Other\r\n"
558322249Sbapt	    "Location: /");
559322249Sbapt	if (*scriptname != '\0')
560322249Sbapt		printf("%s/", scriptname);
561322249Sbapt	if (strcmp(req->q.manpath, req->p[0]))
562322249Sbapt		printf("%s/", req->q.manpath);
563322249Sbapt	if (req->q.arch != NULL)
564322249Sbapt		printf("%s/", req->q.arch);
565322249Sbapt	printf("%s", name);
566322249Sbapt	if (req->q.sec != NULL)
567322249Sbapt		printf(".%s", req->q.sec);
568322249Sbapt	printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
569322249Sbapt}
570322249Sbapt
571322249Sbaptstatic void
572274880Sbaptpg_searchres(const struct req *req, struct manpage *r, size_t sz)
573274880Sbapt{
574274880Sbapt	char		*arch, *archend;
575307795Sbapt	const char	*sec;
576307795Sbapt	size_t		 i, iuse;
577274880Sbapt	int		 archprio, archpriouse;
578274880Sbapt	int		 prio, priouse;
579274880Sbapt
580274880Sbapt	for (i = 0; i < sz; i++) {
581274880Sbapt		if (validate_filename(r[i].file))
582274880Sbapt			continue;
583307795Sbapt		warnx("invalid filename %s in %s database",
584274880Sbapt		    r[i].file, req->q.manpath);
585274880Sbapt		pg_error_internal();
586274880Sbapt		return;
587274880Sbapt	}
588274880Sbapt
589307795Sbapt	if (req->isquery && sz == 1) {
590274880Sbapt		/*
591274880Sbapt		 * If we have just one result, then jump there now
592274880Sbapt		 * without any delay.
593274880Sbapt		 */
594322249Sbapt		printf("Status: 303 See Other\r\n"
595322249Sbapt		    "Location: /");
596322249Sbapt		if (*scriptname != '\0')
597322249Sbapt			printf("%s/", scriptname);
598322249Sbapt		if (strcmp(req->q.manpath, req->p[0]))
599322249Sbapt			printf("%s/", req->q.manpath);
600322249Sbapt		printf("%s\r\n"
601322249Sbapt		    "Content-Type: text/html; charset=utf-8\r\n\r\n",
602322249Sbapt		    r[0].file);
603274880Sbapt		return;
604274880Sbapt	}
605274880Sbapt
606274880Sbapt	/*
607274880Sbapt	 * In man(1) mode, show one of the pages
608274880Sbapt	 * even if more than one is found.
609274880Sbapt	 */
610274880Sbapt
611322249Sbapt	iuse = 0;
612307795Sbapt	if (req->q.equal || sz == 1) {
613307795Sbapt		priouse = 20;
614274880Sbapt		archpriouse = 3;
615274880Sbapt		for (i = 0; i < sz; i++) {
616307795Sbapt			sec = r[i].file;
617307795Sbapt			sec += strcspn(sec, "123456789");
618307795Sbapt			if (sec[0] == '\0')
619274880Sbapt				continue;
620307795Sbapt			prio = sec_prios[sec[0] - '1'];
621307795Sbapt			if (sec[1] != '/')
622307795Sbapt				prio += 10;
623307795Sbapt			if (req->q.arch == NULL) {
624274880Sbapt				archprio =
625307795Sbapt				    ((arch = strchr(sec + 1, '/'))
626307795Sbapt					== NULL) ? 3 :
627307795Sbapt				    ((archend = strchr(arch + 1, '/'))
628307795Sbapt					== NULL) ? 0 :
629274880Sbapt				    strncmp(arch, "amd64/",
630274880Sbapt					archend - arch) ? 2 : 1;
631274880Sbapt				if (archprio < archpriouse) {
632274880Sbapt					archpriouse = archprio;
633274880Sbapt					priouse = prio;
634274880Sbapt					iuse = i;
635274880Sbapt					continue;
636274880Sbapt				}
637274880Sbapt				if (archprio > archpriouse)
638274880Sbapt					continue;
639274880Sbapt			}
640274880Sbapt			if (prio >= priouse)
641274880Sbapt				continue;
642274880Sbapt			priouse = prio;
643274880Sbapt			iuse = i;
644274880Sbapt		}
645322249Sbapt		resp_begin_html(200, NULL, r[iuse].file);
646322249Sbapt	} else
647322249Sbapt		resp_begin_html(200, NULL, NULL);
648322249Sbapt
649322249Sbapt	resp_searchform(req,
650322249Sbapt	    req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
651322249Sbapt
652322249Sbapt	if (sz > 1) {
653322249Sbapt		puts("<table class=\"results\">");
654322249Sbapt		for (i = 0; i < sz; i++) {
655322249Sbapt			printf("  <tr>\n"
656322249Sbapt			       "    <td>"
657322249Sbapt			       "<a class=\"Xr\" href=\"/");
658322249Sbapt			if (*scriptname != '\0')
659322249Sbapt				printf("%s/", scriptname);
660322249Sbapt			if (strcmp(req->q.manpath, req->p[0]))
661322249Sbapt				printf("%s/", req->q.manpath);
662322249Sbapt			printf("%s\">", r[i].file);
663322249Sbapt			html_print(r[i].names);
664322249Sbapt			printf("</a></td>\n"
665322249Sbapt			       "    <td><span class=\"Nd\">");
666322249Sbapt			html_print(r[i].output);
667322249Sbapt			puts("</span></td>\n"
668322249Sbapt			     "  </tr>");
669322249Sbapt		}
670322249Sbapt		puts("</table>");
671322249Sbapt	}
672322249Sbapt
673322249Sbapt	if (req->q.equal || sz == 1) {
674322249Sbapt		puts("<hr>");
675274880Sbapt		resp_show(req, r[iuse].file);
676274880Sbapt	}
677274880Sbapt
678274880Sbapt	resp_end_html();
679274880Sbapt}
680274880Sbapt
681274880Sbaptstatic void
682307795Sbaptresp_catman(const struct req *req, const char *file)
683274880Sbapt{
684274880Sbapt	FILE		*f;
685294113Sbapt	char		*p;
686294113Sbapt	size_t		 sz;
687294113Sbapt	ssize_t		 len;
688274880Sbapt	int		 i;
689274880Sbapt	int		 italic, bold;
690274880Sbapt
691294113Sbapt	if ((f = fopen(file, "r")) == NULL) {
692307795Sbapt		puts("<p>You specified an invalid manual file.</p>");
693274880Sbapt		return;
694274880Sbapt	}
695274880Sbapt
696307795Sbapt	puts("<div class=\"catman\">\n"
697307795Sbapt	     "<pre>");
698274880Sbapt
699294113Sbapt	p = NULL;
700294113Sbapt	sz = 0;
701294113Sbapt
702294113Sbapt	while ((len = getline(&p, &sz, f)) != -1) {
703274880Sbapt		bold = italic = 0;
704294113Sbapt		for (i = 0; i < len - 1; i++) {
705279527Sbapt			/*
706274880Sbapt			 * This means that the catpage is out of state.
707274880Sbapt			 * Ignore it and keep going (although the
708274880Sbapt			 * catpage is bogus).
709274880Sbapt			 */
710274880Sbapt
711274880Sbapt			if ('\b' == p[i] || '\n' == p[i])
712274880Sbapt				continue;
713274880Sbapt
714274880Sbapt			/*
715274880Sbapt			 * Print a regular character.
716274880Sbapt			 * Close out any bold/italic scopes.
717274880Sbapt			 * If we're in back-space mode, make sure we'll
718274880Sbapt			 * have something to enter when we backspace.
719274880Sbapt			 */
720274880Sbapt
721274880Sbapt			if ('\b' != p[i + 1]) {
722274880Sbapt				if (italic)
723307795Sbapt					printf("</i>");
724274880Sbapt				if (bold)
725307795Sbapt					printf("</b>");
726274880Sbapt				italic = bold = 0;
727274880Sbapt				html_putchar(p[i]);
728274880Sbapt				continue;
729294113Sbapt			} else if (i + 2 >= len)
730274880Sbapt				continue;
731274880Sbapt
732274880Sbapt			/* Italic mode. */
733274880Sbapt
734274880Sbapt			if ('_' == p[i]) {
735274880Sbapt				if (bold)
736307795Sbapt					printf("</b>");
737274880Sbapt				if ( ! italic)
738307795Sbapt					printf("<i>");
739274880Sbapt				bold = 0;
740274880Sbapt				italic = 1;
741274880Sbapt				i += 2;
742274880Sbapt				html_putchar(p[i]);
743274880Sbapt				continue;
744274880Sbapt			}
745274880Sbapt
746279527Sbapt			/*
747274880Sbapt			 * Handle funny behaviour troff-isms.
748274880Sbapt			 * These grok'd from the original man2html.c.
749274880Sbapt			 */
750274880Sbapt
751274880Sbapt			if (('+' == p[i] && 'o' == p[i + 2]) ||
752274880Sbapt					('o' == p[i] && '+' == p[i + 2]) ||
753274880Sbapt					('|' == p[i] && '=' == p[i + 2]) ||
754274880Sbapt					('=' == p[i] && '|' == p[i + 2]) ||
755274880Sbapt					('*' == p[i] && '=' == p[i + 2]) ||
756274880Sbapt					('=' == p[i] && '*' == p[i + 2]) ||
757274880Sbapt					('*' == p[i] && '|' == p[i + 2]) ||
758274880Sbapt					('|' == p[i] && '*' == p[i + 2]))  {
759274880Sbapt				if (italic)
760307795Sbapt					printf("</i>");
761274880Sbapt				if (bold)
762307795Sbapt					printf("</b>");
763274880Sbapt				italic = bold = 0;
764274880Sbapt				putchar('*');
765274880Sbapt				i += 2;
766274880Sbapt				continue;
767274880Sbapt			} else if (('|' == p[i] && '-' == p[i + 2]) ||
768274880Sbapt					('-' == p[i] && '|' == p[i + 1]) ||
769274880Sbapt					('+' == p[i] && '-' == p[i + 1]) ||
770274880Sbapt					('-' == p[i] && '+' == p[i + 1]) ||
771274880Sbapt					('+' == p[i] && '|' == p[i + 1]) ||
772274880Sbapt					('|' == p[i] && '+' == p[i + 1]))  {
773274880Sbapt				if (italic)
774307795Sbapt					printf("</i>");
775274880Sbapt				if (bold)
776307795Sbapt					printf("</b>");
777274880Sbapt				italic = bold = 0;
778274880Sbapt				putchar('+');
779274880Sbapt				i += 2;
780274880Sbapt				continue;
781274880Sbapt			}
782274880Sbapt
783274880Sbapt			/* Bold mode. */
784279527Sbapt
785274880Sbapt			if (italic)
786307795Sbapt				printf("</i>");
787274880Sbapt			if ( ! bold)
788307795Sbapt				printf("<b>");
789274880Sbapt			bold = 1;
790274880Sbapt			italic = 0;
791274880Sbapt			i += 2;
792274880Sbapt			html_putchar(p[i]);
793274880Sbapt		}
794274880Sbapt
795279527Sbapt		/*
796274880Sbapt		 * Clean up the last character.
797279527Sbapt		 * We can get to a newline; don't print that.
798274880Sbapt		 */
799274880Sbapt
800274880Sbapt		if (italic)
801307795Sbapt			printf("</i>");
802274880Sbapt		if (bold)
803307795Sbapt			printf("</b>");
804274880Sbapt
805294113Sbapt		if (i == len - 1 && p[i] != '\n')
806274880Sbapt			html_putchar(p[i]);
807274880Sbapt
808274880Sbapt		putchar('\n');
809274880Sbapt	}
810294113Sbapt	free(p);
811274880Sbapt
812307795Sbapt	puts("</pre>\n"
813307795Sbapt	     "</div>");
814274880Sbapt
815274880Sbapt	fclose(f);
816274880Sbapt}
817274880Sbapt
818274880Sbaptstatic void
819307795Sbaptresp_format(const struct req *req, const char *file)
820274880Sbapt{
821294113Sbapt	struct manoutput conf;
822274880Sbapt	struct mparse	*mp;
823294113Sbapt	struct roff_man	*man;
824274880Sbapt	void		*vp;
825274880Sbapt	int		 fd;
826274880Sbapt	int		 usepath;
827274880Sbapt
828274880Sbapt	if (-1 == (fd = open(file, O_RDONLY, 0))) {
829307795Sbapt		puts("<p>You specified an invalid manual file.</p>");
830274880Sbapt		return;
831274880Sbapt	}
832274880Sbapt
833294113Sbapt	mchars_alloc();
834316420Sbapt	mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
835322249Sbapt	    MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, req->q.manpath);
836279527Sbapt	mparse_readfd(mp, fd, file);
837274880Sbapt	close(fd);
838274880Sbapt
839294113Sbapt	memset(&conf, 0, sizeof(conf));
840294113Sbapt	conf.fragment = 1;
841322249Sbapt	conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
842274880Sbapt	usepath = strcmp(req->q.manpath, req->p[0]);
843322249Sbapt	mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
844322249Sbapt	    scriptname, *scriptname == '\0' ? "" : "/",
845307795Sbapt	    usepath ? req->q.manpath : "", usepath ? "/" : "");
846274880Sbapt
847294113Sbapt	mparse_result(mp, &man, NULL);
848294113Sbapt	if (man == NULL) {
849307795Sbapt		warnx("fatal mandoc error: %s/%s", req->q.manpath, file);
850274880Sbapt		pg_error_internal();
851274880Sbapt		mparse_free(mp);
852294113Sbapt		mchars_free();
853274880Sbapt		return;
854274880Sbapt	}
855274880Sbapt
856294113Sbapt	vp = html_alloc(&conf);
857274880Sbapt
858294113Sbapt	if (man->macroset == MACROSET_MDOC) {
859294113Sbapt		mdoc_validate(man);
860294113Sbapt		html_mdoc(vp, man);
861294113Sbapt	} else {
862294113Sbapt		man_validate(man);
863274880Sbapt		html_man(vp, man);
864294113Sbapt	}
865274880Sbapt
866274880Sbapt	html_free(vp);
867274880Sbapt	mparse_free(mp);
868294113Sbapt	mchars_free();
869294113Sbapt	free(conf.man);
870322249Sbapt	free(conf.style);
871274880Sbapt}
872274880Sbapt
873274880Sbaptstatic void
874274880Sbaptresp_show(const struct req *req, const char *file)
875274880Sbapt{
876274880Sbapt
877274880Sbapt	if ('.' == file[0] && '/' == file[1])
878274880Sbapt		file += 2;
879274880Sbapt
880274880Sbapt	if ('c' == *file)
881307795Sbapt		resp_catman(req, file);
882274880Sbapt	else
883307795Sbapt		resp_format(req, file);
884274880Sbapt}
885274880Sbapt
886274880Sbaptstatic void
887274880Sbaptpg_show(struct req *req, const char *fullpath)
888274880Sbapt{
889274880Sbapt	char		*manpath;
890274880Sbapt	const char	*file;
891274880Sbapt
892274880Sbapt	if ((file = strchr(fullpath, '/')) == NULL) {
893274880Sbapt		pg_error_badrequest(
894274880Sbapt		    "You did not specify a page to show.");
895274880Sbapt		return;
896279527Sbapt	}
897274880Sbapt	manpath = mandoc_strndup(fullpath, file - fullpath);
898274880Sbapt	file++;
899274880Sbapt
900274880Sbapt	if ( ! validate_manpath(req, manpath)) {
901274880Sbapt		pg_error_badrequest(
902274880Sbapt		    "You specified an invalid manpath.");
903274880Sbapt		free(manpath);
904274880Sbapt		return;
905274880Sbapt	}
906274880Sbapt
907274880Sbapt	/*
908274880Sbapt	 * Begin by chdir()ing into the manpath.
909274880Sbapt	 * This way we can pick up the database files, which are
910274880Sbapt	 * relative to the manpath root.
911274880Sbapt	 */
912274880Sbapt
913274880Sbapt	if (chdir(manpath) == -1) {
914307795Sbapt		warn("chdir %s", manpath);
915274880Sbapt		pg_error_internal();
916274880Sbapt		free(manpath);
917274880Sbapt		return;
918274880Sbapt	}
919307795Sbapt	free(manpath);
920274880Sbapt
921274880Sbapt	if ( ! validate_filename(file)) {
922274880Sbapt		pg_error_badrequest(
923274880Sbapt		    "You specified an invalid manual file.");
924274880Sbapt		return;
925274880Sbapt	}
926274880Sbapt
927322249Sbapt	resp_begin_html(200, NULL, file);
928307795Sbapt	resp_searchform(req, FOCUS_NONE);
929274880Sbapt	resp_show(req, file);
930274880Sbapt	resp_end_html();
931274880Sbapt}
932274880Sbapt
933274880Sbaptstatic void
934274880Sbaptpg_search(const struct req *req)
935274880Sbapt{
936274880Sbapt	struct mansearch	  search;
937274880Sbapt	struct manpaths		  paths;
938274880Sbapt	struct manpage		 *res;
939275432Sbapt	char			**argv;
940275432Sbapt	char			 *query, *rp, *wp;
941274880Sbapt	size_t			  ressz;
942275432Sbapt	int			  argc;
943274880Sbapt
944274880Sbapt	/*
945274880Sbapt	 * Begin by chdir()ing into the root of the manpath.
946274880Sbapt	 * This way we can pick up the database files, which are
947274880Sbapt	 * relative to the manpath root.
948274880Sbapt	 */
949274880Sbapt
950307795Sbapt	if (chdir(req->q.manpath) == -1) {
951307795Sbapt		warn("chdir %s", req->q.manpath);
952274880Sbapt		pg_error_internal();
953274880Sbapt		return;
954274880Sbapt	}
955274880Sbapt
956274880Sbapt	search.arch = req->q.arch;
957274880Sbapt	search.sec = req->q.sec;
958275432Sbapt	search.outkey = "Nd";
959275432Sbapt	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
960275432Sbapt	search.firstmatch = 1;
961274880Sbapt
962274880Sbapt	paths.sz = 1;
963274880Sbapt	paths.paths = mandoc_malloc(sizeof(char *));
964274880Sbapt	paths.paths[0] = mandoc_strdup(".");
965274880Sbapt
966274880Sbapt	/*
967275432Sbapt	 * Break apart at spaces with backslash-escaping.
968274880Sbapt	 */
969274880Sbapt
970275432Sbapt	argc = 0;
971275432Sbapt	argv = NULL;
972275432Sbapt	rp = query = mandoc_strdup(req->q.query);
973275432Sbapt	for (;;) {
974275432Sbapt		while (isspace((unsigned char)*rp))
975275432Sbapt			rp++;
976275432Sbapt		if (*rp == '\0')
977275432Sbapt			break;
978275432Sbapt		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
979275432Sbapt		argv[argc++] = wp = rp;
980275432Sbapt		for (;;) {
981275432Sbapt			if (isspace((unsigned char)*rp)) {
982275432Sbapt				*wp = '\0';
983275432Sbapt				rp++;
984275432Sbapt				break;
985275432Sbapt			}
986275432Sbapt			if (rp[0] == '\\' && rp[1] != '\0')
987275432Sbapt				rp++;
988275432Sbapt			if (wp != rp)
989275432Sbapt				*wp = *rp;
990275432Sbapt			if (*rp == '\0')
991275432Sbapt				break;
992275432Sbapt			wp++;
993275432Sbapt			rp++;
994275432Sbapt		}
995274880Sbapt	}
996274880Sbapt
997322249Sbapt	res = NULL;
998322249Sbapt	ressz = 0;
999322249Sbapt	if (req->isquery && req->q.equal && argc == 1)
1000322249Sbapt		pg_redirect(req, argv[0]);
1001322249Sbapt	else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1002274880Sbapt		pg_noresult(req, "You entered an invalid query.");
1003322249Sbapt	else if (ressz == 0)
1004274880Sbapt		pg_noresult(req, "No results found.");
1005274880Sbapt	else
1006274880Sbapt		pg_searchres(req, res, ressz);
1007274880Sbapt
1008275432Sbapt	free(query);
1009275432Sbapt	mansearch_free(res, ressz);
1010274880Sbapt	free(paths.paths[0]);
1011274880Sbapt	free(paths.paths);
1012274880Sbapt}
1013274880Sbapt
1014274880Sbaptint
1015274880Sbaptmain(void)
1016274880Sbapt{
1017274880Sbapt	struct req	 req;
1018275432Sbapt	struct itimerval itimer;
1019274880Sbapt	const char	*path;
1020274880Sbapt	const char	*querystring;
1021274880Sbapt	int		 i;
1022274880Sbapt
1023322249Sbapt#if HAVE_PLEDGE
1024322249Sbapt	/*
1025322249Sbapt	 * The "rpath" pledge could be revoked after mparse_readfd()
1026322249Sbapt	 * if the file desciptor to "/footer.html" would be opened
1027322249Sbapt	 * up front, but it's probably not worth the complication
1028322249Sbapt	 * of the code it would cause: it would require scattering
1029322249Sbapt	 * pledge() calls in multiple low-level resp_*() functions.
1030322249Sbapt	 */
1031322249Sbapt
1032322249Sbapt	if (pledge("stdio rpath", NULL) == -1) {
1033322249Sbapt		warn("pledge");
1034322249Sbapt		pg_error_internal();
1035322249Sbapt		return EXIT_FAILURE;
1036322249Sbapt	}
1037322249Sbapt#endif
1038322249Sbapt
1039275432Sbapt	/* Poor man's ReDoS mitigation. */
1040275432Sbapt
1041275432Sbapt	itimer.it_value.tv_sec = 2;
1042275432Sbapt	itimer.it_value.tv_usec = 0;
1043275432Sbapt	itimer.it_interval.tv_sec = 2;
1044275432Sbapt	itimer.it_interval.tv_usec = 0;
1045275432Sbapt	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1046307795Sbapt		warn("setitimer");
1047275432Sbapt		pg_error_internal();
1048294113Sbapt		return EXIT_FAILURE;
1049275432Sbapt	}
1050275432Sbapt
1051274880Sbapt	/*
1052274880Sbapt	 * First we change directory into the MAN_DIR so that
1053274880Sbapt	 * subsequent scanning for manpath directories is rooted
1054274880Sbapt	 * relative to the same position.
1055274880Sbapt	 */
1056274880Sbapt
1057307795Sbapt	if (chdir(MAN_DIR) == -1) {
1058307795Sbapt		warn("MAN_DIR: %s", MAN_DIR);
1059274880Sbapt		pg_error_internal();
1060294113Sbapt		return EXIT_FAILURE;
1061279527Sbapt	}
1062274880Sbapt
1063274880Sbapt	memset(&req, 0, sizeof(struct req));
1064307795Sbapt	req.q.equal = 1;
1065307795Sbapt	parse_manpath_conf(&req);
1066274880Sbapt
1067307795Sbapt	/* Parse the path info and the query string. */
1068274880Sbapt
1069307795Sbapt	if ((path = getenv("PATH_INFO")) == NULL)
1070307795Sbapt		path = "";
1071307795Sbapt	else if (*path == '/')
1072307795Sbapt		path++;
1073274880Sbapt
1074307795Sbapt	if (*path != '\0') {
1075307795Sbapt		parse_path_info(&req, path);
1076322249Sbapt		if (req.q.manpath == NULL || req.q.sec == NULL ||
1077322249Sbapt		    *req.q.query == '\0' || access(path, F_OK) == -1)
1078307795Sbapt			path = "";
1079307795Sbapt	} else if ((querystring = getenv("QUERY_STRING")) != NULL)
1080307795Sbapt		parse_query_string(&req, querystring);
1081307795Sbapt
1082307795Sbapt	/* Validate parsed data and add defaults. */
1083307795Sbapt
1084275432Sbapt	if (req.q.manpath == NULL)
1085275432Sbapt		req.q.manpath = mandoc_strdup(req.p[0]);
1086275432Sbapt	else if ( ! validate_manpath(&req, req.q.manpath)) {
1087274880Sbapt		pg_error_badrequest(
1088274880Sbapt		    "You specified an invalid manpath.");
1089294113Sbapt		return EXIT_FAILURE;
1090274880Sbapt	}
1091274880Sbapt
1092274880Sbapt	if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1093274880Sbapt		pg_error_badrequest(
1094274880Sbapt		    "You specified an invalid architecture.");
1095294113Sbapt		return EXIT_FAILURE;
1096274880Sbapt	}
1097274880Sbapt
1098274880Sbapt	/* Dispatch to the three different pages. */
1099274880Sbapt
1100274880Sbapt	if ('\0' != *path)
1101274880Sbapt		pg_show(&req, path);
1102274880Sbapt	else if (NULL != req.q.query)
1103274880Sbapt		pg_search(&req);
1104274880Sbapt	else
1105274880Sbapt		pg_index(&req);
1106274880Sbapt
1107274880Sbapt	free(req.q.manpath);
1108274880Sbapt	free(req.q.arch);
1109274880Sbapt	free(req.q.sec);
1110274880Sbapt	free(req.q.query);
1111274880Sbapt	for (i = 0; i < (int)req.psz; i++)
1112274880Sbapt		free(req.p[i]);
1113274880Sbapt	free(req.p);
1114294113Sbapt	return EXIT_SUCCESS;
1115274880Sbapt}
1116274880Sbapt
1117274880Sbapt/*
1118307795Sbapt * If PATH_INFO is not a file name, translate it to a query.
1119307795Sbapt */
1120307795Sbaptstatic void
1121307795Sbaptparse_path_info(struct req *req, const char *path)
1122307795Sbapt{
1123307795Sbapt	char	*dir[4];
1124307795Sbapt	int	 i;
1125307795Sbapt
1126307795Sbapt	req->isquery = 0;
1127307795Sbapt	req->q.equal = 1;
1128307795Sbapt	req->q.manpath = mandoc_strdup(path);
1129307795Sbapt	req->q.arch = NULL;
1130307795Sbapt
1131307795Sbapt	/* Mandatory manual page name. */
1132307795Sbapt	if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) {
1133307795Sbapt		req->q.query = req->q.manpath;
1134307795Sbapt		req->q.manpath = NULL;
1135307795Sbapt	} else
1136307795Sbapt		*req->q.query++ = '\0';
1137307795Sbapt
1138307795Sbapt	/* Optional trailing section. */
1139307795Sbapt	if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) {
1140307795Sbapt		if(isdigit((unsigned char)req->q.sec[1])) {
1141307795Sbapt			*req->q.sec++ = '\0';
1142307795Sbapt			req->q.sec = mandoc_strdup(req->q.sec);
1143307795Sbapt		} else
1144307795Sbapt			req->q.sec = NULL;
1145307795Sbapt	}
1146307795Sbapt
1147307795Sbapt	/* Handle the case of name[.section] only. */
1148307795Sbapt	if (req->q.manpath == NULL)
1149307795Sbapt		return;
1150307795Sbapt	req->q.query = mandoc_strdup(req->q.query);
1151307795Sbapt
1152307795Sbapt	/* Split directory components. */
1153307795Sbapt	dir[i = 0] = req->q.manpath;
1154307795Sbapt	while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) {
1155307795Sbapt		if (++i == 3) {
1156307795Sbapt			pg_error_badrequest(
1157307795Sbapt			    "You specified too many directory components.");
1158307795Sbapt			exit(EXIT_FAILURE);
1159307795Sbapt		}
1160307795Sbapt		*dir[i]++ = '\0';
1161307795Sbapt	}
1162307795Sbapt
1163307795Sbapt	/* Optional manpath. */
1164307795Sbapt	if ((i = validate_manpath(req, req->q.manpath)) == 0)
1165307795Sbapt		req->q.manpath = NULL;
1166307795Sbapt	else if (dir[1] == NULL)
1167307795Sbapt		return;
1168307795Sbapt
1169307795Sbapt	/* Optional section. */
1170307795Sbapt	if (strncmp(dir[i], "man", 3) == 0) {
1171307795Sbapt		free(req->q.sec);
1172307795Sbapt		req->q.sec = mandoc_strdup(dir[i++] + 3);
1173307795Sbapt	}
1174307795Sbapt	if (dir[i] == NULL) {
1175307795Sbapt		if (req->q.manpath == NULL)
1176307795Sbapt			free(dir[0]);
1177307795Sbapt		return;
1178307795Sbapt	}
1179307795Sbapt	if (dir[i + 1] != NULL) {
1180307795Sbapt		pg_error_badrequest(
1181307795Sbapt		    "You specified an invalid directory component.");
1182307795Sbapt		exit(EXIT_FAILURE);
1183307795Sbapt	}
1184307795Sbapt
1185307795Sbapt	/* Optional architecture. */
1186307795Sbapt	if (i) {
1187307795Sbapt		req->q.arch = mandoc_strdup(dir[i]);
1188307795Sbapt		if (req->q.manpath == NULL)
1189307795Sbapt			free(dir[0]);
1190307795Sbapt	} else
1191307795Sbapt		req->q.arch = dir[0];
1192307795Sbapt}
1193307795Sbapt
1194307795Sbapt/*
1195274880Sbapt * Scan for indexable paths.
1196274880Sbapt */
1197274880Sbaptstatic void
1198307795Sbaptparse_manpath_conf(struct req *req)
1199274880Sbapt{
1200274880Sbapt	FILE	*fp;
1201274880Sbapt	char	*dp;
1202274880Sbapt	size_t	 dpsz;
1203294113Sbapt	ssize_t	 len;
1204274880Sbapt
1205307795Sbapt	if ((fp = fopen("manpath.conf", "r")) == NULL) {
1206307795Sbapt		warn("%s/manpath.conf", MAN_DIR);
1207274880Sbapt		pg_error_internal();
1208274880Sbapt		exit(EXIT_FAILURE);
1209274880Sbapt	}
1210274880Sbapt
1211294113Sbapt	dp = NULL;
1212294113Sbapt	dpsz = 0;
1213294113Sbapt
1214294113Sbapt	while ((len = getline(&dp, &dpsz, fp)) != -1) {
1215294113Sbapt		if (dp[len - 1] == '\n')
1216294113Sbapt			dp[--len] = '\0';
1217274880Sbapt		req->p = mandoc_realloc(req->p,
1218274880Sbapt		    (req->psz + 1) * sizeof(char *));
1219274880Sbapt		if ( ! validate_urifrag(dp)) {
1220307795Sbapt			warnx("%s/manpath.conf contains "
1221307795Sbapt			    "unsafe path \"%s\"", MAN_DIR, dp);
1222274880Sbapt			pg_error_internal();
1223274880Sbapt			exit(EXIT_FAILURE);
1224274880Sbapt		}
1225307795Sbapt		if (strchr(dp, '/') != NULL) {
1226307795Sbapt			warnx("%s/manpath.conf contains "
1227307795Sbapt			    "path with slash \"%s\"", MAN_DIR, dp);
1228274880Sbapt			pg_error_internal();
1229274880Sbapt			exit(EXIT_FAILURE);
1230274880Sbapt		}
1231274880Sbapt		req->p[req->psz++] = dp;
1232294113Sbapt		dp = NULL;
1233294113Sbapt		dpsz = 0;
1234274880Sbapt	}
1235294113Sbapt	free(dp);
1236274880Sbapt
1237307795Sbapt	if (req->p == NULL) {
1238307795Sbapt		warnx("%s/manpath.conf is empty", MAN_DIR);
1239274880Sbapt		pg_error_internal();
1240274880Sbapt		exit(EXIT_FAILURE);
1241274880Sbapt	}
1242274880Sbapt}
1243