]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.172 2020/04/03 11:35:01 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",
127 "loongson", "luna88k", "macppc", "mips64",
128 "octeon", "sgi", "socppc", "sparc64",
129 "amiga", "arc", "armish", "arm32",
130 "atari", "aviion", "beagle", "cats",
132 "ia64", "mac68k", "mvme68k", "mvme88k",
133 "mvmeppc", "palm", "pc532", "pegasos",
134 "pmax", "powerpc", "solbourne", "sparc",
135 "sun3", "vax", "wgrisc", "x68k",
138 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
141 * Print a character, escaping HTML along the way.
142 * This will pass non-ASCII straight to output: be warned!
162 putchar((unsigned char)c
);
168 * Call through to html_putchar().
169 * Accepts NULL strings.
172 html_print(const char *p
)
182 * Transfer the responsibility for the allocated string *val
183 * to the query structure.
186 set_query_attr(char **attr
, char **val
)
199 * Parse the QUERY_STRING for key-value pairs
200 * and store the values into the query structure.
203 parse_query_string(struct req
*req
, const char *qs
)
209 req
->q
.manpath
= NULL
;
216 while (*qs
!= '\0') {
220 keysz
= strcspn(qs
, "=;&");
221 key
= mandoc_strndup(qs
, keysz
);
226 /* Parse one value. */
228 valsz
= strcspn(++qs
, ";&");
229 val
= mandoc_strndup(qs
, valsz
);
232 /* Decode and catch encoding errors. */
234 if ( ! (http_decode(key
) && http_decode(val
)))
237 /* Handle key-value pairs. */
239 if ( ! strcmp(key
, "query"))
240 set_query_attr(&req
->q
.query
, &val
);
242 else if ( ! strcmp(key
, "apropos"))
243 req
->q
.equal
= !strcmp(val
, "0");
245 else if ( ! strcmp(key
, "manpath")) {
247 if ( ! strncmp(val
, "OpenBSD ", 8)) {
253 set_query_attr(&req
->q
.manpath
, &val
);
256 else if ( ! (strcmp(key
, "sec")
258 && strcmp(key
, "sektion")
261 if ( ! strcmp(val
, "0"))
263 set_query_attr(&req
->q
.sec
, &val
);
266 else if ( ! strcmp(key
, "arch")) {
267 if ( ! strcmp(val
, "default"))
269 set_query_attr(&req
->q
.arch
, &val
);
273 * The key must be freed in any case.
274 * The val may have been handed over to the query
275 * structure, in which case it is now NULL.
289 * HTTP-decode a string. The standard explanation is that this turns
290 * "%4e+foo" into "n foo" in the regular way. This is done in-place
291 * over the allocated string.
303 for ( ; '\0' != *p
; p
++, q
++) {
305 if ('\0' == (hex
[0] = *(p
+ 1)))
307 if ('\0' == (hex
[1] = *(p
+ 2)))
309 if (1 != sscanf(hex
, "%x", &c
))
317 *q
= '+' == *p
? ' ' : *p
;
325 http_encode(const char *p
)
327 for (; *p
!= '\0'; p
++) {
328 if (isalnum((unsigned char)*p
) == 0 &&
329 strchr("-._~", *p
) == NULL
)
330 printf("%%%2.2X", (unsigned char)*p
);
337 resp_begin_http(int code
, const char *msg
)
341 printf("Status: %d %s\r\n", code
, msg
);
343 printf("Content-Type: text/html; charset=utf-8\r\n"
344 "Cache-Control: no-cache\r\n"
345 "Content-Security-Policy: default-src 'none'; "
346 "style-src 'self' 'unsafe-inline'\r\n"
347 "Pragma: no-cache\r\n"
354 resp_copy(const char *filename
)
360 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
362 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
363 write(STDOUT_FILENO
, buf
, sz
);
369 resp_begin_html(int code
, const char *msg
, const char *file
)
373 resp_begin_http(code
, msg
);
375 printf("<!DOCTYPE html>\n"
378 " <meta charset=\"UTF-8\"/>\n"
379 " <meta name=\"viewport\""
380 " content=\"width=device-width, initial-scale=1.0\">\n"
381 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
382 " type=\"text/css\" media=\"all\">\n"
386 if ((cp
= strrchr(file
, '/')) != NULL
)
388 if ((cp
= strrchr(file
, '.')) != NULL
) {
389 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
391 printf("%s - ", file
);
393 printf("%s</title>\n"
398 resp_copy(MAN_DIR
"/header.html");
405 resp_copy(MAN_DIR
"/footer.html");
412 resp_searchform(const struct req
*req
, enum focus focus
)
416 printf("<form action=\"/%s\" method=\"get\" "
417 "autocomplete=\"off\" autocapitalize=\"none\">\n"
419 " <legend>Manual Page Search Parameters</legend>\n",
422 /* Write query input box. */
424 printf(" <input type=\"search\" name=\"query\" value=\"");
425 if (req
->q
.query
!= NULL
)
426 html_print(req
->q
.query
);
427 printf( "\" size=\"40\"");
428 if (focus
== FOCUS_QUERY
)
429 printf(" autofocus");
432 /* Write submission buttons. */
434 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
436 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
440 /* Write section selector. */
442 puts(" <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
]);
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
++) {
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
]);
468 /* Write manpath selector. */
471 puts(" <select name=\"manpath\">");
472 for (i
= 0; i
< (int)req
->psz
; i
++) {
474 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
475 printf(" selected=\"selected\"");
477 html_print(req
->p
[i
]);
483 puts(" </fieldset>\n"
488 validate_urifrag(const char *frag
)
491 while ('\0' != *frag
) {
492 if ( ! (isalnum((unsigned char)*frag
) ||
493 '-' == *frag
|| '.' == *frag
||
494 '/' == *frag
|| '_' == *frag
))
502 validate_manpath(const struct req
*req
, const char* manpath
)
506 for (i
= 0; i
< req
->psz
; i
++)
507 if ( ! strcmp(manpath
, req
->p
[i
]))
514 validate_arch(const char *arch
)
518 for (i
= 0; i
< arch_MAX
; i
++)
519 if (strcmp(arch
, arch_names
[i
]) == 0)
526 validate_filename(const char *file
)
529 if ('.' == file
[0] && '/' == file
[1])
532 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
533 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
537 pg_index(const struct req
*req
)
540 resp_begin_html(200, NULL
, NULL
);
541 resp_searchform(req
, FOCUS_QUERY
);
543 "This web interface is documented in the\n"
544 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
546 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
547 "manual explains the query syntax.\n"
549 scriptname
, *scriptname
== '\0' ? "" : "/",
550 scriptname
, *scriptname
== '\0' ? "" : "/");
555 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
556 const char *user_msg
)
558 resp_begin_html(code
, http_msg
, NULL
);
559 resp_searchform(req
, FOCUS_QUERY
);
567 pg_error_badrequest(const char *msg
)
570 resp_begin_html(400, "Bad Request", NULL
);
571 puts("<h1>Bad Request</h1>\n"
574 printf("Try again from the\n"
575 "<a href=\"/%s\">main page</a>.\n"
581 pg_error_internal(void)
583 resp_begin_html(500, "Internal Server Error", NULL
);
584 puts("<p>Internal Server Error</p>");
589 pg_redirect(const struct req
*req
, const char *name
)
591 printf("Status: 303 See Other\r\n"
593 if (*scriptname
!= '\0')
594 printf("%s/", scriptname
);
595 if (strcmp(req
->q
.manpath
, req
->p
[0]))
596 printf("%s/", req
->q
.manpath
);
597 if (req
->q
.arch
!= NULL
)
598 printf("%s/", req
->q
.arch
);
600 if (req
->q
.sec
!= NULL
) {
602 http_encode(req
->q
.sec
);
604 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
608 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
610 char *arch
, *archend
;
613 int archprio
, archpriouse
;
616 for (i
= 0; i
< sz
; i
++) {
617 if (validate_filename(r
[i
].file
))
619 warnx("invalid filename %s in %s database",
620 r
[i
].file
, req
->q
.manpath
);
625 if (req
->isquery
&& sz
== 1) {
627 * If we have just one result, then jump there now
630 printf("Status: 303 See Other\r\n"
632 if (*scriptname
!= '\0')
633 printf("%s/", scriptname
);
634 if (strcmp(req
->q
.manpath
, req
->p
[0]))
635 printf("%s/", req
->q
.manpath
);
637 "Content-Type: text/html; charset=utf-8\r\n\r\n",
643 * In man(1) mode, show one of the pages
644 * even if more than one is found.
648 if (req
->q
.equal
|| sz
== 1) {
651 for (i
= 0; i
< sz
; i
++) {
653 sec
+= strcspn(sec
, "123456789");
656 prio
= sec_prios
[sec
[0] - '1'];
659 if (req
->q
.arch
== NULL
) {
661 ((arch
= strchr(sec
+ 1, '/'))
663 ((archend
= strchr(arch
+ 1, '/'))
665 strncmp(arch
, "amd64/",
666 archend
- arch
) ? 2 : 1;
667 if (archprio
< archpriouse
) {
668 archpriouse
= archprio
;
673 if (archprio
> archpriouse
)
681 resp_begin_html(200, NULL
, r
[iuse
].file
);
683 resp_begin_html(200, NULL
, NULL
);
686 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
689 puts("<table class=\"results\">");
690 for (i
= 0; i
< sz
; i
++) {
693 "<a class=\"Xr\" href=\"/");
694 if (*scriptname
!= '\0')
695 printf("%s/", scriptname
);
696 if (strcmp(req
->q
.manpath
, req
->p
[0]))
697 printf("%s/", req
->q
.manpath
);
698 printf("%s\">", r
[i
].file
);
699 html_print(r
[i
].names
);
701 " <td><span class=\"Nd\">");
702 html_print(r
[i
].output
);
703 puts("</span></td>\n"
709 if (req
->q
.equal
|| sz
== 1) {
711 resp_show(req
, r
[iuse
].file
);
718 resp_catman(const struct req
*req
, const char *file
)
727 if ((f
= fopen(file
, "r")) == NULL
) {
728 puts("<p>You specified an invalid manual file.</p>");
732 puts("<div class=\"catman\">\n"
738 while ((len
= getline(&p
, &sz
, f
)) != -1) {
740 for (i
= 0; i
< len
- 1; i
++) {
742 * This means that the catpage is out of state.
743 * Ignore it and keep going (although the
747 if ('\b' == p
[i
] || '\n' == p
[i
])
751 * Print a regular character.
752 * Close out any bold/italic scopes.
753 * If we're in back-space mode, make sure we'll
754 * have something to enter when we backspace.
757 if ('\b' != p
[i
+ 1]) {
765 } else if (i
+ 2 >= len
)
783 * Handle funny behaviour troff-isms.
784 * These grok'd from the original man2html.c.
787 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
788 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
789 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
790 ('=' == 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])) {
803 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
804 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
805 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
806 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
807 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
808 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
832 * Clean up the last character.
833 * We can get to a newline; don't print that.
841 if (i
== len
- 1 && p
[i
] != '\n')
855 resp_format(const struct req
*req
, const char *file
)
857 struct manoutput conf
;
859 struct roff_meta
*meta
;
864 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
865 puts("<p>You specified an invalid manual file.</p>");
870 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
871 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
872 mparse_readfd(mp
, fd
, file
);
874 meta
= mparse_result(mp
);
876 memset(&conf
, 0, sizeof(conf
));
878 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
879 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
880 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
881 scriptname
, *scriptname
== '\0' ? "" : "/",
882 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
884 vp
= html_alloc(&conf
);
885 if (meta
->macroset
== MACROSET_MDOC
)
898 resp_show(const struct req
*req
, const char *file
)
901 if ('.' == file
[0] && '/' == file
[1])
905 resp_catman(req
, file
);
907 resp_format(req
, file
);
911 pg_show(struct req
*req
, const char *fullpath
)
916 if ((file
= strchr(fullpath
, '/')) == NULL
) {
918 "You did not specify a page to show.");
921 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
924 if ( ! validate_manpath(req
, manpath
)) {
926 "You specified an invalid manpath.");
932 * Begin by chdir()ing into the manpath.
933 * This way we can pick up the database files, which are
934 * relative to the manpath root.
937 if (chdir(manpath
) == -1) {
938 warn("chdir %s", manpath
);
945 if ( ! validate_filename(file
)) {
947 "You specified an invalid manual file.");
951 resp_begin_html(200, NULL
, file
);
952 resp_searchform(req
, FOCUS_NONE
);
953 resp_show(req
, file
);
958 pg_search(const struct req
*req
)
960 struct mansearch search
;
961 struct manpaths paths
;
964 char *query
, *rp
, *wp
;
969 * Begin by chdir()ing into the root of the manpath.
970 * This way we can pick up the database files, which are
971 * relative to the manpath root.
974 if (chdir(req
->q
.manpath
) == -1) {
975 warn("chdir %s", req
->q
.manpath
);
980 search
.arch
= req
->q
.arch
;
981 search
.sec
= req
->q
.sec
;
982 search
.outkey
= "Nd";
983 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
984 search
.firstmatch
= 1;
987 paths
.paths
= mandoc_malloc(sizeof(char *));
988 paths
.paths
[0] = mandoc_strdup(".");
991 * Break apart at spaces with backslash-escaping.
996 rp
= query
= mandoc_strdup(req
->q
.query
);
998 while (isspace((unsigned char)*rp
))
1002 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1003 argv
[argc
++] = wp
= rp
;
1005 if (isspace((unsigned char)*rp
)) {
1010 if (rp
[0] == '\\' && rp
[1] != '\0')
1023 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1024 pg_redirect(req
, argv
[0]);
1025 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1026 pg_noresult(req
, 400, "Bad Request",
1027 "You entered an invalid query.");
1028 else if (ressz
== 0)
1029 pg_noresult(req
, 404, "Not Found", "No results found.");
1031 pg_searchres(req
, res
, ressz
);
1034 mansearch_free(res
, ressz
);
1035 free(paths
.paths
[0]);
1043 struct itimerval itimer
;
1045 const char *querystring
;
1050 * The "rpath" pledge could be revoked after mparse_readfd()
1051 * if the file desciptor to "/footer.html" would be opened
1052 * up front, but it's probably not worth the complication
1053 * of the code it would cause: it would require scattering
1054 * pledge() calls in multiple low-level resp_*() functions.
1057 if (pledge("stdio rpath", NULL
) == -1) {
1059 pg_error_internal();
1060 return EXIT_FAILURE
;
1064 /* Poor man's ReDoS mitigation. */
1066 itimer
.it_value
.tv_sec
= 2;
1067 itimer
.it_value
.tv_usec
= 0;
1068 itimer
.it_interval
.tv_sec
= 2;
1069 itimer
.it_interval
.tv_usec
= 0;
1070 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1072 pg_error_internal();
1073 return EXIT_FAILURE
;
1077 * First we change directory into the MAN_DIR so that
1078 * subsequent scanning for manpath directories is rooted
1079 * relative to the same position.
1082 if (chdir(MAN_DIR
) == -1) {
1083 warn("MAN_DIR: %s", MAN_DIR
);
1084 pg_error_internal();
1085 return EXIT_FAILURE
;
1088 memset(&req
, 0, sizeof(struct req
));
1090 parse_manpath_conf(&req
);
1092 /* Parse the path info and the query string. */
1094 if ((path
= getenv("PATH_INFO")) == NULL
)
1096 else if (*path
== '/')
1099 if (*path
!= '\0') {
1100 parse_path_info(&req
, path
);
1101 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1102 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1104 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1105 parse_query_string(&req
, querystring
);
1107 /* Validate parsed data and add defaults. */
1109 if (req
.q
.manpath
== NULL
)
1110 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1111 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1112 pg_error_badrequest(
1113 "You specified an invalid manpath.");
1114 return EXIT_FAILURE
;
1117 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1118 pg_error_badrequest(
1119 "You specified an invalid architecture.");
1120 return EXIT_FAILURE
;
1123 /* Dispatch to the three different pages. */
1126 pg_show(&req
, path
);
1127 else if (NULL
!= req
.q
.query
)
1132 free(req
.q
.manpath
);
1136 for (i
= 0; i
< (int)req
.psz
; i
++)
1139 return EXIT_SUCCESS
;
1143 * Translate PATH_INFO to a query.
1146 parse_path_info(struct req
*req
, const char *path
)
1148 const char *name
, *sec
, *end
;
1152 req
->q
.manpath
= NULL
;
1155 /* Mandatory manual page name. */
1156 if ((name
= strrchr(path
, '/')) == NULL
)
1161 /* Optional trailing section. */
1162 sec
= strrchr(name
, '.');
1163 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1164 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1165 req
->q
.sec
= mandoc_strdup(sec
);
1167 req
->q
.query
= mandoc_strdup(name
);
1171 /* Handle the case of name[.section] only. */
1175 /* Optional manpath. */
1176 end
= strchr(path
, '/');
1177 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1178 if (validate_manpath(req
, req
->q
.manpath
)) {
1183 free(req
->q
.manpath
);
1184 req
->q
.manpath
= NULL
;
1187 /* Optional section. */
1188 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1190 end
= strchr(path
, '/');
1192 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1198 /* Optional architecture. */
1199 end
= strchr(path
, '/');
1200 if (end
+ 1 != name
) {
1201 pg_error_badrequest(
1202 "You specified too many directory components.");
1205 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1206 if (validate_arch(req
->q
.arch
) == 0) {
1207 pg_error_badrequest(
1208 "You specified an invalid directory component.");
1214 * Scan for indexable paths.
1217 parse_manpath_conf(struct req
*req
)
1224 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1225 warn("%s/manpath.conf", MAN_DIR
);
1226 pg_error_internal();
1233 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1234 if (dp
[len
- 1] == '\n')
1236 req
->p
= mandoc_realloc(req
->p
,
1237 (req
->psz
+ 1) * sizeof(char *));
1238 if ( ! validate_urifrag(dp
)) {
1239 warnx("%s/manpath.conf contains "
1240 "unsafe path \"%s\"", MAN_DIR
, dp
);
1241 pg_error_internal();
1244 if (strchr(dp
, '/') != NULL
) {
1245 warnx("%s/manpath.conf contains "
1246 "path with slash \"%s\"", MAN_DIR
, dp
);
1247 pg_error_internal();
1250 req
->p
[req
->psz
++] = dp
;
1256 if (req
->p
== NULL
) {
1257 warnx("%s/manpath.conf is empty", MAN_DIR
);
1258 pg_error_internal();