]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.161 2018/10/19 21:10:56 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"
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 http_encode(const char *p
);
73 static void parse_manpath_conf(struct req
*);
74 static void parse_path_info(struct req
*req
, const char *path
);
75 static void parse_query_string(struct req
*, const char *);
76 static void pg_error_badrequest(const char *);
77 static void pg_error_internal(void);
78 static void pg_index(const struct req
*);
79 static void pg_noresult(const struct req
*, const char *);
80 static void pg_redirect(const struct req
*, const char *);
81 static void pg_search(const struct req
*);
82 static void pg_searchres(const struct req
*,
83 struct manpage
*, size_t);
84 static void pg_show(struct req
*, const char *);
85 static void resp_begin_html(int, const char *, const char *);
86 static void resp_begin_http(int, const char *);
87 static void resp_catman(const struct req
*, const char *);
88 static void resp_copy(const char *);
89 static void resp_end_html(void);
90 static void resp_format(const struct req
*, const char *);
91 static void resp_searchform(const struct req
*, enum focus
);
92 static void resp_show(const struct req
*, const char *);
93 static void set_query_attr(char **, char **);
94 static int validate_arch(const char *);
95 static int validate_filename(const char *);
96 static int validate_manpath(const struct req
*, const char *);
97 static int validate_urifrag(const char *);
99 static const char *scriptname
= SCRIPT_NAME
;
101 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
102 static const char *const sec_numbers
[] = {
103 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
105 static const char *const sec_names
[] = {
107 "1 - General Commands",
109 "3 - Library Functions",
111 "4 - Device Drivers",
114 "7 - Miscellaneous Information",
115 "8 - System Manager\'s Manual",
116 "9 - Kernel Developer\'s Manual"
118 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
120 static const char *const arch_names
[] = {
121 "amd64", "alpha", "armv7", "arm64",
122 "hppa", "i386", "landisk",
123 "loongson", "luna88k", "macppc", "mips64",
124 "octeon", "sgi", "socppc", "sparc64",
125 "amiga", "arc", "armish", "arm32",
126 "atari", "aviion", "beagle", "cats",
128 "ia64", "mac68k", "mvme68k", "mvme88k",
129 "mvmeppc", "palm", "pc532", "pegasos",
130 "pmax", "powerpc", "solbourne", "sparc",
131 "sun3", "vax", "wgrisc", "x68k",
134 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
137 * Print a character, escaping HTML along the way.
138 * This will pass non-ASCII straight to output: be warned!
158 putchar((unsigned char)c
);
164 * Call through to html_putchar().
165 * Accepts NULL strings.
168 html_print(const char *p
)
178 * Transfer the responsibility for the allocated string *val
179 * to the query structure.
182 set_query_attr(char **attr
, char **val
)
195 * Parse the QUERY_STRING for key-value pairs
196 * and store the values into the query structure.
199 parse_query_string(struct req
*req
, const char *qs
)
205 req
->q
.manpath
= NULL
;
212 while (*qs
!= '\0') {
216 keysz
= strcspn(qs
, "=;&");
217 key
= mandoc_strndup(qs
, keysz
);
222 /* Parse one value. */
224 valsz
= strcspn(++qs
, ";&");
225 val
= mandoc_strndup(qs
, valsz
);
228 /* Decode and catch encoding errors. */
230 if ( ! (http_decode(key
) && http_decode(val
)))
233 /* Handle key-value pairs. */
235 if ( ! strcmp(key
, "query"))
236 set_query_attr(&req
->q
.query
, &val
);
238 else if ( ! strcmp(key
, "apropos"))
239 req
->q
.equal
= !strcmp(val
, "0");
241 else if ( ! strcmp(key
, "manpath")) {
243 if ( ! strncmp(val
, "OpenBSD ", 8)) {
249 set_query_attr(&req
->q
.manpath
, &val
);
252 else if ( ! (strcmp(key
, "sec")
254 && strcmp(key
, "sektion")
257 if ( ! strcmp(val
, "0"))
259 set_query_attr(&req
->q
.sec
, &val
);
262 else if ( ! strcmp(key
, "arch")) {
263 if ( ! strcmp(val
, "default"))
265 set_query_attr(&req
->q
.arch
, &val
);
269 * The key must be freed in any case.
270 * The val may have been handed over to the query
271 * structure, in which case it is now NULL.
285 * HTTP-decode a string. The standard explanation is that this turns
286 * "%4e+foo" into "n foo" in the regular way. This is done in-place
287 * over the allocated string.
299 for ( ; '\0' != *p
; p
++, q
++) {
301 if ('\0' == (hex
[0] = *(p
+ 1)))
303 if ('\0' == (hex
[1] = *(p
+ 2)))
305 if (1 != sscanf(hex
, "%x", &c
))
313 *q
= '+' == *p
? ' ' : *p
;
321 http_encode(const char *p
)
323 for (; *p
!= '\0'; p
++) {
324 if (isalnum((unsigned char)*p
) == 0 &&
325 strchr("-._~", *p
) == NULL
)
326 printf("%%%02.2X", (unsigned char)*p
);
333 resp_begin_http(int code
, const char *msg
)
337 printf("Status: %d %s\r\n", code
, msg
);
339 printf("Content-Type: text/html; charset=utf-8\r\n"
340 "Cache-Control: no-cache\r\n"
341 "Pragma: no-cache\r\n"
348 resp_copy(const char *filename
)
354 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
356 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
357 write(STDOUT_FILENO
, buf
, sz
);
363 resp_begin_html(int code
, const char *msg
, const char *file
)
367 resp_begin_http(code
, msg
);
369 printf("<!DOCTYPE html>\n"
372 " <meta charset=\"UTF-8\"/>\n"
373 " <meta name=\"viewport\""
374 " content=\"width=device-width, initial-scale=1.0\">\n"
375 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
376 " type=\"text/css\" media=\"all\">\n"
380 if ((cp
= strrchr(file
, '/')) != NULL
)
382 if ((cp
= strrchr(file
, '.')) != NULL
) {
383 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
385 printf("%s - ", file
);
387 printf("%s</title>\n"
392 resp_copy(MAN_DIR
"/header.html");
399 resp_copy(MAN_DIR
"/footer.html");
406 resp_searchform(const struct req
*req
, enum focus focus
)
410 printf("<form action=\"/%s\" method=\"get\">\n"
412 " <legend>Manual Page Search Parameters</legend>\n",
415 /* Write query input box. */
417 printf(" <input type=\"search\" name=\"query\" value=\"");
418 if (req
->q
.query
!= NULL
)
419 html_print(req
->q
.query
);
420 printf( "\" size=\"40\"");
421 if (focus
== FOCUS_QUERY
)
422 printf(" autofocus");
425 /* Write submission buttons. */
427 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
429 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
433 /* Write section selector. */
435 puts(" <select name=\"sec\">");
436 for (i
= 0; i
< sec_MAX
; i
++) {
437 printf(" <option value=\"%s\"", sec_numbers
[i
]);
438 if (NULL
!= req
->q
.sec
&&
439 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
440 printf(" selected=\"selected\"");
441 printf(">%s</option>\n", sec_names
[i
]);
445 /* Write architecture selector. */
447 printf( " <select name=\"arch\">\n"
448 " <option value=\"default\"");
449 if (NULL
== req
->q
.arch
)
450 printf(" selected=\"selected\"");
451 puts(">All Architectures</option>");
452 for (i
= 0; i
< arch_MAX
; i
++) {
454 if (NULL
!= req
->q
.arch
&&
455 0 == strcmp(arch_names
[i
], req
->q
.arch
))
456 printf(" selected=\"selected\"");
457 printf(">%s</option>\n", arch_names
[i
]);
461 /* Write manpath selector. */
464 puts(" <select name=\"manpath\">");
465 for (i
= 0; i
< (int)req
->psz
; i
++) {
467 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
468 printf(" selected=\"selected\"");
470 html_print(req
->p
[i
]);
476 puts(" </fieldset>\n"
481 validate_urifrag(const char *frag
)
484 while ('\0' != *frag
) {
485 if ( ! (isalnum((unsigned char)*frag
) ||
486 '-' == *frag
|| '.' == *frag
||
487 '/' == *frag
|| '_' == *frag
))
495 validate_manpath(const struct req
*req
, const char* manpath
)
499 for (i
= 0; i
< req
->psz
; i
++)
500 if ( ! strcmp(manpath
, req
->p
[i
]))
507 validate_arch(const char *arch
)
511 for (i
= 0; i
< arch_MAX
; i
++)
512 if (strcmp(arch
, arch_names
[i
]) == 0)
519 validate_filename(const char *file
)
522 if ('.' == file
[0] && '/' == file
[1])
525 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
526 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
530 pg_index(const struct req
*req
)
533 resp_begin_html(200, NULL
, NULL
);
534 resp_searchform(req
, FOCUS_QUERY
);
536 "This web interface is documented in the\n"
537 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
539 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
540 "manual explains the query syntax.\n"
542 scriptname
, *scriptname
== '\0' ? "" : "/",
543 scriptname
, *scriptname
== '\0' ? "" : "/");
548 pg_noresult(const struct req
*req
, const char *msg
)
550 resp_begin_html(200, NULL
, NULL
);
551 resp_searchform(req
, FOCUS_QUERY
);
559 pg_error_badrequest(const char *msg
)
562 resp_begin_html(400, "Bad Request", NULL
);
563 puts("<h1>Bad Request</h1>\n"
566 printf("Try again from the\n"
567 "<a href=\"/%s\">main page</a>.\n"
573 pg_error_internal(void)
575 resp_begin_html(500, "Internal Server Error", NULL
);
576 puts("<p>Internal Server Error</p>");
581 pg_redirect(const struct req
*req
, const char *name
)
583 printf("Status: 303 See Other\r\n"
585 if (*scriptname
!= '\0')
586 printf("%s/", scriptname
);
587 if (strcmp(req
->q
.manpath
, req
->p
[0]))
588 printf("%s/", req
->q
.manpath
);
589 if (req
->q
.arch
!= NULL
)
590 printf("%s/", req
->q
.arch
);
592 if (req
->q
.sec
!= NULL
) {
594 http_encode(req
->q
.sec
);
596 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
600 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
602 char *arch
, *archend
;
605 int archprio
, archpriouse
;
608 for (i
= 0; i
< sz
; i
++) {
609 if (validate_filename(r
[i
].file
))
611 warnx("invalid filename %s in %s database",
612 r
[i
].file
, req
->q
.manpath
);
617 if (req
->isquery
&& sz
== 1) {
619 * If we have just one result, then jump there now
622 printf("Status: 303 See Other\r\n"
624 if (*scriptname
!= '\0')
625 printf("%s/", scriptname
);
626 if (strcmp(req
->q
.manpath
, req
->p
[0]))
627 printf("%s/", req
->q
.manpath
);
629 "Content-Type: text/html; charset=utf-8\r\n\r\n",
635 * In man(1) mode, show one of the pages
636 * even if more than one is found.
640 if (req
->q
.equal
|| sz
== 1) {
643 for (i
= 0; i
< sz
; i
++) {
645 sec
+= strcspn(sec
, "123456789");
648 prio
= sec_prios
[sec
[0] - '1'];
651 if (req
->q
.arch
== NULL
) {
653 ((arch
= strchr(sec
+ 1, '/'))
655 ((archend
= strchr(arch
+ 1, '/'))
657 strncmp(arch
, "amd64/",
658 archend
- arch
) ? 2 : 1;
659 if (archprio
< archpriouse
) {
660 archpriouse
= archprio
;
665 if (archprio
> archpriouse
)
673 resp_begin_html(200, NULL
, r
[iuse
].file
);
675 resp_begin_html(200, NULL
, NULL
);
678 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
681 puts("<table class=\"results\">");
682 for (i
= 0; i
< sz
; i
++) {
685 "<a class=\"Xr\" href=\"/");
686 if (*scriptname
!= '\0')
687 printf("%s/", scriptname
);
688 if (strcmp(req
->q
.manpath
, req
->p
[0]))
689 printf("%s/", req
->q
.manpath
);
690 printf("%s\">", r
[i
].file
);
691 html_print(r
[i
].names
);
693 " <td><span class=\"Nd\">");
694 html_print(r
[i
].output
);
695 puts("</span></td>\n"
701 if (req
->q
.equal
|| sz
== 1) {
703 resp_show(req
, r
[iuse
].file
);
710 resp_catman(const struct req
*req
, const char *file
)
719 if ((f
= fopen(file
, "r")) == NULL
) {
720 puts("<p>You specified an invalid manual file.</p>");
724 puts("<div class=\"catman\">\n"
730 while ((len
= getline(&p
, &sz
, f
)) != -1) {
732 for (i
= 0; i
< len
- 1; i
++) {
734 * This means that the catpage is out of state.
735 * Ignore it and keep going (although the
739 if ('\b' == p
[i
] || '\n' == p
[i
])
743 * Print a regular character.
744 * Close out any bold/italic scopes.
745 * If we're in back-space mode, make sure we'll
746 * have something to enter when we backspace.
749 if ('\b' != p
[i
+ 1]) {
757 } else if (i
+ 2 >= len
)
775 * Handle funny behaviour troff-isms.
776 * These grok'd from the original man2html.c.
779 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
780 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
781 ('|' == 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])) {
795 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
796 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
797 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
798 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
799 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
800 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
824 * Clean up the last character.
825 * We can get to a newline; don't print that.
833 if (i
== len
- 1 && p
[i
] != '\n')
847 resp_format(const struct req
*req
, const char *file
)
849 struct manoutput conf
;
851 struct roff_man
*man
;
856 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
857 puts("<p>You specified an invalid manual file.</p>");
862 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
,
863 MANDOCERR_MAX
, NULL
, MANDOC_OS_OTHER
, req
->q
.manpath
);
864 mparse_readfd(mp
, fd
, file
);
867 memset(&conf
, 0, sizeof(conf
));
869 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
871 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
872 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
873 scriptname
, *scriptname
== '\0' ? "" : "/",
874 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
876 mparse_result(mp
, &man
, NULL
);
878 warnx("fatal mandoc error: %s/%s", req
->q
.manpath
, file
);
885 vp
= html_alloc(&conf
);
887 if (man
->macroset
== MACROSET_MDOC
) {
903 resp_show(const struct req
*req
, const char *file
)
906 if ('.' == file
[0] && '/' == file
[1])
910 resp_catman(req
, file
);
912 resp_format(req
, file
);
916 pg_show(struct req
*req
, const char *fullpath
)
921 if ((file
= strchr(fullpath
, '/')) == NULL
) {
923 "You did not specify a page to show.");
926 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
929 if ( ! validate_manpath(req
, manpath
)) {
931 "You specified an invalid manpath.");
937 * Begin by chdir()ing into the manpath.
938 * This way we can pick up the database files, which are
939 * relative to the manpath root.
942 if (chdir(manpath
) == -1) {
943 warn("chdir %s", manpath
);
950 if ( ! validate_filename(file
)) {
952 "You specified an invalid manual file.");
956 resp_begin_html(200, NULL
, file
);
957 resp_searchform(req
, FOCUS_NONE
);
958 resp_show(req
, file
);
963 pg_search(const struct req
*req
)
965 struct mansearch search
;
966 struct manpaths paths
;
969 char *query
, *rp
, *wp
;
974 * Begin by chdir()ing into the root of the manpath.
975 * This way we can pick up the database files, which are
976 * relative to the manpath root.
979 if (chdir(req
->q
.manpath
) == -1) {
980 warn("chdir %s", req
->q
.manpath
);
985 search
.arch
= req
->q
.arch
;
986 search
.sec
= req
->q
.sec
;
987 search
.outkey
= "Nd";
988 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
989 search
.firstmatch
= 1;
992 paths
.paths
= mandoc_malloc(sizeof(char *));
993 paths
.paths
[0] = mandoc_strdup(".");
996 * Break apart at spaces with backslash-escaping.
1001 rp
= query
= mandoc_strdup(req
->q
.query
);
1003 while (isspace((unsigned char)*rp
))
1007 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1008 argv
[argc
++] = wp
= rp
;
1010 if (isspace((unsigned char)*rp
)) {
1015 if (rp
[0] == '\\' && rp
[1] != '\0')
1028 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1029 pg_redirect(req
, argv
[0]);
1030 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1031 pg_noresult(req
, "You entered an invalid query.");
1032 else if (ressz
== 0)
1033 pg_noresult(req
, "No results found.");
1035 pg_searchres(req
, res
, ressz
);
1038 mansearch_free(res
, ressz
);
1039 free(paths
.paths
[0]);
1047 struct itimerval itimer
;
1049 const char *querystring
;
1054 * The "rpath" pledge could be revoked after mparse_readfd()
1055 * if the file desciptor to "/footer.html" would be opened
1056 * up front, but it's probably not worth the complication
1057 * of the code it would cause: it would require scattering
1058 * pledge() calls in multiple low-level resp_*() functions.
1061 if (pledge("stdio rpath", NULL
) == -1) {
1063 pg_error_internal();
1064 return EXIT_FAILURE
;
1068 /* Poor man's ReDoS mitigation. */
1070 itimer
.it_value
.tv_sec
= 2;
1071 itimer
.it_value
.tv_usec
= 0;
1072 itimer
.it_interval
.tv_sec
= 2;
1073 itimer
.it_interval
.tv_usec
= 0;
1074 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1076 pg_error_internal();
1077 return EXIT_FAILURE
;
1081 * First we change directory into the MAN_DIR so that
1082 * subsequent scanning for manpath directories is rooted
1083 * relative to the same position.
1086 if (chdir(MAN_DIR
) == -1) {
1087 warn("MAN_DIR: %s", MAN_DIR
);
1088 pg_error_internal();
1089 return EXIT_FAILURE
;
1092 memset(&req
, 0, sizeof(struct req
));
1094 parse_manpath_conf(&req
);
1096 /* Parse the path info and the query string. */
1098 if ((path
= getenv("PATH_INFO")) == NULL
)
1100 else if (*path
== '/')
1103 if (*path
!= '\0') {
1104 parse_path_info(&req
, path
);
1105 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1106 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1108 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1109 parse_query_string(&req
, querystring
);
1111 /* Validate parsed data and add defaults. */
1113 if (req
.q
.manpath
== NULL
)
1114 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1115 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1116 pg_error_badrequest(
1117 "You specified an invalid manpath.");
1118 return EXIT_FAILURE
;
1121 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1122 pg_error_badrequest(
1123 "You specified an invalid architecture.");
1124 return EXIT_FAILURE
;
1127 /* Dispatch to the three different pages. */
1130 pg_show(&req
, path
);
1131 else if (NULL
!= req
.q
.query
)
1136 free(req
.q
.manpath
);
1140 for (i
= 0; i
< (int)req
.psz
; i
++)
1143 return EXIT_SUCCESS
;
1147 * Translate PATH_INFO to a query.
1150 parse_path_info(struct req
*req
, const char *path
)
1152 const char *name
, *sec
, *end
;
1156 req
->q
.manpath
= NULL
;
1159 /* Mandatory manual page name. */
1160 if ((name
= strrchr(path
, '/')) == NULL
)
1165 /* Optional trailing section. */
1166 sec
= strrchr(name
, '.');
1167 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1168 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1169 req
->q
.sec
= mandoc_strdup(sec
);
1171 req
->q
.query
= mandoc_strdup(name
);
1175 /* Handle the case of name[.section] only. */
1179 /* Optional manpath. */
1180 end
= strchr(path
, '/');
1181 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1182 if (validate_manpath(req
, req
->q
.manpath
)) {
1187 free(req
->q
.manpath
);
1188 req
->q
.manpath
= NULL
;
1191 /* Optional section. */
1192 if (strncmp(path
, "man", 3) == 0) {
1194 end
= strchr(path
, '/');
1196 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1202 /* Optional architecture. */
1203 end
= strchr(path
, '/');
1204 if (end
+ 1 != name
) {
1205 pg_error_badrequest(
1206 "You specified too many directory components.");
1209 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1210 if (validate_arch(req
->q
.arch
) == 0) {
1211 pg_error_badrequest(
1212 "You specified an invalid directory component.");
1218 * Scan for indexable paths.
1221 parse_manpath_conf(struct req
*req
)
1228 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1229 warn("%s/manpath.conf", MAN_DIR
);
1230 pg_error_internal();
1237 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1238 if (dp
[len
- 1] == '\n')
1240 req
->p
= mandoc_realloc(req
->p
,
1241 (req
->psz
+ 1) * sizeof(char *));
1242 if ( ! validate_urifrag(dp
)) {
1243 warnx("%s/manpath.conf contains "
1244 "unsafe path \"%s\"", MAN_DIR
, dp
);
1245 pg_error_internal();
1248 if (strchr(dp
, '/') != NULL
) {
1249 warnx("%s/manpath.conf contains "
1250 "path with slash \"%s\"", MAN_DIR
, dp
);
1251 pg_error_internal();
1254 req
->p
[req
->psz
++] = dp
;
1260 if (req
->p
== NULL
) {
1261 warnx("%s/manpath.conf is empty", MAN_DIR
);
1262 pg_error_internal();