17

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.

Community
  • 1
  • 1
Dov
  • 15,530
  • 13
  • 76
  • 177
  • I couldn't help anyway, but this question is a bit of a travel log through your exploration. It would be great if you could rework it, forgetting about HOW you got where you did. No offense: the question seems to be high-quality, which the 5 upvotes (now 6 with mine) reflect. – Dan Rosenstark May 21 '12 at 15:19
  • @Yar thanks for the input and the upvote. I kept (most of) the updates in for two reasons. First of all, I'm documenting the steps I tried so others can avoid repetitive troubleshooting, and second, I posted the updates at the bottom (instead of working the new information into the main question) to make clearer to those following the question what actually changed. I planned on editing the question after it was answered. Are you suggesting rearranging the content in the question, or removing less-relevant pieces? – Dov May 21 '12 at 15:30
  • both. It would increase your likelihood of getting answered and also make the post more relevant for future users who have totally different explorations, but the same problem. You know, abstract the problem ;) – Dan Rosenstark May 21 '12 at 16:52
  • @Yar Done. Does that look better? – Dov May 21 '12 at 17:16
  • I don't understand how the accepted solution solves the problem. Did you ever resolve why your NSError reference isn't being set and why it wasn't returning an NSData pointer? – JP Richardson Jul 10 '12 at 03:40
  • The reason my code didn't work was because of the way writing a file atomically works. It writes the file to a temporary location, and then moves it to the proper destination. For sandboxing, the ScopedBookmarkAgent adds some metadata to the file, which gets blown away as the `writeToURL:atomically:` function moves the file around. Of course, when I go to read the bookmark, I should get an `NSError` back. It would have made it easier to track down what was happening. – Dov Jul 10 '12 at 13:03

1 Answers1

6

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Tim
  • 1,659
  • 1
  • 21
  • 33
  • Thanks x1000. That thought never even occurred to me. Regarding the renamed entitlement key, can you give a reference? The documentation still says `collection-scope`. – Dov Jun 11 '12 at 23:53
  • 2
    The entitlement name change is mentioned in Table 3-4 of the [Entitlement Key Reference](https://developer.apple.com/library/mac/#documentation/Miscellaneous/Reference/EntitlementKeyReference/EnablingAppSandbox/EnablingAppSandbox.html#//apple_ref/doc/uid/TP40011195-CH4-SW18). – Tim Jun 12 '12 at 06:26
  • @Tim You mention that "it is necessary to have an existing (but empty) file that will contain the document-scoped bookmarks". How is one supposed to know the location at which the user will save a new document for first time. Moreover, how does one create an empty file at some location with sandboxing on and NSSavePanel not coming in picture? – AmaltasCoder Jan 02 '15 at 12:59