~mailman-coders/mailman/2.1

1779 by Mark Sapiro
Bump copyright dates.
1
# Copyright (C) 1998-2018 by the Free Software Foundation, Inc.
1 by
This commit was manufactured by cvs2svn to create branch
2
#
3
# This program is free software; you can redistribute it and/or
4
# modify it under the terms of the GNU General Public License
5
# as published by the Free Software Foundation; either version 2
6
# of the License, or (at your option) any later version.
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
7
#
1 by
This commit was manufactured by cvs2svn to create branch
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
12
#
1 by
This commit was manufactured by cvs2svn to create branch
13
# You should have received a copy of the GNU General Public License
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
14
# along with this program; if not, write to the Free Software
930 by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16
# USA.
1 by
This commit was manufactured by cvs2svn to create branch
17
18
19
"""Routines for presentation of list-specific HTML text."""
20
21
import time
22
import re
23
24
from Mailman import mm_cfg
25
from Mailman import Utils
26
from Mailman import MemberAdaptor
27
from Mailman.htmlformat import *
28
29
from Mailman.i18n import _
30
1668 by Mark Sapiro
Fixes for CVE-2016-6893 and more.
31
from Mailman.CSRFcheck import csrf_token
32
1 by
This commit was manufactured by cvs2svn to create branch
33
34
EMPTYSTRING = ''
35
BR = '<br>'
36
NL = '\n'
37
COMMASPACE = ', '
38
39
40

41
class HTMLFormatter:
42
    def GetMailmanFooter(self):
1705 by Mark Sapiro
Display only the list-owner address in the 'list run by' footer.
43
        ownertext = Utils.ObscureEmail(self.GetOwnerEmail(), 1)
1 by
This commit was manufactured by cvs2svn to create branch
44
        # Remove the .Format() when htmlformat conversion is done.
45
        realname = self.real_name
46
        hostname = self.host_name
47
        listinfo_link  = Link(self.GetScriptURL('listinfo'), realname).Format()
48
        owner_link = Link('mailto:' + self.GetOwnerEmail(), ownertext).Format()
49
        innertext = _('%(listinfo_link)s list run by %(owner_link)s')
50
        return Container(
51
            '<hr>',
52
            Address(
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
53
                Container(
1 by
This commit was manufactured by cvs2svn to create branch
54
                   innertext,
55
                    '<br>',
56
                    Link(self.GetScriptURL('admin'),
57
                         _('%(realname)s administrative interface')),
58
                    _(' (requires authorization)'),
59
                    '<br>',
60
                    Link(Utils.ScriptURL('listinfo'),
61
                         _('Overview of all %(hostname)s mailing lists')),
62
                    '<p>', MailmanLogo()))).Format()
63
972 by msapiro
- CGI/admin.py
64
    def FormatUsers(self, digest, lang=None, list_hidden=False):
1 by
This commit was manufactured by cvs2svn to create branch
65
        if lang is None:
66
            lang = self.preferred_language
67
        conceal_sub = mm_cfg.ConcealSubscription
68
        people = []
69
        if digest:
1042 by Mark Sapiro
HTMLFormatter.py - list_hidden didn't work for digest members. Fixed.
70
            members = self.getDigestMemberKeys()
1 by
This commit was manufactured by cvs2svn to create branch
71
        else:
72
            members = self.getRegularMemberKeys()
1042 by Mark Sapiro
HTMLFormatter.py - list_hidden didn't work for digest members. Fixed.
73
        for m in members:
74
            if list_hidden or not self.getMemberOption(m, conceal_sub):
75
                people.append(m)
76
        num_concealed = len(members) - len(people)
1 by
This commit was manufactured by cvs2svn to create branch
77
        if num_concealed == 1:
78
            concealed = _('<em>(1 private member not shown)</em>')
79
        elif num_concealed > 1:
80
           concealed = _(
81
               '<em>(%(num_concealed)d private members not shown)</em>')
82
        else:
83
            concealed = ''
84
        items = []
85
        people.sort()
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
86
        obscure = self.obscure_addresses
1 by
This commit was manufactured by cvs2svn to create branch
87
        for person in people:
88
            id = Utils.ObscureEmail(person)
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
89
            url = self.GetOptionsURL(person, obscure=obscure)
