Fixed #25283 -- Fixed collectstatic crash if a URL contains a fragment with a path.

A @font-face declaration may contain a fragment that looks like a relative path,
e.g. @font-face { src: url('../fonts/font.svg#../path/like/fragment'); }
In this case, an incorrect path was passed to the storage backend, which raised
an error that caused collectstatic to crash.
This commit is contained in:
msaelices 2015-08-17 17:59:31 +02:00 committed by Tim Graham
parent ce4914eab4
commit 7624fdb9f8
3 changed files with 13 additions and 6 deletions

View File

@ -166,12 +166,15 @@ class HashedFilesMixin(object):
name_parts = name.split(os.sep) name_parts = name.split(os.sep)
# Using posix normpath here to remove duplicates # Using posix normpath here to remove duplicates
url = posixpath.normpath(url) url = posixpath.normpath(url)
url_parts = url.split('/') # Strip off the fragment so that a path-like fragment won't confuse
parent_level, sub_level = url.count('..'), url.count('/') # the lookup.
if url.startswith('/'): url_path, fragment = urldefrag(url)
url_parts = url_path.split('/')
parent_level, sub_level = url_path.count('..'), url_path.count('/')
if url_path.startswith('/'):
sub_level -= 1 sub_level -= 1
url_parts = url_parts[1:] url_parts = url_parts[1:]
if parent_level or not url.startswith('/'): if parent_level or not url_path.startswith('/'):
start, end = parent_level + 1, parent_level start, end = parent_level + 1, parent_level
else: else:
if sub_level: if sub_level:
@ -183,7 +186,9 @@ class HashedFilesMixin(object):
joined_result = '/'.join(name_parts[:-start] + url_parts[end:]) joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
hashed_url = self.url(unquote(joined_result), force=True) hashed_url = self.url(unquote(joined_result), force=True)
file_name = hashed_url.split('/')[-1:] file_name = hashed_url.split('/')[-1:]
relative_url = '/'.join(url.split('/')[:-1] + file_name) relative_url = '/'.join(url_path.split('/')[:-1] + file_name)
if fragment:
relative_url += '?#%s' % fragment if '?#' in url else '#%s' % fragment
# Return the hashed version to the file # Return the hashed version to the file
return template % unquote(relative_url) return template % unquote(relative_url)

View File

@ -1,6 +1,7 @@
@font-face { @font-face {
src: url('fonts/font.eot?#iefix') format('embedded-opentype'), src: url('fonts/font.eot?#iefix') format('embedded-opentype'),
url('fonts/font.svg#webfontIyfZbseF') format('svg'); url('fonts/font.svg#webfontIyfZbseF') format('svg');
url('fonts/font.svg#../path/to/fonts/font.svg') format('svg');
url('data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA'); url('data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA');
} }
div { div {

View File

@ -85,11 +85,12 @@ class TestHashedFiles(object):
def test_path_with_querystring_and_fragment(self): def test_path_with_querystring_and_fragment(self):
relpath = self.hashed_file_path("cached/css/fragments.css") relpath = self.hashed_file_path("cached/css/fragments.css")
self.assertEqual(relpath, "cached/css/fragments.75433540b096.css") self.assertEqual(relpath, "cached/css/fragments.ef92012a8c16.css")
with storage.staticfiles_storage.open(relpath) as relfile: with storage.staticfiles_storage.open(relpath) as relfile:
content = relfile.read() content = relfile.read()
self.assertIn(b'fonts/font.a4b0478549d0.eot?#iefix', content) self.assertIn(b'fonts/font.a4b0478549d0.eot?#iefix', content)
self.assertIn(b'fonts/font.b8d603e42714.svg#webfontIyfZbseF', content) self.assertIn(b'fonts/font.b8d603e42714.svg#webfontIyfZbseF', content)
self.assertIn(b'fonts/font.b8d603e42714.svg#../path/to/fonts/font.svg', content)
self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content) self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content)
self.assertIn(b'#default#VML', content) self.assertIn(b'#default#VML', content)