Subject: WGLC on draft-ietf-sieve-mime-loop

From: Alexey Melnikov <[email-masked]>
Date: Mon, 06 Aug 2007 09:48:58 +0100

Hi,

This message officially starts the Sieve Working Group Last Call for
the following document: 

SIEVE Email Filtering:  MIME part Tests, Iteration, Replacement and Enclosure
<http://www.ietf.org/internet-drafts/draft-ietf-sieve-mime-loop-03.txt>

The Working Group Last Call for this document starts on August 6th and will end on August 20th.

Please send any comments to the Sieve mailing list or directly to me. Please CC Tony Hansen <[email-masked]> and Cyrus Daboo <[email-masked]> in the latter case. Reviews that found no issues are also welcomed, so if you review the document and find it acceptable, please let the mailing list/authors+me know as well.

Thank you,
Alexey, Sieve WG co-chairs

---

From: Dilyan Palauzov <[email-masked]>
Date: Mon, 06 Aug 2007 17:55:38 +0200

Hello,

 > 7 Action extract_text

> QUESTION: What do we do if the Content-Transfer-Encoding is anything other than 7bit?

If it is 8bit, we take the :first number characters (not bytes), taking into account the 
"charset=" parameter of the Content-Type header, when presented. If it is base64 or 
quoted-printable, we convert it to 8bit and proceed as if it was 8bit. The same for binary,
 even if the result will be probably useless. (The useless results can happen using base64, 
too, but the possibility is smaller)

> :type, :subtype, :contenttype

What is the obvious advantage of having them, compared to header :contains ? I mean, isn't 
'header :contains "Content-Type" "text/"' as powerful, as 'header :type "Context-Type" 
"text"'? If this was discussed already on this list, could you tell me the starting date?

Със здраве,
Дилян

Alexey Melnikov wrote:
> Please send any comments to the Sieve mailing list or directly to me. 
> Please CC Tony Hansen <[email-masked]> and Cyrus Daboo 
> <[email-masked]> in the latter case. Reviews that found no issues 
> are also welcomed, so if you review the document and find it 
> acceptable, please let the mailing list/authors+me know as well.
>
> Thank you,

---

From: "Nigel Swinson" <[email-masked]>
Date: Fri, 10 Aug 2007 17:31:44 +0100

WRT to: http://tools.ietf.org/html/draft-ietf-sieve-mime-loop-03 and some
related discussion of http://tools.ietf.org/html/draft-ietf-sieve-body-06

Abstract

I don't think it's necessary to start the abstract by saying what can't be
done with an existing standard.  By stating what this extension offers you
implicitly propose that no other standard offers this.

-   The Sieve email filtering language has no way to examine individual
-   MIME parts or any way to manipulate those individual parts.  However,
-   being able to filter based on MIME content is important.  This
-   document defines extensions for these needs.
+   This document defines extensions to the Sieve email filtering language
+   to permit analysis and manipulation of the MIME body parts of an email
+   message.

1.  Introduction

I don't think it's necessary to justify the current extension based on what
other standards don't allow you to do.  Also while it might be helpful to
refer to other related standards, it seems to just create headaches when you
want to revise the standard later, so I'd suggest:

-   Sieve scripts are used to make decisions about the disposition of an
-   email message.  The base Sieve specification,
-   [I-D.ietf-sieve-3028bis], defines operators for looking at the
-   message headers, such as addresses and the subject.  Other extensions
-   provide access to the body of the message ([I-D.ietf-sieve-body]), or
-   allow you to manipulate the header of the message
-   ([I-D.ietf-sieve-editheader]).  But none of these extensions take
-   into account that MIME messages ([RFC2045]) are often complex
   MIME messages ([RFC2045]) are often complex
   objects, consisting of many parts and sub-parts.  This extension
   defines mechanisms for performing tests on MIME body parts, looping
   through the MIME body parts, extracting information from a MIME body
   part, changing the contents of a MIME body part, and enclosing the
-   entire message with a wrapper.
+  entire message within a wrapper.

