// // MGMSpruceSSL.m // SpruceSSL // // Created by Mr. Gecko on 10/31/10. // Copyright (c) 2010 Mr. Gecko's Media (James Coleman). All rights reserved. http://mrgeckosmedia.com/ // #import "MGMSpruceSSL.h" #import "MGMWebViewMethods.h" #import "MGMSpruceURL.h" #import "Safari.h" #import #import #import #import @protocol MGMFileManagerProtocol - (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error; - (BOOL)createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes; - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error; - (BOOL)removeFileAtPath:(NSString *)path handler:(id)handler; - (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)copyPath:(NSString *)source toPath:(NSString *)destination handler:(id)handler; - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)movePath:(NSString *)source toPath:(NSString *)destination handler:(id)handler; @end NSString * const MGMSpruceSSLDBChanged = @"MGMSpruceSSLDBChanged"; NSString * const MGMChangeCookies = @"MGMChangeCookies"; NSString * const MGMApplicationSupportPath = @"~/Library/Application Support/MrGeckosMedia/SpruceSSL/"; NSString * const MGMHostWhitelist = @"hostWhitelist.plist"; NSString * const MGMHostBlacklist = @"hostBlacklist.plist"; static MGMSpruceSSL *MGMSpruceSSLShared; void _objc_flush_caches(Class cls); void MGMSwizzle(Class class, SEL selector1, SEL selector2) { #if OBJC_API_VERSION >= 2 Method method1 = class_getInstanceMethod(class, selector1); Method method2 = class_getInstanceMethod(class, selector2); class_addMethod(class, selector1, class_getMethodImplementation(class, selector1), method_getTypeEncoding(method1)); class_addMethod(class, selector2, class_getMethodImplementation(class, selector2), method_getTypeEncoding(method2)); if (method1==NULL || method2==NULL) { NSLog(@"Unable to swizzle %@ with %@ in class %@", NSStringFromSelector(selector1), NSStringFromSelector(selector2), NSStringFromClass(class)); return; } method_exchangeImplementations(class_getInstanceMethod(class, selector1), class_getInstanceMethod(class, selector2)); #else Method method1 = NULL, method2 = NULL; void *iterator = NULL; struct objc_method_list *list = class_nextMethodList(class, &iterator); while (list!=NULL) { for (int i=0; imethod_count; i++) { if (list->method_list[i].method_name==selector1 && method1==NULL) method1 = &list->method_list[i]; if (list->method_list[i].method_name==selector2 && method2==NULL) method2 = &list->method_list[i]; } if (method1!=NULL) break; list = class_nextMethodList(class, &iterator); } if (method1==NULL || method2==NULL) { NSLog(@"Unable to swizzle %@ with %@ in class %@", NSStringFromSelector(selector1), NSStringFromSelector(selector2), NSStringFromClass(class)); return; } IMP implementation1 = method1->method_imp; IMP implementation2 = method2->method_imp; method1->method_imp = implementation2; method2->method_imp = implementation1; #endif _objc_flush_caches(class); } @implementation MGMSpruceSSL + (void)initialize { [self sharedController]; } + (void)load { [self sharedController]; } + (MGMSpruceSSL *)sharedController { @synchronized(self) { if (MGMSpruceSSLShared==nil) [[self alloc] init]; } return MGMSpruceSSLShared; } + (id)allocWithZone:(NSZone *)zone { @synchronized(self) { if (MGMSpruceSSLShared==nil) { MGMSpruceSSLShared = [super allocWithZone:zone]; return MGMSpruceSSLShared; } } return nil; } - (id)init { if (self = [super init]) { NSString *identifier = [[NSBundle mainBundle] bundleIdentifier]; NSArray *supportedIdentifiers = [[[NSBundle bundleForClass:[MGMSpruceSSL class]] infoDictionary] objectForKey:@"MGMSupported"]; BOOL shouldLoad = NO; for (int i=0; i<[supportedIdentifiers count]; i++) { if ([[supportedIdentifiers objectAtIndex:i] isEqual:identifier] || ([[supportedIdentifiers objectAtIndex:i] hasSuffix:@"."] && [identifier hasPrefix:[supportedIdentifiers objectAtIndex:i]])) { shouldLoad = YES; break; } } if (!shouldLoad) { [super release]; return nil; } updater = [SUUpdater alloc]; if ([updater respondsToSelector:@selector(initForBundle:)]) { [updater initForBundle:[NSBundle bundleForClass:[MGMSpruceSSL class]]]; } else if ([updater respondsToSelector:@selector(setHostBundle:)]) { [updater init]; [updater performSelector:@selector(setHostBundle:) withObject:[NSBundle bundleForClass:[MGMSpruceSSL class]]]; } else { [updater release]; updater = nil; } NSFileManager *manager = [NSFileManager defaultManager]; if (![manager fileExistsAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath]]) { if ([manager respondsToSelector:@selector(createDirectoryAtPath:attributes:)]) { [manager createDirectoryAtPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByDeletingLastPathComponent] attributes:nil]; [manager createDirectoryAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath] attributes:nil]; } else { [manager createDirectoryAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath] withIntermediateDirectories:YES attributes:nil error:nil]; } } if (![manager fileExistsAtPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostWhitelist]]) { whitelist = [[NSMutableArray arrayWithContentsOfFile:[[[NSBundle bundleForClass:[MGMSpruceSSL class]] resourcePath] stringByAppendingPathComponent:MGMHostWhitelist]] retain]; [whitelist writeToFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostWhitelist] atomically:NO]; } else { whitelist = [[NSMutableArray arrayWithContentsOfFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostWhitelist]] retain]; } if (![manager fileExistsAtPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostBlacklist]]) { blacklist = [[NSMutableArray arrayWithContentsOfFile:[[[NSBundle bundleForClass:[MGMSpruceSSL class]] resourcePath] stringByAppendingPathComponent:MGMHostBlacklist]] retain]; [blacklist writeToFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostBlacklist] atomically:NO]; } else { blacklist = [[NSMutableArray arrayWithContentsOfFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostBlacklist]] retain]; } MGMSwizzle([NSURLConnection class], @selector(initWithRequest:delegate:startImmediately:), @selector(MGMInitWithRequest:delegate:startImmediately:)); MGMSwizzle([NSURLConnection class], @selector(initWithRequest:delegate:), @selector(MGMInitWithRequest:delegate:)); MGMSwizzle([NSURLConnection class], @selector(initWithRequest:delegate:priority:), @selector(MGMInitWithRequest:delegate:priority:)); MGMSwizzle([NSURLConnection class], @selector(_initWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:), @selector(MGMInitWithRequest:delegate:usesCache:maxContentLength:startImmediately:connectionProperties:)); [NSURLProtocol registerClass:[MGMSpruceURL class]]; NSMenu *applicationMenu = [[[NSApp mainMenu] itemAtIndex:0] submenu]; for (int i=0; i<[applicationMenu numberOfItems]; i++) { if ([[applicationMenu itemAtIndex:i] isSeparatorItem]) { SpruceSSLMenu = [[[NSMenuItem alloc] initWithTitle:@"SpruceSSL Preferences" action:@selector(showPreferences:) keyEquivalent:@""] autorelease]; [SpruceSSLMenu setTarget:self]; [applicationMenu insertItem:SpruceSSLMenu atIndex:i]; break; } } NSNotificationCenter *notifications = [NSNotificationCenter defaultCenter]; [notifications addObserver:self selector:@selector(willTerminate) name:NSApplicationWillTerminateNotification object:nil]; [notifications addObserver:self selector:@selector(cookieCheck:) name:WebViewProgressFinishedNotification object:nil]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(dbChanged) name:MGMSpruceSSLDBChanged object:nil]; NSLog(@"SpruceSSL loaded successfully"); } return self; } - (id)copyWithZone:(NSZone *)zone { return self; } - (id)retain { return self; } - (NSUInteger)retainCount { return UINT_MAX; } - (void)release { } - (id)autorelease { return self; } - (void)willTerminate { [[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; [updater release]; [blacklist release]; [whitelist release]; } - (void)registerDefaults { NSMutableDictionary *defaults = [NSMutableDictionary dictionary]; [defaults setObject:[NSNumber numberWithInt:0] forKey:MGMChangeCookies]; [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; } - (void)dbChanged:(NSNotification *)theNotification { if ([theNotification object]!=self) { [whitelist release]; whitelist = [[NSMutableArray arrayWithContentsOfFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostWhitelist]] retain]; [blacklist release]; blacklist = [[NSMutableArray arrayWithContentsOfFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostBlacklist]] retain]; if (preferencesWindow!=nil) { [whitelistTable reloadData]; [blacklistTable reloadData]; } } } - (void)saveDB { [whitelist writeToFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostWhitelist] atomically:NO]; [blacklist writeToFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMHostBlacklist] atomically:NO]; [[NSDistributedNotificationCenter defaultCenter] postNotificationName:MGMSpruceSSLDBChanged object:self]; } - (IBAction)showPreferences:(id)sender { if (preferencesWindow==nil) { if (![NSBundle loadNibNamed:@"preferences" owner:self]) { NSLog(@"Unable to load preferences for SpruceSSL"); } else { NSBundle *bundle = [NSBundle bundleForClass:[MGMSpruceSSL class]]; [nameField setStringValue:[NSString stringWithFormat:@"%@ %@", [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey], [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]]]; [changeCookiesMatrix selectCellAtRow:[[NSUserDefaults standardUserDefaults] integerForKey:MGMChangeCookies] column:0]; [wlRemoveButton setEnabled:NO]; [blRemoveButton setEnabled:NO]; } } [preferencesWindow makeKeyAndOrderFront:self]; } - (IBAction)changeCookies:(id)sender { [[NSUserDefaults standardUserDefaults] setInteger:[changeCookiesMatrix selectedRow] forKey:MGMChangeCookies]; } - (IBAction)checkForUpdate:(id)sender { [updater checkForUpdates:sender]; } - (IBAction)donate:(id)sender { [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SA4DEZGVJSNAL"]]; } - (NSInteger)numberOfRowsInTableView:(NSTableView *)theTableView { if (theTableView==whitelistTable) return [whitelist count]; return [blacklist count]; } - (id)tableView:(NSTableView *)theTableView objectValueForTableColumn:(NSTableColumn *)theTableColumn row:(NSInteger)rowIndex { if (theTableView==whitelistTable) return [whitelist objectAtIndex:rowIndex]; return [blacklist objectAtIndex:rowIndex]; } - (void)tableView:(NSTableView *)theTableView setObjectValue:(id)theObject forTableColumn:(NSTableColumn *)theTableColumn row:(NSInteger)rowIndex { if (theTableView==whitelistTable) { [whitelist replaceObjectAtIndex:rowIndex withObject:theObject]; [self saveDB]; } else { [blacklist replaceObjectAtIndex:rowIndex withObject:theObject]; [self saveDB]; } } - (void)tableViewSelectionDidChange:(NSNotification *)theNotification { if ([theNotification object]==whitelistTable) [wlRemoveButton setEnabled:([whitelistTable selectedRow]>=0)]; else [blRemoveButton setEnabled:([blacklistTable selectedRow]>=0)]; } - (IBAction)wlAdd:(id)sender { [whitelist addObject:@".example.com"]; [whitelistTable reloadData]; [whitelistTable editColumn:0 row:[whitelist count]-1 withEvent:nil select:YES]; [self saveDB]; } - (IBAction)wlRemove:(id)sender { [whitelist removeObjectAtIndex:[whitelistTable selectedRow]]; [whitelistTable reloadData]; [self saveDB]; } - (IBAction)blAdd:(id)sender { [blacklist addObject:@".example.com"]; [blacklistTable reloadData]; [blacklistTable editColumn:0 row:[blacklist count]-1 withEvent:nil select:YES]; [self saveDB]; } - (IBAction)blRemove:(id)sender { [blacklist removeObjectAtIndex:[blacklistTable selectedRow]]; [blacklistTable reloadData]; [self saveDB]; } - (void)windowWillClose:(NSNotification *)theNotification { preferencesWindow = nil; } - (void)cookieCheck:(NSNotification *)theNotification { if ([[theNotification object] mainFrameURL]!=nil) { #if MGMSpruceDebug NSLog(@"Cookie check %@", [[theNotification object] mainFrameURL]); #endif MGMSpruceSSL *spruceSSL = [MGMSpruceSSL sharedController]; NSURL *url = [NSURL URLWithString:[[theNotification object] mainFrameURL]]; if ([url host]!=nil && [[url scheme] isEqual:@"https"] && (([[NSUserDefaults standardUserDefaults] integerForKey:MGMChangeCookies]==1 && [spruceSSL isSSLForHost:[url host]]) || ([[NSUserDefaults standardUserDefaults] integerForKey:MGMChangeCookies]==2 && ![spruceSSL isHostBlackListed:[url host]]))) { NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; NSArray *cookies = [storage cookiesForURL:url]; for (unsigned int i=0; i<[cookies count]; i++) { if (![[cookies objectAtIndex:i] isSecure]) { NSMutableDictionary *cookie = [[[cookies objectAtIndex:i] properties] mutableCopy]; [cookie setObject:[NSNumber numberWithBool:YES] forKey:NSHTTPCookieSecure]; [storage setCookie:[NSHTTPCookie cookieWithProperties:cookie]]; [cookie release]; } } } } } - (NSURLRequest *)requestForRequest:(NSURLRequest *)request { if ([[request URL] host]!=nil && [[[request URL] scheme] isEqual:@"http"] && [self isSSLForHost:[[request URL] host]]) { NSMutableURLRequest *sslRequest = [[request mutableCopy] autorelease]; NSString *url = [[sslRequest URL] absoluteString]; NSRange range = [url rangeOfString:@":"]; url = [@"sprucessl" stringByAppendingString:[url substringFromIndex:range.location]]; #if MGMSpruceDebug NSLog(@"%@", url); #endif [sslRequest setURL:[NSURL URLWithString:url]]; return sslRequest; } return request; } - (BOOL)doesList:(NSArray *)theList containHost:(NSString *)theHost { for (unsigned int i=0; i<[theList count]; i++) { NSString *host = [theList objectAtIndex:i]; if ([host hasPrefix:@"."]) { if ([host isEqual:[@"." stringByAppendingString:theHost]]) { return YES; } else { NSMutableArray *domainComponets = [NSMutableArray arrayWithArray:[theHost componentsSeparatedByString:@"."]]; for (int d=0; d<[domainComponets count]; d++) { [domainComponets removeObjectAtIndex:0]; if ([host isEqual:[@"." stringByAppendingString:[domainComponets componentsJoinedByString:@"."]]]) { return YES; break; } } } } else if ([host isEqual:theHost]) { return YES; } } return NO; } - (BOOL)isHostBlackListed:(NSString *)theHost { return [self doesList:blacklist containHost:theHost]; } - (BOOL)isHostWhiteListed:(NSString *)theHost { return [self doesList:whitelist containHost:theHost]; } - (BOOL)isSSLForHost:(NSString *)theHost { if ([self isHostBlackListed:theHost]) return NO; return [self isHostWhiteListed:theHost]; } @end