Flex FTP Abort

To properly close a transfer to the FTP server, you need to send an Abort command (see http://www.w3.org/Protocols/rfc959/4_FileTransfer.html (ABORT) ). Flex FTP doesn’t support this so I added some functionality to be able to send an abort.

First I added the Abort command to the Commands class:

  1. public static const ABOR:String = "ABOR";

After that I added a new invoker to the invokers package, AbortInv.

The FTPInvoker class required a new public method to support the Abort command, which can be overridden by the specific implementations. In FTPInvoker I added an abort method like this:

  1. /**
  2. * Stops this Invoker from executing.
  3. */
  4. public function abort():void {
  5. // Some Invokers just don't need to do anything.
  6. finalize();
  7. }

There was only one invoker where I had to override the abort method to get things working correctly and that was the UploadInv class. Here I added the following code:

  1. /**
  2. * @inheritDoc
  3. */
  4. override public function abort():void {
  5. super.abort();
  6.  
  7. if(interval) clearInterval(interval);
  8. if(sourceFile) sourceFile.close();
  9. if(passiveSocket && passiveSocket.connected) passiveSocket.close();
  10. }

After that it was a matter of updating the FTPClient class with an abort method to get the circle complete.

  1. /**
  2. * Aborts the previous FTP command.
  3. */
  4. public function abort():void {
  5. if(currentProcess) {
  6. if(currentProcess is UploadInv) {
  7. swallowNextResponse = true;
  8. }
  9. currentProcess.abort();
  10. currentProcess = null;
  11. }
  12.  
  13. invoke( new AbortInv(this) );
  14. }

Unfortunately I also had to update handleData method in the FTPClient class a little bit, because aborting an upload would result into 2 (or more) resonses at once from the FTP server.
My handleData method now looks like this:

  1. private function handleData (pEvt:ProgressEvent):void
  2. {
  3. processing = false;
  4. var response:String = ctrlSocket.readUTFBytes(ctrlSocket.bytesAvailable);
  5. var responseList:Array = response.split("\n");
  6. // Apparently we always get a linebreak with a response...
  7. responseList.pop();
  8.  
  9. var eventList:Array = new Array();
  10. var evt:FTPEvent;
  11. for(var i:int = 0; i < responseList.length; i++) {
  12. evt = new FTPEvent(FTPEvent.RESPONSE);
  13. evt.response = FTPResponse.parseResponse(responseList[i]);
  14. eventList.push(evt);
  15. if (FTPClient.DEBUG) trace("->" + evt.response.code+" "+evt.response.message);
  16. }
  17.  
  18. if(swallowNextResponse) {
  19. if (FTPClient.DEBUG) trace(" - ignoring this response...");
  20. swallowNextResponse = false;
  21. responseList.shift();
  22. }
  23.  
  24. if(responseList.length > 0) {
  25. for(var k:int = 0; k < responseList.length; k++) {
  26. dispatchEvent(eventList[k]);
  27. }
  28. }
  29. }

Links

Flex FTP EPSV support

Still working with Flex FTP and this time I was missing EPSV support. EPSV is roughly the same as PASV, exect that you only get a port number to connect to back and not an IP address. You can read up on RFC 2428 “FTP Extensions for IPv6 and NATs” if you want more details.

The first thing I did to add support for EPSV in Flex FTP is adding a response to the Responses class.

  1. public static const ENTERING_EPSV:int = 229; //Entering Extended Passive Mode.

Second was the commands class, where I added the following line:

  1. public static const EPSV:String = "EPSV";

Then I added an extra argument to the FTPClient class’ constructor that will indicate whether or not to use ESPV.

  1. public function FTPClient (host:String="", port:int=21, useEpsv:Boolean = false)

I store the value internally and use a getter method to determine if an Invoker class (i.e ListInv) needs to use EPSV.

Based on the client epsv setting any invoker can be updated to send the EPSV command instead of the PASV command when needed, i.e. my implementation of ListInv’s startSequence method:

  1. override protected function startSequence ():void {
  2. if(client.useEpsv) {
  3. sendCommand(new FTPCommand(Commands.EPSV));
  4. } else {
  5. sendCommand(new FTPCommand(Commands.PASV));
  6. }
  7. }

I also update the responseHandler method in ListInv to include the following case:

  1. case Responses.ENTERING_EPSV:
  2. passiveSocket =    PassiveSocketInfo.createPassiveSocket(evt.response.message,
  3. handlePassiveConnect,
  4. handleListing, ioErrorHandler,
  5. null, true, client.hostname);
  6. break;

On top of that I made some tweaks to the PassiveSocketInfo class.
The parseFromResponse method now looks like this:

  1. public static function parseFromResponse (pasvResponse:String, usingEpsv:Boolean = false, hostName:String = ""):PassiveSocketInfo {
  2. var host:String;
  3. var port:int;
  4.  
  5. if (usingEpsv) {
  6. host = hostName;
  7. port = pasvResponse.match(/\d+/)[0];
  8. } else {
  9. var match:Array = pasvResponse.match(/(\d{1,3},){5}\d{1,3}/);
  10. if (match == null)
  11. throw new Error("Error parsing passive port! (Response: "+pasvResponse+")");
  12. var data:Array = match[0].split(",");
  13. host = data.slice(0,4).join(".");
  14. port = parseInt(data[4])*256+parseInt(data[5]);
  15. }
  16. return new PassiveSocketInfo(host, port);
  17. }

The createPassiveSocket method now takes two extra optional arguments (usingEpsv:Boolean and host:String) and just passes those on to the parseFromResponse method.

Hope this helps anybody looking for EPSV support for Flex FTP.

PS: It’s been a while since I added EPSV support, so let me know about any gaps.

Flex FTP Make Directory

I’m messing around with Flex FTP at the moment and found that there is no way yet to create a directory on the FTP server. I had a quick look through the code on how the other FTP actions were implemented. Being surprised with the nice setup of the code I had quite easily added the “make directory” action and got it working properly. You can download my MakeDirInv class here: http://ansuz.nl/toys/randomcode/MakeDirInv.as
All you need to do after that is patch the FTPClient class by adding the following method.

ActionScript

  1. /**
  2. * Creates a directory on the remote host.
  3. *
  4. * @param newDirName The name of the new directory to create.
  5. * @param parentDir (Optional) The directory to create the new directory in.
  6. */
  7. public function makeDir(newDirName:String, parentDir:String = "/"):void {
  8. trace("FTPClient.makeDir(newDirName, parentDir)");
  9. invoke(new MakeDirInv(this, newDirName, parentDir));
  10. }

Once you’ve done that, you’re ready to create directories on the FTP server you’re connected to, see the example below.

ActionScript

  1. ftpClient.addEventListener(FTPEvent.CREATE_DIR, dirCreatedHandler);
  2. ftpClient.makeDir("myNewDir", "/someSubDir/");

PS: This isn’t fully tested yet, so do let me know if you find some bugs.

Links