зеркало из https://github.com/mozilla/gecko-dev.git
Update Access-Control implementation to match changes in spec. b=389508 r/sr=jst
This commit is contained in:
Родитель
626dad6df0
Коммит
69b9ff0371
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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___
|
||||
|
|
Загрузка…
Ссылка в новой задаче