Annotation of mandoc/cgi.c, Revision 1.32
1.32 ! kristaps 1: /* $Id: cgi.c,v 1.31 2011/12/14 13:36:59 kristaps Exp $ */
1.6 kristaps 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:
1.1 kristaps 24: #include <assert.h>
1.6 kristaps 25: #include <ctype.h>
26: #include <errno.h>
1.24 kristaps 27: #include <dirent.h>
1.1 kristaps 28: #include <fcntl.h>
1.6 kristaps 29: #include <limits.h>
1.1 kristaps 30: #include <regex.h>
31: #include <stdio.h>
32: #include <stdarg.h>
1.5 kristaps 33: #include <stdint.h>
1.1 kristaps 34: #include <stdlib.h>
35: #include <string.h>
1.6 kristaps 36: #include <unistd.h>
1.1 kristaps 37:
1.6 kristaps 38: #include "apropos_db.h"
1.4 schwarze 39: #include "mandoc.h"
1.8 kristaps 40: #include "mdoc.h"
41: #include "man.h"
42: #include "main.h"
1.6 kristaps 43: #include "manpath.h"
44:
45: #ifdef __linux__
46: # include <db_185.h>
47: #else
48: # include <db.h>
49: #endif
1.1 kristaps 50:
51: enum page {
52: PAGE_INDEX,
53: PAGE_SEARCH,
1.6 kristaps 54: PAGE_SHOW,
1.1 kristaps 55: PAGE__MAX
56: };
57:
1.24 kristaps 58: struct paths {
59: char *name;
60: char *path;
61: };
62:
1.20 kristaps 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 */
1.26 kristaps 70: int manroot; /* manroot index (or -1)*/
1.20 kristaps 71: int whatis; /* whether whatis mode */
72: int legacy; /* whether legacy mode */
73: };
74:
1.1 kristaps 75: struct req {
1.24 kristaps 76: struct query q;
77: struct paths *p;
78: size_t psz;
1.1 kristaps 79: enum page page;
80: };
81:
1.6 kristaps 82: static int atou(const char *, unsigned *);
1.32 ! kristaps 83: static void catman(const struct req *, const char *);
1.15 kristaps 84: static int cmp(const void *, const void *);
1.32 ! kristaps 85: static void format(const struct req *, const char *);
1.6 kristaps 86: static void html_print(const char *);
1.10 kristaps 87: static void html_putchar(char);
1.24 kristaps 88: static int http_decode(char *);
1.26 kristaps 89: static void http_parse(struct req *, char *);
1.24 kristaps 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 *);
1.7 kristaps 95: static void resp_bad(void);
1.6 kristaps 96: static void resp_baddb(void);
1.10 kristaps 97: static void resp_error400(void);
98: static void resp_error404(const char *);
1.6 kristaps 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:
1.29 kristaps 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 */
1.1 kristaps 110:
111: static const char * const pages[PAGE__MAX] = {
112: "index", /* PAGE_INDEX */
113: "search", /* PAGE_SEARCH */
1.6 kristaps 114: "show", /* PAGE_SHOW */
1.1 kristaps 115: };
116:
1.6 kristaps 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: }
1.1 kristaps 139:
1.20 kristaps 140: /*
141: * Print a character, escaping HTML along the way.
142: * This will pass non-ASCII straight to output: be warned!
143: */
1.10 kristaps 144: static void
145: html_putchar(char c)
146: {
147:
148: switch (c) {
149: case ('"'):
150: printf(""e;");
151: break;
152: case ('&'):
153: printf("&");
154: break;
155: case ('>'):
156: printf(">");
157: break;
158: case ('<'):
159: printf("<");
160: break;
161: default:
162: putchar((unsigned char)c);
163: break;
164: }
165: }
166:
1.6 kristaps 167: /*
1.20 kristaps 168: * Call through to html_putchar().
169: * Accepts NULL strings.
1.6 kristaps 170: */
1.1 kristaps 171: static void
1.6 kristaps 172: html_print(const char *p)
1.1 kristaps 173: {
1.6 kristaps 174:
175: if (NULL == p)
176: return;
1.1 kristaps 177: while ('\0' != *p)
1.10 kristaps 178: html_putchar(*p++);
1.1 kristaps 179: }
180:
181: /*
182: * Parse out key-value pairs from an HTTP request variable.
1.6 kristaps 183: * This can be either a cookie or a POST/GET string, although man.cgi
184: * uses only GET for simplicity.
1.1 kristaps 185: */
186: static void
1.26 kristaps 187: http_parse(struct req *req, char *p)
1.1 kristaps 188: {
1.26 kristaps 189: char *key, *val, *manroot;
1.24 kristaps 190: size_t sz;
1.26 kristaps 191: int i, legacy;
1.1 kristaps 192:
1.26 kristaps 193: memset(&req->q, 0, sizeof(struct query));
1.24 kristaps 194:
1.26 kristaps 195: req->q.whatis = 1;
1.24 kristaps 196: legacy = -1;
1.26 kristaps 197: manroot = NULL;
1.1 kristaps 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:
1.24 kristaps 232: if ( ! http_decode(key))
233: break;
234: if ( ! http_decode(val))
235: break;
236:
237: if (0 == strcmp(key, "expr"))
1.26 kristaps 238: req->q.expr = val;
1.24 kristaps 239: else if (0 == strcmp(key, "query"))
1.26 kristaps 240: req->q.expr = val;
1.24 kristaps 241: else if (0 == strcmp(key, "sec"))
1.26 kristaps 242: req->q.sec = val;
1.24 kristaps 243: else if (0 == strcmp(key, "sektion"))
1.26 kristaps 244: req->q.sec = val;
1.24 kristaps 245: else if (0 == strcmp(key, "arch"))
1.26 kristaps 246: req->q.arch = val;
247: else if (0 == strcmp(key, "manpath"))
248: manroot = val;
1.24 kristaps 249: else if (0 == strcmp(key, "apropos"))
250: legacy = 0 == strcmp(val, "0");
251: else if (0 == strcmp(key, "op"))
1.26 kristaps 252: req->q.whatis = 0 == strcasecmp(val, "whatis");
1.24 kristaps 253: }
1.1 kristaps 254:
1.24 kristaps 255: /* Test for old man.cgi compatibility mode. */
1.1 kristaps 256:
1.24 kristaps 257: if (legacy == 0) {
1.26 kristaps 258: req->q.whatis = 0;
259: req->q.legacy = 1;
1.24 kristaps 260: } else if (legacy > 0) {
1.26 kristaps 261: req->q.legacy = 1;
262: req->q.whatis = 1;
1.1 kristaps 263: }
1.24 kristaps 264:
265: /*
266: * Section "0" means no section when in legacy mode.
267: * For some man.cgi scripts, "default" arch is none.
268: */
269:
1.26 kristaps 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: }
1.1 kristaps 285: }
286:
287: /*
1.6 kristaps 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.
1.1 kristaps 291: */
292: static int
1.24 kristaps 293: http_decode(char *p)
1.1 kristaps 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:
1.6 kristaps 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:
1.20 kristaps 328: puts("Content-Type: text/html; charset=utf-8\n"
329: "Cache-Control: no-cache\n"
330: "Pragma: no-cache\n"
1.6 kristaps 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:
1.29 kristaps 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"
1.32 ! kristaps 349: "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
! 350: " TYPE=\"text/css\" media=\"all\">\n"
! 351: "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
1.29 kristaps 352: " TYPE=\"text/css\" media=\"all\">\n"
353: "<TITLE>System Manpage Reference</TITLE>\n"
354: "</HEAD>\n"
355: "<BODY>\n"
1.32 ! kristaps 356: "<!-- Begin page content. //-->\n", css, css);
1.6 kristaps 357: }
358:
359: static void
360: resp_end_html(void)
361: {
362:
1.20 kristaps 363: puts("</BODY>\n"
364: "</HTML>");
1.6 kristaps 365: }
366:
367: static void
368: resp_searchform(const struct req *req)
369: {
1.27 kristaps 370: int i;
1.13 kristaps 371:
1.6 kristaps 372: puts("<!-- Begin search form. //-->");
1.32 ! kristaps 373: printf("<DIV ID=\"mancgi\">\n"
! 374: "<FORM ACTION=\"%s/search.html\" METHOD=\"get\">\n"
1.29 kristaps 375: "<FIELDSET>\n"
1.16 kristaps 376: "<LEGEND>Search Parameters</LEGEND>\n"
1.20 kristaps 377: "<INPUT TYPE=\"submit\" NAME=\"op\""
378: " VALUE=\"Whatis\"> or \n"
379: "<INPUT TYPE=\"submit\" NAME=\"op\""
380: " VALUE=\"apropos\"> for manuals satisfying \n"
1.29 kristaps 381: "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"",
382: progname);
1.24 kristaps 383: html_print(req->q.expr ? req->q.expr : "");
1.14 kristaps 384: printf("\">, section "
1.20 kristaps 385: "<INPUT TYPE=\"text\""
386: " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
1.24 kristaps 387: html_print(req->q.sec ? req->q.sec : "");
1.14 kristaps 388: printf("\">, arch "
1.20 kristaps 389: "<INPUT TYPE=\"text\""
390: " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
1.24 kristaps 391: html_print(req->q.arch ? req->q.arch : "");
1.27 kristaps 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"
1.12 kristaps 408: "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
409: "</FIELDSET>\n"
1.32 ! kristaps 410: "</FORM>\n"
! 411: "</DIV>");
1.20 kristaps 412: puts("<!-- End search form. //-->");
1.6 kristaps 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
1.10 kristaps 425: resp_error400(void)
1.9 kristaps 426: {
427:
1.10 kristaps 428: resp_begin_html(400, "Query Malformed");
1.12 kristaps 429: printf("<H1>Malformed Query</H1>\n"
430: "<P>\n"
1.20 kristaps 431: "The query your entered was malformed.\n"
432: "Try again from the\n"
1.22 kristaps 433: "<A HREF=\"%s/index.html\">main page</A>.\n"
1.12 kristaps 434: "</P>", progname);
1.9 kristaps 435: resp_end_html();
436: }
437:
438: static void
1.10 kristaps 439: resp_error404(const char *page)
1.6 kristaps 440: {
441:
442: resp_begin_html(404, "Not Found");
1.10 kristaps 443: puts("<H1>Page Not Found</H1>\n"
444: "<P>\n"
1.20 kristaps 445: "The page you're looking for, ");
446: printf("<B>");
1.10 kristaps 447: html_print(page);
1.12 kristaps 448: printf("</B>,\n"
1.20 kristaps 449: "could not be found.\n"
450: "Try searching from the\n"
1.22 kristaps 451: "<A HREF=\"%s/index.html\">main page</A>.\n"
1.12 kristaps 452: "</P>", progname);
1.6 kristaps 453: resp_end_html();
454: }
1.1 kristaps 455:
456: static void
1.7 kristaps 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
1.6 kristaps 465: resp_baddb(void)
1.1 kristaps 466: {
467:
1.6 kristaps 468: resp_begin_html(500, "Internal Server Error");
469: puts("<P>Your database is broken.</P>");
470: resp_end_html();
1.1 kristaps 471: }
472:
473: static void
1.6 kristaps 474: resp_search(struct res *r, size_t sz, void *arg)
1.1 kristaps 475: {
1.20 kristaps 476: int i;
1.19 kristaps 477: const struct req *req;
478:
1.29 kristaps 479: req = (const struct req *)arg;
480: assert(req->q.manroot >= 0);
481:
1.6 kristaps 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");
1.29 kristaps 488: printf("Location: http://%s%s/show/%d/%u/%u.html\n",
489: host, progname, req->q.manroot,
1.6 kristaps 490: r[0].volume, r[0].rec);
491: puts("Content-Type: text/html; charset=utf-8\n");
492: return;
493: }
494:
1.15 kristaps 495: qsort(r, sz, sizeof(struct res), cmp);
496:
1.12 kristaps 497: resp_begin_html(200, NULL);
1.19 kristaps 498: resp_searchform(req);
1.6 kristaps 499:
1.14 kristaps 500: if (0 == sz) {
1.21 kristaps 501: printf("<P>\n"
1.24 kristaps 502: "No %s results found.\n",
503: req->q.whatis ? "whatis" : "apropos");
504: if (req->q.whatis) {
1.29 kristaps 505: printf("(Try <A HREF=\"%s/search.html?"
506: "op=apropos&expr=", progname);
1.24 kristaps 507: html_print(req->q.expr ? req->q.expr : "");
1.19 kristaps 508: printf("&sec=");
1.24 kristaps 509: html_print(req->q.sec ? req->q.sec : "");
1.19 kristaps 510: printf("&arch=");
1.24 kristaps 511: html_print(req->q.arch ? req->q.arch : "");
1.19 kristaps 512: puts("\">apropos</A>?)");
513: }
514: puts("</P>");
1.14 kristaps 515: resp_end_html();
516: return;
517: }
518:
519: puts("<P></P>\n"
520: "<TABLE>");
1.1 kristaps 521:
522: for (i = 0; i < (int)sz; i++) {
1.20 kristaps 523: printf("<TR>\n"
524: "<TD CLASS=\"title\">\n"
1.29 kristaps 525: "<A HREF=\"%s/show/%d/%u/%u.html\">",
526: progname, req->q.manroot,
1.26 kristaps 527: r[i].volume, r[i].rec);
1.15 kristaps 528: html_print(r[i].title);
1.1 kristaps 529: putchar('(');
1.6 kristaps 530: html_print(r[i].cat);
531: if (r[i].arch && '\0' != *r[i].arch) {
532: putchar('/');
533: html_print(r[i].arch);
534: }
1.20 kristaps 535: printf(")</A>\n"
536: "</TD>\n"
537: "<TD CLASS=\"desc\">");
1.6 kristaps 538: html_print(r[i].desc);
1.20 kristaps 539: puts("</TD>\n"
540: "</TR>");
1.1 kristaps 541: }
1.16 kristaps 542:
543: puts("</TABLE>");
1.6 kristaps 544: resp_end_html();
545: }
546:
547: /* ARGSUSED */
548: static void
1.24 kristaps 549: pg_index(const struct req *req, char *path)
1.6 kristaps 550: {
551:
552: resp_index(req);
1.1 kristaps 553: }
554:
555: static void
1.32 ! kristaps 556: catman(const struct req *req, const char *file)
1.9 kristaps 557: {
1.10 kristaps 558: FILE *f;
559: size_t len;
560: int i;
561: char *p;
562: int italic, bold;
1.9 kristaps 563:
1.10 kristaps 564: if (NULL == (f = fopen(file, "r"))) {
1.9 kristaps 565: resp_baddb();
566: return;
567: }
568:
1.32 ! kristaps 569: resp_begin_html(200, NULL);
! 570: resp_searchform(req);
! 571: puts("<DIV CLASS=\"catman\">\n"
! 572: "<PRE>");
1.10 kristaps 573:
574: while (NULL != (p = fgetln(f, &len))) {
575: bold = italic = 0;
576: for (i = 0; i < (int)len - 1; i++) {
577: /*
578: * This means that the catpage is out of state.
579: * Ignore it and keep going (although the
580: * catpage is bogus).
581: */
582:
583: if ('\b' == p[i] || '\n' == p[i])
584: continue;
585:
586: /*
587: * Print a regular character.
588: * Close out any bold/italic scopes.
589: * If we're in back-space mode, make sure we'll
590: * have something to enter when we backspace.
591: */
592:
593: if ('\b' != p[i + 1]) {
594: if (italic)
595: printf("</I>");
596: if (bold)
597: printf("</B>");
598: italic = bold = 0;
599: html_putchar(p[i]);
600: continue;
601: } else if (i + 2 >= (int)len)
602: continue;
603:
604: /* Italic mode. */
605:
606: if ('_' == p[i]) {
607: if (bold)
608: printf("</B>");
609: if ( ! italic)
610: printf("<I>");
611: bold = 0;
612: italic = 1;
613: i += 2;
614: html_putchar(p[i]);
615: continue;
616: }
617:
618: /*
619: * Handle funny behaviour troff-isms.
620: * These grok'd from the original man2html.c.
621: */
622:
623: if (('+' == p[i] && 'o' == p[i + 2]) ||
624: ('o' == p[i] && '+' == p[i + 2]) ||
625: ('|' == p[i] && '=' == p[i + 2]) ||
626: ('=' == p[i] && '|' == p[i + 2]) ||
627: ('*' == p[i] && '=' == p[i + 2]) ||
628: ('=' == p[i] && '*' == p[i + 2]) ||
629: ('*' == p[i] && '|' == p[i + 2]) ||
630: ('|' == p[i] && '*' == p[i + 2])) {
631: if (italic)
632: printf("</I>");
633: if (bold)
634: printf("</B>");
635: italic = bold = 0;
636: putchar('*');
637: i += 2;
638: continue;
639: } else if (('|' == p[i] && '-' == p[i + 2]) ||
640: ('-' == p[i] && '|' == p[i + 1]) ||
641: ('+' == p[i] && '-' == p[i + 1]) ||
642: ('-' == p[i] && '+' == p[i + 1]) ||
643: ('+' == p[i] && '|' == p[i + 1]) ||
644: ('|' == p[i] && '+' == p[i + 1])) {
645: if (italic)
646: printf("</I>");
647: if (bold)
648: printf("</B>");
649: italic = bold = 0;
650: putchar('+');
651: i += 2;
652: continue;
653: }
654:
655: /* Bold mode. */
656:
657: if (italic)
658: printf("</I>");
659: if ( ! bold)
660: printf("<B>");
661: bold = 1;
662: italic = 0;
663: i += 2;
664: html_putchar(p[i]);
665: }
666:
667: /*
668: * Clean up the last character.
669: * We can get to a newline; don't print that.
670: */
1.9 kristaps 671:
1.10 kristaps 672: if (italic)
673: printf("</I>");
674: if (bold)
675: printf("</B>");
1.9 kristaps 676:
1.10 kristaps 677: if (i == (int)len - 1 && '\n' != p[i])
678: html_putchar(p[i]);
1.9 kristaps 679:
1.10 kristaps 680: putchar('\n');
681: }
682:
683: puts("</PRE>\n"
1.32 ! kristaps 684: "</DIV>\n"
1.10 kristaps 685: "</BODY>\n"
686: "</HTML>");
687:
688: fclose(f);
1.9 kristaps 689: }
690:
691: static void
1.32 ! kristaps 692: format(const struct req *req, const char *file)
1.7 kristaps 693: {
1.8 kristaps 694: struct mparse *mp;
695: int fd;
696: struct mdoc *mdoc;
697: struct man *man;
698: void *vp;
699: enum mandoclevel rc;
1.10 kristaps 700: char opts[MAXPATHLEN + 128];
1.7 kristaps 701:
1.8 kristaps 702: if (-1 == (fd = open(file, O_RDONLY, 0))) {
703: resp_baddb();
1.7 kristaps 704: return;
705: }
706:
1.8 kristaps 707: mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
708: rc = mparse_readfd(mp, fd, file);
709: close(fd);
1.7 kristaps 710:
1.8 kristaps 711: if (rc >= MANDOCLEVEL_FATAL) {
1.7 kristaps 712: resp_baddb();
713: return;
714: }
715:
1.32 ! kristaps 716: snprintf(opts, sizeof(opts), "fragment,"
1.10 kristaps 717: "man=%s/search.html?sec=%%S&expr=%%N,"
1.11 kristaps 718: /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
1.32 ! kristaps 719: progname);
1.10 kristaps 720:
1.8 kristaps 721: mparse_result(mp, &mdoc, &man);
1.32 ! kristaps 722: if (NULL == man && NULL == mdoc) {
! 723: resp_baddb();
! 724: mparse_free(mp);
! 725: return;
! 726: }
! 727:
! 728: resp_begin_html(200, NULL);
! 729: resp_searchform(req);
! 730:
1.10 kristaps 731: vp = html_alloc(opts);
1.7 kristaps 732:
1.32 ! kristaps 733: if (NULL != mdoc)
1.8 kristaps 734: html_mdoc(vp, mdoc);
1.32 ! kristaps 735: else
1.8 kristaps 736: html_man(vp, man);
1.32 ! kristaps 737:
! 738: puts("</BODY>\n"
! 739: "</HTML>");
1.7 kristaps 740:
1.8 kristaps 741: html_free(vp);
742: mparse_free(mp);
1.7 kristaps 743: }
744:
745: static void
1.24 kristaps 746: pg_show(const struct req *req, char *path)
1.1 kristaps 747: {
1.24 kristaps 748: struct manpaths ps;
1.6 kristaps 749: char *sub;
1.7 kristaps 750: char file[MAXPATHLEN];
1.9 kristaps 751: const char *fn, *cp;
1.6 kristaps 752: int rc;
1.25 kristaps 753: unsigned int vol, rec, mr;
1.9 kristaps 754: DB *idx;
1.6 kristaps 755: DBT key, val;
756:
1.24 kristaps 757: idx = NULL;
758:
1.25 kristaps 759: /* Parse out mroot, volume, and record from the path. */
760:
761: if (NULL == path || NULL == (sub = strchr(path, '/'))) {
762: resp_error400();
763: return;
764: }
765: *sub++ = '\0';
766: if ( ! atou(path, &mr)) {
767: resp_error400();
768: return;
769: }
770: path = sub;
771: if (NULL == (sub = strchr(path, '/'))) {
772: resp_error400();
773: return;
774: }
775: *sub++ = '\0';
776: if ( ! atou(path, &vol) || ! atou(sub, &rec)) {
1.10 kristaps 777: resp_error400();
1.6 kristaps 778: return;
1.25 kristaps 779: } else if (mr >= (unsigned int)req->psz) {
1.10 kristaps 780: resp_error400();
1.6 kristaps 781: return;
1.25 kristaps 782: }
1.6 kristaps 783:
1.24 kristaps 784: /*
1.26 kristaps 785: * Begin by chdir()ing into the manroot.
1.24 kristaps 786: * This way we can pick up the database files, which are
787: * relative to the manpath root.
788: */
789:
1.25 kristaps 790: if (-1 == chdir(req->p[(int)mr].path)) {
791: perror(req->p[(int)mr].path);
792: resp_baddb();
1.24 kristaps 793: return;
794: }
795:
796: memset(&ps, 0, sizeof(struct manpaths));
1.30 schwarze 797: manpath_manconf(&ps, "etc/catman.conf");
1.24 kristaps 798:
1.25 kristaps 799: if (vol >= (unsigned int)ps.sz) {
1.10 kristaps 800: resp_error400();
1.24 kristaps 801: goto out;
1.6 kristaps 802: }
803:
1.24 kristaps 804: strlcpy(file, ps.paths[vol], MAXPATHLEN);
1.6 kristaps 805: strlcat(file, "/mandoc.index", MAXPATHLEN);
806:
807: /* Open the index recno(3) database. */
808:
1.9 kristaps 809: idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
810: if (NULL == idx) {
1.24 kristaps 811: perror(file);
1.6 kristaps 812: resp_baddb();
1.24 kristaps 813: goto out;
1.6 kristaps 814: }
815:
816: key.data = &rec;
817: key.size = 4;
818:
1.9 kristaps 819: if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
1.10 kristaps 820: rc < 0 ? resp_baddb() : resp_error400();
1.9 kristaps 821: goto out;
1.6 kristaps 822: }
823:
1.9 kristaps 824: cp = (char *)val.data;
1.6 kristaps 825:
1.9 kristaps 826: if (NULL == (fn = memchr(cp, '\0', val.size)))
827: resp_baddb();
828: else if (++fn - cp >= (int)val.size)
829: resp_baddb();
830: else if (NULL == memchr(fn, '\0', val.size - (fn - cp)))
831: resp_baddb();
832: else {
833: if (0 == strcmp(cp, "cat"))
1.32 ! kristaps 834: catman(req, fn + 1);
1.9 kristaps 835: else
1.32 ! kristaps 836: format(req, fn + 1);
1.9 kristaps 837: }
838: out:
1.24 kristaps 839: if (idx)
840: (*idx->close)(idx);
841: manpath_free(&ps);
1.6 kristaps 842: }
843:
844: static void
1.24 kristaps 845: pg_search(const struct req *req, char *path)
1.6 kristaps 846: {
847: size_t tt;
1.24 kristaps 848: struct manpaths ps;
1.20 kristaps 849: int i, sz, rc;
1.6 kristaps 850: const char *ep, *start;
851: char **cp;
852: struct opts opt;
853: struct expr *expr;
854:
1.28 kristaps 855: if (req->q.manroot < 0 || 0 == req->psz) {
1.24 kristaps 856: resp_search(NULL, 0, (void *)req);
857: return;
858: }
859:
1.1 kristaps 860: memset(&opt, 0, sizeof(struct opts));
1.6 kristaps 861:
1.24 kristaps 862: ep = req->q.expr;
863: opt.arch = req->q.arch;
864: opt.cat = req->q.sec;
1.20 kristaps 865: rc = -1;
866: sz = 0;
867: cp = NULL;
1.6 kristaps 868:
869: /*
1.24 kristaps 870: * Begin by chdir()ing into the root of the manpath.
871: * This way we can pick up the database files, which are
872: * relative to the manpath root.
873: */
874:
1.26 kristaps 875: assert(req->q.manroot < (int)req->psz);
876: if (-1 == (chdir(req->p[req->q.manroot].path))) {
877: perror(req->p[req->q.manroot].path);
1.24 kristaps 878: resp_search(NULL, 0, (void *)req);
879: return;
880: }
881:
882: memset(&ps, 0, sizeof(struct manpaths));
1.30 schwarze 883: manpath_manconf(&ps, "etc/catman.conf");
1.24 kristaps 884:
885: /*
886: * Poor man's tokenisation: just break apart by spaces.
1.6 kristaps 887: * Yes, this is half-ass. But it works for now.
888: */
889:
890: while (ep && isspace((unsigned char)*ep))
891: ep++;
892:
893: while (ep && '\0' != *ep) {
894: cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
895: start = ep;
896: while ('\0' != *ep && ! isspace((unsigned char)*ep))
897: ep++;
898: cp[sz] = mandoc_malloc((ep - start) + 1);
899: memcpy(cp[sz], start, ep - start);
900: cp[sz++][ep - start] = '\0';
901: while (isspace((unsigned char)*ep))
902: ep++;
903: }
904:
905: /*
906: * Pump down into apropos backend.
907: * The resp_search() function is called with the results.
908: */
909:
1.24 kristaps 910: expr = req->q.whatis ?
911: termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
1.12 kristaps 912:
913: if (NULL != expr)
1.6 kristaps 914: rc = apropos_search
1.24 kristaps 915: (ps.sz, ps.paths, &opt,
1.6 kristaps 916: expr, tt, (void *)req, resp_search);
917:
918: /* ...unless errors occured. */
919:
920: if (0 == rc)
921: resp_baddb();
922: else if (-1 == rc)
1.10 kristaps 923: resp_search(NULL, 0, (void *)req);
1.6 kristaps 924:
925: for (i = 0; i < sz; i++)
926: free(cp[i]);
927:
928: free(cp);
929: exprfree(expr);
1.24 kristaps 930: manpath_free(&ps);
1.1 kristaps 931: }
932:
933: int
934: main(void)
935: {
936: int i;
1.24 kristaps 937: char buf[MAXPATHLEN];
938: DIR *cwd;
1.1 kristaps 939: struct req req;
1.6 kristaps 940: char *p, *path, *subpath;
941:
1.24 kristaps 942: /* Scan our run-time environment. */
1.6 kristaps 943:
1.29 kristaps 944: if (NULL == (cache = getenv("CACHE_DIR")))
945: cache = "/cache/man.cgi";
946:
947: if (NULL == (progname = getenv("SCRIPT_NAME")))
1.6 kristaps 948: progname = "";
949:
1.29 kristaps 950: if (NULL == (css = getenv("CSS_DIR")))
1.31 kristaps 951: css = "";
1.7 kristaps 952:
1.29 kristaps 953: if (NULL == (host = getenv("HTTP_HOST")))
1.24 kristaps 954: host = "localhost";
955:
956: /*
957: * First we change directory into the cache directory so that
958: * subsequent scanning for manpath directories is rooted
959: * relative to the same position.
960: */
961:
1.8 kristaps 962: if (-1 == chdir(cache)) {
1.24 kristaps 963: perror(cache);
964: resp_bad();
965: return(EXIT_FAILURE);
966: } else if (NULL == (cwd = opendir(cache))) {
967: perror(cache);
1.8 kristaps 968: resp_bad();
969: return(EXIT_FAILURE);
1.24 kristaps 970: }
971:
972: memset(&req, 0, sizeof(struct req));
1.7 kristaps 973:
1.24 kristaps 974: strlcpy(buf, ".", MAXPATHLEN);
975: pathgen(cwd, buf, &req);
976: closedir(cwd);
1.1 kristaps 977:
1.24 kristaps 978: /* Next parse out the query string. */
1.1 kristaps 979:
980: if (NULL != (p = getenv("QUERY_STRING")))
1.26 kristaps 981: http_parse(&req, p);
1.1 kristaps 982:
1.24 kristaps 983: /*
984: * Now juggle paths to extract information.
985: * We want to extract our filetype (the file suffix), the
986: * initial path component, then the trailing component(s).
987: * Start with leading subpath component.
988: */
1.1 kristaps 989:
1.6 kristaps 990: subpath = path = NULL;
1.1 kristaps 991: req.page = PAGE__MAX;
992:
993: if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
994: req.page = PAGE_INDEX;
1.6 kristaps 995:
1.1 kristaps 996: if (NULL != path && '/' == *path && '\0' == *++path)
997: req.page = PAGE_INDEX;
998:
1.6 kristaps 999: /* Strip file suffix. */
1000:
1001: if (NULL != path && NULL != (p = strrchr(path, '.')))
1002: if (NULL != p && NULL == strchr(p, '/'))
1003: *p++ = '\0';
1004:
1005: /* Resolve subpath component. */
1.1 kristaps 1006:
1007: if (NULL != path && NULL != (subpath = strchr(path, '/')))
1.6 kristaps 1008: *subpath++ = '\0';
1.1 kristaps 1009:
1.6 kristaps 1010: /* Map path into one we recognise. */
1.1 kristaps 1011:
1012: if (NULL != path && '\0' != *path)
1013: for (i = 0; i < (int)PAGE__MAX; i++)
1014: if (0 == strcmp(pages[i], path)) {
1015: req.page = (enum page)i;
1016: break;
1017: }
1018:
1.6 kristaps 1019: /* Route pages. */
1020:
1.1 kristaps 1021: switch (req.page) {
1022: case (PAGE_INDEX):
1.24 kristaps 1023: pg_index(&req, subpath);
1.1 kristaps 1024: break;
1025: case (PAGE_SEARCH):
1.24 kristaps 1026: pg_search(&req, subpath);
1.6 kristaps 1027: break;
1028: case (PAGE_SHOW):
1.24 kristaps 1029: pg_show(&req, subpath);
1.1 kristaps 1030: break;
1031: default:
1.10 kristaps 1032: resp_error404(path);
1.1 kristaps 1033: break;
1034: }
1035:
1.24 kristaps 1036: for (i = 0; i < (int)req.psz; i++) {
1037: free(req.p[i].path);
1038: free(req.p[i].name);
1039: }
1.6 kristaps 1040:
1.24 kristaps 1041: free(req.p);
1.1 kristaps 1042: return(EXIT_SUCCESS);
1043: }
1.15 kristaps 1044:
1045: static int
1046: cmp(const void *p1, const void *p2)
1047: {
1048:
1049: return(strcasecmp(((const struct res *)p1)->title,
1050: ((const struct res *)p2)->title));
1051: }
1052:
1.24 kristaps 1053: /*
1054: * Check to see if an "etc" path consists of a catman.conf file. If it
1055: * does, that means that the path contains a tree created by catman(8)
1056: * and should be used for indexing.
1057: */
1058: static int
1059: pathstop(DIR *dir)
1060: {
1061: struct dirent *d;
1062:
1063: while (NULL != (d = readdir(dir)))
1064: if (DT_REG == d->d_type)
1065: if (0 == strcmp(d->d_name, "catman.conf"))
1066: return(1);
1067:
1068: return(0);
1069: }
1070:
1071: /*
1072: * Scan for indexable paths.
1073: * This adds all paths with "etc/catman.conf" to the buffer.
1074: */
1075: static void
1076: pathgen(DIR *dir, char *path, struct req *req)
1077: {
1078: struct dirent *d;
1079: char *cp;
1080: DIR *cd;
1081: int rc;
1082: size_t sz, ssz;
1083:
1084: sz = strlcat(path, "/", MAXPATHLEN);
1085: if (sz >= MAXPATHLEN) {
1086: fprintf(stderr, "%s: Path too long", path);
1087: return;
1088: }
1089:
1090: /*
1091: * First, scan for the "etc" directory.
1092: * If it's found, then see if it should cause us to stop. This
1093: * happens when a catman.conf is found in the directory.
1094: */
1095:
1096: rc = 0;
1097: while (0 == rc && NULL != (d = readdir(dir))) {
1098: if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
1099: continue;
1100:
1101: path[(int)sz] = '\0';
1102: ssz = strlcat(path, d->d_name, MAXPATHLEN);
1103:
1104: if (ssz >= MAXPATHLEN) {
1105: fprintf(stderr, "%s: Path too long", path);
1106: return;
1107: } else if (NULL == (cd = opendir(path))) {
1108: perror(path);
1109: return;
1110: }
1111:
1112: rc = pathstop(cd);
1113: closedir(cd);
1114: }
1115:
1116: if (rc > 0) {
1117: /* This also strips the trailing slash. */
1.27 kristaps 1118: path[(int)--sz] = '\0';
1.24 kristaps 1119: req->p = mandoc_realloc
1120: (req->p,
1121: (req->psz + 1) * sizeof(struct paths));
1.27 kristaps 1122: /*
1123: * Strip out the leading "./" unless we're just a ".",
1124: * in which case use an empty string as our name.
1125: */
1.24 kristaps 1126: req->p[(int)req->psz].path = mandoc_strdup(path);
1127: req->p[(int)req->psz].name =
1.27 kristaps 1128: cp = mandoc_strdup(path + (1 == sz ? 1 : 2));
1.24 kristaps 1129: req->psz++;
1130: /*
1131: * The name is just the path with all the slashes taken
1132: * out of it. Simple but effective.
1133: */
1134: for ( ; '\0' != *cp; cp++)
1135: if ('/' == *cp)
1136: *cp = ' ';
1137: return;
1138: }
1139:
1140: /*
1141: * If no etc/catman.conf was found, recursively enter child
1142: * directory and continue scanning.
1143: */
1144:
1145: rewinddir(dir);
1146: while (NULL != (d = readdir(dir))) {
1147: if (DT_DIR != d->d_type || '.' == d->d_name[0])
1148: continue;
1149:
1150: path[(int)sz] = '\0';
1151: ssz = strlcat(path, d->d_name, MAXPATHLEN);
1152:
1153: if (ssz >= MAXPATHLEN) {
1154: fprintf(stderr, "%s: Path too long", path);
1155: return;
1156: } else if (NULL == (cd = opendir(path))) {
1157: perror(path);
1158: return;
1159: }
1160:
1161: pathgen(cd, path, req);
1162: closedir(cd);
1163: }
1164: }
CVSweb