1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/autofill/core/browser/personal_data_manager.h" 6 7#include <math.h> 8 9#import <AddressBook/AddressBook.h> 10 11#include "base/format_macros.h" 12#include "base/guid.h" 13#include "base/logging.h" 14#import "base/mac/scoped_nsexception_enabler.h" 15#include "base/memory/scoped_ptr.h" 16#include "base/memory/scoped_vector.h" 17#include "base/metrics/histogram.h" 18#include "base/prefs/pref_service.h" 19#include "base/strings/stringprintf.h" 20#include "base/strings/sys_string_conversions.h" 21#include "components/autofill/core/browser/autofill_country.h" 22#include "components/autofill/core/browser/autofill_profile.h" 23#include "components/autofill/core/browser/autofill_type.h" 24#include "components/autofill/core/browser/form_structure.h" 25#include "components/autofill/core/browser/phone_number.h" 26#include "components/autofill/core/common/autofill_pref_names.h" 27#include "components/autofill/core/common/form_data.h" 28#include "grit/components_strings.h" 29#include "ui/base/l10n/l10n_util_mac.h" 30 31namespace autofill { 32namespace { 33 34// The maximum number of instances when the access Address Book prompt should 35// be shown. 36int kMaxTimesToShowMacAddressBook = 5; 37 38// There is an uncommon sequence of events that causes the Address Book 39// permissions dialog to appear more than once for a given install of Chrome. 40// 1. Chrome has previously presented the Address Book permissions dialog. 41// 2. Chrome is launched. 42// 3. Chrome performs an auto-update, and changes its binary. 43// 4. Chrome attempts to access the Address Book for the first time since (2). 44// This sequence of events is rare because Chrome attempts to acess the Address 45// Book when the user focuses most form fields, so (4) generally occurs before 46// (3). For more details, see http://crbug.com/381763. 47// 48// When this sequence of events does occur, Chrome should not attempt to access 49// the Address Book unless the user explicitly asks Chrome to do so. The 50// jarring nature of the permissions dialog is worse than the potential benefit 51// of pulling information from the Address Book. 52// 53// Set to true after the Address Book is accessed for the first time. 54static bool g_accessed_address_book = false; 55 56// Set to true after the Chrome binary has been changed. 57static bool g_binary_changed = false; 58 59const char kAddressBookOrigin[] = "OS X Address Book"; 60 61// Whether Chrome has attempted to access the Mac Address Book. 62bool HasQueriedMacAddressBook(PrefService* pref_service) { 63 return pref_service->GetBoolean(prefs::kAutofillMacAddressBookQueried); 64} 65 66// Whether the user wants Chrome to use the AddressBook to populate Autofill 67// entries. 68bool ShouldUseAddressBook(PrefService* pref_service) { 69 return pref_service->GetBoolean(prefs::kAutofillUseMacAddressBook); 70} 71 72// Records a UMA metric indicating whether an attempt to access the Address 73// Book was skipped because doing so would cause the Address Book permissions 74// prompt to incorrectly appear. 75void RecordAccessSkipped(bool skipped) { 76 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBook.AccessSkipped", skipped); 77} 78 79ABAddressBook* GetAddressBook(PrefService* pref_service) { 80 bool first_access = !HasQueriedMacAddressBook(pref_service); 81 82 // +[ABAddressBook sharedAddressBook] throws an exception internally in 83 // circumstances that aren't clear. The exceptions are only observed in crash 84 // reports, so it is unknown whether they would be caught by AppKit and nil 85 // returned, or if they would take down the app. In either case, avoid 86 // crashing. http://crbug.com/129022 87 ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions( 88 ^{ return [ABAddressBook sharedAddressBook]; }); 89 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailable", addressBook != nil); 90 91 if (first_access) { 92 UMA_HISTOGRAM_BOOLEAN("Autofill.AddressBookAvailableOnFirstAttempt", 93 addressBook != nil); 94 } 95 96 g_accessed_address_book = true; 97 pref_service->SetBoolean(prefs::kAutofillMacAddressBookQueried, true); 98 return addressBook; 99} 100 101// This implementation makes use of the Address Book API. Profiles are 102// generated that correspond to addresses in the "me" card that reside in the 103// user's Address Book. The caller passes a vector of profiles into the 104// the constructer and then initiate the fetch from the Mac Address Book "me" 105// card using the main |GetAddressBookMeCard()| method. This clears any 106// existing addresses and populates new addresses derived from the data found 107// in the "me" card. 108class AuxiliaryProfilesImpl { 109 public: 110 // Constructor takes a reference to the |profiles| that will be filled in 111 // by the subsequent call to |GetAddressBookMeCard()|. |profiles| may not 112 // be NULL. 113 explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles) 114 : profiles_(*profiles) { 115 } 116 virtual ~AuxiliaryProfilesImpl() {} 117 118 // Import the "me" card from the Mac Address Book and fill in |profiles_|. 119 void GetAddressBookMeCard(const std::string& app_locale, 120 PrefService* pref_service, 121 bool record_metrics); 122 123 private: 124 void GetAddressBookNames(ABPerson* me, 125 NSString* addressLabelRaw, 126 AutofillProfile* profile); 127 void GetAddressBookAddress(const std::string& app_locale, 128 NSDictionary* address, 129 AutofillProfile* profile); 130 void GetAddressBookEmail(ABPerson* me, 131 NSString* addressLabelRaw, 132 AutofillProfile* profile); 133 void GetAddressBookPhoneNumbers(ABPerson* me, 134 NSString* addressLabelRaw, 135 AutofillProfile* profile); 136 137 private: 138 // A reference to the profiles this class populates. 139 ScopedVector<AutofillProfile>& profiles_; 140 141 DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl); 142}; 143 144// This method uses the |ABAddressBook| system service to fetch the "me" card 145// from the active user's address book. It looks for the user address 146// information and translates it to the internal list of |AutofillProfile| data 147// structures. 148void AuxiliaryProfilesImpl::GetAddressBookMeCard(const std::string& app_locale, 149 PrefService* pref_service, 150 bool record_metrics) { 151 profiles_.clear(); 152 153 // The user does not want Chrome to use the AddressBook to populate Autofill 154 // entries. 155 if (!ShouldUseAddressBook(pref_service)) 156 return; 157 158 // See the comment at the definition of g_accessed_address_book for an 159 // explanation of this logic. 160 if (g_binary_changed && !g_accessed_address_book) { 161 if (record_metrics) 162 RecordAccessSkipped(true); 163 return; 164 } 165 166 if (record_metrics) 167 RecordAccessSkipped(false); 168 169 ABAddressBook* addressBook = GetAddressBook(pref_service); 170 171 ABPerson* me = [addressBook me]; 172 if (!me) 173 return; 174 175 ABMultiValue* addresses = [me valueForProperty:kABAddressProperty]; 176 177 // The number of characters at the end of the GUID to reserve for 178 // distinguishing addresses within the "me" card. Cap the number of addresses 179 // we will fetch to the number that can be distinguished by this fragment of 180 // the GUID. 181 const size_t kNumAddressGUIDChars = 2; 182 const size_t kNumHexDigits = 16; 183 const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars); 184 NSUInteger count = MIN([addresses count], kMaxAddressCount); 185 for (NSUInteger i = 0; i < count; i++) { 186 NSDictionary* address = [addresses valueAtIndex:i]; 187 NSString* addressLabelRaw = [addresses labelAtIndex:i]; 188 189 // Create a new profile where the guid is set to the guid portion of the 190 // |kABUIDProperty| taken from from the "me" address. The format of 191 // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the 192 // raw guid here and using it directly, with one modification: we update the 193 // last |kNumAddressGUIDChars| characters in the GUID to reflect the address 194 // variant. Note that we capped the number of addresses above, so this is 195 // safe. 196 const size_t kGUIDLength = 36U; 197 const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars; 198 std::string guid = base::SysNSStringToUTF8( 199 [me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength); 200 201 // The format string to print |kNumAddressGUIDChars| hexadecimal characters, 202 // left-padded with 0's. 203 const std::string kAddressGUIDFormat = 204 base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars); 205 guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i); 206 DCHECK_EQ(kGUIDLength, guid.size()); 207 208 scoped_ptr<AutofillProfile> profile( 209 new AutofillProfile(guid, kAddressBookOrigin)); 210 DCHECK(base::IsValidGUID(profile->guid())); 211 212 // Fill in name and company information. 213 GetAddressBookNames(me, addressLabelRaw, profile.get()); 214 215 // Fill in address information. 216 GetAddressBookAddress(app_locale, address, profile.get()); 217 218 // Fill in email information. 219 GetAddressBookEmail(me, addressLabelRaw, profile.get()); 220 221 // Fill in phone number information. 222 GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get()); 223 224 profiles_.push_back(profile.release()); 225 } 226} 227 228// Name and company information is stored once in the Address Book against 229// multiple addresses. We replicate that information for each profile. 230// We only propagate the company name to work profiles. 231void AuxiliaryProfilesImpl::GetAddressBookNames( 232 ABPerson* me, 233 NSString* addressLabelRaw, 234 AutofillProfile* profile) { 235 NSString* firstName = [me valueForProperty:kABFirstNameProperty]; 236 NSString* middleName = [me valueForProperty:kABMiddleNameProperty]; 237 NSString* lastName = [me valueForProperty:kABLastNameProperty]; 238 NSString* companyName = [me valueForProperty:kABOrganizationProperty]; 239 240 profile->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName)); 241 profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName)); 242 profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName)); 243 if ([addressLabelRaw isEqualToString:kABAddressWorkLabel]) 244 profile->SetRawInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName)); 245} 246 247// Addresss information from the Address Book may span multiple lines. 248// If it does then we represent the address with two lines in the profile. The 249// second line we join with commas. 250// For example: "c/o John Doe\n1122 Other Avenue\nApt #7" translates to 251// line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7". 252void AuxiliaryProfilesImpl::GetAddressBookAddress(const std::string& app_locale, 253 NSDictionary* address, 254 AutofillProfile* profile) { 255 if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) { 256 // If there are newlines in the address, split into two lines. 257 if ([addressField rangeOfCharacterFromSet: 258 [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { 259 NSArray* chunks = [addressField componentsSeparatedByCharactersInSet: 260 [NSCharacterSet newlineCharacterSet]]; 261 DCHECK([chunks count] > 1); 262 263 NSString* separator = 264 l10n_util::GetNSString(IDS_AUTOFILL_ADDRESS_LINE_SEPARATOR); 265 266 NSString* addressField1 = [chunks objectAtIndex:0]; 267 NSString* addressField2 = 268 [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)] 269 componentsJoinedByString:separator]; 270 profile->SetRawInfo(ADDRESS_HOME_LINE1, 271 base::SysNSStringToUTF16(addressField1)); 272 profile->SetRawInfo(ADDRESS_HOME_LINE2, 273 base::SysNSStringToUTF16(addressField2)); 274 } else { 275 profile->SetRawInfo(ADDRESS_HOME_LINE1, 276 base::SysNSStringToUTF16(addressField)); 277 } 278 } 279 280 if (NSString* city = [address objectForKey:kABAddressCityKey]) 281 profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city)); 282 283 if (NSString* state = [address objectForKey:kABAddressStateKey]) 284 profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state)); 285 286 if (NSString* zip = [address objectForKey:kABAddressZIPKey]) 287 profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip)); 288 289 if (NSString* country = [address objectForKey:kABAddressCountryKey]) { 290 profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY), 291 base::SysNSStringToUTF16(country), 292 app_locale); 293 } 294} 295 296// Fills in email address matching current address label. Note that there may 297// be multiple matching email addresses for a given label. We take the 298// first we find (topmost) as preferred. 299void AuxiliaryProfilesImpl::GetAddressBookEmail( 300 ABPerson* me, 301 NSString* addressLabelRaw, 302 AutofillProfile* profile) { 303 ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty]; 304 NSString* emailAddress = nil; 305 for (NSUInteger j = 0, emailCount = [emailAddresses count]; 306 j < emailCount; j++) { 307 NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j]; 308 if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) { 309 emailAddress = [emailAddresses valueAtIndex:j]; 310 break; 311 } 312 } 313 profile->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress)); 314} 315 316// Fills in telephone numbers. Each of these are special cases. 317// We match two cases: home/tel, work/tel. 318// Note, we traverse in reverse order so that top values in address book 319// take priority. 320void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers( 321 ABPerson* me, 322 NSString* addressLabelRaw, 323 AutofillProfile* profile) { 324 ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty]; 325 for (NSUInteger k = 0, phoneCount = [phoneNumbers count]; 326 k < phoneCount; k++) { 327 NSUInteger reverseK = phoneCount - k - 1; 328 NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK]; 329 if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] && 330 [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) { 331 base::string16 homePhone = base::SysNSStringToUTF16( 332 [phoneNumbers valueAtIndex:reverseK]); 333 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone); 334 } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] && 335 [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) { 336 base::string16 workPhone = base::SysNSStringToUTF16( 337 [phoneNumbers valueAtIndex:reverseK]); 338 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone); 339 } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] || 340 [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) { 341 base::string16 phone = base::SysNSStringToUTF16( 342 [phoneNumbers valueAtIndex:reverseK]); 343 profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone); 344 } 345 } 346} 347 348} // namespace 349 350// Populate |auxiliary_profiles_| with the Address Book data. 351void PersonalDataManager::LoadAuxiliaryProfiles(bool record_metrics) const { 352 AuxiliaryProfilesImpl impl(&auxiliary_profiles_); 353 impl.GetAddressBookMeCard(app_locale_, pref_service_, record_metrics); 354} 355 356bool PersonalDataManager::AccessAddressBook() { 357 // The user is attempting to give Chrome access to the user's Address Book. 358 // This implicitly acknowledges that the user wants to use auxiliary 359 // profiles. 360 pref_service_->SetBoolean(prefs::kAutofillUseMacAddressBook, true); 361 362 // Request permissions. 363 GetAddressBook(pref_service_); 364 return true; 365} 366 367bool PersonalDataManager::ShouldShowAccessAddressBookSuggestion( 368 AutofillType type) { 369 // Don't show the access Address Book prompt if the user has built up any 370 // Autofill state. 371 if (!web_profiles_.empty()) 372 return false; 373 374 if (!enabled_pref_->GetValue()) 375 return false; 376 377 if (HasQueriedMacAddressBook(pref_service_)) 378 return false; 379 380 if (AccessAddressBookPromptCount() >= kMaxTimesToShowMacAddressBook) 381 return false; 382 383 switch (type.group()) { 384 case ADDRESS_BILLING: 385 case ADDRESS_HOME: 386 case EMAIL: 387 case NAME: 388 case NAME_BILLING: 389 case PHONE_BILLING: 390 case PHONE_HOME: 391 return true; 392 case NO_GROUP: 393 case COMPANY: 394 case CREDIT_CARD: 395 case PASSWORD_FIELD: 396 case TRANSACTION: 397 return false; 398 } 399 400 return false; 401} 402 403void PersonalDataManager::ShowedAccessAddressBookPrompt() { 404 pref_service_->SetInteger(prefs::kAutofillMacAddressBookShowedCount, 405 AccessAddressBookPromptCount() + 1); 406} 407 408int PersonalDataManager::AccessAddressBookPromptCount() { 409 return pref_service_->GetInteger(prefs::kAutofillMacAddressBookShowedCount); 410} 411 412void PersonalDataManager::BinaryChanging() { 413 g_binary_changed = true; 414} 415 416} // namespace autofill 417