===================================================================
RCS file: /cvs/mandoc/cgi.c,v
retrieving revision 1.62
retrieving revision 1.131
diff -u -p -r1.62 -r1.131
--- mandoc/cgi.c 2014/07/11 22:25:44 1.62
+++ mandoc/cgi.c 2016/04/29 10:45:36 1.131
@@ -1,103 +1,132 @@
-/* $Id: cgi.c,v 1.62 2014/07/11 22:25:44 schwarze Exp $ */
+/* $Id: cgi.c,v 1.131 2016/04/29 10:45:36 schwarze Exp $ */
/*
* Copyright (c) 2011, 2012 Kristaps Dzonsons
- * Copyright (c) 2014 Ingo Schwarze
+ * Copyright (c) 2014, 2015, 2016 Ingo Schwarze
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
+#include
+#include
+
#include
+#include
#include
#include
#include
+#include
#include
#include
#include
#include
-#include "mandoc.h"
#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "mdoc.h"
+#include "man.h"
#include "main.h"
-#include "manpath.h"
+#include "manconf.h"
#include "mansearch.h"
+#include "cgi.h"
-enum page {
- PAGE_INDEX,
- PAGE_SEARCH,
- PAGE_SHOW,
- PAGE__MAX
-};
-
/*
* A query as passed to the search function.
*/
struct query {
- const char *manpath; /* desired manual directory */
- const char *arch; /* architecture */
- const char *sec; /* manual section */
- const char *expr; /* unparsed expression string */
- int legacy; /* whether legacy mode */
+ char *manpath; /* desired manual directory */
+ char *arch; /* architecture */
+ char *sec; /* manual section */
+ char *query; /* unparsed query expression */
+ int equal; /* match whole names, not substrings */
};
struct req {
struct query q;
char **p; /* array of available manpaths */
size_t psz; /* number of available manpaths */
- enum page page;
+ int isquery; /* QUERY_STRING used, not PATH_INFO */
};
-static void catman(const struct req *, const char *);
-static int cmp(const void *, const void *);
-static void format(const struct req *, const char *);
+enum focus {
+ FOCUS_NONE = 0,
+ FOCUS_QUERY
+};
+
static void html_print(const char *);
-static void html_printquery(const struct req *);
static void html_putchar(char);
-static int http_decode(char *);
-static void http_parse(struct req *, char *);
-static void http_print(const char *);
-static void http_putchar(char);
-static void http_printquery(const struct req *);
-static void pathgen(struct req *);
-static void pg_index(const struct req *, char *);
-static void pg_search(const struct req *, char *);
-static void pg_show(const struct req *, char *);
+static int http_decode(char *);
+static void parse_manpath_conf(struct req *);
+static void parse_path_info(struct req *req, const char *path);
+static void parse_query_string(struct req *, const char *);
+static void pg_error_badrequest(const char *);
+static void pg_error_internal(void);
+static void pg_index(const struct req *);
+static void pg_noresult(const struct req *, const char *);
+static void pg_search(const struct req *);
+static void pg_searchres(const struct req *,
+ struct manpage *, size_t);
+static void pg_show(struct req *, const char *);
static void resp_begin_html(int, const char *);
static void resp_begin_http(int, const char *);
+static void resp_catman(const struct req *, const char *);
+static void resp_copy(const char *);
static void resp_end_html(void);
-static void resp_error_badrequest(const char *);
-static void resp_error_internal(void);
-static void resp_error_notfound(const char *);
-static void resp_index(const struct req *);
-static void resp_noresult(const struct req *,
- const char *);
-static void resp_search(const struct req *,
- struct manpage *, size_t);
-static void resp_searchform(const struct req *);
+static void resp_format(const struct req *, const char *);
+static void resp_searchform(const struct req *, enum focus);
+static void resp_show(const struct req *, const char *);
+static void set_query_attr(char **, char **);
+static int validate_filename(const char *);
+static int validate_manpath(const struct req *, const char *);
+static int validate_urifrag(const char *);
-static const char *scriptname; /* CGI script name */
-static const char *mandir; /* contains all manpath directories */
-static const char *cssdir; /* css directory */
-static const char *httphost; /* hostname used in the URIs */
+static const char *scriptname = SCRIPT_NAME;
-static const char * const pages[PAGE__MAX] = {
- "index", /* PAGE_INDEX */
- "search", /* PAGE_SEARCH */
- "show", /* PAGE_SHOW */
+static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
+static const char *const sec_numbers[] = {
+ "0", "1", "2", "3", "3p", "4", "5", "6", "7", "8", "9"
};
+static const char *const sec_names[] = {
+ "All Sections",
+ "1 - General Commands",
+ "2 - System Calls",
+ "3 - Library Functions",
+ "3p - Perl Library",
+ "4 - Device Drivers",
+ "5 - File Formats",
+ "6 - Games",
+ "7 - Miscellaneous Information",
+ "8 - System Manager\'s Manual",
+ "9 - Kernel Developer\'s Manual"
+};
+static const int sec_MAX = sizeof(sec_names) / sizeof(char *);
+static const char *const arch_names[] = {
+ "amd64", "alpha", "armish", "armv7",
+ "hppa", "hppa64", "i386", "landisk",
+ "loongson", "luna88k", "macppc", "mips64",
+ "octeon", "sgi", "socppc", "sparc",
+ "sparc64", "zaurus",
+ "amiga", "arc", "arm32", "atari",
+ "aviion", "beagle", "cats", "hp300",
+ "ia64", "mac68k", "mvme68k", "mvme88k",
+ "mvmeppc", "palm", "pc532", "pegasos",
+ "pmax", "powerpc", "solbourne", "sun3",
+ "vax", "wgrisc", "x68k"
+};
+static const int arch_MAX = sizeof(arch_names) / sizeof(char *);
+
/*
* Print a character, escaping HTML along the way.
* This will pass non-ASCII straight to output: be warned!
@@ -125,154 +154,125 @@ html_putchar(char c)
}
}
+/*
+ * Call through to html_putchar().
+ * Accepts NULL strings.
+ */
static void
-http_printquery(const struct req *req)
+html_print(const char *p)
{
- if (NULL != req->q.manpath) {
- printf("&manpath=");
- http_print(req->q.manpath);
- }
- if (NULL != req->q.sec) {
- printf("&sec=");
- http_print(req->q.sec);
- }
- if (NULL != req->q.arch) {
- printf("&arch=");
- http_print(req->q.arch);
- }
- if (NULL != req->q.expr) {
- printf("&expr=");
- http_print(req->q.expr ? req->q.expr : "");
- }
-}
-
-static void
-html_printquery(const struct req *req)
-{
-
- if (NULL != req->q.manpath) {
- printf("&manpath=");
- html_print(req->q.manpath);
- }
- if (NULL != req->q.sec) {
- printf("&sec=");
- html_print(req->q.sec);
- }
- if (NULL != req->q.arch) {
- printf("&arch=");
- html_print(req->q.arch);
- }
- if (NULL != req->q.expr) {
- printf("&expr=");
- html_print(req->q.expr ? req->q.expr : "");
- }
-}
-
-static void
-http_print(const char *p)
-{
-
if (NULL == p)
return;
while ('\0' != *p)
- http_putchar(*p++);
+ html_putchar(*p++);
}
/*
- * Call through to html_putchar().
- * Accepts NULL strings.
+ * Transfer the responsibility for the allocated string *val
+ * to the query structure.
*/
static void
-html_print(const char *p)
+set_query_attr(char **attr, char **val)
{
-
- if (NULL == p)
- return;
- while ('\0' != *p)
- html_putchar(*p++);
+
+ free(*attr);
+ if (**val == '\0') {
+ *attr = NULL;
+ free(*val);
+ } else
+ *attr = *val;
+ *val = NULL;
}
/*
- * Parse out key-value pairs from an HTTP request variable.
- * This can be either a cookie or a POST/GET string, although man.cgi
- * uses only GET for simplicity.
+ * Parse the QUERY_STRING for key-value pairs
+ * and store the values into the query structure.
*/
static void
-http_parse(struct req *req, char *p)
+parse_query_string(struct req *req, const char *qs)
{
- char *key, *val;
- int legacy;
+ char *key, *val;
+ size_t keysz, valsz;
- memset(&req->q, 0, sizeof(struct query));
- req->q.manpath = req->p[0];
+ req->isquery = 1;
+ req->q.manpath = NULL;
+ req->q.arch = NULL;
+ req->q.sec = NULL;
+ req->q.query = NULL;
+ req->q.equal = 1;
- legacy = -1;
- while ('\0' != *p) {
- key = p;
- val = NULL;
+ key = val = NULL;
+ while (*qs != '\0') {
- p += (int)strcspn(p, ";&");
- if ('\0' != *p)
- *p++ = '\0';
- if (NULL != (val = strchr(key, '=')))
- *val++ = '\0';
+ /* Parse one key. */
- if ('\0' == *key || NULL == val || '\0' == *val)
- continue;
+ keysz = strcspn(qs, "=;&");
+ key = mandoc_strndup(qs, keysz);
+ qs += keysz;
+ if (*qs != '=')
+ goto next;
- /* Just abort handling. */
+ /* Parse one value. */
- if ( ! http_decode(key))
- break;
- if (NULL != val && ! http_decode(val))
- break;
+ valsz = strcspn(++qs, ";&");
+ val = mandoc_strndup(qs, valsz);
+ qs += valsz;
- if (0 == strcmp(key, "expr"))
- 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"))
- req->q.manpath = val;
- else if (0 == strcmp(key, "apropos"))
- legacy = 0 == strcmp(val, "0");
- }
+ /* Decode and catch encoding errors. */
- /* Test for old man.cgi compatibility mode. */
+ if ( ! (http_decode(key) && http_decode(val)))
+ goto next;
- req->q.legacy = legacy > 0;
+ /* Handle key-value pairs. */
- /*
- * Section "0" means no section when in legacy mode.
- * For some man.cgi scripts, "default" arch is none.
- */
+ if ( ! strcmp(key, "query"))
+ set_query_attr(&req->q.query, &val);
- if (req->q.legacy && NULL != req->q.sec)
- if (0 == strcmp(req->q.sec, "0"))
- req->q.sec = NULL;
- if (req->q.legacy && NULL != req->q.arch)
- if (0 == strcmp(req->q.arch, "default"))
- req->q.arch = NULL;
-}
+ else if ( ! strcmp(key, "apropos"))
+ req->q.equal = !strcmp(val, "0");
-static void
-http_putchar(char c)
-{
+ else if ( ! strcmp(key, "manpath")) {
+#ifdef COMPAT_OLDURI
+ if ( ! strncmp(val, "OpenBSD ", 8)) {
+ val[7] = '-';
+ if ('C' == val[8])
+ val[8] = 'c';
+ }
+#endif
+ set_query_attr(&req->q.manpath, &val);
+ }
- if (isalnum((unsigned char)c)) {
- putchar((unsigned char)c);
- return;
- } else if (' ' == c) {
- putchar('+');
- return;
+ else if ( ! (strcmp(key, "sec")
+#ifdef COMPAT_OLDURI
+ && strcmp(key, "sektion")
+#endif
+ )) {
+ if ( ! strcmp(val, "0"))
+ *val = '\0';
+ set_query_attr(&req->q.sec, &val);
+ }
+
+ else if ( ! strcmp(key, "arch")) {
+ if ( ! strcmp(val, "default"))
+ *val = '\0';
+ set_query_attr(&req->q.arch, &val);
+ }
+
+ /*
+ * The key must be freed in any case.
+ * The val may have been handed over to the query
+ * structure, in which case it is now NULL.
+ */
+next:
+ free(key);
+ key = NULL;
+ free(val);
+ val = NULL;
+
+ if (*qs != '\0')
+ qs++;
}
- printf("%%%.2x", c);
}
/*
@@ -284,29 +284,31 @@ static int
http_decode(char *p)
{
char hex[3];
+ char *q;
int c;
hex[2] = '\0';
- for ( ; '\0' != *p; p++) {
+ q = p;
+ for ( ; '\0' != *p; p++, q++) {
if ('%' == *p) {
if ('\0' == (hex[0] = *(p + 1)))
- return(0);
+ return 0;
if ('\0' == (hex[1] = *(p + 2)))
- return(0);
+ return 0;
if (1 != sscanf(hex, "%x", &c))
- return(0);
+ return 0;
if ('\0' == c)
- return(0);
+ return 0;
- *p = (char)c;
- memmove(p + 1, p + 3, strlen(p + 3) + 1);
+ *q = (char)c;
+ p += 2;
} else
- *p = '+' == *p ? ' ' : *p;
+ *q = '+' == *p ? ' ' : *p;
}
- *p = '\0';
- return(1);
+ *q = '\0';
+ return 1;
}
static void
@@ -325,235 +327,373 @@ resp_begin_http(int code, const char *msg)
}
static void
+resp_copy(const char *filename)
+{
+ char buf[4096];
+ ssize_t sz;
+ int fd;
+
+ if ((fd = open(filename, O_RDONLY)) != -1) {
+ fflush(stdout);
+ while ((sz = read(fd, buf, sizeof(buf))) > 0)
+ write(STDOUT_FILENO, buf, sz);
+ }
+}
+
+static void
resp_begin_html(int code, const char *msg)
{
resp_begin_http(code, msg);
- printf("\n"
- "\n"
- "\n"
- "\n"
- "\n"
- "\n"
- "System Manpage Reference\n"
- "\n"
- "\n"
+ printf("\n"
+ "\n"
+ "\n"
+ "\n"
+ "\n"
+ "%s\n"
+ "\n"
+ "\n"
"\n",
- cssdir, cssdir);
+ CSS_DIR, CUSTOMIZE_TITLE);
+
+ resp_copy(MAN_DIR "/header.html");
}
static void
resp_end_html(void)
{
- puts("\n"
- "");
+ resp_copy(MAN_DIR "/footer.html");
+
+ puts("\n"
+ "");
}
static void
-resp_searchform(const struct req *req)
+resp_searchform(const struct req *req, enum focus focus)
{
int i;
puts("");
- printf("");
puts("");
}
+static int
+validate_urifrag(const char *frag)
+{
+
+ while ('\0' != *frag) {
+ if ( ! (isalnum((unsigned char)*frag) ||
+ '-' == *frag || '.' == *frag ||
+ '/' == *frag || '_' == *frag))
+ return 0;
+ frag++;
+ }
+ return 1;
+}
+
+static int
+validate_manpath(const struct req *req, const char* manpath)
+{
+ size_t i;
+
+ if ( ! strcmp(manpath, "mandoc"))
+ return 1;
+
+ for (i = 0; i < req->psz; i++)
+ if ( ! strcmp(manpath, req->p[i]))
+ return 1;
+
+ return 0;
+}
+
+static int
+validate_filename(const char *file)
+{
+
+ if ('.' == file[0] && '/' == file[1])
+ file += 2;
+
+ return ! (strstr(file, "../") || strstr(file, "/..") ||
+ (strncmp(file, "man", 3) && strncmp(file, "cat", 3)));
+}
+
static void
-resp_index(const struct req *req)
+pg_index(const struct req *req)
{
resp_begin_html(200, NULL);
- puts("\n"
- "Online manuals with "
- "mandoc\n"
- "
");
- resp_searchform(req);
- puts("\n"
- "This web interface is documented in the "
- ""
- "man.cgi manual, and the "
- ""
- "apropos manual explains the query syntax.\n"
- "
");
+ resp_searchform(req, FOCUS_QUERY);
+ printf("\n"
+ "This web interface is documented in the\n"
+ "man.cgi\n"
+ "manual, and the\n"
+ "apropos\n"
+ "manual explains the query syntax.\n"
+ "
\n",
+ scriptname, *scriptname == '\0' ? "" : "/",
+ scriptname, *scriptname == '\0' ? "" : "/");
resp_end_html();
}
static void
-resp_noresult(const struct req *req, const char *msg)
+pg_noresult(const struct req *req, const char *msg)
{
resp_begin_html(200, NULL);
- resp_searchform(req);
- puts("");
+ resp_searchform(req, FOCUS_QUERY);
+ puts("
");
puts(msg);
- puts("
");
+ puts("
");
resp_end_html();
}
static void
-resp_error_badrequest(const char *msg)
+pg_error_badrequest(const char *msg)
{
resp_begin_html(400, "Bad Request");
- puts("Bad Request
\n"
- "\n");
+ puts("
Bad Request
\n"
+ "\n");
puts(msg);
printf("Try again from the\n"
- "main page.\n"
- "
", scriptname);
+ "main page.\n"
+ "", scriptname);
resp_end_html();
}
static void
-resp_error_notfound(const char *page)
+pg_error_internal(void)
{
-
- resp_begin_html(404, "Not Found");
- puts("Page Not Found
\n"
- "\n"
- "The page you're looking for, ");
- printf("");
- html_print(page);
- printf(",\n"
- "could not be found.\n"
- "Try searching from the\n"
- "main page.\n"
- "
", scriptname);
- resp_end_html();
-}
-
-static void
-resp_error_internal(void)
-{
resp_begin_html(500, "Internal Server Error");
- puts("Internal Server Error
");
+ puts("Internal Server Error
");
resp_end_html();
}
static void
-resp_search(const struct req *req, struct manpage *r, size_t sz)
+pg_searchres(const struct req *req, struct manpage *r, size_t sz)
{
- size_t i;
+ char *arch, *archend;
+ const char *sec;
+ size_t i, iuse;
+ int archprio, archpriouse;
+ int prio, priouse;
- if (1 == sz) {
+ for (i = 0; i < sz; i++) {
+ if (validate_filename(r[i].file))
+ continue;
+ warnx("invalid filename %s in %s database",
+ r[i].file, req->q.manpath);
+ pg_error_internal();
+ return;
+ }
+
+ if (req->isquery && sz == 1) {
/*
* If we have just one result, then jump there now
* without any delay.
*/
printf("Status: 303 See Other\r\n");
- printf("Location: http://%s%s/show/%s/%s?",
- httphost, scriptname, req->q.manpath, r[0].file);
- http_printquery(req);
+ printf("Location: http://%s/%s%s%s/%s",
+ HTTP_HOST, scriptname,
+ *scriptname == '\0' ? "" : "/",
+ req->q.manpath, r[0].file);
printf("\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"\r\n");
return;
}
- qsort(r, sz, sizeof(struct manpage), cmp);
-
resp_begin_html(200, NULL);
- resp_searchform(req);
- puts("");
- resp_end_html();
-}
+ /*
+ * In man(1) mode, show one of the pages
+ * even if more than one is found.
+ */
-/* ARGSUSED */
-static void
-pg_index(const struct req *req, char *path)
-{
+ if (req->q.equal || sz == 1) {
+ puts("
");
+ iuse = 0;
+ priouse = 20;
+ archpriouse = 3;
+ for (i = 0; i < sz; i++) {
+ sec = r[i].file;
+ sec += strcspn(sec, "123456789");
+ if (sec[0] == '\0')
+ continue;
+ prio = sec_prios[sec[0] - '1'];
+ if (sec[1] != '/')
+ prio += 10;
+ if (req->q.arch == NULL) {
+ archprio =
+ ((arch = strchr(sec + 1, '/'))
+ == NULL) ? 3 :
+ ((archend = strchr(arch + 1, '/'))
+ == NULL) ? 0 :
+ strncmp(arch, "amd64/",
+ archend - arch) ? 2 : 1;
+ if (archprio < archpriouse) {
+ archpriouse = archprio;
+ priouse = prio;
+ iuse = i;
+ continue;
+ }
+ if (archprio > archpriouse)
+ continue;
+ }
+ if (prio >= priouse)
+ continue;
+ priouse = prio;
+ iuse = i;
+ }
+ resp_show(req, r[iuse].file);
+ }
- resp_index(req);
+ resp_end_html();
}
static void
-catman(const struct req *req, const char *file)
+resp_catman(const struct req *req, const char *file)
{
FILE *f;
- size_t len;
- int i;
char *p;
+ size_t sz;
+ ssize_t len;
+ int i;
int italic, bold;
- if (NULL == (f = fopen(file, "r"))) {
- resp_error_badrequest(
- "You specified an invalid manual file.");
+ if ((f = fopen(file, "r")) == NULL) {
+ puts("You specified an invalid manual file.
");
return;
}
- resp_begin_html(200, NULL);
- resp_searchform(req);
- puts("\n"
- "
");
+ puts("\n"
+ "
");
- while (NULL != (p = fgetln(f, &len))) {
+ p = NULL;
+ sz = 0;
+
+ while ((len = getline(&p, &sz, f)) != -1) {
bold = italic = 0;
- for (i = 0; i < (int)len - 1; i++) {
- /*
+ for (i = 0; i < len - 1; i++) {
+ /*
* This means that the catpage is out of state.
* Ignore it and keep going (although the
* catpage is bogus).
@@ -571,22 +711,22 @@ catman(const struct req *req, const char *file)
if ('\b' != p[i + 1]) {
if (italic)
- printf("");
+ printf("");
if (bold)
- printf("");
+ printf("");
italic = bold = 0;
html_putchar(p[i]);
continue;
- } else if (i + 2 >= (int)len)
+ } else if (i + 2 >= len)
continue;
/* Italic mode. */
if ('_' == p[i]) {
if (bold)
- printf("");
+ printf("");
if ( ! italic)
- printf("");
+ printf("");
bold = 0;
italic = 1;
i += 2;
@@ -594,7 +734,7 @@ catman(const struct req *req, const char *file)
continue;
}
- /*
+ /*
* Handle funny behaviour troff-isms.
* These grok'd from the original man2html.c.
*/
@@ -608,9 +748,9 @@ catman(const struct req *req, const char *file)
('*' == p[i] && '|' == p[i + 2]) ||
('|' == p[i] && '*' == p[i + 2])) {
if (italic)
- printf("");
+ printf("");
if (bold)
- printf("");
+ printf("");
italic = bold = 0;
putchar('*');
i += 2;
@@ -622,9 +762,9 @@ catman(const struct req *req, const char *file)
('+' == p[i] && '|' == p[i + 1]) ||
('|' == p[i] && '+' == p[i + 1])) {
if (italic)
- printf("");
+ printf("");
if (bold)
- printf("");
+ printf("");
italic = bold = 0;
putchar('+');
i += 2;
@@ -632,140 +772,166 @@ catman(const struct req *req, const char *file)
}
/* Bold mode. */
-
+
if (italic)
- printf("");
+ printf("");
if ( ! bold)
- printf("");
+ printf("");
bold = 1;
italic = 0;
i += 2;
html_putchar(p[i]);
}
- /*
+ /*
* Clean up the last character.
- * We can get to a newline; don't print that.
+ * We can get to a newline; don't print that.
*/
if (italic)
- printf("");
+ printf("");
if (bold)
- printf("");
+ printf("");
- if (i == (int)len - 1 && '\n' != p[i])
+ if (i == len - 1 && p[i] != '\n')
html_putchar(p[i]);
putchar('\n');
}
+ free(p);
- puts("
\n"
- "
\n"
- "