Skip to content

Commit b06d3fe

Browse files
committed
Merge branch issue2551008 into default.
2 parents a568e70 + 216f109 commit b06d3fe

File tree

4 files changed

+94
-37
lines changed

4 files changed

+94
-37
lines changed

CHANGES.txt

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ Fixed:
150150
file: config_ini.ini for required config settings that are merged
151151
into the default values producing an up to date config.ini on
152152
install.
153+
- issue2551008: fix incorrect encoding handling in mailgw.py
154+
(Ezio Melotti, John Rouillard)
153155

154156
2018-07-13 1.6.0
155157

@@ -380,9 +382,9 @@ Features:
380382

381383
Fixed:
382384

383-
- issue1615201: Optionally restore the original (version 0.6) mailgw
384-
behaviour of ignoring a Resent-From:-header and using the real
385-
From-header instead: new configuration option EMAIL_KEEP_REAL_FROM
385+
- issue1615201: Optionally restore the original (version 0.6) mailgw
386+
behaviour of ignoring a Resent-From:-header and using the real
387+
From-header instead: new configuration option EMAIL_KEEP_REAL_FROM
386388
(Peter Funk aka Pefu).
387389
- issue2550717: Changed a couple of residual email references into
388390
E-Mail in German translation (John Rouillard)
@@ -395,7 +397,7 @@ Fixed:
395397
multilink values (in classic template). Applied patch so it
396398
now errors the same way as an update does. (applied by John Rouillard)
397399
- issue2550757: one bug raised by issue fixed. Patch created by
398-
W. Trevor King (wking) for documentation of mailgw applied by
400+
W. Trevor King (wking) for documentation of mailgw applied by
399401
John Rouillard.
400402
- Fix processing of additional arguments to cgi method 'menu': This
401403
would not work if more than one additional argument is used.
@@ -643,8 +645,8 @@ Fixed:
643645
may see removed properties. By default only role Admin is allowed to
644646
see these.
645647
- Fix issue2550955: Roundup commits although a Reject exception is raised
646-
Fix the problem that changes are committed to the database (due to
647-
commits to otk handling) even when a Reject exception occurs. The fix
648+
Fix the problem that changes are committed to the database (due to
649+
commits to otk handling) even when a Reject exception occurs. The fix
648650
implements separate database connections for otk/session handling and
649651
normal database operation.
650652
- Allow empty content property for file and message via xmlrpc
@@ -717,11 +719,11 @@ Features:
717719

718720
Fixed:
719721

720-
- issue2550869 Duplicate mail headers (Reply-To, Message-ID, In-Reply-To)
722+
- issue2550869 Duplicate mail headers (Reply-To, Message-ID, In-Reply-To)
721723
when sending out email. Reported with first fix by Mathias Behrle.
722724
(Bernhard Reiter)
723-
- issue2550830 An empty LinkHTMLProperty cannot be compared successfully.
724-
Improves the query editing page. Reported and fixed by R David Murray
725+
- issue2550830 An empty LinkHTMLProperty cannot be compared successfully.
726+
Improves the query editing page. Reported and fixed by R David Murray
725727
(Bernhard Reiter).
726728
- Fix Release-date of 1.5.0 in this file (thanks to Bernhard for
727729
discovery) (Ralf Schlatterbeck)
@@ -815,18 +817,18 @@ Fixed:
815817
always work. (Ralf Schlatterbeck)
816818
- issue2550881 demo.py: Add pointer how to access demo from remote host.
817819
Suggested by Karl-Philipp Richter. (Bernhard Reiter)
818-
- issue2550884 roundup-mailgw --help text improved to explain the allowed
820+
- issue2550884 roundup-mailgw --help text improved to explain the allowed
819821
parameters better. Suggested by by Karl-Philipp Richter. (Bernhard Reiter)
820822
- Fix form-parsing: If multiple new items are added to a multilink
821823
property, the old version would create the new items but only link
822824
one. (Ralf Schlatterbeck)
823-
- issue2550892 (translation error of priority in locale de) Thanks
825+
- issue2550892 (translation error of priority in locale de) Thanks
824826
Martin Thomas Swaton for reporting. (Bernhard Reiter)
825827
- Help-Window now gets focus, this prevents the case that help doesn't
826828
work because an old help-window is below the main window.
827829
(Ralf Schlatterbeck)
828-
- issue2550811 20% fix: jinja2 template engine now has an example
829-
how to use non-ascii unicode contents with a custom filter ('| u').
830+
- issue2550811 20% fix: jinja2 template engine now has an example
831+
how to use non-ascii unicode contents with a custom filter ('| u').
830832
See updates on http://www.roundup-tracker.org/cgi-bin/moin.cgi/Jinja2
831833
(Bernhard Reiter)
832834

@@ -867,7 +869,7 @@ Features:
867869
- Experimental support for Jinja2, try 'jinja2' for template_engine
868870
in config (anatoly techtonik)
869871
- A new jinja2 template based on Classic schema and using Twitter
870-
bootstrap for responsive behaviour. Run as -
872+
bootstrap for responsive behaviour. Run as -
871873
python demo.py -t jinja2 nuke (Pradip P Caulagi)
872874
- roundup_admin.py and other scripts can now be run directly from the
873875
sources dir as roundup\scripts\roundup_admin.py (anatoly techtonik)
@@ -887,7 +889,7 @@ Fixed:
887889

888890
- issue2550789: add documentation on how to initialise a tracker
889891
without exposing the admin password.
890-
- issue2550805: Postgres should search title attribute case insensitive
892+
- issue2550805: Postgres should search title attribute case insensitive
891893
like sqlite. Reported and fixed by Tom Ekberg. (Bernhard Reiter)
892894
- Removed some old left over "rlog" references in documentation and code.
893895
Makes the debugging.txt advise for the database unit tests work again.
@@ -897,7 +899,7 @@ Fixed:
897899
- Make roundup play nice with setup tools (for using with virtualenv)
898900
(Pradip Caulagi)
899901
- [minor] Template responsive: make demo.py work out of the box with it,
900-
by setting the static_files config.ini setting to "static".
902+
by setting the static_files config.ini setting to "static".
901903
Footer: link fixed and hardcoded last modified date removed. (Bernhard Reiter)
902904
- demo.py print location of tracker home and fully erase its directory
903905
when nuking (anatoly techtonik)
@@ -963,7 +965,7 @@ Fixed:
963965
least python 2.5, thanks to John P. Rouillard for discovering this.
964966
(committed by Ralf Schlatterbeck)
965967
- Fix version_check.py to require at least python 2.5 (anatoly techtonik)
966-
- Fixing the download button re-activating the cheeseshop plugin in the
968+
- Fixing the download button re-activating the cheeseshop plugin in the
967969
sphinx config. Thanks to Richard for the hint. (Bernhard Reiter)
968970
- issue2550783 devel template's schema.py permissions referenced the
969971
organization property for the user, but the property is called

roundup/anypy/email_.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import binascii
33
import email
44
from email import quoprimime, base64mime
5+
from email import charset as _charset
56

67
if str == bytes:
78
message_from_bytes = email.message_from_string

