Backed out changeset: 1a8fd714a794

Break out Access-Control code from nsXMLHttpRequest.cpp. r/sr=mrbkap b=459770
which leaks 280 kB.
This commit is contained in:
Serge Gautherie 2008-10-17 04:10:25 +02:00
parent 284444879a
commit c0f5d14cd9
5 changed files with 817 additions and 856 deletions

View File

@ -53,110 +53,8 @@
#include "nsWhitespaceTokenizer.h" #include "nsWhitespaceTokenizer.h"
#include "nsIChannelEventSink.h" #include "nsIChannelEventSink.h"
#include "nsCommaSeparatedTokenizer.h" #include "nsCommaSeparatedTokenizer.h"
#include "prclist.h"
#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "prtime.h"
// ========================================================================= static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
// Support classes
// Class implementing the preflight request cache
#define ACCESS_CONTROL_CACHE_SIZE 100
class nsACPreflightCache
{
public:
static void Shutdown()
{
PR_INIT_CLIST(&mList);
delete mTable;
mTable = nsnull;
}
struct TokenTime
{
nsCString token;
PRTime expirationTime;
};
class CacheEntry : public PRCList
{
public:
CacheEntry(nsCString& aKey)
: mKey(aKey)
{
MOZ_COUNT_CTOR(nsACPreflightCache::CacheEntry);
}
~CacheEntry()
{
MOZ_COUNT_DTOR(nsACPreflightCache::CacheEntry);
}
void PurgeExpired(PRTime now);
PRBool CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aCustomHeaders);
nsCString mKey;
nsTArray<TokenTime> mMethods;
nsTArray<TokenTime> mHeaders;
};
static CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, PRBool aCreate);
private:
PR_STATIC_CALLBACK(PLDHashOperator)
RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
void* aUserData);
static PRBool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, nsACString& _retval);
static nsClassHashtable<nsCStringHashKey, CacheEntry>* mTable;
static PRCList mList;
};
// Class used as streamlistener and notification callback when
// doing the initial GET request for an access-control check
class nsACPreflightListener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
nsACPreflightListener(nsIChannel* aOuterChannel,
nsIStreamListener* aOuterListener,
nsISupports* aOuterContext,
nsIPrincipal* aReferrerPrincipal,
PRBool aWithCredentials)
: mOuterChannel(aOuterChannel), mOuterListener(aOuterListener),
mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal),
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;
PRBool mWithCredentials;
};
// =========================================================================
// nsCrossSiteListenerProxy
NS_IMPL_ISUPPORTS4(nsCrossSiteListenerProxy, nsIStreamListener, NS_IMPL_ISUPPORTS4(nsCrossSiteListenerProxy, nsIStreamListener,
nsIRequestObserver, nsIChannelEventSink, nsIRequestObserver, nsIChannelEventSink,
@ -203,128 +101,6 @@ nsCrossSiteListenerProxy::nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
*aResult = UpdateChannel(aChannel); *aResult = UpdateChannel(aChannel);
} }
nsresult
nsCrossSiteListenerProxy::CheckPreflight(nsIHttpChannel* aRequestChannel,
nsIStreamListener* aRequestListener,
nsISupports* aRequestContext,
nsIPrincipal* aRequestingPrincipal,
PRBool aForcePreflight,
nsTArray<nsCString>& aUnsafeHeaders,
PRBool aWithCredentials,
PRBool* aPreflighted,
nsIChannel** aPreflightChannel,
nsIStreamListener** aPreflightListener)
{
*aPreflighted = PR_FALSE;
nsCAutoString method;
aRequestChannel->GetRequestMethod(method);
if (!aUnsafeHeaders.IsEmpty() || aForcePreflight) {
*aPreflighted = PR_TRUE;
}
else if (method.LowerCaseEqualsLiteral("post")) {
nsCAutoString contentTypeHeader;
aRequestChannel->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
contentTypeHeader);
nsCAutoString contentType, charset;
NS_ParseContentType(contentTypeHeader, contentType, charset);
if (!contentType.LowerCaseEqualsLiteral("text/plain")) {
*aPreflighted = PR_TRUE;
}
}
else if (!method.LowerCaseEqualsLiteral("get")) {
*aPreflighted = PR_TRUE;
}
if (!*aPreflighted) {
return NS_OK;
}
// If so, set up the preflight
// Check to see if this initial OPTIONS request has already been cached
// in our special Access Control Cache.
nsCOMPtr<nsIURI> uri;
nsresult rv = aRequestChannel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsACPreflightCache::CacheEntry* entry =
nsACPreflightCache::GetEntry(uri, aRequestingPrincipal,
aWithCredentials, PR_FALSE);
if (entry && entry->CheckRequest(method, aUnsafeHeaders)) {
return NS_OK;
}
// 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> preflightHttp = do_QueryInterface(preflightChannel);
NS_ASSERTION(preflightHttp, "Failed to QI to nsIHttpChannel!");
rv = preflightHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
NS_ENSURE_SUCCESS(rv, rv);
// Add channel headers
rv = preflightHttp->
SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
method, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
if (!aUnsafeHeaders.IsEmpty()) {
nsCAutoString headers;
for (PRUint32 i = 0; i < aUnsafeHeaders.Length(); ++i) {
if (i != 0) {
headers += ',';
}
headers += aUnsafeHeaders[i];
}
rv = preflightHttp->
SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
headers, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
}
// Set up listener
nsCOMPtr<nsIStreamListener> preflightListener =
new nsACPreflightListener(aRequestChannel, aRequestListener,
aRequestContext, aRequestingPrincipal,
aWithCredentials);
NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY);
preflightListener =
new nsCrossSiteListenerProxy(preflightListener, aRequestingPrincipal,
preflightChannel, aWithCredentials, method,
aUnsafeHeaders, &rv);
NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
preflightChannel.swap(*aPreflightChannel);
preflightListener.swap(*aPreflightListener);
return NS_OK;
}
void
nsCrossSiteListenerProxy::ShutdownPreflightCache()
{
nsACPreflightCache::Shutdown();
}
NS_IMETHODIMP NS_IMETHODIMP
nsCrossSiteListenerProxy::OnStartRequest(nsIRequest* aRequest, nsCrossSiteListenerProxy::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext) nsISupports* aContext)
@ -608,6 +384,28 @@ nsCrossSiteListenerProxy::UpdateChannel(nsIChannel* aChannel)
rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, PR_FALSE); rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Add preflight headers if this is a preflight request
if (mIsPreflight) {
rv = http->
SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"),
mPreflightMethod, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
if (!mPreflightHeaders.IsEmpty()) {
nsCAutoString headers;
for (PRUint32 i = 0; i < mPreflightHeaders.Length(); ++i) {
if (i != 0) {
headers += ',';
}
headers += mPreflightHeaders[i];
}
rv = http->
SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"),
headers, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
}
}
// Make cookie-less if needed // Make cookie-less if needed
if (mIsPreflight || !mWithCredentials) { if (mIsPreflight || !mWithCredentials) {
nsLoadFlags flags; nsLoadFlags flags;
@ -621,387 +419,3 @@ nsCrossSiteListenerProxy::UpdateChannel(nsIChannel* aChannel)
return NS_OK; return NS_OK;
} }
// =========================================================================
// nsACPreflightListener
NS_IMPL_ISUPPORTS4(nsACPreflightListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
void
nsACPreflightListener::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 = PR_MIN(age, 86400);
++iter;
}
if (!age) {
return;
}
// String seems fine, go ahead and cache.
// Note that we have already checked that these headers follow the correct
// syntax.
nsCOMPtr<nsIURI> uri;
http->GetURI(getter_AddRefs(uri));
// PR_Now gives microseconds
PRTime expirationTime = PR_Now() + (PRUint64)age * PR_USEC_PER_SEC;
nsACPreflightCache::CacheEntry* entry =
nsACPreflightCache::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);
nsCCommaSeparatedTokenizer 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()) {
nsACPreflightCache::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);
nsCCommaSeparatedTokenizer 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()) {
nsACPreflightCache::TokenTime* newHeader =
entry->mHeaders.AppendElement();
if (!newHeader) {
return;
}
newHeader->token = header;
newHeader->expirationTime = expirationTime;
}
}
}
NS_IMETHODIMP
nsACPreflightListener::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
nsACPreflightListener::OnStopRequest(nsIRequest *aRequest,
nsISupports *aContext,
nsresult aStatus)
{
return NS_OK;
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
nsACPreflightListener::OnDataAvailable(nsIRequest *aRequest,
nsISupports *ctxt,
nsIInputStream *inStr,
PRUint32 sourceOffset,
PRUint32 count)
{
return NS_OK;
}
NS_IMETHODIMP
nsACPreflightListener::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
// No redirects allowed for now.
return NS_ERROR_DOM_BAD_URI;
}
NS_IMETHODIMP
nsACPreflightListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
// =========================================================================
// nsACPreflightCache
nsClassHashtable<nsCStringHashKey, nsACPreflightCache::CacheEntry>*
nsACPreflightCache::mTable;
PRCList nsACPreflightCache::mList;
void
nsACPreflightCache::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
nsACPreflightCache::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;
}
nsACPreflightCache::CacheEntry*
nsACPreflightCache::GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aWithCredentials, PRBool aCreate)
{
if (!mTable) {
if (!aCreate) {
return nsnull;
}
PR_INIT_CLIST(&mList);
mTable = new nsClassHashtable<nsCStringHashKey, CacheEntry>;
NS_ENSURE_TRUE(mTable, nsnull);
if (!mTable->Init()) {
delete mTable;
mTable = nsnull;
return nsnull;
}
}
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;
}
/* static */ PR_CALLBACK PLDHashOperator
nsACPreflightCache::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
nsACPreflightCache::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;
}

