OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "components/open_from_clipboard/clipboard_recent_content_ios.h" | 5 #import "components/open_from_clipboard/clipboard_recent_content_ios.h" |
6 | 6 |
7 #import <CommonCrypto/CommonDigest.h> | 7 #import <CommonCrypto/CommonDigest.h> |
8 #include <stddef.h> | 8 #include <stddef.h> |
9 #include <stdint.h> | 9 #include <stdint.h> |
10 #import <UIKit/UIKit.h> | 10 #import <UIKit/UIKit.h> |
11 | 11 |
12 #import "base/ios/ios_util.h" | 12 #import "base/ios/ios_util.h" |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/macros.h" | 14 #include "base/macros.h" |
15 #include "base/metrics/user_metrics.h" | 15 #include "base/metrics/user_metrics.h" |
16 #include "base/strings/sys_string_conversions.h" | 16 #include "base/strings/sys_string_conversions.h" |
17 #include "base/sys_info.h" | 17 #include "base/sys_info.h" |
| 18 #import "components/open_from_clipboard/clipboard_recent_content_impl_ios.h" |
| 19 #import "net/base/mac/url_conversions.h" |
18 #include "url/gurl.h" | 20 #include "url/gurl.h" |
| 21 #include "url/url_constants.h" |
19 | 22 |
20 // Bridge that forwards UIApplicationDidBecomeActiveNotification notifications | 23 namespace { |
21 // to its delegate. | |
22 @interface ApplicationDidBecomeActiveNotificationListenerBridge : NSObject | |
23 | 24 |
24 // Initialize the ApplicationDidBecomeActiveNotificationListenerBridge with | 25 // Schemes accepted by the ClipboardRecentContentIOS. |
25 // |delegate| which must not be null. | 26 const char* kAuthorizedSchemes[] = { |
26 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate | 27 url::kHttpScheme, url::kHttpsScheme, url::kDataScheme, url::kAboutScheme, |
27 NS_DESIGNATED_INITIALIZER; | 28 }; |
28 | 29 |
29 - (instancetype)init NS_UNAVAILABLE; | 30 // Get the list of authorized schemes. |
| 31 NSSet<NSString*>* getAuthorizedSchemeList( |
| 32 const std::string& application_scheme) { |
| 33 NSMutableSet<NSString*>* schemes = [NSMutableSet set]; |
| 34 for (size_t i = 0; i < arraysize(kAuthorizedSchemes); ++i) { |
| 35 [schemes addObject:base::SysUTF8ToNSString(kAuthorizedSchemes[i])]; |
| 36 } |
| 37 if (!application_scheme.empty()) { |
| 38 [schemes addObject:base::SysUTF8ToNSString(application_scheme)]; |
| 39 } |
30 | 40 |
| 41 return [schemes copy]; |
| 42 } |
| 43 |
| 44 } // namespace |
| 45 |
| 46 @interface ClipboardRecentContentDelegateImpl |
| 47 : NSObject<ClipboardRecentContentDelegate> |
31 @end | 48 @end |
32 | 49 |
33 @implementation ApplicationDidBecomeActiveNotificationListenerBridge { | 50 @implementation ClipboardRecentContentDelegateImpl |
34 ClipboardRecentContentIOS* _delegate; | |
35 } | |
36 | 51 |
37 - (instancetype)init { | 52 - (void)onClipboardChanged { |
38 NOTREACHED(); | 53 base::RecordAction(base::UserMetricsAction("MobileOmniboxClipboardChanged")); |
39 return nil; | |
40 } | |
41 | |
42 - (instancetype)initWithDelegate:(ClipboardRecentContentIOS*)delegate { | |
43 DCHECK(delegate); | |
44 self = [super init]; | |
45 if (self) { | |
46 _delegate = delegate; | |
47 [[NSNotificationCenter defaultCenter] | |
48 addObserver:self | |
49 selector:@selector(didBecomeActive:) | |
50 name:UIApplicationDidBecomeActiveNotification | |
51 object:nil]; | |
52 } | |
53 return self; | |
54 } | |
55 | |
56 - (void)dealloc { | |
57 [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
58 [super dealloc]; | |
59 } | |
60 | |
61 - (void)didBecomeActive:(NSNotification*)notification { | |
62 if (_delegate) { | |
63 _delegate->LoadFromUserDefaults(); | |
64 _delegate->UpdateIfNeeded(); | |
65 } | |
66 } | |
67 | |
68 - (void)disconnect { | |
69 _delegate = nullptr; | |
70 } | 54 } |
71 | 55 |
72 @end | 56 @end |
73 | 57 |
74 namespace { | 58 ClipboardRecentContentIOS::ClipboardRecentContentIOS( |
75 // Key used to store the pasteboard's current change count. If when resuming | 59 const std::string& application_scheme, |
76 // chrome the pasteboard's change count is different from the stored one, then | 60 NSUserDefaults* group_user_defaults) |
77 // it means that the pasteboard's content has changed. | 61 : ClipboardRecentContentIOS([[ClipboardRecentContentImplIOS alloc] |
78 NSString* kPasteboardChangeCountKey = @"PasteboardChangeCount"; | 62 initWithAuthorizedSchemes:getAuthorizedSchemeList(application_scheme) |
79 // Key used to store the last date at which it was detected that the pasteboard | 63 userDefaults:group_user_defaults |
80 // changed. It is used to evaluate the age of the pasteboard's content. | 64 delegate:[[ClipboardRecentContentDelegateImpl alloc] |
81 NSString* kPasteboardChangeDateKey = @"PasteboardChangeDate"; | 65 init]]) {} |
82 // Key used to store the hash of the content of the pasteboard. Whenever the | |
83 // hash changed, the pasteboard content is considered to have changed. | |
84 NSString* kPasteboardEntryMD5Key = @"PasteboardEntryMD5"; | |
85 | 66 |
86 // Compute a hash consisting of the first 4 bytes of the MD5 hash of |string|. | 67 ClipboardRecentContentIOS::ClipboardRecentContentIOS( |
87 // This value is used to detect pasteboard content change. Keeping only 4 bytes | 68 ClipboardRecentContentImplIOS* implementation) { |
88 // is a privacy requirement to introduce collision and allow deniability of | 69 implementation_.reset(implementation); |
89 // having copied a given string. | |
90 NSData* WeakMD5FromNSString(NSString* string) { | |
91 unsigned char hash[CC_MD5_DIGEST_LENGTH]; | |
92 const std::string clipboard = base::SysNSStringToUTF8(string); | |
93 const char* c_string = clipboard.c_str(); | |
94 CC_MD5(c_string, strlen(c_string), hash); | |
95 NSData* data = [NSData dataWithBytes:hash length:4]; | |
96 return data; | |
97 } | 70 } |
98 | 71 |
99 } // namespace | |
100 | |
101 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) { | 72 bool ClipboardRecentContentIOS::GetRecentURLFromClipboard(GURL* url) { |
102 DCHECK(url); | 73 DCHECK(url); |
103 UpdateIfNeeded(); | 74 NSURL* url_from_pasteboard = [implementation_ recentURLFromClipboard]; |
104 if (GetClipboardContentAge() > kMaximumAgeOfClipboard) { | 75 GURL converted_url = net::GURLWithNSURL(url_from_pasteboard); |
105 return false; | 76 if (converted_url.is_valid()) { |
106 } | 77 *url = std::move(converted_url); |
107 | |
108 GURL url_from_pasteboard = URLFromPasteboard(); | |
109 if (url_from_pasteboard.is_valid()) { | |
110 *url = url_from_pasteboard; | |
111 return true; | 78 return true; |
112 } | 79 } |
113 return false; | 80 return false; |
114 } | 81 } |
115 | 82 |
| 83 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() {} |
| 84 |
116 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { | 85 base::TimeDelta ClipboardRecentContentIOS::GetClipboardContentAge() const { |
117 return base::TimeDelta::FromSeconds(static_cast<int64_t>( | 86 return base::TimeDelta::FromSeconds( |
118 -[last_pasteboard_change_date_ timeIntervalSinceNow])); | 87 static_cast<int64_t>([implementation_ clipboardContentAge])); |
119 } | 88 } |
120 | 89 |
121 void ClipboardRecentContentIOS::SuppressClipboardContent() { | 90 void ClipboardRecentContentIOS::SuppressClipboardContent() { |
122 // User cleared the user data. The pasteboard entry must be removed from the | 91 [implementation_ suppressClipboardContent]; |
123 // omnibox list. Force entry expiration by setting copy date to 1970. | |
124 last_pasteboard_change_date_.reset( | |
125 [[NSDate alloc] initWithTimeIntervalSince1970:0]); | |
126 SaveToUserDefaults(); | |
127 } | 92 } |
128 | |
129 void ClipboardRecentContentIOS::UpdateIfNeeded() { | |
130 if (!HasPasteboardChanged()) | |
131 return; | |
132 | |
133 base::RecordAction(base::UserMetricsAction("MobileClipboardChanged")); | |
134 | |
135 GURL url_from_pasteboard = URLFromPasteboard(); | |
136 last_pasteboard_change_date_.reset([[NSDate date] retain]); | |
137 last_pasteboard_change_count_ = [UIPasteboard generalPasteboard].changeCount; | |
138 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string]; | |
139 if (!pasteboard_string) { | |
140 pasteboard_string = @""; | |
141 } | |
142 NSData* MD5 = WeakMD5FromNSString(pasteboard_string); | |
143 last_pasteboard_entry_md5_.reset([MD5 retain]); | |
144 SaveToUserDefaults(); | |
145 } | |
146 | |
147 ClipboardRecentContentIOS::ClipboardRecentContentIOS( | |
148 const std::string& application_scheme, | |
149 NSUserDefaults* group_user_defaults) | |
150 : application_scheme_(application_scheme), | |
151 shared_user_defaults_([group_user_defaults retain]) { | |
152 last_pasteboard_change_count_ = NSIntegerMax; | |
153 LoadFromUserDefaults(); | |
154 | |
155 UpdateIfNeeded(); | |
156 | |
157 // Makes sure |last_pasteboard_change_count_| was properly initialized. | |
158 DCHECK_NE(last_pasteboard_change_count_, NSIntegerMax); | |
159 notification_bridge_.reset( | |
160 [[ApplicationDidBecomeActiveNotificationListenerBridge alloc] | |
161 initWithDelegate:this]); | |
162 } | |
163 | |
164 bool ClipboardRecentContentIOS::HasPasteboardChanged() const { | |
165 // If |MD5Changed|, we know for sure there has been at least one pasteboard | |
166 // copy since last time it was checked. | |
167 // If the pasteboard content is still the same but the device was not | |
168 // rebooted, the change count can be checked to see if it changed. | |
169 // Note: due to a mismatch between the actual behavior and documentation, and | |
170 // lack of consistency on different reboot scenarios, the change count cannot | |
171 // be checked after a reboot. | |
172 // See radar://21833556 for more information. | |
173 NSInteger change_count = [UIPasteboard generalPasteboard].changeCount; | |
174 bool change_count_changed = change_count != last_pasteboard_change_count_; | |
175 | |
176 bool not_rebooted = Uptime() > GetClipboardContentAge(); | |
177 if (not_rebooted) | |
178 return change_count_changed; | |
179 | |
180 NSString* pasteboard_string = [[UIPasteboard generalPasteboard] string]; | |
181 if (!pasteboard_string) { | |
182 pasteboard_string = @""; | |
183 } | |
184 NSData* md5 = WeakMD5FromNSString(pasteboard_string); | |
185 BOOL md5_changed = ![md5 isEqualToData:last_pasteboard_entry_md5_]; | |
186 | |
187 return md5_changed; | |
188 } | |
189 | |
190 ClipboardRecentContentIOS::~ClipboardRecentContentIOS() { | |
191 [notification_bridge_ disconnect]; | |
192 } | |
193 | |
194 GURL ClipboardRecentContentIOS::URLFromPasteboard() { | |
195 NSString* clipboard_string = [[UIPasteboard generalPasteboard] string]; | |
196 if (!clipboard_string) { | |
197 return GURL::EmptyGURL(); | |
198 } | |
199 const std::string clipboard = base::SysNSStringToUTF8(clipboard_string); | |
200 GURL gurl = GURL(clipboard); | |
201 if (gurl.is_valid()) { | |
202 if (IsAppropriateSuggestion(gurl)) | |
203 return gurl; | |
204 if (!application_scheme_.empty() && | |
205 gurl.SchemeIs(application_scheme_.c_str())) { | |
206 return gurl; | |
207 } | |
208 } | |
209 return GURL::EmptyGURL(); | |
210 } | |
211 | |
212 void ClipboardRecentContentIOS::LoadFromUserDefaults() { | |
213 last_pasteboard_change_count_ = | |
214 [shared_user_defaults_ integerForKey:kPasteboardChangeCountKey]; | |
215 last_pasteboard_change_date_.reset( | |
216 [[shared_user_defaults_ objectForKey:kPasteboardChangeDateKey] retain]); | |
217 last_pasteboard_entry_md5_.reset( | |
218 [[shared_user_defaults_ objectForKey:kPasteboardEntryMD5Key] retain]); | |
219 | |
220 DCHECK(!last_pasteboard_change_date_ || | |
221 [last_pasteboard_change_date_ isKindOfClass:[NSDate class]]); | |
222 } | |
223 | |
224 void ClipboardRecentContentIOS::SaveToUserDefaults() { | |
225 [shared_user_defaults_ setInteger:last_pasteboard_change_count_ | |
226 forKey:kPasteboardChangeCountKey]; | |
227 [shared_user_defaults_ setObject:last_pasteboard_change_date_ | |
228 forKey:kPasteboardChangeDateKey]; | |
229 [shared_user_defaults_ setObject:last_pasteboard_entry_md5_ | |
230 forKey:kPasteboardEntryMD5Key]; | |
231 } | |
232 | |
233 base::TimeDelta ClipboardRecentContentIOS::Uptime() const { | |
234 return base::SysInfo::Uptime(); | |
235 } | |
OLD | NEW |