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