Skip to content

Commit

Permalink
MacOS sandboxing feature (#16090)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
maxkatz6 authored Jul 19, 2024
1 parent 34558f2 commit 32c2f08
Show file tree
Hide file tree
Showing 34 changed files with 1,370 additions and 649 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* StorageProvider.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
Expand Down Expand Up @@ -95,7 +95,7 @@
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = "<group>"; };
37C09D8721580FE4006A6758 /* StorageProvider.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StorageProvider.mm; sourceTree = "<group>"; };
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -202,7 +202,7 @@
523484CB26EA68AA00EA0C2C /* trayicon.h */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
37C09D8721580FE4006A6758 /* StorageProvider.mm */,
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
Expand Down Expand Up @@ -339,7 +339,7 @@
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
37C09D8821580FE4006A6758 /* StorageProvider.mm in Sources */,
1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */,
F10084862BFF1FB40024303E /* TopLevelImpl.mm in Sources */,
1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,91 @@ - (void)popupAction:(id)sender {

@end

class SystemDialogs : public ComSingleObject<IAvnSystemDialogs, &IID_IAvnSystemDialogs>
class StorageProvider : public ComSingleObject<IAvnStorageProvider, &IID_IAvnStorageProvider>
{
ExtensionDropdownHandler* __strong _extension_dropdown_handler;

public:
FORWARD_IUNKNOWN()

virtual HRESULT SaveBookmarkToBytes (
IAvnString* fileUriStr,
void** err,
IAvnString** ppv
) override
{
@autoreleasepool
{
if(ppv == nullptr)
return E_POINTER;

NSError* error;
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
auto bookmarkData = [fileUri bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
if (bookmarkData)
{
*ppv = CreateByteArray((void*)bookmarkData.bytes, (int)bookmarkData.length);
}
if (error != nil)
{
*err = CreateAvnString([error localizedDescription]);
}
return S_OK;
}
}

virtual HRESULT ReadBookmarkFromBytes (
void* ptr,
int len,
IAvnString** ppv
) override {
@autoreleasepool
{
if(ppv == nullptr)
return E_POINTER;

auto bookmarkData = [[NSData alloc] initWithBytes:ptr length:len];
auto fileUri = [NSURL URLByResolvingBookmarkData: bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope|NSURLBookmarkResolutionWithoutUI
relativeToURL:nil
bookmarkDataIsStale:nil
error:nil];

if (fileUri)
{
*ppv = CreateAvnString([fileUri absoluteString]);
}
return S_OK;
}
}

virtual void ReleaseBookmark (
IAvnString* fileUriStr
) override {
// no-op
}

virtual bool OpenSecurityScope (
IAvnString* fileUriStr
) override {
@autoreleasepool
{
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
auto success = [fileUri startAccessingSecurityScopedResource];
return success;
}
}

virtual void CloseSecurityScope (
IAvnString* fileUriStr
) override {
@autoreleasepool
{
auto fileUri = [NSURL URLWithString: GetNSStringAndRelease(fileUriStr)];
[fileUri stopAccessingSecurityScopedResource];
}
}

virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
IAvnSystemDialogEvents* events,
bool allowMultiple,
Expand Down Expand Up @@ -105,19 +184,9 @@ virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,

if(urls.count > 0)
{
void* strings[urls.count];

for(int i = 0; i < urls.count; i++)
{
auto url = [urls objectAtIndex:i];

auto string = [url path];

strings[i] = (void*)[string UTF8String];
}

events->OnCompleted((int)urls.count, &strings[0]);

auto uriStrings = CreateAvnStringArray(urls);
events->OnCompleted(uriStrings);

[panel orderOut:panel];

if(parentWindowHandle != nullptr)
Expand All @@ -130,7 +199,7 @@ virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle,
}
}

events->OnCompleted(0, nullptr);
events->OnCompleted(nullptr);

};

Expand Down Expand Up @@ -188,19 +257,9 @@ virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,

if(urls.count > 0)
{
void* strings[urls.count];

for(int i = 0; i < urls.count; i++)
{
auto url = [urls objectAtIndex:i];

auto string = [url path];

strings[i] = (void*)[string UTF8String];
}

events->OnCompleted((int)urls.count, &strings[0]);

auto uriStrings = CreateAvnStringArray(urls);
events->OnCompleted(uriStrings);

[panel orderOut:panel];

if(parentWindowHandle != nullptr)
Expand All @@ -213,7 +272,7 @@ virtual void OpenFileDialog (IAvnWindow* parentWindowHandle,
}
}

events->OnCompleted(0, nullptr);
events->OnCompleted(nullptr);

};

Expand Down Expand Up @@ -264,15 +323,11 @@ virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
void* strings[1];

auto url = [panel URL];

