Skip to content

Conversation

@ldm314
Copy link

@ldm314 ldm314 commented Dec 17, 2020

The issue looks to be either a race condition or sequencing error. I changed the RETR command to send the 150 response after opening the file, but before waiting for the socket to be connected. This resolved this issue with both WinSCP and an old hardware device I am working with.

@matt-forster
Copy link
Contributor

@ldm314 I am still planning on getting this merged in, I will have some time next week to address this.

if (!this.fs.read) return this.reply(402, 'Not supported by file system');

const filePath = command.arg;
return this.fs.read(filePath, {start: this.restByteCount})
Copy link
Contributor

@matt-forster matt-forster Aug 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had this.fs.read wrapped in a Promise.try block previously because it isn't necessarily going to return a promise in every implementation - best to include that, and your tests will begin passing again most likely.

@matt-forster matt-forster self-assigned this Aug 9, 2021
@ideadesignmedia
Copy link

Found solution, replace src/commands/registration/retr.js:

const Promise = require('bluebird');

module.exports = {
  directive: 'RETR',
  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());
          }
        });
        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();
    });
  },
  syntax: '{{cmd}} <path>',
  description: 'Retrieve a copy of the file'
};

@matt-forster
Copy link
Contributor

@ideadesignmedia Feel free to create another PR that eclipses this one!

@matt-forster matt-forster removed their assignment Dec 13, 2022
@andersnm
Copy link

andersnm commented Feb 28, 2024

[edit; amended my comment so as to not say anything too wrong on the internet] So it seems WinSCP expects a 150 response before STOR and RETR with TLS+passive. For some hysterical reason, WinSCP does not wait for 150 without TLS. On the other hand, FileZilla does not wait for a 150 response up front, but unlike WinSCP it breaks if you send two 150's (which my comment suggested initially, altho the PR already took care of it)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants