From d1cc3da14d85a45ca20b4f1582271fd9cba6d398 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 21 Dec 2018 08:53:02 +0000 Subject: [PATCH] add permit deny lists to file node --- .../@node-red/nodes/core/storage/50-file.js | 65 +++++++++++++++++-- .../nodes/locales/en-US/messages.json | 3 +- test/nodes/core/storage/50-file_spec.js | 2 +- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/storage/50-file.js b/packages/node_modules/@node-red/nodes/core/storage/50-file.js index 99a539a43..46213fbb9 100644 --- a/packages/node_modules/@node-red/nodes/core/storage/50-file.js +++ b/packages/node_modules/@node-red/nodes/core/storage/50-file.js @@ -16,9 +16,14 @@ module.exports = function(RED) { "use strict"; + var mm = require('micromatch'); var fs = require("fs-extra"); var os = require("os"); var path = require("path"); + //var udir = path.join(RED.settings.userDir,"**"); + + var allowlist = [].concat((RED.settings.fileNodeAllowList || ["**"])); + var blocklist = [].concat(RED.settings.fileNodeBlockList); function FileNode(n) { RED.nodes.createNode(this,n); @@ -34,6 +39,30 @@ module.exports = function(RED) { function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; + if (filename === "") { + node.warn(RED._("file.errors.nofilename")); + return; + } + if (filename !== node.lastfile) { + node.lastfile = filename; + node.blocked = true; + if (fs.existsSync(filename)) { filename = fs.realpathSync(filename); } + + // Always block settings.js + if (filename === path.join(RED.settings.userDir,"settings.js")) { + node.warn(RED._("file.errors.blocked")); + return; + } + if (mm.any(filename, allowlist, {matchBase:true, dot:true})) { + node.blocked = false; + if (mm.any(filename, blocklist, {matchBase:true, dot:true})) { + node.warn(RED._("file.errors.blocked")); + node.blocked = true; + return; + } + } + } + if (node.blocked === true) { node.warn(RED._("file.errors.blocked")); return; } if ((!node.filename) && (!node.tout)) { node.tout = setTimeout(function() { node.status({fill:"grey",shape:"dot",text:filename}); @@ -44,7 +73,8 @@ module.exports = function(RED) { if (filename === "") { node.warn(RED._("file.errors.nofilename")); done(); - } else if (node.overwriteFile === "delete") { + } + else if (node.overwriteFile === "delete") { fs.unlink(filename, function (err) { if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); @@ -56,7 +86,8 @@ module.exports = function(RED) { } done(); }); - } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { + } + else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { var dir = path.dirname(filename); if (node.createDir) { try { @@ -232,13 +263,33 @@ module.exports = function(RED) { this.on("input",function(msg) { var filename = (node.filename || msg.filename || "").replace(/\t|\r|\n/g,''); - if (!node.filename) { - node.status({fill:"grey",shape:"dot",text:filename}); - } + if (filename === "") { node.warn(RED._("file.errors.nofilename")); + return; } - else { + if (filename !== node.lastfile) { + node.lastfile = filename; + node.blocked = true; + filename = fs.realpathSync(filename); + // Always block settings.js + if (filename === path.join(RED.settings.userDir,"settings.js")) { + node.warn(RED._("file.errors.blocked")); + return; + } + if (mm.any(filename, allowlist, {matchBase:true, dot:true})) { + node.blocked = false; + if (mm.any(filename, blocklist, {matchBase:true, dot:true})) { + node.warn(RED._("file.errors.blocked")); + node.blocked = true; + return; + } + } + } + if (node.blocked === true) { node.warn(RED._("file.errors.blocked")); return; } + if (!node.filename) { node.status({fill:"grey",shape:"dot",text:filename}); } + + //else { msg.filename = filename; var lines = Buffer.from([]); var spare = ""; @@ -326,7 +377,7 @@ module.exports = function(RED) { node.send(m); } }); - } + // } }); this.on('close', function() { node.status({}); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index cc9b06d41..6bdac2b61 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -849,7 +849,8 @@ "deletefail": "failed to delete file: __error__", "writefail": "failed to write to file: __error__", "appendfail": "failed to append to file: __error__", - "createfail": "failed to create file: __error__" + "createfail": "failed to create file: __error__", + "blocked": "File or directory blocked" }, "tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process." }, diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index 1493ba165..9f9b2f99e 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -58,7 +58,7 @@ describe('file Nodes', function() { }); it('should write to a file', function(done) { - var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]}, + var flow = [{id:"fileNode1", type:"file", name:"fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":true, wires:[["helperNode1"]]}, {id:"helperNode1", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileNode1");