From 59507753c7f098e98000459b2371becf0da18ff7 Mon Sep 17 00:00:00 2001 From: Gary Wilson Jr Date: Fri, 8 May 2009 05:50:31 +0000 Subject: [PATCH] Fixed #9610 -- Fixed duplicate uploaded file name mangling when directory contained a dot and file didn't. Based on patches from fadlytabrani and adurdin. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10701 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/files/storage.py | 16 +++---- tests/regressiontests/file_storage/tests.py | 47 +++++++++++++++++++-- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 13acada64a..e183c17dd3 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -63,15 +63,15 @@ class Storage(object): Returns a filename that's free on the target storage system, and available for new content to be written to. """ - # If the filename already exists, keep adding an underscore to the name - # of the file until the filename doesn't exist. + dir_name, file_name = os.path.split(name) + file_root, file_ext = os.path.splitext(file_name) + # If the filename already exists, keep adding an underscore (before the + # file extension, if one exists) to the filename until the generated + # filename doesn't exist. while self.exists(name): - try: - dot_index = name.rindex('.') - except ValueError: # filename has no dot - name += '_' - else: - name = name[:dot_index] + '_' + name[dot_index:] + file_root += '_' + # file_ext includes the dot. + name = os.path.join(dir_name, file_root + file_ext) return name def path(self, name): diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 6b219f064e..594277797e 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -84,11 +84,12 @@ u'custom_storage.2' """ # Tests for a race condition on file saving (#4948). -# This is written in such a way that it'll always pass on platforms +# This is written in such a way that it'll always pass on platforms # without threading. import os import time import shutil +import sys import tempfile from unittest import TestCase from django.conf import settings @@ -109,13 +110,13 @@ class FileSaveRaceConditionTest(TestCase): self.storage_dir = tempfile.mkdtemp() self.storage = FileSystemStorage(self.storage_dir) self.thread = threading.Thread(target=self.save_file, args=['conflict']) - + def tearDown(self): shutil.rmtree(self.storage_dir) - + def save_file(self, name): name = self.storage.save(name, SlowFile("Data")) - + def test_race_condition(self): self.thread.start() name = self.save_file('conflict') @@ -141,3 +142,41 @@ class FileStoragePermissions(TestCase): actual_mode = os.stat(self.storage.path(name))[0] & 0777 self.assertEqual(actual_mode, 0666) + +class FileStoragePathParsing(TestCase): + def setUp(self): + self.storage_dir = tempfile.mkdtemp() + self.storage = FileSystemStorage(self.storage_dir) + + def tearDown(self): + shutil.rmtree(self.storage_dir) + + def test_directory_with_dot(self): + """Regression test for #9610. + + If the directory name contains a dot and the file name doesn't, make + sure we still mangle the file name instead of the directory name. + """ + + self.storage.save('dotted.path/test', ContentFile("1")) + self.storage.save('dotted.path/test', ContentFile("2")) + + self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) + self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) + self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_'))) + + def test_first_character_dot(self): + """ + File names with a dot as their first character don't have an extension, + and the underscore should get added to the end. + """ + self.storage.save('dotted.path/.test', ContentFile("1")) + self.storage.save('dotted.path/.test', ContentFile("2")) + + self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) + # Before 2.6, a leading dot was treated as an extension, and so + # underscore gets added to beginning instead of end. + if sys.version_info < (2, 6): + self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_.test'))) + else: + self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_')))