Skip to content

Commit d2457ec

Browse files
committed
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 tornadoweb#713.
1 parent 4059df2 commit d2457ec

File tree

2 files changed

+26
-2
lines changed

2 files changed

+26
-2
lines changed

tornado/test/web_test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import, division, print_function, with_statement
22
from tornado import gen
33
from tornado.escape import json_decode, utf8, to_unicode, recursive_unicode, native_str, to_basestring
4+
from tornado.httputil import format_timestamp
45
from tornado.iostream import IOStream
56
from tornado.log import app_log, gen_log
67
from tornado.simple_httpclient import SimpleAsyncHTTPClient
@@ -813,6 +814,29 @@ def test_static_304_if_none_match(self):
813814
'If-None-Match': response1.headers['Etag']})
814815
self.assertEqual(response2.code, 304)
815816

817+
def test_static_if_modified_since_pre_epoch(self):
818+
# On windows, the functions that work with time_t do not accept
819+
# negative values, and at least one client (processing.js) seems
820+
# to use if-modified-since 1/1/1960 as a cache-busting technique.
821+
response = self.fetch("/static/robots.txt", headers={
822+
'If-Modified-Since': 'Fri, 01 Jan 1960 00:00:00 GMT'})
823+
self.assertEqual(response.code, 200)
824+
825+
def test_static_if_modified_since_time_zone(self):
826+
# Instead of the value from Last-Modified, make requests with times
827+
# chosen just before and after the known modification time
828+
# of the file to ensure that the right time zone is being used
829+
# when parsing If-Modified-Since.
830+
stat = os.stat(os.path.join(os.path.dirname(__file__),
831+
'static/robots.txt'))
832+
833+
response = self.fetch('/static/robots.txt', headers={
834+
'If-Modified-Since': format_timestamp(stat.st_mtime - 1)})
835+
self.assertEqual(response.code, 200)
836+
response = self.fetch('/static/robots.txt', headers={
837+
'If-Modified-Since': format_timestamp(stat.st_mtime + 1)})
838+
self.assertEqual(response.code, 304)
839+
816840

817841
@wsgi_safe
818842
class CustomStaticFileTest(WebTestCase):

tornado/web.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,7 +1638,7 @@ def get(self, path, include_body=True):
16381638
raise HTTPError(403, "%s is not a file", path)
16391639

16401640
stat_result = os.stat(abspath)
1641-
modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
1641+
modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
16421642

16431643
self.set_header("Last-Modified", modified)
16441644

@@ -1660,7 +1660,7 @@ def get(self, path, include_body=True):
16601660
ims_value = self.request.headers.get("If-Modified-Since")
16611661
if ims_value is not None:
16621662
date_tuple = email.utils.parsedate(ims_value)
1663-
if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
1663+
if_since = datetime.datetime(*date_tuple[:6])
16641664
if if_since >= modified:
16651665
self.set_status(304)
16661666
return

0 commit comments

Comments
 (0)