Commit f5e7b391 authored by David Champion's avatar David Champion

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.
parent 324752c8
......@@ -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
##
......
......@@ -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>
......
......@@ -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
......
......@@ -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);
......
......@@ -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;
......
......@@ -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
......
......@@ -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 },
......
......@@ -27,7 +27,8 @@ enum
TYPEMODEL,
TYPEMULTIPART,
TYPETEXT,
TYPEVIDEO
TYPEVIDEO,
TYPEANY
};
/* Content-Transfer-Encoding */
......
......@@ -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"
......
......@@ -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