michael@0: /* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/LinkedList.h" michael@0: michael@0: #include "nsCrossSiteListenerProxy.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsError.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsWhitespaceTokenizer.h" michael@0: #include "nsIChannelEventSink.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "nsAsyncRedirectVerifyHelper.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsILoadGroup.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "nsIDOMWindowUtils.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define PREFLIGHT_CACHE_SIZE 100 michael@0: michael@0: static bool gDisableCORS = false; michael@0: static bool gDisableCORSPrivateData = false; michael@0: michael@0: static nsresult michael@0: LogBlockedRequest(nsIRequest* aRequest) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Get the innerWindowID associated with the XMLHTTPRequest michael@0: PRUint64 innerWindowID = 0; michael@0: michael@0: nsCOMPtr loadGroup; michael@0: aRequest->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (loadGroup) { michael@0: nsCOMPtr callbacks; michael@0: loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (callbacks) { michael@0: nsCOMPtr loadContext = do_GetInterface(callbacks); michael@0: if(loadContext) { michael@0: nsCOMPtr window; michael@0: loadContext->GetAssociatedWindow(getter_AddRefs(window)); michael@0: if (window) { michael@0: nsCOMPtr du = do_GetInterface(window); michael@0: du->GetCurrentInnerWindowID(&innerWindowID); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!innerWindowID) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: nsCOMPtr aUri; michael@0: channel->GetURI(getter_AddRefs(aUri)); michael@0: nsAutoCString spec; michael@0: if (aUri) { michael@0: aUri->GetSpec(spec); michael@0: } michael@0: michael@0: // Generate the error message michael@0: nsXPIDLString blockedMessage; michael@0: NS_ConvertUTF8toUTF16 specUTF16(spec); michael@0: const char16_t* params[] = { specUTF16.get() }; michael@0: rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES, michael@0: "CrossSiteRequestBlocked", michael@0: params, michael@0: blockedMessage); michael@0: michael@0: // Build the error object and log it to the console michael@0: nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString msg(blockedMessage.get()); michael@0: rv = scriptError->InitWithWindowID(msg, michael@0: NS_ConvertUTF8toUTF16(spec), michael@0: EmptyString(), michael@0: 0, michael@0: 0, michael@0: nsIScriptError::warningFlag, michael@0: "CORS", michael@0: innerWindowID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = console->LogMessage(scriptError); michael@0: return rv; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: // Preflight cache michael@0: michael@0: class nsPreflightCache michael@0: { michael@0: public: michael@0: struct TokenTime michael@0: { michael@0: nsCString token; michael@0: TimeStamp expirationTime; michael@0: }; michael@0: michael@0: struct CacheEntry : public LinkedListElement michael@0: { michael@0: CacheEntry(nsCString& aKey) michael@0: : mKey(aKey) michael@0: { michael@0: MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry); michael@0: } michael@0: michael@0: ~CacheEntry() michael@0: { michael@0: MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); michael@0: } michael@0: michael@0: void PurgeExpired(TimeStamp now); michael@0: bool CheckRequest(const nsCString& aMethod, michael@0: const nsTArray& aCustomHeaders); michael@0: michael@0: nsCString mKey; michael@0: nsTArray mMethods; michael@0: nsTArray mHeaders; michael@0: }; michael@0: michael@0: nsPreflightCache() michael@0: { michael@0: MOZ_COUNT_CTOR(nsPreflightCache); michael@0: } michael@0: michael@0: ~nsPreflightCache() michael@0: { michael@0: Clear(); michael@0: MOZ_COUNT_DTOR(nsPreflightCache); michael@0: } michael@0: michael@0: bool Initialize() michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal, michael@0: bool aWithCredentials, bool aCreate); michael@0: void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal); michael@0: michael@0: void Clear(); michael@0: michael@0: private: michael@0: static PLDHashOperator michael@0: RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr& aValue, michael@0: void* aUserData); michael@0: michael@0: static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal, michael@0: bool aWithCredentials, nsACString& _retval); michael@0: michael@0: nsClassHashtable mTable; michael@0: LinkedList mList; michael@0: }; michael@0: michael@0: // Will be initialized in EnsurePreflightCache. michael@0: static nsPreflightCache* sPreflightCache = nullptr; michael@0: michael@0: static bool EnsurePreflightCache() michael@0: { michael@0: if (sPreflightCache) michael@0: return true; michael@0: michael@0: nsAutoPtr newCache(new nsPreflightCache()); michael@0: michael@0: if (newCache->Initialize()) { michael@0: sPreflightCache = newCache.forget(); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now) michael@0: { michael@0: uint32_t i; michael@0: for (i = 0; i < mMethods.Length(); ++i) { michael@0: if (now >= mMethods[i].expirationTime) { michael@0: mMethods.RemoveElementAt(i--); michael@0: } michael@0: } michael@0: for (i = 0; i < mHeaders.Length(); ++i) { michael@0: if (now >= mHeaders[i].expirationTime) { michael@0: mHeaders.RemoveElementAt(i--); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod, michael@0: const nsTArray& aHeaders) michael@0: { michael@0: PurgeExpired(TimeStamp::NowLoRes()); michael@0: michael@0: if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) { michael@0: uint32_t i; michael@0: for (i = 0; i < mMethods.Length(); ++i) { michael@0: if (aMethod.Equals(mMethods[i].token)) michael@0: break; michael@0: } michael@0: if (i == mMethods.Length()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < aHeaders.Length(); ++i) { michael@0: uint32_t j; michael@0: for (j = 0; j < mHeaders.Length(); ++j) { michael@0: if (aHeaders[i].Equals(mHeaders[j].token, michael@0: nsCaseInsensitiveCStringComparator())) { michael@0: break; michael@0: } michael@0: } michael@0: if (j == mHeaders.Length()) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsPreflightCache::CacheEntry* michael@0: nsPreflightCache::GetEntry(nsIURI* aURI, michael@0: nsIPrincipal* aPrincipal, michael@0: bool aWithCredentials, michael@0: bool aCreate) michael@0: { michael@0: nsCString key; michael@0: if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) { michael@0: NS_WARNING("Invalid cache key!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: CacheEntry* entry; michael@0: michael@0: if (mTable.Get(key, &entry)) { michael@0: // Entry already existed so just return it. Also update the LRU list. michael@0: michael@0: // Move to the head of the list. michael@0: entry->removeFrom(mList); michael@0: mList.insertFront(entry); michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: if (!aCreate) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // This is a new entry, allocate and insert into the table now so that any michael@0: // failures don't cause items to be removed from a full cache. michael@0: entry = new CacheEntry(key); michael@0: if (!entry) { michael@0: NS_WARNING("Failed to allocate new cache entry!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE, michael@0: "Something is borked, too many entries in the cache!"); michael@0: michael@0: // Now enforce the max count. michael@0: if (mTable.Count() == PREFLIGHT_CACHE_SIZE) { michael@0: // Try to kick out all the expired entries. michael@0: TimeStamp now = TimeStamp::NowLoRes(); michael@0: mTable.Enumerate(RemoveExpiredEntries, &now); michael@0: michael@0: // If that didn't remove anything then kick out the least recently used michael@0: // entry. michael@0: if (mTable.Count() == PREFLIGHT_CACHE_SIZE) { michael@0: CacheEntry* lruEntry = static_cast(mList.popLast()); michael@0: MOZ_ASSERT(lruEntry); michael@0: michael@0: // This will delete 'lruEntry'. michael@0: mTable.Remove(lruEntry->mKey); michael@0: michael@0: NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1, michael@0: "Somehow tried to remove an entry that was never added!"); michael@0: } michael@0: } michael@0: michael@0: mTable.Put(key, entry); michael@0: mList.insertFront(entry); michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: void michael@0: nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal) michael@0: { michael@0: CacheEntry* entry; michael@0: nsCString key; michael@0: if (GetCacheKey(aURI, aPrincipal, true, key) && michael@0: mTable.Get(key, &entry)) { michael@0: entry->removeFrom(mList); michael@0: mTable.Remove(key); michael@0: } michael@0: michael@0: if (GetCacheKey(aURI, aPrincipal, false, key) && michael@0: mTable.Get(key, &entry)) { michael@0: entry->removeFrom(mList); michael@0: mTable.Remove(key); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsPreflightCache::Clear() michael@0: { michael@0: mList.clear(); michael@0: mTable.Clear(); michael@0: } michael@0: michael@0: /* static */ PLDHashOperator michael@0: nsPreflightCache::RemoveExpiredEntries(const nsACString& aKey, michael@0: nsAutoPtr& aValue, michael@0: void* aUserData) michael@0: { michael@0: TimeStamp* now = static_cast(aUserData); michael@0: michael@0: aValue->PurgeExpired(*now); michael@0: michael@0: if (aValue->mHeaders.IsEmpty() && michael@0: aValue->mMethods.IsEmpty()) { michael@0: // Expired, remove from the list as well as the hash table. michael@0: aValue->removeFrom(sPreflightCache->mList); michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsPreflightCache::GetCacheKey(nsIURI* aURI, michael@0: nsIPrincipal* aPrincipal, michael@0: bool aWithCredentials, michael@0: nsACString& _retval) michael@0: { michael@0: NS_ASSERTION(aURI, "Null uri!"); michael@0: NS_ASSERTION(aPrincipal, "Null principal!"); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(space, " "); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: nsAutoCString scheme, host, port; michael@0: if (uri) { michael@0: uri->GetScheme(scheme); michael@0: uri->GetHost(host); michael@0: port.AppendInt(NS_GetRealPort(uri)); michael@0: } michael@0: michael@0: nsAutoCString cred; michael@0: if (aWithCredentials) { michael@0: _retval.AssignLiteral("cred"); michael@0: } michael@0: else { michael@0: _retval.AssignLiteral("nocred"); michael@0: } michael@0: michael@0: nsAutoCString spec; michael@0: rv = aURI->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: _retval.Assign(cred + space + scheme + space + host + space + port + space + michael@0: spec); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: // nsCORSListenerProxy michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, michael@0: nsIRequestObserver, nsIChannelEventSink, michael@0: nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) michael@0: michael@0: /* static */ michael@0: void michael@0: nsCORSListenerProxy::Startup() michael@0: { michael@0: Preferences::AddBoolVarCache(&gDisableCORS, michael@0: "content.cors.disable"); michael@0: Preferences::AddBoolVarCache(&gDisableCORSPrivateData, michael@0: "content.cors.no_private_data"); michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: nsCORSListenerProxy::Shutdown() michael@0: { michael@0: delete sPreflightCache; michael@0: sPreflightCache = nullptr; michael@0: } michael@0: michael@0: nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter, michael@0: nsIPrincipal* aRequestingPrincipal, michael@0: bool aWithCredentials) michael@0: : mOuterListener(aOuter), michael@0: mRequestingPrincipal(aRequestingPrincipal), michael@0: mOriginHeaderPrincipal(aRequestingPrincipal), michael@0: mWithCredentials(aWithCredentials && !gDisableCORSPrivateData), michael@0: mRequestApproved(false), michael@0: mHasBeenCrossSite(false), michael@0: mIsPreflight(false) michael@0: { michael@0: } michael@0: michael@0: nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter, michael@0: nsIPrincipal* aRequestingPrincipal, michael@0: bool aWithCredentials, michael@0: const nsCString& aPreflightMethod, michael@0: const nsTArray& aPreflightHeaders) michael@0: : mOuterListener(aOuter), michael@0: mRequestingPrincipal(aRequestingPrincipal), michael@0: mOriginHeaderPrincipal(aRequestingPrincipal), michael@0: mWithCredentials(aWithCredentials && !gDisableCORSPrivateData), michael@0: mRequestApproved(false), michael@0: mHasBeenCrossSite(false), michael@0: mIsPreflight(true), michael@0: mPreflightMethod(aPreflightMethod), michael@0: mPreflightHeaders(aPreflightHeaders) michael@0: { michael@0: for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { michael@0: ToLowerCase(mPreflightHeaders[i]); michael@0: } michael@0: mPreflightHeaders.Sort(); michael@0: } michael@0: michael@0: nsresult michael@0: nsCORSListenerProxy::Init(nsIChannel* aChannel, bool aAllowDataURI) michael@0: { michael@0: aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks)); michael@0: aChannel->SetNotificationCallbacks(this); michael@0: michael@0: nsresult rv = UpdateChannel(aChannel, aAllowDataURI); michael@0: if (NS_FAILED(rv)) { michael@0: mOuterListener = nullptr; michael@0: mRequestingPrincipal = nullptr; michael@0: mOriginHeaderPrincipal = nullptr; michael@0: mOuterNotificationCallbacks = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext) michael@0: { michael@0: nsresult rv = CheckRequestApproved(aRequest); michael@0: mRequestApproved = NS_SUCCEEDED(rv); michael@0: if (!mRequestApproved) { michael@0: rv = LogBlockedRequest(aRequest); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request"); michael@0: if (sPreflightCache) { michael@0: nsCOMPtr channel = do_QueryInterface(aRequest); michael@0: if (channel) { michael@0: nsCOMPtr uri; michael@0: NS_GetFinalChannelURI(channel, getter_AddRefs(uri)); michael@0: if (uri) { michael@0: // OK to use mRequestingPrincipal since preflights never get michael@0: // redirected. michael@0: sPreflightCache->RemoveEntries(uri, mRequestingPrincipal); michael@0: } michael@0: } michael@0: } michael@0: michael@0: aRequest->Cancel(NS_ERROR_DOM_BAD_URI); michael@0: mOuterListener->OnStartRequest(aRequest, aContext); michael@0: michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: return mOuterListener->OnStartRequest(aRequest, aContext); michael@0: } michael@0: michael@0: bool michael@0: IsValidHTTPToken(const nsCSubstring& aToken) michael@0: { michael@0: if (aToken.IsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: nsCSubstring::const_char_iterator iter, end; michael@0: michael@0: aToken.BeginReading(iter); michael@0: aToken.EndReading(end); michael@0: michael@0: while (iter != end) { michael@0: if (*iter <= 32 || michael@0: *iter >= 127 || michael@0: *iter == '(' || michael@0: *iter == ')' || michael@0: *iter == '<' || michael@0: *iter == '>' || michael@0: *iter == '@' || michael@0: *iter == ',' || michael@0: *iter == ';' || michael@0: *iter == ':' || michael@0: *iter == '\\' || michael@0: *iter == '\"' || michael@0: *iter == '/' || michael@0: *iter == '[' || michael@0: *iter == ']' || michael@0: *iter == '?' || michael@0: *iter == '=' || michael@0: *iter == '{' || michael@0: *iter == '}') { michael@0: return false; michael@0: } michael@0: ++iter; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest) michael@0: { michael@0: // Check if this was actually a cross domain request michael@0: if (!mHasBeenCrossSite) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (gDisableCORS) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: // Check if the request failed michael@0: nsresult status; michael@0: nsresult rv = aRequest->GetStatus(&status); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_SUCCESS(status, status); michael@0: michael@0: // Test that things worked on a HTTP level michael@0: nsCOMPtr http = do_QueryInterface(aRequest); michael@0: NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI); michael@0: michael@0: // Check the Access-Control-Allow-Origin header michael@0: nsAutoCString allowedOriginHeader; michael@0: rv = http->GetResponseHeader( michael@0: NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) { michael@0: nsAutoCString origin; michael@0: rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!allowedOriginHeader.Equals(origin)) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: } michael@0: michael@0: // Check Access-Control-Allow-Credentials header michael@0: if (mWithCredentials) { michael@0: nsAutoCString allowCredentialsHeader; michael@0: rv = http->GetResponseHeader( michael@0: NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!allowCredentialsHeader.EqualsLiteral("true")) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: } michael@0: michael@0: if (mIsPreflight) { michael@0: bool succeedded; michael@0: rv = http->GetRequestSucceeded(&succeedded); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!succeedded) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: nsAutoCString headerVal; michael@0: // The "Access-Control-Allow-Methods" header contains a comma separated michael@0: // list of method names. michael@0: http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), michael@0: headerVal); michael@0: bool foundMethod = mPreflightMethod.EqualsLiteral("GET") || michael@0: mPreflightMethod.EqualsLiteral("HEAD") || michael@0: mPreflightMethod.EqualsLiteral("POST"); michael@0: nsCCharSeparatedTokenizer methodTokens(headerVal, ','); michael@0: while(methodTokens.hasMoreTokens()) { michael@0: const nsDependentCSubstring& method = methodTokens.nextToken(); michael@0: if (method.IsEmpty()) { michael@0: continue; michael@0: } michael@0: if (!IsValidHTTPToken(method)) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: foundMethod |= mPreflightMethod.Equals(method); michael@0: } michael@0: NS_ENSURE_TRUE(foundMethod, NS_ERROR_DOM_BAD_URI); michael@0: michael@0: // The "Access-Control-Allow-Headers" header contains a comma separated michael@0: // list of header names. michael@0: headerVal.Truncate(); michael@0: http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), michael@0: headerVal); michael@0: nsTArray headers; michael@0: nsCCharSeparatedTokenizer headerTokens(headerVal, ','); michael@0: while(headerTokens.hasMoreTokens()) { michael@0: const nsDependentCSubstring& header = headerTokens.nextToken(); michael@0: if (header.IsEmpty()) { michael@0: continue; michael@0: } michael@0: if (!IsValidHTTPToken(header)) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: headers.AppendElement(header); michael@0: } michael@0: for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { michael@0: if (!headers.Contains(mPreflightHeaders[i], michael@0: nsCaseInsensitiveCStringArrayComparator())) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsresult aStatusCode) michael@0: { michael@0: nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode); michael@0: mOuterListener = nullptr; michael@0: mOuterNotificationCallbacks = nullptr; michael@0: mRedirectCallback = nullptr; michael@0: mOldRedirectChannel = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsIInputStream* aInputStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: if (!mRequestApproved) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream, michael@0: aOffset, aCount); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: *aResult = static_cast(this); michael@0: NS_ADDREF_THIS(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mOuterNotificationCallbacks ? michael@0: mOuterNotificationCallbacks->GetInterface(aIID, aResult) : michael@0: NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *cb) michael@0: { michael@0: nsresult rv; michael@0: if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { michael@0: rv = CheckRequestApproved(aOldChannel); michael@0: if (NS_FAILED(rv)) { michael@0: rv = LogBlockedRequest(aOldChannel); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request"); michael@0: michael@0: if (sPreflightCache) { michael@0: nsCOMPtr oldURI; michael@0: NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI)); michael@0: if (oldURI) { michael@0: // OK to use mRequestingPrincipal since preflights never get michael@0: // redirected. michael@0: sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal); michael@0: } michael@0: } michael@0: aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: if (mHasBeenCrossSite) { michael@0: // Once we've been cross-site, cross-origin redirects reset our source michael@0: // origin. michael@0: nsCOMPtr oldChannelPrincipal; michael@0: nsContentUtils::GetSecurityManager()-> michael@0: GetChannelPrincipal(aOldChannel, getter_AddRefs(oldChannelPrincipal)); michael@0: nsCOMPtr newChannelPrincipal; michael@0: nsContentUtils::GetSecurityManager()-> michael@0: GetChannelPrincipal(aNewChannel, getter_AddRefs(newChannelPrincipal)); michael@0: if (!oldChannelPrincipal || !newChannelPrincipal) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: bool equal; michael@0: rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (!equal) { michael@0: // Spec says to set our source origin to a unique origin. michael@0: mOriginHeaderPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); michael@0: if (!mOriginHeaderPrincipal) { michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aOldChannel->Cancel(rv); michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Prepare to receive callback michael@0: mRedirectCallback = cb; michael@0: mOldRedirectChannel = aOldChannel; michael@0: mNewRedirectChannel = aNewChannel; michael@0: michael@0: nsCOMPtr outer = michael@0: do_GetInterface(mOuterNotificationCallbacks); michael@0: if (outer) { michael@0: rv = outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, this); michael@0: if (NS_FAILED(rv)) { michael@0: aOldChannel->Cancel(rv); // is this necessary...? michael@0: mRedirectCallback = nullptr; michael@0: mOldRedirectChannel = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: (void) OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); michael@0: NS_ASSERTION(mOldRedirectChannel, "mOldRedirectChannel not set in callback"); michael@0: NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); michael@0: michael@0: if (NS_SUCCEEDED(result)) { michael@0: nsresult rv = UpdateChannel(mNewRedirectChannel); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("nsCORSListenerProxy::OnRedirectVerifyCallback: " michael@0: "UpdateChannel() returned failure"); michael@0: } michael@0: result = rv; michael@0: } michael@0: michael@0: if (NS_FAILED(result)) { michael@0: mOldRedirectChannel->Cancel(result); michael@0: } michael@0: michael@0: mOldRedirectChannel = nullptr; michael@0: mNewRedirectChannel = nullptr; michael@0: mRedirectCallback->OnRedirectVerifyCallback(result); michael@0: mRedirectCallback = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI) michael@0: { michael@0: nsCOMPtr uri, originalURI; michael@0: nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // exempt data URIs from the same origin check. michael@0: if (aAllowDataURI && originalURI == uri) { michael@0: bool dataScheme = false; michael@0: rv = uri->SchemeIs("data", &dataScheme); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (dataScheme) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // Check that the uri is ok to load michael@0: rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckLoadURIWithPrincipal(mRequestingPrincipal, uri, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (originalURI != uri) { michael@0: rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (!mHasBeenCrossSite && michael@0: NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) && michael@0: (originalURI == uri || michael@0: NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI, michael@0: false, false)))) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // It's a cross site load michael@0: mHasBeenCrossSite = true; michael@0: michael@0: nsCString userpass; michael@0: uri->GetUserPass(userpass); michael@0: NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI); michael@0: michael@0: // Add the Origin header michael@0: nsAutoCString origin; michael@0: rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr http = do_QueryInterface(aChannel); michael@0: NS_ENSURE_TRUE(http, NS_ERROR_FAILURE); michael@0: michael@0: rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add preflight headers if this is a preflight request michael@0: if (mIsPreflight) { michael@0: rv = http-> michael@0: SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"), michael@0: mPreflightMethod, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mPreflightHeaders.IsEmpty()) { michael@0: nsAutoCString headers; michael@0: for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { michael@0: if (i != 0) { michael@0: headers += ','; michael@0: } michael@0: headers += mPreflightHeaders[i]; michael@0: } michael@0: rv = http-> michael@0: SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"), michael@0: headers, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: // Make cookie-less if needed michael@0: if (mIsPreflight || !mWithCredentials) { michael@0: nsLoadFlags flags; michael@0: rv = http->GetLoadFlags(&flags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: flags |= nsIRequest::LOAD_ANONYMOUS; michael@0: rv = http->SetLoadFlags(flags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: // Preflight proxy michael@0: michael@0: // Class used as streamlistener and notification callback when michael@0: // doing the initial OPTIONS request for a CORS check michael@0: class nsCORSPreflightListener MOZ_FINAL : public nsIStreamListener, michael@0: public nsIInterfaceRequestor, michael@0: public nsIChannelEventSink michael@0: { michael@0: public: michael@0: nsCORSPreflightListener(nsIChannel* aOuterChannel, michael@0: nsIStreamListener* aOuterListener, michael@0: nsISupports* aOuterContext, michael@0: nsIPrincipal* aReferrerPrincipal, michael@0: const nsACString& aRequestMethod, michael@0: bool aWithCredentials) michael@0: : mOuterChannel(aOuterChannel), mOuterListener(aOuterListener), michael@0: mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal), michael@0: mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials) michael@0: { } michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: NS_DECL_NSICHANNELEVENTSINK michael@0: michael@0: private: michael@0: void AddResultToCache(nsIRequest* aRequest); michael@0: michael@0: nsCOMPtr mOuterChannel; michael@0: nsCOMPtr mOuterListener; michael@0: nsCOMPtr mOuterContext; michael@0: nsCOMPtr mReferrerPrincipal; michael@0: nsCString mRequestMethod; michael@0: bool mWithCredentials; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener, michael@0: nsIRequestObserver, nsIInterfaceRequestor, michael@0: nsIChannelEventSink) michael@0: michael@0: void michael@0: nsCORSPreflightListener::AddResultToCache(nsIRequest *aRequest) michael@0: { michael@0: nsCOMPtr http = do_QueryInterface(aRequest); michael@0: NS_ASSERTION(http, "Request was not http"); michael@0: michael@0: // The "Access-Control-Max-Age" header should return an age in seconds. michael@0: nsAutoCString headerVal; michael@0: http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"), michael@0: headerVal); michael@0: if (headerVal.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // Sanitize the string. We only allow 'delta-seconds' as specified by michael@0: // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or michael@0: // trailing non-whitespace characters). michael@0: uint32_t age = 0; michael@0: nsCSubstring::const_char_iterator iter, end; michael@0: headerVal.BeginReading(iter); michael@0: headerVal.EndReading(end); michael@0: while (iter != end) { michael@0: if (*iter < '0' || *iter > '9') { michael@0: return; michael@0: } michael@0: age = age * 10 + (*iter - '0'); michael@0: // Cap at 24 hours. This also avoids overflow michael@0: age = std::min(age, 86400U); michael@0: ++iter; michael@0: } michael@0: michael@0: if (!age || !EnsurePreflightCache()) { michael@0: return; michael@0: } michael@0: michael@0: michael@0: // String seems fine, go ahead and cache. michael@0: // Note that we have already checked that these headers follow the correct michael@0: // syntax. michael@0: michael@0: nsCOMPtr uri; michael@0: NS_GetFinalChannelURI(http, getter_AddRefs(uri)); michael@0: michael@0: TimeStamp expirationTime = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age); michael@0: michael@0: nsPreflightCache::CacheEntry* entry = michael@0: sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials, michael@0: true); michael@0: if (!entry) { michael@0: return; michael@0: } michael@0: michael@0: // The "Access-Control-Allow-Methods" header contains a comma separated michael@0: // list of method names. michael@0: http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), michael@0: headerVal); michael@0: michael@0: nsCCharSeparatedTokenizer methods(headerVal, ','); michael@0: while(methods.hasMoreTokens()) { michael@0: const nsDependentCSubstring& method = methods.nextToken(); michael@0: if (method.IsEmpty()) { michael@0: continue; michael@0: } michael@0: uint32_t i; michael@0: for (i = 0; i < entry->mMethods.Length(); ++i) { michael@0: if (entry->mMethods[i].token.Equals(method)) { michael@0: entry->mMethods[i].expirationTime = expirationTime; michael@0: break; michael@0: } michael@0: } michael@0: if (i == entry->mMethods.Length()) { michael@0: nsPreflightCache::TokenTime* newMethod = michael@0: entry->mMethods.AppendElement(); michael@0: if (!newMethod) { michael@0: return; michael@0: } michael@0: michael@0: newMethod->token = method; michael@0: newMethod->expirationTime = expirationTime; michael@0: } michael@0: } michael@0: michael@0: // The "Access-Control-Allow-Headers" header contains a comma separated michael@0: // list of method names. michael@0: http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), michael@0: headerVal); michael@0: michael@0: nsCCharSeparatedTokenizer headers(headerVal, ','); michael@0: while(headers.hasMoreTokens()) { michael@0: const nsDependentCSubstring& header = headers.nextToken(); michael@0: if (header.IsEmpty()) { michael@0: continue; michael@0: } michael@0: uint32_t i; michael@0: for (i = 0; i < entry->mHeaders.Length(); ++i) { michael@0: if (entry->mHeaders[i].token.Equals(header)) { michael@0: entry->mHeaders[i].expirationTime = expirationTime; michael@0: break; michael@0: } michael@0: } michael@0: if (i == entry->mHeaders.Length()) { michael@0: nsPreflightCache::TokenTime* newHeader = michael@0: entry->mHeaders.AppendElement(); michael@0: if (!newHeader) { michael@0: return; michael@0: } michael@0: michael@0: newHeader->token = header; michael@0: newHeader->expirationTime = expirationTime; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext) michael@0: { michael@0: nsresult status; michael@0: nsresult rv = aRequest->GetStatus(&status); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = status; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // Everything worked, try to cache and then fire off the actual request. michael@0: AddResultToCache(aRequest); michael@0: michael@0: rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mOuterChannel->Cancel(rv); michael@0: mOuterListener->OnStartRequest(mOuterChannel, mOuterContext); michael@0: mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSPreflightListener::OnStopRequest(nsIRequest *aRequest, michael@0: nsISupports *aContext, michael@0: nsresult aStatus) michael@0: { michael@0: mOuterChannel = nullptr; michael@0: mOuterListener = nullptr; michael@0: mOuterContext = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsIStreamListener methods **/ michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest, michael@0: nsISupports *ctxt, michael@0: nsIInputStream *inStr, michael@0: uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: uint32_t totalRead; michael@0: return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSPreflightListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel, michael@0: nsIChannel *aNewChannel, michael@0: uint32_t aFlags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: // Only internal redirects allowed for now. michael@0: if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: michael@0: callback->OnRedirectVerifyCallback(NS_OK); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult) michael@0: { michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: NS_StartCORSPreflight(nsIChannel* aRequestChannel, michael@0: nsIStreamListener* aListener, michael@0: nsIPrincipal* aPrincipal, michael@0: bool aWithCredentials, michael@0: nsTArray& aUnsafeHeaders, michael@0: nsIChannel** aPreflightChannel) michael@0: { michael@0: *aPreflightChannel = nullptr; michael@0: michael@0: nsAutoCString method; michael@0: nsCOMPtr httpChannel(do_QueryInterface(aRequestChannel)); michael@0: NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED); michael@0: httpChannel->GetRequestMethod(method); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsPreflightCache::CacheEntry* entry = michael@0: sPreflightCache ? michael@0: sPreflightCache->GetEntry(uri, aPrincipal, aWithCredentials, false) : michael@0: nullptr; michael@0: michael@0: if (entry && entry->CheckRequest(method, aUnsafeHeaders)) { michael@0: // We have a cached preflight result, just start the original channel michael@0: return aRequestChannel->AsyncOpen(aListener, nullptr); michael@0: } michael@0: michael@0: // Either it wasn't cached or the cached result has expired. Build a michael@0: // channel for the OPTIONS request. michael@0: michael@0: nsCOMPtr loadGroup; michael@0: rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsLoadFlags loadFlags; michael@0: rv = aRequestChannel->GetLoadFlags(&loadFlags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr preflightChannel; michael@0: rv = NS_NewChannel(getter_AddRefs(preflightChannel), uri, nullptr, michael@0: loadGroup, nullptr, loadFlags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr preHttp = do_QueryInterface(preflightChannel); michael@0: NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!"); michael@0: michael@0: rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Set up listener which will start the original channel michael@0: nsCOMPtr preflightListener = michael@0: new nsCORSPreflightListener(aRequestChannel, aListener, nullptr, aPrincipal, michael@0: method, aWithCredentials); michael@0: NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsRefPtr corsListener = michael@0: new nsCORSListenerProxy(preflightListener, aPrincipal, michael@0: aWithCredentials, method, michael@0: aUnsafeHeaders); michael@0: rv = corsListener->Init(preflightChannel); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: preflightListener = corsListener; michael@0: michael@0: // Start preflight michael@0: rv = preflightChannel->AsyncOpen(preflightListener, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Return newly created preflight channel michael@0: preflightChannel.forget(aPreflightChannel); michael@0: michael@0: return NS_OK; michael@0: } michael@0: