From d2457ec1eef95b4c4749a11f3957ce799b041b45 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 13 Apr 2013 18:15:49 -0400 Subject: [PATCH] Parse if-modified-since timestamps without going through time_t or local time. This fixes a bug on windows in which mktime cannot work with times before the epoch. Closes #713. --- tornado/test/web_test.py | 24 ++++++++++++++++++++++++ tornado/web.py | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 17a6c67c8..e1d71fb36 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -1,6 +1,7 @@ from __future__ import absolute_import, division, print_function, with_statement from tornado import gen from tornado.escape import json_decode, utf8, to_unicode, recursive_unicode, native_str, to_basestring +from tornado.httputil import format_timestamp from tornado.iostream import IOStream from tornado.log import app_log, gen_log from tornado.simple_httpclient import SimpleAsyncHTTPClient @@ -813,6 +814,29 @@ class StaticFileTest(WebTestCase): 'If-None-Match': response1.headers['Etag']}) self.assertEqual(response2.code, 304) + def test_static_if_modified_since_pre_epoch(self): + # On windows, the functions that work with time_t do not accept + # negative values, and at least one client (processing.js) seems + # to use if-modified-since 1/1/1960 as a cache-busting technique. + response = self.fetch("/static/robots.txt", headers={ + 'If-Modified-Since': 'Fri, 01 Jan 1960 00:00:00 GMT'}) + self.assertEqual(response.code, 200) + + def test_static_if_modified_since_time_zone(self): + # Instead of the value from Last-Modified, make requests with times + # chosen just before and after the known modification time + # of the file to ensure that the right time zone is being used + # when parsing If-Modified-Since. + stat = os.stat(os.path.join(os.path.dirname(__file__), + 'static/robots.txt')) + + response = self.fetch('/static/robots.txt', headers={ + 'If-Modified-Since': format_timestamp(stat.st_mtime - 1)}) + self.assertEqual(response.code, 200) + response = self.fetch('/static/robots.txt', headers={ + 'If-Modified-Since': format_timestamp(stat.st_mtime + 1)}) + self.assertEqual(response.code, 304) + @wsgi_safe class CustomStaticFileTest(WebTestCase): diff --git a/tornado/web.py b/tornado/web.py index 3293b539e..057536e94 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1638,7 +1638,7 @@ class StaticFileHandler(RequestHandler): raise HTTPError(403, "%s is not a file", path) stat_result = os.stat(abspath) - modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME]) + modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME]) self.set_header("Last-Modified", modified) @@ -1660,7 +1660,7 @@ class StaticFileHandler(RequestHandler): ims_value = self.request.headers.get("If-Modified-Since") if ims_value is not None: date_tuple = email.utils.parsedate(ims_value) - if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple)) + if_since = datetime.datetime(*date_tuple[:6]) if if_since >= modified: self.set_status(304) return -- 2.47.2