Skip to content
Permalink
dev
Go to file
 
 
Cannot retrieve contributors at this time
533 lines (492 sloc) 19.6 KB
/* GMCodeInjector.m created by Lukas Pitschl (@lukele) on Fri 14-Jun-2013 */
/*
* Copyright (c) 2000-2013, GPGTools Team <team@gpgtools.org>
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of GPGTools nor the names of GPGMail
* contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE GPGTools Team ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE GPGTools Team BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "CCLog.h"
#import "GPGMail_Prefix.pch"
#import "JRLPSwizzle.h"
#import "GPGMailBundle.h"
#import "GMCodeInjector.h"
@implementation GMCodeInjector
+ (NSDictionary *)commonHooks {
return @{
@"MessageHeaderDisplay": @[
@"_attributedStringForSecurityHeader",
@"textView:clickedOnLink:atIndex:"
],
@"ComposeBackEnd": @[
@"_makeMessageWithContents:isDraft:shouldSign:shouldEncrypt:shouldSkipSignature:shouldBePlainText:",
@"canEncryptForRecipients:sender:",
@"canSignFromAddress:",
@"recipientsThatHaveNoKeyForEncryption",
@"setEncryptIfPossible:",
@"setSignIfPossible:",
@"_saveThreadShouldCancel",
@"_configureLastDraftInformationFromHeaders:overwrite:",
@"sender",
@"outgoingMessageUsingWriter:contents:headers:isDraft:shouldBePlainText:",
@"initCreatingDocumentEditor:"
],
@"HeadersEditor": @[
@"securityControlChanged:",
@"_updateFromAndSignatureControls:",
@"changeFromHeader:",
@"dealloc",
@"awakeFromNib",
@"_updateSignButtonTooltip",
@"_updateEncryptButtonTooltip",
@"updateSecurityControls",
@"_updateSecurityStateInBackgroundForRecipients:sender:"
],
@"MailDocumentEditor": @[
@"backEndDidLoadInitialContent:",
@"dealloc",
@"backEnd:didCancelMessageDeliveryForEncryptionError:",
@"backEnd:didCancelMessageDeliveryForError:",
@"initWithBackEnd:",
@"sendMessageAfterChecking:"
],
@"NSWindow": @[
@"toggleFullScreen:"
],
@"MessageContentController": @[
@"setMessageToDisplay:"
],
@"BannerController": @[
@"updateBannerForViewingState:"
],
@"Message": @[],
@"MimePart": @[
@"isEncrypted",
@"newEncryptedPartWithData:recipients:encryptedData:",
@"newSignedPartWithData:sender:signatureData:",
@"verifySignature",
@"decodeWithContext:",
@"decodeTextPlainWithContext:",
@"decodeTextHtmlWithContext:",
@"decodeApplicationOctet_streamWithContext:",
@"isSigned",
@"isMimeSigned",
@"isMimeEncrypted",
@"usesKnownSignatureProtocol",
@"clearCachedDecryptedMessageBody"
],
@"MimeBody": @[
@"isSignedByMe",
@"_isPossiblySignedOrEncrypted"
],
@"MessageCriterion": @[
@"_evaluateIsDigitallySignedCriterion:",
@"_evaluateIsEncryptedCriterion:"
],
@"Library": @[
@"plistDataForMessage:subject:sender:to:dateSent:remoteID:originalMailbox:flags:mergeWithDictionary:",
],
@"MailAccount": @[
@"accountExistsForSigning",
@"completeDeferredAccountInitialization"
],
@"OptionalView": @[
@"widthIncludingOptionSwitch:"
],
@"NSPreferences": @[
@"sharedPreferences",
@"windowWillResize:toSize:",
@"toolbarItemClicked:",
@"showPreferencesPanelForOwner:"
]
};
}
+ (NSDictionary *)hookChangesForMavericks {
return @{
@"Library": @{
@"status": @"renamed",
@"name": @"MFLibrary",
@"selectors": @{
@"replaced": @[
@[
@"plistDataForMessage:subject:sender:to:dateSent:remoteID:originalMailbox:flags:mergeWithDictionary:",
@"plistDataForMessage:subject:sender:to:dateSent:dateReceived:dateLastViewed:remoteID:originalMailboxURLString:gmailLabels:flags:mergeWithDictionary:"
]
]
},
},
@"HeadersEditor": @{
@"selectors": @{
@"renamed": @[
@[
@"_updateSignButtonTooltip",
@"_updateSignButtonToolTip"
],
@[
@"_updateEncryptButtonTooltip",
@"_updateEncryptButtonToolTip"
]
]
}
},
@"EAEmailAddressParser": @{
@"selectors": @[
@"rawAddressFromFullAddress:"
]
},
@"MimePart": @{
@"status": @"renamed",
@"name": @"MCMimePart"
},
@"MimeBody": @{
@"status": @"renamed",
@"name": @"MCMimeBody"
},
@"Message": @{
@"status": @"renamed",
@"name": @"MCMessage",
@"selectors": @{
@"added": @[
@"setMessageInfo:subjectPrefixLength:to:sender:type:dateReceivedTimeIntervalSince1970:dateSentTimeIntervalSince1970:messageIDHeaderDigest:inReplyToHeaderDigest:dateLastViewedTimeIntervalSince1970:"
]
}
},
@"ComposeBackEnd": @{
@"selectors": @{
@"renamed": @[
@[
@"outgoingMessageUsingWriter:contents:headers:isDraft:shouldBePlainText:",
@"newOutgoingMessageUsingWriter:contents:headers:isDraft:shouldBePlainText:"
]
]
}
},
@"MessageCriterion": @{
@"status": @"renamed",
@"name": @"MFMessageCriterion"
},
@"MailAccount": @{
@"status": @"renamed",
@"name": @"MFMailAccount"
},
@"MailDocumentEditor": @{
@"status": @"renamed",
@"name": @"DocumentEditor"
},
@"MessageRouter": @{
@"status": @"renamed",
@"name": @"MFMessageRouter"
},
@"MessageContentController": @{
@"status": @"renamed",
@"name": @"MessageViewController",
@"selectors": @{
@"replaced": @[
@[
@"setMessageToDisplay:",
@"setRepresentedObject:"
]
]
}
},
@"MessageHeaderDisplay": @{
@"status": @"renamed",
@"name": @"HeaderViewController",
@"selectors": @{
@"added": @[
@"_displayStringForSecurityKey",
@"textView:clickedOnCell:inRect:atIndex:",
@"_updateTextStorageWithHardInvalidation:",
@"toggleDetails:"
],
@"removed": @[
@"_attributedStringForSecurityHeader",
@"textView:clickedOnLink:atIndex:"
]
}
},
@"BannerController": @{
@"status": @"removed"
},
@"ConversationMember": @{
@"selectors": @[
@"_reloadSecurityProperties"
]
},
@"WebDocumentGenerator": @{
@"selectors": @[
@"setWebDocument:"
]
},
@"MCMessageGenerator": @{
@"selectors": @[
@"_newDataForMimePart:withPartData:"
]
}
};
}
+ (NSDictionary *)hookChangesForYosemite {
return @{
@"HeadersEditor": @{
@"selectors": @{
@"renamed": @[
@[@"updateSecurityControls",
@"_updateSecurityControls"
]
],
@"removed": @[
@"_updateSignButtonToolTip",
@"_updateEncryptButtonToolTip",
@"toggleDetails",
@"_updateFromAndSignatureControls:"
],
@"added": @[
@"_updateFromControl",
@"setMessageIsToBeEncrypted:",
@"setMessageIsToBeSigned:",
@"setCanSign:",
@"setCanEncrypt:"
]
}
},
@"ComposeBackEnd": @{
@"selectors": @{
@"added": @[
@"setKnowsCanSign:"
]
}
},
@"HeaderViewController": @{
@"selectors": @{
@"removed": @[
@"_displayStringForSecurityKey",
@"toggleDetails:" // TODO: Implement again?
]
}
},
@"ConversationMember": @{
@"selectors": @{
@"removed": @[
@"_reloadSecurityProperties"
]
}
}
};
}
+ (NSDictionary *)hookChangesForElCapitan {
return @{
// Insert the security method switcher into the toolbar as a toolbar item.
@"MailToolbar": @{
@"selectors": @[
@"_plistForToolbarWithIdentifier:"
]
},
@"ComposeWindowController": @{
@"selectors": @[
@"toolbarDefaultItemIdentifiers:",
@"toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:",
@"_performSendAnimation",
@"_tabBarView:performSendAnimationOfTabBarViewItem:"
]
},
@"ComposeBackEnd": @{
@"selectors": @{
@"added": @[
@"init"
],
@"removed": @[
@"initCreatingDocumentEditor:",
]
}
},
@"DocumentEditor": @{
@"status": @"renamed",
@"name": @"ComposeViewController",
@"selectors": @{
@"removed": @[
@"initWithBackEnd:"
],
@"added": @[
@"setDelegate:",
@"backEndDidAppendMessageToOutbox:result:"
]
}
}
};
}
+ (NSDictionary *)hooks {
static dispatch_once_t onceToken;
static NSDictionary *_hooks;
dispatch_once(&onceToken, ^{
NSMutableDictionary *hooks = [[NSMutableDictionary alloc] init];
NSDictionary *commonHooks = [self commonHooks];
// Make a mutable version of all the dictionary.
for(NSString *class in commonHooks)
hooks[class] = [NSMutableArray arrayWithArray:commonHooks[class]];
/* Fix, once we can compile with stable Xcode including 10.9 SDK. */
if(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8)
[self applyHookChangesForVersion:@"10.9" toHooks:hooks];
if(floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_9)
[self applyHookChangesForVersion:@"10.10" toHooks:hooks];
if([GPGMailBundle isElCapitan])
[self applyHookChangesForVersion:@"10.11" toHooks:hooks];
_hooks = [NSDictionary dictionaryWithDictionary:hooks];
});
return _hooks;
}
+ (void)applyHookChangesForVersion:(NSString *)osxVersion toHooks:(NSMutableDictionary *)hooks {
NSDictionary *hookChanges;
if([osxVersion isEqualToString:@"10.9"])
hookChanges = [self hookChangesForMavericks];
else if([osxVersion isEqualToString:@"10.10"])
hookChanges = [self hookChangesForYosemite];
else if([osxVersion isEqualToString:@"10.11"])
hookChanges = [self hookChangesForElCapitan];
for(NSString *class in hookChanges) {
NSDictionary *hook = hookChanges[class];
// Class was added.
if(!hooks[class]) {
// This check is necessary on older systems. 10.10+ has an additional check for nil value.
// If hook[selectors] is nil, it would call removeObjectForKey instead of setObject:forKey.
// Interestingly enough, this is done in the arclite implementation of the sdk this code is compiled on.
// Setting hook[class] to hook[selectors] would crash on previous version if the code was compiled on 10.9 or lower
// since no nil check was added in the arclite implementation of setObject:forKeyedSubscript:
if(hook[@"selectors"]) {
hooks[class] = hook[@"selectors"];
}
continue;
}
// Class was removed.
if([hook[@"status"] isEqualToString:@"removed"]) {
[hooks removeObjectForKey:class];
continue;
}
// Selectors were updated
if(hook[@"selectors"]) {
for(NSString *action in hook[@"selectors"]) {
for(id selector in hook[@"selectors"][action]) {
if([action isEqualToString:@"added"])
[(NSMutableArray *)hooks[class] addObject:selector];
else if([action isEqualToString:@"removed"]) {
NSMutableArray *tempHooks = [hooks[class] mutableCopy];
[tempHooks removeObject:selector];
hooks[class] = tempHooks;
}
else if([action isEqualToString:@"replaced"]) {
[(NSMutableArray *)hooks[class] removeObject:selector[0]];
[(NSMutableArray *)hooks[class] addObject:selector[1]];
}
else if([action isEqualToString:@"renamed"]) {
[(NSMutableArray *)hooks[class] removeObject:selector[0]];
[(NSMutableArray *)hooks[class] addObject:selector];
}
}
}
}
// Class was renamed.
if([hook[@"status"] isEqualToString:@"renamed"]) {
hooks[hook[@"name"]] = hooks[class];
[hooks removeObjectForKey:class];
}
}
}
+ (NSString *)legacyClassNameForName:(NSString *)className {
// Some classes have been renamed in Mavericks.
// This methods converts known classes to their counterparts in Mavericks.
if([@[@"MC", @"MF"] containsObject:[className substringToIndex:2]])
return [className substringFromIndex:2];
if([className isEqualToString:@"DocumentEditor"])
return @"MailDocumentEditor";
if([className isEqualToString:@"MessageViewController"])
return @"MessageContentController";
if([className isEqualToString:@"HeaderViewController"])
return @"MessageHeaderDisplay";
if([GPGMailBundle isElCapitan] && [className isEqualToString:@"ComposeViewController"])
return @"MailDocumentEditor";
return className;
}
+ (void)injectUsingMethodPrefix:(NSString *)prefix hooks:(NSDictionary*)hooks{
/**
This method replaces all of Mail's methods which are necessary for GPGMail
to work correctly.
For each class of Mail that must be extended, a class with the same name
and suffix _GPGMail (<ClassName>_GPGMail) exists which implements the methods
to be relaced.
On runtime, these methods are first added to the original Mail class and
after that, the original Mail methods are swizzled with the ones of the
<ClassName>_GPGMail class.
swizzleMap contains all classes and methods which need to be swizzled.
*/
NSString *extensionClassSuffix = @"GPGMail";
NSError * __autoreleasing error = nil;
for(NSString *class in hooks) {
NSString *oldClass = [[self class] legacyClassNameForName:class];
error = nil;
NSArray *selectors = hooks[class];
Class mailClass = NSClassFromString(class);
if(!mailClass) {
DebugLog(@"WARNING: Class %@ doesn't exist. This leads to unexpected behaviour!", class);
continue;
}
// Check if a class exists with <class>_GPGMail. If that's
// the case, all the methods of that class, have to be added
// to the original Mail or Messages class.
Class extensionClass = NSClassFromString([oldClass stringByAppendingFormat:@"_%@", extensionClassSuffix]);
if(!extensionClass) {
// In order to correctly hook classes on older versions of OS X than 10.9, the MC and MF prefix
// is removed. There are however some cases, where classes where added to 10.9 which didn't exist
// on < 10.9. In those cases, let's try to find the class with the appropriate prefix.
// Try to find extensions to the original classname.
extensionClass = NSClassFromString([class stringByAppendingFormat:@"_%@", extensionClassSuffix]);
}
BOOL extend = extensionClass != nil ? YES : NO;
if(extend) {
if(![mailClass jrlp_addMethodsFromClass:extensionClass error:&error])
DebugLog(@"WARNING: methods of class %@ couldn't be added to %@ - %@", extensionClass,
mailClass, error);
}
// And on to swizzling methods and class methods.
for(id selectorNames in selectors) {
// If the selector changed from one OS X version to the other, selectorNames is an NSArray and
// the selector name of the GPGMail implementation is item 0 and the Mail implementation name is
// item 1.
NSString *gmSelectorName = [selectorNames isKindOfClass:[NSArray class]] ? selectorNames[0] : selectorNames;
NSString *mailSelectorName = [selectorNames isKindOfClass:[NSArray class]] ? selectorNames[1] : selectorNames;
error = nil;
NSString *extensionSelectorName = [NSString stringWithFormat:@"%@%@%@", prefix, [[gmSelectorName substringToIndex:1] uppercaseString],
[gmSelectorName substringFromIndex:1]];
SEL selector = NSSelectorFromString(mailSelectorName);
SEL extensionSelector = NSSelectorFromString(extensionSelectorName);
// First try to add as instance method.
if(![mailClass jrlp_swizzleMethod:selector withMethod:extensionSelector error:&error]) {
// If that didn't work, try to add as class method.
if(![mailClass jrlp_swizzleClassMethod:selector withClassMethod:extensionSelector error:&error])
DebugLog(@"WARNING: %@ doesn't respond to selector %@", NSStringFromClass(mailClass),
NSStringFromSelector(selector));
}
}
}
}
+ (void)injectUsingMethodPrefix:(NSString *)prefix {
[self injectUsingMethodPrefix:prefix hooks:[self hooks]];
}
@end
You can’t perform that action at this time.