-
-
Save kosso/32b6dcaba0dbf9f9d7fb to your computer and use it in GitHub Desktop.
/* | |
// 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(); | |
I´m done but... my image is corrupt... do you have any idea?
var client = Ti.Network.createHTTPClient(); client.onload = function() { var blobFile = Titanium.Utils.base64encode(this.responseData); Alloy.Globals.fileToUpload = [blobFile,"DSC_0215.JPG"]; alert("Downloaded"); Alloy.Globals.connectFTP(); }; client.open("GET", "http://myurl.com/DCIM/101D3300/DSC_0215.JPG"); client.send();
and then on your 150 code...
var file = Ti.Filesystem.getFile(Ti.Filesystem.tempDirectory,Alloy.Globals.fileToUpload[1]); file.write(Alloy.Globals.fileToUpload[0]); var file_data = file.read(); // verbose for clarity var file_buffer = Ti.createBuffer(); // create empty buffer
....
The files are created on my server, and the file size its correct, but I cant open it. The file is corrupt.
EDIT: the original file size is 6.3MB and the uploaded file to my server is 8.6MB :-0
I don't know if Alloy is causing this. I don't use it. Also this code is quite old.
Thank you! Solved! the responseData don´t need a cast to base64. Works with
var blobFile = this.responseData;
Thank you very much for your support.
One question more... What if I want to create a new Directory in my FTP server? Any idea?
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!!