]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.173 2020/06/29 19:22:09 schwarze Exp $ */
3 * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@usta.de>
4 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
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.
18 * Implementation of the man.cgi(8) program.
22 #include <sys/types.h>
38 #include "mandoc_aux.h"
43 #include "mandoc_parse.h"
46 #include "mansearch.h"
50 * A query as passed to the search function.
53 char *manpath
; /* desired manual directory */
54 char *arch
; /* architecture */
55 char *sec
; /* manual section */
56 char *query
; /* unparsed query expression */
57 int equal
; /* match whole names, not substrings */
62 char **p
; /* array of available manpaths */
63 size_t psz
; /* number of available manpaths */
64 int isquery
; /* QUERY_STRING used, not PATH_INFO */
72 static void html_print(const char *);
73 static void html_putchar(char);
74 static int http_decode(char *);
75 static void http_encode(const char *);
76 static void parse_manpath_conf(struct req
*);
77 static void parse_path_info(struct req
*, const char *);
78 static void parse_query_string(struct req
*, const char *);
79 static void pg_error_badrequest(const char *);
80 static void pg_error_internal(void);
81 static void pg_index(const struct req
*);
82 static void pg_noresult(const struct req
*, int, const char *,
84 static void pg_redirect(const struct req
*, const char *);
85 static void pg_search(const struct req
*);
86 static void pg_searchres(const struct req
*,
87 struct manpage
*, size_t);
88 static void pg_show(struct req
*, const char *);
89 static void resp_begin_html(int, const char *, const char *);
90 static void resp_begin_http(int, const char *);
91 static void resp_catman(const struct req
*, const char *);
92 static void resp_copy(const char *);
93 static void resp_end_html(void);
94 static void resp_format(const struct req
*, const char *);
95 static void resp_searchform(const struct req
*, enum focus
);
96 static void resp_show(const struct req
*, const char *);
97 static void set_query_attr(char **, char **);
98 static int validate_arch(const char *);
99 static int validate_filename(const char *);
100 static int validate_manpath(const struct req
*, const char *);
101 static int validate_urifrag(const char *);
103 static const char *scriptname
= SCRIPT_NAME
;
105 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
106 static const char *const sec_numbers
[] = {
107 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
109 static const char *const sec_names
[] = {
111 "1 - General Commands",
113 "3 - Library Functions",
115 "4 - Device Drivers",
118 "7 - Miscellaneous Information",
119 "8 - System Manager\'s Manual",
120 "9 - Kernel Developer\'s Manual"
122 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
124 static const char *const arch_names
[] = {
125 "amd64", "alpha", "armv7", "arm64",
126 "hppa", "i386", "landisk", "loongson",
127 "luna88k", "macppc", "mips64", "octeon",
128 "powerpc64", "sgi", "socppc", "sparc64",
130 "amiga", "arc", "armish", "arm32",
131 "atari", "aviion", "beagle", "cats",
133 "ia64", "mac68k", "mvme68k", "mvme88k",
134 "mvmeppc", "palm", "pc532", "pegasos",
135 "pmax", "powerpc", "solbourne", "sparc",
136 "sun3", "vax", "wgrisc", "x68k",
139 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
142 * Print a character, escaping HTML along the way.
143 * This will pass non-ASCII straight to output: be warned!
163 putchar((unsigned char)c
);
169 * Call through to html_putchar().
170 * Accepts NULL strings.
173 html_print(const char *p
)
183 * Transfer the responsibility for the allocated string *val
184 * to the query structure.
187 set_query_attr(char **attr
, char **val
)
200 * Parse the QUERY_STRING for key-value pairs
201 * and store the values into the query structure.
204 parse_query_string(struct req
*req
, const char *qs
)
210 req
->q
.manpath
= NULL
;
217 while (*qs
!= '\0') {
221 keysz
= strcspn(qs
, "=;&");
222 key
= mandoc_strndup(qs
, keysz
);
227 /* Parse one value. */
229 valsz
= strcspn(++qs
, ";&");
230 val
= mandoc_strndup(qs
, valsz
);
233 /* Decode and catch encoding errors. */
235 if ( ! (http_decode(key
) && http_decode(val
)))
238 /* Handle key-value pairs. */
240 if ( ! strcmp(key
, "query"))
241 set_query_attr(&req
->q
.query
, &val
);
243 else if ( ! strcmp(key
, "apropos"))
244 req
->q
.equal
= !strcmp(val
, "0");
246 else if ( ! strcmp(key
, "manpath")) {
248 if ( ! strncmp(val
, "OpenBSD ", 8)) {
254 set_query_attr(&req
->q
.manpath
, &val
);
257 else if ( ! (strcmp(key
, "sec")
259 && strcmp(key
, "sektion")
262 if ( ! strcmp(val
, "0"))
264 set_query_attr(&req
->q
.sec
, &val
);
267 else if ( ! strcmp(key
, "arch")) {
268 if ( ! strcmp(val
, "default"))
270 set_query_attr(&req
->q
.arch
, &val
);
274 * The key must be freed in any case.
275 * The val may have been handed over to the query
276 * structure, in which case it is now NULL.
290 * HTTP-decode a string. The standard explanation is that this turns
291 * "%4e+foo" into "n foo" in the regular way. This is done in-place
292 * over the allocated string.
304 for ( ; '\0' != *p
; p
++, q
++) {
306 if ('\0' == (hex
[0] = *(p
+ 1)))
308 if ('\0' == (hex
[1] = *(p
+ 2)))
310 if (1 != sscanf(hex
, "%x", &c
))
318 *q
= '+' == *p
? ' ' : *p
;
326 http_encode(const char *p
)
328 for (; *p
!= '\0'; p
++) {
329 if (isalnum((unsigned char)*p
) == 0 &&
330 strchr("-._~", *p
) == NULL
)
331 printf("%%%2.2X", (unsigned char)*p
);
338 resp_begin_http(int code
, const char *msg
)
342 printf("Status: %d %s\r\n", code
, msg
);
344 printf("Content-Type: text/html; charset=utf-8\r\n"
345 "Cache-Control: no-cache\r\n"
346 "Content-Security-Policy: default-src 'none'; "
347 "style-src 'self' 'unsafe-inline'\r\n"
348 "Pragma: no-cache\r\n"
355 resp_copy(const char *filename
)
361 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
363 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
364 write(STDOUT_FILENO
, buf
, sz
);
370 resp_begin_html(int code
, const char *msg
, const char *file
)
374 resp_begin_http(code
, msg
);
376 printf("<!DOCTYPE html>\n"
379 " <meta charset=\"UTF-8\"/>\n"
380 " <meta name=\"viewport\""
381 " content=\"width=device-width, initial-scale=1.0\">\n"
382 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
383 " type=\"text/css\" media=\"all\">\n"
387 if ((cp
= strrchr(file
, '/')) != NULL
)
389 if ((cp
= strrchr(file
, '.')) != NULL
) {
390 printf("%.*s(%s) - ", (int)(cp
- file
), file
, cp
+ 1);
392 printf("%s - ", file
);
394 printf("%s</title>\n"
399 resp_copy(MAN_DIR
"/header.html");
406 resp_copy(MAN_DIR
"/footer.html");
413 resp_searchform(const struct req
*req
, enum focus focus
)
417 printf("<form action=\"/%s\" method=\"get\" "
418 "autocomplete=\"off\" autocapitalize=\"none\">\n"
420 " <legend>Manual Page Search Parameters</legend>\n",
423 /* Write query input box. */
425 printf(" <input type=\"search\" name=\"query\" value=\"");
426 if (req
->q
.query
!= NULL
)
427 html_print(req
->q
.query
);
428 printf( "\" size=\"40\"");
429 if (focus
== FOCUS_QUERY
)
430 printf(" autofocus");
433 /* Write submission buttons. */
435 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
437 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
441 /* Write section selector. */
443 puts(" <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
++) {
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 (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
476 printf(" selected=\"selected\"");
478 html_print(req
->p
[i
]);
484 puts(" </fieldset>\n"
489 validate_urifrag(const char *frag
)
492 while ('\0' != *frag
) {
493 if ( ! (isalnum((unsigned char)*frag
) ||
494 '-' == *frag
|| '.' == *frag
||
495 '/' == *frag
|| '_' == *frag
))
503 validate_manpath(const struct req
*req
, const char* manpath
)
507 for (i
= 0; i
< req
->psz
; i
++)
508 if ( ! strcmp(manpath
, req
->p
[i
]))
515 validate_arch(const char *arch
)
519 for (i
= 0; i
< arch_MAX
; i
++)
520 if (strcmp(arch
, arch_names
[i
]) == 0)
527 validate_filename(const char *file
)
530 if ('.' == file
[0] && '/' == file
[1])
533 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
534 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
538 pg_index(const struct req
*req
)
541 resp_begin_html(200, NULL
, NULL
);
542 resp_searchform(req
, FOCUS_QUERY
);
544 "This web interface is documented in the\n"
545 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
547 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
548 "manual explains the query syntax.\n"
550 scriptname
, *scriptname
== '\0' ? "" : "/",
551 scriptname
, *scriptname
== '\0' ? "" : "/");
556 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
557 const char *user_msg
)
559 resp_begin_html(code
, http_msg
, NULL
);
560 resp_searchform(req
, FOCUS_QUERY
);
568 pg_error_badrequest(const char *msg
)
571 resp_begin_html(400, "Bad Request", NULL
);
572 puts("<h1>Bad Request</h1>\n"
575 printf("Try again from the\n"
576 "<a href=\"/%s\">main page</a>.\n"
582 pg_error_internal(void)
584 resp_begin_html(500, "Internal Server Error", NULL
);
585 puts("<p>Internal Server Error</p>");
590 pg_redirect(const struct req
*req
, const char *name
)
592 printf("Status: 303 See Other\r\n"
594 if (*scriptname
!= '\0')
595 printf("%s/", scriptname
);
596 if (strcmp(req
->q
.manpath
, req
->p
[0]))
597 printf("%s/", req
->q
.manpath
);
598 if (req
->q
.arch
!= NULL
)
599 printf("%s/", req
->q
.arch
);
601 if (req
->q
.sec
!= NULL
) {
603 http_encode(req
->q
.sec
);
605 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
609 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
611 char *arch
, *archend
;
614 int archprio
, archpriouse
;
617 for (i
= 0; i
< sz
; i
++) {
618 if (validate_filename(r
[i
].file
))
620 warnx("invalid filename %s in %s database",
621 r
[i
].file
, req
->q
.manpath
);
626 if (req
->isquery
&& sz
== 1) {
628 * If we have just one result, then jump there now
631 printf("Status: 303 See Other\r\n"
633 if (*scriptname
!= '\0')
634 printf("%s/", scriptname
);
635 if (strcmp(req
->q
.manpath
, req
->p
[0]))
636 printf("%s/", req
->q
.manpath
);
638 "Content-Type: text/html; charset=utf-8\r\n\r\n",
644 * In man(1) mode, show one of the pages
645 * even if more than one is found.
649 if (req
->q
.equal
|| sz
== 1) {
652 for (i
= 0; i
< sz
; i
++) {
654 sec
+= strcspn(sec
, "123456789");
657 prio
= sec_prios
[sec
[0] - '1'];
660 if (req
->q
.arch
== NULL
) {
662 ((arch
= strchr(sec
+ 1, '/'))
664 ((archend
= strchr(arch
+ 1, '/'))
666 strncmp(arch
, "amd64/",
667 archend
- arch
) ? 2 : 1;
668 if (archprio
< archpriouse
) {
669 archpriouse
= archprio
;
674 if (archprio
> archpriouse
)
682 resp_begin_html(200, NULL
, r
[iuse
].file
);
684 resp_begin_html(200, NULL
, NULL
);
687 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
690 puts("<table class=\"results\">");
691 for (i
= 0; i
< sz
; i
++) {
694 "<a class=\"Xr\" href=\"/");
695 if (*scriptname
!= '\0')
696 printf("%s/", scriptname
);
697 if (strcmp(req
->q
.manpath
, req
->p
[0]))
698 printf("%s/", req
->q
.manpath
);
699 printf("%s\">", r
[i
].file
);
700 html_print(r
[i
].names
);
702 " <td><span class=\"Nd\">");
703 html_print(r
[i
].output
);
704 puts("</span></td>\n"
710 if (req
->q
.equal
|| sz
== 1) {
712 resp_show(req
, r
[iuse
].file
);
719 resp_catman(const struct req
*req
, const char *file
)
728 if ((f
= fopen(file
, "r")) == NULL
) {
729 puts("<p>You specified an invalid manual file.</p>");
733 puts("<div class=\"catman\">\n"
739 while ((len
= getline(&p
, &sz
, f
)) != -1) {
741 for (i
= 0; i
< len
- 1; i
++) {
743 * This means that the catpage is out of state.
744 * Ignore it and keep going (although the
748 if ('\b' == p
[i
] || '\n' == p
[i
])
752 * Print a regular character.
753 * Close out any bold/italic scopes.
754 * If we're in back-space mode, make sure we'll
755 * have something to enter when we backspace.
758 if ('\b' != p
[i
+ 1]) {
766 } else if (i
+ 2 >= len
)
784 * Handle funny behaviour troff-isms.
785 * These grok'd from the original man2html.c.
788 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
789 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
790 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
791 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
792 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
793 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
794 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
795 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
804 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
805 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
806 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
807 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
808 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
809 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
833 * Clean up the last character.
834 * We can get to a newline; don't print that.
842 if (i
== len
- 1 && p
[i
] != '\n')
856 resp_format(const struct req
*req
, const char *file
)
858 struct manoutput conf
;
860 struct roff_meta
*meta
;
865 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
866 puts("<p>You specified an invalid manual file.</p>");
871 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
872 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
873 mparse_readfd(mp
, fd
, file
);
875 meta
= mparse_result(mp
);
877 memset(&conf
, 0, sizeof(conf
));
879 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
880 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
881 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
882 scriptname
, *scriptname
== '\0' ? "" : "/",
883 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
885 vp
= html_alloc(&conf
);
886 if (meta
->macroset
== MACROSET_MDOC
)
899 resp_show(const struct req
*req
, const char *file
)
902 if ('.' == file
[0] && '/' == file
[1])
906 resp_catman(req
, file
);
908 resp_format(req
, file
);
912 pg_show(struct req
*req
, const char *fullpath
)
917 if ((file
= strchr(fullpath
, '/')) == NULL
) {
919 "You did not specify a page to show.");
922 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
925 if ( ! validate_manpath(req
, manpath
)) {
927 "You specified an invalid manpath.");
933 * Begin by chdir()ing into the manpath.
934 * This way we can pick up the database files, which are
935 * relative to the manpath root.
938 if (chdir(manpath
) == -1) {
939 warn("chdir %s", manpath
);
946 if ( ! validate_filename(file
)) {
948 "You specified an invalid manual file.");
952 resp_begin_html(200, NULL
, file
);
953 resp_searchform(req
, FOCUS_NONE
);
954 resp_show(req
, file
);
959 pg_search(const struct req
*req
)
961 struct mansearch search
;
962 struct manpaths paths
;
965 char *query
, *rp
, *wp
;
970 * Begin by chdir()ing into the root of the manpath.
971 * This way we can pick up the database files, which are
972 * relative to the manpath root.
975 if (chdir(req
->q
.manpath
) == -1) {
976 warn("chdir %s", req
->q
.manpath
);
981 search
.arch
= req
->q
.arch
;
982 search
.sec
= req
->q
.sec
;
983 search
.outkey
= "Nd";
984 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
985 search
.firstmatch
= 1;
988 paths
.paths
= mandoc_malloc(sizeof(char *));
989 paths
.paths
[0] = mandoc_strdup(".");
992 * Break apart at spaces with backslash-escaping.
997 rp
= query
= mandoc_strdup(req
->q
.query
);
999 while (isspace((unsigned char)*rp
))
1003 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1004 argv
[argc
++] = wp
= rp
;
1006 if (isspace((unsigned char)*rp
)) {
1011 if (rp
[0] == '\\' && rp
[1] != '\0')
1024 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1025 pg_redirect(req
, argv
[0]);
1026 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1027 pg_noresult(req
, 400, "Bad Request",
1028 "You entered an invalid query.");
1029 else if (ressz
== 0)
1030 pg_noresult(req
, 404, "Not Found", "No results found.");
1032 pg_searchres(req
, res
, ressz
);
1035 mansearch_free(res
, ressz
);
1036 free(paths
.paths
[0]);
1044 struct itimerval itimer
;
1046 const char *querystring
;
1051 * The "rpath" pledge could be revoked after mparse_readfd()
1052 * if the file desciptor to "/footer.html" would be opened
1053 * up front, but it's probably not worth the complication
1054 * of the code it would cause: it would require scattering
1055 * pledge() calls in multiple low-level resp_*() functions.
1058 if (pledge("stdio rpath", NULL
) == -1) {
1060 pg_error_internal();
1061 return EXIT_FAILURE
;
1065 /* Poor man's ReDoS mitigation. */
1067 itimer
.it_value
.tv_sec
= 2;
1068 itimer
.it_value
.tv_usec
= 0;
1069 itimer
.it_interval
.tv_sec
= 2;
1070 itimer
.it_interval
.tv_usec
= 0;
1071 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1073 pg_error_internal();
1074 return EXIT_FAILURE
;
1078 * First we change directory into the MAN_DIR so that
1079 * subsequent scanning for manpath directories is rooted
1080 * relative to the same position.
1083 if (chdir(MAN_DIR
) == -1) {
1084 warn("MAN_DIR: %s", MAN_DIR
);
1085 pg_error_internal();
1086 return EXIT_FAILURE
;
1089 memset(&req
, 0, sizeof(struct req
));
1091 parse_manpath_conf(&req
);
1093 /* Parse the path info and the query string. */
1095 if ((path
= getenv("PATH_INFO")) == NULL
)
1097 else if (*path
== '/')
1100 if (*path
!= '\0') {
1101 parse_path_info(&req
, path
);
1102 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1103 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1105 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1106 parse_query_string(&req
, querystring
);
1108 /* Validate parsed data and add defaults. */
1110 if (req
.q
.manpath
== NULL
)
1111 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1112 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1113 pg_error_badrequest(
1114 "You specified an invalid manpath.");
1115 return EXIT_FAILURE
;
1118 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1119 pg_error_badrequest(
1120 "You specified an invalid architecture.");
1121 return EXIT_FAILURE
;
1124 /* Dispatch to the three different pages. */
1127 pg_show(&req
, path
);
1128 else if (NULL
!= req
.q
.query
)
1133 free(req
.q
.manpath
);
1137 for (i
= 0; i
< (int)req
.psz
; i
++)
1140 return EXIT_SUCCESS
;
1144 * Translate PATH_INFO to a query.
1147 parse_path_info(struct req
*req
, const char *path
)
1149 const char *name
, *sec
, *end
;
1153 req
->q
.manpath
= NULL
;
1156 /* Mandatory manual page name. */
1157 if ((name
= strrchr(path
, '/')) == NULL
)
1162 /* Optional trailing section. */
1163 sec
= strrchr(name
, '.');
1164 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1165 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1166 req
->q
.sec
= mandoc_strdup(sec
);
1168 req
->q
.query
= mandoc_strdup(name
);
1172 /* Handle the case of name[.section] only. */
1176 /* Optional manpath. */
1177 end
= strchr(path
, '/');
1178 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1179 if (validate_manpath(req
, req
->q
.manpath
)) {
1184 free(req
->q
.manpath
);
1185 req
->q
.manpath
= NULL
;
1188 /* Optional section. */
1189 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1191 end
= strchr(path
, '/');
1193 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1199 /* Optional architecture. */
1200 end
= strchr(path
, '/');
1201 if (end
+ 1 != name
) {
1202 pg_error_badrequest(
1203 "You specified too many directory components.");
1206 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1207 if (validate_arch(req
->q
.arch
) == 0) {
1208 pg_error_badrequest(
1209 "You specified an invalid directory component.");
1215 * Scan for indexable paths.
1218 parse_manpath_conf(struct req
*req
)
1225 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1226 warn("%s/manpath.conf", MAN_DIR
);
1227 pg_error_internal();
1234 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1235 if (dp
[len
- 1] == '\n')
1237 req
->p
= mandoc_realloc(req
->p
,
1238 (req
->psz
+ 1) * sizeof(char *));
1239 if ( ! validate_urifrag(dp
)) {
1240 warnx("%s/manpath.conf contains "
1241 "unsafe path \"%s\"", MAN_DIR
, dp
);
1242 pg_error_internal();
1245 if (strchr(dp
, '/') != NULL
) {
1246 warnx("%s/manpath.conf contains "
1247 "path with slash \"%s\"", MAN_DIR
, dp
);
1248 pg_error_internal();
1251 req
->p
[req
->psz
++] = dp
;
1257 if (req
->p
== NULL
) {
1258 warnx("%s/manpath.conf is empty", MAN_DIR
);
1259 pg_error_internal();