Bug 644476 Part 2: Move CORS code from nsXMLHttpRequest.cpp/h to nsCrossSiteListenerProxy.cpp/h. r=smaug

This commit is contained in:
Jonas Sicking 2011-03-28 13:11:09 -07:00
Родитель f94b94a715
Коммит 6061159b90
5 изменённых файлов: 621 добавлений и 599 удалений

Просмотреть файл

@ -54,34 +54,311 @@
#include "nsIChannelEventSink.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsXMLHttpRequest.h"
#include "nsAsyncRedirectVerifyHelper.h"
#include "prclist.h"
#include "prtime.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#define ACCESS_CONTROL_CACHE_SIZE 100
static PRBool gDisableCORS = PR_FALSE;
static PRBool gDisableCORSPrivateData = PR_FALSE;
class nsChannelCanceller
//////////////////////////////////////////////////////////////////////////
// Preflight cache
class nsAccessControlLRUCache
{
public:
nsChannelCanceller(nsIChannel* aChannel)
: mChannel(aChannel)
struct TokenTime
{
nsCString token;
PRTime expirationTime;
};
struct CacheEntry : public PRCList
{
CacheEntry(nsCString& aKey)
: mKey(aKey)
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache::CacheEntry);
}
~CacheEntry()
{
MOZ_COUNT_DTOR(nsAccessControlLRUCache::CacheEntry);
}
void PurgeExpired(PRTime now);
PRBool CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aCustomHeaders);
nsCString mKey;
nsTArray<TokenTime> mMethods;
nsTArray<TokenTime> mHeaders;
};
nsAccessControlLRUCache()
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache);
PR_INIT_CLIST(&mList);
}
~nsChannelCanceller()
~nsAccessControlLRUCache()
{
if (mChannel) {
mChannel->Cancel(NS_ERROR_DOM_BAD_URI);
Clear();
MOZ_COUNT_DTOR(nsAccessControlLRUCache);
}
PRBool Initialize()
{
return mTable.Init();
}
CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, PRBool aCreate);
void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
void Clear();
private:
static PLDHashOperator
RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
void* aUserData);
static PRBool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, nsACString& _retval);
nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
PRCList mList;
};
// Will be initialized in EnsureACCache.
static nsAccessControlLRUCache* sAccessControlCache = nsnull;
static PRBool EnsureACCache()
{
if (sAccessControlCache)
return PR_TRUE;
nsAutoPtr<nsAccessControlLRUCache> newCache(new nsAccessControlLRUCache());
if (newCache->Initialize()) {
sAccessControlCache = newCache.forget();
return PR_TRUE;
}
return PR_FALSE;
}
void
nsAccessControlLRUCache::CacheEntry::PurgeExpired(PRTime now)
{
PRUint32 i;
for (i = 0; i < mMethods.Length(); ++i) {
if (now >= mMethods[i].expirationTime) {
mMethods.RemoveElementAt(i--);
}
}
for (i = 0; i < mHeaders.Length(); ++i) {
if (now >= mHeaders[i].expirationTime) {
mHeaders.RemoveElementAt(i--);
}
}
}
PRBool
nsAccessControlLRUCache::CacheEntry::CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aHeaders)
{
PurgeExpired(PR_Now());
if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
PRUint32 i;
for (i = 0; i < mMethods.Length(); ++i) {
if (aMethod.Equals(mMethods[i].token))
break;
}
if (i == mMethods.Length()) {
return PR_FALSE;
}
}
void DontCancel()
{
mChannel = nsnull;
for (PRUint32 i = 0; i < aHeaders.Length(); ++i) {
PRUint32 j;
for (j = 0; j < mHeaders.Length(); ++j) {
if (aHeaders[i].Equals(mHeaders[j].token,
nsCaseInsensitiveCStringComparator())) {
break;
}
}
if (j == mHeaders.Length()) {
return PR_FALSE;
}
}
private:
nsIChannel* mChannel;
};
return PR_TRUE;
}
nsAccessControlLRUCache::CacheEntry*
nsAccessControlLRUCache::GetEntry(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
PRBool aCreate)
{
nsCString key;
if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
NS_WARNING("Invalid cache key!");
return nsnull;
}
CacheEntry* entry;
if (mTable.Get(key, &entry)) {
// Entry already existed so just return it. Also update the LRU list.
// Move to the head of the list.
PR_REMOVE_LINK(entry);
PR_INSERT_LINK(entry, &mList);
return entry;
}
if (!aCreate) {
return nsnull;
}
// This is a new entry, allocate and insert into the table now so that any
// failures don't cause items to be removed from a full cache.
entry = new CacheEntry(key);
if (!entry) {
NS_WARNING("Failed to allocate new cache entry!");
return nsnull;
}
if (!mTable.Put(key, entry)) {
// Failed, clean up the new entry.
delete entry;
NS_WARNING("Failed to add entry to the access control cache!");
return nsnull;
}
PR_INSERT_LINK(entry, &mList);
NS_ASSERTION(mTable.Count() <= ACCESS_CONTROL_CACHE_SIZE + 1,
"Something is borked, too many entries in the cache!");
// Now enforce the max count.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
// Try to kick out all the expired entries.
PRTime now = PR_Now();
mTable.Enumerate(RemoveExpiredEntries, &now);
// If that didn't remove anything then kick out the least recently used
// entry.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
CacheEntry* lruEntry = static_cast<CacheEntry*>(PR_LIST_TAIL(&mList));
PR_REMOVE_LINK(lruEntry);
// This will delete 'lruEntry'.
mTable.Remove(lruEntry->mKey);
NS_ASSERTION(mTable.Count() == ACCESS_CONTROL_CACHE_SIZE,
"Somehow tried to remove an entry that was never added!");
}
}
return entry;
}
void
nsAccessControlLRUCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
{
CacheEntry* entry;
nsCString key;
if (GetCacheKey(aURI, aPrincipal, PR_TRUE, key) &&
mTable.Get(key, &entry)) {
PR_REMOVE_LINK(entry);
mTable.Remove(key);
}
if (GetCacheKey(aURI, aPrincipal, PR_FALSE, key) &&
mTable.Get(key, &entry)) {
PR_REMOVE_LINK(entry);
mTable.Remove(key);
}
}
void
nsAccessControlLRUCache::Clear()
{
PR_INIT_CLIST(&mList);
mTable.Clear();
}
/* static */ PLDHashOperator
nsAccessControlLRUCache::RemoveExpiredEntries(const nsACString& aKey,
nsAutoPtr<CacheEntry>& aValue,
void* aUserData)
{
PRTime* now = static_cast<PRTime*>(aUserData);
aValue->PurgeExpired(*now);
if (aValue->mHeaders.IsEmpty() &&
aValue->mHeaders.IsEmpty()) {
// Expired, remove from the list as well as the hash table.
PR_REMOVE_LINK(aValue);
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
/* static */ PRBool
nsAccessControlLRUCache::GetCacheKey(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
nsACString& _retval)
{
NS_ASSERTION(aURI, "Null uri!");
NS_ASSERTION(aPrincipal, "Null principal!");
NS_NAMED_LITERAL_CSTRING(space, " ");
nsCOMPtr<nsIURI> uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
nsCAutoString scheme, host, port;
if (uri) {
uri->GetScheme(scheme);
uri->GetHost(host);
port.AppendInt(NS_GetRealPort(uri));
}
nsCAutoString cred;
if (aWithCredentials) {
_retval.AssignLiteral("cred");
}
else {
_retval.AssignLiteral("nocred");
}
nsCAutoString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
_retval.Assign(cred + space + scheme + space + host + space + port + space +
spec);
return PR_TRUE;
}
//////////////////////////////////////////////////////////////////////////
// nsCrossSiteListenerProxy
NS_IMPL_ISUPPORTS5(nsCrossSiteListenerProxy, nsIStreamListener,
nsIRequestObserver, nsIChannelEventSink,
@ -95,6 +372,14 @@ nsCrossSiteListenerProxy::Startup()
nsContentUtils::AddBoolPrefVarCache("content.cors.no_private_data", &gDisableCORSPrivateData);
}
/* static */
void
nsCrossSiteListenerProxy::Shutdown()
{
delete sAccessControlCache;
sAccessControlCache = nsnull;
}
nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
@ -118,7 +403,6 @@ nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
}
}
nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
@ -157,14 +441,13 @@ nsCrossSiteListenerProxy::OnStartRequest(nsIRequest* aRequest,
{
mRequestApproved = NS_SUCCEEDED(CheckRequestApproved(aRequest));
if (!mRequestApproved) {
if (nsXMLHttpRequest::sAccessControlCache) {
if (sAccessControlCache) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if (channel) {
nsCOMPtr<nsIURI> uri;
nsCOMPtr<nsIURI> uri;
NS_GetFinalChannelURI(channel, getter_AddRefs(uri));
if (uri) {
nsXMLHttpRequest::sAccessControlCache->
RemoveEntries(uri, mRequestingPrincipal);
sAccessControlCache->RemoveEntries(uri, mRequestingPrincipal);
}
}
}
@ -372,12 +655,11 @@ nsCrossSiteListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
rv = CheckRequestApproved(aOldChannel);
if (NS_FAILED(rv)) {
if (nsXMLHttpRequest::sAccessControlCache) {
if (sAccessControlCache) {
nsCOMPtr<nsIURI> oldURI;
NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI));
if (oldURI) {
nsXMLHttpRequest::sAccessControlCache->
RemoveEntries(oldURI, mRequestingPrincipal);
sAccessControlCache->RemoveEntries(oldURI, mRequestingPrincipal);
}
}
aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
@ -517,3 +799,301 @@ nsCrossSiteListenerProxy::UpdateChannel(nsIChannel* aChannel)
return NS_OK;
}
//////////////////////////////////////////////////////////////////////////
// Preflight proxy
// Class used as streamlistener and notification callback when
// doing the initial OPTIONS request for an access-control check
class nsACProxyListener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
nsACProxyListener(nsIChannel* aOuterChannel,
nsIStreamListener* aOuterListener,
nsISupports* aOuterContext,
nsIPrincipal* aReferrerPrincipal,
const nsACString& aRequestMethod,
PRBool aWithCredentials)
: mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials)
{ }
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
private:
void AddResultToCache(nsIRequest* aRequest);
nsCOMPtr<nsIChannel> mOuterChannel;
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
nsCString mRequestMethod;
PRBool mWithCredentials;
};
NS_IMPL_ISUPPORTS4(nsACProxyListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
void
nsACProxyListener::AddResultToCache(nsIRequest *aRequest)
{
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
NS_ASSERTION(http, "Request was not http");
// The "Access-Control-Max-Age" header should return an age in seconds.
nsCAutoString headerVal;
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
headerVal);
if (headerVal.IsEmpty()) {
return;
}
// Sanitize the string. We only allow 'delta-seconds' as specified by
// http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
// trailing non-whitespace characters).
PRUint32 age = 0;
nsCSubstring::const_char_iterator iter, end;
headerVal.BeginReading(iter);
headerVal.EndReading(end);
while (iter != end) {
if (*iter < '0' || *iter > '9') {
return;
}
age = age * 10 + (*iter - '0');
// Cap at 24 hours. This also avoids overflow
age = NS_MIN(age, 86400U);
++iter;
}
if (!age || !EnsureACCache()) {
return;
}
// String seems fine, go ahead and cache.
// Note that we have already checked that these headers follow the correct
// syntax.
nsCOMPtr<nsIURI> uri;
NS_GetFinalChannelURI(http, getter_AddRefs(uri));
// PR_Now gives microseconds
PRTime expirationTime = PR_Now() + (PRUint64)age * PR_USEC_PER_SEC;
nsAccessControlLRUCache::CacheEntry* entry =
sAccessControlCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials,
PR_TRUE);
if (!entry) {
return;
}
// The "Access-Control-Allow-Methods" header contains a comma separated
// list of method names.
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
headerVal);
nsCCharSeparatedTokenizer methods(headerVal, ',');
while(methods.hasMoreTokens()) {
const nsDependentCSubstring& method = methods.nextToken();
if (method.IsEmpty()) {
continue;
}
PRUint32 i;
for (i = 0; i < entry->mMethods.Length(); ++i) {
if (entry->mMethods[i].token.Equals(method)) {
entry->mMethods[i].expirationTime = expirationTime;
break;
}
}
if (i == entry->mMethods.Length()) {
nsAccessControlLRUCache::TokenTime* newMethod =
entry->mMethods.AppendElement();
if (!newMethod) {
return;
}
newMethod->token = method;
newMethod->expirationTime = expirationTime;
}
}
// The "Access-Control-Allow-Headers" header contains a comma separated
// list of method names.
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
headerVal);
nsCCharSeparatedTokenizer headers(headerVal, ',');
while(headers.hasMoreTokens()) {
const nsDependentCSubstring& header = headers.nextToken();
if (header.IsEmpty()) {
continue;
}
PRUint32 i;
for (i = 0; i < entry->mHeaders.Length(); ++i) {
if (entry->mHeaders[i].token.Equals(header)) {
entry->mHeaders[i].expirationTime = expirationTime;
break;
}
}
if (i == entry->mHeaders.Length()) {
nsAccessControlLRUCache::TokenTime* newHeader =
entry->mHeaders.AppendElement();
if (!newHeader) {
return;
}
newHeader->token = header;
newHeader->expirationTime = expirationTime;
}
}
}
NS_IMETHODIMP
nsACProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
if (NS_SUCCEEDED(rv)) {
rv = status;
}
if (NS_SUCCEEDED(rv)) {
// Everything worked, try to cache and then fire off the actual request.
AddResultToCache(aRequest);
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
}
if (NS_FAILED(rv)) {
mOuterChannel->Cancel(rv);
mOuterListener->OnStartRequest(mOuterChannel, mOuterContext);
mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv);
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
nsresult aStatus)
{
return NS_OK;
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
nsACProxyListener::OnDataAvailable(nsIRequest *aRequest,
nsISupports *ctxt,
nsIInputStream *inStr,
PRUint32 sourceOffset,
PRUint32 count)
{
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags,
nsIAsyncVerifyRedirectCallback *callback)
{
// Only internal redirects allowed for now.
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags))
return NS_ERROR_DOM_BAD_URI;
callback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
nsresult
NS_StartCORSPreflight(nsIChannel* aRequestChannel,
nsIStreamListener* aListener,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
nsTArray<nsCString>& aACUnsafeHeaders,
nsIChannel** aPreflightChannel)
{
*aPreflightChannel = nsnull;
nsCAutoString method;
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel));
NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED);
httpChannel->GetRequestMethod(method);
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsAccessControlLRUCache::CacheEntry* entry =
sAccessControlCache ?
sAccessControlCache->GetEntry(uri, aPrincipal, aWithCredentials, PR_FALSE) :
nsnull;
if (entry && entry->CheckRequest(method, aACUnsafeHeaders)) {
// We have a cached preflight result, just start the original channel
return aRequestChannel->AsyncOpen(aListener, nsnull);
}
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
nsCOMPtr<nsILoadGroup> loadGroup;
rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
NS_ENSURE_SUCCESS(rv, rv);
nsLoadFlags loadFlags;
rv = aRequestChannel->GetLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIChannel> preflightChannel;
rv = NS_NewChannel(getter_AddRefs(preflightChannel), uri, nsnull,
loadGroup, nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(preflightChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");
rv = acHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
NS_ENSURE_SUCCESS(rv, rv);
// Set up listener which will start the original channel
nsCOMPtr<nsIStreamListener> acProxyListener =
new nsACProxyListener(aRequestChannel, aListener, nsnull, aPrincipal,
method, aWithCredentials);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
acProxyListener =
new nsCrossSiteListenerProxy(acProxyListener, aPrincipal,
preflightChannel, aWithCredentials,
method, aACUnsafeHeaders, &rv);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
// Start preflight
rv = preflightChannel->AsyncOpen(acProxyListener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
// Return newly created preflight channel
preflightChannel.forget(aPreflightChannel);
return NS_OK;
}

Просмотреть файл

@ -55,6 +55,14 @@ class nsIPrincipal;
extern PRBool
IsValidHTTPToken(const nsCSubstring& aToken);
nsresult
NS_StartCORSPreflight(nsIChannel* aRequestChannel,
nsIStreamListener* aListener,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
nsTArray<nsCString>& aACUnsafeHeaders,
nsIChannel** aPreflightChannel);
class nsCrossSiteListenerProxy : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink,
@ -84,6 +92,8 @@ public:
// Must be called at startup.
static void Startup();
static void Shutdown();
private:
nsresult UpdateChannel(nsIChannel* aChannel);
nsresult CheckRequestApproved(nsIRequest* aRequest);

Просмотреть файл

@ -145,8 +145,6 @@
XML_HTTP_REQUEST_SENT | \
XML_HTTP_REQUEST_STOPPED)
#define ACCESS_CONTROL_CACHE_SIZE 100
#define NS_BADCERTHANDLER_CONTRACTID \
"@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
@ -285,225 +283,6 @@ nsMultipartProxyListener::OnDataAvailable(nsIRequest *aRequest,
count);
}
// Class used as streamlistener and notification callback when
// doing the initial GET request for an access-control check
class nsACProxyListener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
nsACProxyListener(nsIChannel* aOuterChannel,
nsIStreamListener* aOuterListener,
nsISupports* aOuterContext,
nsIPrincipal* aReferrerPrincipal,
const nsACString& aRequestMethod,
PRBool aWithCredentials)
: mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials)
{ }
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
private:
void AddResultToCache(nsIRequest* aRequest);
nsCOMPtr<nsIChannel> mOuterChannel;
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIPrincipal> mReferrerPrincipal;
nsCString mRequestMethod;
PRBool mWithCredentials;
};
NS_IMPL_ISUPPORTS4(nsACProxyListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
void
nsACProxyListener::AddResultToCache(nsIRequest *aRequest)
{
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest);
NS_ASSERTION(http, "Request was not http");
// The "Access-Control-Max-Age" header should return an age in seconds.
nsCAutoString headerVal;
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
headerVal);
if (headerVal.IsEmpty()) {
return;
}
// Sanitize the string. We only allow 'delta-seconds' as specified by
// http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or
// trailing non-whitespace characters).
PRUint32 age = 0;
nsCSubstring::const_char_iterator iter, end;
headerVal.BeginReading(iter);
headerVal.EndReading(end);
while (iter != end) {
if (*iter < '0' || *iter > '9') {
return;
}
age = age * 10 + (*iter - '0');
// Cap at 24 hours. This also avoids overflow
age = NS_MIN(age, 86400U);
++iter;
}
if (!age || !nsXMLHttpRequest::EnsureACCache()) {
return;
}
// String seems fine, go ahead and cache.
// Note that we have already checked that these headers follow the correct
// syntax.
nsCOMPtr<nsIURI> uri;
NS_GetFinalChannelURI(http, getter_AddRefs(uri));
// PR_Now gives microseconds
PRTime expirationTime = PR_Now() + (PRUint64)age * PR_USEC_PER_SEC;
nsAccessControlLRUCache::CacheEntry* entry =
nsXMLHttpRequest::sAccessControlCache->
GetEntry(uri, mReferrerPrincipal, mWithCredentials, PR_TRUE);
if (!entry) {
return;
}
// The "Access-Control-Allow-Methods" header contains a comma separated
// list of method names.
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"),
headerVal);
nsCCharSeparatedTokenizer methods(headerVal, ',');
while(methods.hasMoreTokens()) {
const nsDependentCSubstring& method = methods.nextToken();
if (method.IsEmpty()) {
continue;
}
PRUint32 i;
for (i = 0; i < entry->mMethods.Length(); ++i) {
if (entry->mMethods[i].token.Equals(method)) {
entry->mMethods[i].expirationTime = expirationTime;
break;
}
}
if (i == entry->mMethods.Length()) {
nsAccessControlLRUCache::TokenTime* newMethod =
entry->mMethods.AppendElement();
if (!newMethod) {
return;
}
newMethod->token = method;
newMethod->expirationTime = expirationTime;
}
}
// The "Access-Control-Allow-Headers" header contains a comma separated
// list of method names.
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"),
headerVal);
nsCCharSeparatedTokenizer headers(headerVal, ',');
while(headers.hasMoreTokens()) {
const nsDependentCSubstring& header = headers.nextToken();
if (header.IsEmpty()) {
continue;
}
PRUint32 i;
for (i = 0; i < entry->mHeaders.Length(); ++i) {
if (entry->mHeaders[i].token.Equals(header)) {
entry->mHeaders[i].expirationTime = expirationTime;
break;
}
}
if (i == entry->mHeaders.Length()) {
nsAccessControlLRUCache::TokenTime* newHeader =
entry->mHeaders.AppendElement();
if (!newHeader) {
return;
}
newHeader->token = header;
newHeader->expirationTime = expirationTime;
}
}
}
NS_IMETHODIMP
nsACProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
nsresult status;
nsresult rv = aRequest->GetStatus(&status);
if (NS_SUCCEEDED(rv)) {
rv = status;
}
if (NS_SUCCEEDED(rv)) {
// Everything worked, try to cache and then fire off the actual request.
AddResultToCache(aRequest);
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
}
if (NS_FAILED(rv)) {
mOuterChannel->Cancel(rv);
mOuterListener->OnStartRequest(mOuterChannel, mOuterContext);
mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv);
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext,
nsresult aStatus)
{
return NS_OK;
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
nsACProxyListener::OnDataAvailable(nsIRequest *aRequest,
nsISupports *ctxt,
nsIInputStream *inStr,
PRUint32 sourceOffset,
PRUint32 count)
{
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags,
nsIAsyncVerifyRedirectCallback *callback)
{
// Only internal redirects allowed for now.
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags))
return NS_ERROR_DOM_BAD_URI;
callback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
NS_IMETHODIMP
nsACProxyListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
/////////////////////////////////////////////
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXHREventTarget)
@ -632,219 +411,11 @@ NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
void
nsAccessControlLRUCache::CacheEntry::PurgeExpired(PRTime now)
{
PRUint32 i;
for (i = 0; i < mMethods.Length(); ++i) {
if (now >= mMethods[i].expirationTime) {
mMethods.RemoveElementAt(i--);
}
}
for (i = 0; i < mHeaders.Length(); ++i) {
if (now >= mHeaders[i].expirationTime) {
mHeaders.RemoveElementAt(i--);
}
}
}
PRBool
nsAccessControlLRUCache::CacheEntry::CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aHeaders)
{
PurgeExpired(PR_Now());
if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
PRUint32 i;
for (i = 0; i < mMethods.Length(); ++i) {
if (aMethod.Equals(mMethods[i].token))
break;
}
if (i == mMethods.Length()) {
return PR_FALSE;
}
}
for (PRUint32 i = 0; i < aHeaders.Length(); ++i) {
PRUint32 j;
for (j = 0; j < mHeaders.Length(); ++j) {
if (aHeaders[i].Equals(mHeaders[j].token,
nsCaseInsensitiveCStringComparator())) {
break;
}
}
if (j == mHeaders.Length()) {
return PR_FALSE;
}
}
return PR_TRUE;
}
nsAccessControlLRUCache::CacheEntry*
nsAccessControlLRUCache::GetEntry(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
PRBool aCreate)
{
nsCString key;
if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) {
NS_WARNING("Invalid cache key!");
return nsnull;
}
CacheEntry* entry;
if (mTable.Get(key, &entry)) {
// Entry already existed so just return it. Also update the LRU list.
// Move to the head of the list.
PR_REMOVE_LINK(entry);
PR_INSERT_LINK(entry, &mList);
return entry;
}
if (!aCreate) {
return nsnull;
}
// This is a new entry, allocate and insert into the table now so that any
// failures don't cause items to be removed from a full cache.
entry = new CacheEntry(key);
if (!entry) {
NS_WARNING("Failed to allocate new cache entry!");
return nsnull;
}
if (!mTable.Put(key, entry)) {
// Failed, clean up the new entry.
delete entry;
NS_WARNING("Failed to add entry to the access control cache!");
return nsnull;
}
PR_INSERT_LINK(entry, &mList);
NS_ASSERTION(mTable.Count() <= ACCESS_CONTROL_CACHE_SIZE + 1,
"Something is borked, too many entries in the cache!");
// Now enforce the max count.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
// Try to kick out all the expired entries.
PRTime now = PR_Now();
mTable.Enumerate(RemoveExpiredEntries, &now);
// If that didn't remove anything then kick out the least recently used
// entry.
if (mTable.Count() > ACCESS_CONTROL_CACHE_SIZE) {
CacheEntry* lruEntry = static_cast<CacheEntry*>(PR_LIST_TAIL(&mList));
PR_REMOVE_LINK(lruEntry);
// This will delete 'lruEntry'.
mTable.Remove(lruEntry->mKey);
NS_ASSERTION(mTable.Count() == ACCESS_CONTROL_CACHE_SIZE,
"Somehow tried to remove an entry that was never added!");
}
}
return entry;
}
void
nsAccessControlLRUCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal)
{
CacheEntry* entry;
nsCString key;
if (GetCacheKey(aURI, aPrincipal, PR_TRUE, key) &&
mTable.Get(key, &entry)) {
PR_REMOVE_LINK(entry);
mTable.Remove(key);
}
if (GetCacheKey(aURI, aPrincipal, PR_FALSE, key) &&
mTable.Get(key, &entry)) {
PR_REMOVE_LINK(entry);
mTable.Remove(key);
}
}
void
nsAccessControlLRUCache::Clear()
{
PR_INIT_CLIST(&mList);
mTable.Clear();
}
/* static */ PLDHashOperator
nsAccessControlLRUCache::RemoveExpiredEntries(const nsACString& aKey,
nsAutoPtr<CacheEntry>& aValue,
void* aUserData)
{
PRTime* now = static_cast<PRTime*>(aUserData);
aValue->PurgeExpired(*now);
if (aValue->mHeaders.IsEmpty() &&
aValue->mHeaders.IsEmpty()) {
// Expired, remove from the list as well as the hash table.
PR_REMOVE_LINK(aValue);
return PL_DHASH_REMOVE;
}
return PL_DHASH_NEXT;
}
/* static */ PRBool
nsAccessControlLRUCache::GetCacheKey(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRBool aWithCredentials,
nsACString& _retval)
{
NS_ASSERTION(aURI, "Null uri!");
NS_ASSERTION(aPrincipal, "Null principal!");
NS_NAMED_LITERAL_CSTRING(space, " ");
nsCOMPtr<nsIURI> uri;
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
nsCAutoString scheme, host, port;
if (uri) {
uri->GetScheme(scheme);
uri->GetHost(host);
port.AppendInt(NS_GetRealPort(uri));
}
nsCAutoString cred;
if (aWithCredentials) {
_retval.AssignLiteral("cred");
}
else {
_retval.AssignLiteral("nocred");
}
nsCAutoString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
_retval.Assign(cred + space + scheme + space + host + space + port + space +
spec);
return PR_TRUE;
}
/////////////////////////////////////////////
//
//
/////////////////////////////////////////////
// Will be initialized in nsXMLHttpRequest::EnsureACCache.
nsAccessControlLRUCache* nsXMLHttpRequest::sAccessControlCache = nsnull;
nsXMLHttpRequest::nsXMLHttpRequest()
: mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNINITIALIZED),
mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE),
@ -2572,41 +2143,6 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
PRBool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
// If so, set up the preflight
if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) {
// Check to see if this initial OPTIONS request has already been cached
// in our special Access Control Cache.
nsCOMPtr<nsIURI> uri;
rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsAccessControlLRUCache::CacheEntry* entry =
sAccessControlCache ?
sAccessControlCache->GetEntry(uri, mPrincipal, withCredentials, PR_FALSE) :
nsnull;
if (!entry || !entry->CheckRequest(method, mACUnsafeHeaders)) {
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
nsCOMPtr<nsILoadGroup> loadGroup;
GetLoadGroup(getter_AddRefs(loadGroup));
nsLoadFlags loadFlags;
rv = mChannel->GetLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewChannel(getter_AddRefs(mACGetChannel), uri, nsnull,
loadGroup, nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");
rv = acHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
NS_ENSURE_SUCCESS(rv, rv);
}
}
// Hook us up to listen to redirects and the like
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this);
@ -2645,10 +2181,6 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
else if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
AddLoadFlags(mChannel,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
if (mACGetChannel) {
AddLoadFlags(mACGetChannel,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
}
}
// Since we expect XML data, set the type hint accordingly
@ -2656,22 +2188,16 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
// ignoring return value, as this is not critical
mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
// If we're doing a cross-site non-GET request we need to first do
// a GET request to the same URI. Set that up if needed
if (mACGetChannel) {
nsCOMPtr<nsIStreamListener> acProxyListener =
new nsACProxyListener(mChannel, listener, nsnull, mPrincipal, method,
withCredentials);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
// Set up the preflight if needed
if (mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT) {
// Check to see if this initial OPTIONS request has already been cached
// in our special Access Control Cache.
acProxyListener =
new nsCrossSiteListenerProxy(acProxyListener, mPrincipal, mACGetChannel,
withCredentials, method, mACUnsafeHeaders,
&rv);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
rv = NS_StartCORSPreflight(mChannel, listener,
mPrincipal, withCredentials,
mACUnsafeHeaders,
getter_AddRefs(mACGetChannel));
NS_ENSURE_SUCCESS(rv, rv);
rv = mACGetChannel->AsyncOpen(acProxyListener, nsnull);
}
else {
// Start reading from the channel

Просмотреть файл

@ -60,10 +60,6 @@
#include "nsTArray.h"
#include "nsIJSNativeInitializer.h"
#include "nsIDOMLSProgressEvent.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "prclist.h"
#include "prtime.h"
#include "nsIDOMNSEvent.h"
#include "nsITimer.h"
#include "nsIPrivateDOMEvent.h"
@ -74,72 +70,6 @@
class nsILoadGroup;
class AsyncVerifyRedirectCallbackForwarder;
class nsAccessControlLRUCache
{
public:
struct TokenTime
{
nsCString token;
PRTime expirationTime;
};
struct CacheEntry : public PRCList
{
CacheEntry(nsCString& aKey)
: mKey(aKey)
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache::CacheEntry);
}
~CacheEntry()
{
MOZ_COUNT_DTOR(nsAccessControlLRUCache::CacheEntry);
}
void PurgeExpired(PRTime now);
PRBool CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aCustomHeaders);
nsCString mKey;
nsTArray<TokenTime> mMethods;
nsTArray<TokenTime> mHeaders;
};
nsAccessControlLRUCache()
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache);
PR_INIT_CLIST(&mList);
}
~nsAccessControlLRUCache()
{
Clear();
MOZ_COUNT_DTOR(nsAccessControlLRUCache);
}
PRBool Initialize()
{
return mTable.Init();
}
CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, PRBool aCreate);
void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal);
void Clear();
private:
static PLDHashOperator
RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
void* aUserData);
static PRBool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, nsACString& _retval);
nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
PRCList mList;
};
class nsXHREventTarget : public nsDOMEventTargetWrapperCache,
public nsIXMLHttpRequestEventTarget
{
@ -281,32 +211,8 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXMLHttpRequest,
nsXHREventTarget)
static PRBool EnsureACCache()
{
if (sAccessControlCache)
return PR_TRUE;
nsAutoPtr<nsAccessControlLRUCache> newCache(new nsAccessControlLRUCache());
NS_ENSURE_TRUE(newCache, PR_FALSE);
if (newCache->Initialize()) {
sAccessControlCache = newCache.forget();
return PR_TRUE;
}
return PR_FALSE;
}
static void ShutdownACCache()
{
delete sAccessControlCache;
sAccessControlCache = nsnull;
}
PRBool AllowUploadProgress();
static nsAccessControlLRUCache* sAccessControlCache;
protected:
friend class nsMultipartProxyListener;

Просмотреть файл

@ -79,7 +79,7 @@
#include "nsCCUncollectableMarker.h"
#include "nsTextFragment.h"
#include "nsCSSRuleProcessor.h"
#include "nsXMLHttpRequest.h"
#include "nsCrossSiteListenerProxy.h"
#include "nsWebSocket.h"
#include "nsDOMThreadService.h"
#include "nsHTMLDNSPrefetch.h"
@ -365,7 +365,7 @@ nsLayoutStatics::Shutdown()
nsAudioStream::ShutdownLibrary();
#endif
nsXMLHttpRequest::ShutdownACCache();
nsCrossSiteListenerProxy::Shutdown();
nsWebSocket::ReleaseGlobals();