ADR-004: Canonical RTE Image src Storage Convention
Summary
The extension persists every RTE image src in canonical
site-root-relative form with a leading slash (/fileadmin/). This
contract is enforced symmetrically on both ends of the data flow: the
storage write path (Image) and the
validator's strict-equality comparison
(Rte). Slashless
(fileadmin/) and protocol-relative (//) forms
are out-of-contract: slashless is detected as a defect and repaired;
protocol-relative is treated as an external CDN reference and passes
through unchanged.
Context
A series of regressions exposed an implicit contract that had never been written down:
- #778 reported that
getfor the Local FAL driver returns slashless (Public Url () fileadmin/) while the RTE stored leading-slash (x /fileadmin/). The validator's naïve string-compare flagged the stored value asx Srcand "fixed" it by stripping the leading slash — silently breaking frontend rendering on every page.Mismatch - The first attempt at #778 (#779) made the comparison tolerant: both slashless and leading-slash were accepted as equivalent. This stopped the over-correction but turned the validator blind to a different defect.
- #837 surfaced that defect: older
upgrade:versions had stripped the leading slash from existing storage.run validate --was the intended repair tool but, after the over-correction in #779, it silently treated the broken value as already-correct.fix - #839 tightened the validator to strict equality, restoring repair.
- #840 closed the loop:
Imagewas still producing slashless on save in some paths, which meant fresh inserts would be flagged + rewritten by the next validate run. The storage side now normalises to leading-slash for every local input.Tag Builder:: make Relative Src
The decision below codifies the convention so it does not get re-broken.
Decision Drivers
- TYPO3 v12+ does not emit ``<base href>``.
config.was deprecated and removed. Modern TYPO3 uses site configuration'sbase URL basekey for routing; the rendered HTML carries no<base href>tag. A browser therefore resolves slashlesssrcagainst the current page URL — broken on every non-root page.- Storage and rendering must be decoupled cleanly.
- The storage convention should be independent of where the install is
served from (root vs subpath). TYPO3's standard mechanism for this
is
config.: a leading-slash storage value is prefixed at render time. This keeps every install layout identical at the database level.abs Ref Prefix - Two write paths must not disagree.
- The editor saves through
Rte➜Image Processor Image. The validator/listener writes throughTag Builder:: make Relative Src RteandImage Reference Validator:: fix () Update. If these two paths produce different canonical forms, every save-then-validate cycle creates spurious rewrites.Image References - External references must be preserved bit-for-bit.
- Protocol-relative (
//), scheme URLs (cdn. example. com/... http://,https://,data:,mailto:) reference assets outside the site's FAL. The contract must not coerce them into a site-root-relative form.
Decision
- Canonical storage form. Every local image
srcis persisted with a leading slash:/fileadmin/.... Slashless storage (fileadmin/...) is treated as a defect and repaired to the canonical form. - External references pass through unchanged. Scheme URLs and
protocol-relative URLs are detected via the RFC 3986 scheme grammar
pattern
^(PHP PCRE form:(?: [a- z] [a- z0- 9+.\-]*: |//) #…#i) and returned as-is. - Write paths agree. Both
Imageand the validator's repair path (Tag Builder:: make Relative Src normalize➜Public Url src➜Matches Public Url apply) produce the same canonical leading-slash output.Fixes - Subpath installs use ``config.absRefPrefix``. The prefix is prepended at render time by TYPO3 Core. Storage stays canonical across root and subpath installs. See Frontend Issues for setup.
- Empty ``siteUrl`` is a safety valve. When the editor save path
is invoked without a resolved site URL (CLI, certain test contexts),
makereturns the input unchanged. This prevents accidental rewrites in contexts where the site identity is unknown.Relative Src
Worked Example: Subpath Install
Given a TYPO3 instance served at https:// with
config. configured:
| Pipeline stage | Value |
|---|---|
Editor JS url produces | /fileadmin/ |
Editor save → make stores | /fileadmin/ |
Database column tt_ holds | <img src="/ |
Rendered HTML (after abs) | <img src="/~user/ |
Validator normalize expects | /fileadmin/ |
Rte no-op | (storage already matches) |
The storage column is identical to a site-root install. The subpath only appears in the rendered HTML, applied by TYPO3 Core's render chain. The validator therefore stays site-agnostic — it does not need to know about the subpath at all.
JavaScript / PHP normalisation parity
The CKEditor-side helper url
(Resources/) produces the
same leading-slash form when inserting a new image, so the PHP
make normalises a value that already arrives in the
canonical shape on the editor save path. The PHP rewrite covers the
defence-in-depth case: server-side imports, pastes from other editors,
and any callers that bypass the JS helper still produce canonical
storage.
Out of Scope
- Emitting ``<base href>``. Removed from TYPO3 Core in v9.5 and never restored; the extension does not attempt to bring it back.
- Sub-path rendering at the HTML layer. Delegated to
config., a standard TYPO3 mechanism. The extension does not duplicate this functionality.abs Ref Prefix - Per-site validation context. The validator does not currently
inject
siteto compute per-site expected forms. The contract above makes this unnecessary: storage is uniform across sites.Path - Path canonicalisation.
makedoes not collapseRelative Src ..or.segments. The FAL UID round-trip plus the validator's strict-equality check (Rte) reject anyImage Reference Validator:: src Matches Public Url srcthat does not match a real file's normalised public URL, so a smuggled/../../cannot point at content that the FAL would not have served anyway. Whitespace is trimmed up-front to prevent WHATWG-URL bypass of the scheme-grammar guard.etc/ passwd - HTML output escaping.
makereturns raw text; the caller (Relative Src Image, ultimately Fluid) is responsible for HTML-attribute escaping. See theTag Builder:: build () @securityblock on the method docblock.
Consequences
Positive
- A single storage convention across root and subpath installs.
- The validator's strict-equality rule is correct for every layout.
- Save-then-validate cycles are stable:
validateafter a fresh save flags nothing. - External CDN and
data:URIs are explicitly preserved.
Negative
- Subpath operators with pre-existing slashless storage (from older
upgrade:versions) must runrun rte_once to migrate. See Image Reference Validation.ckeditor_ image: validate -- fix -- table=tt_ content - Operators who run a subpath install without
config.configured will see broken images. This is the same broken state as before #840; the convention now makes it explicit and diagnosable.abs Ref Prefix makeis named for what it used to do (strip the site URL); it now also normalises. The name is retained for backwards compatibility of the publicRelative Src Image.Tag Builder Interface