Merge lp:~mbp/subunit/505078-chunked into lp:~subunit/subunit/trunk

Proposed by Martin Pool
Status: Merged
Merged at revision: 139
Proposed branch: lp:~mbp/subunit/505078-chunked
Merge into: lp:~subunit/subunit/trunk
Diff against target: 94 lines (+39/-2)
2 files modified
python/subunit/chunked.py (+15/-2)
python/subunit/tests/test_chunked.py (+24/-0)
To merge this branch: bzr merge lp:~mbp/subunit/505078-chunked
Reviewer Review Type Date Requested Status
Jonathan Lange Approve
Martin Pool Pending
Review via email: mp+47754@code.launchpad.net

This proposal supersedes a proposal from 2011-01-10.

Description of the change

This makes subunit not barf if a \r is missing from the input. You can choose strict or non-strict parsing.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote : Posted in a previous version of this proposal

Robert said offline: he would like a 'strict' mode flag, defaulting to on, in the chunked parser that controls whether the parser knowingly accepts noncompliant data.

The presence of this flag ought not to imply that it strictly validates the input. There are many things that are incorrect that it will accept at the moment. But it could go towards that in future.

review: Needs Fixing
Revision history for this message
Jonathan Lange (jml) wrote :

Thanks Martin.

I don't like raising ValueError, but that's essentially what happens now anyway, so it's fine by me.

I've merged a version of your patch that changes 'cr' to 'CR' in comments (sometimes a man must take a stand) and that updates NEWS.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'python/subunit/chunked.py'
--- python/subunit/chunked.py 2009-10-13 04:52:06 +0000
+++ python/subunit/chunked.py 2011-01-28 01:08:53 +0000
@@ -1,6 +1,7 @@
1#1#
2# subunit: extensions to python unittest to get test results from subprocesses.2# subunit: extensions to python unittest to get test results from subprocesses.
3# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>3# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4# Copyright (C) 2011 Martin Pool <mbp@sourcefrog.net>
4#5#
5# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause6# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6# license at the users choice. A copy of both licenses are available in the7# license at the users choice. A copy of both licenses are available in the
@@ -19,7 +20,7 @@
19class Decoder(object):20class Decoder(object):
20 """Decode chunked content to a byte stream."""21 """Decode chunked content to a byte stream."""
2122
22 def __init__(self, output):23 def __init__(self, output, strict=True):
23 """Create a decoder decoding to output.24 """Create a decoder decoding to output.
2425
25 :param output: A file-like object. Bytes written to the Decoder are26 :param output: A file-like object. Bytes written to the Decoder are
@@ -29,11 +30,18 @@
29 when no more data is available, to detect short streams; the30 when no more data is available, to detect short streams; the
30 write method will return none-None when the end of a stream is31 write method will return none-None when the end of a stream is
31 detected.32 detected.
33
34 :param strict: If True (the default), the decoder will not knowingly
35 accept input that is not conformant to the HTTP specification.
36 (This does not imply that it will catch every nonconformance.)
37 If False, it will accept incorrect input that is still
38 unambiguous.
32 """39 """
33 self.output = output40 self.output = output
34 self.buffered_bytes = []41 self.buffered_bytes = []
35 self.state = self._read_length42 self.state = self._read_length
36 self.body_length = 043 self.body_length = 0
44 self.strict = strict
3745
38 def close(self):46 def close(self):
39 """Close the decoder.47 """Close the decoder.
@@ -87,7 +95,12 @@
87 if count_chars[-1][-1] != '\n':95 if count_chars[-1][-1] != '\n':
88 return96 return
89 count_str = ''.join(count_chars)97 count_str = ''.join(count_chars)
90 self.body_length = int(count_str[:-2], 16)98 if self.strict:
99 if count_str[-2:] != '\r\n':
100 raise ValueError("chunk header invalid: %r" % count_str)
101 if '\r' in count_str[:-2]:
102 raise ValueError("too many crs in chunk header %r" % count_str)
103 self.body_length = int(count_str.rstrip('\n\r'), 16)
91 excess_bytes = len(count_str)104 excess_bytes = len(count_str)
92 while excess_bytes:105 while excess_bytes:
93 if excess_bytes >= len(self.buffered_bytes[0]):106 if excess_bytes >= len(self.buffered_bytes[0]):
94107
=== modified file 'python/subunit/tests/test_chunked.py'
--- python/subunit/tests/test_chunked.py 2009-10-13 04:52:06 +0000
+++ python/subunit/tests/test_chunked.py 2011-01-28 01:08:53 +0000
@@ -1,6 +1,7 @@
1#1#
2# subunit: extensions to python unittest to get test results from subprocesses.2# subunit: extensions to python unittest to get test results from subprocesses.
3# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>3# Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
4# Copyright (C) 2011 Martin Pool <mbp@sourcefrog.net>
4#5#
5# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause6# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6# license at the users choice. A copy of both licenses are available in the7# license at the users choice. A copy of both licenses are available in the
@@ -86,6 +87,29 @@
86 self.assertEqual('', self.decoder.write('0\r\n'))87 self.assertEqual('', self.decoder.write('0\r\n'))
87 self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue())88 self.assertEqual('1' * 65536 + '2' * 65536, self.output.getvalue())
8889
90 def test_decode_newline_nonstrict(self):
91 """Tolerate chunk markers with no cr character."""
92 # From <http://pad.lv/505078>
93 self.decoder = subunit.chunked.Decoder(self.output, strict=False)
94 self.assertEqual(None, self.decoder.write('a\n'))
95 self.assertEqual(None, self.decoder.write('abcdeabcde'))
96 self.assertEqual('', self.decoder.write('0\n'))
97 self.assertEqual('abcdeabcde', self.output.getvalue())
98
99 def test_decode_strict_newline_only(self):
100 """Reject chunk markers with no cr character in strict mode."""
101 # From <http://pad.lv/505078>
102 self.assertRaises(ValueError,
103 self.decoder.write, 'a\n')
104
105 def test_decode_strict_multiple_crs(self):
106 self.assertRaises(ValueError,
107 self.decoder.write, 'a\r\r\n')
108
109 def test_decode_short_header(self):
110 self.assertRaises(ValueError,
111 self.decoder.write, '\n')
112
89113
90class TestEncode(unittest.TestCase):114class TestEncode(unittest.TestCase):
91115

Subscribers

People subscribed via source and target branches