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