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