mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
284444879a
commit
c0f5d14cd9
@ -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;
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user