/*
 iosFtpServer
 Copyright (C) 2008  Richard Dearlove ( monsta )
 
 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.
 
 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.
 
 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#import "FtpConnection.h"
#import "FtpServer.h"


@implementation FtpConnection

@synthesize transferMode;
@synthesize currentFile;	
@synthesize currentDir;
@synthesize rnfrFilename;

// ----------------------------------------------------------------------------------------------------------
-(id)initWithAsyncSocket:(AsyncSocket*)newSocket forServer:(id)myServer 
// ----------------------------------------------------------------------------------------------------------
{
	self = [super init ];
	if (self)
	{
		connectionSocket = [newSocket retain ];
		server = myServer;
		[ connectionSocket setDelegate:self ];
		[ connectionSocket writeData:DATASTR(@"220 iosFtp server ready.\r\n") withTimeout:-1 tag:0 ];					// send out the welcome message to the client
		[ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];		// start listening for commands on this connection to client
		dataListeningSocket = nil;
		dataPort=2001;
		transferMode = pasvftp;
		queuedData = [[ NSMutableArray alloc ] init ];								// A buffer for sending data when the connection isn't quite up yet
		self.currentDir = [ server.baseDir copy];												// the working directory for this connection, Starts in the directory the server is set to.  set chroot=true in server code to sandbox in
		currentFile = nil;	
		currentFileHandle = nil;													// not saving to a file yet
		rnfrFilename = nil; 
		
		currentUser = nil;
		NSLog(@"FC: Current Directory starting at %@",currentDir );
	}
	return self;
}
// ----------------------------------------------------------------------------------------------------------
-(void)dealloc
// ----------------------------------------------------------------------------------------------------------
{
	if(connectionSocket)
	{
		[connectionSocket setDelegate:nil];
		[connectionSocket disconnect];
		[connectionSocket release];
	}
	
	if(dataListeningSocket){														// 12/2/10 - think this code  is now redundant, dataListeningSocket, does it do anything anymore...?
		[dataListeningSocket setDelegate:nil];
		[dataListeningSocket disconnect];
		[dataListeningSocket release];
		
	}
	if(dataSocket)
	{
		[dataSocket setDelegate:nil];
		[dataSocket disconnect];
		[dataSocket release];
		
	}
	if(dataConnection)[dataConnection release];
	//	[msgComponents release];
	if(queuedData)[queuedData release];
	if(currentFile)[currentFile release];
	if(currentUser)[currentUser release];
	[currentDir release];
	if(currentFileHandle) [currentFileHandle release];
	
	[super dealloc];
}
#pragma mark STATE 
// ----------------------------------------------------------------------------------------------------------
//-(void)setTransferMode:(int)mode
// ----------------------------------------------------------------------------------------------------------
//{
//	transferMode = mode;
//}

// ----------------------------------------------------------------------------------------------------------
-(NSString*)connectionAddress
// ----------------------------------------------------------------------------------------------------------
{
	return [connectionSocket connectedHost];
	
}


// ==========================================================================================================
#pragma mark CHOOSE DATA SOCKET 
// ==========================================================================================================



// ----------------------------------------------------------------------------------------------------------
-(BOOL)openDataSocket:(int)portNumber
// ----------------------------------------------------------------------------------------------------------
{
	NSString *responseString;
	NSError *error = nil;		
	
	if (dataSocket)																			// Code changed 2010-02-12 - As per Jon's fix. stops memory leak on socket and connection
	{ 
		[dataSocket release]; 
		dataSocket = nil; 
	} 
	dataSocket = [ [ AsyncSocket alloc ] initWithDelegate:self ];                           // 	create our socket for Listening or Direct Connection 
	if (dataConnection) 
	{ 
		[dataConnection release]; 
		dataConnection = nil; 
	} 


	
	switch (transferMode) {
		case portftp:
			dataPort = portNumber;
			responseString = [ NSString stringWithFormat:@"200 PORT command successful."];
			[ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
			//			sleep(1);
			dataConnection = [[ FtpDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];	
			
			break;
			
		case lprtftp:					// FIXME wrong return message
			dataPort = portNumber;
			responseString = [ NSString stringWithFormat:@"228 Entering Long Passive Mode 	(af, hal, h1, h2, h3,..., pal, p1, p2...)", dataPort >>8, dataPort & 0xff];
			[ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
			dataConnection = [[ FtpDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];	
			break;
			
		case eprtftp:
			dataPort = portNumber;
			responseString = @"200 EPRT command successful.";
			[ dataSocket connectToHost:[self connectionAddress] onPort:portNumber error:&error ];
			dataConnection = [[ FtpDataConnection alloc ] initWithAsyncSocket:dataSocket forConnection:self withQueuedData:queuedData ];	
			break;
			
		case pasvftp:
			dataPort = [ self choosePasvDataPort ];
			NSString *address = [ [connectionSocket localHost ] stringByReplacingOccurrencesOfString:@"." withString:@"," ]; // autoreleased
			responseString = [ NSString stringWithFormat:@"227 Entering Passive Mode (%@,%d,%d)",address, dataPort >>8, dataPort & 0xff];				
			[ dataSocket acceptOnPort: dataPort error:&error  ];
			dataConnection = nil;  // will pickup from the listening socket
			break;
			
		case epsvftp:
			dataPort = [ self choosePasvDataPort ];
			responseString = [ NSString stringWithFormat:@"229 Entering Extended Passive Mode (|||%d|)", dataPort ];				
			[ dataSocket acceptOnPort: dataPort error:&error  ];
			
			dataConnection = nil;  // will pickup from the listening socket
			break;
			
			
		default:
			break;
	}
	NSLog( @"-- %@", [ error localizedDescription ] );
	
	[ self sendMessage:responseString ];
	
	return YES;
}


// ----------------------------------------------------------------------------------------------------------
-(int)choosePasvDataPort
// ----------------------------------------------------------------------------------------------------------
{
	struct timeval tv;
    unsigned short int seed[3];
	
    gettimeofday(&tv, NULL);
    seed[0] = (tv.tv_sec >> 16) & 0xFFFF;
    seed[1] = tv.tv_sec & 0xFFFF;
    seed[2] = tv.tv_usec & 0xFFFF;
    seed48(seed);
	
	int portNumber;
	portNumber = (lrand48() % 64512) + 1024;
	//	NSLog(@"New Port number is %i", portNumber );
	
	return portNumber;																// FIXME - presetting to 2001 for the moment
	
	//return 2001;
	
}



// ==========================================================================================================
#pragma mark ASYNCSOCKET DATACONNECTION 
// ==========================================================================================================


// ----------------------------------------------------------------------------------------------------------
-(BOOL)onSocketWillConnect:(AsyncSocket *)sock 
// ----------------------------------------------------------------------------------------------------------
{
	NSLog(@"FC:onSocketWillConnect");
	[ sock readDataWithTimeout:READ_TIMEOUT tag:0 ];

	return YES;
}

// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket		// should be for a data connection on 2001
// ----------------------------------------------------------------------------------------------------------
{
	// opened socket for passive data  - new socket connected to this which is the passive connection
	
	NSLog(@"FC:New Connection -- should be for the data port");
	
	dataConnection = [[ FtpDataConnection alloc ] initWithAsyncSocket:newSocket forConnection:self  withQueuedData:queuedData];	
}



// ==========================================================================================================
#pragma mark ASYNCSOCKET FTPCLIENT CONNECTION 
// ==========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket*)sock didReadData:(NSData*)data withTag:(long)tag			// DATA READ
// ----------------------------------------------------------------------------------------------------------
{
	
	NSLog(@"FC:didReadData");
	[ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];		// start reading again
	
	[ self processDataRead:data ];

}


// ----------------------------------------------------------------------------------------------------------
-(void)onSocket:(AsyncSocket*)sock didWriteDataWithTag:(long)tag						// DATA WRITTEN
// ----------------------------------------------------------------------------------------------------------
{
	[ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];	// start reading again	
	//	NSLog(@"FC:didWriteData");
	
}

// ----------------------------------------------------------------------------------------------------------
-(void)sendMessage:(NSString*)ftpMessage				// REDUNDANT really - FIXME
// ----------------------------------------------------------------------------------------------------------
{
	NSLog(@">%@",ftpMessage );
	NSMutableData *dataString = [[ ftpMessage dataUsingEncoding:NSUTF8StringEncoding ] mutableCopy];				// Autoreleased
	[ dataString appendData:[AsyncSocket CRLFData] ];												
	
	[ connectionSocket writeData:dataString withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];
	[dataString release];
	[ connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];	// start reading again		
	//	[ connectionSocket readDataWithTimeout:READ_TIMEOUT tag:FTP_CLIENT_REQUEST ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)sendDataString:(NSString*)dataString
// ----------------------------------------------------------------------------------------------------------
{
	NSMutableString *message = [[NSMutableString alloc] initWithString:dataString];
	CFStringNormalize((CFMutableStringRef)message, kCFStringNormalizationFormC);
	NSMutableData *data = [[ message dataUsingEncoding:server.clientEncoding ] mutableCopy];				// Autoreleased
	[message release];

	if (dataConnection )
	{	NSLog(@"FC:sendData");
		[ dataConnection writeData:data ];
		
	}
	else
	{
		[ queuedData addObject:data ];
	}
	[data release];
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)sendData:(NSMutableData*)data
// ----------------------------------------------------------------------------------------------------------
{
	if (dataConnection )
	{	NSLog(@"FC:sendData");
		[ dataConnection writeData:data ];
	}
	else
	{
		[ queuedData addObject:data ];
	}
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)didReceiveDataWritten			// notification from FtpDataConnection that the dataWasWritten
// ----------------------------------------------------------------------------------------------------------
{
	
	
	
	NSLog(@"SENDING COMPLETED");
	
	[ self sendMessage:@"226 Transfer complete." ]; // send completed message to client
	[ dataConnection closeConnection ];		
}

// ----------------------------------------------------------------------------------------------------------
-(void)didReceiveDataRead			// notification from FtpDataConnection that the dataWasWritten
// ----------------------------------------------------------------------------------------------------------
{
	
	NSLog(@"FC:didReceiveDataRead");
	
	// must have sent a file
	
	// Should start writing file out if not written yet : FIXNOW
	
	if ( currentFileHandle != nil )
	{
		// Append data on
		NSLog(@"FC:Writing File to %@", currentFile );
		[ currentFileHandle writeData:dataConnection.receivedData ];
		
	}
	else
	{
		NSLog(@"Couldnt write data");
	}
	
	
	
}

// ----------------------------------------------------------------------------------------------------------
-(void)didFinishReading							// Called at the end of a data connection from the client we presume
// ----------------------------------------------------------------------------------------------------------
{
	if (currentFile)
	{
		
		NSLog(@"Closing File Handle");
		[currentFile release];
		currentFile = nil;
	}
	else
	{
		NSLog(@"FC:Data Sent but not sure where its for ");
	}
	
	[ self sendMessage:@"226 Transfer complete." ]; // send completed message to client
	
	//	[ dataConnection closeConnection ];									// It must be closed, it dropped us
	
	if ( currentFileHandle != nil )
	{
		NSLog(@"Closing File Handle");
		
		[ currentFileHandle closeFile ];								// Close the file handle
		[ currentFileHandle release ];
		currentFileHandle = nil;
		[ server didReceiveFileListChanged];
	}
	
	dataConnection.connectionState = clientQuiet;
	
}


// ==========================================================================================================
#pragma mark PROCESS 
// =========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)processDataRead:(NSData*)data			// convert to commands as Client Connection
// ----------------------------------------------------------------------------------------------------------
{	
	NSData *strData = [data subdataWithRange:NSMakeRange(0, [data length] - 2)];				// remove last 2 chars
	NSString *crlfmessage = [[[NSString alloc] initWithData:strData encoding:server.clientEncoding] autorelease];
	NSString *message;
	
	//	message = [ crlfmessage stringByReplacingOccurrencesOfString:@"\r\n" withString:@""];		// gets autoreleased
	
	message = [ crlfmessage stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet ]];
	NSLog(@"<%@",message );
    msgComponents = [message componentsSeparatedByString:@" "];										// change this to use spaces - for the FTP protocol	
	
	[ self processCommand ];
	
	[connectionSocket readDataToData:[AsyncSocket CRLFData] withTimeout:-1 tag:0 ];					// force to readdata CHECK
}

// ----------------------------------------------------------------------------------------------------------
-(void)processCommand // assumes data has been place in Array msgComponents
// ----------------------------------------------------------------------------------------------------------
{
	NSString *commandString =  [ msgComponents objectAtIndex:0];
	
	if ([ commandString length ] > 0)																// If there is a command here
	{
		// Search through dictionary for correct matching command and method that it calls
		
		NSString *commandSelector = [ [[server commands] objectForKey:[commandString lowercaseString] ] stringByAppendingString:@"arguments:"];
		
		if ( commandSelector )																		// If we have a matching command
		{
			
			SEL action = NSSelectorFromString(commandSelector);										// Turn into a method
			
			if ( [ self respondsToSelector:action ])												// If we respond to this method
			{	
				// DO COMMAND
				[self performSelector:action withObject:self withObject:msgComponents ];			// Preform method with arguments
			}
			else
			{			// UNKNOWN COMMAND
				NSString *outputString =[ NSString stringWithFormat:@"500 '%@': command not understood.", commandString ];
				[ self sendMessage:outputString ];
				
				NSLog(@"DONT UNDERSTAND");
			}
		}
		else			// UNKNOWN COMMAND
		{
			NSString *outputString =[ NSString stringWithFormat:@"500 '%@': command not understood.", commandString ];
			[ self sendMessage:outputString ];
		}
	}
	else
	{
		// Write out an error msg
	}
	
}

// ==========================================================================================================
#pragma mark COMMANDS 
// ==========================================================================================================

// ----------------------------------------------------------------------------------------------------------
-(void)doQuit:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSLog(@"Quit : %@",arguments);
	
	[ self sendMessage:@"221- Data traffic for this session was 0 bytes in 0 files"];
	
	[self sendMessage:@"221 Thank you for using the FTP service on localhost." ];
	
	if(connectionSocket)
	{
		[connectionSocket disconnectAfterWriting ];				// Will this close the socket ?
		//	[connectionSocket disconnect];
	}
	
	[ server closeConnection:self ];			// Tell the server to close us down, remove us from the list of connections
	
	
	// FIXME - delete the dataconnection if its open
	
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doUser:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	// send out confirmation message -- 331 password required for
	if ( currentUser != nil )
		[currentUser release];
	currentUser =  [[ arguments objectAtIndex:1 ] retain];
	NSString *outputString = [ NSString stringWithFormat:@"331 Password required for %@", currentUser ];
	[ sender sendMessage:outputString];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPass:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
//	NSString *pass = [ arguments objectAtIndex:1 ]; 
	NSString *outputString = [ NSString stringWithFormat:@"230 User %@ logged in.", currentUser ];
	[ sender sendMessage:outputString];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doStat:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	// Send out stat message
	[ sender sendMessage:@"211-localhost FTP server status:"];
	// FIXME - add in the stats
	[ sender sendMessage:@"211 End of Status"];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doFeat:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	[ sender sendMessage:@"211-Features supported"];
    
    // If encoding is UTF8, notify the client
    if (server.clientEncoding == NSUTF8StringEncoding)
        [ sender sendMessage:@" UTF8" ];
    
	[ sender sendMessage:@"211 End"];
}




// ----------------------------------------------------------------------------------------------------------
-(void)doList:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	// Get the name of any additional directory that we are asked to list,  if its empty we use the current directory
	
	NSString *lsDir = [ self fileNameFromArgs:arguments] ;				// autoreleased,  get the directory that we're being asked for
	NSString *listText;
	

	if ([lsDir length]<1) {
		lsDir = currentDir;
	}
	else {
		lsDir = [self rootedPath:lsDir ];
	}

	

	NSLog( @"doList currentDir(%@) changeRoot%d", lsDir, server.changeRoot );
	NSLog(@"Will list %@ ",lsDir);
	listText = [ [ server createList:lsDir] retain ];						// Can list directory so do it	
//	if ([self canChangeDirectoryTo:lsDir]) {									
//		listText = [ [ server createList:lsDir] retain ];						// Can list directory so do it
//	}
//	else {
//		listText = @"";															// return nothing as not in chroot
//	}
	
	NSLog( @"doList sending this. %@", listText );
																	
	[ sender sendMessage:@"150 Opening ASCII mode data connection for '/bin/ls'."];	

	[ sender sendDataString:listText  ];	
	[listText release ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPwd:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	
	NSLog(@"Will PWD %@ ",[self visibleCurrentDir]);
	
	// CHECKME - changed to show basedir - currentdir
	

	NSString *cmdString = [ NSString stringWithFormat:@"257 \"%@\" is the current directory.", [self visibleCurrentDir] ]; // autoreleased
	[ sender sendMessage:cmdString ];			// FIXME - seems to be buggy on ftp command line client
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doNoop:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	[sender sendMessage:@"200 NOOP command successful." ];
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doSyst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	[ sender sendMessage:@"215 UNIX Type: L8 Version: iosFtp 20080912" ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doLprt:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{	
	// LPRT,"6,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,231,92"	
	NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
	NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"," ];
	
	int hb = [[socketAddr objectAtIndex:19] intValue ];
	int lb = [[socketAddr objectAtIndex:20] intValue ];
	
	NSLog(@"%d %d %d",hb <<8, hb,lb );
	int clientPort = (hb <<8 ) + lb;
	
	[sender setTransferMode:lprtftp];
	
	[ sender openDataSocket:clientPort ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doEprt:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	// EPRT |2|1080::8:800:200C:417A|5282|
	
	NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
	NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"|" ];
	
	NSString *item;
	for (item in socketAddr) {
		NSLog(@"%@", item);
	}
	int clientPort = [[ socketAddr objectAtIndex:3 ] intValue ];
	
	NSLog(@"Got Send Port %d", clientPort );
	
	[sender setTransferMode:eprtftp];
	//	[ sender initDataSocket:clientPort ];
	[ sender openDataSocket:clientPort ];
}


// ----------------------------------------------------------------------------------------------------------
-(void)doPasv:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	[sender setTransferMode:pasvftp];
	//	[ sender initDataSocket:0 ];
	[ sender openDataSocket:0 ];
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doEpsv:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	// FIXME - open a port random high address
	[sender setTransferMode:epsvftp];
	//	[ sender initDataSocket:0 ];
	[ sender openDataSocket:0 ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doPort:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	int hb, lb;
	
	//  PORT 127,0,0,1,197,251  looks like this
	// get 2nd argument and split up by , and then take the last 2 bits
	
	NSString *socketDesc = [ arguments objectAtIndex:1 ] ;
	NSArray *socketAddr = [ socketDesc componentsSeparatedByString:@"," ];
	
	hb = [[socketAddr objectAtIndex:4] intValue ];
	lb = [[socketAddr objectAtIndex:5] intValue ];
	
	
	int clientPort = (hb <<8 ) + lb;
	
	
	[sender setTransferMode:portftp];
	
	[ sender openDataSocket:clientPort ];
	
}

// ----------------------------------------------------------------------------------------------------------
-(void)doOpts:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSString *cmd = [ arguments objectAtIndex:1 ];
	NSString *cmdstr = [ NSString stringWithFormat:@"502 Unknown command '%@'",cmd ];
	[ sender sendMessage:cmdstr ];
	//	502 Unknown command 'sj'		
}
// ----------------------------------------------------------------------------------------------------------
-(void)doType:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	//		200 Type set to A.
	
	// FIXME - change the data output to the matching type -- we dont do anything with this yet,, there are many types apart from this one.
	
	NSString *cmd =  [ arguments objectAtIndex:1 ];
	
	//if ( [ [cmd lowercaseString] isEqualToString:@"i" ])
	//	{
	NSString *cmdstr = [ NSString stringWithFormat:@"200 Type set to  %@.",cmd ];
	[ sender sendMessage:cmdstr ];
	//	}
	//	else
	//	{
	//		NSString *cmdstr = [ NSString stringWithFormat:@"500 'type %@': command not understood.",cmd ];
	//		[ sender sendMessage:cmdstr ];		
	//	}
}
// ----------------------------------------------------------------------------------------------------------
-(void)doCwd:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	//	250 
	//	500, 501, 502, 421, 530, 550
	//	250 CWD command successful.
	
	NSLog(@"DoCwd arguments is %@",arguments);
	
	NSString *cmdstr;
	NSString *cwdDir = [ self fileNameFromArgs:arguments] ;				// autoreleased,  get the directory that we're being asked for

	if ( [ self changedCurrentDirectoryTo:cwdDir ] )											// tries to change to that directory, checks in bounds and viable
	{
		cmdstr = [ NSString stringWithFormat:@"250 OK. Current directory is %@", [self visibleCurrentDir]];		// currentDir is now in the new place
		cmdstr = @"250 CWD command successful.";
	}
	else
	{
		cmdstr = @"550 CWD failed.";
	}
	
	[ sender sendMessage:cmdstr];
	
	NSLog(@"currentDir is now %@",currentDir );	
	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doNlst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	[self doList:sender arguments:arguments ];
}
// ----------------------------------------------------------------------------------------------------------
-(void)doStor:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{	
	// eg STOR my filename here.mp3        STOR=0 my=1 filename=2 etc
	
	NSFileManager	*fs = [NSFileManager defaultManager];	
	NSString		*filename = [ self fileNameFromArgs:arguments];		// autoreleased
	NSString		*cmdstr;										

	self.currentFile = [self makeFilePathFrom:filename];														// makes a filepath, using absolute or relative filename
	
	// Check this falls within the area of filesystem we are allowed to write to
	if ( [self validNewFilePath:self.currentFile] )																// FIXME - finish function for test
	{		
		// CREATE and then OPEN NEW FILE FOR WRITING
		if ([fs createFileAtPath:self.currentFile contents:nil attributes:nil]==YES)								
		{
			currentFileHandle = [[ NSFileHandle fileHandleForWritingAtPath:currentFile] retain];				// Open the file handle to write to
			cmdstr   = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];	// autoreleased
		}
		else
		{
			// couldn't make file, send out the error
			cmdstr = [ NSString stringWithFormat:@"553 %@: Permission denied.", filename ];

		}
	}
	else
	{
		// couldn't make file as out of root area
		cmdstr = [ NSString stringWithFormat:@"553 %@: Permission denied.", filename ];
	}

	NSLog(@"FC:doStor  %@", currentFile );

	
	[sender sendMessage:cmdstr];
	
																									// CHECKME - data connection should have been brought up by client?
	if (dataConnection )																			// if not we're in trouble. mind u currentFile is doing similar job. 
	{
		NSLog(@"FC:setting connection state to clientSending");
		dataConnection.connectionState = clientSending;
	}
	else
	{
		NSLog(@"FC:Erorr  Cant set connection state to Client Sending : no Connection yet ");
	}
	
	
	
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRetr:(id)sender arguments:(NSArray*)arguments			// DOWNLOAD to CLIENT
// ----------------------------------------------------------------------------------------------------------
{
	BOOL isDir;
	NSString *cmdstr; 
	
	NSString *filename = [self fileNameFromArgs:arguments];		// autoreleased
	NSString *filePath = [self makeFilePathFrom:filename];			// turns relative or absolute path into format we need
	
	NSLog(@"FC:doRetr: %@", filePath );
	
	if ( [self accessibleFilePath:filePath ] )					// if this filepath is in rooted area, and is a real file
	{
		if ( [ [ NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory: &isDir ])			// FIXME - fold into previous line
		{
			if ( isDir ){																				// reject if its a directory request
				[ sender sendMessage: [NSString stringWithFormat:@"550 %@: Not a plain file.",filename]];
			}
			else																						// SEND FILE
			{	// FIXME URGENT - need to stop loading whole file into memory to send
				NSMutableData   *fileData = [[ NSMutableData dataWithContentsOfFile:filePath ] retain];										// FIXME - open in bits ? seems risky opening file in one piece
				cmdstr = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];
				[sender sendMessage:cmdstr];
				
				[ sender sendData:fileData  ];																								// Send file
				[fileData release ];
			}		
		}
	}
	else			// doesn't exist or not in basedir sandbox
	{
		cmdstr = [ NSString stringWithFormat:@"50 %@ No such file or directory.",filename ];		
		NSLog(@"FC:doRetr: file %@ doesnt' exist ", filePath);
		[sender sendMessage:cmdstr];
	}


}

// ----------------------------------------------------------------------------------------------------------
-(void)doDele:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSString *cmdStr;
	NSError  *error;
	NSString *filename =[self fileNameFromArgs:arguments];		// autoreleased
	NSString *filePath = [self makeFilePathFrom:filename];
	
	NSLog(@"filename is %@",filename);

	// attempt to delete the file
	
	if ( [self accessibleFilePath:filePath ])						// exists and can access
	{				
		if ([[ NSFileManager defaultManager ] removeItemAtPath:filePath error:&error ])
		{
			cmdStr = [ NSString stringWithFormat:@"250 DELE command successful.",filename ];
			[ server didReceiveFileListChanged];				// Addition by Brendan copied in by Rich 16/12/08
		}
		else
		{
			cmdStr = [ NSString stringWithFormat:@"550 DELE command unsuccessful.",filename ];					// FIXME put correct error code in
		}
	}
	else
	{
		cmdStr = [ NSString stringWithFormat:@"550 %@ No such file or directory.", filename];
	}
	
	[ sender sendMessage:cmdStr	];

	
}
// ----------------------------------------------------------------------------------------------------------
-(void)doMlst:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSString *filename = [self fileNameFromArgs: arguments];
	NSString *cmdstr = [ NSString stringWithFormat:@"150 Opening BINARY mode data connection for '%@'.",filename ];
	// tell connection to expect a file	
	
	[sender sendMessage:cmdstr];										// FIXME - this doesn't do anything beyond respond with first message
	/*  typiccal output to generate
	 250: MLST 2012.pdf
	 Type=file;Size=2420017;Modify=20080808074805;Perm=adfrw;Unique=AgAADpIHZwA; /Users/monsta/Documents/2012.pdf
	 End
	 */
}
// ----------------------------------------------------------------------------------------------------------
-(void)doSize:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSString *cmdStr;
	NSString *filename = [self fileNameFromArgs: arguments];									// Autoreleased
	NSString *filePath = [self makeFilePathFrom:filename];										// Autoreleased  

	if ([self accessibleFilePath:filePath])														// Can we reach that file ?
	{
		if ([self fileSize:filePath] < 10240)													// If small enough for old style size command
		{
			cmdStr = [ NSString stringWithFormat:@"213 %qu",[self fileSize:filePath] ];			// report size
		}
		else
		{
			cmdStr = [ NSString stringWithFormat:@"550 %@ file too large for SIZE.",filename ];
		}		
	}
	else
	{
		cmdStr = [ NSString stringWithFormat:@"550 %@ No such file or directory.",filename ];	// report file not found		
	}	
	
	// tell connection to expect a file	
	
	[sender sendMessage:cmdStr];

}


