Flutter iOS File Sharing - Cannot Open Shared File - ios

I am building a flutter app for iOS. I have created a file type that my app and another app can share back and forth. I am using the flutter receive_sharing_intent library to achieve this. To test it, I made all the necessary changes to my info.plist file to handle my custom file type, I placed an example file of that file type in the downloads folder of my device (testing on ipad), and I click on it from there to open it up in my app. The OS knows that my app can handle it. So, my app opens, it is receives the path of the shared file, but my app can't open the file. Here is the code in my main.dart that is handling reception of the file:
StreamSubscription _intentDataStreamSubscription =
ReceiveSharingIntent.getTextStream().listen((String value) {
setState(() {
try{
//This was added to test and make sure the directory actually exists.
//The value for the directory path was added after I ran it once to capture where the shared file was coming from.
Directory dir = Directory("/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/");
bool dirExists = dir.existsSync();
var files = dir.listSync();
File drawingFile = File.fromUri(Uri.parse(value));
bool fileExists = drawingFile.existsSync();
var contents = drawingFile.readAsStringSync();
DrawingCanvas canvas = DrawingCanvas.fromFileContents(contents);
DrawingCanvasBloc canvasBloc = DrawingCanvasBloc(canvas);
Navigator.of(context).push(MaterialPageRoute(builder: (context) => CanvasScreen(canvasBloc)));
}
catch(e){
log(e.toString());
}
});
}, onError: (err) {
print("getLinkStream error: $err");
});
Scenario: I run the application. I go into my downloads folder in files on the ipad. I select my example.fbg file located there (my custom file type). My app opens up.
In the above code it blows up when I try to list the contents of the directory. I put this in here (after previously catching the directory path and hard coding it) to test to make sure I was even getting the right location. dirExists is true but I can't list the files in it. The error I get is:
FileSystemException: Directory listing failed, path = '/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/' (OS Error: Operation not permitted, errno = 1)
If I take that line out and continue down to the opening of the file (readAsStringSync) I get:
FileSystemException: Cannot open file, path = '/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/example.fbg' (OS Error: Operation not permitted, errno = 1)
I'm not sure why it won't let me access this file. Is there a permissions thing I'm missing? Let me know if I need to include more information in the question and I will update.

Finally found a work around and got it working using this answer. What I ended up doing was opening the file in the app delegate, saving the file to the apps documents directory, then passing that url to the Flutter application. I opened the iOS module in xcode and changed the AppDelegate.swift to the following:
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
var drawingFilename = ""
do {
let isAcccessing = url.startAccessingSecurityScopedResource()
var error: Error? = nil
let path = url.path
let string = try String(contentsOf: url)
drawingFilename = (url.path as NSString).lastPathComponent
print(drawingFilename)
let filename = getDocumentsDirectory().appendingPathComponent(drawingFilename)
do {
try string.write(to: filename, atomically: true, encoding: String.Encoding.utf8)
} catch {
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
}
if isAcccessing {
url.stopAccessingSecurityScopedResource()
}
if #available(iOS 9.0, *) {
return super.application(app, open: filename, options: options)
} else {
return false
}
} catch {
print("Unable to load data: \(error)")
return false
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
}
Hope this helps someone in the future.

Related

How to capture print statements from iOS app installed on iOS device?

