Files
xash3d-fwgs/engine/platform/ios/FtpConnection.m
ksagameng2 a9bf700da4 Restore iOS build support (#2352)
* initial commit

* initial commit

* add port files

* ios: Changes to allow creating app bundle without xcode projects

* ios: Added mic perms request

* ios: Very basic app bundle creation script

* ios: Fix -log parameter

* ios: Sdk no longer hardcoded and option to compile for simulator

* general: Update gitignore

* ios: Enable game mode for the app

* ios: Compile instructions and updated ipa creation script

* ios: Code cleanup

* ios: Remove dylibs

* general: Remove extra submodules

* ios: Remove dylibs again

* ios: Implement suggested changes

* ios: Fix building as dylib instead of binary

* ios: Scripts for building mods

* ios: Move 'ios' contents to engine/platform/ios/bundle and modify scripts

* ios: Update createipa.sh to use waf install

* ios: Clean script and updated README

* general: Fix spelling mistake and outdated instructions

* ios: Clean up in xcompile.py

* ios: Fix createipa.sh generating an invalid ipa

* ios: Temporary fix for launch dialog textfield colors in dark mode
2025-11-01 18:32:00 +05:00

1221 lines
45 KiB
Objective-C

/*
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