Stop using email.utils.mktime_tz

Prior to Python 2.7.4 [1], this would convert the input to a local
timestamp, then adjust for both the local timezone and the timezone of
the input. Ordinarily, this would be fine (excluding, apparently, some
"argument out of range" issues on Windows).

However, Swift (since v1.9) manually adjusts the TZ environment
variable, apparently with the intention of making the timezone static
and avoiding extra overhead from checking /etc/timezone for changes.
In practice this sets TZ to "+0000" which has the effect of setting the
timezone to UTC (at least for some functions, such as time.localtime and
time.mktime) while *not* changing the offset stored in time.timezone.
This, in turn, causes email.utils.mktime_tz to produce bad timestamps
and swift3 to reject requests with RequestTimeTooSkewed errors if the
server was not in UTC.

Now, we'll avoid local timestamps by using calendar.timegm ourselves,
essentially inlining the upstream Python fix.

Note that Ubuntu Precise provides Python 2.7.3 (and thus *is* affected);
neither Ubuntu Trusty (which provides 2.7.6) nor CentOS 7 (which
provides 2.7.5) is affected.

[1] https://hg.python.org/cpython/rev/a283563c8cc4

Change-Id: Iee7488d03ab404072d3d0c1a262f004bb0f2da26
Related-Change: I007425301914144e228b9cfece5533443e851b6e
Related-Change: Ifc78236a99ed193a42389e383d062b38f57a5a31
Related-Change: I8ec80202789707f723abfe93ccc9cf1e677e4dc6
Closes-Bug: 1593863
This commit is contained in:
Tim Burke 2016-06-16 13:58:03 -07:00
parent 6c12c58867
commit f12274138a
2 changed files with 49 additions and 11 deletions

View File

@ -13,10 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import time
import unittest
import mock
from swift3 import utils
from swift3 import utils, request
strs = [
('Owner', 'owner'),
@ -97,6 +99,38 @@ class TestSwift3Utils(unittest.TestCase):
ts = utils.S3Timestamp(1.9)
self.assertEqual(expected, ts.s3xmlformat)
def test_mktime(self):
date_headers = [
'Thu, 01 Jan 1970 00:00:00 -0000',
'Thu, 01 Jan 1970 00:00:00 GMT',
'Thu, 01 Jan 1970 00:00:00 UTC',
'Thu, 01 Jan 1970 08:00:00 +0800',
'Wed, 31 Dec 1969 16:00:00 -0800',
'Wed, 31 Dec 1969 16:00:00 PST',
]
for header in date_headers:
ts = utils.mktime(header)
self.assertEqual(0, ts, 'Got %r for header %s' % (ts, header))
# Last-Modified response style
self.assertEqual(0, utils.mktime('1970-01-01T00:00:00'))
# X-Amz-Date style
self.assertEqual(0, utils.mktime('19700101T000000Z',
request.SIGV4_X_AMZ_DATE_FORMAT))
def test_mktime_weird_tz(self):
orig_tz = os.environ.get('TZ', '')
try:
os.environ['TZ'] = 'EST+05EDT,M4.1.0,M10.5.0'
time.tzset()
os.environ['TZ'] = '+0000'
# No tzset! Simulating what Swift would do.
self.assertNotEqual(0, time.timezone)
self.test_mktime()
finally:
os.environ['TZ'] = orig_tz
time.tzset()
if __name__ == '__main__':
unittest.main()

View File

@ -13,19 +13,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import uuid
import base64
import calendar
import email.utils
import re
import socket
import time
from urllib import unquote
import uuid
from swift.common.utils import get_logger
import email.utils
# Need for check_path_header
from swift.common import utils
from swift.common.swob import HTTPPreconditionFailed
from urllib import unquote
from swift3.cfg import CONF
@ -186,14 +187,17 @@ def mktime(timestamp_str, time_format='%Y-%m-%dT%H:%M:%S'):
:param time_format: a string of format to parse in (b) process
:return : a float instance in epoch time
"""
try:
epoch_time = email.utils.mktime_tz(
email.utils.parsedate_tz(timestamp_str))
except TypeError:
# time_tuple is the *remote* local time
time_tuple = email.utils.parsedate_tz(timestamp_str)
if time_tuple is None:
time_tuple = time.strptime(timestamp_str, time_format)
# add timezone info as utc (no time difference)
time_tuple += (0, )
epoch_time = email.utils.mktime_tz(time_tuple)
# We prefer calendar.gmtime and a manual adjustment over
# email.utils.mktime_tz because older versions of Python (<2.7.4) may
# double-adjust for timezone in some situations (such when swift changes
# os.environ['TZ'] without calling time.tzset()).
epoch_time = calendar.timegm(time_tuple) - time_tuple[9]
return epoch_time