Active FTP client for Node.js

I am trying to write an ftp client letter against Filezilla supporting active mode using node.js. I am new to ftp and node.js. I thought I could understand the connection with the tcp socket and the ftp protocol well by doing this exercise. Also, node-ftp a jsftp doesn't seem to support active mode, so I think it will be a nice (albeit rarely used) addition to npm.

I have some evidence of concept code that works at least sometimes, but not all the time. In the case when it works, the client downloads a file called file.txt with the text "hello". When this works, I get the following:

 220-FileZilla Server version 0.9.41 beta 220-written by Tim Kosse (Tim.Kosse@gmx.de) 220 Please visit http://sourceforge.net/projects/filezilla/ 331 Password required for testuser 230 Logged on listening 200 Port command successful 150 Opening data channel for file transfer. server close 226 Transfer OK half closed closed Process finished with exit code 0 

When this does not work, I get the following:

 220-FileZilla Server version 0.9.41 beta 220-written by Tim Kosse (Tim.Kosse@gmx.de) 220 Please visit http://sourceforge.net/projects/filezilla/ 331 Password required for testuser 230 Logged on listening 200 Port command successful 150 Opening data channel for file transfer. server close half closed closed Process finished with exit code 0 

So, I am not getting 226, and I am not sure why I am getting inconsistent results.

