Annotation of mandoc/cgi.c, Revision 1.79
1.79 ! schwarze 1: /* $Id: cgi.c,v 1.78 2014/07/21 15:45:17 schwarze Exp $ */
1.6 kristaps 2: /*
1.42 kristaps 3: * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
1.52 schwarze 4: * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
1.6 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18: #ifdef HAVE_CONFIG_H
19: #include "config.h"
20: #endif
21:
22: #include <ctype.h>
1.58 schwarze 23: #include <errno.h>
1.1 kristaps 24: #include <fcntl.h>
1.6 kristaps 25: #include <limits.h>
1.1 kristaps 26: #include <stdio.h>
27: #include <stdlib.h>
28: #include <string.h>
1.6 kristaps 29: #include <unistd.h>
1.1 kristaps 30:
1.4 schwarze 31: #include "mandoc.h"
1.50 schwarze 32: #include "mandoc_aux.h"
1.8 kristaps 33: #include "main.h"
1.6 kristaps 34: #include "manpath.h"
1.52 schwarze 35: #include "mansearch.h"
1.67 schwarze 36: #include "cgi.h"
1.1 kristaps 37:
1.20 kristaps 38: /*
39: * A query as passed to the search function.
40: */
41: struct query {
1.58 schwarze 42: const char *manpath; /* desired manual directory */
1.20 kristaps 43: const char *arch; /* architecture */
44: const char *sec; /* manual section */
45: const char *expr; /* unparsed expression string */
1.65 schwarze 46: int equal; /* match whole names, not substrings */
1.20 kristaps 47: };
48:
1.1 kristaps 49: struct req {
1.58 schwarze 50: struct query q;
51: char **p; /* array of available manpaths */
52: size_t psz; /* number of available manpaths */
1.1 kristaps 53: };
54:
1.32 kristaps 55: static void catman(const struct req *, const char *);
1.15 kristaps 56: static int cmp(const void *, const void *);
1.32 kristaps 57: static void format(const struct req *, const char *);
1.6 kristaps 58: static void html_print(const char *);
1.36 kristaps 59: static void html_printquery(const struct req *);
1.10 kristaps 60: static void html_putchar(char);
1.24 kristaps 61: static int http_decode(char *);
1.26 kristaps 62: static void http_parse(struct req *, char *);
1.36 kristaps 63: static void http_print(const char *);
64: static void http_putchar(char);
65: static void http_printquery(const struct req *);
1.54 schwarze 66: static void pathgen(struct req *);
1.72 schwarze 67: static void pg_error_badrequest(const char *);
68: static void pg_error_internal(void);
69: static void pg_index(const struct req *);
70: static void pg_noresult(const struct req *, const char *);
1.66 schwarze 71: static void pg_search(const struct req *);
1.72 schwarze 72: static void pg_searchres(const struct req *,
73: struct manpage *, size_t);
1.79 ! schwarze 74: static void pg_show(struct req *, const char *);
1.6 kristaps 75: static void resp_begin_html(int, const char *);
76: static void resp_begin_http(int, const char *);
77: static void resp_end_html(void);
78: static void resp_searchform(const struct req *);
1.70 schwarze 79: static void resp_show(const struct req *, const char *);
1.6 kristaps 80:
1.58 schwarze 81: static const char *scriptname; /* CGI script name */
1.1 kristaps 82:
1.70 schwarze 83: static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
1.68 schwarze 84: static const char *const sec_numbers[] = {
85: "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
86: };
87: static const char *const sec_names[] = {
88: "All Sections",
89: "1 - General Commands",
90: "2 - System Calls",
91: "3 - Subroutines",
92: "3p - Perl Subroutines",
93: "4 - Special Files",
94: "5 - File Formats",
95: "6 - Games",
96: "7 - Macros and Conventions",
97: "8 - Maintenance Commands",
98: "9 - Kernel Interface"
99: };
100: static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
101:
102: static const char *const arch_names[] = {
103: "amd64", "alpha", "armish", "armv7",
104: "aviion", "hppa", "hppa64", "i386",
105: "ia64", "landisk", "loongson", "luna88k",
106: "macppc", "mips64", "octeon", "sgi",
107: "socppc", "solbourne", "sparc", "sparc64",
108: "vax", "zaurus",
109: "amiga", "arc", "arm32", "atari",
110: "beagle", "cats", "hp300", "mac68k",
111: "mvme68k", "mvme88k", "mvmeppc", "palm",
112: "pc532", "pegasos", "pmax", "powerpc",
113: "sun3", "wgrisc", "x68k"
114: };
115: static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
116:
1.6 kristaps 117: /*
1.20 kristaps 118: * Print a character, escaping HTML along the way.
119: * This will pass non-ASCII straight to output: be warned!
120: */
1.10 kristaps 121: static void
122: html_putchar(char c)
123: {
124:
125: switch (c) {
126: case ('"'):
127: printf(""e;");
128: break;
129: case ('&'):
130: printf("&");
131: break;
132: case ('>'):
133: printf(">");
134: break;
135: case ('<'):
136: printf("<");
137: break;
138: default:
139: putchar((unsigned char)c);
140: break;
141: }
142: }
1.57 schwarze 143:
1.36 kristaps 144: static void
145: http_printquery(const struct req *req)
146: {
147:
1.58 schwarze 148: if (NULL != req->q.manpath) {
1.53 schwarze 149: printf("&manpath=");
1.58 schwarze 150: http_print(req->q.manpath);
1.53 schwarze 151: }
152: if (NULL != req->q.sec) {
153: printf("&sec=");
154: http_print(req->q.sec);
155: }
156: if (NULL != req->q.arch) {
157: printf("&arch=");
158: http_print(req->q.arch);
159: }
160: if (NULL != req->q.expr) {
1.65 schwarze 161: printf("&query=");
162: http_print(req->q.expr);
1.53 schwarze 163: }
1.65 schwarze 164: if (0 == req->q.equal)
165: printf("&apropos=1");
1.36 kristaps 166: }
167:
168: static void
169: html_printquery(const struct req *req)
170: {
171:
1.58 schwarze 172: if (NULL != req->q.manpath) {
1.53 schwarze 173: printf("&manpath=");
1.58 schwarze 174: html_print(req->q.manpath);
1.53 schwarze 175: }
176: if (NULL != req->q.sec) {
177: printf("&sec=");
178: html_print(req->q.sec);
179: }
180: if (NULL != req->q.arch) {
181: printf("&arch=");
182: html_print(req->q.arch);
183: }
184: if (NULL != req->q.expr) {
1.65 schwarze 185: printf("&query=");
1.63 schwarze 186: html_print(req->q.expr);
1.53 schwarze 187: }
1.65 schwarze 188: if (0 == req->q.equal)
189: printf("&apropos=1");
1.36 kristaps 190: }
191:
192: static void
193: http_print(const char *p)
194: {
195:
196: if (NULL == p)
197: return;
198: while ('\0' != *p)
199: http_putchar(*p++);
200: }
1.10 kristaps 201:
1.6 kristaps 202: /*
1.20 kristaps 203: * Call through to html_putchar().
204: * Accepts NULL strings.
1.6 kristaps 205: */
1.1 kristaps 206: static void
1.6 kristaps 207: html_print(const char *p)
1.1 kristaps 208: {
1.6 kristaps 209:
210: if (NULL == p)
211: return;
1.1 kristaps 212: while ('\0' != *p)
1.10 kristaps 213: html_putchar(*p++);
1.1 kristaps 214: }
215:
216: /*
217: * Parse out key-value pairs from an HTTP request variable.
1.6 kristaps 218: * This can be either a cookie or a POST/GET string, although man.cgi
219: * uses only GET for simplicity.
1.1 kristaps 220: */
221: static void
1.26 kristaps 222: http_parse(struct req *req, char *p)
1.1 kristaps 223: {
1.52 schwarze 224: char *key, *val;
1.1 kristaps 225:
1.26 kristaps 226: memset(&req->q, 0, sizeof(struct query));
1.58 schwarze 227: req->q.manpath = req->p[0];
1.65 schwarze 228: req->q.equal = 1;
1.24 kristaps 229:
1.36 kristaps 230: while ('\0' != *p) {
1.1 kristaps 231: key = p;
232: val = NULL;
233:
1.36 kristaps 234: p += (int)strcspn(p, ";&");
235: if ('\0' != *p)
1.1 kristaps 236: *p++ = '\0';
1.36 kristaps 237: if (NULL != (val = strchr(key, '=')))
238: *val++ = '\0';
1.1 kristaps 239:
1.36 kristaps 240: if ('\0' == *key || NULL == val || '\0' == *val)
1.1 kristaps 241: continue;
242:
243: /* Just abort handling. */
244:
1.24 kristaps 245: if ( ! http_decode(key))
246: break;
1.36 kristaps 247: if (NULL != val && ! http_decode(val))
1.24 kristaps 248: break;
249:
1.65 schwarze 250: if (0 == strcmp(key, "query"))
1.26 kristaps 251: req->q.expr = val;
1.73 schwarze 252: else if (0 == strcmp(key, "manpath")) {
253: #ifdef COMPAT_OLDURI
254: if (0 == strncmp(val, "OpenBSD ", 8)) {
255: val[7] = '-';
256: if ('C' == val[8])
257: val[8] = 'c';
258: }
259: #endif
1.58 schwarze 260: req->q.manpath = val;
1.73 schwarze 261: } else if (0 == strcmp(key, "apropos"))
1.65 schwarze 262: req->q.equal = !strcmp(val, "0");
1.73 schwarze 263: else if (0 == strcmp(key, "sec")) {
264: if (strcmp(val, "0"))
265: req->q.sec = val;
266: #ifdef COMPAT_OLDURI
267: } else if (0 == strcmp(key, "sektion")) {
1.65 schwarze 268: if (strcmp(val, "0"))
269: req->q.sec = val;
1.73 schwarze 270: #endif
1.65 schwarze 271: } else if (0 == strcmp(key, "arch")) {
272: if (strcmp(val, "default"))
273: req->q.arch = val;
274: }
1.24 kristaps 275: }
1.1 kristaps 276: }
277:
1.36 kristaps 278: static void
279: http_putchar(char c)
280: {
281:
282: if (isalnum((unsigned char)c)) {
283: putchar((unsigned char)c);
284: return;
285: } else if (' ' == c) {
286: putchar('+');
287: return;
288: }
289: printf("%%%.2x", c);
290: }
291:
1.1 kristaps 292: /*
1.6 kristaps 293: * HTTP-decode a string. The standard explanation is that this turns
294: * "%4e+foo" into "n foo" in the regular way. This is done in-place
295: * over the allocated string.
1.1 kristaps 296: */
297: static int
1.24 kristaps 298: http_decode(char *p)
1.1 kristaps 299: {
300: char hex[3];
1.63 schwarze 301: char *q;
1.1 kristaps 302: int c;
303:
304: hex[2] = '\0';
305:
1.63 schwarze 306: q = p;
307: for ( ; '\0' != *p; p++, q++) {
1.1 kristaps 308: if ('%' == *p) {
309: if ('\0' == (hex[0] = *(p + 1)))
310: return(0);
311: if ('\0' == (hex[1] = *(p + 2)))
312: return(0);
313: if (1 != sscanf(hex, "%x", &c))
314: return(0);
315: if ('\0' == c)
316: return(0);
317:
1.63 schwarze 318: *q = (char)c;
319: p += 2;
1.1 kristaps 320: } else
1.63 schwarze 321: *q = '+' == *p ? ' ' : *p;
1.1 kristaps 322: }
323:
1.63 schwarze 324: *q = '\0';
1.1 kristaps 325: return(1);
326: }
327:
1.6 kristaps 328: static void
329: resp_begin_http(int code, const char *msg)
330: {
331:
332: if (200 != code)
1.62 schwarze 333: printf("Status: %d %s\r\n", code, msg);
1.6 kristaps 334:
1.62 schwarze 335: printf("Content-Type: text/html; charset=utf-8\r\n"
336: "Cache-Control: no-cache\r\n"
337: "Pragma: no-cache\r\n"
338: "\r\n");
1.6 kristaps 339:
340: fflush(stdout);
341: }
342:
343: static void
344: resp_begin_html(int code, const char *msg)
345: {
346:
347: resp_begin_http(code, msg);
348:
1.29 kristaps 349: printf("<!DOCTYPE HTML PUBLIC "
350: " \"-//W3C//DTD HTML 4.01//EN\""
351: " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
352: "<HTML>\n"
353: "<HEAD>\n"
354: "<META HTTP-EQUIV=\"Content-Type\""
355: " CONTENT=\"text/html; charset=utf-8\">\n"
1.32 kristaps 356: "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
357: " TYPE=\"text/css\" media=\"all\">\n"
358: "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
1.29 kristaps 359: " TYPE=\"text/css\" media=\"all\">\n"
1.67 schwarze 360: "<TITLE>%s</TITLE>\n"
1.29 kristaps 361: "</HEAD>\n"
362: "<BODY>\n"
1.58 schwarze 363: "<!-- Begin page content. //-->\n",
1.67 schwarze 364: CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
1.6 kristaps 365: }
366:
367: static void
368: resp_end_html(void)
369: {
370:
1.20 kristaps 371: puts("</BODY>\n"
372: "</HTML>");
1.6 kristaps 373: }
374:
375: static void
376: resp_searchform(const struct req *req)
377: {
1.27 kristaps 378: int i;
1.13 kristaps 379:
1.67 schwarze 380: puts(CUSTOMIZE_BEGIN);
1.6 kristaps 381: puts("<!-- Begin search form. //-->");
1.32 kristaps 382: printf("<DIV ID=\"mancgi\">\n"
1.66 schwarze 383: "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
1.29 kristaps 384: "<FIELDSET>\n"
1.68 schwarze 385: "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
1.58 schwarze 386: scriptname);
1.68 schwarze 387:
388: /* Write query input box. */
389:
390: printf( "<TABLE><TR><TD>\n"
391: "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
392: if (NULL != req->q.expr)
393: html_print(req->q.expr);
394: puts("\" SIZE=\"40\">");
395:
396: /* Write submission and reset buttons. */
397:
398: printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
399: "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
400:
401: /* Write show radio button */
402:
403: printf( "</TD><TD>\n"
404: "<INPUT TYPE=\"radio\" ");
1.65 schwarze 405: if (req->q.equal)
1.68 schwarze 406: printf("CHECKED ");
407: printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
408: "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
409:
410: /* Write section selector. */
411:
412: printf( "</TD></TR><TR><TD>\n"
413: "<SELECT NAME=\"sec\">");
414: for (i = 0; i < sec_MAX; i++) {
415: printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
416: if (NULL != req->q.sec &&
417: 0 == strcmp(sec_numbers[i], req->q.sec))
418: printf(" SELECTED");
419: printf(">%s</OPTION>\n", sec_names[i]);
420: }
421: puts("</SELECT>");
422:
423: /* Write architecture selector. */
424:
425: puts("<SELECT NAME=\"arch\">");
426: for (i = 0; i < arch_MAX; i++) {
427: printf("<OPTION VALUE=\"%s\"", arch_names[i]);
428: if (NULL != req->q.arch &&
429: 0 == strcmp(arch_names[i], req->q.arch))
430: printf(" SELECTED");
431: printf(">%s</OPTION>\n", arch_names[i]);
432: }
433: puts("</SELECT>");
434:
435: /* Write manpath selector. */
436:
1.27 kristaps 437: if (req->psz > 1) {
1.68 schwarze 438: puts("<SELECT NAME=\"manpath\">");
1.27 kristaps 439: for (i = 0; i < (int)req->psz; i++) {
1.52 schwarze 440: printf("<OPTION ");
1.58 schwarze 441: if (NULL == req->q.manpath ? 0 == i :
442: 0 == strcmp(req->q.manpath, req->p[i]))
1.68 schwarze 443: printf("SELECTED ");
1.52 schwarze 444: printf("VALUE=\"");
445: html_print(req->p[i]);
1.27 kristaps 446: printf("\">");
1.52 schwarze 447: html_print(req->p[i]);
1.27 kristaps 448: puts("</OPTION>");
449: }
450: puts("</SELECT>");
451: }
1.68 schwarze 452:
453: /* Write search radio button */
454:
455: printf( "</TD><TD>\n"
456: "<INPUT TYPE=\"radio\" ");
457: if (0 == req->q.equal)
458: printf("CHECKED ");
459: printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
460: "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
461:
462: puts("</TD></TR></TABLE>\n"
1.12 kristaps 463: "</FIELDSET>\n"
1.32 kristaps 464: "</FORM>\n"
465: "</DIV>");
1.20 kristaps 466: puts("<!-- End search form. //-->");
1.6 kristaps 467: }
468:
1.76 schwarze 469: static int
1.77 schwarze 470: validate_manpath(const struct req *req, const char* manpath)
471: {
472: size_t i;
473:
474: if ( ! strcmp(manpath, "mandoc"))
475: return(1);
476:
477: for (i = 0; i < req->psz; i++)
478: if ( ! strcmp(manpath, req->p[i]))
479: return(1);
480:
481: return(0);
482: }
483:
484: static int
1.76 schwarze 485: validate_filename(const char *file)
486: {
487:
488: if ('.' == file[0] && '/' == file[1])
489: file += 2;
490:
491: return ( ! (strstr(file, "../") || strstr(file, "/..") ||
492: (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
493: }
494:
1.6 kristaps 495: static void
1.72 schwarze 496: pg_index(const struct req *req)
1.6 kristaps 497: {
498:
499: resp_begin_html(200, NULL);
500: resp_searchform(req);
1.64 schwarze 501: printf("<P>\n"
502: "This web interface is documented in the "
1.69 schwarze 503: "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
504: "manual, and the "
505: "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
506: "manual explains the query syntax.\n"
1.64 schwarze 507: "</P>\n",
508: scriptname, scriptname);
1.6 kristaps 509: resp_end_html();
510: }
511:
512: static void
1.72 schwarze 513: pg_noresult(const struct req *req, const char *msg)
1.59 schwarze 514: {
515: resp_begin_html(200, NULL);
516: resp_searchform(req);
517: puts("<P>");
518: puts(msg);
519: puts("</P>");
520: resp_end_html();
521: }
522:
523: static void
1.72 schwarze 524: pg_error_badrequest(const char *msg)
1.9 kristaps 525: {
526:
1.59 schwarze 527: resp_begin_html(400, "Bad Request");
528: puts("<H1>Bad Request</H1>\n"
529: "<P>\n");
530: puts(msg);
531: printf("Try again from the\n"
532: "<A HREF=\"%s\">main page</A>.\n"
1.58 schwarze 533: "</P>", scriptname);
1.9 kristaps 534: resp_end_html();
535: }
536:
537: static void
1.72 schwarze 538: pg_error_internal(void)
1.7 kristaps 539: {
540: resp_begin_html(500, "Internal Server Error");
1.58 schwarze 541: puts("<P>Internal Server Error</P>");
1.7 kristaps 542: resp_end_html();
543: }
544:
545: static void
1.72 schwarze 546: pg_searchres(const struct req *req, struct manpage *r, size_t sz)
1.1 kristaps 547: {
1.70 schwarze 548: size_t i, iuse, isec;
549: int prio, priouse;
550: char sec;
1.19 kristaps 551:
1.76 schwarze 552: for (i = 0; i < sz; i++) {
553: if (validate_filename(r[i].file))
554: continue;
555: fprintf(stderr, "invalid filename %s in %s database\n",
556: r[i].file, req->q.manpath);
557: pg_error_internal();
558: return;
559: }
560:
1.52 schwarze 561: if (1 == sz) {
1.6 kristaps 562: /*
563: * If we have just one result, then jump there now
564: * without any delay.
565: */
1.62 schwarze 566: printf("Status: 303 See Other\r\n");
1.78 schwarze 567: printf("Location: http://%s%s/%s/%s?",
568: HTTP_HOST, scriptname, req->q.manpath, r[0].file);
1.36 kristaps 569: http_printquery(req);
1.62 schwarze 570: printf("\r\n"
571: "Content-Type: text/html; charset=utf-8\r\n"
572: "\r\n");
1.6 kristaps 573: return;
574: }
575:
1.59 schwarze 576: qsort(r, sz, sizeof(struct manpage), cmp);
577:
1.12 kristaps 578: resp_begin_html(200, NULL);
1.19 kristaps 579: resp_searchform(req);
1.33 kristaps 580: puts("<DIV CLASS=\"results\">");
581: puts("<TABLE>");
1.1 kristaps 582:
1.41 kristaps 583: for (i = 0; i < sz; i++) {
1.20 kristaps 584: printf("<TR>\n"
585: "<TD CLASS=\"title\">\n"
1.66 schwarze 586: "<A HREF=\"%s/%s/%s?",
1.58 schwarze 587: scriptname, req->q.manpath, r[i].file);
1.36 kristaps 588: html_printquery(req);
589: printf("\">");
1.52 schwarze 590: html_print(r[i].names);
591: printf("</A>\n"
1.20 kristaps 592: "</TD>\n"
593: "<TD CLASS=\"desc\">");
1.52 schwarze 594: html_print(r[i].output);
1.20 kristaps 595: puts("</TD>\n"
596: "</TR>");
1.1 kristaps 597: }
1.16 kristaps 598:
1.33 kristaps 599: puts("</TABLE>\n"
600: "</DIV>");
1.70 schwarze 601:
602: /*
603: * In man(1) mode, show one of the pages
604: * even if more than one is found.
605: */
606:
607: if (req->q.equal) {
608: puts("<HR>");
609: iuse = 0;
610: priouse = 10;
611: for (i = 0; i < sz; i++) {
612: isec = strcspn(r[i].file, "123456789");
613: sec = r[i].file[isec];
614: if ('\0' == sec)
615: continue;
616: prio = sec_prios[sec - '1'];
617: if (prio >= priouse)
618: continue;
619: priouse = prio;
620: iuse = i;
621: }
622: resp_show(req, r[iuse].file);
623: }
624:
1.6 kristaps 625: resp_end_html();
626: }
627:
1.1 kristaps 628: static void
1.32 kristaps 629: catman(const struct req *req, const char *file)
1.9 kristaps 630: {
1.10 kristaps 631: FILE *f;
632: size_t len;
633: int i;
634: char *p;
635: int italic, bold;
1.9 kristaps 636:
1.10 kristaps 637: if (NULL == (f = fopen(file, "r"))) {
1.70 schwarze 638: puts("<P>You specified an invalid manual file.</P>");
1.9 kristaps 639: return;
640: }
641:
1.32 kristaps 642: puts("<DIV CLASS=\"catman\">\n"
643: "<PRE>");
1.10 kristaps 644:
645: while (NULL != (p = fgetln(f, &len))) {
646: bold = italic = 0;
647: for (i = 0; i < (int)len - 1; i++) {
648: /*
649: * This means that the catpage is out of state.
650: * Ignore it and keep going (although the
651: * catpage is bogus).
652: */
653:
654: if ('\b' == p[i] || '\n' == p[i])
655: continue;
656:
657: /*
658: * Print a regular character.
659: * Close out any bold/italic scopes.
660: * If we're in back-space mode, make sure we'll
661: * have something to enter when we backspace.
662: */
663:
664: if ('\b' != p[i + 1]) {
665: if (italic)
666: printf("</I>");
667: if (bold)
668: printf("</B>");
669: italic = bold = 0;
670: html_putchar(p[i]);
671: continue;
672: } else if (i + 2 >= (int)len)
673: continue;
674:
675: /* Italic mode. */
676:
677: if ('_' == p[i]) {
678: if (bold)
679: printf("</B>");
680: if ( ! italic)
681: printf("<I>");
682: bold = 0;
683: italic = 1;
684: i += 2;
685: html_putchar(p[i]);
686: continue;
687: }
688:
689: /*
690: * Handle funny behaviour troff-isms.
691: * These grok'd from the original man2html.c.
692: */
693:
694: if (('+' == p[i] && 'o' == p[i + 2]) ||
695: ('o' == p[i] && '+' == p[i + 2]) ||
696: ('|' == p[i] && '=' == p[i + 2]) ||
697: ('=' == p[i] && '|' == p[i + 2]) ||
698: ('*' == p[i] && '=' == p[i + 2]) ||
699: ('=' == p[i] && '*' == p[i + 2]) ||
700: ('*' == p[i] && '|' == p[i + 2]) ||
701: ('|' == p[i] && '*' == p[i + 2])) {
702: if (italic)
703: printf("</I>");
704: if (bold)
705: printf("</B>");
706: italic = bold = 0;
707: putchar('*');
708: i += 2;
709: continue;
710: } else if (('|' == p[i] && '-' == p[i + 2]) ||
711: ('-' == p[i] && '|' == p[i + 1]) ||
712: ('+' == p[i] && '-' == p[i + 1]) ||
713: ('-' == p[i] && '+' == p[i + 1]) ||
714: ('+' == p[i] && '|' == p[i + 1]) ||
715: ('|' == p[i] && '+' == p[i + 1])) {
716: if (italic)
717: printf("</I>");
718: if (bold)
719: printf("</B>");
720: italic = bold = 0;
721: putchar('+');
722: i += 2;
723: continue;
724: }
725:
726: /* Bold mode. */
727:
728: if (italic)
729: printf("</I>");
730: if ( ! bold)
731: printf("<B>");
732: bold = 1;
733: italic = 0;
734: i += 2;
735: html_putchar(p[i]);
736: }
737:
738: /*
739: * Clean up the last character.
740: * We can get to a newline; don't print that.
741: */
1.9 kristaps 742:
1.10 kristaps 743: if (italic)
744: printf("</I>");
745: if (bold)
746: printf("</B>");
1.9 kristaps 747:
1.10 kristaps 748: if (i == (int)len - 1 && '\n' != p[i])
749: html_putchar(p[i]);
1.9 kristaps 750:
1.10 kristaps 751: putchar('\n');
752: }
753:
754: puts("</PRE>\n"
1.70 schwarze 755: "</DIV>");
1.10 kristaps 756:
757: fclose(f);
1.9 kristaps 758: }
759:
760: static void
1.32 kristaps 761: format(const struct req *req, const char *file)
1.7 kristaps 762: {
1.8 kristaps 763: struct mparse *mp;
764: int fd;
765: struct mdoc *mdoc;
766: struct man *man;
767: void *vp;
768: enum mandoclevel rc;
1.45 schwarze 769: char opts[PATH_MAX + 128];
1.7 kristaps 770:
1.8 kristaps 771: if (-1 == (fd = open(file, O_RDONLY, 0))) {
1.70 schwarze 772: puts("<P>You specified an invalid manual file.</P>");
1.7 kristaps 773: return;
774: }
775:
1.56 schwarze 776: mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
1.58 schwarze 777: req->q.manpath);
1.8 kristaps 778: rc = mparse_readfd(mp, fd, file);
779: close(fd);
1.7 kristaps 780:
1.8 kristaps 781: if (rc >= MANDOCLEVEL_FATAL) {
1.59 schwarze 782: fprintf(stderr, "fatal mandoc error: %s/%s\n",
783: req->q.manpath, file);
1.72 schwarze 784: pg_error_internal();
1.7 kristaps 785: return;
786: }
787:
1.79 ! schwarze 788: snprintf(opts, sizeof(opts), "fragment,man=%s?"
! 789: "manpath=%s&query=%%N&sec=%%S&arch=%s",
! 790: scriptname, req->q.manpath,
! 791: req->q.arch ? req->q.arch : "");
1.10 kristaps 792:
1.49 schwarze 793: mparse_result(mp, &mdoc, &man, NULL);
1.32 kristaps 794: if (NULL == man && NULL == mdoc) {
1.59 schwarze 795: fprintf(stderr, "fatal mandoc error: %s/%s\n",
796: req->q.manpath, file);
1.72 schwarze 797: pg_error_internal();
1.32 kristaps 798: mparse_free(mp);
799: return;
800: }
801:
1.10 kristaps 802: vp = html_alloc(opts);
1.7 kristaps 803:
1.32 kristaps 804: if (NULL != mdoc)
1.8 kristaps 805: html_mdoc(vp, mdoc);
1.32 kristaps 806: else
1.8 kristaps 807: html_man(vp, man);
1.32 kristaps 808:
1.8 kristaps 809: html_free(vp);
810: mparse_free(mp);
1.7 kristaps 811: }
812:
813: static void
1.70 schwarze 814: resp_show(const struct req *req, const char *file)
815: {
1.76 schwarze 816:
817: if ('.' == file[0] && '/' == file[1])
1.71 schwarze 818: file += 2;
1.70 schwarze 819:
820: if ('c' == *file)
821: catman(req, file);
822: else
823: format(req, file);
824: }
825:
826: static void
1.79 ! schwarze 827: pg_show(struct req *req, const char *path)
1.1 kristaps 828: {
1.6 kristaps 829: char *sub;
1.25 kristaps 830:
831: if (NULL == path || NULL == (sub = strchr(path, '/'))) {
1.72 schwarze 832: pg_error_badrequest(
1.59 schwarze 833: "You did not specify a page to show.");
1.25 kristaps 834: return;
835: }
836: *sub++ = '\0';
1.6 kristaps 837:
1.77 schwarze 838: if ( ! validate_manpath(req, path)) {
839: pg_error_badrequest(
840: "You specified an invalid manpath.");
841: return;
842: }
843:
1.24 kristaps 844: /*
1.58 schwarze 845: * Begin by chdir()ing into the manpath.
1.24 kristaps 846: * This way we can pick up the database files, which are
847: * relative to the manpath root.
848: */
849:
1.52 schwarze 850: if (-1 == chdir(path)) {
1.77 schwarze 851: fprintf(stderr, "chdir %s: %s\n",
852: path, strerror(errno));
853: pg_error_internal();
1.76 schwarze 854: return;
855: }
856:
857: if ( ! validate_filename(sub)) {
858: pg_error_badrequest(
859: "You specified an invalid manual file.");
1.24 kristaps 860: return;
861: }
1.79 ! schwarze 862:
! 863: if (strcmp(path, "mandoc"))
! 864: req->q.manpath = path;
1.24 kristaps 865:
1.70 schwarze 866: resp_begin_html(200, NULL);
867: resp_searchform(req);
868: resp_show(req, sub);
869: resp_end_html();
1.6 kristaps 870: }
871:
872: static void
1.66 schwarze 873: pg_search(const struct req *req)
1.6 kristaps 874: {
1.52 schwarze 875: struct mansearch search;
876: struct manpaths paths;
877: struct manpage *res;
878: char **cp;
879: const char *ep, *start;
880: size_t ressz;
881: int i, sz;
1.6 kristaps 882:
883: /*
1.24 kristaps 884: * Begin by chdir()ing into the root of the manpath.
885: * This way we can pick up the database files, which are
886: * relative to the manpath root.
887: */
888:
1.58 schwarze 889: if (-1 == (chdir(req->q.manpath))) {
1.77 schwarze 890: fprintf(stderr, "chdir %s: %s\n",
891: req->q.manpath, strerror(errno));
892: pg_error_internal();
1.24 kristaps 893: return;
894: }
895:
1.52 schwarze 896: search.arch = req->q.arch;
897: search.sec = req->q.sec;
1.65 schwarze 898: search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
899: search.flags = req->q.equal ? MANSEARCH_MAN : 0;
1.52 schwarze 900:
901: paths.sz = 1;
902: paths.paths = mandoc_malloc(sizeof(char *));
903: paths.paths[0] = mandoc_strdup(".");
1.24 kristaps 904:
905: /*
906: * Poor man's tokenisation: just break apart by spaces.
1.6 kristaps 907: * Yes, this is half-ass. But it works for now.
908: */
909:
1.52 schwarze 910: ep = req->q.expr;
1.6 kristaps 911: while (ep && isspace((unsigned char)*ep))
912: ep++;
913:
1.52 schwarze 914: sz = 0;
915: cp = NULL;
1.6 kristaps 916: while (ep && '\0' != *ep) {
1.51 schwarze 917: cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
1.6 kristaps 918: start = ep;
919: while ('\0' != *ep && ! isspace((unsigned char)*ep))
920: ep++;
921: cp[sz] = mandoc_malloc((ep - start) + 1);
922: memcpy(cp[sz], start, ep - start);
923: cp[sz++][ep - start] = '\0';
924: while (isspace((unsigned char)*ep))
925: ep++;
926: }
927:
1.59 schwarze 928: if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
1.72 schwarze 929: pg_noresult(req, "You entered an invalid query.");
1.59 schwarze 930: else if (0 == ressz)
1.72 schwarze 931: pg_noresult(req, "No results found.");
1.59 schwarze 932: else
1.72 schwarze 933: pg_searchres(req, res, ressz);
1.6 kristaps 934:
935: for (i = 0; i < sz; i++)
936: free(cp[i]);
1.52 schwarze 937: free(cp);
938:
939: for (i = 0; i < (int)ressz; i++) {
940: free(res[i].file);
941: free(res[i].names);
942: free(res[i].output);
943: }
944: free(res);
1.6 kristaps 945:
1.52 schwarze 946: free(paths.paths[0]);
947: free(paths.paths);
1.1 kristaps 948: }
949:
950: int
951: main(void)
952: {
1.66 schwarze 953: struct req req;
954: const char *path;
955: char *querystring;
1.1 kristaps 956: int i;
1.6 kristaps 957:
1.24 kristaps 958: /* Scan our run-time environment. */
1.6 kristaps 959:
1.58 schwarze 960: if (NULL == (scriptname = getenv("SCRIPT_NAME")))
961: scriptname = "";
1.24 kristaps 962:
963: /*
1.67 schwarze 964: * First we change directory into the MAN_DIR so that
1.24 kristaps 965: * subsequent scanning for manpath directories is rooted
966: * relative to the same position.
967: */
968:
1.67 schwarze 969: if (-1 == chdir(MAN_DIR)) {
1.58 schwarze 970: fprintf(stderr, "MAN_DIR: %s: %s\n",
1.67 schwarze 971: MAN_DIR, strerror(errno));
1.72 schwarze 972: pg_error_internal();
1.24 kristaps 973: return(EXIT_FAILURE);
974: }
975:
976: memset(&req, 0, sizeof(struct req));
1.54 schwarze 977: pathgen(&req);
1.1 kristaps 978:
1.24 kristaps 979: /* Next parse out the query string. */
1.1 kristaps 980:
1.58 schwarze 981: if (NULL != (querystring = getenv("QUERY_STRING")))
982: http_parse(&req, querystring);
1.77 schwarze 983:
984: if ( ! validate_manpath(&req, req.q.manpath)) {
985: pg_error_badrequest(
986: "You specified an invalid manpath.");
987: return(EXIT_FAILURE);
988: }
1.1 kristaps 989:
1.66 schwarze 990: /* Dispatch to the three different pages. */
1.1 kristaps 991:
1.66 schwarze 992: path = getenv("PATH_INFO");
993: if (NULL == path)
994: path = "";
995: else if ('/' == *path)
996: path++;
997:
998: if ('\0' != *path)
999: pg_show(&req, path);
1000: else if (NULL != req.q.expr)
1001: pg_search(&req);
1002: else
1.72 schwarze 1003: pg_index(&req);
1.1 kristaps 1004:
1.52 schwarze 1005: for (i = 0; i < (int)req.psz; i++)
1006: free(req.p[i]);
1.24 kristaps 1007: free(req.p);
1.1 kristaps 1008: return(EXIT_SUCCESS);
1009: }
1.15 kristaps 1010:
1011: static int
1012: cmp(const void *p1, const void *p2)
1013: {
1014:
1.52 schwarze 1015: return(strcasecmp(((const struct manpage *)p1)->names,
1016: ((const struct manpage *)p2)->names));
1.24 kristaps 1017: }
1018:
1019: /*
1020: * Scan for indexable paths.
1021: */
1022: static void
1.54 schwarze 1023: pathgen(struct req *req)
1.24 kristaps 1024: {
1.54 schwarze 1025: FILE *fp;
1026: char *dp;
1027: size_t dpsz;
1028:
1.74 schwarze 1029: if (NULL == (fp = fopen("manpath.conf", "r"))) {
1030: fprintf(stderr, "%s/manpath.conf: %s\n",
1031: MAN_DIR, strerror(errno));
1032: pg_error_internal();
1033: exit(EXIT_FAILURE);
1034: }
1.24 kristaps 1035:
1.54 schwarze 1036: while (NULL != (dp = fgetln(fp, &dpsz))) {
1.55 schwarze 1037: if ('\n' == dp[dpsz - 1])
1038: dpsz--;
1.54 schwarze 1039: req->p = mandoc_realloc(req->p,
1040: (req->psz + 1) * sizeof(char *));
1041: req->p[req->psz++] = mandoc_strndup(dp, dpsz);
1.74 schwarze 1042: }
1043:
1044: if ( req->p == NULL ) {
1045: fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1046: pg_error_internal();
1047: exit(EXIT_FAILURE);
1.24 kristaps 1048: }
1049: }
CVSweb