Skip to content

Instantly share code, notes, and snippets.

@abada
Forked from kosso/TiFTPSockets.js
Created February 28, 2016 14:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abada/d762f2e1151903505be9 to your computer and use it in GitHub Desktop.
Save abada/d762f2e1151903505be9 to your computer and use it in GitHub Desktop.
FTP binary data file uploader in Titanium using Sockets
/*
// Author : @kosso
// Date : May 10, 2015.
// FTP binary data file uploader. With notes etc. which I cobbled togethr while trying to work out this elusive dark art.
All examples I found were using the 'STOR' command incorrectly and wondering why they were getting empty files appear (if they were lucky).
Also, most if not all were using the deprecated Ti.Network.createTCPSocket() method instead of Ti.Network.Socket.createTCP().
eg : http://stackoverflow.com/questions/23971311/titanium-appcelerator-ftp-upload
So... research:
By *far* the most useful comment thread I found on the topic was provided by Doug Handy,
who pointed out that the STOR command was not being used correctly.
https://developer.appcelerator.com/question/120494/upload-file-via-ftp-using-createtcpsocket-
(look way down the many comments).
The most important thing to discover is that *two* socket connections are required.
One for connecting, logging in and 'controlling' things. And another (when needed) for the data transfers themselves.
This second socket is connected to a different port than the 'control socket' port 21.
To determine what port this is, you have to do things in the right order.
To connect to an FTP server:
1. Connect a socket to host:port and a listener for a reply containing status code 220.
- http://en.wikipedia.org/wiki/List_of_FTP_server_return_codes
2. write to socket: 'USER '+ftp_username+'\n' - then wait for status code 331 (error : 430)
3. write to socket: 'PASS '+ftp_password+'\n' - then wait for status code 230 (error : 530)
4. Connected! Now chnage working directory...
5. write to socket: 'CWD '+ftp_home_path+'\n' - then wait for status code 250 (error : 550)
The ftp 'control socket' should now be connected to the server and navigated to the desired folder
To send a file:
1. Send a 'PASV' command to the server and note the response.
The response will contain a string ending with six numbers in parentheses. eg: (123,123,123,123,44,55).
This needs to be parsed, since it contains the info of the *second* 'data socket' we need to create.
The first four numbers are the host IP address : 123.123.123.123
The second two (44 and 55) contain the magic ingredient of the port we need to connect to (it changes, y'see.
Connections should be closed after sending a file.)
We calculate the port using the 5th and 6th numbers : (FIFTH * 256) + SIXTH
eg: (44 * 256) + 55 = 11319 :
So in the example above, we now connect another socket to 123.123.123.123:11319 and set up another listener for it.
2. Once connected, tell the 'control socket' that we want to send a BINARY transfer. (Default is ASCII.)...
: write to control socket : 'TYPE I\n'
3. Now we send the 'control socket' the 'STOR remote_filename' command. This creates the empty file on the server which many people get stuck on.
: write to control socket : 'STOR some_filename.jpg\n' - then wait for status code 150
4. NOW, write the file data (in a buffer stream) to the *data socket*.
5. Close the data socket. (The control socket can stay open for further actions).
Phew!
OK.. Let's do this...
*/
// FTP binary files via FTP using Sockets.
var ftp_host = '1.2.3.4'; // the IP address of your FTP server
var ftp_port = 21;
var ftp_username = 'username';
var ftp_password = 'yourftppassword';
var ftp_home = '/home/folder'; // Your FTP home folder. Or where ever you want to 'CWD' to.
var socket_control; // main control socket
var socket_data; // for read/write
var open_data = false;
function connectFTP(){
Ti.API.info('Connecting FTP Socket.. ');
var socket = Ti.Network.Socket.createTCP({
host: ftp_host,
port: ftp_port,
connected: function (e) {
Ti.API.info('Socket opened!');
socket_control = e.socket;
Ti.Stream.pump(e.socket, readCallback, 1024, true);
},
error: function (e) {
Ti.API.info('Error (' + e.errorCode + '): ' + e.error);
},
});
socket.connect();
}
function readCallback(e) {
if (e.bytesProcessed == -1)
{
// Error / EOF on socket. Do any cleanup here.
console.log('readCallback error');
}
try {
if(e.buffer) {
var responseCode = e.buffer.toString().substr(0,3);
// http://en.wikipedia.org/wiki/List_of_FTP_server_return_codes
var received = e.buffer.toString();
Ti.API.info('Received: ' + received);
Ti.API.info('Status: ' + responseCode);
// Ti.API.info(e);
switch(responseCode) {
case "220":
Ti.API.info("FTP: Logging in");
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'USER ' + ftp_username + '\n'
}), function(_e){
// console.log('callback after USER...');
// console.log(_e);
});
break;
case "331":
Ti.API.info("FTP: Sending credentials");
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'PASS ' + ftp_password + '\n'
}), function(_e){
// console.log('callback after PASS...');
// console.log(_e);
});
break;
case "230":
Ti.API.info("FTP: Log in successful");
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'CWD ' + ftp_home + '\n'
}), function(_e){
// console.log('callback after login CWD ...');
// console.log(_e);
});
break;
case "225":
Ti.API.info("FTP: Data Connection Opened");
break;
case "226":
Ti.API.info("FTP: Closing Data Connection");
break;
case "257":
Ti.API.info("FTP: Pathname created...");
break;
case "221":
Ti.API.info("FTP: Disconnected");
break;
case "227":
Ti.API.info("FTP: Passive mode entered : open_data:"+open_data);
if(open_data){ // Trap this when we want it and not after other 'PASV' commands
console.log('preflight STOR PASV response: ');
// Parse the response..
var msg = e.buffer.toString();
msg = msg.split('(')[1].split(')')[0].split(',');
if(msg.length==6){
// looks ok..
var m6 = msg.pop()*1; // forces int
var m5 = msg.pop()*1;
var remote_ip = msg.join('.');
console.log('remote ip : '+remote_ip);
var remote_port = Math.round((m5 * 256) + m6); // Magic port calculation!
console.log('remote port : '+remote_port);
// OK.. now set up the data socket..
var socket = Ti.Network.Socket.createTCP({
host: remote_ip,
port: remote_port,
connected: function (e) {
Ti.API.info('DATA Socket opened!');
// saving this
socket_data = e.socket;
Ti.Stream.pump(e.socket, readDataCallback, 1024, true);
// set binary transfer mode TYPE I
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'TYPE I\n'
}), function(_e){
console.log('callback after setting TYPE I ...');
console.log(_e);
if(_e.success){
// Binary transfer mode is now ready
// Tell the server the name of the file
// this does not have to be the actual name of the local file
var remote_filename = 'test.jpg';
console.log('send STOR '+remote_filename+' to control socket...');
// now send the STOR to the control socket.. hopefully get a 150 back...
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'STOR '+remote_filename+'\n'
}), function(_e){
//console.log('callback after pre-STOR PASV ...');
//console.log(_e);
});
}
});
},
error: function (e) {
Ti.API.info('Error (' + e.errorCode + '): ' + e.error);
},
});
console.log('connecting DATA socket');
socket.connect();
}
}
break;
case "150":
console.log('DATA connection initiated on control socket!... writing file contents to socket_data');
// by default the data connection is in ASCII mode.
// so, we called 'TYPE I' beforehand to set it to BINARY mode.
// Now get the file we want to send... (from wherever is is). Need to be a
var file = Ti.Filesystem.getFile(Ti.Filesystem.resourcesDirectory, 'test.jpg');
var file_data = file.read(); // verbose for clarity
var file_buffer = Ti.createBuffer(); // create empty buffer
// Create a file stream object with the file_data
var file_stream = Ti.Stream.createStream({ source : file_data, mode : Titanium.Stream.MODE_READ});
// Add it to the buffer
var content_size = file_buffer.append(Ti.Stream.readAll(file_stream));
console.log('File Size : ' + content_size ); // append returns the bytes appended
console.log('WRITE FILE to socket now .. ');
// write to socket_data :
Ti.Stream.write(socket_data, file_buffer, function(_e){
console.log('callback after socket_data write.. ');
console.log(_e);
if(_e.success){
// WOO HOOOOOO!!!!!! It worked!!
console.log('FTP : file uploaded OK! closing connection..');
socket_data.close();
// Nullify stuff
file_buffer = null;
file_stream = null;
file_data = null;
file = null;
open_data = false; // Our PASV trap
} else {
console.log('FTP ERROR ');
}
});
break;
case "250":
Ti.API.info("FTP: Changed active directory to " + ftp_home);
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'PASV\n'
}), function(_e){
console.log('callback after PASV ...');
console.log(_e);
});
break;
case "530":
Ti.API.info("FTP: Login failed, disconnecting");
break;
default:
Ti.API.info("FTP: " + e.buffer.toString());
break;
}
} else {
Ti.API.error('Error: read callback called with no buffer!');
}
} catch (ex) {
Ti.API.error(ex);
}
}
function readDataCallback(e) {
if (e.bytesProcessed == -1)
{
// Error / EOF on socket. Do any cleanup here.
console.log('readDataCallback error');
}
try {
if(e.buffer) {
var responseCode = e.buffer.toString().substr(0,3);
var received = e.buffer.toString();
Ti.API.info('DATA Received: ' + received);
Ti.API.info('DATA Status: ' + responseCode);
Ti.API.info(e);
} else {
Ti.API.error('Error: readDataCallback called with no buffer!');
}
} catch (ex) {
Ti.API.error(ex);
}
}
// Set up some UI and buttons
var win = Ti.UI.createWindow({ backgroundColor: "#fff" });
var buttonConnect = Ti.UI.createButton({ title: "Connect", width: 280, height: 40, top: 40, left: 20 });
var buttonWrite = Ti.UI.createButton({ title: "Write File", width: 280, height: 40, top: 100, left: 20 });
var buttonDisconnect = Ti.UI.createButton({ title: "Disconnect", width: 280, height: 40, top: 160, left: 20 });
buttonConnect.addEventListener("click", function(_event) {
// Set up connection
connectFTP();
});
buttonWrite.addEventListener("click", function(_event) {
open_data = true;// will open second datasocket after the PASV before the STOR
// Send the initial PASV command, so we can get the data host:port to use
Ti.Stream.write(socket_control, Ti.createBuffer({
value: 'PASV\n'
}), function(_e){
//console.log(_e);
});
});
buttonDisconnect.addEventListener("click", function(_event) {
});
win.add(buttonConnect);
win.add(buttonWrite);
win.add(buttonDisconnect);
win.open();
@frodfigu
Copy link

frodfigu commented Feb 3, 2017

Great example!! Congrats!
So I´ve a question. How can I pass the file (blob) to your function? Maybe on Stream.write but... How can I do this?

Thank you!!

@egonbeermat
Copy link

egonbeermat commented Jul 6, 2017

In the hope that you are still looking here: This is the only decent example I found of FTP via Titanium. Using this code unmodified except for IP address, username, password, I call connectFTP() and it creates a socket connection to my FTP server (I get the FTP server header message and a status 220 for login).....but it ends right there on the Ti.Stream.write for USER. No errors visible, but no further progress is made. Eventually, I will see FTP: 421 login timeout. Any help would be appreciated!

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