Swift Create file with integrity FileManager - swift

I'm currently trying to store some files on my iOS device. The contents of the file are encrypted, but I was wondering if I can append some kind of integrity check to the file as well, preferably using the FileAttributeKey.
I tried the following, which doesn't work
extension FileAttributeKey {
static let integrity = FileAttributeKey("NSFileIntegrity")
}
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = "test"
let filePath = docs.appendingPathComponent(fileName).path
defer {
try! FileManager.default.removeItem(atPath: filePath)
}
let data = Data("Hello world".utf8)
// This line fails too
// FileManager.default.createFile(atPath: filePath, contents: data, attributes: [.integrity: "SHA256"])
FileManager.default.createFile(atPath: filePath, contents: data, attributes: [:])
do {
try FileManager.default.setAttributes([.integrity: "SHA256"], ofItemAtPath: filePath)
} catch {
print(error)
}
print(try FileManager.default.attributesOfItem(atPath: filePath))
So the questions are:
Is there a way to create and append a custom FileAttributeKey to a file.
Is there a (better) way to add integrity checks to a file?

If you use authenticated encryption then you get integrity checks for free. Every time you decrypt, integrity will be checked for you and in case of errors the decryption will fail. Just use a mode like GCM or OCB and you are done.

Related

Can't find the json file that I created

I used this code for encoding some data into a json file, but I can't really find the location of the file.
func save () {
let users: [UserCredentials] = []
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(users)
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("users.json")
try data.write(to: url)
} catch {
print("Error encoding JSON: \(error)")
}
}
I didn't find anything in the Document directory. Then I found out that I have to go to my Bundle ID that's located in library/application support, but I didn't find mine there.
Any idea where that json file is hiding?
Insert the line print(url.path) after the let url... line.
Run the code.
Copy (⌘C) the printed path in the console.
Switch to Finder, press ⇧⌘G, then ⌘V and return.

uncaught exemption when writing to a static dictionary in an HTTP request

I'm getting an Uncaught exemption within a http closure related to a dictionary stating there's an uncaught exemption. When I set a breakpoint exemptions, it points to a dictionary. The dictionary in question is declared in a struct as a static var and has multiple values already in it so how can this be happening? Here's the http request.
session.dataTask(with: request){ (data, response, error) in
if let data = data,
let tile = UIImage(data: data),
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first{
let fileName = Date().timeIntervalSince1970
let filePath = documentsURL.appendingPathComponent(String(describing: fileName))
Maps.tileCachePath[url] = fileName //<- this is where the exception happens
//make sure there is no old file and if so delete it
if FileManager.default.fileExists(atPath: filePath.path){
do {
try FileManager.default.removeItem(at: filePath)
} catch{
print("error deleting old tile")
}
}
//now write the new file
FileManager.default.createFile(atPath: filePath.path, contents: data, attributes: nil)
print(filePath.path)
//return
result(tile, error)
} else {
result(nil, error)
}
}.resume()
It's a typo
Replace
Maps.tileCachePath[url] = fileName
with
Maps.tileCachePath[url] = filePath
Basically Date().timeIntervalSince1970 as a filename is a very bad idea. The number contains fractional seconds which are treated as a file extension.
Use a more reliable file name like a formatted date or at least remove the fractional seconds and add a real file extension.
Date().timeIntervalSince1970
is a double, you might need a string value there.

Saving multiple lines of text in swift

