NSSavePanel not saving on desktop? - objective-c

can i use NSSavePanel with a sandboxed OS X app to let user save on desktop? i gave user read/write entitlements for downloads and user selected folder, for some reason my app saves in downloads folder fine but when i change directory and select desktop it doesnt save at all.
here is the code am using for NSSavePanel
if([self.mActiveQRFileName isEqualToString:kQR_DEFAULT_FILE_NAME])
{
NSSavePanel *savePanel = [NSSavePanel savePanel];
//[savePanel setDirectoryURL:[NSURL URLWithString:[Utilities getQRDefaultDirectoryPath]]];
[savePanel setNameFieldStringValue:kQR_DEFAULT_FILE_NAME];
[savePanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton) {
NSString *qrFilePath = [NSString stringWithFormat:#"%#.%#",[[savePanel URL] path],kQR_FILE_EXT];
[qrd saveQRFile:qrFilePath];
self.mActiveQRFileName = [NSString stringWithString:qrFilePath];
blnChangesSaved = YES;
}
}];
}
else
{
[qrd saveQRFile:self.mActiveQRFileName];
blnChangesSaved = YES;
}
Any help would be greatly appreciated :)

A NSSavePanel will give you the user selected path for a file in it's URL property. The sandbox will only grant you access to this file, with the name specified by the user.
In your example code this line possibly modifies the selected path by giving it a different file extension:
NSString *qrFilePath = [NSString stringWithFormat:#"%#.%#",[[savePanel URL] path],kQR_FILE_EXT];
Which could result in a filename different from the originally selected file for which you don't have access in the sandbox. Try logging the qrFilePath and see if it still equals the path for the selected URL. Also check your sandbox exceptions to see what the exact error is.
If you want to restrict the NSSavePanel to let the user only specify files of a certain type use the setAllowedFileTypes: methos.
If you want the user to grant you access to a directory to write to where you can output any file, as opposed to a specific path: use a NSOpenPanel. This has the disadvantage that the user cannot specify a specific file name like in a NSSavePanel.

Related

How to get permission with NSSavePanel in sandbox environment

There is a project with sandbox environment, right now i want to let users choose a dir to save files. For example, when user choose "Desktop" in the save panel, then i will save file to the dir "~/Desktop".
The code is as below:
NSSavePanel *savePanel = [NSSavePanel savePanel];
NSInteger result = [savePanel runModal];
if(result == NSFileHandlingPanelOKButton)
{
NSString *dir = [savePanel.URL.path.stringByDeletingLastPathComponent stringByAppendingPathComponent:#"2.txt"];
NSError *err;
NSLog(#"%#",dir);
[#"asasasa" writeToFile:dir atomically:YES encoding:NSUTF8StringEncoding error:&err];
NSLog(#"%#",err);
}
The error happens that Code=513 "You don’t have permission to save the file “2.txt” in the folder “Desktop”.
I have already enable Read/Write of User Selected File in the Capabilities settings. Anybody can tell me what should i do right now ?
Thanks in advance.
You are not saving the file to the URL you got permission for, you changed the URL. This is not allowed.
Specify 2.txt in the save panel so the URL you get back is ~/Desktop/2.txt. Then saving will work.
Side note: There is also an URL related API like writeToURL:atomically:encoding:error and URLByAppendingPathComponent:

My OSX app is sandboxed and I am not able to read data from file by specifying the absolute path

I am completely new to objective C and currently I am trying to advance the functionality of an already existing project.
There is a finder extension in the project which on getting clicked performs an action inside (IBAction) Share(id) sender.
Inside this action , I want to read a file from a particular location (the file contains the port number) and using that port I want to connect to the server.
But what I found was when I click on this extension , nothing happens because it tries to go and read data from the file and is not able to read anything.
I tried to debug this by printing out whatever it has read to some other file but all it printed was blank confirming that it is not able to read the data. Below is my code trying to read the port from a temporary location :
- (IBAction)privateShareAction:(id)sender {
NSFileManager *filemgr;
filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: #"/var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties" ] == YES)
{
//create file handle
NSFileHandle *file;
file = [NSFileHandle fileHandleForReadingAtPath:#"/var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties"];
//read data into file in NSData format
NSData *filedata;
filedata = [file readDataToEndOfFile];
NSLog(#"fileDATA = %#", filedata);
//convert NSData to NSString
NSString *string;
string = [[NSString alloc] initWithData:filedata encoding:NSASCIIStringEncoding];
NSMutableString *directoryPath1 = [NSMutableString stringWithString: #"share1>"];
[directoryPath1 appendString: string];
NSData *dataToWrite3 = [directoryPath1 dataUsingEncoding: NSUTF8StringEncoding];
NSFileHandle* outputFile = [NSFileHandle fileHandleForWritingAtPath:#"/Users/yp/Downloads/a.txt"];
[outputFile seekToEndOfFile];
[outputFile writeData:dataToWrite3];
//convert from string to array
NSArray *lines = [string componentsSeparatedByString:#"="];
NSLog(#"arrau = %#", lines);
//take one of the string and store it in sword
NSString *sword = [lines objectAtIndex:1];
NSLog(#"port : %#", sword);
int port1=[sword intValue];
Communicator *c = [[Communicator alloc ]init];
c.host=#"http:127.0.0.1";
c.port=port1;
[c setup];
}
else
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:#"Error"];
[alert setInformativeText:#"You are not logged in.Kindly login to start performing the operations"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
}
The above code, on the action performed first tries to check if the file is present at the /var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties location or not.
This works perfectly fine , If the file is present , it shows a popup alert (which happens).
But if the file is present , it goes inside the if condition and tries to read the file where it fails .It always prints a blank string showing that nothing is being read.
So then I went and checked the entitlements in App Sandbox.
I tried to add an entitlement named com.apple.security.temporary-exception.files.absolute-path.read-only with a string value set to /var/folders/y3/jv117_75505fnk8htdrs0qm40000gr/T/com.aprivacy.xmlCorePort.properties so that it gets the permission to read the file from this location but still it doesn't solve my problem.
Could anyone please suggest how to get this file reading permission accessible in my app because the same code works completely fine in a newly created test project.
Following steps : Original client app running -login with user name and password Once logged in -it writes the port in a file At the same time ,once you are logged in with your application , if now you right click on any file in your system you will see certain extra extensions like share ,grant access etc. (This is because a finder project used to add extensions is merged with the original client) Now when I click on say share (on right clicking a file) , I want an action to be performed.The logic for action is written in (IBAction)Share (id) sender method This app used to add extensions is sandboxed because of which the permissions are restricted. So while I clicked on share , my logic was to read that file ,get the port and then connect to server using that port. I want to do everything inside action but I am unable to do so . It is not able to find the file data from /var/folder/y3/jv117_755fdlvfldsvgr/T/com.aprivacy.xmlcorePort.properties
Sandboxed apps (all in iOS) are only allowed access to specific directories. Use NSSearchPathForDirectoriesInDomainsto obtain paths to available directories.
Ex:
Objective-C:
NSArray *documentDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
NSError *error;
BOOL status = [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (status == NSError) {
NSLog(#"error: %#", error)
}
Swift:
let filePath = "path/file.txt";
let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as! String
let path = documentDirectoryPath + filePath
Note: Sandboxed paths is not consistent across clean builds.
Don't use absolute paths in sandboxed applications.
In OS X there is the NSTemporaryDirectory() function to have access to the temporary directory for this specific application in the container. Entitlements are not needed.
From the documentation
Some path-finding APIs (above the POSIX layer) refer to app-specific
locations outside of the user’s home directory. In a sandboxed app,
for example, the NSTemporaryDirectory function provides a path to a
directory that is outside of the user’s home directory but specific to
your app and within your sandbox; you have unrestricted read/write
access to it for the current user. The behavior of these path-finding
APIs is suitably adjusted for App Sandbox and no code change is
needed.
Source: App Sandbox in Depth

Getting privilege to write a file to \Library\ColorSync\Profiles\ in a mac application

From my mac application, I need to write a file to \Library\ColorSync\Profiles. For this, the app needs admin privilege to read write to the folder. Is possible to get the same with in the application? Any help will be appreciated.
I am able to pop up the permission dialog with the following code snippet
NSSavePanel *tvarNSSavePanelObj = [NSSavePanel savePanel];
[tvarNSSavePanelObj setDirectoryURL:[NSURL URLWithString:#"/Library/ColorSync/Profiles"]];
__block NSString *filePathexn = nil;
[tvarNSSavePanelObj beginSheetModalForWindow:[NSApplication sharedApplication].mainWindow completionHandler:^(NSInteger tvarInt) {
if(tvarInt == NSModalResponseOK){
NSURL* tvarUrl = [tvarNSSavePanelObj URL];
NSLog(#"doSaveAs filename = %#",[tvarUrl path]);
NSString *filePath = [tvarUrl path];
filePathexn = [filePath stringByAppendingPathExtension:#"rtf"];
OSStatus status;
if(![[NSFileManager defaultManager]isWritableFileAtPath:filePath]){
NSLog(#"Not Writable at path");
AuthorizationRef authRef;
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights rights = {1, &right};
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagPreAuthorize, &authRef);
AuthorizationFlags authFlags = kAuthorizationFlagDefaults | kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize;
status = AuthorizationCopyRights(authRef, &rights, kAuthorizationEmptyEnvironment, authFlags, NULL);
}
BOOL status1 = [[NSFileManager defaultManager]createFileAtPath:filePathexn contents:nil attributes:nil];
} else if(tvarInt == NSModalResponseCancel) {
return;
} else {
NSLog(#"doSave As tvarInt not equal 1 or zero = %3ld",(long)tvarInt);
return;
}
}];
I need to know how the file write could be done. Still file is not written to the path. Is any tool name need to be specified? Will it be possible with SMJobless() ? Kindly advise a solution!!
Another solution I am posting here. I went through the app distribution guide. It is mentioned that the Authorization APIS are not recommended for an app that is distributed out side of app store.
Any way this solution is working for me, it is a little hack, I don't know. I try to run the apple script form the application, which gives full privilege to the folder (chmod 777 path) and after finishing my task set back to previous privilege set.
- (void)runapplescript{
NSDictionary* errorDict;
NSAppleEventDescriptor* returnDescriptor = NULL;
NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
#"do shell script \"chmod 777 /Library/ColorSync/Profiles\" with administrator privileges"];
returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
}
I can suggest 2 approaches. One approach is fast but deprecated. Second approach is modern but needs a bit of coding and reading documentation:
You need to write bash script which will do what you need and execute it with privileges which will be granted using something like this: STPrivilegedTask
Write privileged helper tool which will be installed once on first access with root privileges and you will be able do any privileged operation How to write privileged helper tool
If it is saving a ColorSync profile you are after, ColorSyncProfileInstall will do the heavy lifting for you, like prompting the user for permission & authentication. 🏖️
It is also necessary to include the code-signing COLORSYNC_PROFILE_INSTALL_ENTITLEMENT entitlement:
<key>com.apple.developer.ColorSync.profile.install</key>
<true/>
The documentation for the aforementioned symbol can be found in the ColorSyncProfile.h header—partially reproduced here for convenience.
CSEXTERN bool ColorSyncProfileInstall(ColorSyncProfileRef profile, CFStringRef domain, CFStringRef subpath, CFErrorRef* error);
/*
* profile - profile to be installed
* domain - either kColorSyncProfileComputerDomain or kColorSyncProfileUserDomain.
* kColorSyncProfileComputerDomain is for sharing the profiles (from /Library/ColorSync/Profiles).
* kColorSyncProfileUserDomain is for user custom profiles (installed under home directory, i.e. in
* ~/Library/ColorSync/Profiles.
* NULL is the same as kColorSyncProfileUserDomain.
* subpath - CFString created from the file system representation of the path of the file to contain the installed
* profile. The last component of the path is interpreted as a file name if it ends with the extension ".icc".
* Otherwise, the subpath is interpreted as the directory path and file name will be created from the
* profile description tag, appended with the ".icc" extension.
* error - (optional) pointer to the error which will be returned in case of failure.
*
* bool value true is returned if success or false in case of error.
*/

Objective-C Directory Picker ( OSX 10.7 )

So I currently have this bit of code to get a dir:
-(NSString *)get {
NSOpenPanel *gitDir = [NSOpenPanel openPanel];
NSInteger *ger = [gitDir runModalForTypes:nil];
NSString *Directory = [gitDir directory];
return Directory;
}
But it gives me errors and says it has now been depreciated.
Is there a better way for OSX 10.7?
This is a supplement to sosborn's answer, not a replacement.
runModalForTypes: is deprecated, and the correct replacement is runModal (or setAllowedFileTypes: followed by runModal, but in this case you're passing nil for the types).
directory is also deprecated, and the correct replacement is directoryURL. (If you actually must return an NSString path rather than an NSURL, just return [[gitDir directoryURL] path].)
However, what you're doing is asking the user to select a file, and then returning the directory that file is in, when what you really want is to ask the user to select a directory. To do that, you want to call setCanChooseFiles to NO and setCanChooseDirectories to YES, and then call URLs to get the directory the user selected.
Also, you're ignoring the result of runModal (or runModalForTypes:). I'm sure the compiler is warning you about the unused variable "ger", and you shouldn't just ignore warnings. If the user cancels the panel, you're going to treat that as clicking OK, and select whatever directory she happened to be in when she canceled.
Here's a better implementation, which will return the URL of the selected directory, or nil if the user canceled (or somehow managed to not select anything). Again, if you need an NSString, just add a "path" call to the return statement:
-(NSURL *)get {
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
if ([panel runModal] != NSModalResponseOK) return nil;
return [[panel URLs] lastObject];
}
Whenever you see a deprecation warning you should go straight to the official documentation. In this case, the docs for NSOpenPanel say:
runModalForTypes: Displays the panel and begins a modal event loop
that is terminated when the user clicks either OK or Cancel.
(Deprecated in Mac OS X v10.6. Use runModal instead. You can set
fileTypes using setAllowedFileTypes:.)
I adapted the code by abarnert for swift. tx for the code just what I needed.
func askUserForDirectory() -> NSURL? {
let myPanel:NSOpenPanel = NSOpenPanel()
myPanel.allowsMultipleSelection = false
myPanel.canChooseDirectories = true
myPanel.canChooseFiles = false
if ( myPanel.runModal() != NSFileHandlingPanelOKButton ) {
return nil
}
return myPanel.URLs[0] as? NSURL
}

NSSavePanel is not saving a file after sandboxing an app

I'm having a problem saving a string file with NSSavePanel after sandboxing the app for the Mac App Store. I set com.apple.security.files.user-selected.read-write to YES and the NSOpenPanel is working as it should.
When I try to save a new file, though, it seems that everything is working fine but then there is no saved file where it should be....
This is the code I am using to save the file:
NSSavePanel *save = [NSSavePanel savePanel];
long int result = [save runModal];
if (result == NSOKButton)
{
NSString *selectedFile = [save filename];
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.dat", selectedFile];
NSString *arrayCompleto = [[NSString alloc]initWithFormat:#"bla bla bla"];
[arrayCompleto writeToFile:fileName
atomically:NO
encoding:NSUTF8StringEncoding
error:nil];
}
First of all, the -[NSSavePanel filename] selector has been deprecated. Use -[NSSavePanel URL] instead. Second, the way that the -[NSString writeToFile:atomically:encoding:error] tells you what you're doing wrong is with the error:(NSError**) argument.
You should also handle errors for file I/O in particular, because even if your code is 100% correct, there still might be errors on the user's system (insufficient privileges, etc.) and presenting the error to the user will allow them to see it failed (and have some idea why). Handling the error in code will also allow your app to recover. For instance, if you tried to read in the file below the code you pasted (after writing it to disk), but the user tried writing it to a network share they didn't have access to, your app might crash. If you know the write failed, you can proceed accordingly (perhaps prompting for a different save location).
In this case, though, I believe the following line is your problem:
NSString *fileName = [[NSString alloc] initWithFormat:#"%#.dat", selectedFile];
When your app is sandboxed, the user needs to give you permission for either a specific file or a specific directory through the open/save panels to bring them into your sandbox. What you're doing is taking the file the user gave you permission to write and saying "that's great, but I want to save a different file", which violates the sandbox. What you should do instead is set the extension in the Save Panel. The complete fixed solution would be:
NSSavePanel *save = [NSSavePanel savePanel];
[save setAllowedFileTypes:[NSArray arrayWithObject:#"dat"]];
[save setAllowsOtherFileTypes:NO];
NSInteger result = [save runModal];
if (result == NSOKButton)
{
NSString *selectedFile = [[save URL] path];
NSString *arrayCompleto = #"bla bla bla";
NSError *error = nil;
[arrayCompleto writeToFile:selectedFile
atomically:NO
encoding:NSUTF8StringEncoding
error:&error];
}
if (error) {
// This is one way to handle the error, as an example
[NSApp presentError:error];
}
If in the future something else is wrong, you can check the value of error at runtime. While debugging, set a breakpoint inside the if (error) statement to check error object's value (do a po error in Xcode's debugger). That should help you figure out what's wrong.