Edoardo Vicoli
Edoardo Vicoli

Reputation: 237

Implementing FTP file transfer (STOR) in Swift?

I am working on a client iOS app written in Swift which let users transfer files on FTP server. I know Apple offers APIs to do this, but they are deprecated since 2016 and I need an alternative. I know also FTP is not secure, but I have to do it this way. My goal is to open a Socket through the server and send commands to it in order to transfer the file. I'm using BlueSocket but I can't get it. Here's my code:

do {
    let socket = try Socket.create()
                    
    try socket.connect(to: "127.0.0.1", port: 21)
    let answer = try socket.readString()
    print(answer!)
                    
    try socket.write(from: "USER test\r\n")
    let userAnswer = try socket.readString()
    print(userAnswer!)
                    
    try socket.write(from: "PASS 0000\r\n")
    let passwordAnswer = try socket.readString()
    print(passwordAnswer!)
                    
    try socket.write(from: "PASV\r\n")
    let passiveModeAnswer = try socket.readString()
    print(passiveModeAnswer!)
                    
    try socket.write(from: "STOR myFile.txt\r\n")
    let storAnswer = try socket.readString()
    print(storAnswer!)

    /* CODE FOR SENDING FILE BELOW */

} catch let error as NSError {
    print(error.debugDescription)
}

I'm currently working on my local server and I login with success. Server is ready to accept data. I see this in the console:

150 Ok to receive data.

Here's the code I'm using to send the file:

guard let path = Bundle.main.path(forResource: "myFile", ofType: "txt"),
      let fileInputStream = InputStream(fileAtPath: path) 
else {
    return
}
            
fileInputStream.open()

var buffer = [UInt8](repeating: 0, count: 1024)
var bytesRead = 0
var fileData = Data()

while fileInputStream.hasBytesAvailable {
    bytesRead = fileInputStream.read(&buffer, maxLength: buffer.count)
    fileData.append(buffer, count: bytesRead)
}

try socket.write(from: fileData)
let fileAnswer = try socket.readString()
print(fileAnswer!)

fileInputStream.close()
socket.close()

After the execution I see "myFile.txt" in my local server, but it's empty. The original file is stored in Xcode: it is a 13 byte file (a simple text file). I see with a breakpoint that bytesRead changes to 13 and fileData is valued. I don't receive any response from socket after the try socket.write(from: fileData) instruction but only this error message: Error code: -9982(0x-26FE), Interrupted system call. Is something missing or wrong? Am I doing well to achieve my goal?


As Martin correctly said, I've parsed the result of the PASV answer, here's the code:

guard let path = Bundle.main.path(forResource: "myFile", ofType: "txt"),
      let fileInputStream = InputStream(fileAtPath: path) 
else {
    return
}
            
fileInputStream.open()
            
guard let pasvAnswer = passiveModeAnswer else {
    return
}
            
var editedAnswer = String(pasvAnswer.dropFirst(26))
editedAnswer = editedAnswer.replacingOccurrences(of: ")", with: "")
editedAnswer = editedAnswer.replacingOccurrences(of: "(", with: "")
editedAnswer = editedAnswer.replacingOccurrences(of: ",", with: ".")
editedAnswer = String(editedAnswer.dropLast())
let cmps = editedAnswer.components(separatedBy: ".")
            
let dataHost = cmps[0] + "." + cmps[1] + "." + cmps[2] + "." + cmps[3]
let dataPort = ((Int(cmps[4]))!<<8) + Int(cmps[5])!
            
print("dataHost: \(dataHost), dataPort: \(dataPort)")
            
try socket.write(from: "STOR myFile.txt\r\n")
let storAnswer = try socket.readString()
print(storAnswer!)
            
let dataSocket = try Socket.create()
try dataSocket.connect(to: dataHost, port: Int32(dataPort))
            
var buffer = [UInt8](repeating: 0, count: 1024)
var bytesRead = 0
var fileData = Data()

while fileInputStream.hasBytesAvailable {
    bytesRead = fileInputStream.read(&buffer, maxLength: buffer.count)
    fileData.append(buffer, count: bytesRead)
}
            
try dataSocket.write(from: fileData)
let fileAnswer = try dataSocket.readString()
print(fileAnswer!)
            
fileInputStream.close()
dataSocket.close()
socket.close()

I observed that file is completely transferred only when i kill the app. Any thoughts?

Upvotes: 1

Views: 1190

Answers (1)

Martin Prikryl
Martin Prikryl

Reputation: 202474

You have to parse the PASV response (passiveModeAnswer). And connect to the provided IP address and port and send the file contents to the new connection.

Upvotes: 1

Related Questions