1721 by Mark Sapiro
Show case preserved emails in the roster.
90
            person = self.getMemberCPAddress(person)
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
91
            if obscure:
1 by
This commit was manufactured by cvs2svn to create branch
92
                showing = Utils.ObscureEmail(person, for_text=1)
93
            else:
94
                showing = person
1516 by Mark Sapiro
A number of changes from the unofficial 2.2 branch have been backported to
95
            realname = Utils.uncanonstr(self.getMemberName(person), lang)
1541 by Mark Sapiro
Because of privacy concerns with the 2.2 backport adding real name to
96
            if realname and mm_cfg.ROSTER_DISPLAY_REALNAME:
1516 by Mark Sapiro
A number of changes from the unofficial 2.2 branch have been backported to
97
                showing += " (%s)" % Utils.websafe(realname)
1 by
This commit was manufactured by cvs2svn to create branch
98
            got = Link(url, showing)
99
            if self.getDeliveryStatus(person) <> MemberAdaptor.ENABLED:
100
                got = Italic('(', got, ')')
101
            items.append(got)
102
        # Just return the .Format() so this works until I finish
103
        # converting everything to htmlformat...
104
        return concealed + UnorderedList(*tuple(items)).Format()
105
106
    def FormatOptionButton(self, option, value, user):
107
        if option == mm_cfg.DisableDelivery:
108
            optval = self.getDeliveryStatus(user) <> MemberAdaptor.ENABLED
109
        else:
110
            optval = self.getMemberOption(user, option)
111
        if optval == value:
112
            checked = ' CHECKED'
113
        else:
114
            checked = ''
115
        name = {mm_cfg.DontReceiveOwnPosts      : 'dontreceive',
116
                mm_cfg.DisableDelivery          : 'disablemail',
117
                mm_cfg.DisableMime              : 'mime',
118
                mm_cfg.AcknowledgePosts         : 'ackposts',
119
                mm_cfg.Digests                  : 'digest',
120
                mm_cfg.ConcealSubscription      : 'conceal',
121
                mm_cfg.SuppressPasswordReminder : 'remind',
122
                mm_cfg.ReceiveNonmatchingTopics : 'rcvtopic',
123
                mm_cfg.DontReceiveDuplicates    : 'nodupes',
124
                }[option]
125
        return '<input type=radio name="%s" value="%d"%s>' % (
126
            name, value, checked)
127
128
    def FormatDigestButton(self):
129
        if self.digest_is_default:
130
            checked = ' CHECKED'
131
        else:
132
            checked = ''
133
        return '<input type=radio name="digest" value="1"%s>' % checked
134
135
    def FormatDisabledNotice(self, user):
136
        status = self.getDeliveryStatus(user)
137
        reason = None
138
        info = self.getBounceInfo(user)
139
        if status == MemberAdaptor.BYUSER:
140
            reason = _('; it was disabled by you')
141
        elif status == MemberAdaptor.BYADMIN:
142
            reason = _('; it was disabled by the list administrator')
143
        elif status == MemberAdaptor.BYBOUNCE:
144
            date = time.strftime('%d-%b-%Y',
145
                                 time.localtime(Utils.midnight(info.date)))
146
            reason = _('''; it was disabled due to excessive bounces.  The
147
            last bounce was received on %(date)s''')
148
        elif status == MemberAdaptor.UNKNOWN:
149
            reason = _('; it was disabled for unknown reasons')
150
        if reason:
151
            note = FontSize('+1', _(
152
                'Note: your list delivery is currently disabled%(reason)s.'
153
                )).Format()
154
            link = Link('#disable', _('Mail delivery')).Format()
155
            mailto = Link('mailto:' + self.GetOwnerEmail(),
156
                          _('the list administrator')).Format()
157
            return _('''<p>%(note)s
158
159
            <p>You may have disabled list delivery intentionally,
160
            or it may have been triggered by bounces from your email
161
            address.  In either case, to re-enable delivery, change the
162
            %(link)s option below.  Contact %(mailto)s if you have any
163
            questions or need assistance.''')
164
        elif info and info.score > 0:
165
            # Provide information about their current bounce score.  We know
166
            # their membership is currently enabled.
167
            score = info.score
168
            total = self.bounce_score_threshold
