Reviewed & tested by the community
Project:
File (Field) Paths
Version:
8.x-1.x-dev
Component:
Code
Priority:
Normal
Category:
Bug report
Assigned:
Unassigned
Reporter:
Created:
15 Mar 2022 at 12:19 UTC
Updated:
8 Jul 2026 at 06:58 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
sleepingmonkHere's a patch that checks for the private scheme and uses `system/files` if it finds it, otherwise it uses the wrapper directory path.
Comment #3
c-logemannI can confirm this error. I successfully tested the #2 path. This a good fix which means the uri of the new file is now a private path. But in my test I got "/system/files?file=" redirect paths. Maybe this is based on a missing config which I didn't found yet.
Comment #4
solideogloria commentedIs this still an issue?
Comment #5
volegerYes, looks like the issue is outdated, needs confirmation that the problem persists.
Comment #8
decipheredConfirmed this still reproduces on 8.x-1.x. The root cause is in
Redirect::getPath(): it callsLocalStream::getDirectoryPath()to resolve a file URI to a redirect destination. Forpublic://that happens to be web-accessible, but forprivate://it returns the raw filesystem base path (Settings::get('file_private_path')) rather than a route Drupal can actually serve. In simple terms, the redirect points at a path on disk instead of/system/files/..., which matches what's reported here and what c-logemann saw with the 2022 patch in comment #2.I've pushed a Kernel test to a fork branch (
feature/3269636-private_redirect_path) that reproduces this directly: it asserts a redirect to aprivate://destination resolves to the/system/files/...route, and currently fails against the raw filesystem path instead. Two other cases pass already: thepublic://happy path, and the existing duplicate-redirect pre-check.On that note, while writing the duplicate-redirect test I found a second, separate bug: the pre-check in
createRedirect()hashes the destination path, but the redirect entity's ownpreSave()always recomputes its stored hash from the source path. The two never match for a real redirect, so callingcreateRedirect()twice with the same source and destination throws an uncaught database exception instead of being skipped as intended. Not filing that separately yet, just noting it here since it surfaced from the same investigation.Comment #11
decipheredPushed the fix to the same fork branch (
feature/3269636-private_redirect_path).Redirect::getPath()was unconditionally callingLocalStream::getDirectoryPath(), which only happens to be web-accessible forpublic://. The fix keeps that behaviour forpublic://only; every other scheme (private://,temporary://, etc.) now resolves through the stream wrapper's owngetExternalUrl(), which is what actually builds thesystem.private_file_downloadroute forprivate://.The Kernel test added in the first MR now passes against this.
Ready for review.
Comment #12
anybodyThanks @deciphered works like a charm!
I also added a comment / question in https://www.drupal.org/project/filefield_paths/issues/3419618#comment-16... (#3419618: How to move existing files from public to private folder) and would like to understand if this module is able to switch a field that was set "public" before to "private" and move over all its files to private (making them safe).
If yes, I think this feature should be described on the module page.