summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2017-06-01 21:41:11 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2017-06-01 22:01:57 +0300
commit58cc7c8f6bd1956a47f56c3aba1be8a417916064 (patch)
tree04dfcd03e11372075aaf1623a5c59ab344617a8d
parentbe0acbf50cdf76521a549a8495052c9ab88ed553 (diff)
downloadmailutils-58cc7c8f6bd1956a47f56c3aba1be8a417916064.tar.gz
mailutils-58cc7c8f6bd1956a47f56c3aba1be8a417916064.tar.bz2
Revise the mime.types lexer; provide the testsuite
* am/testsuite.m4: New file. * configure.ac: Use the MU_CONFIG_TESTSUITE macro. * mimeview/Makefile.am (SUBDIRS): Add tests * mimeview/mimetypes.l: Rewrite in three exclusive states. * mimeview/mimetypes.y: Simplify grammar. * mimeview/mimeview.c: New option --identify (-i). * mimeview/mimeview.h: Update. * mimeview/tests/Makefile.am: New file. * mimeview/tests/atlocal.in: New file. * mimeview/tests/bf.c: New file. * mimeview/tests/testsuite.at: New file. * README: Update. * doc/texinfo/programs.texi
-rw-r--r--NEWS5
-rw-r--r--am/testsuite.m45
-rw-r--r--configure.ac48
-rw-r--r--doc/texinfo/programs.texi4
-rw-r--r--mimeview/Makefile.am2
-rw-r--r--mimeview/mimetypes.l190
-rw-r--r--mimeview/mimetypes.y91
-rw-r--r--mimeview/mimeview.c29
-rw-r--r--mimeview/mimeview.h13
-rw-r--r--mimeview/tests/.gitignore7
-rw-r--r--mimeview/tests/Makefile.am68
-rw-r--r--mimeview/tests/atlocal.in5
-rw-r--r--mimeview/tests/bf.c200
-rw-r--r--mimeview/tests/testsuite.at200
14 files changed, 693 insertions, 174 deletions
diff --git a/NEWS b/NEWS
index 99e8b35bb..266a99912 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-GNU mailutils NEWS -- history of user-visible changes. 2017-05-31
+GNU mailutils NEWS -- history of user-visible changes. 2017-06-01
Copyright (C) 2002-2017 Free Software Foundation, Inc.
See the end of file for copying conditions.
@@ -145,6 +145,9 @@ New option '--lint' (short '-t') instructs the tool to check the
syntax of the mime.types file and exit, ignoring any surplus command
line arguments.
+New option '-i' ('--identify') identifies and prints the MIME type for
+each input file, but not starts viewer.
+
Added support for priority and regex functions.
Debugging considerably improved.
diff --git a/am/testsuite.m4 b/am/testsuite.m4
new file mode 100644
index 000000000..f61df4a08
--- /dev/null
+++ b/am/testsuite.m4
@@ -0,0 +1,5 @@
+# Initialize the (autotest) test suite.
+AC_DEFUN([MU_CONFIG_TESTSUITE],
+[AC_CONFIG_TESTDIR([$1/tests])
+AC_CONFIG_FILES([$1/tests/Makefile $1/tests/atlocal])
+])
diff --git a/configure.ac b/configure.ac
index 8288a4d9a..3f8418873 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1324,40 +1324,22 @@ test -z "$server_list" && server_list=" [NONE]"
test -z "$client_list" && client_list=" [NONE]"
# Initialize the (autotest) test suite.
-AC_CONFIG_TESTDIR(libmailutils/tests)
+
AC_CONFIG_TESTDIR(testsuite)
-AC_CONFIG_TESTDIR(frm/tests)
-AC_CONFIG_TESTDIR(maidag/tests)
-AC_CONFIG_TESTDIR(mail/tests)
-AC_CONFIG_TESTDIR(messages/tests)
-AC_CONFIG_TESTDIR(readmsg/tests)
-AC_CONFIG_TESTDIR(sieve/tests)
-AC_CONFIG_TESTDIR(mh/tests)
-AC_CONFIG_TESTDIR(comsat/tests)
-AC_CONFIG_TESTDIR(imap4d/tests)
-
-AC_CONFIG_FILES([libmailutils/tests/Makefile
- libmailutils/tests/atlocal
- testsuite/Makefile
- testsuite/atlocal
- comsat/tests/Makefile
- comsat/tests/atlocal
- frm/tests/Makefile
- frm/tests/atlocal
- imap4d/tests/Makefile
- imap4d/tests/atlocal
- maidag/tests/Makefile
- maidag/tests/atlocal
- mail/tests/Makefile
- mail/tests/atlocal
- messages/tests/Makefile
- messages/tests/atlocal
- readmsg/tests/Makefile
- readmsg/tests/atlocal
- sieve/tests/Makefile
- sieve/tests/atlocal
- mh/tests/Makefile
- mh/tests/atlocal])
+AC_CONFIG_FILES([testsuite/Makefile testsuite/atlocal])
+
+MU_CONFIG_TESTSUITE(libmailutils)
+MU_CONFIG_TESTSUITE(frm)
+MU_CONFIG_TESTSUITE(maidag)
+MU_CONFIG_TESTSUITE(mail)
+MU_CONFIG_TESTSUITE(messages)
+MU_CONFIG_TESTSUITE(readmsg)
+MU_CONFIG_TESTSUITE(sieve)
+MU_CONFIG_TESTSUITE(mh)
+MU_CONFIG_TESTSUITE(comsat)
+MU_CONFIG_TESTSUITE(imap4d)
+MU_CONFIG_TESTSUITE(mimeview)
+
AM_MISSING_PROG([AUTOM4TE], [autom4te])
dnl Make sysconfdir available to the application
diff --git a/doc/texinfo/programs.texi b/doc/texinfo/programs.texi
index 954f782a3..9bcd6c363 100644
--- a/doc/texinfo/programs.texi
+++ b/doc/texinfo/programs.texi
@@ -7359,6 +7359,10 @@ By default @command{mimeview} behaves as if given
@option{--no-interactive} option whenever its standard input is not
a @asis{tty} device.
+@item -i
+@itemx --identify
+Identifies and prints the MIME type for each input file.
+
@item -n
@itemx --dry-run
Do not do anything, just print what would be done. Implies
diff --git a/mimeview/Makefile.am b/mimeview/Makefile.am
index bb2bc2227..2ae80fc92 100644
--- a/mimeview/Makefile.am
+++ b/mimeview/Makefile.am
@@ -15,6 +15,8 @@
## You should have received a copy of the GNU General Public License
## along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
+SUBDIRS = tests
+
AM_CPPFLAGS = \
@MU_APP_COMMON_INCLUDES@\
-D_GNU_SOURCE=1\
diff --git a/mimeview/mimetypes.l b/mimeview/mimetypes.l
index 6e74163b7..de03ea097 100644
--- a/mimeview/mimetypes.l
+++ b/mimeview/mimetypes.l
@@ -42,9 +42,16 @@ digit_to_number (char c)
c-'a'+10);
}
+static struct mu_locus prev_loc;
+static struct mu_locus string_beg;
+static int prev_newline;
+
static void
advance_locus (void)
{
+ prev_loc = loc;
+ prev_newline = newline;
+
if (newline)
{
loc.mu_line++;
@@ -54,99 +61,174 @@ advance_locus (void)
loc.mu_col += yyleng;
yylloc.end = loc;
yylloc.end.mu_col--;
-
+
+#if 0
+ printf ("+%2d> %u:%u-%u:%u: %s\n",
+ yyleng,
+ yylloc.beg.mu_line, yylloc.beg.mu_col,
+ yylloc.end.mu_line, yylloc.end.mu_col, yytext);
+#endif
newline = yytext[yyleng-1] == '\n';
mu_stream_ioctl (mu_strerr, MU_IOCTL_LOGSTREAM,
MU_IOCTL_LOGSTREAM_SET_LOCUS, &loc);
}
-#define YY_USER_ACTION advance_locus ();
+static void
+retreat_locus (void)
+{
+ loc = prev_loc;
+ newline = prev_newline;
+}
+static void
+finish_string (void)
+{
+ mu_opool_append_char (pool, 0);
+ yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
+ yylval.string.len--;
+ yylloc.end = yylloc.beg;
+ yylloc.end.mu_col--;
+ yylloc.beg = string_beg;
+ if (mu_debug_level_p (MU_DEBCAT_APP, MU_DEBUG_TRACE5))
+ {
+ size_t i;
+ mu_debug_log_begin ("string %d: ", yylval.string.len);
+ for (i = 0; i < yylval.string.len; i++)
+ if (mu_isprint (yylval.string.ptr[i]))
+ mu_debug_log_cont ("%c", yylval.string.ptr[i]);
+ else
+ mu_debug_log_cont ("\\%03o", yylval.string.ptr[i]);
+ mu_debug_log_nl ();
+ }
+#if 0
+ YY_LOCATION_PRINT (stderr, yylloc);
+ fprintf (stderr, ": %s\n", yylval.string.ptr);
+#endif
+}
+
+#define YY_USER_ACTION advance_locus ();
%}
%option nounput
%option noinput
-%s RULE ARGS
+%x RULE ARGS ASTRING
X [0-9a-fA-F]
IDENT [a-zA-Z_\.][a-zA-Z0-9_\.-]*
-WS [ \t]*
+WS [ \t][ \t]*
%%
+
+<INITIAL>{
/* Comments */
^#.*\n ;
- /* Tokens */
-\\\n ;
-\n+ { loc.mu_line += yyleng - 1; return EOL; }
-{WS} ;
-<INITIAL,RULE>^[^ \t\n/]+"/"[^ \t\n]+ {
+\n ;
+^[^ \t\n/]+"/"[^ \t\n]+ {
mu_opool_append (pool, yytext, yyleng);
mu_opool_append_char (pool, 0);
yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
+ yylval.string.len--;
BEGIN (RULE);
return TYPE;
}
+}
+
+<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 ',';
-<RULE>"priority"/"(" {
+"priority"/"(" {
return PRIORITY;
}
-<RULE>{IDENT}/"(" {
+
+{IDENT}/"(" {
mu_opool_append (pool, yytext, yyleng);
mu_opool_append_char (pool, 0);
yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
+ BEGIN (ARGS);
return IDENT;
-}
-<RULE>{IDENT} {
+}
+
+[a-zA-Z0-9_.-]+/[^(] {
mu_opool_append (pool, yytext, yyleng);
- mu_opool_append_char (pool, 0);
yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
return STRING;
}
-<RULE,ARGS>\"[^"\n]*\" {
+
+. mu_error("unexpected character '%c'", yytext[0]);
+}
+
+<ARGS>{
+"("|"," return yytext[0];
+")" {
+ BEGIN (RULE);
+ return yytext[0];
+}
+{WS} mu_error ("unexpected whitespace in argument list");
+\n {
+ mu_error ("unexpected newline in argument list");
+ BEGIN (RULE);
+ return EOL;
+}
+. {
+ string_beg = yylloc.beg;
+ retreat_locus ();
+ yyless (0);
+ BEGIN (ASTRING);
+}
+}
+
+<ASTRING>{
+ /* Quoted string */
+\"[^"\n]*\" {
+ mu_opool_append (pool, yytext+1, yyleng-2);
+}
+"'"[^'\n]*"'" {
mu_opool_append (pool, yytext+1, yyleng-2);
- mu_opool_append_char (pool, 0);
- yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
- return STRING;
}
-<RULE,ARGS>"<"({X}{X})+">" {
+
+ /* Hex string */
+"<"({X}{X})+">" {
int i;
- for (i = 0; i < yyleng; i += 2)
+ for (i = 1; i < yyleng - 2; i += 2)
{
mu_opool_append_char (pool, digit_to_number (yytext[i])*16
+ digit_to_number (yytext[i+1]));
- }
- yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
- return STRING;
+ }
}
-<ARGS>[^ \t<\n),<"]+/[),<"] {
+
+ /* Unquoted character sequence */
+[^ \t\n,)<"']+/[^"'<] {
mu_opool_append (pool, yytext, yyleng);
- mu_opool_append_char (pool, 0);
- yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
- return STRING;
}
-<RULE>[^ \t<\\\n)+,&]/[ \t\\\n)+,&] {
+
+[^ \t\n,)<"]+/< {
mu_opool_append (pool, yytext, yyleng);
- mu_opool_append_char (pool, 0);
- yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
- return STRING;
}
-<ARGS>[^ \t<\\\n),]/[ \t\\\n] {
+
+[^ \t\n,)<"]+/["'] {
mu_opool_append (pool, yytext, yyleng);
- mu_opool_append_char (pool, 0);
- yylval.string.ptr = mu_opool_finish (pool, &yylval.string.len);
+}
+
+. {
+ retreat_locus ();
+ yyless (0);
+ BEGIN (ARGS);
+ finish_string ();
return STRING;
}
- /* Special cases: && and ||. Docs don't say anything about them, but
- I've found them in my mime.types file... --Sergey */
-"&&" return '+';
-"||" return ',';
- /* Operators */
-<RULE>"!"|"+"|","|"("|")"|"/" return yytext[0];
-<ARGS>"," return yytext[0];
-<ARGS>")" { BEGIN (RULE); return yytext[0]; }
-<*>. {
- mu_error ("invalid character '%c', state %d", yytext[0], YYSTATE);
- return BOGUS;
}
+
%%
int
mimetypes_open (const char *name)
@@ -236,21 +318,7 @@ mimetypes_malloc (size_t size)
}
void
-lex_arglist (int enable)
-{
- if (enable)
- BEGIN (ARGS);
- else
- BEGIN (RULE);
-}
-
-void
-lex_concat (struct concat_segm *p, struct mimetypes_string *ret)
+lex_reset (void)
{
- for (; p; p = p->next)
- {
- mu_opool_appendz (pool, p->val);
- }
- mu_opool_append_char (pool, 0);
- ret->ptr = mu_opool_finish (pool, &ret->len);
+ BEGIN (INITIAL);
}
diff --git a/mimeview/mimetypes.y b/mimeview/mimetypes.y
index 051aa9967..6d6bc31ed 100644
--- a/mimeview/mimetypes.y
+++ b/mimeview/mimetypes.y
@@ -132,12 +132,10 @@ static mu_list_t rule_list;
%left ','
%left '+'
-%type <string> string arg
+%type <string> arg
%type <list> arglist
%type <node> function stmt rule maybe_rule
%type <result> priority maybe_priority
-%type <concat> concat;
-%type <segment> simple_string
%union {
struct mimetypes_string string;
@@ -145,8 +143,6 @@ static mu_list_t rule_list;
mu_list_t list;
int result;
struct node *node;
- struct { struct concat_segm *head, *tail; } concat;
- struct concat_segm *segment;
}
%%
@@ -176,7 +172,7 @@ rule_line: /* empty */
if (arg_list)
mu_list_destroy (&arg_list);
arg_list = NULL;
- lex_arglist (0);
+ lex_reset ();
}
;
@@ -219,39 +215,14 @@ stmt : '!' stmt
{
$$ = $2;
}
- | string
+ | STRING
{
$$ = make_suffix_node (&$1, &@1);
}
| function
;
-string : concat
- {
- lex_concat ($1.head, &$$);
- }
- ;
-
-concat : simple_string
- {
- $$.head = $$.tail = $1;
- }
- | concat simple_string
- {
- $$.tail->next = $2;
- $$.tail = $2;
- }
- ;
-
-simple_string : STRING
- {
- $$ = mu_alloc (sizeof $$);
- $$->next = NULL;
- $$->val = $1.ptr;
- }
- ;
-
-priority : PRIORITY oparen arglist cparen
+priority : PRIORITY '(' arglist ')'
{
size_t count = 0;
struct mimetypes_string *arg;
@@ -275,19 +246,7 @@ maybe_priority: /* empty */
| priority
;
-oparen : '('
- {
- lex_arglist (1);
- }
- ;
-
-cparen : ')'
- {
- lex_arglist (0);
- }
- ;
-
-function : IDENT oparen arglist cparen
+function : IDENT '(' arglist ')'
{
struct mu_locus_range lr;
lr.beg = @1.beg;
@@ -312,7 +271,7 @@ arglist : arg
}
;
-arg : string
+arg : STRING
;
%%
@@ -387,6 +346,9 @@ b_match (union argument *args)
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)
{
@@ -402,13 +364,13 @@ b_ascii (union argument *args)
for (i = 0; i < args[1].number; i++)
{
- char c;
+ unsigned char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0)
break;
- if (!mu_isascii (c))
+ if (!ISASCII (c))
return 0;
}
@@ -419,10 +381,8 @@ b_ascii (union argument *args)
True if bytes are printable 8-bit chars (CR, NL, TAB,
BS, 32-126, 128-254)
*/
-#define ISPRINT(c) ((c) &&\
- (strchr ("\n\r\t\b",c) \
- || (32<=(c) && (c)<=126) \
- || (128<=(c) && (c)<=254)))
+#define ISPRINT(c) (ISASCII (c) \
+ || (128<=((unsigned) c) && ((unsigned) c)<=254))
static int
b_printable (union argument *args)
{
@@ -438,13 +398,13 @@ b_printable (union argument *args)
for (i = 0; i < args[1].number; i++)
{
- char c;
+ unsigned char c;
size_t n;
rc = mu_stream_read (mimeview_stream, &c, 1, &n);
if (rc || n == 0)
break;
- if (!ISPRINT ((unsigned)c))
+ if (!ISPRINT (c))
return 0;
}
return 1;
@@ -552,8 +512,8 @@ b_char (union argument *args)
static int
b_short (union argument *args)
{
- unsigned short val = args[1].number;
- unsigned short buf;
+ uint16_t val = args[1].number;
+ uint16_t buf;
return compare_bytes (args, &val, &buf, sizeof (buf));
}
@@ -564,8 +524,8 @@ b_short (union argument *args)
static int
b_int (union argument *args)
{
- unsigned int val = args[1].number;
- unsigned int buf;
+ uint32_t val = args[1].number;
+ uint32_t buf;
return compare_bytes (args, &val, &buf, sizeof (buf));
}
@@ -604,7 +564,7 @@ b_contains (union argument *args)
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_read", NULL, rc);
}
else if (count > str->len)
- for (i = 0; i < count - str->len; i++)
+ for (i = 0; i <= count - str->len; i++)
if (buf[i] == str->ptr[0] && memcmp (buf + i, str->ptr, str->len) == 0)
{
free (buf);
@@ -896,7 +856,16 @@ rule_cmp (const void *a, const void *b)
struct rule_tab const *brule = b;
if (arule->priority == brule->priority)
- return mu_c_strcasecmp (arule->type, brule->type);
+ {
+ 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;
}
diff --git a/mimeview/mimeview.c b/mimeview/mimeview.c
index 84a28adc1..e67593d14 100644
--- a/mimeview/mimeview.c
+++ b/mimeview/mimeview.c
@@ -33,12 +33,13 @@
static int dry_run; /* Dry run mode */
static int lint; /* Syntax check mode */
+static int identify; /* Print only the file's type */
static char *metamail; /* Name of metamail program, if requested */
static char *mimetypes_config = DEFAULT_CUPS_CONFDIR;
static char *no_ask_types; /* List of MIME types for which no questions
should be asked */
static int interactive = -1;
-char *mimeview_file; /* Name of the file to view */
+char const *mimeview_file; /* Name of the file to view */
mu_stream_t mimeview_stream; /* The corresponding stream */
@@ -121,6 +122,10 @@ static struct mu_option mimeview_options[] = {
{ "lint", 't', NULL, MU_OPTION_DEFAULT,
N_("test mime.types syntax and exit"),
mu_c_bool, &lint },
+
+ { "identify", 'i', NULL, MU_OPTION_DEFAULT,
+ N_("identify MIME type of each file"),
+ mu_c_bool, &identify },
{ "metamail", 0, N_("FILE"), MU_OPTION_ARG_OPTIONAL,
N_("use metamail to display files"),
@@ -157,7 +162,7 @@ static char *capa[] = {
};
static int
-open_file (char *name)
+open_file (char const *name)
{
int rc;
struct stat st;
@@ -190,9 +195,18 @@ close_file ()
}
void
-display_file (const char *type)
+display_file (const char *file, const char *type)
{
int status;
+
+ if (identify)
+ {
+ printf ("%s: %s\n", file, type ? type : "unknown");
+ return;
+ }
+
+ if (!type)
+ return;
if (metamail)
{
@@ -205,7 +219,7 @@ display_file (const char *type)
argv[3] = "-c";
argv[4] = (char*) type;
- argv[5] = mimeview_file;
+ argv[5] = (char*) mimeview_file;
argv[6] = NULL;
if (mu_debug_level_p (MU_DEBCAT_MIME, MU_DEBUG_TRACE0))
@@ -269,12 +283,11 @@ main (int argc, char **argv)
while (argc--)
{
const char *type;
-
- if (open_file (*argv++))
+ char const *file = *argv++;
+ if (open_file (file))
continue;
type = get_file_type ();
- if (type)
- display_file (type);
+ display_file (file, type);
close_file ();
}
diff --git a/mimeview/mimeview.h b/mimeview/mimeview.h
index b940b0836..36f9fbfa3 100644
--- a/mimeview/mimeview.h
+++ b/mimeview/mimeview.h
@@ -37,24 +37,17 @@ int mimetypes_open (const char *name);
void mimetypes_close (void);
int mimetypes_parse (const char *name);
void mimetypes_lex_init (void);
-void lex_arglist (int);
+
+void lex_reset (void);
void *mimetypes_malloc (size_t size);
struct mimetypes_string *mimetypes_string_dup (struct mimetypes_string *s);
const char *get_file_type (void);
-extern char *mimeview_file;
+extern char const *mimeview_file;
extern mu_stream_t mimeview_stream;
-struct concat_segm
-{
- struct concat_segm *next;
- char const *val;
-};
-
-void lex_concat (struct concat_segm *p, struct mimetypes_string *ret);
-
struct mu_locus_range
{
struct mu_locus beg;
diff --git a/mimeview/tests/.gitignore b/mimeview/tests/.gitignore
new file mode 100644
index 000000000..a0a8955c1
--- /dev/null
+++ b/mimeview/tests/.gitignore
@@ -0,0 +1,7 @@
+atconfig
+atlocal
+bf
+package.m4
+testsuite
+testsuite.dir
+testsuite.log
diff --git a/mimeview/tests/Makefile.am b/mimeview/tests/Makefile.am
new file mode 100644
index 000000000..ae04580f6
--- /dev/null
+++ b/mimeview/tests/Makefile.am
@@ -0,0 +1,68 @@
+# 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 = bf
+
+## ------------ ##
+## 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/mimeview/tests/atlocal.in b/mimeview/tests/atlocal.in
new file mode 100644
index 000000000..8112255c5
--- /dev/null
+++ b/mimeview/tests/atlocal.in
@@ -0,0 +1,5 @@
+# @configure_input@ -*- shell-script -*-
+# Configurable variable values for Mailutils test suite.
+
+PATH=@abs_builddir@:@abs_top_builddir@/mimeview:$top_srcdir:$srcdir:$PATH
+
diff --git a/mimeview/tests/bf.c b/mimeview/tests/bf.c
new file mode 100644
index 000000000..e27561654
--- /dev/null
+++ b/mimeview/tests/bf.c
@@ -0,0 +1,200 @@
+/* This file is part of the GNU Mailutils testsuite.
+ 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/>. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <inttypes.h>
+
+typedef int (*ACTION) (FILE *, char *);
+
+int
+seek_file (FILE *fp, char *arg)
+{
+ long off;
+ int whence;
+
+ switch (arg[0])
+ {
+ case '+':
+ whence = SEEK_CUR;
+ arg++;
+ break;
+
+ case '-':
+ whence = SEEK_CUR;
+ break;
+
+ case '$':
+ whence = SEEK_END;
+ arg++;
+ break;
+
+ default:
+ whence = SEEK_SET;
+ }
+
+ if (sscanf (arg, "%ld", &off) != 1)
+ {
+ fprintf (stderr, "bad offset: %s\n", arg);
+ abort ();
+ }
+ return fseek (fp, off, whence);
+}
+
+int
+write_string (FILE *fp, char *arg)
+{
+ size_t n = strlen (arg);
+ return fwrite (arg, n, 1, fp) != 1;
+}
+
+int
+write_byte (FILE *fp, char *arg)
+{
+ int c;
+ if (strlen (arg) == 3
+ && (arg[0] == '\'' || arg[0] == '"'))
+ c = arg[1];
+ else
+ {
+ char *p;
+ unsigned long n = strtoul (arg, &p, 0);
+ if (*p || n > UCHAR_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ c = n;
+ }
+ return fwrite (&c, 1, 1, fp) != 1;
+}
+
+int
+write_short (FILE *fp, char *arg)
+{
+ uint16_t val;
+ char *p;
+ unsigned long n = strtoul (arg, &p, 0);
+ if (*p || n > UINT16_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ val = n;
+ return fwrite (&val, sizeof(val), 1, fp) != 1;
+}
+
+int
+write_int (FILE *fp, char *arg)
+{
+ uint32_t val;
+ char *p;
+ unsigned long n = strtoul (arg, &p, 0);
+ if (*p || n > UINT32_MAX)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ val = n;
+ return fwrite (&val, sizeof(val), 1, fp) != 1;
+}
+
+static struct dispatch {
+ char *opt;
+ int (*act) (FILE *, char *);
+} dispatch[] = {
+ { "-seek", seek_file },
+ { "-string", write_string },
+ { "-byte", write_byte },
+ { "-short", write_short },
+ { "-int", write_int },
+ { NULL }
+};
+
+static ACTION
+find_action (char const *opt)
+{
+ struct dispatch *p;
+
+ for (p = dispatch; p->opt; p++)
+ if (strcmp (p->opt, opt) == 0)
+ return p->act;
+ return NULL;
+}
+
+/*
+ -seek OFF
+ -string STRING
+ -byte BYTE
+ -short SHORT
+ -int INT
+ -repeat N
+ */
+int
+main (int argc, char **argv)
+{
+ char *name;
+ FILE *fp;
+ ACTION action;
+ char **pp;
+
+ assert (argc > 1);
+
+ name = argv[1];
+ fp = fopen (name, "w");
+ if (!fp)
+ {
+ perror (name);
+ abort ();
+ }
+
+ pp = argv + 2;
+ while (*pp)
+ {
+ char *opt = *pp++, *arg;
+
+ if (opt[0] != '-')
+ {
+ fprintf (stderr, "not an option: %s\n", opt);
+ abort ();
+ }
+
+ action = find_action (opt);
+ if (!action)
+ {
+ fprintf (stderr, "unknown action: %s\n", opt);
+ abort ();
+ }
+
+ if (!*pp)
+ {
+ fprintf (stderr, "argument to %s missing\n", opt);
+ abort ();
+ }
+ arg = *pp++;
+ if (action (fp, arg))
+ {
+ fprintf (stderr, "%s %s: %s\n", opt, arg, strerror (errno));
+ abort ();
+ }
+ }
+ fclose (fp);
+ return 0;
+}
diff --git a/mimeview/tests/testsuite.at b/mimeview/tests/testsuite.at
new file mode 100644
index 000000000..971b1a211
--- /dev/null
+++ b/mimeview/tests/testsuite.at
@@ -0,0 +1,200 @@
+# This file is part of GNU Mailutils. -*- Autotest -*-
+# 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/>.
+
+m4_include([testsuite.inc])
+
+dnl ------------------------------------------------------------
+dnl MIMEVIEW_OPTIONS -- default options for mimeview
+m4_pushdef([MIMEVIEW_OPTIONS],[--no-site --no-user])
+
+m4_pushdef([__prepare_input],[
+m4_if([$1],[],[],[bf $1 $2
+__prepare_input(m4_shift(m4_shift(m4_shift($@))))])])
+
+m4_pushdef([prepare_input],[__prepare_input(m4_shift(m4_shift($@)))])
+
+m4_pushdef([__select_args],[dnl
+m4_if([$2],[],[$1],[dnl
+__select_args([$1 $2], m4_shift(m4_shift(m4_shift(m4_shift($@)))))])])
+
+m4_pushdef([select_args],[__select_args([],m4_shift(m4_shift($@)))])
+
+m4_pushdef([__build_expect],[dnl
+m4_if([$2],[],[$1],[__build_expect([dnl
+$1[]dnl
+$2: $4
+],m4_shift(m4_shift(m4_shift(m4_shift($@)))))])])
+
+m4_pushdef([build_expect],[__build_expect([],m4_shift(m4_shift($@)))])
+
+# MIMETE