iOS folder bookmarks

I have an iOS app that allows user to select a folder (from Files). I want to bookmark that folder and later on (perhaps on a different launch of the app) access the contents of it. Is that scenario supported or not? Can't make it work for some reason (e.g. I'm getting no error from the call to create a bookmark, from a call to resolve the bookmark, the folder URL is not stale, but... startAccessingSecurityScopedResource() is returning false.

Answered by DTS Engineer in 854824022

I have an iOS app that allows users to select a folder (from Files). I want to bookmark that folder and later on (perhaps on a different launch of the app) access the contents of it. Is that scenario supported or not?

Technically, yes, this works. However, practically speaking... it doesn't work very well. The problem here is that due to a bug (r.102995804), NSURL isn't able to resolve bookmarks across volume mounts. That means bookmarks within the device work fine and bookmarks to other volumes initially work... but then break completely once the volume has been unmounted.

Unfortunately, I think that makes them pretty unusable in practice, since one of the main reasons a user would WANT to persistently reference an external directory is that they're accessing external storage. More to the point, it's hard to build the kind of "automatic" interface that bookmarks allow when the probability is so high that the bookmark simply won't work at all.

Finally, while this is a known bug, I'd appreciate you filing a bug on this and posting the bug number back here. It turns out that is a somewhat tricky issue to resolve, so duplicate bugs are important.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

It feels you are talking here about withoutImplicitSecurityStore creation option instead of withoutImplicitStartAccessing resolving option.

No, I'm talking about "withoutImplicitStartAccessing". We explicitly say it does not apply to security-scoped bookmarks:

"This option causes an implicit call to startAccessingSecurityScopedResource() on the returned URL when it’s ready to use the resource.

This option isn’t applicable to security-scoped bookmarks."

In my tests, withoutImplicitStartAccessing does pretty much what it says on the tin.

You're testing on iOS, not macOS. As I said earlier:

"If it's feasible, I'd recommend testing your code as a "native" macOS app (not the simulator or in compatibility mode). macOS is better at enforcing the "right" behavior, so code that works there will generally work on iOS."

I'll state that more directly— the internal implementation of iOS means that some things work that really shouldn't. The right approach here is to use macOS as the guide.

Breaking down a few specifics:

  1. Create a bookmark (without specifying neither withoutImplicitSecurityStore nor withSecurityScope).

This works on iOS because the system is basically ONLY creating security-scoped bookmarks. It doesn't work on macOS.

  1. Resolve bookmark specifying withoutImplicitStartAccessing.
  2. Access the URL— that would fail.
  3. Call startAccessingSecurityScopedResource on it - it returns true.

You can see the correct code for this here, which shows that resolving "withSecurityScope" requires you to call "startAccessing".

  1. If instead of 4 above you resolve the bookmark without specifying withoutImplicitStartAccessing, then
  2. access the URL - that would work right away.

I'd need to do a deep dive to sort out what's going on here, but I think you're basically going down the secondary code path that's used to support bookmark IPC (the same mechanism the open panel uses). Again, this ONLY works on iOS because historical details mean that "all" bookmarks end up having security scope attached. The code above fails completely on macOS.

FYI, I grabbed the "BookmarksBench.swift" file you'd attached to your bugs and on macOS it matches what I'm describing above. The one detail is that you need to quit and relaunch the app after you select a file, otherwise you end up using the access the open panel granted in the kernel.

It feels you are talking here about withoutImplicitSecurityStore creation option instead of withoutImplicitStartAccessing resolving option.

Part of what complicates this entire discussion is that the bookmark API is used to both preserve URL access and save data references. Strictly speaking, all the code above is (sort of) within the API contract. Your app never created a security-scoped bookmark, which means it got implicit access when resolve succeeded and it could disable access using withoutImplicitSecurityScope. The problem here is the words "when resolve succeeded". Strictly speaking, resolve did not need to succeed and it would NOT have succeeded on macOS.

In any case, relying on this mechanism is a mistake. The right approach here is to use exactly the same code macOS does. That means:

The pattern above is the only pattern that's specifically documented to actually "work". There are absolutely other sequences that work, but relying on those other sequences is a mistake. The exceptions are either bugs or they work because of odd edge cases/details. Either way, those other flows aren't something you can rely on.

