X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/f5050a7a94fe2713e123c515c13900a73dbee9c1..e1c43d6711ac3ae9f955128631c8bb0dc69772f8:/cgi.c

diff --git a/cgi.c b/cgi.c
index adc34929..00f033ef 100644
--- a/cgi.c
+++ b/cgi.c
@@ -1,6 +1,7 @@
-/*	$Id: cgi.c,v 1.27 2011/12/10 23:04:31 kristaps Exp $ */
+/*	$Id: cgi.c,v 1.72 2014/07/13 12:55:45 schwarze Exp $ */
 /*
- * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -18,123 +19,101 @@
 #include "config.h"
 #endif
 
-#include <sys/param.h>
-#include <sys/wait.h>
-
-#include <assert.h>
 #include <ctype.h>
 #include <errno.h>
-#include <dirent.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <regex.h>
 #include <stdio.h>
-#include <stdarg.h>
-#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-#include "apropos_db.h"
 #include "mandoc.h"
-#include "mdoc.h"
-#include "man.h"
+#include "mandoc_aux.h"
 #include "main.h"
 #include "manpath.h"
-
-#ifdef __linux__
-# include <db_185.h>
-#else
-# include <db.h>
-#endif
-
-enum	page {
-	PAGE_INDEX,
-	PAGE_SEARCH,
-	PAGE_SHOW,
-	PAGE__MAX
-};
-
-struct	paths {
-	char		*name;
-	char		*path;
-};
+#include "mansearch.h"
+#include "cgi.h"
 
 /*
  * A query as passed to the search function.
  */
 struct	query {
+	const char	*manpath; /* desired manual directory */
 	const char	*arch; /* architecture */
 	const char	*sec; /* manual section */
 	const char	*expr; /* unparsed expression string */
-	int		 manroot; /* manroot index (or -1)*/
-	int		 whatis; /* whether whatis mode */
-	int		 legacy; /* whether legacy mode */
+	int		 equal; /* match whole names, not substrings */
 };
 
 struct	req {
-	struct query	 q;
-	struct paths	*p;
-	size_t		 psz;
-	enum page	 page;
+	struct query	  q;
+	char		**p; /* array of available manpaths */
+	size_t		  psz; /* number of available manpaths */
 };
 
-static	int		 atou(const char *, unsigned *);
-static	void		 catman(const char *);
+static	void		 catman(const struct req *, const char *);
 static	int	 	 cmp(const void *, const void *);
-static	void		 format(const char *);
+static	void		 format(const struct req *, const char *);
 static	void		 html_print(const char *);
+static	void		 html_printquery(const struct req *);
 static	void		 html_putchar(char);
 static	int 		 http_decode(char *);
 static	void		 http_parse(struct req *, char *);
-static	int		 pathstop(DIR *);
-static	void		 pathgen(DIR *, char *, struct req *);
-static	void		 pg_index(const struct req *, char *);
-static	void		 pg_search(const struct req *, char *);
-static	void		 pg_show(const struct req *, char *);
-static	void		 resp_bad(void);
-static	void		 resp_baddb(void);
-static	void		 resp_error400(void);
-static	void		 resp_error404(const char *);
+static	void		 http_print(const char *);
+static	void 		 http_putchar(char);
+static	void		 http_printquery(const struct req *);
+static	void		 pathgen(struct req *);
+static	void		 pg_error_badrequest(const char *);
+static	void		 pg_error_internal(void);
+static	void		 pg_index(const struct req *);
+static	void		 pg_noresult(const struct req *, const char *);
+static	void		 pg_search(const struct req *);
+static	void		 pg_searchres(const struct req *,
+				struct manpage *, size_t);
+static	void		 pg_show(const struct req *, const char *);
 static	void		 resp_begin_html(int, const char *);
 static	void		 resp_begin_http(int, const char *);
 static	void		 resp_end_html(void);
-static	void		 resp_index(const struct req *);
-static	void		 resp_search(struct res *, size_t, void *);
 static	void		 resp_searchform(const struct req *);
+static	void		 resp_show(const struct req *, const char *);
 
-static	const char	 *progname;
-static	const char	 *cache;
-static	const char	 *host;
+static	const char	 *scriptname; /* CGI script name */
+static	const char	 *httphost; /* hostname used in the URIs */
 
