summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org>2018-01-03 10:23:44 (GMT)
committer Sergey Poznyakoff <gray@gnu.org>2018-01-03 10:23:52 (GMT)
commit80ed5e611de0e31eed65ee207f1fd64ad097e6d1 (patch) (side-by-side diff)
tree40979fe5ecadaa3d1351c645ff7988a87218e142
parenta4edd153e4cdf703c98008313743b112274e760f (diff)
downloadfileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.gz
fileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.bz2
Add mimetypes library.
Ported from GNU Mailutils.
Diffstat (more/less context) (ignore whitespace changes)
-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
--- a/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
--- a/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
--- a/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
--- a/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
--- a/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
--- a/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
--- a/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);
+ string_buffer[string_level++] = c;
+}
+
+static unsigned
+digit_to_number (char c)
+{
+ return (unsigned) (c >= '0' && c <= '9' ? c-'0' :
+ c >= 'A' && c <= 'Z' ? c-'A'+10 :
+ c-'a'+10);
+}
+
+static void
+drop_string (void)
+{
+ string_level = 0;
+}
+
+static void
+finish_string (void)
+{
+ yylval.string.ptr = malloc (string_level + 1);
+ if (!yylval.string.ptr)
+ abort ();
+ memcpy (yylval.string.ptr, string_buffer, string_level);
+ yylval.string.ptr[string_level] = 0;
+ yylval.string.len = string_level;
+ string_level = 0;
+}
+
+static void
+finish_astring (void)
+{
+ yylval.string.ptr = malloc (string_level + 1);
+ if (!yylval.string.ptr)
+ abort ();
+ memcpy (yylval.string.ptr, string_buffer, string_level);
+ yylval.string.ptr[string_level] = 0;
+ yylval.string.len = string_level;
+
+ locus_point_copy (&yylloc.end, &yylloc.beg);
+ yylloc.end.col--;
+ locus_point_copy (&yylloc.beg, &string_beg);
+ locus_point_deinit (&string_beg);
+
+ string_level = 0;
+
+ if (yy_flex_debug)
+ {
+ size_t i;
+ printf ("string %zu: ", yylval.string.len);
+ for (i = 0; i < yylval.string.len; i++)
+ if (isprint (yylval.string.ptr[i]))
+ printf ("%c", yylval.string.ptr[i]);
+ else
+ printf ("\\%03o", yylval.string.ptr[i]);
+ putchar ('\n');
+ }
+#if 0
+ YY_LOCATION_PRINT (stderr, yylloc);
+ fprintf (stderr, ": %s\n", yylval.string.ptr);
+#endif
+}
+
+#define YY_USER_ACTION \
+ linetrack_advance (trk, &yylloc, yytext, yyleng);
+
+%}
+
+%x RULE ARGS ASTRING
+X [0-9a-fA-F]
+IDENT [a-zA-Z_\.][a-zA-Z0-9_\.-]*
+WS [ \t][ \t]*
+%%
+
+<INITIAL>{
+ /* Comments */
+^#.*\n ;
+\n ;
+^[^ \t\n/]+"/"[^ \t\n]+ {
+ string_append (yytext, yyleng);
+ finish_string ();
+ BEGIN (RULE);
+ return TYPE;
+}
+
+. {
+ yyerror ("type/subtype is missing");
+ return BOGUS;
+}
+}
+
+<RULE>{
+\\\n ;
+\n {
+ BEGIN (INITIAL);
+ return EOL;
+}
+{WS} ;
+
+ /* Operators */
+"!"|"+"|","|"("|")"|"/" return yytext[0];
+ /* Special cases: && and ||. Docs don't say anything about them, but
+ I've found them in my mime.types file... --Sergey */
+"&&" return '+';
+"||" return ',';
+
+"priority"/"(" {
+ return PRIORITY;
+}
+
+{IDENT}/"(" {
+ string_append (yytext, yyleng);
+ finish_string ();
+ BEGIN (ARGS);
+ return IDENT;
+}
+
+[a-zA-Z0-9_.-]+/[^(] {
+ string_append (yytext, yyleng);
+ finish_string ();
+ return STRING;
+}
+
+. yyerror("unexpected character '%c'", yytext[0]);
+}
+
+<ARGS>{
+"("|"," return yytext[0];
+")" {
+ BEGIN (RULE);
+ return yytext[0];
+}
+{WS} yyerror ("unexpected whitespace in argument list");
+\n {
+ yyerror ("unexpected newline in argument list");
+ return BOGUS;
+}
+. {
+ locus_point_copy (&string_beg, &yylloc.beg);
+ linetrack_retreat (trk, 1);
+ yyless (0);
+ BEGIN (ASTRING);
+}
+}
+
+<ASTRING>{
+ /* Quoted string */
+\"[^"\n]*\" {
+ string_append (yytext+1, yyleng-2);
+}
+"'"[^'\n]*"'" {
+ string_append (yytext+1, yyleng-2);
+}
+
+ /* Hex string */
+"<"({X}{X})+">" {
+ int i;
+ for (i = 1; i < yyleng - 2; i += 2)
+ {
+ string_append_char (digit_to_number (yytext[i])*16
+ + digit_to_number (yytext[i+1]));
+ }
+}
+
+ /* Unquoted character sequence */
+[^ \t\n,)<"']+/[^"'<] {
+ string_append (yytext, yyleng);
+}
+
+[^ \t\n,)<"]+/< {
+ string_append (yytext, yyleng);
+}
+
+[^ \t\n,)<"]+/["'] {
+ string_append (yytext, yyleng);
+}
+
+\n {
+ yyerror ("unexpected newline in argument");
+ drop_string ();
+ return BOGUS;
+}
+
+. {
+ linetrack_retreat (trk, 1);
+ yyless (0);
+ BEGIN (ARGS);
+ finish_astring ();
+ return STRING;
+}
+}
+
+%%
+int
+mimetypes_open (const char *name)
+{
+ struct stat st;
+ char *filename;
+ char *p;
+
+ p = getenv ("MIMETYPE_DEBUG_LEX");
+ yy_flex_debug = p ? (*p - '0') : 0;
+
+ if (stat (name, &st))
+ {
+ yyerror ("cannot stat `%s': %s", name, strerror (errno));
+ return -1;
+ }
+
+ if (S_ISDIR (st.st_mode))
+ {
+ size_t blen = strlen (name);
+ static char *fname = "mime.types";
+ while (blen && name[blen-1] == '/')
+ blen--;
+ if (!blen)
+ abort ();
+ filename = malloc (blen + 1 + strlen (fname) + 1);
+ if (!filename)
+ abort ();
+ strcpy (filename, name);
+ strcat (filename, "/");
+ strcat (filename, fname);
+ }
+ else
+ {
+ filename = strdup (name);
+ if (!filename)
+ abort ();
+ }
+
+ yyin = fopen (filename, "r");
+ if (!yyin)
+ {
+ yyerror ("cannot open `%s': %s", filename, strerror (errno));
+ free (filename);
+ return -1;
+ }
+
+ assert (linetrack_create (&trk, filename, 3) == 0);
+ free (filename);
+
+ drop_string ();
+ return 0;
+}
+
+void
+mimetypes_close (void)
+{
+ fclose (yyin);
+ locus_range_deinit (&yylloc);
+ linetrack_destroy (&trk);
+}
+
+int
+yyerror (char *s)
+{
+ struct locus_range lr = LOCUS_RANGE_INITIALIZER;
+ linetrack_locus (trk, &lr.beg);
+ mimetypes_error_at (&lr, "%s", s);
+ locus_range_deinit (&lr);
+ return 0;
+}
+
+int
+yywrap (void)
+{
+ return 1;
+}
+
+/* Position input at the beginning of the next rule as a final part of error
+ recovery */
+void
+lex_next_rule (void)
+{
+ int c;
+
+ if (yy_flex_debug)
+ {
+ YY_LOCATION_PRINT (stderr, yylloc);
+ fprintf (stderr, ": started error recovery\n");
+ }
+ while ((c = input ()) != EOF)
+ {
+ char ch = c;
+ if (!isspace (c) && linetrack_at_bol (trk))
+ {
+ unput (c);
+ break;
+ }
+ linetrack_advance (trk, &yylloc, &ch, 1);
+ }
+ if (yy_flex_debug)
+ {
+ struct locus_range lr = LOCUS_RANGE_INITIALIZER;
+ linetrack_locus (trk, &lr.beg);
+ YY_LOCATION_PRINT (stderr, lr);
+ fprintf (stderr, ": finished error recovery\n");
+ locus_point_deinit (&lr.beg);
+ }
+ BEGIN (RULE);
+ unput ('\n');
+ linetrack_retreat (trk, 1);
+}
diff --git a/mimetypes/linetrack.c b/mimetypes/linetrack.c
new file mode 100644
index 0000000..90754ea
--- a/dev/null
+++ b/mimetypes/linetrack.c
@@ -0,0 +1,380 @@
+/* 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/>. */
+
+#include <stdlib.h>
+#include <errno.h>
+#include "locus.h"
+
+/* The line-tracker structure keeps track of the last N lines read from one
+ or more input files. For each line read it keeps the number of characters
+ in that line including the newline. This information is stored in a
+ cyclic stack of N elements (N >= 2). Top of stack always represents the
+ current line. For the purpose of line tracker, current line is the line
+ that is being visited, such that its final newline character has not yet
+ been seen. Once the newline is seen, the line is pushed on stack, and a
+ new current line is assumed.
+
+ Each input file is represented by a directory entry keeping its name,
+ the number of the first line that is stored in the tracker and the index of
+ that line in the cols stack. Entries form a doubly-linked list, with
+ head pointing to the most recent (current) source. When a new line is
+ being added to the stack which is full, its eldest entry is discarded
+ and re-assigned to that line; at the same time the directory of the
+ eldest source is updated accordingly. If the entry to be discarded
+ represents the only line of the source, the source is discarded.
+*/
+
+struct source
+{
+ char const *file_name; /* Name of the source file */
+ size_t idx; /* Index of the first element on stack */
+ unsigned line; /* Number of line corresponding to cols[idx] */
+ struct source *next, *prev;
+};
+
+struct linetrack
+{
+ struct source *s_head, *s_tail;
+ /* Directory of source files. Most recent one is
+ s_head */
+
+ size_t max_lines; /* Max. number of lines history kept by tracker (N) */
+ size_t head; /* Index of the eldest element on stack */
+ size_t tos; /* Index of the most recent element on stack
+ (< max_lines) */
+ unsigned *cols; /* Cyclic stack or character counts.
+ Number of characters in line (head + n) is
+ cols[head + n] (0 <= n <= tos). */
+};
+
+static inline size_t
+trk_incr (struct linetrack *trk, size_t a)
+{
+ return (a + 1) % trk->max_lines;
+}
+
+static inline size_t
+trk_decr (struct linetrack *trk, size_t a)
+{
+ return (a + trk->max_lines - 1) % trk->max_lines;
+}
+
+static inline unsigned
+count_lines (linetrack_t trk, size_t from)
+{
+ return (trk->tos + trk->max_lines - from) % trk->max_lines + 1;
+}
+
+#ifndef SIZE_MAX
+# define SIZE_MAX (~((size_t)0))
+#endif
+
+static int
+count_chars (struct linetrack *trk, size_t i, size_t *ret)
+{
+ size_t nch = 0;
+
+ while (1)
+ {
+ unsigned n = trk->cols[i];
+ if (SIZE_MAX - nch < n)
+ return ERANGE;
+ nch += n;
+ if (i == trk->tos)
+ break;
+ i = trk_incr (trk, i);
+ }
+ *ret = nch;
+ return 0;
+}
+
+static size_t
+count_files (struct linetrack *trk)
+{
+ struct source *sp;
+ size_t n = 0;
+ for (sp = trk->s_head; sp; sp = sp->next)
+ n++;
+ return n;
+}
+
+static void
+del_source (linetrack_t trk, struct source *sp)
+{
+ if (sp->prev)
+ sp->prev->next = sp->next;
+ else
+ trk->s_head = sp->next;
+ if (sp->next)
+ sp->next->prev = sp->prev;
+ else
+ trk->s_tail = sp->prev;
+ ident_deref (sp->file_name);
+ free (sp);
+}
+
+static inline unsigned *
+push (linetrack_t trk)
+{
+ trk->tos = trk_incr (trk, trk->tos);
+ if (trk->tos == trk->head)
+ {
+ trk->head = trk_incr (trk, trk->head);
+ trk->s_tail->idx = trk->head;
+ trk->s_tail->line++;
+ }
+ if (trk->s_tail->prev && trk->s_tail->idx == trk->s_tail->prev->idx)
+ del_source (trk, trk->s_tail);
+ trk->cols[trk->tos] = 0;
+ return &trk->cols[trk->tos];
+}
+
+static inline unsigned *
+pop (linetrack_t trk)
+{
+ if (trk->tos == trk->head)
+ return NULL;
+ if (trk->tos == trk->s_head->idx)
+ del_source (trk, trk->s_head);
+
+ trk->tos = trk_decr (trk, trk->tos);
+
+ return &trk->cols[trk->tos];
+}
+
+int
+linetrack_origin (linetrack_t trk, struct locus_point const *pt)
+{
+ int rc;
+ struct source *sp;
+ char const *file_name;
+
+ if (!trk || !pt || pt->line == 0)
+ return EINVAL;
+ if (pt->file)
+ file_name = pt->file;
+ else if (trk->s_head)
+ file_name = trk->s_head->file_name;
+ else
+ return EINVAL;
+ sp = malloc (sizeof *sp);
+ if (!sp)
+ return errno;
+ rc = ident_ref (file_name, &sp->file_name);
+ if (rc)
+ {
+ free (sp);
+ return rc;
+ }
+
+ if (trk->cols[trk->tos])
+ push (trk);
+
+ sp->idx = trk->tos;
+ sp->line = pt->line;
+ trk->cols[sp->idx] = pt->col;
+
+ sp->prev = NULL;
+ sp->next = trk->s_head;
+ if (trk->s_head)
+ trk->s_head->prev = sp;
+ else
+ trk->s_tail = sp;
+ trk->s_head = sp;
+ return 0;
+}
+
+int
+linetrack_create (linetrack_t *ret, char const *file_name, size_t max_lines)
+{
+ int rc;
+ struct linetrack *trk;
+ struct locus_point pt;
+
+ trk = malloc (sizeof *trk);
+ if (!trk)
+ return errno;
+
+ trk->cols = calloc (max_lines, sizeof (trk->cols[0]));
+ if (!trk->cols)
+ {
+ rc = errno;
+ free (trk);
+ return rc;
+ }
+ trk->s_head = trk->s_tail = NULL;
+
+ if (max_lines < 2)
+ max_lines = 2;
+ trk->max_lines = max_lines;
+ trk->head = 0;
+ trk->tos = 0;
+ trk->cols[0] = 0;
+
+ pt.file = file_name;
+ pt.line = 1;
+ pt.col = 0;
+ rc = linetrack_origin (trk, &pt);
+ if (rc)
+ {
+ free (trk->cols);
+ free (trk);
+ return rc;
+ }
+
+ *ret = trk;
+ return 0;
+}
+
+int
+linetrack_rebase (linetrack_t trk, struct locus_point const *pt)
+{
+ char const *file_name;
+ int rc = ident_ref (pt->file, &file_name);
+ if (rc)
+ return rc;
+ ident_deref (trk->s_head->file_name);
+ trk->s_head->file_name = file_name;
+ trk->s_head->line = pt->line;
+ trk->cols[trk->s_head->idx] = pt->col;
+ return 0;
+}
+
+void
+linetrack_free (linetrack_t trk)
+{
+ if (trk)
+ {
+ while (trk->s_head)
+ del_source (trk, trk->s_head);
+ free (trk->cols);
+ free (trk);
+ }
+}
+
+void
+linetrack_destroy (linetrack_t *trk)
+{
+ if (trk)
+ {
+ linetrack_free (*trk);
+ *trk = NULL;
+ }
+}
+
+int
+linetrack_stat (struct linetrack *trk, struct linetrack_stat *st)
+{
+ if (count_chars (trk, trk->head, &st->n_chars))
+ return ERANGE;
+ st->n_files = count_files (trk);
+ st->n_lines = count_lines (trk, trk->head);
+ return 0;
+}
+
+int
+linetrack_at_bol (struct linetrack *trk)
+{
+ return trk->cols[trk->tos] == 0;
+}
+
+void
+linetrack_advance (struct linetrack *trk,
+ struct locus_range *loc,
+ char const *text, size_t leng)
+{
+ unsigned *ptr;
+
+ if (text == NULL || leng == 0)
+ return;
+
+ locus_point_set_file (&loc->beg, trk->s_head->file_name);
+ locus_point_set_file (&loc->end, trk->s_head->file_name);
+ loc->beg.line =
+ trk->s_head->line + count_lines (trk, trk->s_head->idx) - 1;
+ ptr = &trk->cols[trk->tos];
+ loc->beg.col = *ptr + 1;
+ while (leng--)
+ {
+ (*ptr)++;
+ if (*text == '\n')
+ ptr = push (trk);
+ text++;
+ }
+
+ loc->end.line =
+ trk->s_head->line + count_lines (trk, trk->s_head->idx) - 1;
+ if (*ptr)
+ {
+ loc->end.col = *ptr;
+ }
+ else
+ {
+ /* Text ends with a newline. Keep the previous line number. */
+ loc->end.line--;
+ loc->end.col = trk->cols[trk_decr (trk, trk->tos)] - 1;
+ if (loc->end.col + 1 == loc->beg.col)
+ {
+ /* This happens if the previous line contained only newline. */
+ loc->beg.col = loc->end.col;
+ }
+ }
+}
+
+int
+linetrack_locus (struct linetrack *trk, struct locus_point *lp)
+{
+ int rc = locus_point_set_file (lp, trk->s_head->file_name);
+ if (rc == 0)
+ {
+ lp->line =
+ trk->s_head->line + count_lines (trk, trk->s_head->idx) - 1;
+ lp->col = trk->cols[trk->tos];
+ }
+ return rc;
+}
+
+int
+linetrack_retreat (struct linetrack *trk, size_t n)
+{
+ size_t nch;
+
+ if (count_chars (trk, trk->head, &nch))
+ return ERANGE;
+ if (n > nch)
+ return ERANGE;
+ else
+ {
+ unsigned *ptr = &trk->cols[trk->tos];
+ while (n--)
+ {
+ if (*ptr == 0)
+ {
+ ptr = pop (trk);
+ if (!ptr || *ptr == 0)
+ {
+ mimetypes_error ("%s:%d: INTERNAL ERROR: out of pop back\n",
+ __FILE__, __LINE__);
+ return ERANGE;
+ }
+ }
+ --*ptr;
+ }
+ }
+ return 0;
+}
+
+
diff --git a/mimetypes/locus.c b/mimetypes/locus.c
new file mode 100644
index 0000000..e3eba86
--- a/dev/null
+++ b/mimetypes/locus.c
@@ -0,0 +1,95 @@
+/* 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/>. */
+
+#include <stdlib.h>
+#include <errno.h>
+#include "locus.h"
+
+int
+locus_point_set_file (struct locus_point *pt, const char *filename)
+{
+ int rc;
+ char const *ref;
+
+ rc = ident_ref (filename, &ref);
+ if (rc)
+ return rc;
+ ident_deref (pt->file);
+ pt->file = ref;
+ return 0;
+}
+
+void
+locus_point_init (struct locus_point *pt)
+{
+ memset (pt, 0, sizeof *pt);
+}
+
+void
+locus_point_deinit (struct locus_point *pt)
+{
+ ident_deref (pt->file);
+ memset (pt, 0, sizeof *pt);
+}
+
+int
+locus_point_copy (struct locus_point *dest, struct locus_point const *src)
+{
+ int rc = locus_point_set_file (dest, src->file);
+ if (rc == 0)
+ {
+ dest->col = src->col;
+ dest->line = src->line;
+ }
+ return rc;
+}
+
+void
+locus_range_init (struct locus_range *dest)
+{
+ memset (dest, 0, sizeof *dest);
+}
+
+int
+locus_range_copy (struct locus_range *dest, struct locus_range const *src)
+{
+ int rc;
+ struct locus_range tmp = LOCUS_RANGE_INITIALIZER;
+
+ if (!dest)
+ return EINVAL;
+
+ rc = locus_point_copy (&tmp.beg, &src->beg);
+ if (rc == 0)
+ {
+ rc = locus_point_copy (&tmp.end, &src->end);
+ if (rc)
+ locus_point_deinit (&tmp.beg);
+ else
+ {
+ locus_range_deinit (dest);
+ *dest = tmp;
+ }
+ }
+ return rc;
+}
+
+void
+locus_range_deinit (struct locus_range *lr)
+{
+ locus_point_deinit (&lr->beg);
+ locus_point_deinit (&lr->end);
+}
diff --git a/mimetypes/locus.h b/mimetypes/locus.h
new file mode 100644
index 0000000..8a56995
--- a/dev/null
+++ b/mimetypes/locus.h
@@ -0,0 +1,104 @@
+/* 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/>. */
+
+#ifndef _MIMETYPES_LOCUS_H
+#define _MIMETYPES_LOCUS_H
+
+#include <string.h>
+#include <stdarg.h>
+
+struct locus_point
+{
+ char const *file;
+ unsigned line;
+ unsigned col;
+};
+
+#define LOCUS_POINT_INITIALIZER { NULL, 0, 0 }
+
+struct locus_range
+{
+ struct locus_point beg;
+ struct locus_point end;
+};
+
+#define LOCUS_RANGE_INITIALIZER \
+ { LOCUS_POINT_INITIALIZER, LOCUS_POINT_INITIALIZER }
+
+typedef struct linetrack *linetrack_t;
+
+struct linetrack_stat
+{
+ size_t n_files; /* Number of source files */
+ size_t n_lines; /* Number of lines, including the recent (incomplete) one */
+ size_t n_chars; /* Total number of characters */
+};
+
+int ident_ref (char const *name, char const **refname);
+int ident_deref (char const *);
+
+int locus_point_set_file (struct locus_point *pt, const char *filename);
+void locus_point_init (struct locus_point *pt);
+void locus_point_deinit (struct locus_point *pt);
+int locus_point_copy (struct locus_point *dest,
+ struct locus_point const *src);
+
+void locus_range_init (struct locus_range *dest);
+int locus_range_copy (struct locus_range *dest, struct locus_range const *src);
+void locus_range_deinit (struct locus_range *lr);
+
+static inline int
+locus_point_same_file (struct locus_point const *a,
+ struct locus_point const *b)
+{
+ return a->file == b->file
+ || (a->file && b->file && strcmp(a->file, b->file) == 0);
+}
+
+static inline int
+locus_point_same_line (struct locus_point const *a,
+ struct locus_point const *b)
+{
+ return locus_point_same_file (a, b) && a->line == b->line;
+}
+
+static inline int
+locus_point_eq (struct locus_point const *a,
+ struct locus_point const *b)
+{
+ return locus_point_same_line (a, b) && a->col == b->col;
+}
+
+int linetrack_create (linetrack_t *ret,
+ char const *file_name, size_t max_lines);
+int linetrack_origin (linetrack_t trk, struct locus_point const *pt);
+int linetrack_rebase (linetrack_t trk, struct locus_point const *pt);
+void linetrack_free (linetrack_t trk);
+void linetrack_destroy (linetrack_t *trk);
+void linetrack_advance (linetrack_t trk,
+ struct locus_range *loc,
+ char const *text, size_t leng);
+int linetrack_retreat (linetrack_t trk, size_t n);
+
+int linetrack_locus (struct linetrack *trk, struct locus_point *lp);
+int linetrack_stat (linetrack_t trk, struct linetrack_stat *st);
+int linetrack_at_bol (struct linetrack *trk);
+
+void lrange_debug (struct locus_range const *loc, char const *fmt, ...);
+ssize_t locus_range_format (char **pbuf, size_t *psize, struct locus_range const *lr);
+
+
+#endif
diff --git a/mimetypes/mimetypes.h b/mimetypes/mimetypes.h
new file mode 100644
index 0000000..0cf2688
--- a/dev/null
+++ b/mimetypes/mimetypes.h
@@ -0,0 +1,3 @@
+void (*mimetypes_error_printer) (char const *);
+int mimetypes_parse (const char *name);
+const char *get_file_type (char const *filename);
diff --git a/mimetypes/mtint.h b/mimetypes/mtint.h
new file mode 100644
index 0000000..0458e4d
--- a/dev/null
+++ b/mimetypes/mtint.h
@@ -0,0 +1,169 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <regex.h>
+#include "locus.h"
+#include "yyloc.h"
+
+#include "mimetypes.h"
+
+struct mimetypes_string
+{
+ char *ptr;
+ size_t len;
+};
+
+#define LL_ENTRY(type) \
+ struct \
+ { \
+ struct type *lle_next; /* next element */ \
+ struct type *lle_prev; /* previous element */ \
+ }
+#define LL_HEAD(name, type) \
+ struct name \
+ { \
+ struct type *ll_head; \
+ struct type *ll_tail; \
+ size_t ll_count; \
+ }
+#define LL_HEAD_INIT(head) \
+ do \
+ { \
+ (head)->ll_head = (head)->ll_tail = NULL; \
+ (head)->ll_count = 0; \
+ } \
+ while (0)
+#define LL_HEAD_INITIALIZER \
+ { NULL, NULL, 0 }
+#define LL_FIRST(head) \
+ (head)->ll_head
+#define LL_LAST(head) \
+ (head)->ll_tail
+#define LL_COUNT(head) \
+ (head)->ll_count
+#define LLE_NEXT(elt,field) \
+ (elt)->field.lle_next
+#define LLE_PREV(elt,field) \
+ (elt)->field.lle_prev
+#define LLE_APPEND(head,elt,field) \
+ do \
+ { \
+ if (((elt)->field.lle_prev = LL_LAST (head)) == NULL) \
+ LL_FIRST (head) = (elt); \
+ else \
+ (elt)->field.lle_prev->field.lle_next = (elt); \
+ (elt)->field.lle_next = NULL; \
+ LL_LAST (head) = (elt); \
+ (head)->ll_count++; \
+ } \
+ while (0)
+#define LLE_UNLINK(head,elt,field) \
+ do \
+ { \
+ if ((elt)->field.lle_next != NULL) \
+ (elt)->field.lle_next->field.lle_prev = (elt)->field.lle_prev; \
+ else \
+ LL_LAST (head) = (elt)->field.lle_prev; \
+ if ((elt)->field.lle_prev != NULL) \
+ (elt)->field.lle_prev->field.lle_next = (elt)->field.lle_next; \
+ else \
+ LL_FIRST (head) = (elt)->field.lle_next; \
+ (head)->ll_count--; \
+ } \
+ while (0)
+#define LL_FOREACH(head,elt,field) \
+ for (elt = LL_FIRST (head); elt; elt = LLE_NEXT (elt,field))
+
+#define L_OR 0
+#define L_AND 1
+
+enum node_type
+ {
+ true_node,
+ functional_node,
+ binary_node,
+ negation_node,
+ suffix_node
+ };
+
+struct rule
+{
+ char *type;
+ int priority;
+ struct locus_range loc;
+ struct node *node;
+ LL_ENTRY(rule) link;
+};
+
+LL_HEAD(rule_list, rule);
+
+union argument
+{
+ struct mimetypes_string string;
+ unsigned number;
+ int c;
+ regex_t rx;
+};
+
+struct filebuf
+{
+ char const *name;
+ FILE *fp;
+};
+
+typedef int (*builtin_t) (union argument *args, struct filebuf const *fb);
+
+struct node
+{
+ enum node_type type;
+ struct locus_range loc;
+ union
+ {
+ struct
+ {
+ builtin_t fun;
+ union argument *args;
+ } function;
+ struct node *arg;
+ struct
+ {
+ int op;
+ struct node *arg1;
+ struct node *arg2;
+ } bin;
+ struct mimetypes_string suffix;
+ } v;
+};
+
+struct builtin_tab
+{
+ char *name;
+ char *args;
+ builtin_t handler;
+};
+
+struct builtin_tab const *find_builtin (char const *ident);
+
+struct arg_elt
+{
+ struct mimetypes_string string;
+ LL_ENTRY(arg_elt) link;
+};
+
+LL_HEAD(arg_list, arg_elt);
+
+ssize_t mimetypes_error_format (char **pbuf, size_t *psize,
+ struct locus_range const *lr, char const *fmt,
+ va_list ap);
+void mimetypes_error_at (struct locus_range const *lr, char const *fmt, ...);
+void mimetypes_error (char const *fmt, ...);
+void print_locus_range (FILE *fp, struct locus_range const *lr);
+
+int mimetypes_open (const char *name);
+void mimetypes_close (void);
+void lex_next_rule (void);
+
+extern struct rule_list rule_list;
+
+
+
+
diff --git a/mimetypes/prloc.c b/mimetypes/prloc.c
new file mode 100644
index 0000000..d53883c
--- a/dev/null
+++ b/mimetypes/prloc.c
@@ -0,0 +1,172 @@
+#include <stdlib.h>
+#include <errno.h>
+#include "locus.h"
+
+size_t
+nprlen (unsigned x)
+{
+ size_t len = 1;
+ while (x /= 10)
+ len++;
+ return len;
+}
+
+size_t
+lpprlen (struct locus_point const *lp)
+{
+ size_t len = 0;
+
+ if (lp->file)
+ {
+ len = strlen (lp->file) + 1 + nprlen (lp->line);
+ if (lp->col)
+ len += 1 + nprlen (lp->col);
+ }
+ return len;
+}
+
+size_t
+lrprlen (struct locus_range const *lr)
+{
+ size_t len;
+
+ len = lpprlen (&lr->end);
+ if (len)
+ len++;
+ len += lpprlen (&lr->beg);
+ return len;
+}
+
+enum
+ {
+ PR_NON = 0x00,
+ PR_FIL = 0x01,
+ PR_LIN = 0x02,
+ PR_COL = 0x04,
+ PR_ALL = PR_FIL|PR_LIN|PR_COL
+ };
+
+static ssize_t
+_numprint (char *p, unsigned num)
+{
+ size_t len = 0;
+ char *q;
+ do
+ {
+ p[len++] = (num % 10) + '0';
+ }
+ while (num /= 10);
+ for (q = p + len - 1; q > p; p++, q--)
+ {
+ char t = *p;
+ *p = *q;
+ *q = t;
+ }
+ return len;
+}
+
+ssize_t
+_lpprint (char *buf, struct locus_point const *lp, int pr)
+{
+ size_t n = 0;
+
+ if (!lp->file)
+ pr &= ~PR_FIL;
+ if (!lp->col)
+ pr &= ~PR_COL;
+
+ if (pr & PR_FIL)
+ {
+ n = strlen (lp->file);
+ memcpy (buf, lp->file, n);
+ }
+ if (pr & PR_LIN)
+ {
+ if (pr & PR_FIL)
+ buf[n++] = ':';
+ n += _numprint (buf + n, lp->line);
+ }
+ if (pr & PR_COL)
+ {
+ if (pr & PR_LIN)
+ buf[n++] = '.';
+ n += _numprint (buf + n, lp->col);
+ }
+ return n;
+}
+
+ssize_t
+locus_point_format (char **pbuf, size_t *psize, struct locus_point const *lp)
+{
+ char *buf;
+ size_t size;
+ size_t prlen;
+
+ if (!pbuf || !psize || !lp)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ buf = *pbuf;
+ size = *psize;
+
+ prlen = lpprlen (lp);
+ if (prlen + 1 > size)
+ {
+ size = prlen + 1;
+ buf = realloc (buf, size);
+ if (!buf)
+ return -1;
+ *pbuf = buf;
+ *psize = size;
+ }
+ return _lpprint (buf, lp, PR_ALL);
+}
+
+ssize_t
+locus_range_format (char **pbuf, size_t *psize, struct locus_range const *lr)
+{
+ char *buf;
+ size_t size;
+ size_t prlen;
+ size_t n;
+
+ if (!pbuf || !psize || !lr)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ buf = *pbuf;
+ size = *psize;
+
+ prlen = lrprlen (lr);
+ if (prlen + 1 > size)
+ {
+ size = prlen + 1;
+ buf = realloc (buf, size);
+ if (!buf)
+ return -1;
+ *pbuf = buf;
+ *psize = size;
+ }
+ n = _lpprint (buf, &lr->beg, PR_ALL);
+ buf += n;
+ if (lr->end.file)
+ {
+ int flag = PR_NON;
+
+ if (!locus_point_same_file (&lr->beg, &lr->end))
+ flag = PR_ALL;
+ else if (lr->beg.line != lr->end.line)
+ flag = PR_LIN|PR_COL;
+ else if (lr->beg.col && lr->beg.col != lr->end.col)
+ flag = PR_COL;
+
+ if (flag != PR_NON)
+ {
+ buf[n++] = '-';
+ n += _lpprint (buf + n, &lr->end, flag);
+ }
+ }
+ return n;
+}
diff --git a/mimetypes/tests/.gitignore b/mimetypes/tests/.gitignore
new file mode 100644
index 0000000..182f6aa
--- a/dev/null
+++ b/mimetypes/tests/.gitignore
@@ -0,0 +1,3 @@
+atlocal
+atconfig
+mimetest
diff --git a/mimetypes/tests/Makefile.am b/mimetypes/tests/Makefile.am
new file mode 100644
index 0000000..fe48300
--- a/dev/null
+++ b/mimetypes/tests/Makefile.am
@@ -0,0 +1,69 @@
+# This file is part of GNU Mailutils.
+# Copyright (C) 2017 Free Software Foundation, Inc.
+#
+# GNU Mailutils 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.
+#
+# GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+DISTCLEANFILES = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4. ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+ $(AM_V_GEN){ \
+ echo '# Signature of the current package.'; \
+ echo 'm4_define([AT_PACKAGE_NAME], [@PACKAGE_NAME@])'; \
+ echo 'm4_define([AT_PACKAGE_TARNAME], [@PACKAGE_TARNAME@])'; \
+ echo 'm4_define([AT_PACKAGE_VERSION], [@PACKAGE_VERSION@])'; \
+ echo 'm4_define([AT_PACKAGE_STRING], [@PACKAGE_STRING@])'; \
+ echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+ } >$(srcdir)/package.m4
+
+## -------------------------- ##
+## Non-installable programs ##
+## -------------------------- ##
+
+noinst_PROGRAMS = mimetest
+AM_CPPFLAGS = -I ..
+LDADD = ../libmimetypes.a
+## ------------ ##
+## Test suite. ##
+## ------------ ##
+
+TESTSUITE_AT = testsuite.at
+
+TESTSUITE = $(srcdir)/testsuite
+M4=m4
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT) $(top_srcdir)/testsuite/testsuite.inc
+ $(AM_V_GEN)$(AUTOTEST) -I $(srcdir) -I $(top_srcdir)/testsuite testsuite.at -o $@.tmp
+ $(AM_V_at)mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+ cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+ @test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig atlocal $(TESTSUITE)
+ @$(SHELL) $(TESTSUITE)
+
+# Run the test suite on the *installed* tree.
+#installcheck-local:
+# $(SHELL) $(TESTSUITE) AUTOTEST_PATH=$(exec_prefix)/bin
+
+
diff --git a/mimetypes/tests/atlocal.in b/mimetypes/tests/atlocal.in
new file mode 100644
index 0000000..19f0918
--- a/dev/null
+++ b/mimetypes/tests/atlocal.in
@@ -0,0 +1,5 @@
+# @configure_input@ -*- shell-script -*-
+# Configurable variable values for Mailutils test suite.
+
+PATH=@abs_builddir@:@abs_top_builddir@/mimetypes:$top_srcdir:$srcdir:$PATH
+
diff --git a/mimetypes/tests/mimetest.c b/mimetypes/tests/mimetest.c
new file mode 100644
index 0000000..a0d3353
--- a/dev/null
+++ b/mimetypes/tests/mimetest.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <assert.h>
+#include "mimetypes.h"
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ char const *type;
+ assert (argc >= 3);
+ assert (mimetypes_parse (argv[1]) == 0);
+ for (i = 2; i < argc; i++)
+ {
+ printf ("%s:", argv[i]);
+ type = get_file_type (argv[i]);
+ if (type)
+ printf (" %s", type);
+ putchar ('\n');
+ }
+ return 0;
+}
+
+
diff --git a/mimetypes/tests/testsuite.at b/mimetypes/tests/testsuite.at
new file mode 100644
index 0000000..5962837
--- a/dev/null
+++ b/mimetypes/tests/testsuite.at
@@ -0,0 +1 @@
+AT_INIT
diff --git a/mimetypes/yyloc.h b/mimetypes/yyloc.h
new file mode 100644
index 0000000..bb76d60
--- a/dev/null
+++ b/mimetypes/yyloc.h
@@ -0,0 +1,40 @@
+/* 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/>. */
+
+#ifndef __YYLOC_H
+#define __YYLOC_H
+
+#define YYLTYPE struct locus_range
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ { \
+ if (N) \
+ { \
+ (Current).beg = YYRHSLOC(Rhs, 1).beg; \
+ (Current).end = YYRHSLOC(Rhs, N).end; \
+ } \
+ else \
+ { \
+ (Current).beg = YYRHSLOC(Rhs, 0).end; \
+ (Current).end = (Current).beg; \
+ } \
+ } while (0)
+
+#define YY_LOCATION_PRINT(File, Loc) \
+ print_locus_range (File, &(Loc))
+
+#endif
+

Return to:

Send suggestions and report system problems to the System administrator.