-
Notifications
You must be signed in to change notification settings - Fork 2
/
lftp.js
347 lines (314 loc) · 11.9 KB
/
lftp.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
module.exports = function(RED) {
"use strict";
var fs = require("fs");
var FTPS = require("ftps");
var Parser = require("parse-listing");
var tmp = require("tmp");
var utils = require("./utils");
function LftpConfigNode(n) {
RED.nodes.createNode(this, n);
this.options = {
host: n.host || "localhost", // required
protocol: n.protocol || "ftp", // Optional, values : 'ftp', 'sftp', 'ftps', ... default: 'ftp'
// protocol is added on beginning of host, ex : sftp://domain.com in this case
port: n.port || 21, // Optional
// port is added to the end of the host, ex: sftp://domain.com:22 in this case
escape: n.escape || true, // optional, used for escaping shell characters (space, $, etc.), default: true
retries: n.retries || 2, // Optional, defaults to 1 (1 = no retries, 0 = unlimited retries)
timeout: n.timeout || 10, // Optional, Time before failing a connection attempt. Defaults to 10
retryInterval: n.retryInterval || 5, // Optional, Time in seconds between attempts. Defaults to 5
retryMultiplier: n.retryMultiplier || 1, // Optional, Multiplier by which retryInterval is multiplied each time new attempt fails. Defaults to 1
requiresPassword: n.requiresPassword || false, // Optional, defaults to true
autoConfirm: false, // Optional, is used to auto confirm ssl questions on sftp or fish protocols, defaults to false
cwd: "", // Optional, defaults to the directory from where the script is executed
additionalLftpCommands: n.additionalLftpCommands || "", // Additional commands to pass to lftp, splitted by ';'
requireSSHKey: false, // Optional, defaults to false, This option for SFTP Protocol with ssh key authentication
sshKeyPath: "/path/id_dsa" // Required if requireSSHKey: true , defaults to empty string, This option for SFTP Protocol with ssh key authentication
};
this.options.username = "";
this.options.password = "";
if (this.credentials && this.credentials.hasOwnProperty("username")) {
this.options.username = this.credentials.username;
}
if (this.credentials && this.credentials.hasOwnProperty("password")) {
this.options.password = this.credentials.password;
}
}
RED.nodes.registerType("lftp-config", LftpConfigNode, {
credentials: {
username: { type: "text" },
password: { type: "password" }
}
});
function LftpCommandNode(n) {
RED.nodes.createNode(this, n);
var node = this;
this.server = n.server;
this.operation = n.operation;
this.filename = n.filename;
this.localFilename = n.localFilename;
this.workdir = n.workdir;
this.savedir = n.savedir;
this.serverConfig = RED.nodes.getNode(this.server);
var statuses = {
active: { fill: "blue", shape: "dot", text: "executing" },
error: { fill: "red", shape: "dot", text: "error" },
blank: {}
};
/**
* Returns true if the reponse is an error, false otherwise
*
* @param {*} err
* @param {*} res
*/
var responseErrorHandler = function(err, res, msg) {
let message = null;
try {
if (err) {
message = err;
} else if (res.error && res.error.toLowerCase().includes("error")) {
// When disk is readonly, the output from lftp goes to stderr.
// so lets filter this by checking if the string contains
// any kind of error.
// try catch in the case that res.error is not a string.
message = res.error;
}
} catch (e) {
// silently fail and continue
}
if (message) {
node.error(message, msg);
node.status(statuses.error);
return true;
} else {
return false;
}
};
this.commands = {};
this.commands.list = function(event, msg) {
var conn = new FTPS(node.serverConfig.options);
conn
.cd(event.workdir)
.ls()
.exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
Parser.parseEntries(res.data, function(err, data) {
if (err) {
node.error(statuses.error);
}
msg.workdir = event.workdir;
msg.payload = data;
node.send(msg);
node.status(statuses.blank);
});
}
});
};
this.commands.get = function(event, msg) {
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var targetFilename = event.localFilename;
var conn = new FTPS(node.serverConfig.options);
conn.cat(filename).exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filedata = res.data;
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
if (targetFilename) {
fs.writeFile(targetFilename, res.data, function(err) {
if (err) {
throw err;
}
node.send(msg);
node.status(statuses.blank);
});
} else {
node.send(msg);
node.status(statuses.blank);
}
}
});
};
this.commands.put = function(event, msg) {
if (!event.filename.length > 0) {
var d = new Date();
var guid = d.getTime().toString();
if (event.fileExtension === "") {
event.fileExtension = ".txt";
}
event.filename = guid + node.fileExtension;
}
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var filedata = msg.payload.filedata || JSON.stringify(msg.payload);
var sourceFilename = event.localFilename;
if (sourceFilename) {
node.debug("putting " + sourceFilename + " directly");
// If we have a sourcefile instead of file data, we can just
// directly give this file to FPTS
var conn = new FTPS(node.serverConfig.options);
conn.put(sourceFilename, filename).exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
node.send(msg);
node.status(statuses.blank);
}
});
} else if (filedata) {
node.debug("putting " + filedata + " temporarily");
// If we don't have a sourcefile, we will have to make a
// temporary file because lftp can't stream data directly.
// This file is temporarily written to disk, put, then
// deleted locally.
tmp.file(function(err, path, fd, cleanupCallback) {
if (err) throw err;
fs.writeFile(path, filedata, function(err) {
if (err) {
cleanupCallback();
throw err;
}
var conn = new FTPS(node.serverConfig.options);
conn.put(path, filename).exec(function(err, res) {
cleanupCallback();
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
node.send(msg);
node.status(statuses.blank);
}
});
});
});
} else {
// Nothing to write!
node.error("nothing to write", msg);
node.status(statuses.error);
}
};
this.commands.delete = function(event, msg) {
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var conn = new FTPS(node.serverConfig.options);
conn.rm(filename).exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
node.send(msg);
node.status(statuses.blank);
}
});
};
this.commands.rmdir = function(event, msg) {
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var conn = new FTPS(node.serverConfig.options);
conn.rmdir(filename).exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
node.send(msg);
node.status(statuses.blank);
}
});
};
this.commands.rmrf = function(event, msg) {
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var conn = new FTPS(node.serverConfig.options);
conn.raw("rm -r -f " + conn._escapeshell(filename));
conn.exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.filename;
msg.payload.filepath = filename;
node.send(msg);
node.status(statuses.blank);
}
});
};
this.commands.move = function(event, msg) {
var filename = utils.addTrailingSlash(event.workdir) + event.filename;
var targetFilename =
utils.addTrailingSlash(event.workdir) + event.targetFilename;
var conn = new FTPS(node.serverConfig.options);
conn.mv(filename, targetFilename).exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.workdir = event.workdir;
msg.payload = {};
msg.payload.filename = event.targetFilename;
msg.payload.filepath = targetFilename;
node.send(msg);
node.status(statuses.blank);
}
});
};
this.commands.raw = function(event, msg) {
var conn = new FTPS(node.serverConfig.options);
if (Array.isArray(msg.payload)) {
for (var i = 0, len = msg.payload.length; i < len; i++) {
conn.raw(msg.payload[i]);
}
} else {
conn.raw(msg.payload);
}
conn.exec(function(err, res) {
if (!responseErrorHandler(err, res, msg)) {
msg.payload = res.data;
node.send(msg);
node.status(statuses.blank);
}
});
};
if (this.server) {
node.on("input", function(msg) {
try {
/**
* flag status immediately
*/
node.status(statuses.active);
/**
* need to ensure all event values can be set via node or msg
* to facilitate the per msg operation functionality
*/
var event = {};
event.operation = node.operation || msg.operation || "";
event.workdir = node.workdir || msg.workdir || "";
event.filename = node.filename || msg.payload.filename || "";
event.targetFilename =
node.targetFilename || msg.payload.targetFilename || "";
event.savedir = node.savedir || msg.savedir || "";
event.localFilename =
node.localFilename ||
msg.localFilename ||
msg.payload.localFilename ||
"";
/**
* set this across the board so downstream processing has the
* canonical last operation
*/
msg.operation = event.operation;
if (event.operation && node.commands[event.operation]) {
node.commands[event.operation](event, msg);
} else {
node.error("invalid operation: " + event.operation, msg);
node.status(statuses.error);
}
} catch (error) {
node.error(error, msg);
node.status(statuses.blank);
}
});
} else {
node.error("missing server configuration");
node.status(statuses.blank);
}
}
RED.nodes.registerType("lftp-command", LftpCommandNode);
};