Failed attempt using Related Items to create backup file in sandboxed app

278 views Asked by At

The App Sandbox design guide says:

The related items feature of App Sandbox lets your app access files that have the same name as a user-chosen file, but a different extension. This feature consists of two parts: a list of related extensions in the application’s Info.plist file and code to tell the sandbox what you’re doing.

My Info.plist defines a document type for .pnd files (the user-chosen file), as well as a document type for .bak files. The entry for the .bak files has, among other properties, the property NSIsRelatedItemType = YES.

I am trying to use Related Items to move an existing file to a backup file (change .pnd suffix to .bak suffix) when the user writes a new version of the .pnd file. The application is sandboxed. I am not proficient with sandboxing.

I am using PasteurOrgManager as the NSFilePresenter class for both the original and backup files:

@interface PasteurOrgData : NSObject <NSFilePresenter>
. . . .
@property (readonly, copy) NSURL *primaryPresentedItemURL;
@property (readonly, copy) NSURL *presentedItemURL;
@property (readwrite) NSOperationQueue *presentedItemOperationQueue;
@property (readwrite) NSFileCoordinator *fileCoordinator;
. . . .
- (void) doBackupOf: (NSString*) path;
. . . .
@end

The doBackupOf: method is as follows. Notice that it also sets the NSFilePresenter properties:

- (void) doBackupOf: (NSString*) path
{
    NSError *error = nil;
    NSString *appSuffix = @".pnd";

    NSURL *const pathAsURL = [NSURL URLWithString: [NSString stringWithFormat: @"file://%@", path]];
    NSString *const baseName = [pathAsURL lastPathComponent];
    NSString *const prefixToBasename = [path substringToIndex: [path length] - [baseName length] - 1];
    NSString *const baseNameWithoutExtension = [baseName substringToIndex: [baseName length] - [appSuffix length]];
    NSString *backupPath = [NSString stringWithFormat: @"%@/%@.bak", prefixToBasename, baseNameWithoutExtension];
    NSURL *const backupURL = [NSURL URLWithString: [NSString stringWithFormat: @"file://%@", backupPath]];

    // Move backup to trash — I am sure this will be my next challenge
    // (it's a no-op now because there is no pre-existing .bak file)
    [[NSFileManager defaultManager] trashItemAtURL: backupURL
                                  resultingItemURL: nil
                                             error: &error];

    // Move file to backup
    primaryPresentedItemURL = pathAsURL;
    presentedItemURL = backupURL;
    presentedItemOperationQueue = [NSOperationQueue mainQueue];

    [NSFileCoordinator addFilePresenter: self];
    fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter: self];   // error here
    [self backupItemWithCoordinationFrom: pathAsURL
                                      to: backupURL];
    [NSFileCoordinator removeFilePresenter: self];
    fileCoordinator = nil;
}

The backupItemWithCoordinationFrom: method does the heavy lifting, basically:

[fileCoordinator coordinateWritingItemAtURL: from
                                    options: NSFileCoordinatorWritingForMoving
                                      error: &error
                                 byAccessor: ^(NSURL *oldURL) {
                                     [self.fileCoordinator itemAtURL: oldURL willMoveToURL: to];
                                     [[NSFileManager defaultManager] moveItemAtURL: oldURL
                                                                             toURL: to
                                                                             error: &error];
                                     [self.fileCoordinator itemAtURL: oldURL didMoveToURL: to];
                                 }

but the code doesn't make it that far. I have traced the code and the URL variables are as I expect, and are reasonable. At the point of "error here" in the above code, where I allocate the File Presenter, I get:

NSFileSandboxingRequestRelatedItemExtension: an error was received from pboxd instead of a token. Domain: NSPOSIXErrorDomain, code: 1
[presenter] +[NSFileCoordinator addFilePresenter:] could not get a sandbox extension. primaryPresentedItemURL: file:///Users/cope/Me.pnd, presentedItemURL: file:///Users/cope/Me.bak

Any help is appreciated.

(I have read related posts Where can a sandboxed Mac app save files? and Why do NSFilePresenter protocol methods never get called?. I have taken note of several other sandboxing-related posts that don't seem relevant to this issue.)

MacBook Pro, MacOS 10.13.5, XCode Version 9.3 (9E145)

1

There are 1 answers

1
Ol Sen On

do not read too much about avoiding sandboxing. Most explenations go too far out of the most obvious problem. Instead of explaining the pitfalls that rightfully triggers sandboxing they explain mostly how to avoid the Sandbox at all. Which is not a solution - it is a thread!

So the most obvious problem is exposing a URL to pasteboard that still needs properly escaped characters in the string before you transform to NSURL.

So your NSString beginning with "file://" should use something like..

NSString *encodeStringForURL = [yourstring stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];

before you transform to NSURL with

NSURL *fileurl = [NSURL URLWithString:encodeStringForURL];
NString *output = fileurl.absoluteString;