Movatterモバイル変換


[0]ホーム

URL:


Showing81322437:Bug2002687 - Don't retry transaction during shutdown, r=necko-reviewers,valentin
firefox-main
/netwerk/protocol/http/nsCORSListenerProxy.cpp(file symbol)

Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one athttp://mozilla.org/MPL/2.0/. */
#include"nsIThreadRetargetableStreamListener.h"
#include"nsString.h"
#include"mozilla/Assertions.h"
#include"mozilla/Components.h"
#include"mozilla/LinkedList.h"
#include"mozilla/StaticPrefs_content.h"
#include"mozilla/StoragePrincipalHelper.h"
#include"nsCORSListenerProxy.h"
#include"nsIChannel.h"
#include"nsIHttpChannel.h"
#include"HttpChannelChild.h"
#include"nsIHttpChannelInternal.h"
#include"nsError.h"
#include"nsContentUtils.h"
#include"nsNetUtil.h"
#include"nsComponentManagerUtils.h"
#include"nsIInterfaceRequestorUtils.h"
#include"nsServiceManagerUtils.h"
#include"nsMimeTypes.h"
#include"nsStringStream.h"
#include"nsGkAtoms.h"
#include"nsWhitespaceTokenizer.h"
#include"nsIChannelEventSink.h"
#include"nsIDNSService.h"
#include"nsIAsyncVerifyRedirectCallback.h"
#include"nsCharSeparatedTokenizer.h"
#include"nsAsyncRedirectVerifyHelper.h"
#include"nsClassHashtable.h"
#include"nsHashKeys.h"
#include"nsStreamUtils.h"
#include"mozilla/Preferences.h"
#include"nsIScriptError.h"
#include"nsILoadGroup.h"
#include"nsILoadContext.h"
#include"nsIConsoleService.h"
#include"nsICORSPreflightCache.h"
#include"nsINetworkInterceptController.h"
#include"nsICorsPreflightCallback.h"
#include"nsISupportsImpl.h"
#include"nsHttpChannel.h"
#include"mozilla/BasePrincipal.h"
#include"mozilla/ExpandedPrincipal.h"
#include"mozilla/LoadInfo.h"
#include"mozilla/NullPrincipal.h"
#include"nsIHttpHeaderVisitor.h"
#include"nsQueryObject.h"
#include"mozilla/StaticPrefs_network.h"
#include"mozilla/StaticPrefs_dom.h"
#include"mozilla/dom/nsHTTPSOnlyUtils.h"
#include"mozilla/dom/ReferrerInfo.h"
#include"mozilla/dom/RequestBinding.h"
#include"mozilla/glean/NetwerkProtocolHttpMetrics.h"
#include <algorithm>
usingnamespace mozilla;
usingnamespace mozilla::net;
structCORSCacheEntry;
#definePREFLIGHT_CACHE_SIZE 100
// 5 seconds is chosen to be compatible with Chromium.
#definePREFLIGHT_DEFAULT_EXPIRY_SECONDS 5
staticinlinensAutoStringGetStatusCodeAsString(nsIHttpChannel*aHttp) {
nsAutoStringresult;
uint32_tcode;
if (NS_SUCCEEDED(aHttp->GetResponseStatus(&code))) {
result.AppendInt(code);
}
returnresult;
}
staticvoidLogBlockedRequest(nsIRequest*aRequest,constchar*aProperty,
constchar16_t*aParam,uint32_taBlockingReason,
nsIHttpChannel*aCreatingChannel,
boolaIsWarning =false) {
nsresultrv =NS_OK;
nsCOMPtr<nsIChannel>channel =do_QueryInterface(aRequest);
if (!aIsWarning) {
NS_SetRequestBlockingReason(channel,aBlockingReason);
}
nsCOMPtr<nsIURI>aUri;
channel->GetURI(getter_AddRefs(aUri));
nsAutoCStringspec;
if (aUri) {
spec =aUri->GetSpecOrDefault();
}
// Generate the error message
nsAutoStringblockedMessage;
AutoTArray<nsString, 2>params;
CopyUTF8toUTF16(spec, *params.AppendElement());
if (aParam) {
params.AppendElement(aParam);
}
NS_ConvertUTF8toUTF16specUTF16(spec);
rv =nsContentUtils::FormatLocalizedString(
nsContentUtils::eSECURITY_PROPERTIES,aProperty,params,blockedMessage);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to log blocked cross-site request (no formalizedStr");
return;
}
nsAutoStringmsg(blockedMessage.get());
nsDependentCStringcategory(aProperty);
if (XRE_IsParentProcess()) {
if (aCreatingChannel) {
rv =aCreatingChannel->LogBlockedCORSRequest(msg,category,aIsWarning);
if (NS_SUCCEEDED(rv)) {
return;
}
}
NS_WARNING(
"Failed to log blocked cross-site request to web console from "
"parent->child, falling back to browser console");
}
boolprivateBrowsing =false;
if (aRequest) {
nsCOMPtr<nsILoadGroup>loadGroup;
rv =aRequest->GetLoadGroup(getter_AddRefs(loadGroup));
NS_ENSURE_SUCCESS_VOID(rv);
privateBrowsing =nsContentUtils::IsInPrivateBrowsing(loadGroup);
}
boolfromChromeContext =false;
if (channel) {
nsCOMPtr<nsILoadInfo>loadInfo =channel->LoadInfo();
fromChromeContext =loadInfo->TriggeringPrincipal()->IsSystemPrincipal();
}
// we are passing aProperty as the category so we can link to the
// appropriate MDN docs depending on the specific error.
uint64_tinnerWindowID =nsContentUtils::GetInnerWindowID(aRequest);
// The |innerWindowID| could be 0 if this request is created from script.
// We can always try top level content window id in this case,
// since the window id can lead to current top level window's web console.
if (!innerWindowID) {
if (nsCOMPtr<nsIHttpChannel>httpChannel =do_QueryInterface(aRequest)) {
(void)httpChannel->GetTopLevelContentWindowId(&innerWindowID);
}
}
nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID,privateBrowsing,
fromChromeContext,msg,category,
aIsWarning);
}
//////////////////////////////////////////////////////////////////////////
// Preflight cache
classnsPreflightCache :publicnsICORSPreflightCache {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSICORSPREFLIGHTCACHE
structTokenTime {
nsCStringtoken;
TimeStampexpirationTime;
};
already_AddRefed<CORSCacheEntry>GetEntry(
nsIURI*aURI,nsIPrincipal*aPrincipal,boolaWithCredentials,
const OriginAttributes&aOriginAttributes,boolaCreate);
voidRemoveEntries(nsIURI*aURI,nsIPrincipal*aPrincipal,
const OriginAttributes&aOriginAttributes);
voidPurgePrivateBrowsingEntries();
voidClear();
protected:
virtual ~nsPreflightCache() {Clear(); }
private:
nsRefPtrHashtable<nsCStringHashKey,CORSCacheEntry>mTable;
LinkedList<CORSCacheEntry>mList;
};
structCORSCacheEntry :publicLinkedListElement<CORSCacheEntry>,
publicnsICORSPreflightCacheEntry {
NS_DECL_ISUPPORTS
NS_DECL_NSICORSPREFLIGHTCACHEENTRY
explicitCORSCacheEntry(nsIURI*aUri,
const OriginAttributes&aOriginAttributes,
nsIPrincipal*aPrincipal,boolwithCredentials,
nsCString&aKey)
:mURI(aUri),
mOA(aOriginAttributes),
mPrincipal(aPrincipal),
mWithCredentials(withCredentials),
mKey(aKey) {}
voidPurgeExpired(TimeStampnow);
boolCheckRequest(constnsCString&aMethod,
constnsTArray<nsCString>&aHeaders);
nsCOMPtr<nsIURI>mURI;
const OriginAttributesmOA;
nsCOMPtr<nsIPrincipal>mPrincipal;
boolmWithCredentials;
nsCStringmKey;// serialized key
constTimeStampmCreationTime{TimeStamp::NowLoRes()};
boolmDoomed{false};
boolmIsProxyUsed{false};
nsTArray<nsPreflightCache::TokenTime>mMethods;
nsTArray<nsPreflightCache::TokenTime>mHeaders;
private:
virtual ~CORSCacheEntry() =default;
boolCheckDNSCache();
};
NS_IMPL_ISUPPORTS(nsPreflightCache,nsICORSPreflightCache)
NS_IMETHODIMP
nsPreflightCache::GetEntries(
nsIPrincipal*aPrincipal,
nsTArray<RefPtr<nsICORSPreflightCacheEntry>>&aEntries) {
for (autoiter =mTable.Iter(); !iter.Done();iter.Next()) {
RefPtr<nsIPrincipal>iterPrincipal;
iter.Data()->GetPrincipal(getter_AddRefs(iterPrincipal));
if (iterPrincipal->Equals(aPrincipal)) {
auto*entry =iter.UserData();
aEntries.AppendElement(entry);
}
}
returnNS_OK;
}
NS_IMETHODIMP
nsPreflightCache::ClearEntry(nsICORSPreflightCacheEntry*entry) {
nsCOMPtr<nsIURI>uri;
nsresultrv =entry->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsIPrincipal>principal;
rv =entry->GetPrincipal(getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv,rv);
const OriginAttributesoa =entry->OriginAttributesRef();
RemoveEntries(uri,principal,oa);
returnNS_OK;
}
// Will be initialized in EnsurePreflightCache.
staticStaticRefPtr<nsPreflightCache>sPreflightCache;
staticboolEnsurePreflightCache() {
if (sPreflightCache)returntrue;
RefPtr<nsPreflightCache>newCache(newnsPreflightCache());
sPreflightCache =newCache;
returntrue;
}
voidnsPreflightCache::PurgePrivateBrowsingEntries() {
for (autoiter =mTable.Iter(); !iter.Done();iter.Next()) {
auto*entry =iter.UserData();
if (entry->mOA.IsPrivateBrowsing()) {
// last private browsing window closed, remove preflight cache entries
entry->removeFrom(sPreflightCache->mList);
iter.Remove();
}
}
}
NS_IMPL_ISUPPORTS(CORSCacheEntry,nsICORSPreflightCacheEntry)
NS_IMETHODIMP
CORSCacheEntry::GetKey(nsACString&aKey) {
aKey =mKey;
returnNS_OK;
}
NS_IMETHODIMP
CORSCacheEntry::GetURI(nsIURI**aURI) {
*aURI =do_AddRef(mURI).take();
returnNS_OK;
}
NS_IMETHODIMP
CORSCacheEntry::GetOriginAttributes(JSContext*aCx,
JS::MutableHandle<JS::Value>aVal) {
if (NS_WARN_IF(!ToJSValue(aCx,mOA,aVal))) {
returnNS_ERROR_FAILURE;
}
returnNS_OK;
}
const OriginAttributes&CORSCacheEntry::OriginAttributesRef() {returnmOA; }
NS_IMETHODIMP
CORSCacheEntry::GetPrincipal(nsIPrincipal**aPrincipal) {
*aPrincipal =do_AddRef(mPrincipal).take();
returnNS_OK;
}
NS_IMETHODIMP
CORSCacheEntry::GetPrivateBrowsing(bool*aPrivateBrowsing) {
*aPrivateBrowsing =mOA.IsPrivateBrowsing();
returnNS_OK;
}
NS_IMETHODIMP
CORSCacheEntry::GetWithCredentials(bool*aWithCredentials) {
*aWithCredentials =mWithCredentials;
returnNS_OK;
}
voidCORSCacheEntry::PurgeExpired(TimeStampnow) {
for (uint32_ti = 0,len =mMethods.Length();i <len; ++i) {
if (now >=mMethods[i].expirationTime) {
mMethods.UnorderedRemoveElementAt(i);
--i;// Examine the element again, if necessary.
--len;
}
}
for (uint32_ti = 0,len =mHeaders.Length();i <len; ++i) {
if (now >=mHeaders[i].expirationTime) {
mHeaders.UnorderedRemoveElementAt(i);
--i;// Examine the element again, if necessary.
--len;
}
}
}
boolCORSCacheEntry::CheckDNSCache() {
// When proxy is used, the DNS lookup is done by proxy, so we skip this check.
if (mIsProxyUsed) {
returntrue;
}
nsCOMPtr<nsIDNSService>dns;
dns = mozilla::components::DNS::Service();
if (!dns) {
returnfalse;
}
nsAutoCStringhost;
if (NS_FAILED(mURI->GetAsciiHost(host))) {
returnfalse;
}
nsCOMPtr<nsIDNSRecord>record;
nsresultrv =dns->ResolveNative(host,nsIDNSService::RESOLVE_OFFLINE,mOA,
getter_AddRefs(record));
if (NS_FAILED(rv) || !record) {
returnfalse;
}
nsCOMPtr<nsIDNSAddrRecord>addrRec =do_QueryInterface(record);
if (!addrRec) {
returnfalse;
}
TimeStamplastUpdate;
(void)addrRec->GetLastUpdate(&lastUpdate);
if (lastUpdate >mCreationTime) {
returnfalse;
}
returntrue;
}
boolCORSCacheEntry::CheckRequest(constnsCString&aMethod,
constnsTArray<nsCString>&aHeaders) {
PurgeExpired(TimeStamp::NowLoRes());
if (!CheckDNSCache()) {
mMethods.Clear();
mHeaders.Clear();
mDoomed =true;
returnfalse;
}
if (!aMethod.EqualsLiteral("GET") && !aMethod.EqualsLiteral("POST")) {
structCheckToken {
boolEquals(constnsPreflightCache::TokenTime&e,
constnsCString&method)const {
returne.token.Equals(method);
}
};
if (!mMethods.Contains(aMethod,CheckToken())) {
returnfalse;
}
}
structCheckHeaderToken {
boolEquals(constnsPreflightCache::TokenTime&e,
constnsCString&header)const {
returne.token.Equals(header,nsCaseInsensitiveCStringComparator);
}
}checker;
for (uint32_ti = 0;i <aHeaders.Length(); ++i) {
if (!mHeaders.Contains(aHeaders[i],checker)) {
returnfalse;
}
}
returntrue;
}
already_AddRefed<CORSCacheEntry>nsPreflightCache::GetEntry(
nsIURI*aURI,nsIPrincipal*aPrincipal,boolaWithCredentials,
const OriginAttributes&aOriginAttributes,boolaCreate) {
nsAutoCStringkey;
if (NS_FAILED(aPrincipal->GetPrefLightCacheKey(aURI,aWithCredentials,
aOriginAttributes,key))) {
NS_WARNING("Invalid cache key!");
returnnullptr;
}
RefPtr<CORSCacheEntry>existingEntry =nullptr;
if ((existingEntry =mTable.Get(key))) {
if (existingEntry->mDoomed) {
existingEntry->removeFrom(mList);
mTable.Remove(key);
}else {
// Entry already existed so just return it. Also update the LRU list.
// Move to the head of the list.
existingEntry->removeFrom(mList);
mList.insertFront(existingEntry);
returnexistingEntry.forget();
}
}
if (!aCreate) {
returnnullptr;
}
// 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.
RefPtr<CORSCacheEntry>newEntry =newCORSCacheEntry(
aURI,aOriginAttributes,aPrincipal,aWithCredentials,key);
NS_ASSERTION(mTable.Count() <=PREFLIGHT_CACHE_SIZE,
"Something is borked, too many entries in the cache!");
// Now enforce the max count.
if (mTable.Count() ==PREFLIGHT_CACHE_SIZE) {
// Try to kick out all the expired entries.
TimeStampnow =TimeStamp::NowLoRes();
for (autoiter =mTable.Iter(); !iter.Done();iter.Next()) {
auto*entry =iter.UserData();
entry->PurgeExpired(now);
if (entry->mHeaders.IsEmpty() &&entry->mMethods.IsEmpty()) {
// Expired, remove from the list as well as the hash table.
entry->removeFrom(sPreflightCache->mList);
iter.Remove();
}
}
// If that didn't remove anything then kick out the least recently used
// entry.
if (mTable.Count() ==PREFLIGHT_CACHE_SIZE) {
CORSCacheEntry*lruEntry =static_cast<CORSCacheEntry*>(mList.popLast());
MOZ_ASSERT(lruEntry);
// This will delete 'lruEntry'.
nsAutoCStringlruKey;
lruEntry->GetKey(lruKey);
mTable.Remove(lruKey);
NS_ASSERTION(mTable.Count() ==PREFLIGHT_CACHE_SIZE - 1,
"Somehow tried to remove an entry that was never added!");
}
}
CORSCacheEntry*newEntryWeak =newEntry.get();
mTable.InsertOrUpdate(key,newEntry);
mList.insertFront(newEntryWeak);
returnnewEntry.forget();
}
voidnsPreflightCache::RemoveEntries(
nsIURI*aURI,nsIPrincipal*aPrincipal,
const OriginAttributes&aOriginAttributes) {
RefPtr<CORSCacheEntry>entry;
nsCStringkey;
if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI,true,
aOriginAttributes,key))) {
if ((entry =mTable.Get(key))) {
entry->removeFrom(mList);
mTable.Remove(key);
}
}
if (NS_SUCCEEDED(aPrincipal->GetPrefLightCacheKey(aURI,false,
aOriginAttributes,key))) {
if ((entry =mTable.Get(key))) {
entry->removeFrom(mList);
mTable.Remove(key);
}
}
}
voidnsPreflightCache::Clear() {
mList.clear();
mTable.Clear();
}
//////////////////////////////////////////////////////////////////////////
// nsCORSListenerProxy
NS_IMPL_ISUPPORTS(nsCORSListenerProxy,nsIStreamListener,nsIRequestObserver,
nsIChannelEventSink,nsIInterfaceRequestor,
nsIThreadRetargetableStreamListener)
/* static */
already_AddRefed<nsICORSPreflightCache>
nsCORSListenerProxy::GetCORSPreflightSingleton() {
NS_ASSERTION(!IsNeckoChild(),"not a parent process");
if (!EnsurePreflightCache()) {
NS_ASSERTION(false,"Failed to get the preflightCache");
}
returndo_AddRef(sPreflightCache);
}
/* static */
voidnsCORSListenerProxy::Shutdown() {sPreflightCache =nullptr; }
/* static */
voidnsCORSListenerProxy::ClearCache() {
if (!sPreflightCache) {
return;
}
sPreflightCache->Clear();
}
// static
voidnsCORSListenerProxy::ClearPrivateBrowsingCache() {
if (!sPreflightCache) {
return;
}
sPreflightCache->PurgePrivateBrowsingEntries();
}
// Usually, when using an expanded principal, there's no particularly good
// origin to do the request with. However if the expanded principal only wraps
// one principal, we can use that one instead.
//
// This is needed so that DevTools can still do CORS-enabled requests (since
// DevTools uses a triggering principal expanding the node principal to bypass
// CSP checks, see Element::CreateDevToolsPrincipal(),bug 1604562, and bug
// 1391994).
staticnsIPrincipal*GetOriginHeaderPrincipal(nsIPrincipal*aPrincipal) {
while (aPrincipal &&aPrincipal->GetIsExpandedPrincipal()) {
auto*ep =BasePrincipal::Cast(aPrincipal)->As<ExpandedPrincipal>();
if (ep->AllowList().Length() != 1) {
break;
}
aPrincipal =ep->AllowList()[0];
}
returnaPrincipal;
}
nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener*aOuter,
nsIPrincipal*aRequestingPrincipal,
boolaWithCredentials)
:mOuterListener(aOuter),
mRequestingPrincipal(aRequestingPrincipal),
mOriginHeaderPrincipal(GetOriginHeaderPrincipal(aRequestingPrincipal)),
mWithCredentials(aWithCredentials),
mRequestApproved(false),
mHasBeenCrossSite(false),
#ifdefDEBUG
mInited(false),
#endif
mMutex("nsCORSListenerProxy") {
}
nsresultnsCORSListenerProxy::Init(nsIChannel*aChannel,
DataURIHandlingaAllowDataURI) {
aChannel->GetNotificationCallbacks(
getter_AddRefs(mOuterNotificationCallbacks));
aChannel->SetNotificationCallbacks(this);
nsresultrv =
UpdateChannel(aChannel,aAllowDataURI,UpdateType::Default,false);
if (NS_FAILED(rv)) {
{
MutexAutoLocklock(mMutex);
mOuterListener =nullptr;
}
mRequestingPrincipal =nullptr;
mOriginHeaderPrincipal =nullptr;
mOuterNotificationCallbacks =nullptr;
mHttpChannel =nullptr;
}
#ifdefDEBUG
mInited =true;
#endif
returnrv;
}
NS_IMETHODIMP
nsCORSListenerProxy::OnStartRequest(nsIRequest*aRequest) {
MOZ_ASSERT(mInited,"nsCORSListenerProxy has not been initialized properly");
nsresultrv =CheckRequestApproved(aRequest);
mRequestApproved =NS_SUCCEEDED(rv);
if (!mRequestApproved) {
nsCOMPtr<nsIChannel>channel =do_QueryInterface(aRequest);
if (channel) {
nsCOMPtr<nsIURI>uri;
NS_GetFinalChannelURI(channel,getter_AddRefs(uri));
if (uri) {
OriginAttributesattrs;
StoragePrincipalHelper::GetOriginAttributesForNetworkState(channel,
attrs);
if (sPreflightCache) {
// OK to use mRequestingPrincipal since preflights never get
// redirected.
sPreflightCache->RemoveEntries(uri,mRequestingPrincipal,attrs);
}else {
nsCOMPtr<nsIHttpChannelChild>httpChannelChild =
do_QueryInterface(channel);
if (httpChannelChild) {
rv =httpChannelChild->RemoveCorsPreflightCacheEntry(
uri,mRequestingPrincipal,attrs);
if (NS_FAILED(rv)) {
// Only warn here to ensure we fall through the request Cancel()
// and outer listener OnStartRequest() calls.
NS_WARNING("Failed to remove CORS preflight cache entry!");
}
}
}
}
}
aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
nsCOMPtr<nsIStreamListener>listener;
{
MutexAutoLocklock(mMutex);
listener =mOuterListener;
}
listener->OnStartRequest(aRequest);
// Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
returnNS_ERROR_DOM_BAD_URI;
}
nsCOMPtr<nsIStreamListener>listener;
{
MutexAutoLocklock(mMutex);
listener =mOuterListener;
}
returnlistener->OnStartRequest(aRequest);
}
namespace {
classCheckOriginHeader final :publicnsIHttpHeaderVisitor {
public:
NS_DECL_ISUPPORTS
CheckOriginHeader() =default;
NS_IMETHOD
VisitHeader(constnsACString&aHeader,constnsACString&aValue) override {
if (aHeader.EqualsLiteral("Access-Control-Allow-Origin")) {
mHeaderCount++;
}
if (mHeaderCount > 1) {
returnNS_ERROR_DOM_BAD_URI;
}
returnNS_OK;
}
private:
uint32_tmHeaderCount{0};
~CheckOriginHeader() =default;
};
NS_IMPL_ISUPPORTS(CheckOriginHeader,nsIHttpHeaderVisitor)
}// namespace
nsresultnsCORSListenerProxy::CheckRequestApproved(nsIRequest*aRequest) {
// Check if this was actually a cross domain request
if (!mHasBeenCrossSite) {
returnNS_OK;
}
nsCOMPtr<nsIHttpChannel>topChannel;
topChannel.swap(mHttpChannel);
if (StaticPrefs::content_cors_disable()) {
LogBlockedRequest(aRequest,"CORSDisabled",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSDISABLED,topChannel);
returnNS_ERROR_DOM_BAD_URI;
}
// Check if the request failed
nsresultstatus;
nsresultrv =aRequest->GetStatus(&status);
if (NS_FAILED(rv)) {
LogBlockedRequest(aRequest,"CORSDidNotSucceed2",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
topChannel);
returnrv;
}
if (NS_FAILED(status)) {
if (NS_BINDING_ABORTED !=status) {
// Don't want to log mere cancellation as an error.
LogBlockedRequest(aRequest,"CORSDidNotSucceed2",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
topChannel);
}
returnstatus;
}
// Test that things worked on a HTTP level
nsCOMPtr<nsIHttpChannel>http =do_QueryInterface(aRequest);
if (!http) {
nsCOMPtr<nsIChannel>channel =do_QueryInterface(aRequest);
nsCOMPtr<nsIURI>uri;
NS_GetFinalChannelURI(channel,getter_AddRefs(uri));
if (uri &&uri->SchemeIs("moz-extension")) {
// moz-extension:-URLs do not support CORS, but can universally be read
// if an extension lists the resource in web_accessible_resources.
// Access will be checked in UpdateChannel.
returnNS_OK;
}
LogBlockedRequest(aRequest,"CORSRequestNotHttp",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
topChannel);
returnNS_ERROR_DOM_BAD_URI;
}
nsCOMPtr<nsILoadInfo>loadInfo =http->LoadInfo();
if (loadInfo->GetServiceWorkerTaintingSynthesized()) {
// For synthesized responses, we don't need to perform any checks.
// Note: This would be unsafe if we ever changed our behavior to allow
// service workers to intercept CORS preflights.
returnNS_OK;
}
// Check the Access-Control-Allow-Origin header
RefPtr<CheckOriginHeader>visitor =newCheckOriginHeader();
nsAutoCStringallowedOriginHeader;
// check for duplicate headers
rv =http->VisitOriginalResponseHeaders(visitor);
if (NS_FAILED(rv)) {
LogBlockedRequest(
aRequest,"CORSMultipleAllowOriginNotAllowed",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSMULTIPLEALLOWORIGINNOTALLOWED,
topChannel);
returnrv;
}
rv =http->GetResponseHeader("Access-Control-Allow-Origin"_ns,
allowedOriginHeader);
if (NS_FAILED(rv)) {
autostatusCode =GetStatusCodeAsString(http);
LogBlockedRequest(aRequest,"CORSMissingAllowOrigin2",statusCode.get(),
nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWORIGIN,
topChannel);
returnrv;
}
//Bug 1210985 - Explicitly point out the error that the credential is
// not supported if the allowing origin is '*'. Note that this check
// has to be done before the condition
//
// >> if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*"))
//
// below since "if (A && B)" is included in "if (A || !B)".
//
if (mWithCredentials &&allowedOriginHeader.EqualsLiteral("*")) {
LogBlockedRequest(aRequest,"CORSNotSupportingCredentials",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSNOTSUPPORTINGCREDENTIALS,
topChannel);
returnNS_ERROR_DOM_BAD_URI;
}
if (mWithCredentials || !allowedOriginHeader.EqualsLiteral("*")) {
MOZ_ASSERT(!mOriginHeaderPrincipal->GetIsExpandedPrincipal());
nsAutoCStringorigin;
mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
if (!allowedOriginHeader.Equals(origin)) {
LogBlockedRequest(
aRequest,"CORSAllowOriginNotMatchingOrigin",
NS_ConvertUTF8toUTF16(allowedOriginHeader).get(),
nsILoadInfo::BLOCKING_REASON_CORSALLOWORIGINNOTMATCHINGORIGIN,
topChannel);
returnNS_ERROR_DOM_BAD_URI;
}
}
// Check Access-Control-Allow-Credentials header
if (mWithCredentials) {
nsAutoCStringallowCredentialsHeader;
rv =http->GetResponseHeader("Access-Control-Allow-Credentials"_ns,
allowCredentialsHeader);
if (!allowCredentialsHeader.EqualsLiteral("true")) {
LogBlockedRequest(
aRequest,"CORSMissingAllowCredentials",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWCREDENTIALS,topChannel);
returnNS_ERROR_DOM_BAD_URI;
}
}
returnNS_OK;
}
NS_IMETHODIMP
nsCORSListenerProxy::OnStopRequest(nsIRequest*aRequest,nsresultaStatusCode) {
MOZ_ASSERT(mInited,"nsCORSListenerProxy has not been initialized properly");
nsCOMPtr<nsIStreamListener>listener;
{
MutexAutoLocklock(mMutex);
listener = std::move(mOuterListener);
}
nsresultrv =listener->OnStopRequest(aRequest,aStatusCode);
mOuterNotificationCallbacks =nullptr;
mHttpChannel =nullptr;
returnrv;
}
NS_IMETHODIMP
nsCORSListenerProxy::OnDataAvailable(nsIRequest*aRequest,
nsIInputStream*aInputStream,
uint64_taOffset,uint32_taCount) {
// NB: This can be called on any thread! But we're guaranteed that it is
// called between OnStartRequest and OnStopRequest, so we don't need to worry
// about races.
MOZ_ASSERT(mInited,"nsCORSListenerProxy has not been initialized properly");
if (!mRequestApproved) {
// Reason for NS_ERROR_DOM_BAD_URI already logged in CheckRequestApproved()
returnNS_ERROR_DOM_BAD_URI;
}
nsCOMPtr<nsIStreamListener>listener;
{
MutexAutoLocklock(mMutex);
listener =mOuterListener;
}
returnlistener->OnDataAvailable(aRequest,aInputStream,aOffset,aCount);
}
NS_IMETHODIMP
nsCORSListenerProxy::OnDataFinished(nsresultaStatus) {
nsCOMPtr<nsIStreamListener>listener;
{
MutexAutoLocklock(mMutex);
listener =mOuterListener;
}
if (!listener) {
returnNS_ERROR_FAILURE;
}
nsCOMPtr<nsIThreadRetargetableStreamListener>retargetableListener =
do_QueryInterface(listener);
if (retargetableListener) {
returnretargetableListener->OnDataFinished(aStatus);
}
returnNS_OK;
}
voidnsCORSListenerProxy::SetInterceptController(
nsINetworkInterceptController*aInterceptController) {
mInterceptController =aInterceptController;
}
NS_IMETHODIMP
nsCORSListenerProxy::GetInterface(constnsIID&aIID,void**aResult) {
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
*aResult =static_cast<nsIChannelEventSink*>(this);
NS_ADDREF_THIS();
returnNS_OK;
}
if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
mInterceptController) {
nsCOMPtr<nsINetworkInterceptController>copy(mInterceptController);
*aResult =copy.forget().take();
returnNS_OK;
}
returnmOuterNotificationCallbacks
?mOuterNotificationCallbacks->GetInterface(aIID,aResult)
:NS_ERROR_NO_INTERFACE;
}
NS_IMETHODIMP
nsCORSListenerProxy::AsyncOnChannelRedirect(
nsIChannel*aOldChannel,nsIChannel*aNewChannel,uint32_taFlags,
nsIAsyncVerifyRedirectCallback*aCb) {
nsresultrv;
if (NS_IsInternalSameURIRedirect(aOldChannel,aNewChannel,aFlags) ||
NS_IsHSTSUpgradeRedirect(aOldChannel,aNewChannel,aFlags)) {
// Internal redirects still need to be updated in order to maintain
// the correct headers. We use DataURIHandling::Allow, since unallowed
// data URIs should have been blocked before we got to the internal
// redirect.
rv =UpdateChannel(aNewChannel,DataURIHandling::Allow,
UpdateType::InternalOrHSTSRedirect,false);
if (NS_FAILED(rv)) {
NS_WARNING(
"nsCORSListenerProxy::AsyncOnChannelRedirect: "
"internal redirect UpdateChannel() returned failure");
aOldChannel->Cancel(rv);
returnrv;
}
}else {
mIsRedirect =true;
// A real, external redirect. Perform CORS checking on new URL.
rv =CheckRequestApproved(aOldChannel);
if (NS_FAILED(rv)) {
nsCOMPtr<nsIURI>oldURI;
NS_GetFinalChannelURI(aOldChannel,getter_AddRefs(oldURI));
if (oldURI) {
OriginAttributesattrs;
StoragePrincipalHelper::GetOriginAttributesForNetworkState(aOldChannel,
attrs);
if (sPreflightCache) {
// OK to use mRequestingPrincipal since preflights never get
// redirected.
sPreflightCache->RemoveEntries(oldURI,mRequestingPrincipal,attrs);
}else {
nsCOMPtr<nsIHttpChannelChild>httpChannelChild =
do_QueryInterface(aOldChannel);
if (httpChannelChild) {
rv =httpChannelChild->RemoveCorsPreflightCacheEntry(
oldURI,mRequestingPrincipal,attrs);
if (NS_FAILED(rv)) {
// Only warn here to ensure we call the channel Cancel() below
NS_WARNING("Failed to remove CORS preflight cache entry!");
}
}
}
}
aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
// Reason for NS_ERROR_DOM_BAD_URI already logged in
// CheckRequestApproved()
returnNS_ERROR_DOM_BAD_URI;
}
if (mHasBeenCrossSite) {
// Once we've been cross-site, cross-origin redirects reset our source
// origin. Note that we need to call GetChannelURIPrincipal() because
// we are looking for the principal that is actually being loaded and not
// the principal that initiated the load.
nsCOMPtr<nsIPrincipal>oldChannelPrincipal;
nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
aOldChannel,getter_AddRefs(oldChannelPrincipal));
nsCOMPtr<nsIPrincipal>newChannelPrincipal;
nsContentUtils::GetSecurityManager()->GetChannelURIPrincipal(
aNewChannel,getter_AddRefs(newChannelPrincipal));
if (!oldChannelPrincipal || !newChannelPrincipal) {
rv =NS_ERROR_OUT_OF_MEMORY;
}
if (NS_FAILED(rv)) {
aOldChannel->Cancel(rv);
returnrv;
}
if (!oldChannelPrincipal->Equals(newChannelPrincipal)) {
// Spec says to set our source origin to a unique origin.
mOriginHeaderPrincipal =
NullPrincipal::CreateWithInheritedAttributes(oldChannelPrincipal);
}
}
boolrewriteToGET =false;
// We need to strip auth header from preflight request for
// cross-origin redirects.
// SeeBug 1874132
boolstripAuthHeader =
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel,aNewChannel,aFlags);
nsCOMPtr<nsIHttpChannel>oldHttpChannel =do_QueryInterface(aOldChannel);
if (oldHttpChannel) {
nsAutoCStringmethod;
(void)oldHttpChannel->GetRequestMethod(method);
(void)oldHttpChannel->ShouldStripRequestBodyHeader(method, &rewriteToGET);
}
rv =UpdateChannel(
aNewChannel,DataURIHandling::Disallow,
rewriteToGET ?UpdateType::StripRequestBodyHeader :UpdateType::Default,
stripAuthHeader);
if (NS_FAILED(rv)) {
NS_WARNING(
"nsCORSListenerProxy::AsyncOnChannelRedirect: "
"UpdateChannel() returned failure");
aOldChannel->Cancel(rv);
returnrv;
}
}
nsCOMPtr<nsIChannelEventSink>outer =
do_GetInterface(mOuterNotificationCallbacks);
if (outer) {
returnouter->AsyncOnChannelRedirect(aOldChannel,aNewChannel,aFlags,aCb);
}
aCb->OnRedirectVerifyCallback(NS_OK);
returnNS_OK;
}
NS_IMETHODIMP
nsCORSListenerProxy::CheckListenerChain() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIThreadRetargetableStreamListener>retargetableListener;
{
MutexAutoLocklock(mMutex);
retargetableListener =do_QueryInterface(mOuterListener);
}
if (!retargetableListener) {
returnNS_ERROR_NO_INTERFACE;
}
returnretargetableListener->CheckListenerChain();
}
// Please note that the CSP directive 'upgrade-insecure-requests' and the
// HTTPS-Only Mode are relying on the promise that channels get updated from
// http: to https: before the channel fetches any data from the netwerk. Such
// channels should not be blocked by CORS and marked as cross origin requests.
// E.g.: toplevel page:https://www.example.com loads
// xhr:http://www.example.com/foo which gets updated to
//https://www.example.com/foo
// In such a case we should bail out of CORS and rely on the promise that
// nsHttpChannel::Connect() upgrades the request from http to https.
boolCheckInsecureUpgradePreventsCORS(nsIPrincipal*aRequestingPrincipal,
nsIChannel*aChannel) {
nsCOMPtr<nsIURI>channelURI;
nsresultrv =NS_GetFinalChannelURI(aChannel,getter_AddRefs(channelURI));
NS_ENSURE_SUCCESS(rv,false);
// upgrade insecure requests is only applicable to http requests
if (!channelURI->SchemeIs("http")) {
returnfalse;
}
nsCOMPtr<nsIURI>originalURI;
rv =aChannel->GetOriginalURI(getter_AddRefs(originalURI));
NS_ENSURE_SUCCESS(rv,false);
nsAutoCStringprincipalHost,channelHost,origChannelHost;
// if we can not query a host from the uri, there is nothing to do
if (NS_FAILED(aRequestingPrincipal->GetAsciiHost(principalHost)) ||
NS_FAILED(channelURI->GetAsciiHost(channelHost)) ||
NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) {
returnfalse;
}
// if the hosts do not match, there is nothing to do
if (!principalHost.EqualsIgnoreCase(channelHost.get())) {
returnfalse;
}
// also check that uri matches the one of the originalURI
if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) {
returnfalse;
}
returntrue;
}
nsresultnsCORSListenerProxy::UpdateChannel(nsIChannel*aChannel,
DataURIHandlingaAllowDataURI,
UpdateTypeaUpdateType,
boolaStripAuthHeader) {
MOZ_ASSERT_IF(aUpdateType ==UpdateType::InternalOrHSTSRedirect,
!aStripAuthHeader);
nsCOMPtr<nsIURI>uri,originalURI;
nsresultrv =NS_GetFinalChannelURI(aChannel,getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv,rv);
rv =aChannel->GetOriginalURI(getter_AddRefs(originalURI));
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsILoadInfo>loadInfo =aChannel->LoadInfo();
// Introduced for DevTools in order to allow overriding some requests
// with the content of data: URIs.
if (loadInfo->GetAllowInsecureRedirectToDataURI() &&uri->SchemeIs("data")) {
returnNS_OK;
}
// exempt data URIs from the same origin check.
if (aAllowDataURI ==DataURIHandling::Allow &&originalURI ==uri) {
if (uri->SchemeIs("data")) {
returnNS_OK;
}
if (loadInfo->GetAboutBlankInherits() &&NS_IsAboutBlank(uri)) {
returnNS_OK;
}
}
// Set CORS attributes on channel so that intercepted requests get correct
// values. We have to do this here because the CheckMayLoad checks may lead
// to early return. We can't be sure this is an http channel though, so we
// can't return early on failure.
nsCOMPtr<nsIHttpChannelInternal>internal =do_QueryInterface(aChannel);
if (internal) {
rv =internal->SetRequestMode(dom::RequestMode::Cors);
NS_ENSURE_SUCCESS(rv,rv);
rv =internal->SetCorsIncludeCredentials(mWithCredentials);
NS_ENSURE_SUCCESS(rv,rv);
}
// TODO:Bug 1353683
// consider calling SetBlockedRequest in nsCORSListenerProxy::UpdateChannel
//
// Check that the uri is ok to load
uint32_tflags =loadInfo->CheckLoadURIFlags();
rv =nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
mRequestingPrincipal,uri,flags,loadInfo->GetInnerWindowID());
NS_ENSURE_SUCCESS(rv,rv);
if (originalURI !=uri) {
rv =nsContentUtils::GetSecurityManager()->CheckLoadURIWithPrincipal(
mRequestingPrincipal,originalURI,flags,loadInfo->GetInnerWindowID());
NS_ENSURE_SUCCESS(rv,rv);
}
if (uri->SchemeIs("moz-extension")) {
// moz-extension:-URLs do not support CORS, but can universally be read
// if an extension lists the resource in web_accessible_resources.
// This is enforced via the CheckLoadURIWithPrincipal call above:
// moz-extension resources have the URI_DANGEROUS_TO_LOAD flag, unless
// listed in web_accessible_resources.
returnNS_OK;
}
if (!mHasBeenCrossSite &&
NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(uri,false)) &&
(originalURI ==uri ||
NS_SUCCEEDED(mRequestingPrincipal->CheckMayLoad(originalURI,false)))) {
returnNS_OK;
}
// If the CSP directive 'upgrade-insecure-requests' is used or the HTTPS-Only
// Mode is enabled then we should not incorrectly require CORS if the only
// difference of a subresource request and the main page is the scheme. e.g.
// toplevel page:https://www.example.com loads
// xhr:http://www.example.com/somefoo,
// then the xhr request will be upgraded to https before it fetches any data
// from the netwerk, hence we shouldn't require CORS in that specific case.
if (CheckInsecureUpgradePreventsCORS(mRequestingPrincipal,aChannel)) {
// Check if https-only mode upgrades this later anyway
nsCOMPtr<nsILoadInfo>loadinfo =aChannel->LoadInfo();
if (nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(loadinfo)) {
returnNS_OK;
}
// Check if 'upgrade-insecure-requests' is used
if (loadInfo->GetUpgradeInsecureRequests() ||
loadInfo->GetBrowserUpgradeInsecureRequests()) {
returnNS_OK;
}
}
// Check if we need to do a preflight, and if so set one up. This must be
// called once we know that the request is going, or has gone, cross-origin.
rv =CheckPreflightNeeded(aChannel,aUpdateType,aStripAuthHeader);
NS_ENSURE_SUCCESS(rv,rv);
// It's a cross site load
mHasBeenCrossSite =true;
if (mIsRedirect) {
//https://fetch.spec.whatwg.org/#http-redirect-fetch
// Step 9. If request’s mode is "cors", locationURL includes credentials,
// and request’s origin is not same origin with locationURL’s origin,
// then return a network error.
nsAutoCStringuserpass;
uri->GetUserPass(userpass);
NS_ENSURE_TRUE(userpass.IsEmpty(),NS_ERROR_DOM_BAD_URI);
}
// If we have an expanded principal here, we'll reject the CORS request,
// because we can't send a useful Origin header which is required for CORS.
if (nsContentUtils::IsExpandedPrincipal(mOriginHeaderPrincipal)) {
nsCOMPtr<nsIHttpChannel>httpChannel =do_QueryInterface(aChannel);
LogBlockedRequest(aChannel,"CORSOriginHeaderNotAdded",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSORIGINHEADERNOTADDED,
httpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
// Add the Origin header
nsAutoCStringorigin;
rv =mOriginHeaderPrincipal->GetWebExposedOriginSerialization(origin);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsIHttpChannel>http =do_QueryInterface(aChannel);
NS_ENSURE_TRUE(http,NS_ERROR_FAILURE);
// hide the Origin header when requesting from .onion and requesting CORS
if (StaticPrefs::network_http_referer_hideOnionSource()) {
if (mOriginHeaderPrincipal->GetIsOnion()) {
origin.AssignLiteral("null");
}
}
rv =http->SetRequestHeader(net::nsHttp::Origin.val(),origin,false);
NS_ENSURE_SUCCESS(rv,rv);
// Make cookie-less if needed. We don't need to do anything here if the
// channel was opened with AsyncOpen, since then AsyncOpen will take
// care of the cookie policy for us.
if (!mWithCredentials) {
nsLoadFlagsflags;
rv =http->GetLoadFlags(&flags);
NS_ENSURE_SUCCESS(rv,rv);
flags |=nsIRequest::LOAD_ANONYMOUS;
if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
flags |=nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
}
rv =http->SetLoadFlags(flags);
NS_ENSURE_SUCCESS(rv,rv);
}
mHttpChannel =http;
returnNS_OK;
}
nsresultnsCORSListenerProxy::CheckPreflightNeeded(nsIChannel*aChannel,
UpdateTypeaUpdateType,
boolaStripAuthHeader) {
// If this caller isn't using AsyncOpen, or if this *is* a preflight channel,
// then we shouldn't initiate preflight for this channel.
nsCOMPtr<nsILoadInfo>loadInfo =aChannel->LoadInfo();
if (loadInfo->GetSecurityMode() !=
nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT ||
loadInfo->GetIsPreflight()) {
returnNS_OK;
}
booldoPreflight =loadInfo->GetForcePreflight();
nsCOMPtr<nsIHttpChannel>http =do_QueryInterface(aChannel);
if (!http) {
// Note: A preflight is not needed for moz-extension:-requests either, but
// there is already a check for that in the caller of CheckPreflightNeeded,
// in UpdateChannel.
LogBlockedRequest(aChannel,"CORSRequestNotHttp",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSREQUESTNOTHTTP,
mHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
nsAutoCStringmethod;
(void)http->GetRequestMethod(method);
if (!method.LowerCaseEqualsLiteral("get") &&
!method.LowerCaseEqualsLiteral("post") &&
!method.LowerCaseEqualsLiteral("head")) {
doPreflight =true;
}
// Avoid copying the array here
constnsTArray<nsCString>&loadInfoHeaders =loadInfo->CorsUnsafeHeaders();
if (!loadInfoHeaders.IsEmpty()) {
doPreflight =true;
}
// Add Content-Type header if needed
nsTArray<nsCString>headers;
nsAutoCStringcontentTypeHeader;
nsresultrv =http->GetRequestHeader("Content-Type"_ns,contentTypeHeader);
// GetRequestHeader return an error if the header is not set. Don't add
// "content-type" to the list if that's the case.
if (NS_SUCCEEDED(rv) &&
!nsContentUtils::IsAllowedNonCorsContentType(contentTypeHeader) &&
!loadInfoHeaders.Contains("content-type"_ns,
nsCaseInsensitiveCStringArrayComparator())) {
headers.AppendElements(loadInfoHeaders);
headers.AppendElement("content-type"_ns);
doPreflight =true;
}
if (!doPreflight) {
returnNS_OK;
}
nsCOMPtr<nsIHttpChannelInternal>internal =do_QueryInterface(http);
if (!internal) {
autostatusCode =GetStatusCodeAsString(http);
LogBlockedRequest(aChannel,"CORSDidNotSucceed2",statusCode.get(),
nsILoadInfo::BLOCKING_REASON_CORSDIDNOTSUCCEED,
mHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
internal->SetCorsPreflightParameters(
headers.IsEmpty() ?loadInfoHeaders :headers,
aUpdateType ==UpdateType::StripRequestBodyHeader,aStripAuthHeader);
returnNS_OK;
}
//////////////////////////////////////////////////////////////////////////
// Preflight proxy
// Class used as streamlistener and notification callback when
// doing the initial OPTIONS request for a CORS check
classnsCORSPreflightListener final :publicnsIStreamListener,
publicnsIInterfaceRequestor,
publicnsIChannelEventSink {
public:
nsCORSPreflightListener(nsIPrincipal*aReferrerPrincipal,
nsICorsPreflightCallback*aCallback,
nsILoadContext*aLoadContext,boolaWithCredentials,
constnsCString&aPreflightMethod,
constnsTArray<nsCString>&aPreflightHeaders)
:mPreflightMethod(aPreflightMethod),
mPreflightHeaders(aPreflightHeaders.Clone()),
mReferrerPrincipal(aReferrerPrincipal),
mCallback(aCallback),
mLoadContext(aLoadContext),
mWithCredentials(aWithCredentials) {}
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
nsresultCheckPreflightRequestApproved(nsIRequest*aRequest);
private:
~nsCORSPreflightListener() =default;
voidAddResultToCache(nsIRequest*aRequest);
nsCStringmPreflightMethod;
nsTArray<nsCString>mPreflightHeaders;
nsCOMPtr<nsIPrincipal>mReferrerPrincipal;
nsCOMPtr<nsICorsPreflightCallback>mCallback;
nsCOMPtr<nsILoadContext>mLoadContext;
boolmWithCredentials;
};
NS_IMPL_ISUPPORTS(nsCORSPreflightListener,nsIStreamListener,
nsIRequestObserver,nsIInterfaceRequestor,
nsIChannelEventSink)
voidnsCORSPreflightListener::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.
nsAutoCStringheaderVal;
uint32_tage = 0;
(void)http->GetResponseHeader("Access-Control-Max-Age"_ns,headerVal);
if (headerVal.IsEmpty()) {
age =PREFLIGHT_DEFAULT_EXPIRY_SECONDS;
}else {
// 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).
nsACString::const_char_iteratoriter,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 = std::min(age, 86400U);
++iter;
}
}
if (!age || !EnsurePreflightCache()) {
return;
}
// String seems fine, go ahead and cache.
// Note that we have already checked that these headers follow the correct
// syntax.
nsCOMPtr<nsIURI>uri;
NS_GetFinalChannelURI(http,getter_AddRefs(uri));
TimeStampexpirationTime =
TimeStamp::NowLoRes() + TimeDuration::FromSeconds(age);
OriginAttributesattrs;
StoragePrincipalHelper::GetOriginAttributesForNetworkState(http,attrs);
RefPtr<CORSCacheEntry>entry =sPreflightCache->GetEntry(
uri,mReferrerPrincipal,mWithCredentials,attrs,true);
if (!entry) {
return;
}
nsCOMPtr<nsIHttpChannelInternal>httpChannelInternal(
do_QueryInterface(aRequest));
if (httpChannelInternal) {
(void)httpChannelInternal->GetIsProxyUsed(&entry->mIsProxyUsed);
}
// The "Access-Control-Allow-Methods" header contains a comma separated
// list of method names.
(void)http->GetResponseHeader("Access-Control-Allow-Methods"_ns,headerVal);
for (constnsACString&method :
nsCCharSeparatedTokenizer(headerVal,',').ToRange()) {
if (method.IsEmpty()) {
continue;
}
uint32_ti;
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()) {
nsPreflightCache::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.
(void)http->GetResponseHeader("Access-Control-Allow-Headers"_ns,headerVal);
for (constnsACString&header :
nsCCharSeparatedTokenizer(headerVal,',').ToRange()) {
if (header.IsEmpty()) {
continue;
}
uint32_ti;
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()) {
nsPreflightCache::TokenTime*newHeader =entry->mHeaders.AppendElement();
if (!newHeader) {
return;
}
newHeader->token =header;
newHeader->expirationTime =expirationTime;
}
}
}
NS_IMETHODIMP
nsCORSPreflightListener::OnStartRequest(nsIRequest*aRequest) {
#ifdefDEBUG
{
nsCOMPtr<nsIChannel>channel =do_QueryInterface(aRequest);
nsCOMPtr<nsILoadInfo>loadInfo =channel ?channel->LoadInfo() :nullptr;
MOZ_ASSERT(!loadInfo || !loadInfo->GetServiceWorkerTaintingSynthesized());
}
#endif
nsresultrv =CheckPreflightRequestApproved(aRequest);
if (NS_SUCCEEDED(rv)) {
// Everything worked, try to cache and then fire off the actual request.
AddResultToCache(aRequest);
mCallback->OnPreflightSucceeded();
}else {
mCallback->OnPreflightFailed(rv);
}
returnrv;
}
NS_IMETHODIMP
nsCORSPreflightListener::OnStopRequest(nsIRequest*aRequest,nsresultaStatus) {
mCallback =nullptr;
returnNS_OK;
}
/** nsIStreamListener methods **/
NS_IMETHODIMP
nsCORSPreflightListener::OnDataAvailable(nsIRequest*aRequest,
nsIInputStream*inStr,
uint64_tsourceOffset,
uint32_tcount) {
uint32_ttotalRead;
returninStr->ReadSegments(NS_DiscardSegment,nullptr,count, &totalRead);
}
NS_IMETHODIMP
nsCORSPreflightListener::AsyncOnChannelRedirect(
nsIChannel*aOldChannel,nsIChannel*aNewChannel,uint32_taFlags,
nsIAsyncVerifyRedirectCallback*callback) {
// Only internal redirects allowed for now.
if (!NS_IsInternalSameURIRedirect(aOldChannel,aNewChannel,aFlags) &&
!NS_IsHSTSUpgradeRedirect(aOldChannel,aNewChannel,aFlags)) {
nsCOMPtr<nsIHttpChannel>httpChannel =do_QueryInterface(aOldChannel);
LogBlockedRequest(
aOldChannel,"CORSExternalRedirectNotAllowed",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSEXTERNALREDIRECTNOTALLOWED,
httpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
callback->OnRedirectVerifyCallback(NS_OK);
returnNS_OK;
}
nsresultnsCORSPreflightListener::CheckPreflightRequestApproved(
nsIRequest*aRequest) {
nsresultstatus;
nsresultrv =aRequest->GetStatus(&status);
NS_ENSURE_SUCCESS(rv,rv);
NS_ENSURE_SUCCESS(status,status);
// Test that things worked on a HTTP level
nsCOMPtr<nsIHttpChannel>http =do_QueryInterface(aRequest);
nsCOMPtr<nsIHttpChannelInternal>internal =do_QueryInterface(aRequest);
NS_ENSURE_STATE(internal);
nsCOMPtr<nsIHttpChannel>parentHttpChannel =do_QueryInterface(mCallback);
boolsucceedded;
rv =http->GetRequestSucceeded(&succeedded);
if (NS_FAILED(rv) || !succeedded) {
autostatusCode =GetStatusCodeAsString(http);
LogBlockedRequest(aRequest,"CORSPreflightDidNotSucceed3",statusCode.get(),
nsILoadInfo::BLOCKING_REASON_CORSPREFLIGHTDIDNOTSUCCEED,
parentHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
nsAutoCStringheaderVal;
// The "Access-Control-Allow-Methods" header contains a comma separated
// list of method names.
(void)http->GetResponseHeader("Access-Control-Allow-Methods"_ns,headerVal);
boolfoundMethod =mPreflightMethod.EqualsLiteral("GET") ||
mPreflightMethod.EqualsLiteral("HEAD") ||
mPreflightMethod.EqualsLiteral("POST");
for (constnsACString&method :
nsCCharSeparatedTokenizer(headerVal,',').ToRange()) {
if (method.IsEmpty()) {
continue;
}
if (!NS_IsValidHTTPToken(method)) {
LogBlockedRequest(aRequest,"CORSInvalidAllowMethod",
NS_ConvertUTF8toUTF16(method).get(),
nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWMETHOD,
parentHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
if (method.EqualsLiteral("*") && !mWithCredentials) {
foundMethod =true;
}else {
foundMethod |=mPreflightMethod.Equals(method);
}
}
if (!foundMethod) {
LogBlockedRequest(aRequest,"CORSMethodNotFound",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSMETHODNOTFOUND,
parentHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
// The "Access-Control-Allow-Headers" header contains a comma separated
// list of header names.
(void)http->GetResponseHeader("Access-Control-Allow-Headers"_ns,headerVal);
nsTArray<nsCString>headers;
boolwildcard =false;
boolhasAuthorizationHeader =false;
for (constnsACString&header :
nsCCharSeparatedTokenizer(headerVal,',').ToRange()) {
if (header.IsEmpty()) {
continue;
}
if (!NS_IsValidHTTPToken(header)) {
LogBlockedRequest(aRequest,"CORSInvalidAllowHeader",
NS_ConvertUTF8toUTF16(header).get(),
nsILoadInfo::BLOCKING_REASON_CORSINVALIDALLOWHEADER,
parentHttpChannel);
returnNS_ERROR_DOM_BAD_URI;
}
if (header.EqualsLiteral("*") && !mWithCredentials) {
wildcard =true;
}else {
headers.AppendElement(header);
}
if (header.LowerCaseEqualsASCII("authorization")) {
hasAuthorizationHeader =true;
}
}
boolauthorizationInPreflightHeaders =false;
boolauthorizationCoveredByWildcard =false;
for (uint32_ti = 0;i <mPreflightHeaders.Length(); ++i) {
// Cache the result of the authorization header.
boolisAuthorization =
mPreflightHeaders[i].LowerCaseEqualsASCII("authorization");
if (wildcard) {
if (!isAuthorization) {
continue;
}else {
authorizationInPreflightHeaders =true;
if (StaticPrefs::
network_cors_preflight_authorization_covered_by_wildcard() &&
!hasAuthorizationHeader) {
// When `Access-Control-Allow-Headers` is `*` and there is no
// `Authorization` header listed, we send a deprecation warning to the
// console.
LogBlockedRequest(aRequest,"CORSAllowHeaderFromPreflightDeprecation",
nullptr, 0,parentHttpChannel,true);
glean::network::cors_authorization_header
.Get("covered_by_wildcard"_ns)
.Add(1);
authorizationCoveredByWildcard =true;
continue;
}
}
}
constauto&comparator =nsCaseInsensitiveCStringArrayComparator();
if (!headers.Contains(mPreflightHeaders[i],comparator)) {
LogBlockedRequest(
aRequest,"CORSMissingAllowHeaderFromPreflight2",
NS_ConvertUTF8toUTF16(mPreflightHeaders[i]).get(),
nsILoadInfo::BLOCKING_REASON_CORSMISSINGALLOWHEADERFROMPREFLIGHT,
parentHttpChannel);
if (isAuthorization) {
glean::network::cors_authorization_header.Get("disallowed"_ns).Add(1);
}
returnNS_ERROR_DOM_BAD_URI;
}
}
if (authorizationInPreflightHeaders && !authorizationCoveredByWildcard) {
glean::network::cors_authorization_header.Get("allowed"_ns).Add(1);
}
returnNS_OK;
}
NS_IMETHODIMP
nsCORSPreflightListener::GetInterface(constnsIID&aIID,void**aResult) {
if (aIID.Equals(NS_GET_IID(nsILoadContext)) &&mLoadContext) {
nsCOMPtr<nsILoadContext>copy =mLoadContext;
copy.forget(aResult);
returnNS_OK;
}
returnQueryInterface(aIID,aResult);
}
voidnsCORSListenerProxy::RemoveFromCorsPreflightCache(
nsIURI*aURI,nsIPrincipal*aRequestingPrincipal,
const OriginAttributes&aOriginAttributes) {
MOZ_ASSERT(XRE_IsParentProcess());
if (sPreflightCache) {
sPreflightCache->RemoveEntries(aURI,aRequestingPrincipal,
aOriginAttributes);
}
}
// static
nsresultnsCORSListenerProxy::StartCORSPreflight(
nsIChannel*aRequestChannel,nsICorsPreflightCallback*aCallback,
nsTArray<nsCString>&aUnsafeHeaders,nsIChannel**aPreflightChannel) {
*aPreflightChannel =nullptr;
if (StaticPrefs::content_cors_disable()) {
nsCOMPtr<nsIHttpChannel>http =do_QueryInterface(aRequestChannel);
LogBlockedRequest(aRequestChannel,"CORSDisabled",nullptr,
nsILoadInfo::BLOCKING_REASON_CORSDISABLED,http);
returnNS_ERROR_DOM_BAD_URI;
}
nsAutoCStringmethod;
nsCOMPtr<nsIHttpChannel>httpChannel(do_QueryInterface(aRequestChannel));
NS_ENSURE_TRUE(httpChannel,NS_ERROR_UNEXPECTED);
(void)httpChannel->GetRequestMethod(method);
nsCOMPtr<nsIURI>uri;
nsresultrv =NS_GetFinalChannelURI(aRequestChannel,getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsILoadInfo>originalLoadInfo =aRequestChannel->LoadInfo();
MOZ_ASSERT(originalLoadInfo->GetSecurityMode() ==
nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT,
"how did we end up here?");
nsCOMPtr<nsIPrincipal>principal =originalLoadInfo->GetLoadingPrincipal();
MOZ_ASSERT(principal &&originalLoadInfo->GetExternalContentPolicyType() !=
ExtContentPolicy::TYPE_DOCUMENT,
"Should not do CORS loads for top-level loads, so a "
"loadingPrincipal should always exist.");
boolwithCredentials =
originalLoadInfo->GetCookiePolicy() ==nsILoadInfo::SEC_COOKIES_INCLUDE;
RefPtr<CORSCacheEntry>entry;
nsLoadFlagsloadFlags;
rv =aRequestChannel->GetLoadFlags(&loadFlags);
NS_ENSURE_SUCCESS(rv,rv);
// Disable preflight cache if devtools says so or on other force reloads
booldisableCache = (loadFlags &nsIRequest::LOAD_BYPASS_CACHE);
if (sPreflightCache && !disableCache) {
OriginAttributesattrs;
StoragePrincipalHelper::GetOriginAttributesForNetworkState(aRequestChannel,
attrs);
entry =sPreflightCache->GetEntry(uri,principal,withCredentials,attrs,
false);
}
if (entry &&entry->CheckRequest(method,aUnsafeHeaders)) {
aCallback->OnPreflightSucceeded();
returnNS_OK;
}
// Either it wasn't cached or the cached result has expired. Build a
// channel for the OPTIONS request.
nsCOMPtr<nsILoadInfo>loadInfo =
static_cast<mozilla::net::LoadInfo*>(originalLoadInfo.get())
->CloneForNewRequest();
static_cast<mozilla::net::LoadInfo*>(loadInfo.get())->SetIsPreflight();
nsCOMPtr<nsILoadGroup>loadGroup;
rv =aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
NS_ENSURE_SUCCESS(rv,rv);
// We want to give the preflight channel's notification callbacks the same
// load context as the original channel's notification callbacks had. We
// don't worry about a load context provided via the loadgroup here, since
// they have the same loadgroup.
nsCOMPtr<nsIInterfaceRequestor>callbacks;
rv =aRequestChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsILoadContext>loadContext =do_GetInterface(callbacks);
// Preflight requests should never be intercepted by service workers and
// are always anonymous.
// NOTE: We ignore CORS checks on synthesized responses (see the CORS
// preflights, then we need to extend the GetResponseSynthesized() check in
// nsCORSListenerProxy::CheckRequestApproved()). If we change our behavior
// here and allow service workers to intercept CORS preflights, then that
// check won't be safe any more.
loadFlags |=
nsIChannel::LOAD_BYPASS_SERVICE_WORKER |nsIRequest::LOAD_ANONYMOUS;
if (StaticPrefs::network_cors_preflight_allow_client_cert()) {
loadFlags |=nsIRequest::LOAD_ANONYMOUS_ALLOW_CLIENT_CERT;
}
nsCOMPtr<nsIChannel>preflightChannel;
rv =NS_NewChannelInternal(getter_AddRefs(preflightChannel),uri,loadInfo,
nullptr,// PerformanceStorage
loadGroup,
nullptr,// aCallbacks
loadFlags);
NS_ENSURE_SUCCESS(rv,rv);
// Set method and headers
nsCOMPtr<nsIHttpChannel>preHttp =do_QueryInterface(preflightChannel);
NS_ASSERTION(preHttp,"Failed to QI to nsIHttpChannel!");
rv =preHttp->SetRequestMethod("OPTIONS"_ns);
NS_ENSURE_SUCCESS(rv,rv);
rv =preHttp->SetRequestHeader("Access-Control-Request-Method"_ns,method,
false);
NS_ENSURE_SUCCESS(rv,rv);
// Set the CORS preflight channel's warning reporter to be the same as the
// requesting channel so that all log messages are able to be reported through
// the warning reporter.
RefPtr<nsHttpChannel>reqCh =do_QueryObject(aRequestChannel);
RefPtr<nsHttpChannel>preCh =do_QueryObject(preHttp);
if (preCh &&reqCh) {// there are other implementers of nsIHttpChannel
preCh->SetWarningReporter(reqCh->GetWarningReporter());
}
nsTArray<nsCString>preflightHeaders;
if (!aUnsafeHeaders.IsEmpty()) {
for (uint32_ti = 0;i <aUnsafeHeaders.Length(); ++i) {
preflightHeaders.AppendElement();
ToLowerCase(aUnsafeHeaders[i],preflightHeaders[i]);
}
preflightHeaders.Sort();
nsAutoCStringheaders;
for (uint32_ti = 0;i <preflightHeaders.Length(); ++i) {
if (i != 0) {
headers +=',';
}
headers +=preflightHeaders[i];
}
rv =preHttp->SetRequestHeader("Access-Control-Request-Headers"_ns,headers,
false);
NS_ENSURE_SUCCESS(rv,rv);
}
// Set up listener which will start the original channel
RefPtr<nsCORSPreflightListener>preflightListener =
newnsCORSPreflightListener(principal,aCallback,loadContext,
withCredentials,method,preflightHeaders);
rv =preflightChannel->SetNotificationCallbacks(preflightListener);
NS_ENSURE_SUCCESS(rv,rv);
if (preCh &&reqCh) {
// Perhttps://fetch.spec.whatwg.org/#cors-preflight-fetch step 1, the
// request's referrer and referrer policy should match the original request.
nsCOMPtr<nsIReferrerInfo>referrerInfo;
rv =reqCh->GetReferrerInfo(getter_AddRefs(referrerInfo));
NS_ENSURE_SUCCESS(rv,rv);
if (referrerInfo) {
nsCOMPtr<nsIReferrerInfo>newReferrerInfo =
static_cast<dom::ReferrerInfo*>(referrerInfo.get())->Clone();
rv =preCh->SetReferrerInfo(newReferrerInfo);
NS_ENSURE_SUCCESS(rv,rv);
}
}
// Start preflight
rv =preflightChannel->AsyncOpen(preflightListener);
NS_ENSURE_SUCCESS(rv,rv);
// Return newly created preflight channel
preflightChannel.forget(aPreflightChannel);
returnNS_OK;
}
// static
voidnsCORSListenerProxy::LogBlockedCORSRequest(
uint64_taInnerWindowID,boolaPrivateBrowsing,boolaFromChromeContext,
constnsAString&aMessage,constnsACString&aCategory,boolaIsWarning) {
nsresultrv =NS_OK;
// Build the error object and log it to the console
nsCOMPtr<nsIConsoleService>console(
mozilla::components::Console::Service(&rv));
if (NS_FAILED(rv)) {
NS_WARNING("Failed to log blocked cross-site request (no console)");
return;
}
nsCOMPtr<nsIScriptError>scriptError =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to log blocked cross-site request (no scriptError)");
return;
}
uint32_terrorFlag =
aIsWarning ?nsIScriptError::warningFlag :nsIScriptError::errorFlag;
// query innerWindowID and log to web console, otherwise log to
// the error to the browser console.
if (aInnerWindowID > 0) {
rv =scriptError->InitWithSanitizedSource(aMessage,
""_ns,// sourceName
0,// lineNumber
0,// columnNumber
errorFlag,aCategory,
aInnerWindowID);
}else {
rv =scriptError->Init(aMessage,
""_ns,// sourceName
0,// lineNumber
0,// columnNumber
errorFlag,aCategory,aPrivateBrowsing,
aFromChromeContext);// From chrome context
}
if (NS_FAILED(rv)) {
NS_WARNING(
"Failed to log blocked cross-site request (scriptError init failed)");
return;
}
console->LogMessage(scriptError);
}
This page was generated by Searchfox.

[8]ページ先頭

©2009-2025 Movatter.jp