Update Access-Control implementation to match changes in spec. b=389508 r/sr=jst

This commit is contained in:
Jonas Sicking 2008-09-30 17:50:42 -07:00
Родитель f9b484d02c
Коммит 4e7c8085c2
15 изменённых файлов: 1721 добавлений и 995 удалений

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -51,9 +51,10 @@ class nsIURI;
class nsIParser;
class nsIPrincipal;
extern PRBool
IsValidHTTPToken(const nsCSubstring& aToken);
class nsCrossSiteListenerProxy : public nsIStreamListener,
public nsIXMLContentSink,
public nsIExpatSink,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
@ -62,48 +63,29 @@ public:
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
nsresult* aResult);
nsCrossSiteListenerProxy(nsIStreamListener* aOuter,
nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel,
const nsCString& aPreflightMethod,
const nsTArray<nsCString>& aPreflightHeaders,
nsresult* aResult);
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIEXPATSINK
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
// nsIContentSink
NS_IMETHOD WillTokenize(void) { return NS_OK; }
NS_IMETHOD WillBuildModel(void);
NS_IMETHOD DidBuildModel() { return NS_OK; }
NS_IMETHOD WillInterrupt(void) { return NS_OK; }
NS_IMETHOD WillResume(void) { return NS_OK; }
NS_IMETHOD SetParser(nsIParser* aParser) { return NS_OK; }
virtual void FlushPendingNotifications(mozFlushType aType) { }
NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; }
virtual nsISupports *GetTarget() { return nsnull; }
private:
nsresult UpdateChannel(nsIChannel* aChannel);
nsresult ForwardRequest(PRBool aCallStop);
PRBool MatchPatternList(const char*& aIter, const char* aEnd);
void CheckHeader(const nsCString& aHeader);
PRBool VerifyAndMatchDomainPattern(const nsACString& aDomainPattern);
nsresult CheckRequestApproved(nsIRequest* aRequest);
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsIRequest> mOuterRequest;
nsCOMPtr<nsISupports> mOuterContext;
nsCOMPtr<nsIStreamListener> mParserListener;
nsCOMPtr<nsIParser> mParser;
nsCOMPtr<nsIURI> mRequestingURI;
nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
nsTArray<nsCString> mReqSubdomains;
nsCString mStoredData;
enum {
eAccept,
eDeny,
eNotSet
} mAcceptState;
PRBool mHasForwardedRequest;
PRBool mRequestApproved;
PRBool mHasBeenCrossSite;
PRBool mIsPreflight;
nsCString mPreflightMethod;
nsTArray<nsCString> mPreflightHeaders;
};

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

