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