169
            return _('''<p>We have received some recent bounces from your
170
            address.  Your current <em>bounce score</em> is %(score)s out of a
171
            maximum of %(total)s.  Please double check that your subscribed
172
            address is correct and that there are no problems with delivery to
173
            this address.  Your bounce score will be automatically reset if
174
            the problems are corrected soon.''')
175
        else:
176
            return ''
177
178
    def FormatUmbrellaNotice(self, user, type):
179
        addr = self.GetMemberAdminEmail(user)
180
        if self.umbrella_list:
181
            return _("(Note - you are subscribing to a list of mailing lists, "
182
                     "so the %(type)s notice will be sent to the admin address"
183
                     " for your membership, %(addr)s.)<p>")
184
        else:
185
            return ""
186
187
    def FormatSubscriptionMsg(self):
188
        msg = ''
189
        also = ''
190
        if self.subscribe_policy == 1:
191
            msg += _('''You will be sent email requesting confirmation, to
192
            prevent others from gratuitously subscribing you.''')
193
        elif self.subscribe_policy == 2:
194
            msg += _("""This is a closed list, which means your subscription
195
            will be held for approval.  You will be notified of the list
196
            moderator's decision by email.""")
197
            also = _('also ')
198
        elif self.subscribe_policy == 3:
199
            msg += _("""You will be sent email requesting confirmation, to
200
            prevent others from gratuitously subscribing you.  Once
201
            confirmation is received, your request will be held for approval
202
            by the list moderator.  You will be notified of the moderator's
203
            decision by email.""")
204
            also = _("also ")
205
        if msg:
206
            msg += ' '
207
        if self.private_roster == 1:
208
            msg += _('''This is %(also)sa private list, which means that the
209
            list of members is not available to non-members.''')
210
        elif self.private_roster:
211
            msg += _('''This is %(also)sa hidden list, which means that the
212
            list of members is available only to the list administrator.''')
213
        else:
214
            msg += _('''This is %(also)sa public list, which means that the
215
            list of members list is available to everyone.''')
216
            if self.obscure_addresses:
217
                msg += _(''' (but we obscure the addresses so they are not
218
                easily recognizable by spammers).''')
219
220
        if self.umbrella_list:
221
            sfx = self.umbrella_member_suffix
222
            msg += _("""<p>(Note that this is an umbrella list, intended to
223
            have only other mailing lists as members.  Among other things,
224
            this means that your confirmation request will be sent to the
225
            `%(sfx)s' account for your address.)""")
226
        return msg
227
228
    def FormatUndigestButton(self):
229
        if self.digest_is_default:
230
            checked = ''
231
        else:
232
            checked = ' CHECKED'
233
        return '<input type=radio name="digest" value="0"%s>' % checked
234
235
    def FormatMimeDigestsButton(self):
236
        if self.mime_is_default_digest:
237
            checked = ' CHECKED'
238
        else:
239
            checked = ''
240
        return '<input type=radio name="mime" value="1"%s>' % checked
241
242
    def FormatPlainDigestsButton(self):
243
        if self.mime_is_default_digest:
244
            checked = ''
245
        else:
246
            checked = ' CHECKED'
247
        return '<input type=radio name="plain" value="1"%s>' % checked
248
249
    def FormatEditingOption(self, lang):
250
        if self.private_roster == 0:
251
            either = _('<b><i>either</i></b> ')
252
        else:
253
            either = ''
254
        realname = self.real_name
255
256
        text = (_('''To unsubscribe from %(realname)s, get a password reminder,
257
        or change your subscription options %(either)senter your subscription
258
        email address:
259
        <p><center> ''')
260
                + TextBox('email', size=30).Format()
261
                + '  '
262
                + SubmitButton('UserOptions',
263
                               _('Unsubscribe or edit options')).Format()
264
                + Hidden('language', lang).Format()
265
                + '</center>')
266
        if self.private_roster == 0:
267
            text += _('''<p>... <b><i>or</i></b> select your entry from
268
                      the subscribers list (see above).''')
269
        text += _(''' If you leave the field blank, you will be prompted for
270
        your email address''')
271
        return text
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
272
1 by
This commit was manufactured by cvs2svn to create branch
273
    def RestrictedListMessage(self, which, restriction):
274
        if not restriction:
275
            return ''
276
        elif restriction == 1:
277
            return _(
278
                '''(<i>%(which)s is only available to the list
279
                members.</i>)''')
