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