From f19d29cd64d4d784618c64cbd095223e79861b4b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 30 Jan 2026 08:18:17 +0100 Subject: [PATCH] add json validation to file inputs in UI and minify before upload (#5248) * also updated edit.htm to do the same --- wled00/data/common.js | 20 +++++++++++++++----- wled00/data/edit.htm | 30 +++++++++++++----------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index 5f73c946..a6223daa 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -137,16 +137,26 @@ function showToast(text, error = false) { x.style.animation = 'none'; timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); } -function uploadFile(fileObj, name) { +async function uploadFile(fileObj, name, callback) { + let file = fileObj.files?.[0]; // get first file, "?"" = optional chaining in case no file is selected + if (!file) { callback?.(false); return; } + if (/\.json$/i.test(name)) { // same as name.toLowerCase().endsWith('.json') + try { + const minified = JSON.stringify(JSON.parse(await file.text())); // validate and minify JSON + file = new Blob([minified], { type: file.type || "application/json" }); + } catch (err) { + if (!confirm("JSON invalid. Continue?")) { callback?.(false); return; } + // proceed with original file if invalid but user confirms + } + } var req = new XMLHttpRequest(); - req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)}); - req.addEventListener('error', function(e){showToast(e.stack,true);}); + req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400); if(callback) callback(this.status < 400);}); + req.addEventListener('error', function(e){showToast("Upload failed",true); if(callback) callback(false);}); req.open("POST", "/upload"); var formData = new FormData(); - formData.append("data", fileObj.files[0], name); + formData.append("data", file, name); req.send(formData); fileObj.value = ''; - return false; } // connect to WebSocket, use parent WS or open new, callback function gets passed the new WS object function connectWs(onOpen) { diff --git a/wled00/data/edit.htm b/wled00/data/edit.htm index 2606e4e5..31a51e23 100644 --- a/wled00/data/edit.htm +++ b/wled00/data/edit.htm @@ -134,6 +134,7 @@ loadFiles('common.js', -1, () => { }); }); }); + var QueuedRequester = function(){ this.q=[]; this.r=false; this.x=null; } QueuedRequester.prototype = { _request: function(req){ @@ -432,7 +433,7 @@ function createEditor(element,file){ // Check filename from text field or current file var pathField = gId("filepath"); var filename = (pathField && pathField.value) ? pathField.value : currentFile; - aceEditor.session.setMode(filename && filename.toLowerCase().endsWith('.json') ? "ace/mode/json" : "ace/mode/text"); + aceEditor.session.setMode(filename && (/\.json$/i.test(filename)) ? "ace/mode/json" : "ace/mode/text"); // same as filename.toLowerCase().endsWith('.json') } // Try to initialize Ace editor if available @@ -488,7 +489,7 @@ function createEditor(element,file){ var filename = pathField ? pathField.value : currentFile; var border = "2px solid #333"; - if (filename && filename.toLowerCase().endsWith('.json')) { + if (filename && (/\.json$/i.test(filename))) { // same as filename.toLowerCase().endsWith('.json') try { JSON.parse(ta.value); } catch(e) { @@ -499,23 +500,19 @@ function createEditor(element,file){ }; function saveFile(filename,data){ - var finalData = data; - // Minify JSON files before upload - if (filename.toLowerCase().endsWith('.json')) { + var outdata = data; + if (/\.json$/i.test(filename)) { // same as filename.toLowerCase().endsWith('.json') try { - finalData = JSON.stringify(JSON.parse(data)); + outdata = JSON.stringify(JSON.parse(data)); // validate and minify } catch(e) { - alert("Invalid JSON! Please fix syntax."); + alert("Invalid JSON! Please fix."); return; } } - var fd=new FormData(); - fd.append("file",new Blob([finalData],{type:"text/plain"}),filename); - req.add("POST","/upload",fd,function(st,resp){ - if (st!=200) alert("ERROR "+st+": "+resp); - else { - showToast("File saved"); + uploadFile({files: [new Blob([outdata], {type:"text/plain"})]}, filename, function(s) { + if(s) { refreshTree(); + loadFile(filename); // (re)load if saved successfully to update formating or show file content } }); } @@ -526,9 +523,9 @@ function createEditor(element,file){ gId("preview").style.display="none"; gId("editor").style.display="flex"; if (st==200) { - if (filename.toLowerCase().endsWith('.json')) { + if ((/\.json$/i.test(filename))) { // same as filename.toLowerCase().endsWith('.json') try { - setContent(filename.toLowerCase().includes('ledmap') ? prettyLedmap(resp) : JSON.stringify(JSON.parse(resp), null, 2)); + setContent(/ledmap/i.test(filename) ? prettyLedmap(resp) : JSON.stringify(JSON.parse(resp), null, 2)); // pretty-print ledmap files (i.e. if file name includes "ledmap" case-insensitive) } catch(e) { setContent(resp); } @@ -555,8 +552,7 @@ function createEditor(element,file){ } if (!fn.startsWith("/")) fn = "/" + fn; currentFile = fn; // Update current file - saveFile(fn, getContent()); - loadFile(fn); + saveFile(fn, getContent()) }, loadText:function(fn){ currentFile=fn;