280
        else:
281
            return _('''(<i>%(which)s is only available to the list
282
            administrator.</i>)''')
283
284
    def FormatRosterOptionForUser(self, lang):
285
        return self.RosterOption(lang).Format()
286
287
    def RosterOption(self, lang):
288
        container = Container()
289
        container.AddItem(Hidden('language', lang))
290
        if not self.private_roster:
291
            container.AddItem(_("Click here for the list of ")
292
                              + self.real_name
293
                              + _(" subscribers: "))
294
            container.AddItem(SubmitButton('SubscriberRoster',
295
                                           _("Visit Subscriber list")))
296
        else:
297
            if self.private_roster == 1:
298
                only = _('members')
299
                whom = _('Address:')
300
            else:
301
                only = _('the list administrator')
302
                whom = _('Admin address:')
303
            # Solicit the user and password.
304
            container.AddItem(
305
                self.RestrictedListMessage(_('The subscribers list'),
306
                                           self.private_roster)
307
                              + _(" <p>Enter your ")
308
                              + whom[:-1].lower()
309
                              + _(" and password to visit"
310
                              "  the subscribers list: <p><center> ")
311
                              + whom
312
                              + " ")
313
            container.AddItem(self.FormatBox('roster-email'))
314
            container.AddItem(_("Password: ")
315
                              + self.FormatSecureBox('roster-pw')
316
                              + "&nbsp;&nbsp;")
317
            container.AddItem(SubmitButton('SubscriberRoster',
318
                                           _('Visit Subscriber List')))
319
            container.AddItem("</center>")
320
        return container
321
1668 by Mark Sapiro
Fixes for CVE-2016-6893 and more.
322
    def FormatFormStart(self, name, extra='',
323
                        mlist=None, contexts=None, user=None):
1 by
This commit was manufactured by cvs2svn to create branch
324
        base_url = self.GetScriptURL(name)
325
        if extra:
326
            full_url = "%s/%s" % (base_url, extra)
327
        else:
328
            full_url = base_url
1668 by Mark Sapiro
Fixes for CVE-2016-6893 and more.
329
        if mlist:
330
            return ("""<form method="POST" action="%s">
331
<input type="hidden" name="csrf_token" value="%s">""" 
332
                % (full_url, csrf_token(mlist, contexts, user)))
1 by
This commit was manufactured by cvs2svn to create branch
333
        return ('<FORM Method=POST ACTION="%s">' % full_url)
334
335
    def FormatArchiveAnchor(self):
336
        return '<a href="%s">' % self.GetBaseArchiveURL()
337
338
    def FormatFormEnd(self):
339
        return '</FORM>'
340
341
    def FormatBox(self, name, size=20, value=''):
930 by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by
342
        if isinstance(value, str):
343
            safevalue = Utils.websafe(value)
344
        else:
345
            safevalue = value
1 by
This commit was manufactured by cvs2svn to create branch
346
        return '<INPUT type="Text" name="%s" size="%d" value="%s">' % (
930 by bwarsaw
CVE-2006-3636. Fixes for various cross-site scripting issues. Discovery by
347
            name, size, safevalue)
1 by
This commit was manufactured by cvs2svn to create branch
348
349
    def FormatSecureBox(self, name):
350
        return '<INPUT type="Password" name="%s" size="15">' % name
351
352
    def FormatButton(self, name, text='Submit'):
353
        return '<INPUT type="Submit" name="%s" value="%s">' % (name, text)
354
355
    def FormatReminder(self, lang):
356
        if self.send_reminders:
357
            return _('Once a month, your password will be emailed to you as'
358
                     ' a reminder.')
359
        return ''
360
361
    def ParseTags(self, template, replacements, lang=None):
362
        if lang is None:
363
            charset = 'us-ascii'
364
        else:
365
            charset = Utils.GetCharSet(lang)
366
        text = Utils.maketext(template, raw=1, lang=lang, mlist=self)
367
        parts = re.split('(</?[Mm][Mm]-[^>]*>)', text)
368
        i = 1
369
        while i < len(parts):
370
            tag = parts[i].lower()
371
            if replacements.has_key(tag):
372
                repl = replacements[tag]
373
                if isinstance(repl, type(u'')):
374
                    repl = repl.encode(charset, 'replace')
375
                parts[i] = repl
376
            else:
