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
  • Loading branch information
trs authored Mar 10, 2017
2 parents 5154743 + b7e17af commit 8227c51
Show file tree
Hide file tree
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
Expand Up @@ -141,6 +141,11 @@ Options:
> Used in `SITE CHMOD`
`getUniqueName()`
> Return a unique file name to write to
> Used in `STOU`
<!--[RM_CONTRIBUTING]-->
## Contributing

Expand Down
3 changes: 1 addition & 2 deletions src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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) {
Expand Down
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)',
Expand Down
5 changes: 3 additions & 2 deletions src/commands/registration/stor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
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',
Expand Down
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) {
Expand Down
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'),
Expand Down Expand Up @@ -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'),
Expand Down
12 changes: 5 additions & 7 deletions src/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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();
Expand Down
6 changes: 3 additions & 3 deletions src/connector/active.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
3 changes: 1 addition & 2 deletions src/connector/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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);
}

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

Expand Down
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');
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Down

0 comments on commit 8227c51

Please sign in to comment.