├── AppSandboxFileAccess.podspec ├── AppSandboxFileAccess.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── AppSandboxFileAccess ├── AppSandboxFileAccess-Info.plist ├── AppSandboxFileAccess-Prefix.pch └── Classes │ ├── AppSandboxFileAccess.h │ ├── AppSandboxFileAccess.m │ ├── AppSandboxFileAccessOpenSavePanelDelegate.h │ ├── AppSandboxFileAccessOpenSavePanelDelegate.m │ ├── AppSandboxFileAccessPersist.h │ └── AppSandboxFileAccessPersist.m ├── AppSandboxFileAccessDemo ├── AppDelegate.h ├── AppDelegate.m ├── AppSandboxFileAccessDemo.entitlements ├── Base.lproj │ └── MainMenu.xib ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── main.m ├── AppSandboxFileAccessTests ├── AppSandboxFileAccessOpenSavePanelDelegateTests.m └── Info.plist ├── LICENSE ├── README.md ├── screenshot-1.png ├── screenshot-2.png └── screenshot-3.png /AppSandboxFileAccess.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "AppSandboxFileAccess" 4 | s.version = "1.0.14" 5 | s.summary = "A class that wraps up writing and accessing files outside a Mac apps App Sandbox files into a simple interface." 6 | 7 | s.description = <<-DESC 8 | A class that wraps up writing and accessing files outside a Mac apps App Sandbox files into a simple interface. 9 | The class will request permission from the user with a simple to understand dialog consistent 10 | with Apple's documentation and persist permissions across application runs. 11 | DESC 12 | 13 | s.homepage = "https://github.com/leighmcculloch/AppSandboxFileAccess" 14 | s.license = { :type => "BSD-2", :file => "LICENSE" } 15 | s.author = { "Leigh McCulloch" => "leigh@mcchouse.com" } 16 | s.platform = :osx, "10.7.3" 17 | s.source = { :git => "https://github.com/leighmcculloch/AppSandboxFileAccess.git", :tag => "1.0.14" } 18 | s.source_files = "AppSandboxFileAccess/Classes/*.{h,m}" 19 | s.requires_arc = true 20 | 21 | end 22 | -------------------------------------------------------------------------------- /AppSandboxFileAccess.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 201DCD311840BDE600B43456 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 201DCD301840BDE600B43456 /* Cocoa.framework */; }; 11 | C212FEC51A7E83ED00BE3532 /* AppSandboxFileAccess.h in Headers */ = {isa = PBXBuildFile; fileRef = C212FEBF1A7E83ED00BE3532 /* AppSandboxFileAccess.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | C212FEC61A7E83ED00BE3532 /* AppSandboxFileAccess.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FEC01A7E83ED00BE3532 /* AppSandboxFileAccess.m */; }; 13 | C212FEC71A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = C212FEC11A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | C212FEC81A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FEC21A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.m */; }; 15 | C212FEC91A7E83ED00BE3532 /* AppSandboxFileAccessPersist.h in Headers */ = {isa = PBXBuildFile; fileRef = C212FEC31A7E83ED00BE3532 /* AppSandboxFileAccessPersist.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | C212FECA1A7E83ED00BE3532 /* AppSandboxFileAccessPersist.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FEC41A7E83ED00BE3532 /* AppSandboxFileAccessPersist.m */; }; 17 | C212FED91A7E8B6700BE3532 /* AppSandboxFileAccessOpenSavePanelDelegateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FED81A7E8B6700BE3532 /* AppSandboxFileAccessOpenSavePanelDelegateTests.m */; }; 18 | C212FEDA1A7E8B6700BE3532 /* AppSandboxFileAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */; }; 19 | C212FEEA1A7E944E00BE3532 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FEE91A7E944E00BE3532 /* AppDelegate.m */; }; 20 | C212FEEC1A7E944E00BE3532 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C212FEEB1A7E944E00BE3532 /* main.m */; }; 21 | C212FEEE1A7E944E00BE3532 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C212FEED1A7E944E00BE3532 /* Images.xcassets */; }; 22 | C212FEF11A7E944E00BE3532 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C212FEEF1A7E944E00BE3532 /* MainMenu.xib */; }; 23 | C212FF061A7E950600BE3532 /* AppSandboxFileAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */; }; 24 | C212FF081A7E951600BE3532 /* AppSandboxFileAccess.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | C212FEDB1A7E8B6700BE3532 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 201DCD241840BDE600B43456 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 201DCD2C1840BDE600B43456; 33 | remoteInfo = AppSandboxFileAccess; 34 | }; 35 | C212FF041A7E950200BE3532 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 201DCD241840BDE600B43456 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 201DCD2C1840BDE600B43456; 40 | remoteInfo = AppSandboxFileAccess; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXCopyFilesBuildPhase section */ 45 | C212FF071A7E950A00BE3532 /* Copy Frameworks */ = { 46 | isa = PBXCopyFilesBuildPhase; 47 | buildActionMask = 2147483647; 48 | dstPath = ""; 49 | dstSubfolderSpec = 10; 50 | files = ( 51 | C212FF081A7E951600BE3532 /* AppSandboxFileAccess.framework in Copy Frameworks */, 52 | ); 53 | name = "Copy Frameworks"; 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXCopyFilesBuildPhase section */ 57 | 58 | /* Begin PBXFileReference section */ 59 | 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AppSandboxFileAccess.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 201DCD301840BDE600B43456 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 61 | 201DCD331840BDE600B43456 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 62 | 201DCD341840BDE600B43456 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 63 | 201DCD351840BDE600B43456 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 64 | C212FEBF1A7E83ED00BE3532 /* AppSandboxFileAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSandboxFileAccess.h; sourceTree = ""; }; 65 | C212FEC01A7E83ED00BE3532 /* AppSandboxFileAccess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSandboxFileAccess.m; sourceTree = ""; }; 66 | C212FEC11A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSandboxFileAccessOpenSavePanelDelegate.h; sourceTree = ""; }; 67 | C212FEC21A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSandboxFileAccessOpenSavePanelDelegate.m; sourceTree = ""; }; 68 | C212FEC31A7E83ED00BE3532 /* AppSandboxFileAccessPersist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppSandboxFileAccessPersist.h; sourceTree = ""; }; 69 | C212FEC41A7E83ED00BE3532 /* AppSandboxFileAccessPersist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppSandboxFileAccessPersist.m; sourceTree = ""; }; 70 | C212FECB1A7E83F800BE3532 /* AppSandboxFileAccess-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "AppSandboxFileAccess-Info.plist"; path = "AppSandboxFileAccess/AppSandboxFileAccess-Info.plist"; sourceTree = ""; }; 71 | C212FECC1A7E83F800BE3532 /* AppSandboxFileAccess-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AppSandboxFileAccess-Prefix.pch"; path = "AppSandboxFileAccess/AppSandboxFileAccess-Prefix.pch"; sourceTree = ""; }; 72 | C212FED41A7E8B6700BE3532 /* AppSandboxFileAccessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppSandboxFileAccessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | C212FED71A7E8B6700BE3532 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 74 | C212FED81A7E8B6700BE3532 /* AppSandboxFileAccessOpenSavePanelDelegateTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppSandboxFileAccessOpenSavePanelDelegateTests.m; sourceTree = ""; }; 75 | C212FEE41A7E944E00BE3532 /* AppSandboxFileAccessDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppSandboxFileAccessDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | C212FEE71A7E944E00BE3532 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 77 | C212FEE81A7E944E00BE3532 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 78 | C212FEE91A7E944E00BE3532 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 79 | C212FEEB1A7E944E00BE3532 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 80 | C212FEED1A7E944E00BE3532 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 81 | C212FEF01A7E944E00BE3532 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 82 | C212FF091A7E97F700BE3532 /* AppSandboxFileAccessDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = AppSandboxFileAccessDemo.entitlements; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 201DCD291840BDE600B43456 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 201DCD311840BDE600B43456 /* Cocoa.framework in Frameworks */, 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | C212FED11A7E8B6700BE3532 /* Frameworks */ = { 95 | isa = PBXFrameworksBuildPhase; 96 | buildActionMask = 2147483647; 97 | files = ( 98 | C212FEDA1A7E8B6700BE3532 /* AppSandboxFileAccess.framework in Frameworks */, 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | C212FEE11A7E944E00BE3532 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | C212FF061A7E950600BE3532 /* AppSandboxFileAccess.framework in Frameworks */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXFrameworksBuildPhase section */ 111 | 112 | /* Begin PBXGroup section */ 113 | 201DCD231840BDE600B43456 = { 114 | isa = PBXGroup; 115 | children = ( 116 | 201DCD361840BDE600B43456 /* AppSandboxFileAccess */, 117 | C212FECF1A7E840D00BE3532 /* Supporting Files */, 118 | C212FED51A7E8B6700BE3532 /* AppSandboxFileAccessTests */, 119 | C212FEE51A7E944E00BE3532 /* AppSandboxFileAccessDemo */, 120 | 201DCD2F1840BDE600B43456 /* Frameworks */, 121 | 201DCD2E1840BDE600B43456 /* Products */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | 201DCD2E1840BDE600B43456 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */, 129 | C212FED41A7E8B6700BE3532 /* AppSandboxFileAccessTests.xctest */, 130 | C212FEE41A7E944E00BE3532 /* AppSandboxFileAccessDemo.app */, 131 | ); 132 | name = Products; 133 | sourceTree = ""; 134 | }; 135 | 201DCD2F1840BDE600B43456 /* Frameworks */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 201DCD301840BDE600B43456 /* Cocoa.framework */, 139 | 201DCD321840BDE600B43456 /* Other Frameworks */, 140 | ); 141 | name = Frameworks; 142 | sourceTree = ""; 143 | }; 144 | 201DCD321840BDE600B43456 /* Other Frameworks */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 201DCD331840BDE600B43456 /* Foundation.framework */, 148 | 201DCD341840BDE600B43456 /* CoreData.framework */, 149 | 201DCD351840BDE600B43456 /* AppKit.framework */, 150 | ); 151 | name = "Other Frameworks"; 152 | sourceTree = ""; 153 | }; 154 | 201DCD361840BDE600B43456 /* AppSandboxFileAccess */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | C212FEBE1A7E83ED00BE3532 /* Classes */, 158 | ); 159 | path = AppSandboxFileAccess; 160 | sourceTree = ""; 161 | }; 162 | C212FEBE1A7E83ED00BE3532 /* Classes */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | C212FEBF1A7E83ED00BE3532 /* AppSandboxFileAccess.h */, 166 | C212FEC01A7E83ED00BE3532 /* AppSandboxFileAccess.m */, 167 | C212FEC11A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.h */, 168 | C212FEC21A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.m */, 169 | C212FEC31A7E83ED00BE3532 /* AppSandboxFileAccessPersist.h */, 170 | C212FEC41A7E83ED00BE3532 /* AppSandboxFileAccessPersist.m */, 171 | ); 172 | path = Classes; 173 | sourceTree = ""; 174 | }; 175 | C212FECF1A7E840D00BE3532 /* Supporting Files */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | C212FECB1A7E83F800BE3532 /* AppSandboxFileAccess-Info.plist */, 179 | C212FECC1A7E83F800BE3532 /* AppSandboxFileAccess-Prefix.pch */, 180 | ); 181 | name = "Supporting Files"; 182 | sourceTree = ""; 183 | }; 184 | C212FED51A7E8B6700BE3532 /* AppSandboxFileAccessTests */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | C212FED81A7E8B6700BE3532 /* AppSandboxFileAccessOpenSavePanelDelegateTests.m */, 188 | C212FED61A7E8B6700BE3532 /* Supporting Files */, 189 | ); 190 | path = AppSandboxFileAccessTests; 191 | sourceTree = ""; 192 | }; 193 | C212FED61A7E8B6700BE3532 /* Supporting Files */ = { 194 | isa = PBXGroup; 195 | children = ( 196 | C212FED71A7E8B6700BE3532 /* Info.plist */, 197 | ); 198 | name = "Supporting Files"; 199 | sourceTree = ""; 200 | }; 201 | C212FEE51A7E944E00BE3532 /* AppSandboxFileAccessDemo */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | C212FF091A7E97F700BE3532 /* AppSandboxFileAccessDemo.entitlements */, 205 | C212FEE81A7E944E00BE3532 /* AppDelegate.h */, 206 | C212FEE91A7E944E00BE3532 /* AppDelegate.m */, 207 | C212FEED1A7E944E00BE3532 /* Images.xcassets */, 208 | C212FEEF1A7E944E00BE3532 /* MainMenu.xib */, 209 | C212FEE61A7E944E00BE3532 /* Supporting Files */, 210 | ); 211 | path = AppSandboxFileAccessDemo; 212 | sourceTree = ""; 213 | }; 214 | C212FEE61A7E944E00BE3532 /* Supporting Files */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | C212FEE71A7E944E00BE3532 /* Info.plist */, 218 | C212FEEB1A7E944E00BE3532 /* main.m */, 219 | ); 220 | name = "Supporting Files"; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXGroup section */ 224 | 225 | /* Begin PBXHeadersBuildPhase section */ 226 | 201DCD2A1840BDE600B43456 /* Headers */ = { 227 | isa = PBXHeadersBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | C212FEC91A7E83ED00BE3532 /* AppSandboxFileAccessPersist.h in Headers */, 231 | C212FEC51A7E83ED00BE3532 /* AppSandboxFileAccess.h in Headers */, 232 | C212FEC71A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.h in Headers */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXHeadersBuildPhase section */ 237 | 238 | /* Begin PBXNativeTarget section */ 239 | 201DCD2C1840BDE600B43456 /* AppSandboxFileAccess */ = { 240 | isa = PBXNativeTarget; 241 | buildConfigurationList = 201DCD551840BDE600B43456 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccess" */; 242 | buildPhases = ( 243 | 201DCD281840BDE600B43456 /* Sources */, 244 | 201DCD291840BDE600B43456 /* Frameworks */, 245 | 201DCD2A1840BDE600B43456 /* Headers */, 246 | 201DCD2B1840BDE600B43456 /* Resources */, 247 | ); 248 | buildRules = ( 249 | ); 250 | dependencies = ( 251 | ); 252 | name = AppSandboxFileAccess; 253 | productName = AppSandboxFileAccess; 254 | productReference = 201DCD2D1840BDE600B43456 /* AppSandboxFileAccess.framework */; 255 | productType = "com.apple.product-type.framework"; 256 | }; 257 | C212FED31A7E8B6700BE3532 /* AppSandboxFileAccessTests */ = { 258 | isa = PBXNativeTarget; 259 | buildConfigurationList = C212FEDD1A7E8B6700BE3532 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccessTests" */; 260 | buildPhases = ( 261 | C212FED01A7E8B6700BE3532 /* Sources */, 262 | C212FED11A7E8B6700BE3532 /* Frameworks */, 263 | C212FED21A7E8B6700BE3532 /* Resources */, 264 | ); 265 | buildRules = ( 266 | ); 267 | dependencies = ( 268 | C212FEDC1A7E8B6700BE3532 /* PBXTargetDependency */, 269 | ); 270 | name = AppSandboxFileAccessTests; 271 | productName = AppSandboxFileAccessTests; 272 | productReference = C212FED41A7E8B6700BE3532 /* AppSandboxFileAccessTests.xctest */; 273 | productType = "com.apple.product-type.bundle.unit-test"; 274 | }; 275 | C212FEE31A7E944E00BE3532 /* AppSandboxFileAccessDemo */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = C212FEFE1A7E944E00BE3532 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccessDemo" */; 278 | buildPhases = ( 279 | C212FEE01A7E944E00BE3532 /* Sources */, 280 | C212FEE11A7E944E00BE3532 /* Frameworks */, 281 | C212FEE21A7E944E00BE3532 /* Resources */, 282 | C212FF071A7E950A00BE3532 /* Copy Frameworks */, 283 | ); 284 | buildRules = ( 285 | ); 286 | dependencies = ( 287 | C212FF051A7E950200BE3532 /* PBXTargetDependency */, 288 | ); 289 | name = AppSandboxFileAccessDemo; 290 | productName = AppSandboxFileAccessDemo; 291 | productReference = C212FEE41A7E944E00BE3532 /* AppSandboxFileAccessDemo.app */; 292 | productType = "com.apple.product-type.application"; 293 | }; 294 | /* End PBXNativeTarget section */ 295 | 296 | /* Begin PBXProject section */ 297 | 201DCD241840BDE600B43456 /* Project object */ = { 298 | isa = PBXProject; 299 | attributes = { 300 | LastUpgradeCheck = 0500; 301 | ORGANIZATIONNAME = "Leigh McCulloch"; 302 | TargetAttributes = { 303 | C212FED31A7E8B6700BE3532 = { 304 | CreatedOnToolsVersion = 6.1.1; 305 | }; 306 | C212FEE31A7E944E00BE3532 = { 307 | CreatedOnToolsVersion = 6.1.1; 308 | SystemCapabilities = { 309 | com.apple.Sandbox = { 310 | enabled = 1; 311 | }; 312 | }; 313 | }; 314 | }; 315 | }; 316 | buildConfigurationList = 201DCD271840BDE600B43456 /* Build configuration list for PBXProject "AppSandboxFileAccess" */; 317 | compatibilityVersion = "Xcode 3.2"; 318 | developmentRegion = English; 319 | hasScannedForEncodings = 0; 320 | knownRegions = ( 321 | en, 322 | Base, 323 | ); 324 | mainGroup = 201DCD231840BDE600B43456; 325 | productRefGroup = 201DCD2E1840BDE600B43456 /* Products */; 326 | projectDirPath = ""; 327 | projectRoot = ""; 328 | targets = ( 329 | 201DCD2C1840BDE600B43456 /* AppSandboxFileAccess */, 330 | C212FED31A7E8B6700BE3532 /* AppSandboxFileAccessTests */, 331 | C212FEE31A7E944E00BE3532 /* AppSandboxFileAccessDemo */, 332 | ); 333 | }; 334 | /* End PBXProject section */ 335 | 336 | /* Begin PBXResourcesBuildPhase section */ 337 | 201DCD2B1840BDE600B43456 /* Resources */ = { 338 | isa = PBXResourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | C212FED21A7E8B6700BE3532 /* Resources */ = { 345 | isa = PBXResourcesBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | ); 349 | runOnlyForDeploymentPostprocessing = 0; 350 | }; 351 | C212FEE21A7E944E00BE3532 /* Resources */ = { 352 | isa = PBXResourcesBuildPhase; 353 | buildActionMask = 2147483647; 354 | files = ( 355 | C212FEEE1A7E944E00BE3532 /* Images.xcassets in Resources */, 356 | C212FEF11A7E944E00BE3532 /* MainMenu.xib in Resources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXResourcesBuildPhase section */ 361 | 362 | /* Begin PBXSourcesBuildPhase section */ 363 | 201DCD281840BDE600B43456 /* Sources */ = { 364 | isa = PBXSourcesBuildPhase; 365 | buildActionMask = 2147483647; 366 | files = ( 367 | C212FEC81A7E83ED00BE3532 /* AppSandboxFileAccessOpenSavePanelDelegate.m in Sources */, 368 | C212FECA1A7E83ED00BE3532 /* AppSandboxFileAccessPersist.m in Sources */, 369 | C212FEC61A7E83ED00BE3532 /* AppSandboxFileAccess.m in Sources */, 370 | ); 371 | runOnlyForDeploymentPostprocessing = 0; 372 | }; 373 | C212FED01A7E8B6700BE3532 /* Sources */ = { 374 | isa = PBXSourcesBuildPhase; 375 | buildActionMask = 2147483647; 376 | files = ( 377 | C212FED91A7E8B6700BE3532 /* AppSandboxFileAccessOpenSavePanelDelegateTests.m in Sources */, 378 | ); 379 | runOnlyForDeploymentPostprocessing = 0; 380 | }; 381 | C212FEE01A7E944E00BE3532 /* Sources */ = { 382 | isa = PBXSourcesBuildPhase; 383 | buildActionMask = 2147483647; 384 | files = ( 385 | C212FEEC1A7E944E00BE3532 /* main.m in Sources */, 386 | C212FEEA1A7E944E00BE3532 /* AppDelegate.m in Sources */, 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | /* End PBXSourcesBuildPhase section */ 391 | 392 | /* Begin PBXTargetDependency section */ 393 | C212FEDC1A7E8B6700BE3532 /* PBXTargetDependency */ = { 394 | isa = PBXTargetDependency; 395 | target = 201DCD2C1840BDE600B43456 /* AppSandboxFileAccess */; 396 | targetProxy = C212FEDB1A7E8B6700BE3532 /* PBXContainerItemProxy */; 397 | }; 398 | C212FF051A7E950200BE3532 /* PBXTargetDependency */ = { 399 | isa = PBXTargetDependency; 400 | target = 201DCD2C1840BDE600B43456 /* AppSandboxFileAccess */; 401 | targetProxy = C212FF041A7E950200BE3532 /* PBXContainerItemProxy */; 402 | }; 403 | /* End PBXTargetDependency section */ 404 | 405 | /* Begin PBXVariantGroup section */ 406 | C212FEEF1A7E944E00BE3532 /* MainMenu.xib */ = { 407 | isa = PBXVariantGroup; 408 | children = ( 409 | C212FEF01A7E944E00BE3532 /* Base */, 410 | ); 411 | name = MainMenu.xib; 412 | sourceTree = ""; 413 | }; 414 | /* End PBXVariantGroup section */ 415 | 416 | /* Begin XCBuildConfiguration section */ 417 | 201DCD531840BDE600B43456 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ALWAYS_SEARCH_USER_PATHS = NO; 421 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 422 | CLANG_CXX_LIBRARY = "libc++"; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_WARN_BOOL_CONVERSION = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INT_CONVERSION = YES; 430 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | COPY_PHASE_STRIP = NO; 433 | GCC_C_LANGUAGE_STANDARD = gnu99; 434 | GCC_DYNAMIC_NO_PIC = NO; 435 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 436 | GCC_OPTIMIZATION_LEVEL = 0; 437 | GCC_PRECOMPILE_PREFIX_HEADER = NO; 438 | GCC_PREPROCESSOR_DEFINITIONS = ( 439 | "DEBUG=1", 440 | "$(inherited)", 441 | ); 442 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 443 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 444 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 445 | GCC_WARN_UNDECLARED_SELECTOR = YES; 446 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 447 | GCC_WARN_UNUSED_FUNCTION = YES; 448 | GCC_WARN_UNUSED_VARIABLE = YES; 449 | MACOSX_DEPLOYMENT_TARGET = 10.9; 450 | ONLY_ACTIVE_ARCH = YES; 451 | SDKROOT = macosx; 452 | }; 453 | name = Debug; 454 | }; 455 | 201DCD541840BDE600B43456 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ALWAYS_SEARCH_USER_PATHS = NO; 459 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 460 | CLANG_CXX_LIBRARY = "libc++"; 461 | CLANG_ENABLE_OBJC_ARC = YES; 462 | CLANG_WARN_BOOL_CONVERSION = YES; 463 | CLANG_WARN_CONSTANT_CONVERSION = YES; 464 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 465 | CLANG_WARN_EMPTY_BODY = YES; 466 | CLANG_WARN_ENUM_CONVERSION = YES; 467 | CLANG_WARN_INT_CONVERSION = YES; 468 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 469 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 470 | COPY_PHASE_STRIP = YES; 471 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 472 | ENABLE_NS_ASSERTIONS = NO; 473 | GCC_C_LANGUAGE_STANDARD = gnu99; 474 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 475 | GCC_PRECOMPILE_PREFIX_HEADER = NO; 476 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 477 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 478 | GCC_WARN_UNDECLARED_SELECTOR = YES; 479 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 480 | GCC_WARN_UNUSED_FUNCTION = YES; 481 | GCC_WARN_UNUSED_VARIABLE = YES; 482 | MACOSX_DEPLOYMENT_TARGET = 10.9; 483 | SDKROOT = macosx; 484 | }; 485 | name = Release; 486 | }; 487 | 201DCD561840BDE600B43456 /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | CLANG_ENABLE_MODULES = YES; 491 | COMBINE_HIDPI_IMAGES = YES; 492 | DYLIB_COMPATIBILITY_VERSION = 1; 493 | DYLIB_CURRENT_VERSION = 1; 494 | FRAMEWORK_VERSION = A; 495 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 496 | GCC_PREFIX_HEADER = "AppSandboxFileAccess/AppSandboxFileAccess-Prefix.pch"; 497 | INFOPLIST_FILE = "AppSandboxFileAccess/AppSandboxFileAccess-Info.plist"; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | WRAPPER_EXTENSION = framework; 500 | }; 501 | name = Debug; 502 | }; 503 | 201DCD571840BDE600B43456 /* Release */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | CLANG_ENABLE_MODULES = YES; 507 | COMBINE_HIDPI_IMAGES = YES; 508 | DYLIB_COMPATIBILITY_VERSION = 1; 509 | DYLIB_CURRENT_VERSION = 1; 510 | FRAMEWORK_VERSION = A; 511 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 512 | GCC_PREFIX_HEADER = "AppSandboxFileAccess/AppSandboxFileAccess-Prefix.pch"; 513 | INFOPLIST_FILE = "AppSandboxFileAccess/AppSandboxFileAccess-Info.plist"; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | WRAPPER_EXTENSION = framework; 516 | }; 517 | name = Release; 518 | }; 519 | C212FEDE1A7E8B6700BE3532 /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | CLANG_ENABLE_MODULES = YES; 523 | CLANG_WARN_UNREACHABLE_CODE = YES; 524 | COMBINE_HIDPI_IMAGES = YES; 525 | ENABLE_STRICT_OBJC_MSGSEND = YES; 526 | FRAMEWORK_SEARCH_PATHS = ( 527 | "$(DEVELOPER_FRAMEWORKS_DIR)", 528 | "$(inherited)", 529 | ); 530 | GCC_PREPROCESSOR_DEFINITIONS = ( 531 | "DEBUG=1", 532 | "$(inherited)", 533 | ); 534 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 535 | INFOPLIST_FILE = AppSandboxFileAccessTests/Info.plist; 536 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 537 | MACOSX_DEPLOYMENT_TARGET = 10.10; 538 | MTL_ENABLE_DEBUG_INFO = YES; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | }; 541 | name = Debug; 542 | }; 543 | C212FEDF1A7E8B6700BE3532 /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | CLANG_ENABLE_MODULES = YES; 547 | CLANG_WARN_UNREACHABLE_CODE = YES; 548 | COMBINE_HIDPI_IMAGES = YES; 549 | ENABLE_STRICT_OBJC_MSGSEND = YES; 550 | FRAMEWORK_SEARCH_PATHS = ( 551 | "$(DEVELOPER_FRAMEWORKS_DIR)", 552 | "$(inherited)", 553 | ); 554 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 555 | INFOPLIST_FILE = AppSandboxFileAccessTests/Info.plist; 556 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 557 | MACOSX_DEPLOYMENT_TARGET = 10.10; 558 | MTL_ENABLE_DEBUG_INFO = NO; 559 | PRODUCT_NAME = "$(TARGET_NAME)"; 560 | }; 561 | name = Release; 562 | }; 563 | C212FEFF1A7E944E00BE3532 /* Debug */ = { 564 | isa = XCBuildConfiguration; 565 | buildSettings = { 566 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 567 | CLANG_ENABLE_MODULES = YES; 568 | CLANG_WARN_UNREACHABLE_CODE = YES; 569 | CODE_SIGN_ENTITLEMENTS = AppSandboxFileAccessDemo/AppSandboxFileAccessDemo.entitlements; 570 | CODE_SIGN_IDENTITY = "-"; 571 | COMBINE_HIDPI_IMAGES = YES; 572 | ENABLE_STRICT_OBJC_MSGSEND = YES; 573 | GCC_PREPROCESSOR_DEFINITIONS = ( 574 | "DEBUG=1", 575 | "$(inherited)", 576 | ); 577 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 578 | INFOPLIST_FILE = AppSandboxFileAccessDemo/Info.plist; 579 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 580 | MACOSX_DEPLOYMENT_TARGET = 10.10; 581 | MTL_ENABLE_DEBUG_INFO = YES; 582 | PRODUCT_NAME = "$(TARGET_NAME)"; 583 | }; 584 | name = Debug; 585 | }; 586 | C212FF001A7E944E00BE3532 /* Release */ = { 587 | isa = XCBuildConfiguration; 588 | buildSettings = { 589 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 590 | CLANG_ENABLE_MODULES = YES; 591 | CLANG_WARN_UNREACHABLE_CODE = YES; 592 | CODE_SIGN_ENTITLEMENTS = AppSandboxFileAccessDemo/AppSandboxFileAccessDemo.entitlements; 593 | CODE_SIGN_IDENTITY = "-"; 594 | COMBINE_HIDPI_IMAGES = YES; 595 | ENABLE_STRICT_OBJC_MSGSEND = YES; 596 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 597 | INFOPLIST_FILE = AppSandboxFileAccessDemo/Info.plist; 598 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 599 | MACOSX_DEPLOYMENT_TARGET = 10.10; 600 | MTL_ENABLE_DEBUG_INFO = NO; 601 | PRODUCT_NAME = "$(TARGET_NAME)"; 602 | }; 603 | name = Release; 604 | }; 605 | /* End XCBuildConfiguration section */ 606 | 607 | /* Begin XCConfigurationList section */ 608 | 201DCD271840BDE600B43456 /* Build configuration list for PBXProject "AppSandboxFileAccess" */ = { 609 | isa = XCConfigurationList; 610 | buildConfigurations = ( 611 | 201DCD531840BDE600B43456 /* Debug */, 612 | 201DCD541840BDE600B43456 /* Release */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | 201DCD551840BDE600B43456 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccess" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | 201DCD561840BDE600B43456 /* Debug */, 621 | 201DCD571840BDE600B43456 /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | C212FEDD1A7E8B6700BE3532 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccessTests" */ = { 627 | isa = XCConfigurationList; 628 | buildConfigurations = ( 629 | C212FEDE1A7E8B6700BE3532 /* Debug */, 630 | C212FEDF1A7E8B6700BE3532 /* Release */, 631 | ); 632 | defaultConfigurationIsVisible = 0; 633 | defaultConfigurationName = Release; 634 | }; 635 | C212FEFE1A7E944E00BE3532 /* Build configuration list for PBXNativeTarget "AppSandboxFileAccessDemo" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | C212FEFF1A7E944E00BE3532 /* Debug */, 639 | C212FF001A7E944E00BE3532 /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | /* End XCConfigurationList section */ 645 | }; 646 | rootObject = 201DCD241840BDE600B43456 /* Project object */; 647 | } 648 | -------------------------------------------------------------------------------- /AppSandboxFileAccess.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/AppSandboxFileAccess-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.mcchouse.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleShortVersionString 20 | 1.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | NSHumanReadableCopyright 26 | Copyright © 2013 Leigh McCulloch. All rights reserved. 27 | NSPrincipalClass 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/AppSandboxFileAccess-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccess.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccess.h 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | #import 37 | @import AppKit; 38 | 39 | #pragma mark - 40 | #pragma mark AppSandboxFileAccessProtocol 41 | 42 | @protocol AppSandboxFileAccessProtocol 43 | 44 | @required 45 | - (NSData *)bookmarkDataForURL:(NSURL *)url; 46 | - (void)setBookmarkData:(NSData *)data forURL:(NSURL *)url; 47 | - (void)clearBookmarkDataForURL:(NSURL *)url; 48 | 49 | @end 50 | 51 | #pragma mark - 52 | #pragma mark AppSandboxFileAccess 53 | 54 | typedef void (^AppSandboxFileAccessBlock)(void); 55 | typedef void (^AppSandboxFileSecurityScopeBlock)(NSURL *securityScopedFileURL, NSData *bookmarkData); 56 | 57 | @interface AppSandboxFileAccess : NSObject 58 | 59 | /*! @brief The title of the NSOpenPanel displayed when asking permission to access a file. 60 | Default: "Allow Access" 61 | */ 62 | @property (readwrite, copy, nonatomic) NSString *title; 63 | /*! @brief The message contained on the the NSOpenPanel displayed when asking permission to access a file. 64 | Default: "[Application Name] needs to access this path to continue. Click Allow to continue." 65 | */ 66 | @property (readwrite, copy, nonatomic) NSString *message; 67 | /*! @brief The prompt button on the the NSOpenPanel displayed when asking permission to access a file. 68 | Default: "Allow" 69 | */ 70 | @property (readwrite, copy, nonatomic) NSString *prompt; 71 | 72 | /*! @brief This is an optional delegate object that can be provided to customize the persistance of bookmark data (e.g. in a Core Data database). 73 | Default: nil (Default uses the AppSandboxFileAccessPersist class.) 74 | */ 75 | @property (nonatomic, weak) id bookmarkPersistanceDelegate; 76 | 77 | /*! @brief Create the object with the default values. */ 78 | + (AppSandboxFileAccess *)fileAccess; 79 | 80 | /*! @brief Initialise the object with the default values. */ 81 | - (instancetype)init; 82 | 83 | /*! @brief Access a file path to read or write, automatically gaining permission from the user with NSOpenPanel if required 84 | and using persisted permissions if possible. 85 | 86 | @see accessFile:persistPermission:withBlock: 87 | @see securityScopedURLForFilePath:persistPermission:bookmark: 88 | 89 | @param path A file path, either a file or folder, that the caller needs access to. 90 | @param persist If YES will save the permission for future calls. 91 | @param block The block that will be given access to the file or folder. 92 | @return YES if permission was granted or already available, NO otherwise. 93 | */ 94 | - (BOOL)accessFilePath:(NSString *)path persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block; 95 | 96 | /*! 97 | @warning Deprecated. 98 | 99 | @see accessFilePath:persistPermission:withBlock: 100 | 101 | @param path A file path, either a file or folder, that the caller needs access to. 102 | @param block The block that will be given access to the file or folder. 103 | @param persist If YES will save the permission for future calls. 104 | @return YES if permission was granted or already available, NO otherwise. 105 | */ 106 | - (BOOL)accessFilePath:(NSString *)path withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist __attribute__((deprecated("Use 'accessFilePath:persistPermission:withBlock:' instead."))); 107 | 108 | /*! @brief Access a file URL to read or write, automatically gaining permission from the user with NSOpenPanel if required 109 | and using persisted permissions if possible. 110 | 111 | @see requestAccessPermissionsForFileURL:persistPermission:withBlock: 112 | 113 | @discussion Internally calls `requestAccessPermissionsForFileURL:persistPermission:withBlock:` and accesses the returned scoped URL if successful. 114 | 115 | @discussion See `requestAccessPermissionsForFileURL:persistPermission:withBlock:` for detailed behaviour. 116 | 117 | @param fileURL A file URL, either a file or folder, that the caller needs access to. 118 | @param persist If YES will save the permission for future calls. 119 | @param block The block that will be given access to the file or folder. 120 | @return YES if permission was granted or already available, NO otherwise. 121 | */ 122 | - (BOOL)accessFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block; 123 | 124 | /*! 125 | @warning Deprecated. 126 | 127 | @see accessFileURL:persistPermission:withBlock: 128 | 129 | @param fileURL A file URL, either a file or folder, that the caller needs access to. 130 | @param persist If YES will save the permission for future calls. 131 | @param block The block that will be given access to the file or folder. 132 | @return YES if permission was granted or already available, NO otherwise. 133 | */ 134 | - (BOOL)accessFileURL:(NSURL *)fileURL withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist __attribute__((deprecated("Use 'accessFileURL:persistPermission:withBlock:' instead."))); 135 | 136 | /*! @brief Request access permission for a file path to read or write, automatically with NSOpenPanel if required 137 | and using persisted permissions if possible. 138 | 139 | @see securityScopedURLForFilePath:persistPermission:bookmark: 140 | 141 | @param path A file path, either a file or folder, that the caller needs access to. 142 | @param persist If YES will save the permission for future calls. 143 | @return YES if permission was granted or already available, NO otherwise. 144 | */ 145 | - (BOOL)requestAccessPermissionsForFilePath:(NSString *)filePath persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block; 146 | 147 | /*! @brief Request access permission for a file path to read or write, automatically with NSOpenPanel if required 148 | and using persisted permissions if possible. 149 | 150 | @discussion Use this function to access a file URL to either read or write in an application restricted by the App Sandbox. 151 | This function will ask the user for permission if necessary using a well formed NSOpenPanel. The user will 152 | have the option of approving access to the URL you specify, or a parent path for that URL. If persist is YES 153 | the permission will be stored as a bookmark in NSUserDefaults and further calls to this function will 154 | load the saved permission and not ask for permission again. 155 | 156 | @discussion If the file URL does not exist, it's parent directory will be asked for permission instead, since permission 157 | to the directory will be required to write the file. If the parent directory doesn't exist, it will ask for 158 | permission of whatever part of the parent path exists. 159 | 160 | @discussion Note: If the caller has permission to access a file because it was dropped onto the application or introduced 161 | to the application in some other way, this function will not be aware of that permission and still prompt 162 | the user. To prevent this, use the persistPermission function to persist a permission you've been given 163 | whenever a user introduces a file to the application. E.g. when dropping a file onto the application window 164 | or dock or when using an NSOpenPanel. 165 | 166 | @param fileURL A file URL, either a file or folder, that the caller needs access to. 167 | @param persist If YES will save the permission for future calls. 168 | @param block The block that will be given access to the file or folder. 169 | @return YES if permission was granted or already available, NO otherwise. 170 | */ 171 | - (BOOL)requestAccessPermissionsForFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block; 172 | 173 | /*! @brief Persist a security bookmark for the given path. The calling application must already have permission. 174 | 175 | @see persistPermissionURL: 176 | 177 | @param path The path with permission that will be persisted. 178 | @return Bookmark data if permission was granted or already available, nil otherwise. 179 | */ 180 | - (NSData *)persistPermissionPath:(NSString *)path; 181 | 182 | /*! @brief Persist a security bookmark for the given URL. The calling application must already have permission. 183 | 184 | @discussion Use this function to persist permission of a URL that has already been granted when a user introduced 185 | a file to the calling application. E.g. by dropping the file onto the application window, or dock icon, 186 | or when using an NSOpenPanel. 187 | 188 | Note: If the calling application does not have access to this file, this call will do nothing. 189 | 190 | @param url The URL with permission that will be persisted. 191 | @return Bookmark data if permission was granted or already available, nil otherwise. 192 | */ 193 | - (NSData *)persistPermissionURL:(NSURL *)url; 194 | 195 | @end 196 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccess.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccess.m 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | #import "AppSandboxFileAccess.h" 37 | #import "AppSandboxFileAccessPersist.h" 38 | #import "AppSandboxFileAccessOpenSavePanelDelegate.h" 39 | 40 | #if !__has_feature(objc_arc) 41 | #error ARC must be enabled! 42 | #endif 43 | 44 | #define CFBundleDisplayName @"CFBundleDisplayName" 45 | #define CFBundleName @"CFBundleName" 46 | 47 | @interface AppSandboxFileAccess () 48 | @property (nonatomic, strong) AppSandboxFileAccessPersist *defaultDelegate; 49 | @end 50 | 51 | @implementation AppSandboxFileAccess 52 | 53 | + (AppSandboxFileAccess *)fileAccess { 54 | return [[AppSandboxFileAccess alloc] init]; 55 | } 56 | 57 | - (instancetype)init { 58 | self = [super init]; 59 | if (self) { 60 | NSString *applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:CFBundleDisplayName]; 61 | if (!applicationName) { 62 | applicationName = [[NSBundle mainBundle] objectForInfoDictionaryKey:CFBundleName]; 63 | } 64 | 65 | self.title = NSLocalizedString(@"Allow Access", @"Sandbox Access panel title."); 66 | NSString *formatString = NSLocalizedString(@"%@ needs to access this path to continue. Click Allow to continue.", @"Sandbox Access panel message."); 67 | self.message = [NSString stringWithFormat:formatString, applicationName]; 68 | self.prompt = NSLocalizedString(@"Allow", @"Sandbox Access panel prompt."); 69 | 70 | // create default delegate object that persists bookmarks to user defaults 71 | self.defaultDelegate = [[AppSandboxFileAccessPersist alloc] init]; 72 | self.bookmarkPersistanceDelegate = _defaultDelegate; 73 | } 74 | return self; 75 | } 76 | 77 | - (NSURL *)askPermissionForURL:(NSURL *)url { 78 | NSParameterAssert(url); 79 | 80 | // this url will be the url allowed, it might be a parent url of the url passed in 81 | __block NSURL *allowedURL = nil; 82 | 83 | // create delegate that will limit which files in the open panel can be selected, to ensure only a folder 84 | // or file giving permission to the file requested can be selected 85 | AppSandboxFileAccessOpenSavePanelDelegate *openPanelDelegate = [[AppSandboxFileAccessOpenSavePanelDelegate alloc] initWithFileURL:url]; 86 | 87 | // check that the url exists, if it doesn't, find the parent path of the url that does exist and ask permission for that 88 | NSFileManager *fileManager = [NSFileManager defaultManager]; 89 | NSString *path = [url path]; 90 | while (path.length > 1) { // give up when only '/' is left in the path or if we get to a path that exists 91 | if ([fileManager fileExistsAtPath:path isDirectory:NULL]) { 92 | break; 93 | } 94 | path = [path stringByDeletingLastPathComponent]; 95 | } 96 | url = [NSURL fileURLWithPath:path]; 97 | 98 | // display the open panel 99 | dispatch_block_t displayOpenPanelBlock = ^{ 100 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 101 | [openPanel setMessage:self.message]; 102 | [openPanel setCanCreateDirectories:NO]; 103 | [openPanel setCanChooseFiles:YES]; 104 | [openPanel setCanChooseDirectories:YES]; 105 | [openPanel setAllowsMultipleSelection:NO]; 106 | [openPanel setPrompt:self.prompt]; 107 | [openPanel setTitle:self.title]; 108 | [openPanel setShowsHiddenFiles:NO]; 109 | [openPanel setExtensionHidden:NO]; 110 | [openPanel setDirectoryURL:url]; 111 | [openPanel setDelegate:openPanelDelegate]; 112 | [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; 113 | NSInteger openPanelButtonPressed = [openPanel runModal]; 114 | if (openPanelButtonPressed == NSFileHandlingPanelOKButton) { 115 | allowedURL = [openPanel URL]; 116 | } 117 | }; 118 | if ([NSThread isMainThread]) { 119 | displayOpenPanelBlock(); 120 | } else { 121 | dispatch_sync(dispatch_get_main_queue(), displayOpenPanelBlock); 122 | } 123 | 124 | return allowedURL; 125 | } 126 | 127 | - (NSData *)persistPermissionPath:(NSString *)path { 128 | NSParameterAssert(path); 129 | 130 | return [self persistPermissionURL:[NSURL fileURLWithPath:path]]; 131 | } 132 | 133 | - (NSData *)persistPermissionURL:(NSURL *)url { 134 | NSParameterAssert(url); 135 | 136 | // store the sandbox permissions 137 | NSData *bookmarkData = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:NULL]; 138 | if (bookmarkData) { 139 | [self.bookmarkPersistanceDelegate setBookmarkData:bookmarkData forURL:url]; 140 | } 141 | return bookmarkData; 142 | } 143 | 144 | - (BOOL)accessFilePath:(NSString *)path withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist { 145 | // Deprecated. Use 'accessFilePath:persistPermission:withBlock:' instead. 146 | return [self accessFilePath:path persistPermission:persist withBlock:block]; 147 | } 148 | 149 | - (BOOL)accessFileURL:(NSURL *)fileURL withBlock:(AppSandboxFileAccessBlock)block persistPermission:(BOOL)persist { 150 | // Deprecated. Use 'accessFileURL:persistPermission:withBlock:' instead. 151 | return [self accessFileURL:fileURL persistPermission:persist withBlock:block]; 152 | } 153 | 154 | - (BOOL)accessFilePath:(NSString *)path persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block { 155 | return [self accessFileURL:[NSURL fileURLWithPath:path] persistPermission:persist withBlock:block]; 156 | } 157 | 158 | - (BOOL)accessFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileAccessBlock)block { 159 | NSParameterAssert(fileURL); 160 | NSParameterAssert(block); 161 | 162 | BOOL success = [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:^(NSURL *securityScopedFileURL, NSData *bookmarkData) { 163 | // execute the block with the file access permissions 164 | @try { 165 | [securityScopedFileURL startAccessingSecurityScopedResource]; 166 | block(); 167 | } @finally { 168 | [securityScopedFileURL stopAccessingSecurityScopedResource]; 169 | } 170 | }]; 171 | 172 | return success; 173 | } 174 | 175 | - (BOOL)requestAccessPermissionsForFilePath:(NSString *)filePath persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block { 176 | NSParameterAssert(filePath); 177 | 178 | NSURL *fileURL = [NSURL fileURLWithPath:filePath]; 179 | return [self requestAccessPermissionsForFileURL:fileURL persistPermission:persist withBlock:block]; 180 | } 181 | 182 | - (BOOL)requestAccessPermissionsForFileURL:(NSURL *)fileURL persistPermission:(BOOL)persist withBlock:(AppSandboxFileSecurityScopeBlock)block { 183 | NSParameterAssert(fileURL); 184 | 185 | NSURL *allowedURL = nil; 186 | 187 | // 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 188 | fileURL = [[fileURL URLByStandardizingPath] URLByResolvingSymlinksInPath]; 189 | 190 | // lookup bookmark data for this url, this will automatically load bookmark data for a parent path if we have it 191 | NSData *bookmarkData = [self.bookmarkPersistanceDelegate bookmarkDataForURL:fileURL]; 192 | if (bookmarkData) { 193 | // resolve the bookmark data into an NSURL object that will allow us to use the file 194 | BOOL bookmarkDataIsStale; 195 | allowedURL = [NSURL URLByResolvingBookmarkData:bookmarkData options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI relativeToURL:nil bookmarkDataIsStale:&bookmarkDataIsStale error:NULL]; 196 | // if the bookmark data is stale we'll attempt to recreate it with the existing url object if possible (not guaranteed) 197 | if (bookmarkDataIsStale) { 198 | bookmarkData = nil; 199 | [self.bookmarkPersistanceDelegate clearBookmarkDataForURL:fileURL]; 200 | if (allowedURL) { 201 | bookmarkData = [self persistPermissionURL:allowedURL]; 202 | if (!bookmarkData) { 203 | allowedURL = nil; 204 | } 205 | } 206 | } 207 | } 208 | 209 | // if allowed url is nil, we need to ask the user for permission 210 | if (!allowedURL) { 211 | allowedURL = [self askPermissionForURL:fileURL]; 212 | if (!allowedURL) { 213 | // if the user did not give permission, exit out here 214 | return NO; 215 | } 216 | } 217 | 218 | // if we have no bookmark data and we want to persist, we need to create it 219 | if (persist && !bookmarkData) { 220 | bookmarkData = [self persistPermissionURL:allowedURL]; 221 | } 222 | 223 | if (block) { 224 | block(allowedURL, bookmarkData); 225 | } 226 | 227 | return YES; 228 | } 229 | 230 | - (void)setBookmarkPersistanceDelegate:(NSObject *)bookmarkPersistanceDelegate 231 | { 232 | // revert to default delegate object if no delegate provided 233 | if (bookmarkPersistanceDelegate == nil) { 234 | _bookmarkPersistanceDelegate = self.defaultDelegate; 235 | } else { 236 | _bookmarkPersistanceDelegate = bookmarkPersistanceDelegate; 237 | } 238 | } 239 | 240 | @end 241 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccessOpenSavePanelDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccessOpenSavePanelDelegate.h 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | 37 | #import 38 | @import AppKit; 39 | 40 | @interface AppSandboxFileAccessOpenSavePanelDelegate : NSObject 41 | 42 | - (instancetype)initWithFileURL:(NSURL *)fileURL; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccessOpenSavePanelDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccessOpenSavePanelDelegate.m 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | 37 | #import "AppSandboxFileAccessOpenSavePanelDelegate.h" 38 | 39 | #if !__has_feature(objc_arc) 40 | #error ARC must be enabled! 41 | #endif 42 | 43 | @interface AppSandboxFileAccessOpenSavePanelDelegate () 44 | 45 | @property (readwrite, strong, nonatomic) NSArray *pathComponents; 46 | 47 | @end 48 | 49 | @implementation AppSandboxFileAccessOpenSavePanelDelegate 50 | 51 | - (instancetype)initWithFileURL:(NSURL *)fileURL { 52 | self = [super init]; 53 | if (self) { 54 | NSParameterAssert(fileURL); 55 | self.pathComponents = fileURL.pathComponents; 56 | } 57 | return self; 58 | } 59 | 60 | #pragma mark -- NSOpenSavePanelDelegate 61 | 62 | - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { 63 | NSParameterAssert(url); 64 | 65 | NSArray *pathComponents = self.pathComponents; 66 | NSArray *otherPathComponents = url.pathComponents; 67 | 68 | // if the url passed in has more components, it could not be a parent path or a exact same path 69 | if (otherPathComponents.count > pathComponents.count) { 70 | return NO; 71 | } 72 | 73 | // check that each path component in url, is the same as each corresponding component in self.url 74 | for (NSUInteger i = 0; i < otherPathComponents.count; ++i) { 75 | NSString *comp1 = otherPathComponents[i]; 76 | NSString *comp2 = pathComponents[i]; 77 | // not the same, therefore url is not a parent or exact match to self.url 78 | if (![comp1 isEqualToString:comp2]) { 79 | return NO; 80 | } 81 | } 82 | 83 | // there were no mismatches (or no components meaning url is root) 84 | return YES; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccessPersist.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccessPersist.h 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | #import 37 | #import "AppSandboxFileAccess.h" 38 | 39 | @interface AppSandboxFileAccessPersist : NSObject 40 | 41 | - (NSData *)bookmarkDataForURL:(NSURL *)url; 42 | - (void)setBookmarkData:(NSData *)data forURL:(NSURL *)url; 43 | - (void)clearBookmarkDataForURL:(NSURL *)url; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /AppSandboxFileAccess/Classes/AppSandboxFileAccessPersist.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccessPersist.m 3 | // AppSandboxFileAccess 4 | // 5 | // Created by Leigh McCulloch on 23/11/2013. 6 | // 7 | // Copyright (c) 2013, Leigh McCulloch 8 | // All rights reserved. 9 | // 10 | // BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 11 | // 12 | // Redistribution and use in source and binary forms, with or without 13 | // modification, are permitted provided that the following conditions are 14 | // met: 15 | // 16 | // 1. Redistributions of source code must retain the above copyright 17 | // notice, this list of conditions and the following disclaimer. 18 | // 19 | // 2. Redistributions in binary form must reproduce the above copyright 20 | // notice, this list of conditions and the following disclaimer in the 21 | // documentation and/or other materials provided with the distribution. 22 | // 23 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 | // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 25 | // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 26 | // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | // 35 | 36 | #import "AppSandboxFileAccessPersist.h" 37 | 38 | #if !__has_feature(objc_arc) 39 | #error ARC must be enabled! 40 | #endif 41 | 42 | @implementation AppSandboxFileAccessPersist 43 | 44 | + (NSString *)keyForBookmarkDataForURL:(NSURL *)url { 45 | NSString *urlStr = [url absoluteString]; 46 | return [NSString stringWithFormat:@"bd_%1$@", urlStr]; 47 | } 48 | 49 | - (NSData *)bookmarkDataForURL:(NSURL *)url { 50 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 51 | 52 | // loop through the bookmarks one path at a time down the URL 53 | NSURL *subURL = url; 54 | while ([subURL path].length > 1) { // give up when only '/' is left in the path 55 | NSString *key = [AppSandboxFileAccessPersist keyForBookmarkDataForURL:subURL]; 56 | NSData *bookmark = [defaults dataForKey:key]; 57 | if (bookmark) { // if a bookmark is found, return it 58 | return bookmark; 59 | } 60 | subURL = [subURL URLByDeletingLastPathComponent]; 61 | } 62 | 63 | // no bookmarks for the URL, or parent to the URL were found 64 | return nil; 65 | } 66 | 67 | - (void)setBookmarkData:(NSData *)data forURL:(NSURL *)url { 68 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 69 | NSString *key = [AppSandboxFileAccessPersist keyForBookmarkDataForURL:url]; 70 | [defaults setObject:data forKey:key]; 71 | } 72 | 73 | - (void)clearBookmarkDataForURL:(NSURL *)url { 74 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 75 | NSString *key = [AppSandboxFileAccessPersist keyForBookmarkDataForURL:url]; 76 | [defaults removeObjectForKey:key]; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // AppSandboxFileAccessDemo 4 | // 5 | // Created by Definite Loop on 01/02/15. 6 | // Copyright (c) 2015 Leigh McCulloch. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // AppSandboxFileAccessDemo 4 | // 5 | // Created by Definite Loop on 01/02/15. 6 | // Copyright (c) 2015 Leigh McCulloch. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import 12 | 13 | @interface AppDelegate () 14 | 15 | @property (weak) IBOutlet NSWindow *window; 16 | 17 | @end 18 | 19 | @implementation AppDelegate 20 | 21 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 22 | 23 | // initialise the file access class 24 | AppSandboxFileAccess *fileAccess = [AppSandboxFileAccess fileAccess]; 25 | 26 | // the application was provided this file when the user dragged this file on to the app 27 | NSString *file = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) firstObject]; 28 | 29 | BOOL isDirectory = NO; 30 | BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:&isDirectory]; 31 | NSAssert(fileExists, @"File not found!"); 32 | 33 | // persist permission to access the file the user introduced to the app, so we can always 34 | // access it and then the AppSandboxFileAccess class won't prompt for it if you wrap access to it 35 | [fileAccess persistPermissionPath:file]; 36 | 37 | // get the parent directory for the file 38 | NSString *directory = (isDirectory) ? file : [file stringByDeletingLastPathComponent]; 39 | 40 | // get access to the parent directory 41 | BOOL accessAllowed = [fileAccess accessFilePath:directory persistPermission:YES withBlock:^{ 42 | NSAlert *alert = [[NSAlert alloc] init]; 43 | alert.informativeText = @"Touching file now."; 44 | alert.messageText = @"Access Granted"; 45 | [alert addButtonWithTitle:@"OK"]; 46 | [alert runModal]; 47 | 48 | NSFileManager *fileManager = [NSFileManager defaultManager]; 49 | NSError *error = nil; 50 | if (![fileManager setAttributes:@{NSFileModificationDate: [NSDate date]} ofItemAtPath:file error:&error]) { 51 | NSLog(@"Error: %@", error); 52 | } 53 | }]; 54 | 55 | if (!accessAllowed) { 56 | NSLog(@"Sad Wookie"); 57 | } 58 | } 59 | 60 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 61 | // Insert code here to tear down your application 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/AppSandboxFileAccessDemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | Default 560 | 561 | 562 | 563 | 564 | 565 | 566 | Left to Right 567 | 568 | 569 | 570 | 571 | 572 | 573 | Right to Left 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.mcchouse.$(PRODUCT_NAME:rfc1034identifier) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2015 Leigh McCulloch. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /AppSandboxFileAccessDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AppSandboxFileAccessDemo 4 | // 5 | // Created by Definite Loop on 01/02/15. 6 | // Copyright (c) 2015 Leigh McCulloch. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /AppSandboxFileAccessTests/AppSandboxFileAccessOpenSavePanelDelegateTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppSandboxFileAccessOpenSavePanelDelegateTests.m 3 | // AppSandboxFileAccessTests 4 | // 5 | // Created by Vincent Esche on 01/02/15. 6 | // Copyright (c) 2015 Vincent Esche. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "AppSandboxFileAccessOpenSavePanelDelegate.h" 13 | 14 | @interface AppSandboxFileAccessOpenSavePanelDelegateTests : XCTestCase 15 | 16 | @property (readwrite, strong, nonatomic) AppSandboxFileAccessOpenSavePanelDelegate *delegate; 17 | 18 | @end 19 | 20 | @implementation AppSandboxFileAccessOpenSavePanelDelegateTests 21 | 22 | - (void)setUp { 23 | [super setUp]; 24 | 25 | NSURL *fileURL = [NSURL fileURLWithPath:@"/a/b/c"]; 26 | self.delegate = [[AppSandboxFileAccessOpenSavePanelDelegate alloc] initWithFileURL:fileURL]; 27 | } 28 | 29 | - (void)test__panel_shouldEnableURL__withSameURLs { 30 | NSURL *fileURL = [NSURL fileURLWithPath:@"/a/b/c"]; 31 | BOOL enabled = [self.delegate panel:nil shouldEnableURL:fileURL]; 32 | XCTAssertTrue(enabled, @"Should enable URL if same."); 33 | } 34 | 35 | - (void)test__panel_shouldEnableURL__withLongerURL { 36 | NSURL *fileURL = [NSURL fileURLWithPath:@"/a/b/c/d"]; 37 | BOOL enabled = [self.delegate panel:nil shouldEnableURL:fileURL]; 38 | XCTAssertFalse(enabled, @"Should not enable URL if longer."); 39 | } 40 | 41 | - (void)test__panel_shouldEnableURL__withShorterURL { 42 | NSURL *fileURL = [NSURL fileURLWithPath:@"/a/b/"]; 43 | BOOL enabled = [self.delegate panel:nil shouldEnableURL:fileURL]; 44 | XCTAssertTrue(enabled, @"Should enable URL if shorter."); 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /AppSandboxFileAccessTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.mcchouse.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Leigh McCulloch All rights reserved. 2 | 3 | BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 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. 10 | 11 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AppSandboxFileAccess 2 | ==================== 3 | 4 | [![](https://img.shields.io/badge/swift%20fork-ConfusedVorlon%2FSwiftySandboxFileAccess-brightgreen)](https://github.com/ConfusedVorlon/SwiftySandboxFileAccess) 5 | 6 | A simple class that wraps up writing and accessing files outside a Mac apps App Sandbox files. The class will request permission from the user with a simple to understand dialog consistent with Apple's documentation and persist permissions across application runs using security bookmarks. 7 | 8 | This is specifically useful for when you need to write files, or gain access to directories that are not already accessible to your application. For example if your application is introduced to file AwesomeRecipe.txt and wishes to generate AwesomeRecipe.txt.gz, this is not possible without gaining permission from the user. (Note: It is possible to write AwesomeRecipe.gz, you don't need this class to do that.) 9 | 10 | When using this class, if the user needs to give permission to access the folder, the NSOpenPanel is used to request permission. Only the path or file requiring permission, or parent paths are selectable in the NSOpenPanel. The panel text, title and button are customisable. 11 | ![](screenshot-1.png) 12 | 13 | Uses in the Real World 14 | ==================== 15 | 16 | http://minifyapp.com – Minify uses this code to write combined, minified and compressed files to the same directory as the original. E.g. styles.css is minified into styles.min.css, then compressed to styles.min.css.gz. 17 | 18 | How to Use 19 | ==================== 20 | 21 | Include the source .h and .m files into your own project. If you'd like to keep up-to-date with the latest updates, add this project as a submodule to your application and then include the .h and .m files into your own project. 22 | 23 | ![](screenshot-3.png) 24 | 25 | In Xcode click on your project file, then the Capabilities tab. Turn on App Sandbox and change 'User Selected File' to 'Read/Write' or 'Read Only', whichever you need. In your project Xcode will have created a .entitlements file. Open this and you should see the below. If you plan on persisting permissions you'll need to add the third entitlement. 26 | 27 | ![](screenshot-2.png) 28 | 29 | In your application, whenever you need to read or write a file, wrap the code accessing the file wrap like the following. The following example will get permission to access the parent directory of a file the application already knows about. 30 | 31 | ``` 32 | #import "AppSandboxFileAccess.h" 33 | 34 | ... 35 | 36 | // initialise the file access class 37 | AppSandboxFileAccess *fileAccess = [AppSandboxFileAccess fileAccess]; 38 | 39 | // the application was provided this file when the user dragged this file on to the app 40 | NSString *file = @"/Users/Wookie/AwesomeRecipe.txt"; 41 | 42 | // persist permission to access the file the user introduced to the app, so we can always 43 | // access it and then the AppSandboxFileAccess class won't prompt for it if you wrap access to it 44 | [fileAccess persistPermissionPath:file]; 45 | 46 | // get the parent directory for the file 47 | NSString *parentDirectory = [file stringByDeletingLastPathComponent]; 48 | 49 | // get access to the parent directory 50 | BOOL accessAllowed = [fileAccess accessFilePath:parentDirectory withBlock:^{ 51 | 52 | // write or read files in that directory 53 | // e.g. write AwesomeRecipe.txt.gz to the same directory as the txt file 54 | 55 | } persistPermission:YES]; 56 | 57 | if (!accessAllowed) { 58 | NSLog(@"Sad Wookie"); 59 | } 60 | 61 | ``` 62 | 63 | License 64 | ==================== 65 | 66 | Copyright (c) 2013, Leigh McCulloch 67 | All rights reserved. 68 | 69 | BSD-2-Clause License: http://opensource.org/licenses/BSD-2-Clause 70 | 71 | Redistribution and use in source and binary forms, with or without 72 | modification, are permitted provided that the following conditions are 73 | met: 74 | 75 | 1. Redistributions of source code must retain the above copyright 76 | notice, this list of conditions and the following disclaimer. 77 | 78 | 2. Redistributions in binary form must reproduce the above copyright 79 | notice, this list of conditions and the following disclaimer in the 80 | documentation and/or other materials provided with the distribution. 81 | 82 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 83 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 84 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 85 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 86 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 87 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 88 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 89 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 90 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 91 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 92 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 93 | -------------------------------------------------------------------------------- /screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leighmcculloch/AppSandboxFileAccess/e2bd2910f1846eea13cb2e00bc4945e96262c705/screenshot-1.png -------------------------------------------------------------------------------- /screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leighmcculloch/AppSandboxFileAccess/e2bd2910f1846eea13cb2e00bc4945e96262c705/screenshot-2.png -------------------------------------------------------------------------------- /screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leighmcculloch/AppSandboxFileAccess/e2bd2910f1846eea13cb2e00bc4945e96262c705/screenshot-3.png --------------------------------------------------------------------------------