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