// ----------------------------------------------------------------------------------------------------------
-(void)doMkdir:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSLog(@"current dir is %@",currentDir);
	
	NSString *cmdStr;
	NSString		*p  = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
	NSFileManager	*fs = [NSFileManager defaultManager];
	
	if ( [self validNewFilePath:p ])										// FIXME - make sure function works. see function
	{			
		if( [fs fileExistsAtPath:p isDirectory:nil] )
		{
			cmdStr = [ NSString stringWithFormat:@"Error %@ exists",[self fileNameFromArgs:arguments] ];	
		}
		else
		{
			// FIXME  - check that its ok to make a directory in this position
			
			[fs createDirectoryAtPath:p withIntermediateDirectories:YES attributes:nil error:nil]; 
			cmdStr = [ NSString stringWithFormat:@"250 MKD command successful."];	

		}
	}
	[sender sendMessage:cmdStr];
}

// ----------------------------------------------------------------------------------------------------------
-(void)doCdUp:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSLog(@"CurrentDir is %@",[self visibleCurrentDir]);
	
	NSString *upDir=[[self visibleCurrentDir] stringByDeletingLastPathComponent];
	
	
	
	if ( [self changedCurrentDirectoryTo:upDir] )											// checks to see if its ok before moving
	{			
		[sender sendMessage:@"250 CDUP command successful."]; 		
	}
	else
	{
		// create message saying you cant go to that directory - FIXME
		[ sender sendMessage:@"550 CDUP command failed." ];									// CHECKME - look at a typical ftp mkdr command failure message
	}
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRnfr:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{		
	self.rnfrFilename = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
	
	if ( [ self accessibleFilePath:self.rnfrFilename ] )										// FIXME - finish function
	{
				
		if ( [[ NSFileManager defaultManager] fileExistsAtPath: rnfrFilename ] )
		{
			[ sender sendMessage:@"350 RNFR command successful." ];
		}
		else
			[ sender sendMessage:@"550 RNFR command failed." ];
	}
}

