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