From a3cc308cb408b3e470ffa65ba2f93003fbb0b8b2 Mon Sep 17 00:00:00 2001 From: "danm-moz%comcast.net" Date: Tue, 3 Feb 2004 02:22:54 +0000 Subject: [PATCH] generally disallow popup windows spawned by event handlers, or too many simultaneous popups. bug 197919 r=bryner,jst --- dom/src/base/nsGlobalWindow.cpp | 268 +++++++++++++++++++++++++++----- dom/src/base/nsGlobalWindow.h | 8 +- 2 files changed, 239 insertions(+), 37 deletions(-) diff --git a/dom/src/base/nsGlobalWindow.cpp b/dom/src/base/nsGlobalWindow.cpp index ab8512ee6c3..e2f333f0bc0 100644 --- a/dom/src/base/nsGlobalWindow.cpp +++ b/dom/src/base/nsGlobalWindow.cpp @@ -157,6 +157,7 @@ static nsIEntropyCollector *gEntropyCollector = nsnull; static nsIPrefBranch *gPrefBranch = nsnull; static PRInt32 gRefCnt = 0; +static PRInt32 gOpenPopupSpamCount = 0; nsIXPConnect *GlobalWindowImpl::sXPConnect = nsnull; nsIScriptSecurityManager *GlobalWindowImpl::sSecMan = nsnull; nsIFactory *GlobalWindowImpl::sComputedDOMStyleFactory = nsnull; @@ -186,6 +187,44 @@ static const char kDOMSecurityWarningsBundleURL[] = "chrome://communicator/local static const char kCryptoContractID[] = NS_CRYPTO_CONTRACTID; static const char kPkcs11ContractID[] = NS_PKCS11_CONTRACTID; +// CheckForAbusePoint return values: +enum { + openAllow = 0, // open that window without worries + openControlled, // it's a popup, but allow it + openAbused, // it's a popup. disallow it, but allow domain override. + openAbuseOverride // disallow window open +}; + +// return true if eventName is contained within events, delimited by spaces +static PRBool +ContainsEventName(const char *eventName, const nsAFlatCString& events) +{ + nsAFlatCString::const_iterator start, end; + events.BeginReading(start); + events.EndReading(end); + + nsAFlatCString::const_iterator startiter(start); + + while (startiter != end) { + nsAFlatCString::const_iterator enditer(end); + + if (!FindInReadable(nsDependentCString(eventName), startiter, enditer)) + return PR_FALSE; + + // the match is surrounded by spaces, or at a string boundary + if ((startiter == start || *--startiter == ' ') && + (enditer == end || *enditer == ' ')) + return PR_TRUE; + + /* Move on and see if there are other matches. (The delimitation + requirement makes it pointless to begin the next search before + the end of the invalid match just found.) */ + startiter = enditer; + } + + return PR_FALSE; +} + //***************************************************************************** //*** GlobalWindowImpl: Object Management //***************************************************************************** @@ -203,9 +242,11 @@ GlobalWindowImpl::GlobalWindowImpl() mFullScreen(PR_FALSE), mIsClosed(PR_FALSE), mOpenerWasCleared(PR_FALSE), + mIsPopupSpam(PR_FALSE), mLastMouseButtonAction(LL_ZERO), mGlobalObjectOwner(nsnull), mDocShell(nsnull), + mCurrentEvent(0), mMutationBits(0), mChromeEventHandler(nsnull), mFrameElement(nsnull) @@ -287,6 +328,13 @@ GlobalWindowImpl::CleanUp() mOpener = nsnull; // Forces Release mContext = nsnull; // Forces Release mChromeEventHandler = nsnull; // Forces Release + + PRBool popup; + IsPopupSpamWindow(&popup); + if (popup) { + SetPopupSpamWindow(PR_FALSE); + --gOpenPopupSpamCount; + } } void @@ -755,6 +803,9 @@ GlobalWindowImpl::HandleDOMEvent(nsIPresContext* aPresContext, nsIDOMEvent *domEvent = nsnull; static PRUint32 count = 0; + nsEvent *oldEvent = mCurrentEvent; + mCurrentEvent = aEvent; + /* mChromeEventHandler and mContext go dangling in the middle of this function under some circumstances (events that destroy the window) without this addref. */ @@ -926,6 +977,7 @@ GlobalWindowImpl::HandleDOMEvent(nsIPresContext* aPresContext, } } + mCurrentEvent = oldEvent; return ret; } @@ -2974,13 +3026,12 @@ GlobalWindowImpl::CanSetProperty(const char *aPrefName) /* * Examine the current document state to see if we're in a way that is - * typically abused by web designers. This routine returns PR_TRUE if - * we're running a top level script, running an onload or onunload - * handler, or running a timeout. The window.open code uses this - * routine to determine wether or not to allow the new window. + * typically abused by web designers. The window.open code uses this + * routine to determine whether to allow the new window. + * Returns a value from the CheckAbusePoint enum. */ -PRBool -GlobalWindowImpl::CheckForAbusePoint () +PRUint32 +GlobalWindowImpl::CheckForAbusePoint() { nsCOMPtr item(do_QueryInterface(mDocShell)); @@ -2988,44 +3039,161 @@ GlobalWindowImpl::CheckForAbusePoint () PRInt32 type = nsIDocShellTreeItem::typeChrome; item->GetItemType(&type); - if (type != nsIDocShellTreeItem::typeContent) - return PR_FALSE; + return openAllow; } - if (!gPrefBranch) { - return PR_FALSE; - } + if (!gPrefBranch) + return openAllow; - if (!mIsDocumentLoaded || mRunningTimeout) { - return PR_TRUE; - } + PRInt32 intPref = 0; - PRInt32 clickDelay = 0; - gPrefBranch->GetIntPref("dom.disable_open_click_delay", &clickDelay); - if (clickDelay) { - PRTime now, ll_delta; - PRInt32 delta; - now = PR_Now(); + // disallow windows after a user-defined click delay + gPrefBranch->GetIntPref("dom.disable_open_click_delay", &intPref); + if (intPref != 0) { + PRTime now = PR_Now(); + PRTime ll_delta; + PRUint32 delta; LL_SUB(ll_delta, now, mLastMouseButtonAction); - LL_L2I(delta, ll_delta); - delta /= 1000; - if (delta > clickDelay) { - return PR_TRUE; + LL_L2UI(delta, ll_delta); + if (delta/1000 > (PRUint32) intPref) + return openAbuseOverride; + } + + // limit the number of simultaneously open popups + intPref = 0; + gPrefBranch->GetIntPref("dom.popup_maximum", &intPref); + if (intPref > 0 && gOpenPopupSpamCount >= intPref) + return openAbuseOverride; + + // is a timer running? + if (mRunningTimeout) + return openAbused; + + // is the document being loaded or unloaded? + if (!mIsDocumentLoaded) + return openAbused; + + // we'll need to know what DOM event is being processed now, if any + nsEvent *currentEvent = mCurrentEvent; + if (!currentEvent && mDocShell) { + /* The DOM window's current event is accurate for events that make it + all the way to the window. But it doesn't see events handled directly + by a target element. For those, check the EventStateManager. */ + nsCOMPtr presShell; + mDocShell->GetPresShell(getter_AddRefs(presShell)); + if (presShell) { + nsCOMPtr presContext; + presShell->GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + nsCOMPtr esManager; + presContext->GetEventStateManager(getter_AddRefs(esManager)); + if (esManager) + esManager->GetCurrentEvent(¤tEvent); + } } } - return PR_FALSE; + // fetch pref string detailing which events are allowed + nsXPIDLCString eventPref; + gPrefBranch->GetCharPref("dom.popup_allowed_events", + getter_Copies(eventPref)); + nsCAutoString eventPrefStr(eventPref); + + // generally if an event handler is running, new windows are disallowed. + // check for exceptions: + if (currentEvent) { + PRBool prefMatch = PR_FALSE; + switch(currentEvent->eventStructType) { + case NS_EVENT : + switch(currentEvent->message) { + case NS_FORM_SELECTED : + prefMatch = ::ContainsEventName("select", eventPref); + break; + case NS_RESIZE_EVENT : + prefMatch = ::ContainsEventName("resize", eventPref); + break; + } + break; + case NS_GUI_EVENT : + switch(currentEvent->message) { + case NS_FORM_INPUT : + prefMatch = ::ContainsEventName("input", eventPref); + break; + } + break; + case NS_INPUT_EVENT : + switch(currentEvent->message) { + case NS_FORM_CHANGE : + prefMatch = ::ContainsEventName("change", eventPref); + break; + } + break; + case NS_KEY_EVENT : + switch(currentEvent->message) { + case NS_KEY_PRESS : + prefMatch = ::ContainsEventName("keypress", eventPref); + break; + case NS_KEY_UP : + prefMatch = ::ContainsEventName("keyup", eventPref); + break; + case NS_KEY_DOWN : + prefMatch = ::ContainsEventName("keydown", eventPref); + break; + } + break; + case NS_MOUSE_EVENT : + switch(currentEvent->message) { + case NS_MOUSE_LEFT_BUTTON_UP : + prefMatch = ::ContainsEventName("mouseup", eventPref); + break; + case NS_MOUSE_LEFT_BUTTON_DOWN : + prefMatch = ::ContainsEventName("mousedown", eventPref); + break; + case NS_MOUSE_LEFT_CLICK : + prefMatch = ::ContainsEventName("click", eventPref); + break; + case NS_MOUSE_LEFT_DOUBLECLICK : + prefMatch = ::ContainsEventName("dblclick", eventPref); + break; + } + break; + case NS_SCRIPT_ERROR_EVENT : + switch(currentEvent->message) { + case NS_SCRIPT_ERROR : + prefMatch = ::ContainsEventName("error", eventPref); + break; + } + break; + case NS_FORM_EVENT : + switch(currentEvent->message) { + case NS_FORM_SUBMIT : + prefMatch = ::ContainsEventName("submit", eventPref); + break; + case NS_FORM_RESET : + prefMatch = ::ContainsEventName("reset", eventPref); + break; + } + break; + } + return prefMatch ? openControlled : openAbused; + } + + // damn. nothing? then allow the new window. + return openAllow; } /* Allow or deny a window open based on whether popups are suppressed. - This method assumes we're in a popup situation; otherwise why call it? + A popup generally will be allowed if it's from a white-listed domain, + or if its target is an extant window. Returns PR_TRUE if the window should be opened. */ -PRBool GlobalWindowImpl::CheckOpenAllow(const nsAString &aName) +PRBool GlobalWindowImpl::CheckOpenAllow(PRUint32 aAbuseLevel, + const nsAString &aName) { PRBool allowWindow = PR_TRUE; - if (IsPopupBlocked(mDocument)) { + if (aAbuseLevel == openAbuseOverride || + aAbuseLevel == openAbused && IsPopupBlocked(mDocument)) { allowWindow = PR_FALSE; // However it might still not be blocked. // Special case items that don't actually open new windows. @@ -3124,18 +3292,27 @@ GlobalWindowImpl::Open(const nsAString& aUrl, const nsAString& aOptions, nsIDOMWindow **_retval) { - PRBool abusedWindow = CheckForAbusePoint(); nsresult rv; - if (abusedWindow && !CheckOpenAllow(aName)) { + PRUint32 abuseLevel = CheckForAbusePoint(); + if (!CheckOpenAllow(abuseLevel, aName)) { FireAbuseEvents(PR_TRUE, PR_FALSE, aUrl); return NS_ERROR_FAILURE; // unlike the public Open method, return an error } rv = OpenInternal(aUrl, aName, aOptions, PR_FALSE, nsnull, 0, nsnull, _retval); - if (NS_SUCCEEDED(rv) && abusedWindow) - FireAbuseEvents(PR_FALSE, PR_TRUE, aUrl); + if (NS_SUCCEEDED(rv)) { + if (abuseLevel >= openControlled) { + nsCOMPtr opened(do_QueryInterface(*_retval)); + if (opened) { + opened->SetPopupSpamWindow(PR_TRUE); + ++gOpenPopupSpamCount; + } + } + if (abuseLevel >= openAbused) + FireAbuseEvents(PR_FALSE, PR_TRUE, aUrl); + } return rv; } @@ -3180,8 +3357,8 @@ GlobalWindowImpl::Open(nsIDOMWindow **_retval) } } - PRBool abusedWindow = CheckForAbusePoint(); - if (abusedWindow && !CheckOpenAllow(name)) { + PRUint32 abuseLevel = CheckForAbusePoint(); + if (!CheckOpenAllow(abuseLevel, name)) { FireAbuseEvents(PR_TRUE, PR_FALSE, url); return NS_OK; // don't open the window, but also don't throw a JS exception } @@ -3213,7 +3390,14 @@ GlobalWindowImpl::Open(nsIDOMWindow **_retval) (*_retval)->GetDocument(getter_AddRefs(doc)); } - if (abusedWindow) + if (abuseLevel >= openControlled) { + nsCOMPtr opened(do_QueryInterface(*_retval)); + if (opened) { + opened->SetPopupSpamWindow(PR_TRUE); + ++gOpenPopupSpamCount; + } + } + if (abuseLevel >= openAbused) FireAbuseEvents(PR_FALSE, PR_TRUE, url); } @@ -3548,6 +3732,20 @@ GlobalWindowImpl::IsLoadingOrRunningTimeout(PRBool* aResult) return NS_OK; } +NS_IMETHODIMP +GlobalWindowImpl::IsPopupSpamWindow(PRBool *aResult) +{ + *aResult = mIsPopupSpam; + return NS_OK; +} + +NS_IMETHODIMP +GlobalWindowImpl::SetPopupSpamWindow(PRBool aPopup) +{ + mIsPopupSpam = aPopup; + return NS_OK; +} + NS_IMETHODIMP GlobalWindowImpl::UpdateCommands(const nsAString& anAction) { diff --git a/dom/src/base/nsGlobalWindow.h b/dom/src/base/nsGlobalWindow.h index eaf9577ef21..f9939f022a8 100644 --- a/dom/src/base/nsGlobalWindow.h +++ b/dom/src/base/nsGlobalWindow.h @@ -190,6 +190,8 @@ public: NS_IMETHOD ReallyCloseWindow(); NS_IMETHOD IsLoadingOrRunningTimeout(PRBool* aResult); + NS_IMETHOD IsPopupSpamWindow(PRBool *aResult); + NS_IMETHOD SetPopupSpamWindow(PRBool aPopup); NS_IMETHOD GetFrameElementInternal(nsIDOMElement** aFrameElement); NS_IMETHOD SetFrameElementInternal(nsIDOMElement* aFrameElement); @@ -243,8 +245,8 @@ protected: nsresult GetScrollInfo(nsIScrollableView** aScrollableView, float* aP2T, float* aT2P); nsresult SecurityCheckURL(const char *aURL); - PRBool CheckForAbusePoint(); - PRBool CheckOpenAllow(const nsAString &aName); + PRUint32 CheckForAbusePoint(); + PRBool CheckOpenAllow(PRUint32 aAbuseLevel, const nsAString &aName); void FireAbuseEvents(PRBool aBlocked, PRBool aWindow, const nsAString &aPopupURL); @@ -305,12 +307,14 @@ protected: PRPackedBool mFullScreen; PRPackedBool mIsClosed; PRPackedBool mOpenerWasCleared; + PRPackedBool mIsPopupSpam; PRTime mLastMouseButtonAction; nsString mStatus; nsString mDefaultStatus; nsIScriptGlobalObjectOwner* mGlobalObjectOwner; // Weak Reference nsIDocShell* mDocShell; // Weak Reference + nsEvent* mCurrentEvent; PRUint32 mMutationBits; nsCOMPtr mChromeEventHandler; // [Strong] We break it when we get torn down. nsCOMPtr mCrypto;