]>
git.cameronkatri.com Git - mandoc.git/blob - cgi.c
1 /* $Id: cgi.c,v 1.7 2011/11/24 12:27:18 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"
65 static int atou(const char *, unsigned *);
66 static void format_insecure(const char *);
67 static void format_secure(const char *);
68 static void html_print(const char *);
69 static int kval_decode(char *);
70 static void kval_parse(struct kval
**, size_t *, char *);
71 static void kval_free(struct kval
*, size_t);
72 static void pg_index(const struct manpaths
*,
73 const struct req
*, char *);
74 static void pg_search(const struct manpaths
*,
75 const struct req
*, char *);
76 static void pg_show(const struct manpaths
*,
77 const struct req
*, char *);
78 static void resp_bad(void);
79 static void resp_baddb(void);
80 static void resp_badexpr(const struct req
*);
81 static void resp_badmanual(void);
82 static void resp_begin_html(int, const char *);
83 static void resp_begin_http(int, const char *);
84 static void resp_end_html(void);
85 static void resp_index(const struct req
*);
86 static void resp_search(struct res
*, size_t, void *);
87 static void resp_searchform(const struct req
*);
89 static int insecure
= 1;
90 static const char *progname
;
91 static const char *cache
;
92 static const char *host
;
94 static const char * const pages
[PAGE__MAX
] = {
95 "index", /* PAGE_INDEX */
96 "search", /* PAGE_SEARCH */
97 "show", /* PAGE_SHOW */
101 * This is just OpenBSD's strtol(3) suggestion.
102 * I use it instead of strtonum(3) for portability's sake.
105 atou(const char *buf
, unsigned *v
)
111 lval
= strtol(buf
, &ep
, 10);
112 if (buf
[0] == '\0' || *ep
!= '\0')
114 if ((errno
== ERANGE
&& (lval
== LONG_MAX
||
115 lval
== LONG_MIN
)) ||
116 (lval
> UINT_MAX
|| lval
< 0))
119 *v
= (unsigned int)lval
;
124 * Print a word, escaping HTML along the way.
125 * This will pass non-ASCII straight to output: be warned!
128 html_print(const char *p
)
136 switch ((c
= *p
++)) {
150 putchar((unsigned char)c
);
156 kval_free(struct kval
*p
, size_t sz
)
160 for (i
= 0; i
< (int)sz
; i
++) {
168 * Parse out key-value pairs from an HTTP request variable.
169 * This can be either a cookie or a POST/GET string, although man.cgi
170 * uses only GET for simplicity.
173 kval_parse(struct kval
**kv
, size_t *kvsz
, char *p
)
180 while (p
&& '\0' != *p
) {
187 if (NULL
!= (p
= strchr(p
, '='))) {
191 sz
= strcspn(p
, ";&");
199 sz
= strcspn(p
, ";&");
208 if ('\0' == *key
|| '\0' == *val
)
211 /* Just abort handling. */
213 if ( ! kval_decode(key
))
215 if ( ! kval_decode(val
))
218 if (*kvsz
+ 1 >= cur
) {
221 (*kv
, cur
* sizeof(struct kval
));
224 (*kv
)[(int)*kvsz
].key
= mandoc_strdup(key
);
225 (*kv
)[(int)*kvsz
].val
= mandoc_strdup(val
);
231 * HTTP-decode a string. The standard explanation is that this turns
232 * "%4e+foo" into "n foo" in the regular way. This is done in-place
233 * over the allocated string.
243 for ( ; '\0' != *p
; p
++) {
245 if ('\0' == (hex
[0] = *(p
+ 1)))
247 if ('\0' == (hex
[1] = *(p
+ 2)))
249 if (1 != sscanf(hex
, "%x", &c
))
255 memmove(p
+ 1, p
+ 3, strlen(p
+ 3) + 1);
257 *p
= '+' == *p
? ' ' : *p
;
265 resp_begin_http(int code
, const char *msg
)
269 printf("Status: %d %s\n", code
, msg
);
271 puts("Content-Type: text/html; charset=utf-8" "\n"
272 "Cache-Control: no-cache" "\n"
273 "Pragma: no-cache" "\n"
280 resp_begin_html(int code
, const char *msg
)
283 resp_begin_http(code
, msg
);
285 puts("<!DOCTYPE HTML PUBLIC " "\n"
286 " \"-//W3C//DTD HTML 4.01//EN\"" "\n"
287 " \"http://www.w3.org/TR/html4/strict.dtd\">" "\n"
290 " <TITLE>System Manpage Reference</TITLE>" "\n"
293 "<!-- Begin page content. //-->");
300 puts(" </BODY>\n</HTML>");
304 resp_searchform(const struct req
*req
)
307 const char *expr
, *sec
, *arch
;
309 expr
= sec
= arch
= "";
311 for (i
= 0; i
< (int)req
->fieldsz
; i
++)
312 if (0 == strcmp(req
->fields
[i
].key
, "expr"))
313 expr
= req
->fields
[i
].val
;
314 else if (0 == strcmp(req
->fields
[i
].key
, "sec"))
315 sec
= req
->fields
[i
].val
;
316 else if (0 == strcmp(req
->fields
[i
].key
, "arch"))
317 arch
= req
->fields
[i
].val
;
319 puts("<!-- Begin search form. //-->");
320 printf("<FORM ACTION=\"");
321 html_print(progname
);
322 printf("/search\" METHOD=\"get\">\n");
323 puts(" <FIELDSET>" "\n"
324 " <INPUT TYPE=\"submit\" VALUE=\"Search:\">");
325 printf(" Terms: <INPUT TYPE=\"text\" "
326 "SIZE=\"60\" NAME=\"expr\" VALUE=\"");
329 printf(" Section: <INPUT TYPE=\"text\" "
330 "SIZE=\"4\" NAME=\"sec\" VALUE=\"");
333 printf(" Arch: <INPUT TYPE=\"text\" "
334 "SIZE=\"8\" NAME=\"arch\" VALUE=\"");
337 puts(" </FIELDSET>\n</FORM>\n<!-- End search form. //-->");
341 resp_index(const struct req
*req
)
344 resp_begin_html(200, NULL
);
345 resp_searchform(req
);
353 resp_begin_html(404, "Not Found");
354 puts("<P>Requested manual not found.</P>");
359 resp_badexpr(const struct req
*req
)
362 resp_begin_html(200, NULL
);
363 resp_searchform(req
);
364 puts("<P>Your search didn't work.</P>");
371 resp_begin_html(500, "Internal Server Error");
372 puts("<P>Generic badness happened.</P>");
380 resp_begin_html(500, "Internal Server Error");
381 puts("<P>Your database is broken.</P>");
386 resp_search(struct res
*r
, size_t sz
, void *arg
)
392 * If we have just one result, then jump there now
395 puts("Status: 303 See Other");
396 printf("Location: http://%s%s/show/%u/%u.html\n",
398 r
[0].volume
, r
[0].rec
);
399 puts("Content-Type: text/html; charset=utf-8\n");
403 resp_begin_html(200, NULL
);
404 resp_searchform((const struct req
*)arg
);
407 puts("<P>No results found.</P>");
409 for (i
= 0; i
< (int)sz
; i
++) {
410 printf("<P><A HREF=\"");
411 html_print(progname
);
412 printf("/show/%u/%u.html\">", r
[i
].volume
, r
[i
].rec
);
413 html_print(r
[i
].title
);
415 html_print(r
[i
].cat
);
416 if (r
[i
].arch
&& '\0' != *r
[i
].arch
) {
418 html_print(r
[i
].arch
);
421 html_print(r
[i
].desc
);
430 pg_index(const struct manpaths
*ps
, const struct req
*req
, char *path
)
437 format_insecure(const char *file
)
440 char cmd
[MAXPATHLEN
];
442 strlcpy(cmd
, "man=", MAXPATHLEN
);
443 strlcat(cmd
, progname
, MAXPATHLEN
);
444 strlcat(cmd
, "/search?expr=%N&sec=%S", MAXPATHLEN
);
446 /* Get ready to call the child mandoc(1) process. */
448 if (-1 == (pid
= fork()))
452 waitpid(pid
, NULL
, 0);
456 dup2(STDOUT_FILENO
, STDERR_FILENO
);
458 puts("Content-Type: text/html; charset=utf-8\n");
462 execlp("mandoc", "mandoc", "-T",
463 "html", "-O", cmd
, file
, (char *)NULL
);
467 format_secure(const char *file
)
473 if (-1 == (fd
= open(file
, O_RDONLY
, 0))) {
478 resp_begin_http(200, NULL
);
481 ssz
= read(fd
, buf
, BUFSIZ
);
483 write(STDOUT_FILENO
, buf
, ssz
);
490 pg_show(const struct manpaths
*ps
, const struct req
*req
, char *path
)
493 char file
[MAXPATHLEN
];
495 unsigned int vol
, rec
;
502 } else if (NULL
== (sub
= strrchr(path
, '/'))) {
508 if ( ! (atou(path
, &vol
) && atou(sub
, &rec
))) {
511 } else if (vol
>= (unsigned int)ps
->sz
) {
516 strlcpy(file
, ps
->paths
[vol
], MAXPATHLEN
);
517 strlcat(file
, "/mandoc.index", MAXPATHLEN
);
519 /* Open the index recno(3) database. */
521 db
= dbopen(file
, O_RDONLY
, 0, DB_RECNO
, NULL
);
530 if (0 != (rc
= (*db
->get
)(db
, &key
, &val
, 0))) {
531 rc
< 0 ? resp_baddb() : resp_badmanual();
536 /* Extra filename: the first nil-terminated entry. */
540 strlcpy(file
, ps
->paths
[vol
], MAXPATHLEN
);
541 strlcat(file
, "/", MAXPATHLEN
);
542 strlcat(file
, (char *)val
.data
, MAXPATHLEN
);
545 strlcat(file
, ".html", MAXPATHLEN
);
548 format_insecure(file
);
552 pg_search(const struct manpaths
*ps
, const struct req
*req
, char *path
)
556 const char *ep
, *start
;
566 memset(&opt
, 0, sizeof(struct opts
));
568 for (sz
= i
= 0; i
< (int)req
->fieldsz
; i
++)
569 if (0 == strcmp(req
->fields
[i
].key
, "expr"))
570 ep
= req
->fields
[i
].val
;
571 else if (0 == strcmp(req
->fields
[i
].key
, "sec"))
572 opt
.cat
= req
->fields
[i
].val
;
573 else if (0 == strcmp(req
->fields
[i
].key
, "arch"))
574 opt
.arch
= req
->fields
[i
].val
;
577 * Poor man's tokenisation.
578 * Just break apart by spaces.
579 * Yes, this is half-ass. But it works for now.
582 while (ep
&& isspace((unsigned char)*ep
))
585 while (ep
&& '\0' != *ep
) {
586 cp
= mandoc_realloc(cp
, (sz
+ 1) * sizeof(char *));
588 while ('\0' != *ep
&& ! isspace((unsigned char)*ep
))
590 cp
[sz
] = mandoc_malloc((ep
- start
) + 1);
591 memcpy(cp
[sz
], start
, ep
- start
);
592 cp
[sz
++][ep
- start
] = '\0';
593 while (isspace((unsigned char)*ep
))
600 * Pump down into apropos backend.
601 * The resp_search() function is called with the results.
604 if (NULL
!= (expr
= exprcomp(sz
, cp
, &tt
)))
606 (ps
->sz
, ps
->paths
, &opt
,
607 expr
, tt
, (void *)req
, resp_search
);
609 /* ...unless errors occured. */
616 for (i
= 0; i
< sz
; i
++)
628 char *p
, *path
, *subpath
;
629 struct manpaths paths
;
631 /* HTTP init: read and parse the query string. */
633 progname
= getenv("SCRIPT_NAME");
634 if (NULL
== progname
)
637 cache
= getenv("CACHE_DIR");
639 cache
= "/cache/man.cgi";
641 if (NULL
== getenv("INSECURE")) {
643 if (-1 == chdir(cache
)) {
645 return(EXIT_FAILURE
);
649 host
= getenv("HTTP_HOST");
653 memset(&req
, 0, sizeof(struct req
));
655 if (NULL
!= (p
= getenv("QUERY_STRING")))
656 kval_parse(&req
.fields
, &req
.fieldsz
, p
);
658 /* Resolve leading subpath component. */
660 subpath
= path
= NULL
;
661 req
.page
= PAGE__MAX
;
663 if (NULL
== (path
= getenv("PATH_INFO")) || '\0' == *path
)
664 req
.page
= PAGE_INDEX
;
666 if (NULL
!= path
&& '/' == *path
&& '\0' == *++path
)
667 req
.page
= PAGE_INDEX
;
669 /* Strip file suffix. */
671 if (NULL
!= path
&& NULL
!= (p
= strrchr(path
, '.')))
672 if (NULL
!= p
&& NULL
== strchr(p
, '/'))
675 /* Resolve subpath component. */
677 if (NULL
!= path
&& NULL
!= (subpath
= strchr(path
, '/')))
680 /* Map path into one we recognise. */
682 if (NULL
!= path
&& '\0' != *path
)
683 for (i
= 0; i
< (int)PAGE__MAX
; i
++)
684 if (0 == strcmp(pages
[i
], path
)) {
685 req
.page
= (enum page
)i
;
689 /* Initialise MANPATH. */
691 memset(&paths
, 0, sizeof(struct manpaths
));
693 manpath_manconf("etc/man.conf", &paths
);
695 manpath_parse(&paths
, NULL
, NULL
);
701 pg_index(&paths
, &req
, subpath
);
704 pg_search(&paths
, &req
, subpath
);
707 pg_show(&paths
, &req
, subpath
);
713 manpath_free(&paths
);
714 kval_free(req
.fields
, req
.fieldsz
);
716 return(EXIT_SUCCESS
);