Restrict access to certain folders using NSOpenPanel
Asked Answered
S

2

5

I'm using NSOpenPanel to allow a user to select a folder to save documents into. I would like to restrict what folder (in terms of hierarchy) they can save into. Essentially, I want to prevent them from choosing any folder above:

/Users/username/

So the folder

/Users/username/cats/

would be acceptable but

/Users/username/

/Applications/cats/

would not be allowed. I was wondering how to implement this restriction.

Thanks.

Scholiast answered 15/4, 2011 at 21:45 Comment(0)
O
16

Note that NSOpenPanel inherits from NSSavePanel, which in turn defines a delegate and a corresponding delegate protocol NSOpenSavePanelDelegate. You can use the delegate to extend the behaviour of the open panel so as to include the restriction you’ve listed in your question.

For instance, assuming the application delegate implements the open panel restriction, make it conform to the NSOpenSavePanelDelegate protocol:

@interface AppDelegate : NSObject <NSApplicationDelegate, NSOpenSavePanelDelegate>
@end

In the implementation of your application delegate, tell the open panel that the application delegate acts as the open panel delegate:

NSOpenPanel *openPanel = [NSOpenPanel openPanel];
[openPanel setDirectory:NSHomeDirectory()];
[openPanel setCanChooseDirectories:NO];
[openPanel setDelegate:self];
[openPanel runModal];

And implement the following delegate methods:

- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url {
    NSString *path = [url path];
    NSString *homeDir = NSHomeDirectory();

    return [path hasPrefix:homeDir] && ! [path isEqualToString:homeDir];
}

- (void)panel:(id)sender didChangeToDirectoryURL:(NSURL *)url {
    NSString *path = [url path];
    NSString *homeDir = NSHomeDirectory();

    // If the user has changed to a non home directory, send him back home!
    if (! [path hasPrefix:homeDir]) [sender setDirectory:homeDir];
}

- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError {
    NSString *path = [url path];
    NSString *homeDir = NSHomeDirectory();

    if (![path hasPrefix:homeDir]) {
        if (outError)
           *outError = ; // create an appropriate NSError instance

        return NO;    
    }
    return YES;
}
Outlay answered 15/4, 2011 at 22:21 Comment(2)
Would be great if you could provide an additional solution in Swift!Multivibrator
@Multivibrator • Both the question and the answer was posted in 2011. Swift 1.0 was introduced in 2014. The question tags do not have swift. The person who answered the question is no longer a SO member.Hexamerous
F
0

So, I took a stab at updating this for Swift 5.5.

I am including all the delegate methods for clarity to anyone who stumbles across this.

class Utility {
    var homeDirectory: URL?

    func openPanel(url: URL, sender: Any) -> URL? {
        let openPanel = NSOpenPanel()
        openPanel.canChooseDirectories = true
        openPanel.canChooseFiles = false
        openPanel.delegate = self
        appHomeDirectory = url
        openPanel.directoryURL = homeDirectory
        openPanel.showsHiddenFiles = false
        openPanel.canCreateDirectories = true
        switch openPanel.runModal() {
            case .OK:
                print("OK")
            case .cancel:
                print("Cancel")
            case .abort:
                print("Abort")
            case .continue:
                print("Continue")
            case .stop:
                print("Stop")
            default:
                print("Unknown Response")
        }
        return nil
    }

    func panel(_ sender: Any, didChangeToDirectoryURL url: URL?) {
        guard let _url = url else {
            return
        }
        print("didChangeToDirectoryURL")
        print("url: \(_url)")
    }

    func panel(_ sender: Any, shouldEnable url: URL) -> Bool {
        guard let homeDirectory = self.homeDirectory else {
            // Since homeDirectory cannot be set
            return false
        }
        print("shouldEnable")
        print("url path: \(url.path)")
        print("homeDirectory path: \(homeDirectory.path)")
        print("url.path.hasSuffix(homeDirectory.path): \(url.path.hasSuffix(homeDirectory.path))")
        // Removing the last path component of the sent URL
        print("url.deletingLastPathComponent().path.hasSuffix(homeDirectory.path): \(url.deletingLastPathComponent().path.hasSuffix(homeDirectory.path))")
        if url == homeDirectory {
            // This ensures the user can get back into the homeDirectory if
            // they navigated above the homeDirectory.
            return true
        } else {
            // Delete the last path component and then compare if the suffix of the url
            // path is the same as the homeDirectory path and return result.
            return (url.deletingLastPathComponent().path.hasSuffix(homeDirectory.path))
        }
    }

    func panel(_ sender: Any, validate url: URL) throws {
        print("validate")
        print("url: \(url)")
    }

    func panel(_ sender: Any, willExpand expanding: Bool) {
        print("willExpand")
        print("expanding: \(expanding)")
    }

    func panelSelectionDidChange(_ sender: Any?) {
        print("panelSelectionDidChange")
    }

}

In my ViewController I have an instance of “Utility“ as “utility“ and an IBAction for an imageButton that has a Storyboard identity of “fileBrowseImageButton“.

Usage:

@IBAction func fileBrowseImageButtonClicked(sender: Any?) {
    guard let url = utility.openPanel(url: url, sender: sender as! NSButton) else {
        return
    }
    // Do whatever needed with the returned url.
}
Fiorenze answered 21/3, 2022 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.