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