fix(image): keep data: URI images on markdown round-trip - #9159
fix(image): keep data: URI images on markdown round-trip#9159guillaume-flambard wants to merge 2 commits into
Conversation
Markdown files can legitimately contain base64 images. Both image parse rules excluded img[src^=data:] because the TipTap allowBase64 option defaults to false, so those images never made it into the document and were silently dropped from the file on the next save (nextcloud#9108). Enable allowBase64 on the Image and ImageInline extensions: the file content is the user's own, and the editor is expected to preserve it, not to filter it. Regression test covers both the block (figure) and the inline image path. Signed-off-by: Guillaume Flambard <g.flambard@gmail.com>
1c5f849 to
51a4828
Compare
max-nextcloud
left a comment
There was a problem hiding this comment.
Thanks for tackling this. The approach makes sense to me.
We will need to check if this has any security implications. data: uris may contain other mime types such as text/html or application/javascript.
I hope our CSP have us covered there - but we need to confirm that.
My understanding is that you are only trying to preserve the images in the file - not render them. Is that correct?
|
Hi @max-nextcloud, thanks for looking at this. On your second question first, because it changes the answer to the first one: no, preserving is not the whole intent. These images are meant to render, and the code for that is already there and predates this PR.
On the security side, three things, and one caveat I would rather raise myself. The CSP does cover it. The server default in The src also only ever reaches one sink. In The caveat: TipTap's Tell me which way you prefer and I will push it. |
allowBase64 flips the parse selector to accept any data: URI, with no mime filtering. The CSP already refuses to load a data: script, object or frame, and the src only ever reaches an <img>, so a non image payload cannot execute. This narrows the parse rules anyway, so a malformed mime type never becomes a node in the first place rather than relying on those two properties. The syntax then stays literal text, which keeps the user's characters intact and preserves the intent of nextcloud#9108: nothing is dropped on save, it is simply not promoted to an image whose src could never render. Assisted-by: claude-code:claude-opus-5 Signed-off-by: Guillaume Flambard <g.flambard@gmail.com>
|
Rather than leave you with a question, I pushed the stricter option so you can see both and pick. Both extensions now narrow the permissive branch instead of accepting any data: URI: parseHTML() {
if (!this.options.allowBase64) {
return [{ tag: 'figure img[src]:not([src^="data:"])' }]
}
return [
{ tag: 'figure img[src]:not([src^="data:"])' },
{ tag: 'figure img[src^="data:image/"]' },
]
}
Writing the test for it turned up something worth knowing: the payload is not lost either. A non image data: URI round-trips as escaped literal text, Full suite is green locally, 1583 tests, plus eslint and tsc on the changed files. If you prefer to keep it minimal and rely on the CSP, drop the last commit and the two line version stands on its own. |
Fixes #9108.
Editing a markdown file that contains base64 images (data: URI) deleted those images from the file on the next save. The root cause sits in the image parse rules: both the block
ImageandImageInlineextensions excludeimg[src^=data:]when the TipTapallowBase64option is false, and nothing in this app turns it on. markdown-it parses the data: URI fine, but the img never becomes a document node, so the save writes the file without it. That exclusion is the upstream TipTap default, not a decision this app ever made, and there is nothing in the git history suggesting it was deliberate here.The change enables
allowBase64on both extensions. The file content is the user's own and the editor's job is to preserve it, not to filter it. Rendering already goes through theImageViewnode view, which handles the src as-is.Regression test added in
markdown.spec.js, covering both paths:Both round-trip through the editor byte-identical. Full unit suite: 1582/1582 across 50 files. Before the change the new test fails (the image is dropped), which is the reported data loss.