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