You mean it's doable to get the app name from the Application/2A6A7975-6CE1-40F0-B7D0-18A6E64C0D89/ url via some undocumented means?

No. What I was referring to is the system providing a way that your app could open up Files.app and have it show the file you passed in. We haven't documented a URL scheme for that, but a bit of searching will show that "shareddocuments://" isn't really secret either.

Thank you for the bugs and following up on this one:

FB19996364 - "iOS and macOS: bookmarkData expected to fail but it doesn't"

You gave this example flow in your bug:

  1. on macOS: run the attached app in Xcode
  2. tap "Pick File" and select a file
  3. tap "Stop Access" (this is important as macOS picker just called start access on the URL).
  4. tap "Create bookmark" - that succeeds
  5. tap "Resolve bookmark" - that succeeds
  6. tap "Read" - that fails with NSUnderlyingError=0x6000007fa0d0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

I think this happens because you're creating a standard bookmark, not a security scoped bookmark. I'd need to dig into the code to be sure of the details, but I suspect the standard creation succeeds because the URL object itself already has the metadata bookmark creation needs, so the system never checks against the sandbox. However, security scoped creation fails at #3.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

You're testing on iOS, not macOS

In fact, that was on macOS...

Your app never created a security-scoped bookmark

Well, the bench UI is flexible, it's a matter of selecting that checkbox in the bench UI.. However, now that I tried it (on Mac) - the create bookmark fails with error.. "NSCocoaErrorDomain Code=256 "Could not open() the item", specifically this very first step (after the picker):

Create with NSURL.BookmarkCreationOptions.withSecurityScope.

However, security scoped creation fails at #3.

Yep, for me it's failing on 4. tap "Create bookmark" - if I select "withSecurityScope (Mac only)" in the UI.

Yep, for me it's failing on 4. tap "Create bookmark" - if I select "withSecurityScope (Mac only)" in the UI.

The way to understand this is that a standard (non-security scoped) bookmark is roughly equivalent to a URL. Your process does need to be able to "see" the object (so it can retrieve the necessary metadata), but that only requires access to the parent directory, not that target object. You can actually create a bookmark to a file that no one has access to (literally, "chmod 000 <target>").

A security scoped bookmark is that "standard" bookmark, plus some additional data that both prevents the bookmark data from being modified (so you can't point it at something else) and binds the bookmark to your app (so that no other process can resolve it). The generation of that additional data is then tied into the sandbox so that you cannot generate a security scoped bookmark to a file that you don't already have access to.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you Kevin,

My question at this point is whether there's this other bug on macOS worth filing that when creating a bookmark with "withSecurityScope=on" option I'm getting an error. Very easy to demonstrate using my test bench.

My question at this point is whether there's this other bug on macOS worth filing that when creating a bookmark with "withSecurityScope=on" option I'm getting an error. Very easy to demonstrate using my test bench.

The flows have gotten a bit confused so, making this explicit, the failure here:

  1. on macOS: run the attached app in Xcode
  2. tap "Pick File" and select a file
  3. tap "Stop Access"
  4. set "withSecurityScope=on"
  5. tap "Create bookmark" - that succeeds
  6. Create fails with error.

...is expected behavior. Your app "released" it's access at #4, so it's no longer allowed to make a bookmark.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

I didn't file the following as I'm not sure if it is correct flow or not:

  1. on macOS: run the attached app in Xcode
  2. tap "Pick File" and select a file
  3. set "withSecurityScope=on" in the UI
  4. tap "Create bookmark" - that fails

I didn't file the following as I'm not sure if it is correct flow or not:

  1. on macOS: run the attached app in Xcode
  2. tap "Pick File" and select a file
  3. set "withSecurityScope=on" in the UI
  4. tap "Create bookmark" - that fails

That flow works fine for me. What error are you getting? And what file are you picking?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

It was:

Error Domain=NSCocoaErrorDomain Code=256 "Could not open() the item"

but then I look in the console:

error 00:47:00.954979+0100 kernel Sandbox: BookmarksBench(61956) deny(1) file-write-data /Users/user/Documents/file

which reminded me to check the entitlements – turns out I forgot to specify read/write access for user selected files... Fixed that - and everything is good now. Thank you.

PS. Quite interesting that that affects secure bookmark creation, but not normal bookmark creation..

iOS folder bookmarks
 
 
Q