]> granicus.if.org Git - mutt/commitdiff
Attachment counting for index display (patch-1.5.11.dgc.attach.6).
authorDavid Champion <dgc@bikeshed.us>
Tue, 4 Oct 2005 06:05:39 +0000 (06:05 +0000)
committerDavid Champion <dgc@bikeshed.us>
Tue, 4 Oct 2005 06:05:39 +0000 (06:05 +0000)
Modifications: attach_recurse and attach_ignore_fundamental stripped,
some debugging code removed, some bones thrown to check_sec.sh.

13 files changed:
Muttrc.head.in
doc/manual.xml.head
doc/muttrc.man.head
globals.h
hdrline.c
init.c
init.h
mime.h
mutt.h
parse.c
pattern.c
protos.h
recvattach.c

index 44ec4d2c165b4bbe3a0d867e8158782f13d2372a..93730726daf029823423d3bc37c8332d6ded5828 100644 (file)
@@ -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
 ##
index 10155002cb68b7aa8ff6257d82b9b128b103ae92..8bf0a101b71758e47da2eb75fb230cded96ace0d 100644 (file)
@@ -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 <literal>alternative&lowbar;order</literal> list,
 
 </sect2>
 
+<sect2 id="attachments">
+<title>Attachment Searching and Counting</title>
+
+<para>
+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.
+</para>
+
+<para>
+The syntax is:
+</para>
+<screen>
+attachments   {+|-}disposition mime-type
+unattachments {+|-}disposition mime-type
+</screen>
+
+<para>
+Disposition is the attachment's Content-disposition type -- either
+"inline" or "attachment".  You can abbreviate this to I or A.
+</para>
+
+<para>
+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.
+</para>
+
+<para>
+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.)
+</para>
+
+<para>
+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.
+</para>
+
+<para>
+Some examples might help to illustrate.  The examples that are not
+commented out define the default configuration of the lists.
+</para>
+
+<screen>
+## 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
+</screen>
+
+</sect2>
+
 <sect2 id="mime-lookup">
 <title>MIME Lookup</title>
 
index dd7ad05eaf0b8016dec18958347da64ec085380f..5a1a51499d90cfe4a8b191d826794a29414a737c 100644 (file)
@@ -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
index 196594d31c7a82350f27a4e8b1fa26a0dc8fc6ef..ae3f822785759c40d5ccfdc5633f4582ad049764 100644 (file)
--- 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);
index a938c81df9ab4ac6823b359bbd516beed2598220..3a40747a1fa5a0bdd57fe5bac4c0d1e09a326b40 100644 (file)
--- 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 b064ec25fcd8307441a290d22e66a53fd023fb24..858a6afd0cd1871b7fffceae6a04fc662e132453 100644 (file)
--- 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 6b05b7cea6b9e23034e898913f572de601d21353..af82e8e7fa28d8f8f4c6a8cefe37793454cb072e 100644 (file)
--- 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 4c72c2d0bf71bbb8ca07d9db3e49d54ef34f68f7..a374f25aa6601cc5aa8293d8f5ed9d6ff1a27744 100644 (file)
--- 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 164e5edfd39bf6c3a49329631727f46d7e6bc9bc..7b2d3e3ef6e4a1caff2a031833d61b6325baa498 100644 (file)
--- 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 3d15af3eb34c16d5fa64bddba7f757a0bc585264..697662a11c73d8b6a62052321921aca0c50e3cb1 100644 (file)
--- 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;
+}
index 09cde4474ac79e3fc332af7486dfb01fb2f8b1d8..12d413f0e467031fb2d0aa28d57d1d256373264c 100644 (file)
--- 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));
   }
index 327b7e14bb93caafa0da483efd47e58412b0472a..f1fda6c23e3bf0fac64a39e1f43d7cf48e27247a 100644 (file)
--- 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 *);
index 7442b182ebd5c15a591b73d0d876921ac2bf2e4d..03fc71a073b8a0f8140357deee963b5b22b85118 100644 (file)
@@ -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;
   }