// ----------------------------------------------------------------------------------------------------------
-(void)doRnto:(id)sender arguments:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{	
	// FIXME - check its ok to use the new filename - ie in sandbox/basedir	
	
	if ( self.rnfrFilename == nil ){
		[ sender sendMessage:@"550 RNTO command failed." ];
		return;
	}
	
	NSError *error;
	NSString *rntoFilename = [self makeFilePathFrom:[self fileNameFromArgs:arguments]];
	
	NSLog( @"%@", rnfrFilename );
	NSLog( @"%@", rntoFilename );
	
	if ([self validNewFilePath:rntoFilename])													// FIXME - finish function
	{	
		if ( [[ NSFileManager defaultManager] moveItemAtPath:rnfrFilename toPath: rntoFilename error:&error ] ){
				[ server didReceiveFileListChanged];
			[ sender sendMessage:@"250 RNTO command successful." ];
		}
		else{
			NSString *errorString = [error localizedDescription];
			NSLog( @"RNTO failed %@", errorString );
			[ sender sendMessage:@"550 RNTO command failed." ];
		}
//		[rnfrFilename release];
//		rnfrFilename = nil;
//		[rntoFilename release];													// auto released, should be fine to just forget
		
	}
	else
	{
		[ sender sendMessage:@"550 RNTO command failed." ];										// CHECKME - have a look at a typical server response for this error
	}
}

