--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -177,16 +177,32 @@ http://itisatracker.org:80
http://trackertest.org:80
https://malware.example.com:443
https://unwanted.example.com:443
https://tracking.example.com:443
https://not-tracking.example.com:443
https://tracking.example.org:443
+#
+# Used while testing flash blocking (Bug 1307604)
+#
+http://flashallow.example.com:80
+http://exception.flashallow.example.com:80
+http://flashblock.example.com:80
+http://exception.flashblock.example.com:80
+http://subdocument.example.com:80
+http://exception.subdocument.example.com:80
+
+#
+# Flash usage can fail unless this URL exists
+#
+http://fpdownload2.macromedia.com:80
+https://fpdownload2.macromedia.com:443
+
# Bug 1281083
http://bug1281083.example.com:80
# Bug 483437, 484111
https://www.bank1.com:443 privileged,cert=escapeattack1
#
# CONNECT for redirproxy results in a 302 redirect to
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -258,16 +258,18 @@
#include "nsISpeculativeConnect.h"
#include "mozilla/MediaManager.h"
#ifdef MOZ_WEBRTC
#include "IPeerConnection.h"
#endif // MOZ_WEBRTC
+#include "nsIURIClassifier.h"
+
using namespace mozilla;
using namespace mozilla::dom;
typedef nsTArray<Link*> LinkArray;
static LazyLogModule gDocumentLeakPRLog("DocumentLeak");
static LazyLogModule gCspPRLog("CSP");
@@ -1375,16 +1377,17 @@ nsIDocument::nsIDocument()
PR_INIT_CLIST(&mDOMMediaQueryLists);
}
nsDocument::nsDocument(const char* aContentType)
: nsIDocument()
, mIsTopLevelContentDocument(false)
, mIsContentDocument(false)
, mSubDocuments(nullptr)
+ , mFlashClassification(FlashClassification::Unclassified)
, mHeaderData(nullptr)
, mIsGoingAway(false)
, mInDestructor(false)
, mMayHaveTitleElement(false)
, mHasWarnedAboutBoxObjects(false)
, mDelayFrameLoaderInitialization(false)
, mSynchronousDOMContentLoaded(false)
, mInXBLUpdate(false)
@@ -12968,8 +12971,203 @@ nsDocument::CheckCustomElementName(const
// Throw NotFoundError if 'is' is not-null and definition is null
if (!nsContentUtils::LookupCustomElementDefinition(this, aLocalName,
aNamespaceID, is)) {
rv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
}
return is;
}
+
+/**
+ * Helper function for |nsDocument::PrincipalFlashClassification|
+ *
+ * Adds a table name string to a table list (a comma separated string). The
+ * table will not be added if the name is an empty string.
+ */
+static void
+MaybeAddTableToTableList(const nsACString& aTableNames,
+ nsACString& aTableList)
+{
+ if (aTableNames.IsEmpty()) {
+ return;
+ }
+ if (!aTableList.IsEmpty()) {
+ aTableList.AppendLiteral(",");
+ }
+ aTableList.Append(aTableNames);
+}
+
+/**
+ * Helper function for |nsDocument::PrincipalFlashClassification|
+ *
+ * Takes an array of table names and a comma separated list of table names
+ * Returns |true| if any table name in the array matches a table name in the
+ * comma separated list.
+ */
+static bool
+ArrayContainsTable(const nsTArray<nsCString>& aTableArray,
+ const nsACString& aTableNames)
+{
+ for (const nsCString& table : aTableArray) {
+ // This check is sufficient because table names cannot contain commas and
+ // cannot contain another existing table name.
+ if (FindInReadable(table, aTableNames)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Retrieves the classification of the Flash plugins in the document based on
+ * the classification lists.
+ *
+ * For more information, see
+ * toolkit/components/url-classifier/flash-block-lists.rst
+ */
+nsIDocument::FlashClassification
+nsDocument::PrincipalFlashClassification(bool aIsTopLevel)
+{
+ nsresult rv;
+
+ // If flash blocking is disabled, it is equivalent to all sites being
+ // whitelisted.
+ if (!Preferences::GetBool("plugins.flashBlock.enabled")) {
+ return FlashClassification::Allowed;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
+ if (principal->GetIsNullPrincipal()) {
+ return FlashClassification::Denied;
+ }
+
+ nsCOMPtr<nsIURI> classificationURI;
+ rv = principal->GetURI(getter_AddRefs(classificationURI));
+ if (NS_FAILED(rv) || !classificationURI) {
+ return FlashClassification::Denied;
+ }
+
+ nsAutoCString allowTables, allowExceptionsTables,
+ denyTables, denyExceptionsTables,
+ subDocDenyTables, subDocDenyExceptionsTables,
+ tables;
+ Preferences::GetCString("urlclassifier.flashAllowTable", &allowTables);
+ MaybeAddTableToTableList(allowTables, tables);
+ Preferences::GetCString("urlclassifier.flashAllowExceptTable",
+ &allowExceptionsTables);
+ MaybeAddTableToTableList(allowExceptionsTables, tables);
+ Preferences::GetCString("urlclassifier.flashTable", &denyTables);
+ MaybeAddTableToTableList(denyTables, tables);
+ Preferences::GetCString("urlclassifier.flashExceptTable",
+ &denyExceptionsTables);
+ MaybeAddTableToTableList(denyExceptionsTables, tables);
+ if (!aIsTopLevel) {
+ Preferences::GetCString("urlclassifier.flashSubDocTable",
+ &subDocDenyTables);
+ MaybeAddTableToTableList(subDocDenyTables, tables);
+ Preferences::GetCString("urlclassifier.flashSubDocExceptTable",
+ &subDocDenyExceptionsTables);
+ MaybeAddTableToTableList(subDocDenyExceptionsTables, tables);
+ }
+
+ if (tables.IsEmpty()) {
+ return FlashClassification::Unknown;
+ }
+
+ nsCOMPtr<nsIURIClassifier> uriClassifier =
+ do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return FlashClassification::Denied;
+ }
+
+ nsTArray<nsCString> results;
+ rv = uriClassifier->ClassifyLocalWithTables(classificationURI,
+ tables,
+ results);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ // This means that the URI had no hostname (ex: file://doc.html). In this
+ // case, we allow the default (Unknown plugin) behavior.
+ return FlashClassification::Unknown;
+ } else {
+ return FlashClassification::Denied;
+ }
+ }
+
+ if (results.IsEmpty()) {
+ return FlashClassification::Unknown;
+ }
+
+ if (ArrayContainsTable(results, denyTables) &&
+ !ArrayContainsTable(results, denyExceptionsTables)) {
+ return FlashClassification::Denied;
+ } else if (ArrayContainsTable(results, allowTables) &&
+ !ArrayContainsTable(results, allowExceptionsTables)) {
+ return FlashClassification::Allowed;
+ }
+
+ if (!aIsTopLevel && ArrayContainsTable(results, subDocDenyTables) &&
+ !ArrayContainsTable(results, subDocDenyExceptionsTables)) {
+ return FlashClassification::Denied;
+ }
+
+ return FlashClassification::Unknown;
+}
+
+nsIDocument::FlashClassification
+nsDocument::ComputeFlashClassification()
+{
+ nsCOMPtr<nsIDocShellTreeItem> current = this->GetDocShell();
+ if (!current) {
+ return FlashClassification::Denied;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ DebugOnly<nsresult> rv = current->GetSameTypeParent(getter_AddRefs(parent));
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "nsIDocShellTreeItem::GetSameTypeParent should never fail");
+
+ bool isTopLevel = !parent;
+ FlashClassification classification;
+ if (isTopLevel) {
+ classification = PrincipalFlashClassification(isTopLevel);
+ } else {
+ nsCOMPtr<nsIDocument> parentDocument = GetParentDocument();
+ FlashClassification parentClassification =
+ parentDocument->DocumentFlashClassification();
+
+ if (parentClassification == FlashClassification::Denied) {
+ classification = FlashClassification::Denied;
+ } else {
+ classification = PrincipalFlashClassification(isTopLevel);
+
+ // Allow unknown children to inherit allowed status from parent, but
+ // do not allow denied children to do so.
+ if (classification == FlashClassification::Unknown &&
+ parentClassification == FlashClassification::Allowed) {
+ classification = FlashClassification::Allowed;
+ }
+ }
+ }
+
+ return classification;
+}
+
+/**
+ * Retrieves the classification of plugins in this document. This is dependent
+ * on the classification of this document and all parent documents.
+ * This function is infallible - It must return some classification that
+ * callers can act on.
+ *
+ * This function will NOT return FlashClassification::Unclassified
+ */
+nsIDocument::FlashClassification
+nsDocument::DocumentFlashClassification()
+{
+ if (mFlashClassification == FlashClassification::Unclassified) {
+ FlashClassification result = ComputeFlashClassification();
+ mFlashClassification = result;
+ MOZ_ASSERT(result != FlashClassification::Unclassified,
+ "nsDocument::GetPluginClassification should never return Unclassified");
+ }
+
+ return mFlashClassification;
+}
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -1300,16 +1300,18 @@ protected:
bool aPersisted);
virtual nsPIDOMWindowOuter* GetWindowInternal() const override;
virtual nsIScriptGlobalObject* GetScriptHandlingObjectInternal() const override;
virtual bool InternalAllowXULXBL() override;
void UpdateScreenOrientation();
+ virtual FlashClassification DocumentFlashClassification() override;
+
#define NS_DOCUMENT_NOTIFY_OBSERVERS(func_, params_) \
NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, nsIDocumentObserver, \
func_, params_);
#ifdef DEBUG
void VerifyRootContentState();
#endif
@@ -1320,16 +1322,24 @@ protected:
void NotifyStyleSheetApplicableStateChanged();
// Apply the fullscreen state to the document, and trigger related
// events. It returns false if the fullscreen element ready check
// fails and nothing gets changed.
bool ApplyFullscreen(const FullscreenRequest& aRequest);
+ // Retrieves the classification of the Flash plugins in the document based on
+ // the classification lists.
+ FlashClassification PrincipalFlashClassification(bool aIsTopLevel);
+
+ // Attempts to determine the Flash classification of this page based on the
+ // the classification lists and the classification of parent documents.
+ FlashClassification ComputeFlashClassification();
+
nsTArray<nsIObserver*> mCharSetObservers;
PLDHashTable *mSubDocuments;
// Array of owning references to all children
nsAttrAndChildArray mChildren;
// Pointer to our parser if we're currently in the process of being
@@ -1364,16 +1374,17 @@ protected:
// full-screen element onto this stack, and when we cancel full-screen we
// pop one off this stack, restoring the previous full-screen state
nsTArray<nsWeakPtr> mFullScreenStack;
// The root of the doc tree in which this document is in. This is only
// non-null when this document is in fullscreen mode.
nsWeakPtr mFullscreenRoot;
+ FlashClassification mFlashClassification;
private:
static bool CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
/**
* Check if the passed custom element name, aOptions.mIs, is a registered
* custom element type or not, then return the custom element name for future
* usage.
*
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2888,16 +2888,27 @@ public:
// The URLs passed to these functions should match what
// JS::DescribeScriptedCaller() returns, since these APIs are used to
// determine whether some code is being called from a tracking script.
void NoteScriptTrackingStatus(const nsACString& aURL, bool isTracking);
bool IsScriptTracking(const nsACString& aURL) const;
bool PrerenderHref(nsIURI* aHref);
+ // For more information on Flash classification, see
+ // toolkit/components/url-classifier/flash-block-lists.rst
+ enum class FlashClassification {
+ Unclassified, // Denotes a classification that has not yet been computed.
+ // Allows for lazy classification.
+ Unknown, // Site is not on the whitelist or blacklist
+ Allowed, // Site is on the Flash whitelist
+ Denied // Site is on the Flash blacklist
+ };
+ virtual FlashClassification DocumentFlashClassification() = 0;
+
protected:
bool GetUseCounter(mozilla::UseCounter aUseCounter)
{
return mUseCounters[aUseCounter];
}
void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
{
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3292,16 +3292,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
if (!aIgnoreCurrentType && mType != eType_Plugin) {
return true;
}
// Order of checks:
// * Assume a default of click-to-play
// * If globally disabled, per-site permissions cannot override.
// * If blocklisted, override the reason with the blocklist reason
+ // * Check if the flash blocking status for this page denies flash from loading.
// * Check per-site permissions and follow those if specified.
// * Honor per-plugin disabled permission
// * Blocklisted plugins are forced to CtP
// * Check per-plugin permission and follow that.
aReason = eFallbackClickToPlay;
uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
@@ -3327,32 +3328,42 @@ nsObjectLoadingContent::ShouldPlay(Fallb
if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) {
aReason = eFallbackVulnerableUpdatable;
}
else if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
aReason = eFallbackVulnerableNoUpdate;
}
- // Check the permission manager for permission based on the principal of
- // the toplevel content.
-
+ // Document and window lookup
nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
MOZ_ASSERT(thisContent);
nsIDocument* ownerDoc = thisContent->OwnerDoc();
nsCOMPtr<nsPIDOMWindowOuter> window = ownerDoc->GetWindow();
if (!window) {
return false;
}
nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetTop();
NS_ENSURE_TRUE(topWindow, false);
nsCOMPtr<nsIDocument> topDoc = topWindow->GetDoc();
NS_ENSURE_TRUE(topDoc, false);
+ // Check the flash blocking status for this page (this applies to Flash only)
+ nsIDocument::FlashClassification documentClassification = nsIDocument::FlashClassification::Allowed;
+ if (IsFlashMIME(mContentType)) {
+ documentClassification = ownerDoc->DocumentFlashClassification();
+ }
+ if (documentClassification == nsIDocument::FlashClassification::Denied) {
+ aReason = eFallbackSuppressed;
+ return false;
+ }
+
+ // Check the permission manager for permission based on the principal of
+ // the toplevel content.
nsCOMPtr<nsIPermissionManager> permissionManager = services::GetPermissionManager();
NS_ENSURE_TRUE(permissionManager, false);
// For now we always say that the system principal uses click-to-play since
// that maintains current behavior and we have tests that expect this.
// What we really should do is disable plugins entirely in pages that use
// 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.
@@ -3409,17 +3420,17 @@ nsObjectLoadingContent::ShouldPlay(Fallb
if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
aReason = eFallbackAlternate;
return false;
}
switch (enabledState) {
case nsIPluginTag::STATE_ENABLED:
- return true;
+ return documentClassification == nsIDocument::FlashClassification::Allowed;
case nsIPluginTag::STATE_CLICKTOPLAY:
return false;
}
MOZ_CRASH("Unexpected enabledState");
}
bool
nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5114,17 +5114,17 @@ pref("urlclassifier.downloadBlockTable",
// Only download the whitelist on Windows, since the whitelist is
// only useful for suppressing remote lookups for signed binaries which we can
// only verify on Windows (Bug 974579). Other platforms always do remote lookups.
pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256");
#else
pref("urlclassifier.downloadAllowTable", "");
#endif
-pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
+pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-block-simple,test-flashallow-simple,testexcept-flashallow-simple,test-flash-simple,testexcept-flash-simple,test-flashsubdoc-simple,testexcept-flashsubdoc-simple,goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256");
// The table and update/gethash URLs for Safebrowsing phishing and malware
// checks.
pref("urlclassifier.trackingTable", "test-track-simple,base-track-digest256");
pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
// The number of random entries to send with a gethash request.
pref("urlclassifier.gethashnoise", 4);
@@ -5189,16 +5189,25 @@ pref("browser.safebrowsing.provider.mozi
pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
// Block lists for tracking protection. The name values will be used as the keys
// to lookup the localized name in preferences.properties.
pref("browser.safebrowsing.provider.mozilla.lists.base.name", "mozstdName");
pref("browser.safebrowsing.provider.mozilla.lists.base.description", "mozstdDesc");
pref("browser.safebrowsing.provider.mozilla.lists.content.name", "mozfullName");
pref("browser.safebrowsing.provider.mozilla.lists.content.description", "mozfullDesc");
+pref("urlclassifier.flashAllowTable", "test-flashallow-simple");
+pref("urlclassifier.flashAllowExceptTable", "testexcept-flashallow-simple");
+pref("urlclassifier.flashTable", "test-flash-simple");
+pref("urlclassifier.flashExceptTable", "testexcept-flash-simple");
+pref("urlclassifier.flashSubDocTable", "test-flashsubdoc-simple");
+pref("urlclassifier.flashSubDocExceptTable", "testexcept-flashsubdoc-simple");
+
+pref("plugins.flashBlock.enabled", false);
+
// Allow users to ignore Safe Browsing warnings.
pref("browser.safebrowsing.allowOverride", true);
#ifdef MOZILLA_OFFICIAL
// Normally the "client ID" sent in updates is appinfo.name, but for
// official Firefox releases from Mozilla we use a special identifier.
pref("browser.safebrowsing.id", "navclient-auto-ffox");
#else
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -45,17 +45,23 @@ function getLists(prefName) {
const tablePreferences = [
"urlclassifier.phishTable",
"urlclassifier.malwareTable",
"urlclassifier.downloadBlockTable",
"urlclassifier.downloadAllowTable",
"urlclassifier.trackingTable",
"urlclassifier.trackingWhitelistTable",
- "urlclassifier.blockedTable"
+ "urlclassifier.blockedTable",
+ "urlclassifier.flashAllowTable",
+ "urlclassifier.flashAllowExceptTable",
+ "urlclassifier.flashTable",
+ "urlclassifier.flashExceptTable",
+ "urlclassifier.flashSubDocTable",
+ "urlclassifier.flashSubDocExceptTable"
];
this.SafeBrowsing = {
init: function() {
if (this.initialized) {
log("Already initialized");
return;
@@ -107,25 +113,29 @@ this.SafeBrowsing = {
this.registerTableWithURLs(this.trackingProtectionLists[i]);
}
for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
this.registerTableWithURLs(this.trackingProtectionWhitelists[i]);
}
for (let i = 0; i < this.blockedLists.length; ++i) {
this.registerTableWithURLs(this.blockedLists[i]);
}
+ for (let i = 0; i < this.flashLists.length; ++i) {
+ this.registerTableWithURLs(this.flashLists[i]);
+ }
},
initialized: false,
phishingEnabled: false,
malwareEnabled: false,
trackingEnabled: false,
blockedEnabled: false,
trackingAnnotations: false,
+ flashBlockEnabled: false,
phishingLists: [],
malwareLists: [],
downloadBlockLists: [],
downloadAllowLists: [],
trackingProtectionLists: [],
trackingProtectionWhitelists: [],
blockedLists: [],
@@ -178,24 +188,41 @@ this.SafeBrowsing = {
log("reading prefs");
this.debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.phishing.enabled");
this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
this.blockedEnabled = Services.prefs.getBoolPref("browser.safebrowsing.blockedURIs.enabled");
this.trackingAnnotations = Services.prefs.getBoolPref("privacy.trackingprotection.annotate_channels");
+ this.flashBlockEnabled = Services.prefs.getBoolPref("plugins.flashBlock.enabled");
+
+ let flashAllowTable, flashAllowExceptTable, flashTable,
+ flashExceptTable, flashSubDocTable,
+ flashSubDocExceptTable;
[this.phishingLists,
this.malwareLists,
this.downloadBlockLists,
this.downloadAllowLists,
this.trackingProtectionLists,
this.trackingProtectionWhitelists,
- this.blockedLists] = tablePreferences.map(getLists);
+ this.blockedLists,
+ flashAllowTable,
+ flashAllowExceptTable,
+ flashTable,
+ flashExceptTable,
+ flashSubDocTable,
+ flashSubDocExceptTable] = tablePreferences.map(getLists);
+
+ this.flashLists = flashAllowTable.concat(flashAllowExceptTable,
+ flashTable,
+ flashExceptTable,
+ flashSubDocTable,
+ flashSubDocExceptTable)
this.updateProviderURLs();
this.registerTables();
// XXX The listManager backend gets confused if this is called before the
// lists are registered. So only call it here when a pref changes, and not
// when doing initialization. I expect to refactor this later, so pardon the hack.
if (this.initialized) {
@@ -264,17 +291,17 @@ this.SafeBrowsing = {
}
}, this);
},
controlUpdateChecking: function() {
log("phishingEnabled:", this.phishingEnabled, "malwareEnabled:",
this.malwareEnabled, "trackingEnabled:", this.trackingEnabled,
"blockedEnabled:", this.blockedEnabled, "trackingAnnotations",
- this.trackingAnnotations);
+ this.trackingAnnotations, "flashBlockEnabled", this.flashBlockEnabled);
let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
getService(Ci.nsIUrlListManager);
for (let i = 0; i < this.phishingLists.length; ++i) {
if (this.phishingEnabled) {
listManager.enableUpdate(this.phishingLists[i]);
} else {
@@ -318,16 +345,23 @@ this.SafeBrowsing = {
}
for (let i = 0; i < this.blockedLists.length; ++i) {
if (this.blockedEnabled) {
listManager.enableUpdate(this.blockedLists[i]);
} else {
listManager.disableUpdate(this.blockedLists[i]);
}
}
+ for (let i = 0; i < this.flashLists.length; ++i) {
+ if (this.flashBlockEnabled) {
+ listManager.enableUpdate(this.flashLists[i]);
+ } else {
+ listManager.disableUpdate(this.flashLists[i]);
+ }
+ }
listManager.maybeToggleUpdateChecking();
},
addMozEntries: function() {
// Add test entries to the DB.
// XXX bug 779008 - this could be done by DB itself?
const phishURL = "itisatrap.org/firefox/its-a-trap.html";
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/flash-block-lists.rst
@@ -0,0 +1,37 @@
+=========================
+List Based Flash Blocking
+=========================
+
+List based Flash blocking currently uses six lists.
+The lists specify what domains/subdomains Flash is allowed to or denied from loading on.
+The domains specified by the lists indicate the domain of the document that the Flash is loaded in, not the domain hosting the Flash content itself.
+
+* Allow List
+* Allow Exceptions List
+* Deny List
+* Deny Exceptions List
+* Sub-Document Deny List
+* Sub-Document Deny Exceptions List
+
+If a page is on a list and the corresponding "Exceptions List", it is treated as though it is not on that list.
+
+Classification
+==============
+
+Documents can be classified as Allow, Deny or Unknown.
+Documents with an Allow classification may load Flash normally.
+Documents with a Deny classification may not load Flash at all.
+A Deny classification overrides an Allow classification.
+The Unknown classification is the fall-through classification; it essentially just means that the document did not receive an Allow or Deny classification.
+Documents with an Unknown classification will have Flash set to Click To Activate.
+
+If the document is at the top level (its address is in the URL bar), then the Deny List is checked first followed by the Allow List to determine its classification.
+
+If the document is not at the top level, it will receive a Deny classification if the classification of the parent document is Deny or if the document is on the Deny List or the Sub-Document Deny List.
+If the document did not receive a Deny classification, it can receive an Allow classification if it is on the Allow List or if the parent document received an Allow classification.
+
+If for any reason, the document has a null principal, it will receive a Deny classification.
+Some examples of documents that would have a null principal are:
+
+* Data URIs <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs> loaded directly from the URL bar. Data URIs loaded by a page should inherit the loading page's permissions.
+* URIs that are rendered with the JSON viewer
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+ flash_block_frame.html
+ classifierHelper.js
+
+[browser_flash_block_lists.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/browser_flash_block_lists.js
@@ -0,0 +1,296 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+requestLongerTimeout(2);
+
+var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Task.jsm");
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/components/url-classifier/tests/browser/classifierHelper.js",
+ this);
+
+const URL_PATH = "/browser/toolkit/components/url-classifier/tests/browser/flash_block_frame.html";
+const OBJECT_ID = "testObject";
+const IFRAME_ID = "testFrame";
+const FLASHBLOCK_ENABLE_PREF = "plugins.flashBlock.enabled";
+
+var dbUrls = [
+ {
+ url: "flashallow.example.com/",
+ db: "test-flashallow-simple",
+ pref: "urlclassifier.flashAllowTable"
+ },
+ {
+ url: "exception.flashallow.example.com/",
+ db: "testexcept-flashallow-simple",
+ pref: "urlclassifier.flashAllowExceptTable"
+ },
+ {
+ url: "flashblock.example.com/",
+ db: "test-flash-simple",
+ pref: "urlclassifier.flashTable"
+ },
+ {
+ url: "exception.flashblock.example.com/",
+ db: "testexcept-flash-simple",
+ pref: "urlclassifier.flashExceptTable"
+ },
+ {
+ url: "subdocument.example.com/",
+ db: "test-flashsubdoc-simple",
+ pref: "urlclassifier.flashThirdPartyTable"
+ },
+ {
+ url: "exception.subdocument.example.com/",
+ db: "testexcept-flashsubdoc-simple",
+ pref: "urlclassifier.flashThirdPartyExceptTable"
+ }
+];
+
+function setDBPrefs() {
+ for (let dbData of dbUrls) {
+ Services.prefs.setCharPref(dbData.pref, dbData.db);
+ }
+ Services.prefs.setBoolPref(FLASHBLOCK_ENABLE_PREF, true);
+}
+
+function unsetDBPrefs() {
+ for (let dbData of dbUrls) {
+ Services.prefs.clearUserPref(dbData.pref);
+ }
+ Services.prefs.clearUserPref(FLASHBLOCK_ENABLE_PREF);
+}
+registerCleanupFunction(unsetDBPrefs);
+
+// The |domains| property describes the domains of the nested documents making
+// up the page. |domains[0]| represents the domain in the URL bar. The last
+// domain in the list is the domain of the most deeply nested iframe.
+// Only the plugin in the most deeply nested document will be checked.
+var testCases = [
+ {
+ name: "Unknown domain",
+ domains: ["http://example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Nested unknown domains",
+ domains: ["http://example.com", "http://example.org"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Allowed domain",
+ domains: ["http://flashallow.example.com"],
+ expectedActivated: true,
+ expectedHasRunningPlugin: true
+ },
+ {
+ name: "Allowed nested domain",
+ domains: ["http://example.com", "http://flashallow.example.com"],
+ expectedActivated: true,
+ expectedHasRunningPlugin: true
+ },
+ {
+ name: "Subdocument of allowed domain",
+ domains: ["http://flashallow.example.com", "http://example.com"],
+ expectedActivated: true,
+ expectedHasRunningPlugin: true
+ },
+ {
+ name: "Exception to allowed domain",
+ domains: ["http://exception.flashallow.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Blocked domain",
+ domains: ["http://flashblock.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Nested blocked domain",
+ domains: ["http://example.com", "http://flashblock.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Subdocument of blocked subdocument",
+ domains: ["http://example.com", "http://flashblock.example.com", "http://example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Blocked subdocument nested among in allowed documents",
+ domains: ["http://flashallow.example.com", "http://flashblock.example.com", "http://flashallow.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Exception to blocked domain",
+ domains: ["http://exception.flashblock.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Sub-document blocked domain in top-level context",
+ domains: ["http://subdocument.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Sub-document blocked domain",
+ domains: ["http://example.com", "http://subdocument.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Sub-document blocked subdocument of an allowed domain",
+ domains: ["http://flashallow.example.com", "http://subdocument.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Subdocument of Sub-document blocked domain",
+ domains: ["http://example.com", "http://subdocument.example.com", "http://example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_SUPPRESSED,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Sub-document exception in top-level context",
+ domains: ["http://exception.subdocument.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ },
+ {
+ name: "Sub-document blocked domain exception",
+ domains: ["http://example.com", "http://exception.subdocument.example.com"],
+ expectedPluginFallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ expectedActivated: false,
+ expectedHasRunningPlugin: false
+ }
+];
+
+function buildDocumentStructure(browser, iframeDomains) {
+ return Task.spawn(function* () {
+ let depth = 0;
+ for (let domain of iframeDomains) {
+ // Firefox does not like to load the same page in its own iframe. Put some
+ // bogus query strings in the URL to make it happy.
+ let url = domain + URL_PATH + "?date=" + Date.now() + "rand=" + Math.random();
+ let domainLoaded = BrowserTestUtils.browserLoaded(browser, true, url);
+
+ ContentTask.spawn(browser, {iframeId: IFRAME_ID, url: url, depth: depth},
+ function*({iframeId, url, depth}) {
+ let doc = content.document;
+ for (let i = 0; i < depth; ++i) {
+ doc = doc.getElementById(iframeId).contentDocument;
+ }
+ doc.getElementById(iframeId).src = url;
+ });
+
+ yield domainLoaded;
+ ++depth;
+ }
+ });
+}
+
+function getPlugin(browser, depth) {
+ return ContentTask.spawn(browser,
+ {iframeId: IFRAME_ID, depth: depth},
+ function* ({iframeId, depth}) {
+ let doc = content.document;
+ for (let i = 0; i < depth; ++i) {
+ doc = doc.getElementById(iframeId).contentDocument;
+ }
+
+ let pluginObj = doc.getElementById("testObject");
+ if (!(pluginObj instanceof Ci.nsIObjectLoadingContent)) {
+ throw new Error("Unable to find plugin!");
+ }
+ return {
+ pluginFallbackType: pluginObj.pluginFallbackType,
+ activated: pluginObj.activated,
+ hasRunningPlugin: pluginObj.hasRunningPlugin
+ };
+ });
+}
+
+add_task(function* checkFlashBlockLists() {
+ setDBPrefs();
+
+ yield classifierHelper.waitForInit();
+ yield classifierHelper.addUrlToDB(dbUrls);
+
+ for (let testCase of testCases) {
+ info(`RUNNING TEST: ${testCase.name}`);
+
+ let iframeDomains = testCase.domains.slice();
+ let pageDomain = iframeDomains.shift();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ pageDomain + URL_PATH);
+
+ yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
+
+ let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
+
+ if ("expectedPluginFallbackType" in testCase) {
+ is(plugin.pluginFallbackType, testCase.expectedPluginFallbackType,
+ "Plugin should have the correct fallback type");
+ }
+ if ("expectedActivated" in testCase) {
+ is(plugin.activated, testCase.expectedActivated,
+ "Plugin should have the correct activation");
+ }
+ if ("expectedHasRunningPlugin" in testCase) {
+ is(plugin.hasRunningPlugin, testCase.expectedHasRunningPlugin,
+ "Plugin should have the correct 'plugin running' state");
+ }
+
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
+
+add_task(function* checkFlashBlockDisabled() {
+ setDBPrefs();
+ Services.prefs.setBoolPref(FLASHBLOCK_ENABLE_PREF, false);
+
+ yield classifierHelper.waitForInit();
+ yield classifierHelper.addUrlToDB(dbUrls);
+
+ for (let testCase of testCases) {
+ info(`RUNNING TEST: ${testCase.name} (flashblock disabled)`);
+
+ let iframeDomains = testCase.domains.slice();
+ let pageDomain = iframeDomains.shift();
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+ pageDomain + URL_PATH);
+
+ yield buildDocumentStructure(tab.linkedBrowser, iframeDomains);
+
+ let plugin = yield getPlugin(tab.linkedBrowser, iframeDomains.length);
+
+ // With flashblock disabled, all plugins should be activated.
+ ok(plugin.activated, "Plugin should be activated");
+ ok(plugin.hasRunningPlugin, "Plugin should be running");
+
+ yield BrowserTestUtils.removeTab(tab);
+ }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/classifierHelper.js
@@ -0,0 +1,221 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Created from toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
+// Unfortunately, browser tests cannot load that script as it is too reliant on
+// being loaded in the content process.
+
+Cu.import("resource://gre/modules/Task.jsm");
+
+let dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+ .getService(Ci.nsIUrlClassifierDBService);
+
+if (typeof(classifierHelper) == "undefined") {
+ var classifierHelper = {};
+}
+
+const ADD_CHUNKNUM = 524;
+const SUB_CHUNKNUM = 523;
+const HASHLEN = 32;
+
+const PREFS = {
+ PROVIDER_LISTS : "browser.safebrowsing.provider.mozilla.lists",
+ DISALLOW_COMPLETIONS : "urlclassifier.disallow_completions",
+ PROVIDER_GETHASHURL : "browser.safebrowsing.provider.mozilla.gethashURL"
+};
+
+// Keep urls added to database, those urls should be automatically
+// removed after test complete.
+classifierHelper._updatesToCleanup = [];
+
+// This function returns a Promise resolved when SafeBrowsing.jsm is initialized.
+// SafeBrowsing.jsm is initialized after mozEntries are added. Add observer
+// to receive "finished" event. For the case when this function is called
+// after the event had already been notified, we lookup entries to see if
+// they are already added to database.
+classifierHelper.waitForInit = function() {
+ let observerService = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let iosvc = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ // This url must sync with the table, url in SafeBrowsing.jsm addMozEntries
+ const table = "test-phish-simple";
+ const url = "http://itisatrap.org/firefox/its-a-trap.html";
+ let principal = secMan.createCodebasePrincipal(
+ iosvc.newURI(url, null, null), {});
+
+ return new Promise(function(resolve, reject) {
+ observerService.addObserver(function() {
+ resolve();
+ }, "mozentries-update-finished", false);
+
+ let listener = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ handleEvent: function(value)
+ {
+ if (value === table) {
+ resolve();
+ }
+ },
+ };
+ dbService.lookup(principal, table, listener);
+ });
+}
+
+// This function is used to allow completion for specific "list",
+// some lists like "test-malware-simple" is default disabled to ask for complete.
+// "list" is the db we would like to allow it
+// "url" is the completion server
+classifierHelper.allowCompletion = function(lists, url) {
+ for (let list of lists) {
+ // Add test db to provider
+ let pref = Services.prefs.getCharPref(PREFS.PROVIDER_LISTS);
+ pref += "," + list;
+ Services.prefs.setCharPref(PREFS.PROVIDER_LISTS, pref);
+
+ // Rename test db so we will not disallow it from completions
+ pref = Services.prefs.getCharPref(PREFS.DISALLOW_COMPLETIONS);
+ pref = pref.replace(list, list + "-backup");
+ Services.prefs.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref);
+ }
+
+ // Set get hash url
+ Services.prefs.setCharPref(PREFS.PROVIDER_GETHASHURL, url);
+}
+
+// Pass { url: ..., db: ... } to add url to database,
+// Returns a Promise.
+classifierHelper.addUrlToDB = function(updateData) {
+ let testUpdate = "";
+ for (let update of updateData) {
+ let LISTNAME = update.db;
+ let CHUNKDATA = update.url;
+ let CHUNKLEN = CHUNKDATA.length;
+ let HASHLEN = update.len ? update.len : 32;
+
+ classifierHelper._updatesToCleanup.push(update);
+ testUpdate +=
+ "n:1000\n" +
+ "i:" + LISTNAME + "\n" +
+ "ad:1\n" +
+ "a:" + ADD_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" +
+ CHUNKDATA;
+ }
+
+ return classifierHelper._update(testUpdate);
+}
+
+// Pass { url: ..., db: ... } to remove url from database,
+// Returns a Promise.
+classifierHelper.removeUrlFromDB = function(updateData) {
+ var testUpdate = "";
+ for (var update of updateData) {
+ var LISTNAME = update.db;
+ var CHUNKDATA = ADD_CHUNKNUM + ":" + update.url;
+ var CHUNKLEN = CHUNKDATA.length;
+ var HASHLEN = update.len ? update.len : 32;
+
+ testUpdate +=
+ "n:1000\n" +
+ "i:" + LISTNAME + "\n" +
+ "s:" + SUB_CHUNKNUM + ":" + HASHLEN + ":" + CHUNKLEN + "\n" +
+ CHUNKDATA;
+ }
+
+ classifierHelper._updatesToCleanup =
+ classifierHelper._updatesToCleanup.filter((v) => {
+ return updateData.indexOf(v) == -1;
+ });
+
+ return classifierHelper._update(testUpdate);
+};
+
+// This API is used to expire all add/sub chunks we have updated
+// by using addUrlToDB and removeUrlFromDB.
+// Returns a Promise.
+classifierHelper.resetDB = function() {
+ var testUpdate = "";
+ for (var update of classifierHelper._updatesToCleanup) {
+ if (testUpdate.includes(update.db))
+ continue;
+
+ testUpdate +=
+ "n:1000\n" +
+ "i:" + update.db + "\n" +
+ "ad:" + ADD_CHUNKNUM + "\n" +
+ "sd:" + SUB_CHUNKNUM + "\n"
+ }
+
+ return classifierHelper._update(testUpdate);
+};
+
+classifierHelper.reloadDatabase = function() {
+ dbService.reloadDatabase();
+}
+
+classifierHelper._update = function(update) {
+ return Task.spawn(function* () {
+ // beginUpdate may fail if there's an existing update in progress
+ // retry until success or testcase timeout.
+ let success = false;
+ while (!success) {
+ try {
+ yield new Promise((resolve, reject) => {
+ let listener = {
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+ updateUrlRequested: function(url) { },
+ streamFinished: function(status) { },
+ updateError: function(errorCode) {
+ reject(errorCode);
+ },
+ updateSuccess: function(requestedTimeout) {
+ resolve();
+ }
+ };
+ dbService.beginUpdate(listener, "", "");
+ dbService.beginStream("", "");
+ dbService.updateStream(update);
+ dbService.finishStream();
+ dbService.finishUpdate();
+ });
+ success = true;
+ } catch(e) {
+ // Wait 1 second before trying again.
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+ }
+ }
+ });
+};
+
+classifierHelper._cleanup = function() {
+ // Clean all the preferences that may have been touched by classifierHelper
+ for (var pref in PREFS) {
+ Services.prefs.clearUserPref(pref);
+ }
+
+ if (!classifierHelper._updatesToCleanup) {
+ return Promise.resolve();
+ }
+
+ return classifierHelper.resetDB();
+};
+// Cleanup will be called at end of each testcase to remove all the urls added to database.
+registerCleanupFunction(classifierHelper._cleanup);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/browser/flash_block_frame.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the URI Classifier</title>
+</head>
+<body>
+ <h1>Test page</h1>
+ <object id="testObject" width="100" height="100" type="application/x-shockwave-flash-test"></object>
+ <br>
+ <iframe id="testFrame">
+ </iframe>
+</body>
+</html>
--- a/toolkit/components/url-classifier/tests/moz.build
+++ b/toolkit/components/url-classifier/tests/moz.build
@@ -2,16 +2,17 @@
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
JAR_MANIFESTS += ['jar.mn']
TESTING_JS_MODULES += [
'UrlClassifierTestUtils.jsm',
]
if CONFIG['ENABLE_TESTS']: