Ref: Bugzilla Bug 2920
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.
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:
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--
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--
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 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 |
Open Save As... Detach... Delete |
Save All... Detach All... Delete All |
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 |
|||
(jpg)
fizzgig.jpg (txt) wobble.txt (XX) Deleted: antelope.jpg |
(jpg) .jpg icon, (txt) .txt icon, (XX) special deleted attachment image
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); };
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.
onMessageStructure(messageHeaders) onMessageStructure(messageBody)
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)
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)
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)
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.