/////////////////////////////////////////
#pragma mark UTILITIES

// ----------------------------------------------------------------------------------------------------------
-(NSString*)makeFilePathFrom:(NSString*)filename
// ----------------------------------------------------------------------------------------------------------
{
	if ( [filename characterAtIndex:0] == '/' )						// if absolute file position
	{
		if ( server.changeRoot )									// then its actually relative to basedir
		{
			return [ [server.baseDir stringByAppendingPathComponent: filename] stringByResolvingSymlinksInPath ];
		}
		else
		{
			return [[filename copy] autorelease ];											// Don't want to hang onto a variable, user should retain if needed.
		}

	}
	else															// add onto the current file postion
	{
		return [ currentDir stringByAppendingPathComponent:filename];					// FIXME / CHECKME - need to set autorelease or retain etc.
	
	}
}

// ----------------------------------------------------------------------------------------------------------
-(unsigned long long)fileSize:(NSString*)filePath
// ----------------------------------------------------------------------------------------------------------
{
	
	
	NSError *error;
	NSDictionary *fileAttribs = [ [ NSFileManager defaultManager ] attributesOfItemAtPath:filePath error:&error ];
	
	NSNumber *fileSize = [ fileAttribs valueForKey:NSFileSize ];
	NSLog(@"File size is %qu ", [fileSize unsignedLongLongValue]);
	return [ fileSize unsignedLongLongValue];
	
}
// ----------------------------------------------------------------------------------------------------------
-(NSString*)fileNameFromArgs:(NSArray*)arguments
// ----------------------------------------------------------------------------------------------------------
{
	NSString *filename = [NSString string];
	NSLog(@"%@", [arguments description ] );
	if ([arguments count] >1) {
		
		
		for ( int n=1; n<[arguments count]; n++) {														// Start at arg2, 
			// test to see if argument begins with a - as it might be just a parameter, not the name
			if (![[[arguments objectAtIndex:n] substringToIndex:1] isEqualToString:@"-"]) 
			{
				if ([filename length]==0) {
					filename = [arguments objectAtIndex:n ];
				}
				else {
					filename = [ NSString stringWithFormat:@"%@ %@", filename, [arguments objectAtIndex:n ] ];						// autoreleased
				}


			}
			else {
				NSLog(@"HYPHEN FOUND IGNORE");
			}


		}
	}
	

	
	return  filename  ;			// autoreleased
}


