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