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 = "
[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"}); }); });