// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/profile_resetter/resettable_settings_snapshot.h" #include "base/guid.h" #include "base/json/json_writer.h" #include "base/md5.h" #include "base/prefs/pref_service.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/cancellation_flag.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profile_resetter/profile_reset_report.pb.h" #include "chrome/browser/profile_resetter/reset_report_uploader.h" #include "chrome/browser/profile_resetter/reset_report_uploader_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/common/channel_info.h" #include "chrome/common/chrome_content_client.h" #include "chrome/common/pref_names.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "components/feedback/feedback_data.h" #include "components/feedback/feedback_util.h" #include "components/search_engines/template_url_service.h" #include "components/version_info/version_info.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/extension_registry.h" #include "grit/components_strings.h" #include "ui/base/l10n/l10n_util.h" using feedback::FeedbackData; namespace { // Feedback bucket label. const char kProfileResetWebUIBucket[] = "ProfileResetReport"; // Dictionary keys for feedback report. const char kDefaultSearchEnginePath[] = "default_search_engine"; const char kEnabledExtensions[] = "enabled_extensions"; const char kHomepageIsNewTabPage[] = "homepage_is_ntp"; const char kHomepagePath[] = "homepage"; const char kShortcuts[] = "shortcuts"; const char kShowHomeButton[] = "show_home_button"; const char kStartupTypePath[] = "startup_type"; const char kStartupURLPath[] = "startup_urls"; const char kGuid[] = "guid"; template void AddPair(base::ListValue* list, const base::string16& key, const StringType& value) { base::DictionaryValue* results = new base::DictionaryValue(); results->SetString("key", key); results->SetString("value", value); list->Append(results); } } // namespace ResettableSettingsSnapshot::ResettableSettingsSnapshot( Profile* profile) : startup_(SessionStartupPref::GetStartupPref(profile)), shortcuts_determined_(false), weak_ptr_factory_(this) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // URLs are always stored sorted. std::sort(startup_.urls.begin(), startup_.urls.end()); PrefService* prefs = profile->GetPrefs(); DCHECK(prefs); homepage_ = prefs->GetString(prefs::kHomePage); homepage_is_ntp_ = prefs->GetBoolean(prefs::kHomePageIsNewTabPage); show_home_button_ = prefs->GetBoolean(prefs::kShowHomeButton); TemplateURLService* service = TemplateURLServiceFactory::GetForProfile(profile); DCHECK(service); TemplateURL* dse = service->GetDefaultSearchProvider(); if (dse) dse_url_ = dse->url(); const extensions::ExtensionSet& enabled_ext = extensions::ExtensionRegistry::Get(profile)->enabled_extensions(); enabled_extensions_.reserve(enabled_ext.size()); for (extensions::ExtensionSet::const_iterator it = enabled_ext.begin(); it != enabled_ext.end(); ++it) enabled_extensions_.push_back(std::make_pair((*it)->id(), (*it)->name())); // ExtensionSet is sorted but it seems to be an implementation detail. std::sort(enabled_extensions_.begin(), enabled_extensions_.end()); // Calculate the MD5 sum of the GUID to make sure that no part of the GUID // contains information identifying the sender of the report. guid_ = base::MD5String(base::GenerateGUID()); } ResettableSettingsSnapshot::~ResettableSettingsSnapshot() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (cancellation_flag_.get()) cancellation_flag_->data.Set(); } void ResettableSettingsSnapshot::Subtract( const ResettableSettingsSnapshot& snapshot) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ExtensionList extensions = base::STLSetDifference( enabled_extensions_, snapshot.enabled_extensions_); enabled_extensions_.swap(extensions); } int ResettableSettingsSnapshot::FindDifferentFields( const ResettableSettingsSnapshot& snapshot) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); int bit_mask = 0; if (startup_.type != snapshot.startup_.type || startup_.urls != snapshot.startup_.urls) bit_mask |= STARTUP_MODE; if (homepage_is_ntp_ != snapshot.homepage_is_ntp_ || homepage_ != snapshot.homepage_ || show_home_button_ != snapshot.show_home_button_) bit_mask |= HOMEPAGE; if (dse_url_ != snapshot.dse_url_) bit_mask |= DSE_URL; if (enabled_extensions_ != snapshot.enabled_extensions_) bit_mask |= EXTENSIONS; if (shortcuts_ != snapshot.shortcuts_) bit_mask |= SHORTCUTS; static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31, "new field needs to be added here"); return bit_mask; } void ResettableSettingsSnapshot::RequestShortcuts( const base::Closure& callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(!cancellation_flag_.get() && !shortcuts_determined()); cancellation_flag_ = new SharedCancellationFlag; content::BrowserThread::PostTaskAndReplyWithResult( content::BrowserThread::FILE, FROM_HERE, base::Bind(&GetChromeLaunchShortcuts, cancellation_flag_), base::Bind(&ResettableSettingsSnapshot::SetShortcutsAndReport, weak_ptr_factory_.GetWeakPtr(), callback)); } void ResettableSettingsSnapshot::SetShortcutsAndReport( const base::Closure& callback, const std::vector& shortcuts) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); shortcuts_ = shortcuts; shortcuts_determined_ = true; cancellation_flag_ = NULL; if (!callback.is_null()) callback.Run(); } std::string SerializeSettingsReport(const ResettableSettingsSnapshot& snapshot, int field_mask) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); base::DictionaryValue dict; if (field_mask & ResettableSettingsSnapshot::STARTUP_MODE) { base::ListValue* list = new base::ListValue; const std::vector& urls = snapshot.startup_urls(); for (std::vector::const_iterator i = urls.begin(); i != urls.end(); ++i) list->AppendString(i->spec()); dict.Set(kStartupURLPath, list); dict.SetInteger(kStartupTypePath, snapshot.startup_type()); } if (field_mask & ResettableSettingsSnapshot::HOMEPAGE) { dict.SetString(kHomepagePath, snapshot.homepage()); dict.SetBoolean(kHomepageIsNewTabPage, snapshot.homepage_is_ntp()); dict.SetBoolean(kShowHomeButton, snapshot.show_home_button()); } if (field_mask & ResettableSettingsSnapshot::DSE_URL) dict.SetString(kDefaultSearchEnginePath, snapshot.dse_url()); if (field_mask & ResettableSettingsSnapshot::EXTENSIONS) { base::ListValue* list = new base::ListValue; const ResettableSettingsSnapshot::ExtensionList& extensions = snapshot.enabled_extensions(); for (ResettableSettingsSnapshot::ExtensionList::const_iterator i = extensions.begin(); i != extensions.end(); ++i) { // Replace "\"" to simplify server-side analysis. std::string ext_name; base::ReplaceChars(i->second, "\"", "\'", &ext_name); list->AppendString(i->first + ";" + ext_name); } dict.Set(kEnabledExtensions, list); } if (field_mask & ResettableSettingsSnapshot::SHORTCUTS) { base::ListValue* list = new base::ListValue; const std::vector& shortcuts = snapshot.shortcuts(); for (std::vector::const_iterator i = shortcuts.begin(); i != shortcuts.end(); ++i) { base::string16 arguments; // Replace "\"" to simplify server-side analysis. base::ReplaceChars(i->second, base::ASCIIToUTF16("\""), base::ASCIIToUTF16("\'"), &arguments); list->AppendString(arguments); } dict.Set(kShortcuts, list); } dict.SetString(kGuid, snapshot.guid()); static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31, "new field needs to be serialized here"); std::string json; base::JSONWriter::Write(dict, &json); return json; } scoped_ptr SerializeSettingsReportToProto( const ResettableSettingsSnapshot& snapshot, int field_mask) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); scoped_ptr report( new reset_report::ChromeResetReport()); if (field_mask & ResettableSettingsSnapshot::STARTUP_MODE) { for (const auto& url : snapshot.startup_urls()) report->add_startup_url_path(url.spec()); switch (snapshot.startup_type()) { case SessionStartupPref::DEFAULT: report->set_startup_type( reset_report::ChromeResetReport_SessionStartupType_DEFAULT); break; case SessionStartupPref::LAST: report->set_startup_type( reset_report::ChromeResetReport_SessionStartupType_LAST); break; case SessionStartupPref::URLS: report->set_startup_type( reset_report::ChromeResetReport_SessionStartupType_URLS); break; } } if (field_mask & ResettableSettingsSnapshot::HOMEPAGE) { report->set_homepage_path(snapshot.homepage()); report->set_homepage_is_new_tab_page(snapshot.homepage_is_ntp()); report->set_show_home_button(snapshot.show_home_button()); } if (field_mask & ResettableSettingsSnapshot::DSE_URL) report->set_default_search_engine_path(snapshot.dse_url()); if (field_mask & ResettableSettingsSnapshot::EXTENSIONS) { for (const auto& enabled_extension : snapshot.enabled_extensions()) { reset_report::ChromeResetReport_Extension* new_extension = report->add_enabled_extensions(); new_extension->set_extension_id(enabled_extension.first); new_extension->set_extension_name(enabled_extension.second); } } if (field_mask & ResettableSettingsSnapshot::SHORTCUTS) { for (const auto& shortcut_command : snapshot.shortcuts()) report->add_shortcuts(base::UTF16ToUTF8(shortcut_command.second)); } report->set_guid(snapshot.guid()); static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31, "new field needs to be serialized here"); return report; } void SendSettingsFeedback(const std::string& report, Profile* profile) { scoped_refptr feedback_data = new FeedbackData(); feedback_data->set_category_tag(kProfileResetWebUIBucket); feedback_data->set_description(report); feedback_data->set_image(make_scoped_ptr(new std::string)); feedback_data->set_context(profile); feedback_data->set_page_url(""); feedback_data->set_user_email(""); feedback_util::SendReport(feedback_data); } void SendSettingsFeedbackProto(const reset_report::ChromeResetReport& report, Profile* profile) { ResetReportUploaderFactory::GetForBrowserContext(profile) ->DispatchReport(report); } scoped_ptr GetReadableFeedbackForSnapshot( Profile* profile, const ResettableSettingsSnapshot& snapshot) { DCHECK(profile); DCHECK_CURRENTLY_ON(content::BrowserThread::UI); scoped_ptr list(new base::ListValue); AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_LOCALE), g_browser_process->GetApplicationLocale()); AddPair(list.get(), l10n_util::GetStringUTF16(IDS_VERSION_UI_USER_AGENT), GetUserAgent()); std::string version = version_info::GetVersionNumber(); version += chrome::GetChannelString(); AddPair(list.get(), l10n_util::GetStringUTF16(IDS_PRODUCT_NAME), version); // Add snapshot data. const std::vector& urls = snapshot.startup_urls(); std::string startup_urls; for (std::vector::const_iterator i = urls.begin(); i != urls.end(); ++i) { if (!startup_urls.empty()) startup_urls += ' '; startup_urls += i->host(); } if (!startup_urls.empty()) { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_URLS), startup_urls); } base::string16 startup_type; switch (snapshot.startup_type()) { case SessionStartupPref::DEFAULT: startup_type = l10n_util::GetStringUTF16(IDS_OPTIONS_STARTUP_SHOW_NEWTAB); break; case SessionStartupPref::LAST: startup_type = l10n_util::GetStringUTF16( IDS_OPTIONS_STARTUP_RESTORE_LAST_SESSION); break; case SessionStartupPref::URLS: startup_type = l10n_util::GetStringUTF16(IDS_OPTIONS_STARTUP_SHOW_PAGES); break; default: break; } AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_STARTUP_TYPE), startup_type); if (!snapshot.homepage().empty()) { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE), snapshot.homepage()); } int is_ntp_message_id = snapshot.homepage_is_ntp() ? IDS_RESET_PROFILE_SETTINGS_YES : IDS_RESET_PROFILE_SETTINGS_NO; AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_HOMEPAGE_IS_NTP), l10n_util::GetStringUTF16(is_ntp_message_id)); int show_home_button_id = snapshot.show_home_button() ? IDS_RESET_PROFILE_SETTINGS_YES : IDS_RESET_PROFILE_SETTINGS_NO; AddPair( list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHOW_HOME_BUTTON), l10n_util::GetStringUTF16(show_home_button_id)); TemplateURLService* service = TemplateURLServiceFactory::GetForProfile(profile); DCHECK(service); TemplateURL* dse = service->GetDefaultSearchProvider(); if (dse) { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_DSE), dse->GenerateSearchURL(service->search_terms_data()).host()); } if (snapshot.shortcuts_determined()) { base::string16 shortcut_targets; const std::vector& shortcuts = snapshot.shortcuts(); for (std::vector::const_iterator i = shortcuts.begin(); i != shortcuts.end(); ++i) { if (!shortcut_targets.empty()) shortcut_targets += base::ASCIIToUTF16("\n"); shortcut_targets += base::ASCIIToUTF16("chrome.exe "); shortcut_targets += i->second; } if (!shortcut_targets.empty()) { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS), shortcut_targets); } } else { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_SHORTCUTS), l10n_util::GetStringUTF16( IDS_RESET_PROFILE_SETTINGS_PROCESSING_SHORTCUTS)); } const ResettableSettingsSnapshot::ExtensionList& extensions = snapshot.enabled_extensions(); std::string extension_names; for (ResettableSettingsSnapshot::ExtensionList::const_iterator i = extensions.begin(); i != extensions.end(); ++i) { if (!extension_names.empty()) extension_names += '\n'; extension_names += i->second; } if (!extension_names.empty()) { AddPair(list.get(), l10n_util::GetStringUTF16(IDS_RESET_PROFILE_SETTINGS_EXTENSIONS), extension_names); } return list; }