Skip to content

Commit 32c2f08

Browse files
authored
MacOS sandboxing feature (#16090)
* Set isDirectory:true explicitly to help [NSURL fileURLWithPath] method Might solve some rare/random issues with initial directory not being applied * Fix dialogs page incorrectly setting parent folder * Move SecurityScopedStream out of iOS project and share it with macOS project * Refactor BclStorageItem to be more reusable across platforms * [Breaking] Set BclStorageItem.CanBookmark to false, as it never was supposed to be true. Plain BCL doesn't provide files bookmarking. * Reimplement storage provider support on macOS, support (optional) sandboxing * Fix build * Fix AppSandboxEnabled=false usage * Re-enable BCL bookmarks, keep them base64 * Fix nullable error * Prefix all bookmarks with a platform key * Fix devtools breaking sandboxed app * Try to read errors after saving bookmark * Don't crash sample app if has no access * Add internal IStorageItemWithFileSystemInfo abstraction * Log information if OpenSecurityScope returned false * Fix build * Prefix bookmarks with "ava.v1." * Support opening old-style bookmarks to avoid breaking changes
1 parent 34558f2 commit 32c2f08

34 files changed

+1370
-649
lines changed

native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
3535
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
3636
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37-
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37+
37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* StorageProvider.mm */; };
3838
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
3939
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
4040
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
@@ -95,7 +95,7 @@
9595
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
9696
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
9797
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
98-
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
98+
37C09D8721580FE4006A6758 /* StorageProvider.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageProvider.mm; sourceTree = "<group>"; };
9999
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
100100
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
101101
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
@@ -202,7 +202,7 @@
202202
523484CB26EA68AA00EA0C2C /* trayicon.h */,
203203
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
204204
37A517B22159597E00FBA241 /* Screens.mm */,
205-
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
205+
37C09D8721580FE4006A6758 /* StorageProvider.mm */,
206206
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
207207
AB7A61F02147C815003C5833 /* Products */,
208208
AB661C1C2148230E00291242 /* Frameworks */,
@@ -339,7 +339,7 @@
339339
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
340340
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
341341
AB00E4F72147CA920032A60A /* main.mm in Sources */,
342-
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
342+
37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */,
343343
1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */,
344344
F10084862BFF1FB40024303E /* TopLevelImpl.mm in Sources */,
345345
1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */,

