@@ -1893,6 +1893,149 @@ def test_invalid_closed(self):
18931893 self .assertTrue (conn .fakesock .closed , "Connection not closed" )
18941894
18951895
1896+ class FakeGzipResponse :
1897+ # Minimal stand-in for the http.client.HTTPResponse that a response
1898+ # processor sees: headers plus a readable, closable body.
1899+ def __init__ (self , data , headers , code = 200 ):
1900+ self ._file = io .BytesIO (data )
1901+ self .headers = headers
1902+ self .code = code
1903+ self .msg = "OK"
1904+ self .url = "http://example.com/"
1905+ self .closed = False
1906+
1907+ def read (self , amt = - 1 ):
1908+ return self ._file .read (amt )
1909+
1910+ def close (self ):
1911+ self .closed = True
1912+ self ._file .close ()
1913+
1914+
1915+ class HTTPGzipHandlerTests (unittest .TestCase ):
1916+
1917+ body = b"The quick brown fox jumps over the lazy dog.\n " * 200
1918+
1919+ def make_response (self , data , content_encoding = "gzip" , content_length = True ):
1920+ import email .message
1921+ headers = email .message .Message ()
1922+ if content_encoding is not None :
1923+ headers ["Content-Encoding" ] = content_encoding
1924+ if content_length :
1925+ headers ["Content-Length" ] = str (len (data ))
1926+ headers ["Content-Type" ] = "text/plain"
1927+ return FakeGzipResponse (data , headers )
1928+
1929+ def gzip_bytes (self , data ):
1930+ import gzip
1931+ return gzip .compress (data )
1932+
1933+ def opened_request (self , ** kwargs ):
1934+ h = urllib .request .HTTPGzipHandler ()
1935+ req = Request ("http://example.com/" , ** kwargs )
1936+ h .http_request (req )
1937+ return h , req
1938+
1939+ def test_not_installed_by_default (self ):
1940+ # Opt-in: build_opener() does not install it, but accepts it explicitly.
1941+ o = urllib .request .build_opener ()
1942+ self .assertFalse (any (isinstance (h , urllib .request .HTTPGzipHandler )
1943+ for h in o .handlers ))
1944+ o = urllib .request .build_opener (urllib .request .HTTPGzipHandler )
1945+ self .assertTrue (any (isinstance (h , urllib .request .HTTPGzipHandler )
1946+ for h in o .handlers ))
1947+
1948+ def test_request_adds_accept_encoding (self ):
1949+ h , req = self .opened_request ()
1950+ self .assertEqual (req .get_header ("Accept-encoding" ), "gzip" )
1951+
1952+ def test_request_keeps_user_accept_encoding (self ):
1953+ h , req = self .opened_request (headers = {"Accept-Encoding" : "identity" })
1954+ self .assertEqual (req .get_header ("Accept-encoding" ), "identity" )
1955+
1956+ def test_response_is_decoded (self ):
1957+ h , req = self .opened_request ()
1958+ response = self .make_response (self .gzip_bytes (self .body ))
1959+ with h .http_response (req , response ) as result :
1960+ self .assertEqual (result .read (), self .body )
1961+
1962+ def test_response_headers_stripped (self ):
1963+ h , req = self .opened_request ()
1964+ response = self .make_response (self .gzip_bytes (self .body ))
1965+ result = h .http_response (req , response )
1966+ info = result .info ()
1967+ self .assertIsNone (info .get ("Content-Encoding" ))
1968+ self .assertIsNone (info .get ("Content-Length" ))
1969+ self .assertEqual (info .get ("Content-Type" ), "text/plain" )
1970+ self .assertEqual (result .status , 200 )
1971+ result .close ()
1972+
1973+ def test_response_streamed_in_chunks (self ):
1974+ h , req = self .opened_request ()
1975+ response = self .make_response (self .gzip_bytes (self .body ))
1976+ result = h .http_response (req , response )
1977+ chunks = []
1978+ while chunk := result .read (64 ):
1979+ self .assertLessEqual (len (chunk ), 64 )
1980+ chunks .append (chunk )
1981+ self .assertEqual (b"" .join (chunks ), self .body )
1982+ result .close ()
1983+
1984+ def test_multiple_gzip_members_decoded (self ):
1985+ # A response may be several concatenated gzip members; all of them
1986+ # must be decoded, not just the first.
1987+ h , req = self .opened_request ()
1988+ comp = self .gzip_bytes (b"AAA" ) + self .gzip_bytes (b"BBB" )
1989+ response = self .make_response (comp )
1990+ with h .http_response (req , response ) as result :
1991+ self .assertEqual (result .read (), b"AAABBB" )
1992+
1993+ def test_trailing_junk_after_member_ignored (self ):
1994+ # Bytes after the final member that are not another gzip member are
1995+ # tolerated: the body decodes without error and is not corrupted.
1996+ h , req = self .opened_request ()
1997+ comp = self .gzip_bytes (self .body ) + b"trailing garbage"
1998+ response = self .make_response (comp )
1999+ with h .http_response (req , response ) as result :
2000+ self .assertEqual (result .read (), self .body )
2001+
2002+ def test_user_accept_encoding_passes_through (self ):
2003+ # A caller-supplied Accept-Encoding is left to the caller to decode:
2004+ # the gzipped response is returned untouched.
2005+ h , req = self .opened_request (headers = {"Accept-Encoding" : "gzip" })
2006+ response = self .make_response (self .gzip_bytes (self .body ))
2007+ result = h .http_response (req , response )
2008+ self .assertIs (result , response )
2009+ self .assertEqual (result .headers .get ("Content-Encoding" ), "gzip" )
2010+
2011+ def test_non_gzip_response_passes_through (self ):
2012+ h , req = self .opened_request ()
2013+ response = self .make_response (self .body , content_encoding = None )
2014+ result = h .http_response (req , response )
2015+ self .assertIs (result , response )
2016+
2017+ def test_truncated_stream_raises (self ):
2018+ h , req = self .opened_request ()
2019+ comp = self .gzip_bytes (self .body )
2020+ response = self .make_response (comp [:len (comp ) // 2 ])
2021+ result = h .http_response (req , response )
2022+ with self .assertRaises (EOFError ):
2023+ result .read ()
2024+ result .close ()
2025+
2026+ def test_close_propagates (self ):
2027+ h , req = self .opened_request ()
2028+ response = self .make_response (self .gzip_bytes (self .body ))
2029+ result = h .http_response (req , response )
2030+ result .close ()
2031+ self .assertTrue (response .closed )
2032+
2033+ def test_requires_zlib (self ):
2034+ with mock .patch .object (urllib .request , "zlib" , None ):
2035+ with self .assertRaises (ImportError ):
2036+ urllib .request .HTTPGzipHandler ()
2037+
2038+
18962039class MiscTests (unittest .TestCase ):
18972040
18982041 def opener_has_handler (self , opener , handler_class ):
0 commit comments