]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
271f9b0a7ff4cc6d60661cc5fbc69f33122c6c76
1 /* $Id: cgi.c,v 1.178 2022/07/05 14:04:25 schwarze Exp $ */
3 * Copyright (c) 2014-2019, 2021, 2022 Ingo Schwarze <schwarze@usta.de>
4 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 * Implementation of the man.cgi(8) program.
23 #include <sys/types.h>
39 #include "mandoc_aux.h"
44 #include "mandoc_parse.h"
47 #include "mansearch.h"
51 * A query as passed to the search function.
54 char *manpath
; /* desired manual directory */
55 char *arch
; /* architecture */
56 char *sec
; /* manual section */
57 char *query
; /* unparsed query expression */
58 int equal
; /* match whole names, not substrings */
63 char **p
; /* array of available manpaths */
64 size_t psz
; /* number of available manpaths */
65 int isquery
; /* QUERY_STRING used, not PATH_INFO */
73 static void html_print(const char *);
74 static void html_putchar(char);
75 static int http_decode(char *);
76 static void http_encode(const char *);
77 static void parse_manpath_conf(struct req
*);
78 static void parse_path_info(struct req
*, const char *);
79 static void parse_query_string(struct req
*, const char *);
80 static void pg_error_badrequest(const char *);
81 static void pg_error_internal(void);
82 static void pg_index(const struct req
*);
83 static void pg_noresult(const struct req
*, int, const char *,
85 static void pg_redirect(const struct req
*, const char *);
86 static void pg_search(const struct req
*);
87 static void pg_searchres(const struct req
*,
88 struct manpage
*, size_t);
89 static void pg_show(struct req
*, const char *);
90 static int resp_begin_html(int, const char *, const char *);
91 static void resp_begin_http(int, const char *);
92 static void resp_catman(const struct req
*, const char *);
93 static int resp_copy(const char *, const char *);
94 static void resp_end_html(void);
95 static void resp_format(const struct req
*, const char *);
96 static void resp_searchform(const struct req
*, enum focus
);
97 static void resp_show(const struct req
*, const char *);
98 static void set_query_attr(char **, char **);
99 static int validate_arch(const char *);
100 static int validate_filename(const char *);
101 static int validate_manpath(const struct req
*, const char *);
102 static int validate_urifrag(const char *);
104 static const char *scriptname
= SCRIPT_NAME
;
106 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
107 static const char *const sec_numbers
[] = {
108 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
110 static const char *const sec_names
[] = {
112 "1 - General Commands",
114 "3 - Library Functions",
116 "4 - Device Drivers",
119 "7 - Miscellaneous Information",
120 "8 - System Manager\'s Manual",
121 "9 - Kernel Developer\'s Manual"
123 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
125 static const char *const arch_names
[] = {
126 "amd64", "alpha", "armv7", "arm64",
127 "hppa", "i386", "landisk", "loongson",
128 "luna88k", "macppc", "mips64", "octeon",
129 "powerpc64", "riscv64", "sparc64",
131 "amiga", "arc", "armish", "arm32",
132 "atari", "aviion", "beagle", "cats",
134 "ia64", "mac68k", "mvme68k", "mvme88k",
135 "mvmeppc", "palm", "pc532", "pegasos",
136 "pmax", "powerpc", "sgi", "socppc",
137 "solbourne", "sparc",
138 "sun3", "vax", "wgrisc", "x68k",
141 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
144 * Print a character, escaping HTML along the way.
145 * This will pass non-ASCII straight to output: be warned!
165 putchar((unsigned char)c
);
171 * Call through to html_putchar().
172 * Accepts NULL strings.
175 html_print(const char *p
)
185 * Transfer the responsibility for the allocated string *val
186 * to the query structure.
189 set_query_attr(char **attr
, char **val
)
202 * Parse the QUERY_STRING for key-value pairs
203 * and store the values into the query structure.
206 parse_query_string(struct req
*req
, const char *qs
)
212 req
->q
.manpath
= NULL
;
219 while (*qs
!= '\0') {
223 keysz
= strcspn(qs
, "=;&");
224 key
= mandoc_strndup(qs
, keysz
);
229 /* Parse one value. */
231 valsz
= strcspn(++qs
, ";&");
232 val
= mandoc_strndup(qs
, valsz
);
235 /* Decode and catch encoding errors. */
237 if ( ! (http_decode(key
) && http_decode(val
)))
240 /* Handle key-value pairs. */
242 if ( ! strcmp(key
, "query"))
243 set_query_attr(&req
->q
.query
, &val
);
245 else if ( ! strcmp(key
, "apropos"))
246 req
->q
.equal
= !strcmp(val
, "0");
248 else if ( ! strcmp(key
, "manpath")) {
250 if ( ! strncmp(val
, "OpenBSD ", 8)) {
256 set_query_attr(&req
->q
.manpath
, &val
);
259 else if ( ! (strcmp(key
, "sec")
261 && strcmp(key
, "sektion")
264 if ( ! strcmp(val
, "0"))
266 set_query_attr(&req
->q
.sec
, &val
);
269 else if ( ! strcmp(key
, "arch")) {
270 if ( ! strcmp(val
, "default"))
272 set_query_attr(&req
->q
.arch
, &val
);
276 * The key must be freed in any case.
277 * The val may have been handed over to the query
278 * structure, in which case it is now NULL.
292 * HTTP-decode a string. The standard explanation is that this turns
293 * "%4e+foo" into "n foo" in the regular way. This is done in-place
294 * over the allocated string.
306 for ( ; '\0' != *p
; p
++, q
++) {
308 if ('\0' == (hex
[0] = *(p
+ 1)))
310 if ('\0' == (hex
[1] = *(p
+ 2)))
312 if (1 != sscanf(hex
, "%x", &c
))
320 *q
= '+' == *p
? ' ' : *p
;
328 http_encode(const char *p
)
330 for (; *p
!= '\0'; p
++) {
331 if (isalnum((unsigned char)*p
) == 0 &&
332 strchr("-._~", *p
) == NULL
)
333 printf("%%%2.2X", (unsigned char)*p
);
340 resp_begin_http(int code
, const char *msg
)
344 printf("Status: %d %s\r\n", code
, msg
);
346 printf("Content-Type: text/html; charset=utf-8\r\n"
347 "Cache-Control: no-cache\r\n"
348 "Content-Security-Policy: default-src 'none'; "
349 "style-src 'self' 'unsafe-inline'\r\n"
350 "Pragma: no-cache\r\n"
357 resp_copy(const char *element
, const char *filename
)
363 if ((fd
= open(filename
, O_RDONLY
)) == -1)
367 printf("<%s>\n", element
);
369 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
370 write(STDOUT_FILENO
, buf
, sz
);
376 resp_begin_html(int code
, const char *msg
, const char *file
)
378 const char *name
, *sec
, *cp
;
381 resp_begin_http(code
, msg
);
383 printf("<!DOCTYPE html>\n"
386 " <meta charset=\"UTF-8\"/>\n"
387 " <meta name=\"viewport\""
388 " content=\"width=device-width, initial-scale=1.0\">\n"
389 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
390 " type=\"text/css\" media=\"all\">\n"
394 cp
= strrchr(file
, '/');
395 name
= cp
== NULL
? file
: cp
+ 1;
396 cp
= strrchr(name
, '.');
397 namesz
= cp
== NULL
? strlen(name
) : cp
- name
;
399 if (cp
!= NULL
&& cp
[1] != '0') {
402 } else if (name
- file
> 1) {
403 for (cp
= name
- 2; cp
>= file
; cp
--) {
404 if (*cp
< '1' || *cp
> '9')
407 secsz
= name
- cp
- 1;
411 printf("%.*s", namesz
, name
);
413 printf("(%.*s)", secsz
, sec
);
414 fputs(" - ", stdout
);
416 printf("%s</title>\n"
421 return resp_copy("header", MAN_DIR
"/header.html");
427 if (resp_copy("footer", MAN_DIR
"/footer.html"))
435 resp_searchform(const struct req
*req
, enum focus focus
)
439 printf("<form role=\"search\" action=\"/%s\" method=\"get\" "
440 "autocomplete=\"off\" autocapitalize=\"none\">\n"
442 " <legend>Manual Page Search Parameters</legend>\n",
445 /* Write query input box. */
447 printf(" <input type=\"search\" name=\"query\" value=\"");
448 if (req
->q
.query
!= NULL
)
449 html_print(req
->q
.query
);
450 printf( "\" size=\"40\"");
451 if (focus
== FOCUS_QUERY
)
452 printf(" autofocus");
455 /* Write submission buttons. */
457 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
459 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
463 /* Write section selector. */
465 puts(" <select name=\"sec\" aria-label=\"manual section\">");
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
))
470 printf(" selected=\"selected\"");
471 printf(">%s</option>\n", sec_names
[i
]);
475 /* Write architecture selector. */
477 printf( " <select name=\"arch\" aria-label=\"CPU architecture\">\n"
478 " <option value=\"default\"");
479 if (NULL
== req
->q
.arch
)
480 printf(" selected=\"selected\"");
481 puts(">All Architectures</option>");
482 for (i
= 0; i
< arch_MAX
; i
++) {
484 if (NULL
!= req
->q
.arch
&&
485 0 == strcmp(arch_names
[i
], req
->q
.arch
))
486 printf(" selected=\"selected\"");
487 printf(">%s</option>\n", arch_names
[i
]);
491 /* Write manpath selector. */
494 puts(" <select name=\"manpath\">");
495 for (i
= 0; i
< (int)req
->psz
; i
++) {
497 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
498 printf(" selected=\"selected\"");
500 html_print(req
->p
[i
]);
506 puts(" </fieldset>\n"
511 validate_urifrag(const char *frag
)
514 while ('\0' != *frag
) {
515 if ( ! (isalnum((unsigned char)*frag
) ||
516 '-' == *frag
|| '.' == *frag
||
517 '/' == *frag
|| '_' == *frag
))
525 validate_manpath(const struct req
*req
, const char* manpath
)
529 for (i
= 0; i
< req
->psz
; i
++)
530 if ( ! strcmp(manpath
, req
->p
[i
]))
537 validate_arch(const char *arch
)
541 for (i
= 0; i
< arch_MAX
; i
++)
542 if (strcmp(arch
, arch_names
[i
]) == 0)
549 validate_filename(const char *file
)
552 if ('.' == file
[0] && '/' == file
[1])
555 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
556 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
560 pg_index(const struct req
*req
)
562 if (resp_begin_html(200, NULL
, NULL
) == 0)
564 resp_searchform(req
, FOCUS_QUERY
);
567 "<p role=\"doc-notice\" aria-label=\"usage\">\n"
568 "This web interface is documented in the\n"
569 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
570 " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
572 "<a class=\"Xr\" href=\"/%s%sapropos.1\""
573 " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
574 "manual explains the query syntax.\n"
577 scriptname
, *scriptname
== '\0' ? "" : "/",
578 scriptname
, *scriptname
== '\0' ? "" : "/");
583 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
584 const char *user_msg
)
586 if (resp_begin_html(code
, http_msg
, NULL
) == 0)
588 resp_searchform(req
, FOCUS_QUERY
);
591 puts("<p role=\"doc-notice\" aria-label=\"no result\">");
599 pg_error_badrequest(const char *msg
)
601 if (resp_begin_html(400, "Bad Request", NULL
))
604 "<h1>Bad Request</h1>\n"
605 "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
607 printf("Try again from the\n"
608 "<a href=\"/%s\">main page</a>.\n"
610 "</main>\n", scriptname
);
615 pg_error_internal(void)
617 if (resp_begin_html(500, "Internal Server Error", NULL
))
619 puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
624 pg_redirect(const struct req
*req
, const char *name
)
626 printf("Status: 303 See Other\r\n"
628 if (*scriptname
!= '\0')
629 printf("%s/", scriptname
);
630 if (strcmp(req
->q
.manpath
, req
->p
[0]))
631 printf("%s/", req
->q
.manpath
);
632 if (req
->q
.arch
!= NULL
)
633 printf("%s/", req
->q
.arch
);
635 if (req
->q
.sec
!= NULL
) {
637 http_encode(req
->q
.sec
);
639 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
643 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
645 char *arch
, *archend
;
648 int archprio
, archpriouse
;
652 for (i
= 0; i
< sz
; i
++) {
653 if (validate_filename(r
[i
].file
))
655 warnx("invalid filename %s in %s database",
656 r
[i
].file
, req
->q
.manpath
);
661 if (req
->isquery
&& sz
== 1) {
663 * If we have just one result, then jump there now
666 printf("Status: 303 See Other\r\n"
668 if (*scriptname
!= '\0')
669 printf("%s/", scriptname
);
670 if (strcmp(req
->q
.manpath
, req
->p
[0]))
671 printf("%s/", req
->q
.manpath
);
673 "Content-Type: text/html; charset=utf-8\r\n\r\n",
679 * In man(1) mode, show one of the pages
680 * even if more than one is found.
684 if (req
->q
.equal
|| sz
== 1) {
687 for (i
= 0; i
< sz
; i
++) {
689 sec
+= strcspn(sec
, "123456789");
692 prio
= sec_prios
[sec
[0] - '1'];
695 if (req
->q
.arch
== NULL
) {
697 ((arch
= strchr(sec
+ 1, '/'))
699 ((archend
= strchr(arch
+ 1, '/'))
701 strncmp(arch
, "amd64/",
702 archend
- arch
) ? 2 : 1;
703 if (archprio
< archpriouse
) {
704 archpriouse
= archprio
;
709 if (archprio
> archpriouse
)
717 have_header
= resp_begin_html(200, NULL
, r
[iuse
].file
);
719 have_header
= resp_begin_html(200, NULL
, NULL
);
721 if (have_header
== 0)
724 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
729 puts("<table class=\"results\">");
730 for (i
= 0; i
< sz
; i
++) {
733 "<a class=\"Xr\" href=\"/");
734 if (*scriptname
!= '\0')
735 printf("%s/", scriptname
);
736 if (strcmp(req
->q
.manpath
, req
->p
[0]))
737 printf("%s/", req
->q
.manpath
);
738 printf("%s\">", r
[i
].file
);
739 html_print(r
[i
].names
);
741 " <td><span class=\"Nd\">");
742 html_print(r
[i
].output
);
743 puts("</span></td>\n"
750 if (req
->q
.equal
|| sz
== 1) {
752 resp_show(req
, r
[iuse
].file
);
759 resp_catman(const struct req
*req
, const char *file
)
768 if ((f
= fopen(file
, "r")) == NULL
) {
769 puts("<p role=\"doc-notice\">\n"
770 " You specified an invalid manual file.\n"
775 puts("<div class=\"catman\">\n"
781 while ((len
= getline(&p
, &sz
, f
)) != -1) {
783 for (i
= 0; i
< len
- 1; i
++) {
785 * This means that the catpage is out of state.
786 * Ignore it and keep going (although the
790 if ('\b' == p
[i
] || '\n' == p
[i
])
794 * Print a regular character.
795 * Close out any bold/italic scopes.
796 * If we're in back-space mode, make sure we'll
797 * have something to enter when we backspace.
800 if ('\b' != p
[i
+ 1]) {
808 } else if (i
+ 2 >= len
)
826 * Handle funny behaviour troff-isms.
827 * These grok'd from the original man2html.c.
830 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
831 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
832 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
833 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
834 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
835 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
836 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
837 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
846 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
847 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
848 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
849 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
850 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
851 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
875 * Clean up the last character.
876 * We can get to a newline; don't print that.
884 if (i
== len
- 1 && p
[i
] != '\n')
898 resp_format(const struct req
*req
, const char *file
)
900 struct manoutput conf
;
902 struct roff_meta
*meta
;
907 if (-1 == (fd
= open(file
, O_RDONLY
))) {
908 puts("<p role=\"doc-notice\">\n"
909 " You specified an invalid manual file.\n"
915 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
916 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
917 mparse_readfd(mp
, fd
, file
);
919 meta
= mparse_result(mp
);
921 memset(&conf
, 0, sizeof(conf
));
923 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
924 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
925 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
926 scriptname
, *scriptname
== '\0' ? "" : "/",
927 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
929 vp
= html_alloc(&conf
);
930 if (meta
->macroset
== MACROSET_MDOC
)
943 resp_show(const struct req
*req
, const char *file
)
946 if ('.' == file
[0] && '/' == file
[1])
950 resp_catman(req
, file
);
952 resp_format(req
, file
);
956 pg_show(struct req
*req
, const char *fullpath
)
961 if ((file
= strchr(fullpath
, '/')) == NULL
) {
963 "You did not specify a page to show.");
966 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
969 if ( ! validate_manpath(req
, manpath
)) {
971 "You specified an invalid manpath.");
977 * Begin by chdir()ing into the manpath.
978 * This way we can pick up the database files, which are
979 * relative to the manpath root.
982 if (chdir(manpath
) == -1) {
983 warn("chdir %s", manpath
);
990 if ( ! validate_filename(file
)) {
992 "You specified an invalid manual file.");
996 if (resp_begin_html(200, NULL
, file
) == 0)
998 resp_searchform(req
, FOCUS_NONE
);
1000 resp_show(req
, file
);
1005 pg_search(const struct req
*req
)
1007 struct mansearch search
;
1008 struct manpaths paths
;
1009 struct manpage
*res
;
1011 char *query
, *rp
, *wp
;
1016 * Begin by chdir()ing into the root of the manpath.
1017 * This way we can pick up the database files, which are
1018 * relative to the manpath root.
1021 if (chdir(req
->q
.manpath
) == -1) {
1022 warn("chdir %s", req
->q
.manpath
);
1023 pg_error_internal();
1027 search
.arch
= req
->q
.arch
;
1028 search
.sec
= req
->q
.sec
;
1029 search
.outkey
= "Nd";
1030 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
1031 search
.firstmatch
= 1;
1034 paths
.paths
= mandoc_malloc(sizeof(char *));
1035 paths
.paths
[0] = mandoc_strdup(".");
1038 * Break apart at spaces with backslash-escaping.
1043 rp
= query
= mandoc_strdup(req
->q
.query
);
1045 while (isspace((unsigned char)*rp
))
1049 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1050 argv
[argc
++] = wp
= rp
;
1052 if (isspace((unsigned char)*rp
)) {
1057 if (rp
[0] == '\\' && rp
[1] != '\0')
1070 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1071 pg_redirect(req
, argv
[0]);
1072 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1073 pg_noresult(req
, 400, "Bad Request",
1074 "You entered an invalid query.");
1075 else if (ressz
== 0)
1076 pg_noresult(req
, 404, "Not Found", "No results found.");
1078 pg_searchres(req
, res
, ressz
);
1081 mansearch_free(res
, ressz
);
1082 free(paths
.paths
[0]);
1090 struct itimerval itimer
;
1092 const char *querystring
;
1097 * The "rpath" pledge could be revoked after mparse_readfd()
1098 * if the file desciptor to "/footer.html" would be opened
1099 * up front, but it's probably not worth the complication
1100 * of the code it would cause: it would require scattering
1101 * pledge() calls in multiple low-level resp_*() functions.
1104 if (pledge("stdio rpath", NULL
) == -1) {
1106 pg_error_internal();
1107 return EXIT_FAILURE
;
1111 /* Poor man's ReDoS mitigation. */
1113 itimer
.it_value
.tv_sec
= 2;
1114 itimer
.it_value
.tv_usec
= 0;
1115 itimer
.it_interval
.tv_sec
= 2;
1116 itimer
.it_interval
.tv_usec
= 0;
1117 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1119 pg_error_internal();
1120 return EXIT_FAILURE
;
1124 * First we change directory into the MAN_DIR so that
1125 * subsequent scanning for manpath directories is rooted
1126 * relative to the same position.
1129 if (chdir(MAN_DIR
) == -1) {
1130 warn("MAN_DIR: %s", MAN_DIR
);
1131 pg_error_internal();
1132 return EXIT_FAILURE
;
1135 memset(&req
, 0, sizeof(struct req
));
1137 parse_manpath_conf(&req
);
1139 /* Parse the path info and the query string. */
1141 if ((path
= getenv("PATH_INFO")) == NULL
)
1143 else if (*path
== '/')
1146 if (*path
!= '\0') {
1147 parse_path_info(&req
, path
);
1148 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1149 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1151 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1152 parse_query_string(&req
, querystring
);
1154 /* Validate parsed data and add defaults. */
1156 if (req
.q
.manpath
== NULL
)
1157 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1158 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1159 pg_error_badrequest(
1160 "You specified an invalid manpath.");
1161 return EXIT_FAILURE
;
1164 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1165 pg_error_badrequest(
1166 "You specified an invalid architecture.");
1167 return EXIT_FAILURE
;
1170 /* Dispatch to the three different pages. */
1173 pg_show(&req
, path
);
1174 else if (NULL
!= req
.q
.query
)
1179 free(req
.q
.manpath
);
1183 for (i
= 0; i
< (int)req
.psz
; i
++)
1186 return EXIT_SUCCESS
;
1190 * Translate PATH_INFO to a query.
1193 parse_path_info(struct req
*req
, const char *path
)
1195 const char *name
, *sec
, *end
;
1199 req
->q
.manpath
= NULL
;
1202 /* Mandatory manual page name. */
1203 if ((name
= strrchr(path
, '/')) == NULL
)
1208 /* Optional trailing section. */
1209 sec
= strrchr(name
, '.');
1210 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1211 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1212 req
->q
.sec
= mandoc_strdup(sec
);
1214 req
->q
.query
= mandoc_strdup(name
);
1218 /* Handle the case of name[.section] only. */
1222 /* Optional manpath. */
1223 end
= strchr(path
, '/');
1224 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1225 if (validate_manpath(req
, req
->q
.manpath
)) {
1230 free(req
->q
.manpath
);
1231 req
->q
.manpath
= NULL
;
1234 /* Optional section. */
1235 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1237 end
= strchr(path
, '/');
1239 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1245 /* Optional architecture. */
1246 end
= strchr(path
, '/');
1247 if (end
+ 1 != name
) {
1248 pg_error_badrequest(
1249 "You specified too many directory components.");
1252 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1253 if (validate_arch(req
->q
.arch
) == 0) {
1254 pg_error_badrequest(
1255 "You specified an invalid directory component.");
1261 * Scan for indexable paths.
1264 parse_manpath_conf(struct req
*req
)
1271 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1272 warn("%s/manpath.conf", MAN_DIR
);
1273 pg_error_internal();
1280 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1281 if (dp
[len
- 1] == '\n')
1283 req
->p
= mandoc_realloc(req
->p
,
1284 (req
->psz
+ 1) * sizeof(char *));
1285 if ( ! validate_urifrag(dp
)) {
1286 warnx("%s/manpath.conf contains "
1287 "unsafe path \"%s\"", MAN_DIR
, dp
);
1288 pg_error_internal();
1291 if (strchr(dp
, '/') != NULL
) {
1292 warnx("%s/manpath.conf contains "
1293 "path with slash \"%s\"", MAN_DIR
, dp
);
1294 pg_error_internal();
1297 req
->p
[req
->psz
++] = dp
;
1303 if (req
->p
== NULL
) {
1304 warnx("%s/manpath.conf is empty", MAN_DIR
);
1305 pg_error_internal();