diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2018-01-03 12:23:44 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2018-01-03 12:23:52 +0200 |
commit | 80ed5e611de0e31eed65ee207f1fd64ad097e6d1 (patch) | |
tree | 40979fe5ecadaa3d1351c645ff7988a87218e142 | |
parent | a4edd153e4cdf703c98008313743b112274e760f (diff) | |
download | fileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.gz fileserv-80ed5e611de0e31eed65ee207f1fd64ad097e6d1.tar.bz2 |
Add mimetypes library.
Ported from GNU Mailutils.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | configure.ac | 19 | ||||
-rw-r--r-- | fileserv.c | 36 | ||||
-rw-r--r-- | mimetypes/.gitignore | 4 | ||||
-rw-r--r-- | mimetypes/Makefile.am | 18 | ||||
-rw-r--r-- | mimetypes/err.c | 150 | ||||
-rw-r--r-- | mimetypes/eval.c | 422 | ||||
-rw-r--r-- | mimetypes/grammar.y | 397 | ||||
-rw-r--r-- | mimetypes/ident.c | 85 | ||||
-rw-r--r-- | mimetypes/lexer.l | 371 | ||||
-rw-r--r-- | mimetypes/linetrack.c | 380 | ||||
-rw-r--r-- | mimetypes/locus.c | 95 | ||||
-rw-r--r-- | mimetypes/locus.h | 104 | ||||
-rw-r--r-- | mimetypes/mimetypes.h | 3 | ||||
-rw-r--r-- | mimetypes/mtint.h | 169 | ||||
-rw-r--r-- | mimetypes/prloc.c | 172 | ||||
-rw-r--r-- | mimetypes/tests/.gitignore | 3 | ||||
-rw-r--r-- | mimetypes/tests/Makefile.am | 69 | ||||
-rw-r--r-- | mimetypes/tests/atlocal.in | 5 | ||||
-rw-r--r-- | mimetypes/tests/mimetest.c | 23 | ||||
-rw-r--r-- | mimetypes/tests/testsuite.at | 1 | ||||
-rw-r--r-- | mimetypes/yyloc.h | 40 |
23 files changed, 2563 insertions, 7 deletions
@@ -23 +23,2 @@ install-sh missing +ylwrap diff --git a/Makefile.am b/Makefile.am index 5a3428f..cf663e3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7 +7,4 @@ 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 @@ -2,3 +2,3 @@ # This file is part of fileserv. -# Copyright (C) 2017 Sergey Poznyakoff +# Copyright (C) 2017, 2018 Sergey Poznyakoff # @@ -25,2 +25,13 @@ 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],[], @@ -33,3 +44,7 @@ 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 @@ -1,3 +1,3 @@ /* This file is part of fileserv. - Copyright (C) 2017 Sergey Poznyakoff + Copyright (C) 2017, 2018 Sergey Poznyakoff @@ -37,2 +37,3 @@ #include "fileserv.h" +#include "mimetypes.h" @@ -687,2 +688,3 @@ fileserv_handler(void *cls, int fd; + char const *type; @@ -701,7 +703,6 @@ fileserv_handler(void *cls, 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); @@ -709,2 +710,5 @@ fileserv_handler(void *cls, + type = get_file_type(file_name); + free(file_name); + if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { @@ -719,2 +723,8 @@ fileserv_handler(void *cls, } + + if (type) + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + type); + ret = MHD_queue_response(conn, MHD_HTTP_OK, response); @@ -726,2 +736,8 @@ fileserv_handler(void *cls, +static void +fileserv_error_printer (char const *msg) +{ + error("%s", msg); +} + int @@ -744,2 +760,3 @@ main(int argc, char **argv) }; + char *mime_types_file = NULL; @@ -750,4 +767,6 @@ main(int argc, char **argv) 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) { @@ -768,2 +787,5 @@ main(int argc, char **argv) exit(0); + case 'm': + mime_types_file = optarg; + break; case 'p': @@ -789,2 +811,6 @@ main(int argc, char **argv) pidfile_check(); + + if (mime_types_file) { + mimetypes_parse (mime_types_file); //FIXME: diag + } 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, |