]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.141 2016/09/12 00:06:20 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014, 2015, 2016 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>
34 #include "mandoc_aux.h"
41 #include "mansearch.h"
45 * A query as passed to the search function.
48 char *manpath
; /* desired manual directory */
49 char *arch
; /* architecture */
50 char *sec
; /* manual section */
51 char *query
; /* unparsed query expression */
52 int equal
; /* match whole names, not substrings */
57 char **p
; /* array of available manpaths */
58 size_t psz
; /* number of available manpaths */
59 int isquery
; /* QUERY_STRING used, not PATH_INFO */
67 static void html_print(const char *);
68 static void html_putchar(char);
69 static int http_decode(char *);
70 static void parse_manpath_conf(struct req
*);
71 static void parse_path_info(struct req
*req
, const char *path
);
72 static void parse_query_string(struct req
*, const char *);
73 static void pg_error_badrequest(const char *);
74 static void pg_error_internal(void);
75 static void pg_index(const struct req
*);
76 static void pg_noresult(const struct req
*, const char *);
77 static void pg_search(const struct req
*);
78 static void pg_searchres(const struct req
*,
79 struct manpage
*, size_t);
80 static void pg_show(struct req
*, const char *);
81 static void resp_begin_html(int, const char *);
82 static void resp_begin_http(int, const char *);
83 static void resp_catman(const struct req
*, const char *);
84 static void resp_copy(const char *);
85 static void resp_end_html(void);
86 static void resp_format(const struct req
*, const char *);
87 static void resp_searchform(const struct req
*, enum focus
);
88 static void resp_show(const struct req
*, const char *);
89 static void set_query_attr(char **, char **);
90 static int validate_filename(const char *);
91 static int validate_manpath(const struct req
*, const char *);
92 static int validate_urifrag(const char *);
94 static const char *scriptname
= SCRIPT_NAME
;
96 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
97 static const char *const sec_numbers
[] = {
98 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
100 static const char *const sec_names
[] = {
102 "1 - General Commands",
104 "3 - Library Functions",
106 "4 - Device Drivers",
109 "7 - Miscellaneous Information",
110 "8 - System Manager\'s Manual",
111 "9 - Kernel Developer\'s Manual"
113 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
115 static const char *const arch_names
[] = {
116 "amd64", "alpha", "armv7",
117 "hppa", "i386", "landisk",
118 "loongson", "luna88k", "macppc", "mips64",
119 "octeon", "sgi", "socppc", "sparc64",
120 "amiga", "arc", "armish", "arm32",
121 "atari", "aviion", "beagle", "cats",
123 "ia64", "mac68k", "mvme68k", "mvme88k",
124 "mvmeppc", "palm", "pc532", "pegasos",
125 "pmax", "powerpc", "solbourne", "sparc",
126 "sun3", "vax", "wgrisc", "x68k",
129 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
132 * Print a character, escaping HTML along the way.
133 * This will pass non-ASCII straight to output: be warned!
153 putchar((unsigned char)c
);
159 * Call through to html_putchar().
160 * Accepts NULL strings.
163 html_print(const char *p
)
173 * Transfer the responsibility for the allocated string *val
174 * to the query structure.
177 set_query_attr(char **attr
, char **val
)
190 * Parse the QUERY_STRING for key-value pairs
191 * and store the values into the query structure.
194 parse_query_string(struct req
*req
, const char *qs
)
200 req
->q
.manpath
= NULL
;
207 while (*qs
!= '\0') {
211 keysz
= strcspn(qs
, "=;&");
212 key
= mandoc_strndup(qs
, keysz
);
217 /* Parse one value. */
219 valsz
= strcspn(++qs
, ";&");
220 val
= mandoc_strndup(qs
, valsz
);
223 /* Decode and catch encoding errors. */
225 if ( ! (http_decode(key
) && http_decode(val
)))
228 /* Handle key-value pairs. */
230 if ( ! strcmp(key
, "query"))
231 set_query_attr(&req
->q
.query
, &val
);
233 else if ( ! strcmp(key
, "apropos"))
234 req
->q
.equal
= !strcmp(val
, "0");
236 else if ( ! strcmp(key
, "manpath")) {
238 if ( ! strncmp(val
, "OpenBSD ", 8)) {
244 set_query_attr(&req
->q
.manpath
, &val
);
247 else if ( ! (strcmp(key
, "sec")
249 && strcmp(key
, "sektion")
252 if ( ! strcmp(val
, "0"))
254 set_query_attr(&req
->q
.sec
, &val
);
257 else if ( ! strcmp(key
, "arch")) {
258 if ( ! strcmp(val
, "default"))
260 set_query_attr(&req
->q
.arch
, &val
);
264 * The key must be freed in any case.
265 * The val may have been handed over to the query
266 * structure, in which case it is now NULL.
280 * HTTP-decode a string. The standard explanation is that this turns
281 * "%4e+foo" into "n foo" in the regular way. This is done in-place
282 * over the allocated string.
294 for ( ; '\0' != *p
; p
++, q
++) {
296 if ('\0' == (hex
[0] = *(p
+ 1)))
298 if ('\0' == (hex
[1] = *(p
+ 2)))
300 if (1 != sscanf(hex
, "%x", &c
))
308 *q
= '+' == *p
? ' ' : *p
;
316 resp_begin_http(int code
, const char *msg
)
320 printf("Status: %d %s\r\n", code
, msg
);
322 printf("Content-Type: text/html; charset=utf-8\r\n"
323 "Cache-Control: no-cache\r\n"
324 "Pragma: no-cache\r\n"
331 resp_copy(const char *filename
)
337 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
339 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
340 write(STDOUT_FILENO
, buf
, sz
);
346 resp_begin_html(int code
, const char *msg
)
349 resp_begin_http(code
, msg
);
351 printf("<!DOCTYPE html>\n"
354 "<meta charset=\"UTF-8\"/>\n"
355 "<link rel=\"stylesheet\" href=\"%s/mandoc.css\""
356 " type=\"text/css\" media=\"all\">\n"
357 "<title>%s</title>\n"
360 "<!-- Begin page content. //-->\n",
361 CSS_DIR
, CUSTOMIZE_TITLE
);
363 resp_copy(MAN_DIR
"/header.html");
370 resp_copy(MAN_DIR
"/footer.html");
377 resp_searchform(const struct req
*req
, enum focus focus
)
381 puts("<!-- Begin search form. //-->");
382 printf("<div id=\"mancgi\">\n"
383 "<form action=\"/%s\" method=\"get\">\n"
385 "<legend>Manual Page Search Parameters</legend>\n",
388 /* Write query input box. */
390 printf("<input type=\"text\" name=\"query\" value=\"");
391 if (req
->q
.query
!= NULL
)
392 html_print(req
->q
.query
);
393 printf( "\" size=\"40\"");
394 if (focus
== FOCUS_QUERY
)
395 printf(" autofocus");
398 /* Write submission buttons. */
400 printf( "<button type=\"submit\" name=\"apropos\" value=\"0\">"
402 "<button type=\"submit\" name=\"apropos\" value=\"1\">"
403 "apropos</button>\n<br/>\n");
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
]);
453 puts("<!-- End search form. //-->");
457 validate_urifrag(const char *frag
)
460 while ('\0' != *frag
) {
461 if ( ! (isalnum((unsigned char)*frag
) ||
462 '-' == *frag
|| '.' == *frag
||
463 '/' == *frag
|| '_' == *frag
))
471 validate_manpath(const struct req
*req
, const char* manpath
)
475 for (i
= 0; i
< req
->psz
; i
++)
476 if ( ! strcmp(manpath
, req
->p
[i
]))
483 validate_filename(const char *file
)
486 if ('.' == file
[0] && '/' == file
[1])
489 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
490 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
494 pg_index(const struct req
*req
)
497 resp_begin_html(200, NULL
);
498 resp_searchform(req
, FOCUS_QUERY
);
500 "This web interface is documented in the\n"
501 "<a href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
503 "<a href=\"/%s%sapropos.1\">apropos(1)</a>\n"
504 "manual explains the query syntax.\n"
506 scriptname
, *scriptname
== '\0' ? "" : "/",
507 scriptname
, *scriptname
== '\0' ? "" : "/");
512 pg_noresult(const struct req
*req
, const char *msg
)
514 resp_begin_html(200, NULL
);
515 resp_searchform(req
, FOCUS_QUERY
);
523 pg_error_badrequest(const char *msg
)
526 resp_begin_html(400, "Bad Request");
527 puts("<h1>Bad Request</h1>\n"
530 printf("Try again from the\n"
531 "<a href=\"/%s\">main page</a>.\n"
537 pg_error_internal(void)
539 resp_begin_html(500, "Internal Server Error");
540 puts("<p>Internal Server Error</p>");
545 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
547 char *arch
, *archend
;
550 int archprio
, archpriouse
;
553 for (i
= 0; i
< sz
; i
++) {
554 if (validate_filename(r
[i
].file
))
556 warnx("invalid filename %s in %s database",
557 r
[i
].file
, req
->q
.manpath
);
562 if (req
->isquery
&& sz
== 1) {
564 * If we have just one result, then jump there now
567 printf("Status: 303 See Other\r\n");
568 printf("Location: http://%s/%s%s%s/%s",
569 HTTP_HOST
, scriptname
,
570 *scriptname
== '\0' ? "" : "/",
571 req
->q
.manpath
, r
[0].file
);
573 "Content-Type: text/html; charset=utf-8\r\n"
578 resp_begin_html(200, NULL
);
580 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
583 puts("<div class=\"results\">");
586 for (i
= 0; i
< sz
; i
++) {
588 "<td class=\"title\">\n"
589 "<a href=\"/%s%s%s/%s",
590 scriptname
, *scriptname
== '\0' ? "" : "/",
591 req
->q
.manpath
, r
[i
].file
);
593 html_print(r
[i
].names
);
596 "<td class=\"desc\">");
597 html_print(r
[i
].output
);
607 * In man(1) mode, show one of the pages
608 * even if more than one is found.
611 if (req
->q
.equal
|| sz
== 1) {
616 for (i
= 0; i
< sz
; i
++) {
618 sec
+= strcspn(sec
, "123456789");
621 prio
= sec_prios
[sec
[0] - '1'];
624 if (req
->q
.arch
== NULL
) {
626 ((arch
= strchr(sec
+ 1, '/'))
628 ((archend
= strchr(arch
+ 1, '/'))
630 strncmp(arch
, "amd64/",
631 archend
- arch
) ? 2 : 1;
632 if (archprio
< archpriouse
) {
633 archpriouse
= archprio
;
638 if (archprio
> archpriouse
)
646 resp_show(req
, r
[iuse
].file
);
653 resp_catman(const struct req
*req
, const char *file
)
662 if ((f
= fopen(file
, "r")) == NULL
) {
663 puts("<p>You specified an invalid manual file.</p>");
667 puts("<div class=\"catman\">\n"
673 while ((len
= getline(&p
, &sz
, f
)) != -1) {
675 for (i
= 0; i
< len
- 1; i
++) {
677 * This means that the catpage is out of state.
678 * Ignore it and keep going (although the
682 if ('\b' == p
[i
] || '\n' == p
[i
])
686 * Print a regular character.
687 * Close out any bold/italic scopes.
688 * If we're in back-space mode, make sure we'll
689 * have something to enter when we backspace.
692 if ('\b' != p
[i
+ 1]) {
700 } else if (i
+ 2 >= len
)
718 * Handle funny behaviour troff-isms.
719 * These grok'd from the original man2html.c.
722 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
723 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
724 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
725 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
726 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
727 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
728 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
729 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
738 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
739 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
740 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
741 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
742 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
743 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
767 * Clean up the last character.
768 * We can get to a newline; don't print that.
776 if (i
== len
- 1 && p
[i
] != '\n')
790 resp_format(const struct req
*req
, const char *file
)
792 struct manoutput conf
;
794 struct roff_man
*man
;
799 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
800 puts("<p>You specified an invalid manual file.</p>");
805 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
,
806 MANDOCLEVEL_BADARG
, NULL
, req
->q
.manpath
);
807 mparse_readfd(mp
, fd
, file
);
810 memset(&conf
, 0, sizeof(conf
));
812 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
813 mandoc_asprintf(&conf
.man
, "/%s%s%%N.%%S",
814 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
816 mparse_result(mp
, &man
, NULL
);
818 warnx("fatal mandoc error: %s/%s", req
->q
.manpath
, file
);
825 vp
= html_alloc(&conf
);
827 if (man
->macroset
== MACROSET_MDOC
) {
842 resp_show(const struct req
*req
, const char *file
)
845 if ('.' == file
[0] && '/' == file
[1])
849 resp_catman(req
, file
);
851 resp_format(req
, file
);
855 pg_show(struct req
*req
, const char *fullpath
)
860 if ((file
= strchr(fullpath
, '/')) == NULL
) {
862 "You did not specify a page to show.");
865 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
868 if ( ! validate_manpath(req
, manpath
)) {
870 "You specified an invalid manpath.");
876 * Begin by chdir()ing into the manpath.
877 * This way we can pick up the database files, which are
878 * relative to the manpath root.
881 if (chdir(manpath
) == -1) {
882 warn("chdir %s", manpath
);
889 if ( ! validate_filename(file
)) {
891 "You specified an invalid manual file.");
895 resp_begin_html(200, NULL
);
896 resp_searchform(req
, FOCUS_NONE
);
897 resp_show(req
, file
);
902 pg_search(const struct req
*req
)
904 struct mansearch search
;
905 struct manpaths paths
;
908 char *query
, *rp
, *wp
;
913 * Begin by chdir()ing into the root of the manpath.
914 * This way we can pick up the database files, which are
915 * relative to the manpath root.
918 if (chdir(req
->q
.manpath
) == -1) {
919 warn("chdir %s", req
->q
.manpath
);
924 search
.arch
= req
->q
.arch
;
925 search
.sec
= req
->q
.sec
;
926 search
.outkey
= "Nd";
927 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
928 search
.firstmatch
= 1;
931 paths
.paths
= mandoc_malloc(sizeof(char *));
932 paths
.paths
[0] = mandoc_strdup(".");
935 * Break apart at spaces with backslash-escaping.
940 rp
= query
= mandoc_strdup(req
->q
.query
);
942 while (isspace((unsigned char)*rp
))
946 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
947 argv
[argc
++] = wp
= rp
;
949 if (isspace((unsigned char)*rp
)) {
954 if (rp
[0] == '\\' && rp
[1] != '\0')
965 if (0 == mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
))
966 pg_noresult(req
, "You entered an invalid query.");
968 pg_noresult(req
, "No results found.");
970 pg_searchres(req
, res
, ressz
);
973 mansearch_free(res
, ressz
);
974 free(paths
.paths
[0]);
982 struct itimerval itimer
;
984 const char *querystring
;
987 /* Poor man's ReDoS mitigation. */
989 itimer
.it_value
.tv_sec
= 2;
990 itimer
.it_value
.tv_usec
= 0;
991 itimer
.it_interval
.tv_sec
= 2;
992 itimer
.it_interval
.tv_usec
= 0;
993 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1000 * First we change directory into the MAN_DIR so that
1001 * subsequent scanning for manpath directories is rooted
1002 * relative to the same position.
1005 if (chdir(MAN_DIR
) == -1) {
1006 warn("MAN_DIR: %s", MAN_DIR
);
1007 pg_error_internal();
1008 return EXIT_FAILURE
;
1011 memset(&req
, 0, sizeof(struct req
));
1013 parse_manpath_conf(&req
);
1015 /* Parse the path info and the query string. */
1017 if ((path
= getenv("PATH_INFO")) == NULL
)
1019 else if (*path
== '/')
1022 if (*path
!= '\0') {
1023 parse_path_info(&req
, path
);
1024 if (req
.q
.manpath
== NULL
|| access(path
, F_OK
) == -1)
1026 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1027 parse_query_string(&req
, querystring
);
1029 /* Validate parsed data and add defaults. */
1031 if (req
.q
.manpath
== NULL
)
1032 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1033 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1034 pg_error_badrequest(
1035 "You specified an invalid manpath.");
1036 return EXIT_FAILURE
;
1039 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1040 pg_error_badrequest(
1041 "You specified an invalid architecture.");
1042 return EXIT_FAILURE
;
1045 /* Dispatch to the three different pages. */
1048 pg_show(&req
, path
);
1049 else if (NULL
!= req
.q
.query
)
1054 free(req
.q
.manpath
);
1058 for (i
= 0; i
< (int)req
.psz
; i
++)
1061 return EXIT_SUCCESS
;
1065 * If PATH_INFO is not a file name, translate it to a query.
1068 parse_path_info(struct req
*req
, const char *path
)
1075 req
->q
.manpath
= mandoc_strdup(path
);
1078 /* Mandatory manual page name. */
1079 if ((req
->q
.query
= strrchr(req
->q
.manpath
, '/')) == NULL
) {
1080 req
->q
.query
= req
->q
.manpath
;
1081 req
->q
.manpath
= NULL
;
1083 *req
->q
.query
++ = '\0';
1085 /* Optional trailing section. */
1086 if ((req
->q
.sec
= strrchr(req
->q
.query
, '.')) != NULL
) {
1087 if(isdigit((unsigned char)req
->q
.sec
[1])) {
1088 *req
->q
.sec
++ = '\0';
1089 req
->q
.sec
= mandoc_strdup(req
->q
.sec
);
1094 /* Handle the case of name[.section] only. */
1095 if (req
->q
.manpath
== NULL
)
1097 req
->q
.query
= mandoc_strdup(req
->q
.query
);
1099 /* Split directory components. */
1100 dir
[i
= 0] = req
->q
.manpath
;
1101 while ((dir
[i
+ 1] = strchr(dir
[i
], '/')) != NULL
) {
1103 pg_error_badrequest(
1104 "You specified too many directory components.");
1110 /* Optional manpath. */
1111 if ((i
= validate_manpath(req
, req
->q
.manpath
)) == 0)
1112 req
->q
.manpath
= NULL
;
1113 else if (dir
[1] == NULL
)
1116 /* Optional section. */
1117 if (strncmp(dir
[i
], "man", 3) == 0) {
1119 req
->q
.sec
= mandoc_strdup(dir
[i
++] + 3);
1121 if (dir
[i
] == NULL
) {
1122 if (req
->q
.manpath
== NULL
)
1126 if (dir
[i
+ 1] != NULL
) {
1127 pg_error_badrequest(
1128 "You specified an invalid directory component.");
1132 /* Optional architecture. */
1134 req
->q
.arch
= mandoc_strdup(dir
[i
]);
1135 if (req
->q
.manpath
== NULL
)
1138 req
->q
.arch
= dir
[0];
1142 * Scan for indexable paths.
1145 parse_manpath_conf(struct req
*req
)
1152 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1153 warn("%s/manpath.conf", MAN_DIR
);
1154 pg_error_internal();
1161 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1162 if (dp
[len
- 1] == '\n')
1164 req
->p
= mandoc_realloc(req
->p
,
1165 (req
->psz
+ 1) * sizeof(char *));
1166 if ( ! validate_urifrag(dp
)) {
1167 warnx("%s/manpath.conf contains "
1168 "unsafe path \"%s\"", MAN_DIR
, dp
);
1169 pg_error_internal();
1172 if (strchr(dp
, '/') != NULL
) {
1173 warnx("%s/manpath.conf contains "
1174 "path with slash \"%s\"", MAN_DIR
, dp
);
1175 pg_error_internal();
1178 req
->p
[req
->psz
++] = dp
;
1184 if (req
->p
== NULL
) {
1185 warnx("%s/manpath.conf is empty", MAN_DIR
);
1186 pg_error_internal();