@ -85,12 +85,12 @@
#include "nsDOMError.h"
#include "nsIHTMLDocument.h"
#include "nsIDOM3Document.h"
#include "nsWhitespaceTokenizer.h"
#include "nsIMultiPartChannel.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIStorageStream.h"
#include "nsIPromptFactory.h"
#include "nsIWindowWatcher.h"
#include "nsCommaSeparatedTokenizer.h"
#define LOAD_STR "load"
#define ERROR_STR "error"
@ -126,7 +126,7 @@
// but haven't yet started to process the first part.
#define XML_HTTP_REQUEST_MPART_HEADERS (1 << 15) // Internal
#define XML_HTTP_REQUEST_USE_XSITE_AC (1 << 16) // Internal
#define XML_HTTP_REQUEST_NON_GET (1 << 17) // Internal
#define XML_HTTP_REQUEST_NEED_AC_PREFLIGHT (1 << 17) // Internal
#define XML_HTTP_REQUEST_LOADSTATES \
(XML_HTTP_REQUEST_UNINITIALIZED | \
@ -306,6 +306,8 @@ public:
NS_DECL_NSICHANNELEVENTSINK
private:
void AddResultToCache(nsIRequest* aRequest);
nsCOMPtr<nsIChannel> mOuterChannel;
nsCOMPtr<nsIStreamListener> mOuterListener;
nsCOMPtr<nsISupports> mOuterContext;
@ -316,6 +318,120 @@ private:
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, 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)
{
@ -327,41 +443,9 @@ nsACProxyListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
}
if (NS_SUCCEEDED(rv)) {
// Everything worked, check to see if there is an expiration time set on
// this access control list. If so go ahead and cache it.
// Everything worked, try to cache and then fire off the actual request.
AddResultToCache(aRequest);
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest, &rv);
// The "Access-Control-Max-Age" header should return an age in seconds.
nsCAutoString ageString;
http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"),
ageString);
// 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). We don't allow a + or - character
// but PR_sscanf does so we ensure that the first character is actually a
// digit.
ageString.StripWhitespace();
if (ageString.CharAt(0) >= '0' || ageString.CharAt(0) <= '9') {
PRUint64 age;
PRInt32 convertedChars = PR_sscanf(ageString.get(), "%llu", &age);
if ((PRInt32)ageString.Length() == convertedChars &&
nsXMLHttpRequest::EnsureACCache()) {
// String seems fine, go ahead and cache.
nsCOMPtr<nsIURI> uri;
http->GetURI(getter_AddRefs(uri));
// PR_Now gives microseconds
PRTime expirationTime = PR_Now() + age * PR_USEC_PER_SEC;
nsXMLHttpRequest::sAccessControlCache->PutEntry(uri, mReferrerPrincipal,
expirationTime);
}
}
}
if (NS_SUCCEEDED(rv)) {
rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext);
}
@ -751,46 +835,87 @@ NS_IMPL_ADDREF_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
NS_IMPL_RELEASE_INHERITED(nsXMLHttpRequestUpload, nsXHREventTarget)
void
nsAccessControlLRUCache::GetEntry(nsIURI* aURI,
nsIPrincipal* aPrincipal,
PRTime* _retval)
nsAccessControlLRUCache::CacheEntry::PurgeExpired(PRTime now)
{
nsCAutoString key;
if (GetCacheKey(aURI, aPrincipal, key)) {
CacheEntry* entry;
if (GetEntryInternal(key, &entry)) {
*_retval = entry->value;
return;
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--);
}
}
*_retval = 0;
}
void
nsAccessControlLRUCache::PutEntry(nsIURI* aURI,
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,
PRTime aValue)
PRBool aCreate)
{
nsCString key;
if (!GetCacheKey(aURI, aPrincipal, key)) {
NS_WARNING("Invalid cache key!");
return;
return nsnull;
}
CacheEntry* entry;
if (GetEntryInternal(key, &entry)) {
// Entry already existed, just update the expiration time and bail. The LRU
// list is updated as a result of the call to GetEntryInternal.
entry->value = aValue;
return;
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, aValue);
entry = new CacheEntry(key);
if (!entry) {
NS_WARNING("Failed to allocate new cache entry!");
return;
return nsnull;
}
if (!mTable.Put(key, entry)) {
@ -798,7 +923,7 @@ nsAccessControlLRUCache::PutEntry(nsIURI* aURI,
delete entry;
NS_WARNING("Failed to add entry to the access control cache!");
return;
return nsnull;
}
PR_INSERT_LINK(entry, &mList);
@ -819,12 +944,14 @@ nsAccessControlLRUCache::PutEntry(nsIURI* aURI,
PR_REMOVE_LINK(lruEntry);
// This will delete 'lruEntry'.
mTable.Remove(lruEntry->key);
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
@ -834,32 +961,22 @@ nsAccessControlLRUCache::Clear()
mTable.Clear();
}
PRBool
nsAccessControlLRUCache::GetEntryInternal(const nsACString& aKey,
CacheEntry** _retval)
{
if (!mTable.Get(aKey, _retval))
return PR_FALSE;
// Move to the head of the list.
PR_REMOVE_LINK(*_retval);
PR_INSERT_LINK(*_retval, &mList);
return PR_TRUE;
}
/* static */ PR_CALLBACK PLDHashOperator
nsAccessControlLRUCache::RemoveExpiredEntries(const nsACString& aKey,
nsAutoPtr<CacheEntry>& aValue,
void* aUserData)
{
PRTime* now = static_cast<PRTime*>(aUserData);
if (*now >= aValue->value) {
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;
}
// Keep going.
return PL_DHASH_NEXT;
}
@ -877,16 +994,19 @@ nsAccessControlLRUCache::GetCacheKey(nsIURI* aURI,
nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, PR_FALSE);
nsCAutoString host;
nsCAutoString scheme, host, port;
if (uri) {
uri->GetScheme(scheme);
uri->GetHost(host);
PRInt32 portInt = NS_GetRealPort(uri);
port.AppendInt(portInt);
}
nsCAutoString spec;
rv = aURI->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
_retval.Assign(host + space + spec);
_retval.Assign(scheme + space + host + space + port + space + spec);
return PR_TRUE;
}
@ -1280,6 +1400,20 @@ NS_IMETHODIMP nsXMLHttpRequest::GetResponseText(nsAString& aResponseText)
NS_IMETHODIMP
nsXMLHttpRequest::GetStatus(PRUint32 *aStatus)
{
*aStatus = 0;
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
// Make sure we don't leak status information from denied cross-site
// requests.
if (mChannel) {
nsresult status;
mChannel->GetStatus(&status);
if (NS_FAILED(status)) {
return NS_OK;
}
}
}
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (httpChannel) {
@ -1298,7 +1432,6 @@ nsXMLHttpRequest::GetStatus(PRUint32 *aStatus)
return rv;
}
*aStatus = 0;
return NS_OK;
}
@ -1602,20 +1735,8 @@ nsXMLHttpRequest::CheckChannelForCrossSiteRequest(nsIChannel* aChannel)
}
// This is a cross-site request
// The request is now cross-site, so update flag.
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
// Remove dangerous headers
nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
if (http) {
PRUint32 i;
for (i = 0; i < mExtraRequestHeaders.Length(); ++i) {
http->SetRequestHeader(mExtraRequestHeaders[i], EmptyCString(), PR_FALSE);
}
mExtraRequestHeaders.Clear();
}
return NS_OK;
}
@ -1737,46 +1858,11 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
// Chrome callers are always allowed to read from different origins.
mState |= XML_HTTP_REQUEST_XSITEENABLED;
}
else if (!(mState & XML_HTTP_REQUEST_XSITEENABLED) &&
!CheckMayLoad(mPrincipal, mChannel)) {
mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
}
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
rv = httpChannel->SetRequestMethod(method);
NS_ENSURE_SUCCESS(rv, rv);
if (!method.LowerCaseEqualsLiteral("get")) {
mState |= XML_HTTP_REQUEST_NON_GET;
}
}
// Do we need to set up an initial OPTIONS request to make sure that it is
// safe to make the request?
if ((mState & XML_HTTP_REQUEST_USE_XSITE_AC) &&
(mState & XML_HTTP_REQUEST_NON_GET)) {
// Check to see if this initial OPTIONS request has already been cached in
// our special Access Control Cache.
PRTime expiration = 0;
if (sAccessControlCache) {
sAccessControlCache->GetEntry(uri, mPrincipal, &expiration);
}
if (expiration <= PR_Now()) {
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
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);
}
}
ChangeState(XML_HTTP_REQUEST_OPENED);
@ -1815,11 +1901,6 @@ nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url)
rv = NS_NewURI(getter_AddRefs(targetURI), url, nsnull, GetBaseURI());
if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager();
if (!secMan) {
return NS_ERROR_FAILURE;
}
// Find out if UniversalBrowserRead privileges are enabled
if (nsContentUtils::IsCallerTrustedForRead()) {
mState |= XML_HTTP_REQUEST_XSITEENABLED;
@ -2488,6 +2569,68 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
rv = CheckChannelForCrossSiteRequest(mChannel);
NS_ENSURE_SUCCESS(rv, rv);
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()) {
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, PR_FALSE) :
nsnull;
if (!entry || !entry->CheckRequest(method, mACUnsafeHeaders)) {
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
nsCOMPtr<nsILoadGroup> loadGroup;
GetLoadGroup(getter_AddRefs(loadGroup));
nsLoadFlags loadFlags;
rv = mChannel->GetLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewChannel(getter_AddRefs(mACGetChannel), uri, nsnull,
loadGroup, nsnull, loadFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> acHttp = do_QueryInterface(mACGetChannel);
NS_ASSERTION(acHttp, "Failed to QI to nsIHttpChannel!");
rv = acHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS"));
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
// Hook us up to listen to redirects and the like
mChannel->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks));
mChannel->SetNotificationCallbacks(this);
@ -2540,16 +2683,17 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
// If we're doing a cross-site non-GET request we need to first do
// a GET request to the same URI. Set that up if needed
if (mACGetChannel) {
nsCOMPtr<nsIStreamListener> acListener =
nsCOMPtr<nsIStreamListener> acProxyListener =
new nsACProxyListener(mChannel, listener, nsnull, mPrincipal, method);
NS_ENSURE_TRUE(acListener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
listener = new nsCrossSiteListenerProxy(acListener, mPrincipal,
mACGetChannel, &rv);
NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
acProxyListener =
new nsCrossSiteListenerProxy(acProxyListener, mPrincipal, mACGetChannel,
method, mACUnsafeHeaders, &rv);
NS_ENSURE_TRUE(acProxyListener, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_SUCCESS(rv, rv);
rv = mACGetChannel->AsyncOpen(listener, nsnull);
rv = mACGetChannel->AsyncOpen(acProxyListener, nsnull);
}
else {
// Start reading from the channel
@ -2559,6 +2703,7 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
if (NS_FAILED(rv)) {
// Drop our ref to the channel to avoid cycles
mChannel = nsnull;
mACGetChannel = nsnull;
return rv;
}
@ -2600,6 +2745,11 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
{
nsresult rv;
// Make sure we don't store an invalid header name in mACUnsafeHeaders
if (!IsValidHTTPToken(header)) {
return NS_ERROR_FAILURE;
}
// Check that we haven't already opened the channel. We can't rely on
// the channel throwing from mChannel->SetRequestHeader since we might
// still be waiting for mACGetChannel to actually open mChannel
@ -2634,8 +2784,7 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
const char *kInvalidHeaders[] = {
"accept-charset", "accept-encoding", "connection", "content-length",
"content-transfer-encoding", "date", "expect", "host", "keep-alive",
"referer", "access-control-origin", "te", "trailer",
"transfer-encoding", "upgrade", "via", "xmlhttprequest-security-check"
"referer", "te", "trailer", "transfer-encoding", "upgrade", "via"
};
PRUint32 i;
for (i = 0; i < NS_ARRAY_LENGTH(kInvalidHeaders); ++i) {
@ -2656,7 +2805,7 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
PRBool safeHeader = !!(mState & XML_HTTP_REQUEST_XSITEENABLED);
if (!safeHeader) {
const char *kCrossOriginSafeHeaders[] = {
"accept", "accept-language"
"accept", "accept-language", "content-type"
};
for (i = 0; i < NS_ARRAY_LENGTH(kCrossOriginSafeHeaders); ++i) {
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
@ -2667,15 +2816,7 @@ nsXMLHttpRequest::SetRequestHeader(const nsACString& header,
}
if (!safeHeader) {
// The header is unsafe for cross-site requests. If this is a cross-site
// request throw an exception...
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
return NS_ERROR_FAILURE;
}
// ...otherwise just add it to mExtraRequestHeaders so that we can
// remove it in case we're redirected to another site
mExtraRequestHeaders.AppendElement(header);
mACUnsafeHeaders.AppendElement(header);
}
}
@ -2889,11 +3030,10 @@ nsXMLHttpRequest::OnChannelRedirect(nsIChannel *aOldChannel,
rv = CheckChannelForCrossSiteRequest(aNewChannel);
NS_ENSURE_SUCCESS(rv, rv);
// Disable redirects for non-get cross-site requests entirely for now
// Disable redirects for preflighted cross-site requests entirely for now
// Note, do this after the call to CheckChannelForCrossSiteRequest
// to make sure that XML_HTTP_REQUEST_USE_XSITE_AC is up-to-date
if ((mState & XML_HTTP_REQUEST_NON_GET) &&
(mState & XML_HTTP_REQUEST_USE_XSITE_AC)) {
if ((mState & XML_HTTP_REQUEST_NEED_AC_PREFLIGHT)) {
return NS_ERROR_DOM_BAD_URI;
}

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

@ -77,10 +77,17 @@ class nsILoadGroup;
class nsAccessControlLRUCache
{
public:
struct TokenTime
{
nsCString token;
PRTime expirationTime;
};
struct CacheEntry : public PRCList
{
CacheEntry(const nsACString& aKey, PRTime aValue)
: key(aKey), value(aValue)
CacheEntry(nsCString& aKey)
: mKey(aKey)
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache::CacheEntry);
}
@ -89,12 +96,16 @@ class nsAccessControlLRUCache
{
MOZ_COUNT_DTOR(nsAccessControlLRUCache::CacheEntry);
}
nsCString key;
PRTime value;
void PurgeExpired(PRTime now);
PRBool CheckRequest(const nsCString& aMethod,
const nsTArray<nsCString>& aCustomHeaders);
nsCString mKey;
nsTArray<TokenTime> mMethods;
nsTArray<TokenTime> mHeaders;
};
public:
nsAccessControlLRUCache()
{
MOZ_COUNT_CTOR(nsAccessControlLRUCache);
@ -112,17 +123,12 @@ public:
return mTable.Init();
}
void GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRTime* _retval);
void PutEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRTime aValue);
CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal,
PRBool aCreate);
void Clear();
private:
PRBool GetEntryInternal(const nsACString& aKey, CacheEntry** _retval);
PR_STATIC_CALLBACK(PLDHashOperator)
RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue,
void* aUserData);
@ -389,6 +395,7 @@ protected:
nsCOMPtr<nsIRequest> mReadRequest;
nsCOMPtr<nsIDOMDocument> mDocument;
nsCOMPtr<nsIChannel> mACGetChannel;
nsTArray<nsCString> mACUnsafeHeaders;
nsRefPtr<nsDOMEventListenerWrapper> mOnUploadProgressListener;
nsRefPtr<nsDOMEventListenerWrapper> mOnReadystatechangeListener;
@ -428,10 +435,6 @@ protected:
PRUint32 mState;
// List of potentially dangerous headers explicitly set using
// SetRequestHeader.
nsTArray<nsCString> mExtraRequestHeaders;
nsRefPtr<nsXMLHttpRequestUpload> mUpload;
PRUint32 mUploadTransferred;
PRUint32 mUploadTotal;

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

@ -217,6 +217,11 @@ _TEST_FILES = test_bug5141.html \
test_bug454326.html \
test_bug457746.html \
bug457746.sjs \
test_CrossSiteXHR.html \
file_CrossSiteXHR_inner.html \
file_CrossSiteXHR_server.sjs \
test_CrossSiteXHR_cache.html \
file_CrossSiteXHR_cache_server.sjs \
$(NULL)
libs:: $(_TEST_FILES)

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

@ -0,0 +1,45 @@
function d(s) { dump(s + "\n"); }
function handleRequest(request, response)
{
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
var isPreflight = request.method == "OPTIONS";
// Send response
response.setHeader("Access-Control-Allow-Origin", query.allowOrigin);
if (isPreflight) {
var secData = {};
if (request.hasHeader("Access-Control-Request-Headers")) {
var magicHeader =
request.getHeader("Access-Control-Request-Headers").split(",").
filter(function(name) /^magic-/.test(name))[0];
}
if (magicHeader) {
secData = eval(unescape(magicHeader.substr(6)));
secData.allowHeaders = (secData.allowHeaders || "") + "," + magicHeader;
}
if (secData.allowHeaders)
response.setHeader("Access-Control-Allow-Headers", secData.allowHeaders);
if (secData.allowMethods)
response.setHeader("Access-Control-Allow-Methods", secData.allowMethods);
if (secData.cacheTime)
response.setHeader("Access-Control-Max-Age", secData.cacheTime.toString());
return;
}
response.setHeader("Content-Type", "application/xml", false);
response.write("<res>hello pass</res>\n");
}

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

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<head>
<script>
window.addEventListener('message', function(e) {
sendData = null;
req = eval(e.data);
var res = {
didFail: false,
events: [],
progressEvents: 0
};
var xhr = new XMLHttpRequest();
for each(type in ["load", "abort", "error", "loadstart"]) {
xhr.addEventListener(type, function(e) {
res.events.push(e.type);
}, false);
}
xhr.addEventListener("readystatechange", function(e) {
res.events.push("rs" + xhr.readyState);
}, false);
xhr.addEventListener("progress", function(e) {
res.progressEvents++;
}, false);
xhr.onload = function () {
res.status = xhr.status;
res.responseXML = xhr.responseXML ?
(new XMLSerializer()).serializeToString(xhr.responseXML) :
null;
res.responseText = xhr.responseText;
post(e, res);
};
xhr.onerror = function () {
res.didFail = true;
res.status = xhr.status;
res.responseXML = xhr.responseXML ?
(new XMLSerializer()).serializeToString(xhr.responseXML) :
null;
res.responseText = xhr.responseText;
post(e, res);
}
res.events.push("opening");
xhr.open(req.method, req.url, true);
for (header in req.headers) {
xhr.setRequestHeader(header, req.headers[header]);
}
res.events.push("sending");
xhr.send(sendData);
}, false);
function post(e, res) {
e.source.postMessage(res.toSource(), "http://localhost:8888");
}
</script>
</head>
<body>
Inner page
</body>
</html>

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

@ -0,0 +1,63 @@
function handleRequest(request, response)
{
try {
var query = {};
request.queryString.split('&').forEach(function (val) {
var [name, value] = val.split('=');
query[name] = unescape(value);
});
var isPreflight = request.method == "OPTIONS";
// Check that request was correct
if (!isPreflight && "headers" in query) {
headers = eval(query.headers);
for(headerName in headers) {
if (request.getHeader(headerName) != headers[headerName]) {
throw "Header " + headerName + " had wrong value. Expected " +
headers[headerName] + " got " + request.getHeader(headerName);
}
}
}
if (isPreflight && "requestHeaders" in query &&
request.getHeader("Access-Control-Request-Headers") != query.requestHeaders) {
throw "Access-Control-Request-Headers had wrong value. Expected " +
query.requestHeaders + " got " +
request.getHeader("Access-Control-Request-Headers");
}
if (isPreflight && "requestMethod" in query &&
request.getHeader("Access-Control-Request-Method") != query.requestMethod) {
throw "Access-Control-Request-Method had wrong value. Expected " +
query.requestMethod + " got " +
request.getHeader("Access-Control-Request-Method");
}
if ("origin" in query && request.getHeader("Origin") != query.origin) {
throw "Origin had wrong value. Expected " + query.origin + " got " +
request.getHeader("Origin");
}
// Send response
if (query.allowOrigin && (!isPreflight || !query.noAllowPreflight))
response.setHeader("Access-Control-Allow-Origin", query.allowOrigin);
if (isPreflight) {
if (query.allowHeaders)
response.setHeader("Access-Control-Allow-Headers", query.allowHeaders);
if (query.allowMethods)
response.setHeader("Access-Control-Allow-Methods", query.allowMethods);
return;
}
response.setHeader("Content-Type", "application/xml", false);
response.write("<res>hello pass</res>\n");
} catch (e) {
dump(e + "\n");
throw e;
}
}

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

@ -0,0 +1,427 @@
<!DOCTYPE HTML>
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
<title>Test for Cross Site XMLHttpRequest</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="gen.next()">
<p id="display">
<iframe id=loader></iframe>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
var origins =
[['http://example.org'],
['http://example.org:80', 'http://example.org'],
['http://sub1.test1.example.org'],
['http://test2.example.org:8000'],
//['https://example.com:443'],
//['https://sub1.test1.example.com:443'],
['http://sub1.\xe4lt.example.org:8000', 'http://sub1.xn--lt-uia.example.org:8000'],
['http://sub2.\xe4lt.example.org', 'http://sub2.xn--lt-uia.example.org'],
['http://ex\xe4mple.test', 'http://xn--exmple-cua.test'],
['http://\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1.\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae',
'http://xn--hxajbheg2az3al.xn--jxalpdlp'],
];
window.addEventListener("message", function(e) {
gen.send(e.data);
}, false);
gen = runTest();
function runTest() {
var loader = document.getElementById('loader');
var loaderWindow = loader.contentWindow;
loader.onload = function () { gen.next() };
// Test preflight-less requests
baseURL = "http://localhost:8888/tests/content/base/test/" +
"file_CrossSiteXHR_server.sjs?";
for each(originPair in origins) {
origin = originPair[1] || originPair[0];
loader.src = originPair[0] + "/tests/content/base/test/file_CrossSiteXHR_inner.html";
yield;
port = /:\d+/;
passTests = [
origin,
"*",
" \t " + origin + "\t \t",
"\t \t* \t ",
];
failTests = [
"",
" ",
port.test(origin) ? origin.replace(port, "")
: origin + ":1234",
port.test(origin) ? origin.replace(port, ":")
: origin + ":",
origin + "/",
origin + "#",
origin + "?",
origin + "\\",
origin + "%",
origin + "@",
origin + "/hello",
"foo:bar@" + origin,
"* " + origin,
origin + " " + origin,
"allow <" + origin + ">",
"<" + origin + ">",
"<*>",
origin.substr(0, 5) == "https" ? origin.replace("https", "http")
: origin.replace("http", "https"),
origin.replace("://", "://www."),
origin.replace("://", ":// "),
origin.replace(/\/[^.]+\./, "/"),
];
for each(method in ["GET", "POST"]) {
var headers = method == "POST" ?
{ "Content-Type": "text/plain" } :
null;
for each(allowOrigin in passTests) {
req = {
url: baseURL +
"allowOrigin=" + escape(allowOrigin) +
"&origin=" + escape(origin),
method: method,
headers: headers,
};
loaderWindow.postMessage(req.toSource(), origin);
res = eval(yield);
is(res.didFail, false, "shouldn't have failed");
is(res.status, 200, "wrong status");
is(res.responseXML,
"<res>hello pass</res>",
"wrong responseXML in test for " + allowOrigin);
is(res.responseText, "<res>hello pass</res>\n",
"wrong responseText in test for " + allowOrigin);
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs3,rs4,load",
"wrong responseText in test for " + allowOrigin);
}
for each(allowOrigin in failTests) {
req = {
url: baseURL + "allowOrigin=" + escape(allowOrigin),
method: method,
headers: headers,
};
loaderWindow.postMessage(req.toSource(), origin);
res = eval(yield);
is(res.didFail, true, "should have failed for " + allowOrigin);
is(res.responseText, "", "should have no text for " + allowOrigin);
is(res.status, 0, "should have no status for " + allowOrigin);
is(res.responseXML, null, "should have no XML for " + allowOrigin);
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs4,error",
"wrong events in test for " + allowOrigin);
is(res.progressEvents, 0,
"wrong events in test for " + allowOrigin);
}
}
}
// Test preflighted requests
loader.src = "http://example.org/tests/content/base/test/file_CrossSiteXHR_inner.html";
origin = "http://example.org";
yield;
passTests = [{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue" },
allowHeaders: "x-my-header, second-header, third-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue" },
allowHeaders: "x-my-header,second-header,third-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue" },
allowHeaders: "x-my-header ,second-header ,third-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue",
"third-header": "thirdValue" },
allowHeaders: "x-my-header , second-header , third-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue" },
allowHeaders: ", x-my-header, , ,, second-header, , ",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"second-header": "secondValue" },
allowHeaders: "x-my-header, second-header, unused-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "X-My-Header",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header": "secondValue" },
allowHeaders: "x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
},
{ method: "GET",
headers: { "x-my%-header": "myValue" },
allowHeaders: "x-my%-header",
},
{ method: "GET",
headers: { "Content-Type": "baz/bin",
"Accept": "foo/bar",
"Accept-Language": "sv-SE" },
},
{ method: "POST",
headers: { "Content-Type": "text/plain" },
noAllowPreflight: 1,
},
{ method: "POST",
},
{ method: "POST",
headers: { "Content-Type": "foo/bar" },
},
{ method: "POST",
headers: { "Content-Type": "text/plain",
"Accept": "foo/bar",
"Accept-Language": "sv-SE" },
noAllowPreflight: 1,
},
{ method: "POST",
headers: { "Accept": "foo/bar",
"Accept-Language": "sv-SE",
"x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ method: "POST",
headers: { "Content-Type": "text/plain",
"x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ method: "POST",
headers: { "Content-Type": "foo/bar",
"x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ method: "POST",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
},
{ method: "POST",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, $_%",
},
{ method: "DELETE",
allowMethods: "DELETE",
},
{ method: "DELETE",
allowMethods: "POST, PUT, DELETE",
},
{ method: "DELETE",
allowMethods: "POST, DELETE, PUT",
},
{ method: "DELETE",
allowMethods: "DELETE, POST, PUT",
},
{ method: "DELETE",
allowMethods: "POST ,PUT ,DELETE",
},
{ method: "DELETE",
allowMethods: "POST,PUT,DELETE",
},
{ method: "DELETE",
allowMethods: "POST , PUT , DELETE",
},
{ method: "DELETE",
allowMethods: " ,, PUT ,, , , DELETE , ,",
},
];
failTests = [{ method: "GET",
headers: { "x-my-header": "myValue" },
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "",
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "y-my-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header y-my-header",
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, y-my-header z",
},
{ method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, y-my-he(ader",
},
{ method: "GET",
headers: { "x-my-header": "myValue",
"y-my-header": "secondValue" },
allowHeaders: "x-my-header",
},
{ method: "GET",
headers: { "x-my-header": "" },
},
{ method: "GET",
headers: { "x-my-header": "",
"y-my-header": "" },
allowHeaders: "x-my-header",
},
{ method: "POST",
noAllowPreflight: 1,
},
{ method: "POST",
headers: { "Content-Type": "foo/bar" },
noAllowPreflight: 1,
},
{ method: "DELETE",
},
{ method: "DELETE",
allowMethods: "",
},
{ method: "DELETE",
allowMethods: "PUT",
},
{ method: "DELETE",
allowMethods: "DELETEZ",
},
{ method: "DELETE",
allowMethods: "DELETE PUT",
},
{ method: "DELETE",
allowMethods: "DELETE, PUT Z",
},
{ method: "DELETE",
allowMethods: "DELETE, PU(T",
},
{ method: "DELETE",
allowMethods: "PUT DELETE",
},
{ method: "DELETE",
allowMethods: "PUT Z, DELETE",
},
{ method: "DELETE",
allowMethods: "PU(T, DELETE",
},
{ method: "MYMETHOD",
allowMethods: "myMethod",
},
{ method: "PUT",
allowMethods: "put",
},
];
for each(test in passTests) {
req = {
url: baseURL + "&allowOrigin=" + escape(origin) +
"&origin=" + escape(origin) +
"&requestMethod=" + test.method,
method: test.method,
headers: test.headers,
};
if (test.noAllowPreflight)
req.url += "&noAllowPreflight";
if ("headers" in test) {
req.url += "&headers=" + escape(test.headers.toSource());
reqHeaders =
escape([name for (name in test.headers)].filter(function(name)
name != "Content-Type" &&
name != "Accept" &&
name != "Accept-Language").join(","));
req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
}
if ("allowHeaders" in test)
req.url += "&allowHeaders=" + escape(test.allowHeaders);
if ("allowMethods" in test)
req.url += "&allowMethods=" + escape(test.allowMethods);
loaderWindow.postMessage(req.toSource(), origin);
res = eval(yield);
is(res.didFail, false,
"shouldn't have failed in test for " + test.toSource());
is(res.status, 200, "wrong status in test for " + test.toSource());
is(res.responseXML, "<res>hello pass</res>",
"wrong responseXML in test for " + test.toSource());
is(res.responseText, "<res>hello pass</res>\n",
"wrong responseText in test for " + test.toSource());
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs3,rs4,load",
"wrong responseText in test for " + test.toSource());
}
for each(test in failTests) {
req = {
url: baseURL + "allowOrigin=" + escape(origin),
method: test.method,
headers: test.headers,
};
if (test.noAllowPreflight)
req.url += "&noAllowPreflight";
if ("allowHeaders" in test)
req.url += "&allowHeaders=" + escape(test.allowHeaders);
if ("allowMethods" in test)
req.url += "&allowMethods=" + escape(test.allowMethods);
loaderWindow.postMessage(req.toSource(), origin);
res = eval(yield);
is(res.didFail, true,
"should have failed in test for " + test.toSource());
is(res.status, 0, "wrong status in test for " + test.toSource());
is(res.responseXML, null,
"wrong responseXML in test for " + test.toSource());
is(res.responseText, "",
"wrong responseText in test for " + test.toSource());
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs4,error",
"wrong events in test for " + test.toSource());
is(res.progressEvents, 0,
"wrong events in test for " + test.toSource());
}
SimpleTest.finish();
yield;
}
</script>
</pre>
</body>
</html>

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

