%{
/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2005, 2007, 2009-2012, 2014-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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
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 (mu_isprint (toknum))
fprintf (output, "'%c'", toknum);
else
fprintf (output, "tok(%d)", toknum);
break;
}
}
#define YYPRINT yyprint
static mu_list_t arg_list; /* For error recovery */
#define L_OR 0
#define L_AND 1
enum node_type
{
true_node,
functional_node,
binary_node,
negation_node,
suffix_node
};
union argument
{
struct mimetypes_string *string;
unsigned number;
int c;
regex_t rx;
};
typedef int (*builtin_t) (union argument *args);
struct node
{
enum node_type type;
struct mu_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;
};
static struct node *make_node (enum node_type type,
struct mu_locus_range const *loc);
static struct node *make_binary_node (int op,
struct node *left, struct node *rigth,
struct mu_locus_range const *loc);
static struct node *make_negation_node (struct node *p,
struct mu_locus_range const *loc);
static struct node *make_suffix_node (struct mimetypes_string *suffix,
struct mu_locus_range const *loc);
static struct node *make_functional_node (char *ident, mu_list_t list,
struct mu_locus_range const *loc);
static int eval_rule (struct node *root);
struct rule_tab
{
char *type;
int priority;
struct mu_locus_range loc;
struct node *node;
};
static mu_list_t rule_list;
static size_t errors;
%}
%locations
%expect 15
%token TYPE IDENT
%token STRING
%token EOL BOGUS PRIORITY
%left ','
%left '+'
%type arg
%type arglist
%type function stmt rule maybe_rule
%type priority maybe_priority
%union {
struct mimetypes_string string;
char *s;
mu_list_t 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_tab *p = mimetypes_malloc (sizeof (*p));
if (!rule_list)
mu_list_create (&rule_list);
p->type = $1.ptr;
p->node = $2;
p->priority = $3;
mu_locus_point_copy (&p->loc.beg, &@1.beg);
mu_locus_point_copy (&p->loc.end, &@3.end);
#if 0
YY_LOCATION_PRINT (stderr, p->loc);
fprintf (stderr, ": rule %s\n", p->type);
#endif
mu_list_append (rule_list, p);
}
| BOGUS
{
YYERROR;
}
| error
{
errors++;
if (arg_list)
mu_list_destroy (&arg_list);
arg_list = NULL;
lex_next_rule ();
yyerrok;
yyclearin;
}
;
maybe_rule: /* empty */
{
$$ = make_node (true_node, &yylloc);
}
| rule
;
rule : stmt
| rule rule %prec ','
{
struct mu_locus_range lr;
lr.beg = @1.beg;
lr.end = @2.end;
$$ = make_binary_node (L_OR, $1, $2, &lr);
}
| rule ',' rule
{
struct mu_locus_range lr;
lr.beg = @1.beg;
lr.end = @3.end;
$$ = make_binary_node (L_OR, $1, $3, &lr);
}
| rule '+' rule
{
struct mu_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 ')'
{
size_t count = 0;
struct mimetypes_string *arg;
mu_list_count ($3, &count);
if (count != 1)
{
yyerror (_("priority takes single numberic argument"));
YYERROR;
}
mu_list_head ($3, (void**) &arg);
$$ = atoi (arg->ptr);
mu_list_destroy (&$3);
}
;
maybe_priority: /* empty */
{
$$ = 100;
}
| priority
;
function : IDENT '(' arglist ')'
{
struct mu_locus_range lr;
lr.beg = @1.beg;
lr.end = @4.end;
$$ = make_functional_node ($1.ptr, $3, &lr);
if (!$$)
YYERROR;
}
;
arglist : arg
{
mu_list_create (&arg_list);
$$ = arg_list;
mu_list_append ($$, mimetypes_string_dup (&$1));
}
| arglist ',' arg
{
mu_list_append ($1, mimetypes_string_dup (&$3));
$$ = $1;
}
;
arg : STRING
| BOGUS
{
YYERROR;
}
;
%%
int
mimetypes_parse (const char *name)
{
int rc;
if (mimetypes_open (name))
return 1;
yydebug = mu_debug_level_p (MU_DEBCAT_APP, MU_DEBUG_TRACE3);
rc = yyparse ();
mimetypes_close ();
return rc || errors;
}
static struct node *
make_node (enum node_type type, struct mu_locus_range const *loc)
{
struct node *p = mimetypes_malloc (sizeof *p);
p->type = type;
mu_locus_range_copy (&p->loc, loc);
return p;
}
static struct node *
make_binary_node (int op, struct node *left, struct node *right,
struct mu_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 mu_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 mu_locus_range const *loc)
{
struct node *node = make_node (suffix_node, loc);
node->v.suffix = *suffix;
return node;
}
struct builtin_tab
{
char *name;
char *args;
builtin_t handler;
};
/* match("pattern")
Pattern match on filename
*/
static int
b_match (union argument *args)
{
return fnmatch (args[0].string->ptr, mimeview_file, 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)
{
int i;
int rc;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
for (i = 0; i < args[1].number; i++)
{
unsigned char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0)
break;
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)
{
int i;
int rc;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
for (i = 0; i < args[1].number; i++)
{
unsigned char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0)
break;
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 mimetypes_string *str = args[1].string;
int i;
int rc;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
for (i = 0; i < str->len; i++)
{
char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0 || 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)
{
int i;
struct mimetypes_string *str = args[1].string;
int rc;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
for (i = 0; i < str->len; i++)
{
char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0 || mu_tolower (c) != mu_tolower (str->ptr[i]))
return 0;
}
return 1;
}
int
compare_bytes (union argument *args, void *sample, void *buf, size_t size)
{
int rc;
size_t n;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
rc = mu_stream_read (mimeview_stream, buf, size, &n);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
return 0;
}
else if (n != size)
return 0;
return memcmp (sample, buf, size) == 0;
}
/* char(offset,value)
True if byte is identical
*/
static int
b_char (union argument *args)
{
char val = args[1].number;
char buf;
return compare_bytes (args, &val, &buf, sizeof (buf));
}
/* short(offset,value)
True if 16-bit integer is identical
FIXME: Byte order
*/
static int
b_short (union argument *args)
{
uint16_t val = args[1].number;
uint16_t buf;
return compare_bytes (args, &val, &buf, sizeof (buf));
}
/* int(offset,value)
True if 32-bit integer is identical
FIXME: Byte order
*/
static int
b_int (union argument *args)
{
uint32_t val = args[1].number;
uint32_t buf;
return compare_bytes (args, &val, &buf, sizeof (buf));
}
/* locale("string")
True if current locale matches string
*/
static int
b_locale (union argument *args)
{
abort (); /* FIXME */
return 0;
}
/* contains(offset,range,"string")
True if the range contains the string
*/
static int
b_contains (union argument *args)
{
size_t i, count;
char *buf;
struct mimetypes_string *str = args[2].string;
int rc;
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
buf = mu_alloc (args[1].number);
rc = mu_stream_read (mimeview_stream, buf, args[1].number, &count);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
}
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)
{
size_t count;
int rc;
char buf[MIME_MAX_BUFFER];
rc = mu_stream_seek (mimeview_stream, args[0].number, MU_SEEK_SET, NULL);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_seek", NULL, rc);
return 0;
}
rc = mu_stream_read (mimeview_stream, buf, sizeof buf - 1, &count);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
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 node *
make_functional_node (char *ident, mu_list_t list,
struct mu_locus_range const *loc)
{
size_t count, i;
struct builtin_tab *p;
struct node *node;
union argument *args;
mu_iterator_t itr;
int rc;
for (p = builtin_tab; ; p++)
{
if (!p->name)
{
char *s;
mu_asprintf (&s, _("%s: unknown function"), ident);
yyerror (s);
free (s);
return NULL;
}
if (strcmp (ident, p->name) == 0)
break;
}
mu_list_count (list, &count);
i = strlen (p->args);
if (count < i)
{
char *s;
mu_asprintf (&s, _("too few arguments in call to `%s'"), ident);
yyerror (s);
free (s);
return NULL;
}
else if (count > i)
{
char *s;
mu_asprintf (&s, _("too many arguments in call to `%s'"), ident);
yyerror (s);
free (s);
return NULL;
}
args = mimetypes_malloc (count * sizeof *args);
mu_list_get_iterator (list, &itr);
for (i = 0, mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr), i++)
{
struct mimetypes_string *data;
char *tmp;
mu_iterator_current (itr, (void **)&data);
switch (p->args[i])
{
case 'd':
args[i].number = strtoul (data->ptr, &tmp, 0);
if (*tmp)
goto err;
break;
case 's':
args[i].string = data;
break;
case 'x':
{
char *s;
rc = mu_c_str_unescape_trans (data->ptr,
"\\\\\"\"a\ab\bf\fn\nr\rt\tv\v", &s);
if (rc)
{
mu_diag_funcall (MU_DIAG_ERROR, "mu_c_str_unescape_trans",
data->ptr, rc);
return NULL;
}
rc = regcomp (&args[i].rx, s, REG_EXTENDED|REG_NOSUB);
free (s);
if (rc)
{
char errbuf[512];
regerror (rc, &args[i].rx, errbuf, sizeof errbuf);
yyerror (errbuf);
return NULL;
}
}
break;
case 'c':
args[i].c = strtoul (data->ptr, &tmp, 0);
if (*tmp)
goto err;
break;
default:
abort ();
}
}
node = make_node (functional_node, loc);
node->v.function.fun = p->handler;
node->v.function.args = args;
return node;
err:
{
char *s;
mu_asprintf (&s,
_("argument %lu has wrong type in call to `%s'"),
(unsigned long) i, ident);
yyerror (s);
free (s);
return NULL;
}
}
static int
check_suffix (char *suf)
{
char *p = strrchr (mimeview_file, '.');
if (!p)
return 0;
return strcmp (p+1, suf) == 0;
}
void
mime_debug (int lev, struct mu_locus_range const *loc, char const *fmt, ...)
{
if (mu_debug_level_p (MU_DEBCAT_APP, lev))
{
va_list ap;
if (loc->beg.mu_col == 0)
mu_debug_log_begin ("%s:%u", loc->beg.mu_file, loc->beg.mu_line);
else if (strcmp(loc->beg.mu_file, loc->end.mu_file))
mu_debug_log_begin ("%s:%u.%u-%s:%u.%u",
loc->beg.mu_file,
loc->beg.mu_line, loc->beg.mu_col,
loc->end.mu_file,
loc->end.mu_line, loc->end.mu_col);
else if (loc->beg.mu_line != loc->end.mu_line)
mu_debug_log_begin ("%s:%u.%u-%u.%u",
loc->beg.mu_file,
loc->beg.mu_line, loc->beg.mu_col,
loc->end.mu_line, loc->end.mu_col);
else if (loc->beg.mu_col != loc->end.mu_col)
mu_debug_log_begin ("%s:%u.%u-%u",
loc->beg.mu_file,
loc->beg.mu_line, loc->beg.mu_col,
loc->end.mu_col);
else
mu_debug_log_begin ("%s:%u.%u",
loc->beg.mu_file,
loc->beg.mu_line, loc->beg.mu_col);
mu_stream_write (mu_strerr, ": ", 2, NULL);
va_start (ap, fmt);
mu_stream_vprintf (mu_strerr, fmt, ap);
va_end (ap);
mu_debug_log_nl ();
}
}
static int
eval_rule (struct node *root)
{
int result;
switch (root->type)
{
case true_node:
result = 1;
break;
case functional_node:
result = root->v.function.fun (root->v.function.args);
break;
case binary_node:
result = eval_rule (root->v.bin.arg1);
switch (root->v.bin.op)
{
case L_OR:
if (!result)
result |= eval_rule (root->v.bin.arg2);
break;
case L_AND:
if (result)
result &= eval_rule (root->v.bin.arg2);
break;
default:
abort ();
}
break;
case negation_node:
result = !eval_rule (root->v.arg);
break;
case suffix_node:
result = check_suffix (root->v.suffix.ptr);
break;
default:
abort ();
}
mime_debug (MU_DEBUG_TRACE2, &root->loc, "result %s", result ? "true" : "false");
return result;
}
static int
evaluate (void **itmv, size_t itmc, void *call_data)
{
struct rule_tab *p = itmv[0];
if (eval_rule (p->node))
{
itmv[0] = p;
mime_debug (MU_DEBUG_TRACE1, &p->loc, "rule %s matches", p->type);
return MU_LIST_MAP_OK;
}
return MU_LIST_MAP_SKIP;
}
static int
rule_cmp (const void *a, const void *b)
{
struct rule_tab const *arule = a;
struct rule_tab const *brule = b;
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 mu_c_strcasecmp (arule->type, brule->type);
}
return arule->priority - brule->priority;
}
const char *
get_file_type ()
{
mu_list_t res = NULL;
const char *type = NULL;
mu_list_map (rule_list, evaluate, NULL, 1, &res);
if (!mu_list_is_empty (res))
{
struct rule_tab *rule;
mu_list_sort (res, rule_cmp);
mu_list_head (res, (void**) &rule);
mime_debug (MU_DEBUG_TRACE0, &rule->loc, "selected rule %s", rule->type);
type = rule->type;
}
mu_list_destroy (&res);
return type;
}