Deleting Attachments From Message

Ref: Bugzilla Bug 2920

Introduction

This feature is about taking messages which have been received and are currently stored in either local folders or in an IMAP folder, and deleting an attachment from it. The rationale for wanting to delete the attachment is irrelevant, but may include a desire to save space or simply because the attachment is not wanted whereas the message is.

Feature Summary

Audit Data

When an attachment is deleted from a message, the structure and contents of the original message has been changed. To ensure that any person subsequently viewing that message knows that the message has been altered, the message will have audit information added to it.

The requirements for this audit information are the following:

  1. clearly include audit data which states that attachments which were attached to the message at time of receipt have subsequently been deleted by user action.
  2. include this audit data such that users are not locked into Mozilla as a mail client but can access this information with any RFC compliant mail client.
  3. include in the audit data the following information:
  4. include in the audit data the following information if available:

Message Format

Note: the specification for MIME messages is defined by: RFC 1521 (Obsoleted MIME RFC), RFC 2045 (Message Bodies), RFC 2046 (Content Types), RFC 2047 (Non-ASCII Header Extensions), RFC 2048, RFC 2049 and probably others.

Messages which conform to the relevant RFC standards at time of receipt must conform the the same standards after having attachments deleted. The only exception to this rule is that the Message-ID field of the altered message will not be updated as RFC 822 and RFC 2822 requires.

In order to meet the requirement above, and the requirements of the Audit Data, it is proposed that every deleted attachment is replaced with a new attachment of MIME "Content-Type: text/x-moz-deleted". As this MIME type should be treated by a MIME compliant viewer to be 'text/plain' (ref: RFC 2046, 4.1.4), this new attachment can be rendered inline as text and opened in a text editor by any mail viewer.

By utilizing a custom sub-type of the 'text' content type, it is possible for Mozilla to know that the attachment is audit data for a deleted attachment and process it correctly (e.g. special display icon, refuse to delete it again, etc). The name of the original attachment would be reused with a special prefix (e.g. "Deleted: ") to show both the name of the original attachment and the fact that it has been deleted.

An extra custom header would also be added to the new MIME section, "X-Mozilla-Altered:" which provides extra confirmation that the attachment was deleted.

Note: the custom "X-Mozilla-Altered" header would need to be stripped and the "text/x-moz-deleted" MIME Content-Type would need to be replaced with "text/plain" in all newly received messages to prevent spoofing of deleted attachments (see the section Security).

The contents of this new text attachment is formatted text containing the data mentioned in the section Audit Data.

The complete header information that would be created for a deleted attachment is:

--part
Content-Type: text/x-moz-deleted; name="Deleted: <original-name>"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline; filename="Deleted: <original-name>"
X-Mozilla-Altered: AttachmentDeleted; date="<deletion-timestamp>"

This attachment "<original-name>" (<original-type>, <encoded-size> bytes)
was deleted on <deletion-timestamp>.

The original MIME headers for this attachment are:
<original-mime-headers-of-message-part>

--part--

The complete MIME section for a detached attachment is:

--part
Content-Type: text/x-moz-deleted; name="Deleted: <original-name>"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline; filename="Deleted: <original-name>"
X-Mozilla-Altered: AttachmentDeleted; date="<deletion-timestamp>"

This attachment "<original-name>" (<original-type>, <encoded-size> bytes)
was deleted on <deletion-timestamp>.

Before deletion it was saved to the following file:
  Path: <file-path>
  Size: <file-size> bytes
  Timestamp: <file-creation-timestamp>

The original MIME headers for this attachment are:
<original-mime-headers-of-message-part>

--part--

Example

Given the following email:

Received: (qmail 28935 invoked from network); 21 Feb 2004 17:13:11 -0000
Received: from unknown (HELO jellycan.com) (202.13.7.221)
by hoster900.com with SMTP; 21 Feb 2004 17:13:11 -0000
Message-ID: <40379124.1090900@jellycan.com>
Date: Sun, 22 Feb 2004 02:11:00 +0900
From: foobar-name <foobar-address@jellycan.com>
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113
X-Accept-Language: en-us, en
MIME-Version: 1.0
To:  foobar-address@jellycan.com
Subject: attachment test
Content-Type: multipart/mixed;
boundary="------------010007050301010701030105"

This is a multi-part message in MIME format.
--------------010007050301010701030105
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

foobar

--------------010007050301010701030105
Content-Type: text/plain;
 name="linkify_test.txt"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline;
 filename="linkify_test.txt"

This is a test email for the mozTXTToHTMLConv.cpp stream converter.
<snip>
The international domain http://日本語.com/ doesn't work yet...

--------------010007050301010701030105
Content-Type: image/jpeg;
 name="is405.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
 filename="is405.jpg"

