/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 1999-2021 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 yyerror (const char *); int yylex (void); static int msgset_flags = MSG_NODELETED; static size_t message_count; static msgset_t *result; static mu_opool_t tokpool; typedef int (*message_selector_t) (mu_message_t, void *); static message_selector_t find_type_selector (int type); %} %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 */ { result = msgset_make_1 (get_cursor ()); } | '.' { result = msgset_make_1 (get_cursor ()); } | msgset { result = $1; } | '^' { result = msgset_select (select_deleted, NULL, 0, 1); } | '$' { result = msgset_select (select_deleted, NULL, 1, 1); } | '*' { result = msgset_select (select_deleted, NULL, 0, total); } | '-' { result = msgset_select (select_deleted, NULL, 1, 1); } | '+' { result = msgset_select (select_deleted, NULL, 0, 1); } ; msgset : msgexpr | msgset ',' msgexpr { $$ = msgset_append ($1, $3); } | msgset msgexpr { $$ = msgset_append ($1, $2); } ; msgexpr : msgspec { $$ = $1; if (check_set (&$$)) YYABORT; } | '{' msgset '}' { $$ = $2; } | '!' msgexpr { $$ = msgset_negate ($2); } ; msgspec : msg | msg '.' rangeset { $$ = msgset_expand ($1, $3); msgset_free ($1); msgset_free ($3); } | msg '[' rangeset ']' { $$ = msgset_expand ($1, $3); msgset_free ($1); msgset_free ($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 (_("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) { $$ = msgset_range ($1, msgset_msgno ($3)); } else { $$ = msgset_range ($1, msgset_msgno ($3) - 1); if (!$$) YYERROR; msgset_append ($$, $3); } } | NUMBER '-' '*' { $$ = msgset_range ($1, total); } ; number : partno { } | partno '.' rangeset { $$ = msgset_expand ($1, $3); msgset_free ($1); msgset_free ($3); } | partno '[' rangeset ']' { $$ = msgset_expand ($1, $3); msgset_free ($1); msgset_free ($3); } ; partno : NUMBER { $$ = msgset_make_1 ($1); } | '(' rangeset ')' { $$ = $2; } ; %% static int xargc; static char **xargv; static int cur_ind; static char *cur_p; int yyerror (const char *s) { mu_stream_printf (mu_strerr, "%s: ", xargv[0]); mu_stream_printf (mu_strerr, "%s", s); if (!cur_p) mu_stream_printf (mu_strerr, _(" near end")); else if (*cur_p == 0) { int i = (*cur_p == 0) ? cur_ind + 1 : cur_ind; if (i == xargc) mu_stream_printf (mu_strerr, _(" near end")); else mu_stream_printf (mu_strerr, _(" near %s"), xargv[i]); } else mu_stream_printf (mu_strerr, _(" near %s"), cur_p); mu_stream_printf (mu_strerr, "\n"); return 0; } int yylex (void) { if (cur_ind == xargc) return 0; if (!cur_p) cur_p = xargv[cur_ind]; if (*cur_p == 0) { cur_ind++; cur_p = NULL; return yylex (); } if (mu_isdigit (*cur_p)) { yylval.number = strtoul (cur_p, &cur_p, 10); return NUMBER; } if (mu_isalpha (*cur_p)) { char *p = cur_p; while (*cur_p && *cur_p != ',' && *cur_p != ':') cur_p++; mu_opool_append (tokpool, p, cur_p - p); mu_opool_append_char (tokpool, 0); yylval.string = mu_opool_finish (tokpool, NULL); if (*cur_p == ':') { ++cur_p; return HEADER; } return IDENT; } if (*cur_p == '/') { char *p = ++cur_p; while (*cur_p && *cur_p != '/') cur_p++; mu_opool_append (tokpool, p, cur_p - p); mu_opool_append_char (tokpool, 0); yylval.string = mu_opool_finish (tokpool, NULL); if (*cur_p) cur_p++; return REGEXP; } if (*cur_p == ':') { cur_p++; if (*cur_p == '/') { char *p = ++cur_p; while (*cur_p && *cur_p != '/') cur_p++; mu_opool_append (tokpool, p, cur_p - p); mu_opool_append_char (tokpool, 0); yylval.string = mu_opool_finish (tokpool, NULL); if (*cur_p) cur_p++; return BODY; } if (*cur_p == 0) return 0; yylval.type = *cur_p++; return TYPE; } return *cur_p++; } int msgset_parse (const int argc, char **argv, int flags, msgset_t **mset) { int rc; xargc = argc; xargv = argv; msgset_flags = flags; cur_ind = 1; cur_p = NULL; result = NULL; mu_opool_create (&tokpool, MU_OPOOL_ENOMEMABRT); mu_mailbox_messages_count (mbox, &message_count); rc = yyparse (); if (rc == 0) { if (!result) { util_noapp (); rc = 1; } else { if (result->crd[1] > message_count) { util_error_range (result->crd[1]); msgset_free (result); return 1; } *mset = result; } } mu_opool_destroy (&tokpool); 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) { yyerror (_("range error")); 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_ARG_UNUSED) { mu_attribute_t attr= NULL; int rc; mu_message_get_attribute (msg, &attr); rc = mu_attribute_is_deleted (attr); return strcmp (xargv[0], "undelete") == 0 ? rc : !rc; } int check_set (msgset_t **pset) { int flags = msgset_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