1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/base/src/nsCrossSiteListenerProxy.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1199 @@ 1.4 +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/Assertions.h" 1.10 +#include "mozilla/LinkedList.h" 1.11 + 1.12 +#include "nsCrossSiteListenerProxy.h" 1.13 +#include "nsIChannel.h" 1.14 +#include "nsIHttpChannel.h" 1.15 +#include "nsError.h" 1.16 +#include "nsContentUtils.h" 1.17 +#include "nsIScriptSecurityManager.h" 1.18 +#include "nsNetUtil.h" 1.19 +#include "nsMimeTypes.h" 1.20 +#include "nsIStreamConverterService.h" 1.21 +#include "nsStringStream.h" 1.22 +#include "nsGkAtoms.h" 1.23 +#include "nsWhitespaceTokenizer.h" 1.24 +#include "nsIChannelEventSink.h" 1.25 +#include "nsIAsyncVerifyRedirectCallback.h" 1.26 +#include "nsCharSeparatedTokenizer.h" 1.27 +#include "nsAsyncRedirectVerifyHelper.h" 1.28 +#include "nsClassHashtable.h" 1.29 +#include "nsHashKeys.h" 1.30 +#include "nsStreamUtils.h" 1.31 +#include "mozilla/Preferences.h" 1.32 +#include "nsIScriptError.h" 1.33 +#include "nsILoadGroup.h" 1.34 +#include "nsILoadContext.h" 1.35 +#include "nsIConsoleService.h" 1.36 +#include "nsIDOMWindowUtils.h" 1.37 +#include "nsIDOMWindow.h" 1.38 +#include <algorithm> 1.39 + 1.40 +using namespace mozilla; 1.41 + 1.42 +#define PREFLIGHT_CACHE_SIZE 100 1.43 + 1.44 +static bool gDisableCORS = false; 1.45 +static bool gDisableCORSPrivateData = false; 1.46 + 1.47 +static nsresult 1.48 +LogBlockedRequest(nsIRequest* aRequest) 1.49 +{ 1.50 + nsresult rv = NS_OK; 1.51 + 1.52 + // Get the innerWindowID associated with the XMLHTTPRequest 1.53 + PRUint64 innerWindowID = 0; 1.54 + 1.55 + nsCOMPtr<nsILoadGroup> loadGroup; 1.56 + aRequest->GetLoadGroup(getter_AddRefs(loadGroup)); 1.57 + if (loadGroup) { 1.58 + nsCOMPtr<nsIInterfaceRequestor> callbacks; 1.59 + loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 1.60 + if (callbacks) { 1.61 + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks); 1.62 + if(loadContext) { 1.63 + nsCOMPtr<nsIDOMWindow> window; 1.64 + loadContext->GetAssociatedWindow(getter_AddRefs(window)); 1.65 + if (window) { 1.66 + nsCOMPtr<nsIDOMWindowUtils> du = do_GetInterface(window); 1.67 + du->GetCurrentInnerWindowID(&innerWindowID); 1.68 + } 1.69 + } 1.70 + } 1.71 + } 1.72 + 1.73 + if (!innerWindowID) { 1.74 + return NS_ERROR_FAILURE; 1.75 + } 1.76 + 1.77 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 1.78 + nsCOMPtr<nsIURI> aUri; 1.79 + channel->GetURI(getter_AddRefs(aUri)); 1.80 + nsAutoCString spec; 1.81 + if (aUri) { 1.82 + aUri->GetSpec(spec); 1.83 + } 1.84 + 1.85 + // Generate the error message 1.86 + nsXPIDLString blockedMessage; 1.87 + NS_ConvertUTF8toUTF16 specUTF16(spec); 1.88 + const char16_t* params[] = { specUTF16.get() }; 1.89 + rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES, 1.90 + "CrossSiteRequestBlocked", 1.91 + params, 1.92 + blockedMessage); 1.93 + 1.94 + // Build the error object and log it to the console 1.95 + nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv)); 1.96 + NS_ENSURE_SUCCESS(rv, rv); 1.97 + 1.98 + nsCOMPtr<nsIScriptError> scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); 1.99 + NS_ENSURE_SUCCESS(rv, rv); 1.100 + 1.101 + nsAutoString msg(blockedMessage.get()); 1.102 + rv = scriptError->InitWithWindowID(msg, 1.103 + NS_ConvertUTF8toUTF16(spec), 1.104 + EmptyString(), 1.105 + 0, 1.106 + 0, 1.107 + nsIScriptError::warningFlag, 1.108 + "CORS", 1.109 + innerWindowID); 1.110 + NS_ENSURE_SUCCESS(rv, rv); 1.111 + 1.112 + rv = console->LogMessage(scriptError); 1.113 + return rv; 1.114 +} 1.115 + 1.116 +////////////////////////////////////////////////////////////////////////// 1.117 +// Preflight cache 1.118 + 1.119 +class nsPreflightCache 1.120 +{ 1.121 +public: 1.122 + struct TokenTime 1.123 + { 1.124 + nsCString token; 1.125 + TimeStamp expirationTime; 1.126 + }; 1.127 + 1.128 + struct CacheEntry : public LinkedListElement<CacheEntry> 1.129 + { 1.130 + CacheEntry(nsCString& aKey) 1.131 + : mKey(aKey) 1.132 + { 1.133 + MOZ_COUNT_CTOR(nsPreflightCache::CacheEntry); 1.134 + } 1.135 + 1.136 + ~CacheEntry() 1.137 + { 1.138 + MOZ_COUNT_DTOR(nsPreflightCache::CacheEntry); 1.139 + } 1.140 + 1.141 + void PurgeExpired(TimeStamp now); 1.142 + bool CheckRequest(const nsCString& aMethod, 1.143 + const nsTArray<nsCString>& aCustomHeaders); 1.144 + 1.145 + nsCString mKey; 1.146 + nsTArray<TokenTime> mMethods; 1.147 + nsTArray<TokenTime> mHeaders; 1.148 + }; 1.149 + 1.150 + nsPreflightCache() 1.151 + { 1.152 + MOZ_COUNT_CTOR(nsPreflightCache); 1.153 + } 1.154 + 1.155 + ~nsPreflightCache() 1.156 + { 1.157 + Clear(); 1.158 + MOZ_COUNT_DTOR(nsPreflightCache); 1.159 + } 1.160 + 1.161 + bool Initialize() 1.162 + { 1.163 + return true; 1.164 + } 1.165 + 1.166 + CacheEntry* GetEntry(nsIURI* aURI, nsIPrincipal* aPrincipal, 1.167 + bool aWithCredentials, bool aCreate); 1.168 + void RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal); 1.169 + 1.170 + void Clear(); 1.171 + 1.172 +private: 1.173 + static PLDHashOperator 1.174 + RemoveExpiredEntries(const nsACString& aKey, nsAutoPtr<CacheEntry>& aValue, 1.175 + void* aUserData); 1.176 + 1.177 + static bool GetCacheKey(nsIURI* aURI, nsIPrincipal* aPrincipal, 1.178 + bool aWithCredentials, nsACString& _retval); 1.179 + 1.180 + nsClassHashtable<nsCStringHashKey, CacheEntry> mTable; 1.181 + LinkedList<CacheEntry> mList; 1.182 +}; 1.183 + 1.184 +// Will be initialized in EnsurePreflightCache. 1.185 +static nsPreflightCache* sPreflightCache = nullptr; 1.186 + 1.187 +static bool EnsurePreflightCache() 1.188 +{ 1.189 + if (sPreflightCache) 1.190 + return true; 1.191 + 1.192 + nsAutoPtr<nsPreflightCache> newCache(new nsPreflightCache()); 1.193 + 1.194 + if (newCache->Initialize()) { 1.195 + sPreflightCache = newCache.forget(); 1.196 + return true; 1.197 + } 1.198 + 1.199 + return false; 1.200 +} 1.201 + 1.202 +void 1.203 +nsPreflightCache::CacheEntry::PurgeExpired(TimeStamp now) 1.204 +{ 1.205 + uint32_t i; 1.206 + for (i = 0; i < mMethods.Length(); ++i) { 1.207 + if (now >= mMethods[i].expirationTime) { 1.208 + mMethods.RemoveElementAt(i--); 1.209 + } 1.210 + } 1.211 + for (i = 0; i < mHeaders.Length(); ++i) { 1.212 + if (now >= mHeaders[i].expirationTime) { 1.213 + mHeaders.RemoveElementAt(i--); 1.214 + } 1.215 + } 1.216 +} 1.217 + 1.218 +bool 1.219 +nsPreflightCache::CacheEntry::CheckRequest(const nsCString& aMethod, 1.220 + const nsTArray<nsCString>& aHeaders) 1.221 +{ 1.222 + PurgeExpired(TimeStamp::NowLoRes()); 1.223 + 1.224 + if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) { 1.225 + uint32_t i; 1.226 + for (i = 0; i < mMethods.Length(); ++i) { 1.227 + if (aMethod.Equals(mMethods[i].token)) 1.228 + break; 1.229 + } 1.230 + if (i == mMethods.Length()) { 1.231 + return false; 1.232 + } 1.233 + } 1.234 + 1.235 + for (uint32_t i = 0; i < aHeaders.Length(); ++i) { 1.236 + uint32_t j; 1.237 + for (j = 0; j < mHeaders.Length(); ++j) { 1.238 + if (aHeaders[i].Equals(mHeaders[j].token, 1.239 + nsCaseInsensitiveCStringComparator())) { 1.240 + break; 1.241 + } 1.242 + } 1.243 + if (j == mHeaders.Length()) { 1.244 + return false; 1.245 + } 1.246 + } 1.247 + 1.248 + return true; 1.249 +} 1.250 + 1.251 +nsPreflightCache::CacheEntry* 1.252 +nsPreflightCache::GetEntry(nsIURI* aURI, 1.253 + nsIPrincipal* aPrincipal, 1.254 + bool aWithCredentials, 1.255 + bool aCreate) 1.256 +{ 1.257 + nsCString key; 1.258 + if (!GetCacheKey(aURI, aPrincipal, aWithCredentials, key)) { 1.259 + NS_WARNING("Invalid cache key!"); 1.260 + return nullptr; 1.261 + } 1.262 + 1.263 + CacheEntry* entry; 1.264 + 1.265 + if (mTable.Get(key, &entry)) { 1.266 + // Entry already existed so just return it. Also update the LRU list. 1.267 + 1.268 + // Move to the head of the list. 1.269 + entry->removeFrom(mList); 1.270 + mList.insertFront(entry); 1.271 + 1.272 + return entry; 1.273 + } 1.274 + 1.275 + if (!aCreate) { 1.276 + return nullptr; 1.277 + } 1.278 + 1.279 + // This is a new entry, allocate and insert into the table now so that any 1.280 + // failures don't cause items to be removed from a full cache. 1.281 + entry = new CacheEntry(key); 1.282 + if (!entry) { 1.283 + NS_WARNING("Failed to allocate new cache entry!"); 1.284 + return nullptr; 1.285 + } 1.286 + 1.287 + NS_ASSERTION(mTable.Count() <= PREFLIGHT_CACHE_SIZE, 1.288 + "Something is borked, too many entries in the cache!"); 1.289 + 1.290 + // Now enforce the max count. 1.291 + if (mTable.Count() == PREFLIGHT_CACHE_SIZE) { 1.292 + // Try to kick out all the expired entries. 1.293 + TimeStamp now = TimeStamp::NowLoRes(); 1.294 + mTable.Enumerate(RemoveExpiredEntries, &now); 1.295 + 1.296 + // If that didn't remove anything then kick out the least recently used 1.297 + // entry. 1.298 + if (mTable.Count() == PREFLIGHT_CACHE_SIZE) { 1.299 + CacheEntry* lruEntry = static_cast<CacheEntry*>(mList.popLast()); 1.300 + MOZ_ASSERT(lruEntry); 1.301 + 1.302 + // This will delete 'lruEntry'. 1.303 + mTable.Remove(lruEntry->mKey); 1.304 + 1.305 + NS_ASSERTION(mTable.Count() == PREFLIGHT_CACHE_SIZE - 1, 1.306 + "Somehow tried to remove an entry that was never added!"); 1.307 + } 1.308 + } 1.309 + 1.310 + mTable.Put(key, entry); 1.311 + mList.insertFront(entry); 1.312 + 1.313 + return entry; 1.314 +} 1.315 + 1.316 +void 1.317 +nsPreflightCache::RemoveEntries(nsIURI* aURI, nsIPrincipal* aPrincipal) 1.318 +{ 1.319 + CacheEntry* entry; 1.320 + nsCString key; 1.321 + if (GetCacheKey(aURI, aPrincipal, true, key) && 1.322 + mTable.Get(key, &entry)) { 1.323 + entry->removeFrom(mList); 1.324 + mTable.Remove(key); 1.325 + } 1.326 + 1.327 + if (GetCacheKey(aURI, aPrincipal, false, key) && 1.328 + mTable.Get(key, &entry)) { 1.329 + entry->removeFrom(mList); 1.330 + mTable.Remove(key); 1.331 + } 1.332 +} 1.333 + 1.334 +void 1.335 +nsPreflightCache::Clear() 1.336 +{ 1.337 + mList.clear(); 1.338 + mTable.Clear(); 1.339 +} 1.340 + 1.341 +/* static */ PLDHashOperator 1.342 +nsPreflightCache::RemoveExpiredEntries(const nsACString& aKey, 1.343 + nsAutoPtr<CacheEntry>& aValue, 1.344 + void* aUserData) 1.345 +{ 1.346 + TimeStamp* now = static_cast<TimeStamp*>(aUserData); 1.347 + 1.348 + aValue->PurgeExpired(*now); 1.349 + 1.350 + if (aValue->mHeaders.IsEmpty() && 1.351 + aValue->mMethods.IsEmpty()) { 1.352 + // Expired, remove from the list as well as the hash table. 1.353 + aValue->removeFrom(sPreflightCache->mList); 1.354 + return PL_DHASH_REMOVE; 1.355 + } 1.356 + 1.357 + return PL_DHASH_NEXT; 1.358 +} 1.359 + 1.360 +/* static */ bool 1.361 +nsPreflightCache::GetCacheKey(nsIURI* aURI, 1.362 + nsIPrincipal* aPrincipal, 1.363 + bool aWithCredentials, 1.364 + nsACString& _retval) 1.365 +{ 1.366 + NS_ASSERTION(aURI, "Null uri!"); 1.367 + NS_ASSERTION(aPrincipal, "Null principal!"); 1.368 + 1.369 + NS_NAMED_LITERAL_CSTRING(space, " "); 1.370 + 1.371 + nsCOMPtr<nsIURI> uri; 1.372 + nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri)); 1.373 + NS_ENSURE_SUCCESS(rv, false); 1.374 + 1.375 + nsAutoCString scheme, host, port; 1.376 + if (uri) { 1.377 + uri->GetScheme(scheme); 1.378 + uri->GetHost(host); 1.379 + port.AppendInt(NS_GetRealPort(uri)); 1.380 + } 1.381 + 1.382 + nsAutoCString cred; 1.383 + if (aWithCredentials) { 1.384 + _retval.AssignLiteral("cred"); 1.385 + } 1.386 + else { 1.387 + _retval.AssignLiteral("nocred"); 1.388 + } 1.389 + 1.390 + nsAutoCString spec; 1.391 + rv = aURI->GetSpec(spec); 1.392 + NS_ENSURE_SUCCESS(rv, false); 1.393 + 1.394 + _retval.Assign(cred + space + scheme + space + host + space + port + space + 1.395 + spec); 1.396 + 1.397 + return true; 1.398 +} 1.399 + 1.400 +////////////////////////////////////////////////////////////////////////// 1.401 +// nsCORSListenerProxy 1.402 + 1.403 +NS_IMPL_ISUPPORTS(nsCORSListenerProxy, nsIStreamListener, 1.404 + nsIRequestObserver, nsIChannelEventSink, 1.405 + nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) 1.406 + 1.407 +/* static */ 1.408 +void 1.409 +nsCORSListenerProxy::Startup() 1.410 +{ 1.411 + Preferences::AddBoolVarCache(&gDisableCORS, 1.412 + "content.cors.disable"); 1.413 + Preferences::AddBoolVarCache(&gDisableCORSPrivateData, 1.414 + "content.cors.no_private_data"); 1.415 +} 1.416 + 1.417 +/* static */ 1.418 +void 1.419 +nsCORSListenerProxy::Shutdown() 1.420 +{ 1.421 + delete sPreflightCache; 1.422 + sPreflightCache = nullptr; 1.423 +} 1.424 + 1.425 +nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter, 1.426 + nsIPrincipal* aRequestingPrincipal, 1.427 + bool aWithCredentials) 1.428 + : mOuterListener(aOuter), 1.429 + mRequestingPrincipal(aRequestingPrincipal), 1.430 + mOriginHeaderPrincipal(aRequestingPrincipal), 1.431 + mWithCredentials(aWithCredentials && !gDisableCORSPrivateData), 1.432 + mRequestApproved(false), 1.433 + mHasBeenCrossSite(false), 1.434 + mIsPreflight(false) 1.435 +{ 1.436 +} 1.437 + 1.438 +nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter, 1.439 + nsIPrincipal* aRequestingPrincipal, 1.440 + bool aWithCredentials, 1.441 + const nsCString& aPreflightMethod, 1.442 + const nsTArray<nsCString>& aPreflightHeaders) 1.443 + : mOuterListener(aOuter), 1.444 + mRequestingPrincipal(aRequestingPrincipal), 1.445 + mOriginHeaderPrincipal(aRequestingPrincipal), 1.446 + mWithCredentials(aWithCredentials && !gDisableCORSPrivateData), 1.447 + mRequestApproved(false), 1.448 + mHasBeenCrossSite(false), 1.449 + mIsPreflight(true), 1.450 + mPreflightMethod(aPreflightMethod), 1.451 + mPreflightHeaders(aPreflightHeaders) 1.452 +{ 1.453 + for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { 1.454 + ToLowerCase(mPreflightHeaders[i]); 1.455 + } 1.456 + mPreflightHeaders.Sort(); 1.457 +} 1.458 + 1.459 +nsresult 1.460 +nsCORSListenerProxy::Init(nsIChannel* aChannel, bool aAllowDataURI) 1.461 +{ 1.462 + aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks)); 1.463 + aChannel->SetNotificationCallbacks(this); 1.464 + 1.465 + nsresult rv = UpdateChannel(aChannel, aAllowDataURI); 1.466 + if (NS_FAILED(rv)) { 1.467 + mOuterListener = nullptr; 1.468 + mRequestingPrincipal = nullptr; 1.469 + mOriginHeaderPrincipal = nullptr; 1.470 + mOuterNotificationCallbacks = nullptr; 1.471 + } 1.472 + return rv; 1.473 +} 1.474 + 1.475 +NS_IMETHODIMP 1.476 +nsCORSListenerProxy::OnStartRequest(nsIRequest* aRequest, 1.477 + nsISupports* aContext) 1.478 +{ 1.479 + nsresult rv = CheckRequestApproved(aRequest); 1.480 + mRequestApproved = NS_SUCCEEDED(rv); 1.481 + if (!mRequestApproved) { 1.482 + rv = LogBlockedRequest(aRequest); 1.483 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request"); 1.484 + if (sPreflightCache) { 1.485 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 1.486 + if (channel) { 1.487 + nsCOMPtr<nsIURI> uri; 1.488 + NS_GetFinalChannelURI(channel, getter_AddRefs(uri)); 1.489 + if (uri) { 1.490 + // OK to use mRequestingPrincipal since preflights never get 1.491 + // redirected. 1.492 + sPreflightCache->RemoveEntries(uri, mRequestingPrincipal); 1.493 + } 1.494 + } 1.495 + } 1.496 + 1.497 + aRequest->Cancel(NS_ERROR_DOM_BAD_URI); 1.498 + mOuterListener->OnStartRequest(aRequest, aContext); 1.499 + 1.500 + return NS_ERROR_DOM_BAD_URI; 1.501 + } 1.502 + 1.503 + return mOuterListener->OnStartRequest(aRequest, aContext); 1.504 +} 1.505 + 1.506 +bool 1.507 +IsValidHTTPToken(const nsCSubstring& aToken) 1.508 +{ 1.509 + if (aToken.IsEmpty()) { 1.510 + return false; 1.511 + } 1.512 + 1.513 + nsCSubstring::const_char_iterator iter, end; 1.514 + 1.515 + aToken.BeginReading(iter); 1.516 + aToken.EndReading(end); 1.517 + 1.518 + while (iter != end) { 1.519 + if (*iter <= 32 || 1.520 + *iter >= 127 || 1.521 + *iter == '(' || 1.522 + *iter == ')' || 1.523 + *iter == '<' || 1.524 + *iter == '>' || 1.525 + *iter == '@' || 1.526 + *iter == ',' || 1.527 + *iter == ';' || 1.528 + *iter == ':' || 1.529 + *iter == '\\' || 1.530 + *iter == '\"' || 1.531 + *iter == '/' || 1.532 + *iter == '[' || 1.533 + *iter == ']' || 1.534 + *iter == '?' || 1.535 + *iter == '=' || 1.536 + *iter == '{' || 1.537 + *iter == '}') { 1.538 + return false; 1.539 + } 1.540 + ++iter; 1.541 + } 1.542 + 1.543 + return true; 1.544 +} 1.545 + 1.546 +nsresult 1.547 +nsCORSListenerProxy::CheckRequestApproved(nsIRequest* aRequest) 1.548 +{ 1.549 + // Check if this was actually a cross domain request 1.550 + if (!mHasBeenCrossSite) { 1.551 + return NS_OK; 1.552 + } 1.553 + 1.554 + if (gDisableCORS) { 1.555 + return NS_ERROR_DOM_BAD_URI; 1.556 + } 1.557 + 1.558 + // Check if the request failed 1.559 + nsresult status; 1.560 + nsresult rv = aRequest->GetStatus(&status); 1.561 + NS_ENSURE_SUCCESS(rv, rv); 1.562 + NS_ENSURE_SUCCESS(status, status); 1.563 + 1.564 + // Test that things worked on a HTTP level 1.565 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest); 1.566 + NS_ENSURE_TRUE(http, NS_ERROR_DOM_BAD_URI); 1.567 + 1.568 + // Check the Access-Control-Allow-Origin header 1.569 + nsAutoCString allowedOriginHeader; 1.570 + rv = http->GetResponseHeader( 1.571 + NS_LITERAL_CSTRING("Access-Control-Allow-Origin"), allowedOriginHeader); 1.572 + NS_ENSURE_SUCCESS(rv, rv); 1.573 + 1.574 + if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) { 1.575 + nsAutoCString origin; 1.576 + rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin); 1.577 + NS_ENSURE_SUCCESS(rv, rv); 1.578 + 1.579 + if (!allowedOriginHeader.Equals(origin)) { 1.580 + return NS_ERROR_DOM_BAD_URI; 1.581 + } 1.582 + } 1.583 + 1.584 + // Check Access-Control-Allow-Credentials header 1.585 + if (mWithCredentials) { 1.586 + nsAutoCString allowCredentialsHeader; 1.587 + rv = http->GetResponseHeader( 1.588 + NS_LITERAL_CSTRING("Access-Control-Allow-Credentials"), allowCredentialsHeader); 1.589 + NS_ENSURE_SUCCESS(rv, rv); 1.590 + 1.591 + if (!allowCredentialsHeader.EqualsLiteral("true")) { 1.592 + return NS_ERROR_DOM_BAD_URI; 1.593 + } 1.594 + } 1.595 + 1.596 + if (mIsPreflight) { 1.597 + bool succeedded; 1.598 + rv = http->GetRequestSucceeded(&succeedded); 1.599 + NS_ENSURE_SUCCESS(rv, rv); 1.600 + if (!succeedded) { 1.601 + return NS_ERROR_DOM_BAD_URI; 1.602 + } 1.603 + 1.604 + nsAutoCString headerVal; 1.605 + // The "Access-Control-Allow-Methods" header contains a comma separated 1.606 + // list of method names. 1.607 + http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), 1.608 + headerVal); 1.609 + bool foundMethod = mPreflightMethod.EqualsLiteral("GET") || 1.610 + mPreflightMethod.EqualsLiteral("HEAD") || 1.611 + mPreflightMethod.EqualsLiteral("POST"); 1.612 + nsCCharSeparatedTokenizer methodTokens(headerVal, ','); 1.613 + while(methodTokens.hasMoreTokens()) { 1.614 + const nsDependentCSubstring& method = methodTokens.nextToken(); 1.615 + if (method.IsEmpty()) { 1.616 + continue; 1.617 + } 1.618 + if (!IsValidHTTPToken(method)) { 1.619 + return NS_ERROR_DOM_BAD_URI; 1.620 + } 1.621 + foundMethod |= mPreflightMethod.Equals(method); 1.622 + } 1.623 + NS_ENSURE_TRUE(foundMethod, NS_ERROR_DOM_BAD_URI); 1.624 + 1.625 + // The "Access-Control-Allow-Headers" header contains a comma separated 1.626 + // list of header names. 1.627 + headerVal.Truncate(); 1.628 + http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), 1.629 + headerVal); 1.630 + nsTArray<nsCString> headers; 1.631 + nsCCharSeparatedTokenizer headerTokens(headerVal, ','); 1.632 + while(headerTokens.hasMoreTokens()) { 1.633 + const nsDependentCSubstring& header = headerTokens.nextToken(); 1.634 + if (header.IsEmpty()) { 1.635 + continue; 1.636 + } 1.637 + if (!IsValidHTTPToken(header)) { 1.638 + return NS_ERROR_DOM_BAD_URI; 1.639 + } 1.640 + headers.AppendElement(header); 1.641 + } 1.642 + for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { 1.643 + if (!headers.Contains(mPreflightHeaders[i], 1.644 + nsCaseInsensitiveCStringArrayComparator())) { 1.645 + return NS_ERROR_DOM_BAD_URI; 1.646 + } 1.647 + } 1.648 + } 1.649 + 1.650 + return NS_OK; 1.651 +} 1.652 + 1.653 +NS_IMETHODIMP 1.654 +nsCORSListenerProxy::OnStopRequest(nsIRequest* aRequest, 1.655 + nsISupports* aContext, 1.656 + nsresult aStatusCode) 1.657 +{ 1.658 + nsresult rv = mOuterListener->OnStopRequest(aRequest, aContext, aStatusCode); 1.659 + mOuterListener = nullptr; 1.660 + mOuterNotificationCallbacks = nullptr; 1.661 + mRedirectCallback = nullptr; 1.662 + mOldRedirectChannel = nullptr; 1.663 + mNewRedirectChannel = nullptr; 1.664 + return rv; 1.665 +} 1.666 + 1.667 +NS_IMETHODIMP 1.668 +nsCORSListenerProxy::OnDataAvailable(nsIRequest* aRequest, 1.669 + nsISupports* aContext, 1.670 + nsIInputStream* aInputStream, 1.671 + uint64_t aOffset, 1.672 + uint32_t aCount) 1.673 +{ 1.674 + if (!mRequestApproved) { 1.675 + return NS_ERROR_DOM_BAD_URI; 1.676 + } 1.677 + return mOuterListener->OnDataAvailable(aRequest, aContext, aInputStream, 1.678 + aOffset, aCount); 1.679 +} 1.680 + 1.681 +NS_IMETHODIMP 1.682 +nsCORSListenerProxy::GetInterface(const nsIID & aIID, void **aResult) 1.683 +{ 1.684 + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 1.685 + *aResult = static_cast<nsIChannelEventSink*>(this); 1.686 + NS_ADDREF_THIS(); 1.687 + 1.688 + return NS_OK; 1.689 + } 1.690 + 1.691 + return mOuterNotificationCallbacks ? 1.692 + mOuterNotificationCallbacks->GetInterface(aIID, aResult) : 1.693 + NS_ERROR_NO_INTERFACE; 1.694 +} 1.695 + 1.696 +NS_IMETHODIMP 1.697 +nsCORSListenerProxy::AsyncOnChannelRedirect(nsIChannel *aOldChannel, 1.698 + nsIChannel *aNewChannel, 1.699 + uint32_t aFlags, 1.700 + nsIAsyncVerifyRedirectCallback *cb) 1.701 +{ 1.702 + nsresult rv; 1.703 + if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) { 1.704 + rv = CheckRequestApproved(aOldChannel); 1.705 + if (NS_FAILED(rv)) { 1.706 + rv = LogBlockedRequest(aOldChannel); 1.707 + NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to log blocked cross-site request"); 1.708 + 1.709 + if (sPreflightCache) { 1.710 + nsCOMPtr<nsIURI> oldURI; 1.711 + NS_GetFinalChannelURI(aOldChannel, getter_AddRefs(oldURI)); 1.712 + if (oldURI) { 1.713 + // OK to use mRequestingPrincipal since preflights never get 1.714 + // redirected. 1.715 + sPreflightCache->RemoveEntries(oldURI, mRequestingPrincipal); 1.716 + } 1.717 + } 1.718 + aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); 1.719 + return NS_ERROR_DOM_BAD_URI; 1.720 + } 1.721 + 1.722 + if (mHasBeenCrossSite) { 1.723 + // Once we've been cross-site, cross-origin redirects reset our source 1.724 + // origin. 1.725 + nsCOMPtr<nsIPrincipal> oldChannelPrincipal; 1.726 + nsContentUtils::GetSecurityManager()-> 1.727 + GetChannelPrincipal(aOldChannel, getter_AddRefs(oldChannelPrincipal)); 1.728 + nsCOMPtr<nsIPrincipal> newChannelPrincipal; 1.729 + nsContentUtils::GetSecurityManager()-> 1.730 + GetChannelPrincipal(aNewChannel, getter_AddRefs(newChannelPrincipal)); 1.731 + if (!oldChannelPrincipal || !newChannelPrincipal) { 1.732 + rv = NS_ERROR_OUT_OF_MEMORY; 1.733 + } 1.734 + 1.735 + if (NS_SUCCEEDED(rv)) { 1.736 + bool equal; 1.737 + rv = oldChannelPrincipal->Equals(newChannelPrincipal, &equal); 1.738 + if (NS_SUCCEEDED(rv)) { 1.739 + if (!equal) { 1.740 + // Spec says to set our source origin to a unique origin. 1.741 + mOriginHeaderPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); 1.742 + if (!mOriginHeaderPrincipal) { 1.743 + rv = NS_ERROR_OUT_OF_MEMORY; 1.744 + } 1.745 + } 1.746 + } 1.747 + } 1.748 + 1.749 + if (NS_FAILED(rv)) { 1.750 + aOldChannel->Cancel(rv); 1.751 + return rv; 1.752 + } 1.753 + } 1.754 + } 1.755 + 1.756 + // Prepare to receive callback 1.757 + mRedirectCallback = cb; 1.758 + mOldRedirectChannel = aOldChannel; 1.759 + mNewRedirectChannel = aNewChannel; 1.760 + 1.761 + nsCOMPtr<nsIChannelEventSink> outer = 1.762 + do_GetInterface(mOuterNotificationCallbacks); 1.763 + if (outer) { 1.764 + rv = outer->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, this); 1.765 + if (NS_FAILED(rv)) { 1.766 + aOldChannel->Cancel(rv); // is this necessary...? 1.767 + mRedirectCallback = nullptr; 1.768 + mOldRedirectChannel = nullptr; 1.769 + mNewRedirectChannel = nullptr; 1.770 + } 1.771 + return rv; 1.772 + } 1.773 + 1.774 + (void) OnRedirectVerifyCallback(NS_OK); 1.775 + return NS_OK; 1.776 +} 1.777 + 1.778 +NS_IMETHODIMP 1.779 +nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result) 1.780 +{ 1.781 + NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); 1.782 + NS_ASSERTION(mOldRedirectChannel, "mOldRedirectChannel not set in callback"); 1.783 + NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); 1.784 + 1.785 + if (NS_SUCCEEDED(result)) { 1.786 + nsresult rv = UpdateChannel(mNewRedirectChannel); 1.787 + if (NS_FAILED(rv)) { 1.788 + NS_WARNING("nsCORSListenerProxy::OnRedirectVerifyCallback: " 1.789 + "UpdateChannel() returned failure"); 1.790 + } 1.791 + result = rv; 1.792 + } 1.793 + 1.794 + if (NS_FAILED(result)) { 1.795 + mOldRedirectChannel->Cancel(result); 1.796 + } 1.797 + 1.798 + mOldRedirectChannel = nullptr; 1.799 + mNewRedirectChannel = nullptr; 1.800 + mRedirectCallback->OnRedirectVerifyCallback(result); 1.801 + mRedirectCallback = nullptr; 1.802 + return NS_OK; 1.803 +} 1.804 + 1.805 +nsresult 1.806 +nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI) 1.807 +{ 1.808 + nsCOMPtr<nsIURI> uri, originalURI; 1.809 + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); 1.810 + NS_ENSURE_SUCCESS(rv, rv); 1.811 + rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI)); 1.812 + NS_ENSURE_SUCCESS(rv, rv); 1.813 + 1.814 + // exempt data URIs from the same origin check. 1.815 + if (aAllowDataURI && originalURI == uri) { 1.816 + bool dataScheme = false; 1.817 + rv = uri->SchemeIs("data", &dataScheme); 1.818 + NS_ENSURE_SUCCESS(rv, rv); 1.819 + if (dataScheme) { 1.820 + return NS_OK; 1.821 + } 1.822 + } 1.823 + 1.824 + // Check that the uri is ok to load 1.825 + rv = nsContentUtils::GetSecurityManager()-> 1.826 + CheckLoadURIWithPrincipal(mRequestingPrincipal, uri, 1.827 + nsIScriptSecurityManager::STANDARD); 1.828 + NS_ENSURE_SUCCESS(rv, rv); 1.829 + 1.830 + if (originalURI != uri) { 1.831 + rv = nsContentUtils::GetSecurityManager()-> 1.832 + CheckLoadURIWithPrincipal(mRequestingPrincipal, originalURI, 1.833 + nsIScriptSecurityManager::STANDARD); 1.834 + NS_ENSURE_SUCCESS(rv, rv); 1.835 + } 1.836 + 1.837 + if (!mHasBeenCrossSite && 1.838 + NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri, false, false)) && 1.839 + (originalURI == uri || 1.840 + NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI, 1.841 + false, false)))) { 1.842 + return NS_OK; 1.843 + } 1.844 + 1.845 + // It's a cross site load 1.846 + mHasBeenCrossSite = true; 1.847 + 1.848 + nsCString userpass; 1.849 + uri->GetUserPass(userpass); 1.850 + NS_ENSURE_TRUE(userpass.IsEmpty(), NS_ERROR_DOM_BAD_URI); 1.851 + 1.852 + // Add the Origin header 1.853 + nsAutoCString origin; 1.854 + rv = nsContentUtils::GetASCIIOrigin(mOriginHeaderPrincipal, origin); 1.855 + NS_ENSURE_SUCCESS(rv, rv); 1.856 + 1.857 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel); 1.858 + NS_ENSURE_TRUE(http, NS_ERROR_FAILURE); 1.859 + 1.860 + rv = http->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), origin, false); 1.861 + NS_ENSURE_SUCCESS(rv, rv); 1.862 + 1.863 + // Add preflight headers if this is a preflight request 1.864 + if (mIsPreflight) { 1.865 + rv = http-> 1.866 + SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Method"), 1.867 + mPreflightMethod, false); 1.868 + NS_ENSURE_SUCCESS(rv, rv); 1.869 + 1.870 + if (!mPreflightHeaders.IsEmpty()) { 1.871 + nsAutoCString headers; 1.872 + for (uint32_t i = 0; i < mPreflightHeaders.Length(); ++i) { 1.873 + if (i != 0) { 1.874 + headers += ','; 1.875 + } 1.876 + headers += mPreflightHeaders[i]; 1.877 + } 1.878 + rv = http-> 1.879 + SetRequestHeader(NS_LITERAL_CSTRING("Access-Control-Request-Headers"), 1.880 + headers, false); 1.881 + NS_ENSURE_SUCCESS(rv, rv); 1.882 + } 1.883 + } 1.884 + 1.885 + // Make cookie-less if needed 1.886 + if (mIsPreflight || !mWithCredentials) { 1.887 + nsLoadFlags flags; 1.888 + rv = http->GetLoadFlags(&flags); 1.889 + NS_ENSURE_SUCCESS(rv, rv); 1.890 + 1.891 + flags |= nsIRequest::LOAD_ANONYMOUS; 1.892 + rv = http->SetLoadFlags(flags); 1.893 + NS_ENSURE_SUCCESS(rv, rv); 1.894 + } 1.895 + 1.896 + return NS_OK; 1.897 +} 1.898 + 1.899 +////////////////////////////////////////////////////////////////////////// 1.900 +// Preflight proxy 1.901 + 1.902 +// Class used as streamlistener and notification callback when 1.903 +// doing the initial OPTIONS request for a CORS check 1.904 +class nsCORSPreflightListener MOZ_FINAL : public nsIStreamListener, 1.905 + public nsIInterfaceRequestor, 1.906 + public nsIChannelEventSink 1.907 +{ 1.908 +public: 1.909 + nsCORSPreflightListener(nsIChannel* aOuterChannel, 1.910 + nsIStreamListener* aOuterListener, 1.911 + nsISupports* aOuterContext, 1.912 + nsIPrincipal* aReferrerPrincipal, 1.913 + const nsACString& aRequestMethod, 1.914 + bool aWithCredentials) 1.915 + : mOuterChannel(aOuterChannel), mOuterListener(aOuterListener), 1.916 + mOuterContext(aOuterContext), mReferrerPrincipal(aReferrerPrincipal), 1.917 + mRequestMethod(aRequestMethod), mWithCredentials(aWithCredentials) 1.918 + { } 1.919 + 1.920 + NS_DECL_ISUPPORTS 1.921 + NS_DECL_NSISTREAMLISTENER 1.922 + NS_DECL_NSIREQUESTOBSERVER 1.923 + NS_DECL_NSIINTERFACEREQUESTOR 1.924 + NS_DECL_NSICHANNELEVENTSINK 1.925 + 1.926 +private: 1.927 + void AddResultToCache(nsIRequest* aRequest); 1.928 + 1.929 + nsCOMPtr<nsIChannel> mOuterChannel; 1.930 + nsCOMPtr<nsIStreamListener> mOuterListener; 1.931 + nsCOMPtr<nsISupports> mOuterContext; 1.932 + nsCOMPtr<nsIPrincipal> mReferrerPrincipal; 1.933 + nsCString mRequestMethod; 1.934 + bool mWithCredentials; 1.935 +}; 1.936 + 1.937 +NS_IMPL_ISUPPORTS(nsCORSPreflightListener, nsIStreamListener, 1.938 + nsIRequestObserver, nsIInterfaceRequestor, 1.939 + nsIChannelEventSink) 1.940 + 1.941 +void 1.942 +nsCORSPreflightListener::AddResultToCache(nsIRequest *aRequest) 1.943 +{ 1.944 + nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aRequest); 1.945 + NS_ASSERTION(http, "Request was not http"); 1.946 + 1.947 + // The "Access-Control-Max-Age" header should return an age in seconds. 1.948 + nsAutoCString headerVal; 1.949 + http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Max-Age"), 1.950 + headerVal); 1.951 + if (headerVal.IsEmpty()) { 1.952 + return; 1.953 + } 1.954 + 1.955 + // Sanitize the string. We only allow 'delta-seconds' as specified by 1.956 + // http://dev.w3.org/2006/waf/access-control (digits 0-9 with no leading or 1.957 + // trailing non-whitespace characters). 1.958 + uint32_t age = 0; 1.959 + nsCSubstring::const_char_iterator iter, end; 1.960 + headerVal.BeginReading(iter); 1.961 + headerVal.EndReading(end); 1.962 + while (iter != end) { 1.963 + if (*iter < '0' || *iter > '9') { 1.964 + return; 1.965 + } 1.966 + age = age * 10 + (*iter - '0'); 1.967 + // Cap at 24 hours. This also avoids overflow 1.968 + age = std::min(age, 86400U); 1.969 + ++iter; 1.970 + } 1.971 + 1.972 + if (!age || !EnsurePreflightCache()) { 1.973 + return; 1.974 + } 1.975 + 1.976 + 1.977 + // String seems fine, go ahead and cache. 1.978 + // Note that we have already checked that these headers follow the correct 1.979 + // syntax. 1.980 + 1.981 + nsCOMPtr<nsIURI> uri; 1.982 + NS_GetFinalChannelURI(http, getter_AddRefs(uri)); 1.983 + 1.984 + TimeStamp expirationTime = TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age); 1.985 + 1.986 + nsPreflightCache::CacheEntry* entry = 1.987 + sPreflightCache->GetEntry(uri, mReferrerPrincipal, mWithCredentials, 1.988 + true); 1.989 + if (!entry) { 1.990 + return; 1.991 + } 1.992 + 1.993 + // The "Access-Control-Allow-Methods" header contains a comma separated 1.994 + // list of method names. 1.995 + http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Methods"), 1.996 + headerVal); 1.997 + 1.998 + nsCCharSeparatedTokenizer methods(headerVal, ','); 1.999 + while(methods.hasMoreTokens()) { 1.1000 + const nsDependentCSubstring& method = methods.nextToken(); 1.1001 + if (method.IsEmpty()) { 1.1002 + continue; 1.1003 + } 1.1004 + uint32_t i; 1.1005 + for (i = 0; i < entry->mMethods.Length(); ++i) { 1.1006 + if (entry->mMethods[i].token.Equals(method)) { 1.1007 + entry->mMethods[i].expirationTime = expirationTime; 1.1008 + break; 1.1009 + } 1.1010 + } 1.1011 + if (i == entry->mMethods.Length()) { 1.1012 + nsPreflightCache::TokenTime* newMethod = 1.1013 + entry->mMethods.AppendElement(); 1.1014 + if (!newMethod) { 1.1015 + return; 1.1016 + } 1.1017 + 1.1018 + newMethod->token = method; 1.1019 + newMethod->expirationTime = expirationTime; 1.1020 + } 1.1021 + } 1.1022 + 1.1023 + // The "Access-Control-Allow-Headers" header contains a comma separated 1.1024 + // list of method names. 1.1025 + http->GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Allow-Headers"), 1.1026 + headerVal); 1.1027 + 1.1028 + nsCCharSeparatedTokenizer headers(headerVal, ','); 1.1029 + while(headers.hasMoreTokens()) { 1.1030 + const nsDependentCSubstring& header = headers.nextToken(); 1.1031 + if (header.IsEmpty()) { 1.1032 + continue; 1.1033 + } 1.1034 + uint32_t i; 1.1035 + for (i = 0; i < entry->mHeaders.Length(); ++i) { 1.1036 + if (entry->mHeaders[i].token.Equals(header)) { 1.1037 + entry->mHeaders[i].expirationTime = expirationTime; 1.1038 + break; 1.1039 + } 1.1040 + } 1.1041 + if (i == entry->mHeaders.Length()) { 1.1042 + nsPreflightCache::TokenTime* newHeader = 1.1043 + entry->mHeaders.AppendElement(); 1.1044 + if (!newHeader) { 1.1045 + return; 1.1046 + } 1.1047 + 1.1048 + newHeader->token = header; 1.1049 + newHeader->expirationTime = expirationTime; 1.1050 + } 1.1051 + } 1.1052 +} 1.1053 + 1.1054 +NS_IMETHODIMP 1.1055 +nsCORSPreflightListener::OnStartRequest(nsIRequest *aRequest, 1.1056 + nsISupports *aContext) 1.1057 +{ 1.1058 + nsresult status; 1.1059 + nsresult rv = aRequest->GetStatus(&status); 1.1060 + 1.1061 + if (NS_SUCCEEDED(rv)) { 1.1062 + rv = status; 1.1063 + } 1.1064 + 1.1065 + if (NS_SUCCEEDED(rv)) { 1.1066 + // Everything worked, try to cache and then fire off the actual request. 1.1067 + AddResultToCache(aRequest); 1.1068 + 1.1069 + rv = mOuterChannel->AsyncOpen(mOuterListener, mOuterContext); 1.1070 + } 1.1071 + 1.1072 + if (NS_FAILED(rv)) { 1.1073 + mOuterChannel->Cancel(rv); 1.1074 + mOuterListener->OnStartRequest(mOuterChannel, mOuterContext); 1.1075 + mOuterListener->OnStopRequest(mOuterChannel, mOuterContext, rv); 1.1076 + 1.1077 + return rv; 1.1078 + } 1.1079 + 1.1080 + return NS_OK; 1.1081 +} 1.1082 + 1.1083 +NS_IMETHODIMP 1.1084 +nsCORSPreflightListener::OnStopRequest(nsIRequest *aRequest, 1.1085 + nsISupports *aContext, 1.1086 + nsresult aStatus) 1.1087 +{ 1.1088 + mOuterChannel = nullptr; 1.1089 + mOuterListener = nullptr; 1.1090 + mOuterContext = nullptr; 1.1091 + return NS_OK; 1.1092 +} 1.1093 + 1.1094 +/** nsIStreamListener methods **/ 1.1095 + 1.1096 +NS_IMETHODIMP 1.1097 +nsCORSPreflightListener::OnDataAvailable(nsIRequest *aRequest, 1.1098 + nsISupports *ctxt, 1.1099 + nsIInputStream *inStr, 1.1100 + uint64_t sourceOffset, 1.1101 + uint32_t count) 1.1102 +{ 1.1103 + uint32_t totalRead; 1.1104 + return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead); 1.1105 +} 1.1106 + 1.1107 +NS_IMETHODIMP 1.1108 +nsCORSPreflightListener::AsyncOnChannelRedirect(nsIChannel *aOldChannel, 1.1109 + nsIChannel *aNewChannel, 1.1110 + uint32_t aFlags, 1.1111 + nsIAsyncVerifyRedirectCallback *callback) 1.1112 +{ 1.1113 + // Only internal redirects allowed for now. 1.1114 + if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) 1.1115 + return NS_ERROR_DOM_BAD_URI; 1.1116 + 1.1117 + callback->OnRedirectVerifyCallback(NS_OK); 1.1118 + return NS_OK; 1.1119 +} 1.1120 + 1.1121 +NS_IMETHODIMP 1.1122 +nsCORSPreflightListener::GetInterface(const nsIID & aIID, void **aResult) 1.1123 +{ 1.1124 + return QueryInterface(aIID, aResult); 1.1125 +} 1.1126 + 1.1127 + 1.1128 +nsresult 1.1129 +NS_StartCORSPreflight(nsIChannel* aRequestChannel, 1.1130 + nsIStreamListener* aListener, 1.1131 + nsIPrincipal* aPrincipal, 1.1132 + bool aWithCredentials, 1.1133 + nsTArray<nsCString>& aUnsafeHeaders, 1.1134 + nsIChannel** aPreflightChannel) 1.1135 +{ 1.1136 + *aPreflightChannel = nullptr; 1.1137 + 1.1138 + nsAutoCString method; 1.1139 + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequestChannel)); 1.1140 + NS_ENSURE_TRUE(httpChannel, NS_ERROR_UNEXPECTED); 1.1141 + httpChannel->GetRequestMethod(method); 1.1142 + 1.1143 + nsCOMPtr<nsIURI> uri; 1.1144 + nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(uri)); 1.1145 + NS_ENSURE_SUCCESS(rv, rv); 1.1146 + 1.1147 + nsPreflightCache::CacheEntry* entry = 1.1148 + sPreflightCache ? 1.1149 + sPreflightCache->GetEntry(uri, aPrincipal, aWithCredentials, false) : 1.1150 + nullptr; 1.1151 + 1.1152 + if (entry && entry->CheckRequest(method, aUnsafeHeaders)) { 1.1153 + // We have a cached preflight result, just start the original channel 1.1154 + return aRequestChannel->AsyncOpen(aListener, nullptr); 1.1155 + } 1.1156 + 1.1157 + // Either it wasn't cached or the cached result has expired. Build a 1.1158 + // channel for the OPTIONS request. 1.1159 + 1.1160 + nsCOMPtr<nsILoadGroup> loadGroup; 1.1161 + rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 1.1162 + NS_ENSURE_SUCCESS(rv, rv); 1.1163 + 1.1164 + nsLoadFlags loadFlags; 1.1165 + rv = aRequestChannel->GetLoadFlags(&loadFlags); 1.1166 + NS_ENSURE_SUCCESS(rv, rv); 1.1167 + 1.1168 + nsCOMPtr<nsIChannel> preflightChannel; 1.1169 + rv = NS_NewChannel(getter_AddRefs(preflightChannel), uri, nullptr, 1.1170 + loadGroup, nullptr, loadFlags); 1.1171 + NS_ENSURE_SUCCESS(rv, rv); 1.1172 + 1.1173 + nsCOMPtr<nsIHttpChannel> preHttp = do_QueryInterface(preflightChannel); 1.1174 + NS_ASSERTION(preHttp, "Failed to QI to nsIHttpChannel!"); 1.1175 + 1.1176 + rv = preHttp->SetRequestMethod(NS_LITERAL_CSTRING("OPTIONS")); 1.1177 + NS_ENSURE_SUCCESS(rv, rv); 1.1178 + 1.1179 + // Set up listener which will start the original channel 1.1180 + nsCOMPtr<nsIStreamListener> preflightListener = 1.1181 + new nsCORSPreflightListener(aRequestChannel, aListener, nullptr, aPrincipal, 1.1182 + method, aWithCredentials); 1.1183 + NS_ENSURE_TRUE(preflightListener, NS_ERROR_OUT_OF_MEMORY); 1.1184 + 1.1185 + nsRefPtr<nsCORSListenerProxy> corsListener = 1.1186 + new nsCORSListenerProxy(preflightListener, aPrincipal, 1.1187 + aWithCredentials, method, 1.1188 + aUnsafeHeaders); 1.1189 + rv = corsListener->Init(preflightChannel); 1.1190 + NS_ENSURE_SUCCESS(rv, rv); 1.1191 + preflightListener = corsListener; 1.1192 + 1.1193 + // Start preflight 1.1194 + rv = preflightChannel->AsyncOpen(preflightListener, nullptr); 1.1195 + NS_ENSURE_SUCCESS(rv, rv); 1.1196 + 1.1197 + // Return newly created preflight channel 1.1198 + preflightChannel.forget(aPreflightChannel); 1.1199 + 1.1200 + return NS_OK; 1.1201 +} 1.1202 +