@ -0,0 +1,356 @@
<!DOCTYPE HTML>
<html>
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
<title>Test for Cross Site XMLHttpRequest</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="gen.next()">
<p id="display">
<iframe id=loader></iframe>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
window.addEventListener("message", function(e) {
gen.send(e.data);
}, false);
gen = runTest();
function runTest() {
var loader = document.getElementById('loader');
var loaderWindow = loader.contentWindow;
loader.onload = function () { gen.next() };
loader.src = "http://example.org/tests/content/base/test/file_CrossSiteXHR_inner.html";
origin = "http://example.org";
yield;
tests = [{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
cacheTime: 3600
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue",
"y-my-header": "second" },
},
{ pass: 1,
method: "GET",
headers: { "y-my-header": "hello" },
allowHeaders: "y-my-header",
},
{ pass: 0,
method: "GET",
headers: { "y-my-header": "hello" },
},
{ pass: 1,
method: "GET",
headers: { "y-my-header": "hello" },
allowHeaders: "y-my-header",
cacheTime: 3600,
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue",
"y-my-header": "second" },
},
{ newTest: "*******" },
{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
cacheTime: 2
},
{ pause: 2.1 },
{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header, y-my-header",
cacheTime: 3600
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "y-my-header": "myValue" },
},
{ pass: 0,
method: "GET",
headers: { "z-my-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
cacheTime: "\t 3600 \t ",
},
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
cacheTime: "3600 3",
},
{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 1,
method: "GET",
headers: { "x-my-header": "myValue" },
allowHeaders: "x-my-header",
cacheTime: "asdf",
},
{ pass: 0,
method: "GET",
headers: { "x-my-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 1,
method: "GET",
headers: { "first-header": "myValue" },
allowHeaders: "first-header",
cacheTime: 2,
},
{ pass: 1,
method: "GET",
headers: { "second-header": "myValue" },
allowHeaders: "second-header",
cacheTime: 3600,
},
{ pass: 0,
method: "GET",
headers: { "third-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "third-header": "myValue" },
allowHeaders: "third-header",
cacheTime: 2,
},
{ pause: 2.1 },
{ pass: 0,
method: "GET",
headers: { "first-header": "myValue" },
},
{ pass: 1,
method: "GET",
headers: { "second-header": "myValue" },
},
{ pass: 0,
method: "GET",
headers: { "third-header": "myValue" },
},
{ newTest: "*******" },
{ pass: 0,
method: "DELETE",
},
{ pass: 1,
method: "DELETE",
allowMethods: "DELETE",
cacheTime: 3600
},
{ pass: 1,
method: "DELETE",
},
{ pass: 1,
method: "DELETE",
},
{ pass: 0,
method: "PATCH",
},
{ pass: 1,
method: "PATCH",
allowMethods: "PATCH",
},
{ pass: 0,
method: "PATCH",
},
{ pass: 1,
method: "PATCH",
allowMethods: "PATCH",
cacheTime: 3600,
},
{ pass: 1,
method: "PATCH",
},
{ pass: 1,
method: "DELETE",
},
{ pass: 0,
method: "PUT",
},
{ newTest: "*******" },
{ pass: 0,
method: "DELETE",
},
{ pass: 1,
method: "DELETE",
allowMethods: "DELETE",
cacheTime: 2
},
{ pause: 2.1 },
{ pass: 0,
method: "DELETE",
},
{ newTest: "*******" },
{ pass: 1,
method: "DELETE",
allowMethods: "DELETE, PUT",
cacheTime: 3600
},
{ pass: 1,
method: "DELETE",
},
{ pass: 1,
method: "PUT",
},
{ pass: 0,
method: "PATCH",
},
{ newTest: "*******" },
{ pass: 1,
method: "FIRST",
allowMethods: "FIRST",
cacheTime: 2,
},
{ pass: 1,
method: "SECOND",
allowMethods: "SECOND",
cacheTime: 3600,
},
{ pass: 0,
method: "THIRD",
},
{ pass: 1,
method: "THIRD",
allowMethods: "THIRD",
cacheTime: 2,
},
{ pause: 2.1 },
{ pass: 0,
method: "FIRST",
},
{ pass: 1,
method: "SECOND",
},
{ pass: 0,
method: "THIRD",
},
];
baseURL = "http://localhost:8888/tests/content/base/test/" +
"file_CrossSiteXHR_cache_server.sjs?";
var unique = Date.now();
for each (test in tests) {
if (test.newTest) {
unique++;
continue;
}
if (test.pause) {
setTimeout(function() { gen.next() }, test.pause * 1000);
yield;
continue;
}
req = {
url: baseURL + "c=" + unique +
"&allowOrigin=" + escape(origin),
method: test.method,
headers: test.headers,
};
if (test.cacheTime || test.allowHeaders || test.allowMethods) {
sec = { allowHeaders: test.allowHeaders,
allowMethods: test.allowMethods,
cacheTime: test.cacheTime };
req.headers = req.headers || {};
req.headers["magic-" + escape(sec.toSource())] = "";
}
loaderWindow.postMessage(req.toSource(), origin);
res = eval(yield);
testName = test.toSource() + " (index " + tests.indexOf(test) + ")";
if (test.pass) {
is(res.didFail, false,
"shouldn't have failed in test for " + testName);
is(res.status, 200, "wrong status in test for " + testName);
is(res.responseXML, "<res>hello pass</res>",
"wrong responseXML in test for " + testName);
is(res.responseText, "<res>hello pass</res>\n",
"wrong responseText in test for " + testName);
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs3,rs4,load",
"wrong events in test for " + testName);
}
else {
is(res.didFail, true,
"should have failed in test for " + testName);
is(res.status, 0, "wrong status in test for " + testName);
is(res.responseXML, null,
"wrong responseXML in test for " + testName);
is(res.responseText, "",
"wrong responseText in test for " + testName);
is(res.events.join(","),
"opening,rs1,sending,rs1,loadstart,rs2,rs4,error",
"wrong events in test for " + testName);
is(res.progressEvents, 0,
"wrong events in test for " + testName);
}
}
SimpleTest.finish();
yield;
}
</script>
</pre>
</body>
</html>

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

