]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.177 2022/07/04 16:20:42 schwarze Exp $ */
3 * Copyright (c) 2014-2019, 2021 Ingo Schwarze <schwarze@usta.de>
4 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Copyright (c) 2022 Anna Vyalkova <cyber@sysrq.in>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 * Implementation of the man.cgi(8) program.
23 #include <sys/types.h>
39 #include "mandoc_aux.h"
44 #include "mandoc_parse.h"
47 #include "mansearch.h"
51 * A query as passed to the search function.
54 char *manpath
; /* desired manual directory */
55 char *arch
; /* architecture */
56 char *sec
; /* manual section */
57 char *query
; /* unparsed query expression */
58 int equal
; /* match whole names, not substrings */
63 char **p
; /* array of available manpaths */
64 size_t psz
; /* number of available manpaths */
65 int isquery
; /* QUERY_STRING used, not PATH_INFO */
73 static void html_print(const char *);
74 static void html_putchar(char);
75 static int http_decode(char *);
76 static void http_encode(const char *);
77 static void parse_manpath_conf(struct req
*);
78 static void parse_path_info(struct req
*, const char *);
79 static void parse_query_string(struct req
*, const char *);
80 static void pg_error_badrequest(const char *);
81 static void pg_error_internal(void);
82 static void pg_index(const struct req
*);
83 static void pg_noresult(const struct req
*, int, const char *,
85 static void pg_redirect(const struct req
*, const char *);
86 static void pg_search(const struct req
*);
87 static void pg_searchres(const struct req
*,
88 struct manpage
*, size_t);
89 static void pg_show(struct req
*, const char *);
90 static void resp_begin_html(int, const char *, const char *);
91 static void resp_begin_http(int, const char *);
92 static void resp_catman(const struct req
*, const char *);
93 static void resp_copy(const char *);
94 static void resp_end_html(void);
95 static void resp_format(const struct req
*, const char *);
96 static void resp_searchform(const struct req
*, enum focus
);
97 static void resp_show(const struct req
*, const char *);
98 static void set_query_attr(char **, char **);
99 static int validate_arch(const char *);
100 static int validate_filename(const char *);
101 static int validate_manpath(const struct req
*, const char *);
102 static int validate_urifrag(const char *);
104 static const char *scriptname
= SCRIPT_NAME
;
106 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
107 static const char *const sec_numbers
[] = {
108 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
110 static const char *const sec_names
[] = {
112 "1 - General Commands",
114 "3 - Library Functions",
116 "4 - Device Drivers",
119 "7 - Miscellaneous Information",
120 "8 - System Manager\'s Manual",
121 "9 - Kernel Developer\'s Manual"
123 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
125 static const char *const arch_names
[] = {
126 "amd64", "alpha", "armv7", "arm64",
127 "hppa", "i386", "landisk", "loongson",
128 "luna88k", "macppc", "mips64", "octeon",
129 "powerpc64", "riscv64", "sparc64",
131 "amiga", "arc", "armish", "arm32",
132 "atari", "aviion", "beagle", "cats",
134 "ia64", "mac68k", "mvme68k", "mvme88k",
135 "mvmeppc", "palm", "pc532", "pegasos",
136 "pmax", "powerpc", "sgi", "socppc",
137 "solbourne", "sparc",
138 "sun3", "vax", "wgrisc", "x68k",
141 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
144 * Print a character, escaping HTML along the way.
145 * This will pass non-ASCII straight to output: be warned!
165 putchar((unsigned char)c
);
171 * Call through to html_putchar().
172 * Accepts NULL strings.
175 html_print(const char *p
)
185 * Transfer the responsibility for the allocated string *val
186 * to the query structure.
189 set_query_attr(char **attr
, char **val
)
202 * Parse the QUERY_STRING for key-value pairs
203 * and store the values into the query structure.
206 parse_query_string(struct req
*req
, const char *qs
)
212 req
->q
.manpath
= NULL
;
219 while (*qs
!= '\0') {
223 keysz
= strcspn(qs
, "=;&");
224 key
= mandoc_strndup(qs
, keysz
);
229 /* Parse one value. */
231 valsz
= strcspn(++qs
, ";&");
232 val
= mandoc_strndup(qs
, valsz
);
235 /* Decode and catch encoding errors. */
237 if ( ! (http_decode(key
) && http_decode(val
)))
240 /* Handle key-value pairs. */
242 if ( ! strcmp(key
, "query"))
243 set_query_attr(&req
->q
.query
, &val
);
245 else if ( ! strcmp(key
, "apropos"))
246 req
->q
.equal
= !strcmp(val
, "0");
248 else if ( ! strcmp(key
, "manpath")) {
250 if ( ! strncmp(val
, "OpenBSD ", 8)) {
256 set_query_attr(&req
->q
.manpath
, &val
);
259 else if ( ! (strcmp(key
, "sec")
261 && strcmp(key
, "sektion")
264 if ( ! strcmp(val
, "0"))
266 set_query_attr(&req
->q
.sec
, &val
);
269 else if ( ! strcmp(key
, "arch")) {
270 if ( ! strcmp(val
, "default"))
272 set_query_attr(&req
->q
.arch
, &val
);
276 * The key must be freed in any case.
277 * The val may have been handed over to the query
278 * structure, in which case it is now NULL.
292 * HTTP-decode a string. The standard explanation is that this turns
293 * "%4e+foo" into "n foo" in the regular way. This is done in-place
294 * over the allocated string.
306 for ( ; '\0' != *p
; p
++, q
++) {
308 if ('\0' == (hex
[0] = *(p
+ 1)))
310 if ('\0' == (hex
[1] = *(p
+ 2)))
312 if (1 != sscanf(hex
, "%x", &c
))
320 *q
= '+' == *p
? ' ' : *p
;
328 http_encode(const char *p
)
330 for (; *p
!= '\0'; p
++) {
331 if (isalnum((unsigned char)*p
) == 0 &&
332 strchr("-._~", *p
) == NULL
)
333 printf("%%%2.2X", (unsigned char)*p
);
340 resp_begin_http(int code
, const char *msg
)
344 printf("Status: %d %s\r\n", code
, msg
);
346 printf("Content-Type: text/html; charset=utf-8\r\n"
347 "Cache-Control: no-cache\r\n"
348 "Content-Security-Policy: default-src 'none'; "
349 "style-src 'self' 'unsafe-inline'\r\n"
350 "Pragma: no-cache\r\n"
357 resp_copy(const char *filename
)
363 if ((fd
= open(filename
, O_RDONLY
)) != -1) {
365 while ((sz
= read(fd
, buf
, sizeof(buf
))) > 0)
366 write(STDOUT_FILENO
, buf
, sz
);
372 resp_begin_html(int code
, const char *msg
, const char *file
)
374 const char *name
, *sec
, *cp
;
377 resp_begin_http(code
, msg
);
379 printf("<!DOCTYPE html>\n"
382 " <meta charset=\"UTF-8\"/>\n"
383 " <meta name=\"viewport\""
384 " content=\"width=device-width, initial-scale=1.0\">\n"
385 " <link rel=\"stylesheet\" href=\"%s/mandoc.css\""
386 " type=\"text/css\" media=\"all\">\n"
390 cp
= strrchr(file
, '/');
391 name
= cp
== NULL
? file
: cp
+ 1;
392 cp
= strrchr(name
, '.');
393 namesz
= cp
== NULL
? strlen(name
) : cp
- name
;
395 if (cp
!= NULL
&& cp
[1] != '0') {
398 } else if (name
- file
> 1) {
399 for (cp
= name
- 2; cp
>= file
; cp
--) {
400 if (*cp
< '1' || *cp
> '9')
403 secsz
= name
- cp
- 1;
407 printf("%.*s", namesz
, name
);
409 printf("(%.*s)", secsz
, sec
);
410 fputs(" - ", stdout
);
412 printf("%s</title>\n"
417 resp_copy(MAN_DIR
"/header.html");
424 resp_copy(MAN_DIR
"/footer.html");
431 resp_searchform(const struct req
*req
, enum focus focus
)
436 "<form role=\"search\" action=\"/%s\" method=\"get\" "
437 "autocomplete=\"off\" autocapitalize=\"none\">\n"
439 " <legend>Manual Page Search Parameters</legend>\n",
442 /* Write query input box. */
444 printf(" <input type=\"search\" name=\"query\" value=\"");
445 if (req
->q
.query
!= NULL
)
446 html_print(req
->q
.query
);
447 printf( "\" size=\"40\"");
448 if (focus
== FOCUS_QUERY
)
449 printf(" autofocus");
452 /* Write submission buttons. */
454 printf( " <button type=\"submit\" name=\"apropos\" value=\"0\">"
456 " <button type=\"submit\" name=\"apropos\" value=\"1\">"
460 /* Write section selector. */
462 puts(" <select name=\"sec\" aria-label=\"manual section\">");
463 for (i
= 0; i
< sec_MAX
; i
++) {
464 printf(" <option value=\"%s\"", sec_numbers
[i
]);
465 if (NULL
!= req
->q
.sec
&&
466 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
467 printf(" selected=\"selected\"");
468 printf(">%s</option>\n", sec_names
[i
]);
472 /* Write architecture selector. */
474 printf( " <select name=\"arch\" aria-label=\"CPU architecture\">\n"
475 " <option value=\"default\"");
476 if (NULL
== req
->q
.arch
)
477 printf(" selected=\"selected\"");
478 puts(">All Architectures</option>");
479 for (i
= 0; i
< arch_MAX
; i
++) {
481 if (NULL
!= req
->q
.arch
&&
482 0 == strcmp(arch_names
[i
], req
->q
.arch
))
483 printf(" selected=\"selected\"");
484 printf(">%s</option>\n", arch_names
[i
]);
488 /* Write manpath selector. */
491 puts(" <select name=\"manpath\">");
492 for (i
= 0; i
< (int)req
->psz
; i
++) {
494 if (strcmp(req
->q
.manpath
, req
->p
[i
]) == 0)
495 printf(" selected=\"selected\"");
497 html_print(req
->p
[i
]);
503 puts(" </fieldset>\n"
509 validate_urifrag(const char *frag
)
512 while ('\0' != *frag
) {
513 if ( ! (isalnum((unsigned char)*frag
) ||
514 '-' == *frag
|| '.' == *frag
||
515 '/' == *frag
|| '_' == *frag
))
523 validate_manpath(const struct req
*req
, const char* manpath
)
527 for (i
= 0; i
< req
->psz
; i
++)
528 if ( ! strcmp(manpath
, req
->p
[i
]))
535 validate_arch(const char *arch
)
539 for (i
= 0; i
< arch_MAX
; i
++)
540 if (strcmp(arch
, arch_names
[i
]) == 0)
547 validate_filename(const char *file
)
550 if ('.' == file
[0] && '/' == file
[1])
553 return ! (strstr(file
, "../") || strstr(file
, "/..") ||
554 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3)));
558 pg_index(const struct req
*req
)
561 resp_begin_html(200, NULL
, NULL
);
562 resp_searchform(req
, FOCUS_QUERY
);
564 "<p role=\"doc-notice\" aria-label=\"usage\">\n"
565 "This web interface is documented in the\n"
566 "<a class=\"Xr\" href=\"/%s%sman.cgi.8\""
567 " aria-label=\"man dot CGI, section 8\">man.cgi(8)</a>\n"
569 "<a class=\"Xr\" href=\"/%s%sapropos.1\""
570 " aria-label=\"apropos, section 1\">apropos(1)</a>\n"
571 "manual explains the query syntax.\n"
574 scriptname
, *scriptname
== '\0' ? "" : "/",
575 scriptname
, *scriptname
== '\0' ? "" : "/");
580 pg_noresult(const struct req
*req
, int code
, const char *http_msg
,
581 const char *user_msg
)
583 resp_begin_html(code
, http_msg
, NULL
);
584 resp_searchform(req
, FOCUS_QUERY
);
586 puts("<p role=\"doc-notice\" aria-label=\"no result\">");
594 pg_error_badrequest(const char *msg
)
597 resp_begin_html(400, "Bad Request", NULL
);
599 "<h1>Bad Request</h1>\n"
600 "<p role=\"doc-notice\" aria-label=\"Bad Request\">");
602 printf("Try again from the\n"
603 "<a href=\"/%s\">main page</a>.\n"
605 "</main>", scriptname
);
610 pg_error_internal(void)
612 resp_begin_html(500, "Internal Server Error", NULL
);
613 puts("<main><p role=\"doc-notice\">Internal Server Error</p></main>");
618 pg_redirect(const struct req
*req
, const char *name
)
620 printf("Status: 303 See Other\r\n"
622 if (*scriptname
!= '\0')
623 printf("%s/", scriptname
);
624 if (strcmp(req
->q
.manpath
, req
->p
[0]))
625 printf("%s/", req
->q
.manpath
);
626 if (req
->q
.arch
!= NULL
)
627 printf("%s/", req
->q
.arch
);
629 if (req
->q
.sec
!= NULL
) {
631 http_encode(req
->q
.sec
);
633 printf("\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
637 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
639 char *arch
, *archend
;
642 int archprio
, archpriouse
;
645 for (i
= 0; i
< sz
; i
++) {
646 if (validate_filename(r
[i
].file
))
648 warnx("invalid filename %s in %s database",
649 r
[i
].file
, req
->q
.manpath
);
654 if (req
->isquery
&& sz
== 1) {
656 * If we have just one result, then jump there now
659 printf("Status: 303 See Other\r\n"
661 if (*scriptname
!= '\0')
662 printf("%s/", scriptname
);
663 if (strcmp(req
->q
.manpath
, req
->p
[0]))
664 printf("%s/", req
->q
.manpath
);
666 "Content-Type: text/html; charset=utf-8\r\n\r\n",
672 * In man(1) mode, show one of the pages
673 * even if more than one is found.
677 if (req
->q
.equal
|| sz
== 1) {
680 for (i
= 0; i
< sz
; i
++) {
682 sec
+= strcspn(sec
, "123456789");
685 prio
= sec_prios
[sec
[0] - '1'];
688 if (req
->q
.arch
== NULL
) {
690 ((arch
= strchr(sec
+ 1, '/'))
692 ((archend
= strchr(arch
+ 1, '/'))
694 strncmp(arch
, "amd64/",
695 archend
- arch
) ? 2 : 1;
696 if (archprio
< archpriouse
) {
697 archpriouse
= archprio
;
702 if (archprio
> archpriouse
)
710 resp_begin_html(200, NULL
, r
[iuse
].file
);
712 resp_begin_html(200, NULL
, NULL
);
715 req
->q
.equal
|| sz
== 1 ? FOCUS_NONE
: FOCUS_QUERY
);
719 puts("<table class=\"results\">");
720 for (i
= 0; i
< sz
; i
++) {
723 "<a class=\"Xr\" href=\"/");
724 if (*scriptname
!= '\0')
725 printf("%s/", scriptname
);
726 if (strcmp(req
->q
.manpath
, req
->p
[0]))
727 printf("%s/", req
->q
.manpath
);
728 printf("%s\">", r
[i
].file
);
729 html_print(r
[i
].names
);
731 " <td><span class=\"Nd\">");
732 html_print(r
[i
].output
);
733 puts("</span></td>\n"
740 if (req
->q
.equal
|| sz
== 1) {
742 resp_show(req
, r
[iuse
].file
);
749 resp_catman(const struct req
*req
, const char *file
)
758 if ((f
= fopen(file
, "r")) == NULL
) {
759 puts("<p role=\"doc-notice\">\n"
760 " You specified an invalid manual file.\n"
765 puts("<div class=\"catman\">\n"
771 while ((len
= getline(&p
, &sz
, f
)) != -1) {
773 for (i
= 0; i
< len
- 1; i
++) {
775 * This means that the catpage is out of state.
776 * Ignore it and keep going (although the
780 if ('\b' == p
[i
] || '\n' == p
[i
])
784 * Print a regular character.
785 * Close out any bold/italic scopes.
786 * If we're in back-space mode, make sure we'll
787 * have something to enter when we backspace.
790 if ('\b' != p
[i
+ 1]) {
798 } else if (i
+ 2 >= len
)
816 * Handle funny behaviour troff-isms.
817 * These grok'd from the original man2html.c.
820 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
821 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
822 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
823 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
824 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
825 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
826 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
827 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
836 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
837 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
838 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
839 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
840 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
841 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
865 * Clean up the last character.
866 * We can get to a newline; don't print that.
874 if (i
== len
- 1 && p
[i
] != '\n')
888 resp_format(const struct req
*req
, const char *file
)
890 struct manoutput conf
;
892 struct roff_meta
*meta
;
897 if (-1 == (fd
= open(file
, O_RDONLY
))) {
898 puts("<p role=\"doc-notice\">\n"
899 " You specified an invalid manual file.\n"
905 mp
= mparse_alloc(MPARSE_SO
| MPARSE_UTF8
| MPARSE_LATIN1
|
906 MPARSE_VALIDATE
, MANDOC_OS_OTHER
, req
->q
.manpath
);
907 mparse_readfd(mp
, fd
, file
);
909 meta
= mparse_result(mp
);
911 memset(&conf
, 0, sizeof(conf
));
913 conf
.style
= mandoc_strdup(CSS_DIR
"/mandoc.css");
914 usepath
= strcmp(req
->q
.manpath
, req
->p
[0]);
915 mandoc_asprintf(&conf
.man
, "/%s%s%s%s%%N.%%S",
916 scriptname
, *scriptname
== '\0' ? "" : "/",
917 usepath
? req
->q
.manpath
: "", usepath
? "/" : "");
919 vp
= html_alloc(&conf
);
920 if (meta
->macroset
== MACROSET_MDOC
)
933 resp_show(const struct req
*req
, const char *file
)
936 if ('.' == file
[0] && '/' == file
[1])
940 resp_catman(req
, file
);
942 resp_format(req
, file
);
946 pg_show(struct req
*req
, const char *fullpath
)
951 if ((file
= strchr(fullpath
, '/')) == NULL
) {
953 "You did not specify a page to show.");
956 manpath
= mandoc_strndup(fullpath
, file
- fullpath
);
959 if ( ! validate_manpath(req
, manpath
)) {
961 "You specified an invalid manpath.");
967 * Begin by chdir()ing into the manpath.
968 * This way we can pick up the database files, which are
969 * relative to the manpath root.
972 if (chdir(manpath
) == -1) {
973 warn("chdir %s", manpath
);
980 if ( ! validate_filename(file
)) {
982 "You specified an invalid manual file.");
986 resp_begin_html(200, NULL
, file
);
987 resp_searchform(req
, FOCUS_NONE
);
988 resp_show(req
, file
);
993 pg_search(const struct req
*req
)
995 struct mansearch search
;
996 struct manpaths paths
;
999 char *query
, *rp
, *wp
;
1004 * Begin by chdir()ing into the root of the manpath.
1005 * This way we can pick up the database files, which are
1006 * relative to the manpath root.
1009 if (chdir(req
->q
.manpath
) == -1) {
1010 warn("chdir %s", req
->q
.manpath
);
1011 pg_error_internal();
1015 search
.arch
= req
->q
.arch
;
1016 search
.sec
= req
->q
.sec
;
1017 search
.outkey
= "Nd";
1018 search
.argmode
= req
->q
.equal
? ARG_NAME
: ARG_EXPR
;
1019 search
.firstmatch
= 1;
1022 paths
.paths
= mandoc_malloc(sizeof(char *));
1023 paths
.paths
[0] = mandoc_strdup(".");
1026 * Break apart at spaces with backslash-escaping.
1031 rp
= query
= mandoc_strdup(req
->q
.query
);
1033 while (isspace((unsigned char)*rp
))
1037 argv
= mandoc_reallocarray(argv
, argc
+ 1, sizeof(char *));
1038 argv
[argc
++] = wp
= rp
;
1040 if (isspace((unsigned char)*rp
)) {
1045 if (rp
[0] == '\\' && rp
[1] != '\0')
1058 if (req
->isquery
&& req
->q
.equal
&& argc
== 1)
1059 pg_redirect(req
, argv
[0]);
1060 else if (mansearch(&search
, &paths
, argc
, argv
, &res
, &ressz
) == 0)
1061 pg_noresult(req
, 400, "Bad Request",
1062 "You entered an invalid query.");
1063 else if (ressz
== 0)
1064 pg_noresult(req
, 404, "Not Found", "No results found.");
1066 pg_searchres(req
, res
, ressz
);
1069 mansearch_free(res
, ressz
);
1070 free(paths
.paths
[0]);
1078 struct itimerval itimer
;
1080 const char *querystring
;
1085 * The "rpath" pledge could be revoked after mparse_readfd()
1086 * if the file desciptor to "/footer.html" would be opened
1087 * up front, but it's probably not worth the complication
1088 * of the code it would cause: it would require scattering
1089 * pledge() calls in multiple low-level resp_*() functions.
1092 if (pledge("stdio rpath", NULL
) == -1) {
1094 pg_error_internal();
1095 return EXIT_FAILURE
;
1099 /* Poor man's ReDoS mitigation. */
1101 itimer
.it_value
.tv_sec
= 2;
1102 itimer
.it_value
.tv_usec
= 0;
1103 itimer
.it_interval
.tv_sec
= 2;
1104 itimer
.it_interval
.tv_usec
= 0;
1105 if (setitimer(ITIMER_VIRTUAL
, &itimer
, NULL
) == -1) {
1107 pg_error_internal();
1108 return EXIT_FAILURE
;
1112 * First we change directory into the MAN_DIR so that
1113 * subsequent scanning for manpath directories is rooted
1114 * relative to the same position.
1117 if (chdir(MAN_DIR
) == -1) {
1118 warn("MAN_DIR: %s", MAN_DIR
);
1119 pg_error_internal();
1120 return EXIT_FAILURE
;
1123 memset(&req
, 0, sizeof(struct req
));
1125 parse_manpath_conf(&req
);
1127 /* Parse the path info and the query string. */
1129 if ((path
= getenv("PATH_INFO")) == NULL
)
1131 else if (*path
== '/')
1134 if (*path
!= '\0') {
1135 parse_path_info(&req
, path
);
1136 if (req
.q
.manpath
== NULL
|| req
.q
.sec
== NULL
||
1137 *req
.q
.query
== '\0' || access(path
, F_OK
) == -1)
1139 } else if ((querystring
= getenv("QUERY_STRING")) != NULL
)
1140 parse_query_string(&req
, querystring
);
1142 /* Validate parsed data and add defaults. */
1144 if (req
.q
.manpath
== NULL
)
1145 req
.q
.manpath
= mandoc_strdup(req
.p
[0]);
1146 else if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1147 pg_error_badrequest(
1148 "You specified an invalid manpath.");
1149 return EXIT_FAILURE
;
1152 if (req
.q
.arch
!= NULL
&& validate_arch(req
.q
.arch
) == 0) {
1153 pg_error_badrequest(
1154 "You specified an invalid architecture.");
1155 return EXIT_FAILURE
;
1158 /* Dispatch to the three different pages. */
1161 pg_show(&req
, path
);
1162 else if (NULL
!= req
.q
.query
)
1167 free(req
.q
.manpath
);
1171 for (i
= 0; i
< (int)req
.psz
; i
++)
1174 return EXIT_SUCCESS
;
1178 * Translate PATH_INFO to a query.
1181 parse_path_info(struct req
*req
, const char *path
)
1183 const char *name
, *sec
, *end
;
1187 req
->q
.manpath
= NULL
;
1190 /* Mandatory manual page name. */
1191 if ((name
= strrchr(path
, '/')) == NULL
)
1196 /* Optional trailing section. */
1197 sec
= strrchr(name
, '.');
1198 if (sec
!= NULL
&& isdigit((unsigned char)*++sec
)) {
1199 req
->q
.query
= mandoc_strndup(name
, sec
- name
- 1);
1200 req
->q
.sec
= mandoc_strdup(sec
);
1202 req
->q
.query
= mandoc_strdup(name
);
1206 /* Handle the case of name[.section] only. */
1210 /* Optional manpath. */
1211 end
= strchr(path
, '/');
1212 req
->q
.manpath
= mandoc_strndup(path
, end
- path
);
1213 if (validate_manpath(req
, req
->q
.manpath
)) {
1218 free(req
->q
.manpath
);
1219 req
->q
.manpath
= NULL
;
1222 /* Optional section. */
1223 if (strncmp(path
, "man", 3) == 0 || strncmp(path
, "cat", 3) == 0) {
1225 end
= strchr(path
, '/');
1227 req
->q
.sec
= mandoc_strndup(path
, end
- path
);
1233 /* Optional architecture. */
1234 end
= strchr(path
, '/');
1235 if (end
+ 1 != name
) {
1236 pg_error_badrequest(
1237 "You specified too many directory components.");
1240 req
->q
.arch
= mandoc_strndup(path
, end
- path
);
1241 if (validate_arch(req
->q
.arch
) == 0) {
1242 pg_error_badrequest(
1243 "You specified an invalid directory component.");
1249 * Scan for indexable paths.
1252 parse_manpath_conf(struct req
*req
)
1259 if ((fp
= fopen("manpath.conf", "r")) == NULL
) {
1260 warn("%s/manpath.conf", MAN_DIR
);
1261 pg_error_internal();
1268 while ((len
= getline(&dp
, &dpsz
, fp
)) != -1) {
1269 if (dp
[len
- 1] == '\n')
1271 req
->p
= mandoc_realloc(req
->p
,
1272 (req
->psz
+ 1) * sizeof(char *));
1273 if ( ! validate_urifrag(dp
)) {
1274 warnx("%s/manpath.conf contains "
1275 "unsafe path \"%s\"", MAN_DIR
, dp
);
1276 pg_error_internal();
1279 if (strchr(dp
, '/') != NULL
) {
1280 warnx("%s/manpath.conf contains "
1281 "path with slash \"%s\"", MAN_DIR
, dp
);
1282 pg_error_internal();
1285 req
->p
[req
->psz
++] = dp
;
1291 if (req
->p
== NULL
) {
1292 warnx("%s/manpath.conf is empty", MAN_DIR
);
1293 pg_error_internal();