Annotation of mandoc/cgi.c, Revision 1.26
1.26 ! kristaps 1: /* $Id: cgi.c,v 1.25 2011/12/10 22:20: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.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.13 kristaps 367:
1.6 kristaps 368: puts("<!-- Begin search form. //-->");
369: printf("<FORM ACTION=\"");
370: html_print(progname);
1.9 kristaps 371: printf("/search.html\" METHOD=\"get\">\n");
1.14 kristaps 372: printf("<FIELDSET>\n"
1.16 kristaps 373: "<LEGEND>Search Parameters</LEGEND>\n"
1.20 kristaps 374: "<INPUT TYPE=\"submit\" NAME=\"op\""
375: " VALUE=\"Whatis\"> or \n"
376: "<INPUT TYPE=\"submit\" NAME=\"op\""
377: " VALUE=\"apropos\"> for manuals satisfying \n"
1.14 kristaps 378: "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"");
1.24 kristaps 379: html_print(req->q.expr ? req->q.expr : "");
1.14 kristaps 380: printf("\">, section "
1.20 kristaps 381: "<INPUT TYPE=\"text\""
382: " SIZE=\"4\" NAME=\"sec\" VALUE=\"");
1.24 kristaps 383: html_print(req->q.sec ? req->q.sec : "");
1.14 kristaps 384: printf("\">, arch "
1.20 kristaps 385: "<INPUT TYPE=\"text\""
386: " SIZE=\"8\" NAME=\"arch\" VALUE=\"");
1.24 kristaps 387: html_print(req->q.arch ? req->q.arch : "");
1.12 kristaps 388: puts("\">.\n"
389: "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
390: "</FIELDSET>\n"
1.20 kristaps 391: "</FORM>");
392: puts("<!-- End search form. //-->");
1.6 kristaps 393: }
394:
395: static void
396: resp_index(const struct req *req)
397: {
398:
399: resp_begin_html(200, NULL);
400: resp_searchform(req);
401: resp_end_html();
402: }
403:
404: static void
1.10 kristaps 405: resp_error400(void)
1.9 kristaps 406: {
407:
1.10 kristaps 408: resp_begin_html(400, "Query Malformed");
1.12 kristaps 409: printf("<H1>Malformed Query</H1>\n"
410: "<P>\n"
1.20 kristaps 411: "The query your entered was malformed.\n"
412: "Try again from the\n"
1.22 kristaps 413: "<A HREF=\"%s/index.html\">main page</A>.\n"
1.12 kristaps 414: "</P>", progname);
1.9 kristaps 415: resp_end_html();
416: }
417:
418: static void
1.10 kristaps 419: resp_error404(const char *page)
1.6 kristaps 420: {
421:
422: resp_begin_html(404, "Not Found");
1.10 kristaps 423: puts("<H1>Page Not Found</H1>\n"
424: "<P>\n"
1.20 kristaps 425: "The page you're looking for, ");
426: printf("<B>");
1.10 kristaps 427: html_print(page);
1.12 kristaps 428: printf("</B>,\n"
1.20 kristaps 429: "could not be found.\n"
430: "Try searching from the\n"
1.22 kristaps 431: "<A HREF=\"%s/index.html\">main page</A>.\n"
1.12 kristaps 432: "</P>", progname);
1.6 kristaps 433: resp_end_html();
434: }
1.1 kristaps 435:
436: static void
1.7 kristaps 437: resp_bad(void)
438: {
439: resp_begin_html(500, "Internal Server Error");
440: puts("<P>Generic badness happened.</P>");
441: resp_end_html();
442: }
443:
444: static void
1.6 kristaps 445: resp_baddb(void)
1.1 kristaps 446: {
447:
1.6 kristaps 448: resp_begin_html(500, "Internal Server Error");
449: puts("<P>Your database is broken.</P>");
450: resp_end_html();
1.1 kristaps 451: }
452:
453: static void
1.6 kristaps 454: resp_search(struct res *r, size_t sz, void *arg)
1.1 kristaps 455: {
1.20 kristaps 456: int i;
1.19 kristaps 457: const struct req *req;
458:
1.6 kristaps 459: if (1 == sz) {
460: /*
461: * If we have just one result, then jump there now
462: * without any delay.
463: */
464: puts("Status: 303 See Other");
465: printf("Location: http://%s%s/show/%u/%u.html\n",
466: host, progname,
467: r[0].volume, r[0].rec);
468: puts("Content-Type: text/html; charset=utf-8\n");
469: return;
470: }
471:
1.15 kristaps 472: qsort(r, sz, sizeof(struct res), cmp);
473:
1.12 kristaps 474: resp_begin_html(200, NULL);
1.20 kristaps 475:
476: req = (const struct req *)arg;
1.19 kristaps 477: resp_searchform(req);
1.6 kristaps 478:
1.14 kristaps 479: if (0 == sz) {
1.21 kristaps 480: printf("<P>\n"
1.24 kristaps 481: "No %s results found.\n",
482: req->q.whatis ? "whatis" : "apropos");
483: if (req->q.whatis) {
1.19 kristaps 484: printf("(Try <A HREF=\"");
485: html_print(progname);
486: printf("/search.html?op=apropos&expr=");
1.24 kristaps 487: html_print(req->q.expr ? req->q.expr : "");
1.19 kristaps 488: printf("&sec=");
1.24 kristaps 489: html_print(req->q.sec ? req->q.sec : "");
1.19 kristaps 490: printf("&arch=");
1.24 kristaps 491: html_print(req->q.arch ? req->q.arch : "");
1.19 kristaps 492: puts("\">apropos</A>?)");
493: }
494: puts("</P>");
1.14 kristaps 495: resp_end_html();
496: return;
497: }
498:
499: puts("<P></P>\n"
500: "<TABLE>");
1.1 kristaps 501:
1.26 ! kristaps 502: assert(req->q.manroot >= 0);
1.1 kristaps 503: for (i = 0; i < (int)sz; i++) {
1.20 kristaps 504: printf("<TR>\n"
505: "<TD CLASS=\"title\">\n"
506: "<A HREF=\"");
1.6 kristaps 507: html_print(progname);
1.26 ! kristaps 508: printf("/show/%d/%u/%u.html\">",
! 509: req->q.manroot,
! 510: r[i].volume, r[i].rec);
1.15 kristaps 511: html_print(r[i].title);
1.1 kristaps 512: putchar('(');
1.6 kristaps 513: html_print(r[i].cat);
514: if (r[i].arch && '\0' != *r[i].arch) {
515: putchar('/');
516: html_print(r[i].arch);
517: }
1.20 kristaps 518: printf(")</A>\n"
519: "</TD>\n"
520: "<TD CLASS=\"desc\">");
1.6 kristaps 521: html_print(r[i].desc);
1.20 kristaps 522: puts("</TD>\n"
523: "</TR>");
1.1 kristaps 524: }
1.16 kristaps 525:
526: puts("</TABLE>");
1.6 kristaps 527: resp_end_html();
528: }
529:
530: /* ARGSUSED */
531: static void
1.24 kristaps 532: pg_index(const struct req *req, char *path)
1.6 kristaps 533: {
534:
535: resp_index(req);
1.1 kristaps 536: }
537:
538: static void
1.9 kristaps 539: catman(const char *file)
540: {
1.10 kristaps 541: FILE *f;
542: size_t len;
543: int i;
544: char *p;
545: int italic, bold;
1.9 kristaps 546:
1.10 kristaps 547: if (NULL == (f = fopen(file, "r"))) {
1.9 kristaps 548: resp_baddb();
549: return;
550: }
551:
1.12 kristaps 552: resp_begin_http(200, NULL);
1.20 kristaps 553: puts("<!DOCTYPE HTML PUBLIC "
554: " \"-//W3C//DTD HTML 4.01//EN\""
555: " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
556: "<HTML>\n"
557: "<HEAD>\n"
558: "<META HTTP-EQUIV=\"Content-Type\""
559: " CONTENT=\"text/html; charset=utf-8\">\n"
560: "<LINK REL=\"stylesheet\" HREF=\"/catman.css\""
561: " TYPE=\"text/css\" media=\"all\">\n"
562: "<TITLE>System Manpage Reference</TITLE>\n"
563: "</HEAD>\n"
564: "<BODY>\n"
565: "<!-- Begin page content. //-->\n"
566: "<PRE>");
1.10 kristaps 567:
568: while (NULL != (p = fgetln(f, &len))) {
569: bold = italic = 0;
570: for (i = 0; i < (int)len - 1; i++) {
571: /*
572: * This means that the catpage is out of state.
573: * Ignore it and keep going (although the
574: * catpage is bogus).
575: */
576:
577: if ('\b' == p[i] || '\n' == p[i])
578: continue;
579:
580: /*
581: * Print a regular character.
582: * Close out any bold/italic scopes.
583: * If we're in back-space mode, make sure we'll
584: * have something to enter when we backspace.
585: */
586:
587: if ('\b' != p[i + 1]) {
588: if (italic)
589: printf("</I>");
590: if (bold)
591: printf("</B>");
592: italic = bold = 0;
593: html_putchar(p[i]);
594: continue;
595: } else if (i + 2 >= (int)len)
596: continue;
597:
598: /* Italic mode. */
599:
600: if ('_' == p[i]) {
601: if (bold)
602: printf("</B>");
603: if ( ! italic)
604: printf("<I>");
605: bold = 0;
606: italic = 1;
607: i += 2;
608: html_putchar(p[i]);
609: continue;
610: }
611:
612: /*
613: * Handle funny behaviour troff-isms.
614: * These grok'd from the original man2html.c.
615: */
616:
617: if (('+' == p[i] && 'o' == p[i + 2]) ||
618: ('o' == p[i] && '+' == p[i + 2]) ||
619: ('|' == p[i] && '=' == p[i + 2]) ||
620: ('=' == p[i] && '|' == p[i + 2]) ||
621: ('*' == p[i] && '=' == p[i + 2]) ||
622: ('=' == p[i] && '*' == p[i + 2]) ||
623: ('*' == p[i] && '|' == p[i + 2]) ||
624: ('|' == p[i] && '*' == p[i + 2])) {
625: if (italic)
626: printf("</I>");
627: if (bold)
628: printf("</B>");
629: italic = bold = 0;
630: putchar('*');
631: i += 2;
632: continue;
633: } else if (('|' == p[i] && '-' == p[i + 2]) ||
634: ('-' == p[i] && '|' == p[i + 1]) ||
635: ('+' == p[i] && '-' == p[i + 1]) ||
636: ('-' == p[i] && '+' == p[i + 1]) ||
637: ('+' == p[i] && '|' == p[i + 1]) ||
638: ('|' == p[i] && '+' == p[i + 1])) {
639: if (italic)
640: printf("</I>");
641: if (bold)
642: printf("</B>");
643: italic = bold = 0;
644: putchar('+');
645: i += 2;
646: continue;
647: }
648:
649: /* Bold mode. */
650:
651: if (italic)
652: printf("</I>");
653: if ( ! bold)
654: printf("<B>");
655: bold = 1;
656: italic = 0;
657: i += 2;
658: html_putchar(p[i]);
659: }
660:
661: /*
662: * Clean up the last character.
663: * We can get to a newline; don't print that.
664: */
1.9 kristaps 665:
1.10 kristaps 666: if (italic)
667: printf("</I>");
668: if (bold)
669: printf("</B>");
1.9 kristaps 670:
1.10 kristaps 671: if (i == (int)len - 1 && '\n' != p[i])
672: html_putchar(p[i]);
1.9 kristaps 673:
1.10 kristaps 674: putchar('\n');
675: }
676:
677: puts("</PRE>\n"
678: "</BODY>\n"
679: "</HTML>");
680:
681: fclose(f);
1.9 kristaps 682: }
683:
684: static void
1.8 kristaps 685: format(const char *file)
1.7 kristaps 686: {
1.8 kristaps 687: struct mparse *mp;
688: int fd;
689: struct mdoc *mdoc;
690: struct man *man;
691: void *vp;
692: enum mandoclevel rc;
1.10 kristaps 693: char opts[MAXPATHLEN + 128];
1.7 kristaps 694:
1.8 kristaps 695: if (-1 == (fd = open(file, O_RDONLY, 0))) {
696: resp_baddb();
1.7 kristaps 697: return;
698: }
699:
1.8 kristaps 700: mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
701: rc = mparse_readfd(mp, fd, file);
702: close(fd);
1.7 kristaps 703:
1.8 kristaps 704: if (rc >= MANDOCLEVEL_FATAL) {
1.7 kristaps 705: resp_baddb();
706: return;
707: }
708:
1.11 kristaps 709: snprintf(opts, sizeof(opts), "style=/man.css,"
1.10 kristaps 710: "man=%s/search.html?sec=%%S&expr=%%N,"
1.11 kristaps 711: /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
1.10 kristaps 712: progname);
713:
1.8 kristaps 714: mparse_result(mp, &mdoc, &man);
1.10 kristaps 715: vp = html_alloc(opts);
1.7 kristaps 716:
1.8 kristaps 717: if (NULL != mdoc) {
718: resp_begin_http(200, NULL);
719: html_mdoc(vp, mdoc);
720: } else if (NULL != man) {
721: resp_begin_http(200, NULL);
722: html_man(vp, man);
723: } else
724: resp_baddb();
1.7 kristaps 725:
1.8 kristaps 726: html_free(vp);
727: mparse_free(mp);
1.7 kristaps 728: }
729:
730: static void
1.24 kristaps 731: pg_show(const struct req *req, char *path)
1.1 kristaps 732: {
1.24 kristaps 733: struct manpaths ps;
1.6 kristaps 734: char *sub;
1.7 kristaps 735: char file[MAXPATHLEN];
1.9 kristaps 736: const char *fn, *cp;
1.6 kristaps 737: int rc;
1.25 kristaps 738: unsigned int vol, rec, mr;
1.9 kristaps 739: DB *idx;
1.6 kristaps 740: DBT key, val;
741:
1.24 kristaps 742: idx = NULL;
743:
1.25 kristaps 744: /* Parse out mroot, volume, and record from the path. */
745:
746: if (NULL == path || NULL == (sub = strchr(path, '/'))) {
747: resp_error400();
748: return;
749: }
750: *sub++ = '\0';
751: if ( ! atou(path, &mr)) {
752: resp_error400();
753: return;
754: }
755: path = sub;
756: if (NULL == (sub = strchr(path, '/'))) {
757: resp_error400();
758: return;
759: }
760: *sub++ = '\0';
761: if ( ! atou(path, &vol) || ! atou(sub, &rec)) {
1.10 kristaps 762: resp_error400();
1.6 kristaps 763: return;
1.25 kristaps 764: } else if (mr >= (unsigned int)req->psz) {
1.10 kristaps 765: resp_error400();
1.6 kristaps 766: return;
1.25 kristaps 767: }
1.6 kristaps 768:
1.24 kristaps 769: /*
1.26 ! kristaps 770: * Begin by chdir()ing into the manroot.
1.24 kristaps 771: * This way we can pick up the database files, which are
772: * relative to the manpath root.
773: */
774:
1.25 kristaps 775: if (-1 == chdir(req->p[(int)mr].path)) {
776: perror(req->p[(int)mr].path);
777: resp_baddb();
1.24 kristaps 778: return;
779: }
780:
781: memset(&ps, 0, sizeof(struct manpaths));
782: manpath_manconf("etc/catman.conf", &ps);
783:
1.25 kristaps 784: if (vol >= (unsigned int)ps.sz) {
1.10 kristaps 785: resp_error400();
1.24 kristaps 786: goto out;
1.6 kristaps 787: }
788:
1.24 kristaps 789: strlcpy(file, ps.paths[vol], MAXPATHLEN);
1.6 kristaps 790: strlcat(file, "/mandoc.index", MAXPATHLEN);
791:
792: /* Open the index recno(3) database. */
793:
1.9 kristaps 794: idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
795: if (NULL == idx) {
1.24 kristaps 796: perror(file);
1.6 kristaps 797: resp_baddb();
1.24 kristaps 798: goto out;
1.6 kristaps 799: }
800:
801: key.data = &rec;
802: key.size = 4;
803:
1.9 kristaps 804: if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
1.10 kristaps 805: rc < 0 ? resp_baddb() : resp_error400();
1.9 kristaps 806: goto out;
1.6 kristaps 807: }
808:
1.9 kristaps 809: cp = (char *)val.data;
1.6 kristaps 810:
1.9 kristaps 811: if (NULL == (fn = memchr(cp, '\0', val.size)))
812: resp_baddb();
813: else if (++fn - cp >= (int)val.size)
814: resp_baddb();
815: else if (NULL == memchr(fn, '\0', val.size - (fn - cp)))
816: resp_baddb();
817: else {
818: if (0 == strcmp(cp, "cat"))
1.24 kristaps 819: catman(fn + 1);
1.9 kristaps 820: else
1.24 kristaps 821: format(fn + 1);
1.9 kristaps 822: }
823: out:
1.24 kristaps 824: if (idx)
825: (*idx->close)(idx);
826: manpath_free(&ps);
1.6 kristaps 827: }
828:
829: static void
1.24 kristaps 830: pg_search(const struct req *req, char *path)
1.6 kristaps 831: {
832: size_t tt;
1.24 kristaps 833: struct manpaths ps;
1.20 kristaps 834: int i, sz, rc;
1.6 kristaps 835: const char *ep, *start;
836: char **cp;
837: struct opts opt;
838: struct expr *expr;
839:
1.26 ! kristaps 840: if (req->q.manroot < 0) {
1.24 kristaps 841: resp_search(NULL, 0, (void *)req);
842: return;
843: }
844:
1.1 kristaps 845: memset(&opt, 0, sizeof(struct opts));
1.6 kristaps 846:
1.24 kristaps 847: ep = req->q.expr;
848: opt.arch = req->q.arch;
849: opt.cat = req->q.sec;
1.20 kristaps 850: rc = -1;
851: sz = 0;
852: cp = NULL;
1.6 kristaps 853:
854: /*
1.24 kristaps 855: * Begin by chdir()ing into the root of the manpath.
856: * This way we can pick up the database files, which are
857: * relative to the manpath root.
858: */
859:
1.26 ! kristaps 860: assert(req->q.manroot < (int)req->psz);
! 861: if (-1 == (chdir(req->p[req->q.manroot].path))) {
! 862: perror(req->p[req->q.manroot].path);
1.24 kristaps 863: resp_search(NULL, 0, (void *)req);
864: return;
865: }
866:
867: memset(&ps, 0, sizeof(struct manpaths));
868: manpath_manconf("etc/catman.conf", &ps);
869:
870: /*
871: * Poor man's tokenisation: just break apart by spaces.
1.6 kristaps 872: * Yes, this is half-ass. But it works for now.
873: */
874:
875: while (ep && isspace((unsigned char)*ep))
876: ep++;
877:
878: while (ep && '\0' != *ep) {
879: cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
880: start = ep;
881: while ('\0' != *ep && ! isspace((unsigned char)*ep))
882: ep++;
883: cp[sz] = mandoc_malloc((ep - start) + 1);
884: memcpy(cp[sz], start, ep - start);
885: cp[sz++][ep - start] = '\0';
886: while (isspace((unsigned char)*ep))
887: ep++;
888: }
889:
890: /*
891: * Pump down into apropos backend.
892: * The resp_search() function is called with the results.
893: */
894:
1.24 kristaps 895: expr = req->q.whatis ?
896: termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
1.12 kristaps 897:
898: if (NULL != expr)
1.6 kristaps 899: rc = apropos_search
1.24 kristaps 900: (ps.sz, ps.paths, &opt,
1.6 kristaps 901: expr, tt, (void *)req, resp_search);
902:
903: /* ...unless errors occured. */
904:
905: if (0 == rc)
906: resp_baddb();
907: else if (-1 == rc)
1.10 kristaps 908: resp_search(NULL, 0, (void *)req);
1.6 kristaps 909:
910: for (i = 0; i < sz; i++)
911: free(cp[i]);
912:
913: free(cp);
914: exprfree(expr);
1.24 kristaps 915: manpath_free(&ps);
1.1 kristaps 916: }
917:
918: int
919: main(void)
920: {
921: int i;
1.24 kristaps 922: char buf[MAXPATHLEN];
923: DIR *cwd;
1.1 kristaps 924: struct req req;
1.6 kristaps 925: char *p, *path, *subpath;
926:
1.24 kristaps 927: /* Scan our run-time environment. */
1.6 kristaps 928:
929: progname = getenv("SCRIPT_NAME");
930: if (NULL == progname)
931: progname = "";
932:
1.7 kristaps 933: cache = getenv("CACHE_DIR");
934: if (NULL == cache)
935: cache = "/cache/man.cgi";
936:
1.24 kristaps 937: host = getenv("HTTP_HOST");
938: if (NULL == host)
939: host = "localhost";
940:
941: /*
942: * First we change directory into the cache directory so that
943: * subsequent scanning for manpath directories is rooted
944: * relative to the same position.
945: */
946:
1.8 kristaps 947: if (-1 == chdir(cache)) {
1.24 kristaps 948: perror(cache);
949: resp_bad();
950: return(EXIT_FAILURE);
951: } else if (NULL == (cwd = opendir(cache))) {
952: perror(cache);
1.8 kristaps 953: resp_bad();
954: return(EXIT_FAILURE);
1.24 kristaps 955: }
956:
957: memset(&req, 0, sizeof(struct req));
1.7 kristaps 958:
1.24 kristaps 959: strlcpy(buf, ".", MAXPATHLEN);
960: pathgen(cwd, buf, &req);
961: closedir(cwd);
1.1 kristaps 962:
1.24 kristaps 963: /* Next parse out the query string. */
1.1 kristaps 964:
965: if (NULL != (p = getenv("QUERY_STRING")))
1.26 ! kristaps 966: http_parse(&req, p);
1.1 kristaps 967:
1.24 kristaps 968: /*
969: * Now juggle paths to extract information.
970: * We want to extract our filetype (the file suffix), the
971: * initial path component, then the trailing component(s).
972: * Start with leading subpath component.
973: */
1.1 kristaps 974:
1.6 kristaps 975: subpath = path = NULL;
1.1 kristaps 976: req.page = PAGE__MAX;
977:
978: if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
979: req.page = PAGE_INDEX;
1.6 kristaps 980:
1.1 kristaps 981: if (NULL != path && '/' == *path && '\0' == *++path)
982: req.page = PAGE_INDEX;
983:
1.6 kristaps 984: /* Strip file suffix. */
985:
986: if (NULL != path && NULL != (p = strrchr(path, '.')))
987: if (NULL != p && NULL == strchr(p, '/'))
988: *p++ = '\0';
989:
990: /* Resolve subpath component. */
1.1 kristaps 991:
992: if (NULL != path && NULL != (subpath = strchr(path, '/')))
1.6 kristaps 993: *subpath++ = '\0';
1.1 kristaps 994:
1.6 kristaps 995: /* Map path into one we recognise. */
1.1 kristaps 996:
997: if (NULL != path && '\0' != *path)
998: for (i = 0; i < (int)PAGE__MAX; i++)
999: if (0 == strcmp(pages[i], path)) {
1000: req.page = (enum page)i;
1001: break;
1002: }
1003:
1.6 kristaps 1004: /* Route pages. */
1005:
1.1 kristaps 1006: switch (req.page) {
1007: case (PAGE_INDEX):
1.24 kristaps 1008: pg_index(&req, subpath);
1.1 kristaps 1009: break;
1010: case (PAGE_SEARCH):
1.24 kristaps 1011: pg_search(&req, subpath);
1.6 kristaps 1012: break;
1013: case (PAGE_SHOW):
1.24 kristaps 1014: pg_show(&req, subpath);
1.1 kristaps 1015: break;
1016: default:
1.10 kristaps 1017: resp_error404(path);
1.1 kristaps 1018: break;
1019: }
1020:
1.24 kristaps 1021: for (i = 0; i < (int)req.psz; i++) {
1022: free(req.p[i].path);
1023: free(req.p[i].name);
1024: }
1.6 kristaps 1025:
1.24 kristaps 1026: free(req.p);
1.1 kristaps 1027: return(EXIT_SUCCESS);
1028: }
1.15 kristaps 1029:
1030: static int
1031: cmp(const void *p1, const void *p2)
1032: {
1033:
1034: return(strcasecmp(((const struct res *)p1)->title,
1035: ((const struct res *)p2)->title));
1036: }
1037:
1.24 kristaps 1038: /*
1039: * Check to see if an "etc" path consists of a catman.conf file. If it
1040: * does, that means that the path contains a tree created by catman(8)
1041: * and should be used for indexing.
1042: */
1043: static int
1044: pathstop(DIR *dir)
1045: {
1046: struct dirent *d;
1047:
1048: while (NULL != (d = readdir(dir)))
1049: if (DT_REG == d->d_type)
1050: if (0 == strcmp(d->d_name, "catman.conf"))
1051: return(1);
1052:
1053: return(0);
1054: }
1055:
1056: /*
1057: * Scan for indexable paths.
1058: * This adds all paths with "etc/catman.conf" to the buffer.
1059: */
1060: static void
1061: pathgen(DIR *dir, char *path, struct req *req)
1062: {
1063: struct dirent *d;
1064: char *cp;
1065: DIR *cd;
1066: int rc;
1067: size_t sz, ssz;
1068:
1069: sz = strlcat(path, "/", MAXPATHLEN);
1070: if (sz >= MAXPATHLEN) {
1071: fprintf(stderr, "%s: Path too long", path);
1072: return;
1073: }
1074:
1075: /*
1076: * First, scan for the "etc" directory.
1077: * If it's found, then see if it should cause us to stop. This
1078: * happens when a catman.conf is found in the directory.
1079: */
1080:
1081: rc = 0;
1082: while (0 == rc && NULL != (d = readdir(dir))) {
1083: if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
1084: continue;
1085:
1086: path[(int)sz] = '\0';
1087: ssz = strlcat(path, d->d_name, MAXPATHLEN);
1088:
1089: if (ssz >= MAXPATHLEN) {
1090: fprintf(stderr, "%s: Path too long", path);
1091: return;
1092: } else if (NULL == (cd = opendir(path))) {
1093: perror(path);
1094: return;
1095: }
1096:
1097: rc = pathstop(cd);
1098: closedir(cd);
1099: }
1100:
1101: if (rc > 0) {
1102: /* This also strips the trailing slash. */
1103: path[(int)sz - 1] = '\0';
1104: req->p = mandoc_realloc
1105: (req->p,
1106: (req->psz + 1) * sizeof(struct paths));
1107: req->p[(int)req->psz].path = mandoc_strdup(path);
1108: /* And this strips out the leading "./". */
1109: req->p[(int)req->psz].name =
1110: cp = mandoc_strdup(path + 2);
1111: req->psz++;
1112: /*
1113: * The name is just the path with all the slashes taken
1114: * out of it. Simple but effective.
1115: */
1116: for ( ; '\0' != *cp; cp++)
1117: if ('/' == *cp)
1118: *cp = ' ';
1119: return;
1120: }
1121:
1122: /*
1123: * If no etc/catman.conf was found, recursively enter child
1124: * directory and continue scanning.
1125: */
1126:
1127: rewinddir(dir);
1128: while (NULL != (d = readdir(dir))) {
1129: if (DT_DIR != d->d_type || '.' == d->d_name[0])
1130: continue;
1131:
1132: path[(int)sz] = '\0';
1133: ssz = strlcat(path, d->d_name, MAXPATHLEN);
1134:
1135: if (ssz >= MAXPATHLEN) {
1136: fprintf(stderr, "%s: Path too long", path);
1137: return;
1138: } else if (NULL == (cd = opendir(path))) {
1139: perror(path);
1140: return;
1141: }
1142:
1143: pathgen(cd, path, req);
1144: closedir(cd);
1145: }
1146: }
CVSweb