Following on from the work done in the parent issue addressing error handling.
The Download process doesn't currently handle any clean up in terms of deleting the destination file when the error occurs.
For example, downloading a http://example.com/example.jpg file which 404s will throw an exception, but will keep the destination example.jpg file on disk and have it contain the raw HTML for the 404 response, which might not be expected behaviour.
This is especially hard to work with when using the file_exists: 'use existing' option.
My proposed solution for this is to make it an option so that people who don't care about the content of the files can keep the old behaviour.
After discussion with Framwork managers, we should delete the file on a 400 response, and there is no need to preserve current behavior, as saving a 404 response to a jpg file is a bug.
Additional notes. There's currently some differing opinions on how to proceed here. I currently see three options.
Do nothing, potentially add a log message or warning if we detect a non-200 response while downloading a file.-
Benefits: it's easy. -
Downside: you may have files in your filesystem named my_file.jpg that contain the body of a 404 or 403 response. - Delete the file on non-200 responses.
- Benefits: No error messages stored in files with an image/document extension after the migration completes.
- Downside: downloading and then deleting a file from the filesystem. The file will have to exist in the public file system, for at least a short while. Could we accidentally delete a file we don't want to? could we potentially overwrite a good file with a bad one and then delete it? What are the edge cases to handle?
Download files to the temporary file system, verify integrity, then move to the final destination.-
Benefits: safest option, could eliminate files with bad responses from ever being in the filesystem, could also do integrity checks against the file to prevent malicious scripts or XSS attacks from being migrated into the filesystem. Currently migrated files are only as safe as your file source. Downsides: potential slowdown of file migrations (which already can take a long time for large filesystems), particularly if the temp directory and the public directory are on different filesystems, which is often likely to be the case.
-
| Comment | File | Size | Author |
|---|
Issue fork drupal-3114887
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 #2
codebymikey commentedComment #3
xjmThis would likely be a minor-only change. Since 8.9.x and 9.0.x are now in beta, I'm moving this to 9.1.x. Thanks!
Comment #9
sluceroI ran into this issue as well even without using the
file_existingflag. The end result of running my file migration was that I had a bunch of image requests producing 404 responses, so the migration rows failed due to theMigrateExceptionbeing thrown, but since the file stream was open already and used directly by Guzzle with thesinkoption the file was created with the invalid response data that came through. This left my file system after the migration full of files with corrupted content even if they weren't represented in thefile_managedtable.The merge request I submitted is intended as a starting place for this issue to prevent invalid files from being left on the filesystem after failed download requests. It simply deletes the created file if an exception is caught from the download request.
For reference, this is a trimmed down example of the migration being used to produce this issue:
Comment #12
sluceroComment #13
mikelutzIt makes sense to not have errors exist in the filesystem as files with image extensions. My gut says rather than deleting the file from the filesystem, we should download the file to the temp directory, and then move it once we know there is no errors. I'll leave at NR for others to comment.
Comment #15
mikelutzThis was discussed a while back in the weekly meeting #3277845: [meeting] Migrate Meeting 2022-05-05 1400Z @heddn pointed out that it's very possible for the temp filesystem and the public file system to be on different disks, so we can't count on the final move being free, and this would definitely half the speed of a file migration in such cases. @heddn was inclined to leave the bad file, and potentially warn/log. I'd prefer to fix it, but I'm always wary of writing code in core that deletes things, even if it is a file we just created. I wouldn't mind some thoughts from a framework manager here, on how important it is that we don't store these responses and whether deleting them after the download is a good and safe idea.
Comment #16
mikelutzSo to recap, three options as I currently see it, though if anyone can think of a 4th, I'd love to hear it.
Comment #17
mikelutzComment #18
mikelutzComment #19
mikelutzDiscussed this with @alexpott. tldr; We should delete the file on a guzzle exception since we created it above. Saving a 404 response as a .jpg file is a bug, we don't need any BC compatibility layer to fix it. NW to tighten the exception handling.
@alexpott Could I bug you for your thoughts on what we should do about #3114887: Error responses are stored when using the Download migration process sometime if you possibly get a chance? No rush, it’s not new, I’m just trying to figure out the best thing (if anything) to do, and I could use the opinion of framework manager.
// unnecessarily. We're suppressing fopen() errors because we want to try
// to prepare the directory before we give up and fail.
Page not found
The requested page could not be found.
Participants:
Comment #26
benjifisherI rebased @slucero's branch on the current 11.x and made a new MR.
Comment #27
xurizaemonI support the "delete on error" approach in MR 9785.
Discussed above in 19 is the idea of using a HEAD request to check the file's existence. (I realise I'm responding to a Slack thread from two years ago here, but wanted to put this on record in case the solution turns back from the current MR approach.)
Working recently with Migrate in Islandora and large file ingests (>1GB assets) recently, there's a case where an initial HEAD will not prevent this error: if the file URL is correct and the HEAD returns 200, then retrieval fails for eg timeout or resource limits (server returns a 200 for the second GET, but the content length is incorrect). My recollection is that Guzzle does correctly throw an exception and terminate the stream in this case.
The current behaviour of the Download plugin is that the terminated stream will be written as a partial download when the request exits. This MR looks like it would correctly ensure the failed download is removed.
Comment #28
das-peter commentedJust stumbled over this issue when migrating files.
I'm bold enough to claim this is RTBC:
file_existsis used, no file with unexpected / invalid / incomplete content remains.Comment #29
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #31
benjifisherI rebased on the
mainbranch. The automated tests are passing, so back to RTBC.Comment #32
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #33
benjifisherThere seem to be problems with the bot. See this thread in Slack: https://drupal.slack.com/archives/C079NQPQUEN/p1769810618759899.
I merged the current
mainbranch, then pushed both themainbranch and the feature branch to the issue fork. The CI jobs all pass.Back to RTBC.
Comment #39
godotislateRemoved the "Needs framework manager review" tag because #19 has sign off.
Committed and pushed to main, 11.x, and 11.3.x. It also applied cleanly to 10.6.x, so pushed there as well. Thanks everyone!