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