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