// ----------------------------------------------------------------------------------------------------------
- (Boolean)changedCurrentDirectoryTo:(NSString *)newDirectory // NOTE - there is probably a unix way of expanding pathnames and taking all the ../. stuff out.   : should look this up 	
// ----------------------------------------------------------------------------------------------------------
{		
	NSFileManager *fileManager = [ NSFileManager defaultManager ];
	
	NSString *currentDirectory = [ fileManager currentDirectoryPath ];  // store current directory
	NSString *testDirectory, *expandedPath;
	

	expandedPath = [[self rootedPath:newDirectory ] retain ];

	// TEST IF ALLOWED TO USE THAT DIRECTORY ( ie is it in allowable filesystem )
	
	if (![self canChangeDirectoryTo:expandedPath]) {
		return false;
	}
	
	
	// CHANGE TO NEW DIRECTORY & GET SYSTEM FILE PATH ( no .. etc )
	if ( ! [ fileManager changeCurrentDirectoryPath:expandedPath ] )	// try changing to new directory
	{
		return false;													// Not a valid directory
	}
	
	testDirectory = [ fileManager currentDirectoryPath ];				// get new directory as string
	

	// CHANGE BACK
	if ( ! [ fileManager changeCurrentDirectoryPath:currentDirectory ] )// Change back to our own directory
	{
		return false;													// Not a valid directory, shouldnt happen, but could u know
	}
	

		  
	self.currentDir = [ testDirectory copy ];								// make a copy, and retain release
	
	[ expandedPath release];
	return true;														// its fine.  would have failed before if not possible
}
// ----------------------------------------------------------------------------------------------------------
-(Boolean)canChangeDirectoryTo:(NSString *)testDirectory // NOTE - there is probably a unix way of expanding pathnames and taking all the ../. stuff out.   : should look this up 	
// ----------------------------------------------------------------------------------------------------------
{
	if (  [server changeRoot] && ( ! [ testDirectory hasPrefix:server.baseDir ]) ) {
		return false;
	}
	else {
		return true;
	}

	
}
// ----------------------------------------------------------------------------------------------------------
- (Boolean)accessibleFilePath:(NSString*)filePath						// checks its a proper name, and also in the chroot sandbox if set
// ----------------------------------------------------------------------------------------------------------
{
	// check, file is accessible within servers rights
	// && file exists
	
	// FIXME - need to expand the filename reference somehow to check where it really is, whether in sandbox. praps look  at tnftpd src code for this.

	return [ [ NSFileManager defaultManager ] fileExistsAtPath:filePath ];
}
// ----------------------------------------------------------------------------------------------------------
- (Boolean)validNewFilePath:(NSString*)filePath
// ----------------------------------------------------------------------------------------------------------
{	
	return true;			// FIXME - check this is within the area we can write to
}
// ----------------------------------------------------------------------------------------------------------
- (NSString *)visibleCurrentDir
// ----------------------------------------------------------------------------------------------------------
{
	
	if ( server.changeRoot )											// if root changed, to basedir
	{
		int alength = [server.baseDir length ];							// length of basedir
		
		NSLog(@"Length is %u", alength );								
		NSString *aString = [ currentDir substringFromIndex:alength ];	// get the bit after basedir
		
		if ( ! [ aString hasSuffix:@"/" ] )								// add a / if needed
		{
			aString = [ aString stringByAppendingString:@"/" ];										
		}
		
		
		// return with basedir prefix removed.
		
		return aString;				// returns just the end part, not the base part
	}
	else
	{
		return currentDir;							// return complete path
	}
	
	
}
// ----------------------------------------------------------------------------------------------------------
-(NSString *)rootedPath:(NSString*)path
// ----------------------------------------------------------------------------------------------------------
{
	NSString  *expandedPath;
	
	// GET FULL PATH OF NEW DIRECTORY
	if ( [ path characterAtIndex:0 ] == '/')					// if its an absolute path
	{
		if ( server.changeRoot )										// if rooted, its a relative path really, so add to basedir
		{
			expandedPath = [ [server.baseDir stringByAppendingPathComponent: path] stringByResolvingSymlinksInPath ];
		}
		else															// it really is an absolute path
		{
			expandedPath = path;								// use the absolute path
		}
	}
	else																// or append onto currentdir ( the one the client chose ), as its a relative path
	{
		expandedPath =[[ currentDir  stringByAppendingPathComponent:path ] stringByResolvingSymlinksInPath];		
	}
	
	expandedPath = [ expandedPath stringByStandardizingPath ];
	
	return expandedPath; // Autoreleased etc
	
}
@end
