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