I've recently become interested in writing some system services for OS X but since I have no application to advertise the services under I must resort to writing standalone system services. Apple's documentation on System Services is spartan as it is, but its documentation on standalone services is non-existant.
What I have thus far is an Xcode project built from the bundle package, with the two sources HashifyService.h and HashifyService.m. Here is the test code I have:
- (void) doServiceWork:(NSPasteboard *)pboard
userData:(NSString *)userData
error:(NSString **)error {
NSLog(#"Actually in the service now");
NSString *pboardString;
NSArray *types;
NSLog(#"do test magic service! (pboard: %#, types: %#)", pboard, [pboard types]);
NSString* outputString = #"It Worked";
types = [NSArray arrayWithObject:NSStringPboardType];
[pboard declareTypes:types owner:nil];
[pboard setString:outputString forType:NSStringPboardType];
[outputString release];
return;
}
and this is the NSServices entry in my Info.plist:
<dict>
<key>NSMenuItem</key>
<dict>
<key>Menu item title</key>
<string>HashifyTest</string>
</dict>
<key>NSMessage</key>
<string>doServiceWork</string>
<key>NSPortName</key>
<string>HashifyService</string>
<key>NSReturnTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
</array>
</dict>
I then build the service bundle and place it in ~/Library/Services/ where it is appropriately detected and I am given the option to use the service. Upon activating the service, however, an error occurs and is logged to Console:
..../Hashify.service/Contents/MacOS/Hashify: cannot execute binary file
What am I doing wrong?
You need a main() function. That should register the service using NSRegisterServicesProvider() and enter the run loop. That is in the documentation.
Related
How to incorporate SSL pinning in React Native using axios for api calls.
I came across TrustKit but my continues efforts to use the same ends up crashing my app. I tried both the ways of adding TrustKit.
Via Info.list
<key>TSKConfiguration</key>
<dict>
<key>TSKSwizzleNetworkDelegates</key>
<true/>
<key>TSKPinnedDomains</key>
<dict>
<key>yourDomain.com</key>
<dict>
<key>TSKPublicKeyHashes</key>
<array>
<string>public key 1</string>
<string>public key 2</string>
</array>
<key>TSKPublicKeyAlgorithms</key>
<array>
<string>TSKAlgorithmRsa2048</string>
</array>
<key>TSKIncludeSubdomains</key>
<true/>
<key>TSKEnforcePinning</key>
<true/>
</dict>
</dict>
</dict>
Via AppDelegate.mm within didFinishLaunchingWithOptions
// Override TrustKit's logger method, useful for local debugging
void (^loggerBlock)(NSString *) = ^void(NSString *message)
{
NSLog(#"TrustKit log: %#", message);
};
[TrustKit setLoggerBlock:loggerBlock];
NSDictionary *trustKitConfig =
#{
// Swizzling because we can't access the NSURLSession instance used in React Native's fetch method
kTSKSwizzleNetworkDelegates: #YES,
kTSKPinnedDomains: #{
#"busdue.com" : #{
kTSKIncludeSubdomains: #YES, // Pin all subdomains
kTSKEnforcePinning: #YES, // Block connections if pinning validation failed
kTSKDisableDefaultReportUri: #YES,
kTSKPublicKeyHashes : #[
#"dz0GbS1i4LnBsJwhRw3iuZmVcgqpn+AlxSBRxUbOz0k=",
#"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake backup key but we need to provide 2 pins
],
},
}};
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
[TrustKit sharedInstance].pinningValidatorCallback = ^(TSKPinningValidatorResult *result, NSString *notedHostname, TKSDomainPinningPolicy *policy) {
if (result.finalTrustDecision == TSKTrustEvaluationFailedNoMatchingPin) {
NSLog(#"TrustKit certificate matching failed");
// Add more logging here. i.e. Sentry, BugSnag etc
}
};
I always end up getting this crash (https://i.stack.imgur.com/673OW.png)
Any suggestions are appreciated.
First Issue: No Sandboxing
I am having an issue with some code to copy a file. With sandboxing turned off completely and this code.
- (IBAction)installWidget:(id)sender
{
// copy widgets to users library
NSError* error = nil;
NSString *testUrl = #"~/Library/Widgets/test.wdgt";
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl]) {
NSLog(#"yes");
}
NSString *testUrl2 = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: #"test.wdgt"];
if ([[NSFileManager defaultManager]fileExistsAtPath:testUrl2]) {
NSLog(#"yes");
}
[[NSFileManager defaultManager] removeItemAtPath:testUrl error:nil];
[[NSFileManager defaultManager]copyItemAtPath:testUrl2 toPath:testUrl error:&error];
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
}
With sandboxing turned off I get the following error.
The file “test.wdgt” doesn’t exist. I tried zipping it thinking it was a flat file issue when changing the code from test.wdgt to test.zip I got the same error. The file is included in the resources bundle but its not letting me copy it.
I tried moving the app from outside of the build folder, same issue. I also tried cleaning the build folder same issue.
Second Issue: With Sandboxing
I get the following error with Sandboxing enabled. You don't have permission to access the directory Widgets.
Entitlement file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-write</key>
<true/>
<key>com.apple.security.assets.music.read-write</key>
<true/>
<key>com.apple.security.assets.pictures.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.print</key>
<true/>
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
<array>
<string>/Users/jon/Library/Widgets/</string>
</array>
</dict>
</plist>
I am beating my head against the wall. I tried adding a simple image and then replacing the image name with the widget name and it copied fine as long as sandboxing was disabled. I wrote a widget thats works with my app and I want to distribute it by allowing the user to install the widget by clicking a button in the app or from a menu item. Any help is appreciated.
On the non-sandbox question, you need to expand the tilde(~) before using that in a path for file system operations. One way to do this is by calling -stringByExpandingTildeInPath on the string you have created, but the best practice for getting folders like this would be to use:
NSArray *paths = NSSearchPathForDirectoriesInDomains
( NSLibraryDirectory, NSUserDomainMask, YES);
This will return an array (should only be 1 element) containing the string to the path for the User's Library directory. Then you can add your specific path elements to that by using -stringByAppendingPathComponent:.
Thus, you'd get the full path by taking:
NSString *widgetsPath = [[paths objectAtIndex: 0] stringByAppendingPathComponent:
#"Widgets"];
As for doing in the sandbox, your current code will fail due to the wrong directory, which won't have access (since ~ is being interpreted as a path component and not to replace the User's home directory). However, you obviously can't use the absolute-path exception in shipping code by enumerating every user. Chances are you will need to find another approach to installing this if you are going to be sandboxed. You may be able to just open the widget file and thus get the OS to offer to copy it for you. Otherwise, you're going to have to ask the user's permission in some way, such as by popping up an open window and passing in the path to the user's Widget folder.
I'm working on a small app that works with image (it is not a document based app).
I want to be able to open image files by dragging them to the app icon.
I have looked at this very clear explanation
Cocoa/Obj-C - Open file when dragging it to application icon
and have added the ability to open .png files by dragging to the app icon.
Do I have to add ALL the file types I require separately , or is there a way to specify ALL image types (for instance all image types that an NSImageView will accept)?
In your application’s Info.plist’s, you can specify the Uniform Type Identifier public.image in LSItemContentTypes for a document type even if your application is not document-based:
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.image</string>
</array>
</dict>
</array>
or
If you’re opening other types of files as well, you may want to check whether the dropped file is an image:
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSError *error;
NSString *fileUTI = [[NSWorkspace sharedWorkspace] typeOfFile:filename error:&error];
if (!fileUTI)
NSLog(#"Error when trying to detect the type of file %#: %#", filename, error);
else if (UTTypeConformsTo((__bridge CFStringRef)fileUTI, kUTTypeImage))
NSLog(#"%# is an image", filename);
else
NSLog(#"%# is not an image", filename);
return YES;
}
I'm trying to add a Finder service and all looks fine until I want the service to do its job.
This is the method in my AppDelegate.m:
-(void)uploadFromPasteboard:(NSPasteboard *)pboard userData:(NSString *)udata error:(NSString **)err
{
NSString *filename = [pboard stringForType:NSURLPboardType];
dbg(#"file: %#", filename);
}
The plist configuration:
<key>NSServices</key>
<array>
<dict>
<key>NSRequiredContext</key>
<dict/>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Upload File</string>
</dict>
<key>NSMessage</key>
<string>uploadFromPasteboard</string>
<key>NSPortName</key>
<string>Finder</string>
<key>NSSendTypes</key>
<array>
<string>NSURLPboardType</string>
</array>
<key>NSReturnTypes</key>
<array/>
</dict>
</array>
All seems fine, the service is displayed in the service menu, but when I click it, nothing happens, no logs or anything else, like its not called at all.
Could someone point me whats wrong cos I'm starting to pull my hair hardly :(
Are you setting your service provider instance? Like this (from: Providing a Service):
EncryptoClass* encryptor = [[EncryptoClass alloc] init];
[NSApp setServicesProvider:encryptor];
Merely having this method in your app delegate class is not enough. In the standard application set-up, having this in your app delegate might be sufficient:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSApp setServicesProvider: self];
}
Also you're specifying "Finder" for NSPortName. This is probably not correct. From Services Properties (emphasis mine):
NSPortName is the name of the port on which the application should
listen for service requests. Its value depends on how the service
provider application is registered. In most cases, this is the
application name. This property is ignored for Automator workflows
being used as services.
My reading of the documentation is that the application whose name is in NSPortName is the application that will be used to handle the service request. If the name of your app isn't "Finder" (and it shouldn't be, for obvious reasons) then your app will never be called by the service.
I am trying to create a new document UTI for my App so that people can share points of interest with others. From what I can understand on SO, Tutorials, and from Apple's documentation, you need to do the following:
Create a Document Type in the .plist
Create an Exported UTI that corresponds with it
Use the method: -(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
From what I understand, as long as you did those right, you should be able to open the file through Mail without any problems. Unfortunately it isn't working for my own custom UTIs. I DO see my App in the list of "Open with..." in Mail, but when I select it, my App doesn't open at all. It just dosen't do anything at all not only when the App isn't open, but when the App is open. Mail stays up and nothing happens at all. I also checked the console using "Organizer" and there is absolutely nothing that happens.
Originally I thought my plist was wrong, so I tested opening a public UTI (I added the com.adobe.pdf document type) and my app launched just fine (though it promptly crashed because I don't actually support PDFs ;)). But the point was that it launched without any problem.
The only thing I can think of that might be a problem is HOW I am creating the file. I am creating the file in an email by the use of this method (also in the App to export):
MFMailComposeViewController *picker = [[[MFMailComposeViewController alloc] init] autorelease];
[picker setSubject:[NSString stringWithFormat:#"My place: %#",POIName]];
[picker addAttachmentData:customPOIData mimeType:#"application/customPOI" fileName:[NSString stringWithFormat:#"%#.icp",POIName]];
[picker setMessageBody:#"Check out this great place I found!" isHTML:NO];
[picker setMailComposeDelegate:self];
[self presentModalViewController:picker animated:YES];
Is there anything wrong with that?
Also, here is my plist code:
CFBundleDocumentTypes:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>Custom POI</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.imadev.icp</string>
</array>
</dict>
</array>
UTExportedTypeDeclarations:
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Custom POI</string>
<key>UTTypeIdentifier</key>
<string>com.imadev.icp</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>icp</string>
</array>
<key>public.mime-type</key>
<string>application/customPOI</string>
</dict>
</dict>
</array>
Thanks a lot for any help!!
-Mark
I finally figured out what was wrong by pulling apart all the code.
When I change "public.filename-extension" to a string and not an array of strings it works. Don't ask me why... I think it's strange we can't use an array of file extensions. But apparently that was it.
Any ideas as to why that happened?
In my case, I could keep filename-extension as an array without problems. Instead, it was the 'public.data' that was the issue. I switched to 'public.image' and then had jpg, jpeg, and png as file extensions, and it suddenly worked.