]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.180 2022/07/06 17:21:04 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(" <label>Search query:\n"
448 " <input type=\"search\" name=\"query\" value=\"");
449 if (req
->q
.query
!= NULL
)
450 html_print(req
->q
.query
);
451 printf("\" size=\"40\"");
452 if (focus
== FOCUS_QUERY
)
453 printf(" autofocus");
454 puts(">\n </label>");
456 /* Write submission buttons. */
458 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
460 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
464 /* Write section selector. */
466 puts(" <select name=\"sec\" aria-label=\"Manual section\">");
467 for (i
= 0; i
< sec_MAX
; i
++) {
468 printf(" <option value=\"%s\"", sec_numbers
[i
]);
469 if (NULL
!= req
->q
.sec
&&
470 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
471 printf(" selected=\"selected\"");
472 printf(">%s</option>\n", sec_names
[i
]);
476 /* Write architecture selector. */
478 printf( " <select name=\"arch\" aria-label=\"CPU architecture\">\n"
479 " <option value=\"default\"");
480 if (NULL
== req
->q
.arch
)
481 printf(" selected=\"selected\"");
482 puts(">All Architectures</option>");
483 for (i
= 0; i
< arch_MAX
; i
++) {
485 if (NULL
!= req
->q
.arch
&&
486 0 == strcmp(arch_names
[i
], req
->q
.arch
))
487 printf(" selected=\"selected\"");
488 printf(">%s</option>\n", arch_names
[i
]);
492 /* Write manpath selector. */
495 puts(" <select name=\"manpath\""
496 " aria-label=\"Manual path\">");
497 for (i
= 0; i
< (int)req
->psz
; i
++) {
499 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
500 printf(" selected=\"selected\"");
502 html_print(req
->p
[i
]);
508 puts(" </fieldset>\n"
513 validate_urifrag(const char *frag
)
516 while ('\0' != *frag
) {
517 if ( ! (isalnum((unsigned char)*frag
) ||
518 '-' == *frag
|| '.' == *frag
||
519 '/' == *frag
|| '_' == *frag
))
527 validate_manpath(const struct req
*req
, const char* manpath
)
531 for (i
= 0; i
< req
->psz
; i
++)
532 if ( ! strcmp(manpath
, req
->p
[i
]))
539 validate_arch(const char *arch
)
543 for (i
= 0; i
< arch_MAX
; i
++)
544 if (strcmp(arch
, arch_names
[i
]) == 0)
551 validate_filename(const char *file
)
554 if ('.' == file
[0] && '/' == file
[1])
557 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
558 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
562 pg_index(const struct req
*req
)
564 if (resp_begin_html(200, NULL
, NULL
) == 0)
566 resp_searchform(req
, FOCUS_QUERY
);
569 "<p role=\"doc-notice\" aria-label=\"Usage\">\n"
570 "This web interface is documented in the\n"
571 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
572 " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
574 "<a class=\"Xr\" href=\"/%s%sapropos.1\""
575 " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
576 "manual explains the query syntax.\n"
579 scriptname
, *scriptname
== '\0' ? "" : "/",
580 scriptname
, *scriptname
== '\0' ? "" : "/");
585 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
586 const char *user_msg
)
588 if (resp_begin_html(code
, http_msg
, NULL
) == 0)
590 resp_searchform(req
, FOCUS_QUERY
);
593 puts("<p role=\"doc-notice\" aria-label=\"No result\">");
601 pg_error_badrequest(const char *msg
)
603 if (resp_begin_html(400, "Bad Request", NULL
))
606 "<h1>Bad Request</h1>\n"
607 "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
609 printf("Try again from the\n"
610 "<a href=\"/%s\">main page</a>.\n"
612 "</main>\n", scriptname
);
617 pg_error_internal(void)
619 if (resp_begin_html(500, "Internal Server Error", NULL
))
621 puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
626 pg_redirect(const struct req
*req
, const char *name
)
628 printf("Status: 303 See Other\r\n"
630 if (*scriptname
!= '\0')
631 printf("%s/", scriptname
);
632 if (strcmp(req
->q
.manpath
, req
->p
[0]))
633 printf("%s/", req
->q
.manpath
);
634 if (req
->q
.arch
!= NULL
)
635 printf("%s/", req
->q
.arch
);
637 if (req
->q
.sec
!= NULL
) {
639 http_encode(req
->q
.sec
);
641 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
645 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
647 char *arch
, *archend
;
650 int archprio
, archpriouse
;
654 for (i
= 0; i
< sz
; i
++) {
655 if (validate_filename(r
[i
].file
))
657 warnx("invalid filename %s in %s database",
658 r
[i
].file
, req
->q
.manpath
);
663 if (req
->isquery
&& sz
== 1) {
665 * If we have just one result, then jump there now
668 printf("Status: 303 See Other\r\n"
670 if (*scriptname
!= '\0')
671 printf("%s/", scriptname
);
672 if (strcmp(req
->q
.manpath
, req
->p
[0]))
673 printf("%s/", req
->q
.manpath
);
675 "Content-Type: text/html; charset=utf-8\r\n\r\n",
681 * In man(1) mode, show one of the pages
682 * even if more than one is found.
686 if (req
->q
.equal
|| sz
== 1) {
689 for (i
= 0; i
< sz
; i
++) {
691 sec
+= strcspn(sec
, "123456789");
694 prio
= sec_prios
[sec
[0] - '1'];
697 if (req
->q
.arch
== NULL
) {
699 ((arch
= strchr(sec
+ 1, '/'))
701 ((archend
= strchr(arch
+ 1, '/'))
703 strncmp(arch
, "amd64/",
704 archend
- arch
) ? 2 : 1;
705 if (archprio
< archpriouse
) {
706 archpriouse
= archprio
;
711 if (archprio
> archpriouse
)
719 have_header
= resp_begin_html(200, NULL
, r
[iuse
].file
);
721 have_header
= resp_begin_html(200, NULL
, NULL
);
723 if (have_header
== 0)
726 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
731 puts("<table class=\"results\">");
732 for (i
= 0; i
< sz
; i
++) {
735 "<a class=\"Xr\" href=\"/");
736 if (*scriptname
!= '\0')
737 printf("%s/", scriptname
);
738 if (strcmp(req
->q
.manpath
, req
->p
[0]))
739 printf("%s/", req
->q
.manpath
);
740 printf("%s\">", r
[i
].file
);
741 html_print(r
[i
].names
);
743 " <td><span class=\"Nd\">");
744 html_print(r
[i
].output
);
745 puts("</span></td>\n"
752 if (req
->q
.equal
|| sz
== 1) {
754 resp_show(req
, r
[iuse
].file
);
761 resp_catman(const struct req
*req
, const char *file
)
770 if ((f
= fopen(file
, "r")) == NULL
) {
771 puts("<p role=\"doc-notice\">\n"
772 " You specified an invalid manual file.\n"
777 puts("<div class=\"catman\">\n"
783 while ((len
= getline(&p
, &sz
, f
)) != -1) {
785 for (i
= 0; i
< len
- 1; i
++) {
787 * This means that the catpage is out of state.
788 * Ignore it and keep going (although the
792 if ('\b' == p
[i
] || '\n' == p
[i
])
796 * Print a regular character.
797 * Close out any bold/italic scopes.
798 * If we're in back-space mode, make sure we'll
799 * have something to enter when we backspace.
802 if ('\b' != p
[i
+ 1]) {
810 } else if (i
+ 2 >= len
)
828 * Handle funny behaviour troff-isms.
829 * These grok'd from the original man2html.c.
832 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
833 ('o' == 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]) ||
838 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
839 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
848 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
849 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
850 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
851 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
852 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
853 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
877 * Clean up the last character.
878 * We can get to a newline; don't print that.
886 if (i
== len
- 1 && p
[i
] != '\n')
900 resp_format(const struct req
*req
, const char *file
)
902 struct manoutput conf
;
904 struct roff_meta
*meta
;
909 if (-1 == (fd
= open(file
, O_RDONLY
))) {
910 puts("<p role=\"doc-notice\">\n"
911 " You specified an invalid manual file.\n"
917 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
918 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
919 mparse_readfd(mp
, fd
, file
);
921 meta
= mparse_result(mp
);
923 memset(&conf
, 0, sizeof(conf
));
925 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
926 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
927 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
928 scriptname
, *scriptname
== '\0' ? "" : "/",
929 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
931 vp
= html_alloc(&conf
);
932 if (meta
->macroset
== MACROSET_MDOC
)
945 resp_show(const struct req
*req
, const char *file
)
948 if ('.' == file
[0] && '/' == file
[1])
952 resp_catman(req
, file
);
954 resp_format(req
, file
);
958 pg_show(struct req
*req
, const char *fullpath
)
963 if ((file
= strchr(fullpath
, '/')) == NULL
) {
965 "You did not specify a page to show.");
968 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
971 if ( ! validate_manpath(req
, manpath
)) {
973 "You specified an invalid manpath.");
979 * Begin by chdir()ing into the manpath.
980 * This way we can pick up the database files, which are
981 * relative to the manpath root.
984 if (chdir(manpath
) == -1) {
985 warn("chdir %s", manpath
);
992 if ( ! validate_filename(file
)) {
994 "You specified an invalid manual file.");
998 if (resp_begin_html(200, NULL
, file
) == 0)
1000 resp_searchform(req
, FOCUS_NONE
);
1002 resp_show(req
, file
);
1007 pg_search(const struct req
*req
)
1009 struct mansearch search
;
1010 struct manpaths paths
;
1011 struct manpage
*res
;
1013 char *query
, *rp
, *wp
;
1018 * Begin by chdir()ing into the root of the manpath.
1019 * This way we can pick up the database files, which are
1020 * relative to the manpath root.
1023 if (chdir(req
->q
.manpath
) == -1) {
1024 warn("chdir %s", req
->q
.manpath
);
1025 pg_error_internal();
1029 search
.arch
= req
->q
.arch
;
1030 search
.sec
= req
->q
.sec
;
1031 search
.outkey
= "Nd";
1032 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
1033 search
.firstmatch
= 1;
1036 paths
.paths
= mandoc_malloc(sizeof(char *));
1037 paths
.paths
[0] = mandoc_strdup(".");
1040 * Break apart at spaces with backslash-escaping.
1045 rp
= query
= mandoc_strdup(req
->q
.query
);
1047 while (isspace((unsigned char)*rp
))
1051 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1052 argv
[argc
++] = wp
= rp
;
1054 if (isspace((unsigned char)*rp
)) {
1059 if (rp
[0] == '\\' && rp
[1] != '\0')
1072 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1073 pg_redirect(req
, argv
[0]);
1074 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1075 pg_noresult(req
, 400, "Bad Request",
1076 "You entered an invalid query.");
1077 else if (ressz
== 0)
1078 pg_noresult(req
, 404, "Not Found", "No results found.");
1080 pg_searchres(req
, res
, ressz
);
1083 mansearch_free(res
, ressz
);
1084 free(paths
.paths
[0]);
1092 struct itimerval itimer
;
1094 const char *querystring
;
1099 * The "rpath" pledge could be revoked after mparse_readfd()
1100 * if the file desciptor to "/footer.html" would be opened
1101 * up front, but it's probably not worth the complication
1102 * of the code it would cause: it would require scattering
1103 * pledge() calls in multiple low-level resp_*() functions.
1106 if (pledge("stdio rpath", NULL
) == -1) {
1108 pg_error_internal();
1109 return EXIT_FAILURE
;
1113 /* Poor man's ReDoS mitigation. */
1115 itimer
.it_value
.tv_sec
= 2;
1116 itimer
.it_value
.tv_usec
= 0;
1117 itimer
.it_interval
.tv_sec
= 2;
1118 itimer
.it_interval
.tv_usec
= 0;
1119 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1121 pg_error_internal();
1122 return EXIT_FAILURE
;
1126 * First we change directory into the MAN_DIR so that
1127 * subsequent scanning for manpath directories is rooted
1128 * relative to the same position.
1131 if (chdir(MAN_DIR
) == -1) {
1132 warn("MAN_DIR: %s", MAN_DIR
);
1133 pg_error_internal();
1134 return EXIT_FAILURE
;
1137 memset(&req
, 0, sizeof(struct req
));
1139 parse_manpath_conf(&req
);
1141 /* Parse the path info and the query string. */
1143 if ((path
= getenv("PATH_INFO")) == NULL
)
1145 else if (*path
== '/')
1148 if (*path
!= '\0') {
1149 parse_path_info(&req
, path
);
1150 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1151 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1153 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1154 parse_query_string(&req
, querystring
);
1156 /* Validate parsed data and add defaults. */
1158 if (req
.q
.manpath
== NULL
)
1159 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1160 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1161 pg_error_badrequest(
1162 "You specified an invalid manpath.");
1163 return EXIT_FAILURE
;
1166 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1167 pg_error_badrequest(
1168 "You specified an invalid architecture.");
1169 return EXIT_FAILURE
;
1172 /* Dispatch to the three different pages. */
1175 pg_show(&req
, path
);
1176 else if (NULL
!= req
.q
.query
)
1181 free(req
.q
.manpath
);
1185 for (i
= 0; i
< (int)req
.psz
; i
++)
1188 return EXIT_SUCCESS
;
1192 * Translate PATH_INFO to a query.
1195 parse_path_info(struct req
*req
, const char *path
)
1197 const char *name
, *sec
, *end
;
1201 req
->q
.manpath
= NULL
;
1204 /* Mandatory manual page name. */
1205 if ((name
= strrchr(path
, '/')) == NULL
)
1210 /* Optional trailing section. */
1211 sec
= strrchr(name
, '.');
1212 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1213 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1214 req
->q
.sec
= mandoc_strdup(sec
);
1216 req
->q
.query
= mandoc_strdup(name
);
1220 /* Handle the case of name[.section] only. */
1224 /* Optional manpath. */
1225 end
= strchr(path
, '/');
1226 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1227 if (validate_manpath(req
, req
->q
.manpath
)) {
1232 free(req
->q
.manpath
);
1233 req
->q
.manpath
= NULL
;
1236 /* Optional section. */
1237 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1239 end
= strchr(path
, '/');
1241 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1247 /* Optional architecture. */
1248 end
= strchr(path
, '/');
1249 if (end
+ 1 != name
) {
1250 pg_error_badrequest(
1251 "You specified too many directory components.");
1254 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1255 if (validate_arch(req
->q
.arch
) == 0) {
1256 pg_error_badrequest(
1257 "You specified an invalid directory component.");
1263 * Scan for indexable paths.
1266 parse_manpath_conf(struct req
*req
)
1273 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1274 warn("%s/manpath.conf", MAN_DIR
);
1275 pg_error_internal();
1282 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1283 if (dp
[len
- 1] == '\n')
1285 req
->p
= mandoc_realloc(req
->p
,
1286 (req
->psz
+ 1) * sizeof(char *));
1287 if ( ! validate_urifrag(dp
)) {
1288 warnx("%s/manpath.conf contains "
1289 "unsafe path \"%s\"", MAN_DIR
, dp
);
1290 pg_error_internal();
1293 if (strchr(dp
, '/') != NULL
) {
1294 warnx("%s/manpath.conf contains "
1295 "path with slash \"%s\"", MAN_DIR
, dp
);
1296 pg_error_internal();
1299 req
->p
[req
->psz
++] = dp
;
1305 if (req
->p
== NULL
) {
1306 warnx("%s/manpath.conf is empty", MAN_DIR
);
1307 pg_error_internal();