From 46b9baaf5b5507cfb7caadec1d80ad5cb91ef669 Mon Sep 17 00:00:00 2001 From: ideadesignmedia Date: Sat, 21 May 2022 09:52:20 -0700 Subject: [PATCH] Update RETR and STOR commands Update RETR and STOR commands to reply 150 before waiting for the client to connect. Previously, the client would wait for a signal before connecting and the server would wait for a connection, causing a timeout error. --- src/commands/registration/retr.js | 96 +++++++++++++-------------- src/commands/registration/stor.js | 105 +++++++++++++++--------------- 2 files changed, 99 insertions(+), 102 deletions(-) diff --git a/src/commands/registration/retr.js b/src/commands/registration/retr.js index de906359..a855bdbc 100644 --- a/src/commands/registration/retr.js +++ b/src/commands/registration/retr.js @@ -2,63 +2,61 @@ const Promise = require('bluebird'); module.exports = { directive: 'RETR', - handler: function ({log, command} = {}) { + 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'); const filePath = command.arg; - + this.reply(150).then(() => this.connector.socket && this.connector.socket.resume()) return this.connector.waitForConnection() - .tap(() => this.commandSocket.pause()) - .then(() => Promise.try(() => this.fs.read(filePath, {start: this.restByteCount}))) - .then((fsResponse) => { - let {stream, clientPath} = fsResponse; - if (!stream && !clientPath) { - stream = fsResponse; - clientPath = filePath; - } - const serverPath = stream.path || filePath; - - const destroyConnection = (connection, reject) => (err) => { - if (connection) connection.destroy(err); - reject(err); - }; - - const eventsPromise = new Promise((resolve, reject) => { - stream.on('data', (data) => { - if (stream) stream.pause(); - if (this.connector.socket) { - this.connector.socket.write(data, () => stream && stream.resume()); - } + .tap(() => this.commandSocket.pause()) + .then(() => Promise.try(() => this.fs.read(filePath, { start: this.restByteCount }))) + .then((fsResponse) => { + let { stream, clientPath } = fsResponse; + if (!stream && !clientPath) { + stream = fsResponse; + clientPath = filePath; + } + const serverPath = stream.path || filePath; + + const destroyConnection = (connection, reject) => (err) => { + if (connection) connection.destroy(err); + reject(err); + }; + + const eventsPromise = new Promise((resolve, reject) => { + stream.on('data', (data) => { + if (stream) stream.pause(); + if (this.connector.socket) { + this.connector.socket.write(data, () => stream && stream.resume()); + } + }); + stream.once('end', () => resolve()); + stream.once('error', destroyConnection(this.connector.socket, reject)); + + this.connector.socket.once('error', destroyConnection(stream, reject)); }); - stream.once('end', () => resolve()); - stream.once('error', destroyConnection(this.connector.socket, reject)); - this.connector.socket.once('error', destroyConnection(stream, reject)); + this.restByteCount = 0; + + return eventsPromise.tap(() => this.emit('RETR', null, serverPath)) + .then(() => this.reply(226, clientPath)) + .finally(() => stream.destroy && stream.destroy()); + }) + .catch(Promise.TimeoutError, (err) => { + log.error(err); + return this.reply(425, 'No connection established'); + }) + .catch((err) => { + log.error(err); + this.emit('RETR', err); + return this.reply(551, err.message); + }) + .finally(() => { + this.connector.end(); + this.commandSocket.resume(); }); - - this.restByteCount = 0; - - return this.reply(150).then(() => stream.resume() && this.connector.socket.resume()) - .then(() => eventsPromise) - .tap(() => this.emit('RETR', null, serverPath)) - .then(() => this.reply(226, clientPath)) - .finally(() => stream.destroy && stream.destroy()); - }) - .catch(Promise.TimeoutError, (err) => { - log.error(err); - return this.reply(425, 'No connection established'); - }) - .catch((err) => { - log.error(err); - this.emit('RETR', err); - return this.reply(551, err.message); - }) - .finally(() => { - this.connector.end(); - this.commandSocket.resume(); - }); }, syntax: '{{cmd}} ', description: 'Retrieve a copy of the file' -}; +}; \ No newline at end of file diff --git a/src/commands/registration/stor.js b/src/commands/registration/stor.js index ed6a7adb..98786e1b 100644 --- a/src/commands/registration/stor.js +++ b/src/commands/registration/stor.js @@ -2,72 +2,71 @@ const Promise = require('bluebird'); module.exports = { directive: 'STOR', - handler: function ({log, command} = {}) { + 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'; const fileName = command.arg; - + this.reply(150).then((() => this.connector.socket && this.connector.socket.resume())) return this.connector.waitForConnection() - .tap(() => this.commandSocket.pause()) - .then(() => Promise.try(() => this.fs.write(fileName, {append, start: this.restByteCount}))) - .then((fsResponse) => { - let {stream, clientPath} = fsResponse; - if (!stream && !clientPath) { - stream = fsResponse; - clientPath = fileName; - } - const serverPath = stream.path || fileName; + .tap(() => this.commandSocket.pause()) + .then(() => Promise.try(() => this.fs.write(fileName, { append, start: this.restByteCount }))) + .then((fsResponse) => { + let { stream, clientPath } = fsResponse; + if (!stream && !clientPath) { + stream = fsResponse; + clientPath = fileName; + } + const serverPath = stream.path || fileName; - const destroyConnection = (connection, reject) => (err) => { - try { - if (connection) { - if (connection.writable) connection.end(); - connection.destroy(err); + const destroyConnection = (connection, reject) => (err) => { + try { + if (connection) { + if (connection.writable) connection.end(); + connection.destroy(err); + } + } finally { + reject(err); } - } finally { - reject(err); - } - }; + }; - const streamPromise = new Promise((resolve, reject) => { - stream.once('error', destroyConnection(this.connector.socket, reject)); - stream.once('finish', () => resolve()); - }); + const streamPromise = new Promise((resolve, reject) => { + stream.once('error', destroyConnection(this.connector.socket, reject)); + stream.once('finish', () => resolve()); + }); - const socketPromise = new Promise((resolve, reject) => { - this.connector.socket.pipe(stream, {end: false}); - this.connector.socket.once('end', () => { - if (stream.listenerCount('close')) stream.emit('close'); - else stream.end(); - resolve(); + const socketPromise = new Promise((resolve, reject) => { + this.connector.socket.pipe(stream, { end: false }); + this.connector.socket.once('end', () => { + if (stream.listenerCount('close')) stream.emit('close'); + else stream.end(); + resolve(); + }); + this.connector.socket.once('error', destroyConnection(stream, reject)); }); - this.connector.socket.once('error', destroyConnection(stream, reject)); - }); - this.restByteCount = 0; + this.restByteCount = 0; - return this.reply(150).then(() => this.connector.socket && this.connector.socket.resume()) - .then(() => Promise.all([streamPromise, socketPromise])) - .tap(() => this.emit('STOR', null, serverPath)) - .then(() => this.reply(226, clientPath)) - .finally(() => stream.destroy && stream.destroy()); - }) - .catch(Promise.TimeoutError, (err) => { - log.error(err); - return this.reply(425, 'No connection established'); - }) - .catch((err) => { - log.error(err); - this.emit('STOR', err); - return this.reply(550, err.message); - }) - .finally(() => { - this.connector.end(); - this.commandSocket.resume(); - }); + return Promise.all([streamPromise, socketPromise]) + .tap(() => this.emit('STOR', null, serverPath)) + .then(() => this.reply(226, clientPath)) + .finally(() => stream.destroy && stream.destroy()); + }) + .catch(Promise.TimeoutError, (err) => { + log.error(err); + return this.reply(425, 'No connection established'); + }) + .catch((err) => { + log.error(err); + this.emit('STOR', err); + return this.reply(550, err.message); + }) + .finally(() => { + this.connector.end(); + this.commandSocket.resume(); + }); }, syntax: '{{cmd}} ', description: 'Store data as a file at the server site' -}; +}; \ No newline at end of file