]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.174 2021/05/13 13:33:11 schwarze Exp $ */
3 * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@usta.de>
4 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
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 AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 * Implementation of the man.cgi(8) program.
22 #include <sys/types.h>
38 #include "mandoc_aux.h"
43 #include "mandoc_parse.h"
46 #include "mansearch.h"
50 * A query as passed to the search function.
53 char *manpath
; /* desired manual directory */
54 char *arch
; /* architecture */
55 char *sec
; /* manual section */
56 char *query
; /* unparsed query expression */
57 int equal
; /* match whole names, not substrings */
62 char **p
; /* array of available manpaths */
63 size_t psz
; /* number of available manpaths */
64 int isquery
; /* QUERY_STRING used, not PATH_INFO */
72 static void html_print(const char *);
73 static void html_putchar(char);
74 static int http_decode(char *);
75 static void http_encode(const char *);
76 static void parse_manpath_conf(struct req
*);
77 static void parse_path_info(struct req
*, const char *);
78 static void parse_query_string(struct req
*, const char *);
79 static void pg_error_badrequest(const char *);
80 static void pg_error_internal(void);
81 static void pg_index(const struct req
*);
82 static void pg_noresult(const struct req
*, int, const char *,
84 static void pg_redirect(const struct req
*, const char *);
85 static void pg_search(const struct req
*);
86 static void pg_searchres(const struct req
*,
87 struct manpage
*, size_t);
88 static void pg_show(struct req
*, const char *);
89 static void resp_begin_html(int, const char *, const char *);
90 static void resp_begin_http(int, const char *);
91 static void resp_catman(const struct req
*, const char *);
92 static void resp_copy(const char *);
93 static void resp_end_html(void);
94 static void resp_format(const struct req
*, const char *);
95 static void resp_searchform(const struct req
*, enum focus
);
96 static void resp_show(const struct req
*, const char *);
97 static void set_query_attr(char **, char **);
98 static int validate_arch(const char *);
99 static int validate_filename(const char *);
100 static int validate_manpath(const struct req
*, const char *);
101 static int validate_urifrag(const char *);
103 static const char *scriptname
= SCRIPT_NAME
;
105 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
106 static const char *const sec_numbers
[] = {
107 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
109 static const char *const sec_names
[] = {
111 "1 - General Commands",
113 "3 - Library Functions",
115 "4 - Device Drivers",
118 "7 - Miscellaneous Information",
119 "8 - System Manager\'s Manual",
120 "9 - Kernel Developer\'s Manual"
122 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
124 static const char *const arch_names
[] = {
125 "amd64", "alpha", "armv7", "arm64",
126 "hppa", "i386", "landisk", "loongson",
127 "luna88k", "macppc", "mips64", "octeon",
128 "powerpc64", "riscv64", "sparc64",
130 "amiga", "arc", "armish", "arm32",
131 "atari", "aviion", "beagle", "cats",
133 "ia64", "mac68k", "mvme68k", "mvme88k",
134 "mvmeppc", "palm", "pc532", "pegasos",
135 "pmax", "powerpc", "sgi", "socppc",
136 "solbourne", "sparc",
137 "sun3", "vax", "wgrisc", "x68k",
140 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
143 * Print a character, escaping HTML along the way.
144 * This will pass non-ASCII straight to output: be warned!
164 putchar((unsigned char)c
);
170 * Call through to html_putchar().
171 * Accepts NULL strings.
174 html_print(const char *p
)
184 * Transfer the responsibility for the allocated string *val
185 * to the query structure.
188 set_query_attr(char **attr
, char **val
)
201 * Parse the QUERY_STRING for key-value pairs
202 * and store the values into the query structure.
205 parse_query_string(struct req
*req
, const char *qs
)
211 req
->q
.manpath
= NULL
;
218 while (*qs
!= '\0') {
222 keysz
= strcspn(qs
, "=;&");
223 key
= mandoc_strndup(qs
, keysz
);
228 /* Parse one value. */
230 valsz
= strcspn(++qs
, ";&");
231 val
= mandoc_strndup(qs
, valsz
);
234 /* Decode and catch encoding errors. */
236 if ( ! (http_decode(key
) && http_decode(val
)))
239 /* Handle key-value pairs. */
241 if ( ! strcmp(key
, "query"))
242 set_query_attr(&req
->q
.query
, &val
);
244 else if ( ! strcmp(key
, "apropos"))
245 req
->q
.equal
= !strcmp(val
, "0");
247 else if ( ! strcmp(key
, "manpath")) {
249 if ( ! strncmp(val
, "OpenBSD ", 8)) {
255 set_query_attr(&req
->q
.manpath
, &val
);
258 else if ( ! (strcmp(key
, "sec")
260 && strcmp(key
, "sektion")
263 if ( ! strcmp(val
, "0"))
265 set_query_attr(&req
->q
.sec
, &val
);
268 else if ( ! strcmp(key
, "arch")) {
269 if ( ! strcmp(val
, "default"))
271 set_query_attr(&req
->q
.arch
, &val
);
275 * The key must be freed in any case.
276 * The val may have been handed over to the query
277 * structure, in which case it is now NULL.
291 * HTTP-decode a string. The standard explanation is that this turns
292 * "%4e+foo" into "n foo" in the regular way. This is done in-place
293 * over the allocated string.
305 for ( ; '\0' != *p
; p
++, q
++) {
307 if ('\0' == (hex
[0] = *(p
+ 1)))
309 if ('\0' == (hex
[1] = *(p
+ 2)))
311 if (1 != sscanf(hex
, "%x", &c
))
319 *q
= '+' == *p
? ' ' : *p
;
327 http_encode(const char *p
)
329 for (; *p
!= '\0'; p
++) {
330 if (isalnum((unsigned char)*p
) == 0 &&
331 strchr("-._~", *p
) == NULL
)
332 printf("%%%2.2X", (unsigned char)*p
);
339 resp_begin_http(int code
, const char *msg
)
343 printf("Status: %d %s\r\n", code
, msg
);
345 printf("Content-Type: text/html; charset=utf-8\r\n"
346 "Cache-Control: no-cache\r\n"
347 "Content-Security-Policy: default-src 'none'; "
348 "style-src 'self' 'unsafe-inline'\r\n"
349 "Pragma: no-cache\r\n"
356 resp_copy(const char *filename
)
362 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
364 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
365 write(STDOUT_FILENO
, buf
, sz
);
371 resp_begin_html(int code
, const char *msg
, const char *file
)
375 resp_begin_http(code
, msg
);
377 printf("<!DOCTYPE html>\n"
380 " <meta charset=\"UTF-8\"/>\n"
381 " <meta name=\"viewport\""
382 " content=\"width=device-width, initial-scale=1.0\">\n"
383 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
384 " type=\"text/css\" media=\"all\">\n"
388 if ((cp
= strrchr(file
, '/')) != NULL
)
390 if ((cp
= strrchr(file
, '.')) != NULL
) {
391 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
393 printf("%s - ", file
);
395 printf("%s</title>\n"
400 resp_copy(MAN_DIR
"/header.html");
407 resp_copy(MAN_DIR
"/footer.html");
414 resp_searchform(const struct req
*req
, enum focus focus
)
418 printf("<form action=\"/%s\" method=\"get\" "
419 "autocomplete=\"off\" autocapitalize=\"none\">\n"
421 " <legend>Manual Page Search Parameters</legend>\n",
424 /* Write query input box. */
426 printf(" <input type=\"search\" name=\"query\" value=\"");
427 if (req
->q
.query
!= NULL
)
428 html_print(req
->q
.query
);
429 printf( "\" size=\"40\"");
430 if (focus
== FOCUS_QUERY
)
431 printf(" autofocus");
434 /* Write submission buttons. */
436 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
438 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
442 /* Write section selector. */
444 puts(" <select name=\"sec\">");
445 for (i
= 0; i
< sec_MAX
; i
++) {
446 printf(" <option value=\"%s\"", sec_numbers
[i
]);
447 if (NULL
!= req
->q
.sec
&&
448 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
449 printf(" selected=\"selected\"");
450 printf(">%s</option>\n", sec_names
[i
]);
454 /* Write architecture selector. */
456 printf( " <select name=\"arch\">\n"
457 " <option value=\"default\"");
458 if (NULL
== req
->q
.arch
)
459 printf(" selected=\"selected\"");
460 puts(">All Architectures</option>");
461 for (i
= 0; i
< arch_MAX
; i
++) {
463 if (NULL
!= req
->q
.arch
&&
464 0 == strcmp(arch_names
[i
], req
->q
.arch
))
465 printf(" selected=\"selected\"");
466 printf(">%s</option>\n", arch_names
[i
]);
470 /* Write manpath selector. */
473 puts(" <select name=\"manpath\">");
474 for (i
= 0; i
< (int)req
->psz
; i
++) {
476 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
477 printf(" selected=\"selected\"");
479 html_print(req
->p
[i
]);
485 puts(" </fieldset>\n"
490 validate_urifrag(const char *frag
)
493 while ('\0' != *frag
) {
494 if ( ! (isalnum((unsigned char)*frag
) ||
495 '-' == *frag
|| '.' == *frag
||
496 '/' == *frag
|| '_' == *frag
))
504 validate_manpath(const struct req
*req
, const char* manpath
)
508 for (i
= 0; i
< req
->psz
; i
++)
509 if ( ! strcmp(manpath
, req
->p
[i
]))
516 validate_arch(const char *arch
)
520 for (i
= 0; i
< arch_MAX
; i
++)
521 if (strcmp(arch
, arch_names
[i
]) == 0)
528 validate_filename(const char *file
)
531 if ('.' == file
[0] && '/' == file
[1])
534 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
535 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
539 pg_index(const struct req
*req
)
542 resp_begin_html(200, NULL
, NULL
);
543 resp_searchform(req
, FOCUS_QUERY
);
545 "This web interface is documented in the\n"
546 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
548 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
549 "manual explains the query syntax.\n"
551 scriptname
, *scriptname
== '\0' ? "" : "/",
552 scriptname
, *scriptname
== '\0' ? "" : "/");
557 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
558 const char *user_msg
)
560 resp_begin_html(code
, http_msg
, NULL
);
561 resp_searchform(req
, FOCUS_QUERY
);
569 pg_error_badrequest(const char *msg
)
572 resp_begin_html(400, "Bad Request", NULL
);
573 puts("<h1>Bad Request</h1>\n"
576 printf("Try again from the\n"
577 "<a href=\"/%s\">main page</a>.\n"
583 pg_error_internal(void)
585 resp_begin_html(500, "Internal Server Error", NULL
);
586 puts("<p>Internal Server Error</p>");
591 pg_redirect(const struct req
*req
, const char *name
)
593 printf("Status: 303 See Other\r\n"
595 if (*scriptname
!= '\0')
596 printf("%s/", scriptname
);
597 if (strcmp(req
->q
.manpath
, req
->p
[0]))
598 printf("%s/", req
->q
.manpath
);
599 if (req
->q
.arch
!= NULL
)
600 printf("%s/", req
->q
.arch
);
602 if (req
->q
.sec
!= NULL
) {
604 http_encode(req
->q
.sec
);
606 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
610 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
612 char *arch
, *archend
;
615 int archprio
, archpriouse
;
618 for (i
= 0; i
< sz
; i
++) {
619 if (validate_filename(r
[i
].file
))
621 warnx("invalid filename %s in %s database",
622 r
[i
].file
, req
->q
.manpath
);
627 if (req
->isquery
&& sz
== 1) {
629 * If we have just one result, then jump there now
632 printf("Status: 303 See Other\r\n"
634 if (*scriptname
!= '\0')
635 printf("%s/", scriptname
);
636 if (strcmp(req
->q
.manpath
, req
->p
[0]))
637 printf("%s/", req
->q
.manpath
);
639 "Content-Type: text/html; charset=utf-8\r\n\r\n",
645 * In man(1) mode, show one of the pages
646 * even if more than one is found.
650 if (req
->q
.equal
|| sz
== 1) {
653 for (i
= 0; i
< sz
; i
++) {
655 sec
+= strcspn(sec
, "123456789");
658 prio
= sec_prios
[sec
[0] - '1'];
661 if (req
->q
.arch
== NULL
) {
663 ((arch
= strchr(sec
+ 1, '/'))
665 ((archend
= strchr(arch
+ 1, '/'))
667 strncmp(arch
, "amd64/",
668 archend
- arch
) ? 2 : 1;
669 if (archprio
< archpriouse
) {
670 archpriouse
= archprio
;
675 if (archprio
> archpriouse
)
683 resp_begin_html(200, NULL
, r
[iuse
].file
);
685 resp_begin_html(200, NULL
, NULL
);
688 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
691 puts("<table class=\"results\">");
692 for (i
= 0; i
< sz
; i
++) {
695 "<a class=\"Xr\" href=\"/");
696 if (*scriptname
!= '\0')
697 printf("%s/", scriptname
);
698 if (strcmp(req
->q
.manpath
, req
->p
[0]))
699 printf("%s/", req
->q
.manpath
);
700 printf("%s\">", r
[i
].file
);
701 html_print(r
[i
].names
);
703 " <td><span class=\"Nd\">");
704 html_print(r
[i
].output
);
705 puts("</span></td>\n"
711 if (req
->q
.equal
|| sz
== 1) {
713 resp_show(req
, r
[iuse
].file
);
720 resp_catman(const struct req
*req
, const char *file
)
729 if ((f
= fopen(file
, "r")) == NULL
) {
730 puts("<p>You specified an invalid manual file.</p>");
734 puts("<div class=\"catman\">\n"
740 while ((len
= getline(&p
, &sz
, f
)) != -1) {
742 for (i
= 0; i
< len
- 1; i
++) {
744 * This means that the catpage is out of state.
745 * Ignore it and keep going (although the
749 if ('\b' == p
[i
] || '\n' == p
[i
])
753 * Print a regular character.
754 * Close out any bold/italic scopes.
755 * If we're in back-space mode, make sure we'll
756 * have something to enter when we backspace.
759 if ('\b' != p
[i
+ 1]) {
767 } else if (i
+ 2 >= len
)
785 * Handle funny behaviour troff-isms.
786 * These grok'd from the original man2html.c.
789 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
790 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
791 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
792 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
793 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
794 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
795 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
796 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
805 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
806 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
807 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
808 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
809 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
810 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
834 * Clean up the last character.
835 * We can get to a newline; don't print that.
843 if (i
== len
- 1 && p
[i
] != '\n')
857 resp_format(const struct req
*req
, const char *file
)
859 struct manoutput conf
;
861 struct roff_meta
*meta
;
866 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
867 puts("<p>You specified an invalid manual file.</p>");
872 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
873 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
874 mparse_readfd(mp
, fd
, file
);
876 meta
= mparse_result(mp
);
878 memset(&conf
, 0, sizeof(conf
));
880 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
881 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
882 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
883 scriptname
, *scriptname
== '\0' ? "" : "/",
884 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
886 vp
= html_alloc(&conf
);
887 if (meta
->macroset
== MACROSET_MDOC
)
900 resp_show(const struct req
*req
, const char *file
)
903 if ('.' == file
[0] && '/' == file
[1])
907 resp_catman(req
, file
);
909 resp_format(req
, file
);
913 pg_show(struct req
*req
, const char *fullpath
)
918 if ((file
= strchr(fullpath
, '/')) == NULL
) {
920 "You did not specify a page to show.");
923 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
926 if ( ! validate_manpath(req
, manpath
)) {
928 "You specified an invalid manpath.");
934 * Begin by chdir()ing into the manpath.
935 * This way we can pick up the database files, which are
936 * relative to the manpath root.
939 if (chdir(manpath
) == -1) {
940 warn("chdir %s", manpath
);
947 if ( ! validate_filename(file
)) {
949 "You specified an invalid manual file.");
953 resp_begin_html(200, NULL
, file
);
954 resp_searchform(req
, FOCUS_NONE
);
955 resp_show(req
, file
);
960 pg_search(const struct req
*req
)
962 struct mansearch search
;
963 struct manpaths paths
;
966 char *query
, *rp
, *wp
;
971 * Begin by chdir()ing into the root of the manpath.
972 * This way we can pick up the database files, which are
973 * relative to the manpath root.
976 if (chdir(req
->q
.manpath
) == -1) {
977 warn("chdir %s", req
->q
.manpath
);
982 search
.arch
= req
->q
.arch
;
983 search
.sec
= req
->q
.sec
;
984 search
.outkey
= "Nd";
985 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
986 search
.firstmatch
= 1;
989 paths
.paths
= mandoc_malloc(sizeof(char *));
990 paths
.paths
[0] = mandoc_strdup(".");
993 * Break apart at spaces with backslash-escaping.
998 rp
= query
= mandoc_strdup(req
->q
.query
);
1000 while (isspace((unsigned char)*rp
))
1004 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1005 argv
[argc
++] = wp
= rp
;
1007 if (isspace((unsigned char)*rp
)) {
1012 if (rp
[0] == '\\' && rp
[1] != '\0')
1025 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1026 pg_redirect(req
, argv
[0]);
1027 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1028 pg_noresult(req
, 400, "Bad Request",
1029 "You entered an invalid query.");
1030 else if (ressz
== 0)
1031 pg_noresult(req
, 404, "Not Found", "No results found.");
1033 pg_searchres(req
, res
, ressz
);
1036 mansearch_free(res
, ressz
);
1037 free(paths
.paths
[0]);
1045 struct itimerval itimer
;
1047 const char *querystring
;
1052 * The "rpath" pledge could be revoked after mparse_readfd()
1053 * if the file desciptor to "/footer.html" would be opened
1054 * up front, but it's probably not worth the complication
1055 * of the code it would cause: it would require scattering
1056 * pledge() calls in multiple low-level resp_*() functions.
1059 if (pledge("stdio rpath", NULL
) == -1) {
1061 pg_error_internal();
1062 return EXIT_FAILURE
;
1066 /* Poor man's ReDoS mitigation. */
1068 itimer
.it_value
.tv_sec
= 2;
1069 itimer
.it_value
.tv_usec
= 0;
1070 itimer
.it_interval
.tv_sec
= 2;
1071 itimer
.it_interval
.tv_usec
= 0;
1072 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1074 pg_error_internal();
1075 return EXIT_FAILURE
;
1079 * First we change directory into the MAN_DIR so that
1080 * subsequent scanning for manpath directories is rooted
1081 * relative to the same position.
1084 if (chdir(MAN_DIR
) == -1) {
1085 warn("MAN_DIR: %s", MAN_DIR
);
1086 pg_error_internal();
1087 return EXIT_FAILURE
;
1090 memset(&req
, 0, sizeof(struct req
));
1092 parse_manpath_conf(&req
);
1094 /* Parse the path info and the query string. */
1096 if ((path
= getenv("PATH_INFO")) == NULL
)
1098 else if (*path
== '/')
1101 if (*path
!= '\0') {
1102 parse_path_info(&req
, path
);
1103 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1104 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1106 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1107 parse_query_string(&req
, querystring
);
1109 /* Validate parsed data and add defaults. */
1111 if (req
.q
.manpath
== NULL
)
1112 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1113 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1114 pg_error_badrequest(
1115 "You specified an invalid manpath.");
1116 return EXIT_FAILURE
;
1119 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1120 pg_error_badrequest(
1121 "You specified an invalid architecture.");
1122 return EXIT_FAILURE
;
1125 /* Dispatch to the three different pages. */
1128 pg_show(&req
, path
);
1129 else if (NULL
!= req
.q
.query
)
1134 free(req
.q
.manpath
);
1138 for (i
= 0; i
< (int)req
.psz
; i
++)
1141 return EXIT_SUCCESS
;
1145 * Translate PATH_INFO to a query.
1148 parse_path_info(struct req
*req
, const char *path
)
1150 const char *name
, *sec
, *end
;
1154 req
->q
.manpath
= NULL
;
1157 /* Mandatory manual page name. */
1158 if ((name
= strrchr(path
, '/')) == NULL
)
1163 /* Optional trailing section. */
1164 sec
= strrchr(name
, '.');
1165 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1166 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1167 req
->q
.sec
= mandoc_strdup(sec
);
1169 req
->q
.query
= mandoc_strdup(name
);
1173 /* Handle the case of name[.section] only. */
1177 /* Optional manpath. */
1178 end
= strchr(path
, '/');
1179 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1180 if (validate_manpath(req
, req
->q
.manpath
)) {
1185 free(req
->q
.manpath
);
1186 req
->q
.manpath
= NULL
;
1189 /* Optional section. */
1190 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1192 end
= strchr(path
, '/');
1194 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1200 /* Optional architecture. */
1201 end
= strchr(path
, '/');
1202 if (end
+ 1 != name
) {
1203 pg_error_badrequest(
1204 "You specified too many directory components.");
1207 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1208 if (validate_arch(req
->q
.arch
) == 0) {
1209 pg_error_badrequest(
1210 "You specified an invalid directory component.");
1216 * Scan for indexable paths.
1219 parse_manpath_conf(struct req
*req
)
1226 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1227 warn("%s/manpath.conf", MAN_DIR
);
1228 pg_error_internal();
1235 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1236 if (dp
[len
- 1] == '\n')
1238 req
->p
= mandoc_realloc(req
->p
,
1239 (req
->psz
+ 1) * sizeof(char *));
1240 if ( ! validate_urifrag(dp
)) {
1241 warnx("%s/manpath.conf contains "
1242 "unsafe path \"%s\"", MAN_DIR
, dp
);
1243 pg_error_internal();
1246 if (strchr(dp
, '/') != NULL
) {
1247 warnx("%s/manpath.conf contains "
1248 "path with slash \"%s\"", MAN_DIR
, dp
);
1249 pg_error_internal();
1252 req
->p
[req
->psz
++] = dp
;
1258 if (req
->p
== NULL
) {
1259 warnx("%s/manpath.conf is empty", MAN_DIR
);
1260 pg_error_internal();