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