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