]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.167 2019/07/10 12:49:20 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014, 2015, 2016, 2017, 2018 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"
41 #include "mandoc_parse.h"
44 #include "mansearch.h"
48 * A query as passed to the search function.
51 char *manpath
; /* desired manual directory */
52 char *arch
; /* architecture */
53 char *sec
; /* manual section */
54 char *query
; /* unparsed query expression */
55 int equal
; /* match whole names, not substrings */
60 char **p
; /* array of available manpaths */
61 size_t psz
; /* number of available manpaths */
62 int isquery
; /* QUERY_STRING used, not PATH_INFO */
70 static void html_print(const char *);
71 static void html_putchar(char);
72 static int http_decode(char *);
73 static void http_encode(const char *p
);
74 static void parse_manpath_conf(struct req
*);
75 static void parse_path_info(struct req
*req
, const char *path
);
76 static void parse_query_string(struct req
*, const char *);
77 static void pg_error_badrequest(const char *);
78 static void pg_error_internal(void);
79 static void pg_index(const struct req
*);
80 static void pg_noresult(const struct req
*, const char *);
81 static void pg_redirect(const struct req
*, const char *);
82 static void pg_search(const struct req
*);
83 static void pg_searchres(const struct req
*,
84 struct manpage
*, size_t);
85 static void pg_show(struct req
*, const char *);
86 static void resp_begin_html(int, const char *, const char *);
87 static void resp_begin_http(int, const char *);
88 static void resp_catman(const struct req
*, const char *);
89 static void resp_copy(const char *);
90 static void resp_end_html(void);
91 static void resp_format(const struct req
*, const char *);
92 static void resp_searchform(const struct req
*, enum focus
);
93 static void resp_show(const struct req
*, const char *);
94 static void set_query_attr(char **, char **);
95 static int validate_arch(const char *);
96 static int validate_filename(const char *);
97 static int validate_manpath(const struct req
*, const char *);
98 static int validate_urifrag(const char *);
100 static const char *scriptname
= SCRIPT_NAME
;
102 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
103 static const char *const sec_numbers
[] = {
104 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
106 static const char *const sec_names
[] = {
108 "1 - General Commands",
110 "3 - Library Functions",
112 "4 - Device Drivers",
115 "7 - Miscellaneous Information",
116 "8 - System Manager\'s Manual",
117 "9 - Kernel Developer\'s Manual"
119 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
121 static const char *const arch_names
[] = {
122 "amd64", "alpha", "armv7", "arm64",
123 "hppa", "i386", "landisk",
124 "loongson", "luna88k", "macppc", "mips64",
125 "octeon", "sgi", "socppc", "sparc64",
126 "amiga", "arc", "armish", "arm32",
127 "atari", "aviion", "beagle", "cats",
129 "ia64", "mac68k", "mvme68k", "mvme88k",
130 "mvmeppc", "palm", "pc532", "pegasos",
131 "pmax", "powerpc", "solbourne", "sparc",
132 "sun3", "vax", "wgrisc", "x68k",
135 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
138 * Print a character, escaping HTML along the way.
139 * This will pass non-ASCII straight to output: be warned!
159 putchar((unsigned char)c
);
165 * Call through to html_putchar().
166 * Accepts NULL strings.
169 html_print(const char *p
)
179 * Transfer the responsibility for the allocated string *val
180 * to the query structure.
183 set_query_attr(char **attr
, char **val
)
196 * Parse the QUERY_STRING for key-value pairs
197 * and store the values into the query structure.
200 parse_query_string(struct req
*req
, const char *qs
)
206 req
->q
.manpath
= NULL
;
213 while (*qs
!= '\0') {
217 keysz
= strcspn(qs
, "=;&");
218 key
= mandoc_strndup(qs
, keysz
);
223 /* Parse one value. */
225 valsz
= strcspn(++qs
, ";&");
226 val
= mandoc_strndup(qs
, valsz
);
229 /* Decode and catch encoding errors. */
231 if ( ! (http_decode(key
) && http_decode(val
)))
234 /* Handle key-value pairs. */
236 if ( ! strcmp(key
, "query"))
237 set_query_attr(&req
->q
.query
, &val
);
239 else if ( ! strcmp(key
, "apropos"))
240 req
->q
.equal
= !strcmp(val
, "0");
242 else if ( ! strcmp(key
, "manpath")) {
244 if ( ! strncmp(val
, "OpenBSD ", 8)) {
250 set_query_attr(&req
->q
.manpath
, &val
);
253 else if ( ! (strcmp(key
, "sec")
255 && strcmp(key
, "sektion")
258 if ( ! strcmp(val
, "0"))
260 set_query_attr(&req
->q
.sec
, &val
);
263 else if ( ! strcmp(key
, "arch")) {
264 if ( ! strcmp(val
, "default"))
266 set_query_attr(&req
->q
.arch
, &val
);
270 * The key must be freed in any case.
271 * The val may have been handed over to the query
272 * structure, in which case it is now NULL.
286 * HTTP-decode a string. The standard explanation is that this turns
287 * "%4e+foo" into "n foo" in the regular way. This is done in-place
288 * over the allocated string.
300 for ( ; '\0' != *p
; p
++, q
++) {
302 if ('\0' == (hex
[0] = *(p
+ 1)))
304 if ('\0' == (hex
[1] = *(p
+ 2)))
306 if (1 != sscanf(hex
, "%x", &c
))
314 *q
= '+' == *p
? ' ' : *p
;
322 http_encode(const char *p
)
324 for (; *p
!= '\0'; p
++) {
325 if (isalnum((unsigned char)*p
) == 0 &&
326 strchr("-._~", *p
) == NULL
)
327 printf("%%%2.2X", (unsigned char)*p
);
334 resp_begin_http(int code
, const char *msg
)
338 printf("Status: %d %s\r\n", code
, msg
);
340 printf("Content-Type: text/html; charset=utf-8\r\n"
341 "Cache-Control: no-cache\r\n"
342 "Pragma: no-cache\r\n"
349 resp_copy(const char *filename
)
355 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
357 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
358 write(STDOUT_FILENO
, buf
, sz
);
364 resp_begin_html(int code
, const char *msg
, const char *file
)
368 resp_begin_http(code
, msg
);
370 printf("<!DOCTYPE html>\n"
373 " <meta charset=\"UTF-8\"/>\n"
374 " <meta name=\"viewport\""
375 " content=\"width=device-width, initial-scale=1.0\">\n"
376 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
377 " type=\"text/css\" media=\"all\">\n"
381 if ((cp
= strrchr(file
, '/')) != NULL
)
383 if ((cp
= strrchr(file
, '.')) != NULL
) {
384 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
386 printf("%s - ", file
);
388 printf("%s</title>\n"
393 resp_copy(MAN_DIR
"/header.html");
400 resp_copy(MAN_DIR
"/footer.html");
407 resp_searchform(const struct req
*req
, enum focus focus
)
411 printf("<form action=\"/%s\" method=\"get\">\n"
413 " <legend>Manual Page Search Parameters</legend>\n",
416 /* Write query input box. */
418 printf(" <input type=\"search\" name=\"query\" value=\"");
419 if (req
->q
.query
!= NULL
)
420 html_print(req
->q
.query
);
421 printf( "\" size=\"40\"");
422 if (focus
== FOCUS_QUERY
)
423 printf(" autofocus");
426 /* Write submission buttons. */
428 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
430 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
434 /* Write section selector. */
436 puts(" <select name=\"sec\">");
437 for (i
= 0; i
< sec_MAX
; i
++) {
438 printf(" <option value=\"%s\"", sec_numbers
[i
]);
439 if (NULL
!= req
->q
.sec
&&
440 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
441 printf(" selected=\"selected\"");
442 printf(">%s</option>\n", sec_names
[i
]);
446 /* Write architecture selector. */
448 printf( " <select name=\"arch\">\n"
449 " <option value=\"default\"");
450 if (NULL
== req
->q
.arch
)
451 printf(" selected=\"selected\"");
452 puts(">All Architectures</option>");
453 for (i
= 0; i
< arch_MAX
; i
++) {
455 if (NULL
!= req
->q
.arch
&&
456 0 == strcmp(arch_names
[i
], req
->q
.arch
))
457 printf(" selected=\"selected\"");
458 printf(">%s</option>\n", arch_names
[i
]);
462 /* Write manpath selector. */
465 puts(" <select name=\"manpath\">");
466 for (i
= 0; i
< (int)req
->psz
; i
++) {
468 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
469 printf(" selected=\"selected\"");
471 html_print(req
->p
[i
]);
477 puts(" </fieldset>\n"
482 validate_urifrag(const char *frag
)
485 while ('\0' != *frag
) {
486 if ( ! (isalnum((unsigned char)*frag
) ||
487 '-' == *frag
|| '.' == *frag
||
488 '/' == *frag
|| '_' == *frag
))
496 validate_manpath(const struct req
*req
, const char* manpath
)
500 for (i
= 0; i
< req
->psz
; i
++)
501 if ( ! strcmp(manpath
, req
->p
[i
]))
508 validate_arch(const char *arch
)
512 for (i
= 0; i
< arch_MAX
; i
++)
513 if (strcmp(arch
, arch_names
[i
]) == 0)
520 validate_filename(const char *file
)
523 if ('.' == file
[0] && '/' == file
[1])
526 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
527 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
531 pg_index(const struct req
*req
)
534 resp_begin_html(200, NULL
, NULL
);
535 resp_searchform(req
, FOCUS_QUERY
);
537 "This web interface is documented in the\n"
538 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
540 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
541 "manual explains the query syntax.\n"
543 scriptname
, *scriptname
== '\0' ? "" : "/",
544 scriptname
, *scriptname
== '\0' ? "" : "/");
549 pg_noresult(const struct req
*req
, const char *msg
)
551 resp_begin_html(200, NULL
, NULL
);
552 resp_searchform(req
, FOCUS_QUERY
);
560 pg_error_badrequest(const char *msg
)
563 resp_begin_html(400, "Bad Request", NULL
);
564 puts("<h1>Bad Request</h1>\n"
567 printf("Try again from the\n"
568 "<a href=\"/%s\">main page</a>.\n"
574 pg_error_internal(void)
576 resp_begin_html(500, "Internal Server Error", NULL
);
577 puts("<p>Internal Server Error</p>");
582 pg_redirect(const struct req
*req
, const char *name
)
584 printf("Status: 303 See Other\r\n"
586 if (*scriptname
!= '\0')
587 printf("%s/", scriptname
);
588 if (strcmp(req
->q
.manpath
, req
->p
[0]))
589 printf("%s/", req
->q
.manpath
);
590 if (req
->q
.arch
!= NULL
)
591 printf("%s/", req
->q
.arch
);
593 if (req
->q
.sec
!= NULL
) {
595 http_encode(req
->q
.sec
);
597 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
601 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
603 char *arch
, *archend
;
606 int archprio
, archpriouse
;
609 for (i
= 0; i
< sz
; i
++) {
610 if (validate_filename(r
[i
].file
))
612 warnx("invalid filename %s in %s database",
613 r
[i
].file
, req
->q
.manpath
);
618 if (req
->isquery
&& sz
== 1) {
620 * If we have just one result, then jump there now
623 printf("Status: 303 See Other\r\n"
625 if (*scriptname
!= '\0')
626 printf("%s/", scriptname
);
627 if (strcmp(req
->q
.manpath
, req
->p
[0]))
628 printf("%s/", req
->q
.manpath
);
630 "Content-Type: text/html; charset=utf-8\r\n\r\n",
636 * In man(1) mode, show one of the pages
637 * even if more than one is found.
641 if (req
->q
.equal
|| sz
== 1) {
644 for (i
= 0; i
< sz
; i
++) {
646 sec
+= strcspn(sec
, "123456789");
649 prio
= sec_prios
[sec
[0] - '1'];
652 if (req
->q
.arch
== NULL
) {
654 ((arch
= strchr(sec
+ 1, '/'))
656 ((archend
= strchr(arch
+ 1, '/'))
658 strncmp(arch
, "amd64/",
659 archend
- arch
) ? 2 : 1;
660 if (archprio
< archpriouse
) {
661 archpriouse
= archprio
;
666 if (archprio
> archpriouse
)
674 resp_begin_html(200, NULL
, r
[iuse
].file
);
676 resp_begin_html(200, NULL
, NULL
);
679 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
682 puts("<table class=\"results\">");
683 for (i
= 0; i
< sz
; i
++) {
686 "<a class=\"Xr\" href=\"/");
687 if (*scriptname
!= '\0')
688 printf("%s/", scriptname
);
689 if (strcmp(req
->q
.manpath
, req
->p
[0]))
690 printf("%s/", req
->q
.manpath
);
691 printf("%s\">", r
[i
].file
);
692 html_print(r
[i
].names
);
694 " <td><span class=\"Nd\">");
695 html_print(r
[i
].output
);
696 puts("</span></td>\n"
702 if (req
->q
.equal
|| sz
== 1) {
704 resp_show(req
, r
[iuse
].file
);
711 resp_catman(const struct req
*req
, const char *file
)
720 if ((f
= fopen(file
, "r")) == NULL
) {
721 puts("<p>You specified an invalid manual file.</p>");
725 puts("<div class=\"catman\">\n"
731 while ((len
= getline(&p
, &sz
, f
)) != -1) {
733 for (i
= 0; i
< len
- 1; i
++) {
735 * This means that the catpage is out of state.
736 * Ignore it and keep going (although the
740 if ('\b' == p
[i
] || '\n' == p
[i
])
744 * Print a regular character.
745 * Close out any bold/italic scopes.
746 * If we're in back-space mode, make sure we'll
747 * have something to enter when we backspace.
750 if ('\b' != p
[i
+ 1]) {
758 } else if (i
+ 2 >= len
)
776 * Handle funny behaviour troff-isms.
777 * These grok'd from the original man2html.c.
780 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
781 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
782 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
783 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
784 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
785 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
786 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
787 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
796 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
797 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
798 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
799 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
800 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
801 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
825 * Clean up the last character.
826 * We can get to a newline; don't print that.
834 if (i
== len
- 1 && p
[i
] != '\n')
848 resp_format(const struct req
*req
, const char *file
)
850 struct manoutput conf
;
852 struct roff_meta
*meta
;
857 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
858 puts("<p>You specified an invalid manual file.</p>");
863 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
864 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
865 mparse_readfd(mp
, fd
, file
);
867 meta
= mparse_result(mp
);
869 memset(&conf
, 0, sizeof(conf
));
871 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
872 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
873 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
874 scriptname
, *scriptname
== '\0' ? "" : "/",
875 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
877 vp
= html_alloc(&conf
);
878 if (meta
->macroset
== MACROSET_MDOC
)
891 resp_show(const struct req
*req
, const char *file
)
894 if ('.' == file
[0] && '/' == file
[1])
898 resp_catman(req
, file
);
900 resp_format(req
, file
);
904 pg_show(struct req
*req
, const char *fullpath
)
909 if ((file
= strchr(fullpath
, '/')) == NULL
) {
911 "You did not specify a page to show.");
914 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
917 if ( ! validate_manpath(req
, manpath
)) {
919 "You specified an invalid manpath.");
925 * Begin by chdir()ing into the manpath.
926 * This way we can pick up the database files, which are
927 * relative to the manpath root.
930 if (chdir(manpath
) == -1) {
931 warn("chdir %s", manpath
);
938 if ( ! validate_filename(file
)) {
940 "You specified an invalid manual file.");
944 resp_begin_html(200, NULL
, file
);
945 resp_searchform(req
, FOCUS_NONE
);
946 resp_show(req
, file
);
951 pg_search(const struct req
*req
)
953 struct mansearch search
;
954 struct manpaths paths
;
957 char *query
, *rp
, *wp
;
962 * Begin by chdir()ing into the root of the manpath.
963 * This way we can pick up the database files, which are
964 * relative to the manpath root.
967 if (chdir(req
->q
.manpath
) == -1) {
968 warn("chdir %s", req
->q
.manpath
);
973 search
.arch
= req
->q
.arch
;
974 search
.sec
= req
->q
.sec
;
975 search
.outkey
= "Nd";
976 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
977 search
.firstmatch
= 1;
980 paths
.paths
= mandoc_malloc(sizeof(char *));
981 paths
.paths
[0] = mandoc_strdup(".");
984 * Break apart at spaces with backslash-escaping.
989 rp
= query
= mandoc_strdup(req
->q
.query
);
991 while (isspace((unsigned char)*rp
))
995 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
996 argv
[argc
++] = wp
= rp
;
998 if (isspace((unsigned char)*rp
)) {
1003 if (rp
[0] == '\\' && rp
[1] != '\0')
1016 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1017 pg_redirect(req
, argv
[0]);
1018 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1019 pg_noresult(req
, "You entered an invalid query.");
1020 else if (ressz
== 0)
1021 pg_noresult(req
, "No results found.");
1023 pg_searchres(req
, res
, ressz
);
1026 mansearch_free(res
, ressz
);
1027 free(paths
.paths
[0]);
1035 struct itimerval itimer
;
1037 const char *querystring
;
1042 * The "rpath" pledge could be revoked after mparse_readfd()
1043 * if the file desciptor to "/footer.html" would be opened
1044 * up front, but it's probably not worth the complication
1045 * of the code it would cause: it would require scattering
1046 * pledge() calls in multiple low-level resp_*() functions.
1049 if (pledge("stdio rpath", NULL
) == -1) {
1051 pg_error_internal();
1052 return EXIT_FAILURE
;
1056 /* Poor man's ReDoS mitigation. */
1058 itimer
.it_value
.tv_sec
= 2;
1059 itimer
.it_value
.tv_usec
= 0;
1060 itimer
.it_interval
.tv_sec
= 2;
1061 itimer
.it_interval
.tv_usec
= 0;
1062 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1064 pg_error_internal();
1065 return EXIT_FAILURE
;
1069 * First we change directory into the MAN_DIR so that
1070 * subsequent scanning for manpath directories is rooted
1071 * relative to the same position.
1074 if (chdir(MAN_DIR
) == -1) {
1075 warn("MAN_DIR: %s", MAN_DIR
);
1076 pg_error_internal();
1077 return EXIT_FAILURE
;
1080 memset(&req
, 0, sizeof(struct req
));
1082 parse_manpath_conf(&req
);
1084 /* Parse the path info and the query string. */
1086 if ((path
= getenv("PATH_INFO")) == NULL
)
1088 else if (*path
== '/')
1091 if (*path
!= '\0') {
1092 parse_path_info(&req
, path
);
1093 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1094 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1096 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1097 parse_query_string(&req
, querystring
);
1099 /* Validate parsed data and add defaults. */
1101 if (req
.q
.manpath
== NULL
)
1102 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1103 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1104 pg_error_badrequest(
1105 "You specified an invalid manpath.");
1106 return EXIT_FAILURE
;
1109 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1110 pg_error_badrequest(
1111 "You specified an invalid architecture.");
1112 return EXIT_FAILURE
;
1115 /* Dispatch to the three different pages. */
1118 pg_show(&req
, path
);
1119 else if (NULL
!= req
.q
.query
)
1124 free(req
.q
.manpath
);
1128 for (i
= 0; i
< (int)req
.psz
; i
++)
1131 return EXIT_SUCCESS
;
1135 * Translate PATH_INFO to a query.
1138 parse_path_info(struct req
*req
, const char *path
)
1140 const char *name
, *sec
, *end
;
1144 req
->q
.manpath
= NULL
;
1147 /* Mandatory manual page name. */
1148 if ((name
= strrchr(path
, '/')) == NULL
)
1153 /* Optional trailing section. */
1154 sec
= strrchr(name
, '.');
1155 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1156 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1157 req
->q
.sec
= mandoc_strdup(sec
);
1159 req
->q
.query
= mandoc_strdup(name
);
1163 /* Handle the case of name[.section] only. */
1167 /* Optional manpath. */
1168 end
= strchr(path
, '/');
1169 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1170 if (validate_manpath(req
, req
->q
.manpath
)) {
1175 free(req
->q
.manpath
);
1176 req
->q
.manpath
= NULL
;
1179 /* Optional section. */
1180 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1182 end
= strchr(path
, '/');
1184 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1190 /* Optional architecture. */
1191 end
= strchr(path
, '/');
1192 if (end
+ 1 != name
) {
1193 pg_error_badrequest(
1194 "You specified too many directory components.");
1197 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1198 if (validate_arch(req
->q
.arch
) == 0) {
1199 pg_error_badrequest(
1200 "You specified an invalid directory component.");
1206 * Scan for indexable paths.
1209 parse_manpath_conf(struct req
*req
)
1216 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1217 warn("%s/manpath.conf", MAN_DIR
);
1218 pg_error_internal();
1225 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1226 if (dp
[len
- 1] == '\n')
1228 req
->p
= mandoc_realloc(req
->p
,
1229 (req
->psz
+ 1) * sizeof(char *));
1230 if ( ! validate_urifrag(dp
)) {
1231 warnx("%s/manpath.conf contains "
1232 "unsafe path \"%s\"", MAN_DIR
, dp
);
1233 pg_error_internal();
1236 if (strchr(dp
, '/') != NULL
) {
1237 warnx("%s/manpath.conf contains "
1238 "path with slash \"%s\"", MAN_DIR
, dp
);
1239 pg_error_internal();
1242 req
->p
[req
->psz
++] = dp
;
1248 if (req
->p
== NULL
) {
1249 warnx("%s/manpath.conf is empty", MAN_DIR
);
1250 pg_error_internal();