I'm reading about some good practices for developing iOS apps and looking at the possibility of monitoring logs of an iOS app installed from App Store using Console.app. So, I was testing here, but I noticed that print statements didn't show up in Console.app, only NSLog does. My question is: is there any way that is possible to see logs that are made with print commands within iOS apps installed on a device? With Frida, Console.app or any other means?
If there is no other method, does it mean that print commands are more secure than NSLog? This seems very counterintuitive to me 🤔
print statement in iOS apps are not logged to one the [persistent] logging systems on iOS, therefore you can not access the output of an app via print statements if they had occur in the past.
By default you can only seem the output of print commands in XCode output panel. However the print commands themselves are always included in the debug and release builds and are therefore executed. Just the output of the print statements is discarded if no XCode is connected to retrieve it.
I tested this by building the following SwiftUI test app (see the end of this answer), made sure the Archive profile is set to RELEASE and the archived the project, to build an IPA file.
The IPA file was then analyzed in IdaPro to see the actual ARM assembler code.
And in all tests using different options (e.g. "Rebuild from Bitcode" (de)activated) the code was always there.
Therefore if you attach Frida to an app you can e.g. hook the print method print(_:separator:terminator:) to retrieve all output that would otherwise be discarded.
struct ContentView: View {
#State var number : Int = 1
var body: some View {
VStack {
Button(" Print ") {
print("print test abcdefgh number %d", number)
}.padding()
Button(" os_log ") {
os_log("os_log test abcdefgh number %d", number)
}.padding()
Button("randomize") {
self.number = Int.random(in: 1...11111)
}.padding()
}
}
}
If, and only if, you want to use print and printf in your app to go to a file or whatever file descriptor:
import SwiftUI
import Darwin
import os.log
extension OSLog {
private static var subsystem = Bundle.main.bundleIdentifier!
static let `default` = OSLog(subsystem: subsystem, category: "default")
}
extension TestApp {
func subscribeFileToStderrAndStdoutIfNotAttachedToDebugger() {
if isatty(STDERR_FILENO) != 1 {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let logfileUrl = documentsUrl.appendingPathComponent("out.log")
logfileUrl.withUnsafeFileSystemRepresentation { path in
guard let path = path else {
return
}
print("redirect stdout and stderr to: \(String(cString: path))")
let file = fopen(path, "a")
assert(file != nil, String(cString: strerror(errno)))
let fd = fileno(file)
assert(fd >= 0, String(cString: strerror(errno)))
let result1 = dup2(fd, STDERR_FILENO)
assert(result1 >= 0, String(cString: strerror(errno)))
let result2 = dup2(fd, STDOUT_FILENO)
assert(result2 >= 0, String(cString: strerror(errno)))
}
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
subscribeFileToStderrAndStdoutIfNotAttachedToDebugger()
return true
}
}

Using fonts from xcfamework

So I have xcframework which have fonts file (from react-native-vector-icons).
In test project (testApp), I am importing xcframework and I was expecting it to copy resources from my framework but unfortunately it doesn't copy these fonts which are there inside the xcframework and I have to manually add it in Copy Bundle Resources.
For end user, I don't want them to go inside framework and add manually.
In my framework, I have Cops Pods resources in Build Phase but haven't done anything such which would tell the project consuming this framework to copy these fonts (or resources).
What do I need to do so that in project it copies fonts (or resources) by itself? instead of manually doing it?
If you distribute your xcframework using Cocoapods then you can have any resource file automatically copied in the destination .app bundle by Cocoapods.
In your podspec you define the resources you need to copy and the xcframework (and NO spec.sources):
spec.resources = ['Fonts/EncodeSans-ExtraBold.ttf']
spec.vendored_frameworks = 'Example.xcframework'
Or from within the xcframework:
spec.resources = ['Example.xcframework/<a path>/EncodeSans-ExtraBold.ttf']
spec.vendored_frameworks = 'Example.xcframework'
When you execute pod install Cocoapods will create a "[CP] Copy Pods Resources" phase that will copy in the destination app whatever you have defined in the resources:
To avoid collisions(the user or other pod might copy the same file), better use resource_bundles instead of resources.
You need to register the fonts with the font manager. I did this extending UIFont
extension UIFont {
private class ModuleClass {}
static func fontsURLs() -> [URL] {
let bundle = Bundle(for: ModuleClass.self)
let fileNames = ["Feather"]
return fileNames.compactMap { fontName in
bundle.url(forResource: fontName, withExtension: "ttf")
}
}
/// Registers the specified graphics font with the font manager.
/// - Parameter url: Bundle URL of the font to register
/// - Throws: If font cannot be registered
static func register(from url: URL) throws {
guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
throw FontError.dataProviderNotCreated(message: "Could not create font data provider for \(url).")
}
guard let font = CGFont(fontDataProvider) else {
throw FontError.fontNotCreated(message: "Could not create font data provider for \(url).")
}
var error: Unmanaged<CFError>?
guard CTFontManagerRegisterGraphicsFont(font, &error) else {
throw error?.takeUnretainedValue() ?? FontError.fontNotRegistered
}
}
}
Then, you need use this.
public extension UIFont {
/// Registers the specified graphics font with the font manager.
/// Registered fonts participate in font descriptor matching.
static func registerFonts() {
let urls = fontsURLs()
for url in urls {
do {
try register(from: url)
} catch {
debugPrint(error)
}
}
}
}
and in your main project (app) register the fonts
// In AppDelegate.swift
import YourModule
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIFont.registerFonts()
return true
}