roundup/mailgw.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,19 @@ class RoundupMessage(email.message.Message):
202202
def _decode_header(self, hdr):
203203
parts = []
204204
for part, encoding in decode_header(hdr):
205-
if encoding:
206-
part = part.decode(encoding)
205+
# decode_header might return either bytes or unicode,
206+
# see https://bugs.python.org/issue21492
207+
# If part is bytes, try to decode it with the specified
208+
# encoding if it's provided, otherwise try utf-8 and
209+
# fallback on iso-8859-1 if that fails.
210+
if isinstance(part, bytes):
211+
if encoding:
212+
part = part.decode(encoding)
213+
else:
214+
try:
215+
part = part.decode('utf-8')
216+
except UnicodeDecodeError:
217+
part = part.decode('iso-8859-1')
207218
# RFC 2047 specifies that between encoded parts spaces are
208219
# swallowed while at the borders from encoded to non-encoded
209220
# or vice-versa we must preserve a space. Multiple adjacent
@@ -530,7 +541,7 @@ def check_subject(self):
530541
def parse_subject(self):
531542
''' Matches subjects like:
532543
Re: "[issue1234] title of issue [status=resolved]"
533-
544+
534545
Each part of the subject is matched, stored, then removed from the
535546
start of the subject string as needed. The stored values are then
536547
returned
@@ -603,11 +614,11 @@ def parse_subject(self):
603614
if self.matches['quote'] and not self.matches['argswhole'] \
604615
and self.matches['title'].endswith('"'):
605616
self.matches['title'] = self.matches['title'][:-1]
606-
617+
607618
def rego_confirm(self):
608619
''' Check for registration OTK and confirm the registration if found
609620
'''
610-
621+
611622
if self.config['EMAIL_REGISTRATION_CONFIRMATION']:
612623
otk_re = re.compile('-- key (?P<otk>[a-zA-Z0-9]{32})')
613624
otk = otk_re.search(self.matches['title'] or '')
@@ -693,14 +704,14 @@ def get_classname(self):
693704
""") % locals())
694705
# get the class properties
695706
self.properties = self.cl.getprops()
696-
707+
697708

698709
def get_nodeid(self):
699710
''' Determine the nodeid from the message and return it if found
700711
'''
701712
title = self.matches['title']
702713
subject = self.subject
703-
714+
704715
if self.pfxmode == 'none':
705716
nodeid = None
706717
else:
@@ -873,10 +884,10 @@ def get_props(self):
873884
''' Generate all the props for the new/updated node and return them
874885
'''
875886
subject = self.subject
876-
887+
877888
# get the commandline arguments for issues
878889
issue_props = self.mailgw.get_class_arguments('issue', self.classname)
879-
890+
880891
#
881892
# handle the subject argument list
882893
#
@@ -885,11 +896,11 @@ def get_props(self):
885896
args = self.matches['args']
886897
argswhole = self.matches['argswhole']
887898
title = self.matches['title']
888-
889-
# Reform the title
899+
900+
# Reform the title
890901
if self.matches['nodeid'] and self.nodeid is None:
891902
title = subject
892-
903+
893904
if args:
894905
if self.sfxmode == 'none':
895906
title += ' ' + argswhole
@@ -986,7 +997,7 @@ def pgp_role():
986997
# something failed with the message decryption/sig
987998
# chain. Pass the error up.
988999
raise
989-
# store the decrypted message
1000+
# store the decrypted message
9901001
self.message = message
9911002
elif pgp_role():
9921003
raise MailUsageError(_("""
@@ -1052,7 +1063,7 @@ def create_msg(self):
10521063
return
10531064
msg_props = self.mailgw.get_class_arguments('msg')
10541065
self.msg_props.update (msg_props)
1055-
1066+
10561067
# Get the message ids
10571068
inreplyto = self.message.get_header('in-reply-to') or ''
10581069
messageid = self.message.get_header('message-id')
@@ -1061,7 +1072,7 @@ def create_msg(self):
10611072
messageid = "<%s.%s.%s%s@%s>"%(time.time(),
10621073
b2s(base64.b32encode(random_.token_bytes(10))),
10631074
self.classname, self.nodeid, self.config['MAIL_DOMAIN'])
1064-
1075+
10651076
if self.content is None:
10661077
raise MailUsageError(_("""
10671078
Roundup requires the submission to be plain text. The message parser could
@@ -1103,7 +1114,7 @@ def create_msg(self):
11031114
self.props['messages'] = [message_id]
11041115

11051116
def create_node(self):
1106-
''' Create/update a node using self.props
1117+
''' Create/update a node using self.props
11071118
'''
11081119
classname = self.classname
11091120
try:
@@ -1583,7 +1594,7 @@ def get_class_arguments(self, class_type, classname=None):
15831594

