]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.136 2016/07/31 23:37:23 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", "armish", "armv7",
117 "hppa", "hppa64", "i386", "landisk",
118 "loongson", "luna88k", "macppc", "mips64",
119 "octeon", "sgi", "socppc", "sparc",
121 "amiga", "arc", "arm32", "atari",
122 "aviion", "beagle", "cats", "hp300",
123 "ia64", "mac68k", "mvme68k", "mvme88k",
124 "mvmeppc", "palm", "pc532", "pegasos",
125 "pmax", "powerpc", "solbourne", "sun3",
126 "vax", "wgrisc", "x68k"
128 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
131 * Print a character, escaping HTML along the way.
132 * This will pass non-ASCII straight to output: be warned!
152 putchar((unsigned char)c
);
158 * Call through to html_putchar().
159 * Accepts NULL strings.
162 html_print(const char *p
)
172 * Transfer the responsibility for the allocated string *val
173 * to the query structure.
176 set_query_attr(char **attr
, char **val
)
189 * Parse the QUERY_STRING for key-value pairs
190 * and store the values into the query structure.
193 parse_query_string(struct req
*req
, const char *qs
)
199 req
->q
.manpath
= NULL
;
206 while (*qs
!= '\0') {
210 keysz
= strcspn(qs
, "=;&");
211 key
= mandoc_strndup(qs
, keysz
);
216 /* Parse one value. */
218 valsz
= strcspn(++qs
, ";&");
219 val
= mandoc_strndup(qs
, valsz
);
222 /* Decode and catch encoding errors. */
224 if ( ! (http_decode(key
) && http_decode(val
)))
227 /* Handle key-value pairs. */
229 if ( ! strcmp(key
, "query"))
230 set_query_attr(&req
->q
.query
, &val
);
232 else if ( ! strcmp(key
, "apropos"))
233 req
->q
.equal
= !strcmp(val
, "0");
235 else if ( ! strcmp(key
, "manpath")) {
237 if ( ! strncmp(val
, "OpenBSD ", 8)) {
243 set_query_attr(&req
->q
.manpath
, &val
);
246 else if ( ! (strcmp(key
, "sec")
248 && strcmp(key
, "sektion")
251 if ( ! strcmp(val
, "0"))
253 set_query_attr(&req
->q
.sec
, &val
);
256 else if ( ! strcmp(key
, "arch")) {
257 if ( ! strcmp(val
, "default"))
259 set_query_attr(&req
->q
.arch
, &val
);
263 * The key must be freed in any case.
264 * The val may have been handed over to the query
265 * structure, in which case it is now NULL.
279 * HTTP-decode a string. The standard explanation is that this turns
280 * "%4e+foo" into "n foo" in the regular way. This is done in-place
281 * over the allocated string.
293 for ( ; '\0' != *p
; p
++, q
++) {
295 if ('\0' == (hex
[0] = *(p
+ 1)))
297 if ('\0' == (hex
[1] = *(p
+ 2)))
299 if (1 != sscanf(hex
, "%x", &c
))
307 *q
= '+' == *p
? ' ' : *p
;
315 resp_begin_http(int code
, const char *msg
)
319 printf("Status: %d %s\r\n", code
, msg
);
321 printf("Content-Type: text/html; charset=utf-8\r\n"
322 "Cache-Control: no-cache\r\n"
323 "Pragma: no-cache\r\n"
330 resp_copy(const char *filename
)
336 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
338 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
339 write(STDOUT_FILENO
, buf
, sz
);
344 resp_begin_html(int code
, const char *msg
)
347 resp_begin_http(code
, msg
);
349 printf("<!DOCTYPE html>\n"
352 "<meta charset=\"UTF-8\"/>\n"
353 "<link rel=\"stylesheet\" href=\"%s/mandoc.css\""
354 " type=\"text/css\" media=\"all\">\n"
355 "<title>%s</title>\n"
358 "<!-- Begin page content. //-->\n",
359 CSS_DIR
, CUSTOMIZE_TITLE
);
361 resp_copy(MAN_DIR
"/header.html");
368 resp_copy(MAN_DIR
"/footer.html");
375 resp_searchform(const struct req
*req
, enum focus focus
)
379 puts("<!-- Begin search form. //-->");
380 printf("<div id=\"mancgi\">\n"
381 "<form action=\"/%s\" method=\"get\">\n"
383 "<legend>Manual Page Search Parameters</legend>\n",
386 /* Write query input box. */
388 printf("<input type=\"text\" name=\"query\" value=\"");
389 if (req
->q
.query
!= NULL
)
390 html_print(req
->q
.query
);
391 printf( "\" size=\"40\"");
392 if (focus
== FOCUS_QUERY
)
393 printf(" autofocus");
396 /* Write submission buttons. */
398 printf( "<button type=\"submit\" name=\"apropos\" value=\"0\">"
400 "<button type=\"submit\" name=\"apropos\" value=\"1\">"
401 "apropos</button>\n<br/>\n");
403 /* Write section selector. */
405 puts("<select name=\"sec\">");
406 for (i
= 0; i
< sec_MAX
; i
++) {
407 printf("<option value=\"%s\"", sec_numbers
[i
]);
408 if (NULL
!= req
->q
.sec
&&
409 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
410 printf(" selected=\"selected\"");
411 printf(">%s</option>\n", sec_names
[i
]);
415 /* Write architecture selector. */
417 printf( "<select name=\"arch\">\n"
418 "<option value=\"default\"");
419 if (NULL
== req
->q
.arch
)
420 printf(" selected=\"selected\"");
421 puts(">All Architectures</option>");
422 for (i
= 0; i
< arch_MAX
; i
++) {
423 printf("<option value=\"%s\"", arch_names
[i
]);
424 if (NULL
!= req
->q
.arch
&&
425 0 == strcmp(arch_names
[i
], req
->q
.arch
))
426 printf(" selected=\"selected\"");
427 printf(">%s</option>\n", arch_names
[i
]);
431 /* Write manpath selector. */
434 puts("<select name=\"manpath\">");
435 for (i
= 0; i
< (int)req
->psz
; i
++) {
437 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
438 printf("selected=\"selected\" ");
440 html_print(req
->p
[i
]);
442 html_print(req
->p
[i
]);
451 puts("<!-- End search form. //-->");
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 href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
501 "<a 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("<div class=\"results\">");
584 for (i
= 0; i
< sz
; i
++) {
586 "<td class=\"title\">\n"
587 "<a href=\"/%s%s%s/%s",
588 scriptname
, *scriptname
== '\0' ? "" : "/",
589 req
->q
.manpath
, r
[i
].file
);
591 html_print(r
[i
].names
);
594 "<td class=\"desc\">");
595 html_print(r
[i
].output
);
605 * In man(1) mode, show one of the pages
606 * even if more than one is found.
609 if (req
->q
.equal
|| sz
== 1) {
614 for (i
= 0; i
< sz
; i
++) {
616 sec
+= strcspn(sec
, "123456789");
619 prio
= sec_prios
[sec
[0] - '1'];
622 if (req
->q
.arch
== NULL
) {
624 ((arch
= strchr(sec
+ 1, '/'))
626 ((archend
= strchr(arch
+ 1, '/'))
628 strncmp(arch
, "amd64/",
629 archend
- arch
) ? 2 : 1;
630 if (archprio
< archpriouse
) {
631 archpriouse
= archprio
;
636 if (archprio
> archpriouse
)
644 resp_show(req
, r
[iuse
].file
);
651 resp_catman(const struct req
*req
, const char *file
)
660 if ((f
= fopen(file
, "r")) == NULL
) {
661 puts("<p>You specified an invalid manual file.</p>");
665 puts("<div class=\"catman\">\n"
671 while ((len
= getline(&p
, &sz
, f
)) != -1) {
673 for (i
= 0; i
< len
- 1; i
++) {
675 * This means that the catpage is out of state.
676 * Ignore it and keep going (although the
680 if ('\b' == p
[i
] || '\n' == p
[i
])
684 * Print a regular character.
685 * Close out any bold/italic scopes.
686 * If we're in back-space mode, make sure we'll
687 * have something to enter when we backspace.
690 if ('\b' != p
[i
+ 1]) {
698 } else if (i
+ 2 >= len
)
716 * Handle funny behaviour troff-isms.
717 * These grok'd from the original man2html.c.
720 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
721 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
722 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
723 ('=' == 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])) {
736 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
737 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
738 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
739 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
740 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
741 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
765 * Clean up the last character.
766 * We can get to a newline; don't print that.
774 if (i
== len
- 1 && p
[i
] != '\n')
788 resp_format(const struct req
*req
, const char *file
)
790 struct manoutput conf
;
792 struct roff_man
*man
;
797 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
798 puts("<p>You specified an invalid manual file.</p>");
803 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
,
804 MANDOCLEVEL_BADARG
, NULL
, req
->q
.manpath
);
805 mparse_readfd(mp
, fd
, file
);
808 memset(&conf
, 0, sizeof(conf
));
810 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
811 mandoc_asprintf(&conf
.man
, "/%s%s%%N.%%S",
812 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
814 mparse_result(mp
, &man
, NULL
);
816 warnx("fatal mandoc error: %s/%s", req
->q
.manpath
, file
);
823 vp
= html_alloc(&conf
);
825 if (man
->macroset
== MACROSET_MDOC
) {
840 resp_show(const struct req
*req
, const char *file
)
843 if ('.' == file
[0] && '/' == file
[1])
847 resp_catman(req
, file
);
849 resp_format(req
, file
);
853 pg_show(struct req
*req
, const char *fullpath
)
858 if ((file
= strchr(fullpath
, '/')) == NULL
) {
860 "You did not specify a page to show.");
863 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
866 if ( ! validate_manpath(req
, manpath
)) {
868 "You specified an invalid manpath.");
874 * Begin by chdir()ing into the manpath.
875 * This way we can pick up the database files, which are
876 * relative to the manpath root.
879 if (chdir(manpath
) == -1) {
880 warn("chdir %s", manpath
);
887 if ( ! validate_filename(file
)) {
889 "You specified an invalid manual file.");
893 resp_begin_html(200, NULL
);
894 resp_searchform(req
, FOCUS_NONE
);
895 resp_show(req
, file
);
900 pg_search(const struct req
*req
)
902 struct mansearch search
;
903 struct manpaths paths
;
906 char *query
, *rp
, *wp
;
911 * Begin by chdir()ing into the root of the manpath.
912 * This way we can pick up the database files, which are
913 * relative to the manpath root.
916 if (chdir(req
->q
.manpath
) == -1) {
917 warn("chdir %s", req
->q
.manpath
);
922 search
.arch
= req
->q
.arch
;
923 search
.sec
= req
->q
.sec
;
924 search
.outkey
= "Nd";
925 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
926 search
.firstmatch
= 1;
929 paths
.paths
= mandoc_malloc(sizeof(char *));
930 paths
.paths
[0] = mandoc_strdup(".");
933 * Break apart at spaces with backslash-escaping.
938 rp
= query
= mandoc_strdup(req
->q
.query
);
940 while (isspace((unsigned char)*rp
))
944 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
945 argv
[argc
++] = wp
= rp
;
947 if (isspace((unsigned char)*rp
)) {
952 if (rp
[0] == '\\' && rp
[1] != '\0')
963 if (0 == mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
))
964 pg_noresult(req
, "You entered an invalid query.");
966 pg_noresult(req
, "No results found.");
968 pg_searchres(req
, res
, ressz
);
971 mansearch_free(res
, ressz
);
972 free(paths
.paths
[0]);
980 struct itimerval itimer
;
982 const char *querystring
;
985 /* Poor man's ReDoS mitigation. */
987 itimer
.it_value
.tv_sec
= 2;
988 itimer
.it_value
.tv_usec
= 0;
989 itimer
.it_interval
.tv_sec
= 2;
990 itimer
.it_interval
.tv_usec
= 0;
991 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
998 * First we change directory into the MAN_DIR so that
999 * subsequent scanning for manpath directories is rooted
1000 * relative to the same position.
1003 if (chdir(MAN_DIR
) == -1) {
1004 warn("MAN_DIR: %s", MAN_DIR
);
1005 pg_error_internal();
1006 return EXIT_FAILURE
;
1009 memset(&req
, 0, sizeof(struct req
));
1011 parse_manpath_conf(&req
);
1013 /* Parse the path info and the query string. */
1015 if ((path
= getenv("PATH_INFO")) == NULL
)
1017 else if (*path
== '/')
1020 if (*path
!= '\0') {
1021 parse_path_info(&req
, path
);
1022 if (req
.q
.manpath
== NULL
|| access(path
, F_OK
) == -1)
1024 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1025 parse_query_string(&req
, querystring
);
1027 /* Validate parsed data and add defaults. */
1029 if (req
.q
.manpath
== NULL
)
1030 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1031 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1032 pg_error_badrequest(
1033 "You specified an invalid manpath.");
1034 return EXIT_FAILURE
;
1037 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1038 pg_error_badrequest(
1039 "You specified an invalid architecture.");
1040 return EXIT_FAILURE
;
1043 /* Dispatch to the three different pages. */
1046 pg_show(&req
, path
);
1047 else if (NULL
!= req
.q
.query
)
1052 free(req
.q
.manpath
);
1056 for (i
= 0; i
< (int)req
.psz
; i
++)
1059 return EXIT_SUCCESS
;
1063 * If PATH_INFO is not a file name, translate it to a query.
1066 parse_path_info(struct req
*req
, const char *path
)
1073 req
->q
.manpath
= mandoc_strdup(path
);
1076 /* Mandatory manual page name. */
1077 if ((req
->q
.query
= strrchr(req
->q
.manpath
, '/')) == NULL
) {
1078 req
->q
.query
= req
->q
.manpath
;
1079 req
->q
.manpath
= NULL
;
1081 *req
->q
.query
++ = '\0';
1083 /* Optional trailing section. */
1084 if ((req
->q
.sec
= strrchr(req
->q
.query
, '.')) != NULL
) {
1085 if(isdigit((unsigned char)req
->q
.sec
[1])) {
1086 *req
->q
.sec
++ = '\0';
1087 req
->q
.sec
= mandoc_strdup(req
->q
.sec
);
1092 /* Handle the case of name[.section] only. */
1093 if (req
->q
.manpath
== NULL
)
1095 req
->q
.query
= mandoc_strdup(req
->q
.query
);
1097 /* Split directory components. */
1098 dir
[i
= 0] = req
->q
.manpath
;
1099 while ((dir
[i
+ 1] = strchr(dir
[i
], '/')) != NULL
) {
1101 pg_error_badrequest(
1102 "You specified too many directory components.");
1108 /* Optional manpath. */
1109 if ((i
= validate_manpath(req
, req
->q
.manpath
)) == 0)
1110 req
->q
.manpath
= NULL
;
1111 else if (dir
[1] == NULL
)
1114 /* Optional section. */
1115 if (strncmp(dir
[i
], "man", 3) == 0) {
1117 req
->q
.sec
= mandoc_strdup(dir
[i
++] + 3);
1119 if (dir
[i
] == NULL
) {
1120 if (req
->q
.manpath
== NULL
)
1124 if (dir
[i
+ 1] != NULL
) {
1125 pg_error_badrequest(
1126 "You specified an invalid directory component.");
1130 /* Optional architecture. */
1132 req
->q
.arch
= mandoc_strdup(dir
[i
]);
1133 if (req
->q
.manpath
== NULL
)
1136 req
->q
.arch
= dir
[0];
1140 * Scan for indexable paths.
1143 parse_manpath_conf(struct req
*req
)
1150 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1151 warn("%s/manpath.conf", MAN_DIR
);
1152 pg_error_internal();
1159 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1160 if (dp
[len
- 1] == '\n')
1162 req
->p
= mandoc_realloc(req
->p
,
1163 (req
->psz
+ 1) * sizeof(char *));
1164 if ( ! validate_urifrag(dp
)) {
1165 warnx("%s/manpath.conf contains "
1166 "unsafe path \"%s\"", MAN_DIR
, dp
);
1167 pg_error_internal();
1170 if (strchr(dp
, '/') != NULL
) {
1171 warnx("%s/manpath.conf contains "
1172 "path with slash \"%s\"", MAN_DIR
, dp
);
1173 pg_error_internal();
1176 req
->p
[req
->psz
++] = dp
;
1182 if (req
->p
== NULL
) {
1183 warnx("%s/manpath.conf is empty", MAN_DIR
);
1184 pg_error_internal();