Index: components/open_from_clipboard/clipboard_recent_content_impl_ios.mm |
diff --git a/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm b/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..669965ced16c4fa9ff7facfdd14c52765a14e340 |
--- /dev/null |
+++ b/components/open_from_clipboard/clipboard_recent_content_impl_ios.mm |
@@ -0,0 +1,217 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h" |
+ |
+#import <CommonCrypto/CommonDigest.h> |
+#import <UIKit/UIKit.h> |
+ |
+#import "base/mac/foundation_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/sys_info.h" |
+ |
+#if !defined(__has_feature) || !__has_feature(objc_arc) |
+#error "This file requires ARC support." |
+#endif |
+ |
+namespace { |
+// Key used to store the pasteboard's current change count. If when resuming |
+// chrome the pasteboard's change count is different from the stored one, then |
+// it means that the pasteboard's content has changed. |
+NSString* const kPasteboardChangeCountKey = @"PasteboardChangeCount"; |
+// Key used to store the last date at which it was detected that the pasteboard |
+// changed. It is used to evaluate the age of the pasteboard's content. |
+NSString* const kPasteboardChangeDateKey = @"PasteboardChangeDate"; |
+// Key used to store the hash of the content of the pasteboard. Whenever the |
+// hash changed, the pasteboard content is considered to have changed. |
+NSString* const kPasteboardEntryMD5Key = @"PasteboardEntryMD5"; |
+// Maximum age of clipboard in seconds. |
+NSTimeInterval const kMaximumAgeOfClipboard = 3 * 60 * 60; |
+ |
+// Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. |
+// This value is used to detect pasteboard content change. Keeping only 4 bytes |
+// is a privacy requirement to introduce collision and allow deniability of |
+// having copied a given string. |
+NSData* WeakMD5FromNSString(NSString* string) { |
+ unsigned char hash[CC_MD5_DIGEST_LENGTH]; |
+ const std::string clipboard = base::SysNSStringToUTF8(string); |
+ const char* c_string = clipboard.c_str(); |
+ CC_MD5(c_string, strlen(c_string), hash); |
+ NSData* data = [NSData dataWithBytes:hash length:4]; |
+ return data; |
+} |
+ |
+} // namespace |
+ |
+@interface ClipboardRecentContentImplIOS () |
+ |
+// The user defaults from the app group used to optimize the pasteboard change |
+// detection. |
+@property(nonatomic, strong) NSUserDefaults* sharedUserDefaults; |
+// The pasteboard's change count. Increases everytime the pasteboard changes. |
+@property(nonatomic) NSInteger lastPasteboardChangeCount; |
+// MD5 hash of the last registered pasteboard entry. |
+@property(nonatomic, strong) NSData* lastPasteboardEntryMD5; |
+// Contains the authorized schemes for URLs. |
+@property(nonatomic, readonly) NSSet* authorizedSchemes; |
+// Delegate for metrics. |
+@property(nonatomic, strong) id<ClipboardRecentContentDelegate> delegate; |
+ |
+// If the content of the pasteboard has changed, updates the change count, |
+// change date, and md5 of the latest pasteboard entry if necessary. |
+- (void)updateIfNeeded; |
+ |
+// Returns whether the pasteboard changed since the last time a pasteboard |
+// change was detected. |
+- (BOOL)hasPasteboardChanged; |
+ |
+// Loads information from the user defaults about the latest pasteboard entry. |
+- (void)loadFromUserDefaults; |
+ |
+// Returns the URL contained in the clipboard (if any). |
+- (NSURL*)URLFromPasteboard; |
+ |
+// Returns the uptime. |
+- (NSTimeInterval)uptime; |
+ |
+@end |
+ |
+@implementation ClipboardRecentContentImplIOS |
+ |
+@synthesize lastPasteboardChangeCount = _lastPasteboardChangeCount; |
+@synthesize lastPasteboardChangeDate = _lastPasteboardChangeDate; |
+@synthesize lastPasteboardEntryMD5 = _lastPasteboardEntryMD5; |
+@synthesize sharedUserDefaults = _sharedUserDefaults; |
+@synthesize authorizedSchemes = _authorizedSchemes; |
+@synthesize delegate = _delegate; |
+ |
+- (instancetype)initWithAuthorizedSchemes:(NSSet<NSString*>*)authorizedSchemes |
+ userDefaults:(NSUserDefaults*)groupUserDefaults |
+ delegate:(id<ClipboardRecentContentDelegate>) |
+ delegate { |
+ self = [super init]; |
+ if (self) { |
+ _delegate = delegate; |
+ _authorizedSchemes = authorizedSchemes; |
+ _sharedUserDefaults = groupUserDefaults; |
+ |
+ _lastPasteboardChangeCount = NSIntegerMax; |
+ [self loadFromUserDefaults]; |
+ [self updateIfNeeded]; |
+ |
+ // Makes sure |last_pasteboard_change_count_| was properly initialized. |
+ DCHECK_NE(_lastPasteboardChangeCount, NSIntegerMax); |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(didBecomeActive:) |
+ name:UIApplicationDidBecomeActiveNotification |
+ object:nil]; |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+} |
+ |
+- (void)didBecomeActive:(NSNotification*)notification { |
+ [self loadFromUserDefaults]; |
+ [self updateIfNeeded]; |
+} |
+ |
+- (NSData*)getCurrentMD5 { |
+ NSString* pasteboardString = [UIPasteboard generalPasteboard].string; |
+ NSData* md5 = WeakMD5FromNSString(pasteboardString); |
+ |
+ return md5; |
+} |
+ |
+- (BOOL)hasPasteboardChanged { |
+ // If |MD5Changed|, we know for sure there has been at least one pasteboard |
+ // copy since last time it was checked. |
+ // If the pasteboard content is still the same but the device was not |
+ // rebooted, the change count can be checked to see if it changed. |
+ // Note: due to a mismatch between the actual behavior and documentation, and |
+ // lack of consistency on different reboot scenarios, the change count cannot |
+ // be checked after a reboot. |
+ // See radar://21833556 for more information. |
+ BOOL deviceRebooted = [self clipboardContentAge] >= [self uptime]; |
+ if (!deviceRebooted) { |
+ NSInteger changeCount = [UIPasteboard generalPasteboard].changeCount; |
+ bool changeCountChanged = changeCount != self.lastPasteboardChangeCount; |
+ return changeCountChanged; |
+ } |
+ |
+ BOOL md5Changed = |
+ ![[self getCurrentMD5] isEqualToData:self.lastPasteboardEntryMD5]; |
+ return md5Changed; |
+} |
+ |
+- (NSURL*)recentURLFromClipboard { |
+ [self updateIfNeeded]; |
+ if ([self clipboardContentAge] > kMaximumAgeOfClipboard) { |
+ return nil; |
+ } |
+ return [self URLFromPasteboard]; |
+} |
+ |
+- (NSTimeInterval)clipboardContentAge { |
+ return -[self.lastPasteboardChangeDate timeIntervalSinceNow]; |
+} |
+ |
+- (void)suppressClipboardContent { |
+ // User cleared the user data. The pasteboard entry must be removed from the |
+ // omnibox list. Force entry expiration by setting copy date to 1970. |
+ self.lastPasteboardChangeDate = |
+ [[NSDate alloc] initWithTimeIntervalSince1970:0]; |
+ [self saveToUserDefaults]; |
+} |
+ |
+- (void)updateIfNeeded { |
+ if (![self hasPasteboardChanged]) { |
+ return; |
+ } |
+ |
+ [self.delegate onClipboardChanged]; |
+ |
+ self.lastPasteboardChangeDate = [NSDate date]; |
+ self.lastPasteboardChangeCount = [UIPasteboard generalPasteboard].changeCount; |
+ self.lastPasteboardEntryMD5 = [self getCurrentMD5]; |
+ |
+ [self saveToUserDefaults]; |
+} |
+ |
+- (NSURL*)URLFromPasteboard { |
+ NSString* clipboardString = [UIPasteboard generalPasteboard].string; |
+ |
+ NSURL* url = [NSURL URLWithString:clipboardString]; |
+ if (![self.authorizedSchemes containsObject:url.scheme]) { |
+ return nil; |
+ } |
+ return url; |
+} |
+ |
+- (void)loadFromUserDefaults { |
+ self.lastPasteboardChangeCount = |
+ [self.sharedUserDefaults integerForKey:kPasteboardChangeCountKey]; |
+ self.lastPasteboardChangeDate = base::mac::ObjCCastStrict<NSDate>( |
+ [self.sharedUserDefaults objectForKey:kPasteboardChangeDateKey]); |
+ self.lastPasteboardEntryMD5 = base::mac::ObjCCastStrict<NSData>( |
+ [self.sharedUserDefaults objectForKey:kPasteboardEntryMD5Key]); |
+} |
+ |
+- (void)saveToUserDefaults { |
+ [self.sharedUserDefaults setInteger:self.lastPasteboardChangeCount |
+ forKey:kPasteboardChangeCountKey]; |
+ [self.sharedUserDefaults setObject:self.lastPasteboardChangeDate |
+ forKey:kPasteboardChangeDateKey]; |
+ [self.sharedUserDefaults setObject:self.lastPasteboardEntryMD5 |
+ forKey:kPasteboardEntryMD5Key]; |
+} |
+ |
+- (NSTimeInterval)uptime { |
+ return base::SysInfo::Uptime().InSecondsF(); |
+} |
+ |
+@end |