Skip to content

Commit 0781e27

Browse files
committed
fix(image-processor): support percent-encoded data URIs
An <img src="data:image/svg+xml,%3Csvg..."> made dataURItoBlob call atob on a payload that is not base64, so the editor threw InvalidCharacterError out of the change handler on every keystroke. dataURItoBlob now decodes percent-encoded payloads as well, the local copy of it in the plugin is gone, and a data URI we cannot decode leaves the image untouched instead of breaking the editor.
1 parent fa387a0 commit 0781e27

3 files changed

Lines changed: 57 additions & 33 deletions

File tree

src/modules/uploader/helpers/data-uri-to-blob.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@
99
*/
1010

1111
/**
12-
* Convert dataURI to Blob
12+
* Convert dataURI to Blob. Both base64 and percent-encoded payloads are
13+
* supported — `data:image/svg+xml,%3Csvg...` is as valid as
14+
* `data:image/png;base64,...`
1315
*/
1416
export function dataURItoBlob(dataURI: string): Blob {
15-
// convert base64 to raw binary data held in a string
16-
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
17-
18-
const byteString: string = atob(dataURI.split(',')[1]),
19-
// separate out the mime component
20-
mimeString: string = dataURI.split(',')[0].split(':')[1].split(';')[0],
21-
// write the bytes of the string to an ArrayBuffer
17+
const [header, ...rest] = dataURI.split(','),
18+
payload = rest.join(','),
19+
mimeString: string = header.split(':')[1]?.split(';')[0] ?? '',
20+
byteString: string = /;base64/i.test(header)
21+
? atob(payload)
22+
: decodeURIComponent(payload),
2223
ab: ArrayBuffer = new ArrayBuffer(byteString.length),
2324
ia: Uint8Array = new Uint8Array(ab);
2425

src/plugins/image-processor/image-processor.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,41 @@ describe('Image processor plugin', () => {
8686
expect(area.value).does.not.include('blob:');
8787
});
8888
});
89+
90+
describe('Percent-encoded data URI', () => {
91+
const SVG_URI =
92+
'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%221%22%20height%3D%221%22%3E%3C%2Fsvg%3E';
93+
94+
it('Should convert it to a blob: URL without throwing on atob', async () => {
95+
const editor = getJodit({
96+
history: { timeout: 0 }
97+
});
98+
99+
editor.value = '<p><img alt="" src="' + SVG_URI + '"></p>';
100+
editor.e.fire('change', editor.value, '');
101+
await delay(150);
102+
103+
expect(editor.editor.querySelector('img').src).to.match(/^blob:/);
104+
expect(editor.value).to.include(SVG_URI);
105+
106+
editor.destruct();
107+
});
108+
109+
it('Should keep an undecodable data URI as is', async () => {
110+
const editor = getJodit({
111+
history: { timeout: 0 }
112+
});
113+
114+
const broken = 'data:image/svg+xml,%%%';
115+
116+
editor.value = '<p><img alt="" src="' + broken + '"></p>';
117+
editor.e.fire('change', editor.value, '');
118+
await delay(150);
119+
120+
expect(editor.editor.querySelector('img').src).to.include('%%%');
121+
expect(editor.value).to.include(broken);
122+
123+
editor.destruct();
124+
});
125+
});
89126
});

src/plugins/image-processor/image-processor.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { cached, debounce, watch } from 'jodit/core/decorators';
1616
import { pluginSystem } from 'jodit/core/global';
1717
import { $$, dataBind } from 'jodit/core/helpers';
1818
import { Plugin } from 'jodit/core/plugin';
19+
import { dataURItoBlob } from 'jodit/modules/uploader/helpers/data-uri-to-blob';
1920

2021
import './config';
2122

@@ -116,8 +117,17 @@ function replaceDataURIToBlobUUID(editor: IJodit, elm: HTMLImageElement): void {
116117
return;
117118
}
118119

119-
const dataUri = elm.src,
120+
const dataUri = elm.src;
121+
122+
let blob: Blob;
123+
124+
try {
120125
blob = dataURItoBlob(dataUri);
126+
} catch {
127+
// A data URI the browser accepts but we cannot decode is not worth
128+
// breaking the editor over — keep the image as it is
129+
return;
130+
}
121131

122132
elm.src = URL.createObjectURL(blob);
123133
editor.e.fire('internalUpdate');
@@ -132,28 +142,4 @@ function replaceDataURIToBlobUUID(editor: IJodit, elm: HTMLImageElement): void {
132142
editor.buffer.set(JODIT_IMAGE_BLOB_ID, list);
133143
}
134144

135-
// https://stackoverflow.com/a/12300351
136-
function dataURItoBlob(dataURI: string): Blob {
137-
// convert base64 to raw binary data held in a string
138-
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
139-
const byteString = atob(dataURI.split(',')[1]);
140-
141-
// separate out the mime component
142-
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
143-
144-
// write the bytes of the string to an ArrayBuffer
145-
const ab = new ArrayBuffer(byteString.length);
146-
147-
// create a view into the buffer
148-
const ia = new Uint8Array(ab);
149-
150-
// set the bytes of the buffer to the correct values
151-
for (let i = 0; i < byteString.length; i++) {
152-
ia[i] = byteString.charCodeAt(i);
153-
}
154-
155-
// write the ArrayBuffer to a blob, and you're done
156-
return new Blob([ab], { type: mimeString });
157-
}
158-
159145
pluginSystem.add('imageProcessor', imageProcessor);

0 commit comments

Comments
 (0)