]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.157 2018/05/18 14:23:00 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_redirect(const struct req
*, const char *);
80 static void pg_search(const struct req
*);
81 static void pg_searchres(const struct req
*,
82 struct manpage
*, size_t);
83 static void pg_show(struct req
*, const char *);
84 static void resp_begin_html(int, const char *, const char *);
85 static void resp_begin_http(int, const char *);
86 static void resp_catman(const struct req
*, const char *);
87 static void resp_copy(const char *);
88 static void resp_end_html(void);
89 static void resp_format(const struct req
*, const char *);
90 static void resp_searchform(const struct req
*, enum focus
);
91 static void resp_show(const struct req
*, const char *);
92 static void set_query_attr(char **, char **);
93 static int validate_filename(const char *);
94 static int validate_manpath(const struct req
*, const char *);
95 static int validate_urifrag(const char *);
97 static const char *scriptname
= SCRIPT_NAME
;
99 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
100 static const char *const sec_numbers
[] = {
101 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
103 static const char *const sec_names
[] = {
105 "1 - General Commands",
107 "3 - Library Functions",
109 "4 - Device Drivers",
112 "7 - Miscellaneous Information",
113 "8 - System Manager\'s Manual",
114 "9 - Kernel Developer\'s Manual"
116 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
118 static const char *const arch_names
[] = {
119 "amd64", "alpha", "armv7", "arm64",
120 "hppa", "i386", "landisk",
121 "loongson", "luna88k", "macppc", "mips64",
122 "octeon", "sgi", "socppc", "sparc64",
123 "amiga", "arc", "armish", "arm32",
124 "atari", "aviion", "beagle", "cats",
126 "ia64", "mac68k", "mvme68k", "mvme88k",
127 "mvmeppc", "palm", "pc532", "pegasos",
128 "pmax", "powerpc", "solbourne", "sparc",
129 "sun3", "vax", "wgrisc", "x68k",
132 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
135 * Print a character, escaping HTML along the way.
136 * This will pass non-ASCII straight to output: be warned!
156 putchar((unsigned char)c
);
162 * Call through to html_putchar().
163 * Accepts NULL strings.
166 html_print(const char *p
)
176 * Transfer the responsibility for the allocated string *val
177 * to the query structure.
180 set_query_attr(char **attr
, char **val
)
193 * Parse the QUERY_STRING for key-value pairs
194 * and store the values into the query structure.
197 parse_query_string(struct req
*req
, const char *qs
)
203 req
->q
.manpath
= NULL
;
210 while (*qs
!= '\0') {
214 keysz
= strcspn(qs
, "=;&");
215 key
= mandoc_strndup(qs
, keysz
);
220 /* Parse one value. */
222 valsz
= strcspn(++qs
, ";&");
223 val
= mandoc_strndup(qs
, valsz
);
226 /* Decode and catch encoding errors. */
228 if ( ! (http_decode(key
) && http_decode(val
)))
231 /* Handle key-value pairs. */
233 if ( ! strcmp(key
, "query"))
234 set_query_attr(&req
->q
.query
, &val
);
236 else if ( ! strcmp(key
, "apropos"))
237 req
->q
.equal
= !strcmp(val
, "0");
239 else if ( ! strcmp(key
, "manpath")) {
241 if ( ! strncmp(val
, "OpenBSD ", 8)) {
247 set_query_attr(&req
->q
.manpath
, &val
);
250 else if ( ! (strcmp(key
, "sec")
252 && strcmp(key
, "sektion")
255 if ( ! strcmp(val
, "0"))
257 set_query_attr(&req
->q
.sec
, &val
);
260 else if ( ! strcmp(key
, "arch")) {
261 if ( ! strcmp(val
, "default"))
263 set_query_attr(&req
->q
.arch
, &val
);
267 * The key must be freed in any case.
268 * The val may have been handed over to the query
269 * structure, in which case it is now NULL.
283 * HTTP-decode a string. The standard explanation is that this turns
284 * "%4e+foo" into "n foo" in the regular way. This is done in-place
285 * over the allocated string.
297 for ( ; '\0' != *p
; p
++, q
++) {
299 if ('\0' == (hex
[0] = *(p
+ 1)))
301 if ('\0' == (hex
[1] = *(p
+ 2)))
303 if (1 != sscanf(hex
, "%x", &c
))
311 *q
= '+' == *p
? ' ' : *p
;
319 resp_begin_http(int code
, const char *msg
)
323 printf("Status: %d %s\r\n", code
, msg
);
325 printf("Content-Type: text/html; charset=utf-8\r\n"
326 "Cache-Control: no-cache\r\n"
327 "Pragma: no-cache\r\n"
334 resp_copy(const char *filename
)
340 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
342 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
343 write(STDOUT_FILENO
, buf
, sz
);
349 resp_begin_html(int code
, const char *msg
, const char *file
)
353 resp_begin_http(code
, msg
);
355 printf("<!DOCTYPE html>\n"
358 " <meta charset=\"UTF-8\"/>\n"
359 " <meta name=\"viewport\""
360 " content=\"width=device-width, initial-scale=1.0\">\n"
361 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
362 " type=\"text/css\" media=\"all\">\n"
366 if ((cp
= strrchr(file
, '/')) != NULL
)
368 if ((cp
= strrchr(file
, '.')) != NULL
) {
369 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
371 printf("%s - ", file
);
373 printf("%s</title>\n"
378 resp_copy(MAN_DIR
"/header.html");
385 resp_copy(MAN_DIR
"/footer.html");
392 resp_searchform(const struct req
*req
, enum focus focus
)
396 printf("<form action=\"/%s\" method=\"get\">\n"
398 " <legend>Manual Page Search Parameters</legend>\n",
401 /* Write query input box. */
403 printf(" <input type=\"text\" name=\"query\" value=\"");
404 if (req
->q
.query
!= NULL
)
405 html_print(req
->q
.query
);
406 printf( "\" size=\"40\"");
407 if (focus
== FOCUS_QUERY
)
408 printf(" autofocus");
411 /* Write submission buttons. */
413 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
415 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
419 /* Write section selector. */
421 puts(" <select name=\"sec\">");
422 for (i
= 0; i
< sec_MAX
; i
++) {
423 printf(" <option value=\"%s\"", sec_numbers
[i
]);
424 if (NULL
!= req
->q
.sec
&&
425 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
426 printf(" selected=\"selected\"");
427 printf(">%s</option>\n", sec_names
[i
]);
431 /* Write architecture selector. */
433 printf( " <select name=\"arch\">\n"
434 " <option value=\"default\"");
435 if (NULL
== req
->q
.arch
)
436 printf(" selected=\"selected\"");
437 puts(">All Architectures</option>");
438 for (i
= 0; i
< arch_MAX
; i
++) {
439 printf(" <option value=\"%s\"", arch_names
[i
]);
440 if (NULL
!= req
->q
.arch
&&
441 0 == strcmp(arch_names
[i
], req
->q
.arch
))
442 printf(" selected=\"selected\"");
443 printf(">%s</option>\n", arch_names
[i
]);
447 /* Write manpath selector. */
450 puts(" <select name=\"manpath\">");
451 for (i
= 0; i
< (int)req
->psz
; i
++) {
453 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
454 printf("selected=\"selected\" ");
456 html_print(req
->p
[i
]);
458 html_print(req
->p
[i
]);
464 puts(" </fieldset>\n"
469 validate_urifrag(const char *frag
)
472 while ('\0' != *frag
) {
473 if ( ! (isalnum((unsigned char)*frag
) ||
474 '-' == *frag
|| '.' == *frag
||
475 '/' == *frag
|| '_' == *frag
))
483 validate_manpath(const struct req
*req
, const char* manpath
)
487 for (i
= 0; i
< req
->psz
; i
++)
488 if ( ! strcmp(manpath
, req
->p
[i
]))
495 validate_filename(const char *file
)
498 if ('.' == file
[0] && '/' == file
[1])
501 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
502 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
506 pg_index(const struct req
*req
)
509 resp_begin_html(200, NULL
, NULL
);
510 resp_searchform(req
, FOCUS_QUERY
);
512 "This web interface is documented in the\n"
513 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
515 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
516 "manual explains the query syntax.\n"
518 scriptname
, *scriptname
== '\0' ? "" : "/",
519 scriptname
, *scriptname
== '\0' ? "" : "/");
524 pg_noresult(const struct req
*req
, const char *msg
)
526 resp_begin_html(200, NULL
, NULL
);
527 resp_searchform(req
, FOCUS_QUERY
);
535 pg_error_badrequest(const char *msg
)
538 resp_begin_html(400, "Bad Request", NULL
);
539 puts("<h1>Bad Request</h1>\n"
542 printf("Try again from the\n"
543 "<a href=\"/%s\">main page</a>.\n"
549 pg_error_internal(void)
551 resp_begin_html(500, "Internal Server Error", NULL
);
552 puts("<p>Internal Server Error</p>");
557 pg_redirect(const struct req
*req
, const char *name
)
559 printf("Status: 303 See Other\r\n"
561 if (*scriptname
!= '\0')
562 printf("%s/", scriptname
);
563 if (strcmp(req
->q
.manpath
, req
->p
[0]))
564 printf("%s/", req
->q
.manpath
);
565 if (req
->q
.arch
!= NULL
)
566 printf("%s/", req
->q
.arch
);
568 if (req
->q
.sec
!= NULL
)
569 printf(".%s", req
->q
.sec
);
570 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
574 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
576 char *arch
, *archend
;
579 int archprio
, archpriouse
;
582 for (i
= 0; i
< sz
; i
++) {
583 if (validate_filename(r
[i
].file
))
585 warnx("invalid filename %s in %s database",
586 r
[i
].file
, req
->q
.manpath
);
591 if (req
->isquery
&& sz
== 1) {
593 * If we have just one result, then jump there now
596 printf("Status: 303 See Other\r\n"
598 if (*scriptname
!= '\0')
599 printf("%s/", scriptname
);
600 if (strcmp(req
->q
.manpath
, req
->p
[0]))
601 printf("%s/", req
->q
.manpath
);
603 "Content-Type: text/html; charset=utf-8\r\n\r\n",
609 * In man(1) mode, show one of the pages
610 * even if more than one is found.
614 if (req
->q
.equal
|| sz
== 1) {
617 for (i
= 0; i
< sz
; i
++) {
619 sec
+= strcspn(sec
, "123456789");
622 prio
= sec_prios
[sec
[0] - '1'];
625 if (req
->q
.arch
== NULL
) {
627 ((arch
= strchr(sec
+ 1, '/'))
629 ((archend
= strchr(arch
+ 1, '/'))
631 strncmp(arch
, "amd64/",
632 archend
- arch
) ? 2 : 1;
633 if (archprio
< archpriouse
) {
634 archpriouse
= archprio
;
639 if (archprio
> archpriouse
)
647 resp_begin_html(200, NULL
, r
[iuse
].file
);
649 resp_begin_html(200, NULL
, NULL
);
652 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
655 puts("<table class=\"results\">");
656 for (i
= 0; i
< sz
; i
++) {
659 "<a class=\"Xr\" href=\"/");
660 if (*scriptname
!= '\0')
661 printf("%s/", scriptname
);
662 if (strcmp(req
->q
.manpath
, req
->p
[0]))
663 printf("%s/", req
->q
.manpath
);
664 printf("%s\">", r
[i
].file
);
665 html_print(r
[i
].names
);
667 " <td><span class=\"Nd\">");
668 html_print(r
[i
].output
);
669 puts("</span></td>\n"
675 if (req
->q
.equal
|| sz
== 1) {
677 resp_show(req
, r
[iuse
].file
);
684 resp_catman(const struct req
*req
, const char *file
)
693 if ((f
= fopen(file
, "r")) == NULL
) {
694 puts("<p>You specified an invalid manual file.</p>");
698 puts("<div class=\"catman\">\n"
704 while ((len
= getline(&p
, &sz
, f
)) != -1) {
706 for (i
= 0; i
< len
- 1; i
++) {
708 * This means that the catpage is out of state.
709 * Ignore it and keep going (although the
713 if ('\b' == p
[i
] || '\n' == p
[i
])
717 * Print a regular character.
718 * Close out any bold/italic scopes.
719 * If we're in back-space mode, make sure we'll
720 * have something to enter when we backspace.
723 if ('\b' != p
[i
+ 1]) {
731 } else if (i
+ 2 >= len
)
749 * Handle funny behaviour troff-isms.
750 * These grok'd from the original man2html.c.
753 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
754 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
755 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
756 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
757 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
758 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
759 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
760 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
769 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
770 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
771 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
772 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
773 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
774 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
798 * Clean up the last character.
799 * We can get to a newline; don't print that.
807 if (i
== len
- 1 && p
[i
] != '\n')
821 resp_format(const struct req
*req
, const char *file
)
823 struct manoutput conf
;
825 struct roff_man
*man
;
830 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
831 puts("<p>You specified an invalid manual file.</p>");
836 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
,
837 MANDOCERR_MAX
, NULL
, MANDOC_OS_OTHER
, req
->q
.manpath
);
838 mparse_readfd(mp
, fd
, file
);
841 memset(&conf
, 0, sizeof(conf
));
843 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
844 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
845 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
846 scriptname
, *scriptname
== '\0' ? "" : "/",
847 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
849 mparse_result(mp
, &man
, NULL
);
851 warnx("fatal mandoc error: %s/%s", req
->q
.manpath
, file
);
858 vp
= html_alloc(&conf
);
860 if (man
->macroset
== MACROSET_MDOC
) {
876 resp_show(const struct req
*req
, const char *file
)
879 if ('.' == file
[0] && '/' == file
[1])
883 resp_catman(req
, file
);
885 resp_format(req
, file
);
889 pg_show(struct req
*req
, const char *fullpath
)
894 if ((file
= strchr(fullpath
, '/')) == NULL
) {
896 "You did not specify a page to show.");
899 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
902 if ( ! validate_manpath(req
, manpath
)) {
904 "You specified an invalid manpath.");
910 * Begin by chdir()ing into the manpath.
911 * This way we can pick up the database files, which are
912 * relative to the manpath root.
915 if (chdir(manpath
) == -1) {
916 warn("chdir %s", manpath
);
923 if ( ! validate_filename(file
)) {
925 "You specified an invalid manual file.");
929 resp_begin_html(200, NULL
, file
);
930 resp_searchform(req
, FOCUS_NONE
);
931 resp_show(req
, file
);
936 pg_search(const struct req
*req
)
938 struct mansearch search
;
939 struct manpaths paths
;
942 char *query
, *rp
, *wp
;
947 * Begin by chdir()ing into the root of the manpath.
948 * This way we can pick up the database files, which are
949 * relative to the manpath root.
952 if (chdir(req
->q
.manpath
) == -1) {
953 warn("chdir %s", req
->q
.manpath
);
958 search
.arch
= req
->q
.arch
;
959 search
.sec
= req
->q
.sec
;
960 search
.outkey
= "Nd";
961 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
962 search
.firstmatch
= 1;
965 paths
.paths
= mandoc_malloc(sizeof(char *));
966 paths
.paths
[0] = mandoc_strdup(".");
969 * Break apart at spaces with backslash-escaping.
974 rp
= query
= mandoc_strdup(req
->q
.query
);
976 while (isspace((unsigned char)*rp
))
980 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
981 argv
[argc
++] = wp
= rp
;
983 if (isspace((unsigned char)*rp
)) {
988 if (rp
[0] == '\\' && rp
[1] != '\0')
1001 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1002 pg_redirect(req
, argv
[0]);
1003 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1004 pg_noresult(req
, "You entered an invalid query.");
1005 else if (ressz
== 0)
1006 pg_noresult(req
, "No results found.");
1008 pg_searchres(req
, res
, ressz
);
1011 mansearch_free(res
, ressz
);
1012 free(paths
.paths
[0]);
1020 struct itimerval itimer
;
1022 const char *querystring
;
1027 * The "rpath" pledge could be revoked after mparse_readfd()
1028 * if the file desciptor to "/footer.html" would be opened
1029 * up front, but it's probably not worth the complication
1030 * of the code it would cause: it would require scattering
1031 * pledge() calls in multiple low-level resp_*() functions.
1034 if (pledge("stdio rpath", NULL
) == -1) {
1036 pg_error_internal();
1037 return EXIT_FAILURE
;
1041 /* Poor man's ReDoS mitigation. */
1043 itimer
.it_value
.tv_sec
= 2;
1044 itimer
.it_value
.tv_usec
= 0;
1045 itimer
.it_interval
.tv_sec
= 2;
1046 itimer
.it_interval
.tv_usec
= 0;
1047 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1049 pg_error_internal();
1050 return EXIT_FAILURE
;
1054 * First we change directory into the MAN_DIR so that
1055 * subsequent scanning for manpath directories is rooted
1056 * relative to the same position.
1059 if (chdir(MAN_DIR
) == -1) {
1060 warn("MAN_DIR: %s", MAN_DIR
);
1061 pg_error_internal();
1062 return EXIT_FAILURE
;
1065 memset(&req
, 0, sizeof(struct req
));
1067 parse_manpath_conf(&req
);
1069 /* Parse the path info and the query string. */
1071 if ((path
= getenv("PATH_INFO")) == NULL
)
1073 else if (*path
== '/')
1076 if (*path
!= '\0') {
1077 parse_path_info(&req
, path
);
1078 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1079 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1081 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1082 parse_query_string(&req
, querystring
);
1084 /* Validate parsed data and add defaults. */
1086 if (req
.q
.manpath
== NULL
)
1087 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1088 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1089 pg_error_badrequest(
1090 "You specified an invalid manpath.");
1091 return EXIT_FAILURE
;
1094 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1095 pg_error_badrequest(
1096 "You specified an invalid architecture.");
1097 return EXIT_FAILURE
;
1100 /* Dispatch to the three different pages. */
1103 pg_show(&req
, path
);
1104 else if (NULL
!= req
.q
.query
)
1109 free(req
.q
.manpath
);
1113 for (i
= 0; i
< (int)req
.psz
; i
++)
1116 return EXIT_SUCCESS
;
1120 * If PATH_INFO is not a file name, translate it to a query.
1123 parse_path_info(struct req
*req
, const char *path
)
1130 req
->q
.manpath
= mandoc_strdup(path
);
1133 /* Mandatory manual page name. */
1134 if ((req
->q
.query
= strrchr(req
->q
.manpath
, '/')) == NULL
) {
1135 req
->q
.query
= req
->q
.manpath
;
1136 req
->q
.manpath
= NULL
;
1138 *req
->q
.query
++ = '\0';
1140 /* Optional trailing section. */
1141 if ((req
->q
.sec
= strrchr(req
->q
.query
, '.')) != NULL
) {
1142 if(isdigit((unsigned char)req
->q
.sec
[1])) {
1143 *req
->q
.sec
++ = '\0';
1144 req
->q
.sec
= mandoc_strdup(req
->q
.sec
);
1149 /* Handle the case of name[.section] only. */
1150 if (req
->q
.manpath
== NULL
)
1152 req
->q
.query
= mandoc_strdup(req
->q
.query
);
1154 /* Split directory components. */
1155 dir
[i
= 0] = req
->q
.manpath
;
1156 while ((dir
[i
+ 1] = strchr(dir
[i
], '/')) != NULL
) {
1158 pg_error_badrequest(
1159 "You specified too many directory components.");
1165 /* Optional manpath. */
1166 if ((i
= validate_manpath(req
, req
->q
.manpath
)) == 0)
1167 req
->q
.manpath
= NULL
;
1168 else if (dir
[1] == NULL
)
1171 /* Optional section. */
1172 if (strncmp(dir
[i
], "man", 3) == 0) {
1174 req
->q
.sec
= mandoc_strdup(dir
[i
++] + 3);
1176 if (dir
[i
] == NULL
) {
1177 if (req
->q
.manpath
== NULL
)
1181 if (dir
[i
+ 1] != NULL
) {
1182 pg_error_badrequest(
1183 "You specified an invalid directory component.");
1187 /* Optional architecture. */
1189 req
->q
.arch
= mandoc_strdup(dir
[i
]);
1190 if (req
->q
.manpath
== NULL
)
1193 req
->q
.arch
= dir
[0];
1197 * Scan for indexable paths.
1200 parse_manpath_conf(struct req
*req
)
1207 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1208 warn("%s/manpath.conf", MAN_DIR
);
1209 pg_error_internal();
1216 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1217 if (dp
[len
- 1] == '\n')
1219 req
->p
= mandoc_realloc(req
->p
,
1220 (req
->psz
+ 1) * sizeof(char *));
1221 if ( ! validate_urifrag(dp
)) {
1222 warnx("%s/manpath.conf contains "
1223 "unsafe path \"%s\"", MAN_DIR
, dp
);
1224 pg_error_internal();
1227 if (strchr(dp
, '/') != NULL
) {
1228 warnx("%s/manpath.conf contains "
1229 "path with slash \"%s\"", MAN_DIR
, dp
);
1230 pg_error_internal();
1233 req
->p
[req
->psz
++] = dp
;
1239 if (req
->p
== NULL
) {
1240 warnx("%s/manpath.conf is empty", MAN_DIR
);
1241 pg_error_internal();