auto string = [url path];
strings[0] = (void*)[string UTF8String];

events->OnCompleted(1, &strings[0]);

auto urls = [NSArray<NSURL*> arrayWithObject:url];
auto uriStrings = CreateAvnStringArray(urls);
events->OnCompleted(uriStrings);

[panel orderOut:panel];

if(parentWindowHandle != nullptr)
Expand All @@ -284,7 +339,7 @@ virtual void SaveFileDialog (IAvnWindow* parentWindowHandle,
return;
}

events->OnCompleted(0, nullptr);
events->OnCompleted(nullptr);

};

Expand Down Expand Up @@ -519,7 +574,7 @@ void SetAccessoryView(NSSavePanel* panel,
};
};

extern IAvnSystemDialogs* CreateSystemDialogs()
extern IAvnStorageProvider* CreateStorageProvider()
{
return new SystemDialogs();
return new StorageProvider();
}
2 changes: 1 addition & 1 deletion native/Avalonia.Native/src/OSX/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ extern void PostDispatcherCallback(IAvnActionCallback* cb);
extern IAvnTopLevel* CreateAvnTopLevel(IAvnTopLevelEvents* events);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnStorageProvider* CreateStorageProvider();
extern IAvnScreens* CreateScreens(IAvnScreenEvents* cb);
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);
Expand Down
4 changes: 2 additions & 2 deletions native/Avalonia.Native/src/OSX/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,13 @@ virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface*
}
}

virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override
virtual HRESULT CreateStorageProvider(IAvnStorageProvider** ppv) override
{
START_COM_CALL;

@autoreleasepool
{
*ppv = ::CreateSystemDialogs();
*ppv = ::CreateStorageProvider();
return S_OK;
}
}
Expand Down
50 changes: 34 additions & 16 deletions samples/ControlCatalog/Pages/DialogsPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,24 @@ List<FileDialogFilter> GetFilters()

if (file is not null)
{
// Sync disposal of StreamWriter is not supported on WASM
try
{
// Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER
await using var stream = await file.OpenWriteAsync();
await using var writer = new System.IO.StreamWriter(stream);
await using var stream = await file.OpenWriteAsync();
await using var writer = new System.IO.StreamWriter(stream);
#else
using var stream = await file.OpenWriteAsync();
using var writer = new System.IO.StreamWriter(stream);
using var stream = await file.OpenWriteAsync();
using var writer = new System.IO.StreamWriter(stream);
#endif
await writer.WriteLineAsync(openedFileContent.Text);
await writer.WriteLineAsync(openedFileContent.Text);

SetFolder(await file.GetParentAsync());
SetFolder(await file.GetParentAsync());
}
catch (Exception ex)
{
openedFileContent.Text = ex.ToString();
}
}

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

await SetPickerResult(folders);

SetFolder(folders.FirstOrDefault());
};
this.Get<Button>("OpenFileFromBookmark").Click += async delegate
{
Expand All @@ -298,7 +303,6 @@ List<FileDialogFilter> GetFilters()
: null;

await SetPickerResult(folder is null ? null : new[] { folder });
SetFolder(folder);
};

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

resultText += await ReadTextFromFile(file, 500);
try
{
resultText += await ReadTextFromFile(file, 500);
}
catch (Exception ex)
{
resultText += ex.ToString();
}
}

openedFileContent.Text = resultText;

var parent = await item.GetParentAsync();
SetFolder(parent);
if (parent is not null)
if (item is IStorageFolder storageFolder)
{
mappedResults.Add(FullPathOrName(parent));
SetFolder(storageFolder);
}
else
{
var parent = await item.GetParentAsync();
SetFolder(parent);
if (parent is not null)
{
mappedResults.Add(FullPathOrName(parent));
}
}

foreach (var selectedItem in items)
Expand All @@ -391,7 +409,7 @@ async Task SetPickerResult(IReadOnlyCollection<IStorageItem>? items)
}
}

public static async Task<string> ReadTextFromFile(IStorageFile file, int length)
internal static async Task<string> ReadTextFromFile(IStorageFile file, int length)
{
#if NET6_0_OR_GREATER
await using var stream = await file.OpenReadAsync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Android.Webkit;
using Avalonia.Logging;
using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO;
using Java.Lang;
using AndroidUri = Android.Net.Uri;
using Exception = System.Exception;
Expand Down Expand Up @@ -53,7 +54,8 @@ protected AndroidStorageItem(Activity activity, AndroidUri uri, bool needsExtern
}

Activity.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Uri.ToString();

return StorageBookmarkHelper.EncodeBookmark(AndroidStorageProvider.AndroidKey, Uri.ToString()!);
}

public async Task ReleaseBookmarkAsync()
Expand Down
Loading

0 comments on commit 32c2f08

Please sign in to comment.