-static	const char * const pages[PAGE__MAX] = {
-	"index", /* PAGE_INDEX */ 
-	"search", /* PAGE_SEARCH */
-	"show", /* PAGE_SHOW */
+static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static	const char *const sec_numbers[] = {
+    "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
 };
-
-/*
- * This is just OpenBSD's strtol(3) suggestion.
- * I use it instead of strtonum(3) for portability's sake.
- */
-static int
-atou(const char *buf, unsigned *v)
-{
-	char		*ep;
-	long		 lval;
-
-	errno = 0;
-	lval = strtol(buf, &ep, 10);
-	if (buf[0] == '\0' || *ep != '\0')
-		return(0);
-	if ((errno == ERANGE && (lval == LONG_MAX || 
-					lval == LONG_MIN)) ||
-			(lval > UINT_MAX || lval < 0))
-		return(0);
-
-	*v = (unsigned int)lval;
-	return(1);
-}
+static	const char *const sec_names[] = {
+    "All Sections",
+    "1 - General Commands",
+    "2 - System Calls",
+    "3 - Subroutines",
+    "3p - Perl Subroutines",
+    "4 - Special Files",
+    "5 - File Formats",
+    "6 - Games",
+    "7 - Macros and Conventions",
+    "8 - Maintenance Commands",
+    "9 - Kernel Interface"
+};
+static	const int sec_MAX = sizeof(sec_names) / sizeof(char *);
+
+static	const char *const arch_names[] = {
+    "amd64",       "alpha",       "armish",      "armv7",
+    "aviion",      "hppa",        "hppa64",      "i386",
+    "ia64",        "landisk",     "loongson",    "luna88k",
+    "macppc",      "mips64",      "octeon",      "sgi",
+    "socppc",      "solbourne",   "sparc",       "sparc64",
+    "vax",         "zaurus",
+    "amiga",       "arc",         "arm32",       "atari",
+    "beagle",      "cats",        "hp300",       "mac68k",
+    "mvme68k",     "mvme88k",     "mvmeppc",     "palm",
+    "pc532",       "pegasos",     "pmax",        "powerpc",
+    "sun3",        "wgrisc",      "x68k"
+};
+static	const int arch_MAX = sizeof(arch_names) / sizeof(char *);
 
 /*
  * Print a character, escaping HTML along the way.
@@ -163,6 +142,64 @@ html_putchar(char c)
 	}
 }
 
+static void
+http_printquery(const struct req *req)
+{
+
+	if (NULL != req->q.manpath) {
+		printf("&manpath=");
+		http_print(req->q.manpath);
+	}
+	if (NULL != req->q.sec) {
+		printf("&sec=");
+		http_print(req->q.sec);
+	}
+	if (NULL != req->q.arch) {
+		printf("&arch=");
+		http_print(req->q.arch);
+	}
+	if (NULL != req->q.expr) {
+		printf("&query=");
+		http_print(req->q.expr);
+	}
+	if (0 == req->q.equal)
+		printf("&apropos=1");
+}
+
+static void
+html_printquery(const struct req *req)
+{
+
+	if (NULL != req->q.manpath) {
+		printf("&amp;manpath=");
+		html_print(req->q.manpath);
+	}
+	if (NULL != req->q.sec) {
+		printf("&amp;sec=");
+		html_print(req->q.sec);
+	}
+	if (NULL != req->q.arch) {
+		printf("&amp;arch=");
+		html_print(req->q.arch);
+	}
+	if (NULL != req->q.expr) {
+		printf("&amp;query=");
+		html_print(req->q.expr);
+	}
+	if (0 == req->q.equal)
+		printf("&amp;apropos=1");
+}
+
+static void
+http_print(const char *p)
+{
+
+	if (NULL == p)
+		return;
+	while ('\0' != *p)
+		http_putchar(*p++);
+}
+
 /*
  * Call through to html_putchar().
  * Accepts NULL strings.
@@ -185,102 +222,61 @@ html_print(const char *p)
 static void
 http_parse(struct req *req, char *p)
 {
-	char            *key, *val, *manroot;
-	size_t		 sz;
-	int		 i, legacy;
+	char            *key, *val;
 
 	memset(&req->q, 0, sizeof(struct query));
+	req->q.manpath = req->p[0];
+	req->q.equal = 1;
 
-	req->q.whatis = 1;
-	legacy = -1;
-	manroot = NULL;
-
-	while (p && '\0' != *p) {
-		while (' ' == *p)
-			p++;
-
+	while ('\0' != *p) {
 		key = p;
 		val = NULL;
 
-		if (NULL != (p = strchr(p, '='))) {
+		p += (int)strcspn(p, ";&");
+		if ('\0' != *p)
 			*p++ = '\0';
-			val = p;
-
-			sz = strcspn(p, ";&");
-			/* LINTED */
-			p += sz;
-
-			if ('\0' != *p)
-				*p++ = '\0';
-		} else {
-			p = key;
-			sz = strcspn(p, ";&");
-			/* LINTED */
-			p += sz;
-
-			if ('\0' != *p)
-				p++;
-			continue;
-		}
+		if (NULL != (val = strchr(key, '=')))
+			*val++ = '\0';
 
-		if ('\0' == *key || '\0' == *val)
+		if ('\0' == *key || NULL == val || '\0' == *val)
 			continue;
 
 		/* Just abort handling. */
 
 		if ( ! http_decode(key))
 			break;
-		if ( ! http_decode(val))
+		if (NULL != val && ! http_decode(val))
 			break;
 
-		if (0 == strcmp(key, "expr"))
-			req->q.expr = val;
-		else if (0 == strcmp(key, "query"))
+		if (0 == strcmp(key, "query"))
 			req->q.expr = val;
-		else if (0 == strcmp(key, "sec"))
-			req->q.sec = val;
-		else if (0 == strcmp(key, "sektion"))
-			req->q.sec = val;
-		else if (0 == strcmp(key, "arch"))
-			req->q.arch = val;
 		else if (0 == strcmp(key, "manpath"))
-			manroot = val;
+			req->q.manpath = val;
 		else if (0 == strcmp(key, "apropos"))
-			legacy = 0 == strcmp(val, "0");
-		else if (0 == strcmp(key, "op"))
-			req->q.whatis = 0 == strcasecmp(val, "whatis");
-	}
-
-	/* Test for old man.cgi compatibility mode. */
-
-	if (legacy == 0) {
-		req->q.whatis = 0;
-		req->q.legacy = 1;
-	} else if (legacy > 0) {
-		req->q.legacy = 1;
-		req->q.whatis = 1;
+			req->q.equal = !strcmp(val, "0");
+		else if (0 == strcmp(key, "sec") ||
+			 0 == strcmp(key, "sektion")) {
+			if (strcmp(val, "0"))
+				req->q.sec = val;
+		} else if (0 == strcmp(key, "arch")) {
+			if (strcmp(val, "default"))
+				req->q.arch = val;
+		}
 	}
+}
 
