1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
==================
Message decoration
==================
Message decoration is the process of adding headers and footers to the
original message. A handler module takes care of this based on the settings
of the mailing list and the type of message being processed.
>>> mlist = create_list('_xtest@example.com')
>>> msg_text = """\
... From: aperson@example.org
...
... Here is a message.
... """
>>> msg = message_from_string(msg_text)
Short circuiting
================
Digest messages get decorated during the digest creation phase so no extra
decorations are added for digest messages.
>>> from mailman.pipeline.decorate import process
>>> process(mlist, msg, dict(isdigest=True))
>>> print msg.as_string()
From: aperson@example.org
<BLANKLINE>
Here is a message.
>>> process(mlist, msg, dict(nodecorate=True))
>>> print msg.as_string()
From: aperson@example.org
<BLANKLINE>
Here is a message.
Decorating simple text messages
===============================
Text messages that have no declared content type character set are by default,
encoded in us-ascii. When the mailing list's preferred language is 'en'
(i.e. English), the character set of the mailing list and of the message will
match. In this case, and when the header and footer have no interpolation
placeholder variables, the message's payload will be prepended by the verbatim
header, and appended with the verbatim footer.
>>> msg = message_from_string(msg_text)
>>> mlist.msg_header = 'header\n'
>>> mlist.msg_footer = 'footer'
>>> mlist.preferred_language = 'en'
>>> process(mlist, msg, {})
>>> print msg.as_string()
From: aperson@example.org
...
<BLANKLINE>
header
Here is a message.
footer
Mailman supports a number of interpolation variables, placeholders in the
header and footer for information to be filled in with mailing list specific
data. An example of such information is the mailing list's "real name" (a
short descriptive name for the mailing list).
>>> msg = message_from_string(msg_text)
>>> mlist.msg_header = '$real_name header\n'
>>> mlist.msg_footer = '$real_name footer'
>>> mlist.real_name = 'XTest'
>>> process(mlist, msg, {})
>>> print msg.as_string()
From: aperson@example.org
...
XTest header
Here is a message.
XTest footer
You can't just pick any interpolation variable though; if you do, the variable
will remain in the header or footer unchanged.
>>> msg = message_from_string(msg_text)
>>> mlist.msg_header = '$dummy header\n'
>>> mlist.msg_footer = '$dummy footer'
>>> process(mlist, msg, {})
>>> print msg.as_string()
From: aperson@example.org
...
$dummy header
Here is a message.
$dummy footer
Handling RFC 3676 'format=flowed' parameters
============================================
RFC 3676 describes a standard by which text/plain messages can marked by
generating MUAs for better readability in compatible receiving MUAs. The
'format' parameter on the text/plain Content-Type header gives hints as to how
the receiving MUA may flow and delete trailing whitespace for better display
in a proportional font.
When Mailman sees text/plain messages with such RFC 3676 parameters, it
preserves these parameters when it concatenates headers and footers to the
message payload.
>>> mlist.msg_header = 'header'
>>> mlist.msg_footer = 'footer'
>>> mlist.preferred_language = 'en'
>>> msg = message_from_string("""\
... From: aperson@example.org
... Content-Type: text/plain; format=flowed; delsp=no
...
... Here is a message\x20
... with soft line breaks.
... """)
>>> process(mlist, msg, {})
>>> # Don't use 'print' here as above because it won't be obvious from the
>>> # output that the soft-line break space at the end of the 'Here is a
>>> # message' line will be retained in the output.
>>> print msg['content-type']
text/plain; format="flowed"; delsp="no"; charset="us-ascii"
>>> [line for line in msg.get_payload().splitlines()]
['header', 'Here is a message ', 'with soft line breaks.', 'footer']
Decorating mixed-charset messages
=================================
When a message has no explicit character set, it is assumed to be us-ascii.
However, if the mailing list's preferred language has a different character
set, Mailman will still try to concatenate the header and footer, but it will
convert the text to utf-8 and base-64 encode the message payload.
# 'ja' = Japanese; charset = 'euc-jp'
>>> mlist.preferred_language = 'ja'
>>> mlist.msg_header = '$description header'
>>> mlist.msg_footer = '$description footer'
>>> mlist.description = '\u65e5\u672c\u8a9e'
>>> from email.message import Message
>>> msg = Message()
>>> msg.set_payload('Fran\xe7aise', 'iso-8859-1')
>>> print msg.as_string()
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
<BLANKLINE>
Fran=E7aise
>>> process(mlist, msg, {})
>>> print msg.as_string()
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
<BLANKLINE>
5pel5pys6KqeIGhlYWRlcgpGcmFuw6dhaXNlCuaXpeacrOiqniBmb290ZXI=
Sometimes the message even has an unknown character set. In this case,
Mailman has no choice but to decorate the original message with MIME
attachments.
>>> mlist.preferred_language = 'en'
>>> mlist.msg_header = 'header'
>>> mlist.msg_footer = 'footer'
>>> msg = message_from_string("""\
... From: aperson@example.org
... Content-Type: text/plain; charset=unknown
... Content-Transfer-Encoding: 7bit
...
... Here is a message.
... """)
>>> process(mlist, msg, {})
>>> msg.set_boundary('BOUNDARY')
>>> print msg.as_string()
From: aperson@example.org
Content-Type: multipart/mixed; boundary="BOUNDARY"
<BLANKLINE>
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
header
--BOUNDARY
Content-Type: text/plain; charset=unknown
Content-Transfer-Encoding: 7bit
<BLANKLINE>
Here is a message.
<BLANKLINE>
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
footer
--BOUNDARY--
Decorating multipart messages
=============================
Multipart messages have to be decorated differently. The header and footer
cannot be simply concatenated into the payload because that will break the
MIME structure of the message. Instead, the header and footer are attached as
separate MIME subparts.
When the outerpart is multipart/mixed, the header and footer can have a
Content-Disposition of 'inline' so that MUAs can display these headers as if
they were simply concatenated.
>>> mlist.preferred_language = 'en'
>>> mlist.msg_header = 'header'
>>> mlist.msg_footer = 'footer'
>>> part_1 = message_from_string("""\
... From: aperson@example.org
...
... Here is the first message.
... """)
>>> part_2 = message_from_string("""\
... From: bperson@example.com
...
... Here is the second message.
... """)
>>> from email.mime.multipart import MIMEMultipart
>>> msg = MIMEMultipart('mixed', boundary='BOUNDARY',
... _subparts=(part_1, part_2))
>>> process(mlist, msg, {})
>>> print msg.as_string()
Content-Type: multipart/mixed; boundary="BOUNDARY"
MIME-Version: 1.0
<BLANKLINE>
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
header
--BOUNDARY
From: aperson@example.org
<BLANKLINE>
Here is the first message.
<BLANKLINE>
--BOUNDARY
From: bperson@example.com
<BLANKLINE>
Here is the second message.
<BLANKLINE>
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
footer
--BOUNDARY--
Decorating other content types
==============================
Non-multipart non-text content types will get wrapped in a multipart/mixed so
that the header and footer can be added as attachments.
>>> msg = message_from_string("""\
... From: aperson@example.org
... Content-Type: image/x-beautiful
...
... IMAGEDATAIMAGEDATAIMAGEDATA
... """)
>>> process(mlist, msg, {})
>>> msg.set_boundary('BOUNDARY')
>>> print msg.as_string()
From: aperson@example.org
...
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
header
--BOUNDARY
Content-Type: image/x-beautiful
<BLANKLINE>
IMAGEDATAIMAGEDATAIMAGEDATA
<BLANKLINE>
--BOUNDARY
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
<BLANKLINE>
footer
--BOUNDARY--
|