3.  Sieve Loops

   The base Sieve language has no looping mechanism.  Given that
   messages may contain multiple parts, in order to support filters that
   apply to any and all parts, we introduce a new control command:
   "for_every_part", which is an iterator that walks though every MIME
   part of a message, including nested parts, and applies the commands
   in the specified block to each of them.  The iterator will start with
   the first MIME part (as its current context) and will execute a
-   command block (Sieve commands enclosed by { ...}).  Upon completion
+   command block (Sieve commands enclosed by {...}).  Upon completion
   of this command block, the iterator advances to the next MIME part
   (as its current context) and executes the same command block again.

   "for_every_part" commands can be nested inside other "for_every_part"
   commands.  When this occurs, the nested "for_every_part" iterates
-   over the MIME parts contained within the MIME part current being
+   over the MIME parts contained within the MIME part currently being
   targeted by the nearest enclosing "for_every_part" command.  If that
   MIME part is a terminal MIME part (i.e. does not contain other MIME
   parts) then the nested "for_every_loop" is simply ignored.

4.1.  Test "header"

   The "header" test is extended with the addition of a new ":mime"
-   tagged argument, which takes a number of other arguments.
+   tagged argument and its associated options.

   Usage:  header [:mime] [:anychild] [MIMEOPTS]
      [COMPARATOR] [MATCH-TYPE]
      <header-names: string-list> <key-list: string-list>

   Usage:  The definition of [MIMEOPTS] is:

      Syntax: ":type" / ":subtype" / ":contenttype" /
      ":param" <param-list: string-list>

   When the ":mime" tagged argument is present in the "header" test, it
-   will parse the MIME header lines in a message so that tests can be
+   will parse the MIME header lines in the message so that tests can be
   performed on specific elements.

I don't think the section below reads well, as I think the list with
introductory partial sentence construct should mean you can prefix each list
item with the indroductory partial sentence and it still read well.  Below
you'd end up with:

- If the ":anychild" tagged argument is NOT specified if used within the
context of a "for_every_part" iterator, etc....
- If the ":anychild" tagged argument is NOT specified if used outside the
context of a "for_every_part" iterator, etc...

Also I think the behaviour should be explained when :anychild is used with
for_every_part.

-   If the ":anychild" tagged argument is NOT specified:
-
-   o  If used within the context of a "for_every_part" iterator, the
-      "header" test will examine the headers associated with the current
-      MIME part context from the loop.
-
-   o  If used outside the context of a "for_every_part" iterator, the
-      "header" test will examine only the outer, top-level, headers of
-      the message.
+   When used outside the context of a "for_every_part" iterator, and
+   without an ":anychild" tagged argument, the "header" test will examine
+   only the outer top-level RFC2822 headers of the message.
+
+   When used inside the context of a "for_every_part" iterator, and
+   without an ":anychild" tagged argument, the "header" test will examine
+   the headers associated with the current MIME part context from the loop.
+
-   If the ":anychild" tagged argument IS specified, the "header" test
+  When used outside the context of a "for_every_part" iterator, and
+  with an ":anychild" tagged argument, the "header" test
   will examine all MIME body parts and return true if any of them
   satisfies the test.
+
+  When used inside the context of a "for_every_part" iterator, and
+  with an ":anychild" tagged argument, the "header" test will examine
+  the current MIME part context and all it's nested MIME body parts,
+  returning true if any of them satisfies the test.

   Example:

   require ["mime", "fileinto"];

-   if header :mime :anychild :contenttype :comparator
+   if header :mime :anychild :contenttype
             "Content-Type" "text/html"
   {
       fileinto "INBOX.html";
   }

   In this example, any message that contains any MIME part with a
   content-type of "text/html" is saved to the mailbox "INBOX.html".

Reading over this again I don't think it's clear when you should use
for_every_part, and when you should use :anychild.  I think we should offer
an example that illustrates why we have both mechanisms.  Consider the
question, "Which one of these should I use:

   if header :mime :anychild :contenttype :comparator
             "Content-Type" "text/html"
   { ... }

Or:

    for_every_part {
       if header :mime :contenttype :comparator
             "Content-Type" "text/html"
       { ... }
    }

