// Copyright (c) 2011 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. #include "chrome/browser/autofill/personal_data_manager.h" #import <AddressBook/AddressBook.h> #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/sys_string_conversions.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/autofill/phone_number.h" #include "chrome/common/guid.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util_mac.h" namespace { // This implementation makes use of the Address Book API. Profiles are // generated that correspond to addresses in the "me" card that reside in the // user's Address Book. The caller passes a vector of profiles into the // the constructer and then initiate the fetch from the Mac Address Book "me" // card using the main |GetAddressBookMeCard()| method. This clears any // existing addresses and populates new addresses derived from the data found // in the "me" card. class AuxiliaryProfilesImpl { public: // Constructor takes a reference to the |profiles| that will be filled in // by the subsequent call to |GetAddressBookMeCard()|. |profiles| may not // be NULL. explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles) : profiles_(*profiles) { } virtual ~AuxiliaryProfilesImpl() {} // Import the "me" card from the Mac Address Book and fill in |profiles_|. void GetAddressBookMeCard(); private: void GetAddressBookNames(ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile); void GetAddressBookAddresses(NSDictionary* address, AutofillProfile* profile); void GetAddressBookEmail(ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile); void GetAddressBookPhoneNumbers(ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile); private: // A reference to the profiles this class populates. ScopedVector<AutofillProfile>& profiles_; DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl); }; // This method uses the |ABAddressBook| system service to fetch the "me" card // from the active user's address book. It looks for the user address // information and translates it to the internal list of |AutofillProfile| data // structures. void AuxiliaryProfilesImpl::GetAddressBookMeCard() { profiles_.reset(); ABAddressBook* addressBook = [ABAddressBook sharedAddressBook]; ABPerson* me = [addressBook me]; if (me) { ABMultiValue* addresses = [me valueForProperty:kABAddressProperty]; for (NSUInteger i = 0, count = [addresses count]; i < count; i++) { NSDictionary* address = [addresses valueAtIndex:i]; NSString* addressLabelRaw = [addresses labelAtIndex:i]; // Create a new profile where the guid is set to the guid portion of the // |kABUIDProperty| taken from from the "me" address. The format of // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the // raw guid here and using it directly. const size_t kGUIDLength = 36U; std::string guid = base::SysNSStringToUTF8( [me valueForProperty:kABUIDProperty]).substr(0, kGUIDLength); scoped_ptr<AutofillProfile> profile(new AutofillProfile(guid)); DCHECK(guid::IsValidGUID(profile->guid())); // Fill in name and company information. GetAddressBookNames(me, addressLabelRaw, profile.get()); // Fill in address information. GetAddressBookAddresses(address, profile.get()); // Fill in email information. GetAddressBookEmail(me, addressLabelRaw, profile.get()); // Fill in phone and fax number information. GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get()); profiles_.push_back(profile.release()); } } } // Name and company information is stored once in the Address Book against // multiple addresses. We replicate that information for each profile. // We only propagate the company name to work profiles. void AuxiliaryProfilesImpl::GetAddressBookNames( ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile) { NSString* firstName = [me valueForProperty:kABFirstNameProperty]; NSString* middleName = [me valueForProperty:kABMiddleNameProperty]; NSString* lastName = [me valueForProperty:kABLastNameProperty]; NSString* companyName = [me valueForProperty:kABOrganizationProperty]; profile->SetInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName)); profile->SetInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName)); profile->SetInfo(NAME_LAST, base::SysNSStringToUTF16(lastName)); if ([addressLabelRaw isEqualToString:kABAddressWorkLabel]) profile->SetInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName)); } // Addresss information from the Address Book may span multiple lines. // If it does then we represent the address with two lines in the profile. The // second line we join with commas. // For example: "c/o John Doe\n1122 Other Avenue\nApt #7" translates to // line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7". void AuxiliaryProfilesImpl::GetAddressBookAddresses( NSDictionary* address, AutofillProfile* profile) { if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) { // If there are newlines in the address, split into two lines. if ([addressField rangeOfCharacterFromSet: [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { NSArray* chunks = [addressField componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; DCHECK([chunks count] > 1); NSString* separator = l10n_util::GetNSString( IDS_AUTOFILL_MAC_ADDRESS_LINE_SEPARATOR); NSString* addressField1 = [chunks objectAtIndex:0]; NSString* addressField2 = [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)] componentsJoinedByString:separator]; profile->SetInfo(ADDRESS_HOME_LINE1, base::SysNSStringToUTF16(addressField1)); profile->SetInfo(ADDRESS_HOME_LINE2, base::SysNSStringToUTF16(addressField2)); } else { profile->SetInfo(ADDRESS_HOME_LINE1, base::SysNSStringToUTF16(addressField)); } } if (NSString* city = [address objectForKey:kABAddressCityKey]) profile->SetInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city)); if (NSString* state = [address objectForKey:kABAddressStateKey]) profile->SetInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state)); if (NSString* zip = [address objectForKey:kABAddressZIPKey]) profile->SetInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip)); if (NSString* country = [address objectForKey:kABAddressCountryKey]) profile->SetInfo(ADDRESS_HOME_COUNTRY, base::SysNSStringToUTF16(country)); } // Fills in email address matching current address label. Note that there may // be multiple matching email addresses for a given label. We take the // first we find (topmost) as preferred. void AuxiliaryProfilesImpl::GetAddressBookEmail( ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile) { ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty]; NSString* emailAddress = nil; for (NSUInteger j = 0, emailCount = [emailAddresses count]; j < emailCount; j++) { NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j]; if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) { emailAddress = [emailAddresses valueAtIndex:j]; break; } } profile->SetInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress)); } // Fills in telephone numbers. Each of these are special cases. // We match four cases: home/tel, home/fax, work/tel, work/fax. // Note, we traverse in reverse order so that top values in address book // take priority. void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers( ABPerson* me, NSString* addressLabelRaw, AutofillProfile* profile) { string16 number; string16 city_code; string16 country_code; ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty]; for (NSUInteger k = 0, phoneCount = [phoneNumbers count]; k < phoneCount; k++) { NSUInteger reverseK = phoneCount - k - 1; NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK]; if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] && [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) { string16 homePhone = base::SysNSStringToUTF16( [phoneNumbers valueAtIndex:reverseK]); PhoneNumber::ParsePhoneNumber( homePhone, &number, &city_code, &country_code); profile->SetInfo(PHONE_HOME_NUMBER, number); profile->SetInfo(PHONE_HOME_CITY_CODE, city_code); profile->SetInfo(PHONE_HOME_COUNTRY_CODE, country_code); } else if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] && [phoneLabelRaw isEqualToString:kABPhoneHomeFAXLabel]) { string16 homeFax = base::SysNSStringToUTF16( [phoneNumbers valueAtIndex:reverseK]); PhoneNumber::ParsePhoneNumber(homeFax, &number, &city_code, &country_code); profile->SetInfo(PHONE_FAX_NUMBER, number); profile->SetInfo(PHONE_FAX_CITY_CODE, city_code); profile->SetInfo(PHONE_FAX_COUNTRY_CODE, country_code); } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] && [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) { string16 workPhone = base::SysNSStringToUTF16( [phoneNumbers valueAtIndex:reverseK]); PhoneNumber::ParsePhoneNumber(workPhone, &number, &city_code, &country_code); profile->SetInfo(PHONE_HOME_NUMBER, number); profile->SetInfo(PHONE_HOME_CITY_CODE, city_code); profile->SetInfo(PHONE_HOME_COUNTRY_CODE, country_code); } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] && [phoneLabelRaw isEqualToString:kABPhoneWorkFAXLabel]) { string16 workFax = base::SysNSStringToUTF16( [phoneNumbers valueAtIndex:reverseK]); PhoneNumber::ParsePhoneNumber(workFax, &number, &city_code, &country_code); profile->SetInfo(PHONE_FAX_NUMBER, number); profile->SetInfo(PHONE_FAX_CITY_CODE, city_code); profile->SetInfo(PHONE_FAX_COUNTRY_CODE, country_code); } } } } // namespace // Populate |auxiliary_profiles_| with the Address Book data. void PersonalDataManager::LoadAuxiliaryProfiles() { AuxiliaryProfilesImpl impl(&auxiliary_profiles_); impl.GetAddressBookMeCard(); }