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