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