]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.73 2014/07/13 15:38:36 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(const 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 */
82 static const char *httphost
; /* hostname used in the URIs */
84 static const int sec_prios
[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
85 static const char *const sec_numbers
[] = {
86 "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
88 static const char *const sec_names
[] = {
90 "1 - General Commands",
93 "3p - Perl Subroutines",
97 "7 - Macros and Conventions",
98 "8 - Maintenance Commands",
99 "9 - Kernel Interface"
101 static const int sec_MAX
= sizeof(sec_names
) / sizeof(char *);
103 static const char *const arch_names
[] = {
104 "amd64", "alpha", "armish", "armv7",
105 "aviion", "hppa", "hppa64", "i386",
106 "ia64", "landisk", "loongson", "luna88k",
107 "macppc", "mips64", "octeon", "sgi",
108 "socppc", "solbourne", "sparc", "sparc64",
110 "amiga", "arc", "arm32", "atari",
111 "beagle", "cats", "hp300", "mac68k",
112 "mvme68k", "mvme88k", "mvmeppc", "palm",
113 "pc532", "pegasos", "pmax", "powerpc",
114 "sun3", "wgrisc", "x68k"
116 static const int arch_MAX
= sizeof(arch_names
) / sizeof(char *);
119 * Print a character, escaping HTML along the way.
120 * This will pass non-ASCII straight to output: be warned!
140 putchar((unsigned char)c
);
146 http_printquery(const struct req
*req
)
149 if (NULL
!= req
->q
.manpath
) {
151 http_print(req
->q
.manpath
);
153 if (NULL
!= req
->q
.sec
) {
155 http_print(req
->q
.sec
);
157 if (NULL
!= req
->q
.arch
) {
159 http_print(req
->q
.arch
);
161 if (NULL
!= req
->q
.expr
) {
163 http_print(req
->q
.expr
);
165 if (0 == req
->q
.equal
)
166 printf("&apropos=1");
170 html_printquery(const struct req
*req
)
173 if (NULL
!= req
->q
.manpath
) {
174 printf("&manpath=");
175 html_print(req
->q
.manpath
);
177 if (NULL
!= req
->q
.sec
) {
179 html_print(req
->q
.sec
);
181 if (NULL
!= req
->q
.arch
) {
182 printf("&arch=");
183 html_print(req
->q
.arch
);
185 if (NULL
!= req
->q
.expr
) {
186 printf("&query=");
187 html_print(req
->q
.expr
);
189 if (0 == req
->q
.equal
)
190 printf("&apropos=1");
194 http_print(const char *p
)
204 * Call through to html_putchar().
205 * Accepts NULL strings.
208 html_print(const char *p
)
218 * Parse out key-value pairs from an HTTP request variable.
219 * This can be either a cookie or a POST/GET string, although man.cgi
220 * uses only GET for simplicity.
223 http_parse(struct req
*req
, char *p
)
227 memset(&req
->q
, 0, sizeof(struct query
));
228 req
->q
.manpath
= req
->p
[0];
235 p
+= (int)strcspn(p
, ";&");
238 if (NULL
!= (val
= strchr(key
, '=')))
241 if ('\0' == *key
|| NULL
== val
|| '\0' == *val
)
244 /* Just abort handling. */
246 if ( ! http_decode(key
))
248 if (NULL
!= val
&& ! http_decode(val
))
251 if (0 == strcmp(key
, "query"))
253 else if (0 == strcmp(key
, "manpath")) {
255 if (0 == strncmp(val
, "OpenBSD ", 8)) {
261 req
->q
.manpath
= val
;
262 } else if (0 == strcmp(key
, "apropos"))
263 req
->q
.equal
= !strcmp(val
, "0");
264 else if (0 == strcmp(key
, "sec")) {
265 if (strcmp(val
, "0"))
268 } else if (0 == strcmp(key
, "sektion")) {
269 if (strcmp(val
, "0"))
272 } else if (0 == strcmp(key
, "arch")) {
273 if (strcmp(val
, "default"))
283 if (isalnum((unsigned char)c
)) {
284 putchar((unsigned char)c
);
286 } else if (' ' == c
) {
294 * HTTP-decode a string. The standard explanation is that this turns
295 * "%4e+foo" into "n foo" in the regular way. This is done in-place
296 * over the allocated string.
308 for ( ; '\0' != *p
; p
++, q
++) {
310 if ('\0' == (hex
[0] = *(p
+ 1)))
312 if ('\0' == (hex
[1] = *(p
+ 2)))
314 if (1 != sscanf(hex
, "%x", &c
))
322 *q
= '+' == *p
? ' ' : *p
;
330 resp_begin_http(int code
, const char *msg
)
334 printf("Status: %d %s\r\n", code
, msg
);
336 printf("Content-Type: text/html; charset=utf-8\r\n"
337 "Cache-Control: no-cache\r\n"
338 "Pragma: no-cache\r\n"
345 resp_begin_html(int code
, const char *msg
)
348 resp_begin_http(code
, msg
);
350 printf("<!DOCTYPE HTML PUBLIC "
351 " \"-//W3C//DTD HTML 4.01//EN\""
352 " \"http://www.w3.org/TR/html4/strict.dtd\">\n"
355 "<META HTTP-EQUIV=\"Content-Type\""
356 " CONTENT=\"text/html; charset=utf-8\">\n"
357 "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\""
358 " TYPE=\"text/css\" media=\"all\">\n"
359 "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\""
360 " TYPE=\"text/css\" media=\"all\">\n"
361 "<TITLE>%s</TITLE>\n"
364 "<!-- Begin page content. //-->\n",
365 CSS_DIR
, CSS_DIR
, CUSTOMIZE_TITLE
);
377 resp_searchform(const struct req
*req
)
381 puts(CUSTOMIZE_BEGIN
);
382 puts("<!-- Begin search form. //-->");
383 printf("<DIV ID=\"mancgi\">\n"
384 "<FORM ACTION=\"%s\" METHOD=\"get\">\n"
386 "<LEGEND>Manual Page Search Parameters</LEGEND>\n",
389 /* Write query input box. */
391 printf( "<TABLE><TR><TD>\n"
392 "<INPUT TYPE=\"text\" NAME=\"query\" VALUE=\"");
393 if (NULL
!= req
->q
.expr
)
394 html_print(req
->q
.expr
);
395 puts("\" SIZE=\"40\">");
397 /* Write submission and reset buttons. */
399 printf( "<INPUT TYPE=\"submit\" VALUE=\"Submit\">\n"
400 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n");
402 /* Write show radio button */
404 printf( "</TD><TD>\n"
405 "<INPUT TYPE=\"radio\" ");
408 printf( "NAME=\"apropos\" ID=\"show\" VALUE=\"0\">\n"
409 "<LABEL FOR=\"show\">Show named manual page</LABEL>\n");
411 /* Write section selector. */
413 printf( "</TD></TR><TR><TD>\n"
414 "<SELECT NAME=\"sec\">");
415 for (i
= 0; i
< sec_MAX
; i
++) {
416 printf("<OPTION VALUE=\"%s\"", sec_numbers
[i
]);
417 if (NULL
!= req
->q
.sec
&&
418 0 == strcmp(sec_numbers
[i
], req
->q
.sec
))
420 printf(">%s</OPTION>\n", sec_names
[i
]);
424 /* Write architecture selector. */
426 puts("<SELECT NAME=\"arch\">");
427 for (i
= 0; i
< arch_MAX
; i
++) {
428 printf("<OPTION VALUE=\"%s\"", arch_names
[i
]);
429 if (NULL
!= req
->q
.arch
&&
430 0 == strcmp(arch_names
[i
], req
->q
.arch
))
432 printf(">%s</OPTION>\n", arch_names
[i
]);
436 /* Write manpath selector. */
439 puts("<SELECT NAME=\"manpath\">");
440 for (i
= 0; i
< (int)req
->psz
; i
++) {
442 if (NULL
== req
->q
.manpath
? 0 == i
:
443 0 == strcmp(req
->q
.manpath
, req
->p
[i
]))
446 html_print(req
->p
[i
]);
448 html_print(req
->p
[i
]);
454 /* Write search radio button */
456 printf( "</TD><TD>\n"
457 "<INPUT TYPE=\"radio\" ");
458 if (0 == req
->q
.equal
)
460 printf( "NAME=\"apropos\" ID=\"search\" VALUE=\"1\">\n"
461 "<LABEL FOR=\"search\">Search with apropos query</LABEL>\n");
463 puts("</TD></TR></TABLE>\n"
467 puts("<!-- End search form. //-->");
471 pg_index(const struct req
*req
)
474 resp_begin_html(200, NULL
);
475 resp_searchform(req
);
477 "This web interface is documented in the "
478 "<A HREF=\"%s/mandoc/man8/man.cgi.8\">man.cgi</A> "
480 "<A HREF=\"%s/mandoc/man1/apropos.1\">apropos</A> "
481 "manual explains the query syntax.\n"
483 scriptname
, scriptname
);
488 pg_noresult(const struct req
*req
, const char *msg
)
490 resp_begin_html(200, NULL
);
491 resp_searchform(req
);
499 pg_error_badrequest(const char *msg
)
502 resp_begin_html(400, "Bad Request");
503 puts("<H1>Bad Request</H1>\n"
506 printf("Try again from the\n"
507 "<A HREF=\"%s\">main page</A>.\n"
513 pg_error_internal(void)
515 resp_begin_html(500, "Internal Server Error");
516 puts("<P>Internal Server Error</P>");
521 pg_searchres(const struct req
*req
, struct manpage
*r
, size_t sz
)
523 size_t i
, iuse
, isec
;
529 * If we have just one result, then jump there now
532 printf("Status: 303 See Other\r\n");
533 printf("Location: http://%s%s/%s/%s?",
534 httphost
, scriptname
, req
->q
.manpath
, r
[0].file
);
535 http_printquery(req
);
537 "Content-Type: text/html; charset=utf-8\r\n"
542 qsort(r
, sz
, sizeof(struct manpage
), cmp
);
544 resp_begin_html(200, NULL
);
545 resp_searchform(req
);
546 puts("<DIV CLASS=\"results\">");
549 for (i
= 0; i
< sz
; i
++) {
551 "<TD CLASS=\"title\">\n"
552 "<A HREF=\"%s/%s/%s?",
553 scriptname
, req
->q
.manpath
, r
[i
].file
);
554 html_printquery(req
);
556 html_print(r
[i
].names
);
559 "<TD CLASS=\"desc\">");
560 html_print(r
[i
].output
);
569 * In man(1) mode, show one of the pages
570 * even if more than one is found.
577 for (i
= 0; i
< sz
; i
++) {
578 isec
= strcspn(r
[i
].file
, "123456789");
579 sec
= r
[i
].file
[isec
];
582 prio
= sec_prios
[sec
- '1'];
588 resp_show(req
, r
[iuse
].file
);
595 catman(const struct req
*req
, const char *file
)
603 if (NULL
== (f
= fopen(file
, "r"))) {
604 puts("<P>You specified an invalid manual file.</P>");
608 puts("<DIV CLASS=\"catman\">\n"
611 while (NULL
!= (p
= fgetln(f
, &len
))) {
613 for (i
= 0; i
< (int)len
- 1; i
++) {
615 * This means that the catpage is out of state.
616 * Ignore it and keep going (although the
620 if ('\b' == p
[i
] || '\n' == p
[i
])
624 * Print a regular character.
625 * Close out any bold/italic scopes.
626 * If we're in back-space mode, make sure we'll
627 * have something to enter when we backspace.
630 if ('\b' != p
[i
+ 1]) {
638 } else if (i
+ 2 >= (int)len
)
656 * Handle funny behaviour troff-isms.
657 * These grok'd from the original man2html.c.
660 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
661 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
662 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
663 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
664 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
665 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
666 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
667 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
676 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
677 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
678 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
679 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
680 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
681 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
705 * Clean up the last character.
706 * We can get to a newline; don't print that.
714 if (i
== (int)len
- 1 && '\n' != p
[i
])
727 format(const struct req
*req
, const char *file
)
735 char opts
[PATH_MAX
+ 128];
737 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
738 puts("<P>You specified an invalid manual file.</P>");
742 mp
= mparse_alloc(MPARSE_SO
, MANDOCLEVEL_FATAL
, NULL
,
744 rc
= mparse_readfd(mp
, fd
, file
);
747 if (rc
>= MANDOCLEVEL_FATAL
) {
748 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
749 req
->q
.manpath
, file
);
754 snprintf(opts
, sizeof(opts
),
755 "fragment,man=%s?query=%%N&sec=%%S",
758 mparse_result(mp
, &mdoc
, &man
, NULL
);
759 if (NULL
== man
&& NULL
== mdoc
) {
760 fprintf(stderr
, "fatal mandoc error: %s/%s\n",
761 req
->q
.manpath
, file
);
767 vp
= html_alloc(opts
);
779 resp_show(const struct req
*req
, const char *file
)
781 if ('.' == file
[0] || '/' == file
[1])
791 pg_show(const struct req
*req
, const char *path
)
795 if (NULL
== path
|| NULL
== (sub
= strchr(path
, '/'))) {
797 "You did not specify a page to show.");
803 * Begin by chdir()ing into the manpath.
804 * This way we can pick up the database files, which are
805 * relative to the manpath root.
808 if (-1 == chdir(path
)) {
810 "You specified an invalid manpath.");
814 resp_begin_html(200, NULL
);
815 resp_searchform(req
);
821 pg_search(const struct req
*req
)
823 struct mansearch search
;
824 struct manpaths paths
;
827 const char *ep
, *start
;
832 * Begin by chdir()ing into the root of the manpath.
833 * This way we can pick up the database files, which are
834 * relative to the manpath root.
837 if (-1 == (chdir(req
->q
.manpath
))) {
839 "You specified an invalid manpath.");
843 search
.arch
= req
->q
.arch
;
844 search
.sec
= req
->q
.sec
;
845 search
.deftype
= req
->q
.equal
? TYPE_Nm
: (TYPE_Nm
| TYPE_Nd
);
846 search
.flags
= req
->q
.equal
? MANSEARCH_MAN
: 0;
849 paths
.paths
= mandoc_malloc(sizeof(char *));
850 paths
.paths
[0] = mandoc_strdup(".");
853 * Poor man's tokenisation: just break apart by spaces.
854 * Yes, this is half-ass. But it works for now.
858 while (ep
&& isspace((unsigned char)*ep
))
863 while (ep
&& '\0' != *ep
) {
864 cp
= mandoc_reallocarray(cp
, sz
+ 1, sizeof(char *));
866 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
868 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
869 memcpy(cp
[sz
], start
, ep
- start
);
870 cp
[sz
++][ep
- start
] = '\0';
871 while (isspace((unsigned char)*ep
))
875 if (0 == mansearch(&search
, &paths
, sz
, cp
, "Nd", &res
, &ressz
))
876 pg_noresult(req
, "You entered an invalid query.");
878 pg_noresult(req
, "No results found.");
880 pg_searchres(req
, res
, ressz
);
882 for (i
= 0; i
< sz
; i
++)
886 for (i
= 0; i
< (int)ressz
; i
++) {
893 free(paths
.paths
[0]);
905 /* Scan our run-time environment. */
907 if (NULL
== (scriptname
= getenv("SCRIPT_NAME")))
910 if (NULL
== (httphost
= getenv("HTTP_HOST")))
911 httphost
= "localhost";
914 * First we change directory into the MAN_DIR so that
915 * subsequent scanning for manpath directories is rooted
916 * relative to the same position.
919 if (-1 == chdir(MAN_DIR
)) {
920 fprintf(stderr
, "MAN_DIR: %s: %s\n",
921 MAN_DIR
, strerror(errno
));
923 return(EXIT_FAILURE
);
926 memset(&req
, 0, sizeof(struct req
));
929 /* Next parse out the query string. */
931 if (NULL
!= (querystring
= getenv("QUERY_STRING")))
932 http_parse(&req
, querystring
);
934 /* Dispatch to the three different pages. */
936 path
= getenv("PATH_INFO");
939 else if ('/' == *path
)
944 else if (NULL
!= req
.q
.expr
)
949 for (i
= 0; i
< (int)req
.psz
; i
++)
952 return(EXIT_SUCCESS
);
956 cmp(const void *p1
, const void *p2
)
959 return(strcasecmp(((const struct manpage
*)p1
)->names
,
960 ((const struct manpage
*)p2
)->names
));
964 * Scan for indexable paths.
967 pathgen(struct req
*req
)
973 if (NULL
== (fp
= fopen("manpath.conf", "r")))
976 while (NULL
!= (dp
= fgetln(fp
, &dpsz
))) {
977 if ('\n' == dp
[dpsz
- 1])
979 req
->p
= mandoc_realloc(req
->p
,
980 (req
->psz
+ 1) * sizeof(char *));
981 req
->p
[req
->psz
++] = mandoc_strndup(dp
, dpsz
);