]> git.cameronkatri.com Git - mandoc.git/blob - cgi.c
Make the stored "cat"/"mdoc"/"man" strings just be c/d/a single-character
[mandoc.git] / cgi.c
1 /* $Id: cgi.c,v 1.35 2011/12/16 12:06:35 kristaps Exp $ */
2 /*
3 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/param.h>
22 #include <sys/wait.h>
23
24 #include <assert.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <dirent.h>
28 #include <fcntl.h>
29 #include <limits.h>
30 #include <regex.h>
31 #include <stdio.h>
32 #include <stdarg.h>
33 #include <stdint.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37
38 #include "apropos_db.h"
39 #include "mandoc.h"
40 #include "mdoc.h"
41 #include "man.h"
42 #include "main.h"
43 #include "manpath.h"
44
45 #ifdef __linux__
46 # include <db_185.h>
47 #else
48 # include <db.h>
49 #endif
50
51 enum page {
52 PAGE_INDEX,
53 PAGE_SEARCH,
54 PAGE_SHOW,
55 PAGE__MAX
56 };
57
58 struct paths {
59 char *name;
60 char *path;
61 };
62
63 /*
64 * A query as passed to the search function.
65 */
66 struct query {
67 const char *arch; /* architecture */
68 const char *sec; /* manual section */
69 const char *expr; /* unparsed expression string */
70 int manroot; /* manroot index (or -1)*/
71 int whatis; /* whether whatis mode */
72 int legacy; /* whether legacy mode */
73 };
74
75 struct req {
76 struct query q;
77 struct paths *p;
78 size_t psz;
79 enum page page;
80 };
81
82 static int atou(const char *, unsigned *);
83 static void catman(const struct req *, const char *);
84 static int cmp(const void *, const void *);
85 static void format(const struct req *, const char *);
86 static void html_print(const char *);
87 static void html_putchar(char);
88 static int http_decode(char *);
89 static void http_parse(struct req *, char *);
90 static int pathstop(DIR *);
91 static void pathgen(DIR *, char *, struct req *);
92 static void pg_index(const struct req *, char *);
93 static void pg_search(const struct req *, char *);
94 static void pg_show(const struct req *, char *);
95 static void resp_bad(void);
96 static void resp_baddb(void);
97 static void resp_error400(void);
98 static void resp_error404(const char *);
99 static void resp_begin_html(int, const char *);
100 static void resp_begin_http(int, const char *);
101 static void resp_end_html(void);
102 static void resp_index(const struct req *);
103 static void resp_search(struct res *, size_t, void *);
104 static void resp_searchform(const struct req *);
105
106 static const char *progname; /* cgi script name */
107 static const char *cache; /* cache directory */
108 static const char *css; /* css directory */
109 static const char *host; /* hostname */
110
111 static const char * const pages[PAGE__MAX] = {
112 "index", /* PAGE_INDEX */
113 "search", /* PAGE_SEARCH */
114 "show", /* PAGE_SHOW */
115 };
116
117 /*
118 * This is just OpenBSD's strtol(3) suggestion.
119 * I use it instead of strtonum(3) for portability's sake.
120 */
121 static int
122 atou(const char *buf, unsigned *v)
123 {
124 char *ep;
125 long lval;
126
127 errno = 0;
128 lval = strtol(buf, &ep, 10);
129 if (buf[0] == '\0' || *ep != '\0')
130 return(0);
131 if ((errno == ERANGE && (lval == LONG_MAX ||
132 lval == LONG_MIN)) ||
133 (lval > UINT_MAX || lval < 0))
134 return(0);
135
136 *v = (unsigned int)lval;
137 return(1);
138 }
139
140 /*
141 * Print a character, escaping HTML along the way.
142 * This will pass non-ASCII straight to output: be warned!
143 */
144 static void
145 html_putchar(char c)
146 {
147
148 switch (c) {
149 case ('"'):
150 printf("&quote;");
151 break;
152 case ('&'):
153 printf("&amp;");
154 break;
155 case ('>'):
156 printf("&gt;");
157 break;
158 case ('<'):
159 printf("&lt;");
160 break;
161 default:
162 putchar((unsigned char)c);
163 break;
164 }
165 }
166
167 /*
168 * Call through to html_putchar().
169 * Accepts NULL strings.
170 */
171 static void
172 html_print(const char *p)
173 {
174
175 if (NULL == p)
176 return;
177 while ('\0' != *p)
178 html_putchar(*p++);
179 }
180
181 /*
182 * Parse out key-value pairs from an HTTP request variable.
183 * This can be either a cookie or a POST/GET string, although man.cgi
184 * uses only GET for simplicity.
185 */
186 static void
187 http_parse(struct req *req, char *p)
188 {
189 char *key, *val, *manroot;
190 size_t sz;
191 int i, legacy;
192
193 memset(&req->q, 0, sizeof(struct query));
194
195 req->q.whatis = 1;
196 legacy = -1;
197 manroot = NULL;
198
199 while (p && '\0' != *p) {
200 while (' ' == *p)
201 p++;
202
203 key = p;
204 val = NULL;
205
206 if (NULL != (p = strchr(p, '='))) {
207 *p++ = '\0';
208 val = p;
209
210 sz = strcspn(p, ";&");
211 /* LINTED */
212 p += sz;
213
214 if ('\0' != *p)
215 *p++ = '\0';
216 } else {
217 p = key;
218 sz = strcspn(p, ";&");
219 /* LINTED */
220 p += sz;
221
222 if ('\0' != *p)
223 p++;
224 continue;
225 }
226
227 if ('\0' == *key || '\0' == *val)
228 continue;
229
230 /* Just abort handling. */
231
232 if ( ! http_decode(key))
233 break;
234 if ( ! http_decode(val))
235 break;
236
237 if (0 == strcmp(key, "expr"))
238 req->q.expr = val;
239 else if (0 == strcmp(key, "query"))
240 req->q.expr = val;
241 else if (0 == strcmp(key, "sec"))
242 req->q.sec = val;
243 else if (0 == strcmp(key, "sektion"))
244 req->q.sec = val;
245 else if (0 == strcmp(key, "arch"))
246 req->q.arch = val;
247 else if (0 == strcmp(key, "manpath"))
248 manroot = val;
249 else if (0 == strcmp(key, "apropos"))
250 legacy = 0 == strcmp(val, "0");
251 else if (0 == strcmp(key, "op"))
252 req->q.whatis = 0 == strcasecmp(val, "whatis");
253 }
254
255 /* Test for old man.cgi compatibility mode. */
256
257 if (legacy == 0) {
258 req->q.whatis = 0;
259 req->q.legacy = 1;
260 } else if (legacy > 0) {
261 req->q.legacy = 1;
262 req->q.whatis = 1;
263 }
264
265 /*
266 * Section "0" means no section when in legacy mode.
267 * For some man.cgi scripts, "default" arch is none.
268 */
269
270 if (req->q.legacy && NULL != req->q.sec)
271 if (0 == strcmp(req->q.sec, "0"))
272 req->q.sec = NULL;
273 if (req->q.legacy && NULL != req->q.arch)
274 if (0 == strcmp(req->q.arch, "default"))
275 req->q.arch = NULL;
276
277 /* Default to first manroot. */
278
279 if (NULL != manroot) {
280 for (i = 0; i < (int)req->psz; i++)
281 if (0 == strcmp(req->p[i].name, manroot))
282 break;
283 req->q.manroot = i < (int)req->psz ? i : -1;
284 }
285 }
286
287 /*
288 * HTTP-decode a string. The standard explanation is that this turns
289 * "%4e+foo" into "n foo" in the regular way. This is done in-place
290 * over the allocated string.
291 */
292 static int
293 http_decode(char *p)
294 {
295 char hex[3];
296 int c;
297
298 hex[2] = '\0';
299
300 for ( ; '\0' != *p; p++) {
301 if ('%' == *p) {
302 if ('\0' == (hex[0] = *(p + 1)))
303 return(0);
304 if ('\0' == (hex[1] = *(p + 2)))
305 return(0);
306 if (1 != sscanf(hex, "%x", &c))
307 return(0);
308 if ('\0' == c)
309 return(0);
310
311 *p = (char)c;
312 memmove(p + 1, p + 3, strlen(p + 3) + 1);
313 } else
314 *p = '+' == *p ? ' ' : *p;
315 }
316
317 *p = '\0';
318 return(1);
319 }
320
321 static void
322 resp_begin_http(int code, const char *msg)
323 {
324
325 if (200 != code)
326 printf("Status: %d %s\n", code, msg);
327
328 puts("Content-Type: text/html; charset=utf-8\n"
329 "Cache-Control: no-cache\n"
330 "Pragma: no-cache\n"
331 "");
332
333 fflush(stdout);
334 }
335
336 static void
337 resp_begin_html(int code, const char *msg)
338 {
339
340 resp_begin_http(code, msg);
341
342 printf("<!DOCTYPE HTML PUBLIC "
343 " \"-//W3C//DTD HTML 4.01//EN\""
344 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
345 "<HTML>\n"
346 "<HEAD>\n"
347 "<META HTTP-EQUIV=\"Content-Type\""
348 " CONTENT=\"text/html; charset=utf-8\">\n"
349 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
350 " TYPE=\"text/css\" media=\"all\">\n"
351 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
352 " TYPE=\"text/css\" media=\"all\">\n"
353 "<TITLE>System Manpage Reference</TITLE>\n"
354 "</HEAD>\n"
355 "<BODY>\n"
356 "<!-- Begin page content. //-->\n", css, css);
357 }
358
359 static void
360 resp_end_html(void)
361 {
362
363 puts("</BODY>\n"
364 "</HTML>");
365 }
366
367 static void
368 resp_searchform(const struct req *req)
369 {
370 int i;
371
372 puts("<!-- Begin search form. //-->");
373 printf("<DIV ID=\"mancgi\">\n"
374 "<FORM ACTION=\"%s/search.html\" METHOD=\"get\">\n"
375 "<FIELDSET>\n"
376 "<LEGEND>Search Parameters</LEGEND>\n"
377 "<INPUT TYPE=\"submit\" NAME=\"op\""
378 " VALUE=\"Whatis\"> or \n"
379 "<INPUT TYPE=\"submit\" NAME=\"op\""
380 " VALUE=\"apropos\"> for manuals satisfying \n"
381 "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"",
382 progname);
383 html_print(req->q.expr ? req->q.expr : "");
384 printf("\">, section "
385 "<INPUT TYPE=\"text\""
386 " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
387 html_print(req->q.sec ? req->q.sec : "");
388 printf("\">, arch "
389 "<INPUT TYPE=\"text\""
390 " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
391 html_print(req->q.arch ? req->q.arch : "");
392 printf("\">");
393 if (req->psz > 1) {
394 puts(", <SELECT NAME=\"manpath\">");
395 for (i = 0; i < (int)req->psz; i++) {
396 printf("<OPTION %s VALUE=\"",
397 (i == req->q.manroot) ||
398 (0 == i && -1 == req->q.manroot) ?
399 "SELECTED=\"selected\"" : "");
400 html_print(req->p[i].name);
401 printf("\">");
402 html_print(req->p[i].name);
403 puts("</OPTION>");
404 }
405 puts("</SELECT>");
406 }
407 puts(".\n"
408 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
409 "</FIELDSET>\n"
410 "</FORM>\n"
411 "</DIV>");
412 puts("<!-- End search form. //-->");
413 }
414
415 static void
416 resp_index(const struct req *req)
417 {
418
419 resp_begin_html(200, NULL);
420 resp_searchform(req);
421 resp_end_html();
422 }
423
424 static void
425 resp_error400(void)
426 {
427
428 resp_begin_html(400, "Query Malformed");
429 printf("<H1>Malformed Query</H1>\n"
430 "<P>\n"
431 "The query your entered was malformed.\n"
432 "Try again from the\n"
433 "<A HREF=\"%s/index.html\">main page</A>.\n"
434 "</P>", progname);
435 resp_end_html();
436 }
437
438 static void
439 resp_error404(const char *page)
440 {
441
442 resp_begin_html(404, "Not Found");
443 puts("<H1>Page Not Found</H1>\n"
444 "<P>\n"
445 "The page you're looking for, ");
446 printf("<B>");
447 html_print(page);
448 printf("</B>,\n"
449 "could not be found.\n"
450 "Try searching from the\n"
451 "<A HREF=\"%s/index.html\">main page</A>.\n"
452 "</P>", progname);
453 resp_end_html();
454 }
455
456 static void
457 resp_bad(void)
458 {
459 resp_begin_html(500, "Internal Server Error");
460 puts("<P>Generic badness happened.</P>");
461 resp_end_html();
462 }
463
464 static void
465 resp_baddb(void)
466 {
467
468 resp_begin_html(500, "Internal Server Error");
469 puts("<P>Your database is broken.</P>");
470 resp_end_html();
471 }
472
473 static void
474 resp_search(struct res *r, size_t sz, void *arg)
475 {
476 int i;
477 const struct req *req;
478
479 req = (const struct req *)arg;
480 assert(req->q.manroot >= 0);
481
482 if (1 == sz) {
483 /*
484 * If we have just one result, then jump there now
485 * without any delay.
486 */
487 puts("Status: 303 See Other");
488 printf("Location: http://%s%s/show/%d/%u/%u.html\n",
489 host, progname, req->q.manroot,
490 r[0].volume, r[0].rec);
491 puts("Content-Type: text/html; charset=utf-8\n");
492 return;
493 }
494
495 qsort(r, sz, sizeof(struct res), cmp);
496
497 resp_begin_html(200, NULL);
498 resp_searchform(req);
499
500 puts("<DIV CLASS=\"results\">");
501
502 if (0 == sz) {
503 printf("<P>\n"
504 "No %s results found.\n",
505 req->q.whatis ? "whatis" : "apropos");
506 if (req->q.whatis) {
507 printf("(Try <A HREF=\"%s/search.html?"
508 "op=apropos&amp;expr=", progname);
509 html_print(req->q.expr ? req->q.expr : "");
510 printf("&amp;sec=");
511 html_print(req->q.sec ? req->q.sec : "");
512 printf("&amp;arch=");
513 html_print(req->q.arch ? req->q.arch : "");
514 puts("\">apropos</A>?)");
515 }
516 puts("</P>");
517 puts("</DIV>");
518 resp_end_html();
519 return;
520 }
521
522 puts("<TABLE>");
523
524 for (i = 0; i < (int)sz; i++) {
525 printf("<TR>\n"
526 "<TD CLASS=\"title\">\n"
527 "<A HREF=\"%s/show/%d/%u/%u.html\">",
528 progname, req->q.manroot,
529 r[i].volume, r[i].rec);
530 html_print(r[i].title);
531 putchar('(');
532 html_print(r[i].cat);
533 if (r[i].arch && '\0' != *r[i].arch) {
534 putchar('/');
535 html_print(r[i].arch);
536 }
537 printf(")</A>\n"
538 "</TD>\n"
539 "<TD CLASS=\"desc\">");
540 html_print(r[i].desc);
541 puts("</TD>\n"
542 "</TR>");
543 }
544
545 puts("</TABLE>\n"
546 "</DIV>");
547 resp_end_html();
548 }
549
550 /* ARGSUSED */
551 static void
552 pg_index(const struct req *req, char *path)
553 {
554
555 resp_index(req);
556 }
557
558 static void
559 catman(const struct req *req, const char *file)
560 {
561 FILE *f;
562 size_t len;
563 int i;
564 char *p;
565 int italic, bold;
566
567 if (NULL == (f = fopen(file, "r"))) {
568 resp_baddb();
569 return;
570 }
571
572 resp_begin_html(200, NULL);
573 resp_searchform(req);
574 puts("<DIV CLASS=\"catman\">\n"
575 "<PRE>");
576
577 while (NULL != (p = fgetln(f, &len))) {
578 bold = italic = 0;
579 for (i = 0; i < (int)len - 1; i++) {
580 /*
581 * This means that the catpage is out of state.
582 * Ignore it and keep going (although the
583 * catpage is bogus).
584 */
585
586 if ('\b' == p[i] || '\n' == p[i])
587 continue;
588
589 /*
590 * Print a regular character.
591 * Close out any bold/italic scopes.
592 * If we're in back-space mode, make sure we'll
593 * have something to enter when we backspace.
594 */
595
596 if ('\b' != p[i + 1]) {
597 if (italic)
598 printf("</I>");
599 if (bold)
600 printf("</B>");
601 italic = bold = 0;
602 html_putchar(p[i]);
603 continue;
604 } else if (i + 2 >= (int)len)
605 continue;
606
607 /* Italic mode. */
608
609 if ('_' == p[i]) {
610 if (bold)
611 printf("</B>");
612 if ( ! italic)
613 printf("<I>");
614 bold = 0;
615 italic = 1;
616 i += 2;
617 html_putchar(p[i]);
618 continue;
619 }
620
621 /*
622 * Handle funny behaviour troff-isms.
623 * These grok'd from the original man2html.c.
624 */
625
626 if (('+' == p[i] && 'o' == p[i + 2]) ||
627 ('o' == p[i] && '+' == p[i + 2]) ||
628 ('|' == p[i] && '=' == p[i + 2]) ||
629 ('=' == p[i] && '|' == p[i + 2]) ||
630 ('*' == p[i] && '=' == p[i + 2]) ||
631 ('=' == p[i] && '*' == p[i + 2]) ||
632 ('*' == p[i] && '|' == p[i + 2]) ||
633 ('|' == p[i] && '*' == p[i + 2])) {
634 if (italic)
635 printf("</I>");
636 if (bold)
637 printf("</B>");
638 italic = bold = 0;
639 putchar('*');
640 i += 2;
641 continue;
642 } else if (('|' == p[i] && '-' == p[i + 2]) ||
643 ('-' == p[i] && '|' == p[i + 1]) ||
644 ('+' == p[i] && '-' == p[i + 1]) ||
645 ('-' == p[i] && '+' == p[i + 1]) ||
646 ('+' == p[i] && '|' == p[i + 1]) ||
647 ('|' == p[i] && '+' == p[i + 1])) {
648 if (italic)
649 printf("</I>");
650 if (bold)
651 printf("</B>");
652 italic = bold = 0;
653 putchar('+');
654 i += 2;
655 continue;
656 }
657
658 /* Bold mode. */
659
660 if (italic)
661 printf("</I>");
662 if ( ! bold)
663 printf("<B>");
664 bold = 1;
665 italic = 0;
666 i += 2;
667 html_putchar(p[i]);
668 }
669
670 /*
671 * Clean up the last character.
672 * We can get to a newline; don't print that.
673 */
674
675 if (italic)
676 printf("</I>");
677 if (bold)
678 printf("</B>");
679
680 if (i == (int)len - 1 && '\n' != p[i])
681 html_putchar(p[i]);
682
683 putchar('\n');
684 }
685
686 puts("</PRE>\n"
687 "</DIV>\n"
688 "</BODY>\n"
689 "</HTML>");
690
691 fclose(f);
692 }
693
694 static void
695 format(const struct req *req, const char *file)
696 {
697 struct mparse *mp;
698 int fd;
699 struct mdoc *mdoc;
700 struct man *man;
701 void *vp;
702 enum mandoclevel rc;
703 char opts[MAXPATHLEN + 128];
704
705 if (-1 == (fd = open(file, O_RDONLY, 0))) {
706 resp_baddb();
707 return;
708 }
709
710 mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
711 rc = mparse_readfd(mp, fd, file);
712 close(fd);
713
714 if (rc >= MANDOCLEVEL_FATAL) {
715 resp_baddb();
716 return;
717 }
718
719 snprintf(opts, sizeof(opts), "fragment,"
720 "man=%s/search.html?sec=%%S&expr=%%N,"
721 /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
722 progname);
723
724 mparse_result(mp, &mdoc, &man);
725 if (NULL == man && NULL == mdoc) {
726 resp_baddb();
727 mparse_free(mp);
728 return;
729 }
730
731 resp_begin_html(200, NULL);
732 resp_searchform(req);
733
734 vp = html_alloc(opts);
735
736 if (NULL != mdoc)
737 html_mdoc(vp, mdoc);
738 else
739 html_man(vp, man);
740
741 puts("</BODY>\n"
742 "</HTML>");
743
744 html_free(vp);
745 mparse_free(mp);
746 }
747
748 static void
749 pg_show(const struct req *req, char *path)
750 {
751 struct manpaths ps;
752 size_t sz;
753 char *sub;
754 char file[MAXPATHLEN];
755 const char *cp;
756 int rc, catm;
757 unsigned int vol, rec, mr;
758 DB *idx;
759 DBT key, val;
760
761 idx = NULL;
762
763 /* Parse out mroot, volume, and record from the path. */
764
765 if (NULL == path || NULL == (sub = strchr(path, '/'))) {
766 resp_error400();
767 return;
768 }
769 *sub++ = '\0';
770 if ( ! atou(path, &mr)) {
771 resp_error400();
772 return;
773 }
774 path = sub;
775 if (NULL == (sub = strchr(path, '/'))) {
776 resp_error400();
777 return;
778 }
779 *sub++ = '\0';
780 if ( ! atou(path, &vol) || ! atou(sub, &rec)) {
781 resp_error400();
782 return;
783 } else if (mr >= (unsigned int)req->psz) {
784 resp_error400();
785 return;
786 }
787
788 /*
789 * Begin by chdir()ing into the manroot.
790 * This way we can pick up the database files, which are
791 * relative to the manpath root.
792 */
793
794 if (-1 == chdir(req->p[(int)mr].path)) {
795 perror(req->p[(int)mr].path);
796 resp_baddb();
797 return;
798 }
799
800 memset(&ps, 0, sizeof(struct manpaths));
801 manpath_manconf(&ps, "etc/catman.conf");
802
803 if (vol >= (unsigned int)ps.sz) {
804 resp_error400();
805 goto out;
806 }
807
808 sz = strlcpy(file, ps.paths[vol], MAXPATHLEN);
809 assert(sz < MAXPATHLEN);
810 strlcat(file, "/mandoc.index", MAXPATHLEN);
811
812 /* Open the index recno(3) database. */
813
814 idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
815 if (NULL == idx) {
816 perror(file);
817 resp_baddb();
818 goto out;
819 }
820
821 key.data = &rec;
822 key.size = 4;
823
824 if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
825 rc < 0 ? resp_baddb() : resp_error400();
826 goto out;
827 } else if (0 == val.size) {
828 resp_baddb();
829 goto out;
830 }
831
832 cp = (char *)val.data;
833 catm = 'c' == *cp++;
834
835 if (NULL == memchr(cp, '\0', val.size - 1))
836 resp_baddb();
837 else {
838 file[(int)sz] = '\0';
839 strlcat(file, "/", MAXPATHLEN);
840 strlcat(file, cp, MAXPATHLEN);
841 if (catm)
842 catman(req, file);
843 else
844 format(req, file);
845 }
846 out:
847 if (idx)
848 (*idx->close)(idx);
849 manpath_free(&ps);
850 }
851
852 static void
853 pg_search(const struct req *req, char *path)
854 {
855 size_t tt;
856 struct manpaths ps;
857 int i, sz, rc;
858 const char *ep, *start;
859 char **cp;
860 struct opts opt;
861 struct expr *expr;
862
863 if (req->q.manroot < 0 || 0 == req->psz) {
864 resp_search(NULL, 0, (void *)req);
865 return;
866 }
867
868 memset(&opt, 0, sizeof(struct opts));
869
870 ep = req->q.expr;
871 opt.arch = req->q.arch;
872 opt.cat = req->q.sec;
873 rc = -1;
874 sz = 0;
875 cp = NULL;
876
877 /*
878 * Begin by chdir()ing into the root of the manpath.
879 * This way we can pick up the database files, which are
880 * relative to the manpath root.
881 */
882
883 assert(req->q.manroot < (int)req->psz);
884 if (-1 == (chdir(req->p[req->q.manroot].path))) {
885 perror(req->p[req->q.manroot].path);
886 resp_search(NULL, 0, (void *)req);
887 return;
888 }
889
890 memset(&ps, 0, sizeof(struct manpaths));
891 manpath_manconf(&ps, "etc/catman.conf");
892
893 /*
894 * Poor man's tokenisation: just break apart by spaces.
895 * Yes, this is half-ass. But it works for now.
896 */
897
898 while (ep && isspace((unsigned char)*ep))
899 ep++;
900
901 while (ep && '\0' != *ep) {
902 cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
903 start = ep;
904 while ('\0' != *ep && ! isspace((unsigned char)*ep))
905 ep++;
906 cp[sz] = mandoc_malloc((ep - start) + 1);
907 memcpy(cp[sz], start, ep - start);
908 cp[sz++][ep - start] = '\0';
909 while (isspace((unsigned char)*ep))
910 ep++;
911 }
912
913 /*
914 * Pump down into apropos backend.
915 * The resp_search() function is called with the results.
916 */
917
918 expr = req->q.whatis ?
919 termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
920
921 if (NULL != expr)
922 rc = apropos_search
923 (ps.sz, ps.paths, &opt,
924 expr, tt, (void *)req, resp_search);
925
926 /* ...unless errors occured. */
927
928 if (0 == rc)
929 resp_baddb();
930 else if (-1 == rc)
931 resp_search(NULL, 0, (void *)req);
932
933 for (i = 0; i < sz; i++)
934 free(cp[i]);
935
936 free(cp);
937 exprfree(expr);
938 manpath_free(&ps);
939 }
940
941 int
942 main(void)
943 {
944 int i;
945 char buf[MAXPATHLEN];
946 DIR *cwd;
947 struct req req;
948 char *p, *path, *subpath;
949
950 /* Scan our run-time environment. */
951
952 if (NULL == (cache = getenv("CACHE_DIR")))
953 cache = "/cache/man.cgi";
954
955 if (NULL == (progname = getenv("SCRIPT_NAME")))
956 progname = "";
957
958 if (NULL == (css = getenv("CSS_DIR")))
959 css = "";
960
961 if (NULL == (host = getenv("HTTP_HOST")))
962 host = "localhost";
963
964 /*
965 * First we change directory into the cache directory so that
966 * subsequent scanning for manpath directories is rooted
967 * relative to the same position.
968 */
969
970 if (-1 == chdir(cache)) {
971 perror(cache);
972 resp_bad();
973 return(EXIT_FAILURE);
974 } else if (NULL == (cwd = opendir(cache))) {
975 perror(cache);
976 resp_bad();
977 return(EXIT_FAILURE);
978 }
979
980 memset(&req, 0, sizeof(struct req));
981
982 strlcpy(buf, ".", MAXPATHLEN);
983 pathgen(cwd, buf, &req);
984 closedir(cwd);
985
986 /* Next parse out the query string. */
987
988 if (NULL != (p = getenv("QUERY_STRING")))
989 http_parse(&req, p);
990
991 /*
992 * Now juggle paths to extract information.
993 * We want to extract our filetype (the file suffix), the
994 * initial path component, then the trailing component(s).
995 * Start with leading subpath component.
996 */
997
998 subpath = path = NULL;
999 req.page = PAGE__MAX;
1000
1001 if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
1002 req.page = PAGE_INDEX;
1003
1004 if (NULL != path && '/' == *path && '\0' == *++path)
1005 req.page = PAGE_INDEX;
1006
1007 /* Strip file suffix. */
1008
1009 if (NULL != path && NULL != (p = strrchr(path, '.')))
1010 if (NULL != p && NULL == strchr(p, '/'))
1011 *p++ = '\0';
1012
1013 /* Resolve subpath component. */
1014
1015 if (NULL != path && NULL != (subpath = strchr(path, '/')))
1016 *subpath++ = '\0';
1017
1018 /* Map path into one we recognise. */
1019
1020 if (NULL != path && '\0' != *path)
1021 for (i = 0; i < (int)PAGE__MAX; i++)
1022 if (0 == strcmp(pages[i], path)) {
1023 req.page = (enum page)i;
1024 break;
1025 }
1026
1027 /* Route pages. */
1028
1029 switch (req.page) {
1030 case (PAGE_INDEX):
1031 pg_index(&req, subpath);
1032 break;
1033 case (PAGE_SEARCH):
1034 pg_search(&req, subpath);
1035 break;
1036 case (PAGE_SHOW):
1037 pg_show(&req, subpath);
1038 break;
1039 default:
1040 resp_error404(path);
1041 break;
1042 }
1043
1044 for (i = 0; i < (int)req.psz; i++) {
1045 free(req.p[i].path);
1046 free(req.p[i].name);
1047 }
1048
1049 free(req.p);
1050 return(EXIT_SUCCESS);
1051 }
1052
1053 static int
1054 cmp(const void *p1, const void *p2)
1055 {
1056
1057 return(strcasecmp(((const struct res *)p1)->title,
1058 ((const struct res *)p2)->title));
1059 }
1060
1061 /*
1062 * Check to see if an "etc" path consists of a catman.conf file. If it
1063 * does, that means that the path contains a tree created by catman(8)
1064 * and should be used for indexing.
1065 */
1066 static int
1067 pathstop(DIR *dir)
1068 {
1069 struct dirent *d;
1070
1071 while (NULL != (d = readdir(dir)))
1072 if (DT_REG == d->d_type)
1073 if (0 == strcmp(d->d_name, "catman.conf"))
1074 return(1);
1075
1076 return(0);
1077 }
1078
1079 /*
1080 * Scan for indexable paths.
1081 * This adds all paths with "etc/catman.conf" to the buffer.
1082 */
1083 static void
1084 pathgen(DIR *dir, char *path, struct req *req)
1085 {
1086 struct dirent *d;
1087 char *cp;
1088 DIR *cd;
1089 int rc;
1090 size_t sz, ssz;
1091
1092 sz = strlcat(path, "/", MAXPATHLEN);
1093 if (sz >= MAXPATHLEN) {
1094 fprintf(stderr, "%s: Path too long", path);
1095 return;
1096 }
1097
1098 /*
1099 * First, scan for the "etc" directory.
1100 * If it's found, then see if it should cause us to stop. This
1101 * happens when a catman.conf is found in the directory.
1102 */
1103
1104 rc = 0;
1105 while (0 == rc && NULL != (d = readdir(dir))) {
1106 if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
1107 continue;
1108
1109 path[(int)sz] = '\0';
1110 ssz = strlcat(path, d->d_name, MAXPATHLEN);
1111
1112 if (ssz >= MAXPATHLEN) {
1113 fprintf(stderr, "%s: Path too long", path);
1114 return;
1115 } else if (NULL == (cd = opendir(path))) {
1116 perror(path);
1117 return;
1118 }
1119
1120 rc = pathstop(cd);
1121 closedir(cd);
1122 }
1123
1124 if (rc > 0) {
1125 /* This also strips the trailing slash. */
1126 path[(int)--sz] = '\0';
1127 req->p = mandoc_realloc
1128 (req->p,
1129 (req->psz + 1) * sizeof(struct paths));
1130 /*
1131 * Strip out the leading "./" unless we're just a ".",
1132 * in which case use an empty string as our name.
1133 */
1134 req->p[(int)req->psz].path = mandoc_strdup(path);
1135 req->p[(int)req->psz].name =
1136 cp = mandoc_strdup(path + (1 == sz ? 1 : 2));
1137 req->psz++;
1138 /*
1139 * The name is just the path with all the slashes taken
1140 * out of it. Simple but effective.
1141 */
1142 for ( ; '\0' != *cp; cp++)
1143 if ('/' == *cp)
1144 *cp = ' ';
1145 return;
1146 }
1147
1148 /*
1149 * If no etc/catman.conf was found, recursively enter child
1150 * directory and continue scanning.
1151 */
1152
1153 rewinddir(dir);
1154 while (NULL != (d = readdir(dir))) {
1155 if (DT_DIR != d->d_type || '.' == d->d_name[0])
1156 continue;
1157
1158 path[(int)sz] = '\0';
1159 ssz = strlcat(path, d->d_name, MAXPATHLEN);
1160
1161 if (ssz >= MAXPATHLEN) {
1162 fprintf(stderr, "%s: Path too long", path);
1163 return;
1164 } else if (NULL == (cd = opendir(path))) {
1165 perror(path);
1166 return;
1167 }
1168
1169 pathgen(cd, path, req);
1170 closedir(cd);
1171 }
1172 }