View File

@ -39,13 +39,17 @@
#include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestor.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsString.h" #include "nsString.h"
#include "nsIURI.h"
#include "nsTArray.h" #include "nsTArray.h"
#include "nsIContentSink.h"
#include "nsIXMLContentSink.h"
#include "nsIExpatSink.h"
#include "nsIInterfaceRequestor.h"
#include "nsIChannelEventSink.h" #include "nsIChannelEventSink.h"
class nsIURI; class nsIURI;
class nsIParser; class nsIParser;
class nsIPrincipal; class nsIPrincipal;
class nsIHttpChannel;
extern PRBool extern PRBool
IsValidHTTPToken(const nsCSubstring& aToken); IsValidHTTPToken(const nsCSubstring& aToken);
@ -68,19 +72,6 @@ public:
const nsTArray<nsCString>& aPreflightHeaders, const nsTArray<nsCString>& aPreflightHeaders,
nsresult* aResult); nsresult* aResult);
static nsresult CheckPreflight(nsIHttpChannel* aRequestChannel,
nsIStreamListener* aRequestListener,
nsISupports* aRequestContext,
nsIPrincipal* aRequestingPrincipal,
PRBool aForcePreflight,
nsTArray<nsCString>& aUnsafeHeaders,
PRBool aWithCredentials,
PRBool* aPreflighted,
nsIChannel** aPreflightChannel,
nsIStreamListener** aPreflightListener);
static void ShutdownPreflightCache();
NS_DECL_ISUPPORTS NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER NS_DECL_NSISTREAMLISTENER

