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