377
                parts[i] = ''
378
            i = i + 2
379
        return EMPTYSTRING.join(parts)
380
381
    # This needs to wait until after the list is inited, so let's build it
382
    # when it's needed only.
383
    def GetStandardReplacements(self, lang=None):
384
        dmember_len = len(self.getDigestMemberKeys())
385
        member_len = len(self.getRegularMemberKeys())
386
        # If only one language is enabled for this mailing list, omit the
387
        # language choice buttons.
388
        if len(self.GetAvailableLanguages()) == 1:
389
            listlangs = _(Utils.GetLanguageDescr(self.preferred_language))
390
        else:
391
            listlangs = self.GetLangSelectBox(lang).Format()
1781.1.2 by Yasuhito FUTATSUKI at POEM
* apply Utils.websafe() to description string in admin.py
392
        if lang:
393
            cset = Utils.GetCharSet(lang) or 'us-ascii'
394
        else:
395
            cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii'
139 by bwarsaw
FormatUsers(): The user portion of the link should honor the setting
396
        d = {
1 by
This commit was manufactured by cvs2svn to create branch
397
            '<mm-mailman-footer>' : self.GetMailmanFooter(),
398
            '<mm-list-name>' : self.real_name,
399
            '<mm-email-user>' : self._internal_name,
1781.1.2 by Yasuhito FUTATSUKI at POEM
* apply Utils.websafe() to description string in admin.py
400
            '<mm-list-description>' :
401
                Utils.websafe(self.GetDescription(cset)),
1262 by Mark Sapiro
Two potential XSS vulnerabilities have been identified and fixed.
402
            '<mm-list-info>' : 
403
                '<!---->' + BR.join(self.info.split(NL)) + '<!---->',
1 by
This commit was manufactured by cvs2svn to create branch
404
            '<mm-form-end>'  : self.FormatFormEnd(),
405
            '<mm-archive>'   : self.FormatArchiveAnchor(),
406
            '</mm-archive>'  : '</a>',
407
            '<mm-list-subscription-msg>' : self.FormatSubscriptionMsg(),
408
            '<mm-restricted-list-message>' : \
409
                self.RestrictedListMessage(_('The current archive'),
410
                                           self.archive_private),
411
            '<mm-num-reg-users>' : `member_len`,
412
            '<mm-num-digesters>' : `dmember_len`,
413
            '<mm-num-members>' : (`member_len + dmember_len`),
414
            '<mm-posting-addr>' : '%s' % self.GetListEmail(),
415
            '<mm-request-addr>' : '%s' % self.GetRequestEmail(),
416
            '<mm-owner>' : self.GetOwnerEmail(),
417
            '<mm-reminder>' : self.FormatReminder(self.preferred_language),
418
            '<mm-host>' : self.host_name,
419
            '<mm-list-langs>' : listlangs,
420
            }
421
        if mm_cfg.IMAGE_LOGOS:
422
            d['<mm-favicon>'] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON
423
        return d
424
972 by msapiro
- CGI/admin.py
425
    def GetAllReplacements(self, lang=None, list_hidden=False):
1 by
This commit was manufactured by cvs2svn to create branch
426
        """
427
        returns standard replaces plus formatted user lists in
428
        a dict just like GetStandardReplacements.
429
        """
430
        if lang is None:
431
            lang = self.preferred_language
432
        d = self.GetStandardReplacements(lang)
972 by msapiro
- CGI/admin.py
433
        d.update({"<mm-regular-users>": self.FormatUsers(0, lang, list_hidden),
434
                  "<mm-digest-users>": self.FormatUsers(1, lang, list_hidden)})
1 by
This commit was manufactured by cvs2svn to create branch
435
        return d
436
437
    def GetLangSelectBox(self, lang=None, varname='language'):
438
        if lang is None:
439
            lang = self.preferred_language
440
        # Figure out the available languages
441
        values = self.GetAvailableLanguages()
442
        legend = map(_, map(Utils.GetLanguageDescr, values))
443
        try:
444
            selected = values.index(lang)
445
        except ValueError:
446
            try:
447
                selected = values.index(self.preferred_language)
448
            except ValueError:
449
                selected = mm_cfg.DEFAULT_SERVER_LANGUAGE
450
        # Return the widget
451
        return SelectOptions(varname, values, legend, selected)