View File

@ -138,6 +138,8 @@
XML_HTTP_REQUEST_SENT | \ XML_HTTP_REQUEST_SENT | \
XML_HTTP_REQUEST_STOPPED) XML_HTTP_REQUEST_STOPPED)
#define ACCESS_CONTROL_CACHE_SIZE 100
#define NS_BADCERTHANDLER_CONTRACTID \ #define NS_BADCERTHANDLER_CONTRACTID \
"@mozilla.org/content/xmlhttprequest-bad-cert-handler;1" "@mozilla.org/content/xmlhttprequest-bad-cert-handler;1"
@ -281,6 +283,220 @@ nsMultipartProxyListener::OnDataAvailable(nsIRequest *aRequest,
count); 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 = PR_MIN(age, 86400);
++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;
http->GetURI(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);
nsCCommaSeparatedTokenizer 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);
nsCCommaSeparatedTokenizer 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::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
// No redirects allowed for now.
return NS_ERROR_DOM_BAD_URI;
}
NS_IMETHODIMP
nsACProxyListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
/** /**
* Gets the nsIDocument given the script context. Will return nsnull on failure. * Gets the nsIDocument given the script context. Will return nsnull on failure.
* *
@ -628,12 +844,201 @@ NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget) NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
NS_IMPL_RELEASE_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::Clear()
{
PR_INIT_CLIST(&mList);
mTable.Clear();
}
/* static */ PR_CALLBACK 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() nsXMLHttpRequest::nsXMLHttpRequest()
: mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNINITIALIZED), : mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNINITIALIZED),
mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE), mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE),
@ -1078,8 +1483,8 @@ nsXMLHttpRequest::Abort()
if (mChannel) { if (mChannel) {
mChannel->Cancel(NS_BINDING_ABORTED); mChannel->Cancel(NS_BINDING_ABORTED);
} }
if (mACPreflightChannel) { if (mACGetChannel) {
mACPreflightChannel->Cancel(NS_BINDING_ABORTED); mACGetChannel->Cancel(NS_BINDING_ABORTED);
} }
mResponseXML = nsnull; mResponseXML = nsnull;
mResponseBody.Truncate(); mResponseBody.Truncate();
@ -1198,6 +1603,24 @@ nsXMLHttpRequest::GetResponseHeader(const nsACString& header,
return rv; return rv;
} }
nsresult
nsXMLHttpRequest::GetLoadGroup(nsILoadGroup **aLoadGroup)
{
NS_ENSURE_ARG_POINTER(aLoadGroup);
*aLoadGroup = nsnull;
if (mState & XML_HTTP_REQUEST_BACKGROUND) {
return NS_OK;
}
nsCOMPtr<nsIDocument> doc = GetDocumentFromScriptContext(mScriptContext);
if (doc) {
*aLoadGroup = doc->GetDocumentLoadGroup().get(); // already_AddRefed
}
return NS_OK;
}
nsresult nsresult
nsXMLHttpRequest::CreateReadystatechangeEvent(nsIDOMEvent** aDOMEvent) nsXMLHttpRequest::CreateReadystatechangeEvent(nsIDOMEvent** aDOMEvent)
{ {
@ -1422,12 +1845,11 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
authp = PR_TRUE; authp = PR_TRUE;
} }
// Find the load group for the page, and add ourselves to it. This way any // When we are called from JS we can find the load group for the page,
// pending requests will be automatically aborted if the user leaves the page. // and add ourselves to it. This way any pending requests
// will be automatically aborted if the user leaves the page.
nsCOMPtr<nsILoadGroup> loadGroup; nsCOMPtr<nsILoadGroup> loadGroup;
if (doc && !(mState & XML_HTTP_REQUEST_BACKGROUND)) { GetLoadGroup(getter_AddRefs(loadGroup));
loadGroup = doc->GetDocumentLoadGroup();
}
// nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which // nsIRequest::LOAD_BACKGROUND prevents throbber from becoming active, which
// in turn keeps STOP button from becoming active. If the consumer passed in // in turn keeps STOP button from becoming active. If the consumer passed in
@ -1443,7 +1865,7 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
} }
rv = NS_NewChannel(getter_AddRefs(mChannel), uri, nsnull, loadGroup, nsnull, rv = NS_NewChannel(getter_AddRefs(mChannel), uri, nsnull, loadGroup, nsnull,
loadFlags); loadFlags);
NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(rv)) return rv;
// Check if we're doing a cross-origin request. // Check if we're doing a cross-origin request.
if (IsSystemPrincipal(mPrincipal)) { if (IsSystemPrincipal(mPrincipal)) {
@ -1948,19 +2370,59 @@ nsXMLHttpRequest::SendAsBinary(const nsAString &aBody)
return Send(variant); return Send(variant);
} }
nsresult /* void send (in nsIVariant aBody); */
nsXMLHttpRequest::SetUploadDataAndType(nsIVariant* aBody) NS_IMETHODIMP
nsXMLHttpRequest::Send(nsIVariant *aBody)
{ {
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel)); NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
NS_ASSERTION(httpChannel, "Should be http channel to get here");
nsresult rv = CheckInnerWindowCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
// Return error if we're already processing a request
if (XML_HTTP_REQUEST_SENT & mState) {
return NS_ERROR_FAILURE;
}
// Make sure we've been opened
if (!mChannel || !(XML_HTTP_REQUEST_OPENED & mState)) {
return NS_ERROR_NOT_INITIALIZED;
}
// XXX We should probably send a warning to the JS console
// if there are no event listeners set and we are doing
// an asynchronous call.
// Ignore argument if method is GET, there is no point in trying to
// upload anything
nsCAutoString method;
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
if (!IsSystemPrincipal(mPrincipal)) {
nsCOMPtr<nsIURI> codebase;
mPrincipal->GetURI(getter_AddRefs(codebase));
httpChannel->SetReferrer(codebase);
}
}
mUploadTransferred = 0;
mUploadTotal = 0;
// By default we don't have any upload, so mark upload complete.
mUploadComplete = PR_TRUE;
mErrorLoad = PR_FALSE;
if (aBody && httpChannel && !method.EqualsLiteral("GET")) {
nsXPIDLString serial; nsXPIDLString serial;
nsCOMPtr<nsIInputStream> postDataStream; nsCOMPtr<nsIInputStream> postDataStream;
nsCAutoString charset(NS_LITERAL_CSTRING("UTF-8")); nsCAutoString charset(NS_LITERAL_CSTRING("UTF-8"));
PRUint16 dataType; PRUint16 dataType;
nsresult rv = aBody->GetDataType(&dataType); rv = aBody->GetDataType(&dataType);
NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(rv))
return rv;
switch (dataType) { switch (dataType) {
case nsIDataType::VTYPE_INTERFACE: case nsIDataType::VTYPE_INTERFACE:
@ -1977,9 +2439,8 @@ nsXMLHttpRequest::SetUploadDataAndType(nsIVariant* aBody)
// document? // document?
nsCOMPtr<nsIDOMDocument> doc(do_QueryInterface(supports)); nsCOMPtr<nsIDOMDocument> doc(do_QueryInterface(supports));
if (doc) { if (doc) {
nsCOMPtr<nsIDOMSerializer> serializer = nsCOMPtr<nsIDOMSerializer> serializer(do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv));
do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv;
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOM3Document> dom3doc(do_QueryInterface(doc)); nsCOMPtr<nsIDOM3Document> dom3doc(do_QueryInterface(doc));
if (dom3doc) { if (dom3doc) {
@ -1995,8 +2456,7 @@ nsXMLHttpRequest::SetUploadDataAndType(nsIVariant* aBody)
// Serialize to a stream so that the encoding used will // Serialize to a stream so that the encoding used will
// match the document's. // match the document's.
nsCOMPtr<nsIStorageStream> storStream; nsCOMPtr<nsIStorageStream> storStream;
rv = NS_NewStorageStream(4096, PR_UINT32_MAX, rv = NS_NewStorageStream(4096, PR_UINT32_MAX, getter_AddRefs(storStream));
getter_AddRefs(storStream));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> output; nsCOMPtr<nsIOutputStream> output;
@ -2038,7 +2498,8 @@ nsXMLHttpRequest::SetUploadDataAndType(nsIVariant* aBody)
default: default:
// try variant string // try variant string
rv = aBody->GetAsWString(getter_Copies(serial)); rv = aBody->GetAsWString(getter_Copies(serial));
NS_ENSURE_SUCCESS(rv, rv); if (NS_FAILED(rv))
return rv;
break; break;
} }
@ -2101,93 +2562,15 @@ nsXMLHttpRequest::SetUploadDataAndType(nsIVariant* aBody)
} }
mUploadComplete = PR_FALSE; mUploadComplete = PR_FALSE;
nsCAutoString method;
httpChannel->GetRequestMethod(method);
postDataStream->Available(&mUploadTotal); postDataStream->Available(&mUploadTotal);
rv = uploadChannel->SetUploadStream(postDataStream, contentType, -1); rv = uploadChannel->SetUploadStream(postDataStream, contentType, -1);
NS_ENSURE_SUCCESS(rv, rv);
// Reset the method to its original value // Reset the method to its original value
if (httpChannel) {
httpChannel->SetRequestMethod(method); httpChannel->SetRequestMethod(method);
} }
return NS_OK;
}
/* void send (in nsIVariant aBody); */
NS_IMETHODIMP
nsXMLHttpRequest::Send(nsIVariant *aBody)
{
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
nsresult rv = CheckInnerWindowCorrectness();
NS_ENSURE_SUCCESS(rv, rv);
// Return error if we're already processing a request
if (XML_HTTP_REQUEST_SENT & mState) {
return NS_ERROR_FAILURE;
}
// Make sure we've been opened
if (!mChannel || !(XML_HTTP_REQUEST_OPENED & mState)) {
return NS_ERROR_NOT_INITIALIZED;
}
// XXX We should probably send a warning to the JS console
// if there are no event listeners set and we are doing
// an asynchronous call.
// Ignore argument if method is GET, there is no point in trying to
// upload anything
nsCAutoString method;
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
// If GET, method name will be uppercase
httpChannel->GetRequestMethod(method);
if (!IsSystemPrincipal(mPrincipal)) {
nsCOMPtr<nsIURI> codebase;
mPrincipal->GetURI(getter_AddRefs(codebase));
httpChannel->SetReferrer(codebase);
} }
} }
mUploadTransferred = 0;
mUploadTotal = 0;
// By default we don't have any upload, so mark upload complete.
mUploadComplete = PR_TRUE;
mErrorLoad = PR_FALSE;
if (aBody && httpChannel && !method.EqualsLiteral("GET")) {
SetUploadDataAndType(aBody);
}
// Bypass the network cache in cases where it makes no sense:
// 1) Multipart responses are very large and would likely be doomed by the
// cache once they grow too large, so they are not worth caching.
// 2) POST responses are always unique, and we provide no API that would
// allow our consumers to specify a "cache key" to access old POST
// responses, so they are not worth caching.
if ((mState & XML_HTTP_REQUEST_MULTIPART) || method.EqualsLiteral("POST")) {
AddLoadFlags(mChannel,
nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING);
}
// When we are sync loading, we need to bypass the local cache when it would
// otherwise block us waiting for exclusive access to the cache. If we don't
// do this, then we could dead lock in some cases (see bug 309424).
else if (!(mState & XML_HTTP_REQUEST_ASYNC)) {
AddLoadFlags(mChannel,
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY);
}
// Since we expect XML data, set the type hint accordingly
// This means that we always try to parse local files as XML
// ignoring return value, as this is not critical
mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
// Reset responseBody // Reset responseBody
mResponseBody.Truncate(); mResponseBody.Truncate();
@ -2197,9 +2580,72 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
rv = CheckChannelForCrossSiteRequest(mChannel); rv = CheckChannelForCrossSiteRequest(mChannel);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// Start request, or preflight request if one is needed.
PRBool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS); PRBool withCredentials = !!(mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS);
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
// Check if we need to do a preflight request.
NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
nsCAutoString method;
httpChannel->GetRequestMethod(method);
if (!mACUnsafeHeaders.IsEmpty() ||
HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) ||
(mUpload && mUpload->HasListeners())) {
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
}
else if (method.LowerCaseEqualsLiteral("post")) {
nsCAutoString contentTypeHeader;
httpChannel->GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
contentTypeHeader);
nsCAutoString contentType, charset;
NS_ParseContentType(contentTypeHeader, contentType, charset);
NS_ENSURE_SUCCESS(rv, rv);
if (!contentType.LowerCaseEqualsLiteral("text/plain")) {
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
}
}
else if (!method.LowerCaseEqualsLiteral("get")) {
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
}
// 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 = mChannel->GetURI(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 // Hook us up to listen to redirects and the like
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this); mChannel->SetNotificationCallbacks(this);
@ -2222,31 +2668,49 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
} }
nsCOMPtr<nsIStreamListener> preflightListener; // Bypass the network cache in cases where it makes no sense:
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) { // 1) Multipart responses are very large and would likely be doomed by the
// Check if we need to do a preflight request. // cache once they grow too large, so they are not worth caching.
NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI); // 2) POST responses are always unique, and we provide no API that would
// allow our consumers to specify a "cache key" to access old POST
// responses, so they are not worth caching.
if ((mState & XML_HTTP_REQUEST_MULTIPART) || method.EqualsLiteral("POST")) {
AddLoadFlags(mChannel,
nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::INHIBIT_CACHING);
}
// When we are sync loading, we need to bypass the local cache when it would
// otherwise block us waiting for exclusive access to the cache. If we don't
// do this, then we could dead lock in some cases (see bug 309424).
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);
}
}
PRBool force = HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) || // Since we expect XML data, set the type hint accordingly
(mUpload && mUpload->HasListeners()); // This means that we always try to parse local files as XML
// ignoring return value, as this is not critical
mChannel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
PRBool preflighted; // If we're doing a cross-site non-GET request we need to first do
rv = nsCrossSiteListenerProxy:: // a GET request to the same URI. Set that up if needed
CheckPreflight(httpChannel, listener, nsnull, mPrincipal, force, if (mACGetChannel) {
mACUnsafeHeaders, withCredentials, &preflighted, nsCOMPtr<nsIStreamListener> acProxyListener =
getter_AddRefs(mACPreflightChannel), new nsACProxyListener(mChannel, listener, nsnull, mPrincipal, method,
getter_AddRefs(preflightListener)); withCredentials);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
acProxyListener =
new nsCrossSiteListenerProxy(acProxyListener, mPrincipal, mACGetChannel,
withCredentials, method, mACUnsafeHeaders,
&rv);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (preflighted) { rv = mACGetChannel->AsyncOpen(acProxyListener, nsnull);
mState |= XML_HTTP_REQUEST_NEED_AC_PREFLIGHT;
}
}
if (mACPreflightChannel) {
// Start preflight. Preflight channel will start mChannel
// if preflight succeeds.
rv = mACPreflightChannel->AsyncOpen(preflightListener, nsnull);
} }
else { else {
// Start reading from the channel // Start reading from the channel
@ -2256,7 +2720,7 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
// Drop our ref to the channel to avoid cycles // Drop our ref to the channel to avoid cycles
mChannel = nsnull; mChannel = nsnull;
mACPreflightChannel = nsnull; mACGetChannel = nsnull;
return rv; return rv;
} }
@ -2306,10 +2770,10 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
// Check that we haven't already opened the channel. We can't rely on // Check that we haven't already opened the channel. We can't rely on
// the channel throwing from mChannel->SetRequestHeader since we might // the channel throwing from mChannel->SetRequestHeader since we might
// still be waiting for mACPreflightChannel to actually open mChannel // still be waiting for mACGetChannel to actually open mChannel
if (mACPreflightChannel) { if (mACGetChannel) {
PRBool pending; PRBool pending;
rv = mACPreflightChannel->IsPending(&pending); rv = mACGetChannel->IsPending(&pending);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (pending) { if (pending) {

View File

@ -64,6 +64,9 @@
#include "nsIJSNativeInitializer.h" #include "nsIJSNativeInitializer.h"
#include "nsPIDOMWindow.h" #include "nsPIDOMWindow.h"
#include "nsIDOMLSProgressEvent.h" #include "nsIDOMLSProgressEvent.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "prclist.h"
#include "prtime.h" #include "prtime.h"
#include "nsIEventListenerManager.h" #include "nsIEventListenerManager.h"
#include "nsIDOMNSEvent.h" #include "nsIDOMNSEvent.h"
@ -72,6 +75,71 @@
class nsILoadGroup; class nsILoadGroup;
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 Clear();
private:
PR_STATIC_CALLBACK(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 nsDOMEventListenerWrapper : public nsIDOMEventListener class nsDOMEventListenerWrapper : public nsIDOMEventListener
{ {
public: public:
@ -265,12 +333,35 @@ public:
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXMLHttpRequest, NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXMLHttpRequest,
nsXHREventTarget) 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(); PRBool AllowUploadProgress();
static nsAccessControlLRUCache* sAccessControlCache;
protected: protected:
friend class nsMultipartProxyListener; friend class nsMultipartProxyListener;
nsresult SetUploadDataAndType(nsIVariant* aBody);
nsresult DetectCharset(nsACString& aCharset); nsresult DetectCharset(nsACString& aCharset);
nsresult ConvertBodyToText(nsAString& aOutBuffer); nsresult ConvertBodyToText(nsAString& aOutBuffer);
static NS_METHOD StreamReaderFunc(nsIInputStream* in, static NS_METHOD StreamReaderFunc(nsIInputStream* in,
@ -283,6 +374,8 @@ protected:
// determines if the onreadystatechange listener should be called. // determines if the onreadystatechange listener should be called.
nsresult ChangeState(PRUint32 aState, PRBool aBroadcast = PR_TRUE); nsresult ChangeState(PRUint32 aState, PRBool aBroadcast = PR_TRUE);
nsresult RequestCompleted(); nsresult RequestCompleted();
nsresult GetLoadGroup(nsILoadGroup **aLoadGroup);
nsIURI *GetBaseURI();
nsresult RemoveAddEventListener(const nsAString& aType, nsresult RemoveAddEventListener(const nsAString& aType,
nsRefPtr<nsDOMEventListenerWrapper>& aCurrent, nsRefPtr<nsDOMEventListenerWrapper>& aCurrent,
@ -307,7 +400,7 @@ protected:
// mReadRequest is different from mChannel for multipart requests // mReadRequest is different from mChannel for multipart requests
nsCOMPtr<nsIRequest> mReadRequest; nsCOMPtr<nsIRequest> mReadRequest;
nsCOMPtr<nsIDOMDocument> mResponseXML; nsCOMPtr<nsIDOMDocument> mResponseXML;
nsCOMPtr<nsIChannel> mACPreflightChannel; nsCOMPtr<nsIChannel> mACGetChannel;
nsTArray<nsCString> mACUnsafeHeaders; nsTArray<nsCString> mACUnsafeHeaders;
nsRefPtr<nsDOMEventListenerWrapper> mOnUploadProgressListener; nsRefPtr<nsDOMEventListenerWrapper> mOnUploadProgressListener;

View File

@ -83,7 +83,6 @@
#include "nsXMLHttpRequest.h" #include "nsXMLHttpRequest.h"
#include "nsIFocusEventSuppressor.h" #include "nsIFocusEventSuppressor.h"
#include "nsDOMThreadService.h" #include "nsDOMThreadService.h"
#include "nsCrossSiteListenerProxy.h"
#ifdef MOZ_XUL #ifdef MOZ_XUL
#include "nsXULPopupManager.h" #include "nsXULPopupManager.h"
@ -347,7 +346,7 @@ nsLayoutStatics::Shutdown()
nsAudioStream::ShutdownLibrary(); nsAudioStream::ShutdownLibrary();
#endif #endif
nsCrossSiteListenerProxy::ShutdownPreflightCache(); nsXMLHttpRequest::ShutdownACCache();
} }
void void