| 
									
										
										
										
											2011-07-20 11:41:21 -04:00
										 |  |  | import datetime | 
					
						
							|  |  |  | from email import utils | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | import test.support | 
					
						
							|  |  |  | import time | 
					
						
							| 
									
										
										
										
											2011-07-20 11:41:21 -04:00
										 |  |  | import unittest | 
					
						
							| 
									
										
										
										
											2012-08-22 22:06:37 -04:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2013-05-08 11:16:02 +03:00
										 |  |  | import os.path | 
					
						
							| 
									
										
										
										
											2011-07-20 11:41:21 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class DateTimeTests(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     datestring = 'Sun, 23 Sep 2001 20:10:55' | 
					
						
							|  |  |  |     dateargs = (2001, 9, 23, 20, 10, 55) | 
					
						
							|  |  |  |     offsetstring = ' -0700' | 
					
						
							|  |  |  |     utcoffset = datetime.timedelta(hours=-7) | 
					
						
							|  |  |  |     tz = datetime.timezone(utcoffset) | 
					
						
							|  |  |  |     naive_dt = datetime.datetime(*dateargs) | 
					
						
							|  |  |  |     aware_dt = datetime.datetime(*dateargs, tzinfo=tz) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_naive_datetime(self): | 
					
						
							|  |  |  |         self.assertEqual(utils.format_datetime(self.naive_dt), | 
					
						
							|  |  |  |                          self.datestring + ' -0000') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_aware_datetime(self): | 
					
						
							|  |  |  |         self.assertEqual(utils.format_datetime(self.aware_dt), | 
					
						
							|  |  |  |                          self.datestring + self.offsetstring) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_usegmt(self): | 
					
						
							|  |  |  |         utc_dt = datetime.datetime(*self.dateargs, | 
					
						
							|  |  |  |                                    tzinfo=datetime.timezone.utc) | 
					
						
							|  |  |  |         self.assertEqual(utils.format_datetime(utc_dt, usegmt=True), | 
					
						
							|  |  |  |                          self.datestring + ' GMT') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_usegmt_with_naive_datetime_raises(self): | 
					
						
							|  |  |  |         with self.assertRaises(ValueError): | 
					
						
							|  |  |  |             utils.format_datetime(self.naive_dt, usegmt=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_usegmt_with_non_utc_datetime_raises(self): | 
					
						
							|  |  |  |         with self.assertRaises(ValueError): | 
					
						
							|  |  |  |             utils.format_datetime(self.aware_dt, usegmt=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_parsedate_to_datetime(self): | 
					
						
							|  |  |  |         self.assertEqual( | 
					
						
							|  |  |  |             utils.parsedate_to_datetime(self.datestring + self.offsetstring), | 
					
						
							|  |  |  |             self.aware_dt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_parsedate_to_datetime_naive(self): | 
					
						
							|  |  |  |         self.assertEqual( | 
					
						
							|  |  |  |             utils.parsedate_to_datetime(self.datestring + ' -0000'), | 
					
						
							|  |  |  |             self.naive_dt) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
											  
											
												bpo-30681: Support invalid date format or value in email Date header (GH-22090)
I am re-submitting an older PR which was abandoned but is still relevant, #10783 by @timb07.
The issue being solved () is still relevant. The original PR #10783 was closed as
the final request changes were not applied and since abandoned.
In this new PR I have re-used the original patch plus applied both comments from the review, by @maxking and @pganssle.
For reference, here is the original PR description:
In email.utils.parsedate_to_datetime(), a failure to parse the date, or invalid date components (such as hour outside 0..23) raises an exception. Document this behaviour, and add tests to test_email/test_utils.py to confirm this behaviour.
In email.headerregistry.DateHeader.parse(), check when parsedate_to_datetime() raises an exception and add a new defect InvalidDateDefect; preserve the invalid value as the string value of the header, but set the datetime attribute to None.
Add tests to test_email/test_headerregistry.py to confirm this behaviour; also added test to test_email/test_inversion.py to confirm emails with such defective date headers round trip successfully.
This pull request incorporates feedback gratefully received from @bitdancer, @brettcannon, @Mariatta and @warsaw, and replaces the earlier PR #2254.
Automerge-Triggered-By: GH:warsaw
											
										 
											2020-10-27 01:31:06 +01:00
										 |  |  |     def test_parsedate_to_datetime_with_invalid_raises_valueerror(self): | 
					
						
							| 
									
										
										
										
											2022-07-25 09:17:25 +03:00
										 |  |  |         # See also test_parsedate_returns_None_for_invalid_strings in test_email. | 
					
						
							|  |  |  |         invalid_dates = [ | 
					
						
							|  |  |  |             '', | 
					
						
							|  |  |  |             ' ', | 
					
						
							|  |  |  |             '0', | 
					
						
							|  |  |  |             'A Complete Waste of Time', | 
					
						
							|  |  |  |             'Wed, 3 Apr 2002 12.34.56.78+0800' | 
					
						
							|  |  |  |             'Tue, 06 Jun 2017 27:39:33 +0600', | 
					
						
							|  |  |  |             'Tue, 06 Jun 2017 07:39:33 +2600', | 
					
						
							|  |  |  |             'Tue, 06 Jun 2017 27:39:33', | 
					
						
							|  |  |  |             '17 June , 2022', | 
					
						
							|  |  |  |             'Friday, -Nov-82 16:14:55 EST', | 
					
						
							|  |  |  |             'Friday, Nov--82 16:14:55 EST', | 
					
						
							|  |  |  |             'Friday, 19-Nov- 16:14:55 EST', | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
											  
											
												bpo-30681: Support invalid date format or value in email Date header (GH-22090)
I am re-submitting an older PR which was abandoned but is still relevant, #10783 by @timb07.
The issue being solved () is still relevant. The original PR #10783 was closed as
the final request changes were not applied and since abandoned.
In this new PR I have re-used the original patch plus applied both comments from the review, by @maxking and @pganssle.
For reference, here is the original PR description:
In email.utils.parsedate_to_datetime(), a failure to parse the date, or invalid date components (such as hour outside 0..23) raises an exception. Document this behaviour, and add tests to test_email/test_utils.py to confirm this behaviour.
In email.headerregistry.DateHeader.parse(), check when parsedate_to_datetime() raises an exception and add a new defect InvalidDateDefect; preserve the invalid value as the string value of the header, but set the datetime attribute to None.
Add tests to test_email/test_headerregistry.py to confirm this behaviour; also added test to test_email/test_inversion.py to confirm emails with such defective date headers round trip successfully.
This pull request incorporates feedback gratefully received from @bitdancer, @brettcannon, @Mariatta and @warsaw, and replaces the earlier PR #2254.
Automerge-Triggered-By: GH:warsaw
											
										 
											2020-10-27 01:31:06 +01:00
										 |  |  |         for dtstr in invalid_dates: | 
					
						
							|  |  |  |             with self.subTest(dtstr=dtstr): | 
					
						
							|  |  |  |                 self.assertRaises(ValueError, utils.parsedate_to_datetime, dtstr) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class LocaltimeTests(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_is_tz_aware_daylight_true(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', True) | 
					
						
							|  |  |  |         t = utils.localtime() | 
					
						
							| 
									
										
										
										
											2013-11-16 12:56:23 +02:00
										 |  |  |         self.assertIsNotNone(t.tzinfo) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_is_tz_aware_daylight_false(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', False) | 
					
						
							|  |  |  |         t = utils.localtime() | 
					
						
							| 
									
										
										
										
											2013-11-16 12:56:23 +02:00
										 |  |  |         self.assertIsNotNone(t.tzinfo) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_daylight_true_dst_false(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', True) | 
					
						
							|  |  |  |         t0 = datetime.datetime(2012, 3, 12, 1, 1) | 
					
						
							| 
									
										
										
										
											2023-03-19 19:20:20 -05:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t2 = utils.localtime(t1) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_daylight_false_dst_false(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', False) | 
					
						
							|  |  |  |         t0 = datetime.datetime(2012, 3, 12, 1, 1) | 
					
						
							| 
									
										
										
										
											2023-03-19 19:20:20 -05:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t2 = utils.localtime(t1) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-27 12:40:50 +01:00
										 |  |  |     @test.support.run_with_tz('Europe/Minsk') | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |     def test_localtime_daylight_true_dst_true(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', True) | 
					
						
							|  |  |  |         t0 = datetime.datetime(2012, 3, 12, 1, 1) | 
					
						
							| 
									
										
										
										
											2023-03-19 19:20:20 -05:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t2 = utils.localtime(t1) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-27 12:40:50 +01:00
										 |  |  |     @test.support.run_with_tz('Europe/Minsk') | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |     def test_localtime_daylight_false_dst_true(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', False) | 
					
						
							|  |  |  |         t0 = datetime.datetime(2012, 3, 12, 1, 1) | 
					
						
							| 
									
										
										
										
											2023-03-19 19:20:20 -05:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t2 = utils.localtime(t1) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-22 21:34:00 -04:00
										 |  |  |     @test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |     def test_localtime_epoch_utc_daylight_true(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', True) | 
					
						
							| 
									
										
										
										
											2012-11-05 02:06:13 +01:00
										 |  |  |         t0 = datetime.datetime(1990, 1, 1, tzinfo = datetime.timezone.utc) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-08-22 21:34:00 -04:00
										 |  |  |         t2 = t0 - datetime.timedelta(hours=5) | 
					
						
							|  |  |  |         t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5))) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-22 21:34:00 -04:00
										 |  |  |     @test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |     def test_localtime_epoch_utc_daylight_false(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', False) | 
					
						
							| 
									
										
										
										
											2012-11-05 02:06:13 +01:00
										 |  |  |         t0 = datetime.datetime(1990, 1, 1, tzinfo = datetime.timezone.utc) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							| 
									
										
										
										
											2012-08-22 21:34:00 -04:00
										 |  |  |         t2 = t0 - datetime.timedelta(hours=5) | 
					
						
							|  |  |  |         t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5))) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_epoch_notz_daylight_true(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', True) | 
					
						
							| 
									
										
										
										
											2012-11-05 02:06:13 +01:00
										 |  |  |         t0 = datetime.datetime(1990, 1, 1) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							|  |  |  |         t2 = utils.localtime(t0.replace(tzinfo=None)) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_localtime_epoch_notz_daylight_false(self): | 
					
						
							|  |  |  |         test.support.patch(self, time, 'daylight', False) | 
					
						
							| 
									
										
										
										
											2012-11-05 02:06:13 +01:00
										 |  |  |         t0 = datetime.datetime(1990, 1, 1) | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  |         t1 = utils.localtime(t0) | 
					
						
							|  |  |  |         t2 = utils.localtime(t0.replace(tzinfo=None)) | 
					
						
							|  |  |  |         self.assertEqual(t1, t2) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-22 22:06:37 -04:00
										 |  |  |     # XXX: Need a more robust test for Olson's tzdata | 
					
						
							|  |  |  |     @unittest.skipIf(sys.platform.startswith('win'), | 
					
						
							|  |  |  |                      "Windows does not use Olson's TZ database") | 
					
						
							| 
									
										
										
										
											2013-05-08 11:16:02 +03:00
										 |  |  |     @unittest.skipUnless(os.path.exists('/usr/share/zoneinfo') or | 
					
						
							|  |  |  |                          os.path.exists('/usr/lib/zoneinfo'), | 
					
						
							|  |  |  |                          "Can't find the Olson's TZ database") | 
					
						
							| 
									
										
										
										
											2012-08-22 22:06:37 -04:00
										 |  |  |     @test.support.run_with_tz('Europe/Kiev') | 
					
						
							|  |  |  |     def test_variable_tzname(self): | 
					
						
							|  |  |  |         t0 = datetime.datetime(1984, 1, 1, tzinfo=datetime.timezone.utc) | 
					
						
							|  |  |  |         t1 = utils.localtime(t0) | 
					
						
							|  |  |  |         self.assertEqual(t1.tzname(), 'MSK') | 
					
						
							|  |  |  |         t0 = datetime.datetime(1994, 1, 1, tzinfo=datetime.timezone.utc) | 
					
						
							|  |  |  |         t1 = utils.localtime(t0) | 
					
						
							|  |  |  |         self.assertEqual(t1.tzname(), 'EET') | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 19:20:20 -05:00
										 |  |  |     def test_isdst_deprecation(self): | 
					
						
							|  |  |  |         with self.assertWarns(DeprecationWarning): | 
					
						
							|  |  |  |             t0 = datetime.datetime(1990, 1, 1) | 
					
						
							|  |  |  |             t1 = utils.localtime(t0, isdst=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-09-18 16:32:23 +02:00
										 |  |  | # Issue #24836: The timezone files are out of date (pre 2011k) | 
					
						
							|  |  |  | # on Mac OS X Snow Leopard. | 
					
						
							|  |  |  | @test.support.requires_mac_ver(10, 7) | 
					
						
							| 
									
										
										
										
											2015-08-01 08:18:22 +12:00
										 |  |  | class FormatDateTests(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @test.support.run_with_tz('Europe/Minsk') | 
					
						
							|  |  |  |     def test_formatdate(self): | 
					
						
							|  |  |  |         timeval = time.mktime((2011, 12, 1, 18, 0, 0, 4, 335, 0)) | 
					
						
							|  |  |  |         string = utils.formatdate(timeval, localtime=False, usegmt=False) | 
					
						
							|  |  |  |         self.assertEqual(string, 'Thu, 01 Dec 2011 15:00:00 -0000') | 
					
						
							|  |  |  |         string = utils.formatdate(timeval, localtime=False, usegmt=True) | 
					
						
							|  |  |  |         self.assertEqual(string, 'Thu, 01 Dec 2011 15:00:00 GMT') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @test.support.run_with_tz('Europe/Minsk') | 
					
						
							|  |  |  |     def test_formatdate_with_localtime(self): | 
					
						
							|  |  |  |         timeval = time.mktime((2011, 1, 1, 18, 0, 0, 6, 1, 0)) | 
					
						
							|  |  |  |         string = utils.formatdate(timeval, localtime=True) | 
					
						
							|  |  |  |         self.assertEqual(string, 'Sat, 01 Jan 2011 18:00:00 +0200') | 
					
						
							|  |  |  |         # Minsk moved from +0200 (with DST) to +0300 (without DST) in 2011 | 
					
						
							|  |  |  |         timeval = time.mktime((2011, 12, 1, 18, 0, 0, 4, 335, 0)) | 
					
						
							|  |  |  |         string = utils.formatdate(timeval, localtime=True) | 
					
						
							|  |  |  |         self.assertEqual(string, 'Thu, 01 Dec 2011 18:00:00 +0300') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-05-25 23:22:59 -04:00
										 |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |     unittest.main() |