diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af2084c6..63c985ff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### 1.2.9: Maintenance Release + +Editor + + - Sanitize node type names when displaying in notifications + - Sanitize branch name before displaying in notification message + +Runtime + + - Handle more valid language codes when validating lang params Fixes #2856 + ### 1.2.8: Maintenance Release Editor diff --git a/package.json b/package.json index 2f3dfa520..206b18bed 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "grunt-simple-nyc": "^3.0.1", "http-proxy": "1.18.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "1.2.7", + "marked": "2.0.0", "minami": "1.2.3", "mocha": "^5.2.0", "node-red-node-test-helper": "^0.2.6", diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js index 058053a29..668075eba 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js @@ -33,6 +33,9 @@ module.exports = { }) } else { opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; + } runtimeAPI.nodes.getNodeConfigs(opts).then(function(configs) { res.send(configs); }) @@ -92,6 +95,9 @@ module.exports = { }) } else { opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; + } runtimeAPI.nodes.getNodeConfig(opts).then(function(result) { return res.send(result); }).catch(function(err) { @@ -161,6 +167,9 @@ module.exports = { lang: req.query.lng, req: apiUtils.getRequestLogObject(req) } + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; + } runtimeAPI.nodes.getModuleCatalog(opts).then(function(result) { res.json(result); }).catch(function(err) { @@ -175,6 +184,9 @@ module.exports = { lang: req.query.lng, req: apiUtils.getRequestLogObject(req) } + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; + } runtimeAPI.nodes.getModuleCatalogs(opts).then(function(result) { res.json(result); }).catch(function(err) { diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js index f9453f55b..3d55f98bc 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js @@ -41,7 +41,7 @@ module.exports = { var namespace = req.params[0]; namespace = namespace.replace(/\.json$/,""); var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []); - if (/[^a-z\-\*]/i.test(lang)) { + if (/[^0-9a-z=\-\*]/i.test(lang)) { res.json({}); return; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 888447599..001776417 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -298,7 +298,7 @@ var RED = (function() { "merge-complete": RED._("notification.project.merge-complete") }[msg.action]; loader.end() - RED.notify("

"+message+"

"); + RED.notify($("

").text(message)); RED.sidebar.info.refresh() }); }); @@ -469,7 +469,7 @@ var RED = (function() { }); }); if (addedTypes.length) { - typeList = "

