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/active_script_controller.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/metrics/histogram.h"
11#include "base/stl_util.h"
12#include "chrome/browser/extensions/active_tab_permission_granter.h"
13#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
14#include "chrome/browser/extensions/extension_action.h"
15#include "chrome/browser/extensions/extension_action_manager.h"
16#include "chrome/browser/extensions/extension_util.h"
17#include "chrome/browser/extensions/permissions_updater.h"
18#include "chrome/browser/extensions/tab_helper.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/sessions/session_tab_helper.h"
21#include "chrome/common/extensions/api/extension_action/action_info.h"
22#include "components/crx_file/id_util.h"
23#include "content/public/browser/navigation_controller.h"
24#include "content/public/browser/navigation_details.h"
25#include "content/public/browser/navigation_entry.h"
26#include "content/public/browser/render_view_host.h"
27#include "content/public/browser/web_contents.h"
28#include "extensions/browser/extension_registry.h"
29#include "extensions/common/extension.h"
30#include "extensions/common/extension_messages.h"
31#include "extensions/common/extension_set.h"
32#include "extensions/common/feature_switch.h"
33#include "extensions/common/manifest.h"
34#include "extensions/common/permissions/permission_set.h"
35#include "extensions/common/permissions/permissions_data.h"
36#include "ipc/ipc_message_macros.h"
37
38namespace extensions {
39
40namespace {
41
42// Returns true if the extension should be regarded as a "permitted" extension
43// for the case of metrics. We need this because we only actually withhold
44// permissions if the switch is enabled, but want to record metrics in all
45// cases.
46// "ExtensionWouldHaveHadHostPermissionsWithheldIfSwitchWasOn()" would be
47// more accurate, but too long.
48bool ShouldRecordExtension(const Extension* extension) {
49  return extension->ShouldDisplayInExtensionSettings() &&
50         !Manifest::IsPolicyLocation(extension->location()) &&
51         !Manifest::IsComponentLocation(extension->location()) &&
52         !PermissionsData::CanExecuteScriptEverywhere(extension) &&
53         extension->permissions_data()
54             ->active_permissions()
55             ->ShouldWarnAllHosts();
56}
57
58}  // namespace
59
60ActiveScriptController::ActiveScriptController(
61    content::WebContents* web_contents)
62    : content::WebContentsObserver(web_contents),
63      enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()),
64      extension_registry_observer_(this) {
65  CHECK(web_contents);
66  extension_registry_observer_.Add(
67      ExtensionRegistry::Get(web_contents->GetBrowserContext()));
68}
69
70ActiveScriptController::~ActiveScriptController() {
71  LogUMA();
72}
73
74// static
75ActiveScriptController* ActiveScriptController::GetForWebContents(
76    content::WebContents* web_contents) {
77  if (!web_contents)
78    return NULL;
79  TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
80  return tab_helper ? tab_helper->active_script_controller() : NULL;
81}
82
83void ActiveScriptController::OnActiveTabPermissionGranted(
84    const Extension* extension) {
85  RunPendingForExtension(extension);
86}
87
88void ActiveScriptController::OnAdInjectionDetected(
89    const std::set<std::string>& ad_injectors) {
90  // We're only interested in data if there are ad injectors detected.
91  if (ad_injectors.empty())
92    return;
93
94  size_t num_preventable_ad_injectors =
95      base::STLSetIntersection<std::set<std::string> >(
96          ad_injectors, permitted_extensions_).size();
97
98  UMA_HISTOGRAM_COUNTS_100(
99      "Extensions.ActiveScriptController.PreventableAdInjectors",
100      num_preventable_ad_injectors);
101  UMA_HISTOGRAM_COUNTS_100(
102      "Extensions.ActiveScriptController.UnpreventableAdInjectors",
103      ad_injectors.size() - num_preventable_ad_injectors);
104}
105
106void ActiveScriptController::AlwaysRunOnVisibleOrigin(
107    const Extension* extension) {
108  const GURL& url = web_contents()->GetVisibleURL();
109  URLPatternSet new_explicit_hosts;
110  URLPatternSet new_scriptable_hosts;
111
112  scoped_refptr<const PermissionSet> withheld_permissions =
113      extension->permissions_data()->withheld_permissions();
114  if (withheld_permissions->explicit_hosts().MatchesURL(url)) {
115    new_explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
116                                 url.GetOrigin());
117  }
118  if (withheld_permissions->scriptable_hosts().MatchesURL(url)) {
119    new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
120                                   url.GetOrigin());
121  }
122
123  scoped_refptr<PermissionSet> new_permissions =
124      new PermissionSet(APIPermissionSet(),
125                        ManifestPermissionSet(),
126                        new_explicit_hosts,
127                        new_scriptable_hosts);
128
129  // Update permissions for the session. This adds |new_permissions| to active
130  // permissions and granted permissions.
131  // TODO(devlin): Make sure that the permission is removed from
132  // withheld_permissions if appropriate.
133  PermissionsUpdater(web_contents()->GetBrowserContext())
134      .AddPermissions(extension, new_permissions.get());
135
136  // Allow current tab to run injection.
137  OnClicked(extension);
138}
139
140void ActiveScriptController::OnClicked(const Extension* extension) {
141  DCHECK(ContainsKey(pending_requests_, extension->id()));
142  RunPendingForExtension(extension);
143}
144
145bool ActiveScriptController::WantsToRun(const Extension* extension) {
146  return enabled_ && pending_requests_.count(extension->id()) > 0;
147}
148
149PermissionsData::AccessType
150ActiveScriptController::RequiresUserConsentForScriptInjection(
151    const Extension* extension,
152    UserScript::InjectionType type) {
153  CHECK(extension);
154
155  // If the feature is not enabled, we automatically allow all extensions to
156  // run scripts.
157  if (!enabled_)
158    permitted_extensions_.insert(extension->id());
159
160  // Allow the extension if it's been explicitly granted permission.
161  if (permitted_extensions_.count(extension->id()) > 0)
162    return PermissionsData::ACCESS_ALLOWED;
163
164  GURL url = web_contents()->GetVisibleURL();
165  int tab_id = SessionTabHelper::IdForTab(web_contents());
166  switch (type) {
167    case UserScript::CONTENT_SCRIPT:
168      return extension->permissions_data()->GetContentScriptAccess(
169          extension, url, url, tab_id, -1, NULL);
170    case UserScript::PROGRAMMATIC_SCRIPT:
171      return extension->permissions_data()->GetPageAccess(
172          extension, url, url, tab_id, -1, NULL);
173  }
174
175  NOTREACHED();
176  return PermissionsData::ACCESS_DENIED;
177}
178
179void ActiveScriptController::RequestScriptInjection(
180    const Extension* extension,
181    const base::Closure& callback) {
182  CHECK(extension);
183  PendingRequestList& list = pending_requests_[extension->id()];
184  list.push_back(callback);
185
186  // If this was the first entry, notify the location bar that there's a new
187  // icon.
188  if (list.size() == 1u) {
189    ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
190        NotifyPageActionsChanged(web_contents());
191  }
192}
193
194void ActiveScriptController::RunPendingForExtension(
195    const Extension* extension) {
196  DCHECK(extension);
197
198  content::NavigationEntry* visible_entry =
199      web_contents()->GetController().GetVisibleEntry();
200  // Refuse to run if there's no visible entry, because we have no idea of
201  // determining if it's the proper page. This should rarely, if ever, happen.
202  if (!visible_entry)
203    return;
204
205  // We add this to the list of permitted extensions and erase pending entries
206  // *before* running them to guard against the crazy case where running the
207  // callbacks adds more entries.
208  permitted_extensions_.insert(extension->id());
209
210  PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
211  if (iter == pending_requests_.end())
212    return;
213
214  PendingRequestList requests;
215  iter->second.swap(requests);
216  pending_requests_.erase(extension->id());
217
218  // Clicking to run the extension counts as granting it permission to run on
219  // the given tab.
220  // The extension may already have active tab at this point, but granting
221  // it twice is essentially a no-op.
222  TabHelper::FromWebContents(web_contents())->
223      active_tab_permission_granter()->GrantIfRequested(extension);
224
225  // Run all pending injections for the given extension.
226  for (PendingRequestList::iterator request = requests.begin();
227       request != requests.end();
228       ++request) {
229    request->Run();
230  }
231
232  // Inform the location bar that the action is now gone.
233  ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
234      NotifyPageActionsChanged(web_contents());
235}
236
237void ActiveScriptController::OnRequestScriptInjectionPermission(
238    const std::string& extension_id,
239    UserScript::InjectionType script_type,
240    int64 request_id) {
241  if (!crx_file::id_util::IdIsValid(extension_id)) {
242    NOTREACHED() << "'" << extension_id << "' is not a valid id.";
243    return;
244  }
245
246  const Extension* extension =
247      ExtensionRegistry::Get(web_contents()->GetBrowserContext())
248          ->enabled_extensions().GetByID(extension_id);
249  // We shouldn't allow extensions which are no longer enabled to run any
250  // scripts. Ignore the request.
251  if (!extension)
252    return;
253
254  // If the request id is -1, that signals that the content script has already
255  // ran (because this feature is not enabled). Add the extension to the list of
256  // permitted extensions (for metrics), and return immediately.
257  if (request_id == -1) {
258    if (ShouldRecordExtension(extension)) {
259      DCHECK(!enabled_);
260      permitted_extensions_.insert(extension->id());
261    }
262    return;
263  }
264
265  switch (RequiresUserConsentForScriptInjection(extension, script_type)) {
266    case PermissionsData::ACCESS_ALLOWED:
267      PermitScriptInjection(request_id);
268      break;
269    case PermissionsData::ACCESS_WITHHELD:
270      // This base::Unretained() is safe, because the callback is only invoked
271      // by this object.
272      RequestScriptInjection(
273          extension,
274          base::Bind(&ActiveScriptController::PermitScriptInjection,
275                     base::Unretained(this),
276                     request_id));
277      break;
278    case PermissionsData::ACCESS_DENIED:
279      // We should usually only get a "deny access" if the page changed (as the
280      // renderer wouldn't have requested permission if the answer was always
281      // "no"). Just let the request fizzle and die.
282      break;
283  }
284}
285
286void ActiveScriptController::PermitScriptInjection(int64 request_id) {
287  // This only sends the response to the renderer - the process of adding the
288  // extension to the list of |permitted_extensions_| is done elsewhere.
289  content::RenderViewHost* render_view_host =
290      web_contents()->GetRenderViewHost();
291  if (render_view_host) {
292    render_view_host->Send(new ExtensionMsg_PermitScriptInjection(
293        render_view_host->GetRoutingID(), request_id));
294  }
295}
296
297void ActiveScriptController::LogUMA() const {
298  UMA_HISTOGRAM_COUNTS_100(
299      "Extensions.ActiveScriptController.ShownActiveScriptsOnPage",
300      pending_requests_.size());
301
302  // We only log the permitted extensions metric if the feature is enabled,
303  // because otherwise the data will be boring (100% allowed).
304  if (enabled_) {
305    UMA_HISTOGRAM_COUNTS_100(
306        "Extensions.ActiveScriptController.PermittedExtensions",
307        permitted_extensions_.size());
308    UMA_HISTOGRAM_COUNTS_100(
309        "Extensions.ActiveScriptController.DeniedExtensions",
310        pending_requests_.size());
311  }
312}
313
314bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) {
315  bool handled = true;
316  IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message)
317    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission,
318                        OnRequestScriptInjectionPermission)
319    IPC_MESSAGE_UNHANDLED(handled = false)
320  IPC_END_MESSAGE_MAP()
321  return handled;
322}
323
324void ActiveScriptController::DidNavigateMainFrame(
325    const content::LoadCommittedDetails& details,
326    const content::FrameNavigateParams& params) {
327  if (details.is_in_page)
328    return;
329
330  LogUMA();
331  permitted_extensions_.clear();
332  pending_requests_.clear();
333}
334
335void ActiveScriptController::OnExtensionUnloaded(
336    content::BrowserContext* browser_context,
337    const Extension* extension,
338    UnloadedExtensionInfo::Reason reason) {
339  PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
340  if (iter != pending_requests_.end()) {
341    pending_requests_.erase(iter);
342    ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
343        NotifyPageActionsChanged(web_contents());
344  }
345}
346
347}  // namespace extensions
348