Problem/Motivation
When uploading a file using a file field, Drupal can add underscores to the file name as a security measure. When the file name contains periods followed by between 2 and 5 letters, Drupal considers that to be a possible extra file extension. Extra file extensions in the file name are made 'safe' by adding an underscore to the end of the extra extension.
File extensions which are considered 'safe' can be configured in the field 'Allowed file extensions' on the 'Manage field' page of the file field. When extra file extensions are in the allowed list, no extra underscore will be added to those file extensions.
Unfortunately the allowed extensions are stored in lower case. The check to see if extra file extensions are in the allowed list is case sensitive. Extra file extensions with capitals will always get an extra underscore, because they are compared with the list of allowed extensions which is lower case.
DETAILS
-------
includes\file.inc
Function file_save_upload() saves an upload and calls file_munge_filename() with the list of allowed extensions (which are lower case).
Function in_array() is used to compare the extra file extension in $filename_part (which can contain capitals) with the allowed extensions in $whitelist (which are lowercase). The check fails and an underscore is added.
REPRODUCE
---------
How to reproduce:
Create a content type.
Add a field of type 'file'.
Add 'test' and 'txt' to the 'Allowed file extensions'.
Create a node of the content type.
Upload file drupal.Test.txt. It will be renamed to drupal.Test_.txt.
Upload file drupal.test.txt. It is not renamed.
Upload file drupal.test.Txt. It is not renamed.
Proposed resolution
The real file extension of a file is checked in a case insensitive way. So in above example it does not matter if the real extension if txt or Txt.
Checking the file extension is done by the function file_validate_extensions(). That function can also be used to correctly check the extra file extensions in the file name. So in stead of using in_array(), we can use file_validate_extensions() to do the checks.
Remaining tasks
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
Add automated tests | Novice | Instructions | |
Manually test the patch | Novice | Instructions | |
Embed before and after screenshots in the issue summary | Novice | Instructions | |
Review patch to ensure that it fixes the issue, stays within scope, is properly documented, and follows coding standards | Instructions |
User interface changes
API changes
Comment | File | Size | Author |
---|---|---|---|
#77 | interdiff.txt | 2.97 KB | quietone |
#77 | tests_followup_valid-2112247-77.patch | 1.48 KB | quietone |
#69 | tests_followup_valid-2112247-69.patch | 1.8 KB | kostyashupenko |
#65 | interdiff-2112247-64-65.txt | 2.15 KB | quietone |
#65 | file_extension_case_sensitivity-2112247-65.patch | 2.11 KB | quietone |
Comments
Comment #1
johnvComment #3
aalamaki CreditAttribution: aalamaki commentedThe same issue persists with drupal 7.26, the following will correct the issue.
Comment #4
aalamaki CreditAttribution: aalamaki commentedComment #5
aalamaki CreditAttribution: aalamaki commentedComment #7
jaredsmith CreditAttribution: jaredsmith commentedComment #8
dgroene CreditAttribution: dgroene commentedWorking on this issue locally
Comment #9
dgroene CreditAttribution: dgroene commentedChanges both whitelist and filename_part to lowercase before comparing them
Comment #10
JacobSanfordLooks like patch applies cleanly, triggering testbot
Comment #12
JacobSanfordLooks like @dgroene made the same mistake creating the patch I did checking the patch. It applies cleanly against 8.x :) Needs 7 port.
Comment #13
JacobSanfordRestesting patch by #3, @aalamaki which applies cleanly to 7.26 as he stated! It appears to have failed due to the version number set at the time of submission. This didn't need reroll AFAICT.
Comment #14
JacobSanford3: 2112247.patch queued for re-testing.
Comment #15
dcam CreditAttribution: dcam commentedThis is still an issue in D8. See the attached image. It will have to be fixed for D8 first.
Comment #16
dcam CreditAttribution: dcam commentedComment #17
dcam CreditAttribution: dcam commentedComment #18
mErilainen CreditAttribution: mErilainen commentedRerolled (offset -1 lines) patch from #9 for the test bot now that the correct version is active. Seems to work.
Added before and after images.
Comment #19
dcam CreditAttribution: dcam commentedSetting as Needs Work because it still needs tests.
Comment #20
er.pushpinderrana CreditAttribution: er.pushpinderrana commentedDavid, Could you please share what other test cases are required that need to be reviewed. As #18 patch look fine, tested manually.
Comment #21
dcam CreditAttribution: dcam commentedWhenever we fix an issue like this we also add an automated test, which helps to ensure that future changes do not revert our hard work and cause the bug to reoccur. Here are the instructions for writing tests.
Comment #22
samuli.as CreditAttribution: samuli.as commentedWorking on the tests.
Comment #23
samuli.as CreditAttribution: samuli.as commentedComment #25
dcam CreditAttribution: dcam commentedI can't do a proper review of the patch right now, but I like it so far. The only thing I'm wondering about at this time is whether we should include a comment about why we have to normalize the extensions to lowercase.
Comment #26
joshjalopy CreditAttribution: joshjalopy commentedPatch #23 fixed the problem for Drupal 8.x-dev using Firefox 30.0 on Ubuntu. Drupal no longer renames approved extensions in upper or lower case.
Comment #27
joshjalopy CreditAttribution: joshjalopy commentedscreen shot of acceptable file.
Comment #28
alexpottThis test belongs in
NameMungingTest
and the first assertNotEqual is not necessary as it is already tested. And actually so is the second test. You could adapt testMungeIgnoreWhitelisted() to test this to.Comment #29
samuli.as CreditAttribution: samuli.as commented@alexpott: Thanks for the feedback, I'll do the changes.
Comment #30
samuli.as CreditAttribution: samuli.as commentedComment #32
dcam CreditAttribution: dcam commentedThis comment needs to wrap at 80 characters.
I think we need to test this differently. Right now you're making the allowed extension uppercase, not the extension in the file name. It may not make any difference, but I think we ought to test against potential user input instead. A new $name with the uppercase extension should be created in the method to test.
Of course, another valid argument is that the allowed file extension is also user input and we should test to make certain it's normalized too. In which case your assertion should stay and my suggestion should be added as a second assertion. The only reason I know of why we might not care is that core file fields already normalize allowed extensions to lowercase when their settings are saved to the database, but that might not be the case forever or with custom file fields.
Comment #33
samuli.as CreditAttribution: samuli.as commentedThanks, I'll just add another assertion.
Comment #34
samuli.as CreditAttribution: samuli.as commentedHere are the updated patches.
Comment #36
benjf CreditAttribution: benjf commentedI followed the steps in the issue summary both before and after applying the patch in #34, and this patch looks to solve the stated problem. Marking as RTBC.
Comment #37
alexpottCommitted 1ddfac8 and pushed to 8.x. Thanks!
Comment #39
mitsuroseba CreditAttribution: mitsuroseba commentedI'm working on the backport.
Comment #40
mitsuroseba CreditAttribution: mitsuroseba commentedComment #41
dcam CreditAttribution: dcam commentedThank you, @mitsuroseba!
#40 is RTBC. Rather than making @mitsuroseba create a tests-only patch, I just ran the tests myself without the fix. The results are shown in the image below. As you can see, the new assertions failed.
After applying the patch a valid file extension with a different case did not have the underscore added.
Comment #42
m1r1k CreditAttribution: m1r1k commentedComment #45
dcam CreditAttribution: dcam commentedComment #48
dcam CreditAttribution: dcam commentedComment #51
dcam CreditAttribution: dcam commentedComment #54
dcam CreditAttribution: dcam commentedComment #57
dcam CreditAttribution: dcam commentedComment #58
David_Rothstein CreditAttribution: David_Rothstein commentedCommitted to 7.x - thanks!
However, I don't understand why this patch removed one of the existing tests?
Before the patch it tested an example like this:
After the patch it tested two examples like this:
I think we should have a quick followup to add the first test back in addition to the new ones, especially since it's the most common case.
Comment #60
nlisgo CreditAttribution: nlisgo commentedComment #61
nlisgo CreditAttribution: nlisgo commentedComment #62
disasm CreditAttribution: disasm at AppliedTrust commentedtagging as needs tests to add the test back in.
Comment #63
KimNyholm CreditAttribution: KimNyholm as a volunteer commentedComment #64
KimNyholm CreditAttribution: KimNyholm as a volunteer commentedI have added the testcase requested in #58 and tested in 8.0.0-beta11.
Comment #65
quietone CreditAttribution: quietone commented@KimNyholm, thanks for adding the test back in.
Made some minor changes to the displayed text and moved the re-added test after the upper case test, because the test results were easier to follow (at least for me).
Comment #67
Mile23A test-only change to 8.1.x is allowable.
The patch doesn't apply though.
Comment #68
Mile23Comment #69
kostyashupenkoRerolled #65 with auto-merge
Comment #70
dcam CreditAttribution: dcam commentedThank you for the patches!
Unfortunately, this change to the comment is unrelated to the issue and should be removed. If it really enhanced the clarity of what's going on in this section of code then I might not care, but it doesn't in my opinion. Furthermore, it violates our documentation standards. Sentences within a single comment should be separated by spaces, not put on new lines.
Comment #77
quietone CreditAttribution: quietone as a volunteer commentedFixed the comment.
Comment #83
quietone CreditAttribution: quietone as a volunteer commentedThe work here is to add a test of the file name munging. The test case being added is for a filename like
random.foo.txt
with allowed extensions offoo</code'. The resulting filename is <code>random.foo_.txt
. AFAICT, a test for that exists in \Drupal\Tests\system\Unit\Event\SecurityFileUploadEventSubscriberTest::testSanitizeName where the filename isfoo.phar.png.php.jpg
with allowed extensionsjpg png
and a resulting filename offoo.phar_.png_.php_.jpg
.Therefore, closing as outdated. If this is incorrect reopen the issue, by setting the status to 'Active', and add a comment explaining what still needs to be done.
Thanks!