summaryrefslogtreecommitdiff
path: root/mimetypes
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2018-01-03 12:23:44 +0200
committerSergey Poznyakoff <gray@gnu.org>2018-01-03 12:23:52 +0200
commit80ed5e611de0e31eed65ee207f1fd64ad097e6d1 (patch)
tree40979fe5ecadaa3d1351c645ff7988a87218e142 /mimetypes
parenta4edd153e4cdf703c98008313743b112274e760f (diff)
downloadfileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.gz
fileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.bz2
Add mimetypes library.
Ported from GNU Mailutils.
Diffstat (limited to 'mimetypes')
-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
19 files changed, 2511 insertions, 0 deletions
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);
+
+ if (count < i)
+ {
+ mimetypes_error_at (loc, "too few arguments in call to %s", ident);
+ return NULL;
+ }
+ else if (count > i)
+ {
+ mimetypes_error_at (loc, "too many arguments in call to %s", ident);
+ return NULL;
+ }
+
+ args = calloc (count, sizeof *args);
+ i = 0;
+ LL_FOREACH (list, elt, link)
+ {
+ char *tmp;
+
+ switch (p->args[i])
+ {
+ case 'd':
+ args[i].number = strtoul (elt->string.ptr, &tmp, 0);
+ if (*tmp)
+ goto err;
+ break;
+
+ case 's':
+ args[i].string = elt->string;
+ break;
+
+ case 'x':
+ {
+ int rc = regcomp (&args[i].rx, elt->string.ptr,
+ REG_EXTENDED|REG_NOSUB);
+ if (rc)
+ {
+ char errbuf[512];
+ regerror (rc, &args[i].rx, errbuf, sizeof errbuf);
+ mimetypes_error_at (loc, "%s", errbuf);
+ return NULL;
+ }
+ }
+ break;
+
+ case 'c':
+ args[i].c = strtoul (elt->string.ptr, &tmp, 0);
+ if (*tmp)
+ goto err;
+ break;
+
+ default:
+ abort ();
+ }
+ i++;
+ }
+
+ node = make_node (functional_node, loc);
+ node->v.function.fun = p->handler;
+ node->v.function.args = args;
+ return node;
+
+ err:
+ mimetypes_error_at (loc, "argument %lu has wrong type in call to %s",
+ (unsigned long) i, ident);
+ return NULL;
+}
+
+
+
diff --git a/mimetypes/ident.c b/mimetypes/ident.c
new file mode 100644
index 0000000..6478e76
--- /dev/null
+++ b/mimetypes/ident.c
@@ -0,0 +1,85 @@
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+struct ident_ref
+{
+ char *name;
+ size_t count;
+ struct ident_ref *next;
+};
+
+static struct ident_ref *nametab;
+
+int
+ident_ref (char const *name, char const **refname)
+{
+ struct ident_ref *ref = NULL;
+
+ if (!refname)
+ return EINVAL;
+
+ if (!name)
+ {
+ *refname = NULL;
+ return 0;
+ }
+
+ if (nametab)
+ {
+ for (ref = nametab; ref; ref = ref->next)
+ {
+ if (strcmp (ref->name, name) == 0)
+ break;
+ }
+ }
+
+ if (!ref)
+ {
+ ref = malloc (sizeof (*ref));
+ if (!ref)
+ return -1;
+ ref->name = strdup (name);
+ if (!ref->name)
+ {
+ free (ref);
+ return -1;
+ }
+ ref->count = 0;
+ ref->next = nametab;
+ nametab = ref;
+ }
+
+ ref->count++;
+ *refname = ref->name;
+
+ return 0;
+}
+
+void
+ident_deref (char const *name)
+{
+ struct ident_ref *ref, *prev;
+
+ if (!name || !nametab)
+ return;
+
+ for (ref = nametab, prev = NULL; ref; prev = ref, ref = ref->next)
+ {
+ if (strcmp (ref->name, name) == 0)
+ {
+ if (--ref->count == 0)
+ {
+ if (prev)
+ prev->next = ref->next;
+ else
+ nametab = ref->next;
+ free (ref->name);
+ free (ref);
+ }
+ break;
+ }
+ }
+}
+
+
diff --git a/mimetypes/lexer.l b/mimetypes/lexer.l
new file mode 100644
index 0000000..568a5cf
--- /dev/null
+++ b/mimetypes/lexer.l
@@ -0,0 +1,371 @@
+%top {
+/* 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 <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <ctype.h>
+#include "mtint.h"
+#include "grammar.h"
+
+static linetrack_t trk;
+struct locus_point string_beg;
+
+char *string_buffer;
+size_t string_level;
+size_t string_size;
+
+static void
+string_moremem (size_t size)
+{
+ if (string_size - string_level < size)
+ {
+ while (string_size - string_level < size)
+ {
+ if ((size_t) -1 / 3 * 2 <= string_size)
+ abort ();
+ string_size += (string_size + 1) / 2 + 1;
+ }
+ string_buffer = realloc (string_buffer, string_size);
+ if (!string_buffer)
+ abort ();
+ }
+}
+
+static void
+string_append (char const *b, size_t n)
+{
+ string_moremem (n);
+ memcpy (string_buffer + string_level, b, n);
+ string_level += n;
+}
+
+static void
+string_append_char (int c)
+{
+ string_moremem (1);