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