/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ //PLAN: // open my bundle, check for an override binary signature // find the newest firefox. open its bundle, get the version number. // if the firefox version is different than ours: // delete our own binary, // copy the newer webapprt binary from Firefox // exec it, and quit #include #include #include #include #include #include #include #include "nsXPCOMGlue.h" #include "nsINIParser.h" #include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL #include "nsXULAppAPI.h" #include "nsComponentManagerUtils.h" #include "nsCOMPtr.h" #include "nsIFile.h" #include "nsStringGlue.h" #include "mozilla/AppData.h" using namespace mozilla; const char WEBAPPRT_EXECUTABLE[] = "webapprt-stub"; const char FXAPPINI_NAME[] = "application.ini"; const char WEBAPPINI_NAME[] = "webapp.ini"; const char WEBRTINI_NAME[] = "webapprt.ini"; //need the correct relative path here const char APP_MACOS_PATH[] = "/Contents/MacOS/"; const char APP_RESOURCES_PATH[] = "/Contents/Resources/"; //the path to the WebappRT subdir within the Firefox app contents dir const char WEBAPPRT_PATH[] = "webapprt/"; void ExecNewBinary(NSString* launchPath, NSDictionary* args); NSString *PathToWebRT(NSString* alternateBinaryID); NSException* MakeException(NSString* name, NSString* message); void DisplayErrorAlert(NSString* title, NSString* message); XRE_GetFileFromPathType XRE_GetFileFromPath; XRE_CreateAppDataType XRE_CreateAppData; XRE_FreeAppDataType XRE_FreeAppData; XRE_mainType XRE_main; const nsDynamicFunctionLoad kXULFuncs[] = { { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, { "XRE_main", (NSFuncPtr*) &XRE_main }, { nullptr, nullptr } }; nsresult AttemptGRELoad(char *greDir) { nsresult rv; char xpcomDLLPath[MAXPATHLEN]; snprintf(xpcomDLLPath, MAXPATHLEN, "%s%s", greDir, XPCOM_DLL); rv = XPCOMGlueStartup(xpcomDLLPath); if (NS_FAILED(rv)) { return rv; } rv = XPCOMGlueLoadXULFunctions(kXULFuncs); if (NS_FAILED(rv)) { return rv; } return rv; } int main(int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSDictionary *args = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSArgumentDomain]; NSString *firefoxPath = nil; NSString *alternateBinaryID = nil; //this is our version, to be compared with the version of the binary we are asked to use NSString* myVersion = [NSString stringWithFormat:@"%s", NS_STRINGIFY(GRE_BUILDID)]; NSLog(@"MY WEBAPPRT BUILDID: %@", myVersion); //I need to look in our bundle first, before deciding what firefox binary to use NSBundle* myBundle = [NSBundle mainBundle]; NSString* myBundlePath = [myBundle bundlePath]; alternateBinaryID = [myBundle objectForInfoDictionaryKey:@"FirefoxBinary"]; NSLog(@"found override firefox binary: %@", alternateBinaryID); @try { // Determine the runtime with which to run the application. // Throws an exception with an error dialog if it can't find one. firefoxPath = [args objectForKey:@"runtime"]; if (firefoxPath) { NSLog(@"Runtime specified with -runtime flag: %@", firefoxPath); } else { firefoxPath = PathToWebRT(alternateBinaryID); NSLog(@"Found runtime: %@", firefoxPath); } NSString* myWebRTPath = [myBundle pathForResource:@"webapprt" ofType:nil]; if (!myWebRTPath) { myWebRTPath = [myBundlePath stringByAppendingPathComponent:@"Contents"]; myWebRTPath = [myWebRTPath stringByAppendingPathComponent:@"MacOS"]; myWebRTPath = [myWebRTPath stringByAppendingPathComponent:@"webapprt"]; if ([[NSFileManager defaultManager] fileExistsAtPath:myWebRTPath] == NO) { @throw MakeException(@"Missing Web Runtime Files", @"Cannot locate binary for this App"); } } //GET FIREFOX BUILD ID NSString *firefoxINIFilePath = [NSString stringWithFormat:@"%@%s%s", firefoxPath, APP_RESOURCES_PATH, FXAPPINI_NAME]; nsINIParser ffparser; NSLog(@"Looking for firefox ini file here: %@", firefoxINIFilePath); if (NS_FAILED(ffparser.Init([firefoxINIFilePath UTF8String]))) { firefoxINIFilePath = [NSString stringWithFormat:@"%@%s%s", firefoxPath, APP_MACOS_PATH, FXAPPINI_NAME]; NSLog(@"Looking for firefox ini file here: %@", firefoxINIFilePath); if (NS_FAILED(ffparser.Init([firefoxINIFilePath UTF8String]))) { NSLog(@"Unable to locate Firefox application.ini"); @throw MakeException(@"Error", @"Unable to parse environment files for application startup"); } } char ffVersChars[MAXPATHLEN]; if (NS_FAILED(ffparser.GetString("App", "BuildID", ffVersChars, MAXPATHLEN))) { NSLog(@"Unable to retrieve Firefox BuildID"); @throw MakeException(@"Error", @"Unable to determine Firefox version."); } NSString* firefoxVersion = [NSString stringWithFormat:@"%s", ffVersChars]; NSLog(@"FIREFOX WEBAPPRT BUILDID: %@", firefoxVersion); //GOT FIREFOX BUILD ID //COMPARE MY BUILD ID AND FIREFOX BUILD ID if ([myVersion compare: firefoxVersion] != NSOrderedSame) { //we are going to assume that if they are different, we need to re-copy the webapprt, regardless of whether // it is newer or older. If we don't find a webapprt, then the current Firefox must not be new enough to run webapps. NSLog(@"### This Application has an old webrt. Updating it."); NSLog(@"### My webapprt path: %@", myWebRTPath); NSFileManager* fileClerk = [[NSFileManager alloc] init]; NSError *errorDesc = nil; //we know the firefox path, so copy the new webapprt here NSString *newWebRTPath = [NSString stringWithFormat: @"%@%s%s", firefoxPath, APP_RESOURCES_PATH, WEBAPPRT_EXECUTABLE]; NSLog(@"### Trying Firefox webapprt path: %@", newWebRTPath); if (![fileClerk fileExistsAtPath:newWebRTPath]) { newWebRTPath = [NSString stringWithFormat: @"%@%s%s", firefoxPath, APP_MACOS_PATH, WEBAPPRT_EXECUTABLE]; NSLog(@"### Trying Firefox webapprt path: %@", newWebRTPath); if (![fileClerk fileExistsAtPath:newWebRTPath]) { NSString* msg = [NSString stringWithFormat: @"This version of Firefox (%@) cannot run web applications, because it is not recent enough or damaged", firefoxVersion]; @throw MakeException(@"Missing Web Runtime Files", msg); } } #if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4) [fileClerk removeFileAtPath: myWebRTPath handler:nil]; #else [fileClerk removeItemAtPath: myWebRTPath error: &errorDesc]; #endif if (errorDesc != nil) { NSLog(@"failed to unlink old binary file at path: %@ with error: %@", myWebRTPath, errorDesc); @throw MakeException(@"Unable To Update", @"Failed preparation for Web Runtime update"); } #if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4) [fileClerk copyPath: newWebRTPath toPath: myWebRTPath handler:nil]; #else [fileClerk copyItemAtPath: newWebRTPath toPath: myWebRTPath error: &errorDesc]; #endif [fileClerk release]; if (errorDesc != nil) { NSLog(@"failed to copy new webrt file: %@", errorDesc); @throw MakeException(@"Unable To Update Web Runtime", @"Failed to update Web Runtime"); } else { NSLog(@"### Successfully updated webapprt, relaunching"); } //execv the new binary, and ride off into the sunset ExecNewBinary(myWebRTPath, args); } else { //we are ready to load XUL and such, and go go go NSLog(@"This Application has the newest webrt. Launching!"); int result = 0; char rtINIPath[MAXPATHLEN]; // Set up our environment to know where webapp.ini was loaded from. char appEnv[MAXPATHLEN]; snprintf(appEnv, MAXPATHLEN, "%s%s%s", [myBundlePath UTF8String], APP_MACOS_PATH, WEBAPPINI_NAME); if (setenv("XUL_APP_FILE", appEnv, 1)) { NSLog(@"Couldn't set XUL_APP_FILE to: %s", appEnv); @throw MakeException(@"Error", @"Unable to set Web Runtime INI file."); } NSLog(@"Set XUL_APP_FILE to: %s", appEnv); //CONSTRUCT GREDIR AND CALL XPCOMGLUE WITH IT char greDir[MAXPATHLEN]; snprintf(greDir, MAXPATHLEN, "%s%s", [firefoxPath UTF8String], APP_RESOURCES_PATH); if (!NS_SUCCEEDED(AttemptGRELoad(greDir))) { @throw MakeException(@"Error", @"Unable to load XUL files for application startup"); } // NOTE: The GRE has successfully loaded, so we can use XPCOM now NS_LogInit(); { // Scope for any XPCOM stuff we create // Get the path to the runtime directory. char rtDir[MAXPATHLEN]; snprintf(rtDir, MAXPATHLEN, "%s%s%s", [firefoxPath UTF8String], APP_RESOURCES_PATH, WEBAPPRT_PATH); // Get the path to the runtime's INI file. This is in the runtime // directory. snprintf(rtINIPath, MAXPATHLEN, "%s%s%s%s", [firefoxPath UTF8String], APP_RESOURCES_PATH, WEBAPPRT_PATH, WEBRTINI_NAME); NSLog(@"WebappRT application.ini path: %s", rtINIPath); // Load the runtime's INI from its path. nsCOMPtr rtINI; if (NS_FAILED(XRE_GetFileFromPath(rtINIPath, getter_AddRefs(rtINI)))) { NSLog(@"Runtime INI path not recognized: '%s'\n", rtINIPath); @throw MakeException(@"Error", @"Incorrect path to base INI file."); } bool exists; nsresult rv = rtINI->Exists(&exists); if (NS_FAILED(rv) || !exists) { NSString* msg = [NSString stringWithFormat: @"This copy of Firefox (%@) cannot run Apps, because it is missing the Web Runtime's application.ini file", firefoxVersion]; @throw MakeException(@"Missing Web Runtime application.ini", msg); } nsXREAppData *webShellAppData; if (NS_FAILED(XRE_CreateAppData(rtINI, &webShellAppData))) { NSLog(@"Couldn't read WebappRT application.ini: %s", rtINIPath); @throw MakeException(@"Error", @"Unable to parse base INI file."); } NSString *profile = [args objectForKey:@"profile"]; if (profile) { NSLog(@"Profile specified with -profile flag: %@", profile); } else { nsINIParser parser; if (NS_FAILED(parser.Init(appEnv))) { NSLog(@"%s was not found\n", appEnv); @throw MakeException(@"Error", @"Unable to parse environment files for application startup"); } char profile[MAXPATHLEN]; if (NS_FAILED(parser.GetString("Webapp", "Profile", profile, MAXPATHLEN))) { NSLog(@"Unable to retrieve profile from App INI file"); @throw MakeException(@"Error", @"Unable to retrieve installation profile."); } NSLog(@"setting app profile: %s", profile); SetAllocatedString(webShellAppData->profile, profile); } nsCOMPtr directory; if (NS_FAILED(XRE_GetFileFromPath(rtDir, getter_AddRefs(directory)))) { NSLog(@"Unable to open app dir"); @throw MakeException(@"Error", @"Unable to open App directory."); } nsCOMPtr xreDir; if (NS_FAILED(XRE_GetFileFromPath(greDir, getter_AddRefs(xreDir)))) { NSLog(@"Unable to open XRE dir"); @throw MakeException(@"Error", @"Unable to open App XRE directory."); } xreDir.forget(&webShellAppData->xreDirectory); NS_IF_RELEASE(webShellAppData->directory); directory.forget(&webShellAppData->directory); // There is only XUL. result = XRE_main(argc, argv, webShellAppData, 0); XRE_FreeAppData(webShellAppData); } NS_LogTerm(); return result; } } @catch (NSException *e) { NSLog(@"got exception: %@", e); DisplayErrorAlert([e name], [e reason]); } @finally { [pool drain]; } return 0; } //end main NSException* MakeException(NSString* name, NSString* message) { NSException* myException = [NSException exceptionWithName:name reason:message userInfo:nil]; return myException; } void DisplayErrorAlert(NSString* title, NSString* message) { CFUserNotificationDisplayNotice(0, kCFUserNotificationNoteAlertLevel, NULL, NULL, NULL, (CFStringRef)title, (CFStringRef)message, CFSTR("Quit") ); } /* Find the currently installed Firefox, if any, and return * an absolute path to it. may return nil */ NSString *PathToWebRT(NSString* alternateBinaryID) { //default is firefox NSString *binaryPath = nil; //we look for these flavors of Firefox, in this order /* NSArray* launchBinarySearchList = [NSArray arrayWithObjects: @"org.mozilla.nightly", @"org.mozilla.firefoxdeveloperedition", @"org.mozilla.firefox", nil]; */ // If they provided a binary ID, use that. if (alternateBinaryID != nil && ([alternateBinaryID length] > 0)) { binaryPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:alternateBinaryID]; if (binaryPath && [binaryPath length] > 0) { return binaryPath; } } //No override found, loop through the various flavors of firefox we have // for (NSString* signature in launchBinarySearchList) { // We only support this for TenFourFox. NSString *signature = @"com.floodgap.tenfourfox"; NSLog(@"SEARCHING for webapprt, trying: %@", signature); binaryPath = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:signature]; if (binaryPath && [binaryPath length] > 0) { return binaryPath; } // } NSLog(@"unable to find a valid webrt path"); @throw MakeException(@"This App requires that Firefox version 16 or above is installed.", @"Firefox 16+ has not been detected."); return nil; } void ExecNewBinary(NSString* launchPath, NSDictionary* args) { #if(0) NSLog(@" launching webrt at path: %@\n", launchPath); NSUInteger numArgs = [args count]; const char *newargv[numArgs + 2]; NSMutableString *commandLine = [NSMutableString string]; newargv[0] = [launchPath UTF8String]; [commandLine appendString:launchPath]; NSUInteger i = 1; for (id key in args) { NSString *name = [@"-" stringByAppendingString:key]; NSString *value = [args objectForKey:key]; newargv[i++] = [name UTF8String]; newargv[i++] = [value UTF8String]; [commandLine appendFormat:@" %@ %@", name, value]; } newargv[i] = NULL; NSLog(@"Command line: '%@'", commandLine); execv([launchPath UTF8String], (char **)newargv); #else NSLog(@"TenFourFoxBox is better than this"); #endif }