From f5e7b39153b414d2f9c2f9e369f41f895d253f40 Mon Sep 17 00:00:00 2001 From: David Champion Date: Tue, 4 Oct 2005 06:05:39 +0000 Subject: [PATCH] Attachment counting for index display (patch-1.5.11.dgc.attach.6). Modifications: attach_recurse and attach_ignore_fundamental stripped, some debugging code removed, some bones thrown to check_sec.sh. --- Muttrc.head.in | 54 +++++++++++ doc/manual.xml.head | 109 +++++++++++++++++++++ doc/muttrc.man.head | 3 +- globals.h | 4 + hdrline.c | 23 +++++ init.c | 231 ++++++++++++++++++++++++++++++++++++++++++++ init.h | 8 ++ mime.h | 3 +- mutt.h | 25 ++++- parse.c | 177 ++++++++++++++++++++++++++++++--- pattern.c | 17 ++++ protos.h | 1 + recvattach.c | 17 ++++ 13 files changed, 658 insertions(+), 14 deletions(-) diff --git a/Muttrc.head.in b/Muttrc.head.in index 44ec4d2c..93730726 100644 --- a/Muttrc.head.in +++ b/Muttrc.head.in @@ -29,6 +29,60 @@ bind browser y exit # # set use_8bitmime +## +## *** DEFAULT SETTINGS FOR THE ATTACHMENTS PATCH *** +## + +## +## Please see the manual (section "attachments") for detailed +## documentation of the "attachments" command. +## +## Removing a pattern from a list removes that pattern literally. It +## does not remove any type matching the pattern. +## +## attachments +A */.* +## attachments +A image/jpeg +## unattachments +A */.* +## +## This leaves "attached" image/jpeg files on the allowed attachments +## list. It does not remove all items, as you might expect, because the +## second */.* is not a matching expression at this time. +## +## Remember: "unattachments" only undoes what "attachments" has done! +## It does not trigger any matching on actual messages. + +## Qualify any MIME part with an "attachment" disposition, EXCEPT for +## text/x-vcard and application/pgp parts. (PGP parts are already known +## to mutt, and can be searched for with ~g, ~G, and ~k.) +## +## I've added x-pkcs7 to this, since it functions (for S/MIME) +## analogously to PGP signature attachments. S/MIME isn't supported +## in a stock mutt build, but we can still treat it specially here. +## +attachments +A */.* +attachments -A text/x-vcard application/pgp.* +attachments -A application/x-pkcs7-.* + +## Discount all MIME parts with an "inline" disposition, unless they're +## text/plain. (Why inline a text/plain part unless it's external to the +## message flow?) +## +attachments +I text/plain + +## These two lines make Mutt qualify MIME containers. (So, for example, +## a message/rfc822 forward will count as an attachment.) The first +## line is unnecessary if you already have "attach-allow */.*", of +## course. These are off by default! The MIME elements contained +## within a message/* or multipart/* are still examined, even if the +## containers themseves don't qualify. +## +#attachments +A message/.* multipart/.* +#attachments +I message/.* multipart/.* + +## You probably don't really care to know about deleted attachments. +attachments -A message/external-body +attachments -I message/external-body + ## ## More settings ## diff --git a/doc/manual.xml.head b/doc/manual.xml.head index 10155002..8bf0a101 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -3307,6 +3307,7 @@ messages: ~v message is part of a collapsed thread. ~V cryptographically verified messages ~x EXPR messages which contain EXPR in the `References' field +~X [MIN]-[MAX] messages with MIN to MAX attachments *) ~y EXPR messages which contain EXPR in the `X-Label' field ~z [MIN]-[MAX] messages with a size in the range MIN to MAX *) ~= duplicated messages (see $duplicate_threads) @@ -5146,6 +5147,114 @@ To remove a MIME type from the alternative_order list, + +Attachment Searching and Counting + + +If you ever lose track of attachments in your mailboxes, Mutt's +attachment-counting and -searching support might be for you. You can +make your message index display the number of qualifying attachments in +each message, or search for messages by attachment count. You also can +configure what kinds of attachments qualify for this feature with the +attachments and unattachments commands. + + + +The syntax is: + + +attachments {+|-}disposition mime-type +unattachments {+|-}disposition mime-type + + + +Disposition is the attachment's Content-disposition type -- either +"inline" or "attachment". You can abbreviate this to I or A. + + + +Disposition is prefixed by either a + symbolor a - symbol. If it's +a +, you're saying that you want to allow this disposition and MIME +type to qualify. If it's a -, you're saying that this disposition +and MIME type is an exception to previous + rules. There are examples +below of how this is useful. + + + +Mime-type is, unsurprisingly, the MIME type of the attachment you want +to affect. A MIME type is always of the format "major/minor", where +"major" describes the broad category of document you're looking at, and +"minor" describes the specific type within that category. The major +part of mim-type must be literal text (or the special token "*"), but +the minor part may be a regular expression. (Therefore, "*/.*" matches +any MIME type.) + + + +The MIME types you give to the attachments directive are a kind of +pattern. When you use the attachments directive, the patterns you +specify are added to a list. When you use unattachments, the pattern +is removed from the list. The patterns are not expanded and matched +to specific MIME types at this time -- they're just text in a list. +They're only matched when actually evaluating a message. + + + +Some examples might help to illustrate. The examples that are not +commented out define the default configuration of the lists. + + + +## Removing a pattern from a list removes that pattern literally. It +## does not remove any type matching the pattern. +## +## attachments +A */.* +## attachments +A image/jpeg +## unattachments +A */.* +## +## This leaves "attached" image/jpeg files on the allowed attachments +## list. It does not remove all items, as you might expect, because the +## second */.* is not a matching expression at this time. +## +## Remember: "unattachments" only undoes what "attachments" has done! +## It does not trigger any matching on actual messages. + + +## Qualify any MIME part with an "attachment" disposition, EXCEPT for +## text/x-vcard and application/pgp parts. (PGP parts are already known +## to mutt, and can be searched for with ~g, ~G, and ~k.) +## +## I've added x-pkcs7 to this, since it functions (for S/MIME) +## analogously to PGP signature attachments. S/MIME isn't supported +## in a stock mutt build, but we can still treat it specially here. +## +attachments +A */.* +attachments -A text/x-vcard application/pgp.* +attachments -A application/x-pkcs7-.* + +## Discount all MIME parts with an "inline" disposition, unless they're +## text/plain. (Why inline a text/plain part unless it's external to the +## message flow?) +## +attachments +I text/plain + +## These two lines make Mutt qualify MIME containers. (So, for example, +## a message/rfc822 forward will count as an attachment.) The first +## line is unnecessary if you already have "attach-allow */.*", of +## course. These are off by default! The MIME elements contained +## within a message/* or multipart/* are still examined, even if the +## containers themseves don't qualify. +## +#attachments +A message/.* multipart/.* +#attachments +I message/.* multipart/.* + +## You probably don't really care to know about deleted attachments. +attachments -A message/external-body +attachments -I message/external-body + + + + MIME Lookup diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head index dd7ad05e..5a1a5149 100644 --- a/doc/muttrc.man.head +++ b/doc/muttrc.man.head @@ -437,6 +437,7 @@ l l. ~v message is part of a collapsed thread. ~V cryptographically verified messages ~x \fIEXPR\fP messages which contain \fIEXPR\fP in the \(lqReferences\(rq field +~X \fIMIN\fP-\fIMAX\fP messages with MIN - MAX attachments ~y \fIEXPR\fP messages which contain \fIEXPR\fP in the \(lqX-Label\(rq field ~z \fIMIN\fP-\fIMAX\fP messages with a size in the range \fIMIN\fP to \fIMAX\fP ~= duplicated messages (see $duplicate_threads) @@ -445,7 +446,7 @@ l l. .PP In the above, \fIEXPR\fP is a regular expression. .PP -With the \fB~m\fP, \fB~n\fP, and \fB~z\fP operators, you can also +With the \fB~m\fP, \fB~n\fP, \fB~X\fP, and \fB~z\fP operators, you can also specify ranges in the forms \fB<\fP\fIMAX\fP, \fB>\fP\fIMIN\fP, \fIMIN\fP\fB-\fP, and \fB-\fP\fIMAX\fP. .SS Matching dates diff --git a/globals.h b/globals.h index 196594d3..ae3f8227 100644 --- a/globals.h +++ b/globals.h @@ -140,6 +140,10 @@ WHERE char *LastFolder; WHERE LIST *AutoViewList INITVAL(0); WHERE LIST *AlternativeOrderList INITVAL(0); +WHERE LIST *AttachAllow INITVAL(0); +WHERE LIST *AttachExclude INITVAL(0); +WHERE LIST *InlineAllow INITVAL(0); +WHERE LIST *InlineExclude INITVAL(0); WHERE LIST *HeaderOrderList INITVAL(0); WHERE LIST *Ignore INITVAL(0); WHERE LIST *MimeLookupList INITVAL(0); diff --git a/hdrline.c b/hdrline.c index a938c81d..3a40747a 100644 --- a/hdrline.c +++ b/hdrline.c @@ -218,6 +218,7 @@ int mutt_user_is_recipient (HEADER *h) * %T = $to_chars * %u = user (login) name of author * %v = first name of author, unless from self + * %X = number of MIME attachments * %y = `x-label:' field (if present) * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label) * %Z = status flags */ @@ -654,6 +655,28 @@ hdr_format_str (char *dest, mutt_format_s (dest, destlen, prefix, buf2); break; + case 'X': + { + int count, flags; + + if (hdr->content->parts) + count = mutt_count_body_parts(hdr, flags); + else + { + mutt_parse_mime_message(ctx, hdr); + count = mutt_count_body_parts(hdr, flags); + mutt_free_body(&hdr->content->parts); + } + + /* The recursion allows messages without depth to return 0. */ + if (optional) + optional = count != 0; + + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, count); + } + break; + case 'y': if (optional) optional = hdr->env->x_label ? 1 : 0; diff --git a/init.c b/init.c index b064ec25..858a6afd 100644 --- a/init.c +++ b/init.c @@ -793,6 +793,237 @@ static int parse_lists (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) return 0; } +/* always wise to do what someone else did before */ +static void _attachments_clean (void) +{ + int i; + if (Context && Context->msgcount) + { + for (i = 0; i < Context->msgcount; i++) + Context->hdrs[i]->attach_valid = 0; + } +} + +static int parse_attach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err) +{ + ATTACH_MATCH *a; + LIST *listp, *lastp; + char *p; + char *tmpminor; + int len; + + /* Find the last item in the list that data points to. */ + lastp = NULL; + dprint(5, (debugfile, "parse_attach_list: ldata = %08x, *ldata = %08x\n", + (unsigned int)ldata, (unsigned int)*ldata)); + for (listp = *ldata; listp; listp = listp->next) + { + a = (ATTACH_MATCH *)listp->data; + dprint(5, (debugfile, "parse_attach_list: skipping %s/%s\n", + a->major, a->minor)); + lastp = listp; + } + + do + { + mutt_extract_token (buf, s, 0); + + if (!buf->data || *buf->data == '\0') + continue; + + a = safe_malloc(sizeof(ATTACH_MATCH)); + + /* some cheap hacks that I expect to remove */ + if (!mutt_strcasecmp(buf->data, "any")) + a->major = safe_strdup("*/.*"); + else if (!mutt_strcasecmp(buf->data, "none")) + a->major = safe_strdup("cheap_hack/this_should_never_match"); + else + a->major = safe_strdup(buf->data); + + if ((p = strchr(a->major, '/'))) + { + *p = '\0'; + ++p; + a->minor = p; + } + else + { + a->minor = "unknown"; + } + + len = strlen(a->minor); + tmpminor = safe_malloc(len+3); + strcpy(&tmpminor[1], a->minor); /* __STRCPY_CHECKED__ */ + tmpminor[0] = '^'; + tmpminor[len+1] = '$'; + tmpminor[len+2] = '\0'; + + a->major_int = mutt_check_mime_type(a->major); + regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED); + + safe_free(&tmpminor); + + dprint(5, (debugfile, "parse_attach_list: added %s/%s [%d]\n", + a->major, a->minor, a->major_int)); + + listp = safe_malloc(sizeof(LIST)); + listp->data = (char *)a; + listp->next = NULL; + if (lastp) + { + lastp->next = listp; + } + else + { + *ldata = listp; + } + lastp = listp; + } + while (MoreArgs (s)); + + _attachments_clean(); + return 0; +} + +static int parse_unattach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err) +{ + ATTACH_MATCH *a; + LIST *lp, *lastp; + char *tmp; + int major; + char *minor; + + do + { + mutt_extract_token (buf, s, 0); + + if (!mutt_strcasecmp(buf->data, "any")) + tmp = safe_strdup("*/.*"); + else if (!mutt_strcasecmp(buf->data, "none")) + tmp = safe_strdup("cheap_hack/this_should_never_match"); + else + tmp = safe_strdup(buf->data); + + if ((minor = strchr(tmp, '/'))) + { + *minor = '\0'; + ++minor; + } + else + { + minor = "unknown"; + } + major = mutt_check_mime_type(tmp); + + lastp = NULL; + for(lp = *ldata; lp; lp = lastp->next) + { + a = (ATTACH_MATCH *)lp->data; + dprint(5, (debugfile, "parse_unattach_list: check %s/%s [%d] : %s/%s [%d]\n", + a->major, a->minor, a->major_int, tmp, minor, major)); + if (a->major_int == major && !mutt_strcasecmp(minor, a->minor)) + { + dprint(5, (debugfile, "parse_unattach_list: removed %s/%s [%d]\n", + a->major, a->minor, a->major_int)); + regfree(&a->minor_rx); + FREE(&a->major); + if (lastp) + { + lastp->next = lp->next; + } + lastp = lp; + FREE (&lp->data); /* same as a */ + FREE (&lp); + } + + lastp = lp; + lp = lp->next; + } + + remove_from_list (ldata, buf->data); + } + while (MoreArgs (s)); + + _attachments_clean(); + return 0; +} + + +static int parse_attachments (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + char op, *p; + LIST **listp; + + mutt_extract_token(buf, s, 0); + if (!buf->data || *buf->data == '\0') { + strfcpy(err->data, _("attachments: no disposition"), err->dsize); + return -1; + } + + p = buf->data; + op = *p++; + if (op != '+' && op != '-') { + op = '+'; + p--; + } + if (!mutt_strncasecmp(p, "attachment", strlen(p))) { + if (op == '+') + listp = &AttachAllow; + else + listp = &AttachExclude; + } + else if (!mutt_strncasecmp(p, "inline", strlen(p))) { + if (op == '+') + listp = &InlineAllow; + else + listp = &InlineExclude; + } + else { + strfcpy(err->data, _("attachments: invalid disposition"), err->dsize); + return -1; + } + + return parse_attach_list(buf, s, listp, err); +} + +static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) +{ + char op, *p; + LIST **listp; + + mutt_extract_token(buf, s, 0); + if (!buf->data || *buf->data == '\0') { + strfcpy(err->data, _("unattachments: no disposition"), err->dsize); + return -1; + } + + p = buf->data; + op = *p++; + if (op != '+' && op != '-') { + op = '+'; + p--; + } + if (mutt_strncasecmp(p, "attachment", strlen(p))) { + if (op == '+') + listp = &AttachAllow; + else + listp = &AttachExclude; + } + else if (mutt_strncasecmp(p, "inline", strlen(p))) { + if (op == '+') + listp = &InlineAllow; + else + listp = &InlineExclude; + } + else { + strfcpy(err->data, _("unattachments: invalid disposition"), err->dsize); + return -1; + } + + return parse_unattach_list(buf, s, listp, err); +} + static int parse_unlists (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err) { do diff --git a/init.h b/init.h index 6b05b7ce..af82e8e7 100644 --- a/init.h +++ b/init.h @@ -220,10 +220,12 @@ struct option_t MuttVars[] = { ** .dt %m .dd major MIME type ** .dt %M .dd MIME subtype ** .dt %n .dd attachment number + ** .dt %Q .dd "Q", if MIME part qualifies for attachment counting ** .dt %s .dd size ** .dt %t .dd tagged flag ** .dt %T .dd graphic tree characters ** .dt %u .dd unlink (=to delete) flag + ** .dt %X .dd number of qualifying MIME parts in this part and its children ** .dt %>X .dd right justify the rest of the string and pad with character "X" ** .dt %|X .dd pad to the end of the line with character "X" ** .de @@ -990,6 +992,7 @@ struct option_t MuttVars[] = { ** .dt %T .dd the appropriate character from the $$to_chars string ** .dt %u .dd user (login) name of the author ** .dt %v .dd first name of the author, or the recipient if the message is from you + ** .dt %X .dd number of attachments ** .dt %y .dd `x-label:' field, if present ** .dt %Y .dd `x-label' field, if present, and (1) not at part of a thread tree, ** (2) at the top of a thread, or (3) `x-label' is different from @@ -2982,6 +2985,9 @@ static int parse_my_hdr (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unmy_hdr (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_subscribe (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unsubscribe (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_attachments (BUFFER *, BUFFER *, unsigned long, BUFFER *); +static int parse_unattachments (BUFFER *, BUFFER *, unsigned long, BUFFER *); + static int parse_alternates (BUFFER *, BUFFER *, unsigned long, BUFFER *); static int parse_unalternates (BUFFER *, BUFFER *, unsigned long, BUFFER *); @@ -3001,6 +3007,8 @@ struct command_t Commands[] = { { "account-hook", mutt_parse_hook, M_ACCOUNTHOOK }, #endif { "alias", parse_alias, 0 }, + { "attachments", parse_attachments, 0 }, + { "unattachments",parse_unattachments,0 }, { "auto_view", parse_list, UL &AutoViewList }, { "alternative_order", parse_list, UL &AlternativeOrderList}, { "bind", mutt_parse_bind, 0 }, diff --git a/mime.h b/mime.h index 4c72c2d0..a374f25a 100644 --- a/mime.h +++ b/mime.h @@ -27,7 +27,8 @@ enum TYPEMODEL, TYPEMULTIPART, TYPETEXT, - TYPEVIDEO + TYPEVIDEO, + TYPEANY }; /* Content-Transfer-Encoding */ diff --git a/mutt.h b/mutt.h index 164e5edf..7b2d3e3e 100644 --- a/mutt.h +++ b/mutt.h @@ -238,6 +238,7 @@ enum M_CRYPT_ENCRYPT, M_PGP_KEY, M_XLABEL, + M_MIMEATTACH, /* Options for Mailcap lookup */ M_EDIT, @@ -646,7 +647,9 @@ typedef struct body struct header *hdr; /* header information for message/rfc822 */ struct attachptr *aptr; /* Menu information, used in recvattach.c */ - + + signed short attach_count; + time_t stamp; /* time stamp of last * encoding update. */ @@ -684,6 +687,7 @@ typedef struct body unsigned int badsig : 1; /* bad cryptographic signature (needed to check encrypted s/mime-signatures) */ unsigned int collapsed : 1; /* used by recvattach */ + unsigned int attach_qualifies : 1; } BODY; @@ -722,6 +726,9 @@ typedef struct header unsigned int searched : 1; unsigned int matched : 1; + /* tells whether the attachment count is valid */ + unsigned int attach_valid : 1; + /* the following are used to support collapsing threads */ unsigned int collapsed : 1; /* is this message part of a collapsed thread? */ unsigned int limited : 1; /* is this message in a limited view? */ @@ -746,6 +753,9 @@ typedef struct header char *tree; /* character string to print thread tree */ struct thread *thread; + /* Number of qualifying attachments in message, if attach_valid */ + short attach_total; + #ifdef MIXMASTER LIST *chain; #endif @@ -883,6 +893,19 @@ void state_attach_puts (const char *, STATE *); void state_prefix_putc (char, STATE *); int state_printf(STATE *, const char *, ...); +/* for attachment counter */ +typedef struct +{ + char *major; + int major_int; + char *minor; + regex_t minor_rx; +} ATTACH_MATCH; + +/* Flags for mutt_count_body_parts() */ +#define M_PARTS_TOPLEVEL (1<<0) /* is the top-level part */ +#define M_PARTS_RECOUNT (1<<1) /* force recount */ + #include "ascii.h" #include "protos.h" #include "lib.h" diff --git a/parse.c b/parse.c index 3d15af3e..697662a1 100644 --- a/parse.c +++ b/parse.c @@ -300,6 +300,10 @@ int mutt_check_mime_type (const char *s) return TYPEVIDEO; else if (ascii_strcasecmp ("model", s) == 0) return TYPEMODEL; + else if (ascii_strcasecmp ("*", s) == 0) + return TYPEANY; + else if (ascii_strcasecmp (".*", s) == 0) + return TYPEANY; else return TYPEOTHER; } @@ -924,22 +928,28 @@ static char *extract_message_id (const char *s) void mutt_parse_mime_message (CONTEXT *ctx, HEADER *cur) { MESSAGE *msg; + int flags = 0; - if (cur->content->type != TYPEMESSAGE && cur->content->type != TYPEMULTIPART) - return; /* nothing to do */ + do { + if (cur->content->type != TYPEMESSAGE && + cur->content->type != TYPEMULTIPART) + break; /* nothing to do */ - if (cur->content->parts) - return; /* The message was parsed earlier. */ + if (cur->content->parts) + break; /* The message was parsed earlier. */ - if ((msg = mx_open_message (ctx, cur->msgno))) - { - mutt_parse_part (msg->fp, cur->content); + if ((msg = mx_open_message (ctx, cur->msgno))) + { + mutt_parse_part (msg->fp, cur->content); - if (WithCrypto) - cur->security = crypt_query (cur->content); + if (WithCrypto) + cur->security = crypt_query (cur->content); - mx_close_message (&msg); - } + mx_close_message (&msg); + } + } while (0); + + mutt_count_body_parts(cur, flags|M_PARTS_RECOUNT); } int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short user_hdrs, short weed, @@ -1457,3 +1467,148 @@ ADDRESS *mutt_parse_adrlist (ADDRESS *p, const char *s) return p; } + +/* Compares mime types to the ok and except lists */ +int count_body_parts_check(LIST **checklist, BODY *b, int dflt) +{ + LIST *type; + ATTACH_MATCH *a; + + /* If list is null, use default behavior. */ + if (! *checklist) + { + /*return dflt;*/ + return 0; + } + + for (type = *checklist; type; type = type->next) + { + a = (ATTACH_MATCH *)type->data; + dprint(5, (debugfile, "cbpc: %s %d/%s ?? %s/%s [%d]... ", + dflt ? "[OK] " : "[EXCL] ", + b->type, b->subtype, a->major, a->minor, a->major_int)); + if ((a->major_int == TYPEANY || a->major_int == b->type) && + !regexec(&a->minor_rx, b->subtype, 0, NULL, 0)) + { + dprint(5, (debugfile, "yes\n")); + return 1; + } + else + { + dprint(5, (debugfile, "no\n")); + } + } + + return 0; +} + +#define AT_COUNT(why) { shallcount = 1; } +#define AT_NOCOUNT(why) { shallcount = 0; } + +int count_body_parts (BODY *body, int flags) +{ + int count = 0; + int shallcount, shallrecurse; + BODY *bp; + + if (body == NULL) + return 0; + + for (bp = body; bp != NULL; bp = bp->next) + { + /* Initial disposition is to count and not to recurse this part. */ + AT_COUNT("default"); + shallrecurse = 0; + + dprint(5, (debugfile, "bp: desc=\"%s\"; fn=\"%s\", type=\"%d/%s\"\n", + bp->description ? bp->description : ("none"), + bp->filename ? bp->filename : + bp->d_filename ? bp->d_filename : "(none)", + bp->type, bp->subtype ? bp->subtype : "*")); + + if (bp->type == TYPEMESSAGE) + { + shallrecurse = 1; + + /* If it's an external body pointer, don't recurse it. */ + if (!ascii_strcasecmp (bp->subtype, "external-body")) + shallrecurse = 0; + + /* Don't count containers if they're top-level. */ + if (flags & M_PARTS_TOPLEVEL) + AT_NOCOUNT("top-level message/*"); + } + else if (bp->type == TYPEMULTIPART) + { + /* Always recurse multiparts, except multipart/alternative. */ + shallrecurse = 1; + if (!mutt_strcasecmp(bp->subtype, "alternative")) + shallrecurse = 0; + + /* Don't count containers if they're top-level. */ + if (flags & M_PARTS_TOPLEVEL) + AT_NOCOUNT("top-level multipart"); + } + + if (bp->disposition == DISPINLINE && + bp->type != TYPEMULTIPART && bp->type != TYPEMESSAGE && bp == body) + AT_NOCOUNT("ignore fundamental inlines"); + + /* If this body isn't scheduled for enumeration already, don't bother + * profiling it further. + */ + if (shallcount) + { + /* Turn off shallcount if message type is not in ok list, + * or if it is in except list. Check is done separately for + * inlines vs. attachments. + */ + + if (bp->disposition == DISPATTACH) + { + if (!count_body_parts_check(&AttachAllow, bp, 1)) + AT_NOCOUNT("attach not allowed"); + if (count_body_parts_check(&AttachExclude, bp, 0)) + AT_NOCOUNT("attach excluded"); + } + else + { + if (!count_body_parts_check(&InlineAllow, bp, 1)) + AT_NOCOUNT("inline not allowed"); + if (count_body_parts_check(&InlineExclude, bp, 0)) + AT_NOCOUNT("excluded"); + } + } + + if (shallcount) + count++; + bp->attach_qualifies = shallcount ? 1 : 0; + + dprint(5, (debugfile, "cbp: %08x shallcount = %d\n", (unsigned int)bp, shallcount)); + + if (shallrecurse) + { + dprint(5, (debugfile, "cbp: %08x pre count = %d\n", (unsigned int)bp, count)); + bp->attach_count = count_body_parts(bp->parts, flags & ~M_PARTS_TOPLEVEL); + count += bp->attach_count; + dprint(5, (debugfile, "cbp: %08x post count = %d\n", (unsigned int)bp, count)); + } + } + + dprint(5, (debugfile, "bp: return %d\n", count < 0 ? 0 : count)); + return count < 0 ? 0 : count; +} + +int mutt_count_body_parts (HEADER *hdr, int flags) +{ + if (hdr->attach_valid && !(flags & M_PARTS_RECOUNT)) + return hdr->attach_total; + + if (AttachAllow || AttachExclude || InlineAllow || InlineExclude) + hdr->attach_total = count_body_parts(hdr->content, flags | M_PARTS_TOPLEVEL); + else + hdr->attach_total = 0; + + hdr->attach_valid = 1; + return hdr->attach_total; +} diff --git a/pattern.c b/pattern.c index 09cde447..12d413f0 100644 --- a/pattern.c +++ b/pattern.c @@ -91,6 +91,7 @@ Flags[] = { 'v', M_COLLAPSED, 0, NULL }, { 'V', M_CRYPT_VERIFIED, 0, NULL }, { 'x', M_REFERENCE, 0, eat_regexp }, + { 'X', M_MIMEATTACH, 0, eat_range }, { 'y', M_XLABEL, 0, eat_regexp }, { 'z', M_SIZE, 0, eat_range }, { '=', M_DUPLICATED, 0, NULL }, @@ -1116,6 +1117,22 @@ mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, return (pat->not ^ (h->env->spam && h->env->spam->data && patmatch (pat, h->env->spam->data) == 0)); case M_DUPLICATED: return (pat->not ^ (h->thread && h->thread->duplicate_thread)); + case M_MIMEATTACH: + { + int count; + + if (h->content->parts) + count = mutt_count_body_parts(h, 0); + else + { + mutt_parse_mime_message(ctx, h); + count = mutt_count_body_parts(h, 0); + mutt_free_body(&h->content->parts); + } + + return (pat->not ^ (count >= pat->min && (pat->max == M_MAXRANGE || + count <= pat->max))); + } case M_UNREFERENCED: return (pat->not ^ (h->thread && !h->thread->child)); } diff --git a/protos.h b/protos.h index 327b7e14..f1fda6c2 100644 --- a/protos.h +++ b/protos.h @@ -169,6 +169,7 @@ void mutt_break_thread (HEADER *); void mutt_buffy (char *, size_t); int mutt_buffy_list (void); void mutt_canonical_charset (char *, size_t, const char *); +int mutt_count_body_parts (HEADER *hdr, int flags); void mutt_check_rescore (CONTEXT *); void mutt_clear_error (void); void mutt_create_alias (ENVELOPE *, ADDRESS *); diff --git a/recvattach.c b/recvattach.c index 7442b182..03fc71a0 100644 --- a/recvattach.c +++ b/recvattach.c @@ -297,6 +297,14 @@ const char *mutt_attach_fmt (char *dest, snprintf (dest, destlen, fmt, aptr->num + 1); } break; + case 'Q': + if (optional) + optional = aptr->content->attach_qualifies; + else { + snprintf (fmt, sizeof (fmt), "%%%sc", prefix); + mutt_format_s (dest, destlen, fmt, "Q"); + } + break; case 's': if (flags & M_FORMAT_STAT_FILE) { @@ -334,6 +342,15 @@ const char *mutt_attach_fmt (char *dest, else if (!aptr->content->unlink) optional = 0; break; + case 'X': + if (optional) + optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0; + else + { + snprintf (fmt, sizeof (fmt), "%%%sd", prefix); + snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies); + } + break; default: *dest = 0; } -- 2.40.0