Perhaps change the for_every_part example.  It had the same ":comparator"
bug that the previous example had anyway.

   Example:

   require ["mime", "for_every_part", "fileinto"];

   for_every_part
   {

+       if allof (
-       if header :mime :param "filename" :comparator
-          "Content-Disposition" "important"
+      header :mime :param "filename" :contains
+         "Content-Disposition" "important",
+      header :mime :subtype "Content-Type" "pdf",
+       size :over "100K")
       {
           fileinto "INBOX.important";
           break;
       }
   }

-   In this example, any message that contains any MIME part with a
-   content-disposition with a filename parameter containing the text
-   "important" is saved to the mailbox "INBOX.important".

+    In this example, any message that contains a MIME part that
+    has a content-disposition with a filename parameter containing the text
+   "important", has a content-subtype of "pdf" and is bigger than 100 Kb
+    is saved to the mailbox "INBOX.important".

4.2.  Test "address"

I've tried and failed to find any specification on the "content-from"
header.  Perhaps partially because googling for the term "content-from" is
fairly unfruitful, and there's no mention in 2045.  So I'd suggest this
section be dropped, or changed to reference where "content-from" is defined.
I'm also not clear at all why you can't just use a vanilla address test.  ie
why doesn't this work:

    if address :is :all "content-from" "[email-masked]"

I can understand utility in:

    if address :mime :anychild :is :all "content-from" [email-masked]

In which case I'd rather it was just:

    if address :anychild :is :all "content-from" [email-masked]

And the ":mime" be the way of accessing the type, subtype, parameters of
those structured headers.

4.3.  Test "exists"

Again I don't see why you couldn't just write:

if exists :anychild "content-md5"

5.  Action Replace

   If the :mime parameter is not specified, the replacement string is a
-   text/plain part.
+   text/plain part in UTF-8.

   If the :mime parameter is specified, then the replacement string is,
   in fact, a MIME entity as defined in [RFC2045] section 2.4, including
+ both MIME headers and content.
-   both MIME headers and content.  If the optional :mime parameter is
-   not supplied, the reason string is considered to be a UTF-8 string.

6.  Action Enclose

   A new Sieve action command is defined to allow an entire message to
   be enclosed as an attachment to a new message.  After enclosure,
   subsequent actions affecting the message header or content use the
-   newly create message instead of the original message; this means that
+   newly created message instead of the original message; this means that
   any use of a "replace" action or other similar actions should be
   executed before the "enclose" action.

The text implies that enclose happens immediately, and therefore subsequent
actions affecting the message header/content apply to the enclosing message,
but then it goes on to say if there are multiple enclose actions, then it's
only the last one that will win.  This means if I have an enclose action,
I'll have to remember that the outer body part is the new "enclosing" body
part, and should I get another enclose action then I should "replace" that
enclosing body part rather than enclose the message yet again such that it
has two layers of enclosing.

This sounds like a real hassle to implement for questionable utility, I'd
rather two enclose actions just add two layers of enclosing mime structure
and body text body parts be present.  Just like they would if the message
goes through two separate Sieve scripts, both of which add an enclose.
Either that or define that the enclose action should happen at the end of
the script and therefore doesn't affect the mime structure during the
existing processing (that might even be inside a for_every_part construct).
I'd actually suggest we leave it to the implementation like we do for
fileinto.  The implementation can choose to have the enclose happen
immediately, or at the end.

7.  Action extract_text

Shouldn't we leave this to the body test?  I know in section 5 of
http://tools.ietf.org/html/draft-ietf-sieve-body-06 it says that we
shouldn't set the match variables, but perhaps it should define an optional
argument of :extracttext in the body spec to do this if we really wanted
them.

8.  Sieve Capability Strings

-   A Sieve implementation that defines the ":mime" tagged arguments to
+  A Sieve implementation that defines the ":mime" tagged argument to
+   the "header" test and the ":anychild" tagged argument to
   the "header", "address" and "exists" commands will advertise the
   capability string "mime".

   A Sieve implementation that defines the "extract_text" action will
   advertise the capability string "extract_text".  Note that to be
   useful, the "extract_text" action also requires the "variables"
