personal_data_manager_mac.mm revision 3240926e260ce088908e02ac07a6cf7b0c0cbf44
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/strings/stringprintf.h"
18#include "base/strings/sys_string_conversions.h"
19#include "components/autofill/core/browser/autofill_country.h"
20#include "components/autofill/core/browser/autofill_profile.h"
21#include "components/autofill/core/browser/autofill_type.h"
22#include "components/autofill/core/browser/phone_number.h"
23#include "grit/component_strings.h"
24#include "ui/base/l10n/l10n_util_mac.h"
25
26namespace autofill {
27namespace {
28
29const char kAddressBookOrigin[] = "OS X Address Book";
30
31// This implementation makes use of the Address Book API.  Profiles are
32// generated that correspond to addresses in the "me" card that reside in the
33// user's Address Book.  The caller passes a vector of profiles into the
34// the constructer and then initiate the fetch from the Mac Address Book "me"
35// card using the main |GetAddressBookMeCard()| method.  This clears any
36// existing addresses and populates new addresses derived from the data found
37// in the "me" card.
38class AuxiliaryProfilesImpl {
39 public:
40  // Constructor takes a reference to the |profiles| that will be filled in
41  // by the subsequent call to |GetAddressBookMeCard()|.  |profiles| may not
42  // be NULL.
43  explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles)
44      : profiles_(*profiles) {
45  }
46  virtual ~AuxiliaryProfilesImpl() {}
47
48  // Import the "me" card from the Mac Address Book and fill in |profiles_|.
49  void GetAddressBookMeCard(const std::string& app_locale);
50
51 private:
52  void GetAddressBookNames(ABPerson* me,
53                           NSString* addressLabelRaw,
54                           AutofillProfile* profile);
55  void GetAddressBookAddress(const std::string& app_locale,
56                             NSDictionary* address,
57                             AutofillProfile* profile);
58  void GetAddressBookEmail(ABPerson* me,
59                           NSString* addressLabelRaw,
60                           AutofillProfile* profile);
61  void GetAddressBookPhoneNumbers(ABPerson* me,
62                                  NSString* addressLabelRaw,
63                                  AutofillProfile* profile);
64
65 private:
66  // A reference to the profiles this class populates.
67  ScopedVector<AutofillProfile>& profiles_;
68
69  DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl);
70};
71
72// This method uses the |ABAddressBook| system service to fetch the "me" card
73// from the active user's address book.  It looks for the user address
74// information and translates it to the internal list of |AutofillProfile| data
75// structures.
76void AuxiliaryProfilesImpl::GetAddressBookMeCard(
77    const std::string& app_locale) {
78  profiles_.clear();
79
80  // +[ABAddressBook sharedAddressBook] throws an exception internally in
81  // circumstances that aren't clear. The exceptions are only observed in crash
82  // reports, so it is unknown whether they would be caught by AppKit and nil
83  // returned, or if they would take down the app. In either case, avoid
84  // crashing. http://crbug.com/129022
85  ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions(^{
86      return [ABAddressBook sharedAddressBook];
87  });
88  ABPerson* me = [addressBook me];
89  if (!me)
90    return;
91
92  ABMultiValue* addresses = [me valueForProperty:kABAddressProperty];
93
94  // The number of characters at the end of the GUID to reserve for
95  // distinguishing addresses within the "me" card.  Cap the number of addresses
96  // we will fetch to the number that can be distinguished by this fragment of
97  // the GUID.
98  const size_t kNumAddressGUIDChars = 2;
99  const size_t kNumHexDigits = 16;
100  const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars);
101  NSUInteger count = MIN([addresses count], kMaxAddressCount);
102  for (NSUInteger i = 0; i < count; i++) {
103    NSDictionary* address = [addresses valueAtIndex:i];
104    NSString* addressLabelRaw = [addresses labelAtIndex:i];
105
106    // Create a new profile where the guid is set to the guid portion of the
107    // |kABUIDProperty| taken from from the "me" address.  The format of
108    // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the
109    // raw guid here and using it directly, with one modification: we update the
110    // last |kNumAddressGUIDChars| characters in the GUID to reflect the address
111    // variant.  Note that we capped the number of addresses above, so this is
112    // safe.
113    const size_t kGUIDLength = 36U;
114    const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars;
115    std::string guid = base::SysNSStringToUTF8(
116        [me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength);
117
118    // The format string to print |kNumAddressGUIDChars| hexadecimal characters,
119    // left-padded with 0's.
120    const std::string kAddressGUIDFormat =
121        base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars);
122    guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i);
123    DCHECK_EQ(kGUIDLength, guid.size());
124
125    scoped_ptr<AutofillProfile> profile(
126        new AutofillProfile(guid, kAddressBookOrigin));
127    DCHECK(base::IsValidGUID(profile->guid()));
128
129    // Fill in name and company information.
130    GetAddressBookNames(me, addressLabelRaw, profile.get());
131
132    // Fill in address information.
133    GetAddressBookAddress(app_locale, address, profile.get());
134
135    // Fill in email information.
136    GetAddressBookEmail(me, addressLabelRaw, profile.get());
137
138    // Fill in phone number information.
139    GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get());
140
141    profiles_.push_back(profile.release());
142  }
143}
144
145// Name and company information is stored once in the Address Book against
146// multiple addresses.  We replicate that information for each profile.
147// We only propagate the company name to work profiles.
148void AuxiliaryProfilesImpl::GetAddressBookNames(
149    ABPerson* me,
150    NSString* addressLabelRaw,
151    AutofillProfile* profile) {
152  NSString* firstName = [me valueForProperty:kABFirstNameProperty];
153  NSString* middleName = [me valueForProperty:kABMiddleNameProperty];
154  NSString* lastName = [me valueForProperty:kABLastNameProperty];
155  NSString* companyName = [me valueForProperty:kABOrganizationProperty];
156
157  profile->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName));
158  profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName));
159  profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName));
160  if ([addressLabelRaw isEqualToString:kABAddressWorkLabel])
161    profile->SetRawInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName));
162}
163
164// Addresss information from the Address Book may span multiple lines.
165// If it does then we represent the address with two lines in the profile.  The
166// second line we join with commas.
167// For example:  "c/o John Doe\n1122 Other Avenue\nApt #7" translates to
168// line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7".
169void AuxiliaryProfilesImpl::GetAddressBookAddress(const std::string& app_locale,
170                                                  NSDictionary* address,
171                                                  AutofillProfile* profile) {
172  if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) {
173    // If there are newlines in the address, split into two lines.
174    if ([addressField rangeOfCharacterFromSet:
175            [NSCharacterSet newlineCharacterSet]].location != NSNotFound) {
176      NSArray* chunks = [addressField componentsSeparatedByCharactersInSet:
177          [NSCharacterSet newlineCharacterSet]];
178      DCHECK([chunks count] > 1);
179
180      NSString* separator = l10n_util::GetNSString(
181            IDS_AUTOFILL_MAC_ADDRESS_LINE_SEPARATOR);
182
183      NSString* addressField1 = [chunks objectAtIndex:0];
184      NSString* addressField2 =
185          [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)]
186              componentsJoinedByString:separator];
187      profile->SetRawInfo(ADDRESS_HOME_LINE1,
188                          base::SysNSStringToUTF16(addressField1));
189      profile->SetRawInfo(ADDRESS_HOME_LINE2,
190                          base::SysNSStringToUTF16(addressField2));
191    } else {
192      profile->SetRawInfo(ADDRESS_HOME_LINE1,
193                          base::SysNSStringToUTF16(addressField));
194    }
195  }
196
197  if (NSString* city = [address objectForKey:kABAddressCityKey])
198    profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city));
199
200  if (NSString* state = [address objectForKey:kABAddressStateKey])
201    profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state));
202
203  if (NSString* zip = [address objectForKey:kABAddressZIPKey])
204    profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip));
205
206  if (NSString* country = [address objectForKey:kABAddressCountryKey]) {
207    profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY),
208                     base::SysNSStringToUTF16(country),
209                     app_locale);
210  }
211}
212
213// Fills in email address matching current address label.  Note that there may
214// be multiple matching email addresses for a given label.  We take the
215// first we find (topmost) as preferred.
216void AuxiliaryProfilesImpl::GetAddressBookEmail(
217    ABPerson* me,
218    NSString* addressLabelRaw,
219    AutofillProfile* profile) {
220  ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty];
221  NSString* emailAddress = nil;
222  for (NSUInteger j = 0, emailCount = [emailAddresses count];
223       j < emailCount; j++) {
224    NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j];
225    if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) {
226      emailAddress = [emailAddresses valueAtIndex:j];
227      break;
228    }
229  }
230  profile->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress));
231}
232
233// Fills in telephone numbers.  Each of these are special cases.
234// We match two cases: home/tel, work/tel.
235// Note, we traverse in reverse order so that top values in address book
236// take priority.
237void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers(
238    ABPerson* me,
239    NSString* addressLabelRaw,
240    AutofillProfile* profile) {
241  ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty];
242  for (NSUInteger k = 0, phoneCount = [phoneNumbers count];
243       k < phoneCount; k++) {
244    NSUInteger reverseK = phoneCount - k - 1;
245    NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK];
246    if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] &&
247        [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) {
248      base::string16 homePhone = base::SysNSStringToUTF16(
249          [phoneNumbers valueAtIndex:reverseK]);
250      profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone);
251    } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] &&
252               [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) {
253      base::string16 workPhone = base::SysNSStringToUTF16(
254          [phoneNumbers valueAtIndex:reverseK]);
255      profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone);
256    } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] ||
257               [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) {
258      base::string16 phone = base::SysNSStringToUTF16(
259          [phoneNumbers valueAtIndex:reverseK]);
260      profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone);
261    }
262  }
263}
264
265}  // namespace
266
267// Populate |auxiliary_profiles_| with the Address Book data.
268void PersonalDataManager::LoadAuxiliaryProfiles() {
269  AuxiliaryProfilesImpl impl(&auxiliary_profiles_);
270  impl.GetAddressBookMeCard(app_locale_);
271}
272
273}  // namespace autofill
274