diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst index 63fae2ab84e213..63e2ca1bb0e017 100644 --- a/Doc/library/email.utils.rst +++ b/Doc/library/email.utils.rst @@ -124,7 +124,10 @@ of the new API. .. function:: parsedate_to_datetime(date) The inverse of :func:`format_datetime`. Performs the same function as - :func:`parsedate`, but on success returns a :mod:`~datetime.datetime`. If + :func:`parsedate`, but on success returns a :mod:`~datetime.datetime`; + otherwise ``None`` may be returned if parsing fails, or a ``ValueError`` + raised if *date* contains an invalid value such as an hour greater than + 23 or a timezone offset not between -24 and 24 hours. If the input date has a timezone of ``-0000``, the ``datetime`` will be a naive ``datetime``, and if the date is conforming to the RFCs it will represent a time in UTC but with no indication of the actual source timezone of the diff --git a/Lib/email/errors.py b/Lib/email/errors.py index d28a6800104bab..1d258c34fc9d4a 100644 --- a/Lib/email/errors.py +++ b/Lib/email/errors.py @@ -108,3 +108,6 @@ class NonASCIILocalPartDefect(HeaderDefect): """local_part contains non-ASCII characters""" # This defect only occurs during unicode parsing, not when # parsing messages decoded from binary. + +class InvalidDateDefect(HeaderDefect): + """Header has unparseable or invalid date""" diff --git a/Lib/email/headerregistry.py b/Lib/email/headerregistry.py index 00652049f2fa2f..f0d1802f885bd4 100644 --- a/Lib/email/headerregistry.py +++ b/Lib/email/headerregistry.py @@ -303,7 +303,14 @@ def parse(cls, value, kwds): kwds['parse_tree'] = parser.TokenList() return if isinstance(value, str): - value = utils.parsedate_to_datetime(value) + kwds['decoded'] = value + try: + value = utils.parsedate_to_datetime(value) + except (ValueError, TypeError): + kwds['defects'].append(errors.InvalidDateDefect('Invalid date value or format')) + kwds['datetime'] = None + kwds['parse_tree'] = parser.TokenList() + return kwds['datetime'] = value kwds['decoded'] = utils.format_datetime(kwds['datetime']) kwds['parse_tree'] = cls.value_parser(kwds['decoded']) diff --git a/Lib/test/test_email/test_headerregistry.py b/Lib/test/test_email/test_headerregistry.py index 30ce0ba54e4728..94867b27871528 100644 --- a/Lib/test/test_email/test_headerregistry.py +++ b/Lib/test/test_email/test_headerregistry.py @@ -203,6 +203,22 @@ def test_no_value_is_defect(self): self.assertEqual(len(h.defects), 1) self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue) + def test_invalid_date_format(self): + s = 'Not a date header' + h = self.make_header('date', s) + self.assertEqual(h, s) + self.assertIsNone(h.datetime) + self.assertEqual(len(h.defects), 1) + self.assertIsInstance(h.defects[0], errors.InvalidDateDefect) + + def test_invalid_date_value(self): + s = 'Tue, 06 Jun 2017 27:39:33 +0600' + h = self.make_header('date', s) + self.assertEqual(h, s) + self.assertIsNone(h.datetime) + self.assertEqual(len(h.defects), 1) + self.assertIsInstance(h.defects[0], errors.InvalidDateDefect) + def test_datetime_read_only(self): h = self.make_header('date', self.datestring) with self.assertRaises(AttributeError): diff --git a/Lib/test/test_email/test_inversion.py b/Lib/test/test_email/test_inversion.py index 8e8d67641b8943..7bd7f2a72067ad 100644 --- a/Lib/test/test_email/test_inversion.py +++ b/Lib/test/test_email/test_inversion.py @@ -46,6 +46,14 @@ def msg_as_input(self, msg): foo """),), + 'header_with_invalid_date': (dedent(b"""\ + Date: Tue, 06 Jun 2017 27:39:33 +0600 + From: abc@xyz.com + Subject: timezones + + How do they work even? + """),), + } payload_params = { diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py index 4e3c3f3a195fc4..0c60554dad378d 100644 --- a/Lib/test/test_email/test_utils.py +++ b/Lib/test/test_email/test_utils.py @@ -48,6 +48,22 @@ def test_parsedate_to_datetime_naive(self): utils.parsedate_to_datetime(self.datestring + ' -0000'), self.naive_dt) + def test_parsedate_to_datetime_with_invalid_raises_typeerror(self): + with self.assertRaises(TypeError): + utils.parsedate_to_datetime('') + with self.assertRaises(TypeError): + utils.parsedate_to_datetime('0') + with self.assertRaises(TypeError): + utils.parsedate_to_datetime('A Complete Waste of Time') + + def test_parsedate_to_datetime_with_invalid_raises_valueerror(self): + with self.assertRaises(ValueError): + utils.parsedate_to_datetime('Tue, 06 Jun 2017 27:39:33 +0600') + with self.assertRaises(ValueError): + utils.parsedate_to_datetime('Tue, 06 Jun 2017 07:39:33 +2600') + with self.assertRaises(ValueError): + utils.parsedate_to_datetime('Tue, 06 Jun 2017 27:39:33') + class LocaltimeTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2018-11-29-11-14-18.bpo-30681.04zEWG.rst b/Misc/NEWS.d/next/Library/2018-11-29-11-14-18.bpo-30681.04zEWG.rst new file mode 100644 index 00000000000000..f35ad643a031d3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-11-29-11-14-18.bpo-30681.04zEWG.rst @@ -0,0 +1,2 @@ +Handle exceptions caused by unparseable date headers when using email +"default" policy. Patch by Tim Bell.