/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999-2024 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 . */
%{
#include "mail.h"
#include
#include
/* Defined in on some systems, but redefined in
if we are using GNU's regex. So, undef it to avoid duplicate definition
warnings. */
#ifdef RE_DUP_MAX
# undef RE_DUP_MAX
#endif
#include
struct header_data
{
char *header;
char *expr;
};
static msgset_t *msgset_select (int (*sel) (mu_message_t, void *),
void *closure, int rev,
unsigned int max_matches);
static int select_header (mu_message_t msg, void *closure);
static int select_body (mu_message_t msg, void *closure);
static int select_sender (mu_message_t msg, void *closure);
static int select_deleted (mu_message_t msg, void *closure);
static int check_set (msgset_t **pset, int flags);
struct msgset_parser
{
int flags;
size_t message_count;
msgset_t *result;
};
struct msgset_lexer
{
int xargc;
char **xargv;
int cur_ind;
char *cur_p;
mu_opool_t tokpool;
};
int yyerror (struct msgset_parser *, struct msgset_lexer *, const char *);
int yylex (struct msgset_lexer *);
static void
msgset_parser_free (struct msgset_parser *parser)
{
msgset_free (parser->result);
}
static void
msgset_lexer_free (struct msgset_lexer *lexer)
{
mu_opool_destroy (&lexer->tokpool);
}
static msgset_t *
msgset_concat (msgset_t *mset, msgset_t *part)
{
msgset_t *ret = msgset_expand (mset, part);
msgset_free (mset);
msgset_free (part);
return ret;
}
typedef int (*message_selector_t) (mu_message_t, void *);
static message_selector_t find_type_selector (int type);
%}
%parse-param { struct msgset_parser *parser } { struct msgset_lexer *lexer }
%lex-param { struct msgset_lexer *lexer }
%expect 6
%union {
char *string;
int number;
int type;
msgset_t *mset;
}
%token TYPE
%token IDENT REGEXP HEADER BODY
%token NUMBER
%type msgset msgspec msgexpr msg rangeset range partno number
%type header
%%
input : /* empty */
{
parser->result = msgset_make_1 (get_cursor ());
}
| '.'
{
parser->result = msgset_make_1 (get_cursor ());
}
| msgset
{
parser->result = $1;
}
| '^'
{
parser->result = msgset_select (select_deleted, lexer, 0, 1);
}
| '$'
{
parser->result = msgset_select (select_deleted, lexer, 1, 1);
}
| '*'
{
parser->result = msgset_select (select_deleted, lexer, 0, total);
}
| '-'
{
parser->result = msgset_select (select_deleted, lexer, 1, 1);
}
| '+'
{
parser->result = msgset_select (select_deleted, lexer, 0, 1);
}
;
msgset : msgexpr
| msgset ',' msgexpr
{
$$ = msgset_append ($1, $3);
}
| msgset msgexpr
{
$$ = msgset_append ($1, $2);
}
;
msgexpr : msgspec
{
$$ = $1;
if (check_set (&$$, parser->flags))
YYABORT;
}
| '{' msgset '}'
{
$$ = $2;
}
| '!' msgexpr
{
$$ = msgset_negate ($2);
}
;
msgspec : msg
| msg dot rangeset
{
$$ = msgset_concat ($1, $3);
}
| msg obracket rangeset ']'
{
$$ = msgset_concat ($1, $3);
}
| range
;
msg : header REGEXP /* /.../ */
{
struct header_data hd;
hd.header = $1;
hd.expr = $2;
$$ = msgset_select (select_header, &hd, 0, 0);
if (!$$)
{
if ($1)
mu_error (_("No applicable messages from {%s:/%s}"), $1, $2);
else
mu_error (_("No applicable messages from {/%s}"), $2);
YYERROR;
}
}
| BODY
{
$$ = msgset_select (select_body, $1, 0, 0);
if (!$$)
{
mu_error (_("No applicable messages from {:/%s}"), $1);
YYERROR;
}
}
| TYPE /* :n, :d, etc */
{
message_selector_t sel = find_type_selector ($1);
if (!sel)
{
yyerror (parser, lexer, _("unknown message type"));
YYERROR;
}
$$ = msgset_select (sel, NULL, 0, 0);
if (!$$)
{
mu_error (_("No messages satisfy :%c"), $1);
YYERROR;
}
}
| IDENT /* Sender name */
{
$$ = msgset_select (select_sender, (void *)$1, 0, 0);
if (!$$)
{
mu_error (_("No applicable messages from {%s}"), $1);
YYERROR;
}
}
;
header : /* empty */
{
$$ = NULL;
}
| HEADER
{
$$ = $1;
}
;
rangeset : range
| rangeset ',' range
{
$$ = msgset_append ($1, $3);
}
| rangeset range
{
$$ = msgset_append ($1, $2);
}
;
range : number
| NUMBER '-' number
{
if (msgset_length ($3) == 1)
{
if (($$ = msgset_range ($1, msgset_msgno ($3))) == NULL)
yyerror (parser, lexer, _("range error"));
}
else
{
$$ = msgset_range ($1, msgset_msgno ($3) - 1);
if (!$$)
{
yyerror (parser, lexer, _("range error"));
YYERROR;
}
msgset_append ($$, $3);
}
}
| NUMBER '-' '*'
{
if (($$ = msgset_range ($1, total)) == NULL)
yyerror (parser, lexer, _("range error"));
}
;
number : partno
| partno dot rangeset
{
$$ = msgset_concat ($1, $3);
}
| partno obracket rangeset ']'
{
$$ = msgset_concat ($1, $3);
}
;
partno : NUMBER
{
$$ = msgset_make_1 ($1);
}
| '(' rangeset ')'
{
$$ = $2;
}
;
dot : '.'
{
if (!(parser->flags & MSG_ALLOWPART))
{
yyerror (parser, lexer, _("message parts not allowed"));
YYERROR;
}
}
obracket : '['
{
if (!(parser->flags & MSG_ALLOWPART))
{
yyerror (parser, lexer, _("message parts not allowed"));
YYERROR;
}
}
%%
int
yyerror (struct msgset_parser *parser, struct msgset_lexer *lexer,
const char *s)
{
mu_stream_printf (mu_strerr, "%s: ", lexer->xargv[0]);
mu_stream_printf (mu_strerr, "%s", s);
if (!lexer->cur_p)
mu_stream_printf (mu_strerr, _(" near end"));
else if (*lexer->cur_p == 0)
{
int i = (*lexer->cur_p == 0) ? lexer->cur_ind + 1 : lexer->cur_ind;
if (i == lexer->xargc)
mu_stream_printf (mu_strerr, _(" near end"));
else
mu_stream_printf (mu_strerr, _(" near %s"), lexer->xargv[i]);
}
else
mu_stream_printf (mu_strerr, _(" near %s"), lexer->cur_p);
mu_stream_printf (mu_strerr, "\n");
return 0;
}
int
yylex (struct msgset_lexer *lexer)
{
if (lexer->cur_ind == lexer->xargc)
return 0;
if (!lexer->cur_p)
lexer->cur_p = lexer->xargv[lexer->cur_ind];
if (*lexer->cur_p == 0)
{
lexer->cur_ind++;
lexer->cur_p = NULL;
return yylex (lexer);
}
if (mu_isdigit (*lexer->cur_p))
{
yylval.number = strtoul (lexer->cur_p, &lexer->cur_p, 10);
return NUMBER;
}
if (mu_isalpha (*lexer->cur_p))
{
char *p = lexer->cur_p;
while (*lexer->cur_p && *lexer->cur_p != ',' && *lexer->cur_p != ':')
lexer->cur_p++;
mu_opool_append (lexer->tokpool, p, lexer->cur_p - p);
mu_opool_append_char (lexer->tokpool, 0);
yylval.string = mu_opool_finish (lexer->tokpool, NULL);
if (*lexer->cur_p == ':')
{
++lexer->cur_p;
return HEADER;
}
return IDENT;
}
if (*lexer->cur_p == '/')
{
char *p = ++lexer->cur_p;
while (*lexer->cur_p && *lexer->cur_p != '/')
lexer->cur_p++;
mu_opool_append (lexer->tokpool, p, lexer->cur_p - p);
mu_opool_append_char (lexer->tokpool, 0);
yylval.string = mu_opool_finish (lexer->tokpool, NULL);
if (*lexer->cur_p)
lexer->cur_p++;
return REGEXP;
}
if (*lexer->cur_p == ':')
{
lexer->cur_p++;
if (*lexer->cur_p == '/')
{
char *p = ++lexer->cur_p;
while (*lexer->cur_p && *lexer->cur_p != '/')
lexer->cur_p++;
mu_opool_append (lexer->tokpool, p, lexer->cur_p - p);
mu_opool_append_char (lexer->tokpool, 0);
yylval.string = mu_opool_finish (lexer->tokpool, NULL);
if (*lexer->cur_p)
lexer->cur_p++;
return BODY;
}
if (*lexer->cur_p == 0)
return 0;
yylval.type = *lexer->cur_p++;
return TYPE;
}
return *lexer->cur_p++;
}
int
msgset_parse (const int argc, char **argv, int flags, msgset_t **mset)
{
int rc;
struct msgset_parser parser;
struct msgset_lexer lexer;
lexer.xargc = argc;
lexer.xargv = argv;
lexer.cur_ind = 1;
lexer.cur_p = NULL;
mu_opool_create (&lexer.tokpool, MU_OPOOL_ENOMEMABRT);
parser.flags = flags;
mu_mailbox_messages_count (mbox, &parser.message_count);
parser.result = NULL;
rc = yyparse (&parser, &lexer);
if (rc == 0)
{
if (!parser.result)
{
util_noapp ();
rc = 1;
}
else
{
if (parser.result->crd[1] > parser.message_count)
{
util_error_range (parser.result->crd[1]);
rc = 1;
}
else
{
*mset = parser.result;
parser.result = NULL;
}
}
}
msgset_parser_free (&parser);
msgset_lexer_free (&lexer);
return rc;
}
void
msgset_free (msgset_t *msg_set)
{
msgset_t *next;
while (msg_set)
{
next = msg_set->next;
free (msg_set->crd);
free (msg_set);
msg_set = next;
}
}
size_t
msgset_count (msgset_t *set)
{
size_t count = 0;
for (; set; set = set->next)
count++;
return count;
}
/* Create a message set consisting of a single msg_num and no subparts */
msgset_t *
msgset_make_1 (size_t number)
{
msgset_t *mp;
if (number == 0)
return NULL;
mp = mu_alloc (sizeof (*mp));
mp->next = NULL;
if (mu_coord_alloc (&mp->crd, 1))
mu_alloc_die ();
mp->crd[1] = number;
return mp;
}
msgset_t *
msgset_dup (const msgset_t *set)
{
msgset_t *mp;
mp = mu_alloc (sizeof (*mp));
mp->next = NULL;
if (mu_coord_dup (set->crd, &mp->crd))
mu_alloc_die ();
return mp;
}
/* Append message set TWO to the end of message set ONE. Take care to
eliminate duplicates. Preserve the ordering of both lists. Return
the resulting set.
The function is destructive: the set TWO is attached to ONE and
eventually modified to avoid duplicates.
*/
msgset_t *
msgset_append (msgset_t *one, msgset_t *two)
{
msgset_t *last;
if (!one)
return two;
for (last = one; last->next; last = last->next)
{
msgset_remove (&two, msgset_msgno (last));
}
last->next = two;
return one;
}
int
msgset_member (msgset_t *set, size_t n)
{
for (; set; set = set->next)
if (msgset_msgno (set) == n)
return 1;
return 0;
}
void
msgset_remove (msgset_t **pset, size_t n)
{
msgset_t *cur = *pset, **pnext = pset;
while (1)
{
if (cur == NULL)
return;
if (msgset_msgno (cur) == n)
break;
pnext = &cur->next;
cur = cur->next;
}
*pnext = cur->next;
cur->next = NULL;
msgset_free (cur);
}
msgset_t *
msgset_negate (msgset_t *set)
{
size_t i;
msgset_t *first = NULL, *last = NULL;
for (i = 1; i <= total; i++)
{
if (!msgset_member (set, i))
{
msgset_t *mp = msgset_make_1 (i);
if (!first)
first = mp;
else
last->next = mp;
last = mp;
}
}
return first;
}
msgset_t *
msgset_range (int low, int high)
{
int i;
msgset_t *mp, *first = NULL, *last = NULL;
if (low == high)
return msgset_make_1 (low);
if (low >= high)
return NULL;
for (i = 0; low <= high; i++, low++)
{
mp = msgset_make_1 (low);
if (!first)
first = mp;
else
last->next = mp;
last = mp;
}
return first;
}
msgset_t *
msgset_expand (msgset_t *set, msgset_t *expand_by)
{
msgset_t *i, *j;
msgset_t *first = NULL, *last = NULL, *mp;
for (i = set; i; i = i->next)
for (j = expand_by; j; j = j->next)
{
mp = mu_alloc (sizeof *mp);
mp->next = NULL;
if (mu_coord_alloc (&mp->crd,
mu_coord_length (i->crd) + mu_coord_length (j->crd)))
mu_alloc_die ();
memcpy (&mp->crd[1], &i->crd[1],
mu_coord_length (i->crd) * sizeof i->crd[0]);
memcpy (&mp->crd[1] + mu_coord_length (i->crd), &j->crd[1],
mu_coord_length (j->crd) * sizeof j->crd[0]);
if (!first)
first = mp;
else
last->next = mp;
last = mp;
}
return first;
}
msgset_t *
msgset_select (message_selector_t sel, void *closure, int rev,
unsigned int max_matches)
{
size_t i, match_count = 0;
msgset_t *first = NULL, *last = NULL, *mp;
mu_message_t msg = NULL;
if (max_matches == 0)
max_matches = total;
if (rev)
{
for (i = total; i > 0; i--)
{
mu_mailbox_get_message (mbox, i, &msg);
if ((*sel) (msg, closure))
{
mp = msgset_make_1 (i);
if (!first)
first = mp;
else
last->next = mp;
last = mp;
if (++match_count == max_matches)
break;
}
}
}
else
{
for (i = 1; i <= total; i++)
{
mu_mailbox_get_message (mbox, i, &msg);
if ((*sel) (msg, closure))
{
mp = msgset_make_1 (i);
if (!first)
first = mp;
else
last->next = mp;
last = mp;
if (++match_count == max_matches)
break;
}
}
}
return first;
}
int
select_header (mu_message_t msg, void *closure)
{
struct header_data *hd = (struct header_data *)closure;
mu_header_t hdr;
char *contents;
const char *header = hd->header ? hd->header : MU_HEADER_SUBJECT;
mu_message_get_header (msg, &hdr);
if (mu_header_aget_value (hdr, header, &contents) == 0)
{
if (mailvar_get (NULL, "regex", mailvar_type_boolean, 0) == 0)
{
/* Match string against the extended regular expression(ignoring
case) in pattern, treating errors as no match.
Return 1 for match, 0 for no match.
*/
regex_t re;
int status;
int flags = REG_EXTENDED;
if (mu_islower (header[0]))
flags |= REG_ICASE;
if (regcomp (&re, hd->expr, flags) != 0)
{
free (contents);
return 0;
}
status = regexec (&re, contents, 0, NULL, 0);
free (contents);
regfree (&re);
return status == 0;
}
else
{
int rc;
mu_strupper (contents);
rc = strstr (contents, hd->expr) != NULL;
free (contents);
return rc;
}
}
return 0;
}
int
select_body (mu_message_t msg, void *closure)
{
char *expr = closure;
int noregex = mailvar_get (NULL, "regex", mailvar_type_boolean, 0);
regex_t re;
int status = 0;
mu_body_t body = NULL;
mu_stream_t stream = NULL;
int rc;
if (noregex)
mu_strupper (expr);
else if (regcomp (&re, expr, REG_EXTENDED | REG_ICASE) != 0)
return 0;
mu_message_get_body (msg, &body);
rc = mu_body_get_streamref (body, &stream);
if (rc == 0)
{
char *buffer = NULL;
size_t size = 0;
size_t n = 0;
while (status == 0
&& (rc = mu_stream_getline (stream, &buffer, &size, &n)) == 0
&& n > 0)
{
if (noregex)
{
/* FIXME: charset */
mu_strupper (buffer);
status = strstr (buffer, expr) != NULL;
}
else
status = regexec (&re, buffer, 0, NULL, 0) == 0;
}
mu_stream_destroy (&stream);
free (buffer);
if (rc)
mu_diag_funcall (MU_DIAG_ERROR, "mu_stream_getline", NULL, rc);
}
else
mu_diag_funcall (MU_DIAG_ERROR, "mu_body_get_streamref", NULL, rc);
if (!noregex)
regfree (&re);
return status;
}
int
select_sender (mu_message_t msg, void *closure)
{
char *needle = (char*) closure;
char *sender = sender_string (msg);
int status = strcmp (sender, needle) == 0;
free (sender);
return status;
}
static int
select_type_d (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_deleted (attr);
return 0;
}
static int
select_type_n (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_recent (attr);
return 0;
}
static int
select_type_o (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_seen (attr);
return 0;
}
static int
select_type_r (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_read (attr);
return 0;
}
static int
select_type_s (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_SAVED);
return 0;
}
static int
select_type_t (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_TAGGED);
return 0;
}
static int
select_type_T (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return !mu_attribute_is_userflag (attr, MAIL_ATTRIBUTE_TAGGED);
return 0;
}
static int
select_type_u (mu_message_t msg, void *unused MU_ARG_UNUSED)
{
mu_attribute_t attr;
if (mu_message_get_attribute (msg, &attr) == 0)
return !mu_attribute_is_read (attr);
return 0;
}
struct type_selector
{
int letter;
message_selector_t func;
};
static struct type_selector type_selector[] = {
{ 'd', select_type_d },
{ 'n', select_type_n },
{ 'o', select_type_o },
{ 'r', select_type_r },
{ 's', select_type_s },
{ 't', select_type_t },
{ 'T', select_type_T },
{ 'u', select_type_u },
{ '/', NULL }, /* A pseudo-entry needed for msgtype_generator only */
{ 0 }
};
static message_selector_t
find_type_selector (int type)
{
struct type_selector *p;
for (p = type_selector; p->func; p++)
{
if (p->letter == type)
return p->func;
}
return NULL;
}
#ifdef WITH_READLINE
char *
msgtype_generator (const char *text, int state)
{
/* Allowed message types, plus '/'. The latter can folow a colon,
meaning body lookup */
static int i;
char c;
if (!state)
{
i = 0;
}
while ((c = type_selector[i].letter))
{
i++;
if (!text[1] || text[1] == c)
{
char *s = mu_alloc (3);
s[0] = ':';
s[1] = c;
s[2] = 0;
return s;
}
}
return NULL;
}
#endif
int
select_deleted (mu_message_t msg, void *closure)
{
mu_attribute_t attr= NULL;
int rc;
struct msgset_lexer *lexer = closure;
mu_message_get_attribute (msg, &attr);
rc = mu_attribute_is_deleted (attr);
return strcmp (lexer->xargv[0], "undelete") == 0 ? rc : !rc;
}
int
check_set (msgset_t **pset, int flags)
{
int rc = 0;
if (!*pset)
{
util_noapp ();
return 1;
}
if (msgset_count (*pset) == 1)
flags ^= MSG_SILENT;
if (flags & MSG_NODELETED)
{
msgset_t *p = *pset, *prev = NULL;
msgset_t *delset = NULL;
while (p)
{
msgset_t *next = p->next;
if (util_isdeleted (msgset_msgno (p)))
{
if ((flags & MSG_SILENT) && (prev || next))
{
/* Mark subset as deleted */
p->next = delset;
delset = p;
/* Remove it from the set */
if (prev)
prev->next = next;
else
*pset = next;
}
else
{
mu_error (_("%lu: Inappropriate message (has been deleted)"),
(unsigned long) msgset_msgno (p));
/* Delete entire set */
delset = *pset;
*pset = NULL;
rc = 1;
break;
}
}
else
prev = p;
p = next;
}
if (delset)
msgset_free (delset);
if (!*pset)
rc = 1;
}
return rc;
}
#if 0
void
msgset_print (msgset_t *mset)
{
int i;
mu_printf ("(");
mu_printf ("%d .", mset->msg_part[0]);
for (i = 1; i < mset->npart; i++)
{
mu_printf (" %d", mset->msg_part[i]);
}
mu_printf (")\n");
}
int
main(int argc, char **argv)
{
msgset_t *mset = NULL;
int rc = msgset_parse (argc, argv, &mset);
for (; mset; mset = mset->next)
msgset_print (mset);
return 0;
}
#endif