~msapiro/mailman/topics

« back to all changes in this revision

Viewing changes to Mailman/Handlers/CookHeaders.py

  • Committer: Mark Sapiro
  • Date: 2014-12-03 23:47:23 UTC
  • mfrom: (1006.1.356 2.2)
  • Revision ID: mark@msapiro.net-20141203234723-3pcwx85xua4n84yx
Merged the 2.2 branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
 
1
# Copyright (C) 1998-2014 by the Free Software Foundation, Inc.
2
2
#
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)
66
66
 
 
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):
 
74
        if delete:
 
75
            del msg[name]
 
76
        msg[name] = value
 
77
 
67
78
 
68
79
 
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'):
72
 
        del msg['x-ack']
73
 
        msg['X-Ack'] = 'no'
 
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,
87
97
            pass
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]
 
127
        if len(faddrs) == 1:
 
128
            realname, email = o_from = faddrs[0]
 
129
        else:
 
130
            # No From: or multiple addresses.  Just punt and take
 
131
            # the get_sender result.
 
132
            realname = ''
 
133
            email = msgdata['original_sender']
 
134
            o_from = (realname, email)
 
135
        if not realname:
 
136
            if mlist.isMember(email):
 
137
                realname = mlist.getMemberName(email) or email
 
138
            else:
 
139
                realname = 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())),
 
145
                      mlist, msg, msgdata)
 
146
    else:
 
147
        # Use this as a flag
 
148
        o_from = None
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):
138
177
                add(pair)
 
178
        # We also need to put the old From: in Reply-To: in all cases.
 
179
        if o_from:
 
180
            add(o_from)
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()))
145
 
        del msg['reply-to']
146
187
        # Don't put Reply-To: back if there's nothing to add!
147
188
        if new:
148
189
            # Preserve order
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]),
 
192
                          mlist, msg, msgdata)
 
193
        else:
 
194
            del msg['reply-to']
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
158
202
        # above code?
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
 
209
        # thing.
 
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.
165
214
            new = []
166
215
            d = {}
167
 
            for pair in getaddresses(msg.get_all('cc', [])):
168
 
                add(pair)
 
216
            # AvoidDuplicates may have set a new Cc: in msgdata.add_header,
 
217
            # so check that.
 
218
            if (msgdata.has_key('add_header') and
 
219
                    msgdata['add_header'].has_key('Cc')):
 
220
                for pair in getaddresses([msgdata['add_header']['Cc']]):
 
221
                    add(pair)
 
222
            else:
 
223
                for pair in getaddresses(msg.get_all('cc', [])):
 
224
                    add(pair)
169
225
            i18ndesc = uheader(mlist, mlist.description, 'Cc')
170
226
            add((str(i18ndesc), mlist.GetListEmail()))
171
 
            del msg['Cc']
172
 
            msg['Cc'] = COMMASPACE.join([formataddr(pair) for pair in new])
 
227
            change_header('Cc',
 
228
                          COMMASPACE.join([formataddr(pair) for pair in new]),
 
229
                          mlist, msg, msgdata)
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.
194
 
    del msg['list-id']
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():
224
 
        del msg[h]
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(', '))
230
 
        msg[h] = v
 
283
        change_header(h, v, mlist, msg, msgdata)
231
284
 
232
285
 
233
286
 
274
327
    else:
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)
278
331
    if rematch:
279
332
        subject = subject[rematch.end():]
280
333
        recolon = 'Re:'
304
357
                    h = u' '.join([prefix, subject])
305
358
            h = h.encode('us-ascii')
306
359
            h = uheader(mlist, h, 'Subject', continuation_ws=ws)
307
 
            del msg['subject']
308
 
            msg['Subject'] = h
 
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)
326
 
    del msg['subject']
327
 
    msg['Subject'] = h
 
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