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