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 "chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/callback.h"
12#include "base/logging.h"
13#include "base/md5.h"
14#include "base/memory/scoped_vector.h"
15#include "base/strings/string_number_conversions.h"
16#include "base/strings/string_util.h"
17#include "base/values.h"
18#include "chrome/app/chrome_command_ids.h"
19#include "chrome/browser/chrome_notification_types.h"
20#include "chrome/browser/google/google_brand.h"
21#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"
22#include "chrome/browser/profile_resetter/profile_reset_global_error.h"
23#include "chrome/browser/profile_resetter/profile_resetter.h"
24#include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/browser/search_engines/template_url_service_factory.h"
27#include "chrome/browser/ui/browser.h"
28#include "chrome/browser/ui/browser_finder.h"
29#include "chrome/browser/ui/global_error/global_error_service.h"
30#include "chrome/browser/ui/global_error/global_error_service_factory.h"
31#include "components/search_engines/template_url_prepopulate_data.h"
32#include "components/search_engines/template_url_service.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/notification_service.h"
35
36#if defined(OS_WIN)
37#include "chrome/browser/enumerate_modules_model_win.h"
38#endif
39
40namespace {
41
42scoped_ptr<base::DictionaryValue> BuildSubTreeFromTemplateURL(
43    const TemplateURL* template_url) {
44  // If this value contains a placeholder in the pre-populated data, it will
45  // have been replaced as it was loaded into a TemplateURL.
46  // BuildSubTreeFromTemplateURL works with TemplateURL (not TemplateURLData)
47  // in order to maintain this behaviour.
48  // TODO(engedy): Confirm the expected behaviour and convert to use
49  // TemplateURLData if possible."
50  scoped_ptr<base::DictionaryValue> tree(new base::DictionaryValue);
51  tree->SetString("name", template_url->short_name());
52  tree->SetString("short_name", template_url->short_name());
53  tree->SetString("keyword", template_url->keyword());
54  tree->SetString("search_url", template_url->url());
55  tree->SetString("url", template_url->url());
56  tree->SetString("suggestions_url", template_url->suggestions_url());
57  tree->SetString("instant_url", template_url->instant_url());
58  tree->SetString("image_url", template_url->image_url());
59  tree->SetString("new_tab_url", template_url->new_tab_url());
60  tree->SetString("search_url_post_params",
61                  template_url->search_url_post_params());
62  tree->SetString("suggestions_url_post_params",
63                  template_url->suggestions_url_post_params());
64  tree->SetString("instant_url_post_params",
65                  template_url->instant_url_post_params());
66  tree->SetString("image_url_post_params",
67                  template_url->image_url_post_params());
68  base::ListValue* alternate_urls = new base::ListValue;
69  alternate_urls->AppendStrings(template_url->alternate_urls());
70  tree->Set("alternate_urls", alternate_urls);
71  tree->SetString("favicon_url", template_url->favicon_url().spec());
72  tree->SetString("originating_url", template_url->originating_url().spec());
73  tree->SetBoolean("safe_for_autoreplace",
74                   template_url->safe_for_autoreplace());
75  base::ListValue* input_encodings = new base::ListValue;
76  input_encodings->AppendStrings(template_url->input_encodings());
77  tree->Set("input_encodings", input_encodings);
78  tree->SetString("id", base::Int64ToString(template_url->id()));
79  tree->SetString("date_created",
80                  base::Int64ToString(
81                      template_url->date_created().ToInternalValue()));
82  tree->SetString("last_modified",
83                  base::Int64ToString(
84                      template_url->last_modified().ToInternalValue()));
85  tree->SetBoolean("created_by_policy", template_url->created_by_policy());
86  tree->SetInteger("usage_count", template_url->usage_count());
87  tree->SetInteger("prepopulate_id", template_url->prepopulate_id());
88  tree->SetString("search_terms_replacement_key",
89                  template_url->search_terms_replacement_key());
90  return tree.Pass();
91}
92
93#if defined(OS_WIN)
94void ExtractLoadedModuleNameDigests(
95    const base::ListValue& module_list,
96    base::ListValue* module_name_digests) {
97  DCHECK(module_name_digests);
98
99  // EnumerateModulesModel produces a list of dictionaries.
100  // Each dictionary corresponds to a module and exposes a number of properties.
101  // We care only about 'type' and 'name'.
102  for (size_t i = 0; i < module_list.GetSize(); ++i) {
103    const base::DictionaryValue* module_dictionary = NULL;
104    if (!module_list.GetDictionary(i, &module_dictionary))
105      continue;
106    ModuleEnumerator::ModuleType module_type =
107        ModuleEnumerator::LOADED_MODULE;
108    if (!module_dictionary->GetInteger(
109            "type", reinterpret_cast<int*>(&module_type)) ||
110        module_type != ModuleEnumerator::LOADED_MODULE) {
111      continue;
112    }
113    std::string module_name;
114    if (!module_dictionary->GetString("name", &module_name))
115      continue;
116    base::StringToLowerASCII(&module_name);
117    module_name_digests->AppendString(base::MD5String(module_name));
118  }
119}
120#endif
121
122}  // namespace
123
124
125// AutomaticProfileResetterDelegateImpl --------------------------------------
126
127AutomaticProfileResetterDelegateImpl::AutomaticProfileResetterDelegateImpl(
128    Profile* profile,
129    ProfileResetter::ResettableFlags resettable_aspects)
130    : profile_(profile),
131      global_error_service_(GlobalErrorServiceFactory::GetForProfile(profile_)),
132      template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
133      resettable_aspects_(resettable_aspects) {
134  DCHECK(profile_);
135  if (template_url_service_) {
136    template_url_service_->AddObserver(this);
137    // Needed so that |template_url_service_ready_event_| will be signaled even
138    // when TemplateURLService had been already initialized before this point.
139    OnTemplateURLServiceChanged();
140  }
141
142#if defined(OS_WIN)
143  module_list_.reset(EnumerateModulesModel::GetInstance()->GetModuleList());
144#endif
145  if (module_list_) {
146    // Having a non-empty module list proves that enumeration had been already
147    // performed before this point.
148    modules_have_been_enumerated_event_.Signal();
149  }
150  registrar_.Add(this,
151                 chrome::NOTIFICATION_MODULE_LIST_ENUMERATED,
152                 content::NotificationService::AllSources());
153}
154
155AutomaticProfileResetterDelegateImpl::~AutomaticProfileResetterDelegateImpl() {
156  if (template_url_service_)
157    template_url_service_->RemoveObserver(this);
158}
159
160void AutomaticProfileResetterDelegateImpl::EnumerateLoadedModulesIfNeeded() {
161  if (!modules_have_been_enumerated_event_.is_signaled()) {
162#if defined(OS_WIN)
163    EnumerateModulesModel::GetInstance()->ScanNow();
164#else
165    modules_have_been_enumerated_event_.Signal();
166#endif
167  }
168}
169
170void AutomaticProfileResetterDelegateImpl::
171    RequestCallbackWhenLoadedModulesAreEnumerated(
172    const base::Closure& ready_callback) const {
173  DCHECK(!ready_callback.is_null());
174  modules_have_been_enumerated_event_.Post(FROM_HERE, ready_callback);
175}
176
177void AutomaticProfileResetterDelegateImpl::LoadTemplateURLServiceIfNeeded() {
178  DCHECK(template_url_service_);
179  template_url_service_->Load();  // Safe to call even if it has loaded already.
180}
181
182void AutomaticProfileResetterDelegateImpl::
183    RequestCallbackWhenTemplateURLServiceIsLoaded(
184    const base::Closure& ready_callback) const {
185  DCHECK(!ready_callback.is_null());
186  template_url_service_ready_event_.Post(FROM_HERE, ready_callback);
187}
188
189void AutomaticProfileResetterDelegateImpl::
190    FetchBrandcodedDefaultSettingsIfNeeded() {
191  if (brandcoded_config_fetcher_ ||
192      brandcoded_defaults_fetched_event_.is_signaled())
193    return;
194
195  std::string brandcode;
196  google_brand::GetBrand(&brandcode);
197  if (brandcode.empty()) {
198    brandcoded_defaults_.reset(new BrandcodedDefaultSettings);
199    brandcoded_defaults_fetched_event_.Signal();
200  } else {
201    brandcoded_config_fetcher_.reset(new BrandcodeConfigFetcher(
202        base::Bind(
203            &AutomaticProfileResetterDelegateImpl::OnBrandcodedDefaultsFetched,
204            base::Unretained(this)),
205        GURL("https://tools.google.com/service/update2"),
206        brandcode));
207  }
208}
209
210void AutomaticProfileResetterDelegateImpl::
211    RequestCallbackWhenBrandcodedDefaultsAreFetched(
212    const base::Closure& ready_callback) const {
213  DCHECK(!ready_callback.is_null());
214  brandcoded_defaults_fetched_event_.Post(FROM_HERE, ready_callback);
215}
216
217scoped_ptr<base::ListValue> AutomaticProfileResetterDelegateImpl::
218    GetLoadedModuleNameDigests() const {
219  DCHECK(modules_have_been_enumerated_event_.is_signaled());
220  scoped_ptr<base::ListValue> result(new base::ListValue);
221#if defined(OS_WIN)
222  if (module_list_)
223    ExtractLoadedModuleNameDigests(*module_list_, result.get());
224#endif
225  return result.Pass();
226}
227
228scoped_ptr<base::DictionaryValue> AutomaticProfileResetterDelegateImpl::
229    GetDefaultSearchProviderDetails() const {
230  DCHECK(template_url_service_);
231  DCHECK(template_url_service_->loaded());
232
233  const TemplateURL* default_search_provider =
234      template_url_service_->GetDefaultSearchProvider();
235
236  // Having a NULL default search provider is due to either:
237  //  1.) default search providers being disabled by policy,
238  //  2.) directly tampering with the Preferences and/or the SQLite DBs.
239  // In this state, Omnibox non-keyword search functionality is disabled.
240  return default_search_provider ?
241      BuildSubTreeFromTemplateURL(default_search_provider) :
242      scoped_ptr<base::DictionaryValue>(new base::DictionaryValue);
243}
244
245bool AutomaticProfileResetterDelegateImpl::
246    IsDefaultSearchProviderManaged() const {
247  DCHECK(template_url_service_);
248  DCHECK(template_url_service_->loaded());
249  return template_url_service_->is_default_search_managed();
250}
251
252scoped_ptr<base::ListValue> AutomaticProfileResetterDelegateImpl::
253    GetPrepopulatedSearchProvidersDetails() const {
254  size_t default_search_index = 0;
255  ScopedVector<TemplateURLData> engines(
256      TemplateURLPrepopulateData::GetPrepopulatedEngines(
257          profile_->GetPrefs(), &default_search_index));
258  scoped_ptr<base::ListValue> engines_details_list(new base::ListValue);
259  for (ScopedVector<TemplateURLData>::const_iterator it = engines.begin();
260       it != engines.end(); ++it) {
261    TemplateURL template_url(**it);
262    engines_details_list->Append(
263        BuildSubTreeFromTemplateURL(&template_url).release());
264  }
265  return engines_details_list.Pass();
266}
267
268bool AutomaticProfileResetterDelegateImpl::TriggerPrompt() {
269  DCHECK(global_error_service_);
270
271  if (!ProfileResetGlobalError::IsSupportedOnPlatform())
272    return false;
273
274  ProfileResetGlobalError* global_error = new ProfileResetGlobalError(profile_);
275  global_error_service_->AddGlobalError(global_error);
276
277  // Do not try to show bubble if another GlobalError is already showing one.
278  const GlobalErrorService::GlobalErrorList& global_errors(
279      global_error_service_->errors());
280  GlobalErrorService::GlobalErrorList::const_iterator it;
281  for (it = global_errors.begin(); it != global_errors.end(); ++it) {
282    if ((*it)->GetBubbleView())
283      break;
284  }
285  if (it == global_errors.end()) {
286    Browser* browser = chrome::FindTabbedBrowser(
287        profile_,
288        false /*match_original_profiles*/,
289        chrome::GetActiveDesktop());
290    if (browser)
291      global_error->ShowBubbleView(browser);
292  }
293  return true;
294}
295
296void AutomaticProfileResetterDelegateImpl::TriggerProfileSettingsReset(
297    bool send_feedback,
298    const base::Closure& completion) {
299  DCHECK(!profile_resetter_);
300  DCHECK(!completion.is_null());
301
302  profile_resetter_.reset(new ProfileResetter(profile_));
303  FetchBrandcodedDefaultSettingsIfNeeded();
304  RequestCallbackWhenBrandcodedDefaultsAreFetched(base::Bind(
305      &AutomaticProfileResetterDelegateImpl::RunProfileSettingsReset,
306      AsWeakPtr(),
307      send_feedback,
308      completion));
309}
310
311void AutomaticProfileResetterDelegateImpl::OnTemplateURLServiceChanged() {
312  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
313  DCHECK(template_url_service_);
314  if (template_url_service_->loaded() &&
315      !template_url_service_ready_event_.is_signaled())
316    template_url_service_ready_event_.Signal();
317}
318
319void AutomaticProfileResetterDelegateImpl::DismissPrompt() {
320  DCHECK(global_error_service_);
321  GlobalError* global_error =
322      global_error_service_->GetGlobalErrorByMenuItemCommandID(
323          IDC_SHOW_SETTINGS_RESET_BUBBLE);
324  if (global_error) {
325    // This will also close/destroy the Bubble UI if it is currently shown.
326    global_error_service_->RemoveGlobalError(global_error);
327    delete global_error;
328  }
329}
330
331void AutomaticProfileResetterDelegateImpl::Observe(
332    int type,
333    const content::NotificationSource& source,
334    const content::NotificationDetails& details) {
335  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
336  if (type == chrome::NOTIFICATION_MODULE_LIST_ENUMERATED &&
337      !modules_have_been_enumerated_event_.is_signaled()) {
338#if defined(OS_WIN)
339    module_list_.reset(EnumerateModulesModel::GetInstance()->GetModuleList());
340#endif
341    modules_have_been_enumerated_event_.Signal();
342  }
343}
344
345void AutomaticProfileResetterDelegateImpl::SendFeedback(
346    const std::string& report) const {
347  SendSettingsFeedback(report, profile_, PROFILE_RESET_PROMPT);
348}
349
350void AutomaticProfileResetterDelegateImpl::RunProfileSettingsReset(
351    bool send_feedback,
352    const base::Closure& completion) {
353  DCHECK(brandcoded_defaults_);
354  scoped_ptr<ResettableSettingsSnapshot> old_settings_snapshot;
355  if (send_feedback) {
356    old_settings_snapshot.reset(new ResettableSettingsSnapshot(profile_));
357    old_settings_snapshot->RequestShortcuts(base::Closure());
358  }
359  profile_resetter_->Reset(resettable_aspects_,
360                           brandcoded_defaults_.Pass(),
361                           send_feedback,
362                           base::Bind(&AutomaticProfileResetterDelegateImpl::
363                                          OnProfileSettingsResetCompleted,
364                                      AsWeakPtr(),
365                                      completion,
366                                      base::Passed(&old_settings_snapshot)));
367}
368
369void AutomaticProfileResetterDelegateImpl::
370    OnBrandcodedDefaultsFetched() {
371  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
372  DCHECK(brandcoded_config_fetcher_);
373  DCHECK(!brandcoded_config_fetcher_->IsActive());
374  brandcoded_defaults_ = brandcoded_config_fetcher_->GetSettings();
375  if (!brandcoded_defaults_)
376    brandcoded_defaults_.reset(new BrandcodedDefaultSettings);
377  brandcoded_defaults_fetched_event_.Signal();
378}
379
380void AutomaticProfileResetterDelegateImpl::OnProfileSettingsResetCompleted(
381    const base::Closure& user_callback,
382    scoped_ptr<ResettableSettingsSnapshot> old_settings_snapshot) {
383  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
384  if (old_settings_snapshot) {
385    ResettableSettingsSnapshot new_settings_snapshot(profile_);
386    int difference =
387        old_settings_snapshot->FindDifferentFields(new_settings_snapshot);
388    if (difference) {
389      old_settings_snapshot->Subtract(new_settings_snapshot);
390      std::string report =
391          SerializeSettingsReport(*old_settings_snapshot, difference);
392      SendFeedback(report);
393    }
394  }
395  content::BrowserThread::PostTask(
396      content::BrowserThread::UI, FROM_HERE, user_callback);
397}
398