// Copyright (c) 2012 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/extensions/extension_web_ui.h" #include #include #include #include "base/command_line.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/thread_task_runner_handle.h" #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/url_constants.h" #include "components/favicon/core/favicon_service.h" #include "components/favicon_base/favicon_util.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "content/public/common/bindings_policy.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/image_loader.h" #include "extensions/common/extension.h" #include "extensions/common/extension_icon_set.h" #include "extensions/common/extension_resource.h" #include "extensions/common/manifest_handlers/icons_handler.h" #include "extensions/common/manifest_handlers/incognito_info.h" #include "net/base/file_stream.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/page_transition_types.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/favicon_size.h" #include "ui/gfx/image/image_skia.h" using content::WebContents; using extensions::Extension; using extensions::URLOverrides; namespace { // The key to the override value for a page. const char kEntry[] = "entry"; // The key to whether or not the override is active (i.e., can be used). // Overrides may be inactive e.g. when an extension is disabled. const char kActive[] = "active"; // Iterates over |list| and: // - Converts any entries of the form to // { 'entry': , 'active': true }. // - Removes any duplicate entries. // We do the conversion because we previously stored these values as strings // rather than objects. // TODO(devlin): Remove the conversion once everyone's updated. void InitializeOverridesList(base::ListValue* list) { base::ListValue migrated; std::set seen_entries; for (base::Value* val : *list) { scoped_ptr new_dict(new base::DictionaryValue()); std::string entry_name; base::DictionaryValue* existing_dict = nullptr; if (val->GetAsDictionary(&existing_dict)) { bool success = existing_dict->GetString(kEntry, &entry_name); if (!success) // See comment about CHECK(success) in ForEachOverrideList. continue; new_dict->Swap(existing_dict); } else if (val->GetAsString(&entry_name)) { new_dict->SetString(kEntry, entry_name); new_dict->SetBoolean(kActive, true); } else { NOTREACHED(); continue; } if (seen_entries.count(entry_name) == 0) { seen_entries.insert(entry_name); migrated.Append(std::move(new_dict)); } } list->Swap(&migrated); } // Adds |override| to |list|, or, if there's already an entry for the override, // marks it as active. void AddOverridesToList(base::ListValue* list, const std::string& override) { for (base::Value* val : *list) { base::DictionaryValue* dict = nullptr; std::string entry; if (!val->GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) { NOTREACHED(); continue; } if (entry == override) { dict->SetBoolean(kActive, true); return; // All done! } } scoped_ptr dict(new base::DictionaryValue()); dict->SetString(kEntry, override); dict->SetBoolean(kActive, true); // Add the entry to the front of the list. list->Insert(0, dict.release()); } // Validates that each entry in |list| contains a valid url and points to an // extension contained in |all_extensions| (and, if not, removes it). void ValidateOverridesList(const extensions::ExtensionSet* all_extensions, base::ListValue* list) { base::ListValue migrated; for (base::Value* val : *list) { base::DictionaryValue* dict = nullptr; std::string entry; if (!val->GetAsDictionary(&dict) || !dict->GetString(kEntry, &entry)) { NOTREACHED(); continue; } scoped_ptr new_dict(new base::DictionaryValue()); new_dict->Swap(dict); GURL override_url(entry); if (!override_url.is_valid()) continue; if (!all_extensions->GetByID(override_url.host())) continue; migrated.Append(std::move(new_dict)); } list->Swap(&migrated); } // Reloads the page in |web_contents| if it uses the same profile as |profile| // and if the current URL is a chrome URL. void UnregisterAndReplaceOverrideForWebContents(const std::string& page, Profile* profile, WebContents* web_contents) { if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile) return; GURL url = web_contents->GetURL(); if (!url.SchemeIs(content::kChromeUIScheme) || url.host_piece() != page) return; // Don't use Reload() since |url| isn't the same as the internal URL that // NavigationController has. web_contents->GetController().LoadURL( url, content::Referrer::SanitizeForRequest( url, content::Referrer(url, blink::WebReferrerPolicyDefault)), ui::PAGE_TRANSITION_RELOAD, std::string()); } enum UpdateBehavior { UPDATE_DEACTIVATE, // Mark 'active' as false. UPDATE_REMOVE, // Remove the entry from the list. }; // Updates the entry (if any) for |override_url| in |overrides_list| according // to |behavior|. Returns true if anything changed. bool UpdateOverridesList(base::ListValue* overrides_list, const std::string& override_url, UpdateBehavior behavior) { base::ListValue::iterator iter = std::find_if(overrides_list->begin(), overrides_list->end(), [&override_url](const base::Value* value) { std::string entry; const base::DictionaryValue* dict = nullptr; return value->GetAsDictionary(&dict) && dict->GetString(kEntry, &entry) && entry == override_url; }); if (iter != overrides_list->end()) { switch (behavior) { case UPDATE_DEACTIVATE: { base::DictionaryValue* dict = nullptr; bool success = (*iter)->GetAsDictionary(&dict); // See comment about CHECK(success) in ForEachOverrideList. if (success) { dict->SetBoolean(kActive, false); break; } // Else fall through and erase the broken pref. } case UPDATE_REMOVE: overrides_list->Erase(iter, nullptr); break; } return true; } return false; } // Updates each list referenced in |overrides| according to |behavior|. void UpdateOverridesLists(Profile* profile, const URLOverrides::URLOverrideMap& overrides, UpdateBehavior behavior) { if (overrides.empty()) return; PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, ExtensionWebUI::kExtensionURLOverrides); base::DictionaryValue* all_overrides = update.Get(); for (const auto& page_override_pair : overrides) { base::ListValue* page_overrides = nullptr; // If it's being unregistered, it should already be in the list. if (!all_overrides->GetList(page_override_pair.first, &page_overrides)) { NOTREACHED(); continue; } if (UpdateOverridesList(page_overrides, page_override_pair.second.spec(), behavior)) { // This is the active override, so we need to find all existing // tabs for this override and get them to reload the original URL. base::Callback callback = base::Bind(&UnregisterAndReplaceOverrideForWebContents, page_override_pair.first, profile); extensions::ExtensionTabUtil::ForEachTab(callback); } } } // Run favicon callbck with image result. If no favicon was available then // |image| will be empty. void RunFaviconCallbackAsync( const favicon_base::FaviconResultsCallback& callback, const gfx::Image& image) { std::vector* favicon_bitmap_results = new std::vector(); const std::vector& image_reps = image.AsImageSkia().image_reps(); for (size_t i = 0; i < image_reps.size(); ++i) { const gfx::ImageSkiaRep& image_rep = image_reps[i]; scoped_refptr bitmap_data( new base::RefCountedBytes()); if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(), false, &bitmap_data->data())) { favicon_base::FaviconRawBitmapResult bitmap_result; bitmap_result.bitmap_data = bitmap_data; bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(), image_rep.pixel_height()); // Leave |bitmap_result|'s icon URL as the default of GURL(). bitmap_result.icon_type = favicon_base::FAVICON; favicon_bitmap_results->push_back(bitmap_result); } else { NOTREACHED() << "Could not encode extension favicon"; } } base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&favicon::FaviconService::FaviconResultsCallbackRunner, callback, base::Owned(favicon_bitmap_results))); } bool ValidateOverrideURL(const base::Value* override_url_value, const GURL& source_url, const extensions::ExtensionSet& extensions, GURL* override_url, const Extension** extension) { const base::DictionaryValue* dict = nullptr; std::string override; bool is_active = false; if (!override_url_value || !override_url_value->GetAsDictionary(&dict) || !dict->GetBoolean(kActive, &is_active) || !is_active || !dict->GetString(kEntry, &override)) { return false; } if (!source_url.query().empty()) override += "?" + source_url.query(); if (!source_url.ref().empty()) override += "#" + source_url.ref(); *override_url = GURL(override); if (!override_url->is_valid()) { return false; } *extension = extensions.GetByID(override_url->host()); if (!*extension) { return false; } return true; } // Fetches each list in the overrides dictionary and runs |callback| on it. void ForEachOverrideList( Profile* profile, const base::Callback& callback) { PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, ExtensionWebUI::kExtensionURLOverrides); base::DictionaryValue* all_overrides = update.Get(); // DictionaryValue::Iterator cannot be used to modify the list. Generate the // set of keys instead. std::vector keys; for (base::DictionaryValue::Iterator iter(*all_overrides); !iter.IsAtEnd(); iter.Advance()) { keys.push_back(iter.key()); } for (const std::string& key : keys) { base::ListValue* list = nullptr; bool success = all_overrides->GetList(key, &list); // In a perfect world, we could CHECK(success) here. Unfortunately, if a // user's prefs are mangled (by malware, user modification, hard drive // corruption, evil robots, etc), this will fail. Instead, delete the pref. if (!success) { all_overrides->Remove(key, nullptr); continue; } callback.Run(list); } } } // namespace const char ExtensionWebUI::kExtensionURLOverrides[] = "extensions.chrome_url_overrides"; ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url) : WebUIController(web_ui), url_(url) { Profile* profile = Profile::FromWebUI(web_ui); const Extension* extension = extensions::ExtensionRegistry::Get( profile)->enabled_extensions().GetExtensionOrAppByURL(url); DCHECK(extension); // The base class defaults to enabling WebUI bindings, but we don't need // those (this is also reflected in ChromeWebUIControllerFactory:: // UseWebUIBindingsForURL). int bindings = 0; web_ui->SetBindings(bindings); // Hack: A few things we specialize just for the bookmark manager. if (extension->id() == extension_misc::kBookmarkManagerId) { bookmark_manager_private_drag_event_router_.reset( new extensions::BookmarkManagerPrivateDragEventRouter( profile, web_ui->GetWebContents())); web_ui->SetLinkTransitionType(ui::PAGE_TRANSITION_AUTO_BOOKMARK); } } ExtensionWebUI::~ExtensionWebUI() {} extensions::BookmarkManagerPrivateDragEventRouter* ExtensionWebUI::bookmark_manager_private_drag_event_router() { return bookmark_manager_private_drag_event_router_.get(); } //////////////////////////////////////////////////////////////////////////////// // chrome:// URL overrides // static void ExtensionWebUI::RegisterProfilePrefs( user_prefs::PrefRegistrySyncable* registry) { registry->RegisterDictionaryPref(kExtensionURLOverrides); } // static bool ExtensionWebUI::HandleChromeURLOverride( GURL* url, content::BrowserContext* browser_context) { if (!url->SchemeIs(content::kChromeUIScheme)) return false; Profile* profile = Profile::FromBrowserContext(browser_context); const base::DictionaryValue* overrides = profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); std::string url_host = url->host(); const base::ListValue* url_list = NULL; if (!overrides || !overrides->GetList(url_host, &url_list)) return false; extensions::ExtensionRegistry* registry = extensions::ExtensionRegistry::Get(browser_context); const extensions::ExtensionSet& extensions = registry->enabled_extensions(); GURL component_url; bool found_component_override = false; // Iterate over the URL list looking for a suitable override. If a // valid non-component override is encountered it is chosen immediately. for (size_t i = 0; i < url_list->GetSize(); ++i) { const base::Value* val = NULL; url_list->Get(i, &val); GURL override_url; const Extension* extension; if (!ValidateOverrideURL( val, *url, extensions, &override_url, &extension)) { // Invalid overrides are cleaned up on startup. continue; } // We can't handle chrome-extension URLs in incognito mode unless the // extension uses split mode. bool incognito_override_allowed = extensions::IncognitoInfo::IsSplitMode(extension) && extensions::util::IsIncognitoEnabled(extension->id(), profile); if (profile->IsOffTheRecord() && !incognito_override_allowed) { continue; } if (!extensions::Manifest::IsComponentLocation(extension->location())) { *url = override_url; return true; } if (!found_component_override) { found_component_override = true; component_url = override_url; } } // If no other non-component overrides were found, use the first known // component override, if any. if (found_component_override) { *url = component_url; return true; } return false; } // static bool ExtensionWebUI::HandleChromeURLOverrideReverse( GURL* url, content::BrowserContext* browser_context) { Profile* profile = Profile::FromBrowserContext(browser_context); const base::DictionaryValue* overrides = profile->GetPrefs()->GetDictionary(kExtensionURLOverrides); if (!overrides) return false; // Find the reverse mapping based on the given URL. For example this maps the // internal URL // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to // chrome://bookmarks/#1 for display in the omnibox. for (base::DictionaryValue::Iterator dict_iter(*overrides); !dict_iter.IsAtEnd(); dict_iter.Advance()) { const base::ListValue* url_list = nullptr; if (!dict_iter.value().GetAsList(&url_list)) continue; for (base::ListValue::const_iterator list_iter = url_list->begin(); list_iter != url_list->end(); ++list_iter) { const base::DictionaryValue* dict = nullptr; if (!(*list_iter)->GetAsDictionary(&dict)) continue; std::string override; if (!dict->GetString(kEntry, &override)) continue; if (base::StartsWith(url->spec(), override, base::CompareCase::SENSITIVE)) { GURL original_url(content::kChromeUIScheme + std::string("://") + dict_iter.key() + url->spec().substr(override.length())); *url = original_url; return true; } } } return false; } // static void ExtensionWebUI::InitializeChromeURLOverrides(Profile* profile) { ForEachOverrideList(profile, base::Bind(&InitializeOverridesList)); } // static void ExtensionWebUI::ValidateChromeURLOverrides(Profile* profile) { scoped_ptr all_extensions = extensions::ExtensionRegistry::Get(profile)-> GenerateInstalledExtensionsSet(); ForEachOverrideList(profile, base::Bind(&ValidateOverridesList, all_extensions.get())); } // static void ExtensionWebUI::RegisterOrActivateChromeURLOverrides( Profile* profile, const URLOverrides::URLOverrideMap& overrides) { if (overrides.empty()) return; PrefService* prefs = profile->GetPrefs(); DictionaryPrefUpdate update(prefs, kExtensionURLOverrides); base::DictionaryValue* all_overrides = update.Get(); for (const auto& page_override_pair : overrides) { base::ListValue* page_overrides = nullptr; if (!all_overrides->GetList(page_override_pair.first, &page_overrides)) { page_overrides = new base::ListValue(); all_overrides->Set(page_override_pair.first, page_overrides); } AddOverridesToList(page_overrides, page_override_pair.second.spec()); } } // static void ExtensionWebUI::DeactivateChromeURLOverrides( Profile* profile, const URLOverrides::URLOverrideMap& overrides) { UpdateOverridesLists(profile, overrides, UPDATE_DEACTIVATE); } // static void ExtensionWebUI::UnregisterChromeURLOverrides( Profile* profile, const URLOverrides::URLOverrideMap& overrides) { UpdateOverridesLists(profile, overrides, UPDATE_REMOVE); } // static void ExtensionWebUI::GetFaviconForURL( Profile* profile, const GURL& page_url, const favicon_base::FaviconResultsCallback& callback) { const Extension* extension = extensions::ExtensionRegistry::Get( profile)->enabled_extensions().GetByID(page_url.host()); if (!extension) { RunFaviconCallbackAsync(callback, gfx::Image()); return; } // Fetch resources for all supported scale factors for which there are // resources. Load image reps for all supported scale factors (in addition to // 1x) immediately instead of in an as needed fashion to be consistent with // how favicons are requested for chrome:// and page URLs. const std::vector& favicon_scales = favicon_base::GetFaviconScales(); std::vector info_list; for (size_t i = 0; i < favicon_scales.size(); ++i) { float scale = favicon_scales[i]; int pixel_size = static_cast(gfx::kFaviconSize * scale); extensions::ExtensionResource icon_resource = extensions::IconsInfo::GetIconResource(extension, pixel_size, ExtensionIconSet::MATCH_BIGGER); ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale); info_list.push_back(extensions::ImageLoader::ImageRepresentation( icon_resource, extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE, gfx::Size(pixel_size, pixel_size), resource_scale_factor)); } // LoadImagesAsync actually can run callback synchronously. We want to force // async. extensions::ImageLoader::Get(profile)->LoadImagesAsync( extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback)); }