123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- //
- // AppSandboxFileAccess.m
- // AppSandboxFileAccess
- //
- // Created by Leigh McCulloch on 23/11/2013.
- //
- // Copyright (c) 2013, Leigh McCulloch
- // All rights reserved.
- //
- // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause
- //
- // Redistribution and use in source and binary forms, with or without
- // modification, are permitted provided that the following conditions are
- // met:
- //
- // 1. Redistributions of source code must retain the above copyright
- // notice, this list of conditions and the following disclaimer.
- //
- // 2. Redistributions in binary form must reproduce the above copyright
- // notice, this list of conditions and the following disclaimer in the
- // documentation and/or other materials provided with the distribution.
- //
- // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- //
- #import "AppSandboxFileAccess.h"
- #import "AppSandboxFileAccessPersist.h"
- #import "AppSandboxFileAccessOpenSavePanelDelegate.h"
- #if !__has_feature(objc_arc)
- #error ARC must be enabled!
- #endif
- #define CFBundleDisplayName @"CFBundleDisplayName"
- #define CFBundleName @"CFBundleName"
- @interface AppSandboxFileAccess ()
- @property (nonatomic, strong) AppSandboxFileAccessPersist *defaultDelegate;
- @end
- @implementation AppSandboxFileAccess
- + (AppSandboxFileAccess *)fileAccess {
- return [[AppSandboxFileAccess alloc] init];
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- NSString *applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:CFBundleDisplayName];
- if (!applicationName) {
- applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:CFBundleName];
- }
-
- self.title = NSLocalizedString(@"Allow Access",nil);
- applicationName = [applicationName stringByAppendingString:@" "];
- self.message = [applicationName stringByAppendingString:NSLocalizedString(@"needs to access this path to continue. Click Allow to continue.", nil)];
- self.prompt = NSLocalizedString(@"Allow",nil);
-
- // create default delegate object that persists bookmarks to user defaults
- self.defaultDelegate = [[AppSandboxFileAccessPersist alloc] init];
- self.bookmarkPersistanceDelegate = _defaultDelegate;
- }
- return self;
- }
- - (NSURL *)askPermissionForURL:(NSURL *)url {
- NSParameterAssert(url);
-
- // this url will be the url allowed, it might be a parent url of the url passed in
- __block NSURL *allowedURL = nil;
-
- // create delegate that will limit which files in the open panel can be selected, to ensure only a folder
- // or file giving permission to the file requested can be selected
- AppSandboxFileAccessOpenSavePanelDelegate *openPanelDelegate = [[AppSandboxFileAccessOpenSavePanelDelegate alloc] initWithFileURL:url];
-
- // check that the url exists, if it doesn't, find the parent path of the url that does exist and ask permission for that
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSString *path = [url path];
- while (path.length > 1) { // give up when only '/' is left in the path or if we get to a path that exists
- if ([fileManager fileExistsAtPath:path isDirectory:NULL]) {
- break;
- }
- path = [path stringByDeletingLastPathComponent];
- }
- url = [NSURL fileURLWithPath:path];
-
- // display the open panel
- dispatch_block_t displayOpenPanelBlock = ^{
-
- NSOpenPanel *openPanel = [NSOpenPanel openPanel];
- [openPanel setMessage:self.message];
- [openPanel setCanCreateDirectories:NO];
- [openPanel setCanChooseFiles:YES];
- [openPanel setCanChooseDirectories:YES];
- [openPanel setAllowsMultipleSelection:NO];
- [openPanel setPrompt:self.prompt];
- [openPanel setTitle:self.title];
- [openPanel setShowsHiddenFiles:NO];
- [openPanel setExtensionHidden:NO];
- [openPanel setDirectoryURL:url];
- [openPanel setDelegate:openPanelDelegate];
- [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- NSInteger openPanelButtonPressed = [openPanel runModal];
- if (openPanelButtonPressed == NSFileHandlingPanelOKButton) {
- allowedURL = [openPanel URL];
- }
- };
- if ([NSThread isMainThread]) {
- displayOpenPanelBlock();
- } else {
- dispatch_sync(dispatch_get_main_queue(), displayOpenPanelBlock);
- }
- return allowedURL;
- }
- - (NSData *)persistPermissionPath:(NSString *)path {
- NSParameterAssert(path);
-
- return [self persistPermissionURL:[NSURL fileURLWithPath:path]];
- }
- - (NSData *)persistPermissionURL:(NSURL *)url {
-
-
- NSParameterAssert(url);
-
- // store the sandbox permissions
- NSData *bookmarkData = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:NULL];
- if (bookmarkData) {
- [self.bookmarkPersistanceDelegate setBookmarkData:bookmarkData forURL:url];
- }
- return bookmarkData;
- }
- - (BOOL)accessFilePath:(NSString *)path withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist {
- // Deprecated. Use 'accessFilePath:persistPermission:withBlock:' instead.
- return [self accessFilePath:path persistPermission:persist withBlock:block];
- }
- - (BOOL)accessFileURL:(NSURL *)fileURL withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist {
- // Deprecated. Use 'accessFileURL:persistPermission:withBlock:' instead.
- return [self accessFileURL:fileURL persistPermission:persist withBlock:block];
- }
- - (BOOL)accessFilePath:(NSString *)path persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block {
- return [self accessFileURL:[NSURL fileURLWithPath:path] persistPermission:persist withBlock:block];
- }
- - (BOOL)accessFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block {
- NSParameterAssert(fileURL);
- NSParameterAssert(block);
-
- BOOL success = [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:^(NSURL *securityScopedFileURL, NSData *bookmarkData) {
- // execute the block with the file access permissions
- @try {
- [securityScopedFileURL startAccessingSecurityScopedResource];
- block();
- } @finally {
- //[securityScopedFileURL stopAccessingSecurityScopedResource];
- }
- }];
-
- return success;
- }
- - (BOOL)requestAccessPermissionsForFilePath:(NSString *)filePath persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block {
- NSParameterAssert(filePath);
-
- NSURL *fileURL = [NSURL fileURLWithPath:filePath];
- return [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:block];
- }
- - (BOOL)requestAccessPermissionsForFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block {
- NSParameterAssert(fileURL);
-
- NSURL *allowedURL = nil;
-
- // standardize the file url and remove any symlinks so that the url we lookup in bookmark data would match a url given by the askPermissionForURL method
- fileURL = [[fileURL URLByStandardizingPath] URLByResolvingSymlinksInPath];
-
- // lookup bookmark data for this url, this will automatically load bookmark data for a parent path if we have it
- NSData *bookmarkData = [self.bookmarkPersistanceDelegate bookmarkDataForURL:fileURL];
- if (bookmarkData) {
- // resolve the bookmark data into an NSURL object that will allow us to use the file
- BOOL bookmarkDataIsStale;
- allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL];
- // if the bookmark data is stale we'll attempt to recreate it with the existing url object if possible (not guaranteed)
- if (bookmarkDataIsStale) {
- bookmarkData = nil;
- [self.bookmarkPersistanceDelegate clearBookmarkDataForURL:fileURL];
- if (allowedURL) {
- bookmarkData = [self persistPermissionURL:allowedURL];
- if (!bookmarkData) {
- allowedURL = nil;
- }
- }
- }
- }
-
- // if allowed url is nil, we need to ask the user for permission
- if (!allowedURL) {
- allowedURL = [self askPermissionForURL:fileURL];
- if (!allowedURL) {
- // if the user did not give permission, exit out here
- return NO;
- }
- }
-
- // if we have no bookmark data and we want to persist, we need to create it
- if (persist && !bookmarkData) {
- bookmarkData = [self persistPermissionURL:allowedURL];
- }
-
- if (block) {
- block(allowedURL, bookmarkData);
- }
-
- return YES;
- }
- - (void)setBookmarkPersistanceDelegate:(NSObject<AppSandboxFileAccessProtocol> *)bookmarkPersistanceDelegate
- {
- // revert to default delegate object if no delegate provided
- if (bookmarkPersistanceDelegate == nil) {
- _bookmarkPersistanceDelegate = self.defaultDelegate;
- } else {
- _bookmarkPersistanceDelegate = bookmarkPersistanceDelegate;
- }
- }
- @end
|