Problem/Motivation
When PHP returns an error during a file upload attempt (see http://php.net/manual/features.file-upload.errors.php), an error message with details potentially about the server configuration are displayed to the user. These file upload errors should be logged instead and a generic message displayed to the user.
Original problem/motivation:
When PHP throws UPLOAD_ERR_NO_TMP_DIR error while uploading a file, function file_check_upload($source = 'upload') won't handle it correctly. in fact this function will quit leaving user with cryptic error message An unknown error has occurred. The error is known, however no one bothered to write separate handler for it.
Steps to reproduce
Proposed resolution
1. Check for the following errors:
- UPLOAD_ERR_FORM_SIZE: "The file $original_file_name could not be saved because it exceeds " . format_size(Environment::getUploadMaxSize()) . ", the maximum allowed size for uploads.";
- UPLOAD_ERR_NO_FILE: "The file $original_file_name could not be saved because the upload did not complete.";
- UPLOAD_ERR_NO_TMP_DIR: "The file $original_file_name could not be saved because the server is missing a temporary folder.";
- UPLOAD_ERR_CANT_WRITE: "The file $original_file_name could not be saved because the server is unable to write to disk.";
- UPLOAD_ERR_EXTENSION: "The file $original_file_name could not be saved because a PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with phpinfo() may help.";
2. Present an unconditional user-facing error that doesn't divulge any specifics that might divulge information about the server and be a security risk: \Drupal::messenger()->addError(t('The file %file could not be saved. A server error has occurred.', ['%file' => $original_file_name]));
Remaining tasks
Needs tests
Agree on the text of the error messages
Review
Commit
User interface changes
API changes
Data model changes
Release notes snippet
Beta phase evaluation
Issue category | Bug because the file_save_upload is ignoring detailed errors and reporting a generic one under certain conditions |
---|---|
Unfrozen changes | Unfrozen because it only changes file saving error reporting to be more detailed |
Prioritized changes | The main goal of this issue is sys-admin sanity when encountering server errors |
Comment | File | Size | Author |
---|---|---|---|
#82 | 92944-82.patch | 8.05 KB | smavri |
#81 | reroll_diff_75-77.txt | 3 KB | ravi.shankar |
#77 | 92944-77.patch | 8 KB | kostyashupenko |
#76 | After-NoTmpDir-log.png | 5.74 KB | quietone |
#76 | After-NoTmpDir.png | 9.29 KB | quietone |
Issue fork drupal-92944
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #1
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedno patch
Comment #2
gregmac CreditAttribution: gregmac commentedThis seems to still be the case in 5.2.
Patch, based on above code:
Comment #3
gregmac CreditAttribution: gregmac commentedComment #4
StevenPatzNo patch.
Comment #6
dpearcefl CreditAttribution: dpearcefl commentedDue to the age of the last comment on this issue and due to the fact that D5 is no longer supported, I am closing this issue.
Comment #7
dpearcefl CreditAttribution: dpearcefl commentedGoing to open this issue until I can confirm it doesn't exist in modern Drupal.
Comment #8
SiliconMind CreditAttribution: SiliconMind commentedSince D6 the UPLOAD_ERR_NO_TMP_DIR is catched as "general" error and users get message: "The file %file could not be saved. An unknown error has occurred." Such a message is not helpful at all :( I Don't get it, PHP defines only 7 possible errors with file upload - it's not that hard to set messages appropriate for each one of these: http://php.net/manual/en/features.file-upload.errors.php
Comment #9
SiliconMind CreditAttribution: SiliconMind commentedAfter over 5 years, a patch. Finally! :)
Also added case for UPLOAD_ERR_CANT_WRITE error.
Comment #10
StevenPatzI can test this, this weekend.
Comment #11
jhedstromPatch still applies, and provides more detailed feedback when such errors occur.
I think due to the procedural nature of
file_save_upload()
, writing a test for these two errors would be quite difficult. If/when we ever switch to use the Symfony file validators, they provide complete coverage of these errors (as unit tests).I've updated the issue summary to include a beta phase evaluation.
Comment #14
jhedstromBack to RTBC. Not sure what's going on with the testbot today.
Comment #15
alexpottIf we are doing this we should make this complete there's now UPLOAD_ERR_EXTENSION see http://php.net/manual/en/features.file-upload.errors.php
I agree that testing this is going to be unnecessarily tricky.
Comment #16
jhedstromThis adds
UPLOAD_ERR_EXTENSION
. I took the error text directly from the php manual.Comment #17
swentel CreditAttribution: swentel commentedOh yes, I've been bitten by this one a couple of times already.
Comment #18
catchCommitted/pushed to 8.0.x, thanks!
Comment #20
David_Rothstein CreditAttribution: David_Rothstein commentedThese technical details should not be shown to end users; 99% won't understand what it means and can't do anything to fix it. It might even be considered a small security flaw, since it exposes information about how the server is configured.
These could perhaps be watchdog messages instead, with the end-user-facing message changed from "unknown error" to "server error" or similar...
But first I think this should be rolled back because it causes more problems than it solves.
Comment #21
David_Rothstein CreditAttribution: David_Rothstein commentedMore accurate title, also. And we can probably backport at least some of this (e.g. watchdog messages).
Comment #23
catchThat's a good point. Reverted the commit.
What about changing these errors to a trigger_error() so it shows up both on screen depending on error reporting levels and in watchdog. Then the more restricted message with dsm() sounds good.
Comment #24
catchComment #25
David_Rothstein CreditAttribution: David_Rothstein commentedHm, I think trigger_error() makes sense, although while trying that I found that the triggered error usually won't be displayed even if it's configured to (since file uploads usually happen via Ajax and Drupal only displays errors to the screen during Ajax if they're fatal, which usually makes sense but in this case seems like a bug).
I also wasn't sure how to handle translation here - do we not run through t() since the primary destination is watchdog or do we translate since it might appear on the screen?
Here's a patch that tries trigger_error() anyway. I also fixed a grammar issue in a couple of the messages ("because server" => "because the server").
Comment #26
David_Rothstein CreditAttribution: David_Rothstein commentedEventually decided that this really seems like a bug - filed #2400477: Drupal never displays non-fatal error messages to the screen during Ajax requests
Comment #27
jhedstromI think the patch in #25 is good to go, but should it perhaps wait on #2400477: Drupal never displays non-fatal error messages to the screen during Ajax requests?
Comment #36
Pavan B S CreditAttribution: Pavan B S at Valuebound commentedRerolled the patch,
Comment #42
mvonfrie CreditAttribution: mvonfrie as a volunteer and commentedCan you reroll patch #36 for Drupal 8.9 and 9.x, please?
Comment #43
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedRe-rolled for 8.9.x
Comment #44
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedComment #45
alexpottLog messages are not translated.
This needs to return FALSE here.
This line shouldn't be removed.
Comment #46
anmolgoyal74 CreditAttribution: anmolgoyal74 at OpenSense Labs for DrupalFit commentedAddressed #45.
Comment #47
quietone CreditAttribution: quietone as a volunteer commentedThis needs to be on 9.2.x and it needs a reroll. Needs IS update - at least have the proposed resolution and the remaining tasks to make it easier to find out what needs to be done here.
Comment #48
vsujeetkumar CreditAttribution: vsujeetkumar at Srijan | A Material+ Company for Drupal India Association commentedReroll patch given for 9.2.x.
Comment #49
vsujeetkumar CreditAttribution: vsujeetkumar at Srijan | A Material+ Company for Drupal India Association commentedFixing some phpcs issues. Please have a look.
Comment #50
quietone CreditAttribution: quietone as a volunteer commentedThis won't display the file name.
This is displaying the temporary file name. Should be $original_file_name.
There is at least one other instance to change.
How to write the test when this triggers and error?
I also think this needs a screenshot of the screen output when one of the newly detected errors occurs. If you make a screenshot - put it in the IS. Thx.
Comment #52
quietone CreditAttribution: quietone as a volunteer commentedComment #53
quietone CreditAttribution: quietone as a volunteer commentedAdding tests and implemented fixes for #50.
Comment #54
quietone CreditAttribution: quietone as a volunteer commentedIgnore that, forgot to run commit-code-check.
Comment #56
quietone CreditAttribution: quietone as a volunteer commentedMore or less started over. This attempts to properly display a message to the user and log an useful, more detailed message. The test has been updated to check both messages.
Update title to what this is doing.
Comment #58
quietone CreditAttribution: quietone as a volunteer commentedOK. Let's get the max upload size differently.
Comment #59
quietone CreditAttribution: quietone as a volunteer commentedComment #60
fubarhouse CreditAttribution: fubarhouse at PreviousNext commentedRead the code, all seems logical.
Looks like a very positive change to have.
I'm afraid I couldn't apply the patch to 9.3.x - any extra information could be useful to apply the patch here.
Else-wise it's line 17 on the Kernel test file which it's failing at.
Comment #61
mstrelan CreditAttribution: mstrelan at PreviousNext commentedThe patch appears to be against 9.2.x, have rerolled against 9.3.x and added keys to the dataProvider to make it easier to see what (if anything) fails.
Comment #62
fubarhouse CreditAttribution: fubarhouse at PreviousNext commentedExcellent. The re-roll worked @mstrelan.
Looks great, applies cleanly.
Comment #63
Kristen PolThanks for the updated patch. Here's some feedback:
Should use
$original_file_name
instead of$file_info->getFilename()
.Nitpick: IMO the parentheses in the comment are unnecessary.
Nitpick: Should be single quotes rather than double.
Comment #64
vsujeetkumar CreditAttribution: vsujeetkumar at Srijan | A Material+ Company for Drupal India Association commentedAddressed #63.1, 63.2 and 63.3, Please have a look.
Comment #65
quietone CreditAttribution: quietone as a volunteer commentedThis looks good to me but I can't RTBC.
Comment #66
kim.pepperThe issue summary might need an update:
However, we have only changed the message that gets logged, the message that is presented to the user is still:
.
Comment #67
mstrelan CreditAttribution: mstrelan at PreviousNext commentedComment #68
Amber Himes Matz1. For the message that is presented to the user, instead of "an unknown error", what about "a server error" and indicate that the details have been logged? (Suggested a long long time ago in #20.)
2. Also, I updated the issue summary about the user-facing error message. (Perhaps could still use an IS update about steps to reproduce?)
Comment #69
mohit_aghera CreditAttribution: mohit_aghera at QED42 commented@kim.pepper
As per the comment in #20, we can skip the updating user facing message.
@Amber Himes Matz
I've updated the patch and test with the message
The file %file could not be saved. A server error has occurred.
Do you think we need to get approval from UX team as well or we are good?
Comment #70
Amber Himes MatzLooks like here is where the generic user message is constructed. Thank you @mohit_aghera for the update.
Yes, though minor, I do think it's a good idea for the UX team to have a look. So I'll add a tag.
Comment #71
Amber Himes MatzI'm going to try to reproduce one of the errors and add a screenshot of the user-facing error message for the benefit of the usability review.
Comment #72
Amber Himes MatzUpdated title, since this issue is specific to error messages that PHP returns, not errors about file field configuration. (See http://php.net/manual/features.file-upload.errors.php.)
Updated issue summary, per comments that indicated that
$original_file_name
should be used and not$file_info->getFilename()
. Also added the missing cases to the IS. And updated point 2 about the user-facing message contents.So, in my attempt to reproduce a PHP file upload error, I updated my local site's PHP ini with the following values:
I added a File field to the Article content type with the defaults, and the help text does show a file size limit of 10 bytes, as set in my INI file, with a file type limit of ".txt".
I then tried to upload INSTALL.txt (94 bytes) to a new article. This is what happened:
1. No error was displayed by AJAX when I uploaded the file. The file name value remained in the field.
2. No error was displayed in the messages block and the file was successfully saved, albeit without a file upload attached. (The file upload error was essentially silent.)
3. The error message was logged as follows:
So, I am not convinced that the
if (isset($error_to_log))
block on lines 973-982 in core/modules/file.module are running "unconditionally". As, what is supposed to happen is that a helpful technical message is logged AND a generic helpful-but-not-detailed-and-not-unknown error message is shown to the user.I am also not sure if this is something to do with AJAX and we're in the wrong file. I'm also not sure if there is a server configuration or permissions issue that prevents this type of PHP error message from being displayed. I tried turning display_errors =1 in my ini file, and I tested the file upload with admin, content editor, and limited editor users.
Comment #73
quietone CreditAttribution: quietone as a volunteer commentedThe changes here are in a function that is now deprecated. It was deprecated in #3232248: Move _file_save_upload_single to a service and deprecate. Perhaps the work here should be in function file_save_upload()?
Comment #74
alexpott@quietone++ the work he should definitely only change file_save_upload(). It shouldn't touch the new FileUploadHandler service.
Comment #75
mohit_aghera CreditAttribution: mohit_aghera at QED42 commented@quietone @alexpott
I've refactored the code and moved all the logic to "file_save_upload()" method.
I had to re-roll the patch as well because we renamed test cases classes. Added re-roll diff file as well.
I've added new test cases instead of modifying existing one to keep things simple.
Comment #76
quietone CreditAttribution: quietone as a volunteer commented@mohit_aghera, thanks for updating the patch. Unfortunately, the patch no longer applies but it was just the changes to the test so I went ahead and did some manual testing.
To generate a On the webserver I moved /tmp to /tmp.bkp and then uploaded a file. That resulted in the same message displayed to the user and in the logs.
UI
Logged
The message logged and the message displayed are supposed to be different. The user can't do anything about the server so they don't need to know the details. This agrees with what Amber Himes Matz said #72 "As, what is supposed to happen is that a helpful technical message is logged AND a generic helpful-but-not-detailed-and-not-unknown error message is shown to the user.'
Do we have agreement on the text of the generic message that is shown to the user? What is in the IS is not in the patch.
Comment #77
kostyashupenkoRerolled against 9.3.x
@quietone don't hesitate to add "Needs reroll" tag next time)
Comment #80
quietone CreditAttribution: quietone at PreviousNext commented@kostyashupenko, just a reminder to always and an interdiff or diff when rerolling a patch. And I did not add the tag because this needs more work than a reroll.
This needs work for #76 and a diff between the patches in #75 and #77.
Comment #81
ravi.shankar CreditAttribution: ravi.shankar at OpenSense Labs commentedAdded reroll diff of patch #75 and #77.
Comment #82
smavri CreditAttribution: smavri at Axess Open Web Services commentedHere is a patch for 9.4
Comment #83
mstrelan CreditAttribution: mstrelan at PreviousNext commentedI attempted to re-roll for 11.x and convert this to an MR but it proved difficult due to changes in #3375447: Create an UploadedFile validator and deprecate error checking methods on UploadedFileInterface. I wondered if this issue is still relevant but it is lacking steps to reproduce.
Comment #84
catchI looked at the diff in https://git.drupalcode.org/project/drupal/-/commit/6bd3ad841f346d650ace3... and I also can't tell what's left here.
::handleFileUpload() creates violations, these are then formatted into a messenger message here: https://git.drupalcode.org/project/drupal/-/commit/6bd3ad841f346d650ace3...
The issue summary is saying that instead of listing the errors, we should log them. The errors we list now are validation constraints, not the raw message, so I think that's fine.
However, it looks like whereas we used to cover
UPLOAD_ERR_NO_TMP_DIR
as an explicit case in FileUploadHandler::handleFileUpload() https://git.drupalcode.org/project/drupal/-/commit/6bd3ad841f346d650ace3... that wasn't handled in the new code, and I can't see anywhere else in core that it's handled. Symfony's FileValidator does handle it explicitly but I don't think we're using that.Given all that, if the above is correct, which I'm not 100% sure it is, then I think what we're missing is logging the full range of errors we used to throw exceptions for, but now only show a handful of violation messages for.
Comment #85
catchI just committed #2838474: Remove dependency of "file_system" service on "logger" which I'm not sure directly affects this issue but might influence the eventual outcome even if it doesn't.
Comment #86
kim.pepperI believe it's because of how we move uploaded files.
We call
\Drupal\Core\File\FileSystem::moveUploadedFile()
which calls\move_uploaded_file()
. We don't call\Symfony\Component\HttpFoundation\File\UploadedFile::move()
which would throw those exceptions. From looking at the comments, due to a lack of stream wrapper support.Comment #87
kim.pepperWe could add those missing validations to
\Drupal\file\Validation\Constraint\UploadedFileConstraintValidator::validate()
, I'm just not sure if they are triggered before we try and move the file.Comment #88
kim.pepperWe are catching
UPLOAD_ERR_NO_TMP_DIR
in https://git.drupalcode.org/project/drupal/-/blob/11.x/core/modules/file/...This means this issue is about adding additional logging to
file_save_upload()
which would reclassify it as a task, IMO.