native/Avalonia.Native/src/OSX/SystemDialogs.mm renamed to native/Avalonia.Native/src/OSX/StorageProvider.mm

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,91 @@ - (void)popupAction:(id)sender {
6464

6565
@end
6666

67-
class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
67+
class StorageProvider : public ComSingleObject<IAvnStorageProvider, &IID_IAvnStorageProvider>
6868
{
6969
ExtensionDropdownHandler* __strong _extension_dropdown_handler;
7070

7171
public:
7272
FORWARD_IUNKNOWN()
73+
74+
virtual HRESULT SaveBookmarkToBytes (
75+
IAvnString* fileUriStr,
76+
void** err,
77+
IAvnString** ppv
78+
) override
79+
{
80+
@autoreleasepool
81+
{
82+
if(ppv == nullptr)
83+
return E_POINTER;
84+
85+
NSError* error;
86+
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
87+
auto bookmarkData = [fileUri bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
88+
if (bookmarkData)
89+
{
90+
*ppv = CreateByteArray((void*)bookmarkData.bytes, (int)bookmarkData.length);
91+
}
92+
if (error != nil)
93+
{
94+
*err = CreateAvnString([error localizedDescription]);
95+
}
96+
return S_OK;
97+
}
98+
}
99+
100+
virtual HRESULT ReadBookmarkFromBytes (
101+
void* ptr,
102+
int len,
103+
IAvnString** ppv
104+
) override {
105+
@autoreleasepool
106+
{
107+
if(ppv == nullptr)
108+
return E_POINTER;
109+
110+
auto bookmarkData = [[NSData alloc] initWithBytes:ptr length:len];
111+
auto fileUri = [NSURL URLByResolvingBookmarkData: bookmarkData
112+
options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI
113+
relativeToURL:nil
114+
bookmarkDataIsStale:nil
115+
error:nil];
116+
117+
if (fileUri)
118+
{
119+
*ppv = CreateAvnString([fileUri absoluteString]);
120+
}
121+
return S_OK;
122+
}
123+
}
124+
125+
virtual void ReleaseBookmark (
126+
IAvnString* fileUriStr
127+
) override {
128+
// no-op
129+
}
130+
131+
virtual bool OpenSecurityScope (
132+
IAvnString* fileUriStr
133+
) override {
134+
@autoreleasepool
135+
{
136+
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
137+
auto success = [fileUri startAccessingSecurityScopedResource];
138+
return success;
139+
}
140+
}
141+
142+
virtual void CloseSecurityScope (
143+
IAvnString* fileUriStr
144+
) override {
145+
@autoreleasepool
146+
{
147+
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
148+
[fileUri stopAccessingSecurityScopedResource];
149+
}
150+
}
151+
73152
virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
74153
IAvnSystemDialogEvents* events,
75154
bool allowMultiple,
@@ -105,19 +184,9 @@ virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
105184

106185
if(urls.count > 0)
107186
{
108-
void* strings[urls.count];
109-
110-
for(int i = 0; i < urls.count; i++)
111-
{
112-
auto url = [urls objectAtIndex:i];
113-
114-
auto string = [url path];
115-
116-
strings[i] = (void*)[string UTF8String];
117-
}
118-
119-
events->OnCompleted((int)urls.count, &strings[0]);
120-
187+
auto uriStrings = CreateAvnStringArray(urls);
188+
events->OnCompleted(uriStrings);
189+
121190
[panel orderOut:panel];
122191

123192
if(parentWindowHandle != nullptr)
@@ -130,7 +199,7 @@ virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
130199
}
131200
}
132201

133-
events->OnCompleted(0, nullptr);
202+
events->OnCompleted(nullptr);
134203

135204
};
136205

@@ -188,19 +257,9 @@ virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
188257

189258
if(urls.count > 0)
190259
{
191-
void* strings[urls.count];
192-
193-
for(int i = 0; i < urls.count; i++)
194-
{
195-
auto url = [urls objectAtIndex:i];
196-
197-
auto string = [url path];
198-
199-
strings[i] = (void*)[string UTF8String];
200-
}
201-
202-
events->OnCompleted((int)urls.count, &strings[0]);
203-
260+
auto uriStrings = CreateAvnStringArray(urls);
261+
events->OnCompleted(uriStrings);
262+
204263
[panel orderOut:panel];
205264

206265
if(parentWindowHandle != nullptr)
@@ -213,7 +272,7 @@ virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
213272
}
214273
}
215274

216-
events->OnCompleted(0, nullptr);
275+
events->OnCompleted(nullptr);
217276

218277
};
219278

@@ -264,15 +323,11 @@ virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
264323
auto handler = ^(NSModalResponse result) {
265324
if(result == NSFileHandlingPanelOKButton)
266325
{
267-
void* strings[1];
268-
269326
auto url = [panel URL];
270-
271-
auto string = [url path];
272-
strings[0] = (void*)[string UTF8String];
273-
274-
events->OnCompleted(1, &strings[0]);
275-
327+
auto urls = [NSArray<NSURL*> arrayWithObject:url];
328+
auto uriStrings = CreateAvnStringArray(urls);
329+
events->OnCompleted(uriStrings);
330+
276331
[panel orderOut:panel];
277332

278333
if(parentWindowHandle != nullptr)
@@ -284,7 +339,7 @@ virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
284339
return;
285340
}
286341

287-
events->OnCompleted(0, nullptr);
342+
events->OnCompleted(nullptr);
288343

289344
};
290345

@@ -519,7 +574,7 @@ void SetAccessoryView(NSSavePanel* panel,
519574
};
520575
};
521576

522-
extern IAvnSystemDialogs* CreateSystemDialogs()
577+
extern IAvnStorageProvider* CreateStorageProvider()
523578
{
524-
return new SystemDialogs();
579+
return new StorageProvider();
525580
}

native/Avalonia.Native/src/OSX/common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extern void PostDispatcherCallback(IAvnActionCallback* cb);
1414
extern IAvnTopLevel* CreateAvnTopLevel(IAvnTopLevelEvents* events);
1515
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
1616
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
17-
extern IAvnSystemDialogs* CreateSystemDialogs();
17+
extern IAvnStorageProvider* CreateStorageProvider();
1818
extern IAvnScreens* CreateScreens(IAvnScreenEvents* cb);
1919
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
2020
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);

native/Avalonia.Native/src/OSX/main.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,13 @@ virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface*
276276
}
277277
}
278278