@ -1,4 +1,4 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
@ -79,6 +79,15 @@ public:
PRUnichar) const;
};
class nsCaseInsensitiveStringArrayComparator
{
public:
template<class A, class B>
PRBool Equals(const A& a, const B& b) const {
return a.Equals(b, nsCaseInsensitiveStringComparator());
}
};
inline PRBool
CaseInsensitiveFindInReadable(const nsAString& aPattern,
nsAString::const_iterator& aSearchStart,

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

@ -2298,12 +2298,13 @@ ServerHandler.prototype =
Ci.nsIFileInputStream.CLOSE_ON_EOF);
var sis = new ScriptableInputStream(fis);
var s = Cu.Sandbox(gGlobalObject);
s.importFunction(dump, "dump");
Cu.evalInSandbox(sis.read(file.fileSize), s);
s.handleRequest(metadata, response);
}
catch (e)
{
dumpn("*** error running SJS: " + e);
dump("*** error running SJS: " + e + " on line " + (e.lineNumber-2192) + "\n");
throw HTTP_500;
}
}

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

@ -104,6 +104,7 @@ EXPORTS = \
nsStringEnumerator.h \
nsHashPropertyBag.h \
nsWhitespaceTokenizer.h \
nsCommaseparatedTokenizer.h \
$(NULL)
XPIDLSRCS = \

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

