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:
Tyler Stewart
2017-03-08 12:31:44 -07:00
parent f6d1a3828a
commit 795c3d7c65
84 changed files with 769 additions and 708 deletions

View File

@@ -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`

View File

@@ -6,6 +6,7 @@
"ftp",
"ftp-server",
"ftp-srv",
"ftp-svr",
"ftpd",
"server"
],

View File

@@ -1,3 +0,0 @@
module.exports = function () {
return this.reply(202);
}

View File

@@ -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);
}

View File

@@ -1,6 +0,0 @@
const cwd = require('./cwd');
module.exports = function(args) {
args.command._ = [args.command._[0], '..'];
return cwd.call(this, args);
}

View File

@@ -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);
});
}

View File

@@ -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);
});
}

View File

@@ -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');
}

View File

@@ -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.');
}
};

View File

@@ -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);
}

View File

@@ -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();
});
}

View File

@@ -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);
});
}

View File

@@ -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);
});
}

View File

@@ -1,3 +0,0 @@
module.exports = function ({command} = {}) {
return this.reply(command._[1] === 'S' ? 200 : 504);
}

View File

@@ -1,3 +0,0 @@
module.exports = function () {
return this.reply(200);
}

View File

@@ -1,3 +0,0 @@
module.exports = function () {
return this.reply(501);
}

View File

@@ -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');
});
};

View File

@@ -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})`);
});
}

View File

@@ -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);
})
}

View File

@@ -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);
});
}

View File

@@ -1,3 +0,0 @@
module.exports = function () {
return this.close(221);
}

View 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
}
};

View 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'
}

View 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);
}

View 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'
}

View 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'
}

View 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'
}

View 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
}
}

View 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
}
}

View 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'
}

View 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'
}
}

View 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'
}

View 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
}
}

View 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'
}

View File

@@ -0,0 +1,11 @@
module.exports = {
directive: 'NOOP',
handler: function () {
return this.reply(200);
},
syntax: '{{cmd}}',
description: 'No operation',
flags: {
no_auth: true
}
}

View File

@@ -0,0 +1,8 @@
module.exports = {
directive: 'OPTS',
handler: function () {
return this.reply(501);
},
syntax: '{{cmd}}',
description: 'Select options for a feature'
}

View 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
}
}

View 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'
}

View 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'
}

View 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'
}

View File

@@ -0,0 +1,11 @@
module.exports = {
directive: 'QUIT',
handler: function () {
return this.close(221);
},
syntax: '{{cmd}}',
description: 'Disconnect',
flags: {
no_auth: true
}
}

View 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'
}

View 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'
}

View 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'
};

View 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'
}

View 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'
}

View 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'
}
}

View 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'
}

View 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'
}

View 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
}
}

View 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
}
}

View 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)'
}

View 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
}
}

View File

@@ -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;

View File

@@ -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();
});
}

View File

@@ -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);
})
}

View File

@@ -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;
});
}

View File

@@ -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 });
}

View File

@@ -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);
});
}

View File

@@ -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');
}
}

View File

@@ -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();
});
}

View File

@@ -1,3 +0,0 @@
module.exports = function ({command} = {}) {
return this.reply(command._[1] === 'F' ? 200 : 504);
}

View File

@@ -1,3 +0,0 @@
module.exports = function () {
return this.reply(215);
}

View File

@@ -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);
}
}

View File

@@ -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);
};

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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(() => {

View File

@@ -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(() => {

View File

@@ -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();

View File

@@ -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(() => {

View File

@@ -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(() => {

View File

@@ -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(() => {

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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(() => {

View File

@@ -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();

View File

@@ -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 () {