@@ -241,6 +241,120 @@ def test_malformed_headers_coped_with(self):
241241 self .assertEqual (resp .getheader ('First' ), 'val' )
242242 self .assertEqual (resp .getheader ('Second' ), 'val' )
243243
244+ def test_malformed_truncation (self ):
245+ # Other malformed header lines, especially without colons, used to
246+ # cause the rest of the header section to be truncated
247+ resp = (
248+ b'HTTP/1.1 200 OK\r \n '
249+ b'Public-Key-Pins: \n '
250+ b'pin-sha256="xxx=";\n '
251+ b'report-uri="https://..."\r \n '
252+ b'Transfer-Encoding: chunked\r \n '
253+ b'\r \n '
254+ b'4\r \n body\r \n 0\r \n \r \n '
255+ )
256+ resp = httplib .HTTPResponse (FakeSocket (resp ))
257+ resp .begin ()
258+ self .assertIsNotNone (resp .getheader ('Public-Key-Pins' ))
259+ self .assertEqual (resp .getheader ('Transfer-Encoding' ), 'chunked' )
260+ self .assertEqual (resp .read (), b'body' )
261+
262+ def test_blank_line_forms (self ):
263+ # Test that both CRLF and LF blank lines can terminate the header
264+ # section and start the body
265+ for blank in (b'\r \n ' , b'\n ' ):
266+ resp = b'HTTP/1.1 200 OK\r \n ' b'Transfer-Encoding: chunked\r \n '
267+ resp += blank
268+ resp += b'4\r \n body\r \n 0\r \n \r \n '
269+ resp = httplib .HTTPResponse (FakeSocket (resp ))
270+ resp .begin ()
271+ self .assertEqual (resp .getheader ('Transfer-Encoding' ), 'chunked' )
272+ self .assertEqual (resp .read (), b'body' )
273+
274+ resp = b'HTTP/1.0 200 OK\r \n ' + blank + b'body'
275+ resp = httplib .HTTPResponse (FakeSocket (resp ))
276+ resp .begin ()
277+ self .assertEqual (resp .read (), b'body' )
278+
279+ # A blank line ending in CR is not treated as the end of the HTTP
280+ # header section, therefore header fields following it should be
281+ # parsed if possible
282+ resp = (
283+ b'HTTP/1.1 200 OK\r \n '
284+ b'\r '
285+ b'Name: value\r \n '
286+ b'Transfer-Encoding: chunked\r \n '
287+ b'\r \n '
288+ b'4\r \n body\r \n 0\r \n \r \n '
289+ )
290+ resp = httplib .HTTPResponse (FakeSocket (resp ))
291+ resp .begin ()
292+ self .assertEqual (resp .getheader ('Transfer-Encoding' ), 'chunked' )
293+ self .assertEqual (resp .read (), b'body' )
294+
295+ # No header fields nor blank line
296+ resp = b'HTTP/1.0 200 OK\r \n '
297+ resp = httplib .HTTPResponse (FakeSocket (resp ))
298+ resp .begin ()
299+ self .assertEqual (resp .read (), b'' )
300+
301+ def test_from_line (self ):
302+ # The parser handles "From" lines specially, so test this does not
303+ # affect parsing the rest of the header section
304+ resp = (
305+ b'HTTP/1.1 200 OK\r \n '
306+ b'From start\r \n '
307+ b' continued\r \n '
308+ b'Name: value\r \n '
309+ b'From middle\r \n '
310+ b' continued\r \n '
311+ b'Transfer-Encoding: chunked\r \n '
312+ b'From end\r \n '
313+ b'\r \n '
314+ b'4\r \n body\r \n 0\r \n \r \n '
315+ )
316+ resp = httplib .HTTPResponse (FakeSocket (resp ))
317+ resp .begin ()
318+ self .assertIsNotNone (resp .getheader ('Name' ))
319+ self .assertEqual (resp .getheader ('Transfer-Encoding' ), 'chunked' )
320+ self .assertEqual (resp .read (), b'body' )
321+
322+ resp = (
323+ b'HTTP/1.0 200 OK\r \n '
324+ b'From alone\r \n '
325+ b'\r \n '
326+ b'body'
327+ )
328+ resp = httplib .HTTPResponse (FakeSocket (resp ))
329+ resp .begin ()
330+ self .assertEqual (resp .read (), b'body' )
331+
332+ def test_parse_all_octets (self ):
333+ # Ensure no valid header field octet breaks the parser
334+ body = (
335+ b'HTTP/1.1 200 OK\r \n '
336+ b"!#$%&'*+-.^_`|~: value\r \n " # Special token characters
337+ b'VCHAR: ' + bytearray (range (0x21 , 0x7E + 1 )) + b'\r \n '
338+ b'obs-text: ' + bytearray (range (0x80 , 0xFF + 1 )) + b'\r \n '
339+ b'obs-fold: text\r \n '
340+ b' folded with space\r \n '
341+ b'\t folded with tab\r \n '
342+ b'Content-Length: 0\r \n '
343+ b'\r \n '
344+ )
345+ sock = FakeSocket (body )
346+ resp = httplib .HTTPResponse (sock )
347+ resp .begin ()
348+ self .assertEqual (resp .getheader ('Content-Length' ), '0' )
349+ self .assertEqual (resp .getheader ("!#$%&'*+-.^_`|~" ), 'value' )
350+ vchar = '' .join (map (chr , range (0x21 , 0x7E + 1 )))
351+ self .assertEqual (resp .getheader ('VCHAR' ), vchar )
352+ self .assertIsNotNone (resp .getheader ('obs-text' ))
353+ folded = resp .getheader ('obs-fold' )
354+ self .assertTrue (folded .startswith ('text' ))
355+ self .assertIn (' folded with space' , folded )
356+ self .assertTrue (folded .endswith ('folded with tab' ))
357+
244358 def test_invalid_headers (self ):
245359 conn = httplib .HTTPConnection ('example.com' )
246360 conn .sock = FakeSocket ('' )
@@ -525,7 +639,7 @@ def test_filenoattr(self):
525639 self .assertTrue (hasattr (resp ,'fileno' ),
526640 'HTTPResponse should expose a fileno attribute' )
527641
528- # Test lines overflowing the max line size (_MAXLINE in http.client )
642+ # Test lines overflowing the max line size (_MAXLINE in httplib )
529643
530644 def test_overflowing_status_line (self ):
531645 self .skipTest ("disabled for HTTP 0.9 support" )
@@ -624,7 +738,7 @@ def testHTTPConnectionSourceAddress(self):
624738 def testHTTPSConnectionSourceAddress (self ):
625739 self .conn = httplib .HTTPSConnection (HOST , self .port ,
626740 source_address = ('' , self .source_port ))
627- # We don't test anything here other the constructor not barfing as
741+ # We don't test anything here other than the constructor not barfing as
628742 # this code doesn't deal with setting up an active running SSL server
629743 # for an ssl_wrapped connect() to actually return from.
630744
0 commit comments