My OS X app (currently not sandboxed) accesses files contained inside a directory set by the user (one chooses the path with a NSOpenPanel and a reference to this path is kept throughout execution). The list of files is generated via NSDirectoryEnumerator and I then read from and write to those files using AVAsset and taglib (in C++ with a bridging header) respectively.
As expected, enabling Sandboxing in Xcode rendered the app useless, the list of files given by NSDirectoryEnumerator is empty and even if it weren't, I would not be able to read from and write to the files. What are the steps I need to take to make my app sandbox-compliant?
Does my app need to be document based? Can my app really be "document-based" since I don't really have proper documents (as in: I don't have a window per file, it doesn't seem to comply to the standard document-based app model)? My app is basically just a table view with files references as rows.
Another important point: can I still use taglib to write to my files if my app is document-based ? I need to pass taglib the path to my file as a string pointer in order for it to work.
Thanks a lot, this topic is quite confusing at the moment.
You don't have to convert your app to be document-based to gain access to user selected files and security scoped bookmarks.
I can think of 2 reasons why your current code does not work in a sandboxed environment:
You don't have the "User Selected File Access" capability set (Xcode > target > Capabilities > App Sandbox > File Access)
You are using the path/NSString based API of the directory enumerator instead of the URL NSURL based one.
A vanilla Xcode project with Sandboxing enabled and the User selected files capabilities set, should enumerate any path obtained via NSOpenPanel:
NSOpenPanel* panel =[NSOpenPanel openPanel];
panel.canChooseDirectories = YES;
[panel beginSheetModalForWindow:self.view.window completionHandler:^(NSInteger result) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSURL *directoryURL = panel.URL;
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:nil
options:0
errorHandler:nil];
for (NSURL *url in enumerator) {
NSLog(#"url:%#", url);
}
}];
If you want to store the ability to access specific folders from the sandbox across app launch/quit cycles, you will need to store a security scoped bookmark.
This post contains information persisting user selected file/directory access via app scoped bookmark:
Trouble creating Security-Scoped Bookmark
It sounds like the current functionality will convert to Sandboxing just fine.
The user selects the directory via NSOpenPanel (which will invoke something called Powerbox in the Sandboxed environment).
This directory is now writable, as the user has explicitly selected it.
You can even maintain write access to this directory by creating a Security Scoped Bookmark and storing it away between sessions.
This has got nothing at all to do with being Document based; that is an internal design that is unrelated to Sandboxing.
Related
I'm attempting to add functionality to an application that is similar in function to the "Open With..." menu in the Finder. In the application I'm working on, a user can select a file and chose to open it with a specific application, rather than with the default one.
The problem I'm experiencing is that there appears to be a mis-match in the APIs I think I should be using in order for this to work properly. Specifically, the recommended NSWorkspace API for opening files with a specific application takes a bundle identifier to specifying the application:
[NSWorkspace openURLs:
withAppBundleIdentifier:
options:
additionalEventParamDescriptor:
launchIdentifiers:]
However, the Launch Services API for getting a list of applications that can open a given file returns an array of URLs, each pointing to a compatible application:
CFArrayRef LSCopyApplicationURLsForURL(CFURLRef inURL, LSRolesMask inRoleMask);
Currently, I'm iterating over the array that launch services returns and extracting the bundle identifier by creating a new NSBundle each time:
for (NSURL *applicationURL in applicationURLs) {
NSBundle *applicationBundle = [NSBundle bundleWithURL:applicationURL];
NSString *bundleIdentifier = applicationBundle.bundleIdentifier;
// Do something with bundle identifier...
}
The problem I'm experiencing is that if the applicationURL points to an application that is not under /Applications (or any other directory my sandboxed application has read access to), then I cannot create an NSBundle to read the bundle identifier. Instead, Gate Keeper will output a sandbox violation to Console.app and NSBundle bundleWithURL will return nil.
Is there a way I've overlooked to get the bundle identifier from a URL that I've overlooked? Or is there a different way to read the bundle identifier from an arbitrary file without causing a sandbox violation? Or maybe there's a different way to get a list of all applications that can open a specific URL?
(Note that the Launch Services method to open multiple URLs, LSOpenURLsWithRole has been deprecated. The header file simply says "Use NSWorkspace".)
I'm trying to create document-scope security scoped bookmarks for file packages. That is, directories where NSURLIsPackageKey is YES. I know you're not normally supposed to be able to create document-scope bookmarks to directories, but I would have expected packages to be exempt from that (after all, I get access to them using an NSOpenPanel which isn't allowed to select directories, but there's no problem there).
I've got my entitlements set up with com.apple.security.files.bookmarks.document-scope = true, and I'm doing a basic bookmark creation call with a file URL that I've just received from an NSOpenPanel (so I have access):
NSError *bookmarkError = nil;
NSData *bookmark = [fileURL
bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:[self fileURL]
error:&bookmarkError];
After this call though, both bookmark and bookmarkError are nil.
Do I just need to give up on this and create app-scope bookmarks, even though I have a document-based app? That seems inappropriate, but I don't see another workaround.
I asked about this elsewhere and was told:
...we didn't implement support for it because it's complicated and there have been very few requests for it.
So that's that, you can't create this kind of bookmark because it's not implemented. I also filed a bug with Apple but the response just quoted the docs at me (i.e. telling me stuff I already knew and had mentioned in my report) before closing it. So, as of now and probably for the foreseeable future, this is not possible.
I'm new to Mac OS development, so I have simple question. I have a NSURL object of a folder, I want to keep this URL and make it bulletproof to app quit/reboot/folder rename. So, I made a NSData bookmark. I'm a little confuse right now, where I must save this NSData object for future use.
It is better to save object to NSUserDefaults?
Or I must make a file in my bundle and store this object in it, if so, what must be an extension of that file, and is it "safe"?
Or I must save that file in Application Support Directory ?
After I write this question, I found another one, what if I will have more data to save like NSString objects or NSNumber or else, I must make another files with info, or could make a one file with all that necessary info?
NSUserDefaults is used to store user preference and other data that will be used by application in the next run. For your instance you can use it.
Application bundle usually not used for writing data. Specifically user may be standard user in that case he/she will have only read,execute permission to App bundle and attempt writing inside bundle will fail.
Application Support directory is used to save files and other big data that application needs in the next run .If data size is less then it is preferred to store in NSUserDefaults.
You can define your own keys to save other NSString, NSNumber etc objects to NSUserDefaults. Same keys is used to read from NSUserDefaults.
One of the nice features of BBEdit is how it keeps track of files no matter what happens to those files. The application that I am working on needs to keep track of the location of a number of files owned by the user. The user can move or delete these files and my application needs to know where those files are even if my application was not running at the time the change in location happened.
What is a recommended strategy for this problem or what could it be?
Keeping track of files while the application is running is not the problem. I want the correct path to the files when my application is running no matter what happened to those files when my application was not running.
Is manually searching for the files the best approach or is there an API that I am overlooking that makes this easier?
Starting from 10.6, it is done by “Bookmarks” functionality of NSURL:
Bookmarks are a new facility for generating persistent references to resources identified by URLs. A bookmark is a data object generated by the system from a resource URL. The bookmark data encapsulates a durable, opaque reference to the underlying resource as well as value of resource properties captured when the bookmark was created. A bookmark can be stored in memory or on disk and later used to access the resource property values it contains, or resolved to cover the underlying resource’s URL. In the case of file system resources, the bookmark is capable of locating resources that have been moved or renamed since the bookmark was created, similar to Alias Manager aliases. Note that in this release, bookmarks resolve only by path.
The following new NSURL methods are further documented in NSURL.h:
- (NSData *)bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)options includingResourceValuesForKeys:(NSArray *)keys relativeToURL:( NSURL*)relativeURL error:(NSURL **)error;
- (NSURL*)initByResolvingBookmarkData:(NSData*)bookmarkData options:(NSURLBookmarkResolutionOptions)options relativeToURL:(NSURL *)relativeURL bookmarkDataIsStale:(BOOL *)isStale error:(NSError **)error;
+ (NSURL *)URLByResolvingBookmarkData:(NSData *)bookmarkData options:(NSURLBookmarkResolutionOptions)options relativeToURL:(NSURL *)relativeURL bookmarkDataIsStale:(BOOL *)isStale error:(NSError **)error;
+ (NSDictionary *)resourceValuesForKeys:(NSArray *)keys fromBookmarkData:(NSData *)bookmarkData;
Before 10.6, it was done with aliases.
I need to open a NSSavePanel with the users Library folder as destination folder. Normally I would do this by entering ~/Library/ in [NSSavePanel beginSheetForDirectory].
This works fine as long as the application is not sandboxed. For sandboxed applications this will result in the NSSavePanel trying to access a folder inside the applications document "box".
I cannot refer to /Users/username/Library/ as I do not know the users username at runtime. So how do I link to this path in cocoa?
I'm not sure how sandboxing fits in with this, but you can find the user's library directory using:
NSArray* paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES );
Not sure if this will work on a sandboxed application but this is how I do it right now. This will return /User/TheirUserName
-(NSString *)homeDirectory
{
return NSHomeDirectory();
}
It depends what you are trying to achieve.
If the behavior is required by your application, then you can request a temporary exception entitlement when submitting the application to the Mac App Store. But sooner or later, you will have to find a solution to remove this exception.
If you want to access data that were previously stored in the ~/Library/ folder, you can define a migration strategy to move back the data into the sandbox.