refactor(commands): commands now store all info about themselves
Makes it easier to modify a command
Each command exports an object:
{
directive: string or array of commands that call this handler
handler: function to process the command
syntax: string of how to call the command
description: human readable explaination of command
flags: optional object of flags
}
This commit is contained in:
@@ -61,9 +61,9 @@ __whitelist__ : `[]`
|
||||
__file_format__ : `ls`
|
||||
> Format to use for [file stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) responses (such as with the `LIST` command).
|
||||
Possible values:
|
||||
- `ls` : [bin/ls format](https://cr.yp.to/ftp/list/binls.html)
|
||||
- `ep` : [Easily Parsed LIST format](https://cr.yp.to/ftp/list/eplf.html)
|
||||
- `function` : pass in your own format function, returning a string:
|
||||
`ls` : [bin/ls format](https://cr.yp.to/ftp/list/binls.html)
|
||||
`ep` : [Easily Parsed LIST format](https://cr.yp.to/ftp/list/eplf.html)
|
||||
`function` : pass in your own format function, returning a string:
|
||||
`function (fileStats) { ... }`
|
||||
|
||||
__log__ : `bunyan`
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ftp",
|
||||
"ftp-server",
|
||||
"ftp-srv",
|
||||
"ftp-svr",
|
||||
"ftpd",
|
||||
"server"
|
||||
],
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function () {
|
||||
return this.reply(202);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function ({command} = {}) {
|
||||
const method = _.upperCase(command._[1]);
|
||||
|
||||
switch (method) {
|
||||
case 'TLS': return handleTLS.call(this);
|
||||
case 'SSL': return handleSSL.call(this);
|
||||
default: return this.reply(504);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTLS() {
|
||||
return this.reply(504);
|
||||
}
|
||||
|
||||
function handleSSL() {
|
||||
return this.reply(504);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
const cwd = require('./cwd');
|
||||
|
||||
module.exports = function(args) {
|
||||
args.command._ = [args.command._[0], '..'];
|
||||
return cwd.call(this, args);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../helpers/escape-path');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.chdir(command._[1]))
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return this.reply(250, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.delete) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.delete(command._[1]))
|
||||
.then(() => {
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function () {
|
||||
const registry = require('./registry');
|
||||
const features = Object.keys(registry)
|
||||
.filter(cmd => registry[cmd].hasOwnProperty('feat'))
|
||||
.reduce((feats, cmd) => _.concat(feats, registry[cmd].feat), [])
|
||||
.map(feat => ` ${feat}`);
|
||||
return this.reply(211, 'Extensions supported', ...features, 'END');
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function ({command} = {}) {
|
||||
const registry = require('./registry');
|
||||
const directive = _.upperCase(command._[1]);
|
||||
if (directive) {
|
||||
if (!registry.hasOwnProperty(directive)) return this.reply(502, `Unknown command ${directive}.`);
|
||||
|
||||
const {syntax, help, obsolete} = registry[directive];
|
||||
const reply = _.concat([syntax, help, obsolete ? 'Obsolete' : null]);
|
||||
return this.reply(214, ...reply);
|
||||
} else {
|
||||
const supportedCommands = _.chunk(Object.keys(registry), 5).map(chunk => chunk.join('\t'));
|
||||
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
|
||||
const REGISTRY = require('./registry');
|
||||
|
||||
class FtpCommands {
|
||||
constructor(connection) {
|
||||
console.log(REGISTRY)
|
||||
this.connection = connection;
|
||||
this.registry = require('./registry');
|
||||
this.previousCommand = {};
|
||||
this.blacklist = _.get(this.connection, 'server.options.blacklist', []).map(cmd => _.upperCase(cmd));
|
||||
this.whitelist = _.get(this.connection, 'server.options.whitelist', []).map(cmd => _.upperCase(cmd));
|
||||
@@ -14,7 +16,7 @@ class FtpCommands {
|
||||
const log = this.connection.log.child({command});
|
||||
log.trace('Handle command');
|
||||
|
||||
if (!this.registry.hasOwnProperty(command.directive)) {
|
||||
if (!REGISTRY.hasOwnProperty(command.directive)) {
|
||||
return this.connection.reply(402, 'Command not allowed');
|
||||
}
|
||||
|
||||
@@ -26,8 +28,9 @@ class FtpCommands {
|
||||
return this.connection.reply(502, 'Command not whitelisted');
|
||||
}
|
||||
|
||||
const commandRegister = this.registry[command.directive];
|
||||
if (!commandRegister.no_auth && !this.connection.authenticated) {
|
||||
const commandRegister = REGISTRY[command.directive];
|
||||
const commandFlags = _.get(commandRegister, 'flags', {});
|
||||
if (!commandFlags.no_auth && !this.connection.authenticated) {
|
||||
return this.connection.reply(530);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
const getFileStat = require('../helpers/file-stat');
|
||||
|
||||
// http://cr.yp.to/ftp/list.html
|
||||
// http://cr.yp.to/ftp/list/eplf.html
|
||||
module.exports = function ({log, command, previous_command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const simple = command.directive === 'NLST';
|
||||
|
||||
let dataSocket;
|
||||
const directory = command._[1] || '.';
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.list(directory)))
|
||||
.then(files => {
|
||||
const getFileMessage = (file) => {
|
||||
if (simple) return file.name;
|
||||
return getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
};
|
||||
|
||||
const fileList = files.map(file => {
|
||||
const message = getFileMessage(file);
|
||||
return {
|
||||
raw: true,
|
||||
message,
|
||||
socket: dataSocket
|
||||
};
|
||||
})
|
||||
return this.reply(150)
|
||||
.then(() => this.reply(...fileList));
|
||||
})
|
||||
.then(() => {
|
||||
return this.reply(226, 'Transfer OK');
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(err.code || 451, err.message || 'No directory');
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const when = require('when');
|
||||
const format = require('date-fns/format');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(command._[1]))
|
||||
.then(fileStat => {
|
||||
const modificationTime = format(fileStat.mtime, 'YYYYMMDDHHmmss.SSS');
|
||||
return this.reply(213, modificationTime)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../helpers/escape-path');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.mkdir(command._[1]))
|
||||
.then(dir => {
|
||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function ({command} = {}) {
|
||||
return this.reply(command._[1] === 'S' ? 200 : 504);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function () {
|
||||
return this.reply(200);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function () {
|
||||
return this.reply(501);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.username) return this.reply(503);
|
||||
if (this.username && this.authenticated &&
|
||||
_.get(this, 'server.options.anonymous') === true) return this.reply(230);
|
||||
|
||||
// 332 : require account name (ACCT)
|
||||
|
||||
const password = command._[1];
|
||||
return this.login(this.username, password)
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
const PassiveConnector = require('../connector/passive');
|
||||
|
||||
module.exports = function ({command} = {}) {
|
||||
this.connector = new PassiveConnector(this);
|
||||
return this.connector.setupServer()
|
||||
.then(server => {
|
||||
const address = this.server.url.hostname;
|
||||
const {port} = server.address();
|
||||
const host = address.replace(/\./g, ',');
|
||||
const portByte1 = port / 256 | 0;
|
||||
const portByte2 = port % 256;
|
||||
|
||||
return this.reply(227, `PASV OK (${host},${portByte1},${portByte2})`);
|
||||
});
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const ActiveConnector = require('../connector/active');
|
||||
|
||||
module.exports = function ({command} = {}) {
|
||||
this.connector = new ActiveConnector(this);
|
||||
const rawConnection = command._[1].split(',');
|
||||
if (rawConnection.length !== 6) return this.reply(425);
|
||||
|
||||
const ip = rawConnection.slice(0, 4).join('.');
|
||||
const portBytes = rawConnection.slice(4).map(p => parseInt(p));
|
||||
const port = portBytes[0] * 256 + portBytes[1];
|
||||
|
||||
return this.connector.setupConnection(ip, port)
|
||||
.then(socket => {
|
||||
return this.reply(200);
|
||||
})
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../helpers/escape-path');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.currentDirectory())
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function () {
|
||||
return this.close(221);
|
||||
}
|
||||
11
src/commands/registration/allo.js
Normal file
11
src/commands/registration/allo.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'ALLO',
|
||||
handler: function () {
|
||||
return this.reply(202);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Allocate sufficient disk space to receive a file',
|
||||
flags: {
|
||||
obsolete: true
|
||||
}
|
||||
};
|
||||
10
src/commands/registration/appe.js
Normal file
10
src/commands/registration/appe.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const stor = require('./stor').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: 'APPE',
|
||||
handler: function (args) {
|
||||
return stor.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Append to a file'
|
||||
}
|
||||
27
src/commands/registration/auth.js
Normal file
27
src/commands/registration/auth.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'AUTH',
|
||||
handler: function ({command} = {}) {
|
||||
const method = _.upperCase(command._[1]);
|
||||
|
||||
switch (method) {
|
||||
case 'TLS': return handleTLS.call(this);
|
||||
case 'SSL': return handleSSL.call(this);
|
||||
default: return this.reply(504);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [type]',
|
||||
description: 'Set authentication mechanism',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
|
||||
function handleTLS() {
|
||||
return this.reply(504);
|
||||
}
|
||||
|
||||
function handleSSL() {
|
||||
return this.reply(504);
|
||||
}
|
||||
11
src/commands/registration/cdup.js
Normal file
11
src/commands/registration/cdup.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const cwd = require('./cwd').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: ['CDUP', 'XCUP'],
|
||||
handler: function(args) {
|
||||
args.command._ = [args.command._[0], '..'];
|
||||
return cwd.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Change to Parent Directory'
|
||||
}
|
||||
22
src/commands/registration/cwd.js
Normal file
22
src/commands/registration/cwd.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['CWD', 'XCWD'],
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.chdir(command._[1]))
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return this.reply(250, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}[path]',
|
||||
description: 'Change working directory'
|
||||
}
|
||||
20
src/commands/registration/dele.js
Normal file
20
src/commands/registration/dele.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'DELE',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.delete) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.delete(command._[1]))
|
||||
.then(() => {
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Delete file'
|
||||
}
|
||||
18
src/commands/registration/feat.js
Normal file
18
src/commands/registration/feat.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'FEAT',
|
||||
handler: function () {
|
||||
const registry = require('../registry');
|
||||
const features = Object.keys(registry)
|
||||
.filter(cmd => registry[cmd].hasOwnProperty('feat'))
|
||||
.reduce((feats, cmd) => _.concat(feats, registry[cmd].feat), [])
|
||||
.map(feat => ` ${feat}`);
|
||||
return this.reply(211, 'Extensions supported', ...features, 'END');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Get the feature list implemented by the server',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
24
src/commands/registration/help.js
Normal file
24
src/commands/registration/help.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'HELP',
|
||||
handler: function ({command} = {}) {
|
||||
const registry = require('../registry');
|
||||
const directive = _.upperCase(command._[1]);
|
||||
if (directive) {
|
||||
if (!registry.hasOwnProperty(directive)) return this.reply(502, `Unknown command ${directive}.`);
|
||||
|
||||
const {syntax, help, obsolete} = registry[directive];
|
||||
const reply = _.concat([syntax, help, obsolete ? 'Obsolete' : null]);
|
||||
return this.reply(214, ...reply);
|
||||
} else {
|
||||
const supportedCommands = _.chunk(Object.keys(registry), 5).map(chunk => chunk.join('\t'));
|
||||
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [command(optional)]',
|
||||
description: 'Returns usage documentation on a command if specified, else a general help document is returned',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
60
src/commands/registration/list.js
Normal file
60
src/commands/registration/list.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
const getFileStat = require('../../helpers/file-stat');
|
||||
|
||||
// http://cr.yp.to/ftp/list.html
|
||||
// http://cr.yp.to/ftp/list/eplf.html
|
||||
module.exports = {
|
||||
directive: 'LIST',
|
||||
handler: function ({log, command, previous_command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const simple = command.directive === 'NLST';
|
||||
|
||||
let dataSocket;
|
||||
const directory = command._[1] || '.';
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.list(directory)))
|
||||
.then(files => {
|
||||
const getFileMessage = (file) => {
|
||||
if (simple) return file.name;
|
||||
return getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
};
|
||||
|
||||
const fileList = files.map(file => {
|
||||
const message = getFileMessage(file);
|
||||
return {
|
||||
raw: true,
|
||||
message,
|
||||
socket: dataSocket
|
||||
};
|
||||
})
|
||||
return this.reply(150)
|
||||
.then(() => {
|
||||
if (fileList.length) return this.reply(...fileList);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
return this.reply(226, 'Transfer OK');
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(err.code || 451, err.message || 'No directory');
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
description: 'Returns information of a file or directory if specified, else information of the current working directory is returned'
|
||||
}
|
||||
25
src/commands/registration/mdtm.js
Normal file
25
src/commands/registration/mdtm.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const when = require('when');
|
||||
const format = require('date-fns/format');
|
||||
|
||||
module.exports = {
|
||||
directive: 'MDTM',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(command._[1]))
|
||||
.then(fileStat => {
|
||||
const modificationTime = format(fileStat.mtime, 'YYYYMMDDHHmmss.SSS');
|
||||
return this.reply(213, modificationTime)
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Return the last-modified time of a specified file',
|
||||
flags: {
|
||||
feat: 'MDTM'
|
||||
}
|
||||
}
|
||||
22
src/commands/registration/mkd.js
Normal file
22
src/commands/registration/mkd.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['MKD', 'XMKD'],
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.mkdir(command._[1]))
|
||||
.then(dir => {
|
||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}[path]',
|
||||
description: 'Make directory'
|
||||
}
|
||||
11
src/commands/registration/mode.js
Normal file
11
src/commands/registration/mode.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'MODE',
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(command._[1] === 'S' ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} [mode]',
|
||||
description: 'Sets the transfer mode (Stream, Block, or Compressed)',
|
||||
flags: {
|
||||
obsolete: true
|
||||
}
|
||||
}
|
||||
10
src/commands/registration/nlst.js
Normal file
10
src/commands/registration/nlst.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const list = require('./list').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: 'NLST',
|
||||
handler: function (args) {
|
||||
return list.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
description: 'Returns a list of file names in a specified directory'
|
||||
}
|
||||
11
src/commands/registration/noop.js
Normal file
11
src/commands/registration/noop.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'NOOP',
|
||||
handler: function () {
|
||||
return this.reply(200);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'No operation',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
8
src/commands/registration/opts.js
Normal file
8
src/commands/registration/opts.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
directive: 'OPTS',
|
||||
handler: function () {
|
||||
return this.reply(501);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Select options for a feature'
|
||||
}
|
||||
27
src/commands/registration/pass.js
Normal file
27
src/commands/registration/pass.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PASS',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.username) return this.reply(503);
|
||||
if (this.username && this.authenticated &&
|
||||
_.get(this, 'server.options.anonymous') === true) return this.reply(230);
|
||||
|
||||
// 332 : require account name (ACCT)
|
||||
|
||||
const password = command._[1];
|
||||
return this.login(this.username, password)
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [password]',
|
||||
description: 'Authentication password',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
20
src/commands/registration/pasv.js
Normal file
20
src/commands/registration/pasv.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const PassiveConnector = require('../../connector/passive');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PASV',
|
||||
handler: function ({command} = {}) {
|
||||
this.connector = new PassiveConnector(this);
|
||||
return this.connector.setupServer()
|
||||
.then(server => {
|
||||
const address = this.server.url.hostname;
|
||||
const {port} = server.address();
|
||||
const host = address.replace(/\./g, ',');
|
||||
const portByte1 = port / 256 | 0;
|
||||
const portByte2 = port % 256;
|
||||
|
||||
return this.reply(227, `PASV OK (${host},${portByte1},${portByte2})`);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Initiate passive mode'
|
||||
}
|
||||
21
src/commands/registration/port.js
Normal file
21
src/commands/registration/port.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const ActiveConnector = require('../../connector/active');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PORT',
|
||||
handler: function ({command} = {}) {
|
||||
this.connector = new ActiveConnector(this);
|
||||
const rawConnection = command._[1].split(',');
|
||||
if (rawConnection.length !== 6) return this.reply(425);
|
||||
|
||||
const ip = rawConnection.slice(0, 4).join('.');
|
||||
const portBytes = rawConnection.slice(4).map(p => parseInt(p));
|
||||
const port = portBytes[0] * 256 + portBytes[1];
|
||||
|
||||
return this.connector.setupConnection(ip, port)
|
||||
.then(socket => {
|
||||
return this.reply(200);
|
||||
})
|
||||
},
|
||||
syntax: '{{cmd}} [x,x,x,x,y,y]',
|
||||
description: 'Specifies an address and port to which the server should connect'
|
||||
}
|
||||
22
src/commands/registration/pwd.js
Normal file
22
src/commands/registration/pwd.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const when = require('when');
|
||||
const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['PWD', 'XPWD'],
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.currentDirectory())
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
})
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Print current working directory'
|
||||
}
|
||||
11
src/commands/registration/quit.js
Normal file
11
src/commands/registration/quit.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'QUIT',
|
||||
handler: function () {
|
||||
return this.close(221);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Disconnect',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
41
src/commands/registration/retr.js
Normal file
41
src/commands/registration/retr.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RETR',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.read) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
let dataSocket;
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.read(command._[1])))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
dataSocket.on('error', err => stream.emit('error', err));
|
||||
|
||||
stream.on('data', data => dataSocket.write(data, this.encoding));
|
||||
stream.on('end', () => resolve(this.reply(226)));
|
||||
stream.on('error', err => reject(err));
|
||||
this.reply(150).then(() => dataSocket.resume());
|
||||
});
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(551);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
})
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Retrieve a copy of the file'
|
||||
}
|
||||
10
src/commands/registration/rmd.js
Normal file
10
src/commands/registration/rmd.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const dele = require('./dele').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: ['RMD', 'XRMD'],
|
||||
handler: function (args) {
|
||||
return dele.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Remove a directory'
|
||||
}
|
||||
22
src/commands/registration/rnfr.js
Normal file
22
src/commands/registration/rnfr.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RNFR',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const fileName = command._[1];
|
||||
return when(this.fs.get(fileName))
|
||||
.then(() => {
|
||||
this.renameFrom = fileName;
|
||||
return this.reply(350);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [name]',
|
||||
description: 'Rename from'
|
||||
};
|
||||
28
src/commands/registration/rnto.js
Normal file
28
src/commands/registration/rnto.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RNTO',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.renameFrom) return this.reply(503);
|
||||
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.rename) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const from = this.renameFrom;
|
||||
const to = command._[1];
|
||||
|
||||
return when(this.fs.rename(from, to))
|
||||
.then(() => {
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
})
|
||||
.finally(() => {
|
||||
delete this.renameFrom;
|
||||
})
|
||||
},
|
||||
syntax: '{{cmd}} [name]',
|
||||
description: 'Rename to'
|
||||
}
|
||||
23
src/commands/registration/site/index.js
Normal file
23
src/commands/registration/site/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'SITE',
|
||||
handler: function ({log, command} = {}) {
|
||||
const registry = require('./registry');
|
||||
let [, subverb, ...subparameters] = command._;
|
||||
subverb = _.upperCase(subverb);
|
||||
const subLog = log.child({subverb});
|
||||
|
||||
if (!registry.hasOwnProperty(subverb)) return this.reply(502);
|
||||
|
||||
const subCommand = {
|
||||
_: [subverb, ...subparameters],
|
||||
directive: subverb
|
||||
}
|
||||
const handler = registry[subverb].handler.bind(this);
|
||||
return when.try(handler, { log: subLog, command: subCommand });
|
||||
},
|
||||
syntax: '{{cmd}} [subVerb] [subParams]',
|
||||
description: 'Sends site specific commands to remote server'
|
||||
}
|
||||
23
src/commands/registration/size.js
Normal file
23
src/commands/registration/size.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'SIZE',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(command._[1]))
|
||||
.then(fileStat => {
|
||||
return this.reply(213, {message: fileStat.size});
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Return the size of a file',
|
||||
flags: {
|
||||
feat: 'SIZE'
|
||||
}
|
||||
}
|
||||
42
src/commands/registration/stat.js
Normal file
42
src/commands/registration/stat.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
const getFileStat = require('../../helpers/file-stat');
|
||||
|
||||
module.exports = {
|
||||
directive: 'STAT',
|
||||
handler: function (args = {}) {
|
||||
const {log, command} = args;
|
||||
const path = command._[1];
|
||||
if (path) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(path))
|
||||
.then(stat => {
|
||||
if (stat.isDirectory()) {
|
||||
return when(this.fs.list(path))
|
||||
.then(files => {
|
||||
const fileList = files.map(file => {
|
||||
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
return {
|
||||
raw: true,
|
||||
message
|
||||
};
|
||||
})
|
||||
return this.reply(213, 'Status begin', ...fileList, 'Status end');
|
||||
})
|
||||
} else {
|
||||
return this.reply(212, getFileStat(stat, _.get(this, 'server.options.file_format', 'ls')))
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(450);
|
||||
})
|
||||
} else {
|
||||
return this.reply(211, 'Status OK');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
description: 'Returns the current status'
|
||||
}
|
||||
43
src/commands/registration/stor.js
Normal file
43
src/commands/registration/stor.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'STOR',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.write) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const append = command.directive === 'APPE';
|
||||
|
||||
let dataSocket;
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.write(command._[1], {append})))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
stream.on('error', err => dataSocket.emit('error', err));
|
||||
|
||||
dataSocket.on('end', () => stream.end(() => resolve(this.reply(226))));
|
||||
dataSocket.on('error', err => reject(err));
|
||||
dataSocket.on('data', data => stream.write(data, this.encoding));
|
||||
this.reply(150).then(() => dataSocket.resume());
|
||||
});
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(553);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
description: 'Store data as a file at the server site'
|
||||
}
|
||||
11
src/commands/registration/stru.js
Normal file
11
src/commands/registration/stru.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'STRU',
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(command._[1] === 'F' ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} [structure]',
|
||||
description: 'Set file transfer structure',
|
||||
flags: {
|
||||
obsolete: true
|
||||
}
|
||||
}
|
||||
11
src/commands/registration/syst.js
Normal file
11
src/commands/registration/syst.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
directive: 'SYST',
|
||||
handler: function () {
|
||||
return this.reply(215);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Return system type',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
20
src/commands/registration/type.js
Normal file
20
src/commands/registration/type.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'TYPE',
|
||||
handler: function ({command} = {}) {
|
||||
const encoding = _.upperCase(command._[1]);
|
||||
switch (encoding) {
|
||||
case 'A':
|
||||
this.encoding = 'utf-8';
|
||||
case 'I':
|
||||
case 'L':
|
||||
this.encoding = 'binary';
|
||||
return this.reply(200);
|
||||
default:
|
||||
return this.reply(501);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [mode]',
|
||||
description: 'Set the transfer mode, binary (I) or utf-8 (A)'
|
||||
}
|
||||
24
src/commands/registration/user.js
Normal file
24
src/commands/registration/user.js
Normal file
@@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
directive: 'USER',
|
||||
handler: function ({log, command} = {}) {
|
||||
console.log('HANDLE USER')
|
||||
if (this.username) return this.reply(530, 'Username already set');
|
||||
this.username = command._[1];
|
||||
if (this.server.options.anonymous === true) {
|
||||
return this.login(this.username, '@anonymous')
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(530, err || 'Authentication failed');
|
||||
});
|
||||
}
|
||||
return this.reply(331);
|
||||
},
|
||||
syntax: '{{cmd}} [username]',
|
||||
description: 'Authentication username',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
}
|
||||
@@ -1,200 +1,41 @@
|
||||
module.exports = {
|
||||
AUTH: {
|
||||
handler: require('./auth'),
|
||||
syntax: 'AUTH [type]',
|
||||
help: 'Not supported',
|
||||
no_auth: true
|
||||
},
|
||||
USER: {
|
||||
handler: require('./user'),
|
||||
syntax: 'USER [username]',
|
||||
help: 'Authentication username',
|
||||
no_auth: true
|
||||
},
|
||||
PASS: {
|
||||
handler: require('./pass'),
|
||||
syntax: 'PASS [password]',
|
||||
help: 'Authentication password',
|
||||
no_auth: true
|
||||
},
|
||||
SYST: {
|
||||
handler: require('./syst'),
|
||||
syntax: 'SYST',
|
||||
help: 'Return system type',
|
||||
no_auth: true
|
||||
},
|
||||
FEAT: {
|
||||
handler: require('./feat'),
|
||||
syntax: 'FEAT',
|
||||
help: 'Get the feature list implemented by the server',
|
||||
no_auth: true
|
||||
},
|
||||
PWD: {
|
||||
handler: require('./pwd'),
|
||||
syntax: 'PWD',
|
||||
help: 'Print current working directory'
|
||||
},
|
||||
XPWD: {
|
||||
handler: require('./pwd'),
|
||||
syntax: 'XPWD',
|
||||
help: 'Print current working directory'
|
||||
},
|
||||
TYPE: {
|
||||
handler: require('./type'),
|
||||
syntax: 'TYPE',
|
||||
help: 'Set the transfer mode'
|
||||
},
|
||||
PASV: {
|
||||
handler: require('./pasv'),
|
||||
syntax: 'PASV',
|
||||
help: 'Initiate passive mode'
|
||||
},
|
||||
PORT: {
|
||||
handler: require('./port'),
|
||||
syntax: 'PORT [x,x,x,x,y,y]',
|
||||
help: 'Specifies an address and port to which the server should connect'
|
||||
},
|
||||
LIST: {
|
||||
handler: require('./list'),
|
||||
syntax: 'LIST [path(optional)]',
|
||||
help: 'Returns information of a file or directory if specified, else information of the current working directory is returned'
|
||||
},
|
||||
NLST: {
|
||||
handler: require('./list'),
|
||||
syntax: 'NLST [path(optional)]',
|
||||
help: 'Returns a list of file names in a specified directory'
|
||||
},
|
||||
CWD: {
|
||||
handler: require('./cwd'),
|
||||
syntax: 'CWD [path]',
|
||||
help: 'Change working directory'
|
||||
},
|
||||
XCWD: {
|
||||
handler: require('./cwd'),
|
||||
syntax: 'XCWD [path]',
|
||||
help: 'Change working directory'
|
||||
},
|
||||
CDUP: {
|
||||
handler: require('./cdup'),
|
||||
syntax: 'CDUP',
|
||||
help: 'Change to Parent Directory'
|
||||
},
|
||||
XCUP: {
|
||||
handler: require('./cdup'),
|
||||
syntax: 'XCUP',
|
||||
help: 'Change to Parent Directory'
|
||||
},
|
||||
STOR: {
|
||||
handler: require('./stor'),
|
||||
syntax: 'STOR [path]',
|
||||
help: 'Accept the data and to store the data as a file at the server site'
|
||||
},
|
||||
APPE: {
|
||||
handler: require('./stor'),
|
||||
syntax: 'APPE [path]',
|
||||
help: 'Append to file'
|
||||
},
|
||||
RETR: {
|
||||
handler: require('./retr'),
|
||||
syntax: 'RETR [path]',
|
||||
help: 'Retrieve a copy of the file'
|
||||
},
|
||||
DELE: {
|
||||
handler: require('./dele'),
|
||||
syntax: 'DELE [path]',
|
||||
help: 'Delete file'
|
||||
},
|
||||
RMD: {
|
||||
handler: require('./dele'),
|
||||
syntax: 'RMD [path]',
|
||||
help: 'Remove a directory'
|
||||
},
|
||||
XRMD: {
|
||||
handler: require('./dele'),
|
||||
syntax: 'XRMD [path]',
|
||||
help: 'Remove a directory'
|
||||
},
|
||||
HELP: {
|
||||
handler: require('./help'),
|
||||
syntax: 'HELP [command(optional)]',
|
||||
help: 'Returns usage documentation on a command if specified, else a general help document is returned'
|
||||
},
|
||||
MDTM: {
|
||||
handler: require('./mdtm'),
|
||||
syntax: 'MDTM [path]',
|
||||
help: 'Return the last-modified time of a specified file',
|
||||
feat: 'MDTM'
|
||||
},
|
||||
MKD: {
|
||||
handler: require('./mkd'),
|
||||
syntax: 'MKD [path]',
|
||||
help: 'Make directory'
|
||||
},
|
||||
XMKD: {
|
||||
handler: require('./mkd'),
|
||||
syntax: 'XMKD [path]',
|
||||
help: 'Make directory'
|
||||
},
|
||||
NOOP: {
|
||||
handler: require('./noop'),
|
||||
syntax: 'NOOP',
|
||||
help: 'No operation',
|
||||
no_auth: true
|
||||
},
|
||||
QUIT: {
|
||||
handler: require('./quit'),
|
||||
syntax: 'QUIT',
|
||||
help: 'Disconnect',
|
||||
no_auth: true
|
||||
},
|
||||
RNFR: {
|
||||
handler: require('./rnfr'),
|
||||
syntax: 'RNFR [name]',
|
||||
help: 'Rename from'
|
||||
},
|
||||
RNTO: {
|
||||
handler: require('./rnto'),
|
||||
syntax: 'RNTO [name]',
|
||||
help: 'Rename to'
|
||||
},
|
||||
SIZE: {
|
||||
handler: require('./size'),
|
||||
syntax: 'SIZE [path]',
|
||||
help: 'Return the size of a file',
|
||||
feat: 'SIZE'
|
||||
},
|
||||
STAT: {
|
||||
handler: require('./stat'),
|
||||
syntax: 'SIZE [path(optional)]',
|
||||
help: 'Returns the current status'
|
||||
},
|
||||
SITE: {
|
||||
handler: require('./site'),
|
||||
syntax: 'SITE [subVerb] [subParams]',
|
||||
help: 'Sends site specific commands to remote server'
|
||||
},
|
||||
OPTS: {
|
||||
handler: require('./opts'),
|
||||
syntax: 'OPTS',
|
||||
help: 'Select options for a feature'
|
||||
},
|
||||
const commands = [
|
||||
require('./registration/allo'),
|
||||
require('./registration/appe'),
|
||||
require('./registration/auth'),
|
||||
require('./registration/cdup'),
|
||||
require('./registration/cwd'),
|
||||
require('./registration/dele'),
|
||||
require('./registration/feat'),
|
||||
require('./registration/help'),
|
||||
require('./registration/list'),
|
||||
require('./registration/mdtm'),
|
||||
require('./registration/mkd'),
|
||||
require('./registration/mode'),
|
||||
require('./registration/nlst'),
|
||||
require('./registration/noop'),
|
||||
require('./registration/opts'),
|
||||
require('./registration/pass'),
|
||||
require('./registration/pasv'),
|
||||
require('./registration/port'),
|
||||
require('./registration/pwd'),
|
||||
require('./registration/retr'),
|
||||
require('./registration/rmd'),
|
||||
require('./registration/rnfr'),
|
||||
require('./registration/rnto'),
|
||||
require('./registration/site'),
|
||||
require('./registration/size'),
|
||||
require('./registration/stat'),
|
||||
require('./registration/stor'),
|
||||
require('./registration/stru'),
|
||||
require('./registration/syst'),
|
||||
require('./registration/type'),
|
||||
require('./registration/user')
|
||||
];
|
||||
|
||||
STRU: {
|
||||
handler: require('./stru'),
|
||||
syntax: 'STRU [structure]',
|
||||
help: 'Set file transfer structure',
|
||||
obsolete: true
|
||||
},
|
||||
ALLO: {
|
||||
handler: require('./allo'),
|
||||
syntax: 'ALLO',
|
||||
help: 'Allocate sufficient disk space to receive a file',
|
||||
obsolete: true
|
||||
},
|
||||
MODE: {
|
||||
handler: require('./mode'),
|
||||
syntax: 'MODE [mode]',
|
||||
help: 'Sets the transfer mode (Stream, Block, or Compressed)',
|
||||
obsolete: true
|
||||
}
|
||||
};
|
||||
const registry = commands.reduce((result, cmd) => {
|
||||
const aliases = Array.isArray(cmd.directive) ? cmd.directive : [cmd.directive];
|
||||
aliases.forEach(alias => result[alias] = cmd);
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
module.exports = registry;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.read) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
let dataSocket;
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.read(command._[1])))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
dataSocket.on('error', err => stream.emit('error', err));
|
||||
|
||||
stream.on('data', data => dataSocket.write(data, this.encoding));
|
||||
stream.on('end', () => resolve(this.reply(226)));
|
||||
stream.on('error', err => reject(err));
|
||||
this.reply(150).then(() => dataSocket.resume());
|
||||
});
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(551);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const fileName = command._[1];
|
||||
return when(this.fs.get(fileName))
|
||||
.then(() => {
|
||||
this.renameFrom = fileName;
|
||||
return this.reply(350);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.renameFrom) return this.reply(503);
|
||||
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.rename) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const from = this.renameFrom;
|
||||
const to = command._[1];
|
||||
|
||||
return when(this.fs.rename(from, to))
|
||||
.then(() => {
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
})
|
||||
.finally(() => {
|
||||
delete this.renameFrom;
|
||||
});
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
const registry = require('./registry');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
let [, subverb, ...subparameters] = command._;
|
||||
subverb = _.upperCase(subverb);
|
||||
const subLog = log.child({subverb});
|
||||
|
||||
if (!registry.hasOwnProperty(subverb)) return this.reply(502);
|
||||
|
||||
const subCommand = {
|
||||
_: [subverb, ...subparameters],
|
||||
directive: subverb
|
||||
}
|
||||
const handler = registry[subverb].handler.bind(this);
|
||||
return when.try(handler, { log: subLog, command: subCommand });
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(command._[1]))
|
||||
.then(fileStat => {
|
||||
return this.reply(213, {message: fileStat.size});
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
});
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
const getFileStat = require('../helpers/file-stat');
|
||||
|
||||
module.exports = function (args = {}) {
|
||||
const {log, command} = args;
|
||||
const path = command._[1];
|
||||
if (path) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when(this.fs.get(path))
|
||||
.then(stat => {
|
||||
if (stat.isDirectory()) {
|
||||
return when(this.fs.list(path))
|
||||
.then(files => {
|
||||
const fileList = files.map(file => {
|
||||
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
return {
|
||||
raw: true,
|
||||
message
|
||||
};
|
||||
})
|
||||
return this.reply(213, 'Status begin', ...fileList, 'Status end');
|
||||
})
|
||||
} else {
|
||||
return this.reply(212, getFileStat(stat, _.get(this, 'server.options.file_format', 'ls')))
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(450);
|
||||
})
|
||||
} else {
|
||||
return this.reply(211, 'Status OK');
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
const when = require('when');
|
||||
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.write) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const append = command.directive === 'APPE';
|
||||
|
||||
let dataSocket;
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when(this.fs.write(command._[1], {append})))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
stream.on('error', err => dataSocket.emit('error', err));
|
||||
|
||||
dataSocket.on('end', () => resolve(this.reply(226)));
|
||||
dataSocket.on('error', err => reject(err));
|
||||
dataSocket.on('data', data => stream.write(data, this.encoding));
|
||||
this.reply(150).then(() => dataSocket.resume());
|
||||
});
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(553);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function ({command} = {}) {
|
||||
return this.reply(command._[1] === 'F' ? 200 : 504);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = function () {
|
||||
return this.reply(215);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function ({command} = {}) {
|
||||
const encoding = _.upperCase(command._[1]);
|
||||
switch (encoding) {
|
||||
case 'A':
|
||||
this.encoding = 'utf-8';
|
||||
case 'I':
|
||||
case 'L':
|
||||
this.encoding = 'binary';
|
||||
return this.reply(200);
|
||||
default:
|
||||
return this.reply(501);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module.exports = function ({log, command} = {}) {
|
||||
if (this.username) return this.reply(530, 'Username already set');
|
||||
this.username = command._[1];
|
||||
if (this.server.options.anonymous === true) {
|
||||
return this.login(this.username, '@anonymous')
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(530, err || 'Authentication failed');
|
||||
});
|
||||
}
|
||||
return this.reply(331);
|
||||
};
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -13,7 +13,7 @@ describe(CMD, done => {
|
||||
chdir: () => when.resolve()
|
||||
}
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(CMD, done => {
|
||||
reply: () => {},
|
||||
fs: { chdir: () => {} }
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -27,7 +27,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -39,7 +39,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs chdir command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(CMD, done => {
|
||||
reply: () => {},
|
||||
fs: { delete: () => {} }
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -27,7 +27,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -39,7 +39,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs delete command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -21,7 +21,7 @@ describe(CMD, done => {
|
||||
}
|
||||
};
|
||||
const mockSocket = {};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -54,7 +54,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -66,7 +66,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs list command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -13,7 +13,7 @@ describe(CMD, done => {
|
||||
fs: { get: () => {} }
|
||||
};
|
||||
const mockSocket = {};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -28,7 +28,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -40,7 +40,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs get command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(CMD, done => {
|
||||
reply: () => {},
|
||||
fs: { mkdir: () => {} }
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -27,7 +27,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -39,7 +39,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs mkdir command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -21,7 +21,7 @@ describe(CMD, done => {
|
||||
}
|
||||
};
|
||||
const mockSocket = {};
|
||||
const CMDFN = require(`../../src/commands/list`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/list`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -14,7 +14,7 @@ describe(CMD, done => {
|
||||
server: { options: { anonymous: false } },
|
||||
username: 'user'
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -12,7 +12,7 @@ describe(CMD, done => {
|
||||
reply: () => {},
|
||||
fs: { currentDirectory: () => {} }
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -27,7 +27,7 @@ describe(CMD, done => {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
@@ -39,7 +39,7 @@ describe(CMD, done => {
|
||||
|
||||
it('fails on no fs currentDirectory command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const BADCMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(badMockClient);
|
||||
const BADCMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
BADCMDFN()
|
||||
.then(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ describe(CMD, done => {
|
||||
const mockClient = {
|
||||
close: () => {}
|
||||
};
|
||||
const CMDFN = require(`../../src/commands/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const CMDFN = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@@ -6,7 +6,6 @@ const fs = require('fs');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const FtpServer = require('../src');
|
||||
const FtpCommands = require('../src/commands');
|
||||
const FtpClient = require('ftp');
|
||||
|
||||
describe('FtpServer', function () {
|
||||
|
||||
Reference in New Issue
Block a user