Skip to content

Commit

Permalink
Don't set Last-Modified header if mtime is zero
Browse files Browse the repository at this point in the history
Some filesystems (e.g. certain FUSE mounts) incorrecly report all mtimes
as zero which causes WhiteNoise to set a Last-Modified header at the
UNIX epoch and then to consider all files as unmodified.

We now ignore zero-valued mtimes as, for all practical purposes, these
are guaranteed to be incorrect.

Closes #222
  • Loading branch information
evansd committed Jul 13, 2019
1 parent 44ea7a8 commit 7c3cd37
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 3 deletions.
18 changes: 18 additions & 0 deletions tests/test_whitenoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
except ImportError:
from urlparse import urljoin
import shutil
import stat
import sys
import warnings
from wsgiref.headers import Headers
from wsgiref.simple_server import demo_app

from .utils import TestServer, Files

from whitenoise import WhiteNoise
from whitenoise.responders import StaticFile


# Update Py2 TestCase to support Py3 method names
Expand Down Expand Up @@ -287,3 +290,18 @@ def test_directory_path_can_be_pathlib_instance(self):
root = Path(Files('root').directory)
# Check we can construct instance without it blowing up
WhiteNoise(None, root=root, autorefresh=True)

def test_last_modified_not_set_when_mtime_is_zero(self):
class FakeStatEntry(object):
st_mtime = 0
st_size = 1024
st_mode = stat.S_IFREG
stat_cache = {
__file__: FakeStatEntry()
}
responder = StaticFile(__file__, [], stat_cache=stat_cache)
response = responder.get_response('GET', {})
response.file.close()
headers_dict = Headers(response.headers)
self.assertNotIn('Last-Modified', headers_dict)
self.assertNotIn('ETag', headers_dict)
15 changes: 12 additions & 3 deletions whitenoise/responders.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
import re
import stat
from time import mktime
try:
from urllib.parse import quote
except ImportError:
Expand Down Expand Up @@ -133,10 +134,16 @@ def get_headers(self, headers_list, files):
headers['Vary'] = 'Accept-Encoding'
if 'Last-Modified' not in headers:
mtime = main_file.stat.st_mtime
headers['Last-Modified'] = formatdate(mtime, usegmt=True)
# Not all filesystems report mtimes, and sometimes they report an
# mtime of 0 which we know is incorrect
if mtime:
headers['Last-Modified'] = formatdate(mtime, usegmt=True)
if 'ETag' not in headers:
headers['ETag'] = '"{:x}-{:x}"'.format(
int(main_file.stat.st_mtime), main_file.stat.st_size)
last_modified = parsedate(headers['Last-Modified'])
if last_modified:
timestamp = int(mktime(last_modified))
headers['ETag'] = '"{:x}-{:x}"'.format(
timestamp, main_file.stat.st_size)
return headers

@staticmethod
Expand Down Expand Up @@ -170,6 +177,8 @@ def is_not_modified(self, request_headers):
previous_etag = request_headers.get('HTTP_IF_NONE_MATCH')
if previous_etag is not None:
return previous_etag == self.etag
if self.last_modified is None:
return False
try:
last_requested = request_headers['HTTP_IF_MODIFIED_SINCE']
except KeyError:
Expand Down

0 comments on commit 7c3cd37

Please sign in to comment.