Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Downloading file using FTPES causes Deadlock #27

Open
Spitzbua opened this issue Aug 25, 2023 · 3 comments
Open

Downloading file using FTPES causes Deadlock #27

Spitzbua opened this issue Aug 25, 2023 · 3 comments
Labels
question Further information is requested

Comments

@Spitzbua
Copy link

Spitzbua commented Aug 25, 2023

Code is working fine when using SecurityType.FTP. But when switching over to SecurityType.FTPES the applications gets stuck at Start downloading...

There is an error in the log which states that the size is not allowed in ascii mode:
[code= 550, message= 550 SIZE not allowed in ASCII mode]

After that it will be switched to TYPE A and the size is returned.

But on opening the DataSocket the code seems to try again in ASCII which might be part of the failure?
I tried to switch to TYPE A, but it had no effect.:
await ftpConnect.sendCustomCommand("TYPE A");

WinSCP is opening binary mode for the data connection which is working.

I tried setting the transfer type manully to await ftpConnect.setTransferType(TransferType.binary);
But it gets stuck on the same position. So it might has to do with the SSL handshake?

@salim-lachdhaf, thank you for your effort implementing this plugin! Do you have a clue why this fails? Is there a way to force binary download instead of ASCII Mode? Thank you!

Log from WinSCP using FTPES successfull:

> 2023-08-25 11:14:39.912 RETR xxxxxx.txt
. 2023-08-25 11:14:39.912 Verbindung mit 13.37.150.67:50069 wird hergestellt …
< 2023-08-25 11:14:39.991 150 Opening BINARY mode data connection for xxxxxx.txt (2091 bytes)
. 2023-08-25 11:14:40.032 Session ID reused
. 2023-08-25 11:14:40.032 Using TLSv1.2, cipher TLSv1.2: ECDHE-RSA-AES128-GCM-SHA256, 2048 bit RSA, ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD
. 2023-08-25 11:14:40.032 TLS-Verbindung hergestellt
. 2023-08-25 11:14:40.112 Preserving timestamp [2023-08-25T08:21:37.000Z]
< 2023-08-25 11:14:40.114 226 Transfer complete
. 2023-08-25 11:14:40.114 Herunterladen erfolgreich
. 2023-08-25 11:14:40.114 Transfer done: '/results/xxxxxx.txt' => 'C:\Users\thoma\Documents\test\xxxxxx.txt.txt' [2091]

Log from flutter:

flutter: [2023-08-25 10:54:31.286524] Connecting...
flutter: [2023-08-25 10:54:31.383074] Connection established, waiting for welcome message...
flutter: [2023-08-25 10:54:31.688468] < FTPReply =  [code= 220, message= 220 FTP on abc1337.kasserver.com ready]
flutter: [2023-08-25 10:54:31.688468] > AUTH TLS
flutter: [2023-08-25 10:54:31.992547] < FTPReply =  [code= 234, message= 234 AUTH TLS successful]
flutter: [2023-08-25 10:54:32.117675] > PBSZ 0
flutter: [2023-08-25 10:54:32.420489] < FTPReply =  [code= 200, message= 200 PBSZ 0 successful]
flutter: [2023-08-25 10:54:32.420489] > PROT P
flutter: [2023-08-25 10:54:32.720492] < FTPReply =  [code= 200, message= 200 Protection set to Private]
flutter: [2023-08-25 10:54:32.721451] > USER xxyy
flutter: [2023-08-25 10:54:33.022062] < FTPReply =  [code= 331, message= 331 Password required for xxyy]
flutter: [2023-08-25 10:54:33.022062] > PASS yyxx
flutter: [2023-08-25 10:54:33.322899] < FTPReply =  [code= 230, message= 230 User xxyy logged in]
flutter: [2023-08-25 10:54:33.322899] Connected!
flutter: [2023-08-25 10:54:33.325508] Download settings.json to C:\Users\thoma\AppData\Roaming\app\settings\settings.json
flutter: [2023-08-25 10:54:33.326509] > SIZE settings.json
flutter: [2023-08-25 10:54:33.627328] < FTPReply =  [code= 550, message= 550 SIZE not allowed in ASCII mode]
flutter: [2023-08-25 10:54:33.627328] > TYPE I
flutter: [2023-08-25 10:54:33.927898] < FTPReply =  [code= 200, message= 200 Type set to I]
flutter: [2023-08-25 10:54:33.927898] > SIZE settings.json
flutter: [2023-08-25 10:54:34.229085] < FTPReply =  [code= 213, message= 213 2397]
flutter: [2023-08-25 10:54:34.229085] > TYPE A
flutter: [2023-08-25 10:54:34.532745] < FTPReply =  [code= 200, message= 200 Type set to A]
flutter: [2023-08-25 10:54:34.532745] > PASV
flutter: [2023-08-25 10:54:34.833702] < FTPReply =  [code= 227, message= 227 Entering Passive Mode (85,13,150,67,195,189).]
flutter: [2023-08-25 10:54:34.834671] > RETR settings.json
flutter: [2023-08-25 10:54:34.834671] Opening DataSocket to Port 50109
flutter: [2023-08-25 10:54:35.185803] < FTPReply =  [code= 150, message= 150 Opening ASCII mode data connection for settings.json (2397 bytes)]
flutter: [2023-08-25 10:54:35.185803] Start downloading...
@rafadompas
Copy link

