diff --git a/src/commands/registration/rest.js b/src/commands/registration/rest.js new file mode 100644 index 00000000..cf456be2 --- /dev/null +++ b/src/commands/registration/rest.js @@ -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}} ', + description: 'Restart transfer from the specified point. Resets after any STORE or RETRIEVE' +}; diff --git a/src/commands/registration/retr.js b/src/commands/registration/retr.js index 0ab8a708..b8d74918 100644 --- a/src/commands/registration/retr.js +++ b/src/commands/registration/retr.js @@ -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)); diff --git a/src/commands/registration/stor.js b/src/commands/registration/stor.js index 2f665bb9..c2277e88 100644 --- a/src/commands/registration/stor.js +++ b/src/commands/registration/stor.js @@ -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))); diff --git a/src/commands/registration/type.js b/src/commands/registration/type.js index 5968875f..d5edc272 100644 --- a/src/commands/registration/type.js +++ b/src/commands/registration/type.js @@ -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)) { diff --git a/src/commands/registry.js b/src/commands/registry.js index b9cfcb26..8dbc0df0 100644 --- a/src/commands/registry.js +++ b/src/commands/registry.js @@ -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'), diff --git a/src/connection.js b/src/connection.js index 94be77e7..90a9af9c 100644 --- a/src/connection.js +++ b/src/connection.js @@ -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}); } diff --git a/src/fs.js b/src/fs.js index 65cac2f4..b14df76a 100644 --- a/src/fs.js +++ b/src/fs.js @@ -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; }); }