feat(REST): add support for REST command

Allows the client to resume a transfer at the specified bytes
This commit is contained in:
Tyler Stewart
2017-06-26 12:36:13 -06:00
parent 8aeb6976d2
commit 2e02dc20ad
7 changed files with 34 additions and 9 deletions

View File

@@ -0,0 +1,16 @@
const _ = require('lodash');
module.exports = {
directive: 'REST',
handler: function ({command} = {}) {
const arg = _.get(command, 'arg');
const byteCount = parseInt(arg, 10);
if (isNaN(byteCount) || byteCount < 0) return this.reply(501, 'Byte count must be 0 or greater');
this.restByteCount = byteCount;
return this.reply(350, `Resarting next transfer at ${byteCount}`);
},
syntax: '{{cmd}} <byte-count>',
description: 'Restart transfer from the specified point. Resets after any STORE or RETRIEVE'
};

View File

@@ -12,8 +12,9 @@ module.exports = {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when.try(this.fs.read.bind(this.fs), command.arg))
.then(() => when.try(this.fs.read.bind(this.fs), command.arg, {start: this.restByteCount}))
.then(stream => {
this.restByteCount = 0;
return when.promise((resolve, reject) => {
dataSocket.on('error', err => stream.emit('error', err));

View File

@@ -15,8 +15,9 @@ module.exports = {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append}))
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append, start: this.restByteCount}))
.then(stream => {
this.restByteCount = 0;
return when.promise((resolve, reject) => {
stream.once('error', err => dataSocket.emit('error', err));
stream.once('finish', () => resolve(this.reply(226, fileName)));

View File

@@ -2,7 +2,6 @@
module.exports = {
directive: 'TYPE',
handler: function ({command} = {}) {
if (/^A[0-9]?$/i.test(command.arg)) {
this.transferType = 'ascii';
} else if (/^L[0-9]?$/i.test(command.arg) || /^I$/i.test(command.arg)) {

View File

@@ -21,6 +21,7 @@ const commands = [
require('./registration/port'),
require('./registration/pwd'),
require('./registration/quit'),
require('./registration/rest'),
require('./registration/retr'),
require('./registration/rmd'),
require('./registration/rnfr'),

View File

@@ -18,7 +18,7 @@ class FtpConnection {
this.transferType = 'binary';
this.encoding = 'utf8';
this.bufferSize = false;
this.restByteCount = 0;
this._restByteCount = 0;
this._secure = false;
this.connector = new BaseConnector(this);
@@ -50,6 +50,13 @@ class FtpConnection {
}
}
get restByteCount() {
return this._restByteCount > 0 ? this._restByteCount : undefined;
}
set restByteCount(rbc) {
this._restByteCount = rbc;
}
get secure() {
return this.server.isTLS || this._secure;
}
@@ -68,7 +75,7 @@ class FtpConnection {
return when.try(() => {
const loginListeners = this.server.listeners('login');
if (!loginListeners || !loginListeners.length) {
if (!this.server.options.anoymous) throw new errors.GeneralError('No "login" listener setup', 500);
if (!this.server.options.anonymous) throw new errors.GeneralError('No "login" listener setup', 500);
} else {
return this.server.emitPromise('login', {connection: this, username, password});
}

View File

@@ -65,22 +65,22 @@ class FileSystem {
});
}
write(fileName, {append = false} = {}) {
write(fileName, {append = false, start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+'});
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
stream.once('error', () => fs.unlink(fsPath));
stream.once('close', () => stream.end());
return stream;
}
read(fileName) {
read(fileName, {start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
return fs.stat(fsPath)
.tap(stat => {
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
})
.then(() => {
const stream = syncFs.createReadStream(fsPath, {flags: 'r'});
const stream = syncFs.createReadStream(fsPath, {flags: 'r', start});
return stream;
});
}