]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.176 2021/11/05 17:04:10 schwarze Exp $ */
3 * Copyright (c) 2014-2019, 2021 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", "riscv64", "sparc64",
130 "amiga", "arc", "armish", "arm32",
131 "atari", "aviion", "beagle", "cats",
133 "ia64", "mac68k", "mvme68k", "mvme88k",
134 "mvmeppc", "palm", "pc532", "pegasos",
135 "pmax", "powerpc", "sgi", "socppc",
136 "solbourne", "sparc",
137 "sun3", "vax", "wgrisc", "x68k",
140 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
143 * Print a character, escaping HTML along the way.
144 * This will pass non-ASCII straight to output: be warned!
164 putchar((unsigned char)c
);
170 * Call through to html_putchar().
171 * Accepts NULL strings.
174 html_print(const char *p
)
184 * Transfer the responsibility for the allocated string *val
185 * to the query structure.
188 set_query_attr(char **attr
, char **val
)
201 * Parse the QUERY_STRING for key-value pairs
202 * and store the values into the query structure.
205 parse_query_string(struct req
*req
, const char *qs
)
211 req
->q
.manpath
= NULL
;
218 while (*qs
!= '\0') {
222 keysz
= strcspn(qs
, "=;&");
223 key
= mandoc_strndup(qs
, keysz
);
228 /* Parse one value. */
230 valsz
= strcspn(++qs
, ";&");
231 val
= mandoc_strndup(qs
, valsz
);
234 /* Decode and catch encoding errors. */
236 if ( ! (http_decode(key
) && http_decode(val
)))
239 /* Handle key-value pairs. */
241 if ( ! strcmp(key
, "query"))
242 set_query_attr(&req
->q
.query
, &val
);
244 else if ( ! strcmp(key
, "apropos"))
245 req
->q
.equal
= !strcmp(val
, "0");
247 else if ( ! strcmp(key
, "manpath")) {
249 if ( ! strncmp(val
, "OpenBSD ", 8)) {
255 set_query_attr(&req
->q
.manpath
, &val
);
258 else if ( ! (strcmp(key
, "sec")
260 && strcmp(key
, "sektion")
263 if ( ! strcmp(val
, "0"))
265 set_query_attr(&req
->q
.sec
, &val
);
268 else if ( ! strcmp(key
, "arch")) {
269 if ( ! strcmp(val
, "default"))
271 set_query_attr(&req
->q
.arch
, &val
);
275 * The key must be freed in any case.
276 * The val may have been handed over to the query
277 * structure, in which case it is now NULL.
291 * HTTP-decode a string. The standard explanation is that this turns
292 * "%4e+foo" into "n foo" in the regular way. This is done in-place
293 * over the allocated string.
305 for ( ; '\0' != *p
; p
++, q
++) {
307 if ('\0' == (hex
[0] = *(p
+ 1)))
309 if ('\0' == (hex
[1] = *(p
+ 2)))
311 if (1 != sscanf(hex
, "%x", &c
))
319 *q
= '+' == *p
? ' ' : *p
;
327 http_encode(const char *p
)
329 for (; *p
!= '\0'; p
++) {
330 if (isalnum((unsigned char)*p
) == 0 &&
331 strchr("-._~", *p
) == NULL
)
332 printf("%%%2.2X", (unsigned char)*p
);
339 resp_begin_http(int code
, const char *msg
)
343 printf("Status: %d %s\r\n", code
, msg
);
345 printf("Content-Type: text/html; charset=utf-8\r\n"
346 "Cache-Control: no-cache\r\n"
347 "Content-Security-Policy: default-src 'none'; "
348 "style-src 'self' 'unsafe-inline'\r\n"
349 "Pragma: no-cache\r\n"
356 resp_copy(const char *filename
)
362 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
364 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
365 write(STDOUT_FILENO
, buf
, sz
);
371 resp_begin_html(int code
, const char *msg
, const char *file
)
373 const char *name
, *sec
, *cp
;
376 resp_begin_http(code
, msg
);
378 printf("<!DOCTYPE html>\n"
381 " <meta charset=\"UTF-8\"/>\n"
382 " <meta name=\"viewport\""
383 " content=\"width=device-width, initial-scale=1.0\">\n"
384 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
385 " type=\"text/css\" media=\"all\">\n"
389 cp
= strrchr(file
, '/');
390 name
= cp
== NULL
? file
: cp
+ 1;
391 cp
= strrchr(name
, '.');
392 namesz
= cp
== NULL
? strlen(name
) : cp
- name
;
394 if (cp
!= NULL
&& cp
[1] != '0') {
397 } else if (name
- file
> 1) {
398 for (cp
= name
- 2; cp
>= file
; cp
--) {
399 if (*cp
< '1' || *cp
> '9')
402 secsz
= name
- cp
- 1;
406 printf("%.*s", namesz
, name
);
408 printf("(%.*s)", secsz
, sec
);
409 fputs(" - ", stdout
);
411 printf("%s</title>\n"
416 resp_copy(MAN_DIR
"/header.html");
423 resp_copy(MAN_DIR
"/footer.html");
430 resp_searchform(const struct req
*req
, enum focus focus
)
434 printf("<form action=\"/%s\" method=\"get\" "
435 "autocomplete=\"off\" autocapitalize=\"none\">\n"
437 " <legend>Manual Page Search Parameters</legend>\n",
440 /* Write query input box. */
442 printf(" <input type=\"search\" name=\"query\" value=\"");
443 if (req
->q
.query
!= NULL
)
444 html_print(req
->q
.query
);
445 printf( "\" size=\"40\"");
446 if (focus
== FOCUS_QUERY
)
447 printf(" autofocus");
450 /* Write submission buttons. */
452 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
454 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
458 /* Write section selector. */
460 puts(" <select name=\"sec\">");
461 for (i
= 0; i
< sec_MAX
; i
++) {
462 printf(" <option value=\"%s\"", sec_numbers
[i
]);
463 if (NULL
!= req
->q
.sec
&&
464 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
465 printf(" selected=\"selected\"");
466 printf(">%s</option>\n", sec_names
[i
]);
470 /* Write architecture selector. */
472 printf( " <select name=\"arch\">\n"
473 " <option value=\"default\"");
474 if (NULL
== req
->q
.arch
)
475 printf(" selected=\"selected\"");
476 puts(">All Architectures</option>");
477 for (i
= 0; i
< arch_MAX
; i
++) {
479 if (NULL
!= req
->q
.arch
&&
480 0 == strcmp(arch_names
[i
], req
->q
.arch
))
481 printf(" selected=\"selected\"");
482 printf(">%s</option>\n", arch_names
[i
]);
486 /* Write manpath selector. */
489 puts(" <select name=\"manpath\">");
490 for (i
= 0; i
< (int)req
->psz
; i
++) {
492 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
493 printf(" selected=\"selected\"");
495 html_print(req
->p
[i
]);
501 puts(" </fieldset>\n"
506 validate_urifrag(const char *frag
)
509 while ('\0' != *frag
) {
510 if ( ! (isalnum((unsigned char)*frag
) ||
511 '-' == *frag
|| '.' == *frag
||
512 '/' == *frag
|| '_' == *frag
))
520 validate_manpath(const struct req
*req
, const char* manpath
)
524 for (i
= 0; i
< req
->psz
; i
++)
525 if ( ! strcmp(manpath
, req
->p
[i
]))
532 validate_arch(const char *arch
)
536 for (i
= 0; i
< arch_MAX
; i
++)
537 if (strcmp(arch
, arch_names
[i
]) == 0)
544 validate_filename(const char *file
)
547 if ('.' == file
[0] && '/' == file
[1])
550 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
551 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
555 pg_index(const struct req
*req
)
558 resp_begin_html(200, NULL
, NULL
);
559 resp_searchform(req
, FOCUS_QUERY
);
561 "This web interface is documented in the\n"
562 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\">man.cgi(8)</a>\n"
564 "<a class=\"Xr\" href=\"/%s%sapropos.1\">apropos(1)</a>\n"
565 "manual explains the query syntax.\n"
567 scriptname
, *scriptname
== '\0' ? "" : "/",
568 scriptname
, *scriptname
== '\0' ? "" : "/");
573 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
574 const char *user_msg
)
576 resp_begin_html(code
, http_msg
, NULL
);
577 resp_searchform(req
, FOCUS_QUERY
);
585 pg_error_badrequest(const char *msg
)
588 resp_begin_html(400, "Bad Request", NULL
);
589 puts("<h1>Bad Request</h1>\n"
592 printf("Try again from the\n"
593 "<a href=\"/%s\">main page</a>.\n"
599 pg_error_internal(void)
601 resp_begin_html(500, "Internal Server Error", NULL
);
602 puts("<p>Internal Server Error</p>");
607 pg_redirect(const struct req
*req
, const char *name
)
609 printf("Status: 303 See Other\r\n"
611 if (*scriptname
!= '\0')
612 printf("%s/", scriptname
);
613 if (strcmp(req
->q
.manpath
, req
->p
[0]))
614 printf("%s/", req
->q
.manpath
);
615 if (req
->q
.arch
!= NULL
)
616 printf("%s/", req
->q
.arch
);
618 if (req
->q
.sec
!= NULL
) {
620 http_encode(req
->q
.sec
);
622 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
626 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
628 char *arch
, *archend
;
631 int archprio
, archpriouse
;
634 for (i
= 0; i
< sz
; i
++) {
635 if (validate_filename(r
[i
].file
))
637 warnx("invalid filename %s in %s database",
638 r
[i
].file
, req
->q
.manpath
);
643 if (req
->isquery
&& sz
== 1) {
645 * If we have just one result, then jump there now
648 printf("Status: 303 See Other\r\n"
650 if (*scriptname
!= '\0')
651 printf("%s/", scriptname
);
652 if (strcmp(req
->q
.manpath
, req
->p
[0]))
653 printf("%s/", req
->q
.manpath
);
655 "Content-Type: text/html; charset=utf-8\r\n\r\n",
661 * In man(1) mode, show one of the pages
662 * even if more than one is found.
666 if (req
->q
.equal
|| sz
== 1) {
669 for (i
= 0; i
< sz
; i
++) {
671 sec
+= strcspn(sec
, "123456789");
674 prio
= sec_prios
[sec
[0] - '1'];
677 if (req
->q
.arch
== NULL
) {
679 ((arch
= strchr(sec
+ 1, '/'))
681 ((archend
= strchr(arch
+ 1, '/'))
683 strncmp(arch
, "amd64/",
684 archend
- arch
) ? 2 : 1;
685 if (archprio
< archpriouse
) {
686 archpriouse
= archprio
;
691 if (archprio
> archpriouse
)
699 resp_begin_html(200, NULL
, r
[iuse
].file
);
701 resp_begin_html(200, NULL
, NULL
);
704 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
707 puts("<table class=\"results\">");
708 for (i
= 0; i
< sz
; i
++) {
711 "<a class=\"Xr\" href=\"/");
712 if (*scriptname
!= '\0')
713 printf("%s/", scriptname
);
714 if (strcmp(req
->q
.manpath
, req
->p
[0]))
715 printf("%s/", req
->q
.manpath
);
716 printf("%s\">", r
[i
].file
);
717 html_print(r
[i
].names
);
719 " <td><span class=\"Nd\">");
720 html_print(r
[i
].output
);
721 puts("</span></td>\n"
727 if (req
->q
.equal
|| sz
== 1) {
729 resp_show(req
, r
[iuse
].file
);
736 resp_catman(const struct req
*req
, const char *file
)
745 if ((f
= fopen(file
, "r")) == NULL
) {
746 puts("<p>You specified an invalid manual file.</p>");
750 puts("<div class=\"catman\">\n"
756 while ((len
= getline(&p
, &sz
, f
)) != -1) {
758 for (i
= 0; i
< len
- 1; i
++) {
760 * This means that the catpage is out of state.
761 * Ignore it and keep going (although the
765 if ('\b' == p
[i
] || '\n' == p
[i
])
769 * Print a regular character.
770 * Close out any bold/italic scopes.
771 * If we're in back-space mode, make sure we'll
772 * have something to enter when we backspace.
775 if ('\b' != p
[i
+ 1]) {
783 } else if (i
+ 2 >= len
)
801 * Handle funny behaviour troff-isms.
802 * These grok'd from the original man2html.c.
805 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
806 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
807 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
808 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
809 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
810 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
811 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
812 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
821 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
822 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
823 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
824 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
825 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
826 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
850 * Clean up the last character.
851 * We can get to a newline; don't print that.
859 if (i
== len
- 1 && p
[i
] != '\n')
873 resp_format(const struct req
*req
, const char *file
)
875 struct manoutput conf
;
877 struct roff_meta
*meta
;
882 if (-1 == (fd
= open(file
, O_RDONLY
))) {
883 puts("<p>You specified an invalid manual file.</p>");
888 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
889 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
890 mparse_readfd(mp
, fd
, file
);
892 meta
= mparse_result(mp
);
894 memset(&conf
, 0, sizeof(conf
));
896 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
897 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
898 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
899 scriptname
, *scriptname
== '\0' ? "" : "/",
900 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
902 vp
= html_alloc(&conf
);
903 if (meta
->macroset
== MACROSET_MDOC
)
916 resp_show(const struct req
*req
, const char *file
)
919 if ('.' == file
[0] && '/' == file
[1])
923 resp_catman(req
, file
);
925 resp_format(req
, file
);
929 pg_show(struct req
*req
, const char *fullpath
)
934 if ((file
= strchr(fullpath
, '/')) == NULL
) {
936 "You did not specify a page to show.");
939 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
942 if ( ! validate_manpath(req
, manpath
)) {
944 "You specified an invalid manpath.");
950 * Begin by chdir()ing into the manpath.
951 * This way we can pick up the database files, which are
952 * relative to the manpath root.
955 if (chdir(manpath
) == -1) {
956 warn("chdir %s", manpath
);
963 if ( ! validate_filename(file
)) {
965 "You specified an invalid manual file.");
969 resp_begin_html(200, NULL
, file
);
970 resp_searchform(req
, FOCUS_NONE
);
971 resp_show(req
, file
);
976 pg_search(const struct req
*req
)
978 struct mansearch search
;
979 struct manpaths paths
;
982 char *query
, *rp
, *wp
;
987 * Begin by chdir()ing into the root of the manpath.
988 * This way we can pick up the database files, which are
989 * relative to the manpath root.
992 if (chdir(req
->q
.manpath
) == -1) {
993 warn("chdir %s", req
->q
.manpath
);
998 search
.arch
= req
->q
.arch
;
999 search
.sec
= req
->q
.sec
;
1000 search
.outkey
= "Nd";
1001 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
1002 search
.firstmatch
= 1;
1005 paths
.paths
= mandoc_malloc(sizeof(char *));
1006 paths
.paths
[0] = mandoc_strdup(".");
1009 * Break apart at spaces with backslash-escaping.
1014 rp
= query
= mandoc_strdup(req
->q
.query
);
1016 while (isspace((unsigned char)*rp
))
1020 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1021 argv
[argc
++] = wp
= rp
;
1023 if (isspace((unsigned char)*rp
)) {
1028 if (rp
[0] == '\\' && rp
[1] != '\0')
1041 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1042 pg_redirect(req
, argv
[0]);
1043 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1044 pg_noresult(req
, 400, "Bad Request",
1045 "You entered an invalid query.");
1046 else if (ressz
== 0)
1047 pg_noresult(req
, 404, "Not Found", "No results found.");
1049 pg_searchres(req
, res
, ressz
);
1052 mansearch_free(res
, ressz
);
1053 free(paths
.paths
[0]);
1061 struct itimerval itimer
;
1063 const char *querystring
;
1068 * The "rpath" pledge could be revoked after mparse_readfd()
1069 * if the file desciptor to "/footer.html" would be opened
1070 * up front, but it's probably not worth the complication
1071 * of the code it would cause: it would require scattering
1072 * pledge() calls in multiple low-level resp_*() functions.
1075 if (pledge("stdio rpath", NULL
) == -1) {
1077 pg_error_internal();
1078 return EXIT_FAILURE
;
1082 /* Poor man's ReDoS mitigation. */
1084 itimer
.it_value
.tv_sec
= 2;
1085 itimer
.it_value
.tv_usec
= 0;
1086 itimer
.it_interval
.tv_sec
= 2;
1087 itimer
.it_interval
.tv_usec
= 0;
1088 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1090 pg_error_internal();
1091 return EXIT_FAILURE
;
1095 * First we change directory into the MAN_DIR so that
1096 * subsequent scanning for manpath directories is rooted
1097 * relative to the same position.
1100 if (chdir(MAN_DIR
) == -1) {
1101 warn("MAN_DIR: %s", MAN_DIR
);
1102 pg_error_internal();
1103 return EXIT_FAILURE
;
1106 memset(&req
, 0, sizeof(struct req
));
1108 parse_manpath_conf(&req
);
1110 /* Parse the path info and the query string. */
1112 if ((path
= getenv("PATH_INFO")) == NULL
)
1114 else if (*path
== '/')
1117 if (*path
!= '\0') {
1118 parse_path_info(&req
, path
);
1119 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1120 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1122 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1123 parse_query_string(&req
, querystring
);
1125 /* Validate parsed data and add defaults. */
1127 if (req
.q
.manpath
== NULL
)
1128 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1129 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1130 pg_error_badrequest(
1131 "You specified an invalid manpath.");
1132 return EXIT_FAILURE
;
1135 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1136 pg_error_badrequest(
1137 "You specified an invalid architecture.");
1138 return EXIT_FAILURE
;
1141 /* Dispatch to the three different pages. */
1144 pg_show(&req
, path
);
1145 else if (NULL
!= req
.q
.query
)
1150 free(req
.q
.manpath
);
1154 for (i
= 0; i
< (int)req
.psz
; i
++)
1157 return EXIT_SUCCESS
;
1161 * Translate PATH_INFO to a query.
1164 parse_path_info(struct req
*req
, const char *path
)
1166 const char *name
, *sec
, *end
;
1170 req
->q
.manpath
= NULL
;
1173 /* Mandatory manual page name. */
1174 if ((name
= strrchr(path
, '/')) == NULL
)
1179 /* Optional trailing section. */
1180 sec
= strrchr(name
, '.');
1181 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1182 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1183 req
->q
.sec
= mandoc_strdup(sec
);
1185 req
->q
.query
= mandoc_strdup(name
);
1189 /* Handle the case of name[.section] only. */
1193 /* Optional manpath. */
1194 end
= strchr(path
, '/');
1195 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1196 if (validate_manpath(req
, req
->q
.manpath
)) {
1201 free(req
->q
.manpath
);
1202 req
->q
.manpath
= NULL
;
1205 /* Optional section. */
1206 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1208 end
= strchr(path
, '/');
1210 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1216 /* Optional architecture. */
1217 end
= strchr(path
, '/');
1218 if (end
+ 1 != name
) {
1219 pg_error_badrequest(
1220 "You specified too many directory components.");
1223 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1224 if (validate_arch(req
->q
.arch
) == 0) {
1225 pg_error_badrequest(
1226 "You specified an invalid directory component.");
1232 * Scan for indexable paths.
1235 parse_manpath_conf(struct req
*req
)
1242 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1243 warn("%s/manpath.conf", MAN_DIR
);
1244 pg_error_internal();
1251 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1252 if (dp
[len
- 1] == '\n')
1254 req
->p
= mandoc_realloc(req
->p
,
1255 (req
->psz
+ 1) * sizeof(char *));
1256 if ( ! validate_urifrag(dp
)) {
1257 warnx("%s/manpath.conf contains "
1258 "unsafe path \"%s\"", MAN_DIR
, dp
);
1259 pg_error_internal();
1262 if (strchr(dp
, '/') != NULL
) {
1263 warnx("%s/manpath.conf contains "
1264 "path with slash \"%s\"", MAN_DIR
, dp
);
1265 pg_error_internal();
1268 req
->p
[req
->psz
++] = dp
;
1274 if (req
->p
== NULL
) {
1275 warnx("%s/manpath.conf is empty", MAN_DIR
);
1276 pg_error_internal();