]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.147 2017/02/08 13:34:27 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014, 2015, 2016, 2017 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 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.
20 #include <sys/types.h>
36 #include "mandoc_aux.h"
43 #include "mansearch.h"
47 * A query as passed to the search function.
50 char *manpath
; /* desired manual directory */
51 char *arch
; /* architecture */
52 char *sec
; /* manual section */
53 char *query
; /* unparsed query expression */
54 int equal
; /* match whole names, not substrings */
59 char **p
; /* array of available manpaths */
60 size_t psz
; /* number of available manpaths */
61 int isquery
; /* QUERY_STRING used, not PATH_INFO */
69 static void html_print(const char *);
70 static void html_putchar(char);
71 static int http_decode(char *);
72 static void parse_manpath_conf(struct req
*);
73 static void parse_path_info(struct req
*req
, const char *path
);
74 static void parse_query_string(struct req
*, const char *);
75 static void pg_error_badrequest(const char *);
76 static void pg_error_internal(void);
77 static void pg_index(const struct req
*);
78 static void pg_noresult(const struct req
*, const char *);
79 static void pg_search(const struct req
*);
80 static void pg_searchres(const struct req
*,
81 struct manpage
*, size_t);
82 static void pg_show(struct req
*, const char *);
83 static void resp_begin_html(int, const char *);
84 static void resp_begin_http(int, const char *);
85 static void resp_catman(const struct req
*, const char *);
86 static void resp_copy(const char *);
87 static void resp_end_html(void);
88 static void resp_format(const struct req
*, const char *);
89 static void resp_searchform(const struct req
*, enum focus
);
90 static void resp_show(const struct req
*, const char *);
91 static void set_query_attr(char **, char **);
92 static int validate_filename(const char *);
93 static int validate_manpath(const struct req
*, const char *);
94 static int validate_urifrag(const char *);
96 static const char *scriptname
= SCRIPT_NAME
;
98 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
99 static const char *const sec_numbers
[] = {
100 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
102 static const char *const sec_names
[] = {
104 "1 - General Commands",
106 "3 - Library Functions",
108 "4 - Device Drivers",
111 "7 - Miscellaneous Information",
112 "8 - System Manager\'s Manual",
113 "9 - Kernel Developer\'s Manual"
115 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
117 static const char *const arch_names
[] = {
118 "amd64", "alpha", "armv7", "arm64",
119 "hppa", "i386", "landisk",
120 "loongson", "luna88k", "macppc", "mips64",
121 "octeon", "sgi", "socppc", "sparc64",
122 "amiga", "arc", "armish", "arm32",
123 "atari", "aviion", "beagle", "cats",
125 "ia64", "mac68k", "mvme68k", "mvme88k",
126 "mvmeppc", "palm", "pc532", "pegasos",
127 "pmax", "powerpc", "solbourne", "sparc",
128 "sun3", "vax", "wgrisc", "x68k",
131 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
134 * Print a character, escaping HTML along the way.
135 * This will pass non-ASCII straight to output: be warned!
155 putchar((unsigned char)c
);
161 * Call through to html_putchar().
162 * Accepts NULL strings.
165 html_print(const char *p
)
175 * Transfer the responsibility for the allocated string *val
176 * to the query structure.
179 set_query_attr(char **attr
, char **val
)
192 * Parse the QUERY_STRING for key-value pairs
193 * and store the values into the query structure.
196 parse_query_string(struct req
*req
, const char *qs
)
202 req
->q
.manpath
= NULL
;
209 while (*qs
!= '\0') {
213 keysz
= strcspn(qs
, "=;&");
214 key
= mandoc_strndup(qs
, keysz
);
219 /* Parse one value. */
221 valsz
= strcspn(++qs
, ";&");
222 val
= mandoc_strndup(qs
, valsz
);
225 /* Decode and catch encoding errors. */
227 if ( ! (http_decode(key
) && http_decode(val
)))
230 /* Handle key-value pairs. */
232 if ( ! strcmp(key
, "query"))
233 set_query_attr(&req
->q
.query
, &val
);
235 else if ( ! strcmp(key
, "apropos"))
236 req
->q
.equal
= !strcmp(val
, "0");
238 else if ( ! strcmp(key
, "manpath")) {
240 if ( ! strncmp(val
, "OpenBSD ", 8)) {
246 set_query_attr(&req
->q
.manpath
, &val
);
249 else if ( ! (strcmp(key
, "sec")
251 && strcmp(key
, "sektion")
254 if ( ! strcmp(val
, "0"))
256 set_query_attr(&req
->q
.sec
, &val
);
259 else if ( ! strcmp(key
, "arch")) {
260 if ( ! strcmp(val
, "default"))
262 set_query_attr(&req
->q
.arch
, &val
);
266 * The key must be freed in any case.
267 * The val may have been handed over to the query
268 * structure, in which case it is now NULL.
282 * HTTP-decode a string. The standard explanation is that this turns
283 * "%4e+foo" into "n foo" in the regular way. This is done in-place
284 * over the allocated string.
296 for ( ; '\0' != *p
; p
++, q
++) {
298 if ('\0' == (hex
[0] = *(p
+ 1)))
300 if ('\0' == (hex
[1] = *(p
+ 2)))
302 if (1 != sscanf(hex
, "%x", &c
))
310 *q
= '+' == *p
? ' ' : *p
;
318 resp_begin_http(int code
, const char *msg
)
322 printf("Status: %d %s\r\n", code
, msg
);
324 printf("Content-Type: text/html; charset=utf-8\r\n"
325 "Cache-Control: no-cache\r\n"
326 "Pragma: no-cache\r\n"
333 resp_copy(const char *filename
)
339 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
341 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
342 write(STDOUT_FILENO
, buf
, sz
);
348 resp_begin_html(int code
, const char *msg
)
351 resp_begin_http(code
, msg
);
353 printf("<!DOCTYPE html>\n"
356 " <meta charset=\"UTF-8\"/>\n"
357 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
358 " type=\"text/css\" media=\"all\">\n"
359 " <title>%s</title>\n"
362 CSS_DIR
, CUSTOMIZE_TITLE
);
364 resp_copy(MAN_DIR
"/header.html");
371 resp_copy(MAN_DIR
"/footer.html");
378 resp_searchform(const struct req
*req
, enum focus focus
)
382 printf("<form action=\"/%s\" method=\"get\">\n"
384 " <legend>Manual Page Search Parameters</legend>\n",
387 /* Write query input box. */
389 printf(" <input type=\"text\" name=\"query\" value=\"");
390 if (req
->q
.query
!= NULL
)
391 html_print(req
->q
.query
);
392 printf( "\" size=\"40\"");
393 if (focus
== FOCUS_QUERY
)
394 printf(" autofocus");
397 /* Write submission buttons. */
399 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
401 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
405 /* Write section selector. */
407 puts(" <select name=\"sec\">");
408 for (i
= 0; i
< sec_MAX
; i
++) {
409 printf(" <option value=\"%s\"", sec_numbers
[i
]);
410 if (NULL
!= req
->q
.sec
&&
411 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
412 printf(" selected=\"selected\"");
413 printf(">%s</option>\n", sec_names
[i
]);
417 /* Write architecture selector. */
419 printf( " <select name=\"arch\">\n"
420 " <option value=\"default\"");
421 if (NULL
== req
->q
.arch
)
422 printf(" selected=\"selected\"");
423 puts(">All Architectures</option>");
424 for (i
= 0; i
< arch_MAX
; i
++) {
425 printf(" <option value=\"%s\"", arch_names
[i
]);
426 if (NULL
!= req
->q
.arch
&&
427 0 == strcmp(arch_names
[i
], req
->q
.arch
))
428 printf(" selected=\"selected\"");
429 printf(">%s</option>\n", arch_names
[i
]);
433 /* Write manpath selector. */
436 puts(" <select name=\"manpath\">");
437 for (i
= 0; i
< (int)req
->psz
; i
++) {
439 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
440 printf("selected=\"selected\" ");
442 html_print(req
->p
[i
]);
444 html_print(req
->p
[i
]);
450 puts(" </fieldset>\n"
455 validate_urifrag(const char *frag
)
458 while ('\0' != *frag
) {
459 if ( ! (isalnum((unsigned char)*frag
) ||
460 '-' == *frag
|| '.' == *frag
||
461 '/' == *frag
|| '_' == *frag
))
469 validate_manpath(const struct req
*req
, const char* manpath
)
473 for (i
= 0; i
< req
->psz
; i
++)
474 if ( ! strcmp(manpath
, req
->p
[i
]))
481 validate_filename(const char *file
)
484 if ('.' == file
[0] && '/' == file
[1])
487 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
488 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
492 pg_index(const struct req
*req
)
495 resp_begin_html(200, NULL
);
496 resp_searchform(req
, FOCUS_QUERY
);
498 "This web interface is documented in the\n"
499 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
501 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
502 "manual explains the query syntax.\n"
504 scriptname
, *scriptname
== '\0' ? "" : "/",
505 scriptname
, *scriptname
== '\0' ? "" : "/");
510 pg_noresult(const struct req
*req
, const char *msg
)
512 resp_begin_html(200, NULL
);
513 resp_searchform(req
, FOCUS_QUERY
);
521 pg_error_badrequest(const char *msg
)
524 resp_begin_html(400, "Bad Request");
525 puts("<h1>Bad Request</h1>\n"
528 printf("Try again from the\n"
529 "<a href=\"/%s\">main page</a>.\n"
535 pg_error_internal(void)
537 resp_begin_html(500, "Internal Server Error");
538 puts("<p>Internal Server Error</p>");
543 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
545 char *arch
, *archend
;
548 int archprio
, archpriouse
;
551 for (i
= 0; i
< sz
; i
++) {
552 if (validate_filename(r
[i
].file
))
554 warnx("invalid filename %s in %s database",
555 r
[i
].file
, req
->q
.manpath
);
560 if (req
->isquery
&& sz
== 1) {
562 * If we have just one result, then jump there now
565 printf("Status: 303 See Other\r\n");
566 printf("Location: http://%s/%s%s%s/%s",
567 HTTP_HOST
, scriptname
,
568 *scriptname
== '\0' ? "" : "/",
569 req
->q
.manpath
, r
[0].file
);
571 "Content-Type: text/html; charset=utf-8\r\n"
576 resp_begin_html(200, NULL
);
578 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
581 puts("<table class=\"results\">");
582 for (i
= 0; i
< sz
; i
++) {
585 "<a class=\"Xr\" href=\"/%s%s%s/%s\">",
586 scriptname
, *scriptname
== '\0' ? "" : "/",
587 req
->q
.manpath
, r
[i
].file
);
588 html_print(r
[i
].names
);
590 " <td><span class=\"Nd\">");
591 html_print(r
[i
].output
);
592 puts("</span></td>\n"
599 * In man(1) mode, show one of the pages
600 * even if more than one is found.
603 if (req
->q
.equal
|| sz
== 1) {
608 for (i
= 0; i
< sz
; i
++) {
610 sec
+= strcspn(sec
, "123456789");
613 prio
= sec_prios
[sec
[0] - '1'];
616 if (req
->q
.arch
== NULL
) {
618 ((arch
= strchr(sec
+ 1, '/'))
620 ((archend
= strchr(arch
+ 1, '/'))
622 strncmp(arch
, "amd64/",
623 archend
- arch
) ? 2 : 1;
624 if (archprio
< archpriouse
) {
625 archpriouse
= archprio
;
630 if (archprio
> archpriouse
)
638 resp_show(req
, r
[iuse
].file
);
645 resp_catman(const struct req
*req
, const char *file
)
654 if ((f
= fopen(file
, "r")) == NULL
) {
655 puts("<p>You specified an invalid manual file.</p>");
659 puts("<div class=\"catman\">\n"
665 while ((len
= getline(&p
, &sz
, f
)) != -1) {
667 for (i
= 0; i
< len
- 1; i
++) {
669 * This means that the catpage is out of state.
670 * Ignore it and keep going (although the
674 if ('\b' == p
[i
] || '\n' == p
[i
])
678 * Print a regular character.
679 * Close out any bold/italic scopes.
680 * If we're in back-space mode, make sure we'll
681 * have something to enter when we backspace.
684 if ('\b' != p
[i
+ 1]) {
692 } else if (i
+ 2 >= len
)
710 * Handle funny behaviour troff-isms.
711 * These grok'd from the original man2html.c.
714 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
715 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
716 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
717 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
718 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
719 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
720 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
721 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
730 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
731 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
732 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
733 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
734 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
735 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
759 * Clean up the last character.
760 * We can get to a newline; don't print that.
768 if (i
== len
- 1 && p
[i
] != '\n')
782 resp_format(const struct req
*req
, const char *file
)
784 struct manoutput conf
;
786 struct roff_man
*man
;
791 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
792 puts("<p>You specified an invalid manual file.</p>");
797 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
,
798 MANDOCLEVEL_BADARG
, NULL
, req
->q
.manpath
);
799 mparse_readfd(mp
, fd
, file
);
802 memset(&conf
, 0, sizeof(conf
));
804 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
805 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
806 mandoc_asprintf(&conf
.man
, "/%s%s%%N.%%S",
807 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
809 mparse_result(mp
, &man
, NULL
);
811 warnx("fatal mandoc error: %s/%s", req
->q
.manpath
, file
);
818 vp
= html_alloc(&conf
);
820 if (man
->macroset
== MACROSET_MDOC
) {
836 resp_show(const struct req
*req
, const char *file
)
839 if ('.' == file
[0] && '/' == file
[1])
843 resp_catman(req
, file
);
845 resp_format(req
, file
);
849 pg_show(struct req
*req
, const char *fullpath
)
854 if ((file
= strchr(fullpath
, '/')) == NULL
) {
856 "You did not specify a page to show.");
859 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
862 if ( ! validate_manpath(req
, manpath
)) {
864 "You specified an invalid manpath.");
870 * Begin by chdir()ing into the manpath.
871 * This way we can pick up the database files, which are
872 * relative to the manpath root.
875 if (chdir(manpath
) == -1) {
876 warn("chdir %s", manpath
);
883 if ( ! validate_filename(file
)) {
885 "You specified an invalid manual file.");
889 resp_begin_html(200, NULL
);
890 resp_searchform(req
, FOCUS_NONE
);
891 resp_show(req
, file
);
896 pg_search(const struct req
*req
)
898 struct mansearch search
;
899 struct manpaths paths
;
902 char *query
, *rp
, *wp
;
907 * Begin by chdir()ing into the root of the manpath.
908 * This way we can pick up the database files, which are
909 * relative to the manpath root.
912 if (chdir(req
->q
.manpath
) == -1) {
913 warn("chdir %s", req
->q
.manpath
);
918 search
.arch
= req
->q
.arch
;
919 search
.sec
= req
->q
.sec
;
920 search
.outkey
= "Nd";
921 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
922 search
.firstmatch
= 1;
925 paths
.paths
= mandoc_malloc(sizeof(char *));
926 paths
.paths
[0] = mandoc_strdup(".");
929 * Break apart at spaces with backslash-escaping.
934 rp
= query
= mandoc_strdup(req
->q
.query
);
936 while (isspace((unsigned char)*rp
))
940 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
941 argv
[argc
++] = wp
= rp
;
943 if (isspace((unsigned char)*rp
)) {
948 if (rp
[0] == '\\' && rp
[1] != '\0')
959 if (0 == mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
))
960 pg_noresult(req
, "You entered an invalid query.");
962 pg_noresult(req
, "No results found.");
964 pg_searchres(req
, res
, ressz
);
967 mansearch_free(res
, ressz
);
968 free(paths
.paths
[0]);
976 struct itimerval itimer
;
978 const char *querystring
;
981 /* Poor man's ReDoS mitigation. */
983 itimer
.it_value
.tv_sec
= 2;
984 itimer
.it_value
.tv_usec
= 0;
985 itimer
.it_interval
.tv_sec
= 2;
986 itimer
.it_interval
.tv_usec
= 0;
987 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
994 * First we change directory into the MAN_DIR so that
995 * subsequent scanning for manpath directories is rooted
996 * relative to the same position.
999 if (chdir(MAN_DIR
) == -1) {
1000 warn("MAN_DIR: %s", MAN_DIR
);
1001 pg_error_internal();
1002 return EXIT_FAILURE
;
1005 memset(&req
, 0, sizeof(struct req
));
1007 parse_manpath_conf(&req
);
1009 /* Parse the path info and the query string. */
1011 if ((path
= getenv("PATH_INFO")) == NULL
)
1013 else if (*path
== '/')
1016 if (*path
!= '\0') {
1017 parse_path_info(&req
, path
);
1018 if (req
.q
.manpath
== NULL
|| access(path
, F_OK
) == -1)
1020 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1021 parse_query_string(&req
, querystring
);
1023 /* Validate parsed data and add defaults. */
1025 if (req
.q
.manpath
== NULL
)
1026 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1027 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1028 pg_error_badrequest(
1029 "You specified an invalid manpath.");
1030 return EXIT_FAILURE
;
1033 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1034 pg_error_badrequest(
1035 "You specified an invalid architecture.");
1036 return EXIT_FAILURE
;
1039 /* Dispatch to the three different pages. */
1042 pg_show(&req
, path
);
1043 else if (NULL
!= req
.q
.query
)
1048 free(req
.q
.manpath
);
1052 for (i
= 0; i
< (int)req
.psz
; i
++)
1055 return EXIT_SUCCESS
;
1059 * If PATH_INFO is not a file name, translate it to a query.
1062 parse_path_info(struct req
*req
, const char *path
)
1069 req
->q
.manpath
= mandoc_strdup(path
);
1072 /* Mandatory manual page name. */
1073 if ((req
->q
.query
= strrchr(req
->q
.manpath
, '/')) == NULL
) {
1074 req
->q
.query
= req
->q
.manpath
;
1075 req
->q
.manpath
= NULL
;
1077 *req
->q
.query
++ = '\0';
1079 /* Optional trailing section. */
1080 if ((req
->q
.sec
= strrchr(req
->q
.query
, '.')) != NULL
) {
1081 if(isdigit((unsigned char)req
->q
.sec
[1])) {
1082 *req
->q
.sec
++ = '\0';
1083 req
->q
.sec
= mandoc_strdup(req
->q
.sec
);
1088 /* Handle the case of name[.section] only. */
1089 if (req
->q
.manpath
== NULL
)
1091 req
->q
.query
= mandoc_strdup(req
->q
.query
);
1093 /* Split directory components. */
1094 dir
[i
= 0] = req
->q
.manpath
;
1095 while ((dir
[i
+ 1] = strchr(dir
[i
], '/')) != NULL
) {
1097 pg_error_badrequest(
1098 "You specified too many directory components.");
1104 /* Optional manpath. */
1105 if ((i
= validate_manpath(req
, req
->q
.manpath
)) == 0)
1106 req
->q
.manpath
= NULL
;
1107 else if (dir
[1] == NULL
)
1110 /* Optional section. */
1111 if (strncmp(dir
[i
], "man", 3) == 0) {
1113 req
->q
.sec
= mandoc_strdup(dir
[i
++] + 3);
1115 if (dir
[i
] == NULL
) {
1116 if (req
->q
.manpath
== NULL
)
1120 if (dir
[i
+ 1] != NULL
) {
1121 pg_error_badrequest(
1122 "You specified an invalid directory component.");
1126 /* Optional architecture. */
1128 req
->q
.arch
= mandoc_strdup(dir
[i
]);
1129 if (req
->q
.manpath
== NULL
)
1132 req
->q
.arch
= dir
[0];
1136 * Scan for indexable paths.
1139 parse_manpath_conf(struct req
*req
)
1146 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1147 warn("%s/manpath.conf", MAN_DIR
);
1148 pg_error_internal();
1155 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1156 if (dp
[len
- 1] == '\n')
1158 req
->p
= mandoc_realloc(req
->p
,
1159 (req
->psz
+ 1) * sizeof(char *));
1160 if ( ! validate_urifrag(dp
)) {
1161 warnx("%s/manpath.conf contains "
1162 "unsafe path \"%s\"", MAN_DIR
, dp
);
1163 pg_error_internal();
1166 if (strchr(dp
, '/') != NULL
) {
1167 warnx("%s/manpath.conf contains "
1168 "path with slash \"%s\"", MAN_DIR
, dp
);
1169 pg_error_internal();
1172 req
->p
[req
->psz
++] = dp
;
1178 if (req
->p
== NULL
) {
1179 warnx("%s/manpath.conf is empty", MAN_DIR
);
1180 pg_error_internal();