I am attempting to store multiple lines of text in a local text file on an iphone. I have code which will create a text document, write data to that document and read data from this document.
However, if i try and add more text to this document, it will only store the most recent line of text which has been added.
The code I have for creating, writing and reading text from this document is as follows:
//Storing user rewards
let fileName = "Rewards"
let DocumentDirURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
//print("File Path: \(fileURL.path)")
let writeString = rewardLBL.text
do {
//writing to the file
try writeString?.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError{
print("failed to write")
print(error)
}
var readString = ""
do {
readString = try String(contentsOf: fileURL)
}catch let error as NSError{
print("failed to readFile")
print(error)
}
print(readString)
I need this to allow for multiple entries of text to be stored, rather than just the most recent data which was written.
I suspect that due to the code being inside the 'viewDidLoadi()' method that it is constantly recreating the same document and thus always making a new version which overwrites the old Rewards.txt document.
Any help is greatly appreciated, thanks.
Since you are using write, it will overwrite whatever is written earlier.
try writeString?.write(to: fileURL, atomically: true, encoding:
String.Encoding.utf8)
You need to append line of text to your file, which will not overwrite previous written lines. Something like this:
writeString.data(using: String.Encoding.utf8)?.write(to: fileURL, options: Data.WritingOptions.withoutOverwriting)

FileManager.contentsEqual returns false when comparing copied files

I need to preload SQLite files from my bundle's resources into the application support directory. I want to make sure the correct files are there vs. the empty files that Core Data puts there by default. To do this, I'm using FileManager.default.contentsEqual; however, this always returns false.
I tried testing with a playground, but the copy there is creating alias files, still resulting in a false comparison.
In the app, the files do copy over with the same name and size. The dates are different: the copies have the current date/time rather than the original's timestamps. Using contentsEqual, though, I wouldn't think that matters.
Update: diff at the command line shows the files are the same...
What am I missing?
Here's the code from the playground, which is virtually the same as my app code:
// get the URL for the application support directory
let appSupportDir: URL = try!
FileManager.default.url(for: FileManager.SearchPathDirectory.applicationSupportDirectory,
in: FileManager.SearchPathDomainMask.userDomainMask,
appropriateFor: nil, create: true)
// get the source URLs for the preload files
let sqliteFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite")!
let sqliteShmFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-shm")!
let sqliteWalFileBundleURL: URL = Bundle.main.url(forResource: "My_DB", withExtension: "sqlite-wal")!
// create target URLs for copy to application support directory
let sqliteFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite")
let sqliteShmFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-shm")
let sqliteWalFileAppSptURL: URL = appSupportDir.appendingPathComponent("My_DB.sqlite-wal")
// remove the files if they already exist at the target (for test - app doesn't do this)
do {
let filesFound: [URL] = try FileManager.default.contentsOfDirectory(at: appSupportDir,
includingPropertiesForKeys: nil,
options: .skipsHiddenFiles)
if !filesFound.isEmpty {
for fileURL in filesFound {
try FileManager.default.removeItem(at: fileURL)
}
print("Removed \(filesFound.count) files without error.")
}
}
catch {
print("Error:\n\(error)")
}
// copy the files to the application support directory
do {
try FileManager.default.copyItem(at: sqliteFileBundleURL, to: sqliteFileAppSptURL)
try FileManager.default.copyItem(at: sqliteShmFileBundleURL, to: sqliteShmFileAppSptURL)
try FileManager.default.copyItem(at: sqliteWalFileBundleURL, to: sqliteWalFileAppSptURL)
}
catch {
print("Error: \(error)")
}
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.absoluteString, andPath: sqliteFileAppSptURL.absoluteString)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.absoluteString, andPath: sqliteShmFileAppSptURL.absoluteString)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.absoluteString, andPath: sqliteWalFileAppSptURL.absoluteString)
Aha! When using FileManager, one should be using path rather than absoluteString to convert a URL to a String:
// compare the copied target files to their source using contentsEqual
let sqliteFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteFileBundleURL.path, andPath: sqliteFileAppSptURL.path)
let sqliteShmFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteShmFileBundleURL.path, andPath: sqliteShmFileAppSptURL.path)
let sqliteWalFileCopied: Bool =
FileManager.default.contentsEqual(atPath: sqliteWalFileBundleURL.path, andPath: sqliteWalFileAppSptURL.path)
The difference between the two is that path generates a file system-type path:
/var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
whereas absoluteString generates a browser-friendly path:
file:///var/folders/kb/y2d_vrl133d1b04_5kc3kw880000gn/T/com.apple.dt.Xcode.pg/resources/238FF955-236A-42FC-B6EA-9A74FC52F235/My_DB.sqlite
Note: path also works in the playground with the alias files.

Alamofire not saving file to disk

I have a video file on S3 that I am trying to save to disk. However, if the file already exists on disk, I want to overwrite it. I wrote this function to download the file but it never saves the file. I can see the progress % increasing. But, how do I access the resulting file and save it to disk?
var finalPath: NSURL?
Alamofire.download(.GET, s3Url) { temporaryURL, response in
let fileManager = NSFileManager.defaultManager()
if let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
let pathComponent = response.suggestedFilename
finalPath = directoryURL.URLByAppendingPathComponent(pathComponent!)
println(finalPath)
//remove the file if it exists
if fileManager.fileExistsAtPath(finalPath!.absoluteString!) {
println("file exists on disk, removing..")
fileManager.removeItemAtPath(finalPath!.absoluteString!, error: nil)
}
return finalPath!
}
return temporaryURL
}
.validate()
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
let progress = (Double(totalBytesRead) / Double(totalBytesExpectedToRead)) * 100
println(String(format: "%.2f", progress))
}
.response { request, response, data, error in
println(request)
println(response)
if let mediaData = data {
println("saving file to disk")
mediaData.writeToURL(finalPath!, atomically: true)
}
}
Normally I would use the example provided in the docs, but It fails if the file already exists. ie:
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
So, how can I download the file, overwrite it if it exists and record the path that the file is written to to my coreData database?
You need to delete the file first. Alamofire only tries to move the file from the temp location to the final location that you provide in the destination closure.
You can create an extension on Alamofire.DownloadRequest to provide options for how to download the file where you can use the option DownloadRequest.DownloadOptions.removePreviousFile.
for details on how to do that see my answer to this question.