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