From b737ede83b48b119dfa2b874ffc26f2a04ebff3c Mon Sep 17 00:00:00 2001 From: David Keeler Date: Tue, 27 Nov 2012 10:09:10 -0800 Subject: [PATCH] bug 746374 - differentiate click-to-play plugin permissions by type and vulnerability status r=jaws r=joshmoz --- browser/base/content/browser-plugins.js | 54 +++-- .../test/browser_pluginnotification.js | 188 +++++++++++++++++- content/base/src/nsObjectLoadingContent.cpp | 5 +- dom/plugins/base/nsIPluginHost.idl | 4 +- dom/plugins/base/nsPluginHost.cpp | 25 +++ 5 files changed, 258 insertions(+), 18 deletions(-) diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js index 0c5c0dc56ce..2212f4096f5 100644 --- a/browser/base/content/browser-plugins.js +++ b/browser/base/content/browser-plugins.js @@ -238,7 +238,16 @@ var gPluginHandler = { }, canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) { + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION; + if (objLoadingContent.actualType) { + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); + let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document); + pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString); + } + return !objLoadingContent.activated && + pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW; }, @@ -351,19 +360,26 @@ var gPluginHandler = { _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) { let doc = aPlugin.ownerDocument; let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); - let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins"); + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION; + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (objLoadingContent.actualType) { + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); + pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString); + } let overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox"); - if (browser._clickToPlayPluginsActivated) { - let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); - objLoadingContent.playPlugin(); - return; - } else if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION) { + if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { if (overlay) overlay.style.visibility = "hidden"; return; } + if (browser._clickToPlayPluginsActivated) { + objLoadingContent.playPlugin(); + return; + } + if (overlay) { overlay.addEventListener("click", function(aEvent) { // Have to check that the target is not the link to update the plugin @@ -417,10 +433,6 @@ var gPluginHandler = { reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { let browser = gBrowser.selectedBrowser; - let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins"); - if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION) - return; - if (gPluginHandler._pluginNeedsActivationExceptThese([])) gPluginHandler._showClickToPlayNotification(browser); }, @@ -513,7 +525,19 @@ var gPluginHandler = { } return centerActions; - }, + }, + + _setPermissionForPlugins: function PH_setPermissionForPlugins(aBrowser, aPermission, aPluginList) { + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + for (let plugin of aPluginList) { + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (gPluginHandler.canActivatePlugin(objLoadingContent) && + objLoadingContent.actualType) { + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); + Services.perms.add(aBrowser.currentURI, permissionString, aPermission); + } + } + }, _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser) { aBrowser._clickToPlayDoorhangerShown = true; @@ -541,14 +565,14 @@ var gPluginHandler = { label: gNavigatorBundle.getString("activatePluginsMessage.always"), accessKey: gNavigatorBundle.getString("activatePluginsMessage.always.accesskey"), callback: function () { - Services.perms.add(aBrowser.currentURI, "plugins", Ci.nsIPermissionManager.ALLOW_ACTION); + gPluginHandler._setPermissionForPlugins(aBrowser, Ci.nsIPermissionManager.ALLOW_ACTION, cwu.plugins); gPluginHandler.activatePlugins(contentWindow); } },{ label: gNavigatorBundle.getString("activatePluginsMessage.never"), accessKey: gNavigatorBundle.getString("activatePluginsMessage.never.accesskey"), callback: function () { - Services.perms.add(aBrowser.currentURI, "plugins", Ci.nsIPermissionManager.DENY_ACTION); + gPluginHandler._setPermissionForPlugins(aBrowser, Ci.nsIPermissionManager.DENY_ACTION, cwu.plugins); let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); if (notification) notification.remove(); @@ -567,7 +591,9 @@ var gPluginHandler = { .getInterface(Ci.nsIDOMWindowUtils); for (let plugin of cwu.plugins) { let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); - overlay.style.visibility = "hidden"; + // for already activated plugins, there will be no overlay + if (overlay) + overlay.style.visibility = "hidden"; } }, diff --git a/browser/base/content/test/browser_pluginnotification.js b/browser/base/content/test/browser_pluginnotification.js index 1692218532c..20ca10ec6d4 100644 --- a/browser/base/content/test/browser_pluginnotification.js +++ b/browser/base/content/test/browser_pluginnotification.js @@ -402,9 +402,26 @@ function test12c() { var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); ok(objLoadingContent.activated, "Test 12c, Plugin should be activated"); + prepareTest(test12d, gHttpTestRoot + "plugin_two_types.html"); +} + +// Test that the "Always" permission, when set for just the Test plugin, +// does not also allow the Second Test plugin. +function test12d() { + var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 12d, Should have a click-to-play notification"); + var test = gTestBrowser.contentDocument.getElementById("test"); + var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA"); + var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB"); + var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 12d, Test plugin should be activated"); + var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 12d, Second Test plugin (A) should not be activated"); + var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 12d, Second Test plugin (B) should not be activated"); + Services.perms.removeAll(); - gNextTest = test13a; - gTestBrowser.reload(); + prepareTest(test13a, gHttpTestRoot + "plugin_clickToPlayDeny.html"); } // Tests that the "Deny Always" permission works for click-to-play plugins (part 1/3) @@ -444,6 +461,55 @@ function test13c() { var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(plugin, "class", "mainBox"); ok(overlay.style.visibility == "hidden", "Test 13c, Plugin should not have visible overlay"); + prepareTest(test13d, gHttpTestRoot + "plugin_two_types.html"); +} + +// Test that the "Deny Always" permission, when set for just the Test plugin, +// does not also block the Second Test plugin (i.e. it gets an overlay and +// there's a notification and everything). +function test13d() { + var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(popupNotification, "Test 13d, Should have a click-to-play notification"); + + var test = gTestBrowser.contentDocument.getElementById("test"); + var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(test, "class", "mainBox"); + ok(overlay.style.visibility == "hidden", "Test 13d, Test plugin should not have visible overlay"); + ok(!objLoadingContent.activated, "Test 13d, Test plugin should not be activated"); + + var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA"); + var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent); + var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtestA, "class", "mainBox"); + ok(overlay.style.visibility != "hidden", "Test 13d, Test plugin should have visible overlay"); + ok(!objLoadingContent.activated, "Test 13d, Second Test plugin (A) should not be activated"); + + var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB"); + var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent); + var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtestB, "class", "mainBox"); + ok(overlay.style.visibility != "hidden", "Test 13d, Test plugin should have visible overlay"); + ok(!objLoadingContent.activated, "Test 13d, Second Test plugin (B) should not be activated"); + + var condition = function() objLoadingContent.activated; + // "click" "Activate All Plugins" + popupNotification.mainAction.callback(); + waitForCondition(condition, test13e, "Test 13d, Waited too long for plugin to activate"); +} + +// Test that clicking "Activate All Plugins" won't activate plugins that +// have previously been "Deny Always"-ed. +function test13e() { + var test = gTestBrowser.contentDocument.getElementById("test"); + var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 13e, Test plugin should not be activated"); + + var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA"); + var objLoadingContent = secondtestA.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 13e, Second Test plugin (A) should be activated"); + + var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB"); + var objLoadingContent = secondtestB.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 13e, Second Test plugin (B) should be activated"); + Services.perms.removeAll(); Services.prefs.setBoolPref("plugins.click_to_play", false); prepareTest(test14, gTestRoot + "plugin_test2.html"); @@ -932,5 +998,123 @@ function test23() { is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, Plugin should be click-to-play"); ok(!pluginNode.activated, "Test 23, plugin node should not be activated"); + prepareTest(test24a, gHttpTestRoot + "plugin_test.html"); +} + +// Test that "always allow"-ing a plugin will not allow it when it becomes +// blocklisted. +function test24a() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 24a, Should have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 24a, Found plugin in page"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 24a, Plugin should be click-to-play"); + ok(!objLoadingContent.activated, "Test 24a, plugin should not be activated"); + + // simulate "always allow" + notification.secondaryActions[0].callback(); + prepareTest(test24b, gHttpTestRoot + "plugin_test.html"); +} + +// did the "always allow" work as intended? +function test24b() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 24b, Should not have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 24b, Found plugin in page"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 24b, plugin should be activated"); + setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml", + function() { + prepareTest(test24c, gHttpTestRoot + "plugin_test.html"); + }); +} + +// the plugin is now blocklisted, so it should not automatically load +function test24c() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 24c, Should have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 24c, Found plugin in page"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 24c, Plugin should be vulnerable/updatable"); + ok(!objLoadingContent.activated, "Test 24c, plugin should not be activated"); + + // simulate "always allow" + notification.secondaryActions[0].callback(); + prepareTest(test24d, gHttpTestRoot + "plugin_test.html"); +} + +// We should still be able to always allow a plugin after we've seen that it's +// blocklisted. +function test24d() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 24d, Should not have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 24d, Found plugin in page"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 24d, plugin should be activated"); + + Services.perms.removeAll(); + resetBlocklist(function () { + prepareTest(test25a, gHttpTestRoot + "plugin_test.html"); + }); +} + +// Test that clicking "always allow" or "always deny" doesn't affect plugins +// that already have permission given to them +function test25a() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 25a, Should have a click-to-play notification"); + var plugin = gTestBrowser.contentDocument.getElementById("test"); + ok(plugin, "Test 25a, Found plugin in page"); + var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 25a, plugin should not be activated"); + + // simulate "always allow" + notification.secondaryActions[0].callback(); + prepareTest(test25b, gHttpTestRoot + "plugin_two_types.html"); +} + +function test25b() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(notification, "Test 25b, Should have a click-to-play notification"); + + var test = gTestBrowser.contentDocument.getElementById("test"); + ok(test, "Test 25b, Found test plugin in page"); + var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 25b, test plugin should be activated"); + + var secondtest = gTestBrowser.contentDocument.getElementById("secondtestA"); + ok(secondtest, "Test 25b, Found second test plugin in page"); + var objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 25b, second test plugin should not be activated"); + + // simulate "always deny" + notification.secondaryActions[1].callback(); + prepareTest(test25c, gHttpTestRoot + "plugin_two_types.html"); +} + +// we should have one plugin allowed to activate and the other plugin(s) denied +// (so it should have an invisible overlay) +function test25c() { + var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser); + ok(!notification, "Test 25c, Should not have a click-to-play notification"); + + var test = gTestBrowser.contentDocument.getElementById("test"); + ok(test, "Test 25c, Found test plugin in page"); + var objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent); + ok(objLoadingContent.activated, "Test 25c, test plugin should be activated"); + + var secondtest = gTestBrowser.contentDocument.getElementById("secondtestA"); + ok(secondtest, "Test 25c, Found second test plugin in page"); + var objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent); + ok(!objLoadingContent.activated, "Test 25c, second test plugin should not be activated"); + var overlay = gTestBrowser.contentDocument.getAnonymousElementByAttribute(secondtest, "class", "mainBox"); + ok(overlay.style.visibility == "hidden", "Test 25c, second test plugin should not have visible overlay"); + + Services.perms.removeAll(); + finishTest(); } diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 575d5ab485a..aea9a94c39a 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -2561,9 +2561,12 @@ nsObjectLoadingContent::ShouldPlay(FallbackType &aReason) // the system principal, i.e. in chrome pages. That way the click-to-play // code here wouldn't matter at all. Bug 775301 is tracking this. if (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) { + nsAutoCString permissionString; + rv = pluginHost->GetPermissionStringForType(mContentType, permissionString); + NS_ENSURE_SUCCESS(rv, false); uint32_t permission; rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(), - "plugins", + permissionString.Data(), &permission); NS_ENSURE_SUCCESS(rv, false); allowPerm = permission == nsIPermissionManager::ALLOW_ACTION; diff --git a/dom/plugins/base/nsIPluginHost.idl b/dom/plugins/base/nsIPluginHost.idl index 7e1261fb1c4..4cbb6bdab13 100644 --- a/dom/plugins/base/nsIPluginHost.idl +++ b/dom/plugins/base/nsIPluginHost.idl @@ -12,7 +12,7 @@ "@mozilla.org/plugin/host;1" %} -[scriptable, uuid(d70af999-cb1f-4429-b85e-f18cdbabc43c)] +[scriptable, uuid(3ac8fe33-c38c-4123-b2f0-0e8a2824b9c5)] interface nsIPluginHost : nsISupports { /** @@ -82,6 +82,8 @@ interface nsIPluginHost : nsISupports void unregisterPlayPreviewMimeType(in AUTF8String mimeType); + ACString getPermissionStringForType(in AUTF8String mimeType); + bool isPluginClickToPlayForType(in AUTF8String mimeType); }; diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index aed61be8d7f..9638fc4252b 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -1347,6 +1347,31 @@ nsPluginHost::GetBlocklistStateForType(const char *aMimeType, uint32_t *aState) return NS_ERROR_FAILURE; } +NS_IMETHODIMP +nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType, nsACString &aPermissionString) +{ + aPermissionString.Truncate(); + uint32_t blocklistState; + nsresult rv = GetBlocklistStateForType(aMimeType.Data(), &blocklistState); + NS_ENSURE_SUCCESS(rv, rv); + nsPluginTag *tag = FindPluginForType(aMimeType.Data(), true); + if (!tag) { + return NS_ERROR_FAILURE; + } + + if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE || + blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { + aPermissionString.AssignLiteral("plugin-vulnerable:"); + } + else { + aPermissionString.AssignLiteral("plugin:"); + } + + aPermissionString.Append(tag->mFileName); + + return NS_OK; +} + // check comma delimitered extensions static int CompareExtensions(const char *aExtensionList, const char *aExtension) {