Browse files

Clear cached requests and pictures when session is closeAndClear-ed

Summary:
When FBSession.closeAndClearTokenInformation() is called, we can go ahead and clear out some of the cached data.
Cached requests will have the session's accessToken in the url query params - which can be used as a filter
Stuff from akamai (images) does not have the same. So we just clear all to err on the side of safety w.r.t. privacy

Test Plan:
Manual testing of Scrumptious with lots of NSLogging.
Multiple login/logouts with different users.

Reviewers: jacl, clang

Reviewed By: jacl

CC: msdkexp@

Differential Revision: https://phabricator.fb.com/D551216

Task ID: 1129032
  • Loading branch information...
1 parent ef39fd4 commit f7771707abafedb50170fc2a11d02da4d97d6024 @laughingguitarist laughingguitarist committed Aug 16, 2012
Showing with 130 additions and 15 deletions.
  1. +3 −0 src/FBCacheIndex.h
  2. +100 −14 src/FBCacheIndex.m
  3. +2 −0 src/FBDataDiskCache.h
  4. +22 −0 src/FBDataDiskCache.m
  5. +1 −1 src/FBSDKVersion-generated.h
  6. +2 −0 src/FBSession.m
View
3 src/FBCacheIndex.h
@@ -47,6 +47,8 @@
sqlite3_stmt* _insertStatement;
sqlite3_stmt* _removeByKeyStatement;
sqlite3_stmt* _selectByKeyStatement;
+ sqlite3_stmt* _selectByKeyFragmentStatement;
+ sqlite3_stmt* _selectExcludingKeyFragmentStatement;
sqlite3_stmt* _trimStatement;
sqlite3_stmt* _updateStatement;
@@ -64,6 +66,7 @@
- (NSString*)fileNameForKey:(NSString*)key;
- (NSString*)storeFileForKey:(NSString*)key withData:(NSData*)data;
- (void)removeEntryForKey:(NSString*)key;
+- (void)removeEntries:(NSString*)keyFragment excludingFragment:(BOOL)exclude;
@end
View
114 src/FBCacheIndex.m
@@ -50,7 +50,13 @@
static const char* selectByKeyQuery =
"SELECT uuid, key, access_time, file_size FROM cache_index WHERE key = ?";
-
+
+static const char* selectByKeyFragmentQuery =
+ "SELECT uuid, key, access_time, file_size FROM cache_index WHERE key LIKE ?";
+
+static const char* selectExcludingKeyFragmentQuery =
+ "SELECT uuid, key, access_time, file_size FROM cache_index WHERE key NOT LIKE ?";
+
static const char* selectStorageSizeQuery =
"SELECT SUM(file_size) FROM cache_index";
@@ -123,6 +129,8 @@ @interface FBCacheIndex() <NSCacheDelegate>
- (FBCacheEntityInfo*)_entryForKey:(NSString*)key;
- (void)_fetchCurrentDiskUsage;
- (FBCacheEntityInfo*)_readEntryFromDatabase:(NSString*)key;
+- (NSMutableArray*) _readEntriesFromDatabase: (NSString*)keyFragment excludingFragment:(BOOL)exclude;
+- (FBCacheEntityInfo*)_createCacheEntityInfo:(sqlite3_stmt*)selectStatement;
- (void)_removeEntryFromDatabaseForKey:(NSString*)key;
- (void)_trimDatabase;
- (void)_updateEntryInDatabaseForKey:(NSString*)key
@@ -203,12 +211,16 @@ - (void)dealloc {
sqlite3* const db = _database;
sqlite3_stmt* const is = _insertStatement;
sqlite3_stmt* const sbks = _selectByKeyStatement;
+ sqlite3_stmt* const sbkfs = _selectByKeyFragmentStatement;
+ sqlite3_stmt* const sekfs = _selectExcludingKeyFragmentStatement;
sqlite3_stmt* const rbks = _removeByKeyStatement;
sqlite3_stmt* const ts = _trimStatement;
sqlite3_stmt* const us = _updateStatement;
dispatch_async(_databaseQueue, ^{
releaseStatement(is, nil);
releaseStatement(sbks, nil);
+ releaseStatement(sbkfs, nil);
+ releaseStatement(sekfs, nil);
releaseStatement(rbks, nil);
releaseStatement(ts, nil);
releaseStatement(us, nil);
@@ -302,6 +314,34 @@ - (void)removeEntryForKey:(NSString*)key
});
}
+- (void)removeEntries:(NSString*)keyFragment excludingFragment:(BOOL)exclude
+{
+ if (keyFragment == nil) {
+ return;
+ }
+
+ __block NSMutableArray* entries;
+
+ dispatch_sync(_databaseQueue, ^{
+ entries = [self _readEntriesFromDatabase:keyFragment excludingFragment:exclude];
+ });
+
+ for (FBCacheEntityInfo* entry in entries) {
+ if ([_cachedEntries objectForKey:entry.key] == nil) {
+ // Adding to the cache since the call to removeEntryForKey will look for the entry and
+ // try to retrieve it from the DB which will in turn add it to the cache anyways. So
+ // pre-emptively adding it to the in memory cache saves some DB roundtrips.
+ //
+ // This is only done for NSCache entries that don't already exist since replacing the
+ // old one with the new one will trigger willEvictObject which will try and perform
+ // a DB write. Since the write is async, we might end up in a weird state.
+ [_cachedEntries setObject:entry forKey:entry.key];
+ }
+
+ [self removeEntryForKey:entry.key];
+ }
+}
+
#pragma mark - NSCache delegate
- (void)cache:(NSCache*)cache willEvictObject:(id)obj
@@ -357,6 +397,7 @@ - (void)_writeEntryInDatabase:(FBCacheEntityInfo*)entry
FBCacheEntityInfo* existing = [self _readEntryFromDatabase:entry.key];
if (existing) {
+
// Entry already exists - update the entry
[self _updateEntryInDatabaseForKey:existing.key
entry:entry];
@@ -410,24 +451,68 @@ - (FBCacheEntityInfo*)_readEntryFromDatabase:(NSString*)key
key.length,
nil), _database);
- int result = sqlite3_step(_selectByKeyStatement);
+ return [self _createCacheEntityInfo:_selectByKeyStatement];
+}
+
+- (NSMutableArray*) _readEntriesFromDatabase:(NSString*)keyFragment
+ excludingFragment:(BOOL)exclude
+{
+ NSAssert(dispatch_get_current_queue() == _databaseQueue, @"");
+
+ sqlite3_stmt* selectStatement;
+ const char* query;
+ if (exclude) {
+ selectStatement = _selectExcludingKeyFragmentStatement;
+ query = selectExcludingKeyFragmentQuery;
+ } else {
+ selectStatement = _selectByKeyFragmentStatement;
+ query = selectByKeyFragmentQuery;
+ }
+
+ initializeStatement(_database, &selectStatement, query);
+ NSString* wildcardKeyFragment = [NSString stringWithFormat:@"%%%@%%", keyFragment];
+
+ CHECK_SQLITE_SUCCESS(sqlite3_bind_text(
+ selectStatement,
+ 1,
+ wildcardKeyFragment.UTF8String,
+ wildcardKeyFragment.length,
+ nil), _database);
+
+ NSMutableArray *entries = [[[NSMutableArray alloc] init] autorelease];
+ FBCacheEntityInfo* entry;
+
+ while ((entry = [self _createCacheEntityInfo:selectStatement]) != nil) {
+ [entries addObject:entry];
+ }
+
+ return entries;
+}
+
+-(FBCacheEntityInfo*)_createCacheEntityInfo:(sqlite3_stmt*)selectStatement
+{
+ int result = sqlite3_step(selectStatement);
if (result != SQLITE_ROW) {
return nil;
}
-
- const unsigned char* uuidStr =
- sqlite3_column_text(_selectByKeyStatement, 0);
- CFTimeInterval accessTime =
- sqlite3_column_double(_selectByKeyStatement, 2);
- NSUInteger fileSize = sqlite3_column_int(_selectByKeyStatement, 3);
+ const unsigned char* uuidStr =
+ sqlite3_column_text(selectStatement, 0);
+ const unsigned char* key =
+ sqlite3_column_text(selectStatement, 1);
+ CFTimeInterval accessTime =
+ sqlite3_column_double(selectStatement, 2);
+ NSUInteger fileSize = sqlite3_column_int(selectStatement, 3);
+
FBCacheEntityInfo* entry = [[FBCacheEntityInfo alloc]
- initWithKey:key
- uuid:[NSString
- stringWithCString:(const char*)uuidStr
- encoding:NSUTF8StringEncoding]
- accessTime:accessTime
- fileSize:fileSize];
+ initWithKey:[NSString
+ stringWithCString:(const char*)key
+ encoding:NSUTF8StringEncoding]
+ uuid:[NSString
+ stringWithCString:(const char*)uuidStr
+ encoding:NSUTF8StringEncoding]
+ accessTime:accessTime
+ fileSize:fileSize];
return [entry autorelease];
}
@@ -466,6 +551,7 @@ - (FBCacheEntityInfo*)_entryForKey:(NSString*)key
- (void)_removeEntryFromDatabaseForKey:(NSString*)key
{
NSAssert(dispatch_get_current_queue() == _databaseQueue, @"");
+
initializeStatement(_database, &_removeByKeyStatement, deleteEntryQuery);
CHECK_SQLITE_SUCCESS(sqlite3_bind_text(
_removeByKeyStatement,
View
2 src/FBDataDiskCache.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
+#import "FBSession.h"
@class FBCacheIndex;
@@ -38,5 +39,6 @@
- (NSData*)dataForURL:(NSURL*)dataURL;
- (void)setData:(NSData*)data forURL:(NSURL*)url;
- (void)removeDataForUrl:(NSURL*)url;
+- (void)removeDataForSession:(FBSession*)session;
@end
View
22 src/FBDataDiskCache.m
@@ -22,6 +22,7 @@
static NSString* const kDataDiskCachePath = @"DataDiskCache";
static NSString* const kCacheInfoFile = @"CacheInfo";
+static NSString *const kAccessTokenKey = @"access_token";
@interface FBDataDiskCache() <FBCacheIndexFileDelegate>
@property (nonatomic, copy) NSString* dataCachePath;
@@ -185,6 +186,27 @@ - (void)removeDataForUrl:(NSURL*)url
}
}
+- (void)removeDataForSession:(FBSession*)session
+{
+ if (session == nil) {
+ return;
+ }
+
+ // Here we are removing all cache entries that don't have session context
+ // These are things like images and the like. The thorough way would
+ // be to maintain refCounts of these entries associated with accessTokens
+ // and use that to decide which images to delete. However, this might be
+ // overkill for a cache. Maybe revisit later?
+ [_cacheIndex removeEntries:kAccessTokenKey excludingFragment:YES];
+
+ NSString* accessToken = [session accessToken];
+ if (accessToken != nil) {
+ // Here we are removing all cache entries that have this session's access
+ // token in the url.
+ [_cacheIndex removeEntries:accessToken excludingFragment:NO];
+ }
+}
+
- (void)setData:(NSData*)data forURL:(NSURL*)url
{
// TODO: Synchronize this across threads
View
2 src/FBSDKVersion-generated.h
@@ -1 +1 @@
-#define FB_IOS_SDK_VERSION_STRING @"3.0.8"
+#define FB_IOS_SDK_VERSION_STRING @"3.0.8-6-g932f4a1"
View
2 src/FBSession.m
@@ -25,6 +25,7 @@
#import "FBError.h"
#import "FBLogger.h"
#import "FBUtility.h"
+#import "FBDataDiskCache.h"
// the sooner we can remove these the better
#import "Facebook.h"
@@ -358,6 +359,7 @@ - (void)close {
- (void)closeAndClearTokenInformation {
NSAssert(self.affinitizedThread == [NSThread currentThread], @"FBSession: should only be used from a single thread");
+ [[FBDataDiskCache sharedCache] removeDataForSession:self];
[self.tokenCachingStrategy clearToken];
[self transitionAndCallHandlerWithState:FBSessionStateClosed
error:nil

0 comments on commit f777170

Please sign in to comment.