Return to cgi.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.13, 2011/12/07 15:12:34 | version 1.45, 2013/06/05 02:00:26 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
#include "config.h" | #include "config.h" | ||
#endif | #endif | ||
#include <sys/param.h> | |||
#include <sys/wait.h> | #include <sys/wait.h> | ||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | #include <ctype.h> | ||
#include <errno.h> | #include <errno.h> | ||
#include <dirent.h> | |||
#include <fcntl.h> | #include <fcntl.h> | ||
#include <limits.h> | #include <limits.h> | ||
#include <regex.h> | #include <regex.h> | ||
|
|
||
#include "man.h" | #include "man.h" | ||
#include "main.h" | #include "main.h" | ||
#include "manpath.h" | #include "manpath.h" | ||
#include "mandocdb.h" | |||
#ifdef __linux__ | #ifdef __linux__ | ||
# include <db_185.h> | # include <db_185.h> | ||
|
|
||
PAGE__MAX | PAGE__MAX | ||
}; | }; | ||
struct kval { | struct paths { | ||
char *key; | char *name; | ||
char *val; | char *path; | ||
}; | }; | ||
/* | |||
* A query as passed to the search function. | |||
*/ | |||
struct query { | |||
const char *arch; /* architecture */ | |||
const char *sec; /* manual section */ | |||
const char *expr; /* unparsed expression string */ | |||
int manroot; /* manroot index (or -1)*/ | |||
int legacy; /* whether legacy mode */ | |||
}; | |||
struct req { | struct req { | ||
struct kval *fields; | struct query q; | ||
size_t fieldsz; | struct paths *p; | ||
size_t psz; | |||
enum page page; | enum page page; | ||
}; | }; | ||
static int atou(const char *, unsigned *); | static int atou(const char *, unsigned *); | ||
static void catman(const char *); | static void catman(const struct req *, const char *); | ||
static void format(const char *); | static int cmp(const void *, const void *); | ||
static void format(const struct req *, const char *); | |||
static void html_print(const char *); | static void html_print(const char *); | ||
static void html_printquery(const struct req *); | |||
static void html_putchar(char); | static void html_putchar(char); | ||
static int kval_decode(char *); | static int http_decode(char *); | ||
static void kval_parse(struct kval **, size_t *, char *); | static void http_parse(struct req *, char *); | ||
static void kval_free(struct kval *, size_t); | static void http_print(const char *); | ||
static void pg_index(const struct manpaths *, | static void http_putchar(char); | ||
const struct req *, char *); | static void http_printquery(const struct req *); | ||
static void pg_search(const struct manpaths *, | static int pathstop(DIR *); | ||
const struct req *, char *); | static void pathgen(DIR *, char *, struct req *); | ||
static void pg_show(const struct manpaths *, | static void pg_index(const struct req *, char *); | ||
const struct req *, char *); | static void pg_search(const struct req *, char *); | ||
static void pg_show(const struct req *, char *); | |||
static void resp_bad(void); | static void resp_bad(void); | ||
static void resp_baddb(void); | static void resp_baddb(void); | ||
static void resp_error400(void); | static void resp_error400(void); | ||
|
|
||
static void resp_search(struct res *, size_t, void *); | static void resp_search(struct res *, size_t, void *); | ||
static void resp_searchform(const struct req *); | static void resp_searchform(const struct req *); | ||
static const char *progname; | static const char *progname; /* cgi script name */ | ||
static const char *cache; | static const char *cache; /* cache directory */ | ||
static const char *host; | static const char *css; /* css directory */ | ||
static const char *host; /* hostname */ | |||
static const char * const pages[PAGE__MAX] = { | static const char * const pages[PAGE__MAX] = { | ||
"index", /* PAGE_INDEX */ | "index", /* PAGE_INDEX */ | ||
|
|
||
return(0); | return(0); | ||
if ((errno == ERANGE && (lval == LONG_MAX || | if ((errno == ERANGE && (lval == LONG_MAX || | ||
lval == LONG_MIN)) || | lval == LONG_MIN)) || | ||
(lval > UINT_MAX || lval < 0)) | (lval > INT_MAX || lval < 0)) | ||
return(0); | return(0); | ||
*v = (unsigned int)lval; | *v = (unsigned int)lval; | ||
return(1); | return(1); | ||
} | } | ||
/* | |||
* Print a character, escaping HTML along the way. | |||
* This will pass non-ASCII straight to output: be warned! | |||
*/ | |||
static void | static void | ||
html_putchar(char c) | html_putchar(char c) | ||
{ | { | ||
|
|
||
break; | break; | ||
} | } | ||
} | } | ||
static void | |||
http_printquery(const struct req *req) | |||
{ | |||
printf("&expr="); | |||
http_print(req->q.expr ? req->q.expr : ""); | |||
printf("&sec="); | |||
http_print(req->q.sec ? req->q.sec : ""); | |||
printf("&arch="); | |||
http_print(req->q.arch ? req->q.arch : ""); | |||
} | |||
static void | |||
html_printquery(const struct req *req) | |||
{ | |||
printf("&expr="); | |||
html_print(req->q.expr ? req->q.expr : ""); | |||
printf("&sec="); | |||
html_print(req->q.sec ? req->q.sec : ""); | |||
printf("&arch="); | |||
html_print(req->q.arch ? req->q.arch : ""); | |||
} | |||
static void | |||
http_print(const char *p) | |||
{ | |||
if (NULL == p) | |||
return; | |||
while ('\0' != *p) | |||
http_putchar(*p++); | |||
} | |||
/* | /* | ||
* Print a word, escaping HTML along the way. | * Call through to html_putchar(). | ||
* This will pass non-ASCII straight to output: be warned! | * Accepts NULL strings. | ||
*/ | */ | ||
static void | static void | ||
html_print(const char *p) | html_print(const char *p) | ||
|
|
||
html_putchar(*p++); | html_putchar(*p++); | ||
} | } | ||
static void | |||
kval_free(struct kval *p, size_t sz) | |||
{ | |||
int i; | |||
for (i = 0; i < (int)sz; i++) { | |||
free(p[i].key); | |||
free(p[i].val); | |||
} | |||
free(p); | |||
} | |||
/* | /* | ||
* Parse out key-value pairs from an HTTP request variable. | * Parse out key-value pairs from an HTTP request variable. | ||
* This can be either a cookie or a POST/GET string, although man.cgi | * This can be either a cookie or a POST/GET string, although man.cgi | ||
* uses only GET for simplicity. | * uses only GET for simplicity. | ||
*/ | */ | ||
static void | static void | ||
kval_parse(struct kval **kv, size_t *kvsz, char *p) | http_parse(struct req *req, char *p) | ||
{ | { | ||
char *key, *val; | char *key, *val, *manroot; | ||
size_t sz, cur; | int i, legacy; | ||
cur = 0; | memset(&req->q, 0, sizeof(struct query)); | ||
while (p && '\0' != *p) { | legacy = -1; | ||
while (' ' == *p) | manroot = NULL; | ||
p++; | |||
while ('\0' != *p) { | |||
key = p; | key = p; | ||
val = NULL; | val = NULL; | ||
if (NULL != (p = strchr(p, '='))) { | p += (int)strcspn(p, ";&"); | ||
if ('\0' != *p) | |||
*p++ = '\0'; | *p++ = '\0'; | ||
val = p; | if (NULL != (val = strchr(key, '='))) | ||
*val++ = '\0'; | |||
sz = strcspn(p, ";&"); | if ('\0' == *key || NULL == val || '\0' == *val) | ||
/* LINTED */ | continue; | ||
p += sz; | |||
if ('\0' != *p) | /* Just abort handling. */ | ||
*p++ = '\0'; | |||
} else { | |||
p = key; | |||
sz = strcspn(p, ";&"); | |||
/* LINTED */ | |||
p += sz; | |||
if ('\0' != *p) | if ( ! http_decode(key)) | ||
p++; | break; | ||
continue; | if (NULL != val && ! http_decode(val)) | ||
} | break; | ||
if ('\0' == *key || '\0' == *val) | if (0 == strcmp(key, "expr")) | ||
continue; | req->q.expr = val; | ||
else if (0 == strcmp(key, "query")) | |||
req->q.expr = val; | |||
else if (0 == strcmp(key, "sec")) | |||
req->q.sec = val; | |||
else if (0 == strcmp(key, "sektion")) | |||
req->q.sec = val; | |||
else if (0 == strcmp(key, "arch")) | |||
req->q.arch = val; | |||
else if (0 == strcmp(key, "manpath")) | |||
manroot = val; | |||
else if (0 == strcmp(key, "apropos")) | |||
legacy = 0 == strcmp(val, "0"); | |||
} | |||
/* Just abort handling. */ | /* Test for old man.cgi compatibility mode. */ | ||
if ( ! kval_decode(key)) | req->q.legacy = legacy > 0; | ||
return; | |||
if ( ! kval_decode(val)) | |||
return; | |||
if (*kvsz + 1 >= cur) { | /* | ||
cur++; | * Section "0" means no section when in legacy mode. | ||
*kv = mandoc_realloc | * For some man.cgi scripts, "default" arch is none. | ||
(*kv, cur * sizeof(struct kval)); | */ | ||
} | |||
(*kv)[(int)*kvsz].key = mandoc_strdup(key); | if (req->q.legacy && NULL != req->q.sec) | ||
(*kv)[(int)*kvsz].val = mandoc_strdup(val); | if (0 == strcmp(req->q.sec, "0")) | ||
(*kvsz)++; | req->q.sec = NULL; | ||
if (req->q.legacy && NULL != req->q.arch) | |||
if (0 == strcmp(req->q.arch, "default")) | |||
req->q.arch = NULL; | |||
/* Default to first manroot. */ | |||
if (NULL != manroot) { | |||
for (i = 0; i < (int)req->psz; i++) | |||
if (0 == strcmp(req->p[i].name, manroot)) | |||
break; | |||
req->q.manroot = i < (int)req->psz ? i : -1; | |||
} | } | ||
} | } | ||
static void | |||
http_putchar(char c) | |||
{ | |||
if (isalnum((unsigned char)c)) { | |||
putchar((unsigned char)c); | |||
return; | |||
} else if (' ' == c) { | |||
putchar('+'); | |||
return; | |||
} | |||
printf("%%%.2x", c); | |||
} | |||
/* | /* | ||
* HTTP-decode a string. The standard explanation is that this turns | * HTTP-decode a string. The standard explanation is that this turns | ||
* "%4e+foo" into "n foo" in the regular way. This is done in-place | * "%4e+foo" into "n foo" in the regular way. This is done in-place | ||
* over the allocated string. | * over the allocated string. | ||
*/ | */ | ||
static int | static int | ||
kval_decode(char *p) | http_decode(char *p) | ||
{ | { | ||
char hex[3]; | char hex[3]; | ||
int c; | int c; | ||
|
|
||
if (200 != code) | if (200 != code) | ||
printf("Status: %d %s\n", code, msg); | printf("Status: %d %s\n", code, msg); | ||
puts("Content-Type: text/html; charset=utf-8" "\n" | puts("Content-Type: text/html; charset=utf-8\n" | ||
"Cache-Control: no-cache" "\n" | "Cache-Control: no-cache\n" | ||
"Pragma: no-cache" "\n" | "Pragma: no-cache\n" | ||
""); | ""); | ||
fflush(stdout); | fflush(stdout); | ||
|
|
||
resp_begin_http(code, msg); | resp_begin_http(code, msg); | ||
puts("<!DOCTYPE HTML PUBLIC " "\n" | printf("<!DOCTYPE HTML PUBLIC " | ||
" \"-//W3C//DTD HTML 4.01//EN\"" "\n" | " \"-//W3C//DTD HTML 4.01//EN\"" | ||
" \"http://www.w3.org/TR/html4/strict.dtd\">" "\n" | " \"http://www.w3.org/TR/html4/strict.dtd\">\n" | ||
"<HTML>" "\n" | "<HTML>\n" | ||
" <HEAD>" "\n" | "<HEAD>\n" | ||
" <META HTTP-EQUIV=\"Content-Type\" " "\n" | "<META HTTP-EQUIV=\"Content-Type\"" | ||
" CONTENT=\"text/html; charset=utf-8\">" "\n" | " CONTENT=\"text/html; charset=utf-8\">\n" | ||
" <LINK REL=\"stylesheet\" HREF=\"/man.cgi.css\"" "\n" | "<LINK REL=\"stylesheet\" HREF=\"%s/man-cgi.css\"" | ||
" TYPE=\"text/css\" media=\"all\">" "\n" | " TYPE=\"text/css\" media=\"all\">\n" | ||
" <TITLE>System Manpage Reference</TITLE>" "\n" | "<LINK REL=\"stylesheet\" HREF=\"%s/man.css\"" | ||
" </HEAD>" "\n" | " TYPE=\"text/css\" media=\"all\">\n" | ||
" <BODY>" "\n" | "<TITLE>System Manpage Reference</TITLE>\n" | ||
"<!-- Begin page content. //-->"); | "</HEAD>\n" | ||
"<BODY>\n" | |||
"<!-- Begin page content. //-->\n", css, css); | |||
} | } | ||
static void | static void | ||
resp_end_html(void) | resp_end_html(void) | ||
{ | { | ||
puts(" </BODY>\n</HTML>"); | puts("</BODY>\n" | ||
"</HTML>"); | |||
} | } | ||
static void | static void | ||
resp_searchform(const struct req *req) | resp_searchform(const struct req *req) | ||
{ | { | ||
int i; | int i; | ||
const char *expr, *sec, *arch; | |||
expr = sec = arch = ""; | |||
for (i = 0; i < (int)req->fieldsz; i++) | |||
if (0 == strcmp(req->fields[i].key, "expr")) | |||
expr = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "query")) | |||
expr = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "sec")) | |||
sec = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "sektion")) | |||
sec = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "arch")) | |||
arch = req->fields[i].val; | |||
if (NULL != sec && 0 == strcmp(sec, "0")) | |||
sec = NULL; | |||
puts("<!-- Begin search form. //-->"); | puts("<!-- Begin search form. //-->"); | ||
printf("<FORM ACTION=\""); | printf("<DIV ID=\"mancgi\">\n" | ||
html_print(progname); | "<FORM ACTION=\"%s/search.html\" METHOD=\"get\">\n" | ||
printf("/search.html\" METHOD=\"get\">\n"); | "<FIELDSET>\n" | ||
puts("<FIELDSET>\n" | "<LEGEND>Search Parameters</LEGEND>\n" | ||
"<INPUT TYPE=\"submit\" NAME=\"op\" " | "<INPUT TYPE=\"submit\" " | ||
"VALUE=\"Whatis\"> or \n" | " VALUE=\"Search\"> for manuals satisfying \n" | ||
"<INPUT TYPE=\"submit\" NAME=\"op\" " | "<INPUT TYPE=\"text\" NAME=\"expr\" VALUE=\"", | ||
"VALUE=\"apropos\"> for manuals satisfying \n" | progname); | ||
"<INPUT TYPE=\"text\" SIZE=\"40\" " | html_print(req->q.expr ? req->q.expr : ""); | ||
"NAME=\"expr\" VALUE=\""); | printf("\">, section " | ||
html_print(expr); | "<INPUT TYPE=\"text\"" | ||
puts("\">, section " | " SIZE=\"4\" NAME=\"sec\" VALUE=\""); | ||
"<INPUT TYPE=\"text\" " | html_print(req->q.sec ? req->q.sec : ""); | ||
"SIZE=\"4\" NAME=\"sec\" VALUE=\""); | printf("\">, arch " | ||
html_print(sec); | "<INPUT TYPE=\"text\"" | ||
puts("\">, arch " | " SIZE=\"8\" NAME=\"arch\" VALUE=\""); | ||
"<INPUT TYPE=\"text\" " | html_print(req->q.arch ? req->q.arch : ""); | ||
"SIZE=\"8\" NAME=\"arch\" VALUE=\""); | printf("\">"); | ||
html_print(arch); | if (req->psz > 1) { | ||
puts("\">.\n" | puts(", <SELECT NAME=\"manpath\">"); | ||
for (i = 0; i < (int)req->psz; i++) { | |||
printf("<OPTION %s VALUE=\"", | |||
(i == req->q.manroot) || | |||
(0 == i && -1 == req->q.manroot) ? | |||
"SELECTED=\"selected\"" : ""); | |||
html_print(req->p[i].name); | |||
printf("\">"); | |||
html_print(req->p[i].name); | |||
puts("</OPTION>"); | |||
} | |||
puts("</SELECT>"); | |||
} | |||
puts(".\n" | |||
"<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n" | "<INPUT TYPE=\"reset\" VALUE=\"Reset\">\n" | ||
"</FIELDSET>\n" | "</FIELDSET>\n" | ||
"</FORM>\n" | "</FORM>\n" | ||
"<!-- End search form. //-->"); | "</DIV>"); | ||
puts("<!-- End search form. //-->"); | |||
} | } | ||
static void | static void | ||
|
|
||
resp_begin_html(400, "Query Malformed"); | resp_begin_html(400, "Query Malformed"); | ||
printf("<H1>Malformed Query</H1>\n" | printf("<H1>Malformed Query</H1>\n" | ||
"<P>\n" | "<P>\n" | ||
" The query your entered was malformed.\n" | "The query your entered was malformed.\n" | ||
" Try again from the\n" | "Try again from the\n" | ||
" <A HREF=\"%s/index.html\">main page</A>\n" | "<A HREF=\"%s/index.html\">main page</A>.\n" | ||
"</P>", progname); | "</P>", progname); | ||
resp_end_html(); | resp_end_html(); | ||
} | } | ||
|
|
||
resp_begin_html(404, "Not Found"); | resp_begin_html(404, "Not Found"); | ||
puts("<H1>Page Not Found</H1>\n" | puts("<H1>Page Not Found</H1>\n" | ||
"<P>\n" | "<P>\n" | ||
" The page you're looking for, "); | "The page you're looking for, "); | ||
printf(" <B>"); | printf("<B>"); | ||
html_print(page); | html_print(page); | ||
printf("</B>,\n" | printf("</B>,\n" | ||
" could not be found.\n" | "could not be found.\n" | ||
" Try searching from the\n" | "Try searching from the\n" | ||
" <A HREF=\"%s/index.html\">main page</A>\n" | "<A HREF=\"%s/index.html\">main page</A>.\n" | ||
"</P>", progname); | "</P>", progname); | ||
resp_end_html(); | resp_end_html(); | ||
} | } | ||
|
|
||
static void | static void | ||
resp_search(struct res *r, size_t sz, void *arg) | resp_search(struct res *r, size_t sz, void *arg) | ||
{ | { | ||
int i; | size_t i, matched; | ||
const struct req *req; | |||
if (1 == sz) { | req = (const struct req *)arg; | ||
if (sz > 0) | |||
assert(req->q.manroot >= 0); | |||
for (matched = i = 0; i < sz; i++) | |||
if (r[i].matched) | |||
matched++; | |||
if (1 == matched) { | |||
for (i = 0; i < sz; i++) | |||
if (r[i].matched) | |||
break; | |||
/* | /* | ||
* If we have just one result, then jump there now | * If we have just one result, then jump there now | ||
* without any delay. | * without any delay. | ||
*/ | */ | ||
puts("Status: 303 See Other"); | puts("Status: 303 See Other"); | ||
printf("Location: http://%s%s/show/%u/%u.html\n", | printf("Location: http://%s%s/show/%d/%u/%u.html?", | ||
host, progname, | host, progname, req->q.manroot, | ||
r[0].volume, r[0].rec); | r[i].volume, r[i].rec); | ||
puts("Content-Type: text/html; charset=utf-8\n"); | http_printquery(req); | ||
puts("\n" | |||
"Content-Type: text/html; charset=utf-8\n"); | |||
return; | return; | ||
} | } | ||
resp_begin_html(200, NULL); | resp_begin_html(200, NULL); | ||
resp_searchform((const struct req *)arg); | resp_searchform(req); | ||
if (0 == sz) | puts("<DIV CLASS=\"results\">"); | ||
puts("<P>No results found.</P>"); | |||
for (i = 0; i < (int)sz; i++) { | if (0 == matched) { | ||
printf("<P><A HREF=\""); | puts("<P>\n" | ||
html_print(progname); | "No results found.\n" | ||
printf("/show/%u/%u.html\">", r[i].volume, r[i].rec); | "</P>\n" | ||
"</DIV>"); | |||
resp_end_html(); | |||
return; | |||
} | |||
qsort(r, sz, sizeof(struct res), cmp); | |||
puts("<TABLE>"); | |||
for (i = 0; i < sz; i++) { | |||
if ( ! r[i].matched) | |||
continue; | |||
printf("<TR>\n" | |||
"<TD CLASS=\"title\">\n" | |||
"<A HREF=\"%s/show/%d/%u/%u.html?", | |||
progname, req->q.manroot, | |||
r[i].volume, r[i].rec); | |||
html_printquery(req); | |||
printf("\">"); | |||
html_print(r[i].title); | html_print(r[i].title); | ||
putchar('('); | putchar('('); | ||
html_print(r[i].cat); | html_print(r[i].cat); | ||
|
|
||
putchar('/'); | putchar('/'); | ||
html_print(r[i].arch); | html_print(r[i].arch); | ||
} | } | ||
printf(")</A> "); | printf(")</A>\n" | ||
"</TD>\n" | |||
"<TD CLASS=\"desc\">"); | |||
html_print(r[i].desc); | html_print(r[i].desc); | ||
puts("</P>"); | puts("</TD>\n" | ||
"</TR>"); | |||
} | } | ||
puts("</TABLE>\n" | |||
"</DIV>"); | |||
resp_end_html(); | resp_end_html(); | ||
} | } | ||
/* ARGSUSED */ | /* ARGSUSED */ | ||
static void | static void | ||
pg_index(const struct manpaths *ps, const struct req *req, char *path) | pg_index(const struct req *req, char *path) | ||
{ | { | ||
resp_index(req); | resp_index(req); | ||
} | } | ||
static void | static void | ||
catman(const char *file) | catman(const struct req *req, const char *file) | ||
{ | { | ||
FILE *f; | FILE *f; | ||
size_t len; | size_t len; | ||
|
|
||
return; | return; | ||
} | } | ||
resp_begin_http(200, NULL); | resp_begin_html(200, NULL); | ||
puts("<!DOCTYPE HTML PUBLIC " "\n" | resp_searchform(req); | ||
" \"-//W3C//DTD HTML 4.01//EN\"" "\n" | puts("<DIV CLASS=\"catman\">\n" | ||
" \"http://www.w3.org/TR/html4/strict.dtd\">" "\n" | "<PRE>"); | ||
"<HTML>" "\n" | |||
" <HEAD>" "\n" | |||
" <META HTTP-EQUIV=\"Content-Type\" " "\n" | |||
" CONTENT=\"text/html; charset=utf-8\">" "\n" | |||
" <LINK REL=\"stylesheet\" HREF=\"/catman.css\"" "\n" | |||
" TYPE=\"text/css\" media=\"all\">" "\n" | |||
" <TITLE>System Manpage Reference</TITLE>" "\n" | |||
" </HEAD>" "\n" | |||
" <BODY>" "\n" | |||
"<!-- Begin page content. //-->"); | |||
puts("<PRE>"); | |||
while (NULL != (p = fgetln(f, &len))) { | while (NULL != (p = fgetln(f, &len))) { | ||
bold = italic = 0; | bold = italic = 0; | ||
for (i = 0; i < (int)len - 1; i++) { | for (i = 0; i < (int)len - 1; i++) { | ||
|
|
||
} | } | ||
puts("</PRE>\n" | puts("</PRE>\n" | ||
"</DIV>\n" | |||
"</BODY>\n" | "</BODY>\n" | ||
"</HTML>"); | "</HTML>"); | ||
|
|
||
} | } | ||
static void | static void | ||
format(const char *file) | format(const struct req *req, const char *file) | ||
{ | { | ||
struct mparse *mp; | struct mparse *mp; | ||
int fd; | int fd; | ||
|
|
||
struct man *man; | struct man *man; | ||
void *vp; | void *vp; | ||
enum mandoclevel rc; | enum mandoclevel rc; | ||
char opts[MAXPATHLEN + 128]; | char opts[PATH_MAX + 128]; | ||
if (-1 == (fd = open(file, O_RDONLY, 0))) { | if (-1 == (fd = open(file, O_RDONLY, 0))) { | ||
resp_baddb(); | resp_baddb(); | ||
return; | return; | ||
} | } | ||
mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL); | mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL, NULL); | ||
rc = mparse_readfd(mp, fd, file); | rc = mparse_readfd(mp, fd, file); | ||
close(fd); | close(fd); | ||
|
|
||
return; | return; | ||
} | } | ||
snprintf(opts, sizeof(opts), "style=/man.css," | snprintf(opts, sizeof(opts), "fragment," | ||
"man=%s/search.html?sec=%%S&expr=%%N," | "man=%s/search.html?sec=%%S&expr=Nm~^%%N$," | ||
/*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/, | /*"includes=/cgi-bin/man.cgi/usr/include/%%I"*/, | ||
progname); | progname); | ||
mparse_result(mp, &mdoc, &man); | mparse_result(mp, &mdoc, &man); | ||
if (NULL == man && NULL == mdoc) { | |||
resp_baddb(); | |||
mparse_free(mp); | |||
return; | |||
} | |||
resp_begin_html(200, NULL); | |||
resp_searchform(req); | |||
vp = html_alloc(opts); | vp = html_alloc(opts); | ||
if (NULL != mdoc) { | if (NULL != mdoc) | ||
resp_begin_http(200, NULL); | |||
html_mdoc(vp, mdoc); | html_mdoc(vp, mdoc); | ||
} else if (NULL != man) { | else | ||
resp_begin_http(200, NULL); | |||
html_man(vp, man); | html_man(vp, man); | ||
} else | |||
resp_baddb(); | |||
puts("</BODY>\n" | |||
"</HTML>"); | |||
html_free(vp); | html_free(vp); | ||
mparse_free(mp); | mparse_free(mp); | ||
} | } | ||
static void | static void | ||
pg_show(const struct manpaths *ps, const struct req *req, char *path) | pg_show(const struct req *req, char *path) | ||
{ | { | ||
struct manpaths ps; | |||
size_t sz; | |||
char *sub; | char *sub; | ||
char file[MAXPATHLEN]; | char file[PATH_MAX]; | ||
const char *fn, *cp; | const char *cp; | ||
int rc; | int rc, catm; | ||
unsigned int vol, rec; | unsigned int vol, rec, mr; | ||
DB *idx; | DB *idx; | ||
DBT key, val; | DBT key, val; | ||
if (NULL == path) { | idx = NULL; | ||
/* Parse out mroot, volume, and record from the path. */ | |||
if (NULL == path || NULL == (sub = strchr(path, '/'))) { | |||
resp_error400(); | resp_error400(); | ||
return; | return; | ||
} else if (NULL == (sub = strrchr(path, '/'))) { | } | ||
*sub++ = '\0'; | |||
if ( ! atou(path, &mr)) { | |||
resp_error400(); | resp_error400(); | ||
return; | return; | ||
} else | } | ||
*sub++ = '\0'; | path = sub; | ||
if (NULL == (sub = strchr(path, '/'))) { | |||
if ( ! (atou(path, &vol) && atou(sub, &rec))) { | |||
resp_error400(); | resp_error400(); | ||
return; | return; | ||
} else if (vol >= (unsigned int)ps->sz) { | } | ||
*sub++ = '\0'; | |||
if ( ! atou(path, &vol) || ! atou(sub, &rec)) { | |||
resp_error400(); | resp_error400(); | ||
return; | return; | ||
} else if (mr >= (unsigned int)req->psz) { | |||
resp_error400(); | |||
return; | |||
} | } | ||
strlcpy(file, ps->paths[vol], MAXPATHLEN); | /* | ||
strlcat(file, "/mandoc.index", MAXPATHLEN); | * Begin by chdir()ing into the manroot. | ||
* This way we can pick up the database files, which are | |||
* relative to the manpath root. | |||
*/ | |||
if (-1 == chdir(req->p[(int)mr].path)) { | |||
perror(req->p[(int)mr].path); | |||
resp_baddb(); | |||
return; | |||
} | |||
memset(&ps, 0, sizeof(struct manpaths)); | |||
manpath_manconf(&ps, "etc/catman.conf"); | |||
if (vol >= (unsigned int)ps.sz) { | |||
resp_error400(); | |||
goto out; | |||
} | |||
sz = strlcpy(file, ps.paths[vol], PATH_MAX); | |||
assert(sz < PATH_MAX); | |||
strlcat(file, "/", PATH_MAX); | |||
strlcat(file, MANDOC_IDX, PATH_MAX); | |||
/* Open the index recno(3) database. */ | /* Open the index recno(3) database. */ | ||
idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL); | idx = dbopen(file, O_RDONLY, 0, DB_RECNO, NULL); | ||
if (NULL == idx) { | if (NULL == idx) { | ||
perror(file); | |||
resp_baddb(); | resp_baddb(); | ||
return; | goto out; | ||
} | } | ||
key.data = &rec; | key.data = &rec; | ||
|
|
||
if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) { | if (0 != (rc = (*idx->get)(idx, &key, &val, 0))) { | ||
rc < 0 ? resp_baddb() : resp_error400(); | rc < 0 ? resp_baddb() : resp_error400(); | ||
goto out; | goto out; | ||
} | } else if (0 == val.size) { | ||
resp_baddb(); | |||
goto out; | |||
} | |||
cp = (char *)val.data; | cp = (char *)val.data; | ||
catm = 'c' == *cp++; | |||
if (NULL == (fn = memchr(cp, '\0', val.size))) | if (NULL == memchr(cp, '\0', val.size - 1)) | ||
resp_baddb(); | resp_baddb(); | ||
else if (++fn - cp >= (int)val.size) | |||
resp_baddb(); | |||
else if (NULL == memchr(fn, '\0', val.size - (fn - cp))) | |||
resp_baddb(); | |||
else { | else { | ||
strlcpy(file, ps->paths[vol], MAXPATHLEN); | file[(int)sz] = '\0'; | ||
strlcat(file, "/", MAXPATHLEN); | strlcat(file, "/", PATH_MAX); | ||
strlcat(file, fn, MAXPATHLEN); | strlcat(file, cp, PATH_MAX); | ||
if (0 == strcmp(cp, "cat")) | if (catm) | ||
catman(file); | catman(req, file); | ||
else | else | ||
format(file); | format(req, file); | ||
} | } | ||
out: | out: | ||
(*idx->close)(idx); | if (idx) | ||
(*idx->close)(idx); | |||
manpath_free(&ps); | |||
} | } | ||
static void | static void | ||
pg_search(const struct manpaths *ps, const struct req *req, char *path) | pg_search(const struct req *req, char *path) | ||
{ | { | ||
size_t tt; | size_t tt, ressz; | ||
int i, sz, rc, whatis; | struct manpaths ps; | ||
int i, sz, rc; | |||
const char *ep, *start; | const char *ep, *start; | ||
struct res *res; | |||
char **cp; | char **cp; | ||
struct opts opt; | struct opts opt; | ||
struct expr *expr; | struct expr *expr; | ||
expr = NULL; | if (req->q.manroot < 0 || 0 == req->psz) { | ||
cp = NULL; | resp_search(NULL, 0, (void *)req); | ||
ep = NULL; | return; | ||
sz = 0; | } | ||
whatis = 0; | |||
memset(&opt, 0, sizeof(struct opts)); | memset(&opt, 0, sizeof(struct opts)); | ||
for (sz = i = 0; i < (int)req->fieldsz; i++) | ep = req->q.expr; | ||
if (0 == strcmp(req->fields[i].key, "expr")) | opt.arch = req->q.arch; | ||
ep = req->fields[i].val; | opt.cat = req->q.sec; | ||
else if (0 == strcmp(req->fields[i].key, "query")) | rc = -1; | ||
ep = req->fields[i].val; | sz = 0; | ||
else if (0 == strcmp(req->fields[i].key, "sec")) | cp = NULL; | ||
opt.cat = req->fields[i].val; | ressz = 0; | ||
else if (0 == strcmp(req->fields[i].key, "sektion")) | res = NULL; | ||
opt.cat = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "arch")) | |||
opt.arch = req->fields[i].val; | |||
else if (0 == strcmp(req->fields[i].key, "apropos")) | |||
whatis = 0 == strcmp | |||
(req->fields[i].val, "0"); | |||
else if (0 == strcmp(req->fields[i].key, "op")) | |||
whatis = 0 == strcasecmp | |||
(req->fields[i].val, "whatis"); | |||
if (NULL != opt.cat && 0 == strcmp(opt.cat, "0")) | /* | ||
opt.cat = NULL; | * Begin by chdir()ing into the root of the manpath. | ||
* This way we can pick up the database files, which are | |||
* relative to the manpath root. | |||
*/ | |||
assert(req->q.manroot < (int)req->psz); | |||
if (-1 == (chdir(req->p[req->q.manroot].path))) { | |||
perror(req->p[req->q.manroot].path); | |||
resp_search(NULL, 0, (void *)req); | |||
return; | |||
} | |||
memset(&ps, 0, sizeof(struct manpaths)); | |||
manpath_manconf(&ps, "etc/catman.conf"); | |||
/* | /* | ||
* Poor man's tokenisation. | * Poor man's tokenisation: just break apart by spaces. | ||
* Just break apart by spaces. | |||
* Yes, this is half-ass. But it works for now. | * Yes, this is half-ass. But it works for now. | ||
*/ | */ | ||
|
|
||
ep++; | ep++; | ||
} | } | ||
rc = -1; | |||
/* | /* | ||
* Pump down into apropos backend. | * Pump down into apropos backend. | ||
* The resp_search() function is called with the results. | * The resp_search() function is called with the results. | ||
*/ | */ | ||
expr = whatis ? termcomp(sz, cp, &tt) : | expr = req->q.legacy ? | ||
exprcomp(sz, cp, &tt); | termcomp(sz, cp, &tt) : exprcomp(sz, cp, &tt); | ||
if (NULL != expr) | if (NULL != expr) | ||
rc = apropos_search | rc = apropos_search | ||
(ps->sz, ps->paths, &opt, | (ps.sz, ps.paths, &opt, expr, tt, | ||
expr, tt, (void *)req, resp_search); | (void *)req, &ressz, &res, resp_search); | ||
/* ...unless errors occured. */ | /* ...unless errors occured. */ | ||
if (0 == rc) | if (0 == rc) | ||
resp_baddb(); | resp_baddb(); | ||
else if (-1 == rc) | else if (-1 == rc) | ||
resp_search(NULL, 0, (void *)req); | resp_search(NULL, 0, NULL); | ||
for (i = 0; i < sz; i++) | for (i = 0; i < sz; i++) | ||
free(cp[i]); | free(cp[i]); | ||
free(cp); | free(cp); | ||
resfree(res, ressz); | |||
exprfree(expr); | exprfree(expr); | ||
manpath_free(&ps); | |||
} | } | ||
int | int | ||
main(void) | main(void) | ||
{ | { | ||
int i; | int i; | ||
char buf[PATH_MAX]; | |||
DIR *cwd; | |||
struct req req; | struct req req; | ||
char *p, *path, *subpath; | char *p, *path, *subpath; | ||
struct manpaths paths; | |||
/* HTTP init: read and parse the query string. */ | /* Scan our run-time environment. */ | ||
progname = getenv("SCRIPT_NAME"); | if (NULL == (cache = getenv("CACHE_DIR"))) | ||
if (NULL == progname) | cache = "/cache/man.cgi"; | ||
if (NULL == (progname = getenv("SCRIPT_NAME"))) | |||
progname = ""; | progname = ""; | ||
cache = getenv("CACHE_DIR"); | if (NULL == (css = getenv("CSS_DIR"))) | ||
if (NULL == cache) | css = ""; | ||
cache = "/cache/man.cgi"; | |||
if (NULL == (host = getenv("HTTP_HOST"))) | |||
host = "localhost"; | |||
/* | |||
* First we change directory into the cache directory so that | |||
* subsequent scanning for manpath directories is rooted | |||
* relative to the same position. | |||
*/ | |||
if (-1 == chdir(cache)) { | if (-1 == chdir(cache)) { | ||
perror(cache); | |||
resp_bad(); | resp_bad(); | ||
return(EXIT_FAILURE); | return(EXIT_FAILURE); | ||
} | } else if (NULL == (cwd = opendir(cache))) { | ||
perror(cache); | |||
resp_bad(); | |||
return(EXIT_FAILURE); | |||
} | |||
host = getenv("HTTP_HOST"); | |||
if (NULL == host) | |||
host = "localhost"; | |||
memset(&req, 0, sizeof(struct req)); | memset(&req, 0, sizeof(struct req)); | ||
strlcpy(buf, ".", PATH_MAX); | |||
pathgen(cwd, buf, &req); | |||
closedir(cwd); | |||
/* Next parse out the query string. */ | |||
if (NULL != (p = getenv("QUERY_STRING"))) | if (NULL != (p = getenv("QUERY_STRING"))) | ||
kval_parse(&req.fields, &req.fieldsz, p); | http_parse(&req, p); | ||
/* Resolve leading subpath component. */ | /* | ||
* Now juggle paths to extract information. | |||
* We want to extract our filetype (the file suffix), the | |||
* initial path component, then the trailing component(s). | |||
* Start with leading subpath component. | |||
*/ | |||
subpath = path = NULL; | subpath = path = NULL; | ||
req.page = PAGE__MAX; | req.page = PAGE__MAX; | ||
|
|
||
break; | break; | ||
} | } | ||
/* Initialise MANPATH. */ | |||
memset(&paths, 0, sizeof(struct manpaths)); | |||
manpath_manconf("etc/catman.conf", &paths); | |||
/* Route pages. */ | /* Route pages. */ | ||
switch (req.page) { | switch (req.page) { | ||
case (PAGE_INDEX): | case (PAGE_INDEX): | ||
pg_index(&paths, &req, subpath); | pg_index(&req, subpath); | ||
break; | break; | ||
case (PAGE_SEARCH): | case (PAGE_SEARCH): | ||
pg_search(&paths, &req, subpath); | pg_search(&req, subpath); | ||
break; | break; | ||
case (PAGE_SHOW): | case (PAGE_SHOW): | ||
pg_show(&paths, &req, subpath); | pg_show(&req, subpath); | ||
break; | break; | ||
default: | default: | ||
resp_error404(path); | resp_error404(path); | ||
break; | break; | ||
} | } | ||
manpath_free(&paths); | for (i = 0; i < (int)req.psz; i++) { | ||
kval_free(req.fields, req.fieldsz); | free(req.p[i].path); | ||
free(req.p[i].name); | |||
} | |||
free(req.p); | |||
return(EXIT_SUCCESS); | return(EXIT_SUCCESS); | ||
} | |||
static int | |||
cmp(const void *p1, const void *p2) | |||
{ | |||
return(strcasecmp(((const struct res *)p1)->title, | |||
((const struct res *)p2)->title)); | |||
} | |||
/* | |||
* Check to see if an "etc" path consists of a catman.conf file. If it | |||
* does, that means that the path contains a tree created by catman(8) | |||
* and should be used for indexing. | |||
*/ | |||
static int | |||
pathstop(DIR *dir) | |||
{ | |||
struct dirent *d; | |||
while (NULL != (d = readdir(dir))) | |||
if (DT_REG == d->d_type) | |||
if (0 == strcmp(d->d_name, "catman.conf")) | |||
return(1); | |||
return(0); | |||
} | |||
/* | |||
* Scan for indexable paths. | |||
* This adds all paths with "etc/catman.conf" to the buffer. | |||
*/ | |||
static void | |||
pathgen(DIR *dir, char *path, struct req *req) | |||
{ | |||
struct dirent *d; | |||
char *cp; | |||
DIR *cd; | |||
int rc; | |||
size_t sz, ssz; | |||
sz = strlcat(path, "/", PATH_MAX); | |||
if (sz >= PATH_MAX) { | |||
fprintf(stderr, "%s: Path too long", path); | |||
return; | |||
} | |||
/* | |||
* First, scan for the "etc" directory. | |||
* If it's found, then see if it should cause us to stop. This | |||
* happens when a catman.conf is found in the directory. | |||
*/ | |||
rc = 0; | |||
while (0 == rc && NULL != (d = readdir(dir))) { | |||
if (DT_DIR != d->d_type || strcmp(d->d_name, "etc")) | |||
continue; | |||
path[(int)sz] = '\0'; | |||
ssz = strlcat(path, d->d_name, PATH_MAX); | |||
if (ssz >= PATH_MAX) { | |||
fprintf(stderr, "%s: Path too long", path); | |||
return; | |||
} else if (NULL == (cd = opendir(path))) { | |||
perror(path); | |||
return; | |||
} | |||
rc = pathstop(cd); | |||
closedir(cd); | |||
} | |||
if (rc > 0) { | |||
/* This also strips the trailing slash. */ | |||
path[(int)--sz] = '\0'; | |||
req->p = mandoc_realloc | |||
(req->p, | |||
(req->psz + 1) * sizeof(struct paths)); | |||
/* | |||
* Strip out the leading "./" unless we're just a ".", | |||
* in which case use an empty string as our name. | |||
*/ | |||
req->p[(int)req->psz].path = mandoc_strdup(path); | |||
req->p[(int)req->psz].name = | |||
cp = mandoc_strdup(path + (1 == sz ? 1 : 2)); | |||
req->psz++; | |||
/* | |||
* The name is just the path with all the slashes taken | |||
* out of it. Simple but effective. | |||
*/ | |||
for ( ; '\0' != *cp; cp++) | |||
if ('/' == *cp) | |||
*cp = ' '; | |||
return; | |||
} | |||
/* | |||
* If no etc/catman.conf was found, recursively enter child | |||
* directory and continue scanning. | |||
*/ | |||
rewinddir(dir); | |||
while (NULL != (d = readdir(dir))) { | |||
if (DT_DIR != d->d_type || '.' == d->d_name[0]) | |||
continue; | |||
path[(int)sz] = '\0'; | |||
ssz = strlcat(path, d->d_name, PATH_MAX); | |||
if (ssz >= PATH_MAX) { | |||
fprintf(stderr, "%s: Path too long", path); | |||
return; | |||
} else if (NULL == (cd = opendir(path))) { | |||
perror(path); | |||
return; | |||
} | |||
pathgen(cd, path, req); | |||
closedir(cd); | |||
} | |||
} | } |