Problem/Motivation
UrlToEntity::findEntityIdByUrl() lowercases the entire URL. Hostnames are case-insensitive, but managed-file paths are not. With Drupal 11.4.4 and Entity Usage 5.0.1, a file whose URI is public://example/MixedCase.pdf is not found by the resulting lookup for public://example/mixedcase.pdf. Current 5.x source has the same normalization.
An ordinary HTML hyperlink in a saved draft therefore produces no html_link reference, although the exact file URL returns 200. Consumers of usage data may consequently delete a still-linked file: Media File Delete's Entity Usage integration can remove the file during bulk media deletion after removing the media's own references.
This differs from Media File Delete's duplicate media-usage issue #3538455. Related Entity Usage issues #3521603 and #3537701 address public-file patterns, not whole-URL lowercasing; their fixes do not resolve this reproduction.
Steps to reproduce
- On a disposable local site, enable Entity Usage with nodes as sources, files as targets, and HTML links enabled.
- Create a managed PDF with URI
public://example/MixedCase.pdf. Verify the stored filename retains capitalization. - Save an unpublished node with a formatted body containing a plain anchor, without media embedding or LinkIt metadata:
<a href="http://localhost/sites/default/files/example/MixedCase.pdf">PDF</a>. - Verify the exact URL returns 200.
- Call
findEntityIdByUrl()on theDrupal\entity_usage\UrlToEntityInterfaceservice and inspect the file's Entity Usage sources. - Observe NULL resolution and no node/body hyperlink source. A wholly lowercase directory/filename control resolves and tracks correctly.
Optional integration reproduction: create Document media referencing that disposable file, then use the actual Media listing to bulk-delete only that media with associated-file deletion selected. The file can be removed although the saved draft still links to it. A single-delete retention message is not sufficient proof, because duplicate media usage can mask the missing hyperlink.
Proposed resolution
- Preserve path and filename capitalization in the shared URL resolver.
- Compare parsed hostnames case-insensitively in SiteDomains; preserve configured and discovered base paths, including public stream-wrapper paths and the legacy domain conversion.
- In the public-file integration, compare scheme and hostname case-insensitively while matching paths case-sensitively. Derive the managed URI from the URL path alone, excluding query/fragment suffixes. Decode path escapes with rawurldecode() so literal plus signs remain plus signs.
- Keep exact URI queries. Files named MixedCase.pdf and mixedcase.pdf must resolve to distinct IDs. Do not rename files or rewrite stored content.
The prepared fix extends the existing kernel file-tracking test with real managed files and saved plain HTML fields. Coverage includes absolute/root-relative/relative links, uppercase hostnames and schemes, encoded spaces/plus signs, query/fragment suffixes, filenames differing only by case, wrong-case directories, external-host rejection, and a non-local wrapper exposing a mixed-case CDN base path. Only the external wrapper URL is stubbed; entity storage, queries and URL resolution are real. Additional focused checks cover SiteDomains and legacy config conversion.
The managed-file regression fails without the fix at MixedDirectory/MixedCase.pdf (NULL resolution) and passes with it. Focused checks: 55 tests, 253 assertions passing. A real local bulk-delete smoke test retained the saved draft's mixed-case file (entity and disk present, HTTP 200), while deleting an unreferenced lowercase control (entity and disk absent, HTTP 404). Retention was backed by actual node/body html_link sources, with no remaining media or core file-usage entries.
Remaining tasks
Review, CI, merge and release. After deployment, rebuild existing usage statistics on each site with drush entity-usage:recreate or /admin/config/entity-usage/batch-update. Avoid file deletion during rebuilds. Installing the fix alone does not repair previously missed references.
User interface changes
None.
API changes
SiteDomains::getInternalUrl() no longer requires lowercased input. It preserves path capitalization and compares hosts case-insensitively. Wrong-case base paths no longer match.
Data model changes
No schema changes.
Disclosure: AI-assisted investigation and coding, verified with executable regression and local integration checks. Examples are synthetic localhost fixtures. This is incorrect reference recognition during authorized deletion, not an identified access-control bypass.
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | entity-usage-3630926-preserve-file-url-case.patch | 16.22 KB | sgalata |
Issue fork entity_usage-3630926
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 #3
sgalata commentedSubmitted MR !253, commit
6871f9497bb2c375e0d3d57ec11da2de7c120abb. Attached a reproducible patch for Entity Usage 5.0.1; it includes the regression tests.The case-sensitive managed-file regression fails without the production fix and passes with it. Final focused URL/file/domain checks pass: 56 tests, 260 assertions, including Unicode hostname case and protocol-relative CDN URLs. Drupal coding-standard checks passed for the changed files. GitLab CI is still running and needs review.
The actual local bulk-delete check retained the plain draft-linked mixed-case file (entity/disk present, HTTP 200), while deleting the unreferenced lowercase control (entity/disk absent, HTTP 404). Retention had genuine node/body html_link sources, with no remaining media/core usage records. Disposable fixtures were removed. Composer applied the patch cleanly to locked 5.0.1.
Rebuild Entity Usage on each environment after deployment and avoid deletion during rebuilds. This addresses case-sensitive reference recognition only, not the separate duplicate-counting issue or all possible deletion paths. AI-assisted work; executable and local integration results verified.