How to run Objective-C binary on a web server? - objective-c

Alright, I have a rather odd question here. I feel much more comfortable writing code in Objective-C over any other language. I recently had to do some server-side programming, which required me to learn PHP. It works, yeah, but for fun I want to achieve the same thing through Objective-C. So, I created a binary using Xcode's Foundation preset. Here's most of the binary:
#import <Foundation/Foundation.h>
#import "JSONKit.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *theURL = [NSString stringWithFormat:#"http://blahblahblah.com/blah"];
NSError *err = nil;
NSURLResponse* response = nil;
NSMutableURLRequest* request = [[[NSMutableURLRequest alloc] init] autorelease];
NSURL*URL = [NSURL URLWithString:theURL];
[request setURL:URL];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[request setTimeoutInterval:30];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
NSDictionary *someData = [data objectFromJSONData];
NSString *someString = [[someData objectForKey:#"foo"]objectForKey:#"bar"];
//do something
[pool drain];
return 0;
}
Quite basic code. It simply downloads some stuff from my server and I parse the JSON result and get a string that I want to use. So, my question is - how can I run this on my Linux-based server? I know it's possible, maybe using GNUStep (or cocotron?), which I don't know how to use. Anyone have ideas?

Well,
I suggest the same thing as the #lacqui.. Use CGI to run your program.. and here's the steps ..
(Side note: using CGI is deprecated as it starts a process each time a request is coming to the server (modern servers/web containers initiate a new thread (vs process).)
So, let's Start:
The input at hand is a program written in Objectiv-c
The output is a CGI script(program or whatever they name it) that
will run inside some http server.
First, let me ask you, What's the target platform to deploy your application?
If target deployment platform is a Mac then you will have to get the binary out of xcode ( I think it would be in a .dmg format) and find some where how to run a .dmg as a CGI program inside a web server ( I am not sure if apache runs under Mac or not)
But If it is Windows or Linux:
You will need to compile your application using GNUstep (I know nothing about portability from Xcode to GNUstep) You will need a GNUstep. Steps to install GNUstep either for Windows or Linux is trivial.
Once Installing GNUstep, you will have to compile your application again using it, refer to the same two links above to know how to compile your application.
The issue here is, AFAIK, GNUstep don't fully support Objc-2, so possibilities that the compilation will fail cause of usage of JSONKit.h is high. If your program compiles successfully, then you are almost done.
Suppose your program compiles, And you now have the binary program.. You will need to deploy it in some HTTP server that have CGI enabled. You can follow my blogpost here to know how to deploy a binary program written in C into some small http server called mini-httpd on Linux (it should apply to any binary program regardless of its source language).

What you want to look at is called the Common Gateway Interface. It is a protocol that states the way a web server will interact with subordinate processes.
What will happen is that, when a user browses to the URL that is mapped to your program, the server will start your program, and put the text of the request into STDIN. Your program will do whatever processing is required, then put the results (as well as some header information) into STDOUT.

What goes wrong when you try? You should be able to compile it with the GCC's Objective-C compiler. You should be able to run it.

Related

Why isn't CFStream using TLS 1.3?

I'm trying to figure out why an iPhone running iOS 14 isn't using TLS 1.3 to connect to a compatible web server.
Relevant code is:
- (void) streamOpened:(NSStream *)stream {
NSDictionary *settings = #{
(__bridge NSString *)kCFStreamSSLValidatesCertificateChain: (__bridge NSNumber *)kCFBooleanFalse
};
CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
}
The full source can be seen here: https://github.com/tls-inspector/tls-inspector/blob/app-store/CertificateKit/Getters/CKAppleCertificateChainGetter.m
I've tried specifying the SSL Level with kCFStreamSSLLevel set to kCFStreamSocketSecurityLevelTLSv1_3 but that didn't do anything.
If I use OpenSSL to connect it uses TLS 1.3 and I can verify that with a packet capture, but using CFStream it sticks to 1.2.
The short answer is that you end up using a deprecated API that does not support TLS 1.3.
The long answer, which details a potential solution, is given below.
I tried to solve this using CFStream but did not succeed.
It might be possible. The problem is that you end up, as you do, at a low level using SSLConnectionRef and friends and at a higher level using NSInputStream and NSOutputStream and friends and at some point you run into this https://developer.apple.com/documentation/security/secure_transport?language=objc legacy API.
On that page it mentions that that API was replaced by the Network framework and really this is what I am suggesting you should use. I was hoping to also implement a solution quickly but it needs a bit more rework than what SO is for so I leave it at that.
However, herewith some suggestions, basically the ideas I was hoping to implement.
As before, there are two levels.
At the lower level you end up using the nw_ and family but I would say do not go that way. It might be required for some specialised needs and your app might be in that category, but still, just take note that the higher level is built on top of this.
At the higher level, and this is where I think your solution lies, you end up using NSURL and friends. Your goto guy here is probably NSURLSession but the implementation will dictate that. I tried to give an outline and you can look at my code to get more detail but I think you will be in a much better position to implement from here onwards.
In that I was hoping to connect your legacy stream code to NSURLSession but when that failed I stopped. That is perhaps a bit optimistic and I think it requires a more serious rework, but the various delegates (NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionStreamDelegate and so on) seem to be ready and waiting for your solution and I don't think it is a lot of work actually.
The most relevant snippet of code from my attempt is below. This switches on TLS 1.3 and I tried to implement around this.
// Some configuration
NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
// Note this one!!!
config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:NSOperationQueue.currentQueue];
From this point onwards I tried to reuse your stream code but to be honest I think the final solution may not even use streams any more, and in stead just rely on using the correct delegate. Forgive me for getting excited at this point, but I suspect you will be able to greatly simplify your code as a result.
I enjoyed your app - it is pretty polished and I look forward to you solving this problem. I also enjoyed toying with it but for now that is my contribution.
First attempt
I eyeballed your code - it needs some reworking for TLS 1.3. I tried to do it but I am now rewriting that class so I stopped. You can do it, but, I can not guarantee that it will work!
Anyhow, here are some thoughts.
First on the existing code. Just some - ahem - observations, nothing serious ...
Note that your streamOpened will apply the settings no matter which stream was triggered. The delegate will message that twice I believe, once for the input and once for the output stream. Although here it seems it does not matter you should be careful as that could introduce some serious bugs in another situation.
Also, I think you need to configure the streams before they are opened but this did not make any difference. If you configure the streams before they are opened in your performTaskForURL or afterwards in the streamOpened it did not matter.
I played with the configuration a bit. You need not set one on the output stream, only on the input. And the only key that is required is the one you already set. I could not get any difference no matter what I did.
Second, the solution I - ahem - think will work here.
You need to configure the URL session. So what I did was the following
- (void) performTaskForURL:(NSURL *)url{
queryURL = url;
// Some configuration
NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
// Note this one!!!
config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:NSOperationQueue.currentQueue];
// Just code to test the idea, not production ready I know
NSURLSessionStreamTask * streamTask = [urlSession streamTaskWithHostName:url.host
port:443];
[streamTask captureStreams];
}
- (void)URLSession:(NSURLSession *)session
streamTask:(NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStreamUrl
outputStream:(NSOutputStream *)outputStreamUrl
{
// I was hoping to get away with this,
// just setting your streams equal to the
// URL stream task streams but it did not
// work ... problem is you need more of the
// stream task delegate methods I believe
inputStream = inputStreamUrl;
outputStream = outputStreamUrl;
// This is some left over code from your performTaskForURL message
// Here you can see how I toyed with the stream configuration
// Configure here before it is opened
// Pretty much your current streamOpened message
// Note only input needs be configured (fwiw)
[self configureStream:inputStream];
inputStream.delegate = self;
outputStream.delegate = self;
[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
[inputStream open];
// I was hoping this would just work but I think it needs more work
}
The idea here is to configure a URL session with kTLSProtocolMaxSupported and then to create a stream task off that. Now this is where I want to stop and think you are much better at going further. I was hoping I can just get the streams and throw them back to your code, but it did not work and for now I'm not going further.
I could not test my idea as I need to hook up a few more things to the NSURLSessionStreamDelegate that your class now also implements. I think you do the same in the class already, but for streams.
Now, again, I may be wrong, and wanted to test before I posted, but either see if this might work and then I think you need to e.g. implement delegate methods such as URLSession:task:didReceiveChallenge:completionHandler:.
I think you already do that sort of thing in your code, but this is why I am stopping here, as I think you will be a much better judge of this idea.
I've toyed with it some more but no success - but I think you need to use NSURLSession here, maybe even in stead of sockets.

Getting a list of all bootable drives mac

I'm currently looking for a way to get a list of all the bootable partitions on a Mac?
I know you can get a list of all the volumes? But I don't know how to check if each particular volume is bootable?
Is there a way to do this using swift or objective c?
With objective-c or swift probably not, but you can run an apple script (terminal commands) from your objective-c app (on OS X, I don't know whether it can be done on iOS)
This is how you execute an apple script:
//Begin of the script
NSAppleScript *script = [[NSAppleScript alloc]
initWithSource:#"Tell application \"Terminal\" \n\
do shell script \" some script here \"\n\
end tell"];
NSDictionary *errors = nil;
NSAppleEventDescriptor *result = [script executeAndReturnError:&errors];
NSLog(#"result: %#", result);
NSLog(#"errors: %#", errors);
if(errors==NULL){
NSLog(#"Succeeded");
}
else{
NSLog(#"Failed");
}
//End of the script
The quick and dirty way to do this is to shell out to either bless --info or systemsetup liststartupdisks commands. You can specify that you want the output in plist format to make it easier to parse as well.
As far as executing those commands the typical NSTask should work. You could also use system() I suppose, but it isn't really standard practice on OS X as NSTask has many advantages over it.
A purely code way to do this would be to get the list of disks and then look at each them for the known files that make OS X bootable. Things like the boot.efi file in /System/Library/CoreServices, the mach kernel file, and the contents of /System/Library/CoreServices/SystemVersion.plist.

Objective-C Scripting Bridge and Apple Remote Desktop

Trying to automatically view a computer in Apple Remote Desktop via Scripting Bridge in Objective-C with this:
#try {
SBApplication *RD = [SBApplication applicationWithBundleIdentifier:#"com.apple.RemoteDesktop"];
// (code to check for ARD running and installed omitted here)
[RD activate]; // works just fine
RemoteDesktopComputer *computer = [[[RD classForScriptingClass:#"computer"] alloc] initWithProperties:
[NSDictionary dictionaryWithObjectsAndKeys:
ipAddress,#"InternetAddress", // looked up from header
nil
]
];
// attempt to add it to a container first:
[(SBElementArray*)[(RemoteDesktopApplication*)RD computers] addObject:computer];
// this is what raises the exception:
[computer observeZooming:Nil];
}
#catch (NSException *e) {
NSLog(#"Exception: %#", [e description]);
}
Running this yields the following exception in the log:
Exception: *** -[SBProxyByClass observeZooming:]: object has not been added to a container yet; selector not recognized [self = 0x6050004819b3]
I've done as much research as there is available on this subject and have learned that SB isn't the easiest to deal with because of how it's wired under the hood, but any experts or veterans of native Scripting Bridge (no third party frameworks or languages other than obj-c, please) is much appreciated.
All prerequisites like linking to the ScriptingBridge.framework and importing Remote Desktop.h are performed - the typecasts are to avoid what appear to be unavoidable link-time errors when building...
Edit 1: Reading the documentation on SBObject (parent of RemoteDesktopComputer) says that it's a reference rather than an actual instance, which you can fetch by calling SBObject's get method (returns id). So I tried running this as well but unfortunately received the same results:
[[computer get] observeZooming:Nil];
Here's the documentation on SBObject: https://developer.apple.com/library/mac/documentation/cocoa/Reference/SBObject_Class/SBObject/SBObject.html#//apple_ref/occ/instm/SBObject/get
Still trying...
(FWIW, I already had the following How To written up, so I'm leaving it here for future reference.)
How to use AppleScript-ObjC in place of Scripting Bridge
Scripting Bridge is, at best, an 80/20/80 "solution" (i.e. 80% of the time it works, 20% of the time it fails, and 80% of the time you've no idea why). There's little point trying to argue with SB when it breaks on stuff that works perfectly well in AppleScript - the Apple engineers responsible designed it that way on purpose and simply refuse to accept they broke spec [1] and screwed up. As a result, the AppleScript language, for all its other deficiencies, remains the only supported solution that is guaranteed to speak Apple events correctly [2].
Fortunately, since OS X 10.6 there has been another option available: use ObjC for all your general programming stuff, and only call into AppleScript via the AppleScript-ObjC bridge for the IPC stuff.
From the POV of your ObjC code, your AppleScript-based ASOC 'classes' are more or less indistinguishable from regular ObjC classes. It requires a bit of fiddling to set up, and you'll pay a bit of a toll when crossing the bridge, but given the crippled, unreliable nature of the alternatives, it's the least horrid of the supported options for anything non-trivial.
Assuming you've already got an existing ObjC-based project, here's how to add an ASOC-based class to it:
In Targets > APPNAME > Build Phases > Link Binary With Libraries, add AppleScriptObjC.framework.
In Supporting Files > main.m, add the import and load lines as shown:
#import <Cocoa/Cocoa.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
int main(int argc, const char * argv[]) {
[[NSBundle mainBundle] loadAppleScriptObjectiveCScripts];
return NSApplicationMain(argc, argv);
}
To define an ASOC-based class named MyStuff that's callable from ObjC, create a MyStuff.h interface file that declares its public methods:
// MyStuff.h
#import <Cocoa/Cocoa.h>
#interface MyStuff : NSObject
// (note: C primitives are only automatically bridged when calling from AS into ObjC;
// AS-based methods with boolean/integer/real parameters or results use NSNumber*)
-(NSNumber *)square:(NSNumber *)aNumber;
#end
along with a MyStuff.applescript file containing its implementation:
-- MyStuff.applescript
script MyStuff
property parent : class "NSObject"
on square_(aNumber)
return aNumber ^ 2
end square_
end script
Because the MyStuff class doesn't have an ObjC implementation, the linker can't link your ObjC code to it at build-time. Instead, use NSClassFromString() to look up the class object at run-time:
#import "MyClass.h"
...
MyStuff *stuff = [[NSClassFromString(#"MyStuff") alloc] init];
Otherwise it's pretty much indistinguishable from a native ObjC class in normal use:
NSNumber *result = [stuff square: #3];
NSLog(#"Result: %#", result);
HTH
--
[1] Apple management broke up the original AppleScript team shortly after its initial release, causing its designers to quit in response, so a lot of knowledge of precisely how this stuff should work was lost. In particular, a full, formal specification was never produced for application developers to follow when designing their scripting support, so all they could do was use personal judgement and best guesses, then test against AppleScript to check it worked as hoped. Thus, AppleScript's own Apple event bridge is the de facto specification that every single scriptable app has been implemented against in the last twenty years, so the only way that other AE bridges can ever work correctly is if they mimic AS's own bridge down to every last query and quirk - a lesson, unfortunately, that the current AS team have repeatedly failed to understand [2].
[2] JavaScript for Automation's Apple event supported is equally crappy and busted, incidentally.
Scripting Bridge is a defective, obfuscated mess, so when an application command fails to work you've no idea if the problem is SB being defective or the application itself being buggy or simply requiring you to phrase it in a different way.
Therefore, the first step is to write a test script in AS to see it works there. If it does, it's SB that's crap; if not, try fiddling with your AS code (e.g. try phrasing the reference for the at parameter in different ways, or omitting it entirely) till it does.
You should also ask on Apple's AppleScript Users and ARD mailing lists and anywhere else that ARD scripters are likely to hang out, as most apps' scripting documentation is grossly inadequate, so a lot of knowledge of how to do things is word of mouth. (The guy you really want to talk to is John C Welch, aka #bynkii, as he's the guru of ARD scripting.)

NSURLConnection messes up iPad memory

we build an iPad app that downloads a bunch of data and PDF documents from a web service (data first, documents later in the background). To do so, we use SOAP via HTTP(S) requests. It works fine and altogether, the app is running well. Problem is, if there are too many documents to download at some point the app crashes. Using Instruments I figured out that it is a memory issue, particularly NSRegularExpression and NSRunLoop. (I'm using ARC)
I could improve my code to optimize the NSRegularExpression creation. But I don't know how to improve the NSRunLoop issue.
I tried both, asynchronous and synchronous HTTP request. Using async, I had to wait for the download to finish and since sleep()/[NSThread sleepForTimeInterval:] aren't an option, I use
while ( _waitToFinish ) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Using sync request, Instruments reveals that
[NSURLConnection sendSynchronousRequest:theRequest returningResponse:&urlResponse error:&error];
also "waits" with help of NSRunLoop and also messes up the memory.
Is this a bug in CoreFoundation or ARC?
Is there another way to idle while waiting for the requests to finish?
Thanks in advance.
Edit:
With "memory issue" I meant that the app crashes (or is killed by iOS) because it uses too much memory.
This is what Instruments shows:
The percentage get higher the longer the app is downloading.
Edit:
Going further down revealed that it is NSURLConnection, that is messing up the memory. It seems that I have somehow missed setting connection and receivedData to nil (see URL Loading System Programming Guide). This improved my memory usage again a little.
Now, there are two more big memory allocation operations:
And this is the code I think belongs to what Instruments displays:
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_receivedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseText = [[NSString alloc] initWithBytes:[_receivedData mutableBytes] length:[_receivedData length] encoding:NSUTF8StringEncoding];
self.lastResponse = responseText;
responseText = nil;
connection = nil;
_receivedData = nil;
_lastResult = TRUE;
_waitToFinish = FALSE;
}
Is there anything I could change to improve the code?
Edit: (Changed title from "NSRunLoop messes up iPad memory")
Edit:
I created a test app to prove that it is the NSURLConnection, that messes up the memory. Then I contacted the Apple Developer Support.
Since I am downloading a lot of PDF in an iteration with NSURLConnection, the solution was to add an #autoreleasepool { .. } in the iteration and another one around the NSRunLoop.
Thanks.
It's not a bug in Core Foundation or ARC. It's a bug in your code.
Don't run the run loop yourself. Just use +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]. It will call your completionHandler block when the request is complete. That's when you process the response. Meanwhile, just return out of your method and let the system worry about running the run loop.
You say “it's a memory issue” with NSRegularExpression and NSRunLoop, but you don't say what the issue is, or what evidence Instruments is showing you. If you describe the “issue” in more detail, maybe we can help you with that.

Discover the environment and relative path of the running application

While playing with RubyCocoa, I keep progressing with my idea for my application. Because my application will be going to use configuration files, I would like to know how I discover the relative path to store these inside my application structure (or if a better idea emerges, please elaborate also the "why").
Also good for me to know is to discover environment variables, such as operating system version, the amount of memory that is available and such. Hyperlinks would be awesome too.
Please notice I use RubyCocoa and thank you for your feedback, comments and answers!
To access inside the application bundle, you use NSBundle. See NSBundle reference. In Obj-C, you use +[NSBundle mainBundle] to get the main bundle of the app. Then you use -[NSBundle pathForResource:ofType:] to get the file. I don't know RubyCocoa syntax, but I assume you know how to translate to it :)
If by the configuration file you mean a user-configurable things, remember you can't write inside the app bundle at runtime. Instead one uses NSUserDefaults. See User Defaults Guide.
Here's some Cocoa code I use to write all the environment variables to the console. Again, I don't use RubyCocoa, so you'll have to translate:
NSProcessInfo *myProcessInfo = [NSProcessInfo processInfo];
NSDictionary *env = [myProcessInfo environment];
for (id key in env)
{
NSLog (#"key: %#, value: %#", key, [env objectForKey: key]);
}