Skip to content

Commit 9524275

Browse files
committed
Fix membership list form field lookups after FieldStorage migration.
Per-member admin fields embed URL-quoted addresses in their names; browsers and parse_qs may submit those keys encoded or decoded. Look up both forms so unsubscribe and other member options work reliably.
1 parent 0a19519 commit 9524275

1 file changed

Lines changed: 30 additions & 8 deletions

File tree

Mailman/Cgi/admin.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ def _field_upload_text(cgidata, fieldname):
8181
return cgidata.getfirst(fieldname, '') or ''
8282

8383

84+
def _member_field_keys(user, suffix):
85+
"""Return possible form keys for a per-member field.
86+
87+
Member list forms use multipart encoding with URL-quoted addresses in
88+
field names. Depending on the browser and encoder, submitted keys may
89+
still contain percent escapes or may already be decoded. Urlencoded
90+
posts are always decoded by parse_qs.
91+
"""
92+
return ('%s_%s' % (user, suffix),
93+
'%s_%s' % (urllib.parse.quote(user), suffix))
94+
95+
96+
def _member_field_in_cgidata(cgidata, user, suffix):
97+
return any(k in cgidata for k in _member_field_keys(user, suffix))
98+
99+
100+
def _member_field_getfirst(cgidata, user, suffix, default=None):
101+
for key in _member_field_keys(user, suffix):
102+
if key in cgidata:
103+
return cgidata.getfirst(key, default)
104+
return default
105+
106+
84107

85108
def main():
86109
# Try to find out which list is being administered
@@ -1763,8 +1786,7 @@ def clean_input(x):
17631786
errors = []
17641787
removes = []
17651788
for user in users:
1766-
quser = urllib.parse.quote(user)
1767-
if '%s_unsub' % quser in cgidata:
1789+
if _member_field_in_cgidata(cgidata, user, 'unsub'):
17681790
try:
17691791
_ = D_
17701792
whence=_('member mgt page')
@@ -1778,7 +1800,7 @@ def clean_input(x):
17781800
doc.addError(_(f'Ignoring changes to deleted member: {user}'),
17791801
tag=_('Warning: '))
17801802
continue
1781-
value = '%s_digest' % quser in cgidata
1803+
value = _member_field_in_cgidata(cgidata, user, 'digest')
17821804
try:
17831805
mlist.setMemberOption(user, mm_cfg.Digests, value)
17841806
except (Errors.AlreadyReceivingDigests,
@@ -1788,28 +1810,28 @@ def clean_input(x):
17881810
# BAW: Hmm...
17891811
pass
17901812

1791-
newname = cgidata.getfirst(quser+'_realname', '')
1813+
newname = _member_field_getfirst(cgidata, user, 'realname', '')
17921814
newname = Utils.canonstr(newname, mlist.preferred_language)
17931815
mlist.setMemberName(user, newname)
17941816

1795-
newlang = cgidata.getfirst(quser+'_language')
1817+
newlang = _member_field_getfirst(cgidata, user, 'language')
17961818
oldlang = mlist.getMemberLanguage(user)
17971819
if Utils.IsLanguage(newlang) and newlang != oldlang:
17981820
mlist.setMemberLanguage(user, newlang)
17991821

1800-
moderate = not not cgidata.getfirst(quser+'_mod')
1822+
moderate = not not _member_field_getfirst(cgidata, user, 'mod')
18011823
mlist.setMemberOption(user, mm_cfg.Moderate, moderate)
18021824

18031825
# Set the `nomail' flag, but only if the user isn't already
18041826
# disabled (otherwise we might change BYUSER into BYADMIN).
1805-
if '%s_nomail' % quser in cgidata:
1827+
if _member_field_in_cgidata(cgidata, user, 'nomail'):
18061828
if mlist.getDeliveryStatus(user) == MemberAdaptor.ENABLED:
18071829
mlist.setDeliveryStatus(user, MemberAdaptor.BYADMIN)
18081830
else:
18091831
mlist.setDeliveryStatus(user, MemberAdaptor.ENABLED)
18101832
for opt in ('hide', 'ack', 'notmetoo', 'nodupes', 'plain'):
18111833
opt_code = mm_cfg.OPTINFO[opt]
1812-
if '%s_%s' % (quser, opt) in cgidata:
1834+
if _member_field_in_cgidata(cgidata, user, opt):
18131835
mlist.setMemberOption(user, opt_code, 1)
18141836
else:
18151837
mlist.setMemberOption(user, opt_code, 0)

0 commit comments

Comments
 (0)