"; + typeList = ""; RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); } loadIconList(); @@ -478,7 +478,7 @@ var RED = (function() { m = msg[i]; info = RED.nodes.removeNodeSet(m.id); if (info.added) { - typeList = ""; + typeList = ""; RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success"); } } @@ -488,12 +488,12 @@ var RED = (function() { info = RED.nodes.getNodeSet(msg.id); if (info.added) { RED.nodes.enableNodeSet(msg.id); - typeList = ""; + typeList = ""; RED.notify(RED._("palette.event.nodeEnabled", {count:msg.types.length})+typeList,"success"); } else { $.get('nodes/'+msg.id, function(data) { appendNodeConfig(data); - typeList = ""; + typeList = ""; RED.notify(RED._("palette.event.nodeAdded", {count:msg.types.length})+typeList,"success"); }); } @@ -501,7 +501,7 @@ var RED = (function() { } else if (topic == "notification/node/disabled") { if (msg.types) { RED.nodes.disableNodeSet(msg.id); - typeList = ""; + typeList = ""; RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success"); } } else if (topic == "notification/node/upgraded") { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 3ff3e7436..4b1064b28 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -31,15 +31,66 @@ RED.palette.editor = (function() { var eventTimers = {}; var activeFilter = ""; - function semVerCompare(A,B) { - var aParts = A.split(".").map(function(m) { return parseInt(m);}); - var bParts = B.split(".").map(function(m) { return parseInt(m);}); - for (var i=0;i<3;i++) { - var j = aParts[i]-bParts[i]; - if (j<0) { return -1 } - if (j>0) { return 1 } + var semverre = /^(?\d+)(\.(?\d+))?(\.(?\d+))?(-(?
[0-9A-Za-z-]+))?(\.(?[0-9A-Za-z-.]+))?$/;
+    var NUMBERS_ONLY = /^\d+$/;
+
+
+	function SemVerPart ( part ) {
+        this.number = 0;
+        this.text = part;
+        if ( NUMBERS_ONLY.test( toe ) )
+        {
+            this.number = parseInt( part );
+            this.type = "N";
+        } else
+        {
+            this.type = part == undefined || part.length < 1 ? "E" : "T";
         }
-        return 0;
+    }
+
+    SemVerPart.prototype.compare = function ( other ) {
+        const types = this.type + other.type;
+
+        switch ( types )
+        {
+            case "EE": return 0;
+
+            case "NT":
+            case "TE":
+            case "EN": return -1;
+
+            case "NN": return this.number - other.number;
+
+            case "TT": return this.text.localeCompare( other.text );
+
+            case "ET":
+            case "TN":
+            case "NE": return 1;
+        }
+    };
+
+    function SemVer ( ver ) {
+        const groups = ver.match( semverre ).groups;
+        this.parts = [ new SemVerPart( groups.major ), new SemVerPart( groups.minor ), new SemVerPart( groups.patch ), new SemVerPart( groups.pre ), new SemVerPart( groups.build ) ];
+    }
+
+    SemVer.prototype.compare = function ( other ) {
+        let result = 0;
+        for ( let i = 0, n = this.parts.length; result == 0 && i < n; i++ )
+        {
+            result = this.parts[ i ].compare( other.parts[ i ] );
+        }
+
+        return result;
+    };
+
+    function semVerCompare ( ver1, ver2 ) {
+        const semver1 = new SemVer( ver1 );
+        const semver2 = new SemVer( ver2 );
+
+        const result = semver1.compare( semver2 );
+
+        return result;
     }
 
     function delayCallback(start,callback) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js
index 7a77217d5..232984246 100755
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js
@@ -1606,6 +1606,11 @@ RED.projects = (function() {
                     done(null,data);
                 },
                 400: {
+                    'credentials_load_failed': function(data) {
+                        dialog.dialog( "close" );
+                        RED.events.emit("project:change", {name:name});
+                        done(null,data);
+                    },
                     '*': done
                 },
             }
diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html
index 7f9c621e4..8691dfbea 100644
--- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html
+++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html
@@ -108,7 +108,7 @@
             toggle: "active",
             visible: function() { return this.tosidebar; },
             onclick: function() {
-                var label = this.name||"debug";
+                var label = RED.utils.sanitize(this.name||"debug");
                 var node = this;
                 activateAjaxCall(node, node.active, function(resp, textStatus, xhr) {
                     var historyEvent = {
diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
index 5e2576491..148bf9e1a 100644
--- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
+++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js
@@ -48,7 +48,8 @@ module.exports = function(RED) {
             else { node.goodtmpl = true; }
             return col;
         }
-        node.template = clean(node.template);
+        var template = clean(node.template);
+        var notemplate = node.template.length === 1 && node.template[0] === '';
         node.hdrSent = false;
 
         this.on("input", function(msg, send, done) {
@@ -58,19 +59,22 @@ module.exports = function(RED) {
             if (msg.hasOwnProperty("payload")) {
                 if (typeof msg.payload == "object") { // convert object to CSV string
                     try {
+                        if (!(notemplate && (msg.hasOwnProperty("parts") && msg.parts.hasOwnProperty("index") && msg.parts.index > 0))) {
+                            template = clean(node.template);
+                        }
                         var ou = "";
                         if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; }
                         if (node.hdrout !== "none" && node.hdrSent === false) {
-                            if ((node.template.length === 1) && (node.template[0] === '')) {
+                            if ((template.length === 1) && (template[0] === '')) {
                                 if (msg.hasOwnProperty("columns")) {
-                                    node.template = clean(msg.columns || "");
+                                    template = clean(msg.columns || "");
                                 }
                                 else {
-                                    node.template = Object.keys(msg.payload[0]);
+                                    template = Object.keys(msg.payload[0]);
                                 }
                             }
                             // ou += node.template.join(node.sep) + node.ret;
-                            ou += node.template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret;
+                            ou += template.map(v => v.indexOf(node.sep)!==-1 ? '"'+v+'"' : v).join(node.sep) + node.ret;
                             if (node.hdrout === "once") { node.hdrSent = true; }
                         }
                         for (var s = 0; s < msg.payload.length; s++) {
@@ -89,10 +93,10 @@ module.exports = function(RED) {
                                 ou += msg.payload[s].join(node.sep) + node.ret;
                             }
                             else {
-                                if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) {
-                                    node.template = clean(msg.columns || "")//.split(","));
+                                if ((template.length === 1) && (template[0] === '') && (msg.hasOwnProperty("columns"))) {
+                                    template = clean(msg.columns || "")//.split(","));
                                 }
-                                if ((node.template.length === 1) && (node.template[0] === '')) {
+                                if ((template.length === 1) && (template[0] === '')) {
                                     /* istanbul ignore else */
                                     if (tmpwarn === true) { // just warn about missing template once
                                         node.warn(RED._("csv.errors.obj_csv"));
@@ -118,12 +122,12 @@ module.exports = function(RED) {
                                     ou = ou.slice(0,-1) + node.ret;
                                 }
                                 else {
-                                    for (var t=0; t < node.template.length; t++) {
-                                        if (node.template[t] === '') {
+                                    for (var t=0; t < template.length; t++) {
+                                        if (template[t] === '') {
                                             ou += node.sep;
                                         }
                                         else {
-                                            var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+node.template[t]+"']"));
+                                            var p = RED.util.ensureString(RED.util.getMessageProperty(msg,"payload["+s+"]['"+template[t]+"']"));
                                             /* istanbul ignore else */
                                             if (p === "undefined") { p = ""; }
                                             if (p.indexOf(node.quo) !== -1) { // add double quotes if any quotes
@@ -141,7 +145,7 @@ module.exports = function(RED) {
                             }
                         }
                         msg.payload = ou;
-                        msg.columns = node.template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
+                        msg.columns = template.map(v => v.indexOf(',')!==-1 ? '"'+v+'"' : v).join(',');
                         if (msg.payload !== '') { send(msg); }
                         done();
                     }
diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
index db4ed32a5..20a4c2a7b 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
@@ -94,7 +94,7 @@ var api = module.exports = {
     getNodeConfig: async function(opts) {
         var id = opts.id;
         var lang = opts.lang;
-        if (/[^a-z\-\*]/i.test(opts.lang)) {
+        if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
             reject(new Error("Invalid language: "+opts.lang));
             return
         }
@@ -120,11 +120,11 @@ var api = module.exports = {
     * @memberof @node-red/runtime_nodes
     */
     getNodeConfigs: async function(opts) {
-        if (/[^a-z\-\*]/i.test(opts.lang)) {
+        runtime.log.audit({event: "nodes.configs.get"}, opts.req);
+        if (/[^0-9a-z=\-\*]/i.test(opts.lang)) {
             reject(new Error("Invalid language: "+opts.lang));
             return
         }
-        runtime.log.audit({event: "nodes.configs.get"}, opts.req);
         return runtime.nodes.getNodeConfigs(opts.lang);
     },
 
@@ -382,7 +382,7 @@ var api = module.exports = {
     getModuleCatalogs: async function(opts) {
         var namespace = opts.module;
         var lang = opts.lang;
-        if (/[^a-z\-\*]/i.test(lang)) {
+        if (/[^0-9a-z=\-\*]/i.test(lang)) {
             reject(new Error("Invalid language: "+lang));
             return
         }
@@ -416,7 +416,7 @@ var api = module.exports = {
     getModuleCatalog: async function(opts) {
         var namespace = opts.module;
         var lang = opts.lang;
-        if (/[^a-z\-\*]/i.test(lang)) {
+        if (/[^0-9a-z=\-\*]/i.test(lang)) {
             reject(new Error("Invalid language: "+lang));
             return
         }
diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
index 343739626..d7eef2b38 100644
--- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
+++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js
@@ -428,6 +428,12 @@ class Flow {
             reportingNode = node;
         }
         if (!muteStatusEvent) {
+            if (statusMessage.hasOwnProperty("text") && typeof(statusMessage.text !== "string")) {
+                try {
+                    statusMessage.text = statusMessage.text.toString();
+                }
+                catch(e) {}
+            }
             events.emit("node-status",{
                 id: node.id,
                 status:statusMessage
diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js
index 4c0d6f00b..2c113fc0e 100644
--- a/test/nodes/core/parsers/70-CSV_spec.js
+++ b/test/nodes/core/parsers/70-CSV_spec.js
@@ -715,6 +715,24 @@ describe('CSV node', function() {
             });
         });
 
+        it('should be able to include column names as first row, and missing properties', function(done) {
+            var flow = [ { id:"n1", type:"csv", hdrout:true, ret:"\r\n", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    try {
+                        msg.should.have.property('payload', 'col1,col2,col3,col4\r\nH1,H2,H3,H4\r\nA,B,,\r\nA,,C,\r\nA,,,D\r\n');
+                        done();
+                    }
+                    catch(e) { done(e); }
+                });
+                var testJson = [{"col1":"H1","col2":"H2","col3":"H3","col4":"H4"},{"col1":"A","col2":"B"},{"col1":"A","col3":"C"},{"col1":"A","col4":"D"}];
+                n1.emit("input", {payload:testJson});
+            });
+        });
+
         it('should be able to pass in column names', function(done) {
             var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] },
                 {id:"n2", type:"helper"} ];
@@ -736,9 +754,27 @@ describe('CSV node', function() {
                     catch(e) { done(e); }
                 });
                 var testJson = [{ d: 1, b: 3, c: 2, a: 4 }];
-                n1.emit("input", {payload:testJson, columns:"a,,b,a"});
-                n1.emit("input", {payload:testJson});
-                n1.emit("input", {payload:testJson});
+                n1.emit("input", {payload:testJson, columns:"a,,b,a", parts:{index:0}});
+                n1.emit("input", {payload:testJson, parts:{index:1}});
+                n1.emit("input", {payload:testJson, parts:{index:2}});
+            });
+        });
+
+        it('should be able to pass in column names - with payload as an array', function(done) {
+            var flow = [ { id:"n1", type:"csv", hdrout:"once", ret:"\r\n", wires:[["n2"]] },
+                {id:"n2", type:"helper"} ];
+            helper.load(csvNode, flow, function() {
+                var n1 = helper.getNode("n1");
+                var n2 = helper.getNode("n2");
+                n2.on("input", function(msg) {
+                    try {
+                        msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n4,,3,4\r\n4,,3,4\r\n');
+                        done()
+                    }
+                    catch(e) { done(e); }
+                });
+                var testJson = { d: 1, b: 3, c: 2, a: 4 };
+                n1.emit("input", {payload:[testJson,testJson,testJson], columns:"a,,b,a"});
             });
         });