-   [I-D.ietf-sieve-variables] and "mime" capabilities.
+   [I-D.ietf-sieve-variables] and "for_every_part" capabilities.

9.1.  Example 1

   A Sieve script to replace all the Windows executable attachments in a
   message would be:

This example isn't indented either and should be:

    require [ "for_every_part", "mime", "replace" ];
    for_every_part
    {
-  if ( anyof (
+  if anyof (
             header :mime :contenttype :is "Content-Type" "application/exe",
             header :mime :param "filename"
               ["Content-Type", "Content-Disposition"] :matches "*.com" )
      {
        replace "Executable attachment removed by user filter";
      }
    }

9.2.  Example 2

   A Sieve script to warn the user about executable attachment types
   would be:

   require [ "for_every_part", "mime", "enclose" ];

   for_every_part
   {
     if header :mime :param "filename"
        ["Content-Type", "Content-Disposition"] :matches
          ["*.com", "*.exe", "*.vbs", "*.scr",
           "*.pif", "*.hta", "*.bat", "*.zip" ]
     {
       # these attachment types are executable
-       enclose :subject "Warning" "
+      enclose :subject "Warning" text:
   WARNING! The enclosed message contains executable attachments.
   These attachments types may contain a computer virus program
-   that can infect your computer and potentently damage your data
+   that can infect your computer and potentently damage your data.

   Before clicking on these message attachments, you should verify
   with the sender that this message was sent by them and not a
   computer virus.
-   ";
    .
       break;
     }
   }

9.3.  Example 3

   A Sieve script to extract subject and text out of messages from the
-   boss
   boss:

11.  Security Considerations

   The "enclose" action creates an entirely new message, as compared to
   just redirecting or forwarding the existing message.  Therefore, any
   site policies applicable to message submission should be enforced.
-   site policies applicable to message submission should be enforced
-   here.

   The looping specification specified here provides easier access to
   information about the message contents, which may also be achieved
   through other sieve tests.  This is not believed to raise any
   additional security issues beyond those for the Sieve "envelope" and
+ "body" ([I-D.ietf-sieve-body]) tests.
-   "body" tests.

Cheers

Nigel

---

From: "Nigel Swinson" <[email-masked]>
Date: Fri, 10 Aug 2007 17:40:11 +0100

> too, but the possibility is smaller)

I guess the reason this is a problem is if you hit some 10MB base64 encoded
attachment and are trying to extract the first 100 bytes.  You either have
to decode the whole 10MB, or write some picky code to extract as little text
as possible while decoding legal units of QP/base64 encoding.

With the body test we offer :raw :content :text to decide what kind of
transform is required, which is why I believe extract_text should be moved
to the body spec, and implemented through an optional argument to force the
setting of the match variables.

> "text"'? If this was discussed already on this list, could you tell me the
starting date?

It's a matter of precision of parsing.  Consider:

    header :contains "Content-Type" "text"

and

    header :type "Content-Type" "text"

Against this content-type header:

Content-Type: application/pdf;  name="presentationtext.pdf"

Nigel

---

From: "Aaron Stone" <[email-masked]>
Date: Fri, 10 Aug 2007 17:14:29 -0000

I like these edits a lot! +1 (with a very large value of 1).

One editing note, recall that RFC's should always use the Oxford comma in
lists of three items or more. I've noted two missing commas below. (Sorry,
this is uber-nit-picking; the RFC Editor should pick these up anyways.)

Aaron

> +    is saved to the mailbox "INBOX.important".

needs a comma after "pdf".

>    capability string "mime".

Needs a comma after "address".

--

---

From: Alexey Melnikov <[email-masked]>
Date: Sat, 11 Aug 2007 10:54:24 +0100

Dilyan Palauzov wrote:

> If it is 8bit, we take the :first number characters (not bytes),

Actually 'octet' should be used, as 'character' might use one or more 
octets, depending on charset.

