Cleanup.
[mailman.git] / src / mailman / pipeline / docs / scrubber.txt
blob9c019d6e4aa8d0088dd4a9b9ef30b8d1553e80c2
1 ============
2 The scrubber
3 ============
5 The scrubber is an integral part of Mailman, both in the normal delivery of
6 messages and in components such as the archiver.  Its primary purpose is to
7 scrub attachments from messages so that binary goop doesn't end up in an
8 archive message.
10     >>> mlist = create_list('_xtest@example.com')
11     >>> mlist.preferred_language = 'en'
13 Helper functions for getting the attachment data.
15     >>> import os, re
16     >>> def read_attachment(filename, remove=True):
17     ...     path = os.path.join(config.PRIVATE_ARCHIVE_FILE_DIR,
18     ...                         mlist.fqdn_listname, filename)
19     ...     fp = open(path)
20     ...     try:
21     ...         data = fp.read()
22     ...     finally:
23     ...         fp.close()
24     ...     if remove:
25     ...         os.unlink(path)
26     ...     return data
28     >>> from urlparse import urlparse
29     >>> def read_url_from_message(msg):
30     ...     url = None
31     ...     for line in msg.get_payload().splitlines():
32     ...         mo = re.match('URL: <(?P<url>[^>]+)>', line)
33     ...         if mo:
34     ...             url = mo.group('url')
35     ...             break
36     ...     path = '/'.join(urlparse(url).path.split('/')[3:])
37     ...     return read_attachment(path)
40 Saving attachments
41 ==================
43 The Scrubber handler exposes a function called save_attachment() which can be
44 used to strip various types of attachments and store them in the archive
45 directory.  This is a public interface used by components outside the normal
46 processing pipeline.
48 Site administrators can decide whether the scrubber should use the attachment
49 filename suggested in the message's Content-Disposition: header or not.  If
50 enabled, the filename will be used when this header attribute is present (yes,
51 this is an unfortunate double negative).
53     >>> config.push('test config', """
54     ... [scrubber]
55     ... use_attachment_filename: yes
56     ... """)
57     >>> msg = message_from_string("""\
58     ... Content-Type: image/gif; name="xtest.gif"
59     ... Content-Transfer-Encoding: base64
60     ... Content-Disposition: attachment; filename="xtest.gif"
61     ... 
62     ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw==
63     ... """)
65     >>> from mailman.pipeline.scrubber import save_attachment
66     >>> print save_attachment(mlist, msg, 'dir')
67     <http://www.example.com/pipermail/_xtest@example.com/dir/xtest.gif>
68     >>> data = read_attachment('dir/xtest.gif')
69     >>> print data[:6]
70     GIF87a
71     >>> len(data)
72     34
74 Saving the attachment does not alter the original message.
76     >>> print msg.as_string()
77     Content-Type: image/gif; name="xtest.gif"
78     Content-Transfer-Encoding: base64
79     Content-Disposition: attachment; filename="xtest.gif"
80     <BLANKLINE>
81     R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw==
83 The site administrator can also configure Mailman to ignore the
84 Content-Disposition: filename.  This is the default.
86     >>> config.pop('test config')
87     >>> config.push('test config', """
88     ... [scrubber]
89     ... use_attachment_filename: no
90     ... """)
91     >>> msg = message_from_string("""\
92     ... Content-Type: image/gif; name="xtest.gif"
93     ... Content-Transfer-Encoding: base64
94     ... Content-Disposition: attachment; filename="xtest.gif"
95     ... 
96     ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw==
97     ... """)
98     >>> print save_attachment(mlist, msg, 'dir')
99     <http://www.example.com/pipermail/_xtest@example.com/dir/attachment.gif>
100     >>> data = read_attachment('dir/xtest.gif')
101     Traceback (most recent call last):
102     IOError: [Errno ...] No such file or directory:
103         u'.../archives/private/_xtest@example.com/dir/xtest.gif'
104     >>> data = read_attachment('dir/attachment.gif')
105     >>> print data[:6]
106     GIF87a
107     >>> len(data)
108     34
111 Scrubbing image attachments
112 ===========================
114 When scrubbing image attachments, the original message is modified to include
115 a reference to the attachment file as available through the on-line archive.
117     >>> msg = message_from_string("""\
118     ... MIME-Version: 1.0
119     ... Content-Type: multipart/mixed; boundary="BOUNDARY"
120     ...
121     ... --BOUNDARY
122     ... Content-type: text/plain; charset=us-ascii
123     ... 
124     ... This is a message.
125     ... --BOUNDARY
126     ... Content-Type: image/gif; name="xtest.gif"
127     ... Content-Transfer-Encoding: base64
128     ... Content-Disposition: attachment; filename="xtest.gif"
129     ... 
130     ... R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw==
131     ... --BOUNDARY--
132     ... """)
133     >>> msgdata = {}
135 The Scrubber.process() function is different than other handler process
136 functions in that it returns the scrubbed message.
138     >>> from mailman.pipeline.scrubber import process
139     >>> scrubbed_msg = process(mlist, msg, msgdata)
140     >>> scrubbed_msg is msg
141     True
142     >>> print scrubbed_msg.as_string()
143     MIME-Version: 1.0
144     Message-ID: ...
145     Content-Type: text/plain; charset="us-ascii"
146     Content-Transfer-Encoding: 7bit
147     <BLANKLINE>
148     This is a message.
149     -------------- next part --------------
150     A non-text attachment was scrubbed...
151     Name: xtest.gif
152     Type: image/gif
153     Size: 34 bytes
154     Desc: not available
155     URL: <http://www.example.com/pipermail/_xtest@example.com/attachments/.../attachment.gif>
156     <BLANKLINE>
158 This is the same as the transformed message originally passed in.
160     >>> print msg.as_string()
161     MIME-Version: 1.0
162     Message-ID: ...
163     Content-Type: text/plain; charset="us-ascii"
164     Content-Transfer-Encoding: 7bit
165     <BLANKLINE>
166     This is a message.
167     -------------- next part --------------
168     A non-text attachment was scrubbed...
169     Name: xtest.gif
170     Type: image/gif
171     Size: 34 bytes
172     Desc: not available
173     URL: <http://www.example.com/pipermail/_xtest@example.com/attachments/.../attachment.gif>
174     <BLANKLINE>
175     >>> msgdata
176     {}
178 The URL will point to the attachment sitting in the archive.
180     >>> data = read_url_from_message(msg)
181     >>> data[:6]
182     'GIF87a'
183     >>> len(data)
184     34
187 Scrubbing text attachments
188 ==========================
190 Similar to image attachments, text attachments will also be scrubbed, but the
191 placeholder will be slightly different.
193     >>> msg = message_from_string("""\
194     ... MIME-Version: 1.0
195     ... Content-Type: multipart/mixed; boundary="BOUNDARY"
196     ...
197     ... --BOUNDARY
198     ... Content-type: text/plain; charset=us-ascii; format=flowed; delsp=no
199     ...
200     ... This is a message.
201     ... --BOUNDARY
202     ... Content-type: text/plain; name="xtext.txt"
203     ... Content-Disposition: attachment; filename="xtext.txt"
204     ...
205     ... This is a text attachment.
206     ... --BOUNDARY--
207     ... """)
208     >>> scrubbed_msg = process(mlist, msg, {})
209     >>> print scrubbed_msg.as_string()
210     MIME-Version: 1.0
211     Message-ID: ...
212     Content-Transfer-Encoding: 7bit
213     Content-Type: text/plain; charset="us-ascii"; format="flowed"; delsp="no"
214     <BLANKLINE>
215     This is a message.
216     -------------- next part --------------
217     An embedded and charset-unspecified text was scrubbed...
218     Name: xtext.txt
219     URL: <http://www.example.com/pipermail/_xtest@example.com/attachments/.../attachment.txt>
220     <BLANKLINE>
221     >>> read_url_from_message(msg)
222     'This is a text attachment.'
225 Clean up
226 ========
228     >>> config.pop('test config')