]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
Start with baby steps towards responsive design:
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.157 2018/05/18 14:23:00 schwarze Exp $ */
2 /*
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014, 2015, 2016, 2017 Ingo Schwarze <schwarze@usta.de>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include "config.h"
19
20 #include <sys/types.h>
21 #include <sys/time.h>
22
23 #include <ctype.h>
24 #if HAVE_ERR
25 #include <err.h>
26 #endif
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <limits.h>
30 #include <stdint.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include "mandoc_aux.h"
37 #include "mandoc.h"
38 #include "roff.h"
39 #include "mdoc.h"
40 #include "man.h"
41 #include "main.h"
42 #include "manconf.h"
43 #include "mansearch.h"
44 #include "cgi.h"
45
46 /*
47 * A query as passed to the search function.
48 */
49 struct query {
50 char *manpath; /* desired manual directory */
51 char *arch; /* architecture */
52 char *sec; /* manual section */
53 char *query; /* unparsed query expression */
54 int equal; /* match whole names, not substrings */
55 };
56
57 struct req {
58 struct query q;
59 char **p; /* array of available manpaths */
60 size_t psz; /* number of available manpaths */
61 int isquery; /* QUERY_STRING used, not PATH_INFO */
62 };
63
64 enum focus {
65 FOCUS_NONE = 0,
66 FOCUS_QUERY
67 };
68
69 static void html_print(const char *);
70 static void html_putchar(char);
71 static int http_decode(char *);
72 static void parse_manpath_conf(struct req *);
73 static void parse_path_info(struct req *req, const char *path);
74 static void parse_query_string(struct req *, const char *);
75 static void pg_error_badrequest(const char *);
76 static void pg_error_internal(void);
77 static void pg_index(const struct req *);
78 static void pg_noresult(const struct req *, const char *);
79 static void pg_redirect(const struct req *, const char *);
80 static void pg_search(const struct req *);
81 static void pg_searchres(const struct req *,
82 struct manpage *, size_t);
83 static void pg_show(struct req *, const char *);
84 static void resp_begin_html(int, const char *, const char *);
85 static void resp_begin_http(int, const char *);
86 static void resp_catman(const struct req *, const char *);
87 static void resp_copy(const char *);
88 static void resp_end_html(void);
89 static void resp_format(const struct req *, const char *);
90 static void resp_searchform(const struct req *, enum focus);
91 static void resp_show(const struct req *, const char *);
92 static void set_query_attr(char **, char **);
93 static int validate_filename(const char *);
94 static int validate_manpath(const struct req *, const char *);
95 static int validate_urifrag(const char *);
96
97 static const char *scriptname = SCRIPT_NAME;
98
99 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
100 static const char *const sec_numbers[] = {
101 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
102 };
103 static const char *const sec_names[] = {
104 "All Sections",
105 "1 - General Commands",
106 "2 - System Calls",
107 "3 - Library Functions",
108 "3p - Perl Library",
109 "4 - Device Drivers",
110 "5 - File Formats",
111 "6 - Games",
112 "7 - Miscellaneous Information",
113 "8 - System Manager\'s Manual",
114 "9 - Kernel Developer\'s Manual"
115 };
116 static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
117
118 static const char *const arch_names[] = {
119 "amd64", "alpha", "armv7", "arm64",
120 "hppa", "i386", "landisk",
121 "loongson", "luna88k", "macppc", "mips64",
122 "octeon", "sgi", "socppc", "sparc64",
123 "amiga", "arc", "armish", "arm32",
124 "atari", "aviion", "beagle", "cats",
125 "hppa64", "hp300",
126 "ia64", "mac68k", "mvme68k", "mvme88k",
127 "mvmeppc", "palm", "pc532", "pegasos",
128 "pmax", "powerpc", "solbourne", "sparc",
129 "sun3", "vax", "wgrisc", "x68k",
130 "zaurus"
131 };
132 static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
133
134 /*
135 * Print a character, escaping HTML along the way.
136 * This will pass non-ASCII straight to output: be warned!
137 */
138 static void
139 html_putchar(char c)
140 {
141
142 switch (c) {
143 case '"':
144 printf("&quot;");
145 break;
146 case '&':
147 printf("&amp;");
148 break;
149 case '>':
150 printf("&gt;");
151 break;
152 case '<':
153 printf("&lt;");
154 break;
155 default:
156 putchar((unsigned char)c);
157 break;
158 }
159 }
160
161 /*
162 * Call through to html_putchar().
163 * Accepts NULL strings.
164 */
165 static void
166 html_print(const char *p)
167 {
168
169 if (NULL == p)
170 return;
171 while ('\0' != *p)
172 html_putchar(*p++);
173 }
174
175 /*
176 * Transfer the responsibility for the allocated string *val
177 * to the query structure.
178 */
179 static void
180 set_query_attr(char **attr, char **val)
181 {
182
183 free(*attr);
184 if (**val == '\0') {
185 *attr = NULL;
186 free(*val);
187 } else
188 *attr = *val;
189 *val = NULL;
190 }
191
192 /*
193 * Parse the QUERY_STRING for key-value pairs
194 * and store the values into the query structure.
195 */
196 static void
197 parse_query_string(struct req *req, const char *qs)
198 {
199 char *key, *val;
200 size_t keysz, valsz;
201
202 req->isquery = 1;
203 req->q.manpath = NULL;
204 req->q.arch = NULL;
205 req->q.sec = NULL;
206 req->q.query = NULL;
207 req->q.equal = 1;
208
209 key = val = NULL;
210 while (*qs != '\0') {
211
212 /* Parse one key. */
213
214 keysz = strcspn(qs, "=;&");
215 key = mandoc_strndup(qs, keysz);
216 qs += keysz;
217 if (*qs != '=')
218 goto next;
219
220 /* Parse one value. */
221
222 valsz = strcspn(++qs, ";&");
223 val = mandoc_strndup(qs, valsz);
224 qs += valsz;
225
226 /* Decode and catch encoding errors. */
227
228 if ( ! (http_decode(key) && http_decode(val)))
229 goto next;
230
231 /* Handle key-value pairs. */
232
233 if ( ! strcmp(key, "query"))
234 set_query_attr(&req->q.query, &val);
235
236 else if ( ! strcmp(key, "apropos"))
237 req->q.equal = !strcmp(val, "0");
238
239 else if ( ! strcmp(key, "manpath")) {
240 #ifdef COMPAT_OLDURI
241 if ( ! strncmp(val, "OpenBSD ", 8)) {
242 val[7] = '-';
243 if ('C' == val[8])
244 val[8] = 'c';
245 }
246 #endif
247 set_query_attr(&req->q.manpath, &val);
248 }
249
250 else if ( ! (strcmp(key, "sec")
251 #ifdef COMPAT_OLDURI
252 && strcmp(key, "sektion")
253 #endif
254 )) {
255 if ( ! strcmp(val, "0"))
256 *val = '\0';
257 set_query_attr(&req->q.sec, &val);
258 }
259
260 else if ( ! strcmp(key, "arch")) {
261 if ( ! strcmp(val, "default"))
262 *val = '\0';
263 set_query_attr(&req->q.arch, &val);
264 }
265
266 /*
267 * The key must be freed in any case.
268 * The val may have been handed over to the query
269 * structure, in which case it is now NULL.
270 */
271 next:
272 free(key);
273 key = NULL;
274 free(val);
275 val = NULL;
276
277 if (*qs != '\0')
278 qs++;
279 }
280 }
281
282 /*
283 * HTTP-decode a string. The standard explanation is that this turns
284 * "%4e+foo" into "n foo" in the regular way. This is done in-place
285 * over the allocated string.
286 */
287 static int
288 http_decode(char *p)
289 {
290 char hex[3];
291 char *q;
292 int c;
293
294 hex[2] = '\0';
295
296 q = p;
297 for ( ; '\0' != *p; p++, q++) {
298 if ('%' == *p) {
299 if ('\0' == (hex[0] = *(p + 1)))
300 return 0;
301 if ('\0' == (hex[1] = *(p + 2)))
302 return 0;
303 if (1 != sscanf(hex, "%x", &c))
304 return 0;
305 if ('\0' == c)
306 return 0;
307
308 *q = (char)c;
309 p += 2;
310 } else
311 *q = '+' == *p ? ' ' : *p;
312 }
313
314 *q = '\0';
315 return 1;
316 }
317
318 static void
319 resp_begin_http(int code, const char *msg)
320 {
321
322 if (200 != code)
323 printf("Status: %d %s\r\n", code, msg);
324
325 printf("Content-Type: text/html; charset=utf-8\r\n"
326 "Cache-Control: no-cache\r\n"
327 "Pragma: no-cache\r\n"
328 "\r\n");
329
330 fflush(stdout);
331 }
332
333 static void
334 resp_copy(const char *filename)
335 {
336 char buf[4096];
337 ssize_t sz;
338 int fd;
339
340 if ((fd = open(filename, O_RDONLY)) != -1) {
341 fflush(stdout);
342 while ((sz = read(fd, buf, sizeof(buf))) > 0)
343 write(STDOUT_FILENO, buf, sz);
344 close(fd);
345 }
346 }
347
348 static void
349 resp_begin_html(int code, const char *msg, const char *file)
350 {
351 char *cp;
352
353 resp_begin_http(code, msg);
354
355 printf("<!DOCTYPE html>\n"
356 "<html>\n"
357 "<head>\n"
358 " <meta charset=\"UTF-8\"/>\n"
359 " <meta name=\"viewport\""
360 " content=\"width=device-width, initial-scale=1.0\">\n"
361 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
362 " type=\"text/css\" media=\"all\">\n"
363 " <title>",
364 CSS_DIR);
365 if (file != NULL) {
366 if ((cp = strrchr(file, '/')) != NULL)
367 file = cp + 1;
368 if ((cp = strrchr(file, '.')) != NULL) {
369 printf("%.*s(%s) - ", (int)(cp - file), file, cp + 1);
370 } else
371 printf("%s - ", file);
372 }
373 printf("%s</title>\n"
374 "</head>\n"
375 "<body>\n",
376 CUSTOMIZE_TITLE);
377
378 resp_copy(MAN_DIR "/header.html");
379 }
380
381 static void
382 resp_end_html(void)
383 {
384
385 resp_copy(MAN_DIR "/footer.html");
386
387 puts("</body>\n"
388 "</html>");
389 }
390
391 static void
392 resp_searchform(const struct req *req, enum focus focus)
393 {
394 int i;
395
396 printf("<form action=\"/%s\" method=\"get\">\n"
397 " <fieldset>\n"
398 " <legend>Manual Page Search Parameters</legend>\n",
399 scriptname);
400
401 /* Write query input box. */
402
403 printf(" <input type=\"text\" name=\"query\" value=\"");
404 if (req->q.query != NULL)
405 html_print(req->q.query);
406 printf( "\" size=\"40\"");
407 if (focus == FOCUS_QUERY)
408 printf(" autofocus");
409 puts(">");
410
411 /* Write submission buttons. */
412
413 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
414 "man</button>\n"
415 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
416 "apropos</button>\n"
417 " <br/>\n");
418
419 /* Write section selector. */
420
421 puts(" <select name=\"sec\">");
422 for (i = 0; i < sec_MAX; i++) {
423 printf(" <option value=\"%s\"", sec_numbers[i]);
424 if (NULL != req->q.sec &&
425 0 == strcmp(sec_numbers[i], req->q.sec))
426 printf(" selected=\"selected\"");
427 printf(">%s</option>\n", sec_names[i]);
428 }
429 puts(" </select>");
430
431 /* Write architecture selector. */
432
433 printf( " <select name=\"arch\">\n"
434 " <option value=\"default\"");
435 if (NULL == req->q.arch)
436 printf(" selected=\"selected\"");
437 puts(">All Architectures</option>");
438 for (i = 0; i < arch_MAX; i++) {
439 printf(" <option value=\"%s\"", arch_names[i]);
440 if (NULL != req->q.arch &&
441 0 == strcmp(arch_names[i], req->q.arch))
442 printf(" selected=\"selected\"");
443 printf(">%s</option>\n", arch_names[i]);
444 }
445 puts(" </select>");
446
447 /* Write manpath selector. */
448
449 if (req->psz > 1) {
450 puts(" <select name=\"manpath\">");
451 for (i = 0; i < (int)req->psz; i++) {
452 printf(" <option ");
453 if (strcmp(req->q.manpath, req->p[i]) == 0)
454 printf("selected=\"selected\" ");
455 printf("value=\"");
456 html_print(req->p[i]);
457 printf("\">");
458 html_print(req->p[i]);
459 puts("</option>");
460 }
461 puts(" </select>");
462 }
463
464 puts(" </fieldset>\n"
465 "</form>");
466 }
467
468 static int
469 validate_urifrag(const char *frag)
470 {
471
472 while ('\0' != *frag) {
473 if ( ! (isalnum((unsigned char)*frag) ||
474 '-' == *frag || '.' == *frag ||
475 '/' == *frag || '_' == *frag))
476 return 0;
477 frag++;
478 }
479 return 1;
480 }
481
482 static int
483 validate_manpath(const struct req *req, const char* manpath)
484 {
485 size_t i;
486
487 for (i = 0; i < req->psz; i++)
488 if ( ! strcmp(manpath, req->p[i]))
489 return 1;
490
491 return 0;
492 }
493
494 static int
495 validate_filename(const char *file)
496 {
497
498 if ('.' == file[0] && '/' == file[1])
499 file += 2;
500
501 return ! (strstr(file, "../") || strstr(file, "/..") ||
502 (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
503 }
504
505 static void
506 pg_index(const struct req *req)
507 {
508
509 resp_begin_html(200, NULL, NULL);
510 resp_searchform(req, FOCUS_QUERY);
511 printf("<p>\n"
512 "This web interface is documented in the\n"
513 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
514 "manual, and the\n"
515 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
516 "manual explains the query syntax.\n"
517 "</p>\n",
518 scriptname, *scriptname == '\0' ? "" : "/",
519 scriptname, *scriptname == '\0' ? "" : "/");
520 resp_end_html();
521 }
522
523 static void
524 pg_noresult(const struct req *req, const char *msg)
525 {
526 resp_begin_html(200, NULL, NULL);
527 resp_searchform(req, FOCUS_QUERY);
528 puts("<p>");
529 puts(msg);
530 puts("</p>");
531 resp_end_html();
532 }
533
534 static void
535 pg_error_badrequest(const char *msg)
536 {
537
538 resp_begin_html(400, "Bad Request", NULL);
539 puts("<h1>Bad Request</h1>\n"
540 "<p>\n");
541 puts(msg);
542 printf("Try again from the\n"
543 "<a href=\"/%s\">main page</a>.\n"
544 "</p>", scriptname);
545 resp_end_html();
546 }
547
548 static void
549 pg_error_internal(void)
550 {
551 resp_begin_html(500, "Internal Server Error", NULL);
552 puts("<p>Internal Server Error</p>");
553 resp_end_html();
554 }
555
556 static void
557 pg_redirect(const struct req *req, const char *name)
558 {
559 printf("Status: 303 See Other\r\n"
560 "Location: /");
561 if (*scriptname != '\0')
562 printf("%s/", scriptname);
563 if (strcmp(req->q.manpath, req->p[0]))
564 printf("%s/", req->q.manpath);
565 if (req->q.arch != NULL)
566 printf("%s/", req->q.arch);
567 printf("%s", name);
568 if (req->q.sec != NULL)
569 printf(".%s", req->q.sec);
570 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
571 }
572
573 static void
574 pg_searchres(const struct req *req, struct manpage *r, size_t sz)
575 {
576 char *arch, *archend;
577 const char *sec;
578 size_t i, iuse;
579 int archprio, archpriouse;
580 int prio, priouse;
581
582 for (i = 0; i < sz; i++) {
583 if (validate_filename(r[i].file))
584 continue;
585 warnx("invalid filename %s in %s database",
586 r[i].file, req->q.manpath);
587 pg_error_internal();
588 return;
589 }
590
591 if (req->isquery && sz == 1) {
592 /*
593 * If we have just one result, then jump there now
594 * without any delay.
595 */
596 printf("Status: 303 See Other\r\n"
597 "Location: /");
598 if (*scriptname != '\0')
599 printf("%s/", scriptname);
600 if (strcmp(req->q.manpath, req->p[0]))
601 printf("%s/", req->q.manpath);
602 printf("%s\r\n"
603 "Content-Type: text/html; charset=utf-8\r\n\r\n",
604 r[0].file);
605 return;
606 }
607
608 /*
609 * In man(1) mode, show one of the pages
610 * even if more than one is found.
611 */
612
613 iuse = 0;
614 if (req->q.equal || sz == 1) {
615 priouse = 20;
616 archpriouse = 3;
617 for (i = 0; i < sz; i++) {
618 sec = r[i].file;
619 sec += strcspn(sec, "123456789");
620 if (sec[0] == '\0')
621 continue;
622 prio = sec_prios[sec[0] - '1'];
623 if (sec[1] != '/')
624 prio += 10;
625 if (req->q.arch == NULL) {
626 archprio =
627 ((arch = strchr(sec + 1, '/'))
628 == NULL) ? 3 :
629 ((archend = strchr(arch + 1, '/'))
630 == NULL) ? 0 :
631 strncmp(arch, "amd64/",
632 archend - arch) ? 2 : 1;
633 if (archprio < archpriouse) {
634 archpriouse = archprio;
635 priouse = prio;
636 iuse = i;
637 continue;
638 }
639 if (archprio > archpriouse)
640 continue;
641 }
642 if (prio >= priouse)
643 continue;
644 priouse = prio;
645 iuse = i;
646 }
647 resp_begin_html(200, NULL, r[iuse].file);
648 } else
649 resp_begin_html(200, NULL, NULL);
650
651 resp_searchform(req,
652 req->q.equal || sz == 1 ? FOCUS_NONE : FOCUS_QUERY);
653
654 if (sz > 1) {
655 puts("<table class=\"results\">");
656 for (i = 0; i < sz; i++) {
657 printf(" <tr>\n"
658 " <td>"
659 "<a class=\"Xr\" href=\"/");
660 if (*scriptname != '\0')
661 printf("%s/", scriptname);
662 if (strcmp(req->q.manpath, req->p[0]))
663 printf("%s/", req->q.manpath);
664 printf("%s\">", r[i].file);
665 html_print(r[i].names);
666 printf("</a></td>\n"
667 " <td><span class=\"Nd\">");
668 html_print(r[i].output);
669 puts("</span></td>\n"
670 " </tr>");
671 }
672 puts("</table>");
673 }
674
675 if (req->q.equal || sz == 1) {
676 puts("<hr>");
677 resp_show(req, r[iuse].file);
678 }
679
680 resp_end_html();
681 }
682
683 static void
684 resp_catman(const struct req *req, const char *file)
685 {
686 FILE *f;
687 char *p;
688 size_t sz;
689 ssize_t len;
690 int i;
691 int italic, bold;
692
693 if ((f = fopen(file, "r")) == NULL) {
694 puts("<p>You specified an invalid manual file.</p>");
695 return;
696 }
697
698 puts("<div class=\"catman\">\n"
699 "<pre>");
700
701 p = NULL;
702 sz = 0;
703
704 while ((len = getline(&p, &sz, f)) != -1) {
705 bold = italic = 0;
706 for (i = 0; i < len - 1; i++) {
707 /*
708 * This means that the catpage is out of state.
709 * Ignore it and keep going (although the
710 * catpage is bogus).
711 */
712
713 if ('\b' == p[i] || '\n' == p[i])
714 continue;
715
716 /*
717 * Print a regular character.
718 * Close out any bold/italic scopes.
719 * If we're in back-space mode, make sure we'll
720 * have something to enter when we backspace.
721 */
722
723 if ('\b' != p[i + 1]) {
724 if (italic)
725 printf("</i>");
726 if (bold)
727 printf("</b>");
728 italic = bold = 0;
729 html_putchar(p[i]);
730 continue;
731 } else if (i + 2 >= len)
732 continue;
733
734 /* Italic mode. */
735
736 if ('_' == p[i]) {
737 if (bold)
738 printf("</b>");
739 if ( ! italic)
740 printf("<i>");
741 bold = 0;
742 italic = 1;
743 i += 2;
744 html_putchar(p[i]);
745 continue;
746 }
747
748 /*
749 * Handle funny behaviour troff-isms.
750 * These grok'd from the original man2html.c.
751 */
752
753 if (('+' == p[i] && 'o' == p[i + 2]) ||
754 ('o' == p[i] && '+' == p[i + 2]) ||
755 ('|' == p[i] && '=' == p[i + 2]) ||
756 ('=' == p[i] && '|' == p[i + 2]) ||
757 ('*' == p[i] && '=' == p[i + 2]) ||
758 ('=' == p[i] && '*' == p[i + 2]) ||
759 ('*' == p[i] && '|' == p[i + 2]) ||
760 ('|' == p[i] && '*' == p[i + 2])) {
761 if (italic)
762 printf("</i>");
763 if (bold)
764 printf("</b>");
765 italic = bold = 0;
766 putchar('*');
767 i += 2;
768 continue;
769 } else if (('|' == p[i] && '-' == p[i + 2]) ||
770 ('-' == p[i] && '|' == p[i + 1]) ||
771 ('+' == p[i] && '-' == p[i + 1]) ||
772 ('-' == p[i] && '+' == p[i + 1]) ||
773 ('+' == p[i] && '|' == p[i + 1]) ||
774 ('|' == p[i] && '+' == p[i + 1])) {
775 if (italic)
776 printf("</i>");
777 if (bold)
778 printf("</b>");
779 italic = bold = 0;
780 putchar('+');
781 i += 2;
782 continue;
783 }
784
785 /* Bold mode. */
786
787 if (italic)
788 printf("</i>");
789 if ( ! bold)
790 printf("<b>");
791 bold = 1;
792 italic = 0;
793 i += 2;
794 html_putchar(p[i]);
795 }
796
797 /*
798 * Clean up the last character.
799 * We can get to a newline; don't print that.
800 */
801
802 if (italic)
803 printf("</i>");
804 if (bold)
805 printf("</b>");
806
807 if (i == len - 1 && p[i] != '\n')
808 html_putchar(p[i]);
809
810 putchar('\n');
811 }
812 free(p);
813
814 puts("</pre>\n"
815 "</div>");
816
817 fclose(f);
818 }
819
820 static void
821 resp_format(const struct req *req, const char *file)
822 {
823 struct manoutput conf;
824 struct mparse *mp;
825 struct roff_man *man;
826 void *vp;
827 int fd;
828 int usepath;
829
830 if (-1 == (fd = open(file, O_RDONLY, 0))) {
831 puts("<p>You specified an invalid manual file.</p>");
832 return;
833 }
834
835 mchars_alloc();
836 mp = mparse_alloc(MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1,
837 MANDOCERR_MAX, NULL, MANDOC_OS_OTHER, req->q.manpath);
838 mparse_readfd(mp, fd, file);
839 close(fd);
840
841 memset(&conf, 0, sizeof(conf));
842 conf.fragment = 1;
843 conf.style = mandoc_strdup(CSS_DIR "/mandoc.css");
844 usepath = strcmp(req->q.manpath, req->p[0]);
845 mandoc_asprintf(&conf.man, "/%s%s%s%s%%N.%%S",
846 scriptname, *scriptname == '\0' ? "" : "/",
847 usepath ? req->q.manpath : "", usepath ? "/" : "");
848
849 mparse_result(mp, &man, NULL);
850 if (man == NULL) {
851 warnx("fatal mandoc error: %s/%s", req->q.manpath, file);
852 pg_error_internal();
853 mparse_free(mp);
854 mchars_free();
855 return;
856 }
857
858 vp = html_alloc(&conf);
859
860 if (man->macroset == MACROSET_MDOC) {
861 mdoc_validate(man);
862 html_mdoc(vp, man);
863 } else {
864 man_validate(man);
865 html_man(vp, man);
866 }
867
868 html_free(vp);
869 mparse_free(mp);
870 mchars_free();
871 free(conf.man);
872 free(conf.style);
873 }
874
875 static void
876 resp_show(const struct req *req, const char *file)
877 {
878
879 if ('.' == file[0] && '/' == file[1])
880 file += 2;
881
882 if ('c' == *file)
883 resp_catman(req, file);
884 else
885 resp_format(req, file);
886 }
887
888 static void
889 pg_show(struct req *req, const char *fullpath)
890 {
891 char *manpath;
892 const char *file;
893
894 if ((file = strchr(fullpath, '/')) == NULL) {
895 pg_error_badrequest(
896 "You did not specify a page to show.");
897 return;
898 }
899 manpath = mandoc_strndup(fullpath, file - fullpath);
900 file++;
901
902 if ( ! validate_manpath(req, manpath)) {
903 pg_error_badrequest(
904 "You specified an invalid manpath.");
905 free(manpath);
906 return;
907 }
908
909 /*
910 * Begin by chdir()ing into the manpath.
911 * This way we can pick up the database files, which are
912 * relative to the manpath root.
913 */
914
915 if (chdir(manpath) == -1) {
916 warn("chdir %s", manpath);
917 pg_error_internal();
918 free(manpath);
919 return;
920 }
921 free(manpath);
922
923 if ( ! validate_filename(file)) {
924 pg_error_badrequest(
925 "You specified an invalid manual file.");
926 return;
927 }
928
929 resp_begin_html(200, NULL, file);
930 resp_searchform(req, FOCUS_NONE);
931 resp_show(req, file);
932 resp_end_html();
933 }
934
935 static void
936 pg_search(const struct req *req)
937 {
938 struct mansearch search;
939 struct manpaths paths;
940 struct manpage *res;
941 char **argv;
942 char *query, *rp, *wp;
943 size_t ressz;
944 int argc;
945
946 /*
947 * Begin by chdir()ing into the root of the manpath.
948 * This way we can pick up the database files, which are
949 * relative to the manpath root.
950 */
951
952 if (chdir(req->q.manpath) == -1) {
953 warn("chdir %s", req->q.manpath);
954 pg_error_internal();
955 return;
956 }
957
958 search.arch = req->q.arch;
959 search.sec = req->q.sec;
960 search.outkey = "Nd";
961 search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
962 search.firstmatch = 1;
963
964 paths.sz = 1;
965 paths.paths = mandoc_malloc(sizeof(char *));
966 paths.paths[0] = mandoc_strdup(".");
967
968 /*
969 * Break apart at spaces with backslash-escaping.
970 */
971
972 argc = 0;
973 argv = NULL;
974 rp = query = mandoc_strdup(req->q.query);
975 for (;;) {
976 while (isspace((unsigned char)*rp))
977 rp++;
978 if (*rp == '\0')
979 break;
980 argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
981 argv[argc++] = wp = rp;
982 for (;;) {
983 if (isspace((unsigned char)*rp)) {
984 *wp = '\0';
985 rp++;
986 break;
987 }
988 if (rp[0] == '\\' && rp[1] != '\0')
989 rp++;
990 if (wp != rp)
991 *wp = *rp;
992 if (*rp == '\0')
993 break;
994 wp++;
995 rp++;
996 }
997 }
998
999 res = NULL;
1000 ressz = 0;
1001 if (req->isquery && req->q.equal && argc == 1)
1002 pg_redirect(req, argv[0]);
1003 else if (mansearch(&search, &paths, argc, argv, &res, &ressz) == 0)
1004 pg_noresult(req, "You entered an invalid query.");
1005 else if (ressz == 0)
1006 pg_noresult(req, "No results found.");
1007 else
1008 pg_searchres(req, res, ressz);
1009
1010 free(query);
1011 mansearch_free(res, ressz);
1012 free(paths.paths[0]);
1013 free(paths.paths);
1014 }
1015
1016 int
1017 main(void)
1018 {
1019 struct req req;
1020 struct itimerval itimer;
1021 const char *path;
1022 const char *querystring;
1023 int i;
1024
1025 #if HAVE_PLEDGE
1026 /*
1027 * The "rpath" pledge could be revoked after mparse_readfd()
1028 * if the file desciptor to "/footer.html" would be opened
1029 * up front, but it's probably not worth the complication
1030 * of the code it would cause: it would require scattering
1031 * pledge() calls in multiple low-level resp_*() functions.
1032 */
1033
1034 if (pledge("stdio rpath", NULL) == -1) {
1035 warn("pledge");
1036 pg_error_internal();
1037 return EXIT_FAILURE;
1038 }
1039 #endif
1040
1041 /* Poor man's ReDoS mitigation. */
1042
1043 itimer.it_value.tv_sec = 2;
1044 itimer.it_value.tv_usec = 0;
1045 itimer.it_interval.tv_sec = 2;
1046 itimer.it_interval.tv_usec = 0;
1047 if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1048 warn("setitimer");
1049 pg_error_internal();
1050 return EXIT_FAILURE;
1051 }
1052
1053 /*
1054 * First we change directory into the MAN_DIR so that
1055 * subsequent scanning for manpath directories is rooted
1056 * relative to the same position.
1057 */
1058
1059 if (chdir(MAN_DIR) == -1) {
1060 warn("MAN_DIR: %s", MAN_DIR);
1061 pg_error_internal();
1062 return EXIT_FAILURE;
1063 }
1064
1065 memset(&req, 0, sizeof(struct req));
1066 req.q.equal = 1;
1067 parse_manpath_conf(&req);
1068
1069 /* Parse the path info and the query string. */
1070
1071 if ((path = getenv("PATH_INFO")) == NULL)
1072 path = "";
1073 else if (*path == '/')
1074 path++;
1075
1076 if (*path != '\0') {
1077 parse_path_info(&req, path);
1078 if (req.q.manpath == NULL || req.q.sec == NULL ||
1079 *req.q.query == '\0' || access(path, F_OK) == -1)
1080 path = "";
1081 } else if ((querystring = getenv("QUERY_STRING")) != NULL)
1082 parse_query_string(&req, querystring);
1083
1084 /* Validate parsed data and add defaults. */
1085
1086 if (req.q.manpath == NULL)
1087 req.q.manpath = mandoc_strdup(req.p[0]);
1088 else if ( ! validate_manpath(&req, req.q.manpath)) {
1089 pg_error_badrequest(
1090 "You specified an invalid manpath.");
1091 return EXIT_FAILURE;
1092 }
1093
1094 if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1095 pg_error_badrequest(
1096 "You specified an invalid architecture.");
1097 return EXIT_FAILURE;
1098 }
1099
1100 /* Dispatch to the three different pages. */
1101
1102 if ('\0' != *path)
1103 pg_show(&req, path);
1104 else if (NULL != req.q.query)
1105 pg_search(&req);
1106 else
1107 pg_index(&req);
1108
1109 free(req.q.manpath);
1110 free(req.q.arch);
1111 free(req.q.sec);
1112 free(req.q.query);
1113 for (i = 0; i < (int)req.psz; i++)
1114 free(req.p[i]);
1115 free(req.p);
1116 return EXIT_SUCCESS;
1117 }
1118
1119 /*
1120 * If PATH_INFO is not a file name, translate it to a query.
1121 */
1122 static void
1123 parse_path_info(struct req *req, const char *path)
1124 {
1125 char *dir[4];
1126 int i;
1127
1128 req->isquery = 0;
1129 req->q.equal = 1;
1130 req->q.manpath = mandoc_strdup(path);
1131 req->q.arch = NULL;
1132
1133 /* Mandatory manual page name. */
1134 if ((req->q.query = strrchr(req->q.manpath, '/')) == NULL) {
1135 req->q.query = req->q.manpath;
1136 req->q.manpath = NULL;
1137 } else
1138 *req->q.query++ = '\0';
1139
1140 /* Optional trailing section. */
1141 if ((req->q.sec = strrchr(req->q.query, '.')) != NULL) {
1142 if(isdigit((unsigned char)req->q.sec[1])) {
1143 *req->q.sec++ = '\0';
1144 req->q.sec = mandoc_strdup(req->q.sec);
1145 } else
1146 req->q.sec = NULL;
1147 }
1148
1149 /* Handle the case of name[.section] only. */
1150 if (req->q.manpath == NULL)
1151 return;
1152 req->q.query = mandoc_strdup(req->q.query);
1153
1154 /* Split directory components. */
1155 dir[i = 0] = req->q.manpath;
1156 while ((dir[i + 1] = strchr(dir[i], '/')) != NULL) {
1157 if (++i == 3) {
1158 pg_error_badrequest(
1159 "You specified too many directory components.");
1160 exit(EXIT_FAILURE);
1161 }
1162 *dir[i]++ = '\0';
1163 }
1164
1165 /* Optional manpath. */
1166 if ((i = validate_manpath(req, req->q.manpath)) == 0)
1167 req->q.manpath = NULL;
1168 else if (dir[1] == NULL)
1169 return;
1170
1171 /* Optional section. */
1172 if (strncmp(dir[i], "man", 3) == 0) {
1173 free(req->q.sec);
1174 req->q.sec = mandoc_strdup(dir[i++] + 3);
1175 }
1176 if (dir[i] == NULL) {
1177 if (req->q.manpath == NULL)
1178 free(dir[0]);
1179 return;
1180 }
1181 if (dir[i + 1] != NULL) {
1182 pg_error_badrequest(
1183 "You specified an invalid directory component.");
1184 exit(EXIT_FAILURE);
1185 }
1186
1187 /* Optional architecture. */
1188 if (i) {
1189 req->q.arch = mandoc_strdup(dir[i]);
1190 if (req->q.manpath == NULL)
1191 free(dir[0]);
1192 } else
1193 req->q.arch = dir[0];
1194 }
1195
1196 /*
1197 * Scan for indexable paths.
1198 */
1199 static void
1200 parse_manpath_conf(struct req *req)
1201 {
1202 FILE *fp;
1203 char *dp;
1204 size_t dpsz;
1205 ssize_t len;
1206
1207 if ((fp = fopen("manpath.conf", "r")) == NULL) {
1208 warn("%s/manpath.conf", MAN_DIR);
1209 pg_error_internal();
1210 exit(EXIT_FAILURE);
1211 }
1212
1213 dp = NULL;
1214 dpsz = 0;
1215
1216 while ((len = getline(&dp, &dpsz, fp)) != -1) {
1217 if (dp[len - 1] == '\n')
1218 dp[--len] = '\0';
1219 req->p = mandoc_realloc(req->p,
1220 (req->psz + 1) * sizeof(char *));
1221 if ( ! validate_urifrag(dp)) {
1222 warnx("%s/manpath.conf contains "
1223 "unsafe path \"%s\"", MAN_DIR, dp);
1224 pg_error_internal();
1225 exit(EXIT_FAILURE);
1226 }
1227 if (strchr(dp, '/') != NULL) {
1228 warnx("%s/manpath.conf contains "
1229 "path with slash \"%s\"", MAN_DIR, dp);
1230 pg_error_internal();
1231 exit(EXIT_FAILURE);
1232 }
1233 req->p[req->psz++] = dp;
1234 dp = NULL;
1235 dpsz = 0;
1236 }
1237 free(dp);
1238
1239 if (req->p == NULL) {
1240 warnx("%s/manpath.conf is empty", MAN_DIR);
1241 pg_error_internal();
1242 exit(EXIT_FAILURE);
1243 }
1244 }