> taking into account the "charset=" parameter of the Content-Type 
> header, when presented. If it is base64 or quoted-printable, we 
> convert it to 8bit and proceed as if it was 8bit. The same for binary,

Agreed.

> even if the result will be probably useless. (The useless results can 
> happen using base64, too, but the possibility is smaller)
>
> What is the obvious advantage of having them, compared to header 
> :contains ? I mean, isn't 'header :contains "Content-Type" "text/"' as 
> powerful,

If I remember correctly, the header test doesn't know anything about 
header field structure, so it can match any of the Content-type parameters.

> as 'header :type "Context-Type" "text"'?

In addition to the above, I think this is slightly easier to understand 
'header :contains "Content-Type" "text/"'

> If this was discussed already on this list, could you tell me the 
> starting date?

---

From: Kjetil Torgrim Homme <[email-masked]>
Date: Sun, 12 Aug 2007 02:38:15 +0200

> octets, depending on charset.

please, no.  it's not good that you can't know if you get a garbage
value, ie. a truncated UTF-8 sequence or UCS-2, Big5, Shift-JIS or any
other character set encoding which uses multiple octets for a single
character.

3028 (and 3028bis) already has wording which says transcoding into UTF-8
SHOULD be supported.  it seems natural that this extension takes the
lead of [SIEVE] section 2.7.2.  I wouldn't mind it if this extension
*requires* support similar to what is described in the second paragraph:

      [...] Implementations convert text
      from header fields in all charsets [MIME3] to Unicode, encoded as
      UTF-8, as input to the comparator (see 2.7.3).  Implementations
      MUST be capable of converting US-ASCII, ISO-8859-1, the US-ASCII
      subset of ISO-8859-* character sets, and UTF-8.  Text that the
      implementation cannot convert to Unicode for any reason MAY be
      treated as plain US-ASCII (including any [MIME3] syntax) or
      processed according to local conventions. [...]

I can't see any advantage to allowing an implementation to cop out of
this -- it's not a very streneous requirement, and it would cause
interoperability problems if it is made a quality of implementation
issue.  remember that the eventual goal of IETF is to make e-mail pure
UTF-8, with as little extraneous protocol as possible.  we shouldn't add
non-i18n aware features now.

> header field structure, so it can match any of the Content-type parameters.

yes, consider
	Content-Type: image/jpeg; x-exif-data="context/something/other"

you could use :matches (since it is an anchored search), but it would
only work on the type, not the subtype.  e.g.

	header :matches "Content-Type" "*/plain"

wouldn't match

	Content-Type: text/plain; format="flowed"

and you can't easily fix the pattern to handle it.

however, I still don't see the need for all three, IMHO
just :contenttype is enough, and

	header :matches :contenttype "Content-Type" "text/*"

is as clear or maybe clearer than

I am hard pressed to find a use for the :subtype test, too.

> 'header :contains "Content-Type" "text/"'

oh, we agree :-)
-- 
Kjetil T.

---

From: "Mark E. Mallett" <[email-masked]>
Date: Mon, 20 Aug 2007 18:25:04 -0400

Hi,

I had intended to get some comments out before the last day (i.e.,
before today) but haven't managed to organize my thoughts well enough.
I'd like to get them out to the list in the next day or two.
Regardless, I don't think this is ready for last call yet.

mm

---

From: Alexey Melnikov <[email-masked]>
Date: Sat, 25 Aug 2007 17:41:00 +0100

> acceptable, please let the mailing list/authors+me know as well.

The WGLC has ended it seems there are some remaining issues that need to 
be addressed before sending the document to IESG.

---

From: "Mark E. Mallett" <[email-masked]>
Date: Mon, 27 Aug 2007 15:58:22 -0400

> I'd like to get them out to the list in the next day or two.

I don't know why I'm having such a hard time with this set of comments,
but I've been fretting on it forever, it seems.  Forgive me that it's still
somewhat muddled, but it's going out now :)

Even though I have a bunch of comments, I do like what this is trying to
accomplish and I'll likely end up implementing it however it ends up.

Some general stuff first:

I think I'd like it a bit better if it didn't seem like this was about
loops with some mime stuff thrown in; rather that it be about mime stuff
with loops being one aspect of it.  I realize the title is "MIME part
tests, iteration, extraction, replacement and enclosure."  However the
introductory text is almost all about loops.  I think that the first
introductory text should bring up a new concept of selecting MIME parts,
and how selecting a MIME part affects various Sieve constructs.  e.g.
when a MIME part is selected, the action of header tests becomes
refocused to deal with the selected mime part's header instead of the
top-level message header.  This is a change: that concept is not really
present in the draft so far, but I think it adds consistency and
clarity.  This would not have to be repeated for every section that
introduces new tagged arguments for header tests, as it is (sort of)
now.  This sort of introduction could lead to clearer text in other
areas too; such text could assume that the reader was familiar with the
notion of a selected MIME part, and go from there.  Each section would
not, as it does now, have to describe the special case of what a sieve
command does when it's inside of a loop vs the special case of what it
does when it's not inside of a loop.  I'd much rather get rid of those
special case descriptions in favor of a description of what the command
does, period.

With the concept of a selected MIME part, I'd also prefer it if a loop
left the last matching MIME part selected, even outside of the loop
after the loop has executed.  This would require the addition of at
least one more SIEVE verb, one that reselects the entire message (goes
back to the top).  It would probably also require a clarification of how
nested loops interact when selecting MIME parts, and possibly the
introduction of a scoping construct that preserves the MIME part
selection outside of its block.

My implementation has its own way selecting and looping through MIME
parts, and I've found that one of the great benefits is being able to
locate a part and then leave the focus on that part.

More specific stuff about the draft:

>    in the specified block to each of them.

Perhaps explicitly say that the walking of the MIME structure is
depth-first.  I can't really imagine any other order, but one never
knows.  And I must say.. one thing I have found useful is being able to
step through siblings without descending into child parts; I think
that's a significant lack here.

>    The iterator will start with
>    command block (Sieve commands enclosed by { ...}).

I would prefer "will start with the first child of the currently selected
message part (or MIME part)" perhaps with a reminder that initially
the "currently selected part" is the top-level message.  

> 
>    The iterator can be terminated prematurely by a new Sieve command,
>    "break".

This may be way too disruptive of a comment, but..  since the draft
provides for nested loops, perhaps some consideration should be given
for being able to break out of a particular level.  One failing in some
languages is a "break" that only applies to the immediately enclosing
scope.  One might, for example, have an optional tag on the
'for_every_part' and an optional matching tag on the "break":

    for_every_part "A" {
        for_every_part "B" {
	    if something
	        break "A";
        }
    }

>    over the MIME parts contained within the MIME part current being
>    targeted by the nearest enclosing "for_every_part" command.

Here one could simply say that it processes the message relative to
the currently selected MIME part.  This remains true regardless of
whether the loop is nested.

>    If that

Again, always true.

>    Sieve implementations MAY limit the number of nested loops that occur
>    within one another, however they MUST support at least one nested
>    loop inside another loop.

The way loops are defined in this draft, I'm trying to figure out what
nesting solves, and where the limit came from.  When the nesting came up
previously, I thought the concensus was to to make the maximum nesting
level implementation-defined, with a mandatory minimum nesting level of
1 (i.e., only one outer loop, no inner loops required).  I'm actually in
favor of nested loops, but I'm not sure how much sense they make when
you can't control the way the loops step through the parts, and when the
loop doesn't leave a particular part selected.  e.g. imagine a code
structure like this, only one that isn't so obviously made-up:

    if header :contenttype "content-type" "multipart/report" {
	for_every_child { # process the immediate children only
	    if header :contenttype "message/feedback-report" {
		# stuff here
	    }
	    elsif header :contenttype "text/plain" {
		# other stuff here
	    }
	    elsif header :contenttype "message/rfc822" {
		# Now descend into this and find a text/plain part
		for_every_part {
		    if header :contenttype "text/plain" {
			# ...
		    }
		}
	    }
	}
    }

