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