]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.16 2011/12/07 16:18:52 kristaps Exp $ */
3 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #include <sys/param.h>
37 #include "apropos_db.h"
68 static int atou(const char *, unsigned *);
69 static void catman(const char *);
70 static int cmp(const void *, const void *);
71 static void format(const char *);
72 static void html_print(const char *);
73 static void html_putchar(char);
74 static int kval_decode(char *);
75 static void kval_parse(struct kval
**, size_t *, char *);
76 static void kval_free(struct kval
*, size_t);
77 static void pg_index(const struct manpaths
*,
78 const struct req
*, char *);
79 static void pg_search(const struct manpaths
*,
80 const struct req
*, char *);
81 static void pg_show(const struct manpaths
*,
82 const struct req
*, char *);
83 static void resp_bad(void);
84 static void resp_baddb(void);
85 static void resp_error400(void);
86 static void resp_error404(const char *);
87 static void resp_begin_html(int, const char *);
88 static void resp_begin_http(int, const char *);
89 static void resp_end_html(void);
90 static void resp_index(const struct req
*);
91 static void resp_search(struct res
*, size_t, void *);
92 static void resp_searchform(const struct req
*);
94 static const char *progname
;
95 static const char *cache
;
96 static const char *host
;
98 static const char * const pages
[PAGE__MAX
] = {
99 "index", /* PAGE_INDEX */
100 "search", /* PAGE_SEARCH */
101 "show", /* PAGE_SHOW */
105 * This is just OpenBSD's strtol(3) suggestion.
106 * I use it instead of strtonum(3) for portability's sake.
109 atou(const char *buf
, unsigned *v
)
115 lval
= strtol(buf
, &ep
, 10);
116 if (buf
[0] == '\0' || *ep
!= '\0')
118 if ((errno
== ERANGE
&& (lval
== LONG_MAX
||
119 lval
== LONG_MIN
)) ||
120 (lval
> UINT_MAX
|| lval
< 0))
123 *v
= (unsigned int)lval
;
145 putchar((unsigned char)c
);
151 * Print a word, escaping HTML along the way.
152 * This will pass non-ASCII straight to output: be warned!
155 html_print(const char *p
)
165 kval_free(struct kval
*p
, size_t sz
)
169 for (i
= 0; i
< (int)sz
; i
++) {
177 * Parse out key-value pairs from an HTTP request variable.
178 * This can be either a cookie or a POST/GET string, although man.cgi
179 * uses only GET for simplicity.
182 kval_parse(struct kval
**kv
, size_t *kvsz
, char *p
)
189 while (p
&& '\0' != *p
) {
196 if (NULL
!= (p
= strchr(p
, '='))) {
200 sz
= strcspn(p
, ";&");
208 sz
= strcspn(p
, ";&");
217 if ('\0' == *key
|| '\0' == *val
)
220 /* Just abort handling. */
222 if ( ! kval_decode(key
))
224 if ( ! kval_decode(val
))
227 if (*kvsz
+ 1 >= cur
) {
230 (*kv
, cur
* sizeof(struct kval
));
233 (*kv
)[(int)*kvsz
].key
= mandoc_strdup(key
);
234 (*kv
)[(int)*kvsz
].val
= mandoc_strdup(val
);
240 * HTTP-decode a string. The standard explanation is that this turns
241 * "%4e+foo" into "n foo" in the regular way. This is done in-place
242 * over the allocated string.
252 for ( ; '\0' != *p
; p
++) {
254 if ('\0' == (hex
[0] = *(p
+ 1)))
256 if ('\0' == (hex
[1] = *(p
+ 2)))
258 if (1 != sscanf(hex
, "%x", &c
))
264 memmove(p
+ 1, p
+ 3, strlen(p
+ 3) + 1);
266 *p
= '+' == *p
? ' ' : *p
;
274 resp_begin_http(int code
, const char *msg
)
278 printf("Status: %d %s\n", code
, msg
);
280 puts("Content-Type: text/html; charset=utf-8" "\n"
281 "Cache-Control: no-cache" "\n"
282 "Pragma: no-cache" "\n"
289 resp_begin_html(int code
, const char *msg
)
292 resp_begin_http(code
, msg
);
294 puts("<!DOCTYPE HTML PUBLIC " "\n"
295 " \"-//W3C//DTD HTML 4.01//EN\"" "\n"
296 " \"http://www.w3.org/TR/html4/strict.dtd\">" "\n"
299 " <META HTTP-EQUIV=\"Content-Type\" " "\n"
300 " CONTENT=\"text/html; charset=utf-8\">" "\n"
301 " <LINK REL=\"stylesheet\" HREF=\"/man.cgi.css\"" "\n"
302 " TYPE=\"text/css\" media=\"all\">" "\n"
303 " <TITLE>System Manpage Reference</TITLE>" "\n"
306 "<!-- Begin page content. //-->");
313 puts(" </BODY>\n</HTML>");
317 resp_searchform(const struct req
*req
)
320 const char *expr
, *sec
, *arch
;
322 expr
= sec
= arch
= "";
324 for (i
= 0; i
< (int)req
->fieldsz
; i
++)
325 if (0 == strcmp(req
->fields
[i
].key
, "expr"))
326 expr
= req
->fields
[i
].val
;
327 else if (0 == strcmp(req
->fields
[i
].key
, "query"))
328 expr
= req
->fields
[i
].val
;
329 else if (0 == strcmp(req
->fields
[i
].key
, "sec"))
330 sec
= req
->fields
[i
].val
;
331 else if (0 == strcmp(req
->fields
[i
].key
, "sektion"))
332 sec
= req
->fields
[i
].val
;
333 else if (0 == strcmp(req
->fields
[i
].key
, "arch"))
334 arch
= req
->fields
[i
].val
;
336 if (NULL
!= sec
&& 0 == strcmp(sec
, "0"))
339 puts("<!-- Begin search form. //-->");
340 printf("<FORM ACTION=\"");
341 html_print(progname
);
342 printf("/search.html\" METHOD=\"get\">\n");
343 printf("<FIELDSET>\n"
344 "<LEGEND>Search Parameters</LEGEND>\n"
345 "<INPUT TYPE=\"submit\" NAME=\"op\" "
346 "VALUE=\"Whatis\"> or \n"
347 "<INPUT TYPE=\"submit\" NAME=\"op\" "
348 "VALUE=\"apropos\"> for manuals satisfying \n"
349 "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"");
351 printf("\">, section "
352 "<INPUT TYPE=\"text\" "
353 "SIZE=\"4\" NAME=\"sec\" VALUE=\"");
356 "<INPUT TYPE=\"text\" "
357 "SIZE=\"8\" NAME=\"arch\" VALUE=\"");
360 "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n"
363 "<!-- End search form. //-->");
367 resp_index(const struct req
*req
)
370 resp_begin_html(200, NULL
);
371 resp_searchform(req
);
379 resp_begin_html(400, "Query Malformed");
380 printf("<H1>Malformed Query</H1>\n"
382 " The query your entered was malformed.\n"
383 " Try again from the\n"
384 " <A HREF=\"%s/index.html\">main page</A>\n"
390 resp_error404(const char *page
)
393 resp_begin_html(404, "Not Found");
394 puts("<H1>Page Not Found</H1>\n"
396 " The page you're looking for, ");
400 " could not be found.\n"
401 " Try searching from the\n"
402 " <A HREF=\"%s/index.html\">main page</A>\n"
410 resp_begin_html(500, "Internal Server Error");
411 puts("<P>Generic badness happened.</P>");
419 resp_begin_html(500, "Internal Server Error");
420 puts("<P>Your database is broken.</P>");
425 resp_search(struct res
*r
, size_t sz
, void *arg
)
431 * If we have just one result, then jump there now
434 puts("Status: 303 See Other");
435 printf("Location: http://%s%s/show/%u/%u.html\n",
437 r
[0].volume
, r
[0].rec
);
438 puts("Content-Type: text/html; charset=utf-8\n");
442 qsort(r
, sz
, sizeof(struct res
), cmp
);
444 resp_begin_html(200, NULL
);
445 resp_searchform((const struct req
*)arg
);
448 puts("<P>No results found.</P>");
456 for (i
= 0; i
< (int)sz
; i
++) {
457 printf("<TR><TD CLASS=\"title\"><A HREF=\"");
458 html_print(progname
);
459 printf("/show/%u/%u.html\">", r
[i
].volume
, r
[i
].rec
);
460 html_print(r
[i
].title
);
462 html_print(r
[i
].cat
);
463 if (r
[i
].arch
&& '\0' != *r
[i
].arch
) {
465 html_print(r
[i
].arch
);
467 printf(")</A></TD><TD CLASS=\"desc\">");
468 html_print(r
[i
].desc
);
479 pg_index(const struct manpaths
*ps
, const struct req
*req
, char *path
)
486 catman(const char *file
)
494 if (NULL
== (f
= fopen(file
, "r"))) {
499 resp_begin_http(200, NULL
);
500 puts("<!DOCTYPE HTML PUBLIC " "\n"
501 " \"-//W3C//DTD HTML 4.01//EN\"" "\n"
502 " \"http://www.w3.org/TR/html4/strict.dtd\">" "\n"
505 " <META HTTP-EQUIV=\"Content-Type\" " "\n"
506 " CONTENT=\"text/html; charset=utf-8\">" "\n"
507 " <LINK REL=\"stylesheet\" HREF=\"/catman.css\"" "\n"
508 " TYPE=\"text/css\" media=\"all\">" "\n"
509 " <TITLE>System Manpage Reference</TITLE>" "\n"
512 "<!-- Begin page content. //-->");
515 while (NULL
!= (p
= fgetln(f
, &len
))) {
517 for (i
= 0; i
< (int)len
- 1; i
++) {
519 * This means that the catpage is out of state.
520 * Ignore it and keep going (although the
524 if ('\b' == p
[i
] || '\n' == p
[i
])
528 * Print a regular character.
529 * Close out any bold/italic scopes.
530 * If we're in back-space mode, make sure we'll
531 * have something to enter when we backspace.
534 if ('\b' != p
[i
+ 1]) {
542 } else if (i
+ 2 >= (int)len
)
560 * Handle funny behaviour troff-isms.
561 * These grok'd from the original man2html.c.
564 if (('+' == p
[i
] && 'o' == p
[i
+ 2]) ||
565 ('o' == p
[i
] && '+' == p
[i
+ 2]) ||
566 ('|' == p
[i
] && '=' == p
[i
+ 2]) ||
567 ('=' == p
[i
] && '|' == p
[i
+ 2]) ||
568 ('*' == p
[i
] && '=' == p
[i
+ 2]) ||
569 ('=' == p
[i
] && '*' == p
[i
+ 2]) ||
570 ('*' == p
[i
] && '|' == p
[i
+ 2]) ||
571 ('|' == p
[i
] && '*' == p
[i
+ 2])) {
580 } else if (('|' == p
[i
] && '-' == p
[i
+ 2]) ||
581 ('-' == p
[i
] && '|' == p
[i
+ 1]) ||
582 ('+' == p
[i
] && '-' == p
[i
+ 1]) ||
583 ('-' == p
[i
] && '+' == p
[i
+ 1]) ||
584 ('+' == p
[i
] && '|' == p
[i
+ 1]) ||
585 ('|' == p
[i
] && '+' == p
[i
+ 1])) {
609 * Clean up the last character.
610 * We can get to a newline; don't print that.
618 if (i
== (int)len
- 1 && '\n' != p
[i
])
632 format(const char *file
)
640 char opts
[MAXPATHLEN
+ 128];
642 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
647 mp
= mparse_alloc(MPARSE_AUTO
, MANDOCLEVEL_FATAL
, NULL
, NULL
);
648 rc
= mparse_readfd(mp
, fd
, file
);
651 if (rc
>= MANDOCLEVEL_FATAL
) {
656 snprintf(opts
, sizeof(opts
), "style=/man.css,"
657 "man=%s/search.html?sec=%%S&expr=%%N,"
658 /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/,
661 mparse_result(mp
, &mdoc
, &man
);
662 vp
= html_alloc(opts
);
665 resp_begin_http(200, NULL
);
667 } else if (NULL
!= man
) {
668 resp_begin_http(200, NULL
);
678 pg_show(const struct manpaths
*ps
, const struct req
*req
, char *path
)
681 char file
[MAXPATHLEN
];
684 unsigned int vol
, rec
;
691 } else if (NULL
== (sub
= strrchr(path
, '/'))) {
697 if ( ! (atou(path
, &vol
) && atou(sub
, &rec
))) {
700 } else if (vol
>= (unsigned int)ps
->sz
) {
705 strlcpy(file
, ps
->paths
[vol
], MAXPATHLEN
);
706 strlcat(file
, "/mandoc.index", MAXPATHLEN
);
708 /* Open the index recno(3) database. */
710 idx
= dbopen(file
, O_RDONLY
, 0, DB_RECNO
, NULL
);
719 if (0 != (rc
= (*idx
->get
)(idx
, &key
, &val
, 0))) {
720 rc
< 0 ? resp_baddb() : resp_error400();
724 cp
= (char *)val
.data
;
726 if (NULL
== (fn
= memchr(cp
, '\0', val
.size
)))
728 else if (++fn
- cp
>= (int)val
.size
)
730 else if (NULL
== memchr(fn
, '\0', val
.size
- (fn
- cp
)))
733 strlcpy(file
, ps
->paths
[vol
], MAXPATHLEN
);
734 strlcat(file
, "/", MAXPATHLEN
);
735 strlcat(file
, fn
, MAXPATHLEN
);
736 if (0 == strcmp(cp
, "cat"))
746 pg_search(const struct manpaths
*ps
, const struct req
*req
, char *path
)
749 int i
, sz
, rc
, whatis
;
750 const char *ep
, *start
;
761 memset(&opt
, 0, sizeof(struct opts
));
763 for (sz
= i
= 0; i
< (int)req
->fieldsz
; i
++)
764 if (0 == strcmp(req
->fields
[i
].key
, "expr"))
765 ep
= req
->fields
[i
].val
;
766 else if (0 == strcmp(req
->fields
[i
].key
, "query"))
767 ep
= req
->fields
[i
].val
;
768 else if (0 == strcmp(req
->fields
[i
].key
, "sec"))
769 opt
.cat
= req
->fields
[i
].val
;
770 else if (0 == strcmp(req
->fields
[i
].key
, "sektion"))
771 opt
.cat
= req
->fields
[i
].val
;
772 else if (0 == strcmp(req
->fields
[i
].key
, "arch"))
773 opt
.arch
= req
->fields
[i
].val
;
774 else if (0 == strcmp(req
->fields
[i
].key
, "apropos"))
776 (req
->fields
[i
].val
, "0");
777 else if (0 == strcmp(req
->fields
[i
].key
, "op"))
778 whatis
= 0 == strcasecmp
779 (req
->fields
[i
].val
, "whatis");
781 if (NULL
!= opt
.cat
&& 0 == strcmp(opt
.cat
, "0"))
785 * Poor man's tokenisation.
786 * Just break apart by spaces.
787 * Yes, this is half-ass. But it works for now.
790 while (ep
&& isspace((unsigned char)*ep
))
793 while (ep
&& '\0' != *ep
) {
794 cp
= mandoc_realloc(cp
, (sz
+ 1) * sizeof(char *));
796 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
798 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
799 memcpy(cp
[sz
], start
, ep
- start
);
800 cp
[sz
++][ep
- start
] = '\0';
801 while (isspace((unsigned char)*ep
))
808 * Pump down into apropos backend.
809 * The resp_search() function is called with the results.
812 expr
= whatis
? termcomp(sz
, cp
, &tt
) :
813 exprcomp(sz
, cp
, &tt
);
817 (ps
->sz
, ps
->paths
, &opt
,
818 expr
, tt
, (void *)req
, resp_search
);
820 /* ...unless errors occured. */
825 resp_search(NULL
, 0, (void *)req
);
827 for (i
= 0; i
< sz
; i
++)
839 char *p
, *path
, *subpath
;
840 struct manpaths paths
;
842 /* HTTP init: read and parse the query string. */
844 progname
= getenv("SCRIPT_NAME");
845 if (NULL
== progname
)
848 cache
= getenv("CACHE_DIR");
850 cache
= "/cache/man.cgi";
852 if (-1 == chdir(cache
)) {
854 return(EXIT_FAILURE
);
857 host
= getenv("HTTP_HOST");
861 memset(&req
, 0, sizeof(struct req
));
863 if (NULL
!= (p
= getenv("QUERY_STRING")))
864 kval_parse(&req
.fields
, &req
.fieldsz
, p
);
866 /* Resolve leading subpath component. */
868 subpath
= path
= NULL
;
869 req
.page
= PAGE__MAX
;
871 if (NULL
== (path
= getenv("PATH_INFO")) || '\0' == *path
)
872 req
.page
= PAGE_INDEX
;
874 if (NULL
!= path
&& '/' == *path
&& '\0' == *++path
)
875 req
.page
= PAGE_INDEX
;
877 /* Strip file suffix. */
879 if (NULL
!= path
&& NULL
!= (p
= strrchr(path
, '.')))
880 if (NULL
!= p
&& NULL
== strchr(p
, '/'))
883 /* Resolve subpath component. */
885 if (NULL
!= path
&& NULL
!= (subpath
= strchr(path
, '/')))
888 /* Map path into one we recognise. */
890 if (NULL
!= path
&& '\0' != *path
)
891 for (i
= 0; i
< (int)PAGE__MAX
; i
++)
892 if (0 == strcmp(pages
[i
], path
)) {
893 req
.page
= (enum page
)i
;
897 /* Initialise MANPATH. */
899 memset(&paths
, 0, sizeof(struct manpaths
));
900 manpath_manconf("etc/catman.conf", &paths
);
906 pg_index(&paths
, &req
, subpath
);
909 pg_search(&paths
, &req
, subpath
);
912 pg_show(&paths
, &req
, subpath
);
919 manpath_free(&paths
);
920 kval_free(req
.fields
, req
.fieldsz
);
922 return(EXIT_SUCCESS
);
926 cmp(const void *p1
, const void *p2
)
929 return(strcasecmp(((const struct res
*)p1
)->title
,
930 ((const struct res
*)p2
)->title
));