Swift macOS How to Implement FTP Support [duplicate] - swift

This question already has an answer here:
Why is retrieving the data from this URL returning nil?
(1 answer)
Closed 3 years ago.
I need to upload a file to an FTP server, check if a folder exists and, if not, create it. I found FileProvider by Amir Abbas Mousavian. I got everything installed and have implemented the following code:
let credential = URLCredential(user: "user", password: "password", persistence: .permanent)
let ftpProvider = FTPFileProvider(baseURL: URL(string:"cflm.org")!, mode: .active, credential: credential, cache: nil)
ftpProvider?.delegate = self as! FileProviderDelegate
ftpProvider?.contentsOfDirectory(path: "/", completionHandler: {
contents, error in
for file in contents {
print("Name: \(file.name)")
print("Size: \(file.size)")
print("Creation Date: \(file.creationDate)")
print("Modification Date: \(file.modifiedDate)")
}
})
When I run the code, the completionHandler for contentsOfDirectory never fires. Does anyone know what I'm doing wrong or have some other way I can do the FTP functions I need to?

Your closure is not being called because ftpProvider is nil, a quick look at the source code tells us that the url sent to the init needs to contain the protocol. So add "ftp" or "ftps" to your url.
And wrap the init in a guard statement
guard let ftpProvider = FTPFileProvider(baseURL: URL(string:"ftp://cflm.org")!, mode: .active, credential: credential, cache: nil) else {
return //or some error handling
}
//... rest of code

Related

Error when saving to keychain using SecItemAdd

I'm getting an error saving an encoded value to keychain at the point of SecItemAdd. I'm fairly new to working with Keychain and not sure how to return the error to see what I'm doing incorrectly.
let encoder = JSONEncoder()
func initiateLogin(forceReconnect: Bool = false, completion: #escaping (Bool)->Void) {
Task {
await loginUser(forceReconnect: forceReconnect, completion: { user in
if let encoded = try? self.encoder.encode(user) {
// MARK: - keychain
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "johnDoe",
kSecValueData as String: encoded,
]
if SecItemAdd(attributes as CFDictionary, nil) == noErr {
print("\(#function) šŸ˜€ user saved successfully in keychain")
} else {
print("\(#function) āš ļø something went wrong")
}
self.initClient(withCredentials: user)
completion(true)
}
})
}
}
You didn't specify which error you are getting (it should be a return value of SecItemAdd), but the most common mistake is this: as documentation states:
The operation might fail, for example, if an item with the given attributes already exists.
In other words: your code will only work once for each unique kSecAttrAccount.
Instead, you need to check if an item already exists, and if yes, update it (or delete the previous one and create a new one).
How to update the items or delete them is explained here.
Side note: it's also a good idea to put keychain management into a separate class (a wrapper), which you can call from anywhere in your code to save / load data from keychain. Here's a good tutorial on how to create such wrapper.

Swift Realm issue in iOS 14+

------LE: We ended up removing the encryption of the database because with realm team suggestions it got worse - all we could do was to remove the database and loose all stored info. Now we encrypt in keychain only the fields we need.------
I have an app released in store and after updating their iOS version to 14+, users started to complain about info not being populated from database. Not all users with iOS 14+ have this issue, it appears randomly on some devices.
The issue goes away for awhile if they reinstall the app or after they update it to another version, but after using it for a few minutes it happens again.
My database uses encryption as documented here.
The store version of my app uses Realm 5.4.8, but I tested their last version (10.0.0) and the issue is still present.
I checked this issue but it's not the case for me, I don't have a shared app group container or a share extension.
Here's how the initialisation of realm looks like:
override init() {
super.init()
do {
guard let config = getMigrationAndEncryptionConfiguration() else {
realmConfigured = try Realm()
return
}
realmConfigured = try Realm(configuration: config)
} catch let error as NSError {
// this is where I got the error:
//"Encrypted interprocess sharing is currently unsupported.DB has been opened by pid: 4848. Current pid is 5806."
}
}
func getMigrationAndEncryptionConfiguration() -> Realm.Configuration? {
let currentSchemaVersion: UInt64 = 19
if Keychain.getData(for: .realmEncryptionKey) == nil {
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
Keychain.save(data: key, for: .realmEncryptionKey)
}
guard let key = Keychain.getData(for: .realmEncryptionKey) else {
return nil
}
let fileUrl = Realm.Configuration().fileURL!.deletingLastPathComponent()
.appendingPathComponent("Explorer.realm")
var config = Realm.Configuration(fileURL: fileUrl,
encryptionKey: key,
schemaVersion: currentSchemaVersion, migrationBlock: { (migration, oldVersion) in
if oldVersion != currentSchemaVersion {
print("we need migration!")
}
})
return config
}
I had another question opened for the same issue on SO, but it was closed because I didn't have enough details. After another release of my app with more logs, I could find the error that appears at initialisation of realm:
"Encrypted interprocess sharing is currently unsupported.DB has been opened by pid: 4848. Current pid is 5806. "
This appears after the app goes to background, it gets terminated (crash or closed by the system/user) and when the users opens it again, realm fails to init.
I read all about encrypted realm not being supported in app groups or in a share extension, but I didn't implement any of that in my app, is there any other reason why this error happens?
I also removed Firebase Performance from my app because I read that this module could generate issues on realm database, but it didn't help.
I opened an issue on realm github page, but I got no answer yet.
Does anyone have any idea how to fix this or why is this happening?
Thank you.

Sending CSV file with SFTP in swift

