1// Copyright 2014 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/chrome_content_browser_client_extensions_part.h"
6
7#include <set>
8
9#include "base/command_line.h"
10#include "chrome/browser/browser_process.h"
11#include "chrome/browser/extensions/browser_permissions_policy_delegate.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/extension_web_ui.h"
14#include "chrome/browser/extensions/extension_webkit_preferences.h"
15#include "chrome/browser/media_galleries/fileapi/media_file_system_backend.h"
16#include "chrome/browser/profiles/profile.h"
17#include "chrome/browser/profiles/profile_io_data.h"
18#include "chrome/browser/profiles/profile_manager.h"
19#include "chrome/browser/renderer_host/chrome_extension_message_filter.h"
20#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
21#include "chrome/common/chrome_constants.h"
22#include "chrome/common/extensions/extension_process_policy.h"
23#include "chrome/common/extensions/manifest_handlers/app_isolation_info.h"
24#include "content/public/browser/browser_thread.h"
25#include "content/public/browser/browser_url_handler.h"
26#include "content/public/browser/render_process_host.h"
27#include "content/public/browser/render_view_host.h"
28#include "content/public/browser/site_instance.h"
29#include "content/public/browser/web_contents.h"
30#include "content/public/common/content_switches.h"
31#include "extensions/browser/api/web_request/web_request_api.h"
32#include "extensions/browser/api/web_request/web_request_api_helpers.h"
33#include "extensions/browser/extension_host.h"
34#include "extensions/browser/extension_message_filter.h"
35#include "extensions/browser/extension_registry.h"
36#include "extensions/browser/extension_system.h"
37#include "extensions/browser/info_map.h"
38#include "extensions/browser/view_type_utils.h"
39#include "extensions/common/constants.h"
40#include "extensions/common/manifest_handlers/background_info.h"
41#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
42#include "extensions/common/switches.h"
43
44using content::BrowserThread;
45using content::BrowserURLHandler;
46using content::RenderViewHost;
47using content::SiteInstance;
48using content::WebContents;
49using content::WebPreferences;
50
51namespace extensions {
52
53namespace {
54
55// Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions
56// below.  Extension, and isolated apps require different privileges to be
57// granted to their RenderProcessHosts.  This classification allows us to make
58// sure URLs are served by hosts with the right set of privileges.
59enum RenderProcessHostPrivilege {
60  PRIV_NORMAL,
61  PRIV_HOSTED,
62  PRIV_ISOLATED,
63  PRIV_EXTENSION,
64};
65
66RenderProcessHostPrivilege GetPrivilegeRequiredByUrl(
67    const GURL& url,
68    ExtensionService* service) {
69  // Default to a normal renderer cause it is lower privileged. This should only
70  // occur if the URL on a site instance is either malformed, or uninitialized.
71  // If it is malformed, then there is no need for better privileges anyways.
72  // If it is uninitialized, but eventually settles on being an a scheme other
73  // than normal webrenderer, the navigation logic will correct us out of band
74  // anyways.
75  if (!url.is_valid())
76    return PRIV_NORMAL;
77
78  if (!url.SchemeIs(kExtensionScheme))
79    return PRIV_NORMAL;
80
81  const Extension* extension = service->extensions()->GetByID(url.host());
82  if (extension && AppIsolationInfo::HasIsolatedStorage(extension))
83    return PRIV_ISOLATED;
84  if (extension && extension->is_hosted_app())
85    return PRIV_HOSTED;
86  return PRIV_EXTENSION;
87}
88
89RenderProcessHostPrivilege GetProcessPrivilege(
90    content::RenderProcessHost* process_host,
91    ProcessMap* process_map,
92    ExtensionService* service) {
93  std::set<std::string> extension_ids =
94      process_map->GetExtensionsInProcess(process_host->GetID());
95  if (extension_ids.empty())
96    return PRIV_NORMAL;
97
98  for (std::set<std::string>::iterator iter = extension_ids.begin();
99       iter != extension_ids.end(); ++iter) {
100    const Extension* extension = service->GetExtensionById(*iter, false);
101    if (extension && AppIsolationInfo::HasIsolatedStorage(extension))
102      return PRIV_ISOLATED;
103    if (extension && extension->is_hosted_app())
104      return PRIV_HOSTED;
105  }
106
107  return PRIV_EXTENSION;
108}
109
110}  // namespace
111
112ChromeContentBrowserClientExtensionsPart::
113    ChromeContentBrowserClientExtensionsPart() {
114  permissions_policy_delegate_.reset(new BrowserPermissionsPolicyDelegate());
115}
116
117ChromeContentBrowserClientExtensionsPart::
118    ~ChromeContentBrowserClientExtensionsPart() {
119}
120
121// static
122GURL ChromeContentBrowserClientExtensionsPart::GetEffectiveURL(
123    Profile* profile, const GURL& url) {
124  // If the input |url| is part of an installed app, the effective URL is an
125  // extension URL with the ID of that extension as the host. This has the
126  // effect of grouping apps together in a common SiteInstance.
127  ExtensionService* extension_service =
128      ExtensionSystem::Get(profile)->extension_service();
129  if (!extension_service)
130    return url;
131
132  const Extension* extension =
133      extension_service->extensions()->GetHostedAppByURL(url);
134  if (!extension)
135    return url;
136
137  // Bookmark apps do not use the hosted app process model, and should be
138  // treated as normal URLs.
139  if (extension->from_bookmark())
140    return url;
141
142  // If the URL is part of an extension's web extent, convert it to an
143  // extension URL.
144  return extension->GetResourceURL(url.path());
145}
146
147// static
148bool ChromeContentBrowserClientExtensionsPart::ShouldUseProcessPerSite(
149    Profile* profile, const GURL& effective_url) {
150  if (!effective_url.SchemeIs(kExtensionScheme))
151    return false;
152
153  ExtensionService* extension_service =
154      ExtensionSystem::Get(profile)->extension_service();
155  if (!extension_service)
156    return false;
157
158  const Extension* extension =
159      extension_service->extensions()->GetExtensionOrAppByURL(effective_url);
160  if (!extension)
161    return false;
162
163  // If the URL is part of a hosted app that does not have the background
164  // permission, or that does not allow JavaScript access to the background
165  // page, we want to give each instance its own process to improve
166  // responsiveness.
167  if (extension->GetType() == Manifest::TYPE_HOSTED_APP) {
168    if (!extension->permissions_data()->HasAPIPermission(
169            APIPermission::kBackground) ||
170        !BackgroundInfo::AllowJSAccess(extension)) {
171      return false;
172    }
173  }
174
175  // Hosted apps that have script access to their background page must use
176  // process per site, since all instances can make synchronous calls to the
177  // background window.  Other extensions should use process per site as well.
178  return true;
179}
180
181// static
182bool ChromeContentBrowserClientExtensionsPart::CanCommitURL(
183    content::RenderProcessHost* process_host, const GURL& url) {
184  // We need to let most extension URLs commit in any process, since this can
185  // be allowed due to web_accessible_resources.  Most hosted app URLs may also
186  // load in any process (e.g., in an iframe).  However, the Chrome Web Store
187  // cannot be loaded in iframes and should never be requested outside its
188  // process.
189  Profile* profile =
190      Profile::FromBrowserContext(process_host->GetBrowserContext());
191  ExtensionService* service =
192      ExtensionSystem::Get(profile)->extension_service();
193  if (!service)
194    return true;
195
196  const Extension* new_extension =
197      service->extensions()->GetExtensionOrAppByURL(url);
198  if (new_extension &&
199      new_extension->is_hosted_app() &&
200      new_extension->id() == extensions::kWebStoreAppId &&
201      !ProcessMap::Get(profile)->Contains(
202          new_extension->id(), process_host->GetID())) {
203    return false;
204  }
205  return true;
206}
207
208// static
209bool ChromeContentBrowserClientExtensionsPart::IsSuitableHost(
210    Profile* profile,
211    content::RenderProcessHost* process_host,
212    const GURL& site_url) {
213  DCHECK(profile);
214
215  ExtensionService* service =
216      ExtensionSystem::Get(profile)->extension_service();
217  ProcessMap* process_map = ProcessMap::Get(profile);
218
219  // These may be NULL during tests. In that case, just assume any site can
220  // share any host.
221  if (!service || !process_map)
222    return true;
223
224  // Otherwise, just make sure the process privilege matches the privilege
225  // required by the site.
226  RenderProcessHostPrivilege privilege_required =
227      GetPrivilegeRequiredByUrl(site_url, service);
228  return GetProcessPrivilege(process_host, process_map, service) ==
229      privilege_required;
230}
231
232// static
233bool
234ChromeContentBrowserClientExtensionsPart::ShouldTryToUseExistingProcessHost(
235    Profile* profile, const GURL& url) {
236  // This function is trying to limit the amount of processes used by extensions
237  // with background pages. It uses a globally set percentage of processes to
238  // run such extensions and if the limit is exceeded, it returns true, to
239  // indicate to the content module to group extensions together.
240  ExtensionService* service = profile ?
241      ExtensionSystem::Get(profile)->extension_service() : NULL;
242  if (!service)
243    return false;
244
245  // We have to have a valid extension with background page to proceed.
246  const Extension* extension =
247      service->extensions()->GetExtensionOrAppByURL(url);
248  if (!extension)
249    return false;
250  if (!BackgroundInfo::HasBackgroundPage(extension))
251    return false;
252
253  std::set<int> process_ids;
254  size_t max_process_count =
255      content::RenderProcessHost::GetMaxRendererProcessCount();
256
257  // Go through all profiles to ensure we have total count of extension
258  // processes containing background pages, otherwise one profile can
259  // starve the other.
260  std::vector<Profile*> profiles = g_browser_process->profile_manager()->
261      GetLoadedProfiles();
262  for (size_t i = 0; i < profiles.size(); ++i) {
263    ProcessManager* epm = ExtensionSystem::Get(profiles[i])->process_manager();
264    for (ProcessManager::const_iterator iter = epm->background_hosts().begin();
265         iter != epm->background_hosts().end(); ++iter) {
266      const ExtensionHost* host = *iter;
267      process_ids.insert(host->render_process_host()->GetID());
268    }
269  }
270
271  return (process_ids.size() >
272          (max_process_count * chrome::kMaxShareOfExtensionProcesses));
273}
274
275// static
276bool ChromeContentBrowserClientExtensionsPart::
277    ShouldSwapBrowsingInstancesForNavigation(SiteInstance* site_instance,
278                                             const GURL& current_url,
279                                             const GURL& new_url) {
280  // If we don't have an ExtensionService, then rely on the SiteInstance logic
281  // in RenderFrameHostManager to decide when to swap.
282  Profile* profile =
283      Profile::FromBrowserContext(site_instance->GetBrowserContext());
284  ExtensionService* service =
285      ExtensionSystem::Get(profile)->extension_service();
286  if (!service)
287    return false;
288
289  // We must use a new BrowsingInstance (forcing a process swap and disabling
290  // scripting by existing tabs) if one of the URLs is an extension and the
291  // other is not the exact same extension.
292  //
293  // We ignore hosted apps here so that other tabs in their BrowsingInstance can
294  // use postMessage with them.  (The exception is the Chrome Web Store, which
295  // is a hosted app that requires its own BrowsingInstance.)  Navigations
296  // to/from a hosted app will still trigger a SiteInstance swap in
297  // RenderFrameHostManager.
298  const Extension* current_extension =
299      service->extensions()->GetExtensionOrAppByURL(current_url);
300  if (current_extension &&
301      current_extension->is_hosted_app() &&
302      current_extension->id() != extensions::kWebStoreAppId)
303    current_extension = NULL;
304
305  const Extension* new_extension =
306      service->extensions()->GetExtensionOrAppByURL(new_url);
307  if (new_extension &&
308      new_extension->is_hosted_app() &&
309      new_extension->id() != extensions::kWebStoreAppId)
310    new_extension = NULL;
311
312  // First do a process check.  We should force a BrowsingInstance swap if the
313  // current process doesn't know about new_extension, even if current_extension
314  // is somehow the same as new_extension.
315  ProcessMap* process_map = ProcessMap::Get(profile);
316  if (new_extension &&
317      site_instance->HasProcess() &&
318      !process_map->Contains(
319          new_extension->id(), site_instance->GetProcess()->GetID()))
320    return true;
321
322  // Otherwise, swap BrowsingInstances if current_extension and new_extension
323  // differ.
324  return current_extension != new_extension;
325}
326
327// static
328bool ChromeContentBrowserClientExtensionsPart::ShouldSwapProcessesForRedirect(
329    content::ResourceContext* resource_context,
330    const GURL& current_url,
331    const GURL& new_url) {
332  ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context);
333  return CrossesExtensionProcessBoundary(
334      io_data->GetExtensionInfoMap()->extensions(),
335      current_url, new_url, false);
336}
337
338// static
339bool ChromeContentBrowserClientExtensionsPart::ShouldAllowOpenURL(
340    content::SiteInstance* site_instance,
341    const GURL& from_url,
342    const GURL& to_url,
343    bool* result) {
344  DCHECK(result);
345
346  // Do not allow pages from the web or other extensions navigate to
347  // non-web-accessible extension resources.
348  if (to_url.SchemeIs(kExtensionScheme) &&
349      (from_url.SchemeIsHTTPOrHTTPS() || from_url.SchemeIs(kExtensionScheme))) {
350    Profile* profile = Profile::FromBrowserContext(
351        site_instance->GetProcess()->GetBrowserContext());
352    ExtensionService* service =
353        ExtensionSystem::Get(profile)->extension_service();
354    if (!service) {
355      *result = true;
356      return true;
357    }
358    const Extension* extension =
359        service->extensions()->GetExtensionOrAppByURL(to_url);
360    if (!extension) {
361      *result = true;
362      return true;
363    }
364    const Extension* from_extension =
365        service->extensions()->GetExtensionOrAppByURL(
366            site_instance->GetSiteURL());
367    if (from_extension && from_extension->id() == extension->id()) {
368      *result = true;
369      return true;
370    }
371
372    if (!WebAccessibleResourcesInfo::IsResourceWebAccessible(
373            extension, to_url.path())) {
374      *result = false;
375      return true;
376    }
377  }
378  return false;
379}
380
381// static
382void ChromeContentBrowserClientExtensionsPart::SetSigninProcess(
383    content::SiteInstance* site_instance) {
384  Profile* profile =
385      Profile::FromBrowserContext(site_instance->GetBrowserContext());
386  DCHECK(profile);
387  BrowserThread::PostTask(
388      BrowserThread::IO,
389      FROM_HERE,
390      base::Bind(&InfoMap::SetSigninProcess,
391                 ExtensionSystem::Get(profile)->info_map(),
392                 site_instance->GetProcess()->GetID()));
393}
394
395void ChromeContentBrowserClientExtensionsPart::RenderProcessWillLaunch(
396    content::RenderProcessHost* host) {
397  int id = host->GetID();
398  Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
399
400  host->AddFilter(new ChromeExtensionMessageFilter(id, profile));
401  host->AddFilter(new ExtensionMessageFilter(id, profile));
402  extension_web_request_api_helpers::SendExtensionWebRequestStatusToHost(host);
403}
404
405void ChromeContentBrowserClientExtensionsPart::SiteInstanceGotProcess(
406    SiteInstance* site_instance) {
407  Profile* profile = Profile::FromBrowserContext(
408      site_instance->GetProcess()->GetBrowserContext());
409  ExtensionService* service =
410      ExtensionSystem::Get(profile)->extension_service();
411  if (!service)
412    return;
413
414  const Extension* extension = service->extensions()->GetExtensionOrAppByURL(
415      site_instance->GetSiteURL());
416  if (!extension)
417    return;
418
419  ProcessMap::Get(profile)->Insert(extension->id(),
420                                   site_instance->GetProcess()->GetID(),
421                                   site_instance->GetId());
422
423  BrowserThread::PostTask(BrowserThread::IO,
424                          FROM_HERE,
425                          base::Bind(&InfoMap::RegisterExtensionProcess,
426                                     ExtensionSystem::Get(profile)->info_map(),
427                                     extension->id(),
428                                     site_instance->GetProcess()->GetID(),
429                                     site_instance->GetId()));
430}
431
432void ChromeContentBrowserClientExtensionsPart::SiteInstanceDeleting(
433    SiteInstance* site_instance) {
434  Profile* profile =
435      Profile::FromBrowserContext(site_instance->GetBrowserContext());
436  ExtensionService* service =
437      ExtensionSystem::Get(profile)->extension_service();
438  if (!service)
439    return;
440
441  const Extension* extension = service->extensions()->GetExtensionOrAppByURL(
442      site_instance->GetSiteURL());
443  if (!extension)
444    return;
445
446  ProcessMap::Get(profile)->Remove(extension->id(),
447                                   site_instance->GetProcess()->GetID(),
448                                   site_instance->GetId());
449
450  BrowserThread::PostTask(BrowserThread::IO,
451                          FROM_HERE,
452                          base::Bind(&InfoMap::UnregisterExtensionProcess,
453                                     ExtensionSystem::Get(profile)->info_map(),
454                                     extension->id(),
455                                     site_instance->GetProcess()->GetID(),
456                                     site_instance->GetId()));
457}
458
459void ChromeContentBrowserClientExtensionsPart::OverrideWebkitPrefs(
460    RenderViewHost* rvh,
461    const GURL& url,
462    WebPreferences* web_prefs) {
463  Profile* profile =
464      Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext());
465
466  ExtensionService* service =
467      ExtensionSystem::Get(profile)->extension_service();
468  if (!service)
469    return;
470
471  // Note: it's not possible for kExtensionsScheme to change during the lifetime
472  // of the process.
473  //
474  // Ensure that we are only granting extension preferences to URLs with
475  // the correct scheme. Without this check, chrome-guest:// schemes used by
476  // webview tags as well as hosts that happen to match the id of an
477  // installed extension would get the wrong preferences.
478  const GURL& site_url = rvh->GetSiteInstance()->GetSiteURL();
479  if (!site_url.SchemeIs(kExtensionScheme))
480    return;
481
482  WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
483  ViewType view_type = GetViewType(web_contents);
484  const Extension* extension = service->extensions()->GetByID(site_url.host());
485  extension_webkit_preferences::SetPreferences(extension, view_type, web_prefs);
486}
487
488void ChromeContentBrowserClientExtensionsPart::BrowserURLHandlerCreated(
489    BrowserURLHandler* handler) {
490  handler->AddHandlerPair(&ExtensionWebUI::HandleChromeURLOverride,
491                          BrowserURLHandler::null_handler());
492  handler->AddHandlerPair(BrowserURLHandler::null_handler(),
493                          &ExtensionWebUI::HandleChromeURLOverrideReverse);
494}
495
496void ChromeContentBrowserClientExtensionsPart::
497    GetAdditionalAllowedSchemesForFileSystem(
498        std::vector<std::string>* additional_allowed_schemes) {
499  additional_allowed_schemes->push_back(kExtensionScheme);
500}
501
502void ChromeContentBrowserClientExtensionsPart::GetURLRequestAutoMountHandlers(
503    std::vector<storage::URLRequestAutoMountHandler>* handlers) {
504  handlers->push_back(
505      base::Bind(MediaFileSystemBackend::AttemptAutoMountForURLRequest));
506}
507
508void ChromeContentBrowserClientExtensionsPart::GetAdditionalFileSystemBackends(
509    content::BrowserContext* browser_context,
510    const base::FilePath& storage_partition_path,
511    ScopedVector<storage::FileSystemBackend>* additional_backends) {
512  base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool();
513  additional_backends->push_back(new MediaFileSystemBackend(
514      storage_partition_path,
515      pool->GetSequencedTaskRunner(
516                pool->GetNamedSequenceToken(
517                    MediaFileSystemBackend::kMediaTaskRunnerName)).get()));
518
519  additional_backends->push_back(new sync_file_system::SyncFileSystemBackend(
520      Profile::FromBrowserContext(browser_context)));
521}
522
523void ChromeContentBrowserClientExtensionsPart::
524    AppendExtraRendererCommandLineSwitches(base::CommandLine* command_line,
525                                           content::RenderProcessHost* process,
526                                           Profile* profile) {
527  if (!process)
528    return;
529  DCHECK(profile);
530  if (ProcessMap::Get(profile)->Contains(process->GetID())) {
531    command_line->AppendSwitch(switches::kExtensionProcess);
532#if defined(ENABLE_WEBRTC)
533    command_line->AppendSwitch(::switches::kEnableWebRtcHWH264Encoding);
534#endif
535  }
536}
537
538}  // namespace extensions
539