blob: 7cdcfded323a1d9da07a4af37c0bbbcb41fe999f [file] [log] [blame]
// Copyright 2013 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 "components/autofill/core/browser/personal_data_manager.h"
#include <math.h>
#import <AddressBook/AddressBook.h>
#include "base/format_macros.h"
#include "base/guid.h"
#include "base/logging.h"
#import "base/mac/scoped_nsexception_enabler.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "components/autofill/core/browser/autofill_country.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/phone_number.h"
#include "grit/component_strings.h"
#include "ui/base/l10n/l10n_util_mac.h"
namespace autofill {
namespace {
const char kAddressBookOrigin[] = "OS X Address Book";
// 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(const std::string& app_locale);
private:
void GetAddressBookNames(ABPerson* me,
NSString* addressLabelRaw,
AutofillProfile* profile);
void GetAddressBookAddress(const std::string& app_locale,
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(
const std::string& app_locale) {
profiles_.clear();
// +[ABAddressBook sharedAddressBook] throws an exception internally in
// circumstances that aren't clear. The exceptions are only observed in crash
// reports, so it is unknown whether they would be caught by AppKit and nil
// returned, or if they would take down the app. In either case, avoid
// crashing. http://crbug.com/129022
ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions(^{
return [ABAddressBook sharedAddressBook];
});
ABPerson* me = [addressBook me];
if (!me)
return;
ABMultiValue* addresses = [me valueForProperty:kABAddressProperty];
// The number of characters at the end of the GUID to reserve for
// distinguishing addresses within the "me" card. Cap the number of addresses
// we will fetch to the number that can be distinguished by this fragment of
// the GUID.
const size_t kNumAddressGUIDChars = 2;
const size_t kNumHexDigits = 16;
const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars);
NSUInteger count = MIN([addresses count], kMaxAddressCount);
for (NSUInteger i = 0; 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, with one modification: we update the
// last |kNumAddressGUIDChars| characters in the GUID to reflect the address
// variant. Note that we capped the number of addresses above, so this is
// safe.
const size_t kGUIDLength = 36U;
const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars;
std::string guid = base::SysNSStringToUTF8(
[me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength);
// The format string to print |kNumAddressGUIDChars| hexadecimal characters,
// left-padded with 0's.
const std::string kAddressGUIDFormat =
base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars);
guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i);
DCHECK_EQ(kGUIDLength, guid.size());
scoped_ptr<AutofillProfile> profile(
new AutofillProfile(guid, kAddressBookOrigin));
DCHECK(base::IsValidGUID(profile->guid()));
// Fill in name and company information.
GetAddressBookNames(me, addressLabelRaw, profile.get());
// Fill in address information.
GetAddressBookAddress(app_locale, address, profile.get());
// Fill in email information.
GetAddressBookEmail(me, addressLabelRaw, profile.get());
// Fill in phone 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->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName));
profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName));
profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName));
if ([addressLabelRaw isEqualToString:kABAddressWorkLabel])
profile->SetRawInfo(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::GetAddressBookAddress(const std::string& app_locale,
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->SetRawInfo(ADDRESS_HOME_LINE1,
base::SysNSStringToUTF16(addressField1));
profile->SetRawInfo(ADDRESS_HOME_LINE2,
base::SysNSStringToUTF16(addressField2));
} else {
profile->SetRawInfo(ADDRESS_HOME_LINE1,
base::SysNSStringToUTF16(addressField));
}
}
if (NSString* city = [address objectForKey:kABAddressCityKey])
profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city));
if (NSString* state = [address objectForKey:kABAddressStateKey])
profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state));
if (NSString* zip = [address objectForKey:kABAddressZIPKey])
profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip));
if (NSString* country = [address objectForKey:kABAddressCountryKey]) {
profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY),
base::SysNSStringToUTF16(country),
app_locale);
}
}
// 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->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress));
}
// Fills in telephone numbers. Each of these are special cases.
// We match two cases: home/tel, work/tel.
// Note, we traverse in reverse order so that top values in address book
// take priority.
void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers(
ABPerson* me,
NSString* addressLabelRaw,
AutofillProfile* profile) {
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]) {
base::string16 homePhone = base::SysNSStringToUTF16(
[phoneNumbers valueAtIndex:reverseK]);
profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone);
} else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
[phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) {
base::string16 workPhone = base::SysNSStringToUTF16(
[phoneNumbers valueAtIndex:reverseK]);
profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone);
} else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] ||
[phoneLabelRaw isEqualToString:kABPhoneMainLabel]) {
base::string16 phone = base::SysNSStringToUTF16(
[phoneNumbers valueAtIndex:reverseK]);
profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone);
}
}
}
} // namespace
// Populate |auxiliary_profiles_| with the Address Book data.
void PersonalDataManager::LoadAuxiliaryProfiles() const {
AuxiliaryProfilesImpl impl(&auxiliary_profiles_);
impl.GetAddressBookMeCard(app_locale_);
}
} // namespace autofill