import unittest from scrapy.http import ( Headers, HtmlResponse, Response, TextResponse, XmlResponse, ) from scrapy.responsetypes import _MIME_TYPES, responsetypes, ResponseTypes class PreXtractmimeResponseTypes(ResponseTypes): def from_args(self, headers=None, url=None, filename=None, body=None): cls = Response if headers is not None: cls = self.from_headers(headers) if cls is Response and url is not None: cls = self.from_filename(url) if cls is Response and filename is not None: cls = self.from_filename(filename) if cls is Response and body is not None: cls = self.from_body(body) return cls _PRE_XTRACTMIME_RESPONSE_TYPES = PreXtractmimeResponseTypes() class ResponseTypesTest(unittest.TestCase): def test_from_filename(self): mappings = [ ('data.bin', Response), ('file.txt', TextResponse), ('file.xml.gz', Response), ('file.xml', XmlResponse), ('file.html', HtmlResponse), ('file.unknownext', Response), ] for source, cls in mappings: retcls = responsetypes.from_filename(source) assert retcls is cls, f"{source} ==> {retcls} != {cls}" def test_from_content_disposition(self): mappings = [ (b'attachment; filename="data.xml"', XmlResponse), (b'attachment; filename=data.xml', XmlResponse), ('attachment;filename=data£.tar.gz'.encode('utf-8'), Response), ('attachment;filename=dataµ.tar.gz'.encode('latin-1'), Response), ('attachment;filename=data高.doc'.encode('gbk'), Response), ('attachment;filename=دورهdata.html'.encode('cp720'), HtmlResponse), ('attachment;filename=日本語版Wikipedia.xml'.encode('iso2022_jp'), XmlResponse), ] for source, cls in mappings: retcls = responsetypes.from_content_disposition(source) assert retcls is cls, f"{source} ==> {retcls} != {cls}" def test_from_content_type(self): mappings = [ ('text/html; charset=UTF-8', HtmlResponse), ('text/xml; charset=UTF-8', XmlResponse), ('application/xhtml+xml; charset=UTF-8', HtmlResponse), ('application/vnd.wap.xhtml+xml; charset=utf-8', HtmlResponse), ('application/xml; charset=UTF-8', XmlResponse), ('application/octet-stream', Response), ('application/x-json; encoding=UTF8;charset=UTF-8', TextResponse), ('application/json-amazonui-streaming;charset=UTF-8', TextResponse), ] for source, cls in mappings: retcls = responsetypes.from_content_type(source) assert retcls is cls, f"{source} ==> {retcls} != {cls}" def test_from_body(self): mappings = [ (b'\x03\x02\xdf\xdd\x23', Response), (b'Some plain text\ndata with tabs\t and null bytes\0', TextResponse), (b'Hello', HtmlResponse), (b' {retcls} != {cls}" def test_from_headers(self): mappings = [ ({'Content-Type': ['text/html; charset=utf-8']}, HtmlResponse), ({'Content-Type': ['text/html; charset=utf-8'], 'Content-Encoding': ['gzip']}, Response), ({'Content-Type': ['application/octet-stream'], 'Content-Disposition': ['attachment; filename=data.txt']}, TextResponse), ] for source, cls in mappings: source = Headers(source) retcls = responsetypes.from_headers(source) assert retcls is cls, f"{source} ==> {retcls} != {cls}" def test_from_args_pre_xtractmime(self): """Each of the following test cases remains unaffected after using xtractmime for MIME sniffing""" mappings = [ ({'url': 'http://www.example.com/data.csv'}, TextResponse), ({'headers': Headers({'Content-Type': ['text/html; charset=utf-8']}), 'url': 'http://www.example.com/item/'}, HtmlResponse), ({'body': b'\x01\x02', 'headers': Headers({'Content-Disposition': ['attachment; filename="data.xml.gz"']}), 'url': 'http://www.example.com/page/'}, Response), ({'body': b'Some plain\0 text data with\0 tabs and null bytes\0'}, TextResponse), ({'body': b'\x03\x02\xdf\xdd\x23', 'headers': Headers({'Content-Encoding': 'UTF-8'})}, Response), ({'body': b'\x00\x01\xff', 'url': '://www.example.com/item/', 'headers': Headers({'Content-Type': ['text/plain']})}, TextResponse), ({'url': 'http://www.example.com/item/file.html'}, HtmlResponse), ({'body': b'Hello'}, HtmlResponse), ({'body': b'\n.'}, HtmlResponse), ({'body': b'\x01\x02', 'headers': Headers({'Content-Type': ['application/pdf']})}, Response), ({'headers': Headers({'Content-Type': ['application/x-json']})}, TextResponse), ({'headers': Headers({'Content-Type': ['application/x-javascript']})}, TextResponse), ({'headers': Headers({'Content-Type': ['application/json-amazonui-streaming']})}, TextResponse), ({'headers': Headers({'Content-Disposition': ['attachment; filename="data.xml.gz"']}), 'url': 'http://www.example.com/page/'}, Response), ({'headers': Headers({'Content-Type': ['application/pdf']})}, Response), ({'url': 'http://www.example.com/page/file.html', 'headers': Headers({'Content-Type': 'application/octet-stream'})}, HtmlResponse), ({'url': 'http://www.example.com/item/file.xml', 'headers': Headers({'Content-Type': 'application/octet-stream'})}, XmlResponse), ({'url': 'http://www.example.com/item/file.html', 'headers': Headers({'Content-Type': 'application/octet-stream'})}, HtmlResponse), ({'url': 'http://www.example.com/item/file.xml', 'headers': Headers({'Content-Disposition': ['attachment; filename="data.xml.gz"'], 'Content-Type': 'application/octet-stream'})}, XmlResponse), ({'url': 'http://www.example.com/item/file.pdf'}, Response), ({'filename': 'file.pdf'}, Response), ({'body': b'\n.', 'url': 'http://www.example.com'}, HtmlResponse), ] for source, cls in mappings: old_cls = _PRE_XTRACTMIME_RESPONSE_TYPES.from_args(**source) new_cls = responsetypes.from_args(**source) message = f"{source} ==> {old_cls} (old) != {new_cls} (current)" assert old_cls == new_cls, message assert new_cls is cls, f"{source} ==> {new_cls} != {cls}" def test_from_args_post_xtractmime(self): """Each of the following test cases got affected after using xtractmime for MIME sniffing""" mappings = [ ({'body': b'\x00\x01\xff', 'url': 'http://www.example.com/item/', 'headers': Headers({'Content-Type': ['text/plain']})}, Response), ({'filename': '/tmp/temp^'}, TextResponse), ({'body': b'%PDF-1.4'}, Response), ({'headers': Headers({'Content-Type': ['application/ecmascript']})}, TextResponse), ({'headers': Headers({'Content-Type': ['application/ld+json']})}, TextResponse), ({'headers': Headers({'Content-Encoding': ['zip'], 'Content-Type': ['text/html']})}, HtmlResponse), ({'headers': Headers({'Content-Encoding': ['zip'], 'Content-Type': ['text/plain']})}, TextResponse), ({'body': b'Non HTML', 'headers': Headers({'Content-Encoding': ['zip'], 'Content-Type': ['text/html']})}, HtmlResponse), ({'body': b'Some plain text', 'headers': Headers({'Content-Type': 'application/octet-stream'})}, Response), ({'body': b'\x0c\x1b'}, TextResponse), ({'body': b'this is not '}, TextResponse), ({'body': b'this is not {old_cls} (old) == {new_cls} (current)" assert old_cls != new_cls, message assert new_cls is cls, f"{source} ==> {new_cls} != {cls}" def test_custom_mime_types_loaded(self): """Check that mime.types files shipped with Scrapy are loaded.""" self.assertEqual( _MIME_TYPES.guess_type('x.scrapytest')[0], 'x-scrapy/test', ) if __name__ == "__main__": unittest.main()