Clean SVG icons before preview/upload client-side (#7831)
* custom icons * fix: ensures svg are sanitized front side on upload/preview * fix: restore default sidebar icons * chore: remove unrelated icon files --------- Co-authored-by: Sammy Ndabo <sammy.ndabo@evoludata.com>
Sammy Ndabo committed
May 26, 2026 at 17:58 UTC
c0ca2406cf088f7812da1255142a2d530fd0f34a
1 file changed
+10
-2
public/js/ui-components.js
+10
-2
@@ -311,12 +311,20 @@ class IconUploadComponent {
311
try {
312
if (!(/^image\/(svg\+xml|png|jpeg)$/i.test(file.type)) && !(/\.(svg|png|jpg|jpeg)$/i.test(file.name || ''))) { throw new Error('Only SVG, PNG and JPEG icon files are supported.'); }
313
if ((file.size < 4) || (file.size > CUSTOM_ICON_MAX_FILE_SIZE)) { throw new Error('Icon files must be non-empty and ' + (CUSTOM_ICON_MAX_FILE_SIZE / 1048576) + ' MB or smaller.'); }
314
- if (!(/\.(svg)$/i.test(file.name || '') || /^image\/svg\+xml$/i.test(file.type))) {
314
+ var uploadFile = file;
315
+ if (/\.(svg)$/i.test(file.name || '') || /^image\/svg\+xml$/i.test(file.type || '')) {
316
+ // Ensure DOMPurify is loaded
317
+ if ((typeof DOMPurify === 'undefined') || (typeof DOMPurify.sanitize !== 'function')) { throw new Error('Unable to clean SVG icon in this browser.'); }
318
+ // Clean SVG file
319
+ const cleanedSvg = DOMPurify.sanitize(await file.text(), { USE_PROFILES: { svg: true, svgFilters: true } });
320
+ if ((typeof cleanedSvg !== 'string') || (cleanedSvg.search(/<svg[\s>]/i) < 0)) { throw new Error('Invalid SVG icon file.'); }
321
+ uploadFile = new File([cleanedSvg], file.name, { type: 'image/svg+xml', lastModified: file.lastModified });
322
+ } else {
323
const dimensions = await this.getImageDimensions(file);
324
if ((dimensions.width < 1) || (dimensions.height < 1) || (dimensions.width > CUSTOM_ICON_MAX_DIMENSION) || (dimensions.height > CUSTOM_ICON_MAX_DIMENSION)) { throw new Error('PNG/JPEG icon images must be ' + CUSTOM_ICON_MAX_DIMENSION + ' x ' + CUSTOM_ICON_MAX_DIMENSION + ' pixels or smaller.'); }
325
}
326
if (this.options.onUpload) {
319
- const result = await this.options.onUpload(this.iconKey, file);
327
+ const result = await this.options.onUpload(this.iconKey, uploadFile);
328
329
// Show success state
330
button.innerHTML = '<i class="fas fa-check me-2"></i>Success!';