I have a server hosted with webfaction that I would like to be able to send a csv file to from my app with FTP or SFTP. I have found many libraries that should help like ConnectionKit, NMSSH, DLSFPT, and LxFTPRequest. However, all of them are in objective-c and not swift which makes them hard to read, understand, and implement in Swift 4. I have tried to implement LXFTPRequest since I found a swift implementation for the upload and here is my code:
let fileName = "user-data.csv"
guard let path = FileManager.default.urls(for: .documentDirectory, in:.userDomainMask).first else {fatalError(ErrorMessageStrings.couldntAccessDocs.rawValue)}
let fileURL = path.appendingPathComponent(fileName)
let folderLocation = "/home/path/"
let uploadUrl = URL(string: "ftp://server-name.webfaction.com" + folderLocation)
let request = LxFTPRequest.upload()
request?.serverURL = uploadUrl
request?.localFileURL = fileURL
request?.username = "username"
request?.password = "password"
request?.successAction = { (resultClass, result) in
print("File uploaded")
}
request?.failAction = { (domain, error, errorMessage) in
print(error)
print(errorMessage?.description)
fatalError("Connection could not be made. Action was not completed.")
}
request?.progressAction = {(_ totalSize: Int, _ finishedSize: Int, _ finishedPercent: CGFloat) -> Void in
print(finishedPercent)
}
request?.start()`
Using this I almost get it to work but I end up with a 550 error "Requested action not taken. File unavailable (e.g., file not found, no access)." Looking through webfaction documentation I get the feeling that I can only send files through SFTP, which this framework doesnt support.
The doc says "To connect with FTP (for shell users only), substitute the connection type with FTP and the port number with 21." I am assuming since I am sending data from my app it does not count as a shell user and so FTP doesn't grant me access (I may be wrong here). If that is the case how would I go about using the other libraries to send my file over SFTP using Swift and not objective-c?
I ended up using NMSSH and using it in Swift it wasn't as complicated as I thought.
let session = NMSSHSession.init(host: serverHost, port: xx, andUsername: serverUsername)
session.connect()
if session.isConnected{
session.authenticate(byPassword: serverPasswordString)
if session.isAuthorized == true {
let sftpsession = NMSFTP(session: session)
sftpsession.connect()
if sftpsession.isConnected {
sftpsession.writeFile(atPath: csvFileURL.path, toFileAtPath: folderLocation)
}
}
}

How to authenticate an iOS app in swift [duplicate]

This question already has an answer here:
iOS swift Post request
(1 answer)
Closed 6 years ago.
I was wondering how to authenticate api calls in swift to a custom service I made that authenticates users with active directory. Currently my application stores an integer value, I want to post that to the service.
let thing = Int(emailTextField.text!)
// POST method goes here ...
I just authenticate my app with this piece of code.
if(isValidEmail && isValidPassword){
print("sending to server for verification")
let params = ["user" : ["email" : "\(email!)", "password" : "\(password!)"]]
let url: String = "http://localhost:3000/users/sign_in"
Alamofire.request(.POST, url, parameters: params, encoding: .JSON, headers: nil).validate().responseJSON{
response in
switch response.result{
case .Success:
if let value = response.result.value{
let jsn = JSON(value)
print("jsn: \(jsn)")
let authentication_token = jsn["authentication_token"]
if(authentication_token != nil){
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("dasboardViewController") as! DashBoardController
self.presentViewController(vc, animated: true, completion: nil)
}
}
break
case .Failure:
break
}
}
}
Make sure Alamofire is imported. I have rails server in the backend and this was tested in localhost. Also, Authentication is done using email and password instead of numbers, And it returns a json containing authentication_token field.

Why is NSData(contentsOfURL: url) always nil?

I download a file from the DocumentPicker (iCloud, dropbox, etc.). This file can be .pages, .docx, .pdf,...
When I want to convert it to NSData for file upload, I always receive a nil value:
if NSFileManager.defaultManager().fileExistsAtPath(urlToFile!.path!) {
let data = NSData(contentsOfURL: urlToFile!) // initializer returns nil, although file and path exist
println(data) // prints nil
}
else {
println("Does not exist")
}
I cannot figure out why it is nil, the file exists at this path!
Where is my mistake?
Some more information
A screenshot, showing that it is not a scope problem, but the initializer (as stated in my question-title) already returns nil. In this example I use a suggestion from an answer below, utilizing contentsOfFile
Utilizing an ErrorPointer
"The operation couldnā€™t be completed. (Cocoa error 257.)" UserInfo=0x7fac556cbf00 {NSFilePath=SUPERLONGPATH<NSUnderlyingError=0x7fac570beb20 "The operation couldnā€™t be completed. Permission denied"})
File's origin
The file is downloaded via an DocumentPicker:
let docPicker = UIDocumentPickerViewController(documentTypes: ["public.content"], inMode: UIDocumentPickerMode.Import)
docPicker.delegate = self
docPicker.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
self.presentViewController(docPicker, animated: true, completion: nil)
func documentPicker(controller: UIDocumentPickerViewController, didPickDocumentAtURL url: NSURL) {
labelDatei.text = "Bereit"
labelDateiChose.text = url.lastPathComponent
urlToFile = url
}
Solution
As it happens .pages and .numbers "files" are no files, but folders(containers). So they cannot be uploaded. Using a .pdf works now. Thanks to all for help
It looks scope problem. You're defining your local variable data inside the braces and it won't exist after the braces.
Get rid of the word let.
That way you will be assigning to a variable data from the enclosing scope