d5b4ab33e31ee73e0ff6b18fd23dbff457ed9220
[platform/framework/web/crosswalk.git] / src / chrome / browser / plugins / plugin_info_message_filter.cc
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/plugins/plugin_info_message_filter.h"
6
7 #include "base/bind.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/content_settings/content_settings_utils.h"
13 #include "chrome/browser/content_settings/host_content_settings_map.h"
14 #include "chrome/browser/plugins/chrome_plugin_service_filter.h"
15 #include "chrome/browser/plugins/plugin_finder.h"
16 #include "chrome/browser/plugins/plugin_metadata.h"
17 #include "chrome/browser/plugins/plugin_prefs.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/pref_names.h"
20 #include "chrome/common/render_messages.h"
21 #include "components/content_settings/core/common/content_settings.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/plugin_service.h"
24 #include "content/public/browser/plugin_service_filter.h"
25 #include "url/gurl.h"
26
27 #include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
28
29 #if defined(ENABLE_EXTENSIONS)
30 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
31 #endif
32
33 #if defined(OS_WIN)
34 #include "base/win/metro.h"
35 #endif
36
37 #if !defined(DISABLE_NACL)
38 #include "components/nacl/common/nacl_constants.h"
39 #endif
40
41 using content::PluginService;
42 using content::WebPluginInfo;
43
44 namespace {
45
46 // For certain sandboxed Pepper plugins, use the JavaScript Content Settings.
47 bool ShouldUseJavaScriptSettingForPlugin(const WebPluginInfo& plugin) {
48   if (plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
49       plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS) {
50     return false;
51   }
52
53 #if !defined(DISABLE_NACL)
54   // Treat Native Client invocations like JavaScript.
55   if (plugin.name == base::ASCIIToUTF16(nacl::kNaClPluginName))
56     return true;
57 #endif
58
59 #if defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
60   // Treat CDM invocations like JavaScript.
61   if (plugin.name == base::ASCIIToUTF16(kWidevineCdmDisplayName)) {
62     DCHECK(plugin.type == WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS);
63     return true;
64   }
65 #endif  // defined(WIDEVINE_CDM_AVAILABLE) && defined(ENABLE_PEPPER_CDMS)
66
67   return false;
68 }
69
70 #if defined(ENABLE_PEPPER_CDMS)
71
72 enum PluginAvailabilityStatusForUMA {
73   PLUGIN_NOT_REGISTERED,
74   PLUGIN_AVAILABLE,
75   PLUGIN_DISABLED,
76   PLUGIN_AVAILABILITY_STATUS_MAX
77 };
78
79 static void SendPluginAvailabilityUMA(const std::string& mime_type,
80                                       PluginAvailabilityStatusForUMA status) {
81 #if defined(WIDEVINE_CDM_AVAILABLE)
82   // Only report results for Widevine CDM.
83   if (mime_type != kWidevineCdmPluginMimeType)
84     return;
85
86   UMA_HISTOGRAM_ENUMERATION("Plugin.AvailabilityStatus.WidevineCdm",
87                             status, PLUGIN_AVAILABILITY_STATUS_MAX);
88 #endif  // defined(WIDEVINE_CDM_AVAILABLE)
89 }
90
91 #endif  // defined(ENABLE_PEPPER_CDMS)
92
93 }  // namespace
94
95 PluginInfoMessageFilter::Context::Context(int render_process_id,
96                                           Profile* profile)
97     : render_process_id_(render_process_id),
98       resource_context_(profile->GetResourceContext()),
99       host_content_settings_map_(profile->GetHostContentSettingsMap()),
100       plugin_prefs_(PluginPrefs::GetForProfile(profile)) {
101   allow_outdated_plugins_.Init(prefs::kPluginsAllowOutdated,
102                                profile->GetPrefs());
103   allow_outdated_plugins_.MoveToThread(
104       content::BrowserThread::GetMessageLoopProxyForThread(
105           content::BrowserThread::IO));
106   always_authorize_plugins_.Init(prefs::kPluginsAlwaysAuthorize,
107                                  profile->GetPrefs());
108   always_authorize_plugins_.MoveToThread(
109       content::BrowserThread::GetMessageLoopProxyForThread(
110           content::BrowserThread::IO));
111 }
112
113 PluginInfoMessageFilter::Context::~Context() {
114 }
115
116 PluginInfoMessageFilter::PluginInfoMessageFilter(
117     int render_process_id,
118     Profile* profile)
119     : BrowserMessageFilter(ChromeMsgStart),
120       context_(render_process_id, profile),
121       weak_ptr_factory_(this) {
122 }
123
124 bool PluginInfoMessageFilter::OnMessageReceived(const IPC::Message& message) {
125   IPC_BEGIN_MESSAGE_MAP(PluginInfoMessageFilter, message)
126     IPC_MESSAGE_HANDLER_DELAY_REPLY(ChromeViewHostMsg_GetPluginInfo,
127                                     OnGetPluginInfo)
128 #if defined(ENABLE_PEPPER_CDMS)
129     IPC_MESSAGE_HANDLER(
130         ChromeViewHostMsg_IsInternalPluginAvailableForMimeType,
131         OnIsInternalPluginAvailableForMimeType)
132 #endif
133     IPC_MESSAGE_UNHANDLED(return false)
134   IPC_END_MESSAGE_MAP()
135   return true;
136 }
137
138 void PluginInfoMessageFilter::OnDestruct() const {
139   const_cast<PluginInfoMessageFilter*>(this)->
140       weak_ptr_factory_.InvalidateWeakPtrs();
141
142   // Destroy on the UI thread because we contain a |PrefMember|.
143   content::BrowserThread::DeleteOnUIThread::Destruct(this);
144 }
145
146 PluginInfoMessageFilter::~PluginInfoMessageFilter() {}
147
148 struct PluginInfoMessageFilter::GetPluginInfo_Params {
149   int render_frame_id;
150   GURL url;
151   GURL top_origin_url;
152   std::string mime_type;
153 };
154
155 void PluginInfoMessageFilter::OnGetPluginInfo(
156     int render_frame_id,
157     const GURL& url,
158     const GURL& top_origin_url,
159     const std::string& mime_type,
160     IPC::Message* reply_msg) {
161   GetPluginInfo_Params params = {
162     render_frame_id,
163     url,
164     top_origin_url,
165     mime_type
166   };
167   PluginService::GetInstance()->GetPlugins(
168       base::Bind(&PluginInfoMessageFilter::PluginsLoaded,
169                  weak_ptr_factory_.GetWeakPtr(),
170                  params, reply_msg));
171 }
172
173 void PluginInfoMessageFilter::PluginsLoaded(
174     const GetPluginInfo_Params& params,
175     IPC::Message* reply_msg,
176     const std::vector<WebPluginInfo>& plugins) {
177   ChromeViewHostMsg_GetPluginInfo_Output output;
178   // This also fills in |actual_mime_type|.
179   scoped_ptr<PluginMetadata> plugin_metadata;
180   if (context_.FindEnabledPlugin(params.render_frame_id, params.url,
181                                  params.top_origin_url, params.mime_type,
182                                  &output.status, &output.plugin,
183                                  &output.actual_mime_type,
184                                  &plugin_metadata)) {
185     context_.DecidePluginStatus(params, output.plugin, plugin_metadata.get(),
186                                 &output.status);
187   }
188
189   if (plugin_metadata) {
190     output.group_identifier = plugin_metadata->identifier();
191     output.group_name = plugin_metadata->name();
192   }
193
194   context_.MaybeGrantAccess(output.status, output.plugin.path);
195
196   ChromeViewHostMsg_GetPluginInfo::WriteReplyParams(reply_msg, output);
197   Send(reply_msg);
198 }
199
200 #if defined(ENABLE_PEPPER_CDMS)
201
202 void PluginInfoMessageFilter::OnIsInternalPluginAvailableForMimeType(
203     const std::string& mime_type,
204     bool* is_available,
205     std::vector<base::string16>* additional_param_names,
206     std::vector<base::string16>* additional_param_values) {
207   std::vector<WebPluginInfo> plugins;
208   PluginService::GetInstance()->GetInternalPlugins(&plugins);
209
210   bool is_plugin_disabled = false;
211   for (size_t i = 0; i < plugins.size(); ++i) {
212     const WebPluginInfo& plugin = plugins[i];
213     const std::vector<content::WebPluginMimeType>& mime_types =
214         plugin.mime_types;
215     for (size_t j = 0; j < mime_types.size(); ++j) {
216       if (mime_types[j].mime_type == mime_type) {
217         if (!context_.IsPluginEnabled(plugin)) {
218           is_plugin_disabled = true;
219           break;
220         }
221
222         *is_available = true;
223         *additional_param_names = mime_types[j].additional_param_names;
224         *additional_param_values = mime_types[j].additional_param_values;
225         SendPluginAvailabilityUMA(mime_type, PLUGIN_AVAILABLE);
226         return;
227       }
228     }
229   }
230
231   *is_available = false;
232   SendPluginAvailabilityUMA(
233       mime_type, is_plugin_disabled ? PLUGIN_DISABLED : PLUGIN_NOT_REGISTERED);
234 }
235
236 #endif // defined(ENABLE_PEPPER_CDMS)
237
238 void PluginInfoMessageFilter::Context::DecidePluginStatus(
239     const GetPluginInfo_Params& params,
240     const WebPluginInfo& plugin,
241     const PluginMetadata* plugin_metadata,
242     ChromeViewHostMsg_GetPluginInfo_Status* status) const {
243 #if defined(OS_WIN)
244   if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI &&
245       base::win::IsMetroProcess()) {
246     status->value =
247         ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
248     return;
249   }
250 #endif
251   if (plugin.type == WebPluginInfo::PLUGIN_TYPE_NPAPI) {
252     CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
253     // NPAPI plugins are not supported inside <webview> guests.
254 #if defined(ENABLE_EXTENSIONS)
255     if (extensions::WebViewRendererState::GetInstance()->IsGuest(
256         render_process_id_)) {
257       status->value =
258           ChromeViewHostMsg_GetPluginInfo_Status::kNPAPINotSupported;
259       return;
260     }
261 #endif
262   }
263
264   ContentSetting plugin_setting = CONTENT_SETTING_DEFAULT;
265   bool uses_default_content_setting = true;
266   bool is_managed = false;
267   // Check plug-in content settings. The primary URL is the top origin URL and
268   // the secondary URL is the plug-in URL.
269   GetPluginContentSetting(plugin, params.top_origin_url, params.url,
270                           plugin_metadata->identifier(), &plugin_setting,
271                           &uses_default_content_setting, &is_managed);
272   DCHECK(plugin_setting != CONTENT_SETTING_DEFAULT);
273
274   PluginMetadata::SecurityStatus plugin_status =
275       plugin_metadata->GetSecurityStatus(plugin);
276 #if defined(ENABLE_PLUGIN_INSTALLATION)
277   // Check if the plug-in is outdated.
278   if (plugin_status == PluginMetadata::SECURITY_STATUS_OUT_OF_DATE &&
279       !allow_outdated_plugins_.GetValue()) {
280     if (allow_outdated_plugins_.IsManaged()) {
281       status->value =
282           ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedDisallowed;
283     } else {
284       status->value = ChromeViewHostMsg_GetPluginInfo_Status::kOutdatedBlocked;
285     }
286     return;
287   }
288 #endif
289   // Check if the plug-in or its group is enabled by policy.
290   PluginPrefs::PolicyStatus plugin_policy =
291       plugin_prefs_->PolicyStatusForPlugin(plugin.name);
292   PluginPrefs::PolicyStatus group_policy =
293       plugin_prefs_->PolicyStatusForPlugin(plugin_metadata->name());
294
295   // Check if the plug-in requires authorization.
296   if (plugin_status ==
297           PluginMetadata::SECURITY_STATUS_REQUIRES_AUTHORIZATION &&
298       plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS &&
299       plugin.type != WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS &&
300       !always_authorize_plugins_.GetValue() &&
301       plugin_setting != CONTENT_SETTING_BLOCK &&
302       uses_default_content_setting &&
303       plugin_policy != PluginPrefs::POLICY_ENABLED &&
304       group_policy != PluginPrefs::POLICY_ENABLED &&
305       !ChromePluginServiceFilter::GetInstance()->IsPluginRestricted(
306           plugin.path)) {
307     status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
308     return;
309   }
310
311   // Check if the plug-in is crashing too much.
312   if (PluginService::GetInstance()->IsPluginUnstable(plugin.path) &&
313       !always_authorize_plugins_.GetValue() &&
314       plugin_setting != CONTENT_SETTING_BLOCK &&
315       uses_default_content_setting) {
316     status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
317     return;
318   }
319
320   if (plugin_setting == CONTENT_SETTING_ASK) {
321       status->value = ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay;
322   } else if (plugin_setting == CONTENT_SETTING_BLOCK) {
323     status->value =
324         is_managed ? ChromeViewHostMsg_GetPluginInfo_Status::kBlockedByPolicy
325                    : ChromeViewHostMsg_GetPluginInfo_Status::kBlocked;
326   }
327
328   if (status->value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed) {
329     // Allow an embedder of <webview> to block a plugin from being loaded inside
330     // the guest. In order to do this, set the status to 'Unauthorized' here,
331     // and update the status as appropriate depending on the response from the
332     // embedder.
333 #if defined(ENABLE_EXTENSIONS)
334     if (extensions::WebViewRendererState::GetInstance()->IsGuest(
335         render_process_id_))
336       status->value = ChromeViewHostMsg_GetPluginInfo_Status::kUnauthorized;
337
338 #endif
339   }
340 }
341
342 bool PluginInfoMessageFilter::Context::FindEnabledPlugin(
343     int render_frame_id,
344     const GURL& url,
345     const GURL& top_origin_url,
346     const std::string& mime_type,
347     ChromeViewHostMsg_GetPluginInfo_Status* status,
348     WebPluginInfo* plugin,
349     std::string* actual_mime_type,
350     scoped_ptr<PluginMetadata>* plugin_metadata) const {
351   bool allow_wildcard = true;
352   std::vector<WebPluginInfo> matching_plugins;
353   std::vector<std::string> mime_types;
354   PluginService::GetInstance()->GetPluginInfoArray(
355       url, mime_type, allow_wildcard, &matching_plugins, &mime_types);
356   if (matching_plugins.empty()) {
357     status->value = ChromeViewHostMsg_GetPluginInfo_Status::kNotFound;
358     return false;
359   }
360
361   content::PluginServiceFilter* filter =
362       PluginService::GetInstance()->GetFilter();
363   size_t i = 0;
364   for (; i < matching_plugins.size(); ++i) {
365     if (!filter || filter->IsPluginAvailable(render_process_id_,
366                                              render_frame_id,
367                                              resource_context_,
368                                              url,
369                                              top_origin_url,
370                                              &matching_plugins[i])) {
371       break;
372     }
373   }
374
375   // If we broke out of the loop, we have found an enabled plug-in.
376   bool enabled = i < matching_plugins.size();
377   if (!enabled) {
378     // Otherwise, we only found disabled plug-ins, so we take the first one.
379     i = 0;
380     status->value = ChromeViewHostMsg_GetPluginInfo_Status::kDisabled;
381   }
382
383   *plugin = matching_plugins[i];
384   *actual_mime_type = mime_types[i];
385   if (plugin_metadata)
386     *plugin_metadata = PluginFinder::GetInstance()->GetPluginMetadata(*plugin);
387
388   return enabled;
389 }
390
391 void PluginInfoMessageFilter::Context::GetPluginContentSetting(
392     const WebPluginInfo& plugin,
393     const GURL& policy_url,
394     const GURL& plugin_url,
395     const std::string& resource,
396     ContentSetting* setting,
397     bool* uses_default_content_setting,
398     bool* is_managed) const {
399   scoped_ptr<base::Value> value;
400   content_settings::SettingInfo info;
401   bool uses_plugin_specific_setting = false;
402   if (ShouldUseJavaScriptSettingForPlugin(plugin)) {
403     value = host_content_settings_map_->GetWebsiteSetting(
404         policy_url,
405         policy_url,
406         CONTENT_SETTINGS_TYPE_JAVASCRIPT,
407         std::string(),
408         &info);
409   } else {
410     content_settings::SettingInfo specific_info;
411     scoped_ptr<base::Value> specific_setting =
412         host_content_settings_map_->GetWebsiteSetting(
413             policy_url,
414             plugin_url,
415             CONTENT_SETTINGS_TYPE_PLUGINS,
416             resource,
417             &specific_info);
418     content_settings::SettingInfo general_info;
419     scoped_ptr<base::Value> general_setting =
420         host_content_settings_map_->GetWebsiteSetting(
421             policy_url,
422             plugin_url,
423             CONTENT_SETTINGS_TYPE_PLUGINS,
424             std::string(),
425             &general_info);
426
427     // If there is a plugin-specific setting, we use it, unless the general
428     // setting was set by policy, in which case it takes precedence.
429     uses_plugin_specific_setting = specific_setting &&
430         (general_info.source != content_settings::SETTING_SOURCE_POLICY);
431     if (uses_plugin_specific_setting) {
432       value = specific_setting.Pass();
433       info = specific_info;
434     } else {
435       value = general_setting.Pass();
436       info = general_info;
437     }
438   }
439   *setting = content_settings::ValueToContentSetting(value.get());
440   *uses_default_content_setting =
441       !uses_plugin_specific_setting &&
442       info.primary_pattern == ContentSettingsPattern::Wildcard() &&
443       info.secondary_pattern == ContentSettingsPattern::Wildcard();
444   *is_managed = info.source == content_settings::SETTING_SOURCE_POLICY;
445 }
446
447 void PluginInfoMessageFilter::Context::MaybeGrantAccess(
448     const ChromeViewHostMsg_GetPluginInfo_Status& status,
449     const base::FilePath& path) const {
450   if (status.value == ChromeViewHostMsg_GetPluginInfo_Status::kAllowed ||
451       status.value == ChromeViewHostMsg_GetPluginInfo_Status::kClickToPlay) {
452     ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
453         render_process_id_, path);
454   }
455 }
456
457 bool PluginInfoMessageFilter::Context::IsPluginEnabled(
458     const content::WebPluginInfo& plugin) const {
459   return plugin_prefs_->IsPluginEnabled(plugin);
460 }