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