Commit 7624fdb9 authored by msaelices's avatar msaelices Committed by Tim Graham
Browse files

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.
parent ce4914ea
Loading
Loading
Loading
Loading
+10 −5
Original line number Diff line number Diff line
@@ -166,12 +166,15 @@ class HashedFilesMixin(object):
            name_parts = name.split(os.sep)
            # Using posix normpath here to remove duplicates
            url = posixpath.normpath(url)
            url_parts = url.split('/')
            parent_level, sub_level = url.count('..'), url.count('/')
            if url.startswith('/'):
            # Strip off the fragment so that a path-like fragment won't confuse
            # the lookup.
            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
                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
            else:
                if sub_level:
@@ -183,7 +186,9 @@ class HashedFilesMixin(object):
            joined_result = '/'.join(name_parts[:-start] + url_parts[end:])
            hashed_url = self.url(unquote(joined_result), force=True)
            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 template % unquote(relative_url)
+1 −0
Original line number Diff line number Diff line
@font-face {
    src: url('fonts/font.eot?#iefix') format('embedded-opentype'),
         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');
}
div {
+2 −1
Original line number Diff line number Diff line
@@ -85,11 +85,12 @@ class TestHashedFiles(object):

    def test_path_with_querystring_and_fragment(self):
        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:
            content = relfile.read()
            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#../path/to/fonts/font.svg', content)
            self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content)
            self.assertIn(b'#default#VML', content)