Question

I'm sandboxing my app, and trying to allow for import/export of multiple files, using an XML file to refer to them. To allow my app (or another sandboxed app) access to the files listed in the XML, I'm also including a serialized security-scoped bookmark. I'm serializing it as described in this answer, and my unit tests (which are not sandboxed) write and read the XML data without issue. When my app resolves the bookmark, the NSURL returned is nil, as is the NSError reference. Since I don't believe that should be the case, why is it happening? I can work around it by prompting the user to select a file/directory with an NSOpenPanel, but I'd still like to get the bookmarks to work as they should.

Reproduced in a test project

To reproduce at home, create a new Cocoa app in Xcode, and use the following Gist for the files in the project: https://gist.github.com/2582589 (updated with a proper next-view loop)

Then, follow Apple's instructions to code-sign the project. You reproduce the problem (which I submitted to Apple as rdar://11369377) by clicking the buttons in sequence. You pick any file on disk (outside the app's container), then an XML to export to, and then the same XML to import.

Hopefully you guys will be able to help me figure out what I'm doing wrong. Either I'm doing something wrong and the framework is erroneously keeping to itself, or I'm doing it right and it's totally broken. I try not to blame the framework, so which is it? Or is there another possibility?

Sample Code

Exporting the XML to docURL:

// After the user picks an XML (docURL) destination with NSSavePanel

[targetURL startAccessingSecurityScopedResource];
NSData *bookmark = [targetURL bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
                       includingResourceValuesForKeys:nil
                                        relativeToURL:docURL
                                                error:&error];
[targetURL stopAccessingSecurityScopedResource];

Importing the XML from docURL:

// After the user selected the XML (docURL) from an NSOpenPanel

NSURL *result = [NSURL URLByResolvingBookmarkData:bookmarkData
                                          options:NSURLBookmarkResolutionWithSecurityScope
                                    relativeToURL:docURL
                              bookmarkDataIsStale:nil
                                            error:&error];

I tried surrounding this call with[docURL ..AccessingSecurityScopedResource], which didn't make a difference (as expected, since the docURL is already within scope after having been selected in the Open Panel

Also, I specify the following in my app.entitlements file:

com.apple.security.files.user-selected.read-write
com.apple.security.files.bookmarks.app-scope
com.apple.security.files.bookmarks.collection-scope

As mentioned above, the second step (resolving the bookmark) completes, but leaves both error and result nil. As I've been implementing sandboxing, most of the mistakes I've made have resulted in an NSError being returned, which helped me to resolve the bug. But now there's no error, and no URL is resolved.

Miscellaneous troubleshooting steps

  • I tried placing the XML file into my app's sandbox, which didn't make a difference, so access to the XML file is not the problem

  • The app uses ARC, but so do the unit tests, which succeed. I tried using an alloc/init instead of the autoreleased class method, too (just in case)

  • I pasted the URL resolution code immediately after creating the bookmark, and it runs fine, producing a security-scoped URL

  • I did a po on the originally created bookmark (before serialization), and then on the bookmark after deserialization, and they match 100%. Serialization is not the problem

  • I replaced the resolution call with CFURLCreateByResolvingBookmarkData(..), with no change. If it is a bug, it's present in the Core Foundation API as well as the Cocoa layer

  • Specifying a value for bookmarkDataIsStale: has no effect

  • If I specify 0 for options:, then I do get back a valid NSURL, but it has no security scope, and therefore subsequent calls to read the file do still fail

    In other words, the deserialized bookmark does appear to be valid. If the bookmark data were corrupted, I doubt NSURL would be able to do anything with it

  • NSURL.h didn't contain any useful comments to point out something I'm doing wrong

Is anyone else using security-scoped document bookmarks in a sandboxed application with success? If so, what are you doing differently than I am?

OS Version Request

Can someone with access to the Mountain Lion beta verify whether or not my sample project shows the same (lack of an) error? If it's a bug that has been fixed after Lion, I won't worry about it. I'm not in the developer program yet, and so don't have access. I'm not sure if answering that question would violate the NDA, but I hope not.

Was it helpful?

Solution

In your Gist code, change the following line in AppDelegate.m (line 61):

[xmlTextFileData writeToURL:savePanel.URL atomically:YES];

to

[xmlTextFileData writeToURL:savePanel.URL atomically:NO];

Your code will then work.

The reason for this is likely the same reason for which it is necessary to have an existing (but empty) file that will contain the document-scoped bookmarks before calling [anURL bookmarkDataWithOptions]: While creating the NSData instance, the ScopedBookmarkAgent adds something (like a tag, probably an extended file attribute) to that file.

If you write data (i.e. the bookmark URLs) to that file atomically, in fact they're written not directly to the file but first to a temporary file that is renamed if the write operation was successful. It seems that the tag that has been added to the (empty, but existing) file that will contain the bookmarks is getting lost during this process of writing to a temporary file and then renaming it (and thereby likely deleting the original, empty file).

By the way: It shouldn't be necessary to create app-scoped bookmarks before passing the respective URLs to the xml file containing the document-scoped bookmarks.

Addition: com.apple.security.files.bookmarks.collection-scope has been renamed to com.apple.security.files.bookmarks.document-scope in 10.7.4.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top