Follow-up to #6092. PR #14634 fixes the two provably-spurious cases (shortcode double-extension #14583, and empty-filename URLs like https://example.com/), but the original #6092 report — badge / API URLs such as https://bestpractices.coreinfrastructure.org/projects/1882/badge — is deliberately left untouched.
The reason: Quarto sets default-image-extension as a Pandoc reader option, and Pandoc appends it at parse time to any extensionless source that isn't a data: URI (no URL guard in Pandoc). After parsing, .../badge becomes .../badge.png, which is byte-identical to a user-typed .../badge.png pointing at a real file. There is no way to tell the two apart, and a remote URL cannot be stat-checked offline at render time. So auto-detection is not possible for these.
Two complementary opt-in mechanisms could close the remaining gap:
Remote URLs — explicit per-image opt-out
For remote sources where the user knows there is no extension (badges, image APIs, embeds), an explicit marker lets them assert it:
{.no-img-ext}
A Lua filter would strip exactly one appended default-image-extension suffix when the marker is present, then consume the marker so it does not leak into output. This is additive and does not change the default, so it is safe outside a release-cycle boundary. Today the only escape is default-image-extension: "", which is document/project-wide and cannot be scoped to a single image in a doc that also relies on the multi-format behaviour.
Local files — resolve by existence (#6092 "option C")
For local sources, the extension could be resolved against the filesystem instead of appended unconditionally: if path/figure.{default} does not exist but path/figure does, use the extensionless file.
This is the long-term direction discussed in #6092. It needs care:
- Path resolution must use the correct base (project dir, resource paths) — a false "doesn't exist" would strip a working extension and silently regress documents that render fine today.
- Multi-format targets generated later in the pipeline will not exist when the filter runs, so the strip condition must be conservative (only when the default-extension target is provably absent and the extensionless file provably present).
Per the discussion in #6092, changing this default-adjacent behaviour should land at the start of a release cycle.
Related: #6092, #14583, #14634.
Follow-up to #6092. PR #14634 fixes the two provably-spurious cases (shortcode double-extension #14583, and empty-filename URLs like
https://example.com/), but the original #6092 report — badge / API URLs such ashttps://bestpractices.coreinfrastructure.org/projects/1882/badge— is deliberately left untouched.The reason: Quarto sets
default-image-extensionas a Pandoc reader option, and Pandoc appends it at parse time to any extensionless source that isn't adata:URI (no URL guard in Pandoc). After parsing,.../badgebecomes.../badge.png, which is byte-identical to a user-typed.../badge.pngpointing at a real file. There is no way to tell the two apart, and a remote URL cannot be stat-checked offline at render time. So auto-detection is not possible for these.Two complementary opt-in mechanisms could close the remaining gap:
Remote URLs — explicit per-image opt-out
For remote sources where the user knows there is no extension (badges, image APIs, embeds), an explicit marker lets them assert it:
A Lua filter would strip exactly one appended
default-image-extensionsuffix when the marker is present, then consume the marker so it does not leak into output. This is additive and does not change the default, so it is safe outside a release-cycle boundary. Today the only escape isdefault-image-extension: "", which is document/project-wide and cannot be scoped to a single image in a doc that also relies on the multi-format behaviour.Local files — resolve by existence (#6092 "option C")
For local sources, the extension could be resolved against the filesystem instead of appended unconditionally: if
path/figure.{default}does not exist butpath/figuredoes, use the extensionless file.This is the long-term direction discussed in #6092. It needs care:
Per the discussion in #6092, changing this default-adjacent behaviour should land at the start of a release cycle.
Related: #6092, #14583, #14634.