Sorry for the poorly written code. I will reorganize as soon as I am sure that I understand how this should work.

 var net = require('net'), Socket = net.Socket; var cmdSocket = new Socket(); cmdSocket.setEncoding('binary') var server = undefined; var port = 21; var host = "localhost"; var user = "testuser"; var password = "Password1*" var active = true; var supplyUser = true; var supplyPassword = true; var supplyPassive = true; var waitingForCommand = true; var sendFile = true; function onConnect(){ } var str=""; function onData(chunk) { console.log(chunk.toString('binary')); //if ftp server return code = 220 if(supplyUser){ supplyUser = false; _send('USER ' + user, function(){ }); }else if(supplyPassword){ supplyPassword = false; _send('PASS ' + password, function(){ }); } else if(supplyPassive){ supplyPassive = false; if(active){ server = net.createServer(function(socket){ console.log('new connection'); socket.setKeepAlive(true, 5000); socket.write('hi', function(){ console.log('write done'); }) socket.on('connect', function(){ console.log('socket connect'); }); socket.on('data', function(d){ console.log('socket data: ' + d); }); socket.on('error', function(err){ console.log('socket error: ' + err); }); socket.on('end', function() { console.log('socket end'); }); socket.on('drain', function(){ console.log('socket drain'); }); socket.on('timeout', function(){ console.log('socket timeout'); }); socket.on('close', function(){ console.log('socket close'); }); }); server.on('error', function(e){ console.log(e); }); server.on('close', function(){ console.log('server close'); }); server.listen(function(){ console.log('listening'); var address = server.address(); var port = address.port; var p1 = Math.floor(port/256); var p2 = port % 256; _sendCommand('PORT 127,0,0,1,' + p1 + ',' + p2, function(){ }); }); }else{ _send('PASV', function(){ }); } } else if(sendFile){ sendFile = false; _send('STOR file.txt', function(){ }); } else if(waitingForCommand){ waitingForCommand = false; cmdSocket.end(null, function(){ }); if(server)server.close(function(){}); } } function onEnd() { console.log('half closed'); } function onClose(){ console.log('closed'); } cmdSocket.once('connect', onConnect); cmdSocket.on('data', onData); cmdSocket.on('end', onEnd); cmdSocket.on('close', onClose); cmdSocket.connect(port, host); function _send(cmd, callback){ cmdSocket.write(cmd + '\r\n', 'binary', callback); } 

Also, is server suitable, or should I do it in a different way?

EDIT: I changed the callback in server.listen to use a random port. This removed the 425 that I was getting earlier. However, I still do not get consistent file transfer behavior.

+8
sockets ftp tcp
source share
1 answer

The stream for FTP transfers in active mode looks something like this:

  • Connection Preamble ( USER / PASS )
  • Set local client socket for data
  • Tell the server of this socket ( PORT )
  • Tell the server to open the remote file for writing ( STOR )
  • Start writing data from the data socket set above ( socket.write() )
  • Close the client-side stream ( socket.end() ) to complete the file transfer.
  • Tell the server that you are done ( QUIT )
  • Clear all open sockets and servers on the client

So, once you have done this:

 else if(sendFile){ sendFile = false; _send('STOR file.txt', function(){ }); } 

The server will respond 150 expression that it is connected to the data installation slot and is ready to receive data.

One of the improvements to simplify the discussion of execution at this point would be to change the control flow to work with the analyzed response code, and not with predefined bools.

 function onData(chunk) { console.log(chunk); var code = chunk.substring(0,3); if(code == '220'){ 

instead:

 function onData(chunk) { console.log(chunk.toString('binary')); //if ftp server return code = 220 if(supplyUser){ 

Then you can add a section to send data:

 //ready for data else if (code == '150') { dataSocket.write('some wonderful file contents\r\n', function(){}); dataSocket.end(null, function(){}); } 

And a little more to clean:

 //transfer finished else if ( code == '226') { _send('QUIT', function(){ console.log("Saying Goodbye");}); } //session end else if ( code == '221') { cmdSocket.end(null, function(){}); if(!!server){ server.close(); } } 

Obviously, the situation will become more complicated if you send multiple files, etc., but this should make your proof of concept more reliable:

 var net = require('net'); Socket = net.Socket; var cmdSocket = new Socket(); cmdSocket.setEncoding('binary') var server = undefined; var dataSocket = undefined; var port = 21; var host = "localhost"; var user = "username"; var password = "password" var active = true; function onConnect(){ } var str=""; function onData(chunk) { console.log(chunk.toString('binary')); var code = chunk.substring(0,3); //if ftp server return code = 220 if(code == '220'){ _send('USER ' + user, function(){ }); }else if(code == '331'){ _send('PASS ' + password, function(){ }); } else if(code == '230'){ if(active){ server = net.createServer(function(socket){ dataSocket = socket; console.log('new connection'); socket.setKeepAlive(true, 5000); socket.on('connect', function(){ console.log('socket connect'); }); socket.on('data', function(d){ console.log('socket data: ' + d); }); socket.on('error', function(err){ console.log('socket error: ' + err); }); socket.on('end', function() { console.log('socket end'); }); socket.on('drain', function(){ console.log('socket drain'); }); socket.on('timeout', function(){ console.log('socket timeout'); }); socket.on('close', function(){ console.log('socket close'); }); }); server.on('error', function(e){ console.log(e); }); server.on('close', function(){ console.log('server close'); }); server.listen(function(){ console.log('listening'); var address = server.address(); var port = address.port; var p1 = Math.floor(port/256); var p2 = port % 256; _send('PORT 127,0,0,1,' + p1 + ',' + p2, function(){ }); }); }else{ _send('PASV', function(){ }); } } else if(code == '200'){ _send('STOR file.txt', function(){ }); } //ready for data else if (code == '150') { dataSocket.write('some wonderful file contents\r\n', function(){}); dataSocket.end(null, function(){}); } //transfer finished else if ( code == '226') { _send('QUIT', function(){ console.log("Saying Goodbye");}); } //session end else if ( code == '221') { cmdSocket.end(null, function(){}); if(!!server){ server.close(); } } } function onEnd() { console.log('half closed'); } function onClose(){ console.log('closed'); } cmdSocket.once('connect', onConnect); cmdSocket.on('data', onData); cmdSocket.on('end', onEnd); cmdSocket.on('close', onClose); cmdSocket.connect(port, host); function _send(cmd, callback){ cmdSocket.write(cmd + '\r\n', 'binary', callback); } 
+1
source share

All Articles