Annotation of mandoc/cgi.c, Revision 1.24
1.24 ! kristaps 1: /* $Id: cgi.c,v 1.23 2011/12/10 10:59:21 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);
494: printf("/show/%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;
722: unsigned int vol, rec;
1.9 kristaps 723: DB *idx;
1.6 kristaps 724: DBT key, val;
725:
1.24 ! kristaps 726: idx = NULL;
! 727:
! 728: if (0 == req->psz || NULL == path) {
1.10 kristaps 729: resp_error400();
1.6 kristaps 730: return;
731: } else if (NULL == (sub = strrchr(path, '/'))) {
1.10 kristaps 732: resp_error400();
1.6 kristaps 733: return;
734: } else
735: *sub++ = '\0';
736:
1.24 ! kristaps 737: /*
! 738: * Begin by chdir()ing into the root of the manpath.
! 739: * This way we can pick up the database files, which are
! 740: * relative to the manpath root.
! 741: */
! 742:
! 743: if (-1 == chdir(req->p[0].path)) {
! 744: perror(req->p[0].path);
! 745: resp_error400();
! 746: return;
! 747: }
! 748:
! 749: memset(&ps, 0, sizeof(struct manpaths));
! 750: manpath_manconf("etc/catman.conf", &ps);
! 751:
1.6 kristaps 752: if ( ! (atou(path, &vol) && atou(sub, &rec))) {
1.10 kristaps 753: resp_error400();
1.24 ! kristaps 754: goto out;
! 755: } else if (vol >= (unsigned int)ps.sz) {
1.10 kristaps 756: resp_error400();
1.24 ! kristaps 757: goto out;
1.6 kristaps 758: }
759:
1.24 ! kristaps 760: strlcpy(file, ps.paths[vol], MAXPATHLEN);
1.6 kristaps 761: strlcat(file, "/mandoc.index", MAXPATHLEN);
762:
763: /* Open the index recno(3) database. */
764:
1.9 kristaps 765: idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL);
766: if (NULL == idx) {
1.24 ! kristaps 767: perror(file);
1.6 kristaps 768: resp_baddb();
1.24 ! kristaps 769: goto out;
1.6 kristaps 770: }
771:
772: key.data = &rec;
773: key.size = 4;
774:
1.9 kristaps 775: if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) {
1.10 kristaps 776: rc < 0 ? resp_baddb() : resp_error400();
1.9 kristaps 777: goto out;
1.6 kristaps 778: }
779:
1.9 kristaps 780: cp = (char *)val.data;
1.6 kristaps 781:
1.9 kristaps 782: if (NULL == (fn = memchr(cp, '\0', val.size)))
783: resp_baddb();
784: else if (++fn - cp >= (int)val.size)
785: resp_baddb();
786: else if (NULL == memchr(fn, '\0', val.size - (fn - cp)))
787: resp_baddb();
788: else {
789: if (0 == strcmp(cp, "cat"))
1.24 ! kristaps 790: catman(fn + 1);
1.9 kristaps 791: else
1.24 ! kristaps 792: format(fn + 1);
1.9 kristaps 793: }
794: out:
1.24 ! kristaps 795: if (idx)
! 796: (*idx->close)(idx);
! 797: manpath_free(&ps);
1.6 kristaps 798: }
799:
800: static void
1.24 ! kristaps 801: pg_search(const struct req *req, char *path)
1.6 kristaps 802: {
803: size_t tt;
1.24 ! kristaps 804: struct manpaths ps;
1.20 kristaps 805: int i, sz, rc;
1.6 kristaps 806: const char *ep, *start;
807: char **cp;
808: struct opts opt;
809: struct expr *expr;
810:
1.24 ! kristaps 811: if (0 == req->psz) {
! 812: resp_search(NULL, 0, (void *)req);
! 813: return;
! 814: }
! 815:
1.1 kristaps 816: memset(&opt, 0, sizeof(struct opts));
1.6 kristaps 817:
1.24 ! kristaps 818: ep = req->q.expr;
! 819: opt.arch = req->q.arch;
! 820: opt.cat = req->q.sec;
1.20 kristaps 821: rc = -1;
822: sz = 0;
823: cp = NULL;
1.6 kristaps 824:
825: /*
1.24 ! kristaps 826: * Begin by chdir()ing into the root of the manpath.
! 827: * This way we can pick up the database files, which are
! 828: * relative to the manpath root.
! 829: */
! 830:
! 831: if (-1 == (chdir(req->p[0].path))) {
! 832: perror(req->p[0].path);
! 833: resp_search(NULL, 0, (void *)req);
! 834: return;
! 835: }
! 836:
! 837: memset(&ps, 0, sizeof(struct manpaths));
! 838: manpath_manconf("etc/catman.conf", &ps);
! 839:
! 840: /*
! 841: * Poor man's tokenisation: just break apart by spaces.
1.6 kristaps 842: * Yes, this is half-ass. But it works for now.
843: */
844:
845: while (ep && isspace((unsigned char)*ep))
846: ep++;
847:
848: while (ep && '\0' != *ep) {
849: cp = mandoc_realloc(cp, (sz + 1) * sizeof(char *));
850: start = ep;
851: while ('\0' != *ep && ! isspace((unsigned char)*ep))
852: ep++;
853: cp[sz] = mandoc_malloc((ep - start) + 1);
854: memcpy(cp[sz], start, ep - start);
855: cp[sz++][ep - start] = '\0';
856: while (isspace((unsigned char)*ep))
857: ep++;
858: }
859:
860: /*
861: * Pump down into apropos backend.
862: * The resp_search() function is called with the results.
863: */
864:
1.24 ! kristaps 865: expr = req->q.whatis ?
! 866: termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt);
1.12 kristaps 867:
868: if (NULL != expr)
1.6 kristaps 869: rc = apropos_search
1.24 ! kristaps 870: (ps.sz, ps.paths, &opt,
1.6 kristaps 871: expr, tt, (void *)req, resp_search);
872:
873: /* ...unless errors occured. */
874:
875: if (0 == rc)
876: resp_baddb();
877: else if (-1 == rc)
1.10 kristaps 878: resp_search(NULL, 0, (void *)req);
1.6 kristaps 879:
880: for (i = 0; i < sz; i++)
881: free(cp[i]);
882:
883: free(cp);
884: exprfree(expr);
1.24 ! kristaps 885: manpath_free(&ps);
1.1 kristaps 886: }
887:
888: int
889: main(void)
890: {
891: int i;
1.24 ! kristaps 892: char buf[MAXPATHLEN];
! 893: DIR *cwd;
1.1 kristaps 894: struct req req;
1.6 kristaps 895: char *p, *path, *subpath;
896:
1.24 ! kristaps 897: /* Scan our run-time environment. */
1.6 kristaps 898:
899: progname = getenv("SCRIPT_NAME");
900: if (NULL == progname)
901: progname = "";
902:
1.7 kristaps 903: cache = getenv("CACHE_DIR");
904: if (NULL == cache)
905: cache = "/cache/man.cgi";
906:
1.24 ! kristaps 907: host = getenv("HTTP_HOST");
! 908: if (NULL == host)
! 909: host = "localhost";
! 910:
! 911: /*
! 912: * First we change directory into the cache directory so that
! 913: * subsequent scanning for manpath directories is rooted
! 914: * relative to the same position.
! 915: */
! 916:
1.8 kristaps 917: if (-1 == chdir(cache)) {
1.24 ! kristaps 918: perror(cache);
! 919: resp_bad();
! 920: return(EXIT_FAILURE);
! 921: } else if (NULL == (cwd = opendir(cache))) {
! 922: perror(cache);
1.8 kristaps 923: resp_bad();
924: return(EXIT_FAILURE);
1.24 ! kristaps 925: }
! 926:
! 927: memset(&req, 0, sizeof(struct req));
1.7 kristaps 928:
1.24 ! kristaps 929: strlcpy(buf, ".", MAXPATHLEN);
! 930: pathgen(cwd, buf, &req);
! 931: closedir(cwd);
1.1 kristaps 932:
1.24 ! kristaps 933: /* Next parse out the query string. */
1.1 kristaps 934:
935: if (NULL != (p = getenv("QUERY_STRING")))
1.24 ! kristaps 936: http_parse(&req.q, p);
1.1 kristaps 937:
1.24 ! kristaps 938: /*
! 939: * Now juggle paths to extract information.
! 940: * We want to extract our filetype (the file suffix), the
! 941: * initial path component, then the trailing component(s).
! 942: * Start with leading subpath component.
! 943: */
1.1 kristaps 944:
1.6 kristaps 945: subpath = path = NULL;
1.1 kristaps 946: req.page = PAGE__MAX;
947:
948: if (NULL == (path = getenv("PATH_INFO")) || '\0' == *path)
949: req.page = PAGE_INDEX;
1.6 kristaps 950:
1.1 kristaps 951: if (NULL != path && '/' == *path && '\0' == *++path)
952: req.page = PAGE_INDEX;
953:
1.6 kristaps 954: /* Strip file suffix. */
955:
956: if (NULL != path && NULL != (p = strrchr(path, '.')))
957: if (NULL != p && NULL == strchr(p, '/'))
958: *p++ = '\0';
959:
960: /* Resolve subpath component. */
1.1 kristaps 961:
962: if (NULL != path && NULL != (subpath = strchr(path, '/')))
1.6 kristaps 963: *subpath++ = '\0';
1.1 kristaps 964:
1.6 kristaps 965: /* Map path into one we recognise. */
1.1 kristaps 966:
967: if (NULL != path && '\0' != *path)
968: for (i = 0; i < (int)PAGE__MAX; i++)
969: if (0 == strcmp(pages[i], path)) {
970: req.page = (enum page)i;
971: break;
972: }
973:
1.6 kristaps 974: /* Route pages. */
975:
1.1 kristaps 976: switch (req.page) {
977: case (PAGE_INDEX):
1.24 ! kristaps 978: pg_index(&req, subpath);
1.1 kristaps 979: break;
980: case (PAGE_SEARCH):
1.24 ! kristaps 981: pg_search(&req, subpath);
1.6 kristaps 982: break;
983: case (PAGE_SHOW):
1.24 ! kristaps 984: pg_show(&req, subpath);
1.1 kristaps 985: break;
986: default:
1.10 kristaps 987: resp_error404(path);
1.1 kristaps 988: break;
989: }
990:
1.24 ! kristaps 991: for (i = 0; i < (int)req.psz; i++) {
! 992: free(req.p[i].path);
! 993: free(req.p[i].name);
! 994: }
1.6 kristaps 995:
1.24 ! kristaps 996: free(req.p);
1.1 kristaps 997: return(EXIT_SUCCESS);
998: }
1.15 kristaps 999:
1000: static int
1001: cmp(const void *p1, const void *p2)
1002: {
1003:
1004: return(strcasecmp(((const struct res *)p1)->title,
1005: ((const struct res *)p2)->title));
1006: }
1007:
1.24 ! kristaps 1008: /*
! 1009: * Check to see if an "etc" path consists of a catman.conf file. If it
! 1010: * does, that means that the path contains a tree created by catman(8)
! 1011: * and should be used for indexing.
! 1012: */
! 1013: static int
! 1014: pathstop(DIR *dir)
! 1015: {
! 1016: struct dirent *d;
! 1017:
! 1018: while (NULL != (d = readdir(dir)))
! 1019: if (DT_REG == d->d_type)
! 1020: if (0 == strcmp(d->d_name, "catman.conf"))
! 1021: return(1);
! 1022:
! 1023: return(0);
! 1024: }
! 1025:
! 1026: /*
! 1027: * Scan for indexable paths.
! 1028: * This adds all paths with "etc/catman.conf" to the buffer.
! 1029: */
! 1030: static void
! 1031: pathgen(DIR *dir, char *path, struct req *req)
! 1032: {
! 1033: struct dirent *d;
! 1034: char *cp;
! 1035: DIR *cd;
! 1036: int rc;
! 1037: size_t sz, ssz;
! 1038:
! 1039: sz = strlcat(path, "/", MAXPATHLEN);
! 1040: if (sz >= MAXPATHLEN) {
! 1041: fprintf(stderr, "%s: Path too long", path);
! 1042: return;
! 1043: }
! 1044:
! 1045: /*
! 1046: * First, scan for the "etc" directory.
! 1047: * If it's found, then see if it should cause us to stop. This
! 1048: * happens when a catman.conf is found in the directory.
! 1049: */
! 1050:
! 1051: rc = 0;
! 1052: while (0 == rc && NULL != (d = readdir(dir))) {
! 1053: if (DT_DIR != d->d_type || strcmp(d->d_name, "etc"))
! 1054: continue;
! 1055:
! 1056: path[(int)sz] = '\0';
! 1057: ssz = strlcat(path, d->d_name, MAXPATHLEN);
! 1058:
! 1059: if (ssz >= MAXPATHLEN) {
! 1060: fprintf(stderr, "%s: Path too long", path);
! 1061: return;
! 1062: } else if (NULL == (cd = opendir(path))) {
! 1063: perror(path);
! 1064: return;
! 1065: }
! 1066:
! 1067: rc = pathstop(cd);
! 1068: closedir(cd);
! 1069: }
! 1070:
! 1071: if (rc > 0) {
! 1072: /* This also strips the trailing slash. */
! 1073: path[(int)sz - 1] = '\0';
! 1074: req->p = mandoc_realloc
! 1075: (req->p,
! 1076: (req->psz + 1) * sizeof(struct paths));
! 1077: req->p[(int)req->psz].path = mandoc_strdup(path);
! 1078: /* And this strips out the leading "./". */
! 1079: req->p[(int)req->psz].name =
! 1080: cp = mandoc_strdup(path + 2);
! 1081: req->psz++;
! 1082: /*
! 1083: * The name is just the path with all the slashes taken
! 1084: * out of it. Simple but effective.
! 1085: */
! 1086: for ( ; '\0' != *cp; cp++)
! 1087: if ('/' == *cp)
! 1088: *cp = ' ';
! 1089: return;
! 1090: }
! 1091:
! 1092: /*
! 1093: * If no etc/catman.conf was found, recursively enter child
! 1094: * directory and continue scanning.
! 1095: */
! 1096:
! 1097: rewinddir(dir);
! 1098: while (NULL != (d = readdir(dir))) {
! 1099: if (DT_DIR != d->d_type || '.' == d->d_name[0])
! 1100: continue;
! 1101:
! 1102: path[(int)sz] = '\0';
! 1103: ssz = strlcat(path, d->d_name, MAXPATHLEN);
! 1104:
! 1105: if (ssz >= MAXPATHLEN) {
! 1106: fprintf(stderr, "%s: Path too long", path);
! 1107: return;
! 1108: } else if (NULL == (cd = opendir(path))) {
! 1109: perror(path);
! 1110: return;
! 1111: }
! 1112:
! 1113: pathgen(cd, path, req);
! 1114: closedir(cd);
! 1115: }
! 1116: }
CVSweb