]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.148 2017/02/22 16:20:01 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
;
983 * The "rpath" pledge could be revoked after mparse_readfd()
984 * if the file desciptor to "/footer.html" would be opened
985 * up front, but it's probably not worth the complication
986 * of the code it would cause: it would require scattering
987 * pledge() calls in multiple low-level resp_*() functions.
990 if (pledge("stdio rpath", NULL
) == -1) {
997 /* Poor man's ReDoS mitigation. */
999 itimer
.it_value
.tv_sec
= 2;
1000 itimer
.it_value
.tv_usec
= 0;
1001 itimer
.it_interval
.tv_sec
= 2;
1002 itimer
.it_interval
.tv_usec
= 0;
1003 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1005 pg_error_internal();
1006 return EXIT_FAILURE
;
1010 * First we change directory into the MAN_DIR so that
1011 * subsequent scanning for manpath directories is rooted
1012 * relative to the same position.
1015 if (chdir(MAN_DIR
) == -1) {
1016 warn("MAN_DIR: %s", MAN_DIR
);
1017 pg_error_internal();
1018 return EXIT_FAILURE
;
1021 memset(&req
, 0, sizeof(struct req
));
1023 parse_manpath_conf(&req
);
1025 /* Parse the path info and the query string. */
1027 if ((path
= getenv("PATH_INFO")) == NULL
)
1029 else if (*path
== '/')
1032 if (*path
!= '\0') {
1033 parse_path_info(&req
, path
);
1034 if (req
.q
.manpath
== NULL
|| access(path
, F_OK
) == -1)
1036 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1037 parse_query_string(&req
, querystring
);
1039 /* Validate parsed data and add defaults. */
1041 if (req
.q
.manpath
== NULL
)
1042 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1043 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1044 pg_error_badrequest(
1045 "You specified an invalid manpath.");
1046 return EXIT_FAILURE
;
1049 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1050 pg_error_badrequest(
1051 "You specified an invalid architecture.");
1052 return EXIT_FAILURE
;
1055 /* Dispatch to the three different pages. */
1058 pg_show(&req
, path
);
1059 else if (NULL
!= req
.q
.query
)
1064 free(req
.q
.manpath
);
1068 for (i
= 0; i
< (int)req
.psz
; i
++)
1071 return EXIT_SUCCESS
;
1075 * If PATH_INFO is not a file name, translate it to a query.
1078 parse_path_info(struct req
*req
, const char *path
)
1085 req
->q
.manpath
= mandoc_strdup(path
);
1088 /* Mandatory manual page name. */
1089 if ((req
->q
.query
= strrchr(req
->q
.manpath
, '/')) == NULL
) {
1090 req
->q
.query
= req
->q
.manpath
;
1091 req
->q
.manpath
= NULL
;
1093 *req
->q
.query
++ = '\0';
1095 /* Optional trailing section. */
1096 if ((req
->q
.sec
= strrchr(req
->q
.query
, '.')) != NULL
) {
1097 if(isdigit((unsigned char)req
->q
.sec
[1])) {
1098 *req
->q
.sec
++ = '\0';
1099 req
->q
.sec
= mandoc_strdup(req
->q
.sec
);
1104 /* Handle the case of name[.section] only. */
1105 if (req
->q
.manpath
== NULL
)
1107 req
->q
.query
= mandoc_strdup(req
->q
.query
);
1109 /* Split directory components. */
1110 dir
[i
= 0] = req
->q
.manpath
;
1111 while ((dir
[i
+ 1] = strchr(dir
[i
], '/')) != NULL
) {
1113 pg_error_badrequest(
1114 "You specified too many directory components.");
1120 /* Optional manpath. */
1121 if ((i
= validate_manpath(req
, req
->q
.manpath
)) == 0)
1122 req
->q
.manpath
= NULL
;
1123 else if (dir
[1] == NULL
)
1126 /* Optional section. */
1127 if (strncmp(dir
[i
], "man", 3) == 0) {
1129 req
->q
.sec
= mandoc_strdup(dir
[i
++] + 3);
1131 if (dir
[i
] == NULL
) {
1132 if (req
->q
.manpath
== NULL
)
1136 if (dir
[i
+ 1] != NULL
) {
1137 pg_error_badrequest(
1138 "You specified an invalid directory component.");
1142 /* Optional architecture. */
1144 req
->q
.arch
= mandoc_strdup(dir
[i
]);
1145 if (req
->q
.manpath
== NULL
)
1148 req
->q
.arch
= dir
[0];
1152 * Scan for indexable paths.
1155 parse_manpath_conf(struct req
*req
)
1162 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1163 warn("%s/manpath.conf", MAN_DIR
);
1164 pg_error_internal();
1171 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1172 if (dp
[len
- 1] == '\n')
1174 req
->p
= mandoc_realloc(req
->p
,
1175 (req
->psz
+ 1) * sizeof(char *));
1176 if ( ! validate_urifrag(dp
)) {
1177 warnx("%s/manpath.conf contains "
1178 "unsafe path \"%s\"", MAN_DIR
, dp
);
1179 pg_error_internal();
1182 if (strchr(dp
, '/') != NULL
) {
1183 warnx("%s/manpath.conf contains "
1184 "path with slash \"%s\"", MAN_DIR
, dp
);
1185 pg_error_internal();
1188 req
->p
[req
->psz
++] = dp
;
1194 if (req
->p
== NULL
) {
1195 warnx("%s/manpath.conf is empty", MAN_DIR
);
1196 pg_error_internal();