• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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/extensions/extension_web_ui.h"
6 
7 #include <set>
8 #include <vector>
9 
10 #include "base/command_line.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_tab_util.h"
18 #include "chrome/browser/extensions/extension_util.h"
19 #include "chrome/browser/favicon/favicon_service.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "chrome/common/url_constants.h"
24 #include "components/favicon_base/favicon_util.h"
25 #include "components/pref_registry/pref_registry_syncable.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_ui.h"
29 #include "content/public/common/bindings_policy.h"
30 #include "content/public/common/page_transition_types.h"
31 #include "extensions/browser/extension_registry.h"
32 #include "extensions/browser/image_loader.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/extension_icon_set.h"
35 #include "extensions/common/extension_resource.h"
36 #include "extensions/common/manifest_handlers/icons_handler.h"
37 #include "extensions/common/manifest_handlers/incognito_info.h"
38 #include "net/base/file_stream.h"
39 #include "third_party/skia/include/core/SkBitmap.h"
40 #include "ui/gfx/codec/png_codec.h"
41 #include "ui/gfx/favicon_size.h"
42 #include "ui/gfx/image/image_skia.h"
43 
44 using content::WebContents;
45 using extensions::Extension;
46 using extensions::URLOverrides;
47 
48 namespace {
49 
50 // De-dupes the items in |list|. Assumes the values are strings.
CleanUpDuplicates(base::ListValue * list)51 void CleanUpDuplicates(base::ListValue* list) {
52   std::set<std::string> seen_values;
53 
54   // Loop backwards as we may be removing items.
55   for (size_t i = list->GetSize() - 1; (i + 1) > 0; --i) {
56     std::string value;
57     if (!list->GetString(i, &value)) {
58       NOTREACHED();
59       continue;
60     }
61 
62     if (seen_values.find(value) == seen_values.end())
63       seen_values.insert(value);
64     else
65       list->Remove(i, NULL);
66   }
67 }
68 
69 // Reloads the page in |web_contents| if it uses the same profile as |profile|
70 // and if the current URL is a chrome URL.
UnregisterAndReplaceOverrideForWebContents(const std::string & page,Profile * profile,WebContents * web_contents)71 void UnregisterAndReplaceOverrideForWebContents(const std::string& page,
72                                                 Profile* profile,
73                                                 WebContents* web_contents) {
74   if (Profile::FromBrowserContext(web_contents->GetBrowserContext()) != profile)
75     return;
76 
77   GURL url = web_contents->GetURL();
78   if (!url.SchemeIs(content::kChromeUIScheme) || url.host() != page)
79     return;
80 
81   // Don't use Reload() since |url| isn't the same as the internal URL that
82   // NavigationController has.
83   web_contents->GetController().LoadURL(
84       url, content::Referrer(url, blink::WebReferrerPolicyDefault),
85       content::PAGE_TRANSITION_RELOAD, std::string());
86 }
87 
88 // Run favicon callbck with image result. If no favicon was available then
89 // |image| will be empty.
RunFaviconCallbackAsync(const favicon_base::FaviconResultsCallback & callback,const gfx::Image & image)90 void RunFaviconCallbackAsync(
91     const favicon_base::FaviconResultsCallback& callback,
92     const gfx::Image& image) {
93   std::vector<favicon_base::FaviconRawBitmapResult>* favicon_bitmap_results =
94       new std::vector<favicon_base::FaviconRawBitmapResult>();
95 
96   const std::vector<gfx::ImageSkiaRep>& image_reps =
97       image.AsImageSkia().image_reps();
98   for (size_t i = 0; i < image_reps.size(); ++i) {
99     const gfx::ImageSkiaRep& image_rep = image_reps[i];
100     scoped_refptr<base::RefCountedBytes> bitmap_data(
101         new base::RefCountedBytes());
102     if (gfx::PNGCodec::EncodeBGRASkBitmap(image_rep.sk_bitmap(),
103                                           false,
104                                           &bitmap_data->data())) {
105       favicon_base::FaviconRawBitmapResult bitmap_result;
106       bitmap_result.bitmap_data = bitmap_data;
107       bitmap_result.pixel_size = gfx::Size(image_rep.pixel_width(),
108                                             image_rep.pixel_height());
109       // Leave |bitmap_result|'s icon URL as the default of GURL().
110       bitmap_result.icon_type = favicon_base::FAVICON;
111 
112       favicon_bitmap_results->push_back(bitmap_result);
113     } else {
114       NOTREACHED() << "Could not encode extension favicon";
115     }
116   }
117 
118   base::MessageLoopProxy::current()->PostTask(
119       FROM_HERE,
120       base::Bind(&FaviconService::FaviconResultsCallbackRunner,
121                  callback,
122                  base::Owned(favicon_bitmap_results)));
123 }
124 
ValidateOverrideURL(const base::Value * override_url_value,const GURL & source_url,const extensions::ExtensionSet & extensions,GURL * override_url,const Extension ** extension)125 bool ValidateOverrideURL(const base::Value* override_url_value,
126                          const GURL& source_url,
127                          const extensions::ExtensionSet& extensions,
128                          GURL* override_url,
129                          const Extension** extension) {
130   std::string override;
131   if (!override_url_value || !override_url_value->GetAsString(&override)) {
132     return false;
133   }
134   if (!source_url.query().empty())
135     override += "?" + source_url.query();
136   if (!source_url.ref().empty())
137     override += "#" + source_url.ref();
138   *override_url = GURL(override);
139   if (!override_url->is_valid()) {
140     return false;
141   }
142   *extension = extensions.GetByID(override_url->host());
143   if (!*extension) {
144     return false;
145   }
146   return true;
147 }
148 
149 }  // namespace
150 
151 const char ExtensionWebUI::kExtensionURLOverrides[] =
152     "extensions.chrome_url_overrides";
153 
ExtensionWebUI(content::WebUI * web_ui,const GURL & url)154 ExtensionWebUI::ExtensionWebUI(content::WebUI* web_ui, const GURL& url)
155     : WebUIController(web_ui),
156       url_(url) {
157   Profile* profile = Profile::FromWebUI(web_ui);
158   ExtensionService* service = profile->GetExtensionService();
159   const Extension* extension =
160       service->extensions()->GetExtensionOrAppByURL(url);
161   DCHECK(extension);
162 
163   // The base class defaults to enabling WebUI bindings, but we don't need
164   // those (this is also reflected in ChromeWebUIControllerFactory::
165   // UseWebUIBindingsForURL).
166   int bindings = 0;
167   web_ui->SetBindings(bindings);
168 
169   // Hack: A few things we specialize just for the bookmark manager.
170   if (extension->id() == extension_misc::kBookmarkManagerId) {
171     bookmark_manager_private_drag_event_router_.reset(
172         new extensions::BookmarkManagerPrivateDragEventRouter(
173             profile, web_ui->GetWebContents()));
174 
175     web_ui->SetLinkTransitionType(content::PAGE_TRANSITION_AUTO_BOOKMARK);
176   }
177 }
178 
~ExtensionWebUI()179 ExtensionWebUI::~ExtensionWebUI() {}
180 
181 extensions::BookmarkManagerPrivateDragEventRouter*
bookmark_manager_private_drag_event_router()182 ExtensionWebUI::bookmark_manager_private_drag_event_router() {
183   return bookmark_manager_private_drag_event_router_.get();
184 }
185 
186 ////////////////////////////////////////////////////////////////////////////////
187 // chrome:// URL overrides
188 
189 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)190 void ExtensionWebUI::RegisterProfilePrefs(
191     user_prefs::PrefRegistrySyncable* registry) {
192   registry->RegisterDictionaryPref(
193       kExtensionURLOverrides,
194       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
195 }
196 
197 // static
HandleChromeURLOverride(GURL * url,content::BrowserContext * browser_context)198 bool ExtensionWebUI::HandleChromeURLOverride(
199     GURL* url,
200     content::BrowserContext* browser_context) {
201   if (!url->SchemeIs(content::kChromeUIScheme))
202     return false;
203 
204   Profile* profile = Profile::FromBrowserContext(browser_context);
205   const base::DictionaryValue* overrides =
206       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
207 
208   std::string url_host = url->host();
209   const base::ListValue* url_list = NULL;
210   if (!overrides || !overrides->GetList(url_host, &url_list))
211     return false;
212 
213   extensions::ExtensionRegistry* registry =
214       extensions::ExtensionRegistry::Get(browser_context);
215   const extensions::ExtensionSet& extensions = registry->enabled_extensions();
216 
217   GURL component_url;
218   bool found_component_override = false;
219 
220   // Iterate over the URL list looking for a suitable override. If a
221   // valid non-component override is encountered it is chosen immediately.
222   for (size_t i = 0; i < url_list->GetSize(); ++i) {
223     const base::Value* val = NULL;
224     url_list->Get(i, &val);
225 
226     GURL override_url;
227     const Extension* extension;
228     if (!ValidateOverrideURL(
229             val, *url, extensions, &override_url, &extension)) {
230       LOG(WARNING) << "Invalid chrome URL override";
231       UnregisterChromeURLOverride(url_host, profile, val);
232       // The above Unregister call will remove this item from url_list.
233       --i;
234       continue;
235     }
236 
237     // We can't handle chrome-extension URLs in incognito mode unless the
238     // extension uses split mode.
239     bool incognito_override_allowed =
240         extensions::IncognitoInfo::IsSplitMode(extension) &&
241         extensions::util::IsIncognitoEnabled(extension->id(), profile);
242     if (profile->IsOffTheRecord() && !incognito_override_allowed) {
243       continue;
244     }
245 
246     if (!extensions::Manifest::IsComponentLocation(extension->location())) {
247       *url = override_url;
248       return true;
249     }
250 
251     if (!found_component_override) {
252       found_component_override = true;
253       component_url = override_url;
254     }
255   }
256 
257   // If no other non-component overrides were found, use the first known
258   // component override, if any.
259   if (found_component_override) {
260     *url = component_url;
261     return true;
262   }
263 
264   return false;
265 }
266 
267 // static
HandleChromeURLOverrideReverse(GURL * url,content::BrowserContext * browser_context)268 bool ExtensionWebUI::HandleChromeURLOverrideReverse(
269     GURL* url, content::BrowserContext* browser_context) {
270   Profile* profile = Profile::FromBrowserContext(browser_context);
271   const base::DictionaryValue* overrides =
272       profile->GetPrefs()->GetDictionary(kExtensionURLOverrides);
273   if (!overrides)
274     return false;
275 
276   // Find the reverse mapping based on the given URL. For example this maps the
277   // internal URL
278   // chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html#1 to
279   // chrome://bookmarks/#1 for display in the omnibox.
280   for (base::DictionaryValue::Iterator it(*overrides); !it.IsAtEnd();
281        it.Advance()) {
282     const base::ListValue* url_list = NULL;
283     if (!it.value().GetAsList(&url_list))
284       continue;
285 
286     for (base::ListValue::const_iterator it2 = url_list->begin();
287          it2 != url_list->end(); ++it2) {
288       std::string override;
289       if (!(*it2)->GetAsString(&override))
290         continue;
291       if (StartsWithASCII(url->spec(), override, true)) {
292         GURL original_url(content::kChromeUIScheme + std::string("://") +
293                           it.key() + url->spec().substr(override.length()));
294         *url = original_url;
295         return true;
296       }
297     }
298   }
299 
300   return false;
301 }
302 
303 // static
RegisterChromeURLOverrides(Profile * profile,const URLOverrides::URLOverrideMap & overrides)304 void ExtensionWebUI::RegisterChromeURLOverrides(
305     Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
306   if (overrides.empty())
307     return;
308 
309   PrefService* prefs = profile->GetPrefs();
310   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
311   base::DictionaryValue* all_overrides = update.Get();
312 
313   // For each override provided by the extension, add it to the front of
314   // the override list if it's not already in the list.
315   URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
316   for (; iter != overrides.end(); ++iter) {
317     const std::string& key = iter->first;
318     base::ListValue* page_overrides = NULL;
319     if (!all_overrides->GetList(key, &page_overrides)) {
320       page_overrides = new base::ListValue();
321       all_overrides->Set(key, page_overrides);
322     } else {
323       CleanUpDuplicates(page_overrides);
324 
325       // Verify that the override isn't already in the list.
326       base::ListValue::iterator i = page_overrides->begin();
327       for (; i != page_overrides->end(); ++i) {
328         std::string override_val;
329         if (!(*i)->GetAsString(&override_val)) {
330           NOTREACHED();
331           continue;
332         }
333         if (override_val == iter->second.spec())
334           break;
335       }
336       // This value is already in the list, leave it alone.
337       if (i != page_overrides->end())
338         continue;
339     }
340     // Insert the override at the front of the list.  Last registered override
341     // wins.
342     page_overrides->Insert(0, new base::StringValue(iter->second.spec()));
343   }
344 }
345 
346 // static
UnregisterAndReplaceOverride(const std::string & page,Profile * profile,base::ListValue * list,const base::Value * override)347 void ExtensionWebUI::UnregisterAndReplaceOverride(const std::string& page,
348                                                   Profile* profile,
349                                                   base::ListValue* list,
350                                                   const base::Value* override) {
351   size_t index = 0;
352   bool found = list->Remove(*override, &index);
353   if (found && index == 0) {
354     // This is the active override, so we need to find all existing
355     // tabs for this override and get them to reload the original URL.
356     base::Callback<void(WebContents*)> callback =
357         base::Bind(&UnregisterAndReplaceOverrideForWebContents, page, profile);
358     extensions::ExtensionTabUtil::ForEachTab(callback);
359   }
360 }
361 
362 // static
UnregisterChromeURLOverride(const std::string & page,Profile * profile,const base::Value * override)363 void ExtensionWebUI::UnregisterChromeURLOverride(const std::string& page,
364                                                  Profile* profile,
365                                                  const base::Value* override) {
366   if (!override)
367     return;
368   PrefService* prefs = profile->GetPrefs();
369   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
370   base::DictionaryValue* all_overrides = update.Get();
371   base::ListValue* page_overrides = NULL;
372   if (!all_overrides->GetList(page, &page_overrides)) {
373     // If it's being unregistered, it should already be in the list.
374     NOTREACHED();
375     return;
376   } else {
377     UnregisterAndReplaceOverride(page, profile, page_overrides, override);
378   }
379 }
380 
381 // static
UnregisterChromeURLOverrides(Profile * profile,const URLOverrides::URLOverrideMap & overrides)382 void ExtensionWebUI::UnregisterChromeURLOverrides(
383     Profile* profile, const URLOverrides::URLOverrideMap& overrides) {
384   if (overrides.empty())
385     return;
386   PrefService* prefs = profile->GetPrefs();
387   DictionaryPrefUpdate update(prefs, kExtensionURLOverrides);
388   base::DictionaryValue* all_overrides = update.Get();
389   URLOverrides::URLOverrideMap::const_iterator iter = overrides.begin();
390   for (; iter != overrides.end(); ++iter) {
391     const std::string& page = iter->first;
392     base::ListValue* page_overrides = NULL;
393     if (!all_overrides->GetList(page, &page_overrides)) {
394       // If it's being unregistered, it should already be in the list.
395       NOTREACHED();
396       continue;
397     } else {
398       base::StringValue override(iter->second.spec());
399       UnregisterAndReplaceOverride(iter->first, profile,
400                                    page_overrides, &override);
401     }
402   }
403 }
404 
405 // static
GetFaviconForURL(Profile * profile,const GURL & page_url,const favicon_base::FaviconResultsCallback & callback)406 void ExtensionWebUI::GetFaviconForURL(
407     Profile* profile,
408     const GURL& page_url,
409     const favicon_base::FaviconResultsCallback& callback) {
410   // Even when the extensions service is enabled by default, it's still
411   // disabled in incognito mode.
412   ExtensionService* service = profile->GetExtensionService();
413   if (!service) {
414     RunFaviconCallbackAsync(callback, gfx::Image());
415     return;
416   }
417   const Extension* extension = service->extensions()->GetByID(page_url.host());
418   if (!extension) {
419     RunFaviconCallbackAsync(callback, gfx::Image());
420     return;
421   }
422 
423   // Fetch resources for all supported scale factors for which there are
424   // resources. Load image reps for all supported scale factors (in addition to
425   // 1x) immediately instead of in an as needed fashion to be consistent with
426   // how favicons are requested for chrome:// and page URLs.
427   const std::vector<float>& favicon_scales = favicon_base::GetFaviconScales();
428   std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
429   for (size_t i = 0; i < favicon_scales.size(); ++i) {
430     float scale = favicon_scales[i];
431     int pixel_size = static_cast<int>(gfx::kFaviconSize * scale);
432     extensions::ExtensionResource icon_resource =
433         extensions::IconsInfo::GetIconResource(extension,
434                                                pixel_size,
435                                                ExtensionIconSet::MATCH_BIGGER);
436 
437     ui::ScaleFactor resource_scale_factor = ui::GetSupportedScaleFactor(scale);
438     info_list.push_back(extensions::ImageLoader::ImageRepresentation(
439         icon_resource,
440         extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
441         gfx::Size(pixel_size, pixel_size),
442         resource_scale_factor));
443   }
444 
445   // LoadImagesAsync actually can run callback synchronously. We want to force
446   // async.
447   extensions::ImageLoader::Get(profile)->LoadImagesAsync(
448       extension, info_list, base::Bind(&RunFaviconCallbackAsync, callback));
449 }
450