fix(#3121826): add on-demand image style delivery for temporary:// staged files#5
fix(#3121826): add on-demand image style delivery for temporary:// staged files#5Decipher wants to merge 6 commits into
Conversation
|
Warning Review limit reached
More reviews will be available in 18 minutes and 35 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
📝 WalkthroughWalkthroughAdds a new Drupal route and services to serve image style derivatives for files stored in the ChangesTemporary Image Style Delivery
PHP Attribute Modernization
Sequence Diagram(s)sequenceDiagram
participant Browser as Browser
participant LegacyHook as filefield_paths_file_url_alter
participant FileUrlHooks as FileUrlHooks::fileUrlAlter
participant Router as filefield_paths.image_style_temporary route
participant AccessCheck as ImageStyleTemporaryAccessCheck
participant Controller as ImageStyleDownloadController::deliver
Browser->>LegacyHook: file_url_alter(temporary://styles/ffp/temporary/img.jpg)
LegacyHook->>FileUrlHooks: fileUrlAlter($uri)
FileUrlHooks-->>Browser: /filefield_paths/image-style/ffp/temporary?file=filefield_paths/img.jpg&itok=...
Browser->>Router: GET /filefield_paths/image-style/ffp/temporary?file=...&itok=...
Router->>AccessCheck: access(request)
alt file empty, contains .., or outside subdirectory
AccessCheck-->>Router: AccessResult::forbidden()
Router-->>Browser: HTTP 403
else itok invalid
Controller-->>Browser: HTTP 404
else valid
AccessCheck-->>Router: AccessResult::allowed()
Router->>Controller: deliver(image_style=ffp, scheme=temporary)
Controller-->>Browser: HTTP 200 image/jpeg
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
a8f056d to
4da2b52
Compare
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. Thanks for integrating Codecov - We've got you covered ☂️ |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php (1)
100-119: ⚡ Quick winAdd a regression test for
../infilequery param.Current negative-path tests are good, but a traversal-shaped
filevalue (for examplefilefield_paths/../...) should be explicitly asserted as 403 to lock the boundary.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php` around lines 100 - 119, Add a new test method in the FileFieldPathsImageStyleTemporaryTest class that specifically tests path traversal attempts using `../` in the file query parameter. The test should follow the same pattern as testFileOutsideSubdirReturns403 and testEmptyFileParamReturns403, constructing a URL where the file parameter contains a traversal sequence like filefield_paths/../, then call drupalGet with that URL, and assert the response status code is 403 to ensure path traversal attempts are properly blocked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/Access/ImageStyleTemporaryAccessCheck.php`:
- Around line 35-45: The access check in ImageStyleTemporaryAccessCheck needs to
enforce both the temporary scheme and prevent path traversal. First, validate
that the temp_location configuration value starts with the temporary:// scheme
before using it for the prefix check. Additionally, add a check to reject file
paths containing traversal segments like ../ to prevent out-of-scope access
patterns. Both validations should return AccessResult::forbidden() if they fail,
ensuring the str_starts_with check on line 44 cannot be bypassed through path
traversal or non-temporary scheme values.
- Around line 31-48: The ImageStyleTemporaryAccessCheck method has multiple
early return statements that create AccessResult objects without cache metadata.
The early returns at lines 32 and 41 (when file is empty or subdir is invalid)
return AccessResult::forbidden() without any cacheability information, while the
final return statement includes addCacheContexts and addCacheTags calls. Add the
same cache contexts ['url.query_args:file'] and cache tags
['config:filefield_paths.settings'] to all AccessResult returns in the method.
This ensures that all forbidden access decisions are properly cached and
invalidated when the file parameter or configuration changes, preventing stale
cached results.
---
Nitpick comments:
In `@tests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php`:
- Around line 100-119: Add a new test method in the
FileFieldPathsImageStyleTemporaryTest class that specifically tests path
traversal attempts using `../` in the file query parameter. The test should
follow the same pattern as testFileOutsideSubdirReturns403 and
testEmptyFileParamReturns403, constructing a URL where the file parameter
contains a traversal sequence like filefield_paths/../, then call drupalGet with
that URL, and assert the response status code is 403 to ensure path traversal
attempts are properly blocked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2328c92f-e5a5-43ba-b3e3-c13f1abaf323
📒 Files selected for processing (6)
.cspell.jsonfilefield_paths.routing.ymlfilefield_paths.services.ymlsrc/Access/ImageStyleTemporaryAccessCheck.phpsrc/Hook/FileUrlHooks.phptests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/Access/ImageStyleTemporaryAccessCheck.php (1)
48-58:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize
$subdirto match$normalized_filehandling.The
$filepath is normalized on line 53 (leading slashes trimmed), but$subdiris used raw fromgetTarget(). If the configuredtemp_locationhas a trailing slash (e.g.,temporary://filefield_paths/),getTarget()returnsfilefield_paths/, causing the line 58 check to compare againstfilefield_paths//—which always fails, incorrectly denying legitimate requests.Suggested fix
- $subdir = StreamWrapperManager::getTarget($temp_location); + $subdir = trim((string) StreamWrapperManager::getTarget($temp_location), '/'); if (!is_string($subdir) || $subdir === '') {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Access/ImageStyleTemporaryAccessCheck.php` around lines 48 - 58, The $subdir variable obtained from StreamWrapperManager::getTarget() is not normalized like $normalized_file is on line 53. When the configured temp_location has a trailing slash, getTarget() returns a path with a trailing slash (e.g., "filefield_paths/"), which causes the str_starts_with check on line 58 to create a double slash ("filefield_paths//"), always failing the comparison. Normalize $subdir by removing trailing slashes using rtrim($subdir, '/') immediately after retrieving it from getTarget(), ensuring consistent path formatting for the subsequent comparison with $normalized_file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/Access/ImageStyleTemporaryAccessCheck.php`:
- Around line 48-58: The $subdir variable obtained from
StreamWrapperManager::getTarget() is not normalized like $normalized_file is on
line 53. When the configured temp_location has a trailing slash, getTarget()
returns a path with a trailing slash (e.g., "filefield_paths/"), which causes
the str_starts_with check on line 58 to create a double slash
("filefield_paths//"), always failing the comparison. Normalize $subdir by
removing trailing slashes using rtrim($subdir, '/') immediately after retrieving
it from getTarget(), ensuring consistent path formatting for the subsequent
comparison with $normalized_file.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9370a0ed-6eee-4380-aa53-70bf944e1381
📒 Files selected for processing (3)
filefield_paths.modulesrc/Access/ImageStyleTemporaryAccessCheck.phptests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/src/Functional/FileFieldPathsImageStyleTemporaryTest.php
4bc2db5 to
ea168e8
Compare
ea168e8 to
01b6b90
Compare
Missed during the base-branch Rector fix (test: convert PHPUnit docblock annotations to attributes) since this file only exists on this branch.
Summary
Fixes broken image thumbnails on the node edit form when File (Field) Paths
stages uploaded files in
temporary://. Image style derivatives are nowgenerated and served on demand via a dedicated delivery route, so the default
temporary://filefield_pathstemp location works without switching toprivate://.Closes #3121826
Background
When an image field uses File (Field) Paths, uploaded files are staged in a
temporary location (
temporary://filefield_pathsby default) and moved totheir final path only when the entity is saved. Drupal core's image style
delivery controller does not handle
temporary://URIs, so derivative URLsreturn 404 — causing the "broken thumbnail" symptom reported in the issue.
The workaround was to switch
temp_locationtoprivate://filefield_paths,but this requires the private filesystem to be configured and adds access
overhead that isn't needed in most cases.
How it works
hook_file_url_alter()— intercepts derivative URIs matchingtemporary://styles/{style}/temporary/{file}and rewrites them to adedicated route.
/system/files/styles/{image_style}/temporary) —delegates to core's
ImageStyleDownloadController::deliver()withscheme: temporary, so derivative generation anditokvalidation workexactly as they do for public/private files.
_ffp_temp_image_style) — restricts the route tofiles within the configured FFP temp subdirectory, preventing arbitrary
temporary://file access. Whentemp_locationuses a non-temporaryscheme (e.g.
private://), the checker returns neutral so the normalprivate file delivery path applies.
Changes
src/Hook/FileUrlHooks.phphook_file_url_alter()— rewrites temporary derivative URIssrc/Access/ImageStyleTemporaryAccessCheck.phpfilefield_paths.routing.ymlfilefield_paths.services.ymltests/src/Functional/FileFieldPathsImageStyleTemporaryTest.phpTest plan
DRUPAL_VERSION=10 make lintpassesDRUPAL_VERSION=10 make testpassestemporary://filefield_paths— thumbnails appear on node edit formSummary by CodeRabbit
?file=path traversal attempts (rejected with HTTP 403).itokand added PHP override annotations plus legacy hook delegation.