Skip to content

Commit

Permalink
Merge pull request #1 from stewarttylerr/feat-abor-stou
Browse files Browse the repository at this point in the history
Feat abor stou
trs authored Mar 10, 2017

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 5154743 + b7e17af commit 8227c51
Showing 15 changed files with 71 additions and 28 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -141,6 +141,11 @@ Options:
> Used in `SITE CHMOD`
`getUniqueName()`
> Return a unique file name to write to
> Used in `STOU`
<!--[RM_CONTRIBUTING]-->
## Contributing

3 changes: 1 addition & 2 deletions src/commands/index.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ const REGISTRY = require('./registry');

class FtpCommands {
constructor(connection) {
console.log(REGISTRY)
this.connection = connection;
this.previousCommand = {};
this.blacklist = _.get(this.connection, 'server.options.blacklist', []).map(cmd => _.upperCase(cmd));
@@ -31,7 +30,7 @@ class FtpCommands {
const commandRegister = REGISTRY[command.directive];
const commandFlags = _.get(commandRegister, 'flags', {});
if (!commandFlags.no_auth && !this.connection.authenticated) {
return this.connection.reply(530);
return this.connection.reply(530, 'Command requires authentication');
}

if (!commandRegister.handler) {
14 changes: 14 additions & 0 deletions src/commands/registration/abor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
directive: 'ABOR',
handler: function () {
return this.connector.waitForConnection()
.then(socket => {
return this.reply(426, {socket})
.then(() => this.connector.end());
})
.catch(() => {})
.then(() => this.reply(226));
},
syntax: '{{cmd}}',
description: 'Abort an active file transfer'
};
2 changes: 1 addition & 1 deletion src/commands/registration/mode.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
directive: 'MODE',
handler: function ({command} = {}) {
return this.reply(command._[1] === 'S' ? 200 : 504);
return this.reply(/^S$/i.test(command._[1]) ? 200 : 504);
},
syntax: '{{cmd}} [mode]',
description: 'Sets the transfer mode (Stream, Block, or Compressed)',
5 changes: 3 additions & 2 deletions src/commands/registration/stor.js
Original file line number Diff line number Diff line change
@@ -7,19 +7,20 @@ module.exports = {
if (!this.fs.write) return this.reply(402, 'Not supported by file system');

const append = command.directive === 'APPE';
const fileName = command._[1];

let dataSocket;
return this.connector.waitForConnection()
.then(socket => {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when(this.fs.write(command._[1], {append})))
.then(() => when(this.fs.write(fileName, {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('end', () => stream.end(() => resolve(this.reply(226, fileName))));
dataSocket.on('error', err => reject(err));
dataSocket.on('data', data => stream.write(data, this.encoding));
this.reply(150).then(() => dataSocket.resume());
20 changes: 20 additions & 0 deletions src/commands/registration/stou.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const stor = require('./stor').handler;

module.exports = {
directive: 'STOU',
handler: function (args) {
if (!this.fs) return this.reply(550, 'File system not instantiated');
if (!this.fs.get || !this.fs.getUniqueName) return this.reply(402, 'Not supported by file system');

const fileName = args.command._[1];
return this.fs.get(fileName)
.catch(() => fileName) // does not exist, name is unique
.then(() => this.fs.getUniqueName()) // exists, must create new unique name
.then(name => {
args.command._[1] = name;
return stor.call(this, args);
});
},
syntax: '{{cmd}}',
description: 'Store file uniquely'
};
2 changes: 1 addition & 1 deletion src/commands/registration/stru.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
directive: 'STRU',
handler: function ({command} = {}) {
return this.reply(command._[1] === 'F' ? 200 : 504);
return this.reply(/^F$/i.test(command._[1]) ? 200 : 504);
},
syntax: '{{cmd}} [structure]',
description: 'Set file transfer structure',
1 change: 0 additions & 1 deletion src/commands/registration/user.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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) {
2 changes: 2 additions & 0 deletions src/commands/registry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const commands = [
require('./registration/abor'),
require('./registration/allo'),
require('./registration/appe'),
require('./registration/auth'),
@@ -26,6 +27,7 @@ const commands = [
require('./registration/size'),
require('./registration/stat'),
require('./registration/stor'),
require('./registration/stou'),
require('./registration/stru'),
require('./registration/syst'),
require('./registration/type'),
12 changes: 5 additions & 7 deletions src/connection.js
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ const _ = require('lodash');
const uuid = require('uuid');
const when = require('when');
const sequence = require('when/sequence');
const parseSentence = require('minimist-string');
const parseCommandString = require('minimist-string');
const net = require('net');

const BaseConnector = require('./connector/base');
@@ -16,28 +16,26 @@ class FtpConnection {
this.server = server;
this.commandSocket = options.socket;
this.id = uuid.v4();
this.log = options.log.child({ftp_session_id: this.commandSocket.ftp_session_id});
this.log = options.log.child({id: this.id});
this.commands = new Commands(this);
this.encoding = 'utf-8';

this.connector = new BaseConnector(this);

this.commandSocket.on('error', err => {
console.log('error', err)
this.server.server.emit('error', {connection: this, error: err});
});
this.commandSocket.on('data', data => {
const messages = _.compact(data.toString('utf-8').split('\r\n'));
const handleMessage = (message) => {
const command = parseSentence(message);
const command = parseCommandString(message);
command.directive = _.upperCase(command._[0]);
return this.commands.handle(command);
};

return sequence(messages.map(message => handleMessage.bind(this, message)));
});
this.commandSocket.on('timeout', () => {
console.log('timeout')
});
this.commandSocket.on('timeout', () => {});
this.commandSocket.on('close', () => {
if (this.connector) this.connector.end();
if (this.commandSocket && !this.commandSocket.destroyed) this.commandSocket.destroy();
6 changes: 3 additions & 3 deletions src/connector/active.js
Original file line number Diff line number Diff line change
@@ -8,12 +8,12 @@ class Active extends Connector {
this.type = 'active';
}

waitForConnection() {
waitForConnection({timeout = 5000, delay = 250} = {}) {
return when.iterate(
() => {},
() => this.dataSocket && this.dataSocket.connected,
() => when().delay(250)
).timeout(5000)
() => when().delay(delay)
).timeout(timeout)
.then(() => this.dataSocket);
}

3 changes: 1 addition & 2 deletions src/connector/base.js
Original file line number Diff line number Diff line change
@@ -21,8 +21,7 @@ class Connector {
if (this.dataServer) this.dataServer.close();
this.dataSocket = null;
this.dataServer = null;

this.connection.connector = new Connector(this.connection);
this.type = false;
}
}
module.exports = Connector;
16 changes: 8 additions & 8 deletions src/connector/passive.js
Original file line number Diff line number Diff line change
@@ -10,15 +10,15 @@ class Passive extends Connector {
this.type = 'passive';
}

waitForConnection() {
waitForConnection({timeout = 5000, delay = 250} = {}) {
if (!this.dataServer) {
return when.reject(new errors.ConnectorError('Passive server not setup'));
}
return when.iterate(
() => {},
() => this.dataServer && this.dataServer.listening && this.dataSocket,
() => when().delay(250)
).timeout(5000)
() => this.dataServer && this.dataServer.listening && this.dataSocket && this.dataSocket.connected,
() => when().delay(delay)
).timeout(timeout)
.then(() => this.dataSocket);
}

@@ -44,18 +44,18 @@ class Passive extends Connector {
return this.connection.reply(550, 'Remote addresses do not match')
.finally(() => this.connection.close());
}
this.log.info({port}, 'Passive connection fulfilled.');
this.log.debug({port}, 'Passive connection fulfilled.');

this.dataSocket = socket;
this.dataSocket.connected = true;
this.dataSocket.setEncoding(this.connection.encoding);
this.dataSocket.on('data', data => {

});
this.dataSocket.on('close', () => {
this.log.debug('Passive connection closed');
this.end();
});
});
this.dataServer.on('close', () => {
this.log.debug('Passive server closed');
this.dataServer = null;
});

5 changes: 5 additions & 0 deletions src/fs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const _ = require('lodash');
const nodePath = require('path');
const uuid = require('uuid');
const when = require('when');
const whenNode = require('when/node');
const syncFs = require('fs');
@@ -97,5 +98,9 @@ class FileSystem {
path = nodePath.resolve(this.cwd, path);
return fs.chmod(path, mode);
}

getUniqueName() {
return uuid.v4().replace(/\W/g, '');
}
}
module.exports = FileSystem;
3 changes: 2 additions & 1 deletion test/start.js
Original file line number Diff line number Diff line change
@@ -3,7 +3,8 @@ const bunyan = require('bunyan');

const FtpServer = require('../src');

const log = bunyan.createLogger({name: 'test', level: 10});
const log = bunyan.createLogger({name: 'test'});
log.level(process.env.LOG_LEVEL || 'trace');
const server = new FtpServer(process.env.FTP_URL, {
log,
pasv_range: process.env.PASV_RANGE

0 comments on commit 8227c51

Please sign in to comment.