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