279-
virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override
279+
virtual HRESULT CreateStorageProvider(IAvnStorageProvider** ppv) override
280280
{
281281
START_COM_CALL;
282282

283283
@autoreleasepool
284284
{
285-
*ppv = ::CreateSystemDialogs();
285+
*ppv = ::CreateStorageProvider();
286286
return S_OK;
287287
}
288288
}

samples/ControlCatalog/Pages/DialogsPage.xaml.cs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -254,17 +254,24 @@ List<FileDialogFilter> GetFilters()
254254

255255
if (file is not null)
256256
{
257-
// Sync disposal of StreamWriter is not supported on WASM
257+
try
258+
{
259+
// Sync disposal of StreamWriter is not supported on WASM
258260
#if NET6_0_OR_GREATER
259-
await using var stream = await file.OpenWriteAsync();
260-
await using var writer = new System.IO.StreamWriter(stream);
261+
await using var stream = await file.OpenWriteAsync();
262+
await using var writer = new System.IO.StreamWriter(stream);
261263
#else
262-
using var stream = await file.OpenWriteAsync();
263-
using var writer = new System.IO.StreamWriter(stream);
264+
using var stream = await file.OpenWriteAsync();
265+
using var writer = new System.IO.StreamWriter(stream);
264266
#endif
265-
await writer.WriteLineAsync(openedFileContent.Text);
267+
await writer.WriteLineAsync(openedFileContent.Text);
266268

267-
SetFolder(await file.GetParentAsync());
269+
SetFolder(await file.GetParentAsync());
270+
}
271+
catch (Exception ex)
272+
{
273+
openedFileContent.Text = ex.ToString();
274+
}
268275
}
269276

270277
await SetPickerResult(file is null ? null : new[] { file });
@@ -280,8 +287,6 @@ List<FileDialogFilter> GetFilters()
280287
});
281288

282289
await SetPickerResult(folders);
283-
284-
SetFolder(folders.FirstOrDefault());
285290
};
286291
this.Get<Button>("OpenFileFromBookmark").Click += async delegate
287292
{
@@ -298,7 +303,6 @@ List<FileDialogFilter> GetFilters()
298303
: null;
299304

300305
await SetPickerResult(folder is null ? null : new[] { folder });
301-
SetFolder(folder);
302306
};
303307

304308
this.Get<Button>("LaunchUri").Click += async delegate
@@ -360,16 +364,30 @@ async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
360364
Content:
361365
";
362366

363-
resultText += await ReadTextFromFile(file, 500);
367+
try
368+
{
369+
resultText += await ReadTextFromFile(file, 500);
370+
}
371+
catch (Exception ex)
372+
{
373+
resultText += ex.ToString();
374+
}
364375
}
365376

366377
openedFileContent.Text = resultText;
367378

368-
var parent = await item.GetParentAsync();
369-
SetFolder(parent);
370-
if (parent is not null)
379+
if (item is IStorageFolder storageFolder)
371380
{
372-
mappedResults.Add(FullPathOrName(parent));
381+
SetFolder(storageFolder);
382+
}
383+
else
384+
{
385+
var parent = await item.GetParentAsync();
386+
SetFolder(parent);
387+
if (parent is not null)
388+
{
389+
mappedResults.Add(FullPathOrName(parent));
390+
}
373391
}
374392

375393
foreach (var selectedItem in items)
@@ -391,7 +409,7 @@ async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
391409
}
392410
}
393411

394-
public static async Task<string> ReadTextFromFile(IStorageFile file, int length)
412+
internal static async Task<string> ReadTextFromFile(IStorageFile file, int length)
395413
{
396414
#if NET6_0_OR_GREATER
397415
await using var stream = await file.OpenReadAsync();

src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Android.Webkit;
1111
using Avalonia.Logging;
1212
using Avalonia.Platform.Storage;
13+
using Avalonia.Platform.Storage.FileIO;
1314
using Java.Lang;
1415
using AndroidUri = Android.Net.Uri;
1516
using Exception = System.Exception;
@@ -53,7 +54,8 @@ protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExtern
5354
}
5455

5556
Activity.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
56-
return Uri.ToString();
57+
58+
return StorageBookmarkHelper.EncodeBookmark(AndroidStorageProvider.AndroidKey, Uri.ToString()!);
5759
}
5860

5961
public async Task ReleaseBookmarkAsync()

0 commit comments

Comments
 (0)