summaryrefslogtreecommitdiff
path: root/QSDeliciousPlugIn_Source.m
diff options
context:
space:
mode:
Diffstat (limited to 'QSDeliciousPlugIn_Source.m')
-rw-r--r--QSDeliciousPlugIn_Source.m315
1 files changed, 166 insertions, 149 deletions
diff --git a/QSDeliciousPlugIn_Source.m b/QSDeliciousPlugIn_Source.m
index fb09a97..55eedf0 100644
--- a/QSDeliciousPlugIn_Source.m
+++ b/QSDeliciousPlugIn_Source.m
@@ -9,9 +9,21 @@
#import "QSDeliciousPlugIn_Source.h"
#import <QSCore/QSCore.h>
#import <Security/Security.h>
+#import <os/log.h>
@implementation QSDeliciousPlugIn_Source
+#pragma mark - Lifecycle
+
+// This method will get called whenever we change which
+// active entry is selected.
+- (void)setSelectedEntry:(id)selectedEntry {
+ [super setSelectedEntry:selectedEntry];
+ [self loadPasswordFromKeychain];
+}
+
+#pragma mark - Quicksilver Source Methods
+
- (BOOL)indexIsValidFromDate:(NSDate *)indexDate forEntry:(NSDictionary *)theEntry {
return -[indexDate timeIntervalSinceNow] < 24 * 60 * 60;
}
@@ -48,7 +60,7 @@
}
- (NSString *)currentPassword {
- return [self.selectedEntry.sourceSettings objectForKey:@"password"];
+ return self.internalPassword;
}
- (BOOL)includeTags {
@@ -56,174 +68,159 @@
}
+// This method is called on action from all the NIB methods
+// to force the catalog to save the current values.
- (IBAction)settingsChanged:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:QSCatalogEntryChangedNotification object:self.selectedEntry];
}
-#pragma mark - Keychain Access
-- (SecProtocolType)protocolTypeForString:(NSString *)protocol {
- if ([protocol isEqualToString:@"ftp"]) return kSecProtocolTypeFTP;
- else if ([protocol isEqualToString:@"http"]) return kSecProtocolTypeHTTP;
- else if ([protocol isEqualToString:@"sftp"]) return kSecProtocolTypeFTPS;
- else if ([protocol isEqualToString:@"eppc"]) return kSecProtocolTypeEPPC;
- else if ([protocol isEqualToString:@"afp"]) return kSecProtocolTypeAFP;
- else if ([protocol isEqualToString:@"smb"]) return kSecProtocolTypeSMB;
- else if ([protocol isEqualToString:@"ssh"]) return kSecProtocolTypeSSH;
- else if ([protocol isEqualToString:@"telnet"]) return kSecProtocolTypeTelnet;
- return 0;
+#pragma mark - Keychain Helper Methods
+
+- (NSString *)keychainKeyForIdentifier:(NSString *)identifier {
+ return [NSString stringWithFormat:@"QSSocialBookmarks-%@", identifier];
}
-- (NSString *)passwordForHost:(NSString *)host user:(NSString *)user andType:(SecProtocolType)type {
- const char *buffer;
- UInt32 length = 0;
- OSErr err;
-
- err = SecKeychainFindInternetPassword(NULL,
- (UInt32)[host length], [host UTF8String],
- 0,
- NULL,
- (UInt32)[user length], [user UTF8String],
- 0, NULL,
- 0,
- type,
- 0,
- &length, (void**)&buffer,
- NULL);
-
- if (err == noErr) {
- NSString *password = [NSString stringWithUTF8String:buffer];
- SecKeychainItemFreeContent(NULL,(void *)buffer);
- return password;
- }
- return nil;
+- (NSString *)passwordFromKeychainForKey:(NSString *)key {
+ const char *service = "QSSocialBookmarks";
+ const char *account = [key UTF8String];
+
+ UInt32 passwordLength = 0;
+ void *passwordData = NULL;
+
+ OSStatus status = SecKeychainFindGenericPassword(NULL,
+ (UInt32)strlen(service), service,
+ (UInt32)strlen(account), account,
+ &passwordLength, &passwordData,
+ NULL);
+
+ if (status == errSecSuccess && passwordData != NULL) {
+ NSString *password = [[NSString alloc] initWithBytes:passwordData
+ length:passwordLength
+ encoding:NSUTF8StringEncoding];
+ SecKeychainItemFreeContent(NULL, passwordData);
+ return password;
+ }
+
+ return nil;
}
-- (NSString *)passwordForHost:(NSString *)host user:(NSString *)user andScheme:(NSString *)scheme {
- NSString *password = nil;
-
- SecProtocolType type = [self protocolTypeForString:scheme];
-
- password = [self passwordForHost:host user:user andType:type];
-
- if (!password && type == kSecProtocolTypeFTP)
- password = [self passwordForHost:host user:user andType:kSecProtocolTypeFTPAccount]; // Workaround for Transmit's old type usage
- if ( !password )
- password = [self passwordForHost:host user:user andType:0];
- if ( !password )
- NSLog(@"Couldn't find password. URL:%@ %@ %@", host, user,scheme );
- return password;
+- (OSStatus)savePasswordToKeychainForKey:(NSString *)key password:(NSString *)password {
+ const char *service = "QSSocialBookmarks";
+ const char *account = [key UTF8String];
+ const char *passwordCString = [password UTF8String];
+
+ // First try to find existing item
+ SecKeychainItemRef item = NULL;
+ OSStatus findStatus = SecKeychainFindGenericPassword(NULL,
+ (UInt32)strlen(service), service,
+ (UInt32)strlen(account), account,
+ NULL, NULL,
+ &item);
+
+ OSStatus status;
+ if (findStatus == errSecSuccess) {
+ // Update existing item
+ status = SecKeychainItemModifyAttributesAndData(item,
+ NULL,
+ (UInt32)strlen(passwordCString),
+ passwordCString);
+ CFRelease(item);
+ } else {
+ // Create new item
+ status = SecKeychainAddGenericPassword(NULL,
+ (UInt32)strlen(service), service,
+ (UInt32)strlen(account), account,
+ (UInt32)strlen(passwordCString), passwordCString,
+ NULL);
+ }
+
+ return status;
}
-- (NSString *)keychainPasswordForURL:(NSURL *)url {
- return [self passwordForHost:[url host] user:[url user] andScheme:[url scheme]];
+- (OSStatus)deletePasswordFromKeychainForKey:(NSString *)key {
+ const char *service = "QSSocialBookmarks";
+ const char *account = [key UTF8String];
+
+ SecKeychainItemRef item = NULL;
+ OSStatus findStatus = SecKeychainFindGenericPassword(NULL,
+ (UInt32)strlen(service), service,
+ (UInt32)strlen(account), account,
+ NULL, NULL,
+ &item);
+
+ if (findStatus == errSecSuccess) {
+ OSStatus deleteStatus = SecKeychainItemDelete(item);
+ CFRelease(item);
+ return deleteStatus;
+ }
+
+ return findStatus;
}
-- (OSErr)addURLPasswordToKeychain:(NSURL *)url {
- OSErr err;
-
- NSString *host = [url host];
- NSString *user = [url user];
- NSString *pass = [url password];
-
- SecProtocolType type = [self protocolTypeForString:[url scheme]];
-
- SecKeychainItemRef existing = NULL;
-
- err = SecKeychainFindInternetPassword(NULL,
- (UInt32)[host length], [host UTF8String],
- 0, NULL,
- (UInt32)[user length], [user UTF8String],
- 0, NULL,
- 0,
- type,
- 0,
- NULL,NULL,
- &existing);
-
- if ( !err ) {
- err = SecKeychainItemModifyContent( existing, NULL, (UInt32)[pass length], [pass UTF8String] );
- CFRelease( existing );
- } else {
- err = SecKeychainAddInternetPassword(NULL,
- (UInt32)[host length], [host UTF8String],
- 0, NULL,
- (UInt32)[user length], [user UTF8String],
- 0, NULL,
- 0,
- type,
- 0,
- (UInt32)[pass length], [pass UTF8String],
- NULL);
+#pragma mark - Password Keychain Methods
+
+- (void)loadPasswordFromKeychain {
+ if (!self.selectedEntry || !self.selectedEntry.identifier) {
+ self.internalPassword = nil;
+ return;
}
-
- return err;
+
+ NSString *keychainKey = [self keychainKeyForIdentifier:self.selectedEntry.identifier];
+ [self setPassword: [self passwordFromKeychainForKey:keychainKey]];
}
-- (NSString *)oldCurrentPassword {
- NSString *account = [self currentUsername];
- if (!account) return nil;
-
- SocialSite site = [self siteIndex];
- NSString *host = nil;
+- (void)savePasswordToKeychain {
+ if (!self.selectedEntry || !self.selectedEntry.identifier || !self.internalPassword) {
+ return;
+ }
- // For Linkding, use the custom host; for others, use the standard site URL
- if (site == SocialSiteLinkding) {
- host = [self currentHost];
- if (!host) return nil;
- } else {
- host = [SocialSiteHelper siteURLForSite:site];
+ NSString *keychainKey = [self keychainKeyForIdentifier:self.selectedEntry.identifier];
+ OSStatus status = [self savePasswordToKeychainForKey:keychainKey password:self.internalPassword];
+
+ if (status != errSecSuccess) {
+ NSLog(@"Failed to save password to keychain for key: %@, status: %d", keychainKey, (int)status);
}
-
- NSURL *keychainURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@@%@/", account, host]];
- NSString *password = [self keychainPasswordForURL:keychainURL];
-
- return password;
}
-- (void)setCurrentPassword:(NSString *)newPassword {
- NSString *account = [self currentUsername];
- if (!account) return;
- if ([newPassword length] <= 0) return;
-
- SocialSite site = [self siteIndex];
- NSString *host = nil;
+#pragma mark - Password Property Accessors
+
+- (NSString *)password {
+ return self.internalPassword;
+}
+
+- (void)setPassword:(NSString *)password {
+ self.internalPassword = password;
- // For Linkding, use the custom host; for others, use the standard site URL
- if (site == SocialSiteLinkding) {
- host = [self currentHost];
- if (!host) return;
- } else {
- host = [SocialSiteHelper siteURLForSite:site];
- }
-
- NSURL *keychainURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%@@%@/", account, newPassword, host]];
-
- [self addURLPasswordToKeychain:keychainURL];
+ // Save to keychain asynchronously
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ [self savePasswordToKeychain];
+ });
}
#pragma mark - Objects For Entry
-- (NSArray *)objectsForEntry:(NSDictionary *)theEntry {
- NSLog(@"WE HAVE BEEN REQUESTED");
+- (NSArray *)objectsForEntry:(QSCatalogEntry *)theEntry {
- SocialSite site = [self siteIndex];
- NSString *username = [self currentUsername];
- NSString *password = [self currentPassword];
- NSString *host = [self currentHost];
- BOOL includeTags = [self includeTags];
+ NSDictionary *settings = theEntry.sourceSettings;
+
+ SocialSite site = [settings objectForKey:@"site"] != nil ? [[settings objectForKey:@"site"] integerValue] : SocialSiteDelicious;
+ NSString *username = [settings objectForKey:@"username"];
+ NSString *identifier = theEntry.identifier;
+ NSString *keychainKey = [self keychainKeyForIdentifier:identifier];
+ NSString *password = [self passwordFromKeychainForKey:keychainKey];
+ NSString *host = [settings objectForKey:@"host"];
+ BOOL includeTags = [settings objectForKey:@"includeTags"];
- // Get the appropriate provider using the factory
QSBookmarkProviderFactory *factory = [QSBookmarkProviderFactory sharedFactory];
id<QSBookmarkProvider> provider = [factory providerForSite:site username:username password:password host:host];
- NSLog(@"Checking for %ld, user %@, pass %@, host %@", (long)site, username, password, host);
if (!provider) {
NSLog(@"No provider available for site %ld with username %@", (long)site, username);
return @[];
}
- return [provider fetchBookmarksForSite:site username:username password:password host:host includeTags:includeTags];
+ return [provider fetchBookmarksForSite:site username:username password:password identifier:identifier host:host includeTags:includeTags];
}
- (NSArray *)objectsForTag:(NSString *)tag username:(NSString *)username {
@@ -254,10 +251,37 @@
[object setIcon:[[NSBundle bundleForClass:[self class]]imageNamed:@"bookmark_icon"]];
}
+// This will receive a tag object. Tag objects will have the
+// source configuration in the meta: source.username,
+// source.site, source.host and source.identifier.
- (BOOL)loadChildrenForObject:(QSObject *)object {
- SocialSite site = [self siteIndex];
-
- NSString *tagType = nil;
+
+ NSNumber *siteNumber = [object objectForMeta:@"source.site"];
+ if (!siteNumber) {
+ NSLog(@"The tag didn't have a valid site.");
+ return NO;
+ }
+
+ SocialSite site = [siteNumber integerValue];
+
+ NSString *username = [object objectForMeta:@"source.username"];
+ if (!username) {
+ NSLog(@"The tag didn't have a valid username.");
+ return NO;
+ }
+ NSString *identifier = [object objectForMeta:@"source.identifier"];
+ if (!identifier) {
+ NSLog(@"The tag didn't have a valid identifier.");
+ return NO;
+ }
+
+ NSString *host = [object objectForMeta:@"source.host"];
+ if (site == SocialSiteLinkding && !host) {
+ NSLog(@"The tag didn't have a host, and its site requires it.");
+ return NO;
+ }
+
+ NSString *tagType = nil;
if (site == SocialSiteLinkding) {
tagType = @"tag.linkding";
} else {
@@ -265,18 +289,11 @@
tagType = [NSString stringWithFormat:@"tag.%@", reversedURL];
}
- NSString *tag = [object objectForType:tagType];
- if (!tag) return NO;
-
- NSString *username = nil;
- if (site == SocialSiteLinkding) {
- username = [object objectForMeta:@"linkding.username"];
- } else {
- NSString *reversedURL = [SocialSiteHelper reversedSiteURLForSite:site];
- username = [object objectForMeta:[NSString stringWithFormat:@"%@.username", reversedURL]];
- }
-
- if (!username) return NO;
+ NSString *tag = [object objectForType:tagType];
+ if (!tag) {
+ NSLog(@"We could not find a valid tag type.");
+ return NO;
+ }
NSArray *children = [self objectsForTag:tag username:username];
[object setChildren:children];