cgi.c revision 279527
1279527Sbapt/*	$Id: cgi.c,v 1.104 2015/02/10 08:05:30 schwarze Exp $ */
2274880Sbapt/*
3274880Sbapt * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4274880Sbapt * Copyright (c) 2014 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 *
10274880Sbapt * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11274880Sbapt * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12274880Sbapt * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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>
24274880Sbapt#include <errno.h>
25274880Sbapt#include <fcntl.h>
26274880Sbapt#include <limits.h>
27274880Sbapt#include <stdint.h>
28274880Sbapt#include <stdio.h>
29274880Sbapt#include <stdlib.h>
30274880Sbapt#include <string.h>
31274880Sbapt#include <unistd.h>
32274880Sbapt
33274880Sbapt#include "mandoc.h"
34274880Sbapt#include "mandoc_aux.h"
35274880Sbapt#include "main.h"
36274880Sbapt#include "manpath.h"
37274880Sbapt#include "mansearch.h"
38274880Sbapt#include "cgi.h"
39274880Sbapt
40274880Sbapt/*
41274880Sbapt * A query as passed to the search function.
42274880Sbapt */
43274880Sbaptstruct	query {
44274880Sbapt	char		*manpath; /* desired manual directory */
45274880Sbapt	char		*arch; /* architecture */
46274880Sbapt	char		*sec; /* manual section */
47274880Sbapt	char		*query; /* unparsed query expression */
48274880Sbapt	int		 equal; /* match whole names, not substrings */
49274880Sbapt};
50274880Sbapt
51274880Sbaptstruct	req {
52274880Sbapt	struct query	  q;
53274880Sbapt	char		**p; /* array of available manpaths */
54274880Sbapt	size_t		  psz; /* number of available manpaths */
55274880Sbapt};
56274880Sbapt
57274880Sbaptstatic	void		 catman(const struct req *, const char *);
58274880Sbaptstatic	void		 format(const struct req *, const char *);
59274880Sbaptstatic	void		 html_print(const char *);
60274880Sbaptstatic	void		 html_putchar(char);
61279527Sbaptstatic	int		 http_decode(char *);
62274880Sbaptstatic	void		 http_parse(struct req *, const char *);
63274880Sbaptstatic	void		 http_print(const char *);
64279527Sbaptstatic	void		 http_putchar(char);
65274880Sbaptstatic	void		 http_printquery(const struct req *, const char *);
66274880Sbaptstatic	void		 pathgen(struct req *);
67274880Sbaptstatic	void		 pg_error_badrequest(const char *);
68274880Sbaptstatic	void		 pg_error_internal(void);
69274880Sbaptstatic	void		 pg_index(const struct req *);
70274880Sbaptstatic	void		 pg_noresult(const struct req *, const char *);
71274880Sbaptstatic	void		 pg_search(const struct req *);
72274880Sbaptstatic	void		 pg_searchres(const struct req *,
73274880Sbapt				struct manpage *, size_t);
74274880Sbaptstatic	void		 pg_show(struct req *, const char *);
75274880Sbaptstatic	void		 resp_begin_html(int, const char *);
76274880Sbaptstatic	void		 resp_begin_http(int, const char *);
77274880Sbaptstatic	void		 resp_end_html(void);
78274880Sbaptstatic	void		 resp_searchform(const struct req *);
79274880Sbaptstatic	void		 resp_show(const struct req *, const char *);
80274880Sbaptstatic	void		 set_query_attr(char **, char **);
81274880Sbaptstatic	int		 validate_filename(const char *);
82274880Sbaptstatic	int		 validate_manpath(const struct req *, const char *);
83274880Sbaptstatic	int		 validate_urifrag(const char *);
84274880Sbapt
85274880Sbaptstatic	const char	 *scriptname; /* CGI script name */
86274880Sbapt
87274880Sbaptstatic	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
88274880Sbaptstatic	const char *const sec_numbers[] = {
89274880Sbapt    "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
90274880Sbapt};
91274880Sbaptstatic	const char *const sec_names[] = {
92274880Sbapt    "All Sections",
93274880Sbapt    "1 - General Commands",
94274880Sbapt    "2 - System Calls",
95275432Sbapt    "3 - Library Functions",
96275432Sbapt    "3p - Perl Library",
97275432Sbapt    "4 - Device Drivers",
98274880Sbapt    "5 - File Formats",
99274880Sbapt    "6 - Games",
100275432Sbapt    "7 - Miscellaneous Information",
101275432Sbapt    "8 - System Manager\'s Manual",
102275432Sbapt    "9 - Kernel Developer\'s Manual"
103274880Sbapt};
104274880Sbaptstatic	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
105274880Sbapt
106274880Sbaptstatic	const char *const arch_names[] = {
107274880Sbapt    "amd64",       "alpha",       "armish",      "armv7",
108274880Sbapt    "aviion",      "hppa",        "hppa64",      "i386",
109274880Sbapt    "ia64",        "landisk",     "loongson",    "luna88k",
110274880Sbapt    "macppc",      "mips64",      "octeon",      "sgi",
111274880Sbapt    "socppc",      "solbourne",   "sparc",       "sparc64",
112274880Sbapt    "vax",         "zaurus",
113274880Sbapt    "amiga",       "arc",         "arm32",       "atari",
114274880Sbapt    "beagle",      "cats",        "hp300",       "mac68k",
115274880Sbapt    "mvme68k",     "mvme88k",     "mvmeppc",     "palm",
116274880Sbapt    "pc532",       "pegasos",     "pmax",        "powerpc",
117274880Sbapt    "sun3",        "wgrisc",      "x68k"
118274880Sbapt};
119274880Sbaptstatic	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
120274880Sbapt
121274880Sbapt/*
122274880Sbapt * Print a character, escaping HTML along the way.
123274880Sbapt * This will pass non-ASCII straight to output: be warned!
124274880Sbapt */
125274880Sbaptstatic void
126274880Sbapthtml_putchar(char c)
127274880Sbapt{
128274880Sbapt
129274880Sbapt	switch (c) {
130274880Sbapt	case ('"'):
131274880Sbapt		printf("&quote;");
132274880Sbapt		break;
133274880Sbapt	case ('&'):
134274880Sbapt		printf("&amp;");
135274880Sbapt		break;
136274880Sbapt	case ('>'):
137274880Sbapt		printf("&gt;");
138274880Sbapt		break;
139274880Sbapt	case ('<'):
140274880Sbapt		printf("&lt;");
141274880Sbapt		break;
142274880Sbapt	default:
143274880Sbapt		putchar((unsigned char)c);
144274880Sbapt		break;
145274880Sbapt	}
146274880Sbapt}
147274880Sbapt
148274880Sbaptstatic void
149274880Sbapthttp_printquery(const struct req *req, const char *sep)
150274880Sbapt{
151274880Sbapt
152274880Sbapt	if (NULL != req->q.query) {
153274880Sbapt		printf("query=");
154274880Sbapt		http_print(req->q.query);
155274880Sbapt	}
156274880Sbapt	if (0 == req->q.equal)
157274880Sbapt		printf("%sapropos=1", sep);
158274880Sbapt	if (NULL != req->q.sec) {
159274880Sbapt		printf("%ssec=", sep);
160274880Sbapt		http_print(req->q.sec);
161274880Sbapt	}
162274880Sbapt	if (NULL != req->q.arch) {
163274880Sbapt		printf("%sarch=", sep);
164274880Sbapt		http_print(req->q.arch);
165274880Sbapt	}
166275432Sbapt	if (strcmp(req->q.manpath, req->p[0])) {
167274880Sbapt		printf("%smanpath=", sep);
168274880Sbapt		http_print(req->q.manpath);
169274880Sbapt	}
170274880Sbapt}
171274880Sbapt
172274880Sbaptstatic void
173274880Sbapthttp_print(const char *p)
174274880Sbapt{
175274880Sbapt
176274880Sbapt	if (NULL == p)
177274880Sbapt		return;
178274880Sbapt	while ('\0' != *p)
179274880Sbapt		http_putchar(*p++);
180274880Sbapt}
181274880Sbapt
182274880Sbapt/*
183274880Sbapt * Call through to html_putchar().
184274880Sbapt * Accepts NULL strings.
185274880Sbapt */
186274880Sbaptstatic void
187274880Sbapthtml_print(const char *p)
188274880Sbapt{
189279527Sbapt
190274880Sbapt	if (NULL == p)
191274880Sbapt		return;
192274880Sbapt	while ('\0' != *p)
193274880Sbapt		html_putchar(*p++);
194274880Sbapt}
195274880Sbapt
196274880Sbapt/*
197274880Sbapt * Transfer the responsibility for the allocated string *val
198274880Sbapt * to the query structure.
199274880Sbapt */
200274880Sbaptstatic void
201274880Sbaptset_query_attr(char **attr, char **val)
202274880Sbapt{
203274880Sbapt
204274880Sbapt	free(*attr);
205274880Sbapt	if (**val == '\0') {
206274880Sbapt		*attr = NULL;
207274880Sbapt		free(*val);
208274880Sbapt	} else
209274880Sbapt		*attr = *val;
210274880Sbapt	*val = NULL;
211274880Sbapt}
212274880Sbapt
213274880Sbapt/*
214274880Sbapt * Parse the QUERY_STRING for key-value pairs
215274880Sbapt * and store the values into the query structure.
216274880Sbapt */
217274880Sbaptstatic void
218274880Sbapthttp_parse(struct req *req, const char *qs)
219274880Sbapt{
220274880Sbapt	char		*key, *val;
221274880Sbapt	size_t		 keysz, valsz;
222274880Sbapt
223274880Sbapt	req->q.manpath	= NULL;
224274880Sbapt	req->q.arch	= NULL;
225274880Sbapt	req->q.sec	= NULL;
226274880Sbapt	req->q.query	= NULL;
227274880Sbapt	req->q.equal	= 1;
228274880Sbapt
229274880Sbapt	key = val = NULL;
230274880Sbapt	while (*qs != '\0') {
231274880Sbapt
232274880Sbapt		/* Parse one key. */
233274880Sbapt
234274880Sbapt		keysz = strcspn(qs, "=;&");
235274880Sbapt		key = mandoc_strndup(qs, keysz);
236274880Sbapt		qs += keysz;
237274880Sbapt		if (*qs != '=')
238274880Sbapt			goto next;
239274880Sbapt
240274880Sbapt		/* Parse one value. */
241274880Sbapt
242274880Sbapt		valsz = strcspn(++qs, ";&");
243274880Sbapt		val = mandoc_strndup(qs, valsz);
244274880Sbapt		qs += valsz;
245274880Sbapt
246274880Sbapt		/* Decode and catch encoding errors. */
247274880Sbapt
248274880Sbapt		if ( ! (http_decode(key) && http_decode(val)))
249274880Sbapt			goto next;
250274880Sbapt
251274880Sbapt		/* Handle key-value pairs. */
252274880Sbapt
253274880Sbapt		if ( ! strcmp(key, "query"))
254274880Sbapt			set_query_attr(&req->q.query, &val);
255274880Sbapt
256274880Sbapt		else if ( ! strcmp(key, "apropos"))
257274880Sbapt			req->q.equal = !strcmp(val, "0");
258274880Sbapt
259274880Sbapt		else if ( ! strcmp(key, "manpath")) {
260274880Sbapt#ifdef COMPAT_OLDURI
261274880Sbapt			if ( ! strncmp(val, "OpenBSD ", 8)) {
262274880Sbapt				val[7] = '-';
263274880Sbapt				if ('C' == val[8])
264274880Sbapt					val[8] = 'c';
265274880Sbapt			}
266274880Sbapt#endif
267274880Sbapt			set_query_attr(&req->q.manpath, &val);
268274880Sbapt		}
269274880Sbapt
270274880Sbapt		else if ( ! (strcmp(key, "sec")
271274880Sbapt#ifdef COMPAT_OLDURI
272274880Sbapt		    && strcmp(key, "sektion")
273274880Sbapt#endif
274274880Sbapt		    )) {
275274880Sbapt			if ( ! strcmp(val, "0"))
276274880Sbapt				*val = '\0';
277274880Sbapt			set_query_attr(&req->q.sec, &val);
278274880Sbapt		}
279274880Sbapt
280274880Sbapt		else if ( ! strcmp(key, "arch")) {
281274880Sbapt			if ( ! strcmp(val, "default"))
282274880Sbapt				*val = '\0';
283274880Sbapt			set_query_attr(&req->q.arch, &val);
284274880Sbapt		}
285274880Sbapt
286274880Sbapt		/*
287274880Sbapt		 * The key must be freed in any case.
288274880Sbapt		 * The val may have been handed over to the query
289274880Sbapt		 * structure, in which case it is now NULL.
290274880Sbapt		 */
291274880Sbaptnext:
292274880Sbapt		free(key);
293274880Sbapt		key = NULL;
294274880Sbapt		free(val);
295274880Sbapt		val = NULL;
296274880Sbapt
297274880Sbapt		if (*qs != '\0')
298274880Sbapt			qs++;
299274880Sbapt	}
300274880Sbapt}
301274880Sbapt
302274880Sbaptstatic void
303274880Sbapthttp_putchar(char c)
304274880Sbapt{
305274880Sbapt
306274880Sbapt	if (isalnum((unsigned char)c)) {
307274880Sbapt		putchar((unsigned char)c);
308274880Sbapt		return;
309274880Sbapt	} else if (' ' == c) {
310274880Sbapt		putchar('+');
311274880Sbapt		return;
312274880Sbapt	}
313274880Sbapt	printf("%%%.2x", c);
314274880Sbapt}
315274880Sbapt
316274880Sbapt/*
317274880Sbapt * HTTP-decode a string.  The standard explanation is that this turns
318274880Sbapt * "%4e+foo" into "n foo" in the regular way.  This is done in-place
319274880Sbapt * over the allocated string.
320274880Sbapt */
321274880Sbaptstatic int
322274880Sbapthttp_decode(char *p)
323274880Sbapt{
324274880Sbapt	char             hex[3];
325274880Sbapt	char		*q;
326274880Sbapt	int              c;
327274880Sbapt
328274880Sbapt	hex[2] = '\0';
329274880Sbapt
330274880Sbapt	q = p;
331274880Sbapt	for ( ; '\0' != *p; p++, q++) {
332274880Sbapt		if ('%' == *p) {
333274880Sbapt			if ('\0' == (hex[0] = *(p + 1)))
334274880Sbapt				return(0);
335274880Sbapt			if ('\0' == (hex[1] = *(p + 2)))
336274880Sbapt				return(0);
337274880Sbapt			if (1 != sscanf(hex, "%x", &c))
338274880Sbapt				return(0);
339274880Sbapt			if ('\0' == c)
340274880Sbapt				return(0);
341274880Sbapt
342274880Sbapt			*q = (char)c;
343274880Sbapt			p += 2;
344274880Sbapt		} else
345274880Sbapt			*q = '+' == *p ? ' ' : *p;
346274880Sbapt	}
347274880Sbapt
348274880Sbapt	*q = '\0';
349274880Sbapt	return(1);
350274880Sbapt}
351274880Sbapt
352274880Sbaptstatic void
353274880Sbaptresp_begin_http(int code, const char *msg)
354274880Sbapt{
355274880Sbapt
356274880Sbapt	if (200 != code)
357274880Sbapt		printf("Status: %d %s\r\n", code, msg);
358274880Sbapt
359274880Sbapt	printf("Content-Type: text/html; charset=utf-8\r\n"
360274880Sbapt	     "Cache-Control: no-cache\r\n"
361274880Sbapt	     "Pragma: no-cache\r\n"
362274880Sbapt	     "\r\n");
363274880Sbapt
364274880Sbapt	fflush(stdout);
365274880Sbapt}
366274880Sbapt
367274880Sbaptstatic void
368274880Sbaptresp_begin_html(int code, const char *msg)
369274880Sbapt{
370274880Sbapt
371274880Sbapt	resp_begin_http(code, msg);
372274880Sbapt
373275432Sbapt	printf("<!DOCTYPE html>\n"
374274880Sbapt	       "<HTML>\n"
375274880Sbapt	       "<HEAD>\n"
376275432Sbapt	       "<META CHARSET=\"UTF-8\" />\n"
377274880Sbapt	       "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
378274880Sbapt	       " TYPE=\"text/css\" media=\"all\">\n"
379274880Sbapt	       "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
380274880Sbapt	       " TYPE=\"text/css\" media=\"all\">\n"
381274880Sbapt	       "<TITLE>%s</TITLE>\n"
382274880Sbapt	       "</HEAD>\n"
383274880Sbapt	       "<BODY>\n"
384274880Sbapt	       "<!-- Begin page content. //-->\n",
385274880Sbapt	       CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
386274880Sbapt}
387274880Sbapt
388274880Sbaptstatic void
389274880Sbaptresp_end_html(void)
390274880Sbapt{
391274880Sbapt
392274880Sbapt	puts("</BODY>\n"
393274880Sbapt	     "</HTML>");
394274880Sbapt}
395274880Sbapt
396274880Sbaptstatic void
397274880Sbaptresp_searchform(const struct req *req)
398274880Sbapt{
399274880Sbapt	int		 i;
400274880Sbapt
401274880Sbapt	puts(CUSTOMIZE_BEGIN);
402274880Sbapt	puts("<!-- Begin search form. //-->");
403274880Sbapt	printf("<DIV ID=\"mancgi\">\n"
404274880Sbapt	       "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
405274880Sbapt	       "<FIELDSET>\n"
406274880Sbapt	       "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
407274880Sbapt	       scriptname);
408274880Sbapt
409274880Sbapt	/* Write query input box. */
410274880Sbapt
411274880Sbapt	printf(	"<TABLE><TR><TD>\n"
412274880Sbapt		"<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
413274880Sbapt	if (NULL != req->q.query)
414274880Sbapt		html_print(req->q.query);
415274880Sbapt	puts("\" SIZE=\"40\">");
416274880Sbapt
417274880Sbapt	/* Write submission and reset buttons. */
418274880Sbapt
419274880Sbapt	printf(	"<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
420274880Sbapt		"<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
421274880Sbapt
422274880Sbapt	/* Write show radio button */
423274880Sbapt
424274880Sbapt	printf(	"</TD><TD>\n"
425274880Sbapt		"<INPUT TYPE=\"radio\" ");
426274880Sbapt	if (req->q.equal)
427274880Sbapt		printf("CHECKED=\"checked\" ");
428274880Sbapt	printf(	"NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
429274880Sbapt		"<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
430274880Sbapt
431274880Sbapt	/* Write section selector. */
432274880Sbapt
433274880Sbapt	puts(	"</TD></TR><TR><TD>\n"
434274880Sbapt		"<SELECT NAME=\"sec\">");
435274880Sbapt	for (i = 0; i < sec_MAX; i++) {
436274880Sbapt		printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
437274880Sbapt		if (NULL != req->q.sec &&
438274880Sbapt		    0 == strcmp(sec_numbers[i], req->q.sec))
439274880Sbapt			printf(" SELECTED=\"selected\"");
440274880Sbapt		printf(">%s</OPTION>\n", sec_names[i]);
441274880Sbapt	}
442274880Sbapt	puts("</SELECT>");
443274880Sbapt
444274880Sbapt	/* Write architecture selector. */
445274880Sbapt
446274880Sbapt	printf(	"<SELECT NAME=\"arch\">\n"
447274880Sbapt		"<OPTION VALUE=\"default\"");
448274880Sbapt	if (NULL == req->q.arch)
449274880Sbapt		printf(" SELECTED=\"selected\"");
450274880Sbapt	puts(">All Architectures</OPTION>");
451274880Sbapt	for (i = 0; i < arch_MAX; i++) {
452274880Sbapt		printf("<OPTION VALUE=\"%s\"", arch_names[i]);
453274880Sbapt		if (NULL != req->q.arch &&
454274880Sbapt		    0 == strcmp(arch_names[i], req->q.arch))
455274880Sbapt			printf(" SELECTED=\"selected\"");
456274880Sbapt		printf(">%s</OPTION>\n", arch_names[i]);
457274880Sbapt	}
458274880Sbapt	puts("</SELECT>");
459274880Sbapt
460274880Sbapt	/* Write manpath selector. */
461274880Sbapt
462274880Sbapt	if (req->psz > 1) {
463274880Sbapt		puts("<SELECT NAME=\"manpath\">");
464274880Sbapt		for (i = 0; i < (int)req->psz; i++) {
465274880Sbapt			printf("<OPTION ");
466275432Sbapt			if (strcmp(req->q.manpath, req->p[i]) == 0)
467274880Sbapt				printf("SELECTED=\"selected\" ");
468274880Sbapt			printf("VALUE=\"");
469274880Sbapt			html_print(req->p[i]);
470274880Sbapt			printf("\">");
471274880Sbapt			html_print(req->p[i]);
472274880Sbapt			puts("</OPTION>");
473274880Sbapt		}
474274880Sbapt		puts("</SELECT>");
475274880Sbapt	}
476274880Sbapt
477274880Sbapt	/* Write search radio button */
478274880Sbapt
479274880Sbapt	printf(	"</TD><TD>\n"
480274880Sbapt		"<INPUT TYPE=\"radio\" ");
481274880Sbapt	if (0 == req->q.equal)
482274880Sbapt		printf("CHECKED=\"checked\" ");
483274880Sbapt	printf(	"NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
484274880Sbapt		"<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
485274880Sbapt
486274880Sbapt	puts("</TD></TR></TABLE>\n"
487274880Sbapt	     "</FIELDSET>\n"
488274880Sbapt	     "</FORM>\n"
489274880Sbapt	     "</DIV>");
490274880Sbapt	puts("<!-- End search form. //-->");
491274880Sbapt}
492274880Sbapt
493274880Sbaptstatic int
494274880Sbaptvalidate_urifrag(const char *frag)
495274880Sbapt{
496274880Sbapt
497274880Sbapt	while ('\0' != *frag) {
498274880Sbapt		if ( ! (isalnum((unsigned char)*frag) ||
499274880Sbapt		    '-' == *frag || '.' == *frag ||
500274880Sbapt		    '/' == *frag || '_' == *frag))
501274880Sbapt			return(0);
502274880Sbapt		frag++;
503274880Sbapt	}
504274880Sbapt	return(1);
505274880Sbapt}
506274880Sbapt
507274880Sbaptstatic int
508274880Sbaptvalidate_manpath(const struct req *req, const char* manpath)
509274880Sbapt{
510274880Sbapt	size_t	 i;
511274880Sbapt
512274880Sbapt	if ( ! strcmp(manpath, "mandoc"))
513274880Sbapt		return(1);
514274880Sbapt
515274880Sbapt	for (i = 0; i < req->psz; i++)
516274880Sbapt		if ( ! strcmp(manpath, req->p[i]))
517274880Sbapt			return(1);
518274880Sbapt
519274880Sbapt	return(0);
520274880Sbapt}
521274880Sbapt
522274880Sbaptstatic int
523274880Sbaptvalidate_filename(const char *file)
524274880Sbapt{
525274880Sbapt
526274880Sbapt	if ('.' == file[0] && '/' == file[1])
527274880Sbapt		file += 2;
528274880Sbapt
529274880Sbapt	return ( ! (strstr(file, "../") || strstr(file, "/..") ||
530274880Sbapt	    (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
531274880Sbapt}
532274880Sbapt
533274880Sbaptstatic void
534274880Sbaptpg_index(const struct req *req)
535274880Sbapt{
536274880Sbapt
537274880Sbapt	resp_begin_html(200, NULL);
538274880Sbapt	resp_searchform(req);
539274880Sbapt	printf("<P>\n"
540274880Sbapt	       "This web interface is documented in the\n"
541274880Sbapt	       "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n"
542274880Sbapt	       "manual, and the\n"
543274880Sbapt	       "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n"
544274880Sbapt	       "manual explains the query syntax.\n"
545274880Sbapt	       "</P>\n",
546274880Sbapt	       scriptname, scriptname);
547274880Sbapt	resp_end_html();
548274880Sbapt}
549274880Sbapt
550274880Sbaptstatic void
551274880Sbaptpg_noresult(const struct req *req, const char *msg)
552274880Sbapt{
553274880Sbapt	resp_begin_html(200, NULL);
554274880Sbapt	resp_searchform(req);
555274880Sbapt	puts("<P>");
556274880Sbapt	puts(msg);
557274880Sbapt	puts("</P>");
558274880Sbapt	resp_end_html();
559274880Sbapt}
560274880Sbapt
561274880Sbaptstatic void
562274880Sbaptpg_error_badrequest(const char *msg)
563274880Sbapt{
564274880Sbapt
565274880Sbapt	resp_begin_html(400, "Bad Request");
566274880Sbapt	puts("<H1>Bad Request</H1>\n"
567274880Sbapt	     "<P>\n");
568274880Sbapt	puts(msg);
569274880Sbapt	printf("Try again from the\n"
570274880Sbapt	       "<A HREF=\"%s\">main page</A>.\n"
571274880Sbapt	       "</P>", scriptname);
572274880Sbapt	resp_end_html();
573274880Sbapt}
574274880Sbapt
575274880Sbaptstatic void
576274880Sbaptpg_error_internal(void)
577274880Sbapt{
578274880Sbapt	resp_begin_html(500, "Internal Server Error");
579274880Sbapt	puts("<P>Internal Server Error</P>");
580274880Sbapt	resp_end_html();
581274880Sbapt}
582274880Sbapt
583274880Sbaptstatic void
584274880Sbaptpg_searchres(const struct req *req, struct manpage *r, size_t sz)
585274880Sbapt{
586274880Sbapt	char		*arch, *archend;
587274880Sbapt	size_t		 i, iuse, isec;
588274880Sbapt	int		 archprio, archpriouse;
589274880Sbapt	int		 prio, priouse;
590274880Sbapt	char		 sec;
591274880Sbapt
592274880Sbapt	for (i = 0; i < sz; i++) {
593274880Sbapt		if (validate_filename(r[i].file))
594274880Sbapt			continue;
595274880Sbapt		fprintf(stderr, "invalid filename %s in %s database\n",
596274880Sbapt		    r[i].file, req->q.manpath);
597274880Sbapt		pg_error_internal();
598274880Sbapt		return;
599274880Sbapt	}
600274880Sbapt
601274880Sbapt	if (1 == sz) {
602274880Sbapt		/*
603274880Sbapt		 * If we have just one result, then jump there now
604274880Sbapt		 * without any delay.
605274880Sbapt		 */
606274880Sbapt		printf("Status: 303 See Other\r\n");
607274880Sbapt		printf("Location: http://%s%s/%s/%s?",
608274880Sbapt		    HTTP_HOST, scriptname, req->q.manpath, r[0].file);
609274880Sbapt		http_printquery(req, "&");
610274880Sbapt		printf("\r\n"
611274880Sbapt		     "Content-Type: text/html; charset=utf-8\r\n"
612274880Sbapt		     "\r\n");
613274880Sbapt		return;
614274880Sbapt	}
615274880Sbapt
616274880Sbapt	resp_begin_html(200, NULL);
617274880Sbapt	resp_searchform(req);
618274880Sbapt	puts("<DIV CLASS=\"results\">");
619274880Sbapt	puts("<TABLE>");
620274880Sbapt
621274880Sbapt	for (i = 0; i < sz; i++) {
622274880Sbapt		printf("<TR>\n"
623274880Sbapt		       "<TD CLASS=\"title\">\n"
624279527Sbapt		       "<A HREF=\"%s/%s/%s?",
625274880Sbapt		    scriptname, req->q.manpath, r[i].file);
626274880Sbapt		http_printquery(req, "&amp;");
627274880Sbapt		printf("\">");
628274880Sbapt		html_print(r[i].names);
629274880Sbapt		printf("</A>\n"
630274880Sbapt		       "</TD>\n"
631274880Sbapt		       "<TD CLASS=\"desc\">");
632274880Sbapt		html_print(r[i].output);
633274880Sbapt		puts("</TD>\n"
634274880Sbapt		     "</TR>");
635274880Sbapt	}
636274880Sbapt
637274880Sbapt	puts("</TABLE>\n"
638274880Sbapt	     "</DIV>");
639274880Sbapt
640274880Sbapt	/*
641274880Sbapt	 * In man(1) mode, show one of the pages
642274880Sbapt	 * even if more than one is found.
643274880Sbapt	 */
644274880Sbapt
645274880Sbapt	if (req->q.equal) {
646274880Sbapt		puts("<HR>");
647274880Sbapt		iuse = 0;
648274880Sbapt		priouse = 10;
649274880Sbapt		archpriouse = 3;
650274880Sbapt		for (i = 0; i < sz; i++) {
651274880Sbapt			isec = strcspn(r[i].file, "123456789");
652274880Sbapt			sec = r[i].file[isec];
653274880Sbapt			if ('\0' == sec)
654274880Sbapt				continue;
655274880Sbapt			prio = sec_prios[sec - '1'];
656274880Sbapt			if (NULL == req->q.arch) {
657274880Sbapt				archprio =
658274880Sbapt				    (NULL == (arch = strchr(
659274880Sbapt					r[i].file + isec, '/'))) ? 3 :
660274880Sbapt				    (NULL == (archend = strchr(
661274880Sbapt					arch + 1, '/'))) ? 0 :
662274880Sbapt				    strncmp(arch, "amd64/",
663274880Sbapt					archend - arch) ? 2 : 1;
664274880Sbapt				if (archprio < archpriouse) {
665274880Sbapt					archpriouse = archprio;
666274880Sbapt					priouse = prio;
667274880Sbapt					iuse = i;
668274880Sbapt					continue;
669274880Sbapt				}
670274880Sbapt				if (archprio > archpriouse)
671274880Sbapt					continue;
672274880Sbapt			}
673274880Sbapt			if (prio >= priouse)
674274880Sbapt				continue;
675274880Sbapt			priouse = prio;
676274880Sbapt			iuse = i;
677274880Sbapt		}
678274880Sbapt		resp_show(req, r[iuse].file);
679274880Sbapt	}
680274880Sbapt
681274880Sbapt	resp_end_html();
682274880Sbapt}
683274880Sbapt
684274880Sbaptstatic void
685274880Sbaptcatman(const struct req *req, const char *file)
686274880Sbapt{
687274880Sbapt	FILE		*f;
688274880Sbapt	size_t		 len;
689274880Sbapt	int		 i;
690274880Sbapt	char		*p;
691274880Sbapt	int		 italic, bold;
692274880Sbapt
693274880Sbapt	if (NULL == (f = fopen(file, "r"))) {
694274880Sbapt		puts("<P>You specified an invalid manual file.</P>");
695274880Sbapt		return;
696274880Sbapt	}
697274880Sbapt
698274880Sbapt	puts("<DIV CLASS=\"catman\">\n"
699274880Sbapt	     "<PRE>");
700274880Sbapt
701274880Sbapt	while (NULL != (p = fgetln(f, &len))) {
702274880Sbapt		bold = italic = 0;
703274880Sbapt		for (i = 0; i < (int)len - 1; i++) {
704279527Sbapt			/*
705274880Sbapt			 * This means that the catpage is out of state.
706274880Sbapt			 * Ignore it and keep going (although the
707274880Sbapt			 * catpage is bogus).
708274880Sbapt			 */
709274880Sbapt
710274880Sbapt			if ('\b' == p[i] || '\n' == p[i])
711274880Sbapt				continue;
712274880Sbapt
713274880Sbapt			/*
714274880Sbapt			 * Print a regular character.
715274880Sbapt			 * Close out any bold/italic scopes.
716274880Sbapt			 * If we're in back-space mode, make sure we'll
717274880Sbapt			 * have something to enter when we backspace.
718274880Sbapt			 */
719274880Sbapt
720274880Sbapt			if ('\b' != p[i + 1]) {
721274880Sbapt				if (italic)
722274880Sbapt					printf("</I>");
723274880Sbapt				if (bold)
724274880Sbapt					printf("</B>");
725274880Sbapt				italic = bold = 0;
726274880Sbapt				html_putchar(p[i]);
727274880Sbapt				continue;
728274880Sbapt			} else if (i + 2 >= (int)len)
729274880Sbapt				continue;
730274880Sbapt
731274880Sbapt			/* Italic mode. */
732274880Sbapt
733274880Sbapt			if ('_' == p[i]) {
734274880Sbapt				if (bold)
735274880Sbapt					printf("</B>");
736274880Sbapt				if ( ! italic)
737274880Sbapt					printf("<I>");
738274880Sbapt				bold = 0;
739274880Sbapt				italic = 1;
740274880Sbapt				i += 2;
741274880Sbapt				html_putchar(p[i]);
742274880Sbapt				continue;
743274880Sbapt			}
744274880Sbapt
745279527Sbapt			/*
746274880Sbapt			 * Handle funny behaviour troff-isms.
747274880Sbapt			 * These grok'd from the original man2html.c.
748274880Sbapt			 */
749274880Sbapt
750274880Sbapt			if (('+' == p[i] && 'o' == p[i + 2]) ||
751274880Sbapt					('o' == p[i] && '+' == p[i + 2]) ||
752274880Sbapt					('|' == 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				if (italic)
759274880Sbapt					printf("</I>");
760274880Sbapt				if (bold)
761274880Sbapt					printf("</B>");
762274880Sbapt				italic = bold = 0;
763274880Sbapt				putchar('*');
764274880Sbapt				i += 2;
765274880Sbapt				continue;
766274880Sbapt			} else if (('|' == p[i] && '-' == p[i + 2]) ||
767274880Sbapt					('-' == p[i] && '|' == p[i + 1]) ||
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				if (italic)
773274880Sbapt					printf("</I>");
774274880Sbapt				if (bold)
775274880Sbapt					printf("</B>");
776274880Sbapt				italic = bold = 0;
777274880Sbapt				putchar('+');
778274880Sbapt				i += 2;
779274880Sbapt				continue;
780274880Sbapt			}
781274880Sbapt
782274880Sbapt			/* Bold mode. */
783279527Sbapt
784274880Sbapt			if (italic)
785274880Sbapt				printf("</I>");
786274880Sbapt			if ( ! bold)
787274880Sbapt				printf("<B>");
788274880Sbapt			bold = 1;
789274880Sbapt			italic = 0;
790274880Sbapt			i += 2;
791274880Sbapt			html_putchar(p[i]);
792274880Sbapt		}
793274880Sbapt
794279527Sbapt		/*
795274880Sbapt		 * Clean up the last character.
796279527Sbapt		 * We can get to a newline; don't print that.
797274880Sbapt		 */
798274880Sbapt
799274880Sbapt		if (italic)
800274880Sbapt			printf("</I>");
801274880Sbapt		if (bold)
802274880Sbapt			printf("</B>");
803274880Sbapt
804274880Sbapt		if (i == (int)len - 1 && '\n' != p[i])
805274880Sbapt			html_putchar(p[i]);
806274880Sbapt
807274880Sbapt		putchar('\n');
808274880Sbapt	}
809274880Sbapt
810274880Sbapt	puts("</PRE>\n"
811274880Sbapt	     "</DIV>");
812274880Sbapt
813274880Sbapt	fclose(f);
814274880Sbapt}
815274880Sbapt
816274880Sbaptstatic void
817274880Sbaptformat(const struct req *req, const char *file)
818274880Sbapt{
819274880Sbapt	struct mparse	*mp;
820275432Sbapt	struct mchars	*mchars;
821274880Sbapt	struct mdoc	*mdoc;
822274880Sbapt	struct man	*man;
823274880Sbapt	void		*vp;
824274880Sbapt	char		*opts;
825274880Sbapt	int		 fd;
826274880Sbapt	int		 usepath;
827274880Sbapt
828274880Sbapt	if (-1 == (fd = open(file, O_RDONLY, 0))) {
829274880Sbapt		puts("<P>You specified an invalid manual file.</P>");
830274880Sbapt		return;
831274880Sbapt	}
832274880Sbapt
833275432Sbapt	mchars = mchars_alloc();
834279527Sbapt	mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL,
835275432Sbapt	    mchars, req->q.manpath);
836279527Sbapt	mparse_readfd(mp, fd, file);
837274880Sbapt	close(fd);
838274880Sbapt
839274880Sbapt	usepath = strcmp(req->q.manpath, req->p[0]);
840274880Sbapt	mandoc_asprintf(&opts,
841274880Sbapt	    "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
842274880Sbapt	    scriptname,
843274880Sbapt	    req->q.arch	? "&arch="       : "",
844274880Sbapt	    req->q.arch	? req->q.arch    : "",
845274880Sbapt	    usepath	? "&manpath="    : "",
846274880Sbapt	    usepath	? req->q.manpath : "");
847274880Sbapt
848274880Sbapt	mparse_result(mp, &mdoc, &man, NULL);
849274880Sbapt	if (NULL == man && NULL == mdoc) {
850274880Sbapt		fprintf(stderr, "fatal mandoc error: %s/%s\n",
851274880Sbapt		    req->q.manpath, file);
852274880Sbapt		pg_error_internal();
853274880Sbapt		mparse_free(mp);
854275432Sbapt		mchars_free(mchars);
855274880Sbapt		return;
856274880Sbapt	}
857274880Sbapt
858275432Sbapt	vp = html_alloc(mchars, opts);
859274880Sbapt
860274880Sbapt	if (NULL != mdoc)
861274880Sbapt		html_mdoc(vp, mdoc);
862274880Sbapt	else
863274880Sbapt		html_man(vp, man);
864274880Sbapt
865274880Sbapt	html_free(vp);
866274880Sbapt	mparse_free(mp);
867275432Sbapt	mchars_free(mchars);
868274880Sbapt	free(opts);
869274880Sbapt}
870274880Sbapt
871274880Sbaptstatic void
872274880Sbaptresp_show(const struct req *req, const char *file)
873274880Sbapt{
874274880Sbapt
875274880Sbapt	if ('.' == file[0] && '/' == file[1])
876274880Sbapt		file += 2;
877274880Sbapt
878274880Sbapt	if ('c' == *file)
879274880Sbapt		catman(req, file);
880274880Sbapt	else
881274880Sbapt		format(req, file);
882274880Sbapt}
883274880Sbapt
884274880Sbaptstatic void
885274880Sbaptpg_show(struct req *req, const char *fullpath)
886274880Sbapt{
887274880Sbapt	char		*manpath;
888274880Sbapt	const char	*file;
889274880Sbapt
890274880Sbapt	if ((file = strchr(fullpath, '/')) == NULL) {
891274880Sbapt		pg_error_badrequest(
892274880Sbapt		    "You did not specify a page to show.");
893274880Sbapt		return;
894279527Sbapt	}
895274880Sbapt	manpath = mandoc_strndup(fullpath, file - fullpath);
896274880Sbapt	file++;
897274880Sbapt
898274880Sbapt	if ( ! validate_manpath(req, manpath)) {
899274880Sbapt		pg_error_badrequest(
900274880Sbapt		    "You specified an invalid manpath.");
901274880Sbapt		free(manpath);
902274880Sbapt		return;
903274880Sbapt	}
904274880Sbapt
905274880Sbapt	/*
906274880Sbapt	 * Begin by chdir()ing into the manpath.
907274880Sbapt	 * This way we can pick up the database files, which are
908274880Sbapt	 * relative to the manpath root.
909274880Sbapt	 */
910274880Sbapt
911274880Sbapt	if (chdir(manpath) == -1) {
912274880Sbapt		fprintf(stderr, "chdir %s: %s\n",
913274880Sbapt		    manpath, strerror(errno));
914274880Sbapt		pg_error_internal();
915274880Sbapt		free(manpath);
916274880Sbapt		return;
917274880Sbapt	}
918274880Sbapt
919274880Sbapt	if (strcmp(manpath, "mandoc")) {
920274880Sbapt		free(req->q.manpath);
921274880Sbapt		req->q.manpath = manpath;
922274880Sbapt	} else
923274880Sbapt		free(manpath);
924274880Sbapt
925274880Sbapt	if ( ! validate_filename(file)) {
926274880Sbapt		pg_error_badrequest(
927274880Sbapt		    "You specified an invalid manual file.");
928274880Sbapt		return;
929274880Sbapt	}
930274880Sbapt
931274880Sbapt	resp_begin_html(200, NULL);
932274880Sbapt	resp_searchform(req);
933274880Sbapt	resp_show(req, file);
934274880Sbapt	resp_end_html();
935274880Sbapt}
936274880Sbapt
937274880Sbaptstatic void
938274880Sbaptpg_search(const struct req *req)
939274880Sbapt{
940274880Sbapt	struct mansearch	  search;
941274880Sbapt	struct manpaths		  paths;
942274880Sbapt	struct manpage		 *res;
943275432Sbapt	char			**argv;
944275432Sbapt	char			 *query, *rp, *wp;
945274880Sbapt	size_t			  ressz;
946275432Sbapt	int			  argc;
947274880Sbapt
948274880Sbapt	/*
949274880Sbapt	 * Begin by chdir()ing into the root of the manpath.
950274880Sbapt	 * This way we can pick up the database files, which are
951274880Sbapt	 * relative to the manpath root.
952274880Sbapt	 */
953274880Sbapt
954274880Sbapt	if (-1 == (chdir(req->q.manpath))) {
955274880Sbapt		fprintf(stderr, "chdir %s: %s\n",
956274880Sbapt		    req->q.manpath, strerror(errno));
957274880Sbapt		pg_error_internal();
958274880Sbapt		return;
959274880Sbapt	}
960274880Sbapt
961274880Sbapt	search.arch = req->q.arch;
962274880Sbapt	search.sec = req->q.sec;
963275432Sbapt	search.outkey = "Nd";
964275432Sbapt	search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
965275432Sbapt	search.firstmatch = 1;
966274880Sbapt
967274880Sbapt	paths.sz = 1;
968274880Sbapt	paths.paths = mandoc_malloc(sizeof(char *));
969274880Sbapt	paths.paths[0] = mandoc_strdup(".");
970274880Sbapt
971274880Sbapt	/*
972275432Sbapt	 * Break apart at spaces with backslash-escaping.
973274880Sbapt	 */
974274880Sbapt
975275432Sbapt	argc = 0;
976275432Sbapt	argv = NULL;
977275432Sbapt	rp = query = mandoc_strdup(req->q.query);
978275432Sbapt	for (;;) {
979275432Sbapt		while (isspace((unsigned char)*rp))
980275432Sbapt			rp++;
981275432Sbapt		if (*rp == '\0')
982275432Sbapt			break;
983275432Sbapt		argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
984275432Sbapt		argv[argc++] = wp = rp;
985275432Sbapt		for (;;) {
986275432Sbapt			if (isspace((unsigned char)*rp)) {
987275432Sbapt				*wp = '\0';
988275432Sbapt				rp++;
989275432Sbapt				break;
990275432Sbapt			}
991275432Sbapt			if (rp[0] == '\\' && rp[1] != '\0')
992275432Sbapt				rp++;
993275432Sbapt			if (wp != rp)
994275432Sbapt				*wp = *rp;
995275432Sbapt			if (*rp == '\0')
996275432Sbapt				break;
997275432Sbapt			wp++;
998275432Sbapt			rp++;
999275432Sbapt		}
1000274880Sbapt	}
1001274880Sbapt
1002275432Sbapt	if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
1003274880Sbapt		pg_noresult(req, "You entered an invalid query.");
1004274880Sbapt	else if (0 == ressz)
1005274880Sbapt		pg_noresult(req, "No results found.");
1006274880Sbapt	else
1007274880Sbapt		pg_searchres(req, res, ressz);
1008274880Sbapt
1009275432Sbapt	free(query);
1010275432Sbapt	mansearch_free(res, ressz);
1011274880Sbapt	free(paths.paths[0]);
1012274880Sbapt	free(paths.paths);
1013274880Sbapt}
1014274880Sbapt
1015274880Sbaptint
1016274880Sbaptmain(void)
1017274880Sbapt{
1018274880Sbapt	struct req	 req;
1019275432Sbapt	struct itimerval itimer;
1020274880Sbapt	const char	*path;
1021274880Sbapt	const char	*querystring;
1022274880Sbapt	int		 i;
1023274880Sbapt
1024275432Sbapt	/* Poor man's ReDoS mitigation. */
1025275432Sbapt
1026275432Sbapt	itimer.it_value.tv_sec = 2;
1027275432Sbapt	itimer.it_value.tv_usec = 0;
1028275432Sbapt	itimer.it_interval.tv_sec = 2;
1029275432Sbapt	itimer.it_interval.tv_usec = 0;
1030275432Sbapt	if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1031275432Sbapt		fprintf(stderr, "setitimer: %s\n", strerror(errno));
1032275432Sbapt		pg_error_internal();
1033275432Sbapt		return(EXIT_FAILURE);
1034275432Sbapt	}
1035275432Sbapt
1036274880Sbapt	/* Scan our run-time environment. */
1037274880Sbapt
1038274880Sbapt	if (NULL == (scriptname = getenv("SCRIPT_NAME")))
1039274880Sbapt		scriptname = "";
1040274880Sbapt
1041274880Sbapt	if ( ! validate_urifrag(scriptname)) {
1042274880Sbapt		fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
1043274880Sbapt		    scriptname);
1044274880Sbapt		pg_error_internal();
1045274880Sbapt		return(EXIT_FAILURE);
1046274880Sbapt	}
1047274880Sbapt
1048274880Sbapt	/*
1049274880Sbapt	 * First we change directory into the MAN_DIR so that
1050274880Sbapt	 * subsequent scanning for manpath directories is rooted
1051274880Sbapt	 * relative to the same position.
1052274880Sbapt	 */
1053274880Sbapt
1054274880Sbapt	if (-1 == chdir(MAN_DIR)) {
1055274880Sbapt		fprintf(stderr, "MAN_DIR: %s: %s\n",
1056274880Sbapt		    MAN_DIR, strerror(errno));
1057274880Sbapt		pg_error_internal();
1058274880Sbapt		return(EXIT_FAILURE);
1059279527Sbapt	}
1060274880Sbapt
1061274880Sbapt	memset(&req, 0, sizeof(struct req));
1062274880Sbapt	pathgen(&req);
1063274880Sbapt
1064274880Sbapt	/* Next parse out the query string. */
1065274880Sbapt
1066274880Sbapt	if (NULL != (querystring = getenv("QUERY_STRING")))
1067274880Sbapt		http_parse(&req, querystring);
1068274880Sbapt
1069275432Sbapt	if (req.q.manpath == NULL)
1070275432Sbapt		req.q.manpath = mandoc_strdup(req.p[0]);
1071275432Sbapt	else if ( ! validate_manpath(&req, req.q.manpath)) {
1072274880Sbapt		pg_error_badrequest(
1073274880Sbapt		    "You specified an invalid manpath.");
1074274880Sbapt		return(EXIT_FAILURE);
1075274880Sbapt	}
1076274880Sbapt
1077274880Sbapt	if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1078274880Sbapt		pg_error_badrequest(
1079274880Sbapt		    "You specified an invalid architecture.");
1080274880Sbapt		return(EXIT_FAILURE);
1081274880Sbapt	}
1082274880Sbapt
1083274880Sbapt	/* Dispatch to the three different pages. */
1084274880Sbapt
1085274880Sbapt	path = getenv("PATH_INFO");
1086274880Sbapt	if (NULL == path)
1087274880Sbapt		path = "";
1088274880Sbapt	else if ('/' == *path)
1089274880Sbapt		path++;
1090274880Sbapt
1091274880Sbapt	if ('\0' != *path)
1092274880Sbapt		pg_show(&req, path);
1093274880Sbapt	else if (NULL != req.q.query)
1094274880Sbapt		pg_search(&req);
1095274880Sbapt	else
1096274880Sbapt		pg_index(&req);
1097274880Sbapt
1098274880Sbapt	free(req.q.manpath);
1099274880Sbapt	free(req.q.arch);
1100274880Sbapt	free(req.q.sec);
1101274880Sbapt	free(req.q.query);
1102274880Sbapt	for (i = 0; i < (int)req.psz; i++)
1103274880Sbapt		free(req.p[i]);
1104274880Sbapt	free(req.p);
1105274880Sbapt	return(EXIT_SUCCESS);
1106274880Sbapt}
1107274880Sbapt
1108274880Sbapt/*
1109274880Sbapt * Scan for indexable paths.
1110274880Sbapt */
1111274880Sbaptstatic void
1112274880Sbaptpathgen(struct req *req)
1113274880Sbapt{
1114274880Sbapt	FILE	*fp;
1115274880Sbapt	char	*dp;
1116274880Sbapt	size_t	 dpsz;
1117274880Sbapt
1118274880Sbapt	if (NULL == (fp = fopen("manpath.conf", "r"))) {
1119274880Sbapt		fprintf(stderr, "%s/manpath.conf: %s\n",
1120274880Sbapt			MAN_DIR, strerror(errno));
1121274880Sbapt		pg_error_internal();
1122274880Sbapt		exit(EXIT_FAILURE);
1123274880Sbapt	}
1124274880Sbapt
1125274880Sbapt	while (NULL != (dp = fgetln(fp, &dpsz))) {
1126274880Sbapt		if ('\n' == dp[dpsz - 1])
1127274880Sbapt			dpsz--;
1128274880Sbapt		req->p = mandoc_realloc(req->p,
1129274880Sbapt		    (req->psz + 1) * sizeof(char *));
1130274880Sbapt		dp = mandoc_strndup(dp, dpsz);
1131274880Sbapt		if ( ! validate_urifrag(dp)) {
1132274880Sbapt			fprintf(stderr, "%s/manpath.conf contains "
1133274880Sbapt			    "unsafe path \"%s\"\n", MAN_DIR, dp);
1134274880Sbapt			pg_error_internal();
1135274880Sbapt			exit(EXIT_FAILURE);
1136274880Sbapt		}
1137274880Sbapt		if (NULL != strchr(dp, '/')) {
1138274880Sbapt			fprintf(stderr, "%s/manpath.conf contains "
1139274880Sbapt			    "path with slash \"%s\"\n", MAN_DIR, dp);
1140274880Sbapt			pg_error_internal();
1141274880Sbapt			exit(EXIT_FAILURE);
1142274880Sbapt		}
1143274880Sbapt		req->p[req->psz++] = dp;
1144274880Sbapt	}
1145274880Sbapt
1146274880Sbapt	if ( req->p == NULL ) {
1147274880Sbapt		fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1148274880Sbapt		pg_error_internal();
1149274880Sbapt		exit(EXIT_FAILURE);
1150274880Sbapt	}
1151274880Sbapt}
1152