summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am3
-rw-r--r--configure.ac19
-rw-r--r--fileserv.c36
-rw-r--r--mimetypes/.gitignore4
-rw-r--r--mimetypes/Makefile.am18
-rw-r--r--mimetypes/err.c150
-rw-r--r--mimetypes/eval.c422
-rw-r--r--mimetypes/grammar.y397
-rw-r--r--mimetypes/ident.c85
-rw-r--r--mimetypes/lexer.l371
-rw-r--r--mimetypes/linetrack.c380
-rw-r--r--mimetypes/locus.c95
-rw-r--r--mimetypes/locus.h104
-rw-r--r--mimetypes/mimetypes.h3
-rw-r--r--mimetypes/mtint.h169
-rw-r--r--mimetypes/prloc.c172
-rw-r--r--mimetypes/tests/.gitignore3
-rw-r--r--mimetypes/tests/Makefile.am69
-rw-r--r--mimetypes/tests/atlocal.in5
-rw-r--r--mimetypes/tests/mimetest.c23
-rw-r--r--mimetypes/tests/testsuite.at1
-rw-r--r--mimetypes/yyloc.h40
23 files changed, 2563 insertions, 7 deletions
diff --git a/.gitignore b/.gitignore
index 8c4c158..ca98c03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ fileserv
fileserv-0.1.tar.xz
install-sh
missing
+ylwrap
diff --git a/Makefile.am b/Makefile.am
index 5a3428f..cf663e3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,3 +5,6 @@ if FSRV_WRAP
endif
dist_man_MANS=fileserv.8
EXTRA_DIST=README
+SUBDIRS = . mimetypes
+LDADD = ./mimetypes/libmimetypes.a
+AM_CPPFLAGS = -I $(top_srcdir)/mimetypes
diff --git a/configure.ac b/configure.ac
index 89d4099..f1b43ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
# -*- Autoconf -*-
# This file is part of fileserv.
-# Copyright (C) 2017 Sergey Poznyakoff
+# Copyright (C) 2017, 2018 Sergey Poznyakoff
#
# Fileserv is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -23,6 +23,17 @@ AM_SILENT_RULES([yes])
# Checks for programs.
AC_PROG_CC
AC_PROG_RANLIB
+AC_PROG_YACC
+if ! $YACC --version 2>/dev/null | grep -q '^bison '; then
+ YACC="$SHELL $missing_dir/missing bison"
+fi
+AC_PROG_LEX
+if ! $LEX --version 2>/dev/null | grep -q '^flex '; then
+ LEX="$SHELL $missing_dir/missing flex"
+ AC_SUBST([LEX_OUTPUT_ROOT], [lex.yy])
+ AC_SUBST([LEXLIB], [''])
+fi
+
AC_CHECK_LIB([microhttpd],[MHD_start_daemon],[],
[AC_MSG_ERROR([required library microhttpd not found
@@ -31,6 +42,10 @@ AC_CHECK_LIB([pthread],[pthread_sigmask])
AC_CHECK_LIB([wrap], [main])
AC_CHECK_LIB([nsl], [main])
AM_CONDITIONAL([FSRV_WRAP],[test x$ac_cv_lib_wrap_main = xyes])
-AC_CONFIG_FILES([Makefile])
+
+AC_CONFIG_TESTDIR([mimetypes/tests])
+AC_CONFIG_FILES([mimetypes/tests/Makefile mimetypes/tests/atlocal])
+
+AC_CONFIG_FILES([Makefile mimetypes/Makefile])
AC_OUTPUT
diff --git a/fileserv.c b/fileserv.c
index f297844..b1e9aaf 100644
--- a/fileserv.c
+++ b/fileserv.c
@@ -1,5 +1,5 @@
/* This file is part of fileserv.
- Copyright (C) 2017 Sergey Poznyakoff
+ Copyright (C) 2017, 2018 Sergey Poznyakoff
Fileserv is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -35,6 +35,7 @@
#include <microhttpd.h>
#include "fileserv.h"
+#include "mimetypes.h"
char *progname;
int verbose; /* reserved for future use */
@@ -685,6 +686,7 @@ fileserv_handler(void *cls,
struct MHD_Response *response;
int ret;
int fd;
+ char const *type;
if (strcmp(method, MHD_HTTP_METHOD_GET) &&
strcmp(method, MHD_HTTP_METHOD_HEAD))
@@ -699,14 +701,16 @@ fileserv_handler(void *cls,
file_name = get_file_name(host, url);
if (!file_name)
return not_found(conn, method, url);
-
+
fd = open(file_name, O_RDONLY);
- free(file_name);
-
if (fd == -1) {
+ free(file_name);
return not_found(conn, method, url);
}
+ type = get_file_type(file_name);
+ free(file_name);
+
if (fstat(fd, &st) || !S_ISREG(st.st_mode)) {
close(fd);
return not_found(conn, method, url);
@@ -717,6 +721,12 @@ fileserv_handler(void *cls,
close(fd);
return MHD_NO;
}
+
+ if (type)
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ type);
+
ret = MHD_queue_response(conn, MHD_HTTP_OK, response);
MHD_destroy_response(response);
http_log(conn, method, url, MHD_HTTP_OK, &st);
@@ -724,6 +734,12 @@ fileserv_handler(void *cls,
return ret;
}
+static void
+fileserv_error_printer (char const *msg)
+{
+ error("%s", msg);
+}
+
int
main(int argc, char **argv)
{
@@ -742,14 +758,17 @@ main(int argc, char **argv)
SIGTERM,
0
};
+ char *mime_types_file = NULL;
p = strrchr(argv[0], '/');
if (p)
progname = p + 1;
else
progname = argv[0];
+
+ mimetypes_error_printer = fileserv_error_printer;
- while ((c = getopt(argc, argv, "a:F:fg:hp:t:x:u:v")) != EOF) {
+ while ((c = getopt(argc, argv, "a:F:fg:hm:p:t:x:u:v")) != EOF) {
switch (c) {
case 'a':
address = optarg;
@@ -766,6 +785,9 @@ main(int argc, char **argv)
case 'h':
usage();
exit(0);
+ case 'm':
+ mime_types_file = optarg;
+ break;
case 'p':
pidfile = optarg;
break;
@@ -787,6 +809,10 @@ main(int argc, char **argv)
}
pidfile_check();
+
+ if (mime_types_file) {
+ mimetypes_parse (mime_types_file); //FIXME: diag
+ }
fd = open_listener(address, &server_addr);
diff --git a/mimetypes/.gitignore b/mimetypes/.gitignore
new file mode 100644
index 0000000..5e34217
--- /dev/null
+++ b/mimetypes/.gitignore
@@ -0,0 +1,4 @@
+grammar.c
+grammar.h
+grammar.output
+lexer.c
diff --git a/mimetypes/Makefile.am b/mimetypes/Makefile.am
new file mode 100644
index 0000000..4b5d695
--- /dev/null
+++ b/mimetypes/Makefile.am
@@ -0,0 +1,18 @@
+noinst_LIBRARIES = libmimetypes.a
+libmimetypes_a_SOURCES = \
+ mimetypes.h\
+ mtint.h\
+ err.c\
+ eval.c\
+ ident.c\
+ grammar.y\
+ lexer.l\
+ linetrack.c\
+ locus.c\
+ locus.h\
+ prloc.c\
+ yyloc.h
+
+AM_YFLAGS=-vtd
+AM_LEXFLAGS=-d
+
diff --git a/mimetypes/err.c b/mimetypes/err.c
new file mode 100644
index 0000000..39f25a5
--- /dev/null
+++ b/mimetypes/err.c
@@ -0,0 +1,150 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include "mtint.h"
+
+ssize_t
+mimetypes_error_format (char **pbuf, size_t *psize,
+ struct locus_range const *lr, char const *fmt,
+ va_list ap)
+{
+ char *buf;
+ size_t buflen;
+ ssize_t n;
+ int rc;
+
+ if (lr)
+ {
+ n = locus_range_format (pbuf, psize, lr);
+ if (n == -1)
+ return -1;
+ buf = *pbuf;
+ buflen = *psize;
+ }
+ else
+ {
+ buf = *pbuf;
+ buflen = *psize;
+ if (buflen == 0)
+ buflen = 512; /* Initial allocation */
+
+ buf = calloc (1, buflen);
+ if (buf == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ for (;;)
+ {
+ va_list aq;
+
+ va_copy(aq, ap);
+ n = vsnprintf (buf, buflen, fmt, aq);
+ va_end(aq);
+
+ if (n < 0 || n >= buflen || !memchr (buf, '\0', n + 1))
+ {
+ char *newbuf;
+ size_t newlen = buflen * 2;
+ if (newlen < buflen)
+ {
+ errno = ENOMEM;
+ rc = -1;
+ break;
+ }
+ newbuf = realloc (buf, newlen);
+ if (newbuf == NULL)
+ {
+ errno = ENOMEM;
+ rc = -1;
+ break;
+ }
+ buflen = newlen;
+ buf = newbuf;
+ }
+ else
+ break;
+ }
+
+ if (rc)
+ {
+ if (!*pbuf)
+ {
+ /* We made first allocation, now free it */
+ free (buf);
+ buf = NULL;
+ buflen = 0;
+ }
+ }
+
+ *pbuf = buf;
+ *psize = buflen;
+ return rc;
+}
+
+
+static void
+default_error_printer (char const *msg)
+{
+ fprintf (stderr, "%s\n", msg);
+}
+
+void (*mimetypes_error_printer) (char const *) = default_error_printer;
+
+void
+mimetypes_error_at (struct locus_range const *lr, char const *fmt, ...)
+{
+ va_list ap;
+ char *buf = NULL;
+ size_t buflen = 0;
+ ssize_t n;
+
+ va_start (ap, fmt);
+ n = mimetypes_error_format (&buf, &buflen, lr, fmt, ap);
+ va_end (ap);
+
+ if (n < 0)
+ return;
+
+ mimetypes_error_printer (buf);
+
+ free (buf);
+}
+
+void
+mimetypes_error (char const *fmt, ...)
+{
+ va_list ap;
+ char *buf = NULL;
+ size_t buflen = 0;
+ ssize_t n;
+
+ va_start (ap, fmt);
+ n = mimetypes_error_format (&buf, &buflen, NULL, fmt, ap);
+ va_end (ap);
+
+ if (n < 0)
+ return;
+
+ mimetypes_error_printer (buf);
+
+ free (buf);
+}
+
+void
+print_locus_range (FILE *fp, struct locus_range const *lr)
+{
+ char *buf = NULL;
+ size_t buflen = 0;
+ ssize_t n;
+ n = locus_range_format (&buf, &buflen, lr);
+ if (n < 0)
+ return;
+ fwrite (buf, buflen, 1, fp);
+ free (buf);
+}
diff --git a/mimetypes/eval.c b/mimetypes/eval.c
new file mode 100644
index 0000000..2c00bcb
--- /dev/null
+++ b/mimetypes/eval.c
@@ -0,0 +1,422 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <fnmatch.h>
+#include <inttypes.h>
+#include <ctype.h>
+#include "mtint.h"
+
+static int eval_rule (struct node *root, struct filebuf const *file);
+
+/* match("pattern")
+ Pattern match on filename
+*/
+static int
+b_match (union argument *args, struct filebuf const *fb)
+{
+ return fnmatch (args[0].string.ptr, fb->name, 0) == 0;
+}
+
+/* ascii(offset,length)
+ True if bytes are valid printable ASCII (CR, NL, TAB,
+ BS, 32-126)
+*/
+#define ISASCII(c) ((c) &&\
+ (strchr ("\n\r\t\b",c) \
+ || (32<=((unsigned) c) && ((unsigned) c)<=126)))
+static int
+b_ascii (union argument *args, struct filebuf const *fb)
+{
+ int i;
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ for (i = 0; i < args[1].number; i++)
+ {
+ int c = fgetc (fb->fp);
+ if (c == EOF)
+ return 0;
+ if (!ISASCII (c))
+ return 0;
+ }
+
+ return 1;
+}
+
+/* printable(offset,length)
+ True if bytes are printable 8-bit chars (CR, NL, TAB,
+ BS, 32-126, 128-254)
+*/
+#define ISPRINT(c) (ISASCII (c) \
+ || (128<=((unsigned) c) && ((unsigned) c)<=254))
+static int
+b_printable (union argument *args, struct filebuf const *fb)
+{
+ int i;
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ for (i = 0; i < args[1].number; i++)
+ {
+ int c = fgetc (fb->fp);
+ if (c == EOF)
+ return 0;
+ if (!ISPRINT (c))
+ return 0;
+ }
+ return 1;
+}
+
+/* string(offset,"string")
+ True if bytes are identical to string
+*/
+static int
+b_string (union argument *args, struct filebuf const *fb)
+{
+ struct mimetypes_string const *str = &args[1].string;
+ int i;
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ for (i = 0; i < str->len; i++)
+ {
+ int c = fgetc (fb->fp);
+ if (c == EOF)
+ return 0;
+ if (c != str->ptr[i])
+ return 0;
+ }
+ return 1;
+}
+
+/* istring(offset,"string")
+ True if a case-insensitive comparison of the bytes is
+ identical
+*/
+static int
+b_istring (union argument *args, struct filebuf const *fb)
+{
+ int i;
+ struct mimetypes_string const *str = &args[1].string;
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ for (i = 0; i < str->len; i++)
+ {
+ int c = fgetc (fb->fp);
+ if (c == EOF)
+ return 0;
+ if (tolower (c) != tolower (str->ptr[i]))
+ return 0;
+ }
+ return 1;
+}
+
+int
+compare_bytes (union argument *args, void *sample, void *buf, size_t size,
+ struct filebuf const *fb)
+{
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ if (fread (buf, size, 1, fb->fp) != 1)
+ {
+ if (ferror (fb->fp))
+ perror ("fread");//FIXME
+ return 0;
+ }
+ return memcmp (sample, buf, size) == 0;
+}
+
+/* char(offset,value)
+ True if byte is identical
+*/
+static int
+b_char (union argument *args, struct filebuf const *fb)
+{
+ char val = args[1].number;
+ char buf;
+ return compare_bytes (args, &val, &buf, sizeof (buf), fb);
+}
+
+/* short(offset,value)
+ True if 16-bit integer is identical
+ FIXME: Byte order
+*/
+static int
+b_short (union argument *args, struct filebuf const *fb)
+{
+ uint16_t val = args[1].number;
+ uint16_t buf;
+ return compare_bytes (args, &val, &buf, sizeof (buf), fb);
+}
+
+/* int(offset,value)
+ True if 32-bit integer is identical
+ FIXME: Byte order
+*/
+static int
+b_int (union argument *args, struct filebuf const *fb)
+{
+ uint32_t val = args[1].number;
+ uint32_t buf;
+ return compare_bytes (args, &val, &buf, sizeof (buf), fb);
+}
+
+/* locale("string")
+ True if current locale matches string
+*/
+static int
+b_locale (union argument *args, struct filebuf const *fb)
+{
+ abort (); /* FIXME */
+ return 0;
+}
+
+/* contains(offset,range,"string")
+ True if the range contains the string
+*/
+static int
+b_contains (union argument *args, struct filebuf const *fb)
+{
+ size_t i;
+ size_t count;
+ char *buf;
+ struct mimetypes_string const *str = &args[2].string;
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek");//FIXME
+ return 0;
+ }
+
+ buf = malloc (args[1].number);
+ count = fread (buf, 1, args[1].number, fb->fp);
+ if (count == 0)
+ {
+ //FIXME
+ }
+ else if (count > str->len)
+ for (i = 0; i <= count - str->len; i++)
+ if (buf[i] == str->ptr[0] && memcmp (buf + i, str->ptr, str->len) == 0)
+ {
+ free (buf);
+ return 1;
+ }
+ free (buf);
+ return 0;
+}
+
+#define MIME_MAX_BUFFER 4096
+
+/* regex(offset,"regex") True if bytes match regular expression
+ */
+static int
+b_regex (union argument *args, struct filebuf const *fb)
+{
+ size_t count;
+ char buf[MIME_MAX_BUFFER];
+
+ if (fseek (fb->fp, args[0].number, SEEK_SET))
+ {
+ perror ("fseek"); //FIXME
+ return 0;
+ }
+
+ count = fread (buf, 1, sizeof buf - 1, fb->fp);
+ if (count == 0)
+ {
+ //FIXME: check err
+ return 0;
+ }
+ buf[count] = 0;
+
+ return regexec (&args[1].rx, buf, 0, NULL, 0) == 0;
+}
+
+static struct builtin_tab builtin_tab[] = {
+ { "match", "s", b_match },
+ { "ascii", "dd", b_ascii },
+ { "printable", "dd", b_printable },
+ { "regex", "dx", b_regex },
+ { "string", "ds", b_string },
+ { "istring", "ds", b_istring },
+ { "char", "dc", b_char },
+ { "short", "dd", b_short },
+ { "int", "dd", b_int },
+ { "locale", "s", b_locale },
+ { "contains", "dds", b_contains },
+ { NULL }
+};
+
+struct builtin_tab const *
+find_builtin (char const *ident)
+{
+ struct builtin_tab *p;
+ for (p = builtin_tab; p->name; p++)
+ if (strcmp (ident, p->name) == 0)
+ return p;
+ return NULL;
+}
+
+static int
+check_suffix (char const *suf, struct filebuf const *fb)
+{
+ char *p = strrchr (fb->name, '.');
+ if (!p)
+ return 0;
+ return strcmp (p+1, suf) == 0;
+}
+
+void
+mime_debug (struct locus_range const *loc, char const *fmt, ...)
+{
+ char *p = getenv ("MIMETYPE_DEBUG");
+ if (p && *p-'0')
+ {
+ va_list ap;
+
+ if (loc->beg.col == 0)
+ printf ("%s:%u", loc->beg.file, loc->beg.line);
+ else if (strcmp (loc->beg.file, loc->end.file))
+ printf ("%s:%u.%u-%s:%u.%u",
+ loc->beg.file,
+ loc->beg.line, loc->beg.col,
+ loc->end.file,
+ loc->end.line, loc->end.col);
+ else if (loc->beg.line != loc->end.line)
+ printf ("%s:%u.%u-%u.%u",
+ loc->beg.file,
+ loc->beg.line, loc->beg.col,
+ loc->end.line, loc->end.col);
+ else if (loc->beg.col != loc->end.col)
+ printf ("%s:%u.%u-%u",
+ loc->beg.file,
+ loc->beg.line, loc->beg.col,
+ loc->end.col);
+ else
+ printf ("%s:%u.%u",
+ loc->beg.file,
+ loc->beg.line, loc->beg.col);
+
+ printf (": ");
+
+ va_start (ap, fmt);
+ vprintf (fmt, ap);
+ va_end (ap);
+ putchar ('\n');
+ }
+}
+
+static int
+eval_rule (struct node *root, struct filebuf const *fb)
+{
+ int result;
+
+ switch (root->type)
+ {
+ case true_node:
+ result = 1;
+ break;
+
+ case functional_node:
+ result = root->v.function.fun (root->v.function.args, fb);
+ break;
+
+ case binary_node:
+ result = eval_rule (root->v.bin.arg1, fb);
+ switch (root->v.bin.op)
+ {
+ case L_OR:
+ if (!result)
+ result |= eval_rule (root->v.bin.arg2, fb);
+ break;
+
+ case L_AND:
+ if (result)
+ result &= eval_rule (root->v.bin.arg2, fb);
+ break;
+
+ default:
+ abort ();
+ }
+ break;
+
+ case negation_node:
+ result = !eval_rule (root->v.arg, fb);
+ break;
+
+ case suffix_node:
+ result = check_suffix (root->v.suffix.ptr, fb);
+ break;
+
+ default:
+ abort ();
+ }
+ mime_debug (&root->loc, "result %s", result ? "true" : "false");
+ return result;
+}
+
+static int
+rule_cmp (struct rule const *arule, struct rule const *brule)
+{
+ if (arule->priority == brule->priority)
+ {
+ if (arule->node->type == true_node
+ && brule->node->type != true_node)
+ return 1;
+ else if (brule->node->type == true_node
+ && arule->node->type != true_node)
+ return -1;
+ else
+ return strcasecmp (arule->type, brule->type);
+ }
+ return arule->priority - brule->priority;
+}
+
+const char *
+get_file_type (char const *filename)
+{
+ struct rule *r;
+ struct rule *last = NULL;
+ struct filebuf fb;
+
+ fb.name = filename;
+ fb.fp = fopen (filename, "r");
+ if (fb.fp == NULL)
+ {
+ perror ("fopen");//FIXME
+ return NULL;
+ }
+
+ LL_FOREACH (&rule_list, r, link)
+ {
+ if (eval_rule (r->node, &fb))
+ {
+ mime_debug (&r->loc, "rule %s matches", r->type);
+ if (!last || rule_cmp (r, last) < 0)
+ last = r;
+ }
+ }
+ fclose (fb.fp);
+ return last ? last->type : NULL;
+}
diff --git a/mimetypes/grammar.y b/mimetypes/grammar.y
new file mode 100644
index 0000000..2ae1997
--- /dev/null
+++ b/mimetypes/grammar.y
@@ -0,0 +1,397 @@
+%{
+/* This file is part of fileserv.
+ Copyright (C) 2017, 2018 Sergey Poznyakoff
+
+ Fileserv is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ Fileserv is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fileserv. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <ctype.h>
+#include "mtint.h"
+#include "grammar.h"
+
+static void
+yyprint (FILE *output, unsigned short toknum, YYSTYPE val)
+{
+ switch (toknum)
+ {
+ case TYPE:
+ case IDENT:
+ case STRING:
+ fprintf (output, "[%lu] %s", (unsigned long) val.string.len,
+ val.string.ptr);
+ break;
+
+ case EOL:
+ fprintf (output, "\\n");
+ break;
+
+ default:
+ if (isprint (toknum))
+ fprintf (output, "'%c'", toknum);
+ else
+ fprintf (output, "tok(%d)", toknum);
+ break;
+ }
+}
+
+#define YYPRINT yyprint
+
+
+static void
+arg_list_append (struct arg_list *al, struct mimetypes_string const *str)
+{
+ struct arg_elt *elt;
+ elt = malloc (sizeof *elt);
+ elt->string.ptr = str->ptr;
+ elt->string.len = str->len;
+ LLE_APPEND (al, elt, link);
+}
+
+static void
+arg_list_destroy (struct arg_list *al)
+{
+ struct arg_elt *elt;
+ while ((elt = LL_FIRST (al)) != NULL)
+ {
+ /* FIXME: free? */
+ LLE_UNLINK (al, elt, link);
+ }
+}
+
+//FIXME
+struct rule_list rule_list = LL_HEAD_INITIALIZER;
+
+static struct node *make_node (enum node_type type,
+ struct locus_range const *loc);
+static struct node *make_binary_node (int op,
+ struct node *left, struct node *rigth,
+ struct locus_range const *loc);
+static struct node *make_negation_node (struct node *p,
+ struct locus_range const *loc);
+
+static struct node *make_suffix_node (struct mimetypes_string *suffix,
+ struct locus_range const *loc);
+static struct node *make_functional_node (char *ident, struct arg_list *list,
+ struct locus_range const *loc);
+
+static size_t errors;
+%}
+
+%locations
+%expect 15
+
+%token <string> TYPE IDENT
+%token <string> STRING
+%token EOL BOGUS PRIORITY
+
+%left ','
+%left '+'
+
+%type <string> arg
+%type <list> arglist
+%type <node> function stmt rule maybe_rule
+%type <result> priority maybe_priority
+
+%union {
+ struct mimetypes_string string;
+ char *s;
+ struct arg_list list;
+ int result;
+ struct node *node;
+}
+
+%%
+
+input : list
+ ;
+
+list : rule_line
+ | list EOL rule_line
+ ;
+
+rule_line: /* empty */
+ | TYPE maybe_rule maybe_priority
+ {
+ struct rule *p = malloc (sizeof (*p));
+ LLE_APPEND (&rule_list, p, link);
+ p->type = $1.ptr;
+ p->node = $2;
+ p->priority = $3;
+ locus_point_copy (&p->loc.beg, &@1.beg);
+ locus_point_copy (&p->loc.end, &@3.end);
+#if 0
+ YY_LOCATION_PRINT (stderr, p->loc);
+ fprintf (stderr, ": rule %s\n", p->type);
+#endif
+ }
+ | BOGUS
+ {
+ YYERROR;
+ }
+ | error
+ {
+ errors++;
+ // arg_list_destroy (); //FIXME
+ lex_next_rule ();
+ yyerrok;
+ yyclearin;
+ }
+ ;
+
+maybe_rule: /* empty */
+ {
+ $$ = make_node (true_node, &yylloc);
+ }
+ | rule
+ ;
+
+rule : stmt
+ | rule rule %prec ','
+ {
+ struct locus_range lr;
+ lr.beg = @1.beg;
+ lr.end = @2.end;
+ $$ = make_binary_node (L_OR, $1, $2, &lr);
+ }
+ | rule ',' rule
+ {
+ struct locus_range lr;
+ lr.beg = @1.beg;
+ lr.end = @3.end;
+ $$ = make_binary_node (L_OR, $1, $3, &lr);
+ }
+ | rule '+' rule
+ {
+ struct locus_range lr;
+ lr.beg = @1.beg;
+ lr.end = @3.end;
+ $$ = make_binary_node (L_AND, $1, $3, &lr);
+ }
+ ;
+
+stmt : '!' stmt
+ {
+ $$ = make_negation_node ($2, &@2);
+ }
+ | '(' rule ')'
+ {
+ $$ = $2;
+ }
+ | STRING
+ {
+ $$ = make_suffix_node (&$1, &@1);
+ }
+ | function
+ | BOGUS
+ {
+ YYERROR;
+ }
+ ;
+
+priority : PRIORITY '(' arglist ')'
+ {
+ if (LL_COUNT (&$3) != 1)
+ {
+ mimetypes_error_at (&@1, "priority takes single numberic argument");
+ YYERROR;
+ }
+ $$ = atoi (LL_FIRST (&$3)->string.ptr);
+ arg_list_destroy (&$3);
+ }
+ ;
+
+maybe_priority: /* empty */
+ {
+ $$ = 100;
+ }
+ | priority
+ ;
+
+function : IDENT '(' arglist ')'
+ {
+ struct locus_range lr;
+ lr.beg = @1.beg;
+ lr.end = @4.end;
+
+ $$ = make_functional_node ($1.ptr, &$3, &lr);
+ if (!$$)
+ YYERROR;
+ }
+ ;
+
+arglist : arg
+ {
+ LL_HEAD_INIT (&$$);
+ arg_list_append (&$$, &$1);
+ }
+ | arglist ',' arg
+ {
+ arg_list_append (&$1, &$3);
+ $$ = $1;
+ }
+ ;
+
+arg : STRING
+ | BOGUS
+ {
+ YYERROR;
+ }
+ ;
+
+%%
+
+int
+mimetypes_parse (const char *name)
+{
+ int rc;
+ char *p;
+ if (mimetypes_open (name))
+ return 1;
+ p = getenv ("MIMETYPE_DEBUG_GRAM");
+ yydebug = p ? (*p - '0') : 0;
+ rc = yyparse ();
+ mimetypes_close ();
+ return rc || errors;
+}
+
+static struct node *
+make_node (enum node_type type, struct locus_range const *loc)
+{
+ struct node *p = malloc (sizeof *p);
+ p->type = type;
+ locus_range_init (&p->loc);
+ locus_range_copy (&p->loc, loc);
+ return p;
+}
+
+static struct node *
+make_binary_node (int op, struct node *left, struct node *right,
+ struct locus_range const *loc)
+{
+ struct node *node = make_node (binary_node, loc);
+
+ node->v.bin.op = op;
+ node->v.bin.arg1 = left;
+ node->v.bin.arg2 = right;
+ return node;
+}
+
+struct node *
+make_negation_node (struct node *p, struct locus_range const *loc)
+{
+ struct node *node = make_node (negation_node, loc);
+ node->v.arg = p;
+ return node;
+}
+
+struct node *
+make_suffix_node (struct mimetypes_string *suffix,
+ struct locus_range const *loc)
+{
+ struct node *node = make_node (suffix_node, loc);
+ node->v.suffix = *suffix;
+ return node;
+}
+
+
+struct node *
+make_functional_node (char *ident, struct arg_list *list,
+ struct locus_range const *loc)
+{
+ size_t count, i;
+ struct builtin_tab const *p;
+ struct node *node;
+ union argument *args;
+ struct arg_elt *elt;
+
+ p = find_builtin (ident);
+ if (!p)
+ {
+ mimetypes_error_at (loc, "unknown builtin: %s", ident);
+ return NULL;
+ }
+
+ count = LL_COUNT (list);
+ i = strlen (p->args);
+