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