]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
9445845bc1e77ced9670e6829571e2a26ad33999
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.79 2014/07/21 22:33:01 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 puts("<SELECT NAME=\"arch\">");
426 for (i = 0; i < arch_MAX; i++) {
427 printf("<OPTION VALUE=\"%s\"", arch_names[i]);
428 if (NULL != req->q.arch &&
429 0 == strcmp(arch_names[i], req->q.arch))
430 printf(" SELECTED");
431 printf(">%s</OPTION>\n", arch_names[i]);
432 }
433 puts("</SELECT>");
434
435 /* Write manpath selector. */
436
437 if (req->psz > 1) {
438 puts("<SELECT NAME=\"manpath\">");
439 for (i = 0; i < (int)req->psz; i++) {
440 printf("<OPTION ");
441 if (NULL == req->q.manpath ? 0 == i :
442 0 == strcmp(req->q.manpath, req->p[i]))
443 printf("SELECTED ");
444 printf("VALUE=\"");
445 html_print(req->p[i]);
446 printf("\">");
447 html_print(req->p[i]);
448 puts("</OPTION>");
449 }
450 puts("</SELECT>");
451 }
452
453 /* Write search radio button */
454
455 printf( "</TD><TD>\n"
456 "<INPUT TYPE=\"radio\" ");
457 if (0 == req->q.equal)
458 printf("CHECKED ");
459 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
460 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
461
462 puts("</TD></TR></TABLE>\n"
463 "</FIELDSET>\n"
464 "</FORM>\n"
465 "</DIV>");
466 puts("<!-- End search form. //-->");
467 }
468
469 static int
470 validate_manpath(const struct req *req, const char* manpath)
471 {
472 size_t i;
473
474 if ( ! strcmp(manpath, "mandoc"))
475 return(1);
476
477 for (i = 0; i < req->psz; i++)
478 if ( ! strcmp(manpath, req->p[i]))
479 return(1);
480
481 return(0);
482 }
483
484 static int
485 validate_filename(const char *file)
486 {
487
488 if ('.' == file[0] && '/' == file[1])
489 file += 2;
490
491 return ( ! (strstr(file, "../") || strstr(file, "/..") ||
492 (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
493 }
494
495 static void
496 pg_index(const struct req *req)
497 {
498
499 resp_begin_html(200, NULL);
500 resp_searchform(req);
501 printf("<P>\n"
502 "This web interface is documented in the "
503 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
504 "manual, and the "
505 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
506 "manual explains the query syntax.\n"
507 "</P>\n",
508 scriptname, scriptname);
509 resp_end_html();
510 }
511
512 static void
513 pg_noresult(const struct req *req, const char *msg)
514 {
515 resp_begin_html(200, NULL);
516 resp_searchform(req);
517 puts("<P>");
518 puts(msg);
519 puts("</P>");
520 resp_end_html();
521 }
522
523 static void
524 pg_error_badrequest(const char *msg)
525 {
526
527 resp_begin_html(400, "Bad Request");
528 puts("<H1>Bad Request</H1>\n"
529 "<P>\n");
530 puts(msg);
531 printf("Try again from the\n"
532 "<A HREF=\"%s\">main page</A>.\n"
533 "</P>", scriptname);
534 resp_end_html();
535 }
536
537 static void
538 pg_error_internal(void)
539 {
540 resp_begin_html(500, "Internal Server Error");
541 puts("<P>Internal Server Error</P>");
542 resp_end_html();
543 }
544
545 static void
546 pg_searchres(const struct req *req, struct manpage *r, size_t sz)
547 {
548 size_t i, iuse, isec;
549 int prio, priouse;
550 char sec;
551
552 for (i = 0; i < sz; i++) {
553 if (validate_filename(r[i].file))
554 continue;
555 fprintf(stderr, "invalid filename %s in %s database\n",
556 r[i].file, req->q.manpath);
557 pg_error_internal();
558 return;
559 }
560
561 if (1 == sz) {
562 /*
563 * If we have just one result, then jump there now
564 * without any delay.
565 */
566 printf("Status: 303 See Other\r\n");
567 printf("Location: http://%s%s/%s/%s?",
568 HTTP_HOST, scriptname, req->q.manpath, r[0].file);
569 http_printquery(req);
570 printf("\r\n"
571 "Content-Type: text/html; charset=utf-8\r\n"
572 "\r\n");
573 return;
574 }
575
576 qsort(r, sz, sizeof(struct manpage), cmp);
577
578 resp_begin_html(200, NULL);
579 resp_searchform(req);
580 puts("<DIV CLASS=\"results\">");
581 puts("<TABLE>");
582
583 for (i = 0; i < sz; i++) {
584 printf("<TR>\n"
585 "<TD CLASS=\"title\">\n"
586 "<A HREF=\"%s/%s/%s?",
587 scriptname, req->q.manpath, r[i].file);
588 html_printquery(req);
589 printf("\">");
590 html_print(r[i].names);
591 printf("</A>\n"
592 "</TD>\n"
593 "<TD CLASS=\"desc\">");
594 html_print(r[i].output);
595 puts("</TD>\n"
596 "</TR>");
597 }
598
599 puts("</TABLE>\n"
600 "</DIV>");
601
602 /*
603 * In man(1) mode, show one of the pages
604 * even if more than one is found.
605 */
606
607 if (req->q.equal) {
608 puts("<HR>");
609 iuse = 0;
610 priouse = 10;
611 for (i = 0; i < sz; i++) {
612 isec = strcspn(r[i].file, "123456789");
613 sec = r[i].file[isec];
614 if ('\0' == sec)
615 continue;
616 prio = sec_prios[sec - '1'];
617 if (prio >= priouse)
618 continue;
619 priouse = prio;
620 iuse = i;
621 }
622 resp_show(req, r[iuse].file);
623 }
624
625 resp_end_html();
626 }
627
628 static void
629 catman(const struct req *req, const char *file)
630 {
631 FILE *f;
632 size_t len;
633 int i;
634 char *p;
635 int italic, bold;
636
637 if (NULL == (f = fopen(file, "r"))) {
638 puts("<P>You specified an invalid manual file.</P>");
639 return;
640 }
641
642 puts("<DIV CLASS=\"catman\">\n"
643 "<PRE>");
644
645 while (NULL != (p = fgetln(f, &len))) {
646 bold = italic = 0;
647 for (i = 0; i < (int)len - 1; i++) {
648 /*
649 * This means that the catpage is out of state.
650 * Ignore it and keep going (although the
651 * catpage is bogus).
652 */
653
654 if ('\b' == p[i] || '\n' == p[i])
655 continue;
656
657 /*
658 * Print a regular character.
659 * Close out any bold/italic scopes.
660 * If we're in back-space mode, make sure we'll
661 * have something to enter when we backspace.
662 */
663
664 if ('\b' != p[i + 1]) {
665 if (italic)
666 printf("</I>");
667 if (bold)
668 printf("</B>");
669 italic = bold = 0;
670 html_putchar(p[i]);
671 continue;
672 } else if (i + 2 >= (int)len)
673 continue;
674
675 /* Italic mode. */
676
677 if ('_' == p[i]) {
678 if (bold)
679 printf("</B>");
680 if ( ! italic)
681 printf("<I>");
682 bold = 0;
683 italic = 1;
684 i += 2;
685 html_putchar(p[i]);
686 continue;
687 }
688
689 /*
690 * Handle funny behaviour troff-isms.
691 * These grok'd from the original man2html.c.
692 */
693
694 if (('+' == p[i] && 'o' == p[i + 2]) ||
695 ('o' == p[i] && '+' == p[i + 2]) ||
696 ('|' == p[i] && '=' == p[i + 2]) ||
697 ('=' == p[i] && '|' == p[i + 2]) ||
698 ('*' == p[i] && '=' == p[i + 2]) ||
699 ('=' == p[i] && '*' == p[i + 2]) ||
700 ('*' == p[i] && '|' == p[i + 2]) ||
701 ('|' == p[i] && '*' == p[i + 2])) {
702 if (italic)
703 printf("</I>");
704 if (bold)
705 printf("</B>");
706 italic = bold = 0;
707 putchar('*');
708 i += 2;
709 continue;
710 } else if (('|' == p[i] && '-' == p[i + 2]) ||
711 ('-' == p[i] && '|' == p[i + 1]) ||
712 ('+' == p[i] && '-' == p[i + 1]) ||
713 ('-' == p[i] && '+' == p[i + 1]) ||
714 ('+' == p[i] && '|' == p[i + 1]) ||
715 ('|' == p[i] && '+' == p[i + 1])) {
716 if (italic)
717 printf("</I>");
718 if (bold)
719 printf("</B>");
720 italic = bold = 0;
721 putchar('+');
722 i += 2;
723 continue;
724 }
725
726 /* Bold mode. */
727
728 if (italic)
729 printf("</I>");
730 if ( ! bold)
731 printf("<B>");
732 bold = 1;
733 italic = 0;
734 i += 2;
735 html_putchar(p[i]);
736 }
737
738 /*
739 * Clean up the last character.
740 * We can get to a newline; don't print that.
741 */
742
743 if (italic)
744 printf("</I>");
745 if (bold)
746 printf("</B>");
747
748 if (i == (int)len - 1 && '\n' != p[i])
749 html_putchar(p[i]);
750
751 putchar('\n');
752 }
753
754 puts("</PRE>\n"
755 "</DIV>");
756
757 fclose(f);
758 }
759
760 static void
761 format(const struct req *req, const char *file)
762 {
763 struct mparse *mp;
764 int fd;
765 struct mdoc *mdoc;
766 struct man *man;
767 void *vp;
768 enum mandoclevel rc;
769 char opts[PATH_MAX + 128];
770
771 if (-1 == (fd = open(file, O_RDONLY, 0))) {
772 puts("<P>You specified an invalid manual file.</P>");
773 return;
774 }
775
776 mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
777 req->q.manpath);
778 rc = mparse_readfd(mp, fd, file);
779 close(fd);
780
781 if (rc >= MANDOCLEVEL_FATAL) {
782 fprintf(stderr, "fatal mandoc error: %s/%s\n",
783 req->q.manpath, file);
784 pg_error_internal();
785 return;
786 }
787
788 snprintf(opts, sizeof(opts), "fragment,man=%s?"
789 "manpath=%s&amp;query=%%N&amp;sec=%%S&amp;arch=%s",
790 scriptname, req->q.manpath,
791 req->q.arch ? req->q.arch : "");
792
793 mparse_result(mp, &mdoc, &man, NULL);
794 if (NULL == man && NULL == mdoc) {
795 fprintf(stderr, "fatal mandoc error: %s/%s\n",
796 req->q.manpath, file);
797 pg_error_internal();
798 mparse_free(mp);
799 return;
800 }
801
802 vp = html_alloc(opts);
803
804 if (NULL != mdoc)
805 html_mdoc(vp, mdoc);
806 else
807 html_man(vp, man);
808
809 html_free(vp);
810 mparse_free(mp);
811 }
812
813 static void
814 resp_show(const struct req *req, const char *file)
815 {
816
817 if ('.' == file[0] && '/' == file[1])
818 file += 2;
819
820 if ('c' == *file)
821 catman(req, file);
822 else
823 format(req, file);
824 }
825
826 static void
827 pg_show(struct req *req, const char *path)
828 {
829 char *sub;
830
831 if (NULL == path || NULL == (sub = strchr(path, '/'))) {
832 pg_error_badrequest(
833 "You did not specify a page to show.");
834 return;
835 }
836 *sub++ = '\0';
837
838 if ( ! validate_manpath(req, path)) {
839 pg_error_badrequest(
840 "You specified an invalid manpath.");
841 return;
842 }
843
844 /*
845 * Begin by chdir()ing into the manpath.
846 * This way we can pick up the database files, which are
847 * relative to the manpath root.
848 */
849
850 if (-1 == chdir(path)) {
851 fprintf(stderr, "chdir %s: %s\n",
852 path, strerror(errno));
853 pg_error_internal();
854 return;
855 }
856
857 if ( ! validate_filename(sub)) {
858 pg_error_badrequest(
859 "You specified an invalid manual file.");
860 return;
861 }
862
863 if (strcmp(path, "mandoc"))
864 req->q.manpath = path;
865
866 resp_begin_html(200, NULL);
867 resp_searchform(req);
868 resp_show(req, sub);
869 resp_end_html();
870 }
871
872 static void
873 pg_search(const struct req *req)
874 {
875 struct mansearch search;
876 struct manpaths paths;
877 struct manpage *res;
878 char **cp;
879 const char *ep, *start;
880 size_t ressz;
881 int i, sz;
882
883 /*
884 * Begin by chdir()ing into the root of the manpath.
885 * This way we can pick up the database files, which are
886 * relative to the manpath root.
887 */
888
889 if (-1 == (chdir(req->q.manpath))) {
890 fprintf(stderr, "chdir %s: %s\n",
891 req->q.manpath, strerror(errno));
892 pg_error_internal();
893 return;
894 }
895
896 search.arch = req->q.arch;
897 search.sec = req->q.sec;
898 search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
899 search.flags = req->q.equal ? MANSEARCH_MAN : 0;
900
901 paths.sz = 1;
902 paths.paths = mandoc_malloc(sizeof(char *));
903 paths.paths[0] = mandoc_strdup(".");
904
905 /*
906 * Poor man's tokenisation: just break apart by spaces.
907 * Yes, this is half-ass. But it works for now.
908 */
909
910 ep = req->q.expr;
911 while (ep && isspace((unsigned char)*ep))
912 ep++;
913
914 sz = 0;
915 cp = NULL;
916 while (ep && '\0' != *ep) {
917 cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
918 start = ep;
919 while ('\0' != *ep && ! isspace((unsigned char)*ep))
920 ep++;
921 cp[sz] = mandoc_malloc((ep - start) + 1);
922 memcpy(cp[sz], start, ep - start);
923 cp[sz++][ep - start] = '\0';
924 while (isspace((unsigned char)*ep))
925 ep++;
926 }
927
928 if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
929 pg_noresult(req, "You entered an invalid query.");
930 else if (0 == ressz)
931 pg_noresult(req, "No results found.");
932 else
933 pg_searchres(req, res, ressz);
934
935 for (i = 0; i < sz; i++)
936 free(cp[i]);
937 free(cp);
938
939 for (i = 0; i < (int)ressz; i++) {
940 free(res[i].file);
941 free(res[i].names);
942 free(res[i].output);
943 }
944 free(res);
945
946 free(paths.paths[0]);
947 free(paths.paths);
948 }
949
950 int
951 main(void)
952 {
953 struct req req;
954 const char *path;
955 char *querystring;
956 int i;
957
958 /* Scan our run-time environment. */
959
960 if (NULL == (scriptname = getenv("SCRIPT_NAME")))
961 scriptname = "";
962
963 /*
964 * First we change directory into the MAN_DIR so that
965 * subsequent scanning for manpath directories is rooted
966 * relative to the same position.
967 */
968
969 if (-1 == chdir(MAN_DIR)) {
970 fprintf(stderr, "MAN_DIR: %s: %s\n",
971 MAN_DIR, strerror(errno));
972 pg_error_internal();
973 return(EXIT_FAILURE);
974 }
975
976 memset(&req, 0, sizeof(struct req));
977 pathgen(&req);
978
979 /* Next parse out the query string. */
980
981 if (NULL != (querystring = getenv("QUERY_STRING")))
982 http_parse(&req, querystring);
983
984 if ( ! validate_manpath(&req, req.q.manpath)) {
985 pg_error_badrequest(
986 "You specified an invalid manpath.");
987 return(EXIT_FAILURE);
988 }
989
990 /* Dispatch to the three different pages. */
991
992 path = getenv("PATH_INFO");
993 if (NULL == path)
994 path = "";
995 else if ('/' == *path)
996 path++;
997
998 if ('\0' != *path)
999 pg_show(&req, path);
1000 else if (NULL != req.q.expr)
1001 pg_search(&req);
1002 else
1003 pg_index(&req);
1004
1005 for (i = 0; i < (int)req.psz; i++)
1006 free(req.p[i]);
1007 free(req.p);
1008 return(EXIT_SUCCESS);
1009 }
1010
1011 static int
1012 cmp(const void *p1, const void *p2)
1013 {
1014
1015 return(strcasecmp(((const struct manpage *)p1)->names,
1016 ((const struct manpage *)p2)->names));
1017 }
1018
1019 /*
1020 * Scan for indexable paths.
1021 */
1022 static void
1023 pathgen(struct req *req)
1024 {
1025 FILE *fp;
1026 char *dp;
1027 size_t dpsz;
1028
1029 if (NULL == (fp = fopen("manpath.conf", "r"))) {
1030 fprintf(stderr, "%s/manpath.conf: %s\n",
1031 MAN_DIR, strerror(errno));
1032 pg_error_internal();
1033 exit(EXIT_FAILURE);
1034 }
1035
1036 while (NULL != (dp = fgetln(fp, &dpsz))) {
1037 if ('\n' == dp[dpsz - 1])
1038 dpsz--;
1039 req->p = mandoc_realloc(req->p,
1040 (req->psz + 1) * sizeof(char *));
1041 req->p[req->psz++] = mandoc_strndup(dp, dpsz);
1042 }
1043
1044 if ( req->p == NULL ) {
1045 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1046 pg_error_internal();
1047 exit(EXIT_FAILURE);
1048 }
1049 }