@ -0,0 +1,193 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is TransforMiiX XSLT processor code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Peter Van der Beken <peterv@propagandism.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef __nsCommaSeparatedTokenizer_h
#define __nsCommaSeparatedTokenizer_h
#include "nsDependentSubstring.h"
/**
* This parses a comma separated string into tokens. Whitespace surrounding
* tokens are not treated as part of tokens, however whitespace inside a token
* is. If the final token is the empty string it is not returned.
*
* Some examples:
*
* "foo, bar, baz" -> "foo" "bar" "baz"
* "foo,bar,baz" -> "foo" "bar" "baz"
* "foo , bar hi , baz" -> "foo" "bar hi" "baz"
* "foo, ,bar,baz" -> "foo" "" "bar" "baz"
* "foo,,bar,baz" -> "foo" "" "bar" "baz"
* "foo,bar,baz," -> "foo" "bar" "baz"
*/
class nsCommaSeparatedTokenizer
{
public:
nsCommaSeparatedTokenizer(const nsSubstring& aSource)
{
aSource.BeginReading(mIter);
aSource.EndReading(mEnd);
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
/**
* Checks if any more tokens are available.
*/
PRBool hasMoreTokens()
{
NS_ASSERTION(mIter == mEnd || !isWhitespace(*mIter),
"Should be at beginning of token if there is one");
return mIter != mEnd;
}
/**
* Returns the next token.
*/
const nsDependentSubstring nextToken()
{
nsSubstring::const_char_iterator end = mIter, begin = mIter;
NS_ASSERTION(mIter == mEnd || !isWhitespace(*mIter),
"Should be at beginning of token if there is one");
// Search until we hit comma or end
while (mIter != mEnd && *mIter != ',') {
while (mIter != mEnd && !isWhitespace(*mIter) && *mIter != ',') {
++mIter;
}
end = mIter;
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
// Skip comma
if (mIter != mEnd) {
NS_ASSERTION(*mIter == ',', "Ended loop too soon");
++mIter;
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
return Substring(begin, end);
}
private:
nsSubstring::const_char_iterator mIter, mEnd;
PRBool isWhitespace(PRUnichar aChar)
{
return aChar <= ' ' &&
(aChar == ' ' || aChar == '\n' ||
aChar == '\r'|| aChar == '\t');
}
};
class nsCCommaSeparatedTokenizer
{
public:
nsCCommaSeparatedTokenizer(const nsCSubstring& aSource)
{
aSource.BeginReading(mIter);
aSource.EndReading(mEnd);
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
/**
* Checks if any more tokens are available.
*/
PRBool hasMoreTokens()
{
return mIter != mEnd;
}
/**
* Returns the next token.
*/
const nsDependentCSubstring nextToken()
{
nsCSubstring::const_char_iterator end = mIter, begin = mIter;
// Search until we hit comma or end
while (mIter != mEnd && *mIter != ',') {
while (mIter != mEnd && !isWhitespace(*mIter) && *mIter != ',') {
++mIter;
}
end = mIter;
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
// Skip comma
if (mIter != mEnd) {
NS_ASSERTION(*mIter == ',', "Ended loop too soon");
++mIter;
while (mIter != mEnd && isWhitespace(*mIter)) {
++mIter;
}
}
return Substring(begin, end);
}
private:
nsCSubstring::const_char_iterator mIter, mEnd;
PRBool isWhitespace(unsigned char aChar)
{
return aChar <= ' ' &&
(aChar == ' ' || aChar == '\n' ||
aChar == '\r'|| aChar == '\t');
}
};
#endif /* __nsWhitespaceTokenizer_h */

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

@ -84,6 +84,14 @@ class NS_COM nsCaseInsensitiveCStringComparator
virtual int operator()( char_type, char_type ) const;
};
class nsCaseInsensitiveCStringArrayComparator
{
public:
template<class A, class B>
PRBool Equals(const A& a, const B& b) const {
return a.Equals(b, nsCaseInsensitiveCStringComparator());
}
};
// included here for backwards compatibility
#ifndef nsSubstringTuple_h___