]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
style cleanup, no functional change
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.110 2015/10/13 22:59:54 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 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 html_mdoc(vp, man);
861 else
862 html_man(vp, man);
863
864 html_free(vp);
865 mparse_free(mp);
866 mchars_free();
867 free(conf.man);
868 }
869
870 static void
871 resp_show(const struct req *req, const char *file)
872 {
873
874 if ('.' == file[0] && '/' == file[1])
875 file += 2;
876
877 if ('c' == *file)
878 catman(req, file);
879 else
880 format(req, file);
881 }
882
883 static void
884 pg_show(struct req *req, const char *fullpath)
885 {
886 char *manpath;
887 const char *file;
888
889 if ((file = strchr(fullpath, '/')) == NULL) {
890 pg_error_badrequest(
891 "You did not specify a page to show.");
892 return;
893 }
894 manpath = mandoc_strndup(fullpath, file - fullpath);
895 file++;
896
897 if ( ! validate_manpath(req, manpath)) {
898 pg_error_badrequest(
899 "You specified an invalid manpath.");
900 free(manpath);
901 return;
902 }
903
904 /*
905 * Begin by chdir()ing into the manpath.
906 * This way we can pick up the database files, which are
907 * relative to the manpath root.
908 */
909
910 if (chdir(manpath) == -1) {
911 fprintf(stderr, "chdir %s: %s\n",
912 manpath, strerror(errno));
913 pg_error_internal();
914 free(manpath);
915 return;
916 }
917
918 if (strcmp(manpath, "mandoc")) {
919 free(req->q.manpath);
920 req->q.manpath = manpath;
921 } else
922 free(manpath);
923
924 if ( ! validate_filename(file)) {
925 pg_error_badrequest(
926 "You specified an invalid manual file.");
927 return;
928 }
929
930 resp_begin_html(200, NULL);
931 resp_searchform(req);
932 resp_show(req, file);
933 resp_end_html();
934 }
935
936 static void
937 pg_search(const struct req *req)
938 {
939 struct mansearch search;
940 struct manpaths paths;
941 struct manpage *res;
942 char **argv;
943 char *query, *rp, *wp;
944 size_t ressz;
945 int argc;
946
947 /*
948 * Begin by chdir()ing into the root of the manpath.
949 * This way we can pick up the database files, which are
950 * relative to the manpath root.
951 */
952
953 if (-1 == (chdir(req->q.manpath))) {
954 fprintf(stderr, "chdir %s: %s\n",
955 req->q.manpath, strerror(errno));
956 pg_error_internal();
957 return;
958 }
959
960 search.arch = req->q.arch;
961 search.sec = req->q.sec;
962 search.outkey = "Nd";
963 search.argmode = req->q.equal ? ARG_NAME : ARG_EXPR;
964 search.firstmatch = 1;
965
966 paths.sz = 1;
967 paths.paths = mandoc_malloc(sizeof(char *));
968 paths.paths[0] = mandoc_strdup(".");
969
970 /*
971 * Break apart at spaces with backslash-escaping.
972 */
973
974 argc = 0;
975 argv = NULL;
976 rp = query = mandoc_strdup(req->q.query);
977 for (;;) {
978 while (isspace((unsigned char)*rp))
979 rp++;
980 if (*rp == '\0')
981 break;
982 argv = mandoc_reallocarray(argv, argc + 1, sizeof(char *));
983 argv[argc++] = wp = rp;
984 for (;;) {
985 if (isspace((unsigned char)*rp)) {
986 *wp = '\0';
987 rp++;
988 break;
989 }
990 if (rp[0] == '\\' && rp[1] != '\0')
991 rp++;
992 if (wp != rp)
993 *wp = *rp;
994 if (*rp == '\0')
995 break;
996 wp++;
997 rp++;
998 }
999 }
1000
1001 if (0 == mansearch(&search, &paths, argc, argv, &res, &ressz))
1002 pg_noresult(req, "You entered an invalid query.");
1003 else if (0 == ressz)
1004 pg_noresult(req, "No results found.");
1005 else
1006 pg_searchres(req, res, ressz);
1007
1008 free(query);
1009 mansearch_free(res, ressz);
1010 free(paths.paths[0]);
1011 free(paths.paths);
1012 }
1013
1014 int
1015 main(void)
1016 {
1017 struct req req;
1018 struct itimerval itimer;
1019 const char *path;
1020 const char *querystring;
1021 int i;
1022
1023 /* Poor man's ReDoS mitigation. */
1024
1025 itimer.it_value.tv_sec = 2;
1026 itimer.it_value.tv_usec = 0;
1027 itimer.it_interval.tv_sec = 2;
1028 itimer.it_interval.tv_usec = 0;
1029 if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) == -1) {
1030 fprintf(stderr, "setitimer: %s\n", strerror(errno));
1031 pg_error_internal();
1032 return EXIT_FAILURE;
1033 }
1034
1035 /* Scan our run-time environment. */
1036
1037 if (NULL == (scriptname = getenv("SCRIPT_NAME")))
1038 scriptname = "";
1039
1040 if ( ! validate_urifrag(scriptname)) {
1041 fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
1042 scriptname);
1043 pg_error_internal();
1044 return EXIT_FAILURE;
1045 }
1046
1047 /*
1048 * First we change directory into the MAN_DIR so that
1049 * subsequent scanning for manpath directories is rooted
1050 * relative to the same position.
1051 */
1052
1053 if (-1 == chdir(MAN_DIR)) {
1054 fprintf(stderr, "MAN_DIR: %s: %s\n",
1055 MAN_DIR, strerror(errno));
1056 pg_error_internal();
1057 return EXIT_FAILURE;
1058 }
1059
1060 memset(&req, 0, sizeof(struct req));
1061 pathgen(&req);
1062
1063 /* Next parse out the query string. */
1064
1065 if (NULL != (querystring = getenv("QUERY_STRING")))
1066 http_parse(&req, querystring);
1067
1068 if (req.q.manpath == NULL)
1069 req.q.manpath = mandoc_strdup(req.p[0]);
1070 else if ( ! validate_manpath(&req, req.q.manpath)) {
1071 pg_error_badrequest(
1072 "You specified an invalid manpath.");
1073 return EXIT_FAILURE;
1074 }
1075
1076 if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1077 pg_error_badrequest(
1078 "You specified an invalid architecture.");
1079 return EXIT_FAILURE;
1080 }
1081
1082 /* Dispatch to the three different pages. */
1083
1084 path = getenv("PATH_INFO");
1085 if (NULL == path)
1086 path = "";
1087 else if ('/' == *path)
1088 path++;
1089
1090 if ('\0' != *path)
1091 pg_show(&req, path);
1092 else if (NULL != req.q.query)
1093 pg_search(&req);
1094 else
1095 pg_index(&req);
1096
1097 free(req.q.manpath);
1098 free(req.q.arch);
1099 free(req.q.sec);
1100 free(req.q.query);
1101 for (i = 0; i < (int)req.psz; i++)
1102 free(req.p[i]);
1103 free(req.p);
1104 return EXIT_SUCCESS;
1105 }
1106
1107 /*
1108 * Scan for indexable paths.
1109 */
1110 static void
1111 pathgen(struct req *req)
1112 {
1113 FILE *fp;
1114 char *dp;
1115 size_t dpsz;
1116
1117 if (NULL == (fp = fopen("manpath.conf", "r"))) {
1118 fprintf(stderr, "%s/manpath.conf: %s\n",
1119 MAN_DIR, strerror(errno));
1120 pg_error_internal();
1121 exit(EXIT_FAILURE);
1122 }
1123
1124 while (NULL != (dp = fgetln(fp, &dpsz))) {
1125 if ('\n' == dp[dpsz - 1])
1126 dpsz--;
1127 req->p = mandoc_realloc(req->p,
1128 (req->psz + 1) * sizeof(char *));
1129 dp = mandoc_strndup(dp, dpsz);
1130 if ( ! validate_urifrag(dp)) {
1131 fprintf(stderr, "%s/manpath.conf contains "
1132 "unsafe path \"%s\"\n", MAN_DIR, dp);
1133 pg_error_internal();
1134 exit(EXIT_FAILURE);
1135 }
1136 if (NULL != strchr(dp, '/')) {
1137 fprintf(stderr, "%s/manpath.conf contains "
1138 "path with slash \"%s\"\n", MAN_DIR, dp);
1139 pg_error_internal();
1140 exit(EXIT_FAILURE);
1141 }
1142 req->p[req->psz++] = dp;
1143 }
1144
1145 if ( req->p == NULL ) {
1146 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1147 pg_error_internal();
1148 exit(EXIT_FAILURE);
1149 }
1150 }