Save/create folder that it to be treated as a file with FileManager

I have a iOS/CatalystMacOS-app that can create, save, open custom text-files (with my own file extension). This works fine. However, now I need more than text. I want to save optional files in this file as well. Apparently macOS (and iOS?) can treat folders as files. But I cannot get it to work as wanted. The folder is still treated as a folder, even if it has a file extension.
This is the code I use to create the folder:
func showNewFilePathDialog(from viewController: UIViewController, saveCompleted: URLCallback?) {
guard !isPresenting else {
return
}
let objectToSave = ...
// Find an available filename
var number = 0
var exportURL: URL!
var data: Data!
var fullFileName = ""
while true {
let numberText = number == 0 ? "" : number.asString()
fullFileName = "baseFileName" + "\(numberText).myFileExtension"
exportURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent(fullFileName)
let dict = objectToSave.toDict()
let json = dict.json!
data = json.data(using: .utf8)!
if FileManager.default.fileExists(atPath: exportURL.path) {
number += 1
continue
} else {
break
}
}
do {
try FileManager.default.createDirectory(atPath: exportURL.path, withIntermediateDirectories: true, attributes: nil)
} catch {
NSLog("Couldn't create document directory")
viewController.presentErrorDialog(from: error)
return
}
// 2. Create containing json file
do {
try data.write(to: exportURL.appendingPathComponent("content.json"))
} catch {
viewController.presentErrorDialog(from: error)
return
}
isPresenting = true
self.onSaveDialogComplete = saveCompleted
let pickerViewController = UIDocumentPickerViewController(url: exportURL, in: .exportToService)
pickerViewController.delegate = self
viewController.present(pickerViewController, animated: true)
}
And then it appears like this in macOS finder:
It will show up similar in iOS, not allowing me to open the folder as a single file either.
Edit: Using UIDocument/public.composite-content/FileWrapper seems to work as well, but the problem still consists: When viewed in macOS finder it is still treated as a folder. Also when trying to open the app from the open-dialog via UIDocumentPickerViewController trying to open the file-bundle only opens the folder and wont let me open it into the app :(
This is my info.list Export Type UTIs:
Edit2: Also tried with removing all but com.apple.package but does not work either. Still cannot open my custom type as it behaves like a folder.
Got it working. Seemed as old builds of my app was interfering with the system file types. So I searched for my app name and removed old builds from my computer. Then the system recognized my file suffix and opened it right away!
But I lost the icon this time, but that's another issue :)

Access Windows/Mac Shared Folder Locally With smb from iOS

I am trying to build an app where I am able to access(read/write) windows/mac shared folders in my local network with swift.
Is there any possible way to do that with swift?
There is an App in the App Store called "FileExplorer" https://apps.apple.com/de/app/fe-file-explorer-file-manager/id510282524 where you can access these shared folders, but I do not know how they programmed this and with which language.
I also tried to access my shared folders via this App and yes it worked I can see my shared folders on my Phone.
But there needs to be a way to do it with swift...
I already tried different things(code bellow).
In the code bellow I tried to access the shared folder of my second mac and write the Text "Write this text to the fileURL as text in iOS using Swift" into the file named "Test.txt" and after that I want to read the same file again.
#IBAction func Button(_ sender: UIButton)
{
var uc = URLComponents()
uc.scheme = "smb"
uc.user = "user"
uc.password = "password"
uc.host = "ip-adress"
uc.path = "document-directory"
// Save data to file
let fileName = "Test"
let url = uc.url
//let DocumentDirURL = URL(fileURLWithPath: "/Users/f/d/t/App/Assets/Apps/TestApp")
let DocumentDirURL = try! URL(resolvingAliasFileAt: url!)
let fileURL = DocumentDirURL.appendingPathComponent(fileName).appendingPathExtension("txt")
print("FilePath: \(fileURL.path)")
let writeString = "Write this text to the fileURL as text in iOS using Swift"
do {
// Write to the file
try writeString.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
} catch let error as NSError {
print("Failed writing to URL: \(fileURL), Error: " + error.localizedDescription)
}
var fullString: String = "" // Used to store the file contents
do {
// Read the file contents
fullString = try String(contentsOf: fileURL, encoding: .utf8)
} catch let error as NSError {
print("Failed reading from URL: \(fileURL), Error: " + error.localizedDescription)
}
print("File Text: \(readString)")
}
If I run the code as shown, he always gives me the error
"smb scheme is not supported" and then some additional errors that he can not write/read the file because he can not access it.
When I change the code and only search on the device I am programming on and run the simulator to search for this file everything works fine. So I have problems with "smb".
Thank you for every helpful answer.
you can use amsmb2 library to do this
you can extend the template class provided to connect to download files, write files, list directories -> on an smb share
everything is asynchronous from memory, with the librarys calls including hooks for progress updates on the ui main thread etc
i believe the amsmb2 library function your after might be uploadItem
iOS 13 includes SMB (server message block protocol) support
https://9to5mac.com/2019/06/17/ios-13-beta-2-enables-smb-server-connectivity-in-the-files-app/

Beginner FMDB iOS Database, Table Creation

How would you create a database file in an iOS app? I am using FMDB in Xcode (Swift) and I have created functions to execute queries here:
func query()
{
let DB = FMDatabase(path: dbFilePath as String)
if DB.open()
{
let querySQL = "SELECT * FROM SHOPS"
let results:FMResultSet? = DB.executeQuery(querySQL, withArgumentsInArray: nil)
print(querySQL)
DB.close()
print(results)
}
else
{
print("Error: \(DB.lastErrorMessage())")
}
}
My create database function needs help as in the FMDB documentation states: An FMDatabase is created with a path to a SQLite database file. This path can be one of these three:
A file system path. The file does not have to exist on disk. If it does not exist, it is created for you. But where can I obtain the file path to the database file?
I also have a question for how to continue the create table function:
func create()
{
let createString = "CREATE TABLE Shops(id INT PRIMARY KEY NOT NULL, name char(255);)"
}
Do I use executeUpdate? Please provide concise and clear answer as I am a beginner if it is not too time consuming for you. Thanks in advance.
Xcode 8.0 Swift 3.0 version
In Your Appdelegate.swift Copy the below code
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
// Override point for customization after application launch.
Util.copyFile("<yourDatabaseName>.sqlite")
getDocDir()
return true
}
func getDocDir() -> String
{
print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
}
And then Run Xcode: Short cut key: build (Cmd + B) -> Run(Cmd + R)
Check console of Xcode get a path like below example:
Sample image: https://i.stack.imgur.com/xH0AV.png
Copy the path from:
/users//tooti/Library/Developer/CoreSimulator/Devices/..../data/Containers/Data/Application/.../Documents/
-> Open Finder-> Go -> Go to folder and paste the path copied from above
Sample image: https://i.stack.imgur.com/DmzH8.png
-> then click Enter. That's it done. You will find a .sqlite file
Also you can open your database file with SQLite manager/Firefox browser/DBManagerExtension tool.

Resources