]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.171 2020/01/10 15:21:19 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014-2019 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
*, int, const char *,
82 static void pg_redirect(const struct req
*, const char *);
83 static void pg_search(const struct req
*);
84 static void pg_searchres(const struct req
*,
85 struct manpage
*, size_t);
86 static void pg_show(struct req
*, const char *);
87 static void resp_begin_html(int, const char *, const char *);
88 static void resp_begin_http(int, const char *);
89 static void resp_catman(const struct req
*, const char *);
90 static void resp_copy(const char *);
91 static void resp_end_html(void);
92 static void resp_format(const struct req
*, const char *);
93 static void resp_searchform(const struct req
*, enum focus
);
94 static void resp_show(const struct req
*, const char *);
95 static void set_query_attr(char **, char **);
96 static int validate_arch(const char *);
97 static int validate_filename(const char *);
98 static int validate_manpath(const struct req
*, const char *);
99 static int validate_urifrag(const char *);
101 static const char *scriptname
= SCRIPT_NAME
;
103 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
104 static const char *const sec_numbers
[] = {
105 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
107 static const char *const sec_names
[] = {
109 "1 - General Commands",
111 "3 - Library Functions",
113 "4 - Device Drivers",
116 "7 - Miscellaneous Information",
117 "8 - System Manager\'s Manual",
118 "9 - Kernel Developer\'s Manual"
120 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
122 static const char *const arch_names
[] = {
123 "amd64", "alpha", "armv7", "arm64",
124 "hppa", "i386", "landisk",
125 "loongson", "luna88k", "macppc", "mips64",
126 "octeon", "sgi", "socppc", "sparc64",
127 "amiga", "arc", "armish", "arm32",
128 "atari", "aviion", "beagle", "cats",
130 "ia64", "mac68k", "mvme68k", "mvme88k",
131 "mvmeppc", "palm", "pc532", "pegasos",
132 "pmax", "powerpc", "solbourne", "sparc",
133 "sun3", "vax", "wgrisc", "x68k",
136 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
139 * Print a character, escaping HTML along the way.
140 * This will pass non-ASCII straight to output: be warned!
160 putchar((unsigned char)c
);
166 * Call through to html_putchar().
167 * Accepts NULL strings.
170 html_print(const char *p
)
180 * Transfer the responsibility for the allocated string *val
181 * to the query structure.
184 set_query_attr(char **attr
, char **val
)
197 * Parse the QUERY_STRING for key-value pairs
198 * and store the values into the query structure.
201 parse_query_string(struct req
*req
, const char *qs
)
207 req
->q
.manpath
= NULL
;
214 while (*qs
!= '\0') {
218 keysz
= strcspn(qs
, "=;&");
219 key
= mandoc_strndup(qs
, keysz
);
224 /* Parse one value. */
226 valsz
= strcspn(++qs
, ";&");
227 val
= mandoc_strndup(qs
, valsz
);
230 /* Decode and catch encoding errors. */
232 if ( ! (http_decode(key
) && http_decode(val
)))
235 /* Handle key-value pairs. */
237 if ( ! strcmp(key
, "query"))
238 set_query_attr(&req
->q
.query
, &val
);
240 else if ( ! strcmp(key
, "apropos"))
241 req
->q
.equal
= !strcmp(val
, "0");
243 else if ( ! strcmp(key
, "manpath")) {
245 if ( ! strncmp(val
, "OpenBSD ", 8)) {
251 set_query_attr(&req
->q
.manpath
, &val
);
254 else if ( ! (strcmp(key
, "sec")
256 && strcmp(key
, "sektion")
259 if ( ! strcmp(val
, "0"))
261 set_query_attr(&req
->q
.sec
, &val
);
264 else if ( ! strcmp(key
, "arch")) {
265 if ( ! strcmp(val
, "default"))
267 set_query_attr(&req
->q
.arch
, &val
);
271 * The key must be freed in any case.
272 * The val may have been handed over to the query
273 * structure, in which case it is now NULL.
287 * HTTP-decode a string. The standard explanation is that this turns
288 * "%4e+foo" into "n foo" in the regular way. This is done in-place
289 * over the allocated string.
301 for ( ; '\0' != *p
; p
++, q
++) {
303 if ('\0' == (hex
[0] = *(p
+ 1)))
305 if ('\0' == (hex
[1] = *(p
+ 2)))
307 if (1 != sscanf(hex
, "%x", &c
))
315 *q
= '+' == *p
? ' ' : *p
;
323 http_encode(const char *p
)
325 for (; *p
!= '\0'; p
++) {
326 if (isalnum((unsigned char)*p
) == 0 &&
327 strchr("-._~", *p
) == NULL
)
328 printf("%%%2.2X", (unsigned char)*p
);
335 resp_begin_http(int code
, const char *msg
)
339 printf("Status: %d %s\r\n", code
, msg
);
341 printf("Content-Type: text/html; charset=utf-8\r\n"
342 "Cache-Control: no-cache\r\n"
343 "Content-Security-Policy: default-src 'none'; "
344 "style-src 'self' 'unsafe-inline'\r\n"
345 "Pragma: no-cache\r\n"
352 resp_copy(const char *filename
)
358 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
360 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
361 write(STDOUT_FILENO
, buf
, sz
);
367 resp_begin_html(int code
, const char *msg
, const char *file
)
371 resp_begin_http(code
, msg
);
373 printf("<!DOCTYPE html>\n"
376 " <meta charset=\"UTF-8\"/>\n"
377 " <meta name=\"viewport\""
378 " content=\"width=device-width, initial-scale=1.0\">\n"
379 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
380 " type=\"text/css\" media=\"all\">\n"
384 if ((cp
= strrchr(file
, '/')) != NULL
)
386 if ((cp
= strrchr(file
, '.')) != NULL
) {
387 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
389 printf("%s - ", file
);
391 printf("%s</title>\n"
396 resp_copy(MAN_DIR
"/header.html");
403 resp_copy(MAN_DIR
"/footer.html");
410 resp_searchform(const struct req
*req
, enum focus focus
)
414 printf("<form action=\"/%s\" method=\"get\" "
415 "autocomplete=\"off\" autocapitalize=\"none\">\n"
417 " <legend>Manual Page Search Parameters</legend>\n",
420 /* Write query input box. */
422 printf(" <input type=\"search\" name=\"query\" value=\"");
423 if (req
->q
.query
!= NULL
)
424 html_print(req
->q
.query
);
425 printf( "\" size=\"40\"");
426 if (focus
== FOCUS_QUERY
)
427 printf(" autofocus");
430 /* Write submission buttons. */
432 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
434 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
438 /* Write section selector. */
440 puts(" <select name=\"sec\">");
441 for (i
= 0; i
< sec_MAX
; i
++) {
442 printf(" <option value=\"%s\"", sec_numbers
[i
]);
443 if (NULL
!= req
->q
.sec
&&
444 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
445 printf(" selected=\"selected\"");
446 printf(">%s</option>\n", sec_names
[i
]);
450 /* Write architecture selector. */
452 printf( " <select name=\"arch\">\n"
453 " <option value=\"default\"");
454 if (NULL
== req
->q
.arch
)
455 printf(" selected=\"selected\"");
456 puts(">All Architectures</option>");
457 for (i
= 0; i
< arch_MAX
; i
++) {
459 if (NULL
!= req
->q
.arch
&&
460 0 == strcmp(arch_names
[i
], req
->q
.arch
))
461 printf(" selected=\"selected\"");
462 printf(">%s</option>\n", arch_names
[i
]);
466 /* Write manpath selector. */
469 puts(" <select name=\"manpath\">");
470 for (i
= 0; i
< (int)req
->psz
; i
++) {
472 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
473 printf(" selected=\"selected\"");
475 html_print(req
->p
[i
]);
481 puts(" </fieldset>\n"
486 validate_urifrag(const char *frag
)
489 while ('\0' != *frag
) {
490 if ( ! (isalnum((unsigned char)*frag
) ||
491 '-' == *frag
|| '.' == *frag
||
492 '/' == *frag
|| '_' == *frag
))
500 validate_manpath(const struct req
*req
, const char* manpath
)
504 for (i
= 0; i
< req
->psz
; i
++)
505 if ( ! strcmp(manpath
, req
->p
[i
]))
512 validate_arch(const char *arch
)
516 for (i
= 0; i
< arch_MAX
; i
++)
517 if (strcmp(arch
, arch_names
[i
]) == 0)
524 validate_filename(const char *file
)
527 if ('.' == file
[0] && '/' == file
[1])
530 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
531 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
535 pg_index(const struct req
*req
)
538 resp_begin_html(200, NULL
, NULL
);
539 resp_searchform(req
, FOCUS_QUERY
);
541 "This web interface is documented in the\n"
542 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
544 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
545 "manual explains the query syntax.\n"
547 scriptname
, *scriptname
== '\0' ? "" : "/",
548 scriptname
, *scriptname
== '\0' ? "" : "/");
553 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
554 const char *user_msg
)
556 resp_begin_html(code
, http_msg
, NULL
);
557 resp_searchform(req
, FOCUS_QUERY
);
565 pg_error_badrequest(const char *msg
)
568 resp_begin_html(400, "Bad Request", NULL
);
569 puts("<h1>Bad Request</h1>\n"
572 printf("Try again from the\n"
573 "<a href=\"/%s\">main page</a>.\n"
579 pg_error_internal(void)
581 resp_begin_html(500, "Internal Server Error", NULL
);
582 puts("<p>Internal Server Error</p>");
587 pg_redirect(const struct req
*req
, const char *name
)
589 printf("Status: 303 See Other\r\n"
591 if (*scriptname
!= '\0')
592 printf("%s/", scriptname
);
593 if (strcmp(req
->q
.manpath
, req
->p
[0]))
594 printf("%s/", req
->q
.manpath
);
595 if (req
->q
.arch
!= NULL
)
596 printf("%s/", req
->q
.arch
);
598 if (req
->q
.sec
!= NULL
) {
600 http_encode(req
->q
.sec
);
602 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
606 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
608 char *arch
, *archend
;
611 int archprio
, archpriouse
;
614 for (i
= 0; i
< sz
; i
++) {
615 if (validate_filename(r
[i
].file
))
617 warnx("invalid filename %s in %s database",
618 r
[i
].file
, req
->q
.manpath
);
623 if (req
->isquery
&& sz
== 1) {
625 * If we have just one result, then jump there now
628 printf("Status: 303 See Other\r\n"
630 if (*scriptname
!= '\0')
631 printf("%s/", scriptname
);
632 if (strcmp(req
->q
.manpath
, req
->p
[0]))
633 printf("%s/", req
->q
.manpath
);
635 "Content-Type: text/html; charset=utf-8\r\n\r\n",
641 * In man(1) mode, show one of the pages
642 * even if more than one is found.
646 if (req
->q
.equal
|| sz
== 1) {
649 for (i
= 0; i
< sz
; i
++) {
651 sec
+= strcspn(sec
, "123456789");
654 prio
= sec_prios
[sec
[0] - '1'];
657 if (req
->q
.arch
== NULL
) {
659 ((arch
= strchr(sec
+ 1, '/'))
661 ((archend
= strchr(arch
+ 1, '/'))
663 strncmp(arch
, "amd64/",
664 archend
- arch
) ? 2 : 1;
665 if (archprio
< archpriouse
) {
666 archpriouse
= archprio
;
671 if (archprio
> archpriouse
)
679 resp_begin_html(200, NULL
, r
[iuse
].file
);
681 resp_begin_html(200, NULL
, NULL
);
684 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
687 puts("<table class=\"results\">");
688 for (i
= 0; i
< sz
; i
++) {
691 "<a class=\"Xr\" href=\"/");
692 if (*scriptname
!= '\0')
693 printf("%s/", scriptname
);
694 if (strcmp(req
->q
.manpath
, req
->p
[0]))
695 printf("%s/", req
->q
.manpath
);
696 printf("%s\">", r
[i
].file
);
697 html_print(r
[i
].names
);
699 " <td><span class=\"Nd\">");
700 html_print(r
[i
].output
);
701 puts("</span></td>\n"
707 if (req
->q
.equal
|| sz
== 1) {
709 resp_show(req
, r
[iuse
].file
);
716 resp_catman(const struct req
*req
, const char *file
)
725 if ((f
= fopen(file
, "r")) == NULL
) {
726 puts("<p>You specified an invalid manual file.</p>");
730 puts("<div class=\"catman\">\n"
736 while ((len
= getline(&p
, &sz
, f
)) != -1) {
738 for (i
= 0; i
< len
- 1; i
++) {
740 * This means that the catpage is out of state.
741 * Ignore it and keep going (although the
745 if ('\b' == p
[i
] || '\n' == p
[i
])
749 * Print a regular character.
750 * Close out any bold/italic scopes.
751 * If we're in back-space mode, make sure we'll
752 * have something to enter when we backspace.
755 if ('\b' != p
[i
+ 1]) {
763 } else if (i
+ 2 >= len
)
781 * Handle funny behaviour troff-isms.
782 * These grok'd from the original man2html.c.
785 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
786 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
787 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
788 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
789 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
790 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
791 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
792 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
801 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
802 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
803 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
804 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
805 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
806 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
830 * Clean up the last character.
831 * We can get to a newline; don't print that.
839 if (i
== len
- 1 && p
[i
] != '\n')
853 resp_format(const struct req
*req
, const char *file
)
855 struct manoutput conf
;
857 struct roff_meta
*meta
;
862 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
863 puts("<p>You specified an invalid manual file.</p>");
868 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
869 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
870 mparse_readfd(mp
, fd
, file
);
872 meta
= mparse_result(mp
);
874 memset(&conf
, 0, sizeof(conf
));
876 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
877 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
878 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
879 scriptname
, *scriptname
== '\0' ? "" : "/",
880 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
882 vp
= html_alloc(&conf
);
883 if (meta
->macroset
== MACROSET_MDOC
)
896 resp_show(const struct req
*req
, const char *file
)
899 if ('.' == file
[0] && '/' == file
[1])
903 resp_catman(req
, file
);
905 resp_format(req
, file
);
909 pg_show(struct req
*req
, const char *fullpath
)
914 if ((file
= strchr(fullpath
, '/')) == NULL
) {
916 "You did not specify a page to show.");
919 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
922 if ( ! validate_manpath(req
, manpath
)) {
924 "You specified an invalid manpath.");
930 * Begin by chdir()ing into the manpath.
931 * This way we can pick up the database files, which are
932 * relative to the manpath root.
935 if (chdir(manpath
) == -1) {
936 warn("chdir %s", manpath
);
943 if ( ! validate_filename(file
)) {
945 "You specified an invalid manual file.");
949 resp_begin_html(200, NULL
, file
);
950 resp_searchform(req
, FOCUS_NONE
);
951 resp_show(req
, file
);
956 pg_search(const struct req
*req
)
958 struct mansearch search
;
959 struct manpaths paths
;
962 char *query
, *rp
, *wp
;
967 * Begin by chdir()ing into the root of the manpath.
968 * This way we can pick up the database files, which are
969 * relative to the manpath root.
972 if (chdir(req
->q
.manpath
) == -1) {
973 warn("chdir %s", req
->q
.manpath
);
978 search
.arch
= req
->q
.arch
;
979 search
.sec
= req
->q
.sec
;
980 search
.outkey
= "Nd";
981 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
982 search
.firstmatch
= 1;
985 paths
.paths
= mandoc_malloc(sizeof(char *));
986 paths
.paths
[0] = mandoc_strdup(".");
989 * Break apart at spaces with backslash-escaping.
994 rp
= query
= mandoc_strdup(req
->q
.query
);
996 while (isspace((unsigned char)*rp
))
1000 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1001 argv
[argc
++] = wp
= rp
;
1003 if (isspace((unsigned char)*rp
)) {
1008 if (rp
[0] == '\\' && rp
[1] != '\0')
1021 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1022 pg_redirect(req
, argv
[0]);
1023 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1024 pg_noresult(req
, 400, "Bad Request",
1025 "You entered an invalid query.");
1026 else if (ressz
== 0)
1027 pg_noresult(req
, 404, "Not Found", "No results found.");
1029 pg_searchres(req
, res
, ressz
);
1032 mansearch_free(res
, ressz
);
1033 free(paths
.paths
[0]);
1041 struct itimerval itimer
;
1043 const char *querystring
;
1048 * The "rpath" pledge could be revoked after mparse_readfd()
1049 * if the file desciptor to "/footer.html" would be opened
1050 * up front, but it's probably not worth the complication
1051 * of the code it would cause: it would require scattering
1052 * pledge() calls in multiple low-level resp_*() functions.
1055 if (pledge("stdio rpath", NULL
) == -1) {
1057 pg_error_internal();
1058 return EXIT_FAILURE
;
1062 /* Poor man's ReDoS mitigation. */
1064 itimer
.it_value
.tv_sec
= 2;
1065 itimer
.it_value
.tv_usec
= 0;
1066 itimer
.it_interval
.tv_sec
= 2;
1067 itimer
.it_interval
.tv_usec
= 0;
1068 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1070 pg_error_internal();
1071 return EXIT_FAILURE
;
1075 * First we change directory into the MAN_DIR so that
1076 * subsequent scanning for manpath directories is rooted
1077 * relative to the same position.
1080 if (chdir(MAN_DIR
) == -1) {
1081 warn("MAN_DIR: %s", MAN_DIR
);
1082 pg_error_internal();
1083 return EXIT_FAILURE
;
1086 memset(&req
, 0, sizeof(struct req
));
1088 parse_manpath_conf(&req
);
1090 /* Parse the path info and the query string. */
1092 if ((path
= getenv("PATH_INFO")) == NULL
)
1094 else if (*path
== '/')
1097 if (*path
!= '\0') {
1098 parse_path_info(&req
, path
);
1099 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1100 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1102 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1103 parse_query_string(&req
, querystring
);
1105 /* Validate parsed data and add defaults. */
1107 if (req
.q
.manpath
== NULL
)
1108 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1109 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1110 pg_error_badrequest(
1111 "You specified an invalid manpath.");
1112 return EXIT_FAILURE
;
1115 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1116 pg_error_badrequest(
1117 "You specified an invalid architecture.");
1118 return EXIT_FAILURE
;
1121 /* Dispatch to the three different pages. */
1124 pg_show(&req
, path
);
1125 else if (NULL
!= req
.q
.query
)
1130 free(req
.q
.manpath
);
1134 for (i
= 0; i
< (int)req
.psz
; i
++)
1137 return EXIT_SUCCESS
;
1141 * Translate PATH_INFO to a query.
1144 parse_path_info(struct req
*req
, const char *path
)
1146 const char *name
, *sec
, *end
;
1150 req
->q
.manpath
= NULL
;
1153 /* Mandatory manual page name. */
1154 if ((name
= strrchr(path
, '/')) == NULL
)
1159 /* Optional trailing section. */
1160 sec
= strrchr(name
, '.');
1161 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1162 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1163 req
->q
.sec
= mandoc_strdup(sec
);
1165 req
->q
.query
= mandoc_strdup(name
);
1169 /* Handle the case of name[.section] only. */
1173 /* Optional manpath. */
1174 end
= strchr(path
, '/');
1175 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1176 if (validate_manpath(req
, req
->q
.manpath
)) {
1181 free(req
->q
.manpath
);
1182 req
->q
.manpath
= NULL
;
1185 /* Optional section. */
1186 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1188 end
= strchr(path
, '/');
1190 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1196 /* Optional architecture. */
1197 end
= strchr(path
, '/');
1198 if (end
+ 1 != name
) {
1199 pg_error_badrequest(
1200 "You specified too many directory components.");
1203 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1204 if (validate_arch(req
->q
.arch
) == 0) {
1205 pg_error_badrequest(
1206 "You specified an invalid directory component.");
1212 * Scan for indexable paths.
1215 parse_manpath_conf(struct req
*req
)
1222 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1223 warn("%s/manpath.conf", MAN_DIR
);
1224 pg_error_internal();
1231 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1232 if (dp
[len
- 1] == '\n')
1234 req
->p
= mandoc_realloc(req
->p
,
1235 (req
->psz
+ 1) * sizeof(char *));
1236 if ( ! validate_urifrag(dp
)) {
1237 warnx("%s/manpath.conf contains "
1238 "unsafe path \"%s\"", MAN_DIR
, dp
);
1239 pg_error_internal();
1242 if (strchr(dp
, '/') != NULL
) {
1243 warnx("%s/manpath.conf contains "
1244 "path with slash \"%s\"", MAN_DIR
, dp
);
1245 pg_error_internal();
1248 req
->p
[req
->psz
++] = dp
;
1254 if (req
->p
== NULL
) {
1255 warnx("%s/manpath.conf is empty", MAN_DIR
);
1256 pg_error_internal();