-	/* 
-	 * Section "0" means no section when in legacy mode.
-	 * For some man.cgi scripts, "default" arch is none.
-	 */
-
-	if (req->q.legacy && NULL != req->q.sec)
-		if (0 == strcmp(req->q.sec, "0"))
-			req->q.sec = NULL;
-	if (req->q.legacy && NULL != req->q.arch)
-		if (0 == strcmp(req->q.arch, "default"))
-			req->q.arch = NULL;
-
-	/* Default to first manroot. */
+static void
+http_putchar(char c)
+{
 
-	if (NULL != manroot) {
-		for (i = 0; i < (int)req->psz; i++)
-			if (0 == strcmp(req->p[i].name, manroot))
-				break;
-		req->q.manroot = i < (int)req->psz ? i : -1;
+	if (isalnum((unsigned char)c)) {
+		putchar((unsigned char)c);
+		return;
+	} else if (' ' == c) {
+		putchar('+');
+		return;
 	}
+	printf("%%%.2x", c);
 }
 
 /*
@@ -292,11 +288,13 @@ static int
 http_decode(char *p)
 {
 	char             hex[3];
+	char		*q;
 	int              c;
 
 	hex[2] = '\0';
 
-	for ( ; '\0' != *p; p++) {
+	q = p;
+	for ( ; '\0' != *p; p++, q++) {
 		if ('%' == *p) {
 			if ('\0' == (hex[0] = *(p + 1)))
 				return(0);
@@ -307,13 +305,13 @@ http_decode(char *p)
 			if ('\0' == c)
 				return(0);
 
-			*p = (char)c;
-			memmove(p + 1, p + 3, strlen(p + 3) + 1);
+			*q = (char)c;
+			p += 2;
 		} else
-			*p = '+' == *p ? ' ' : *p;
+			*q = '+' == *p ? ' ' : *p;
 	}
 
-	*p = '\0';
+	*q = '\0';
 	return(1);
 }
 
@@ -322,12 +320,12 @@ resp_begin_http(int code, const char *msg)
 {
 
 	if (200 != code)
-		printf("Status: %d %s\n", code, msg);
+		printf("Status: %d %s\r\n", code, msg);
 
-	puts("Content-Type: text/html; charset=utf-8\n"
-	     "Cache-Control: no-cache\n"
-	     "Pragma: no-cache\n"
-	     "");
+	printf("Content-Type: text/html; charset=utf-8\r\n"
+	     "Cache-Control: no-cache\r\n"
+	     "Pragma: no-cache\r\n"
+	     "\r\n");
 
 	fflush(stdout);
 }
@@ -338,19 +336,22 @@ resp_begin_html(int code, const char *msg)
 
 	resp_begin_http(code, msg);
 
-	puts("<!DOCTYPE HTML PUBLIC "
-	     " \"-//W3C//DTD HTML 4.01//EN\""
-	     " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
-	     "<HTML>\n"
-	     "<HEAD>\n"
-	     "<META HTTP-EQUIV=\"Content-Type\""
-	     " CONTENT=\"text/html; charset=utf-8\">\n"
-	     "<LINK REL=\"stylesheet\" HREF=\"/man.cgi.css\""
-	     " TYPE=\"text/css\" media=\"all\">\n"
-	     "<TITLE>System Manpage Reference</TITLE>\n"
-	     "</HEAD>\n"
-	     "<BODY>\n"
-	     "<!-- Begin page content. //-->");
+	printf("<!DOCTYPE HTML PUBLIC "
+	       " \"-//W3C//DTD HTML 4.01//EN\""
+	       " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
+	       "<HTML>\n"
+	       "<HEAD>\n"
+	       "<META HTTP-EQUIV=\"Content-Type\""
+	       " CONTENT=\"text/html; charset=utf-8\">\n"
+	       "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
+	       " TYPE=\"text/css\" media=\"all\">\n"
+	       "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
+	       " TYPE=\"text/css\" media=\"all\">\n"
+	       "<TITLE>%s</TITLE>\n"
+	       "</HEAD>\n"
+	       "<BODY>\n"
+	       "<!-- Begin page content. //-->\n",
+	       CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
 }
 
 static void
@@ -366,193 +367,221 @@ resp_searchform(const struct req *req)
 {
 	int		 i;
 
+	puts(CUSTOMIZE_BEGIN);
 	puts("<!-- Begin search form. //-->");
-	printf("<FORM ACTION=\"");
-	html_print(progname);
-	printf("/search.html\" METHOD=\"get\">\n");
-	printf("<FIELDSET>\n"
-	       "<LEGEND>Search Parameters</LEGEND>\n"
-	       "<INPUT TYPE=\"submit\" NAME=\"op\""
-	       " VALUE=\"Whatis\"> or \n"
-	       "<INPUT TYPE=\"submit\" NAME=\"op\""
-	       " VALUE=\"apropos\"> for manuals satisfying \n"
-	       "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"");
-	html_print(req->q.expr ? req->q.expr : "");
-	printf("\">, section "
-	       "<INPUT TYPE=\"text\""
-	       " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
-	html_print(req->q.sec ? req->q.sec : "");
-	printf("\">, arch "
-	       "<INPUT TYPE=\"text\""
-	       " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
-	html_print(req->q.arch ? req->q.arch : "");
-	printf("\">");
+	printf("<DIV ID=\"mancgi\">\n"
+	       "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
+	       "<FIELDSET>\n"
+	       "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
+	       scriptname);
+
+	/* Write query input box. */
+
+	printf(	"<TABLE><TR><TD>\n"
+		"<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
+	if (NULL != req->q.expr)
+		html_print(req->q.expr);
+	puts("\" SIZE=\"40\">");
+
+	/* Write submission and reset buttons. */
+
+	printf(	"<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
+		"<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
+
+	/* Write show radio button */
+
+	printf(	"</TD><TD>\n"
+		"<INPUT TYPE=\"radio\" ");
+	if (req->q.equal)
+		printf("CHECKED ");
+	printf(	"NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
+		"<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
+
+	/* Write section selector. */
+
+	printf(	"</TD></TR><TR><TD>\n"
+		"<SELECT NAME=\"sec\">");
+	for (i = 0; i < sec_MAX; i++) {
+		printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
+		if (NULL != req->q.sec &&
+		    0 == strcmp(sec_numbers[i], req->q.sec))
+			printf(" SELECTED");
+		printf(">%s</OPTION>\n", sec_names[i]);
+	}
+	puts("</SELECT>");
+
+	/* Write architecture selector. */
+
+	puts("<SELECT NAME=\"arch\">");
+	for (i = 0; i < arch_MAX; i++) {
+		printf("<OPTION VALUE=\"%s\"", arch_names[i]);
+		if (NULL != req->q.arch &&
+		    0 == strcmp(arch_names[i], req->q.arch))
+			printf(" SELECTED");
+		printf(">%s</OPTION>\n", arch_names[i]);
+	}
+	puts("</SELECT>");
+
+	/* Write manpath selector. */
+
 	if (req->psz > 1) {
-		puts(", <SELECT NAME=\"manpath\">");
+		puts("<SELECT NAME=\"manpath\">");
 		for (i = 0; i < (int)req->psz; i++) {
-			printf("<OPTION %s VALUE=\"",
-				(i == req->q.manroot) ||
-				(0 == i && -1 == req->q.manroot) ?
-				"SELECTED=\"selected\"" : "");
-			html_print(req->p[i].name);
+			printf("<OPTION ");
+			if (NULL == req->q.manpath ? 0 == i :
+			    0 == strcmp(req->q.manpath, req->p[i]))
+				printf("SELECTED ");
+			printf("VALUE=\"");
+			html_print(req->p[i]);
 			printf("\">");
-			html_print(req->p[i].name);
+			html_print(req->p[i]);
 			puts("</OPTION>");
 		}
 		puts("</SELECT>");
 	}
-	puts(".\n"
-	     "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
+
+	/* Write search radio button */
+
+	printf(	"</TD><TD>\n"
+		"<INPUT TYPE=\"radio\" ");
+	if (0 == req->q.equal)
+		printf("CHECKED ");
+	printf(	"NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
+		"<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
+
+	puts("</TD></TR></TABLE>\n"
 	     "</FIELDSET>\n"
-	     "</FORM>");
+	     "</FORM>\n"
+	     "</DIV>");
 	puts("<!-- End search form. //-->");
 }
 
 static void
-resp_index(const struct req *req)
+pg_index(const struct req *req)
 {
 
 	resp_begin_html(200, NULL);
 	resp_searchform(req);
+	printf("<P>\n"
+	       "This web interface is documented in the "
+	       "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
+	       "manual, and the "
+	       "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
+	       "manual explains the query syntax.\n"
+	       "</P>\n",
+	       scriptname, scriptname);
 	resp_end_html();
 }
 
 static void
-resp_error400(void)
+pg_noresult(const struct req *req, const char *msg)
 {
-
-	resp_begin_html(400, "Query Malformed");
-	printf("<H1>Malformed Query</H1>\n"
-	       "<P>\n"
-	       "The query your entered was malformed.\n"
-	       "Try again from the\n"
-	       "<A HREF=\"%s/index.html\">main page</A>.\n"
-	       "</P>", progname);
+	resp_begin_html(200, NULL);
+	resp_searchform(req);
+	puts("<P>");
+	puts(msg);
+	puts("</P>");
 	resp_end_html();
 }
 
 static void
-resp_error404(const char *page)
+pg_error_badrequest(const char *msg)
 {
 
-	resp_begin_html(404, "Not Found");
-	puts("<H1>Page Not Found</H1>\n"
-	     "<P>\n"
-	     "The page you're looking for, ");
-	printf("<B>");
-	html_print(page);
-	printf("</B>,\n"
-	       "could not be found.\n"
-	       "Try searching from the\n"
-	       "<A HREF=\"%s/index.html\">main page</A>.\n"
-	       "</P>", progname);
-	resp_end_html();
-}
-
-static void
-resp_bad(void)
-{
-	resp_begin_html(500, "Internal Server Error");
-	puts("<P>Generic badness happened.</P>");
+	resp_begin_html(400, "Bad Request");
+	puts("<H1>Bad Request</H1>\n"
+	     "<P>\n");
+	puts(msg);
+	printf("Try again from the\n"
+	       "<A HREF=\"%s\">main page</A>.\n"
+	       "</P>", scriptname);
 	resp_end_html();
 }
 
 static void
-resp_baddb(void)
+pg_error_internal(void)
 {
-
 	resp_begin_html(500, "Internal Server Error");
-	puts("<P>Your database is broken.</P>");
+	puts("<P>Internal Server Error</P>");
 	resp_end_html();
 }
 
 static void
-resp_search(struct res *r, size_t sz, void *arg)
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
 {
-	int		  i;
-	const struct req *req;
+	size_t		 i, iuse, isec;
+	int		 prio, priouse;
+	char		 sec;
 
 	if (1 == sz) {
 		/*
 		 * If we have just one result, then jump there now
 		 * without any delay.
 		 */
-		puts("Status: 303 See Other");
-		printf("Location: http://%s%s/show/%u/%u.html\n",
-				host, progname,
-				r[0].volume, r[0].rec);
-		puts("Content-Type: text/html; charset=utf-8\n");
+		printf("Status: 303 See Other\r\n");
+		printf("Location: http://%s%s/%s/%s?",
+		    httphost, scriptname, req->q.manpath, r[0].file);
+		http_printquery(req);
+		printf("\r\n"
+		     "Content-Type: text/html; charset=utf-8\r\n"
+		     "\r\n");
 		return;
 	}
 
-	qsort(r, sz, sizeof(struct res), cmp);
+	qsort(r, sz, sizeof(struct manpage), cmp);
 
 	resp_begin_html(200, NULL);
-
-	req = (const struct req *)arg;
 	resp_searchform(req);
+	puts("<DIV CLASS=\"results\">");
+	puts("<TABLE>");
 
-	if (0 == sz) {
-		printf("<P>\n"
-		       "No %s results found.\n",
-		       req->q.whatis ? "whatis" : "apropos");
-		if (req->q.whatis) {
-			printf("(Try <A HREF=\"");
-			html_print(progname);
-			printf("/search.html?op=apropos&amp;expr=");
-			html_print(req->q.expr ? req->q.expr : "");
-			printf("&amp;sec=");
-			html_print(req->q.sec ? req->q.sec : "");
-			printf("&amp;arch=");
-			html_print(req->q.arch ? req->q.arch : "");
-			puts("\">apropos</A>?)");
-		}
-		puts("</P>");
-		resp_end_html();
-		return;
-	}
-
-	puts("<P></P>\n"
-	     "<TABLE>");
-
-	assert(req->q.manroot >= 0);
-	for (i = 0; i < (int)sz; i++) {
+	for (i = 0; i < sz; i++) {
 		printf("<TR>\n"
 		       "<TD CLASS=\"title\">\n"
-		       "<A HREF=\"");
-		html_print(progname);
-		printf("/show/%d/%u/%u.html\">", 
-				req->q.manroot,
-				r[i].volume, r[i].rec);
-		html_print(r[i].title);
-		putchar('(');
-		html_print(r[i].cat);
-		if (r[i].arch && '\0' != *r[i].arch) {
-			putchar('/');
-			html_print(r[i].arch);
-		}
-		printf(")</A>\n"
+		       "<A HREF=\"%s/%s/%s?", 
+		    scriptname, req->q.manpath, r[i].file);
+		html_printquery(req);
+		printf("\">");
+		html_print(r[i].names);
+		printf("</A>\n"
 		       "</TD>\n"
 		       "<TD CLASS=\"desc\">");
-		html_print(r[i].desc);
+		html_print(r[i].output);
 		puts("</TD>\n"
 		     "</TR>");
 	}
 
-	puts("</TABLE>");
-	resp_end_html();
-}
+	puts("</TABLE>\n"
+	     "</DIV>");
 
-/* ARGSUSED */
-static void
-pg_index(const struct req *req, char *path)
-{
+	/*
+	 * In man(1) mode, show one of the pages
+	 * even if more than one is found.
+	 */
+
+	if (req->q.equal) {
+		puts("<HR>");
+		iuse = 0;
+		priouse = 10;
+		for (i = 0; i < sz; i++) {
+			isec = strcspn(r[i].file, "123456789");
+			sec = r[i].file[isec];
+			if ('\0' == sec)
+				continue;
+			prio = sec_prios[sec - '1'];
+			if (prio >= priouse)
+				continue;
+			priouse = prio;
+			iuse = i;
+		}
+		resp_show(req, r[iuse].file);
+	}
 
-	resp_index(req);
+	resp_end_html();
 }
 
 static void
-catman(const char *file)
+catman(const struct req *req, const char *file)
 {
 	FILE		*f;
 	size_t		 len;
@@ -561,24 +590,11 @@ catman(const char *file)
 	int		 italic, bold;
 
 	if (NULL == (f = fopen(file, "r"))) {
-		resp_baddb();
+		puts("<P>You specified an invalid manual file.</P>");
 		return;
 	}
 
-	resp_begin_http(200, NULL);
-	puts("<!DOCTYPE HTML PUBLIC "
-	     " \"-//W3C//DTD HTML 4.01//EN\""
-	     " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
-	     "<HTML>\n"
-	     "<HEAD>\n"
-	     "<META HTTP-EQUIV=\"Content-Type\""
-	     " CONTENT=\"text/html; charset=utf-8\">\n"
-	     "<LINK REL=\"stylesheet\" HREF=\"/catman.css\""
-	     " TYPE=\"text/css\" media=\"all\">\n"
-	     "<TITLE>System Manpage Reference</TITLE>\n"
-	     "</HEAD>\n"
-	     "<BODY>\n"
-	     "<!-- Begin page content. //-->\n"
+	puts("<DIV CLASS=\"catman\">\n"
 	     "<PRE>");
 
 	while (NULL != (p = fgetln(f, &len))) {
@@ -691,14 +707,13 @@ catman(const char *file)
 	}
 
 	puts("</PRE>\n"
-	     "</BODY>\n"
-	     "</HTML>");
+	     "</DIV>");
 
 	fclose(f);
 }
 
 static void
-format(const char *file)
+format(const struct req *req, const char *file)
 {
 	struct mparse	*mp;
 	int		 fd;
@@ -706,166 +721,101 @@ format(const char *file)
 	struct man	*man;
 	void		*vp;
 	enum mandoclevel rc;
-	char		 opts[MAXPATHLEN + 128];
+	char		 opts[PATH_MAX + 128];
 
 	if (-1 == (fd = open(file, O_RDONLY, 0))) {
-		resp_baddb();
+		puts("<P>You specified an invalid manual file.</P>");
 		return;
 	}
 
-	mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
+	mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
+	    req->q.manpath);
 	rc = mparse_readfd(mp, fd, file);
 	close(fd);
 
 	if (rc >= MANDOCLEVEL_FATAL) {
-		resp_baddb();
+		fprintf(stderr, "fatal mandoc error: %s/%s\n",
+		    req->q.manpath, file);
+		pg_error_internal();
 		return;
 	}
 
-	snprintf(opts, sizeof(opts), "style=/man.css,"
-			"man=%s/search.html?sec=%%S&expr=%%N,"
-			/*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
-			progname);
+	snprintf(opts, sizeof(opts),
+	    "fragment,man=%s?query=%%N&amp;sec=%%S",
+	    scriptname);
+
+	mparse_result(mp, &mdoc, &man, NULL);
+	if (NULL == man && NULL == mdoc) {
+		fprintf(stderr, "fatal mandoc error: %s/%s\n",
+		    req->q.manpath, file);
+		pg_error_internal();
+		mparse_free(mp);
+		return;
+	}
 
-	mparse_result(mp, &mdoc, &man);
 	vp = html_alloc(opts);
 
-	if (NULL != mdoc) {
-		resp_begin_http(200, NULL);
+	if (NULL != mdoc)
 		html_mdoc(vp, mdoc);
-	} else if (NULL != man) {
-		resp_begin_http(200, NULL);
+	else
 		html_man(vp, man);
-	} else
-		resp_baddb();
 
 	html_free(vp);
 	mparse_free(mp);
 }
 
 static void
-pg_show(const struct req *req, char *path)
+resp_show(const struct req *req, const char *file)
 {
-	struct manpaths	 ps;
-	char		*sub;
-	char		 file[MAXPATHLEN];
-	const char	*fn, *cp;
-	int		 rc;
-	unsigned int	 vol, rec, mr;
-	DB		*idx;
-	DBT		 key, val;
+	if ('.' == file[0] || '/' == file[1])
+		file += 2;
 
-	idx = NULL;
+	if ('c' == *file)
+		catman(req, file);
+	else
+		format(req, file);
+}
 
-	/* Parse out mroot, volume, and record from the path. */
+static void
+pg_show(const struct req *req, const char *path)
+{
+	char		*sub;
 
 	if (NULL == path || NULL == (sub = strchr(path, '/'))) {
-		resp_error400();
+		pg_error_badrequest(
+		    "You did not specify a page to show.");
 		return;
 	} 
 	*sub++ = '\0';
-	if ( ! atou(path, &mr)) {
-		resp_error400();
-		return;
-	}
-	path = sub;
-	if (NULL == (sub = strchr(path, '/'))) {
-		resp_error400();
-		return;
-	}
-	*sub++ = '\0';
-	if ( ! atou(path, &vol) || ! atou(sub, &rec)) {
-		resp_error400();
-		return;
-	} else if (mr >= (unsigned int)req->psz) {
-		resp_error400();
-		return;
-	}
 
 	/*
-	 * Begin by chdir()ing into the manroot.
+	 * Begin by chdir()ing into the manpath.
 	 * This way we can pick up the database files, which are
 	 * relative to the manpath root.
 	 */
 
-	if (-1 == chdir(req->p[(int)mr].path)) {
-		perror(req->p[(int)mr].path);
-		resp_baddb();
+	if (-1 == chdir(path)) {
+		pg_error_badrequest(
+		    "You specified an invalid manpath.");
 		return;
 	}
 
-	memset(&ps, 0, sizeof(struct manpaths));
-	manpath_manconf("etc/catman.conf", &ps);
-
-	if (vol >= (unsigned int)ps.sz) {
-		resp_error400();
-		goto out;
-	}
-
-	strlcpy(file, ps.paths[vol], MAXPATHLEN);
-	strlcat(file, "/mandoc.index", MAXPATHLEN);
-
-	/* Open the index recno(3) database. */
-
-	idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
-	if (NULL == idx) {
-		perror(file);
-		resp_baddb();
-		goto out;
-	}
-
-	key.data = &rec;
-	key.size = 4;
-
-	if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
-		rc < 0 ? resp_baddb() : resp_error400();
-		goto out;
-	} 
-
-	cp = (char *)val.data;
-
-	if (NULL == (fn = memchr(cp, '\0', val.size)))
-		resp_baddb();
-	else if (++fn - cp >= (int)val.size)
-		resp_baddb();
-	else if (NULL == memchr(fn, '\0', val.size - (fn - cp)))
-		resp_baddb();
-	else {
-		if (0 == strcmp(cp, "cat"))
-			catman(fn + 1);
-		else
-			format(fn + 1);
-	}
-out:
-	if (idx)
-		(*idx->close)(idx);
-	manpath_free(&ps);
+	resp_begin_html(200, NULL);
+	resp_searchform(req);
+	resp_show(req, sub);
+	resp_end_html();
 }
 
 static void
-pg_search(const struct req *req, char *path)
+pg_search(const struct req *req)
 {
-	size_t		  tt;
-	struct manpaths	  ps;
-	int		  i, sz, rc;
-	const char	 *ep, *start;
-	char		**cp;
-	struct opts	  opt;
-	struct expr	 *expr;
-
-	if (req->q.manroot < 0) {
-		resp_search(NULL, 0, (void *)req);
-		return;
-	}
-
-	memset(&opt, 0, sizeof(struct opts));
-
-	ep 	 = req->q.expr;
-	opt.arch = req->q.arch;
-	opt.cat  = req->q.sec;
-	rc 	 = -1;
-	sz 	 = 0;
-	cp	 = NULL;
+	struct mansearch	  search;
+	struct manpaths		  paths;
+	struct manpage		 *res;
+	char			**cp;
+	const char		 *ep, *start;
+	size_t			  ressz;
+	int			  i, sz;
 
 	/*
 	 * Begin by chdir()ing into the root of the manpath.
@@ -873,26 +823,34 @@ pg_search(const struct req *req, char *path)
 	 * relative to the manpath root.
 	 */
 
-	assert(req->q.manroot < (int)req->psz);
-	if (-1 == (chdir(req->p[req->q.manroot].path))) {
-		perror(req->p[req->q.manroot].path);
-		resp_search(NULL, 0, (void *)req);
+	if (-1 == (chdir(req->q.manpath))) {
+		pg_error_badrequest(
+		    "You specified an invalid manpath.");
 		return;
 	}
 
-	memset(&ps, 0, sizeof(struct manpaths));
-	manpath_manconf("etc/catman.conf", &ps);
+	search.arch = req->q.arch;
+	search.sec = req->q.sec;
+	search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
+	search.flags = req->q.equal ? MANSEARCH_MAN : 0;
+
+	paths.sz = 1;
+	paths.paths = mandoc_malloc(sizeof(char *));
+	paths.paths[0] = mandoc_strdup(".");
 
 	/*
 	 * Poor man's tokenisation: just break apart by spaces.
 	 * Yes, this is half-ass.  But it works for now.
 	 */
 
+	ep = req->q.expr;
 	while (ep && isspace((unsigned char)*ep))
 		ep++;
 
+	sz = 0;
+	cp = NULL;
 	while (ep && '\0' != *ep) {
-		cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
+		cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
 		start = ep;
 		while ('\0' != *ep && ! isspace((unsigned char)*ep))
 			ep++;
@@ -903,142 +861,82 @@ pg_search(const struct req *req, char *path)
 			ep++;
 	}
 
-	/*
-	 * Pump down into apropos backend.
-	 * The resp_search() function is called with the results.
-	 */
-
-	expr = req->q.whatis ? 
-		termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
-
-	if (NULL != expr)
-		rc = apropos_search
-			(ps.sz, ps.paths, &opt,
-			 expr, tt, (void *)req, resp_search);
-
-	/* ...unless errors occured. */
-
-	if (0 == rc)
-		resp_baddb();
-	else if (-1 == rc)
-		resp_search(NULL, 0, (void *)req);
+	if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
+		pg_noresult(req, "You entered an invalid query.");
+	else if (0 == ressz)
+		pg_noresult(req, "No results found.");
+	else
+		pg_searchres(req, res, ressz);
 
 	for (i = 0; i < sz; i++)
 		free(cp[i]);
-
 	free(cp);
-	exprfree(expr);
-	manpath_free(&ps);
+
+	for (i = 0; i < (int)ressz; i++) {
+		free(res[i].file);
+		free(res[i].names);
+		free(res[i].output);
+	}
+	free(res);
+
+	free(paths.paths[0]);
+	free(paths.paths);
 }
 
 int
 main(void)
 {
-	int		 i;
-	char		 buf[MAXPATHLEN];
-	DIR		*cwd;
 	struct req	 req;
-	char		*p, *path, *subpath;
+	const char	*path;
+	char		*querystring;
+	int		 i;
 
 	/* Scan our run-time environment. */
 
-	progname = getenv("SCRIPT_NAME");
-	if (NULL == progname)
-		progname = "";
-
-	cache = getenv("CACHE_DIR");
-	if (NULL == cache)
-		cache = "/cache/man.cgi";
+	if (NULL == (scriptname = getenv("SCRIPT_NAME")))
+		scriptname = "";
 
-	host = getenv("HTTP_HOST");
-	if (NULL == host)
-		host = "localhost";
+	if (NULL == (httphost = getenv("HTTP_HOST")))
+		httphost = "localhost";
 
 	/*
-	 * First we change directory into the cache directory so that
+	 * First we change directory into the MAN_DIR so that
 	 * subsequent scanning for manpath directories is rooted
 	 * relative to the same position.
 	 */
 
-	if (-1 == chdir(cache)) {
-		perror(cache);
-		resp_bad();
-		return(EXIT_FAILURE);
-	} else if (NULL == (cwd = opendir(cache))) {
-		perror(cache);
-		resp_bad();
+	if (-1 == chdir(MAN_DIR)) {
+		fprintf(stderr, "MAN_DIR: %s: %s\n",
+		    MAN_DIR, strerror(errno));
+		pg_error_internal();
 		return(EXIT_FAILURE);
 	} 
 
 	memset(&req, 0, sizeof(struct req));
-
-	strlcpy(buf, ".", MAXPATHLEN);
-	pathgen(cwd, buf, &req);
-	closedir(cwd);
+	pathgen(&req);
 
 	/* Next parse out the query string. */
 
-	if (NULL != (p = getenv("QUERY_STRING")))
-		http_parse(&req, p);
+	if (NULL != (querystring = getenv("QUERY_STRING")))
+		http_parse(&req, querystring);
 
-	/*
-	 * Now juggle paths to extract information.
-	 * We want to extract our filetype (the file suffix), the
-	 * initial path component, then the trailing component(s).
-	 * Start with leading subpath component. 
-	 */
-
-	subpath = path = NULL;
-	req.page = PAGE__MAX;
-
-	if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
-		req.page = PAGE_INDEX;
-
-	if (NULL != path && '/' == *path && '\0' == *++path)
-		req.page = PAGE_INDEX;
-
-	/* Strip file suffix. */
-
-	if (NULL != path && NULL != (p = strrchr(path, '.')))
-		if (NULL != p && NULL == strchr(p, '/'))
-			*p++ = '\0';
+	/* Dispatch to the three different pages. */
 
-	/* Resolve subpath component. */
+	path = getenv("PATH_INFO");
+	if (NULL == path)
+		path = "";
+	else if ('/' == *path)
+		path++;
 
-	if (NULL != path && NULL != (subpath = strchr(path, '/')))
-		*subpath++ = '\0';
-
-	/* Map path into one we recognise. */
-
-	if (NULL != path && '\0' != *path)
-		for (i = 0; i < (int)PAGE__MAX; i++) 
-			if (0 == strcmp(pages[i], path)) {
-				req.page = (enum page)i;
-				break;
-			}
-
-	/* Route pages. */
-
-	switch (req.page) {
-	case (PAGE_INDEX):
-		pg_index(&req, subpath);
-		break;
-	case (PAGE_SEARCH):
-		pg_search(&req, subpath);
-		break;
-	case (PAGE_SHOW):
-		pg_show(&req, subpath);
-		break;
-	default:
-		resp_error404(path);
-		break;
-	}
-
-	for (i = 0; i < (int)req.psz; i++) {
-		free(req.p[i].path);
-		free(req.p[i].name);
-	}
+	if ('\0' != *path)
+		pg_show(&req, path);
+	else if (NULL != req.q.expr)
+		pg_search(&req);
+	else
+		pg_index(&req);
 
+	for (i = 0; i < (int)req.psz; i++)
+		free(req.p[i]);
 	free(req.p);
 	return(EXIT_SUCCESS);
 }
@@ -1047,119 +945,28 @@ static int
 cmp(const void *p1, const void *p2)
 {
 
-	return(strcasecmp(((const struct res *)p1)->title,
-				((const struct res *)p2)->title));
-}
-
-/*
- * Check to see if an "etc" path consists of a catman.conf file.  If it
- * does, that means that the path contains a tree created by catman(8)
- * and should be used for indexing.
- */
-static int
-pathstop(DIR *dir)
-{
-	struct dirent	*d;
-
-	while (NULL != (d = readdir(dir)))
-		if (DT_REG == d->d_type)
-			if (0 == strcmp(d->d_name, "catman.conf"))
-				return(1);
-
-	return(0);
+	return(strcasecmp(((const struct manpage *)p1)->names,
+	    ((const struct manpage *)p2)->names));
 }
 
 /*
  * Scan for indexable paths.
- * This adds all paths with "etc/catman.conf" to the buffer.
  */
 static void
-pathgen(DIR *dir, char *path, struct req *req)
+pathgen(struct req *req)
 {
-	struct dirent	*d;
-	char		*cp;
-	DIR		*cd;
-	int		 rc;
-	size_t		 sz, ssz;
-
-	sz = strlcat(path, "/", MAXPATHLEN);
-	if (sz >= MAXPATHLEN) {
-		fprintf(stderr, "%s: Path too long", path);
-		return;
-	} 
-
-	/* 
-	 * First, scan for the "etc" directory.
-	 * If it's found, then see if it should cause us to stop.  This
-	 * happens when a catman.conf is found in the directory.
-	 */
-
-	rc = 0;
-	while (0 == rc && NULL != (d = readdir(dir))) {
-		if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
-			continue;
+	FILE	*fp;
+	char	*dp;
+	size_t	 dpsz;
 
-		path[(int)sz] = '\0';
-		ssz = strlcat(path, d->d_name, MAXPATHLEN);
-
-		if (ssz >= MAXPATHLEN) {
-			fprintf(stderr, "%s: Path too long", path);
-			return;
-		} else if (NULL == (cd = opendir(path))) {
-			perror(path);
-			return;
-		} 
-		
-		rc = pathstop(cd);
-		closedir(cd);
-	}
-
-	if (rc > 0) {
-		/* This also strips the trailing slash. */
-		path[(int)--sz] = '\0';
-		req->p = mandoc_realloc
-			(req->p, 
-			 (req->psz + 1) * sizeof(struct paths));
-		/*
-		 * Strip out the leading "./" unless we're just a ".",
-		 * in which case use an empty string as our name.
-		 */
-		req->p[(int)req->psz].path = mandoc_strdup(path);
-		req->p[(int)req->psz].name = 
-			cp = mandoc_strdup(path + (1 == sz ? 1 : 2));
-		req->psz++;
-		/* 
-		 * The name is just the path with all the slashes taken
-		 * out of it.  Simple but effective. 
-		 */
-		for ( ; '\0' != *cp; cp++) 
-			if ('/' == *cp)
-				*cp = ' ';
+	if (NULL == (fp = fopen("manpath.conf", "r")))
 		return;
-	} 
-
-	/*
-	 * If no etc/catman.conf was found, recursively enter child
-	 * directory and continue scanning.
-	 */
-
-	rewinddir(dir);
-	while (NULL != (d = readdir(dir))) {
-		if (DT_DIR != d->d_type || '.' == d->d_name[0])
-			continue;
-
-		path[(int)sz] = '\0';
-		ssz = strlcat(path, d->d_name, MAXPATHLEN);
-
-		if (ssz >= MAXPATHLEN) {
-			fprintf(stderr, "%s: Path too long", path);
-			return;
-		} else if (NULL == (cd = opendir(path))) {
-			perror(path);
-			return;
-		}
 
-		pathgen(cd, path, req);
-		closedir(cd);
+	while (NULL != (dp = fgetln(fp, &dpsz))) {
+		if ('\n' == dp[dpsz - 1])
+			dpsz--;
+		req->p = mandoc_realloc(req->p,
+		    (req->psz + 1) * sizeof(char *));
+		req->p[req->psz++] = mandoc_strndup(dp, dpsz);
 	}
 }