]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.80 2014/07/22 18:14:13 schwarze Exp $ */
3 * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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.
32 #include "mandoc_aux.h"
35 #include "mansearch.h"
39 * A query as passed to the search function.
42 const char *manpath
; /* desired manual directory */
43 const char *arch
; /* architecture */
44 const char *sec
; /* manual section */
45 const char *expr
; /* unparsed expression string */
46 int equal
; /* match whole names, not substrings */
51 char **p
; /* array of available manpaths */
52 size_t psz
; /* number of available manpaths */
55 static void catman(const struct req
*, const char *);
56 static int cmp(const void *, const void *);
57 static void format(const struct req
*, const char *);
58 static void html_print(const char *);
59 static void html_printquery(const struct req
*);
60 static void html_putchar(char);
61 static int http_decode(char *);
62 static void http_parse(struct req
*, char *);
63 static void http_print(const char *);
64 static void http_putchar(char);
65 static void http_printquery(const struct req
*);
66 static void pathgen(struct req
*);
67 static void pg_error_badrequest(const char *);
68 static void pg_error_internal(void);
69 static void pg_index(const struct req
*);
70 static void pg_noresult(const struct req
*, const char *);
71 static void pg_search(const struct req
*);
72 static void pg_searchres(const struct req
*,
73 struct manpage
*, size_t);
74 static void pg_show(struct req
*, const char *);
75 static void resp_begin_html(int, const char *);
76 static void resp_begin_http(int, const char *);
77 static void resp_end_html(void);
78 static void resp_searchform(const struct req
*);
79 static void resp_show(const struct req
*, const char *);
81 static const char *scriptname
; /* CGI script name */
83 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
84 static const char *const sec_numbers
[] = {
85 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
87 static const char *const sec_names
[] = {
89 "1 - General Commands",
92 "3p - Perl Subroutines",
96 "7 - Macros and Conventions",
97 "8 - Maintenance Commands",
98 "9 - Kernel Interface"
100 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
102 static const char *const arch_names
[] = {
103 "amd64", "alpha", "armish", "armv7",
104 "aviion", "hppa", "hppa64", "i386",
105 "ia64", "landisk", "loongson", "luna88k",
106 "macppc", "mips64", "octeon", "sgi",
107 "socppc", "solbourne", "sparc", "sparc64",
109 "amiga", "arc", "arm32", "atari",
110 "beagle", "cats", "hp300", "mac68k",
111 "mvme68k", "mvme88k", "mvmeppc", "palm",
112 "pc532", "pegasos", "pmax", "powerpc",
113 "sun3", "wgrisc", "x68k"
115 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
118 * Print a character, escaping HTML along the way.
119 * This will pass non-ASCII straight to output: be warned!
139 putchar((unsigned char)c
);
145 http_printquery(const struct req
*req
)
148 if (NULL
!= req
->q
.manpath
) {
150 http_print(req
->q
.manpath
);
152 if (NULL
!= req
->q
.sec
) {
154 http_print(req
->q
.sec
);
156 if (NULL
!= req
->q
.arch
) {
158 http_print(req
->q
.arch
);
160 if (NULL
!= req
->q
.expr
) {
162 http_print(req
->q
.expr
);
164 if (0 == req
->q
.equal
)
165 printf("&apropos=1");
169 html_printquery(const struct req
*req
)
172 if (NULL
!= req
->q
.manpath
) {
173 printf("&manpath=");
174 html_print(req
->q
.manpath
);
176 if (NULL
!= req
->q
.sec
) {
178 html_print(req
->q
.sec
);
180 if (NULL
!= req
->q
.arch
) {
181 printf("&arch=");
182 html_print(req
->q
.arch
);
184 if (NULL
!= req
->q
.expr
) {
185 printf("&query=");
186 html_print(req
->q
.expr
);
188 if (0 == req
->q
.equal
)
189 printf("&apropos=1");
193 http_print(const char *p
)
203 * Call through to html_putchar().
204 * Accepts NULL strings.
207 html_print(const char *p
)
217 * Parse out key-value pairs from an HTTP request variable.
218 * This can be either a cookie or a POST/GET string, although man.cgi
219 * uses only GET for simplicity.
222 http_parse(struct req
*req
, char *p
)
226 memset(&req
->q
, 0, sizeof(struct query
));
227 req
->q
.manpath
= req
->p
[0];
234 p
+= (int)strcspn(p
, ";&");
237 if (NULL
!= (val
= strchr(key
, '=')))
240 if ('\0' == *key
|| NULL
== val
|| '\0' == *val
)
243 /* Just abort handling. */
245 if ( ! http_decode(key
))
247 if (NULL
!= val
&& ! http_decode(val
))
250 if (0 == strcmp(key
, "query"))
252 else if (0 == strcmp(key
, "manpath")) {
254 if (0 == strncmp(val
, "OpenBSD ", 8)) {
260 req
->q
.manpath
= val
;
261 } else if (0 == strcmp(key
, "apropos"))
262 req
->q
.equal
= !strcmp(val
, "0");
263 else if (0 == strcmp(key
, "sec")) {
264 if (strcmp(val
, "0"))
267 } else if (0 == strcmp(key
, "sektion")) {
268 if (strcmp(val
, "0"))
271 } else if (0 == strcmp(key
, "arch")) {
272 if (strcmp(val
, "default"))
282 if (isalnum((unsigned char)c
)) {
283 putchar((unsigned char)c
);
285 } else if (' ' == c
) {
293 * HTTP-decode a string. The standard explanation is that this turns
294 * "%4e+foo" into "n foo" in the regular way. This is done in-place
295 * over the allocated string.
307 for ( ; '\0' != *p
; p
++, q
++) {
309 if ('\0' == (hex
[0] = *(p
+ 1)))
311 if ('\0' == (hex
[1] = *(p
+ 2)))
313 if (1 != sscanf(hex
, "%x", &c
))
321 *q
= '+' == *p
? ' ' : *p
;
329 resp_begin_http(int code
, const char *msg
)
333 printf("Status: %d %s\r\n", code
, msg
);
335 printf("Content-Type: text/html; charset=utf-8\r\n"
336 "Cache-Control: no-cache\r\n"
337 "Pragma: no-cache\r\n"
344 resp_begin_html(int code
, const char *msg
)
347 resp_begin_http(code
, msg
);
349 printf("<!DOCTYPE HTML PUBLIC "
350 " \"-//W3C//DTD HTML 4.01//EN\""
351 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
354 "<META HTTP-EQUIV=\"Content-Type\""
355 " CONTENT=\"text/html; charset=utf-8\">\n"
356 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
357 " TYPE=\"text/css\" media=\"all\">\n"
358 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
359 " TYPE=\"text/css\" media=\"all\">\n"
360 "<TITLE>%s</TITLE>\n"
363 "<!-- Begin page content. //-->\n",
364 CSS_DIR
, CSS_DIR
, CUSTOMIZE_TITLE
);
376 resp_searchform(const struct req
*req
)
380 puts(CUSTOMIZE_BEGIN
);
381 puts("<!-- Begin search form. //-->");
382 printf("<DIV ID=\"mancgi\">\n"
383 "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
385 "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
388 /* Write query input box. */
390 printf( "<TABLE><TR><TD>\n"
391 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
392 if (NULL
!= req
->q
.expr
)
393 html_print(req
->q
.expr
);
394 puts("\" SIZE=\"40\">");
396 /* Write submission and reset buttons. */
398 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
399 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
401 /* Write show radio button */
403 printf( "</TD><TD>\n"
404 "<INPUT TYPE=\"radio\" ");
407 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
408 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
410 /* Write section selector. */
412 printf( "</TD></TR><TR><TD>\n"
413 "<SELECT NAME=\"sec\">");
414 for (i
= 0; i
< sec_MAX
; i
++) {
415 printf("<OPTION VALUE=\"%s\"", sec_numbers
[i
]);
416 if (NULL
!= req
->q
.sec
&&
417 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
419 printf(">%s</OPTION>\n", sec_names
[i
]);
423 /* Write architecture selector. */
425 puts("<SELECT NAME=\"arch\">");
426 for (i
= 0; i
< arch_MAX
; i
++) {
427 printf("<OPTION VALUE=\"%s\"", arch_names
[i
]);
428 if (NULL
!= req
->q
.arch
&&
429 0 == strcmp(arch_names
[i
], req
->q
.arch
))
431 printf(">%s</OPTION>\n", arch_names
[i
]);
435 /* Write manpath selector. */
438 puts("<SELECT NAME=\"manpath\">");
439 for (i
= 0; i
< (int)req
->psz
; i
++) {
441 if (NULL
== req
->q
.manpath
? 0 == i
:
442 0 == strcmp(req
->q
.manpath
, req
->p
[i
]))
445 html_print(req
->p
[i
]);
447 html_print(req
->p
[i
]);
453 /* Write search radio button */
455 printf( "</TD><TD>\n"
456 "<INPUT TYPE=\"radio\" ");
457 if (0 == req
->q
.equal
)
459 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
460 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
462 puts("</TD></TR></TABLE>\n"
466 puts("<!-- End search form. //-->");
470 validate_urifrag(const char *frag
)
473 while ('\0' != *frag
) {
474 if ( ! (isalnum((unsigned char)*frag
) ||
475 '-' == *frag
|| '.' == *frag
||
476 '/' == *frag
|| '_' == *frag
))
484 validate_manpath(const struct req
*req
, const char* manpath
)
488 if ( ! strcmp(manpath
, "mandoc"))
491 for (i
= 0; i
< req
->psz
; i
++)
492 if ( ! strcmp(manpath
, req
->p
[i
]))
499 validate_filename(const char *file
)
502 if ('.' == file
[0] && '/' == file
[1])
505 return ( ! (strstr(file
, "../") || strstr(file
, "/..") ||
506 (strncmp(file
, "man", 3) && strncmp(file
, "cat", 3))));
510 pg_index(const struct req
*req
)
513 resp_begin_html(200, NULL
);
514 resp_searchform(req
);
516 "This web interface is documented in the "
517 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
519 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
520 "manual explains the query syntax.\n"
522 scriptname
, scriptname
);
527 pg_noresult(const struct req
*req
, const char *msg
)
529 resp_begin_html(200, NULL
);
530 resp_searchform(req
);
538 pg_error_badrequest(const char *msg
)
541 resp_begin_html(400, "Bad Request");
542 puts("<H1>Bad Request</H1>\n"
545 printf("Try again from the\n"
546 "<A HREF=\"%s\">main page</A>.\n"
552 pg_error_internal(void)
554 resp_begin_html(500, "Internal Server Error");
555 puts("<P>Internal Server Error</P>");
560 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
562 size_t i
, iuse
, isec
;
566 for (i
= 0; i
< sz
; i
++) {
567 if (validate_filename(r
[i
].file
))
569 fprintf(stderr
, "invalid filename %s in %s database\n",
570 r
[i
].file
, req
->q
.manpath
);
577 * If we have just one result, then jump there now
580 printf("Status: 303 See Other\r\n");
581 printf("Location: http://%s%s/%s/%s?",
582 HTTP_HOST
, scriptname
, req
->q
.manpath
, r
[0].file
);
583 http_printquery(req
);
585 "Content-Type: text/html; charset=utf-8\r\n"
590 qsort(r
, sz
, sizeof(struct manpage
), cmp
);
592 resp_begin_html(200, NULL
);
593 resp_searchform(req
);
594 puts("<DIV CLASS=\"results\">");
597 for (i
= 0; i
< sz
; i
++) {
599 "<TD CLASS=\"title\">\n"
600 "<A HREF=\"%s/%s/%s?",
601 scriptname
, req
->q
.manpath
, r
[i
].file
);
602 html_printquery(req
);
604 html_print(r
[i
].names
);
607 "<TD CLASS=\"desc\">");
608 html_print(r
[i
].output
);
617 * In man(1) mode, show one of the pages
618 * even if more than one is found.
625 for (i
= 0; i
< sz
; i
++) {
626 isec
= strcspn(r
[i
].file
, "123456789");
627 sec
= r
[i
].file
[isec
];
630 prio
= sec_prios
[sec
- '1'];
636 resp_show(req
, r
[iuse
].file
);
643 catman(const struct req
*req
, const char *file
)
651 if (NULL
== (f
= fopen(file
, "r"))) {
652 puts("<P>You specified an invalid manual file.</P>");
656 puts("<DIV CLASS=\"catman\">\n"
659 while (NULL
!= (p
= fgetln(f
, &len
))) {
661 for (i
= 0; i
< (int)len
- 1; i
++) {
663 * This means that the catpage is out of state.
664 * Ignore it and keep going (although the
668 if ('\b' == p
[i
] || '\n' == p
[i
])
672 * Print a regular character.
673 * Close out any bold/italic scopes.
674 * If we're in back-space mode, make sure we'll
675 * have something to enter when we backspace.
678 if ('\b' != p
[i
+ 1]) {
686 } else if (i
+ 2 >= (int)len
)
704 * Handle funny behaviour troff-isms.
705 * These grok'd from the original man2html.c.
708 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
709 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
710 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
711 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
712 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
713 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
714 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
715 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
724 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
725 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
726 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
727 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
728 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
729 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
753 * Clean up the last character.
754 * We can get to a newline; don't print that.
762 if (i
== (int)len
- 1 && '\n' != p
[i
])
775 format(const struct req
*req
, const char *file
)
783 char opts
[PATH_MAX
+ 128];
785 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
786 puts("<P>You specified an invalid manual file.</P>");
790 mp
= mparse_alloc(MPARSE_SO
, MANDOCLEVEL_FATAL
, NULL
,
792 rc
= mparse_readfd(mp
, fd
, file
);
795 if (rc
>= MANDOCLEVEL_FATAL
) {
796 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
797 req
->q
.manpath
, file
);
802 snprintf(opts
, sizeof(opts
), "fragment,man=%s?"
803 "manpath=%s&query=%%N&sec=%%S&arch=%s",
804 scriptname
, req
->q
.manpath
,
805 req
->q
.arch
? req
->q
.arch
: "");
807 mparse_result(mp
, &mdoc
, &man
, NULL
);
808 if (NULL
== man
&& NULL
== mdoc
) {
809 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
810 req
->q
.manpath
, file
);
816 vp
= html_alloc(opts
);
828 resp_show(const struct req
*req
, const char *file
)
831 if ('.' == file
[0] && '/' == file
[1])
841 pg_show(struct req
*req
, const char *path
)
845 if (NULL
== path
|| NULL
== (sub
= strchr(path
, '/'))) {
847 "You did not specify a page to show.");
852 if ( ! validate_manpath(req
, path
)) {
854 "You specified an invalid manpath.");
859 * Begin by chdir()ing into the manpath.
860 * This way we can pick up the database files, which are
861 * relative to the manpath root.
864 if (-1 == chdir(path
)) {
865 fprintf(stderr
, "chdir %s: %s\n",
866 path
, strerror(errno
));
871 if ( ! validate_filename(sub
)) {
873 "You specified an invalid manual file.");
877 if (strcmp(path
, "mandoc"))
878 req
->q
.manpath
= path
;
880 resp_begin_html(200, NULL
);
881 resp_searchform(req
);
887 pg_search(const struct req
*req
)
889 struct mansearch search
;
890 struct manpaths paths
;
893 const char *ep
, *start
;
898 * Begin by chdir()ing into the root of the manpath.
899 * This way we can pick up the database files, which are
900 * relative to the manpath root.
903 if (-1 == (chdir(req
->q
.manpath
))) {
904 fprintf(stderr
, "chdir %s: %s\n",
905 req
->q
.manpath
, strerror(errno
));
910 search
.arch
= req
->q
.arch
;
911 search
.sec
= req
->q
.sec
;
912 search
.deftype
= req
->q
.equal
? TYPE_Nm
: (TYPE_Nm
| TYPE_Nd
);
913 search
.flags
= req
->q
.equal
? MANSEARCH_MAN
: 0;
916 paths
.paths
= mandoc_malloc(sizeof(char *));
917 paths
.paths
[0] = mandoc_strdup(".");
920 * Poor man's tokenisation: just break apart by spaces.
921 * Yes, this is half-ass. But it works for now.
925 while (ep
&& isspace((unsigned char)*ep
))
930 while (ep
&& '\0' != *ep
) {
931 cp
= mandoc_reallocarray(cp
, sz
+ 1, sizeof(char *));
933 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
935 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
936 memcpy(cp
[sz
], start
, ep
- start
);
937 cp
[sz
++][ep
- start
] = '\0';
938 while (isspace((unsigned char)*ep
))
942 if (0 == mansearch(&search
, &paths
, sz
, cp
, "Nd", &res
, &ressz
))
943 pg_noresult(req
, "You entered an invalid query.");
945 pg_noresult(req
, "No results found.");
947 pg_searchres(req
, res
, ressz
);
949 for (i
= 0; i
< sz
; i
++)
953 for (i
= 0; i
< (int)ressz
; i
++) {
960 free(paths
.paths
[0]);
972 /* Scan our run-time environment. */
974 if (NULL
== (scriptname
= getenv("SCRIPT_NAME")))
977 if ( ! validate_urifrag(scriptname
)) {
978 fprintf(stderr
, "unsafe SCRIPT_NAME \"%s\"\n",
981 return(EXIT_FAILURE
);
985 * First we change directory into the MAN_DIR so that
986 * subsequent scanning for manpath directories is rooted
987 * relative to the same position.
990 if (-1 == chdir(MAN_DIR
)) {
991 fprintf(stderr
, "MAN_DIR: %s: %s\n",
992 MAN_DIR
, strerror(errno
));
994 return(EXIT_FAILURE
);
997 memset(&req
, 0, sizeof(struct req
));
1000 /* Next parse out the query string. */
1002 if (NULL
!= (querystring
= getenv("QUERY_STRING")))
1003 http_parse(&req
, querystring
);
1005 if ( ! validate_manpath(&req
, req
.q
.manpath
)) {
1006 pg_error_badrequest(
1007 "You specified an invalid manpath.");
1008 return(EXIT_FAILURE
);
1011 if ( ! (NULL
== req
.q
.arch
|| validate_urifrag(req
.q
.arch
))) {
1012 pg_error_badrequest(
1013 "You specified an invalid architecture.");
1014 return(EXIT_FAILURE
);
1017 /* Dispatch to the three different pages. */
1019 path
= getenv("PATH_INFO");
1022 else if ('/' == *path
)
1026 pg_show(&req
, path
);
1027 else if (NULL
!= req
.q
.expr
)
1032 for (i
= 0; i
< (int)req
.psz
; i
++)
1035 return(EXIT_SUCCESS
);
1039 cmp(const void *p1
, const void *p2
)
1042 return(strcasecmp(((const struct manpage
*)p1
)->names
,
1043 ((const struct manpage
*)p2
)->names
));
1047 * Scan for indexable paths.
1050 pathgen(struct req
*req
)
1056 if (NULL
== (fp
= fopen("manpath.conf", "r"))) {
1057 fprintf(stderr
, "%s/manpath.conf: %s\n",
1058 MAN_DIR
, strerror(errno
));
1059 pg_error_internal();
1063 while (NULL
!= (dp
= fgetln(fp
, &dpsz
))) {
1064 if ('\n' == dp
[dpsz
- 1])
1066 req
->p
= mandoc_realloc(req
->p
,
1067 (req
->psz
+ 1) * sizeof(char *));
1068 dp
= mandoc_strndup(dp
, dpsz
);
1069 if ( ! validate_urifrag(dp
)) {
1070 fprintf(stderr
, "%s/manpath.conf contains "
1071 "unsafe path \"%s\"\n", MAN_DIR
, dp
);
1072 pg_error_internal();
1075 if (NULL
!= strchr(dp
, '/')) {
1076 fprintf(stderr
, "%s/manpath.conf contains "
1077 "path with slash \"%s\"\n", MAN_DIR
, dp
);
1078 pg_error_internal();
1081 req
->p
[req
->psz
++] = dp
;
1084 if ( req
->p
== NULL
) {
1085 fprintf(stderr
, "%s/manpath.conf is empty\n", MAN_DIR
);
1086 pg_error_internal();