fixes bug 326155 "Adds support for restricting pings to same origin and limiting pings to no more than one per anchor" r=biesi sr=bzbarsky

This commit is contained in:
darin%meer.net 2006-06-02 04:28:48 +00:00
parent 86f924a271
commit 979f82c981
3 changed files with 184 additions and 27 deletions

View File

@ -66,6 +66,60 @@ httpCacheSession.doomEntriesIfExpired = false;
var ftpCacheSession = cacheService.createSession("FTP", 0, true);
ftpCacheSession.doomEntriesIfExpired = false;
const PREF_PINGS_ENABLED = "browser.send_pings";
const PREF_PINGS_MAX_PER_LINK = "browser.send_pings.max_per_link";
const PREF_PINGS_REQUIRE_SAME_HOST = "browser.send_pings.require_same_host";
/**
* This function generates an array of pings that will be sent if the given
* anchor element is clicked. It basically duplicates the pref checking logic
* found in nsWebShell.cpp. It might be nice to expose that functionality on
* some interface that both of these sections of code could share.
*
* @param elem
* An anchor or area element
* @return
* An array of URL strings corresponding to the pings that would occur if
* the element's href were loaded.
*/
function getPings(elem)
{
var result = [];
const prefs =
Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var enabled = prefs.getBoolPref(PREF_PINGS_ENABLED);
if (!enabled)
return result;
var maxPings = prefs.getIntPref(PREF_PINGS_MAX_PER_LINK);
if (maxPings == 0)
return result;
var requireSameHost = prefs.getBoolPref(PREF_PINGS_REQUIRE_SAME_HOST);
const ios =
Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var doc = elem.ownerDocument;
var docURI = ios.newURI(doc.documentURI, doc.characterSet, null);
// The URL strings returned by elem.ping are absolute URLs.
var pings = elem.ping.split(" ");
for (var i = 0; i < pings.length; ++i) {
if (requireSameHost) {
var uri = ios.newURI(pings[i], doc.characterSet, null);
if (uri.asciiHost == docURI.asciiHost)
result.push(pings[i]);
} else {
result.push(pings[i]);
}
if (result.length == maxPings)
break;
}
return result;
}
function onLoad()
{
@ -234,8 +288,7 @@ function checkForLink(elem, htmllocalname)
setInfo("link-type", elem.getAttribute("type"));
setInfo("link-rel", elem.getAttribute("rel"));
setInfo("link-rev", elem.getAttribute("rev"));
var ping = elem.ping.replace(/ /g, '\n');
setInfo("link-ping", ping);
setInfo("link-ping", getPings(elem).join('\n'));
var target = elem.target;

View File

@ -146,32 +146,46 @@ static PRLogModuleInfo* gLogModule = PR_NewLogModule("webshell");
#define WEB_TRACE(_bit,_args)
#endif
//----------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define PREF_PINGS_ENABLED "browser.send_pings"
#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link"
#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
// Check prefs to see if pings are enabled and if so what restrictions might
// be applied.
//
// @param maxPerLink
// This parameter returns the number of pings that are allowed per link click
//
// @param requireSameHost
// This parameter returns PR_TRUE if pings are restricted to the same host as
// the document in which the click occurs. If the same host restriction is
// imposed, then we still allow for pings to cross over to different
// protocols and ports for flexibility and because it is not possible to send
// a ping via FTP.
//
// @returns
// PR_TRUE if pings are enabled and PR_FALSE otherwise.
//
static PRBool
IsOffline()
{
PRBool offline = PR_TRUE;
nsCOMPtr<nsIIOService> ios(do_GetIOService());
if (ios)
ios->GetOffline(&offline);
return offline;
}
//----------------------------------------------------------------------
// Check prefs to see if pings are enabled
static PRBool
PingsEnabled()
PingsEnabled(PRInt32 *maxPerLink, PRBool *requireSameHost)
{
PRBool allow = PR_FALSE;
*maxPerLink = 1;
*requireSameHost = PR_TRUE;
nsCOMPtr<nsIPrefBranch> prefs =
do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs) {
PRBool val;
if (NS_SUCCEEDED(prefs->GetBoolPref("browser.send_pings", &val)))
if (NS_SUCCEEDED(prefs->GetBoolPref(PREF_PINGS_ENABLED, &val)))
allow = val;
if (allow) {
prefs->GetIntPref(PREF_PINGS_MAX_PER_LINK, maxPerLink);
prefs->GetBoolPref(PREF_PINGS_REQUIRE_SAME_HOST, requireSameHost);
}
}
return allow;
@ -233,8 +247,9 @@ ForEachPing(nsIContent *content, ForEachPingCallback callback, void *closure)
// Ignore non-HTTP(S) pings:
PRBool match;
if ((NS_SUCCEEDED(uri->SchemeIs("http", &match)) && match) ||
(NS_SUCCEEDED(uri->SchemeIs("https", &match)) && match))
(NS_SUCCEEDED(uri->SchemeIs("https", &match)) && match)) {
callback(closure, content, uri, ios);
}
}
}
start = iter = iter + 1;
@ -257,15 +272,37 @@ OnPingTimeout(nsITimer *timer, void *closure)
loadGroup->Release();
}
// Check to see if two URIs have the same host or not
static PRBool
IsSameHost(nsIURI *uri1, nsIURI *uri2)
{
nsCAutoString host1, host2;
uri1->GetAsciiHost(host1);
uri2->GetAsciiHost(host2);
return host1.Equals(host2);
}
class nsPingListener : public nsIStreamListener
, public nsIInterfaceRequestor
, public nsIChannelEventSink
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
nsPingListener(PRBool requireSameHost)
: mRequireSameHost(requireSameHost)
{}
private:
PRBool mRequireSameHost;
};
NS_IMPL_ISUPPORTS2(nsPingListener, nsIStreamListener, nsIRequestObserver)
NS_IMPL_ISUPPORTS4(nsPingListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
NS_IMETHODIMP
nsPingListener::OnStartRequest(nsIRequest *request, nsISupports *context)
@ -289,10 +326,56 @@ nsPingListener::OnStopRequest(nsIRequest *request, nsISupports *context,
return NS_OK;
}
NS_IMETHODIMP
nsPingListener::GetInterface(const nsIID &iid, void **result)
{
if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
NS_ADDREF_THIS();
*result = (nsIChannelEventSink *) this;
return NS_OK;
}
return NS_ERROR_NO_INTERFACE;
}
NS_IMETHODIMP
nsPingListener::OnChannelRedirect(nsIChannel *oldChan, nsIChannel *newChan,
PRUint32 flags)
{
if (!mRequireSameHost)
return NS_OK;
nsCOMPtr<nsIURI> oldURI, newURI;
oldChan->GetURI(getter_AddRefs(oldURI));
newChan->GetURI(getter_AddRefs(newURI));
NS_ENSURE_STATE(oldURI && newURI);
if (!IsSameHost(oldURI, newURI))
return NS_ERROR_ABORT;
return NS_OK;
}
struct SendPingInfo {
PRInt32 numPings;
PRInt32 maxPings;
PRBool requireSameHost;
nsIURI *referrer;
};
static void
SendPing(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios)
{
nsIURI *referrer = NS_STATIC_CAST(nsIURI *, closure);
SendPingInfo *info = NS_STATIC_CAST(SendPingInfo *, closure);
if (info->numPings >= info->maxPings)
return;
if (info->requireSameHost) {
// Make sure the referrer and the given uri share the same origin. We
// only require the same hostname. The scheme and port may differ.
if (!IsSameHost(uri, info->referrer))
return;
}
nsIDocument *doc = content->GetOwnerDoc();
if (!doc)
@ -315,8 +398,8 @@ SendPing(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios)
if (httpInternal)
httpInternal->SetDocumentURI(doc->GetDocumentURI());
if (referrer)
httpChan->SetReferrer(referrer);
if (info->referrer)
httpChan->SetReferrer(info->referrer);
httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
@ -357,12 +440,23 @@ SendPing(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios)
// Construct a listener that merely discards any response. If successful at
// opening the channel, then it is not necessary to hold a reference to the
// channel. The networking subsystem will take care of that for us.
nsCOMPtr<nsIStreamListener> listener = new nsPingListener();
nsCOMPtr<nsIStreamListener> listener =
new nsPingListener(info->requireSameHost);
if (!listener)
return;
// Observe redirects as well:
nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(listener);
NS_ASSERTION(callbacks, "oops");
loadGroup->SetNotificationCallbacks(callbacks);
chan->AsyncOpen(listener, nsnull);
// Even if AsyncOpen failed, we still count this as a successful ping. It's
// possible that AsyncOpen may have failed after triggering some background
// process that may have written something to the network.
info->numPings++;
// Prevent ping requests from stalling and never being garbage collected...
nsCOMPtr<nsITimer> timer =
do_CreateInstance(NS_TIMER_CONTRACTID);
@ -388,9 +482,17 @@ SendPing(void *closure, nsIContent *content, nsIURI *uri, nsIIOService *ios)
static void
DispatchPings(nsIContent *content, nsIURI *referrer)
{
if (!PingsEnabled())
SendPingInfo info;
if (!PingsEnabled(&info.maxPings, &info.requireSameHost))
return;
ForEachPing(content, SendPing, referrer);
if (info.maxPings == 0)
return;
info.numPings = 0;
info.referrer = referrer;
ForEachPing(content, SendPing, &info);
}
//----------------------------------------------------------------------
@ -1064,7 +1166,7 @@ nsresult nsWebShell::EndPageLoad(nsIWebProgress *aProgress,
nsCAutoString method;
if (httpChannel)
httpChannel->GetRequestMethod(method);
if (method.Equals("POST") && !IsOffline()) {
if (method.Equals("POST") && !NS_IsOffline()) {
nsCOMPtr<nsIPrompt> prompter;
PRBool repost;
nsCOMPtr<nsIStringBundle> stringBundle;

View File

@ -86,6 +86,8 @@ pref("browser.enable_automatic_image_resizing", false);
// See http://whatwg.org/specs/web-apps/current-work/#ping
pref("browser.send_pings", false);
pref("browser.send_pings.max_per_link", 1); // limit the number of pings that are sent per link click
pref("browser.send_pings.require_same_host", false); // only send pings to the same host if this is true
pref("browser.display.use_focus_colors", false);
pref("browser.display.focus_background_color", "#117722");