/9j/4AAQSkZJRgABAQEAAAAAAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoM
<snip>
NGjRo0aNGjRo0aNGjRo0aNGjRo0aNGjRo0adN51//9k=
--------------010007050301010701030105--

If the first attachment was detached and the second attachment deleted, the resulting message would be:

Received: (qmail 28935 invoked from network); 21 Feb 2004 17:13:11 -0000
Received: from unknown (HELO jellycan.com) (202.13.7.221)
by hoster900.com with SMTP; 21 Feb 2004 17:13:11 -0000
Message-ID: <40379124.1090900@jellycan.com>
Date: Sun, 22 Feb 2004 02:11:00 +0900
From: foobar-name <foobar-address@jellycan.com>
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113
X-Accept-Language: en-us, en
MIME-Version: 1.0
To:  foobar-address@jellycan.com
Subject: attachment test
Content-Type: multipart/mixed;
boundary="------------010007050301010701030105"

This is a multi-part message in MIME format.
--------------010007050301010701030105
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

foobar

--------------010007050301010701030105
Content-Type: text/x-moz-deleted; name="Deleted: linkify_test.txt"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline; filename="Deleted: linkify_test.txt"
X-Mozilla-Altered: AttachmentDeleted; date="22/02/2004 09:53:01"

This attachment "linkify_test.txt" (text/plain, 9288 bytes)
was deleted on 22/02/2004 09:53:01.

Before deletion it was saved to the following file:
  Path: d:\temp\linkify_test.txt
  Size: 9288 bytes
  Timestamp: 22/02/2004 09:53:01

The original MIME headers for this attachment are:
Content-Type: text/plain;
 name="linkify_test.txt"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline;
 filename="linkify_test.txt"

--------------010007050301010701030105
Content-Type: text/x-moz-deleted; name="Deleted: is405.jpg"
Content-Transfer-Encoding: 8bit
Content-Disposition: inline; filename="Deleted: is405.jpg"
X-Mozilla-Altered: AttachmentDeleted; date="22/02/2004 09:54:01"

This attachment "is405.jpg" (image/jpeg, 67299 bytes)
was deleted on 22/02/2004 09:54:01.

The original MIME headers for this attachment are:
Content-Type: image/jpeg;
 name="is405.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
 filename="is405.jpg"

--------------010007050301010701030105--

User Interface

Menu Items

Menu Item Status Description
Open changed Allows information stored for deleted attachments to be viewed.
Save As... changed Applies only to non-deleted attachments.
Detach... new "Detach Attachment" feature as described in the feature list. Applies only to non-deleted attachments.
Delete new "Delete Attachment" feature as described in the feature list. Applies only to non-deleted attachments.
Save All... changed Applies only to non-deleted attachments. All attachments which have already been deleted will be ignored by this function.
Detach All... new "Detach Attachment" feature applied to all non-deleted attachments in the attachment list as a group. All attachments which have already been deleted will be ignored by this function.
Delete All new "Delete Attachment" feature applied to all non-deleted attachments in the attachment list as a group. All attachments which have already been deleted will be ignored by this function.

Menu Enabling Schedule

Menu Item Enabled Disabled
Open (1) any attachment is selected (1) no attachment is selected
Save As... (2) any non-deleted attachment is selected (2) no attachment is selected or a deleted attachment is selected
Detach... (2) any non-deleted attachment is selected (2) no attachment is selected or a deleted attachment is selected
Delete (2) any non-deleted attachment is selected (2) no attachment is selected or a deleted attachment is selected
Save All... (3) any non-deleted attachment is available (3) no attachments or all attachments are deleted
Detach All... (3) any non-deleted attachment is available (3) no attachments or all attachments are deleted
Delete All (3) any non-deleted attachment is available (3) no attachments or all attachments are deleted
Figure 1: Attachment List Context Menu
Open
Save As...
Detach...
Delete
Save All...
Detach All...
Delete All
Figure 2: File -> Attachments Menu
File      Edit     ....
New
Open Message
Attachments ->
Close

Exit
       
  1 fizzgig.jpg ->
2 wobble.txt
3 Deleted: antelope.jpg

Save All...
Detach All...
Delete All
  Open

Save As...
Detach...
Delete
     
     
       
Figure 3: Mail Window Attachment List
(jpg) fizzgig.jpg
(txt) wobble.txt
(XX) Deleted: antelope.jpg

(jpg)  .jpg icon, (txt) .txt icon, (XX) special deleted attachment image

Flow Of Control

Delete

Delete All

Detach...

Detach All...

MIME Emitter

The new MIME emitter is required to be able to remove specific attachments from the message without otherwise modifying the format of the message. The listener can be told the structure of the message including attachment part ids, and given the raw RFC 822 data.

It then uses the structure information and attachment part ids to remove the attachment body data where requested. The attachments to remove are matched to the message structure via the part ids which are include in the attachment URL.

This is implemented using a new interface derived from nsIStreamListener which must be implemented by the listener.

[scriptable, uuid()]
interface nsIMessageStructureListener : nsIStreamListener
{
    const int MESSAGE_HEADERS       = 1;
    const int MESSAGE_BODY          = 2;
    const int ATTACHMENT_BOUNDARY   = 3;
    const int ATTACHMENT_HEADERS    = 4;
    const int ATTACHMENT_BODY       = 5;
    void onMessageStructure(in int type, in char * partId);
};

void onMessageStructure(int type, char * partId)

Called to notify the listener of the type of message data about to be passed via the onMessageData call. This allows a stateful listener who cares about certain types of data to handle the raw data appropriately.

Valid values for type are:
MESSAGE_HEADERS, MESSAGE_BODY, ATTACHMENT_BOUNDARY, ATTACHMENT_HEADERS, ATTACHMENT_BODY

partId will be nsnull except when type is set to an ATTACHMENT_* value. At these times it will be set to the attachment part id. For example, "1.3". The one exception is for final attachment boundaries where it will be nsnull.

In the examples below, nsIStreamListener::onMessageData() will be called 1 or more times after each nsIMessageStructureListener::onMessageStructure() call.

Example: text message

onMessageStructure(messageHeaders)
onMessageStructure(messageBody)

Example: text message, 1 attachment

onMessageStructure(messageHeaders, nsnull)
onMessageStructure(messageBody, nsnull)
// multipart/mixed, text message
onMessageStructure(attachmentBoundary, "1.1")
onMessageStructure(attachmentHeaders, "1.1")
onMessageStructure(attachmentBody, "1.1")
// attachment 1
onMessageStructure(attachmentBoundary, "1.2")
onMessageStructure(attachmentHeaders, "1.2")
onMessageStructure(attachmentBody, "1.2")
// final boundary
onMessageStructure(attachmentBoundary, nsnull)
// possible trailing body
onMessageStructure(messageBody, nsnull)

Example: text message, 2 attachments

onMessageStructure(messageHeaders, nsnull)
onMessageStructure(messageBody, nsnull)
// multipart/mixed, text message
onMessageStructure(attachmentBoundary, "1.1")
onMessageStructure(attachmentHeaders, "1.1")
onMessageStructure(attachmentBody, "1.1")
// attachment 1
onMessageStructure(attachmentBoundary, "1.2")
onMessageStructure(attachmentHeaders, "1.2")
onMessageStructure(attachmentBody, "1.2")
// attachment 2
onMessageStructure(attachmentBoundary, "1.3")
onMessageStructure(attachmentHeaders, "1.3")
onMessageStructure(attachmentBody, "1.3")
// final boundary
onMessageStructure(attachmentBoundary, nsnull)
// possible trailing body
onMessageStructure(messageBody, nsnull)

Example: text+html message

onMessageStructure(messageHeaders, nsnull)
onMessageStructure(messageBody, nsnull)
// multipart/mixed, text message
onMessageStructure(attachmentBoundary, "1.1")
onMessageStructure(attachmentHeaders, "1.1")
onMessageStructure(attachmentBody, "1.1")
// text
onMessageStructure(attachmentBoundary, "1")   
onMessageStructure(attachmentHeaders, "1")
onMessageStructure(attachmentBody, "1")
// html
onMessageStructure(attachmentBoundary, "2")   
onMessageStructure(attachmentHeaders, "2")
onMessageStructure(attachmentBody, "2")
// final boundary
onMessageStructure(attachmentBoundary, nsnull)

Example: text+html message, 2 attachments

onMessageStructure(messageHeaders, nsnull)
onMessageStructure(messageBody, nsnull)
// multipart/alternative
onMessageStructure(attachmentBoundary, "1")   
// text
onMessageStructure(attachmentBoundary, "1.1")   
onMessageStructure(attachmentHeaders, "1.1")
onMessageStructure(attachmentBody, "1.1")
// html
onMessageStructure(attachmentBoundary, "1.2")   
onMessageStructure(attachmentHeaders, "1.2")
onMessageStructure(attachmentBody, "1.2")
// multipart/alternative final boundary
onMessageStructure(attachmentBoundary, nsnull)
// attachment 1
onMessageStructure(attachmentBoundary, "2")
onMessageStructure(attachmentHeaders, "2")
onMessageStructure(attachmentBody, "2")
// attachment 2
onMessageStructure(attachmentBoundary, "3")
onMessageStructure(attachmentHeaders, "3")
onMessageStructure(attachmentBody, "3")
// final boundary
onMessageStructure(attachmentBoundary, nsnull)

Comments to brofield@jellycan.com.