1
# Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
1
# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
3
3
# This program is free software; you can redistribute it and/or
4
4
# modify it under the terms of the GNU General Public License
64
64
charset = 'us-ascii'
65
65
return Header(s, charset, maxlinelen, header_name, continuation_ws)
67
def change_header(name, value, mlist, msg, msgdata, delete=True, repl=True):
68
if ((msgdata.get('from_is_list') == 2 or
69
(msgdata.get('from_is_list') == 0 and mlist.from_is_list == 2)) and
70
not msgdata.get('_fasttrack')
71
) or name.lower() in ('from', 'reply-to'):
72
msgdata.setdefault('add_header', {})[name] = value
73
elif repl or not msg.has_key(name):
69
80
def process(mlist, msg, msgdata):
70
81
# Set the "X-Ack: no" header if noack flag is set.
71
82
if msgdata.get('noack'):
83
change_header('X-Ack', 'no', mlist, msg, msgdata)
74
84
# Because we're going to modify various important headers in the email
75
85
# message, we want to save some of the information in the msgdata
76
86
# dictionary for later. Specifically, the sender header will get waxed,
88
98
# Mark message so we know we've been here, but leave any existing
89
99
# X-BeenThere's intact.
90
msg['X-BeenThere'] = mlist.GetListEmail()
100
change_header('X-BeenThere', mlist.GetListEmail(),
101
mlist, msg, msgdata, delete=False)
91
102
# Add Precedence: and other useful headers. None of these are standard
92
103
# and finding information on some of them are fairly difficult. Some are
93
104
# just common practice, and we'll add more here as they become necessary.
101
112
# known exploits in a particular version of Mailman and we know a site is
102
113
# using such an old version, they may be vulnerable. It's too easy to
103
114
# edit the code to add a configuration variable to handle this.
104
if not msg.has_key('x-mailman-version'):
105
msg['X-Mailman-Version'] = mm_cfg.VERSION
115
change_header('X-Mailman-Version', mm_cfg.VERSION,
116
mlist, msg, msgdata, repl=False)
106
117
# We set "Precedence: list" because this is the recommendation from the
107
118
# sendmail docs, the most authoritative source of this header's semantics.
108
if not msg.has_key('precedence'):
109
msg['Precedence'] = 'list'
119
change_header('Precedence', 'list',
120
mlist, msg, msgdata, repl=False)
121
# Do we change the from so the list takes ownership of the email
122
if (msgdata.get('from_is_list') or mlist.from_is_list) and not fasttrack:
123
# Be as robust as possible here.
124
faddrs = getaddresses(msg.get_all('from', []))
125
# Strip the nulls and bad emails.
126
faddrs = [x for x in faddrs if x[1].find('@') > 0]
128
realname, email = o_from = faddrs[0]
130
# No From: or multiple addresses. Just punt and take
131
# the get_sender result.
133
email = msgdata['original_sender']
134
o_from = (realname, email)
136
if mlist.isMember(email):
137
realname = mlist.getMemberName(email) or email
140
# Remove domain from realname if it looks like an email address
141
realname = re.sub(r'@([^ .]+\.)+[^ .]+$', '---', realname)
142
change_header('From',
143
formataddr(('%s via %s' % (realname, mlist.real_name),
144
mlist.GetListEmail())),
110
149
# Reply-To: munging. Do not do this if the message is "fast tracked",
111
150
# meaning it is internally crafted and delivered to a specific user. BAW:
112
151
# Yuck, I really hate this feature but I've caved under the sheer pressure
136
175
orig = msg.get_all('reply-to', [])
137
176
for pair in getaddresses(orig):
178
# We also need to put the old From: in Reply-To: in all cases.
139
181
# Set Reply-To: header to point back to this list. Add this last
140
182
# because some folks think that some MUAs make it easier to delete
141
183
# addresses from the right than from the left.
142
184
if mlist.reply_goes_to_list == 1:
143
185
i18ndesc = uheader(mlist, mlist.description, 'Reply-To')
144
186
add((str(i18ndesc), mlist.GetListEmail()))
146
187
# Don't put Reply-To: back if there's nothing to add!
149
msg['Reply-To'] = COMMASPACE.join(
150
[formataddr(pair) for pair in new])
190
change_header('Reply-To',
191
COMMASPACE.join([formataddr(pair) for pair in new]),
151
195
# The To field normally contains the list posting address. However
152
196
# when messages are fully personalized, that header will get
153
197
# overwritten with the address of the recipient. We need to get the
159
203
# Also skip Cc if this is an anonymous list as list posting address
160
204
# is already in From and Reply-To in this case.
161
if mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 \
162
and not mlist.anonymous_list:
205
# We do add the Cc in cases where From: header munging is being done
206
# because even though the list address is in From:, the Reply-To:
207
# poster will override it. Brain dead MUAs may then address the list
208
# twice on a 'reply all', but reasonable MUAs should do the right
210
if (mlist.personalize == 2 and mlist.reply_goes_to_list <> 1 and
211
not mlist.anonymous_list):
163
212
# Watch out for existing Cc headers, merge, and remove dups. Note
164
213
# that RFC 2822 says only zero or one Cc header is allowed.
167
for pair in getaddresses(msg.get_all('cc', [])):
216
# AvoidDuplicates may have set a new Cc: in msgdata.add_header,
218
if (msgdata.has_key('add_header') and
219
msgdata['add_header'].has_key('Cc')):
220
for pair in getaddresses([msgdata['add_header']['Cc']]):
223
for pair in getaddresses(msg.get_all('cc', [])):
169
225
i18ndesc = uheader(mlist, mlist.description, 'Cc')
170
226
add((str(i18ndesc), mlist.GetListEmail()))
172
msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new])
228
COMMASPACE.join([formataddr(pair) for pair in new]),
173
230
# Add list-specific headers as defined in RFC 2369 and RFC 2919, but only
174
231
# if the message is being crafted for a specific list (e.g. not for the
175
232
# password reminders).
191
248
# without desc we need to ensure the MUST brackets
192
249
listid_h = '<%s>' % listid
193
250
# We always add a List-ID: header.
195
msg['List-Id'] = listid_h
251
change_header('List-Id', listid_h, mlist, msg, msgdata)
196
252
# For internally crafted messages, we also add a (nonstandard),
197
253
# "X-List-Administrivia: yes" header. For all others (i.e. those coming
198
254
# from list posts), we add a bunch of other RFC 2369 headers.
215
271
# Add this header if we're archiving
216
272
if mlist.archive:
217
273
archiveurl = mlist.GetBaseArchiveURL()
218
if archiveurl.endswith('/'):
219
archiveurl = archiveurl[:-1]
220
274
headers['List-Archive'] = '<%s>' % archiveurl
221
275
# First we delete any pre-existing headers because the RFC permits only
222
276
# one copy of each, and we want to be sure it's ours.
223
277
for h, v in headers.items():
225
278
# Wrap these lines if they are too long. 78 character width probably
226
279
# shouldn't be hardcoded, but is at least text-MUA friendly. The
227
280
# adding of 2 is for the colon-space separator.
228
281
if len(h) + 2 + len(v) > 78:
229
282
v = CONTINUATION.join(v.split(', '))
283
change_header(h, v, mlist, msg, msgdata)
275
328
old_style = mm_cfg.OLD_STYLE_PREFIXING
276
329
subject = re.sub(prefix_pattern, '', subject)
277
rematch = re.match('((RE|AW|SV|VS)(\[\d+\])?:\s*)+', subject, re.I)
330
rematch = re.match('((RE|AW|SV|VS)\s*(\[\d+\])?\s*:\s*)+', subject, re.I)
279
332
subject = subject[rematch.end():]
304
357
h = u' '.join([prefix, subject])
305
358
h = h.encode('us-ascii')
306
359
h = uheader(mlist, h, 'Subject', continuation_ws=ws)
360
change_header('Subject', h, mlist, msg, msgdata)
309
361
ss = u' '.join([recolon, subject])
310
362
ss = ss.encode('us-ascii')
311
363
ss = uheader(mlist, ss, 'Subject', continuation_ws=ws)
323
375
# TK: Subject is concatenated and unicode string.
324
376
subject = subject.encode(cset, 'replace')
325
377
h.append(subject, cset)
378
change_header('Subject', h, mlist, msg, msgdata)
328
379
ss = uheader(mlist, recolon, 'Subject', continuation_ws=ws)
329
380
ss.append(subject, cset)
330
381
msgdata['stripped_subject'] = ss