15841595
classname = classname or class_type
15851596
cls_lookup = { 'issue' : classname }
1586-
1597+
15871598
# Allow other issue-type classes -- take the real classname from
15881599
# previous parsing-steps of the message:
15891600
clsname = cls_lookup.get (class_type, class_type)
@@ -1598,21 +1609,21 @@ def get_class_arguments(self, class_type, classname=None):
15981609
%(mailadmin)s and have them fix the incorrect class specified as:
15991610
%(clsname)s
16001611
""") % locals())
1601-
1612+
16021613
if self.arguments:
16031614
# The default type on the commandline is msg
16041615
if class_type == 'msg':
16051616
current_type = class_type
16061617
else:
16071618
current_type = None
1608-
1619+
16091620
# Handle the arguments specified by the email gateway command line.
16101621
# We do this by looping over the list of self.arguments looking for
16111622
# a -C to match the class we want, then use the -S setting string.
16121623
for option, propstring in self.arguments:
16131624
if option in ( '-C', '--class'):
16141625
current_type = propstring.strip()
1615-
1626+
16161627
if current_type != class_type:
16171628
current_type = None
16181629

test/test_mailgw_roundupmessage.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,34 @@ class HeaderRoundupMessageTests(TestCase):
7575
This is a test submission of a new issue.
7676
""")
7777

78+
# From line has a null/empty encoding spec
79+
# to trigger failure in mailgw.py:RoundupMessage::_decode_header
80+
bad_msg_utf8 = message_from_string("""
81+
Content-Type: text/plain;
82+
charset="iso-8859-1"
83+
From: =??b?SOKCrGxsbw=====?= <[email protected]>
84+
To: Issue Tracker <[email protected]>
85+
Cc: =?utf8?b?SOKCrGxsbw==?= <[email protected]>,
86+
Some User <[email protected]>
87+
Message-Id: <dummy_test_message_id>
88+
Subject: [issue] Testing...
89+
90+
This is a test submission of a new issue.
91+
""")
92+
93+
bad_msg_iso_8859_1 = message_from_string("""
94+
Content-Type: text/plain;
95+
charset="iso-8859-1"
96+
From: =??q?\x80SOKCrGxsbw=====?= <[email protected]>
97+
To: Issue Tracker <[email protected]>
98+
Cc: =?utf8?b?SOKCrGxsbw==?= <[email protected]>,
99+
Some User <[email protected]>
100+
Message-Id: <dummy_test_message_id>
101+
Subject: [issue] Testing...
102+
103+
This is a test submission of a new issue.
104+
""")
105+
78106
def test_get_plain_header(self):
79107
self.assertEqual(
80108
self.msg.get_header('to'),
@@ -85,6 +113,21 @@ def test_get_encoded_header(self):
85113
self.msg.get_header('from'),
86114
'H€llo <[email protected]>')
87115

116+
# issue2551008 null encoding causes crash.
117+
self.assertEqual(
118+
self.bad_msg_utf8.get_header('from'),
119+
'H€llo <[email protected]>')
120+
121+
# the decoded value is not what the user wanted,
122+
# but they should have created a valid header
123+
# if they wanted the right outcome...
124+
self.assertIn(
125+
self.bad_msg_iso_8859_1.get_header('from'),
126+
(
127+
'\xc2\x80SOKCrGxsbw===== <[email protected]>', # python 2
128+
'\x80SOKCrGxsbw===== <[email protected]>' # python 3
129+
))
130+
88131
def test_get_address_list(self):
89132
self.assertEqual(self.msg.get_address_list('cc'), [
90133
('H€llo', '[email protected]'),

0 commit comments

Comments
 (0)