1*d9a51c35Sjmc /* $OpenBSD: cgi.c,v 1.120 2022/12/26 19:16:02 jmc Exp $ */
2c6c22f12Sschwarze /*
3a43df5a0Sschwarze * Copyright (c) 2014-2019, 2021, 2022 Ingo Schwarze <schwarze@usta.de>
46a6803e4Sschwarze * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5b2d1ada6Sschwarze * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
6c6c22f12Sschwarze *
7c6c22f12Sschwarze * Permission to use, copy, modify, and distribute this software for any
8c6c22f12Sschwarze * purpose with or without fee is hereby granted, provided that the above
9c6c22f12Sschwarze * copyright notice and this permission notice appear in all copies.
10c6c22f12Sschwarze *
114de77decSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12c6c22f12Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
134de77decSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14c6c22f12Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15c6c22f12Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16c6c22f12Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17c6c22f12Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
186a6803e4Sschwarze *
196a6803e4Sschwarze * Implementation of the man.cgi(8) program.
20c6c22f12Sschwarze */
21136c26b8Sschwarze #include <sys/types.h>
22136c26b8Sschwarze #include <sys/time.h>
23136c26b8Sschwarze
24c6c22f12Sschwarze #include <ctype.h>
25c976f0e2Sschwarze #include <err.h>
26c6c22f12Sschwarze #include <errno.h>
27c6c22f12Sschwarze #include <fcntl.h>
28c6c22f12Sschwarze #include <limits.h>
29c2235d37Sschwarze #include <stdint.h>
30c6c22f12Sschwarze #include <stdio.h>
31c6c22f12Sschwarze #include <stdlib.h>
32c6c22f12Sschwarze #include <string.h>
33c6c22f12Sschwarze #include <unistd.h>
34c6c22f12Sschwarze
35c6c22f12Sschwarze #include "mandoc_aux.h"
36f2d5c709Sschwarze #include "mandoc.h"
37f2d5c709Sschwarze #include "roff.h"
38396853b5Sschwarze #include "mdoc.h"
39fec2846bSschwarze #include "man.h"
4099acaf1eSschwarze #include "mandoc_parse.h"
41c6c22f12Sschwarze #include "main.h"
424de77decSschwarze #include "manconf.h"
43c6c22f12Sschwarze #include "mansearch.h"
446fdade3eSschwarze #include "cgi.h"
45c6c22f12Sschwarze
46c6c22f12Sschwarze /*
47c6c22f12Sschwarze * A query as passed to the search function.
48c6c22f12Sschwarze */
49c6c22f12Sschwarze struct query {
5031e689c3Sschwarze char *manpath; /* desired manual directory */
5131e689c3Sschwarze char *arch; /* architecture */
5231e689c3Sschwarze char *sec; /* manual section */
53e89321abSschwarze char *query; /* unparsed query expression */
544477fbfaSschwarze int equal; /* match whole names, not substrings */
55c6c22f12Sschwarze };
56c6c22f12Sschwarze
57c6c22f12Sschwarze struct req {
58c6c22f12Sschwarze struct query q;
59c6c22f12Sschwarze char **p; /* array of available manpaths */
60c6c22f12Sschwarze size_t psz; /* number of available manpaths */
6152b413c1Sschwarze int isquery; /* QUERY_STRING used, not PATH_INFO */
62c6c22f12Sschwarze };
63c6c22f12Sschwarze
6484f05c93Sschwarze enum focus {
6584f05c93Sschwarze FOCUS_NONE = 0,
6684f05c93Sschwarze FOCUS_QUERY
6784f05c93Sschwarze };
6884f05c93Sschwarze
69c6c22f12Sschwarze static void html_print(const char *);
70c6c22f12Sschwarze static void html_putchar(char);
71c6c22f12Sschwarze static int http_decode(char *);
726a6803e4Sschwarze static void http_encode(const char *);
73941df026Sschwarze static void parse_manpath_conf(struct req *);
746a6803e4Sschwarze static void parse_path_info(struct req *, const char *);
75941df026Sschwarze static void parse_query_string(struct req *, const char *);
76facea411Sschwarze static void pg_error_badrequest(const char *);
77facea411Sschwarze static void pg_error_internal(void);
78facea411Sschwarze static void pg_index(const struct req *);
7918ccf011Sschwarze static void pg_noresult(const struct req *, int, const char *,
8018ccf011Sschwarze const char *);
81e1beff2aSschwarze static void pg_redirect(const struct req *, const char *);
8257482ef4Sschwarze static void pg_search(const struct req *);
83facea411Sschwarze static void pg_searchres(const struct req *,
84facea411Sschwarze struct manpage *, size_t);
8581060b1aSschwarze static void pg_show(struct req *, const char *);
86a43df5a0Sschwarze static int resp_begin_html(int, const char *, const char *);
87c6c22f12Sschwarze static void resp_begin_http(int, const char *);
88941df026Sschwarze static void resp_catman(const struct req *, const char *);
89a43df5a0Sschwarze static int resp_copy(const char *, const char *);
90c6c22f12Sschwarze static void resp_end_html(void);
91941df026Sschwarze static void resp_format(const struct req *, const char *);
9284f05c93Sschwarze static void resp_searchform(const struct req *, enum focus);
9346723f19Sschwarze static void resp_show(const struct req *, const char *);
94e89321abSschwarze static void set_query_attr(char **, char **);
95f7a12365Sschwarze static int validate_arch(const char *);
96e89321abSschwarze static int validate_filename(const char *);
97e89321abSschwarze static int validate_manpath(const struct req *, const char *);
98e89321abSschwarze static int validate_urifrag(const char *);
99c6c22f12Sschwarze
1003b9cfc6fSschwarze static const char *scriptname = SCRIPT_NAME;
101c6c22f12Sschwarze
10246723f19Sschwarze static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
10328e449d6Sschwarze static const char *const sec_numbers[] = {
10428e449d6Sschwarze "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
10528e449d6Sschwarze };
10628e449d6Sschwarze static const char *const sec_names[] = {
10728e449d6Sschwarze "All Sections",
10828e449d6Sschwarze "1 - General Commands",
10928e449d6Sschwarze "2 - System Calls",
1104e6618fdSschwarze "3 - Library Functions",
1114e6618fdSschwarze "3p - Perl Library",
1124e6618fdSschwarze "4 - Device Drivers",
11328e449d6Sschwarze "5 - File Formats",
11428e449d6Sschwarze "6 - Games",
1154e6618fdSschwarze "7 - Miscellaneous Information",
1164e6618fdSschwarze "8 - System Manager\'s Manual",
1174e6618fdSschwarze "9 - Kernel Developer\'s Manual"
11828e449d6Sschwarze };
11928e449d6Sschwarze static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
12028e449d6Sschwarze
12128e449d6Sschwarze static const char *const arch_names[] = {
12293d3e4e1Sderaadt "amd64", "alpha", "armv7", "arm64",
123ab2c7ff4Sschwarze "hppa", "i386", "landisk", "loongson",
124ab2c7ff4Sschwarze "luna88k", "macppc", "mips64", "octeon",
125bfc185c1Svisa "powerpc64", "riscv64", "sparc64",
126ab2c7ff4Sschwarze
127766ef059Sschwarze "amiga", "arc", "armish", "arm32",
128766ef059Sschwarze "atari", "aviion", "beagle", "cats",
129766ef059Sschwarze "hppa64", "hp300",
1303bcd4815Sschwarze "ia64", "mac68k", "mvme68k", "mvme88k",
1313bcd4815Sschwarze "mvmeppc", "palm", "pc532", "pegasos",
132bfc185c1Svisa "pmax", "powerpc", "sgi", "socppc",
133bfc185c1Svisa "solbourne", "sparc",
1346250a715Sschwarze "sun3", "vax", "wgrisc", "x68k",
1356250a715Sschwarze "zaurus"
13628e449d6Sschwarze };
13728e449d6Sschwarze static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
13828e449d6Sschwarze
139c6c22f12Sschwarze /*
140c6c22f12Sschwarze * Print a character, escaping HTML along the way.
141c6c22f12Sschwarze * This will pass non-ASCII straight to output: be warned!
142c6c22f12Sschwarze */
143c6c22f12Sschwarze static void
html_putchar(char c)144c6c22f12Sschwarze html_putchar(char c)
145c6c22f12Sschwarze {
146c6c22f12Sschwarze
147c6c22f12Sschwarze switch (c) {
148e74fa2aeSschwarze case '"':
149f765a656Sbentley printf(""");
150c6c22f12Sschwarze break;
151e74fa2aeSschwarze case '&':
152c6c22f12Sschwarze printf("&");
153c6c22f12Sschwarze break;
154e74fa2aeSschwarze case '>':
155c6c22f12Sschwarze printf(">");
156c6c22f12Sschwarze break;
157e74fa2aeSschwarze case '<':
158c6c22f12Sschwarze printf("<");
159c6c22f12Sschwarze break;
160c6c22f12Sschwarze default:
161c6c22f12Sschwarze putchar((unsigned char)c);
162c6c22f12Sschwarze break;
163c6c22f12Sschwarze }
164c6c22f12Sschwarze }
165c6c22f12Sschwarze
166c6c22f12Sschwarze /*
167c6c22f12Sschwarze * Call through to html_putchar().
168c6c22f12Sschwarze * Accepts NULL strings.
169c6c22f12Sschwarze */
170c6c22f12Sschwarze static void
html_print(const char * p)171c6c22f12Sschwarze html_print(const char *p)
172c6c22f12Sschwarze {
173c6c22f12Sschwarze
174c6c22f12Sschwarze if (NULL == p)
175c6c22f12Sschwarze return;
176c6c22f12Sschwarze while ('\0' != *p)
177c6c22f12Sschwarze html_putchar(*p++);
178c6c22f12Sschwarze }
179c6c22f12Sschwarze
180c6c22f12Sschwarze /*
18131e689c3Sschwarze * Transfer the responsibility for the allocated string *val
18231e689c3Sschwarze * to the query structure.
183c6c22f12Sschwarze */
184c6c22f12Sschwarze static void
set_query_attr(char ** attr,char ** val)18531e689c3Sschwarze set_query_attr(char **attr, char **val)
18631e689c3Sschwarze {
18731e689c3Sschwarze
18831e689c3Sschwarze free(*attr);
18931e689c3Sschwarze if (**val == '\0') {
19031e689c3Sschwarze *attr = NULL;
19131e689c3Sschwarze free(*val);
19231e689c3Sschwarze } else
19331e689c3Sschwarze *attr = *val;
19431e689c3Sschwarze *val = NULL;
19531e689c3Sschwarze }
19631e689c3Sschwarze
19731e689c3Sschwarze /*
19831e689c3Sschwarze * Parse the QUERY_STRING for key-value pairs
19931e689c3Sschwarze * and store the values into the query structure.
20031e689c3Sschwarze */
20131e689c3Sschwarze static void
parse_query_string(struct req * req,const char * qs)202941df026Sschwarze parse_query_string(struct req *req, const char *qs)
203c6c22f12Sschwarze {
204c6c22f12Sschwarze char *key, *val;
20531e689c3Sschwarze size_t keysz, valsz;
206c6c22f12Sschwarze
20752b413c1Sschwarze req->isquery = 1;
20831e689c3Sschwarze req->q.manpath = NULL;
20931e689c3Sschwarze req->q.arch = NULL;
21031e689c3Sschwarze req->q.sec = NULL;
211e89321abSschwarze req->q.query = NULL;
2124477fbfaSschwarze req->q.equal = 1;
213c6c22f12Sschwarze
21431e689c3Sschwarze key = val = NULL;
21531e689c3Sschwarze while (*qs != '\0') {
216c6c22f12Sschwarze
21731e689c3Sschwarze /* Parse one key. */
218c6c22f12Sschwarze
21931e689c3Sschwarze keysz = strcspn(qs, "=;&");
22031e689c3Sschwarze key = mandoc_strndup(qs, keysz);
22131e689c3Sschwarze qs += keysz;
22231e689c3Sschwarze if (*qs != '=')
22331e689c3Sschwarze goto next;
224c6c22f12Sschwarze
22531e689c3Sschwarze /* Parse one value. */
226c6c22f12Sschwarze
22731e689c3Sschwarze valsz = strcspn(++qs, ";&");
22831e689c3Sschwarze val = mandoc_strndup(qs, valsz);
22931e689c3Sschwarze qs += valsz;
230c6c22f12Sschwarze
23131e689c3Sschwarze /* Decode and catch encoding errors. */
23231e689c3Sschwarze
23331e689c3Sschwarze if ( ! (http_decode(key) && http_decode(val)))
23431e689c3Sschwarze goto next;
23531e689c3Sschwarze
23631e689c3Sschwarze /* Handle key-value pairs. */
23731e689c3Sschwarze
23831e689c3Sschwarze if ( ! strcmp(key, "query"))
239e89321abSschwarze set_query_attr(&req->q.query, &val);
24031e689c3Sschwarze
24131e689c3Sschwarze else if ( ! strcmp(key, "apropos"))
24231e689c3Sschwarze req->q.equal = !strcmp(val, "0");
24331e689c3Sschwarze
24431e689c3Sschwarze else if ( ! strcmp(key, "manpath")) {
245aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
24631e689c3Sschwarze if ( ! strncmp(val, "OpenBSD ", 8)) {
247aabcc9a1Sschwarze val[7] = '-';
248aabcc9a1Sschwarze if ('C' == val[8])
249aabcc9a1Sschwarze val[8] = 'c';
250aabcc9a1Sschwarze }
251aabcc9a1Sschwarze #endif
25231e689c3Sschwarze set_query_attr(&req->q.manpath, &val);
25331e689c3Sschwarze }
25431e689c3Sschwarze
25531e689c3Sschwarze else if ( ! (strcmp(key, "sec")
256aabcc9a1Sschwarze #ifdef COMPAT_OLDURI
25731e689c3Sschwarze && strcmp(key, "sektion")
258aabcc9a1Sschwarze #endif
25931e689c3Sschwarze )) {
26031e689c3Sschwarze if ( ! strcmp(val, "0"))
26131e689c3Sschwarze *val = '\0';
26231e689c3Sschwarze set_query_attr(&req->q.sec, &val);
263c6c22f12Sschwarze }
26431e689c3Sschwarze
26531e689c3Sschwarze else if ( ! strcmp(key, "arch")) {
26631e689c3Sschwarze if ( ! strcmp(val, "default"))
26731e689c3Sschwarze *val = '\0';
26831e689c3Sschwarze set_query_attr(&req->q.arch, &val);
2694477fbfaSschwarze }
27031e689c3Sschwarze
27131e689c3Sschwarze /*
27231e689c3Sschwarze * The key must be freed in any case.
27331e689c3Sschwarze * The val may have been handed over to the query
27431e689c3Sschwarze * structure, in which case it is now NULL.
27531e689c3Sschwarze */
27631e689c3Sschwarze next:
27731e689c3Sschwarze free(key);
27831e689c3Sschwarze key = NULL;
27931e689c3Sschwarze free(val);
28031e689c3Sschwarze val = NULL;
28131e689c3Sschwarze
28231e689c3Sschwarze if (*qs != '\0')
28331e689c3Sschwarze qs++;
28431e689c3Sschwarze }
285c6c22f12Sschwarze }
286c6c22f12Sschwarze
287c6c22f12Sschwarze /*
288c6c22f12Sschwarze * HTTP-decode a string. The standard explanation is that this turns
289c6c22f12Sschwarze * "%4e+foo" into "n foo" in the regular way. This is done in-place
290c6c22f12Sschwarze * over the allocated string.
291c6c22f12Sschwarze */
292c6c22f12Sschwarze static int
http_decode(char * p)293c6c22f12Sschwarze http_decode(char *p)
294c6c22f12Sschwarze {
295c6c22f12Sschwarze char hex[3];
2961f69f32bStedu char *q;
297c6c22f12Sschwarze int c;
298c6c22f12Sschwarze
299c6c22f12Sschwarze hex[2] = '\0';
300c6c22f12Sschwarze
3011f69f32bStedu q = p;
3021f69f32bStedu for ( ; '\0' != *p; p++, q++) {
303c6c22f12Sschwarze if ('%' == *p) {
304c6c22f12Sschwarze if ('\0' == (hex[0] = *(p + 1)))
305526e306bSschwarze return 0;
306c6c22f12Sschwarze if ('\0' == (hex[1] = *(p + 2)))
307526e306bSschwarze return 0;
308c6c22f12Sschwarze if (1 != sscanf(hex, "%x", &c))
309526e306bSschwarze return 0;
310c6c22f12Sschwarze if ('\0' == c)
311526e306bSschwarze return 0;
312c6c22f12Sschwarze
3131f69f32bStedu *q = (char)c;
3141f69f32bStedu p += 2;
315c6c22f12Sschwarze } else
3161f69f32bStedu *q = '+' == *p ? ' ' : *p;
317c6c22f12Sschwarze }
318c6c22f12Sschwarze
3191f69f32bStedu *q = '\0';
320526e306bSschwarze return 1;
321c6c22f12Sschwarze }
322c6c22f12Sschwarze
323c6c22f12Sschwarze static void
http_encode(const char * p)324f7a12365Sschwarze http_encode(const char *p)
325f7a12365Sschwarze {
326f7a12365Sschwarze for (; *p != '\0'; p++) {
327f7a12365Sschwarze if (isalnum((unsigned char)*p) == 0 &&
328f7a12365Sschwarze strchr("-._~", *p) == NULL)
3291ecf8c4fSschwarze printf("%%%2.2X", (unsigned char)*p);
330f7a12365Sschwarze else
331f7a12365Sschwarze putchar(*p);
332f7a12365Sschwarze }
333f7a12365Sschwarze }
334f7a12365Sschwarze
335f7a12365Sschwarze static void
resp_begin_http(int code,const char * msg)336c6c22f12Sschwarze resp_begin_http(int code, const char *msg)
337c6c22f12Sschwarze {
338c6c22f12Sschwarze
339c6c22f12Sschwarze if (200 != code)
340fa9c540aStedu printf("Status: %d %s\r\n", code, msg);
341c6c22f12Sschwarze
342fa9c540aStedu printf("Content-Type: text/html; charset=utf-8\r\n"
343fa9c540aStedu "Cache-Control: no-cache\r\n"
34453459fdfSbentley "Content-Security-Policy: default-src 'none'; "
34553459fdfSbentley "style-src 'self' 'unsafe-inline'\r\n"
346fa9c540aStedu "Pragma: no-cache\r\n"
347fa9c540aStedu "\r\n");
348c6c22f12Sschwarze
349c6c22f12Sschwarze fflush(stdout);
350c6c22f12Sschwarze }
351c6c22f12Sschwarze
352a43df5a0Sschwarze static int
resp_copy(const char * element,const char * filename)353a43df5a0Sschwarze resp_copy(const char *element, const char *filename)
354711661c7Sschwarze {
355711661c7Sschwarze char buf[4096];
356711661c7Sschwarze ssize_t sz;
357711661c7Sschwarze int fd;
358711661c7Sschwarze
359a43df5a0Sschwarze if ((fd = open(filename, O_RDONLY)) == -1)
360a43df5a0Sschwarze return 0;
361a43df5a0Sschwarze
362a43df5a0Sschwarze if (element != NULL)
363a43df5a0Sschwarze printf("<%s>\n", element);
364711661c7Sschwarze fflush(stdout);
365711661c7Sschwarze while ((sz = read(fd, buf, sizeof(buf))) > 0)
366711661c7Sschwarze write(STDOUT_FILENO, buf, sz);
367fd3cdd86Sjsg close(fd);
368a43df5a0Sschwarze return 1;
369711661c7Sschwarze }
370711661c7Sschwarze
371a43df5a0Sschwarze static int
resp_begin_html(int code,const char * msg,const char * file)372fdef72b0Sschwarze resp_begin_html(int code, const char *msg, const char *file)
373c6c22f12Sschwarze {
374e621f1d2Sschwarze const char *name, *sec, *cp;
375e621f1d2Sschwarze int namesz, secsz;
376c6c22f12Sschwarze
377c6c22f12Sschwarze resp_begin_http(code, msg);
378c6c22f12Sschwarze
379d649d931Sschwarze printf("<!DOCTYPE html>\n"
380735516bdSschwarze "<html>\n"
381735516bdSschwarze "<head>\n"
382735516bdSschwarze " <meta charset=\"UTF-8\"/>\n"
383661a42d8Sschwarze " <meta name=\"viewport\""
384661a42d8Sschwarze " content=\"width=device-width, initial-scale=1.0\">\n"
385735516bdSschwarze " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
386735516bdSschwarze " type=\"text/css\" media=\"all\">\n"
387fdef72b0Sschwarze " <title>",
388fdef72b0Sschwarze CSS_DIR);
389fdef72b0Sschwarze if (file != NULL) {
390e621f1d2Sschwarze cp = strrchr(file, '/');
391e621f1d2Sschwarze name = cp == NULL ? file : cp + 1;
392e621f1d2Sschwarze cp = strrchr(name, '.');
393e621f1d2Sschwarze namesz = cp == NULL ? strlen(name) : cp - name;
394e621f1d2Sschwarze sec = NULL;
395e621f1d2Sschwarze if (cp != NULL && cp[1] != '0') {
396e621f1d2Sschwarze sec = cp + 1;
397e621f1d2Sschwarze secsz = strlen(sec);
398e621f1d2Sschwarze } else if (name - file > 1) {
399e621f1d2Sschwarze for (cp = name - 2; cp >= file; cp--) {
400e621f1d2Sschwarze if (*cp < '1' || *cp > '9')
401e621f1d2Sschwarze continue;
402e621f1d2Sschwarze sec = cp;
403e621f1d2Sschwarze secsz = name - cp - 1;
404e621f1d2Sschwarze break;
405e621f1d2Sschwarze }
406e621f1d2Sschwarze }
407e621f1d2Sschwarze printf("%.*s", namesz, name);
408e621f1d2Sschwarze if (sec != NULL)
409e621f1d2Sschwarze printf("(%.*s)", secsz, sec);
410e621f1d2Sschwarze fputs(" - ", stdout);
411fdef72b0Sschwarze }
412fdef72b0Sschwarze printf("%s</title>\n"
413735516bdSschwarze "</head>\n"
414ce781f36Sschwarze "<body>\n",
415fdef72b0Sschwarze CUSTOMIZE_TITLE);
416711661c7Sschwarze
417a43df5a0Sschwarze return resp_copy("header", MAN_DIR "/header.html");
418c6c22f12Sschwarze }
419c6c22f12Sschwarze
420c6c22f12Sschwarze static void
resp_end_html(void)421c6c22f12Sschwarze resp_end_html(void)
422c6c22f12Sschwarze {
423a43df5a0Sschwarze if (resp_copy("footer", MAN_DIR "/footer.html"))
424a43df5a0Sschwarze puts("</footer>");
425711661c7Sschwarze
426735516bdSschwarze puts("</body>\n"
427735516bdSschwarze "</html>");
428c6c22f12Sschwarze }
429c6c22f12Sschwarze
430c6c22f12Sschwarze static void
resp_searchform(const struct req * req,enum focus focus)43184f05c93Sschwarze resp_searchform(const struct req *req, enum focus focus)
432c6c22f12Sschwarze {
433c6c22f12Sschwarze int i;
434c6c22f12Sschwarze
435a43df5a0Sschwarze printf("<form role=\"search\" action=\"/%s\" method=\"get\" "
4367411869fSschwarze "autocomplete=\"off\" autocapitalize=\"none\">\n"
437735516bdSschwarze " <fieldset>\n"
438735516bdSschwarze " <legend>Manual Page Search Parameters</legend>\n",
439c6c22f12Sschwarze scriptname);
44028e449d6Sschwarze
44128e449d6Sschwarze /* Write query input box. */
44228e449d6Sschwarze
443df46652dSschwarze printf(" <label>Search query:\n"
444df46652dSschwarze " <input type=\"search\" name=\"query\" value=\"");
44584f05c93Sschwarze if (req->q.query != NULL)
446e89321abSschwarze html_print(req->q.query);
44784f05c93Sschwarze printf("\" size=\"40\"");
44884f05c93Sschwarze if (focus == FOCUS_QUERY)
44984f05c93Sschwarze printf(" autofocus");
450df46652dSschwarze puts(">\n </label>");
45128e449d6Sschwarze
452784a63d6Sschwarze /* Write submission buttons. */
45328e449d6Sschwarze
454784a63d6Sschwarze printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
455784a63d6Sschwarze "man</button>\n"
456784a63d6Sschwarze " <button type=\"submit\" name=\"apropos\" value=\"1\">"
457542ee4bfSschwarze "apropos</button>\n"
458542ee4bfSschwarze " <br/>\n");
45928e449d6Sschwarze
46028e449d6Sschwarze /* Write section selector. */
46128e449d6Sschwarze
462f0f927fcSschwarze puts(" <select name=\"sec\" aria-label=\"Manual section\">");
46328e449d6Sschwarze for (i = 0; i < sec_MAX; i++) {
464735516bdSschwarze printf(" <option value=\"%s\"", sec_numbers[i]);
46528e449d6Sschwarze if (NULL != req->q.sec &&
46628e449d6Sschwarze 0 == strcmp(sec_numbers[i], req->q.sec))
467735516bdSschwarze printf(" selected=\"selected\"");
468735516bdSschwarze printf(">%s</option>\n", sec_names[i]);
46928e449d6Sschwarze }
470735516bdSschwarze puts(" </select>");
47128e449d6Sschwarze
47228e449d6Sschwarze /* Write architecture selector. */
47328e449d6Sschwarze
474b2d1ada6Sschwarze printf( " <select name=\"arch\" aria-label=\"CPU architecture\">\n"
475735516bdSschwarze " <option value=\"default\"");
476be14a32aSschwarze if (NULL == req->q.arch)
477735516bdSschwarze printf(" selected=\"selected\"");
478735516bdSschwarze puts(">All Architectures</option>");
47928e449d6Sschwarze for (i = 0; i < arch_MAX; i++) {
4806b824e3bSschwarze printf(" <option");
48128e449d6Sschwarze if (NULL != req->q.arch &&
48228e449d6Sschwarze 0 == strcmp(arch_names[i], req->q.arch))
483735516bdSschwarze printf(" selected=\"selected\"");
484735516bdSschwarze printf(">%s</option>\n", arch_names[i]);
48528e449d6Sschwarze }
486735516bdSschwarze puts(" </select>");
48728e449d6Sschwarze
48828e449d6Sschwarze /* Write manpath selector. */
48928e449d6Sschwarze
490c6c22f12Sschwarze if (req->psz > 1) {
491df46652dSschwarze puts(" <select name=\"manpath\""
492df46652dSschwarze " aria-label=\"Manual path\">");
493c6c22f12Sschwarze for (i = 0; i < (int)req->psz; i++) {
494735516bdSschwarze printf(" <option");
49550eaed2bSschwarze if (strcmp(req->q.manpath, req->p[i]) == 0)
496735516bdSschwarze printf(" selected=\"selected\"");
4976b824e3bSschwarze printf(">");
498c6c22f12Sschwarze html_print(req->p[i]);
499735516bdSschwarze puts("</option>");
500c6c22f12Sschwarze }
501735516bdSschwarze puts(" </select>");
502c6c22f12Sschwarze }
50328e449d6Sschwarze
504784a63d6Sschwarze puts(" </fieldset>\n"
505a43df5a0Sschwarze "</form>");
506c6c22f12Sschwarze }
507c6c22f12Sschwarze
50881475784Sschwarze static int
validate_urifrag(const char * frag)509cf3a545cSschwarze validate_urifrag(const char *frag)
510cf3a545cSschwarze {
511cf3a545cSschwarze
512cf3a545cSschwarze while ('\0' != *frag) {
513cf3a545cSschwarze if ( ! (isalnum((unsigned char)*frag) ||
514cf3a545cSschwarze '-' == *frag || '.' == *frag ||
515cf3a545cSschwarze '/' == *frag || '_' == *frag))
516526e306bSschwarze return 0;
517cf3a545cSschwarze frag++;
518cf3a545cSschwarze }
519526e306bSschwarze return 1;
520cf3a545cSschwarze }
521cf3a545cSschwarze
522cf3a545cSschwarze static int
validate_manpath(const struct req * req,const char * manpath)523631ce2c6Sschwarze validate_manpath(const struct req *req, const char* manpath)
524631ce2c6Sschwarze {
525631ce2c6Sschwarze size_t i;
526631ce2c6Sschwarze
527631ce2c6Sschwarze for (i = 0; i < req->psz; i++)
528631ce2c6Sschwarze if ( ! strcmp(manpath, req->p[i]))
529526e306bSschwarze return 1;
530631ce2c6Sschwarze
531526e306bSschwarze return 0;
532631ce2c6Sschwarze }
533631ce2c6Sschwarze
534631ce2c6Sschwarze static int
validate_arch(const char * arch)535f7a12365Sschwarze validate_arch(const char *arch)
536f7a12365Sschwarze {
537f7a12365Sschwarze int i;
538f7a12365Sschwarze
539f7a12365Sschwarze for (i = 0; i < arch_MAX; i++)
540f7a12365Sschwarze if (strcmp(arch, arch_names[i]) == 0)
541f7a12365Sschwarze return 1;
542f7a12365Sschwarze
543f7a12365Sschwarze return 0;
544f7a12365Sschwarze }
545f7a12365Sschwarze
546f7a12365Sschwarze static int
validate_filename(const char * file)54781475784Sschwarze validate_filename(const char *file)
54881475784Sschwarze {
54981475784Sschwarze
55081475784Sschwarze if ('.' == file[0] && '/' == file[1])
55181475784Sschwarze file += 2;
55281475784Sschwarze
553526e306bSschwarze return ! (strstr(file, "../") || strstr(file, "/..") ||
554526e306bSschwarze (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
55581475784Sschwarze }
55681475784Sschwarze
557c6c22f12Sschwarze static void
pg_index(const struct req * req)558facea411Sschwarze pg_index(const struct req *req)
559c6c22f12Sschwarze {
560a43df5a0Sschwarze if (resp_begin_html(200, NULL, NULL) == 0)
561a43df5a0Sschwarze puts("<header>");
56284f05c93Sschwarze resp_searchform(req, FOCUS_QUERY);
563a43df5a0Sschwarze printf("</header>\n"
564a43df5a0Sschwarze "<main>\n"
565f0f927fcSschwarze "<p role=\"doc-notice\" aria-label=\"Usage\">\n"
566d56ca219Sschwarze "This web interface is documented in the\n"
567b2d1ada6Sschwarze "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
568b2d1ada6Sschwarze " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
569d56ca219Sschwarze "manual, and the\n"
570b2d1ada6Sschwarze "<a class=\"Xr\" href=\"/%s%sapropos.1\""
571b2d1ada6Sschwarze " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
5722a43838fSschwarze "manual explains the query syntax.\n"
573b2d1ada6Sschwarze "</p>\n"
574b2d1ada6Sschwarze "</main>\n",
5753b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/",
5763b9cfc6fSschwarze scriptname, *scriptname == '\0' ? "" : "/");
577c6c22f12Sschwarze resp_end_html();
578c6c22f12Sschwarze }
579c6c22f12Sschwarze
580c6c22f12Sschwarze static void
pg_noresult(const struct req * req,int code,const char * http_msg,const char * user_msg)58118ccf011Sschwarze pg_noresult(const struct req *req, int code, const char *http_msg,
58218ccf011Sschwarze const char *user_msg)
583c6c22f12Sschwarze {
584a43df5a0Sschwarze if (resp_begin_html(code, http_msg, NULL) == 0)
585a43df5a0Sschwarze puts("<header>");
58684f05c93Sschwarze resp_searchform(req, FOCUS_QUERY);
587a43df5a0Sschwarze puts("</header>");
588b2d1ada6Sschwarze puts("<main>");
589f0f927fcSschwarze puts("<p role=\"doc-notice\" aria-label=\"No result\">");
59018ccf011Sschwarze puts(user_msg);
591735516bdSschwarze puts("</p>");
592b2d1ada6Sschwarze puts("</main>");
593c6c22f12Sschwarze resp_end_html();
594c6c22f12Sschwarze }
595c6c22f12Sschwarze
596c6c22f12Sschwarze static void
pg_error_badrequest(const char * msg)597facea411Sschwarze pg_error_badrequest(const char *msg)
598c6c22f12Sschwarze {
599a43df5a0Sschwarze if (resp_begin_html(400, "Bad Request", NULL))
600a43df5a0Sschwarze puts("</header>");
601b2d1ada6Sschwarze puts("<main>\n"
602b2d1ada6Sschwarze "<h1>Bad Request</h1>\n"
603b2d1ada6Sschwarze "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
604c6c22f12Sschwarze puts(msg);
605c6c22f12Sschwarze printf("Try again from the\n"
606735516bdSschwarze "<a href=\"/%s\">main page</a>.\n"
607b2d1ada6Sschwarze "</p>\n"
608a43df5a0Sschwarze "</main>\n", scriptname);
609c6c22f12Sschwarze resp_end_html();
610c6c22f12Sschwarze }
611c6c22f12Sschwarze
612c6c22f12Sschwarze static void
pg_error_internal(void)613facea411Sschwarze pg_error_internal(void)
614c6c22f12Sschwarze {
615a43df5a0Sschwarze if (resp_begin_html(500, "Internal Server Error", NULL))
616a43df5a0Sschwarze puts("</header>");
617b2d1ada6Sschwarze puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
618c6c22f12Sschwarze resp_end_html();
619c6c22f12Sschwarze }
620c6c22f12Sschwarze
621c6c22f12Sschwarze static void
pg_redirect(const struct req * req,const char * name)622e1beff2aSschwarze pg_redirect(const struct req *req, const char *name)
623e1beff2aSschwarze {
624e0d9a108Sschwarze printf("Status: 303 See Other\r\n"
625e0d9a108Sschwarze "Location: /");
626e1beff2aSschwarze if (*scriptname != '\0')
627e1beff2aSschwarze printf("%s/", scriptname);
628e1beff2aSschwarze if (strcmp(req->q.manpath, req->p[0]))
629e1beff2aSschwarze printf("%s/", req->q.manpath);
630e1beff2aSschwarze if (req->q.arch != NULL)
631e1beff2aSschwarze printf("%s/", req->q.arch);
632f7a12365Sschwarze http_encode(name);
633f7a12365Sschwarze if (req->q.sec != NULL) {
634f7a12365Sschwarze putchar('.');
635f7a12365Sschwarze http_encode(req->q.sec);
636f7a12365Sschwarze }
637e1beff2aSschwarze printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
638e1beff2aSschwarze }
639e1beff2aSschwarze
640e1beff2aSschwarze static void
pg_searchres(const struct req * req,struct manpage * r,size_t sz)641facea411Sschwarze pg_searchres(const struct req *req, struct manpage *r, size_t sz)
642c6c22f12Sschwarze {
643be14a32aSschwarze char *arch, *archend;
644db26164eSschwarze const char *sec;
645db26164eSschwarze size_t i, iuse;
646be14a32aSschwarze int archprio, archpriouse;
64746723f19Sschwarze int prio, priouse;
648a43df5a0Sschwarze int have_header;
649c6c22f12Sschwarze
65081475784Sschwarze for (i = 0; i < sz; i++) {
65181475784Sschwarze if (validate_filename(r[i].file))
65281475784Sschwarze continue;
653c976f0e2Sschwarze warnx("invalid filename %s in %s database",
65481475784Sschwarze r[i].file, req->q.manpath);
65581475784Sschwarze pg_error_internal();
65681475784Sschwarze return;
65781475784Sschwarze }
65881475784Sschwarze
65952b413c1Sschwarze if (req->isquery && sz == 1) {
660c6c22f12Sschwarze /*
661c6c22f12Sschwarze * If we have just one result, then jump there now
662c6c22f12Sschwarze * without any delay.
663c6c22f12Sschwarze */
664e0d9a108Sschwarze printf("Status: 303 See Other\r\n"
665e0d9a108Sschwarze "Location: /");
666e0d9a108Sschwarze if (*scriptname != '\0')
667e0d9a108Sschwarze printf("%s/", scriptname);
668e0d9a108Sschwarze if (strcmp(req->q.manpath, req->p[0]))
669e0d9a108Sschwarze printf("%s/", req->q.manpath);
670e0d9a108Sschwarze printf("%s\r\n"
671e0d9a108Sschwarze "Content-Type: text/html; charset=utf-8\r\n\r\n",
672e0d9a108Sschwarze r[0].file);
673c6c22f12Sschwarze return;
674c6c22f12Sschwarze }
675c6c22f12Sschwarze
67646723f19Sschwarze /*
67746723f19Sschwarze * In man(1) mode, show one of the pages
67846723f19Sschwarze * even if more than one is found.
67946723f19Sschwarze */
68046723f19Sschwarze
68146723f19Sschwarze iuse = 0;
682fdef72b0Sschwarze if (req->q.equal || sz == 1) {
683db26164eSschwarze priouse = 20;
684be14a32aSschwarze archpriouse = 3;
68546723f19Sschwarze for (i = 0; i < sz; i++) {
686db26164eSschwarze sec = r[i].file;
687db26164eSschwarze sec += strcspn(sec, "123456789");
688db26164eSschwarze if (sec[0] == '\0')
68946723f19Sschwarze continue;
690db26164eSschwarze prio = sec_prios[sec[0] - '1'];
691db26164eSschwarze if (sec[1] != '/')
692db26164eSschwarze prio += 10;
693db26164eSschwarze if (req->q.arch == NULL) {
694be14a32aSschwarze archprio =
695db26164eSschwarze ((arch = strchr(sec + 1, '/'))
696db26164eSschwarze == NULL) ? 3 :
697db26164eSschwarze ((archend = strchr(arch + 1, '/'))
698db26164eSschwarze == NULL) ? 0 :
699be14a32aSschwarze strncmp(arch, "amd64/",
700be14a32aSschwarze archend - arch) ? 2 : 1;
701be14a32aSschwarze if (archprio < archpriouse) {
702be14a32aSschwarze archpriouse = archprio;
703be14a32aSschwarze priouse = prio;
704be14a32aSschwarze iuse = i;
705be14a32aSschwarze continue;
706be14a32aSschwarze }
707be14a32aSschwarze if (archprio > archpriouse)
708be14a32aSschwarze continue;
709be14a32aSschwarze }
71046723f19Sschwarze if (prio >= priouse)
71146723f19Sschwarze continue;
71246723f19Sschwarze priouse = prio;
71346723f19Sschwarze iuse = i;
71446723f19Sschwarze }
715a43df5a0Sschwarze have_header = resp_begin_html(200, NULL, r[iuse].file);
716fdef72b0Sschwarze } else
717a43df5a0Sschwarze have_header = resp_begin_html(200, NULL, NULL);
718fdef72b0Sschwarze
719a43df5a0Sschwarze if (have_header == 0)
720a43df5a0Sschwarze puts("<header>");
721fdef72b0Sschwarze resp_searchform(req,
722fdef72b0Sschwarze req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
723a43df5a0Sschwarze puts("</header>");
724fdef72b0Sschwarze
725fdef72b0Sschwarze if (sz > 1) {
726b2d1ada6Sschwarze puts("<nav>");
727fdef72b0Sschwarze puts("<table class=\"results\">");
728fdef72b0Sschwarze for (i = 0; i < sz; i++) {
729fdef72b0Sschwarze printf(" <tr>\n"
730fdef72b0Sschwarze " <td>"
7317b3dbe16Sschwarze "<a class=\"Xr\" href=\"/");
7327b3dbe16Sschwarze if (*scriptname != '\0')
7337b3dbe16Sschwarze printf("%s/", scriptname);
7347b3dbe16Sschwarze if (strcmp(req->q.manpath, req->p[0]))
7357b3dbe16Sschwarze printf("%s/", req->q.manpath);
7367b3dbe16Sschwarze printf("%s\">", r[i].file);
737fdef72b0Sschwarze html_print(r[i].names);
738fdef72b0Sschwarze printf("</a></td>\n"
739fdef72b0Sschwarze " <td><span class=\"Nd\">");
740fdef72b0Sschwarze html_print(r[i].output);
741fdef72b0Sschwarze puts("</span></td>\n"
742fdef72b0Sschwarze " </tr>");
743fdef72b0Sschwarze }
744fdef72b0Sschwarze puts("</table>");
745b2d1ada6Sschwarze puts("</nav>");
746fdef72b0Sschwarze }
747fdef72b0Sschwarze
748fdef72b0Sschwarze if (req->q.equal || sz == 1) {
749fdef72b0Sschwarze puts("<hr>");
75046723f19Sschwarze resp_show(req, r[iuse].file);
75146723f19Sschwarze }
75246723f19Sschwarze
753c6c22f12Sschwarze resp_end_html();
754c6c22f12Sschwarze }
755c6c22f12Sschwarze
756c6c22f12Sschwarze static void
resp_catman(const struct req * req,const char * file)757941df026Sschwarze resp_catman(const struct req *req, const char *file)
758c6c22f12Sschwarze {
759c6c22f12Sschwarze FILE *f;
760c6c22f12Sschwarze char *p;
76131f93c25Sschwarze size_t sz;
76231f93c25Sschwarze ssize_t len;
76331f93c25Sschwarze int i;
764c6c22f12Sschwarze int italic, bold;
765c6c22f12Sschwarze
76631f93c25Sschwarze if ((f = fopen(file, "r")) == NULL) {
767b2d1ada6Sschwarze puts("<p role=\"doc-notice\">\n"
768b2d1ada6Sschwarze " You specified an invalid manual file.\n"
769b2d1ada6Sschwarze "</p>");
770c6c22f12Sschwarze return;
771c6c22f12Sschwarze }
772c6c22f12Sschwarze
773735516bdSschwarze puts("<div class=\"catman\">\n"
774735516bdSschwarze "<pre>");
775c6c22f12Sschwarze
77631f93c25Sschwarze p = NULL;
77731f93c25Sschwarze sz = 0;
77831f93c25Sschwarze
77931f93c25Sschwarze while ((len = getline(&p, &sz, f)) != -1) {
780c6c22f12Sschwarze bold = italic = 0;
78131f93c25Sschwarze for (i = 0; i < len - 1; i++) {
782c6c22f12Sschwarze /*
783c6c22f12Sschwarze * This means that the catpage is out of state.
784c6c22f12Sschwarze * Ignore it and keep going (although the
785c6c22f12Sschwarze * catpage is bogus).
786c6c22f12Sschwarze */
787c6c22f12Sschwarze
788c6c22f12Sschwarze if ('\b' == p[i] || '\n' == p[i])
789c6c22f12Sschwarze continue;
790c6c22f12Sschwarze
791c6c22f12Sschwarze /*
792c6c22f12Sschwarze * Print a regular character.
793c6c22f12Sschwarze * Close out any bold/italic scopes.
794c6c22f12Sschwarze * If we're in back-space mode, make sure we'll
795c6c22f12Sschwarze * have something to enter when we backspace.
796c6c22f12Sschwarze */
797c6c22f12Sschwarze
798c6c22f12Sschwarze if ('\b' != p[i + 1]) {
799c6c22f12Sschwarze if (italic)
800735516bdSschwarze printf("</i>");
801c6c22f12Sschwarze if (bold)
802735516bdSschwarze printf("</b>");
803c6c22f12Sschwarze italic = bold = 0;
804c6c22f12Sschwarze html_putchar(p[i]);
805c6c22f12Sschwarze continue;
80631f93c25Sschwarze } else if (i + 2 >= len)
807c6c22f12Sschwarze continue;
808c6c22f12Sschwarze
809c6c22f12Sschwarze /* Italic mode. */
810c6c22f12Sschwarze
811c6c22f12Sschwarze if ('_' == p[i]) {
812c6c22f12Sschwarze if (bold)
813735516bdSschwarze printf("</b>");
814c6c22f12Sschwarze if ( ! italic)
815735516bdSschwarze printf("<i>");
816c6c22f12Sschwarze bold = 0;
817c6c22f12Sschwarze italic = 1;
818c6c22f12Sschwarze i += 2;
819c6c22f12Sschwarze html_putchar(p[i]);
820c6c22f12Sschwarze continue;
821c6c22f12Sschwarze }
822c6c22f12Sschwarze
823c6c22f12Sschwarze /*
824c6c22f12Sschwarze * Handle funny behaviour troff-isms.
825c6c22f12Sschwarze * These grok'd from the original man2html.c.
826c6c22f12Sschwarze */
827c6c22f12Sschwarze
828c6c22f12Sschwarze if (('+' == p[i] && 'o' == p[i + 2]) ||
829c6c22f12Sschwarze ('o' == p[i] && '+' == p[i + 2]) ||
830c6c22f12Sschwarze ('|' == p[i] && '=' == p[i + 2]) ||
831c6c22f12Sschwarze ('=' == p[i] && '|' == p[i + 2]) ||
832c6c22f12Sschwarze ('*' == p[i] && '=' == p[i + 2]) ||
833c6c22f12Sschwarze ('=' == p[i] && '*' == p[i + 2]) ||
834c6c22f12Sschwarze ('*' == p[i] && '|' == p[i + 2]) ||
835c6c22f12Sschwarze ('|' == p[i] && '*' == p[i + 2])) {
836c6c22f12Sschwarze if (italic)
837735516bdSschwarze printf("</i>");
838c6c22f12Sschwarze if (bold)
839735516bdSschwarze printf("</b>");
840c6c22f12Sschwarze italic = bold = 0;
841c6c22f12Sschwarze putchar('*');
842c6c22f12Sschwarze i += 2;
843c6c22f12Sschwarze continue;
844c6c22f12Sschwarze } else if (('|' == p[i] && '-' == p[i + 2]) ||
845c6c22f12Sschwarze ('-' == p[i] && '|' == p[i + 1]) ||
846c6c22f12Sschwarze ('+' == p[i] && '-' == p[i + 1]) ||
847c6c22f12Sschwarze ('-' == p[i] && '+' == p[i + 1]) ||
848c6c22f12Sschwarze ('+' == p[i] && '|' == p[i + 1]) ||
849c6c22f12Sschwarze ('|' == p[i] && '+' == p[i + 1])) {
850c6c22f12Sschwarze if (italic)
851735516bdSschwarze printf("</i>");
852c6c22f12Sschwarze if (bold)
853735516bdSschwarze printf("</b>");
854c6c22f12Sschwarze italic = bold = 0;
855c6c22f12Sschwarze putchar('+');
856c6c22f12Sschwarze i += 2;
857c6c22f12Sschwarze continue;
858c6c22f12Sschwarze }
859c6c22f12Sschwarze
860c6c22f12Sschwarze /* Bold mode. */
861c6c22f12Sschwarze
862c6c22f12Sschwarze if (italic)
863735516bdSschwarze printf("</i>");
864c6c22f12Sschwarze if ( ! bold)
865735516bdSschwarze printf("<b>");
866c6c22f12Sschwarze bold = 1;
867c6c22f12Sschwarze italic = 0;
868c6c22f12Sschwarze i += 2;
869c6c22f12Sschwarze html_putchar(p[i]);
870c6c22f12Sschwarze }
871c6c22f12Sschwarze
872c6c22f12Sschwarze /*
873c6c22f12Sschwarze * Clean up the last character.
874c6c22f12Sschwarze * We can get to a newline; don't print that.
875c6c22f12Sschwarze */
876c6c22f12Sschwarze
877c6c22f12Sschwarze if (italic)
878735516bdSschwarze printf("</i>");
879c6c22f12Sschwarze if (bold)
880735516bdSschwarze printf("</b>");
881c6c22f12Sschwarze
88231f93c25Sschwarze if (i == len - 1 && p[i] != '\n')
883c6c22f12Sschwarze html_putchar(p[i]);
884c6c22f12Sschwarze
885c6c22f12Sschwarze putchar('\n');
886c6c22f12Sschwarze }
88731f93c25Sschwarze free(p);
888c6c22f12Sschwarze
889735516bdSschwarze puts("</pre>\n"
890735516bdSschwarze "</div>");
891c6c22f12Sschwarze
892c6c22f12Sschwarze fclose(f);
893c6c22f12Sschwarze }
894c6c22f12Sschwarze
895c6c22f12Sschwarze static void
resp_format(const struct req * req,const char * file)896941df026Sschwarze resp_format(const struct req *req, const char *file)
897c6c22f12Sschwarze {
8982ccd0917Sschwarze struct manoutput conf;
899c6c22f12Sschwarze struct mparse *mp;
9006b86842eSschwarze struct roff_meta *meta;
901c6c22f12Sschwarze void *vp;
902f74d674aSschwarze int fd;
903f74d674aSschwarze int usepath;
904c6c22f12Sschwarze
905b7041c07Sderaadt if (-1 == (fd = open(file, O_RDONLY))) {
906b2d1ada6Sschwarze puts("<p role=\"doc-notice\">\n"
907b2d1ada6Sschwarze " You specified an invalid manual file.\n"
908b2d1ada6Sschwarze "</p>");
909c6c22f12Sschwarze return;
910c6c22f12Sschwarze }
911c6c22f12Sschwarze
91216536faaSschwarze mchars_alloc();
9136b86842eSschwarze mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1 |
9146b86842eSschwarze MPARSE_VALIDATE, MANDOC_OS_OTHER, req->q.manpath);
915df927bb6Sschwarze mparse_readfd(mp, fd, file);
916c6c22f12Sschwarze close(fd);
9176b86842eSschwarze meta = mparse_result(mp);
918c6c22f12Sschwarze
9192ccd0917Sschwarze memset(&conf, 0, sizeof(conf));
9202ccd0917Sschwarze conf.fragment = 1;
9217c6e1b3aSschwarze conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
922f74d674aSschwarze usepath = strcmp(req->q.manpath, req->p[0]);
923ac2abdb8Sschwarze mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
924ac2abdb8Sschwarze scriptname, *scriptname == '\0' ? "" : "/",
925f211c215Sschwarze usepath ? req->q.manpath : "", usepath ? "/" : "");
926c6c22f12Sschwarze
92716536faaSschwarze vp = html_alloc(&conf);
9286b86842eSschwarze if (meta->macroset == MACROSET_MDOC)
9296b86842eSschwarze html_mdoc(vp, meta);
9306b86842eSschwarze else
9316b86842eSschwarze html_man(vp, meta);
932c6c22f12Sschwarze
933c6c22f12Sschwarze html_free(vp);
934c6c22f12Sschwarze mparse_free(mp);
93516536faaSschwarze mchars_free();
9362ccd0917Sschwarze free(conf.man);
9377c6e1b3aSschwarze free(conf.style);
938c6c22f12Sschwarze }
939c6c22f12Sschwarze
940c6c22f12Sschwarze static void
resp_show(const struct req * req,const char * file)94146723f19Sschwarze resp_show(const struct req *req, const char *file)
94246723f19Sschwarze {
94381475784Sschwarze
94481475784Sschwarze if ('.' == file[0] && '/' == file[1])
9452f7bef27Sschwarze file += 2;
94646723f19Sschwarze
94746723f19Sschwarze if ('c' == *file)
948941df026Sschwarze resp_catman(req, file);
94946723f19Sschwarze else
950941df026Sschwarze resp_format(req, file);
95146723f19Sschwarze }
95246723f19Sschwarze
95346723f19Sschwarze static void
pg_show(struct req * req,const char * fullpath)954b53c14c3Sschwarze pg_show(struct req *req, const char *fullpath)
955c6c22f12Sschwarze {
956b53c14c3Sschwarze char *manpath;
957b53c14c3Sschwarze const char *file;
958c6c22f12Sschwarze
959b53c14c3Sschwarze if ((file = strchr(fullpath, '/')) == NULL) {
960facea411Sschwarze pg_error_badrequest(
961c6c22f12Sschwarze "You did not specify a page to show.");
962c6c22f12Sschwarze return;
963c6c22f12Sschwarze }
964b53c14c3Sschwarze manpath = mandoc_strndup(fullpath, file - fullpath);
965b53c14c3Sschwarze file++;
966c6c22f12Sschwarze
967b53c14c3Sschwarze if ( ! validate_manpath(req, manpath)) {
968631ce2c6Sschwarze pg_error_badrequest(
969631ce2c6Sschwarze "You specified an invalid manpath.");
970b53c14c3Sschwarze free(manpath);
971631ce2c6Sschwarze return;
972631ce2c6Sschwarze }
973631ce2c6Sschwarze
974c6c22f12Sschwarze /*
975c6c22f12Sschwarze * Begin by chdir()ing into the manpath.
976c6c22f12Sschwarze * This way we can pick up the database files, which are
977c6c22f12Sschwarze * relative to the manpath root.
978c6c22f12Sschwarze */
979c6c22f12Sschwarze
980b53c14c3Sschwarze if (chdir(manpath) == -1) {
981c976f0e2Sschwarze warn("chdir %s", manpath);
982631ce2c6Sschwarze pg_error_internal();
983b53c14c3Sschwarze free(manpath);
984c6c22f12Sschwarze return;
985c6c22f12Sschwarze }
986b53c14c3Sschwarze free(manpath);
987b53c14c3Sschwarze
988b53c14c3Sschwarze if ( ! validate_filename(file)) {
98981475784Sschwarze pg_error_badrequest(
99081475784Sschwarze "You specified an invalid manual file.");
99181475784Sschwarze return;
99281475784Sschwarze }
99381475784Sschwarze
994a43df5a0Sschwarze if (resp_begin_html(200, NULL, file) == 0)
995a43df5a0Sschwarze puts("<header>");
99684f05c93Sschwarze resp_searchform(req, FOCUS_NONE);
997a43df5a0Sschwarze puts("</header>");
998b53c14c3Sschwarze resp_show(req, file);
99946723f19Sschwarze resp_end_html();
1000c6c22f12Sschwarze }
1001c6c22f12Sschwarze
1002c6c22f12Sschwarze static void
pg_search(const struct req * req)100357482ef4Sschwarze pg_search(const struct req *req)
1004c6c22f12Sschwarze {
1005c6c22f12Sschwarze struct mansearch search;
1006c6c22f12Sschwarze struct manpaths paths;
1007c6c22f12Sschwarze struct manpage *res;
1008fbeeb774Sschwarze char **argv;
1009fbeeb774Sschwarze char *query, *rp, *wp;
1010c6c22f12Sschwarze size_t ressz;
1011fbeeb774Sschwarze int argc;
1012c6c22f12Sschwarze
1013c6c22f12Sschwarze /*
1014c6c22f12Sschwarze * Begin by chdir()ing into the root of the manpath.
1015c6c22f12Sschwarze * This way we can pick up the database files, which are
1016c6c22f12Sschwarze * relative to the manpath root.
1017c6c22f12Sschwarze */
1018c6c22f12Sschwarze
1019c976f0e2Sschwarze if (chdir(req->q.manpath) == -1) {
1020c976f0e2Sschwarze warn("chdir %s", req->q.manpath);
1021631ce2c6Sschwarze pg_error_internal();
1022c6c22f12Sschwarze return;
1023c6c22f12Sschwarze }
1024c6c22f12Sschwarze
1025c6c22f12Sschwarze search.arch = req->q.arch;
1026c6c22f12Sschwarze search.sec = req->q.sec;
10270f10154cSschwarze search.outkey = "Nd";
10280f10154cSschwarze search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
1029fea71919Sschwarze search.firstmatch = 1;
1030c6c22f12Sschwarze
1031c6c22f12Sschwarze paths.sz = 1;
1032c6c22f12Sschwarze paths.paths = mandoc_malloc(sizeof(char *));
1033c6c22f12Sschwarze paths.paths[0] = mandoc_strdup(".");
1034c6c22f12Sschwarze
1035c6c22f12Sschwarze /*
1036fbeeb774Sschwarze * Break apart at spaces with backslash-escaping.
1037c6c22f12Sschwarze */
1038c6c22f12Sschwarze
1039fbeeb774Sschwarze argc = 0;
1040fbeeb774Sschwarze argv = NULL;
1041fbeeb774Sschwarze rp = query = mandoc_strdup(req->q.query);
1042fbeeb774Sschwarze for (;;) {
1043fbeeb774Sschwarze while (isspace((unsigned char)*rp))
1044fbeeb774Sschwarze rp++;
1045fbeeb774Sschwarze if (*rp == '\0')
1046fbeeb774Sschwarze break;
1047fbeeb774Sschwarze argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
1048fbeeb774Sschwarze argv[argc++] = wp = rp;
1049fbeeb774Sschwarze for (;;) {
1050fbeeb774Sschwarze if (isspace((unsigned char)*rp)) {
1051fbeeb774Sschwarze *wp = '\0';
1052fbeeb774Sschwarze rp++;
1053fbeeb774Sschwarze break;
1054fbeeb774Sschwarze }
1055fbeeb774Sschwarze if (rp[0] == '\\' && rp[1] != '\0')
1056fbeeb774Sschwarze rp++;
1057fbeeb774Sschwarze if (wp != rp)
1058fbeeb774Sschwarze *wp = *rp;
1059fbeeb774Sschwarze if (*rp == '\0')
1060fbeeb774Sschwarze break;
1061fbeeb774Sschwarze wp++;
1062fbeeb774Sschwarze rp++;
1063fbeeb774Sschwarze }
1064c6c22f12Sschwarze }
1065c6c22f12Sschwarze
1066e1beff2aSschwarze res = NULL;
1067e1beff2aSschwarze ressz = 0;
1068e1beff2aSschwarze if (req->isquery && req->q.equal && argc == 1)
1069e1beff2aSschwarze pg_redirect(req, argv[0]);
1070e1beff2aSschwarze else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
107118ccf011Sschwarze pg_noresult(req, 400, "Bad Request",
107218ccf011Sschwarze "You entered an invalid query.");
1073e1beff2aSschwarze else if (ressz == 0)
107418ccf011Sschwarze pg_noresult(req, 404, "Not Found", "No results found.");
1075c6c22f12Sschwarze else
1076facea411Sschwarze pg_searchres(req, res, ressz);
1077c6c22f12Sschwarze
1078fbeeb774Sschwarze free(query);
1079fbeeb774Sschwarze mansearch_free(res, ressz);
1080c6c22f12Sschwarze free(paths.paths[0]);
1081c6c22f12Sschwarze free(paths.paths);
1082c6c22f12Sschwarze }
1083c6c22f12Sschwarze
1084c6c22f12Sschwarze int
main(void)1085c6c22f12Sschwarze main(void)
1086c6c22f12Sschwarze {
1087c6c22f12Sschwarze struct req req;
1088136c26b8Sschwarze struct itimerval itimer;
108957482ef4Sschwarze const char *path;
109031e689c3Sschwarze const char *querystring;
109157482ef4Sschwarze int i;
1092c6c22f12Sschwarze
1093f80eb964Sschwarze /*
1094f80eb964Sschwarze * The "rpath" pledge could be revoked after mparse_readfd()
1095*d9a51c35Sjmc * if the file descriptor to "/footer.html" would be opened
1096f80eb964Sschwarze * up front, but it's probably not worth the complication
1097f80eb964Sschwarze * of the code it would cause: it would require scattering
1098f80eb964Sschwarze * pledge() calls in multiple low-level resp_*() functions.
1099f80eb964Sschwarze */
1100f80eb964Sschwarze
1101f80eb964Sschwarze if (pledge("stdio rpath", NULL) == -1) {
1102f80eb964Sschwarze warn("pledge");
1103f80eb964Sschwarze pg_error_internal();
1104f80eb964Sschwarze return EXIT_FAILURE;
1105f80eb964Sschwarze }
1106f80eb964Sschwarze
1107136c26b8Sschwarze /* Poor man's ReDoS mitigation. */
1108136c26b8Sschwarze
11092935aafcSschwarze itimer.it_value.tv_sec = 2;
1110136c26b8Sschwarze itimer.it_value.tv_usec = 0;
11112935aafcSschwarze itimer.it_interval.tv_sec = 2;
1112136c26b8Sschwarze itimer.it_interval.tv_usec = 0;
1113136c26b8Sschwarze if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1114c976f0e2Sschwarze warn("setitimer");
1115136c26b8Sschwarze pg_error_internal();
1116526e306bSschwarze return EXIT_FAILURE;
1117136c26b8Sschwarze }
1118136c26b8Sschwarze
1119c6c22f12Sschwarze /*
11206fdade3eSschwarze * First we change directory into the MAN_DIR so that
1121c6c22f12Sschwarze * subsequent scanning for manpath directories is rooted
1122c6c22f12Sschwarze * relative to the same position.
1123c6c22f12Sschwarze */
1124c6c22f12Sschwarze
1125c976f0e2Sschwarze if (chdir(MAN_DIR) == -1) {
1126c976f0e2Sschwarze warn("MAN_DIR: %s", MAN_DIR);
1127facea411Sschwarze pg_error_internal();
1128526e306bSschwarze return EXIT_FAILURE;
1129c6c22f12Sschwarze }
1130c6c22f12Sschwarze
1131c6c22f12Sschwarze memset(&req, 0, sizeof(struct req));
1132abf19dc9Sschwarze req.q.equal = 1;
1133941df026Sschwarze parse_manpath_conf(&req);
1134c6c22f12Sschwarze
113502b1b494Sschwarze /* Parse the path info and the query string. */
1136c6c22f12Sschwarze
113702b1b494Sschwarze if ((path = getenv("PATH_INFO")) == NULL)
113802b1b494Sschwarze path = "";
113902b1b494Sschwarze else if (*path == '/')
114002b1b494Sschwarze path++;
114102b1b494Sschwarze
1142aa16a3a8Sschwarze if (*path != '\0') {
1143941df026Sschwarze parse_path_info(&req, path);
1144a9941855Sschwarze if (req.q.manpath == NULL || req.q.sec == NULL ||
1145a9941855Sschwarze *req.q.query == '\0' || access(path, F_OK) == -1)
114602b1b494Sschwarze path = "";
114702b1b494Sschwarze } else if ((querystring = getenv("QUERY_STRING")) != NULL)
1148941df026Sschwarze parse_query_string(&req, querystring);
1149c6c22f12Sschwarze
115002b1b494Sschwarze /* Validate parsed data and add defaults. */
115102b1b494Sschwarze
115250eaed2bSschwarze if (req.q.manpath == NULL)
115350eaed2bSschwarze req.q.manpath = mandoc_strdup(req.p[0]);
115450eaed2bSschwarze else if ( ! validate_manpath(&req, req.q.manpath)) {
1155631ce2c6Sschwarze pg_error_badrequest(
1156631ce2c6Sschwarze "You specified an invalid manpath.");
1157526e306bSschwarze return EXIT_FAILURE;
1158631ce2c6Sschwarze }
1159631ce2c6Sschwarze
1160f7a12365Sschwarze if (req.q.arch != NULL && validate_arch(req.q.arch) == 0) {
1161cf3a545cSschwarze pg_error_badrequest(
1162cf3a545cSschwarze "You specified an invalid architecture.");
1163526e306bSschwarze return EXIT_FAILURE;
1164cf3a545cSschwarze }
1165cf3a545cSschwarze
116657482ef4Sschwarze /* Dispatch to the three different pages. */
1167c6c22f12Sschwarze
116857482ef4Sschwarze if ('\0' != *path)
116957482ef4Sschwarze pg_show(&req, path);
1170e89321abSschwarze else if (NULL != req.q.query)
117157482ef4Sschwarze pg_search(&req);
117257482ef4Sschwarze else
1173facea411Sschwarze pg_index(&req);
1174c6c22f12Sschwarze
117531e689c3Sschwarze free(req.q.manpath);
117631e689c3Sschwarze free(req.q.arch);
117731e689c3Sschwarze free(req.q.sec);
1178e89321abSschwarze free(req.q.query);
1179c6c22f12Sschwarze for (i = 0; i < (int)req.psz; i++)
1180c6c22f12Sschwarze free(req.p[i]);
1181c6c22f12Sschwarze free(req.p);
1182526e306bSschwarze return EXIT_SUCCESS;
1183c6c22f12Sschwarze }
1184c6c22f12Sschwarze
1185c6c22f12Sschwarze /*
1186f5938fa6Sschwarze * Translate PATH_INFO to a query.
118702b1b494Sschwarze */
118802b1b494Sschwarze static void
parse_path_info(struct req * req,const char * path)1189941df026Sschwarze parse_path_info(struct req *req, const char *path)
119002b1b494Sschwarze {
1191f5938fa6Sschwarze const char *name, *sec, *end;
119202b1b494Sschwarze
119352b413c1Sschwarze req->isquery = 0;
119402b1b494Sschwarze req->q.equal = 1;
1195f5938fa6Sschwarze req->q.manpath = NULL;
1196daf4c292Sschwarze req->q.arch = NULL;
119702b1b494Sschwarze
119802b1b494Sschwarze /* Mandatory manual page name. */
1199f5938fa6Sschwarze if ((name = strrchr(path, '/')) == NULL)
1200f5938fa6Sschwarze name = path;
1201f5938fa6Sschwarze else
1202f5938fa6Sschwarze name++;
120302b1b494Sschwarze
120402b1b494Sschwarze /* Optional trailing section. */
1205f5938fa6Sschwarze sec = strrchr(name, '.');
1206f5938fa6Sschwarze if (sec != NULL && isdigit((unsigned char)*++sec)) {
1207f5938fa6Sschwarze req->q.query = mandoc_strndup(name, sec - name - 1);
1208f5938fa6Sschwarze req->q.sec = mandoc_strdup(sec);
1209f5938fa6Sschwarze } else {
1210f5938fa6Sschwarze req->q.query = mandoc_strdup(name);
121102b1b494Sschwarze req->q.sec = NULL;
121202b1b494Sschwarze }
121302b1b494Sschwarze
121402b1b494Sschwarze /* Handle the case of name[.section] only. */
1215f5938fa6Sschwarze if (name == path)
121602b1b494Sschwarze return;
121702b1b494Sschwarze
1218f5938fa6Sschwarze /* Optional manpath. */
1219f5938fa6Sschwarze end = strchr(path, '/');
1220f5938fa6Sschwarze req->q.manpath = mandoc_strndup(path, end - path);
1221f5938fa6Sschwarze if (validate_manpath(req, req->q.manpath)) {
1222f5938fa6Sschwarze path = end + 1;
1223f5938fa6Sschwarze if (name == path)
1224f5938fa6Sschwarze return;
1225f5938fa6Sschwarze } else {
1226f5938fa6Sschwarze free(req->q.manpath);
1227f5938fa6Sschwarze req->q.manpath = NULL;
1228f5938fa6Sschwarze }
1229f5938fa6Sschwarze
1230f5938fa6Sschwarze /* Optional section. */
1231955967fcSschwarze if (strncmp(path, "man", 3) == 0 || strncmp(path, "cat", 3) == 0) {
1232f5938fa6Sschwarze path += 3;
1233f5938fa6Sschwarze end = strchr(path, '/');
1234f5938fa6Sschwarze free(req->q.sec);
1235f5938fa6Sschwarze req->q.sec = mandoc_strndup(path, end - path);
1236f5938fa6Sschwarze path = end + 1;
1237f5938fa6Sschwarze if (name == path)
1238f5938fa6Sschwarze return;
1239f5938fa6Sschwarze }
1240f5938fa6Sschwarze
1241f5938fa6Sschwarze /* Optional architecture. */
1242f5938fa6Sschwarze end = strchr(path, '/');
1243f5938fa6Sschwarze if (end + 1 != name) {
1244daf4c292Sschwarze pg_error_badrequest(
1245daf4c292Sschwarze "You specified too many directory components.");
1246daf4c292Sschwarze exit(EXIT_FAILURE);
124702b1b494Sschwarze }
1248f5938fa6Sschwarze req->q.arch = mandoc_strndup(path, end - path);
1249f5938fa6Sschwarze if (validate_arch(req->q.arch) == 0) {
1250daf4c292Sschwarze pg_error_badrequest(
1251daf4c292Sschwarze "You specified an invalid directory component.");
1252daf4c292Sschwarze exit(EXIT_FAILURE);
1253daf4c292Sschwarze }
125402b1b494Sschwarze }
125502b1b494Sschwarze
125602b1b494Sschwarze /*
1257c6c22f12Sschwarze * Scan for indexable paths.
1258c6c22f12Sschwarze */
1259c6c22f12Sschwarze static void
parse_manpath_conf(struct req * req)1260941df026Sschwarze parse_manpath_conf(struct req *req)
1261c6c22f12Sschwarze {
1262c6c22f12Sschwarze FILE *fp;
1263c6c22f12Sschwarze char *dp;
1264c6c22f12Sschwarze size_t dpsz;
126531f93c25Sschwarze ssize_t len;
1266c6c22f12Sschwarze
1267c976f0e2Sschwarze if ((fp = fopen("manpath.conf", "r")) == NULL) {
1268c976f0e2Sschwarze warn("%s/manpath.conf", MAN_DIR);
1269de651747Sschwarze pg_error_internal();
1270de651747Sschwarze exit(EXIT_FAILURE);
1271de651747Sschwarze }
1272c6c22f12Sschwarze
127331f93c25Sschwarze dp = NULL;
127431f93c25Sschwarze dpsz = 0;
127531f93c25Sschwarze
127631f93c25Sschwarze while ((len = getline(&dp, &dpsz, fp)) != -1) {
127731f93c25Sschwarze if (dp[len - 1] == '\n')
127831f93c25Sschwarze dp[--len] = '\0';
1279c6c22f12Sschwarze req->p = mandoc_realloc(req->p,
1280c6c22f12Sschwarze (req->psz + 1) * sizeof(char *));
1281cf3a545cSschwarze if ( ! validate_urifrag(dp)) {
1282c976f0e2Sschwarze warnx("%s/manpath.conf contains "
1283c976f0e2Sschwarze "unsafe path \"%s\"", MAN_DIR, dp);
1284cf3a545cSschwarze pg_error_internal();
1285cf3a545cSschwarze exit(EXIT_FAILURE);
1286cf3a545cSschwarze }
1287c976f0e2Sschwarze if (strchr(dp, '/') != NULL) {
1288c976f0e2Sschwarze warnx("%s/manpath.conf contains "
1289c976f0e2Sschwarze "path with slash \"%s\"", MAN_DIR, dp);
1290cf3a545cSschwarze pg_error_internal();
1291cf3a545cSschwarze exit(EXIT_FAILURE);
1292cf3a545cSschwarze }
1293cf3a545cSschwarze req->p[req->psz++] = dp;
129431f93c25Sschwarze dp = NULL;
129531f93c25Sschwarze dpsz = 0;
1296c6c22f12Sschwarze }
129731f93c25Sschwarze free(dp);
1298de651747Sschwarze
1299de651747Sschwarze if (req->p == NULL) {
1300c976f0e2Sschwarze warnx("%s/manpath.conf is empty", MAN_DIR);
1301de651747Sschwarze pg_error_internal();
1302de651747Sschwarze exit(EXIT_FAILURE);
1303de651747Sschwarze }
1304c6c22f12Sschwarze }
1305