1 // Copyright (c) 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/resettable_settings_snapshot.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/guid.h"
12 #include "base/hash/md5.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/synchronization/atomic_flag.h"
16 #include "base/task/post_task.h"
17 #include "base/task/task_traits.h"
18 #include "base/task/thread_pool.h"
19 #include "base/task_runner_util.h"
20 #include "base/values.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/chrome_content_browser_client.h"
23 #include "chrome/browser/profile_resetter/profile_reset_report.pb.h"
24 #include "chrome/browser/profile_resetter/reset_report_uploader.h"
25 #include "chrome/browser/profile_resetter/reset_report_uploader_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/search_engines/template_url_service_factory.h"
28 #include "chrome/common/channel_info.h"
29 #include "chrome/common/pref_names.h"
30 #include "chrome/grit/chromium_strings.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "components/prefs/pref_service.h"
33 #include "components/search_engines/template_url_service.h"
34 #include "components/strings/grit/components_strings.h"
35 #include "components/version_info/version_info.h"
36 #include "content/public/browser/browser_thread.h"
37 #include "extensions/browser/extension_registry.h"
38 #include "ui/base/l10n/l10n_util.h"
39 
40 namespace {
41 
42 template <class StringType>
AddPair(base::ListValue * list,const base::string16 & key,const StringType & value)43 void AddPair(base::ListValue* list,
44              const base::string16& key,
45              const StringType& value) {
46   std::unique_ptr<base::DictionaryValue> results(new base::DictionaryValue());
47   results->SetString("key", key);
48   results->SetString("value", value);
49   list->Append(std::move(results));
50 }
51 
52 }  // namespace
53 
ResettableSettingsSnapshot(Profile * profile)54 ResettableSettingsSnapshot::ResettableSettingsSnapshot(Profile* profile)
55     : startup_(SessionStartupPref::GetStartupPref(profile)),
56       shortcuts_determined_(false) {
57   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
58   // URLs are always stored sorted.
59   std::sort(startup_.urls.begin(), startup_.urls.end());
60 
61   PrefService* prefs = profile->GetPrefs();
62   DCHECK(prefs);
63   homepage_ = prefs->GetString(prefs::kHomePage);
64   homepage_is_ntp_ = prefs->GetBoolean(prefs::kHomePageIsNewTabPage);
65   show_home_button_ = prefs->GetBoolean(prefs::kShowHomeButton);
66 
67   TemplateURLService* service =
68       TemplateURLServiceFactory::GetForProfile(profile);
69   DCHECK(service);
70   const TemplateURL* dse = service->GetDefaultSearchProvider();
71   if (dse)
72     dse_url_ = dse->url();
73 
74   const extensions::ExtensionSet& enabled_ext =
75       extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
76   enabled_extensions_.reserve(enabled_ext.size());
77 
78   for (extensions::ExtensionSet::const_iterator it = enabled_ext.begin();
79        it != enabled_ext.end(); ++it)
80     enabled_extensions_.push_back(std::make_pair((*it)->id(), (*it)->name()));
81 
82   // ExtensionSet is sorted but it seems to be an implementation detail.
83   std::sort(enabled_extensions_.begin(), enabled_extensions_.end());
84 
85   // Calculate the MD5 sum of the GUID to make sure that no part of the GUID
86   // contains information identifying the sender of the report.
87   guid_ = base::MD5String(base::GenerateGUID());
88 }
89 
~ResettableSettingsSnapshot()90 ResettableSettingsSnapshot::~ResettableSettingsSnapshot() {
91   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
92   if (cancellation_flag_.get())
93     cancellation_flag_->data.Set();
94 }
95 
Subtract(const ResettableSettingsSnapshot & snapshot)96 void ResettableSettingsSnapshot::Subtract(
97     const ResettableSettingsSnapshot& snapshot) {
98   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
99   ExtensionList extensions = base::STLSetDifference<ExtensionList>(
100       enabled_extensions_, snapshot.enabled_extensions_);
101   enabled_extensions_.swap(extensions);
102 }
103 
FindDifferentFields(const ResettableSettingsSnapshot & snapshot) const104 int ResettableSettingsSnapshot::FindDifferentFields(
105     const ResettableSettingsSnapshot& snapshot) const {
106   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
107   int bit_mask = 0;
108 
109   if (startup_.type != snapshot.startup_.type ||
110       startup_.urls != snapshot.startup_.urls)
111     bit_mask |= STARTUP_MODE;
112 
113   if (homepage_is_ntp_ != snapshot.homepage_is_ntp_ ||
114       homepage_ != snapshot.homepage_ ||
115       show_home_button_ != snapshot.show_home_button_)
116     bit_mask |= HOMEPAGE;
117 
118   if (dse_url_ != snapshot.dse_url_)
119     bit_mask |= DSE_URL;
120 
121   if (enabled_extensions_ != snapshot.enabled_extensions_)
122     bit_mask |= EXTENSIONS;
123 
124   if (shortcuts_ != snapshot.shortcuts_)
125     bit_mask |= SHORTCUTS;
126 
127   static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31,
128                 "new field needs to be added here");
129 
130   return bit_mask;
131 }
132 
RequestShortcuts(base::OnceClosure callback)133 void ResettableSettingsSnapshot::RequestShortcuts(base::OnceClosure callback) {
134   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
135   DCHECK(!cancellation_flag_.get() && !shortcuts_determined());
136 
137   cancellation_flag_ = new SharedCancellationFlag;
138 #if defined(OS_WIN)
139   base::PostTaskAndReplyWithResult(
140       base::ThreadPool::CreateCOMSTATaskRunner(
141           {base::MayBlock(), base::TaskPriority::USER_VISIBLE})
142           .get(),
143       FROM_HERE, base::BindOnce(&GetChromeLaunchShortcuts, cancellation_flag_),
144       base::BindOnce(&ResettableSettingsSnapshot::SetShortcutsAndReport,
145                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
146 #else   // defined(OS_WIN)
147   // Shortcuts are only supported on Windows.
148   std::vector<ShortcutCommand> no_shortcuts;
149   base::SequencedTaskRunnerHandle::Get()->PostTask(
150       FROM_HERE,
151       base::BindOnce(&ResettableSettingsSnapshot::SetShortcutsAndReport,
152                      weak_ptr_factory_.GetWeakPtr(), std::move(callback),
153                      std::move(no_shortcuts)));
154 #endif  // defined(OS_WIN)
155 }
156 
SetShortcutsAndReport(base::OnceClosure callback,const std::vector<ShortcutCommand> & shortcuts)157 void ResettableSettingsSnapshot::SetShortcutsAndReport(
158     base::OnceClosure callback,
159     const std::vector<ShortcutCommand>& shortcuts) {
160   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
161   shortcuts_ = shortcuts;
162   shortcuts_determined_ = true;
163   cancellation_flag_.reset();
164 
165   if (!callback.is_null())
166     std::move(callback).Run();
167 }
168 
SerializeSettingsReportToProto(const ResettableSettingsSnapshot & snapshot,int field_mask)169 std::unique_ptr<reset_report::ChromeResetReport> SerializeSettingsReportToProto(
170     const ResettableSettingsSnapshot& snapshot,
171     int field_mask) {
172   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
173   std::unique_ptr<reset_report::ChromeResetReport> report(
174       new reset_report::ChromeResetReport());
175 
176   if (field_mask & ResettableSettingsSnapshot::STARTUP_MODE) {
177     for (const auto& url : snapshot.startup_urls())
178       report->add_startup_url_path(url.spec());
179     switch (snapshot.startup_type()) {
180       case SessionStartupPref::DEFAULT:
181         report->set_startup_type(
182             reset_report::ChromeResetReport_SessionStartupType_DEFAULT);
183         break;
184       case SessionStartupPref::LAST:
185         report->set_startup_type(
186             reset_report::ChromeResetReport_SessionStartupType_LAST);
187         break;
188       case SessionStartupPref::URLS:
189         report->set_startup_type(
190             reset_report::ChromeResetReport_SessionStartupType_URLS);
191         break;
192     }
193   }
194 
195   if (field_mask & ResettableSettingsSnapshot::HOMEPAGE) {
196     report->set_homepage_path(snapshot.homepage());
197     report->set_homepage_is_new_tab_page(snapshot.homepage_is_ntp());
198     report->set_show_home_button(snapshot.show_home_button());
199   }
200 
201   if (field_mask & ResettableSettingsSnapshot::DSE_URL)
202     report->set_default_search_engine_path(snapshot.dse_url());
203 
204   if (field_mask & ResettableSettingsSnapshot::EXTENSIONS) {
205     for (const auto& enabled_extension : snapshot.enabled_extensions()) {
206       reset_report::ChromeResetReport_Extension* new_extension =
207           report->add_enabled_extensions();
208       new_extension->set_extension_id(enabled_extension.first);
209       new_extension->set_extension_name(enabled_extension.second);
210     }
211   }
212 
213   if (field_mask & ResettableSettingsSnapshot::SHORTCUTS) {
214     for (const auto& shortcut_command : snapshot.shortcuts())
215       report->add_shortcuts(base::UTF16ToUTF8(shortcut_command.second));
216   }
217 
218   report->set_guid(snapshot.guid());
219 
220   static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31,
221                 "new field needs to be serialized here");
222   return report;
223 }
224 
SendSettingsFeedbackProto(const reset_report::ChromeResetReport & report,Profile * profile)225 void SendSettingsFeedbackProto(const reset_report::ChromeResetReport& report,
226                                Profile* profile) {
227   ResetReportUploaderFactory::GetForBrowserContext(profile)
228       ->DispatchReport(report);
229 }
230 
GetReadableFeedbackForSnapshot(Profile * profile,const ResettableSettingsSnapshot & snapshot)231 std::unique_ptr<base::ListValue> GetReadableFeedbackForSnapshot(
232     Profile* profile,
233     const ResettableSettingsSnapshot& snapshot) {
234   DCHECK(profile);
235   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
236   std::unique_ptr<base::ListValue> list(new base::ListValue);
237   AddPair(list.get(),
238           l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_LOCALE),
239           g_browser_process->GetApplicationLocale());
240   AddPair(list.get(),
241           l10n_util::GetStringUTF16(IDS_VERSION_UI_USER_AGENT),
242           GetUserAgent());
243   std::string version = version_info::GetVersionNumber();
244   version += chrome::GetChannelName();
245   AddPair(list.get(),
246           l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
247           version);
248 
249   // Add snapshot data.
250   const std::vector<GURL>& urls = snapshot.startup_urls();
251   std::string startup_urls;
252   for (auto i = urls.begin(); i != urls.end(); ++i) {
253     if (!startup_urls.empty())
254       startup_urls += ' ';
255     startup_urls += i->host();
256   }
257   if (!startup_urls.empty()) {
258     AddPair(list.get(),
259             l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_URLS),
260             startup_urls);
261   }
262 
263   base::string16 startup_type;
264   switch (snapshot.startup_type()) {
265     case SessionStartupPref::DEFAULT:
266       startup_type =
267           l10n_util::GetStringUTF16(IDS_SETTINGS_ON_STARTUP_OPEN_NEW_TAB);
268       break;
269     case SessionStartupPref::LAST:
270       startup_type =
271           l10n_util::GetStringUTF16(IDS_SETTINGS_ON_STARTUP_CONTINUE);
272       break;
273     case SessionStartupPref::URLS:
274       startup_type =
275           l10n_util::GetStringUTF16(IDS_SETTINGS_ON_STARTUP_OPEN_SPECIFIC);
276       break;
277     default:
278       break;
279   }
280   AddPair(list.get(),
281           l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_TYPE),
282           startup_type);
283 
284   if (!snapshot.homepage().empty()) {
285     AddPair(list.get(),
286             l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE),
287             snapshot.homepage());
288   }
289 
290   int is_ntp_message_id = snapshot.homepage_is_ntp()
291       ? IDS_RESET_PROFILE_SETTINGS_YES
292       : IDS_RESET_PROFILE_SETTINGS_NO;
293   AddPair(list.get(),
294           l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE_IS_NTP),
295           l10n_util::GetStringUTF16(is_ntp_message_id));
296 
297   int show_home_button_id = snapshot.show_home_button()
298       ? IDS_RESET_PROFILE_SETTINGS_YES
299       : IDS_RESET_PROFILE_SETTINGS_NO;
300   AddPair(
301       list.get(),
302       l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHOW_HOME_BUTTON),
303       l10n_util::GetStringUTF16(show_home_button_id));
304 
305   TemplateURLService* service =
306       TemplateURLServiceFactory::GetForProfile(profile);
307   DCHECK(service);
308   const TemplateURL* dse = service->GetDefaultSearchProvider();
309   if (dse) {
310     AddPair(list.get(),
311             l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_DSE),
312             dse->GenerateSearchURL(service->search_terms_data()).host());
313   }
314 
315   if (snapshot.shortcuts_determined()) {
316     base::string16 shortcut_targets;
317     const std::vector<ShortcutCommand>& shortcuts = snapshot.shortcuts();
318     for (auto i = shortcuts.begin(); i != shortcuts.end(); ++i) {
319       if (!shortcut_targets.empty())
320         shortcut_targets += base::ASCIIToUTF16("\n");
321       shortcut_targets += base::ASCIIToUTF16("chrome.exe ");
322       shortcut_targets += i->second;
323     }
324     if (!shortcut_targets.empty()) {
325       AddPair(list.get(),
326               l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS),
327               shortcut_targets);
328     }
329   } else {
330     AddPair(list.get(),
331             l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS),
332             l10n_util::GetStringUTF16(
333                 IDS_RESET_PROFILE_SETTINGS_PROCESSING_SHORTCUTS));
334   }
335 
336   const ResettableSettingsSnapshot::ExtensionList& extensions =
337       snapshot.enabled_extensions();
338   std::string extension_names;
339   for (auto i = extensions.begin(); i != extensions.end(); ++i) {
340     if (!extension_names.empty())
341       extension_names += '\n';
342     extension_names += i->second;
343   }
344   if (!extension_names.empty()) {
345     AddPair(list.get(),
346             l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_EXTENSIONS),
347             extension_names);
348   }
349   return list;
350 }
351