rafadompas commented Sep 29, 2023

The same happens to me. When I connect the server in FTPES type, it connects fine but it does not upload the file. it stops and generates the error "Timeout reached for Receiving response !". If I connect the server in FTP type, works perfectly. Can someone help? Thank you!!! With the version 1.0.1 works right. But it's obsolet with intl 1.18.1

@salim-lachdhaf salim-lachdhaf added the question Further information is requested label Feb 25, 2024
@salim-lachdhaf
Copy link
Owner

salim-lachdhaf commented Feb 25, 2024

Yes you can force binary download using await _ftpConnectInstance.setTransferType(TransferType.binary); before downloading.

But i'm not sure if it's the issue.

@nankarewa
Copy link

I had the same problem. I was able to solve it by changing a portion of the library's code.

The issue, in my case, was that the server had an unknown certificate, so when trying to download something with FTPES, it would get stuck on 'Start downloading...' because certificate issues were not being handled.

I slightly modified the code in "lib/src/commands/file.dart". I left the download function as follows:

 Future<bool> download(
    String? sRemoteName,
    File fLocalFile, {
    FileProgress? onProgress,
  }) async {
    _socket.logger.log('Download $sRemoteName to ${fLocalFile.path}');
    //check for file existence and init totalData to receive
    int fileSize = 0;
    fileSize = await FTPFile(_socket).size(sRemoteName);
    if (fileSize == -1) {
      throw FTPConnectException('Remote File $sRemoteName does not exist!');
    }
 
    // Enter passive mode
    FTPReply response = await _socket.openDataTransferChannel();

    //the response will be the file, witch will be loaded with another socket
    _socket.sendCommandWithoutWaitingResponse('RETR $sRemoteName');

    // Data Transfer Socket
    int lPort = Utils.parsePort(response.message, _socket.supportIPV6);
    _socket.logger.log('Opening DataSocket to Port $lPort');
    final Socket dataSocket = await Socket.connect(_socket.host, lPort,
        timeout: Duration(seconds: _socket.timeout));
 
     //------Added-------
    //Unknown Certificates causes Deadlock, we can evade those Certificates
    final SecureSocket secureDataSocket = await SecureSocket.secure(
      dataSocket,
      onBadCertificate: (cert) => true
    );


    // Test if second socket connection accepted or not
    response = await _socket.readResponse();
    //some server return two lines 125 and 226 for transfer finished
    bool isTransferCompleted = response.isSuccessCode();
    if (!isTransferCompleted && response.code != 125 && response.code != 150) {
      throw FTPConnectException('Connection refused. ', response.message);
    }

    // Changed to listen mode instead so that it's possible to send information back on downloaded amount
    _socket.logger.log('Start downloading...');
    var sink = fLocalFile.openWrite(mode: FileMode.writeOnly);
    var received = 0;
    await secureDataSocket.listen((data) {    //<-------- Added -------
      sink.add(data);
      if (onProgress != null) {
        received += data.length;
        var percent = ((received / fileSize) * 100).toStringAsFixed(2);
        //in case that the file size is 0, then pass directly 100
        double percentVal = double.tryParse(percent) ?? 100;
        if (percentVal.isInfinite || percentVal.isNaN) percentVal = 100;
        onProgress(percentVal, received, fileSize);
      }
    }).asFuture();

    await secureDataSocket.close();  //<-------- Added -------
    await sink.flush();
    await sink.close();

    if (!isTransferCompleted) {
      //Test if All data are well transferred
      response = await _socket.readResponse();
      if (!response.isSuccessCode()) {
        throw FTPConnectException('Transfer Error.', response.message);
      }
    }

    _socket.logger.log('File Downloaded!');
    return true;
  }

This way, we ignore the certificates and can download files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants