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