]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
delete some TODO entries that were already fixed
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.108 2015/04/18 16:34:25 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 "main.h"
37 #include "manconf.h"
38 #include "mansearch.h"
39 #include "cgi.h"
40
41 /*
42 * A query as passed to the search function.
43 */
44 struct query {
45 char *manpath; /* desired manual directory */
46 char *arch; /* architecture */
47 char *sec; /* manual section */
48 char *query; /* unparsed query expression */
49 int equal; /* match whole names, not substrings */
50 };
51
52 struct req {
53 struct query q;
54 char **p; /* array of available manpaths */
55 size_t psz; /* number of available manpaths */
56 };
57
58 static void catman(const struct req *, const char *);
59 static void format(const struct req *, const char *);
60 static void html_print(const char *);
61 static void html_putchar(char);
62 static int http_decode(char *);
63 static void http_parse(struct req *, const char *);
64 static void http_print(const char *);
65 static void http_putchar(char);
66 static void http_printquery(const struct req *, const char *);
67 static void pathgen(struct req *);
68 static void pg_error_badrequest(const char *);
69 static void pg_error_internal(void);
70 static void pg_index(const struct req *);
71 static void pg_noresult(const struct req *, const char *);
72 static void pg_search(const struct req *);
73 static void pg_searchres(const struct req *,
74 struct manpage *, size_t);
75 static void pg_show(struct req *, const char *);
76 static void resp_begin_html(int, const char *);
77 static void resp_begin_http(int, const char *);
78 static void resp_end_html(void);
79 static void resp_searchform(const struct req *);
80 static void resp_show(const struct req *, const char *);
81 static void set_query_attr(char **, char **);
82 static int validate_filename(const char *);
83 static int validate_manpath(const struct req *, const char *);
84 static int validate_urifrag(const char *);
85
86 static const char *scriptname; /* CGI script name */
87
88 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
89 static const char *const sec_numbers[] = {
90 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
91 };
92 static const char *const sec_names[] = {
93 "All Sections",
94 "1 - General Commands",
95 "2 - System Calls",
96 "3 - Library Functions",
97 "3p - Perl Library",
98 "4 - Device Drivers",
99 "5 - File Formats",
100 "6 - Games",
101 "7 - Miscellaneous Information",
102 "8 - System Manager\'s Manual",
103 "9 - Kernel Developer\'s Manual"
104 };
105 static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
106
107 static const char *const arch_names[] = {
108 "amd64", "alpha", "armish", "armv7",
109 "aviion", "hppa", "hppa64", "i386",
110 "ia64", "landisk", "loongson", "luna88k",
111 "macppc", "mips64", "octeon", "sgi",
112 "socppc", "solbourne", "sparc", "sparc64",
113 "vax", "zaurus",
114 "amiga", "arc", "arm32", "atari",
115 "beagle", "cats", "hp300", "mac68k",
116 "mvme68k", "mvme88k", "mvmeppc", "palm",
117 "pc532", "pegasos", "pmax", "powerpc",
118 "sun3", "wgrisc", "x68k"
119 };
120 static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
121
122 /*
123 * Print a character, escaping HTML along the way.
124 * This will pass non-ASCII straight to output: be warned!
125 */
126 static void
127 html_putchar(char c)
128 {
129
130 switch (c) {
131 case ('"'):
132 printf("&quote;");
133 break;
134 case ('&'):
135 printf("&amp;");
136 break;
137 case ('>'):
138 printf("&gt;");
139 break;
140 case ('<'):
141 printf("&lt;");
142 break;
143 default:
144 putchar((unsigned char)c);
145 break;
146 }
147 }
148
149 static void
150 http_printquery(const struct req *req, const char *sep)
151 {
152
153 if (NULL != req->q.query) {
154 printf("query=");
155 http_print(req->q.query);
156 }
157 if (0 == req->q.equal)
158 printf("%sapropos=1", sep);
159 if (NULL != req->q.sec) {
160 printf("%ssec=", sep);
161 http_print(req->q.sec);
162 }
163 if (NULL != req->q.arch) {
164 printf("%sarch=", sep);
165 http_print(req->q.arch);
166 }
167 if (strcmp(req->q.manpath, req->p[0])) {
168 printf("%smanpath=", sep);
169 http_print(req->q.manpath);
170 }
171 }
172
173 static void
174 http_print(const char *p)
175 {
176
177 if (NULL == p)
178 return;
179 while ('\0' != *p)
180 http_putchar(*p++);
181 }
182
183 /*
184 * Call through to html_putchar().
185 * Accepts NULL strings.
186 */
187 static void
188 html_print(const char *p)
189 {
190
191 if (NULL == p)
192 return;
193 while ('\0' != *p)
194 html_putchar(*p++);
195 }
196
197 /*
198 * Transfer the responsibility for the allocated string *val
199 * to the query structure.
200 */
201 static void
202 set_query_attr(char **attr, char **val)
203 {
204
205 free(*attr);
206 if (**val == '\0') {
207 *attr = NULL;
208 free(*val);
209 } else
210 *attr = *val;
211 *val = NULL;
212 }
213
214 /*
215 * Parse the QUERY_STRING for key-value pairs
216 * and store the values into the query structure.
217 */
218 static void
219 http_parse(struct req *req, const char *qs)
220 {
221 char *key, *val;
222 size_t keysz, valsz;
223
224 req->q.manpath = NULL;
225 req->q.arch = NULL;
226 req->q.sec = NULL;
227 req->q.query = NULL;
228 req->q.equal = 1;
229
230 key = val = NULL;
231 while (*qs != '\0') {
232
233 /* Parse one key. */
234
235 keysz = strcspn(qs, "=;&");
236 key = mandoc_strndup(qs, keysz);
237 qs += keysz;
238 if (*qs != '=')
239 goto next;
240
241 /* Parse one value. */
242
243 valsz = strcspn(++qs, ";&");
244 val = mandoc_strndup(qs, valsz);
245 qs += valsz;
246
247 /* Decode and catch encoding errors. */
248
249 if ( ! (http_decode(key) && http_decode(val)))
250 goto next;
251
252 /* Handle key-value pairs. */
253
254 if ( ! strcmp(key, "query"))
255 set_query_attr(&req->q.query, &val);
256
257 else if ( ! strcmp(key, "apropos"))
258 req->q.equal = !strcmp(val, "0");
259
260 else if ( ! strcmp(key, "manpath")) {
261 #ifdef COMPAT_OLDURI
262 if ( ! strncmp(val, "OpenBSD ", 8)) {
263 val[7] = '-';
264 if ('C' == val[8])
265 val[8] = 'c';
266 }
267 #endif
268 set_query_attr(&req->q.manpath, &val);
269 }
270
271 else if ( ! (strcmp(key, "sec")
272 #ifdef COMPAT_OLDURI
273 && strcmp(key, "sektion")
274 #endif
275 )) {
276 if ( ! strcmp(val, "0"))
277 *val = '\0';
278 set_query_attr(&req->q.sec, &val);
279 }
280
281 else if ( ! strcmp(key, "arch")) {
282 if ( ! strcmp(val, "default"))
283 *val = '\0';
284 set_query_attr(&req->q.arch, &val);
285 }
286
287 /*
288 * The key must be freed in any case.
289 * The val may have been handed over to the query
290 * structure, in which case it is now NULL.
291 */
292 next:
293 free(key);
294 key = NULL;
295 free(val);
296 val = NULL;
297
298 if (*qs != '\0')
299 qs++;
300 }
301 }
302
303 static void
304 http_putchar(char c)
305 {
306
307 if (isalnum((unsigned char)c)) {
308 putchar((unsigned char)c);
309 return;
310 } else if (' ' == c) {
311 putchar('+');
312 return;
313 }
314 printf("%%%.2x", c);
315 }
316
317 /*
318 * HTTP-decode a string. The standard explanation is that this turns
319 * "%4e+foo" into "n foo" in the regular way. This is done in-place
320 * over the allocated string.
321 */
322 static int
323 http_decode(char *p)
324 {
325 char hex[3];
326 char *q;
327 int c;
328
329 hex[2] = '\0';
330
331 q = p;
332 for ( ; '\0' != *p; p++, q++) {
333 if ('%' == *p) {
334 if ('\0' == (hex[0] = *(p + 1)))
335 return(0);
336 if ('\0' == (hex[1] = *(p + 2)))
337 return(0);
338 if (1 != sscanf(hex, "%x", &c))
339 return(0);
340 if ('\0' == c)
341 return(0);
342
343 *q = (char)c;
344 p += 2;
345 } else
346 *q = '+' == *p ? ' ' : *p;
347 }
348
349 *q = '\0';
350 return(1);
351 }
352
353 static void
354 resp_begin_http(int code, const char *msg)
355 {
356
357 if (200 != code)
358 printf("Status: %d %s\r\n", code, msg);
359
360 printf("Content-Type: text/html; charset=utf-8\r\n"
361 "Cache-Control: no-cache\r\n"
362 "Pragma: no-cache\r\n"
363 "\r\n");
364
365 fflush(stdout);
366 }
367
368 static void
369 resp_begin_html(int code, const char *msg)
370 {
371
372 resp_begin_http(code, msg);
373
374 printf("<!DOCTYPE html>\n"
375 "<HTML>\n"
376 "<HEAD>\n"
377 "<META CHARSET=\"UTF-8\" />\n"
378 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
379 " TYPE=\"text/css\" media=\"all\">\n"
380 "<LINK REL=\"stylesheet\" HREF=\"%s/man.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, 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 mchars *mchars;
823 struct roff_man *man;
824 void *vp;
825 int fd;
826 int usepath;
827
828 if (-1 == (fd = open(file, O_RDONLY, 0))) {
829 puts("<P>You specified an invalid manual file.</P>");
830 return;
831 }
832
833 mchars = mchars_alloc();
834 mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_BADARG, NULL,
835 mchars, req->q.manpath);
836 mparse_readfd(mp, fd, file);
837 close(fd);
838
839 memset(&conf, 0, sizeof(conf));
840 conf.fragment = 1;
841 usepath = strcmp(req->q.manpath, req->p[0]);
842 mandoc_asprintf(&conf.man, "%s?query=%%N&sec=%%S%s%s%s%s",
843 scriptname,
844 req->q.arch ? "&arch=" : "",
845 req->q.arch ? req->q.arch : "",
846 usepath ? "&manpath=" : "",
847 usepath ? req->q.manpath : "");
848
849 mparse_result(mp, &man, NULL);
850 if (man == NULL) {
851 fprintf(stderr, "fatal mandoc error: %s/%s\n",
852 req->q.manpath, file);
853 pg_error_internal();
854 mparse_free(mp);
855 mchars_free(mchars);
856 return;
857 }
858
859 vp = html_alloc(mchars, &conf);
860
861 if (man->macroset == MACROSET_MDOC)
862 html_mdoc(vp, man);
863 else
864 html_man(vp, man);
865
866 html_free(vp);
867 mparse_free(mp);
868 mchars_free(mchars);
869 free(conf.man);
870 }
871
872 static void
873 resp_show(const struct req *req, const char *file)
874 {
875
876 if ('.' == file[0] && '/' == file[1])
877 file += 2;
878
879 if ('c' == *file)
880 catman(req, file);
881 else
882 format(req, file);
883 }
884
885 static void
886 pg_show(struct req *req, const char *fullpath)
887 {
888 char *manpath;
889 const char *file;
890
891 if ((file = strchr(fullpath, '/')) == NULL) {
892 pg_error_badrequest(
893 "You did not specify a page to show.");
894 return;
895 }
896 manpath = mandoc_strndup(fullpath, file - fullpath);
897 file++;
898
899 if ( ! validate_manpath(req, manpath)) {
900 pg_error_badrequest(
901 "You specified an invalid manpath.");
902 free(manpath);
903 return;
904 }
905
906 /*
907 * Begin by chdir()ing into the manpath.
908 * This way we can pick up the database files, which are
909 * relative to the manpath root.
910 */
911
912 if (chdir(manpath) == -1) {
913 fprintf(stderr, "chdir %s: %s\n",
914 manpath, strerror(errno));
915 pg_error_internal();
916 free(manpath);
917 return;
918 }
919
920 if (strcmp(manpath, "mandoc")) {
921 free(req->q.manpath);
922 req->q.manpath = manpath;
923 } else
924 free(manpath);
925
926 if ( ! validate_filename(file)) {
927 pg_error_badrequest(
928 "You specified an invalid manual file.");
929 return;
930 }
931
932 resp_begin_html(200, NULL);
933 resp_searchform(req);
934 resp_show(req, file);
935 resp_end_html();
936 }
937
938 static void
939 pg_search(const struct req *req)
940 {
941 struct mansearch search;
942 struct manpaths paths;
943 struct manpage *res;
944 char **argv;
945 char *query, *rp, *wp;
946 size_t ressz;
947 int argc;
948
949 /*
950 * Begin by chdir()ing into the root of the manpath.
951 * This way we can pick up the database files, which are
952 * relative to the manpath root.
953 */
954
955 if (-1 == (chdir(req->q.manpath))) {
956 fprintf(stderr, "chdir %s: %s\n",
957 req->q.manpath, strerror(errno));
958 pg_error_internal();
959 return;
960 }
961
962 search.arch = req->q.arch;
963 search.sec = req->q.sec;
964 search.outkey = "Nd";
965 search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
966 search.firstmatch = 1;
967
968 paths.sz = 1;
969 paths.paths = mandoc_malloc(sizeof(char *));
970 paths.paths[0] = mandoc_strdup(".");
971
972 /*
973 * Break apart at spaces with backslash-escaping.
974 */
975
976 argc = 0;
977 argv = NULL;
978 rp = query = mandoc_strdup(req->q.query);
979 for (;;) {
980 while (isspace((unsigned char)*rp))
981 rp++;
982 if (*rp == '\0')
983 break;
984 argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
985 argv[argc++] = wp = rp;
986 for (;;) {
987 if (isspace((unsigned char)*rp)) {
988 *wp = '\0';
989 rp++;
990 break;
991 }
992 if (rp[0] == '\\' && rp[1] != '\0')
993 rp++;
994 if (wp != rp)
995 *wp = *rp;
996 if (*rp == '\0')
997 break;
998 wp++;
999 rp++;
1000 }
1001 }
1002
1003 if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
1004 pg_noresult(req, "You entered an invalid query.");
1005 else if (0 == ressz)
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 /* Poor man's ReDoS mitigation. */
1026
1027 itimer.it_value.tv_sec = 2;
1028 itimer.it_value.tv_usec = 0;
1029 itimer.it_interval.tv_sec = 2;
1030 itimer.it_interval.tv_usec = 0;
1031 if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1032 fprintf(stderr, "setitimer: %s\n", strerror(errno));
1033 pg_error_internal();
1034 return(EXIT_FAILURE);
1035 }
1036
1037 /* Scan our run-time environment. */
1038
1039 if (NULL == (scriptname = getenv("SCRIPT_NAME")))
1040 scriptname = "";
1041
1042 if ( ! validate_urifrag(scriptname)) {
1043 fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
1044 scriptname);
1045 pg_error_internal();
1046 return(EXIT_FAILURE);
1047 }
1048
1049 /*
1050 * First we change directory into the MAN_DIR so that
1051 * subsequent scanning for manpath directories is rooted
1052 * relative to the same position.
1053 */
1054
1055 if (-1 == chdir(MAN_DIR)) {
1056 fprintf(stderr, "MAN_DIR: %s: %s\n",
1057 MAN_DIR, strerror(errno));
1058 pg_error_internal();
1059 return(EXIT_FAILURE);
1060 }
1061
1062 memset(&req, 0, sizeof(struct req));
1063 pathgen(&req);
1064
1065 /* Next parse out the query string. */
1066
1067 if (NULL != (querystring = getenv("QUERY_STRING")))
1068 http_parse(&req, querystring);
1069
1070 if (req.q.manpath == NULL)
1071 req.q.manpath = mandoc_strdup(req.p[0]);
1072 else if ( ! validate_manpath(&req, req.q.manpath)) {
1073 pg_error_badrequest(
1074 "You specified an invalid manpath.");
1075 return(EXIT_FAILURE);
1076 }
1077
1078 if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1079 pg_error_badrequest(
1080 "You specified an invalid architecture.");
1081 return(EXIT_FAILURE);
1082 }
1083
1084 /* Dispatch to the three different pages. */
1085
1086 path = getenv("PATH_INFO");
1087 if (NULL == path)
1088 path = "";
1089 else if ('/' == *path)
1090 path++;
1091
1092 if ('\0' != *path)
1093 pg_show(&req, path);
1094 else if (NULL != req.q.query)
1095 pg_search(&req);
1096 else
1097 pg_index(&req);
1098
1099 free(req.q.manpath);
1100 free(req.q.arch);
1101 free(req.q.sec);
1102 free(req.q.query);
1103 for (i = 0; i < (int)req.psz; i++)
1104 free(req.p[i]);
1105 free(req.p);
1106 return(EXIT_SUCCESS);
1107 }
1108
1109 /*
1110 * Scan for indexable paths.
1111 */
1112 static void
1113 pathgen(struct req *req)
1114 {
1115 FILE *fp;
1116 char *dp;
1117 size_t dpsz;
1118
1119 if (NULL == (fp = fopen("manpath.conf", "r"))) {
1120 fprintf(stderr, "%s/manpath.conf: %s\n",
1121 MAN_DIR, strerror(errno));
1122 pg_error_internal();
1123 exit(EXIT_FAILURE);
1124 }
1125
1126 while (NULL != (dp = fgetln(fp, &dpsz))) {
1127 if ('\n' == dp[dpsz - 1])
1128 dpsz--;
1129 req->p = mandoc_realloc(req->p,
1130 (req->psz + 1) * sizeof(char *));
1131 dp = mandoc_strndup(dp, dpsz);
1132 if ( ! validate_urifrag(dp)) {
1133 fprintf(stderr, "%s/manpath.conf contains "
1134 "unsafe path \"%s\"\n", MAN_DIR, dp);
1135 pg_error_internal();
1136 exit(EXIT_FAILURE);
1137 }
1138 if (NULL != strchr(dp, '/')) {
1139 fprintf(stderr, "%s/manpath.conf contains "
1140 "path with slash \"%s\"\n", MAN_DIR, dp);
1141 pg_error_internal();
1142 exit(EXIT_FAILURE);
1143 }
1144 req->p[req->psz++] = dp;
1145 }
1146
1147 if ( req->p == NULL ) {
1148 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1149 pg_error_internal();
1150 exit(EXIT_FAILURE);
1151 }
1152 }