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