(In my implementation I took the tack of adding a generic loop construct
and MIME part navigation primitives, which is a building-block approach
that I tend to favor.)

> 4.  Changes to Sieve tests
> 
>    This specification extends the base Sieve "header", "address" and
>    "exists" tests to support targeting those tests at a specific MIME
>    part or at all MIME parts in the enclosing scope.

As I mentioned, I think this would be better as part of introductory
text that talks about the notion of a currently selected message part.

>    tagged argument, which takes a number of other arguments.
> 

First, I think that having ":mime" take another tagged argument as a
modifier is confusing and is inconsistent with other Sieve constructs.
Take the "relational" extension, for example.  It isn't:

     header :value :gt "some-header" "3"
but it's
     header :value "gt" "some-header" "3"

and if there's going to be a ":mime" option, I would think it would
emulate that kind of syntax.  Either way, it's unfortunate that ":mime"
here has completely different meaning that ":mime" as it already exists
elsewhere.  But really, I think this ":mime" and its tagged-option
arguments are superfluous.  I think that ":param" can do it all.  Let's
say that the presence of ":param" is only valid for a header field that
has a general metasyntax matching

     field-name:  some-fixed-values 0*<;name=value>

A ":param" option would take a string (or string list) as argument.  If
the string is non-empty, it selects the corresponding value matching the
name in the string.  If the string is empty, it selects the
"some-fixed-values" portion of the header field.  Thus one could say
this to look at the subtype of a "content-type" header:

    header :param "" :matches "content-type" "*/html"

and this to look at the "charset" paremeter of that same header:

    header :param "charset" :is "ISO-8859-1"

No need for :mime or other selectors, and this becomes more general,
to boot.

> 4.2.  Test "address"
> 
>    The "address" test is extended with the addition of a new ":mime"
>    tagged argument, which takes a number of other arguments.
> 
>    Usage:  address [:mime] [:anychild] [COMPARATOR]
>       [ADDRESS-PART] [MATCH-TYPE]
>       <header-list: string-list> <key-list: string-list>

I don't really get how :mime is relevant here.
Ditto with "exists"

> 5.  Action Replace
> 
>    Usage:  replace [:mime] [:subject string] [:from string]
>       <replacement: string>
> 
>    The "replace" command is defined to allow a MIME part to be replaced
>    with the text supplied in the command.
> 
>    When used in the context of a "for_every_part" iterator, the MIME
>    part to be replaced is the "current" MIME part.

Again, I would prefer that this just say that it addresses the currently
selected message part.  Then there is no need to talk about the distinction
between its use inside of or outside of a loop body.

>    If the entire message is being replaced, a ":subject" parameter
>    specifies a subject line to attach to the message that is generated.
>    UTF-8 characters can be used in the string argument; implementations
>    MUST convert the string to [RFC2047] encoded words if and only if
>    non-ASCII characters are present.  Implementations MUST preserve the
>    previous Subject header as an Original-Subject header.

And again, for consistency's sake, why not just make the :subject
parameter work (to modify the "Subject" header field within the currently
selected message part) whether it's a child part that's selected or
whether it's the top part that's selected.  Ditto ":from" .  And for
that matter, why are these needed at all, when ":mime" will do?

> 7.  Action extract_text
> 
>    Usage:  extract_text [MODIFIER] [":first" number] <varname: string>
> 
>    The extract_text action may be used within the context of a
>    "for_every_part" loop.  It stores at most :first bytes of the current
>    MIME body part in the variable identified by varname.  If the :first
>    parameter is not present, the whole content of the current MIME body
>    part is stored.  In either case the actually stored data MAY be
>    truncated to conform to implementation specific limit on variable
>    length and/or on MIME body part length.  QUESTION: What do we do if
>    the Content-Transfer-Encoding is anything other than 7bit?
> 
>    If extract_text is used outside the context of a "for_every_part"
>    loop, the action will set the variable identified by varname to the
>    empty string.

Why?

Again, I would prefer that this be defined to operate consistently,
acting on the currently selected MIME part regardless of whether it's
inside of a loop body.

Yours, slowly and tediously,
-mm-