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