]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.96 2014/08/26 11:21:40 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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"
37 #include "mansearch.h"
41 * A query as passed to the search function.
44 char *manpath
; /* desired manual directory */
45 char *arch
; /* architecture */
46 char *sec
; /* manual section */
47 char *query
; /* unparsed query expression */
48 int equal
; /* match whole names, not substrings */
53 char **p
; /* array of available manpaths */
54 size_t psz
; /* number of available manpaths */
57 static void catman(const struct req
*, const char *);
58 static void format(const struct req
*, const char *);
59 static void html_print(const char *);
60 static void html_putchar(char);
61 static int http_decode(char *);
62 static void http_parse(struct req
*, const char *);
63 static void http_print(const char *);
64 static void http_putchar(char);
65 static void http_printquery(const struct req
*, const char *);
66 static void pathgen(struct req
*);
67 static void pg_error_badrequest(const char *);
68 static void pg_error_internal(void);
69 static void pg_index(const struct req
*);
70 static void pg_noresult(const struct req
*, const char *);
71 static void pg_search(const struct req
*);
72 static void pg_searchres(const struct req
*,
73 struct manpage
*, size_t);
74 static void pg_show(struct req
*, const char *);
75 static void resp_begin_html(int, const char *);
76 static void resp_begin_http(int, const char *);
77 static void resp_end_html(void);
78 static void resp_searchform(const struct req
*);
79 static void resp_show(const struct req
*, const char *);
80 static void set_query_attr(char **, char **);
81 static int validate_filename(const char *);
82 static int validate_manpath(const struct req
*, const char *);
83 static int validate_urifrag(const char *);
85 static const char *scriptname
; /* CGI script name */
87 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
88 static const char *const sec_numbers
[] = {
89 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
91 static const char *const sec_names
[] = {
93 "1 - General Commands",
95 "3 - Library Functions",
100 "7 - Miscellaneous Information",
101 "8 - System Manager\'s Manual",
102 "9 - Kernel Developer\'s Manual"
104 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
106 static const char *const arch_names
[] = {
107 "amd64", "alpha", "armish", "armv7",
108 "aviion", "hppa", "hppa64", "i386",
109 "ia64", "landisk", "loongson", "luna88k",
110 "macppc", "mips64", "octeon", "sgi",
111 "socppc", "solbourne", "sparc", "sparc64",
113 "amiga", "arc", "arm32", "atari",
114 "beagle", "cats", "hp300", "mac68k",
115 "mvme68k", "mvme88k", "mvmeppc", "palm",
116 "pc532", "pegasos", "pmax", "powerpc",
117 "sun3", "wgrisc", "x68k"
119 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
122 * Print a character, escaping HTML along the way.
123 * This will pass non-ASCII straight to output: be warned!
143 putchar((unsigned char)c
);
149 http_printquery(const struct req
*req
, const char *sep
)
152 if (NULL
!= req
->q
.query
) {
154 http_print(req
->q
.query
);
156 if (0 == req
->q
.equal
)
157 printf("%sapropos=1", sep
);
158 if (NULL
!= req
->q
.sec
) {
159 printf("%ssec=", sep
);
160 http_print(req
->q
.sec
);
162 if (NULL
!= req
->q
.arch
) {
163 printf("%sarch=", sep
);
164 http_print(req
->q
.arch
);
166 if (NULL
!= req
->q
.manpath
&&
167 strcmp(req
->q
.manpath
, req
->p
[0])) {
168 printf("%smanpath=", sep
);
169 http_print(req
->q
.manpath
);
174 http_print(const char *p
)
184 * Call through to html_putchar().
185 * Accepts NULL strings.
188 html_print(const char *p
)
198 * Transfer the responsibility for the allocated string *val
199 * to the query structure.
202 set_query_attr(char **attr
, char **val
)
215 * Parse the QUERY_STRING for key-value pairs
216 * and store the values into the query structure.
219 http_parse(struct req
*req
, const char *qs
)
224 req
->q
.manpath
= NULL
;
231 while (*qs
!= '\0') {
235 keysz
= strcspn(qs
, "=;&");
236 key
= mandoc_strndup(qs
, keysz
);
241 /* Parse one value. */
243 valsz
= strcspn(++qs
, ";&");
244 val
= mandoc_strndup(qs
, valsz
);
247 /* Decode and catch encoding errors. */
249 if ( ! (http_decode(key
) && http_decode(val
)))
252 /* Handle key-value pairs. */
254 if ( ! strcmp(key
, "query"))
255 set_query_attr(&req
->q
.query
, &val
);
257 else if ( ! strcmp(key
, "apropos"))
258 req
->q
.equal
= !strcmp(val
, "0");
260 else if ( ! strcmp(key
, "manpath")) {
262 if ( ! strncmp(val
, "OpenBSD ", 8)) {
268 set_query_attr(&req
->q
.manpath
, &val
);
271 else if ( ! (strcmp(key
, "sec")
273 && strcmp(key
, "sektion")
276 if ( ! strcmp(val
, "0"))
278 set_query_attr(&req
->q
.sec
, &val
);
281 else if ( ! strcmp(key
, "arch")) {
282 if ( ! strcmp(val
, "default"))
284 set_query_attr(&req
->q
.arch
, &val
);
288 * The key must be freed in any case.
289 * The val may have been handed over to the query
290 * structure, in which case it is now NULL.
302 /* Fall back to the default manpath. */
304 if (req
->q
.manpath
== NULL
)
305 req
->q
.manpath
= mandoc_strdup(req
->p
[0]);
312 if (isalnum((unsigned char)c
)) {
313 putchar((unsigned char)c
);
315 } else if (' ' == c
) {
323 * HTTP-decode a string. The standard explanation is that this turns
324 * "%4e+foo" into "n foo" in the regular way. This is done in-place
325 * over the allocated string.
337 for ( ; '\0' != *p
; p
++, q
++) {
339 if ('\0' == (hex
[0] = *(p
+ 1)))
341 if ('\0' == (hex
[1] = *(p
+ 2)))
343 if (1 != sscanf(hex
, "%x", &c
))
351 *q
= '+' == *p
? ' ' : *p
;
359 resp_begin_http(int code
, const char *msg
)
363 printf("Status: %d %s\r\n", code
, msg
);
365 printf("Content-Type: text/html; charset=utf-8\r\n"
366 "Cache-Control: no-cache\r\n"
367 "Pragma: no-cache\r\n"
374 resp_begin_html(int code
, const char *msg
)
377 resp_begin_http(code
, msg
);
379 printf("<!DOCTYPE HTML PUBLIC "
380 " \"-//W3C//DTD HTML 4.01//EN\""
381 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
384 "<META HTTP-EQUIV=\"Content-Type\""
385 " CONTENT=\"text/html; charset=utf-8\">\n"
386 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
387 " TYPE=\"text/css\" media=\"all\">\n"
388 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
389 " TYPE=\"text/css\" media=\"all\">\n"
390 "<TITLE>%s</TITLE>\n"
393 "<!-- Begin page content. //-->\n",
394 CSS_DIR
, CSS_DIR
, CUSTOMIZE_TITLE
);
406 resp_searchform(const struct req
*req
)
410 puts(CUSTOMIZE_BEGIN
);
411 puts("<!-- Begin search form. //-->");
412 printf("<DIV ID=\"mancgi\">\n"
413 "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
415 "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
418 /* Write query input box. */
420 printf( "<TABLE><TR><TD>\n"
421 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
422 if (NULL
!= req
->q
.query
)
423 html_print(req
->q
.query
);
424 puts("\" SIZE=\"40\">");
426 /* Write submission and reset buttons. */
428 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
429 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
431 /* Write show radio button */
433 printf( "</TD><TD>\n"
434 "<INPUT TYPE=\"radio\" ");
436 printf("CHECKED=\"checked\" ");
437 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
438 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
440 /* Write section selector. */
442 puts( "</TD></TR><TR><TD>\n"
443 "<SELECT NAME=\"sec\">");
444 for (i
= 0; i
< sec_MAX
; i
++) {
445 printf("<OPTION VALUE=\"%s\"", sec_numbers
[i
]);
446 if (NULL
!= req
->q
.sec
&&
447 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
448 printf(" SELECTED=\"selected\"");
449 printf(">%s</OPTION>\n", sec_names
[i
]);
453 /* Write architecture selector. */
455 printf( "<SELECT NAME=\"arch\">\n"
456 "<OPTION VALUE=\"default\"");
457 if (NULL
== req
->q
.arch
)
458 printf(" SELECTED=\"selected\"");
459 puts(">All Architectures</OPTION>");
460 for (i
= 0; i
< arch_MAX
; i
++) {
461 printf("<OPTION VALUE=\"%s\"", arch_names
[i
]);
462 if (NULL
!= req
->q
.arch
&&
463 0 == strcmp(arch_names
[i
], req
->q
.arch
))
464 printf(" SELECTED=\"selected\"");
465 printf(">%s</OPTION>\n", arch_names
[i
]);
469 /* Write manpath selector. */
472 puts("<SELECT NAME=\"manpath\">");
473 for (i
= 0; i
< (int)req
->psz
; i
++) {
475 if (NULL
== req
->q
.manpath
? 0 == i
:
476 0 == strcmp(req
->q
.manpath
, req
->p
[i
]))
477 printf("SELECTED=\"selected\" ");
479 html_print(req
->p
[i
]);
481 html_print(req
->p
[i
]);
487 /* Write search radio button */
489 printf( "</TD><TD>\n"
490 "<INPUT TYPE=\"radio\" ");
491 if (0 == req
->q
.equal
)
492 printf("CHECKED=\"checked\" ");
493 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
494 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
496 puts("</TD></TR></TABLE>\n"
500 puts("<!-- End search form. //-->");
504 validate_urifrag(const char *frag
)
507 while ('\0' != *frag
) {
508 if ( ! (isalnum((unsigned char)*frag
) ||
509 '-' == *frag
|| '.' == *frag
||
510 '/' == *frag
|| '_' == *frag
))
518 validate_manpath(const struct req
*req
, const char* manpath
)
522 if ( ! strcmp(manpath
, "mandoc"))
525 for (i
= 0; i
< req
->psz
; i
++)
526 if ( ! strcmp(manpath
, req
->p
[i
]))
533 validate_filename(const char *file
)
536 if ('.' == file
[0] && '/' == file
[1])
539 return ( ! (strstr(file
, "../") || strstr(file
, "/..") ||
540 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3))));
544 pg_index(const struct req
*req
)
547 resp_begin_html(200, NULL
);
548 resp_searchform(req
);
550 "This web interface is documented in the\n"
551 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A>\n"
553 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A>\n"
554 "manual explains the query syntax.\n"
556 scriptname
, scriptname
);
561 pg_noresult(const struct req
*req
, const char *msg
)
563 resp_begin_html(200, NULL
);
564 resp_searchform(req
);
572 pg_error_badrequest(const char *msg
)
575 resp_begin_html(400, "Bad Request");
576 puts("<H1>Bad Request</H1>\n"
579 printf("Try again from the\n"
580 "<A HREF=\"%s\">main page</A>.\n"
586 pg_error_internal(void)
588 resp_begin_html(500, "Internal Server Error");
589 puts("<P>Internal Server Error</P>");
594 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
596 char *arch
, *archend
;
597 size_t i
, iuse
, isec
;
598 int archprio
, archpriouse
;
602 for (i
= 0; i
< sz
; i
++) {
603 if (validate_filename(r
[i
].file
))
605 fprintf(stderr
, "invalid filename %s in %s database\n",
606 r
[i
].file
, req
->q
.manpath
);
613 * If we have just one result, then jump there now
616 printf("Status: 303 See Other\r\n");
617 printf("Location: http://%s%s/%s/%s?",
618 HTTP_HOST
, scriptname
, req
->q
.manpath
, r
[0].file
);
619 http_printquery(req
, "&");
621 "Content-Type: text/html; charset=utf-8\r\n"
626 resp_begin_html(200, NULL
);
627 resp_searchform(req
);
628 puts("<DIV CLASS=\"results\">");
631 for (i
= 0; i
< sz
; i
++) {
633 "<TD CLASS=\"title\">\n"
634 "<A HREF=\"%s/%s/%s?",
635 scriptname
, req
->q
.manpath
, r
[i
].file
);
636 http_printquery(req
, "&");
638 html_print(r
[i
].names
);
641 "<TD CLASS=\"desc\">");
642 html_print(r
[i
].output
);
651 * In man(1) mode, show one of the pages
652 * even if more than one is found.
660 for (i
= 0; i
< sz
; i
++) {
661 isec
= strcspn(r
[i
].file
, "123456789");
662 sec
= r
[i
].file
[isec
];
665 prio
= sec_prios
[sec
- '1'];
666 if (NULL
== req
->q
.arch
) {
668 (NULL
== (arch
= strchr(
669 r
[i
].file
+ isec
, '/'))) ? 3 :
670 (NULL
== (archend
= strchr(
671 arch
+ 1, '/'))) ? 0 :
672 strncmp(arch
, "amd64/",
673 archend
- arch
) ? 2 : 1;
674 if (archprio
< archpriouse
) {
675 archpriouse
= archprio
;
680 if (archprio
> archpriouse
)
688 resp_show(req
, r
[iuse
].file
);
695 catman(const struct req
*req
, const char *file
)
703 if (NULL
== (f
= fopen(file
, "r"))) {
704 puts("<P>You specified an invalid manual file.</P>");
708 puts("<DIV CLASS=\"catman\">\n"
711 while (NULL
!= (p
= fgetln(f
, &len
))) {
713 for (i
= 0; i
< (int)len
- 1; i
++) {
715 * This means that the catpage is out of state.
716 * Ignore it and keep going (although the
720 if ('\b' == p
[i
] || '\n' == p
[i
])
724 * Print a regular character.
725 * Close out any bold/italic scopes.
726 * If we're in back-space mode, make sure we'll
727 * have something to enter when we backspace.
730 if ('\b' != p
[i
+ 1]) {
738 } else if (i
+ 2 >= (int)len
)
756 * Handle funny behaviour troff-isms.
757 * These grok'd from the original man2html.c.
760 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
761 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
762 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
763 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
764 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
765 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
766 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
767 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
776 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
777 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
778 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
779 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
780 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
781 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
805 * Clean up the last character.
806 * We can get to a newline; don't print that.
814 if (i
== (int)len
- 1 && '\n' != p
[i
])
827 format(const struct req
*req
, const char *file
)
838 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
839 puts("<P>You specified an invalid manual file.</P>");
843 mp
= mparse_alloc(MPARSE_SO
, MANDOCLEVEL_FATAL
, NULL
,
845 rc
= mparse_readfd(mp
, fd
, file
);
848 if (rc
>= MANDOCLEVEL_FATAL
) {
849 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
850 req
->q
.manpath
, file
);
855 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
856 mandoc_asprintf(&opts
,
857 "fragment,man=%s?query=%%N&sec=%%S%s%s%s%s",
859 req
->q
.arch
? "&arch=" : "",
860 req
->q
.arch
? req
->q
.arch
: "",
861 usepath
? "&manpath=" : "",
862 usepath
? req
->q
.manpath
: "");
864 mparse_result(mp
, &mdoc
, &man
, NULL
);
865 if (NULL
== man
&& NULL
== mdoc
) {
866 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
867 req
->q
.manpath
, file
);
873 vp
= html_alloc(opts
);
886 resp_show(const struct req
*req
, const char *file
)
889 if ('.' == file
[0] && '/' == file
[1])
899 pg_show(struct req
*req
, const char *fullpath
)
904 if ((file
= strchr(fullpath
, '/')) == NULL
) {
906 "You did not specify a page to show.");
909 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
912 if ( ! validate_manpath(req
, manpath
)) {
914 "You specified an invalid manpath.");
920 * Begin by chdir()ing into the manpath.
921 * This way we can pick up the database files, which are
922 * relative to the manpath root.
925 if (chdir(manpath
) == -1) {
926 fprintf(stderr
, "chdir %s: %s\n",
927 manpath
, strerror(errno
));
933 if (strcmp(manpath
, "mandoc")) {
934 free(req
->q
.manpath
);
935 req
->q
.manpath
= manpath
;
939 if ( ! validate_filename(file
)) {
941 "You specified an invalid manual file.");
945 resp_begin_html(200, NULL
);
946 resp_searchform(req
);
947 resp_show(req
, file
);
952 pg_search(const struct req
*req
)
954 struct mansearch search
;
955 struct manpaths paths
;
958 const char *ep
, *start
;
963 * Begin by chdir()ing into the root of the manpath.
964 * This way we can pick up the database files, which are
965 * relative to the manpath root.
968 if (-1 == (chdir(req
->q
.manpath
))) {
969 fprintf(stderr
, "chdir %s: %s\n",
970 req
->q
.manpath
, strerror(errno
));
975 search
.arch
= req
->q
.arch
;
976 search
.sec
= req
->q
.sec
;
977 search
.outkey
= "Nd";
978 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
981 paths
.paths
= mandoc_malloc(sizeof(char *));
982 paths
.paths
[0] = mandoc_strdup(".");
985 * Poor man's tokenisation: just break apart by spaces.
986 * Yes, this is half-ass. But it works for now.
990 while (ep
&& isspace((unsigned char)*ep
))
995 while (ep
&& '\0' != *ep
) {
996 cp
= mandoc_reallocarray(cp
, sz
+ 1, sizeof(char *));
998 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
1000 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
1001 memcpy(cp
[sz
], start
, ep
- start
);
1002 cp
[sz
++][ep
- start
] = '\0';
1003 while (isspace((unsigned char)*ep
))
1007 if (0 == mansearch(&search
, &paths
, sz
, cp
, &res
, &ressz
))
1008 pg_noresult(req
, "You entered an invalid query.");
1009 else if (0 == ressz
)
1010 pg_noresult(req
, "No results found.");
1012 pg_searchres(req
, res
, ressz
);
1014 for (i
= 0; i
< sz
; i
++)
1018 for (i
= 0; i
< (int)ressz
; i
++) {
1021 free(res
[i
].output
);
1025 free(paths
.paths
[0]);
1033 struct itimerval itimer
;
1035 const char *querystring
;
1038 /* Poor man's ReDoS mitigation. */
1040 itimer
.it_value
.tv_sec
= 1;
1041 itimer
.it_value
.tv_usec
= 0;
1042 itimer
.it_interval
.tv_sec
= 1;
1043 itimer
.it_interval
.tv_usec
= 0;
1044 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1045 fprintf(stderr
, "setitimer: %s\n", strerror(errno
));
1046 pg_error_internal();
1047 return(EXIT_FAILURE
);
1050 /* Scan our run-time environment. */
1052 if (NULL
== (scriptname
= getenv("SCRIPT_NAME")))
1055 if ( ! validate_urifrag(scriptname
)) {
1056 fprintf(stderr
, "unsafe SCRIPT_NAME \"%s\"\n",
1058 pg_error_internal();
1059 return(EXIT_FAILURE
);
1063 * First we change directory into the MAN_DIR so that
1064 * subsequent scanning for manpath directories is rooted
1065 * relative to the same position.
1068 if (-1 == chdir(MAN_DIR
)) {
1069 fprintf(stderr
, "MAN_DIR: %s: %s\n",
1070 MAN_DIR
, strerror(errno
));
1071 pg_error_internal();
1072 return(EXIT_FAILURE
);
1075 memset(&req
, 0, sizeof(struct req
));
1078 /* Next parse out the query string. */
1080 if (NULL
!= (querystring
= getenv("QUERY_STRING")))
1081 http_parse(&req
, querystring
);
1083 if ( ! (NULL
== req
.q
.manpath
||
1084 validate_manpath(&req
, req
.q
.manpath
))) {
1085 pg_error_badrequest(
1086 "You specified an invalid manpath.");
1087 return(EXIT_FAILURE
);
1090 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1091 pg_error_badrequest(
1092 "You specified an invalid architecture.");
1093 return(EXIT_FAILURE
);
1096 /* Dispatch to the three different pages. */
1098 path
= getenv("PATH_INFO");
1101 else if ('/' == *path
)
1105 pg_show(&req
, path
);
1106 else if (NULL
!= req
.q
.query
)
1111 free(req
.q
.manpath
);
1115 for (i
= 0; i
< (int)req
.psz
; i
++)
1118 return(EXIT_SUCCESS
);
1122 * Scan for indexable paths.
1125 pathgen(struct req
*req
)
1131 if (NULL
== (fp
= fopen("manpath.conf", "r"))) {
1132 fprintf(stderr
, "%s/manpath.conf: %s\n",
1133 MAN_DIR
, strerror(errno
));
1134 pg_error_internal();
1138 while (NULL
!= (dp
= fgetln(fp
, &dpsz
))) {
1139 if ('\n' == dp
[dpsz
- 1])
1141 req
->p
= mandoc_realloc(req
->p
,
1142 (req
->psz
+ 1) * sizeof(char *));
1143 dp
= mandoc_strndup(dp
, dpsz
);
1144 if ( ! validate_urifrag(dp
)) {
1145 fprintf(stderr
, "%s/manpath.conf contains "
1146 "unsafe path \"%s\"\n", MAN_DIR
, dp
);
1147 pg_error_internal();
1150 if (NULL
!= strchr(dp
, '/')) {
1151 fprintf(stderr
, "%s/manpath.conf contains "
1152 "path with slash \"%s\"\n", MAN_DIR
, dp
);
1153 pg_error_internal();
1156 req
->p
[req
->psz
++] = dp
;
1159 if ( req
->p
== NULL
) {
1160 fprintf(stderr
, "%s/manpath.conf is empty\n", MAN_DIR
);
1161 pg_error_internal();