mld6query(8): Rename mld6.c -> mld6query.c
[dragonfly.git] / contrib / mdocml / cgi.c
blob1e38e3d872a5dc2dd5ebc85b46dea96d6339450a
1 /* $Id: cgi.c,v 1.92 2014/08/05 15:29:30 schwarze Exp $ */
2 /*
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014 Ingo Schwarze <schwarze@usta.de>
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.
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.
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
22 #include <ctype.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
32 #include "mandoc.h"
33 #include "mandoc_aux.h"
34 #include "main.h"
35 #include "manpath.h"
36 #include "mansearch.h"
37 #include "cgi.h"
40 * A query as passed to the search function.
42 struct query {
43 char *manpath; /* desired manual directory */
44 char *arch; /* architecture */
45 char *sec; /* manual section */
46 char *query; /* unparsed query expression */
47 int equal; /* match whole names, not substrings */
50 struct req {
51 struct query q;
52 char **p; /* array of available manpaths */
53 size_t psz; /* number of available manpaths */
56 static void catman(const struct req *, const char *);
57 static void format(const struct req *, const char *);
58 static void html_print(const char *);
59 static void html_putchar(char);
60 static int http_decode(char *);
61 static void http_parse(struct req *, const char *);
62 static void http_print(const char *);
63 static void http_putchar(char);
64 static void http_printquery(const struct req *, const char *);
65 static void pathgen(struct req *);
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 *);
70 static void pg_search(const struct req *);
71 static void pg_searchres(const struct req *,
72 struct manpage *, size_t);
73 static void pg_show(struct req *, const char *);
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 *);
78 static void resp_show(const struct req *, const char *);
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 *);
84 static const char *scriptname; /* CGI script name */
86 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
87 static const char *const sec_numbers[] = {
88 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
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"
103 static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
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"
118 static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
121 * Print a character, escaping HTML along the way.
122 * This will pass non-ASCII straight to output: be warned!
124 static void
125 html_putchar(char c)
128 switch (c) {
129 case ('"'):
130 printf("&quote;");
131 break;
132 case ('&'):
133 printf("&amp;");
134 break;
135 case ('>'):
136 printf("&gt;");
137 break;
138 case ('<'):
139 printf("&lt;");
140 break;
141 default:
142 putchar((unsigned char)c);
143 break;
147 static void
148 http_printquery(const struct req *req, const char *sep)
151 if (NULL != req->q.query) {
152 printf("query=");
153 http_print(req->q.query);
155 if (0 == req->q.equal)
156 printf("%sapropos=1", sep);
157 if (NULL != req->q.sec) {
158 printf("%ssec=", sep);
159 http_print(req->q.sec);
161 if (NULL != req->q.arch) {
162 printf("%sarch=", sep);
163 http_print(req->q.arch);
165 if (NULL != req->q.manpath &&
166 strcmp(req->q.manpath, req->p[0])) {
167 printf("%smanpath=", sep);
168 http_print(req->q.manpath);
172 static void
173 http_print(const char *p)
176 if (NULL == p)
177 return;
178 while ('\0' != *p)
179 http_putchar(*p++);
183 * Call through to html_putchar().
184 * Accepts NULL strings.
186 static void
187 html_print(const char *p)
190 if (NULL == p)
191 return;
192 while ('\0' != *p)
193 html_putchar(*p++);
197 * Transfer the responsibility for the allocated string *val
198 * to the query structure.
200 static void
201 set_query_attr(char **attr, char **val)
204 free(*attr);
205 if (**val == '\0') {
206 *attr = NULL;
207 free(*val);
208 } else
209 *attr = *val;
210 *val = NULL;
214 * Parse the QUERY_STRING for key-value pairs
215 * and store the values into the query structure.
217 static void
218 http_parse(struct req *req, const char *qs)
220 char *key, *val;
221 size_t keysz, valsz;
223 req->q.manpath = NULL;
224 req->q.arch = NULL;
225 req->q.sec = NULL;
226 req->q.query = NULL;
227 req->q.equal = 1;
229 key = val = NULL;
230 while (*qs != '\0') {
232 /* Parse one key. */
234 keysz = strcspn(qs, "=;&");
235 key = mandoc_strndup(qs, keysz);
236 qs += keysz;
237 if (*qs != '=')
238 goto next;
240 /* Parse one value. */
242 valsz = strcspn(++qs, ";&");
243 val = mandoc_strndup(qs, valsz);
244 qs += valsz;
246 /* Decode and catch encoding errors. */
248 if ( ! (http_decode(key) && http_decode(val)))
249 goto next;
251 /* Handle key-value pairs. */
253 if ( ! strcmp(key, "query"))
254 set_query_attr(&req->q.query, &val);
256 else if ( ! strcmp(key, "apropos"))
257 req->q.equal = !strcmp(val, "0");
259 else if ( ! strcmp(key, "manpath")) {
260 #ifdef COMPAT_OLDURI
261 if ( ! strncmp(val, "OpenBSD ", 8)) {
262 val[7] = '-';
263 if ('C' == val[8])
264 val[8] = 'c';
266 #endif
267 set_query_attr(&req->q.manpath, &val);
270 else if ( ! (strcmp(key, "sec")
271 #ifdef COMPAT_OLDURI
272 && strcmp(key, "sektion")
273 #endif
274 )) {
275 if ( ! strcmp(val, "0"))
276 *val = '\0';
277 set_query_attr(&req->q.sec, &val);
280 else if ( ! strcmp(key, "arch")) {
281 if ( ! strcmp(val, "default"))
282 *val = '\0';
283 set_query_attr(&req->q.arch, &val);
287 * The key must be freed in any case.
288 * The val may have been handed over to the query
289 * structure, in which case it is now NULL.
291 next:
292 free(key);
293 key = NULL;
294 free(val);
295 val = NULL;
297 if (*qs != '\0')
298 qs++;
301 /* Fall back to the default manpath. */
303 if (req->q.manpath == NULL)
304 req->q.manpath = mandoc_strdup(req->p[0]);
307 static void
308 http_putchar(char c)
311 if (isalnum((unsigned char)c)) {
312 putchar((unsigned char)c);
313 return;
314 } else if (' ' == c) {
315 putchar('+');
316 return;
318 printf("%%%.2x", c);
322 * HTTP-decode a string. The standard explanation is that this turns
323 * "%4e+foo" into "n foo" in the regular way. This is done in-place
324 * over the allocated string.
326 static int
327 http_decode(char *p)
329 char hex[3];
330 char *q;
331 int c;
333 hex[2] = '\0';
335 q = p;
336 for ( ; '\0' != *p; p++, q++) {
337 if ('%' == *p) {
338 if ('\0' == (hex[0] = *(p + 1)))
339 return(0);
340 if ('\0' == (hex[1] = *(p + 2)))
341 return(0);
342 if (1 != sscanf(hex, "%x", &c))
343 return(0);
344 if ('\0' == c)
345 return(0);
347 *q = (char)c;
348 p += 2;
349 } else
350 *q = '+' == *p ? ' ' : *p;
353 *q = '\0';
354 return(1);
357 static void
358 resp_begin_http(int code, const char *msg)
361 if (200 != code)
362 printf("Status: %d %s\r\n", code, msg);
364 printf("Content-Type: text/html; charset=utf-8\r\n"
365 "Cache-Control: no-cache\r\n"
366 "Pragma: no-cache\r\n"
367 "\r\n");
369 fflush(stdout);
372 static void
373 resp_begin_html(int code, const char *msg)
376 resp_begin_http(code, msg);
378 printf("<!DOCTYPE HTML PUBLIC "
379 " \"-//W3C//DTD HTML 4.01//EN\""
380 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
381 "<HTML>\n"
382 "<HEAD>\n"
383 "<META HTTP-EQUIV=\"Content-Type\""
384 " CONTENT=\"text/html; charset=utf-8\">\n"
385 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
386 " TYPE=\"text/css\" media=\"all\">\n"
387 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
388 " TYPE=\"text/css\" media=\"all\">\n"
389 "<TITLE>%s</TITLE>\n"
390 "</HEAD>\n"
391 "<BODY>\n"
392 "<!-- Begin page content. //-->\n",
393 CSS_DIR, CSS_DIR, CUSTOMIZE_TITLE);
396 static void
397 resp_end_html(void)
400 puts("</BODY>\n"
401 "</HTML>");
404 static void
405 resp_searchform(const struct req *req)
407 int i;
409 puts(CUSTOMIZE_BEGIN);
410 puts("<!-- Begin search form. //-->");
411 printf("<DIV ID=\"mancgi\">\n"
412 "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
413 "<FIELDSET>\n"
414 "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
415 scriptname);
417 /* Write query input box. */
419 printf( "<TABLE><TR><TD>\n"
420 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
421 if (NULL != req->q.query)
422 html_print(req->q.query);
423 puts("\" SIZE=\"40\">");
425 /* Write submission and reset buttons. */
427 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
428 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
430 /* Write show radio button */
432 printf( "</TD><TD>\n"
433 "<INPUT TYPE=\"radio\" ");
434 if (req->q.equal)
435 printf("CHECKED=\"checked\" ");
436 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
437 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
439 /* Write section selector. */
441 puts( "</TD></TR><TR><TD>\n"
442 "<SELECT NAME=\"sec\">");
443 for (i = 0; i < sec_MAX; i++) {
444 printf("<OPTION VALUE=\"%s\"", sec_numbers[i]);
445 if (NULL != req->q.sec &&
446 0 == strcmp(sec_numbers[i], req->q.sec))
447 printf(" SELECTED=\"selected\"");
448 printf(">%s</OPTION>\n", sec_names[i]);
450 puts("</SELECT>");
452 /* Write architecture selector. */
454 printf( "<SELECT NAME=\"arch\">\n"
455 "<OPTION VALUE=\"default\"");
456 if (NULL == req->q.arch)
457 printf(" SELECTED=\"selected\"");
458 puts(">All Architectures</OPTION>");
459 for (i = 0; i < arch_MAX; i++) {
460 printf("<OPTION VALUE=\"%s\"", arch_names[i]);
461 if (NULL != req->q.arch &&
462 0 == strcmp(arch_names[i], req->q.arch))
463 printf(" SELECTED=\"selected\"");
464 printf(">%s</OPTION>\n", arch_names[i]);
466 puts("</SELECT>");
468 /* Write manpath selector. */
470 if (req->psz > 1) {
471 puts("<SELECT NAME=\"manpath\">");
472 for (i = 0; i < (int)req->psz; i++) {
473 printf("<OPTION ");
474 if (NULL == req->q.manpath ? 0 == i :
475 0 == strcmp(req->q.manpath, req->p[i]))
476 printf("SELECTED=\"selected\" ");
477 printf("VALUE=\"");
478 html_print(req->p[i]);
479 printf("\">");
480 html_print(req->p[i]);
481 puts("</OPTION>");
483 puts("</SELECT>");
486 /* Write search radio button */
488 printf( "</TD><TD>\n"
489 "<INPUT TYPE=\"radio\" ");
490 if (0 == req->q.equal)
491 printf("CHECKED=\"checked\" ");
492 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
493 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
495 puts("</TD></TR></TABLE>\n"
496 "</FIELDSET>\n"
497 "</FORM>\n"
498 "</DIV>");
499 puts("<!-- End search form. //-->");
502 static int
503 validate_urifrag(const char *frag)
506 while ('\0' != *frag) {
507 if ( ! (isalnum((unsigned char)*frag) ||
508 '-' == *frag || '.' == *frag ||
509 '/' == *frag || '_' == *frag))
510 return(0);
511 frag++;
513 return(1);
516 static int
517 validate_manpath(const struct req *req, const char* manpath)
519 size_t i;
521 if ( ! strcmp(manpath, "mandoc"))
522 return(1);
524 for (i = 0; i < req->psz; i++)
525 if ( ! strcmp(manpath, req->p[i]))
526 return(1);
528 return(0);
531 static int
532 validate_filename(const char *file)
535 if ('.' == file[0] && '/' == file[1])
536 file += 2;
538 return ( ! (strstr(file, "../") || strstr(file, "/..") ||
539 (strncmp(file, "man", 3) && strncmp(file, "cat", 3))));
542 static void
543 pg_index(const struct req *req)
546 resp_begin_html(200, NULL);
547 resp_searchform(req);
548 printf("<P>\n"
549 "This web interface is documented in the\n"
550 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n"
551 "manual, and the\n"
552 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n"
553 "manual explains the query syntax.\n"
554 "</P>\n",
555 scriptname, scriptname);
556 resp_end_html();
559 static void
560 pg_noresult(const struct req *req, const char *msg)
562 resp_begin_html(200, NULL);
563 resp_searchform(req);
564 puts("<P>");
565 puts(msg);
566 puts("</P>");
567 resp_end_html();
570 static void
571 pg_error_badrequest(const char *msg)
574 resp_begin_html(400, "Bad Request");
575 puts("<H1>Bad Request</H1>\n"
576 "<P>\n");
577 puts(msg);
578 printf("Try again from the\n"
579 "<A HREF=\"%s\">main page</A>.\n"
580 "</P>", scriptname);
581 resp_end_html();
584 static void
585 pg_error_internal(void)
587 resp_begin_html(500, "Internal Server Error");
588 puts("<P>Internal Server Error</P>");
589 resp_end_html();
592 static void
593 pg_searchres(const struct req *req, struct manpage *r, size_t sz)
595 char *arch, *archend;
596 size_t i, iuse, isec;
597 int archprio, archpriouse;
598 int prio, priouse;
599 char sec;
601 for (i = 0; i < sz; i++) {
602 if (validate_filename(r[i].file))
603 continue;
604 fprintf(stderr, "invalid filename %s in %s database\n",
605 r[i].file, req->q.manpath);
606 pg_error_internal();
607 return;
610 if (1 == sz) {
612 * If we have just one result, then jump there now
613 * without any delay.
615 printf("Status: 303 See Other\r\n");
616 printf("Location: http://%s%s/%s/%s?",
617 HTTP_HOST, scriptname, req->q.manpath, r[0].file);
618 http_printquery(req, "&");
619 printf("\r\n"
620 "Content-Type: text/html; charset=utf-8\r\n"
621 "\r\n");
622 return;
625 resp_begin_html(200, NULL);
626 resp_searchform(req);
627 puts("<DIV CLASS=\"results\">");
628 puts("<TABLE>");
630 for (i = 0; i < sz; i++) {
631 printf("<TR>\n"
632 "<TD CLASS=\"title\">\n"
633 "<A HREF=\"%s/%s/%s?",
634 scriptname, req->q.manpath, r[i].file);
635 http_printquery(req, "&amp;");
636 printf("\">");
637 html_print(r[i].names);
638 printf("</A>\n"
639 "</TD>\n"
640 "<TD CLASS=\"desc\">");
641 html_print(r[i].output);
642 puts("</TD>\n"
643 "</TR>");
646 puts("</TABLE>\n"
647 "</DIV>");
650 * In man(1) mode, show one of the pages
651 * even if more than one is found.
654 if (req->q.equal) {
655 puts("<HR>");
656 iuse = 0;
657 priouse = 10;
658 archpriouse = 3;
659 for (i = 0; i < sz; i++) {
660 isec = strcspn(r[i].file, "123456789");
661 sec = r[i].file[isec];
662 if ('\0' == sec)
663 continue;
664 prio = sec_prios[sec - '1'];
665 if (NULL == req->q.arch) {
666 archprio =
667 (NULL == (arch = strchr(
668 r[i].file + isec, '/'))) ? 3 :
669 (NULL == (archend = strchr(
670 arch + 1, '/'))) ? 0 :
671 strncmp(arch, "amd64/",
672 archend - arch) ? 2 : 1;
673 if (archprio < archpriouse) {
674 archpriouse = archprio;
675 priouse = prio;
676 iuse = i;
677 continue;
679 if (archprio > archpriouse)
680 continue;
682 if (prio >= priouse)
683 continue;
684 priouse = prio;
685 iuse = i;
687 resp_show(req, r[iuse].file);
690 resp_end_html();
693 static void
694 catman(const struct req *req, const char *file)
696 FILE *f;
697 size_t len;
698 int i;
699 char *p;
700 int italic, bold;
702 if (NULL == (f = fopen(file, "r"))) {
703 puts("<P>You specified an invalid manual file.</P>");
704 return;
707 puts("<DIV CLASS=\"catman\">\n"
708 "<PRE>");
710 while (NULL != (p = fgetln(f, &len))) {
711 bold = italic = 0;
712 for (i = 0; i < (int)len - 1; i++) {
714 * This means that the catpage is out of state.
715 * Ignore it and keep going (although the
716 * catpage is bogus).
719 if ('\b' == p[i] || '\n' == p[i])
720 continue;
723 * Print a regular character.
724 * Close out any bold/italic scopes.
725 * If we're in back-space mode, make sure we'll
726 * have something to enter when we backspace.
729 if ('\b' != p[i + 1]) {
730 if (italic)
731 printf("</I>");
732 if (bold)
733 printf("</B>");
734 italic = bold = 0;
735 html_putchar(p[i]);
736 continue;
737 } else if (i + 2 >= (int)len)
738 continue;
740 /* Italic mode. */
742 if ('_' == p[i]) {
743 if (bold)
744 printf("</B>");
745 if ( ! italic)
746 printf("<I>");
747 bold = 0;
748 italic = 1;
749 i += 2;
750 html_putchar(p[i]);
751 continue;
755 * Handle funny behaviour troff-isms.
756 * These grok'd from the original man2html.c.
759 if (('+' == p[i] && 'o' == p[i + 2]) ||
760 ('o' == p[i] && '+' == p[i + 2]) ||
761 ('|' == p[i] && '=' == p[i + 2]) ||
762 ('=' == p[i] && '|' == p[i + 2]) ||
763 ('*' == p[i] && '=' == p[i + 2]) ||
764 ('=' == p[i] && '*' == p[i + 2]) ||
765 ('*' == p[i] && '|' == p[i + 2]) ||
766 ('|' == p[i] && '*' == p[i + 2])) {
767 if (italic)
768 printf("</I>");
769 if (bold)
770 printf("</B>");
771 italic = bold = 0;
772 putchar('*');
773 i += 2;
774 continue;
775 } else if (('|' == p[i] && '-' == p[i + 2]) ||
776 ('-' == p[i] && '|' == p[i + 1]) ||
777 ('+' == p[i] && '-' == p[i + 1]) ||
778 ('-' == p[i] && '+' == p[i + 1]) ||
779 ('+' == p[i] && '|' == p[i + 1]) ||
780 ('|' == p[i] && '+' == p[i + 1])) {
781 if (italic)
782 printf("</I>");
783 if (bold)
784 printf("</B>");
785 italic = bold = 0;
786 putchar('+');
787 i += 2;
788 continue;
791 /* Bold mode. */
793 if (italic)
794 printf("</I>");
795 if ( ! bold)
796 printf("<B>");
797 bold = 1;
798 italic = 0;
799 i += 2;
800 html_putchar(p[i]);
804 * Clean up the last character.
805 * We can get to a newline; don't print that.
808 if (italic)
809 printf("</I>");
810 if (bold)
811 printf("</B>");
813 if (i == (int)len - 1 && '\n' != p[i])
814 html_putchar(p[i]);
816 putchar('\n');
819 puts("</PRE>\n"
820 "</DIV>");
822 fclose(f);
825 static void
826 format(const struct req *req, const char *file)
828 struct mparse *mp;
829 struct mdoc *mdoc;
830 struct man *man;
831 void *vp;
832 char *opts;
833 enum mandoclevel rc;
834 int fd;
835 int usepath;
837 if (-1 == (fd = open(file, O_RDONLY, 0))) {
838 puts("<P>You specified an invalid manual file.</P>");
839 return;
842 mp = mparse_alloc(MPARSE_SO, MANDOCLEVEL_FATAL, NULL,
843 req->q.manpath);
844 rc = mparse_readfd(mp, fd, file);
845 close(fd);
847 if (rc >= MANDOCLEVEL_FATAL) {
848 fprintf(stderr, "fatal mandoc error: %s/%s\n",
849 req->q.manpath, file);
850 pg_error_internal();
851 return;
854 usepath = strcmp(req->q.manpath, req->p[0]);
855 mandoc_asprintf(&opts,
856 "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
857 scriptname,
858 req->q.arch ? "&arch=" : "",
859 req->q.arch ? req->q.arch : "",
860 usepath ? "&manpath=" : "",
861 usepath ? req->q.manpath : "");
863 mparse_result(mp, &mdoc, &man, NULL);
864 if (NULL == man && NULL == mdoc) {
865 fprintf(stderr, "fatal mandoc error: %s/%s\n",
866 req->q.manpath, file);
867 pg_error_internal();
868 mparse_free(mp);
869 return;
872 vp = html_alloc(opts);
874 if (NULL != mdoc)
875 html_mdoc(vp, mdoc);
876 else
877 html_man(vp, man);
879 html_free(vp);
880 mparse_free(mp);
881 free(opts);
884 static void
885 resp_show(const struct req *req, const char *file)
888 if ('.' == file[0] && '/' == file[1])
889 file += 2;
891 if ('c' == *file)
892 catman(req, file);
893 else
894 format(req, file);
897 static void
898 pg_show(struct req *req, const char *fullpath)
900 char *manpath;
901 const char *file;
903 if ((file = strchr(fullpath, '/')) == NULL) {
904 pg_error_badrequest(
905 "You did not specify a page to show.");
906 return;
908 manpath = mandoc_strndup(fullpath, file - fullpath);
909 file++;
911 if ( ! validate_manpath(req, manpath)) {
912 pg_error_badrequest(
913 "You specified an invalid manpath.");
914 free(manpath);
915 return;
919 * Begin by chdir()ing into the manpath.
920 * This way we can pick up the database files, which are
921 * relative to the manpath root.
924 if (chdir(manpath) == -1) {
925 fprintf(stderr, "chdir %s: %s\n",
926 manpath, strerror(errno));
927 pg_error_internal();
928 free(manpath);
929 return;
932 if (strcmp(manpath, "mandoc")) {
933 free(req->q.manpath);
934 req->q.manpath = manpath;
935 } else
936 free(manpath);
938 if ( ! validate_filename(file)) {
939 pg_error_badrequest(
940 "You specified an invalid manual file.");
941 return;
944 resp_begin_html(200, NULL);
945 resp_searchform(req);
946 resp_show(req, file);
947 resp_end_html();
950 static void
951 pg_search(const struct req *req)
953 struct mansearch search;
954 struct manpaths paths;
955 struct manpage *res;
956 char **cp;
957 const char *ep, *start;
958 size_t ressz;
959 int i, sz;
962 * Begin by chdir()ing into the root of the manpath.
963 * This way we can pick up the database files, which are
964 * relative to the manpath root.
967 if (-1 == (chdir(req->q.manpath))) {
968 fprintf(stderr, "chdir %s: %s\n",
969 req->q.manpath, strerror(errno));
970 pg_error_internal();
971 return;
974 search.arch = req->q.arch;
975 search.sec = req->q.sec;
976 search.deftype = req->q.equal ? TYPE_Nm : (TYPE_Nm | TYPE_Nd);
977 search.flags = req->q.equal ? MANSEARCH_MAN : 0;
979 paths.sz = 1;
980 paths.paths = mandoc_malloc(sizeof(char *));
981 paths.paths[0] = mandoc_strdup(".");
984 * Poor man's tokenisation: just break apart by spaces.
985 * Yes, this is half-ass. But it works for now.
988 ep = req->q.query;
989 while (ep && isspace((unsigned char)*ep))
990 ep++;
992 sz = 0;
993 cp = NULL;
994 while (ep && '\0' != *ep) {
995 cp = mandoc_reallocarray(cp, sz + 1, sizeof(char *));
996 start = ep;
997 while ('\0' != *ep && ! isspace((unsigned char)*ep))
998 ep++;
999 cp[sz] = mandoc_malloc((ep - start) + 1);
1000 memcpy(cp[sz], start, ep - start);
1001 cp[sz++][ep - start] = '\0';
1002 while (isspace((unsigned char)*ep))
1003 ep++;
1006 if (0 == mansearch(&search, &paths, sz, cp, "Nd", &res, &ressz))
1007 pg_noresult(req, "You entered an invalid query.");
1008 else if (0 == ressz)
1009 pg_noresult(req, "No results found.");
1010 else
1011 pg_searchres(req, res, ressz);
1013 for (i = 0; i < sz; i++)
1014 free(cp[i]);
1015 free(cp);
1017 for (i = 0; i < (int)ressz; i++) {
1018 free(res[i].file);
1019 free(res[i].names);
1020 free(res[i].output);
1022 free(res);
1024 free(paths.paths[0]);
1025 free(paths.paths);
1029 main(void)
1031 struct req req;
1032 const char *path;
1033 const char *querystring;
1034 int i;
1036 /* Scan our run-time environment. */
1038 if (NULL == (scriptname = getenv("SCRIPT_NAME")))
1039 scriptname = "";
1041 if ( ! validate_urifrag(scriptname)) {
1042 fprintf(stderr, "unsafe SCRIPT_NAME \"%s\"\n",
1043 scriptname);
1044 pg_error_internal();
1045 return(EXIT_FAILURE);
1049 * First we change directory into the MAN_DIR so that
1050 * subsequent scanning for manpath directories is rooted
1051 * relative to the same position.
1054 if (-1 == chdir(MAN_DIR)) {
1055 fprintf(stderr, "MAN_DIR: %s: %s\n",
1056 MAN_DIR, strerror(errno));
1057 pg_error_internal();
1058 return(EXIT_FAILURE);
1061 memset(&req, 0, sizeof(struct req));
1062 pathgen(&req);
1064 /* Next parse out the query string. */
1066 if (NULL != (querystring = getenv("QUERY_STRING")))
1067 http_parse(&req, querystring);
1069 if ( ! (NULL == req.q.manpath ||
1070 validate_manpath(&req, req.q.manpath))) {
1071 pg_error_badrequest(
1072 "You specified an invalid manpath.");
1073 return(EXIT_FAILURE);
1076 if ( ! (NULL == req.q.arch || validate_urifrag(req.q.arch))) {
1077 pg_error_badrequest(
1078 "You specified an invalid architecture.");
1079 return(EXIT_FAILURE);
1082 /* Dispatch to the three different pages. */
1084 path = getenv("PATH_INFO");
1085 if (NULL == path)
1086 path = "";
1087 else if ('/' == *path)
1088 path++;
1090 if ('\0' != *path)
1091 pg_show(&req, path);
1092 else if (NULL != req.q.query)
1093 pg_search(&req);
1094 else
1095 pg_index(&req);
1097 free(req.q.manpath);
1098 free(req.q.arch);
1099 free(req.q.sec);
1100 free(req.q.query);
1101 for (i = 0; i < (int)req.psz; i++)
1102 free(req.p[i]);
1103 free(req.p);
1104 return(EXIT_SUCCESS);
1108 * Scan for indexable paths.
1110 static void
1111 pathgen(struct req *req)
1113 FILE *fp;
1114 char *dp;
1115 size_t dpsz;
1117 if (NULL == (fp = fopen("manpath.conf", "r"))) {
1118 fprintf(stderr, "%s/manpath.conf: %s\n",
1119 MAN_DIR, strerror(errno));
1120 pg_error_internal();
1121 exit(EXIT_FAILURE);
1124 while (NULL != (dp = fgetln(fp, &dpsz))) {
1125 if ('\n' == dp[dpsz - 1])
1126 dpsz--;
1127 req->p = mandoc_realloc(req->p,
1128 (req->psz + 1) * sizeof(char *));
1129 dp = mandoc_strndup(dp, dpsz);
1130 if ( ! validate_urifrag(dp)) {
1131 fprintf(stderr, "%s/manpath.conf contains "
1132 "unsafe path \"%s\"\n", MAN_DIR, dp);
1133 pg_error_internal();
1134 exit(EXIT_FAILURE);
1136 if (NULL != strchr(dp, '/')) {
1137 fprintf(stderr, "%s/manpath.conf contains "
1138 "path with slash \"%s\"\n", MAN_DIR, dp);
1139 pg_error_internal();
1140 exit(EXIT_FAILURE);
1142 req->p[req->psz++] = dp;
1145 if ( req->p == NULL ) {
1146 fprintf(stderr, "%s/manpath.conf is empty\n", MAN_DIR);
1147 pg_error_internal();
1148 exit(EXIT_FAILURE);