/* -*- 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 at http://mozilla.org/MPL/2.0/. */ #include "ChannelWrapper.h" #include "jsapi.h" #include "xpcpublic.h" #include "mozilla/BasePrincipal.h" #include "mozilla/SystemPrincipal.h" #include "NSSErrorsService.h" #include "nsITransportSecurityInfo.h" #include "mozilla/AddonManagerWebAPI.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Components.h" #include "mozilla/ErrorNames.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Unused.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/EventBinding.h" #include "mozilla/dom/BrowserHost.h" #include "mozIThirdPartyUtil.h" #include "nsContentUtils.h" #include "nsIContentPolicy.h" #include "nsIClassifiedChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILoadContext.h" #include "nsIProxiedChannel.h" #include "nsIProxyInfo.h" #include "nsITraceableChannel.h" #include "nsIWritablePropertyBag.h" #include "nsIWritablePropertyBag2.h" #include "nsNetUtil.h" #include "nsProxyRelease.h" #include "nsPrintfCString.h" using namespace mozilla::dom; using namespace JS; namespace mozilla { namespace extensions { #define CHANNELWRAPPER_PROP_KEY u"ChannelWrapper::CachedInstance"_ns using CF = nsIClassifiedChannel::ClassificationFlags; using MUC = MozUrlClassificationFlags; struct ClassificationStruct { uint32_t mFlag; MozUrlClassificationFlags mValue; }; static const ClassificationStruct classificationArray[] = { {CF::CLASSIFIED_FINGERPRINTING, MUC::Fingerprinting}, {CF::CLASSIFIED_FINGERPRINTING_CONTENT, MUC::Fingerprinting_content}, {CF::CLASSIFIED_CRYPTOMINING, MUC::Cryptomining}, {CF::CLASSIFIED_CRYPTOMINING_CONTENT, MUC::Cryptomining_content}, {CF::CLASSIFIED_EMAILTRACKING, MUC::Emailtracking}, {CF::CLASSIFIED_EMAILTRACKING_CONTENT, MUC::Emailtracking_content}, {CF::CLASSIFIED_TRACKING, MUC::Tracking}, {CF::CLASSIFIED_TRACKING_AD, MUC::Tracking_ad}, {CF::CLASSIFIED_TRACKING_ANALYTICS, MUC::Tracking_analytics}, {CF::CLASSIFIED_TRACKING_SOCIAL, MUC::Tracking_social}, {CF::CLASSIFIED_TRACKING_CONTENT, MUC::Tracking_content}, {CF::CLASSIFIED_SOCIALTRACKING, MUC::Socialtracking}, {CF::CLASSIFIED_SOCIALTRACKING_FACEBOOK, MUC::Socialtracking_facebook}, {CF::CLASSIFIED_SOCIALTRACKING_LINKEDIN, MUC::Socialtracking_linkedin}, {CF::CLASSIFIED_SOCIALTRACKING_TWITTER, MUC::Socialtracking_twitter}, {CF::CLASSIFIED_ANY_BASIC_TRACKING, MUC::Any_basic_tracking}, {CF::CLASSIFIED_ANY_STRICT_TRACKING, MUC::Any_strict_tracking}, {CF::CLASSIFIED_ANY_SOCIAL_TRACKING, MUC::Any_social_tracking}}; /***************************************************************************** * Lifetimes *****************************************************************************/ namespace { class ChannelListHolder : public LinkedList { public: ChannelListHolder() : LinkedList() {} ~ChannelListHolder(); }; } // anonymous namespace ChannelListHolder::~ChannelListHolder() { while (ChannelWrapper* wrapper = popFirst()) { wrapper->Die(); } } static LinkedList* GetChannelList() { static UniquePtr sChannelList; if (!sChannelList && !PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) { sChannelList.reset(new ChannelListHolder()); ClearOnShutdown(&sChannelList, ShutdownPhase::XPCOMShutdown); } return sChannelList.get(); } NS_IMPL_CYCLE_COLLECTING_ADDREF(ChannelWrapper::ChannelWrapperStub) NS_IMPL_CYCLE_COLLECTING_RELEASE(ChannelWrapper::ChannelWrapperStub) NS_IMPL_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub, mChannelWrapper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper::ChannelWrapperStub) NS_INTERFACE_MAP_ENTRY_TEAROFF_AMBIGUOUS(ChannelWrapper, EventTarget, mChannelWrapper) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END /***************************************************************************** * Initialization *****************************************************************************/ ChannelWrapper::ChannelWrapper(nsISupports* aParent, nsIChannel* aChannel) : ChannelHolder(aChannel), mParent(aParent) { mStub = new ChannelWrapperStub(this); if (auto* list = GetChannelList()) { list->insertBack(this); } } ChannelWrapper::~ChannelWrapper() { if (LinkedListElement::isInList()) { LinkedListElement::remove(); } } void ChannelWrapper::Die() { if (mStub) { mStub->mChannelWrapper = nullptr; } } /* static */ already_AddRefed ChannelWrapper::Get(const GlobalObject& global, nsIChannel* channel) { RefPtr wrapper; nsCOMPtr props = do_QueryInterface(channel); if (props) { wrapper = do_GetProperty(props, CHANNELWRAPPER_PROP_KEY); if (wrapper) { // Assume cached attributes may have changed at this point. wrapper->ClearCachedAttributes(); } } if (!wrapper) { wrapper = new ChannelWrapper(global.GetAsSupports(), channel); if (props) { Unused << props->SetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY, wrapper->mStub); } } return wrapper.forget(); } already_AddRefed ChannelWrapper::GetRegisteredChannel( const GlobalObject& global, uint64_t aChannelId, const WebExtensionPolicy& aAddon, nsIRemoteTab* aRemoteTab) { ContentParent* contentParent = nullptr; if (BrowserHost* host = BrowserHost::GetFrom(aRemoteTab)) { contentParent = host->GetActor()->Manager(); } auto& webreq = WebRequestService::GetSingleton(); nsCOMPtr channel = webreq.GetTraceableChannel(aChannelId, aAddon.Id(), contentParent); if (!channel) { return nullptr; } nsCOMPtr chan(do_QueryInterface(channel)); return ChannelWrapper::Get(global, chan); } void ChannelWrapper::SetChannel(nsIChannel* aChannel) { detail::ChannelHolder::SetChannel(aChannel); ClearCachedAttributes(); ChannelWrapper_Binding::ClearCachedFinalURIValue(this); ChannelWrapper_Binding::ClearCachedFinalURLValue(this); mFinalURLInfo.reset(); ChannelWrapper_Binding::ClearCachedProxyInfoValue(this); } void ChannelWrapper::ClearCachedAttributes() { ChannelWrapper_Binding::ClearCachedRemoteAddressValue(this); ChannelWrapper_Binding::ClearCachedStatusCodeValue(this); ChannelWrapper_Binding::ClearCachedStatusLineValue(this); ChannelWrapper_Binding::ClearCachedUrlClassificationValue(this); if (!mFiredErrorEvent) { ChannelWrapper_Binding::ClearCachedErrorStringValue(this); } ChannelWrapper_Binding::ClearCachedRequestSizeValue(this); ChannelWrapper_Binding::ClearCachedResponseSizeValue(this); } /***************************************************************************** * ... *****************************************************************************/ void ChannelWrapper::Cancel(uint32_t aResult, uint32_t aReason, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { nsCOMPtr loadInfo = GetLoadInfo(); if (aReason > 0 && loadInfo) { loadInfo->SetRequestBlockingReason(aReason); } rv = chan->Cancel(nsresult(aResult)); ErrorCheck(); } if (NS_FAILED(rv)) { aRv.Throw(rv); } } void ChannelWrapper::RedirectTo(nsIURI* aURI, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { rv = chan->RedirectTo(aURI); } if (NS_FAILED(rv)) { aRv.Throw(rv); } } void ChannelWrapper::UpgradeToSecure(ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { rv = chan->UpgradeToSecure(); } if (NS_FAILED(rv)) { aRv.Throw(rv); } } void ChannelWrapper::Suspend(const nsCString& aProfileMarkerText, ErrorResult& aRv) { if (!mSuspended) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { rv = chan->Suspend(); } if (NS_FAILED(rv)) { aRv.Throw(rv); } else { mSuspended = true; MOZ_ASSERT(mSuspendedMarkerText.IsVoid()); mSuspendedMarkerText = aProfileMarkerText; PROFILER_MARKER_TEXT("Extension Suspend", NETWORK, MarkerOptions(MarkerTiming::IntervalStart()), mSuspendedMarkerText); } } } void ChannelWrapper::Resume(ErrorResult& aRv) { if (mSuspended) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { rv = chan->Resume(); } if (NS_FAILED(rv)) { aRv.Throw(rv); } else { mSuspended = false; PROFILER_MARKER_TEXT("Extension Suspend", NETWORK, MarkerOptions(MarkerTiming::IntervalEnd()), mSuspendedMarkerText); mSuspendedMarkerText = VoidCString(); } } } void ChannelWrapper::GetContentType(nsCString& aContentType) const { if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetContentType(aContentType); } } void ChannelWrapper::SetContentType(const nsACString& aContentType) { if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->SetContentType(aContentType); } } /***************************************************************************** * Headers *****************************************************************************/ namespace { class MOZ_STACK_CLASS HeaderVisitor final : public nsIHttpHeaderVisitor { public: NS_DECL_NSIHTTPHEADERVISITOR explicit HeaderVisitor(nsTArray& aHeaders) : mHeaders(aHeaders) {} HeaderVisitor(nsTArray& aHeaders, const nsCString& aContentTypeHdr) : mHeaders(aHeaders), mContentTypeHdr(aContentTypeHdr) {} void VisitRequestHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) { CheckResult(aChannel->VisitRequestHeaders(this), aRv); } void VisitResponseHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) { CheckResult(aChannel->VisitResponseHeaders(this), aRv); } NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; // Stub AddRef/Release since this is a stack class. NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return ++mRefCnt; } NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return --mRefCnt; } virtual ~HeaderVisitor() { MOZ_DIAGNOSTIC_ASSERT(mRefCnt == 0); } private: bool CheckResult(nsresult aNSRv, ErrorResult& aRv) { if (NS_FAILED(aNSRv)) { aRv.Throw(aNSRv); return false; } return true; } nsTArray& mHeaders; nsCString mContentTypeHdr = VoidCString(); nsrefcnt mRefCnt = 0; }; NS_IMETHODIMP HeaderVisitor::VisitHeader(const nsACString& aHeader, const nsACString& aValue) { auto dict = mHeaders.AppendElement(fallible); if (!dict) { return NS_ERROR_OUT_OF_MEMORY; } dict->mName = aHeader; if (!mContentTypeHdr.IsVoid() && aHeader.LowerCaseEqualsLiteral("content-type")) { dict->mValue = mContentTypeHdr; } else { dict->mValue = aValue; } return NS_OK; } NS_IMPL_QUERY_INTERFACE(HeaderVisitor, nsIHttpHeaderVisitor) } // anonymous namespace void ChannelWrapper::GetRequestHeaders(nsTArray& aRetVal, ErrorResult& aRv) const { if (nsCOMPtr chan = MaybeHttpChannel()) { HeaderVisitor visitor(aRetVal); visitor.VisitRequestHeaders(chan, aRv); } else { aRv.Throw(NS_ERROR_UNEXPECTED); } } void ChannelWrapper::GetRequestHeader(const nsCString& aHeader, nsCString& aResult, ErrorResult& aRv) const { aResult.SetIsVoid(true); if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetRequestHeader(aHeader, aResult); } else { aRv.Throw(NS_ERROR_UNEXPECTED); } } void ChannelWrapper::GetResponseHeaders(nsTArray& aRetVal, ErrorResult& aRv) const { if (nsCOMPtr chan = MaybeHttpChannel()) { HeaderVisitor visitor(aRetVal, mContentTypeHdr); visitor.VisitResponseHeaders(chan, aRv); } else { aRv.Throw(NS_ERROR_UNEXPECTED); } } void ChannelWrapper::SetRequestHeader(const nsCString& aHeader, const nsCString& aValue, bool aMerge, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { rv = chan->SetRequestHeader(aHeader, aValue, aMerge); } if (NS_FAILED(rv)) { aRv.Throw(rv); } } void ChannelWrapper::SetResponseHeader(const nsCString& aHeader, const nsCString& aValue, bool aMerge, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { if (aHeader.LowerCaseEqualsLiteral("content-type")) { rv = chan->SetContentType(aValue); if (NS_SUCCEEDED(rv)) { mContentTypeHdr = aValue; } } else { rv = chan->SetResponseHeader(aHeader, aValue, aMerge); } } if (NS_FAILED(rv)) { aRv.Throw(rv); } } /***************************************************************************** * LoadInfo *****************************************************************************/ already_AddRefed ChannelWrapper::GetLoadContext() const { if (nsCOMPtr chan = MaybeChannel()) { nsCOMPtr ctxt; // Fetch() from Workers saves BrowsingContext/LoadContext information in // nsILoadInfo.workerAssociatedBrowsingContext. So we can not use // NS_QueryNotificationCallbacks to get LoadContext of the channel. RefPtr bc; nsCOMPtr loadInfo = chan->LoadInfo(); loadInfo->GetWorkerAssociatedBrowsingContext(getter_AddRefs(bc)); if (bc) { ctxt = bc.forget(); return ctxt.forget(); } NS_QueryNotificationCallbacks(chan, ctxt); return ctxt.forget(); } return nullptr; } already_AddRefed ChannelWrapper::GetBrowserElement() const { if (nsCOMPtr ctxt = GetLoadContext()) { RefPtr elem; if (NS_SUCCEEDED(ctxt->GetTopFrameElement(getter_AddRefs(elem)))) { return elem.forget(); } } return nullptr; } bool ChannelWrapper::IsServiceWorkerScript() const { nsCOMPtr chan = MaybeChannel(); return IsServiceWorkerScript(chan); } // static bool ChannelWrapper::IsServiceWorkerScript(const nsCOMPtr& chan) { nsCOMPtr loadInfo; if (chan) { chan->GetLoadInfo(getter_AddRefs(loadInfo)); } if (loadInfo) { // Not a script. if (loadInfo->GetExternalContentPolicyType() != ExtContentPolicy::TYPE_SCRIPT) { return false; } // Service worker main script load. if (loadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { return true; } // Service worker import scripts load. if (loadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS || loadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) { nsLoadFlags loadFlags = 0; chan->GetLoadFlags(&loadFlags); return loadFlags & nsIChannel::LOAD_BYPASS_SERVICE_WORKER; } } return false; } static inline bool IsSystemPrincipal(nsIPrincipal* aPrincipal) { return BasePrincipal::Cast(aPrincipal)->Is(); } bool ChannelWrapper::IsSystemLoad() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) { return IsSystemPrincipal(prin); } if (RefPtr bc = loadInfo->GetBrowsingContext(); !bc || bc->IsTop()) { return false; } if (nsIPrincipal* prin = loadInfo->PrincipalToInherit()) { return IsSystemPrincipal(prin); } if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) { return IsSystemPrincipal(prin); } } return false; } bool ChannelWrapper::CanModify() const { if (WebExtensionPolicy::IsRestrictedURI(FinalURLInfo())) { return false; } if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) { if (IsSystemPrincipal(prin)) { return false; } auto* docURI = DocumentURLInfo(); if (docURI && WebExtensionPolicy::IsRestrictedURI(*docURI)) { return false; } } } return true; } already_AddRefed ChannelWrapper::GetOriginURI() const { nsCOMPtr uri; if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) { if (prin->GetIsContentPrincipal()) { auto* basePrin = BasePrincipal::Cast(prin); Unused << basePrin->GetURI(getter_AddRefs(uri)); } } } return uri.forget(); } already_AddRefed ChannelWrapper::GetDocumentURI() const { nsCOMPtr uri; if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->GetLoadingPrincipal()) { if (prin->GetIsContentPrincipal()) { auto* basePrin = BasePrincipal::Cast(prin); Unused << basePrin->GetURI(getter_AddRefs(uri)); } } } return uri.forget(); } void ChannelWrapper::GetOriginURL(nsCString& aRetVal) const { if (nsCOMPtr uri = GetOriginURI()) { Unused << uri->GetSpec(aRetVal); } } void ChannelWrapper::GetDocumentURL(nsCString& aRetVal) const { if (nsCOMPtr uri = GetDocumentURI()) { Unused << uri->GetSpec(aRetVal); } } const URLInfo& ChannelWrapper::FinalURLInfo() const { if (mFinalURLInfo.isNothing()) { ErrorResult rv; nsCOMPtr uri = FinalURI(); MOZ_ASSERT(uri); // If this is a view-source scheme, get the nested uri. while (uri && uri->SchemeIs("view-source")) { nsCOMPtr nested = do_QueryInterface(uri); if (!nested) { break; } nested->GetInnerURI(getter_AddRefs(uri)); } mFinalURLInfo.emplace(uri.get(), true); // If this is a WebSocket request, mangle the URL so that the scheme is // ws: or wss:, as appropriate. auto& url = mFinalURLInfo.ref(); if (Type() == MozContentPolicyType::Websocket && (url.Scheme() == nsGkAtoms::http || url.Scheme() == nsGkAtoms::https)) { nsAutoCString spec(url.CSpec()); spec.Replace(0, 4, "ws"_ns); Unused << NS_NewURI(getter_AddRefs(uri), spec); MOZ_RELEASE_ASSERT(uri); mFinalURLInfo.reset(); mFinalURLInfo.emplace(uri.get(), true); } } return mFinalURLInfo.ref(); } const URLInfo* ChannelWrapper::DocumentURLInfo() const { if (mDocumentURLInfo.isNothing()) { nsCOMPtr uri = GetDocumentURI(); if (!uri) { return nullptr; } mDocumentURLInfo.emplace(uri.get(), true); } return &mDocumentURLInfo.ref(); } bool ChannelWrapper::Matches( const dom::MozRequestFilter& aFilter, const WebExtensionPolicy* aExtension, const dom::MozRequestMatchOptions& aOptions) const { if (!HaveChannel()) { return false; } if (!aFilter.mTypes.IsNull() && !aFilter.mTypes.Value().Contains(Type())) { return false; } auto& urlInfo = FinalURLInfo(); if (aFilter.mUrls && !aFilter.mUrls->Matches(urlInfo)) { return false; } nsCOMPtr loadInfo = GetLoadInfo(); bool isPrivate = loadInfo && loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; if (!aFilter.mIncognito.IsNull() && aFilter.mIncognito.Value() != isPrivate) { return false; } if (aExtension) { // Verify extension access to private requests if (isPrivate && !aExtension->PrivateBrowsingAllowed()) { return false; } bool isProxy = aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy); // Proxies are allowed access to all urls, including restricted urls. if (!aExtension->CanAccessURI(urlInfo, false, !isProxy, true)) { return false; } // If this isn't the proxy phase of the request, check that the extension // has origin permissions for origin that originated the request. if (!isProxy) { if (IsSystemLoad()) { return false; } auto origin = DocumentURLInfo(); // Extensions with the file:-permission may observe requests from file: // origins, because such documents can already be modified by content // scripts anyway. if (origin && !aExtension->CanAccessURI(*origin, false, true, true)) { return false; } } } return true; } int64_t NormalizeFrameID(nsILoadInfo* aLoadInfo, uint64_t bcID) { RefPtr bc = aLoadInfo->GetWorkerAssociatedBrowsingContext(); if (!bc) { bc = aLoadInfo->GetBrowsingContext(); } if (!bc || bcID == bc->Top()->Id()) { return 0; } return bcID; } uint64_t ChannelWrapper::BrowsingContextId(nsILoadInfo* aLoadInfo) const { auto frameID = aLoadInfo->GetFrameBrowsingContextID(); if (!frameID) { frameID = aLoadInfo->GetWorkerAssociatedBrowsingContextID(); } if (!frameID) { frameID = aLoadInfo->GetBrowsingContextID(); } return frameID; } int64_t ChannelWrapper::FrameId() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { return NormalizeFrameID(loadInfo, BrowsingContextId(loadInfo)); } return 0; } int64_t ChannelWrapper::ParentFrameId() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { RefPtr bc = loadInfo->GetWorkerAssociatedBrowsingContext(); if (!bc) { bc = loadInfo->GetBrowsingContext(); } if (bc) { if (BrowsingContextId(loadInfo) == bc->Top()->Id()) { return -1; } uint64_t parentID = -1; if (loadInfo->GetFrameBrowsingContextID()) { parentID = loadInfo->GetBrowsingContextID(); } else if (bc->GetParent()) { parentID = bc->GetParent()->Id(); } return NormalizeFrameID(loadInfo, parentID); } } return -1; } void ChannelWrapper::GetFrameAncestors( dom::Nullable>& aFrameAncestors, ErrorResult& aRv) const { nsCOMPtr loadInfo = GetLoadInfo(); if (!loadInfo || BrowsingContextId(loadInfo) == 0) { aFrameAncestors.SetNull(); return; } nsresult rv = GetFrameAncestors(loadInfo, aFrameAncestors.SetValue()); if (NS_FAILED(rv)) { aRv.Throw(rv); } } nsresult ChannelWrapper::GetFrameAncestors( nsILoadInfo* aLoadInfo, nsTArray& aFrameAncestors) const { const nsTArray>& ancestorPrincipals = aLoadInfo->AncestorPrincipals(); const nsTArray& ancestorBrowsingContextIDs = aLoadInfo->AncestorBrowsingContextIDs(); uint32_t size = ancestorPrincipals.Length(); MOZ_DIAGNOSTIC_ASSERT(size == ancestorBrowsingContextIDs.Length()); if (size != ancestorBrowsingContextIDs.Length()) { return NS_ERROR_UNEXPECTED; } bool subFrame = aLoadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_SUBDOCUMENT; if (!aFrameAncestors.SetCapacity(subFrame ? size : size + 1, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } // The immediate parent is always the first element in the ancestor arrays, // however SUBDOCUMENTs do not have their immediate parent included, so we // inject it here. This will force wrapper.parentBrowsingContextId == // wrapper.frameAncestors[0].frameId to always be true. All ather requests // already match this way. if (subFrame) { auto ancestor = aFrameAncestors.AppendElement(); GetDocumentURL(ancestor->mUrl); ancestor->mFrameId = ParentFrameId(); } for (uint32_t i = 0; i < size; ++i) { auto ancestor = aFrameAncestors.AppendElement(); MOZ_TRY(ancestorPrincipals[i]->GetAsciiSpec(ancestor->mUrl)); ancestor->mFrameId = NormalizeFrameID(aLoadInfo, ancestorBrowsingContextIDs[i]); } return NS_OK; } /***************************************************************************** * Response filtering *****************************************************************************/ void ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsIRemoteTab* aBrowserParent) { // We can't attach new listeners after the response has started, so don't // bother registering anything. if (mResponseStarted || !CanModify()) { return; } mAddonEntries.InsertOrUpdate(aAddon.Id(), aBrowserParent); if (!mChannelEntry) { mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this); CheckEventListeners(); } } already_AddRefed ChannelWrapper::GetTraceableChannel( nsAtom* aAddonId, dom::ContentParent* aContentParent) const { nsCOMPtr remoteTab; if (mAddonEntries.Get(aAddonId, getter_AddRefs(remoteTab))) { ContentParent* contentParent = nullptr; if (remoteTab) { contentParent = BrowserHost::GetFrom(remoteTab.get())->GetActor()->Manager(); } if (contentParent == aContentParent) { nsCOMPtr chan = QueryChannel(); return chan.forget(); } } return nullptr; } /***************************************************************************** * ... *****************************************************************************/ MozContentPolicyType GetContentPolicyType(ExtContentPolicyType aType) { // Note: Please keep this function in sync with the external types in // nsIContentPolicy.idl switch (aType) { case ExtContentPolicy::TYPE_DOCUMENT: return MozContentPolicyType::Main_frame; case ExtContentPolicy::TYPE_SUBDOCUMENT: return MozContentPolicyType::Sub_frame; case ExtContentPolicy::TYPE_STYLESHEET: return MozContentPolicyType::Stylesheet; case ExtContentPolicy::TYPE_SCRIPT: return MozContentPolicyType::Script; case ExtContentPolicy::TYPE_IMAGE: return MozContentPolicyType::Image; case ExtContentPolicy::TYPE_OBJECT: return MozContentPolicyType::Object; case ExtContentPolicy::TYPE_OBJECT_SUBREQUEST: return MozContentPolicyType::Object_subrequest; case ExtContentPolicy::TYPE_XMLHTTPREQUEST: return MozContentPolicyType::Xmlhttprequest; // TYPE_FETCH returns xmlhttprequest for cross-browser compatibility. case ExtContentPolicy::TYPE_FETCH: return MozContentPolicyType::Xmlhttprequest; case ExtContentPolicy::TYPE_XSLT: return MozContentPolicyType::Xslt; case ExtContentPolicy::TYPE_PING: return MozContentPolicyType::Ping; case ExtContentPolicy::TYPE_BEACON: return MozContentPolicyType::Beacon; case ExtContentPolicy::TYPE_DTD: return MozContentPolicyType::Xml_dtd; case ExtContentPolicy::TYPE_FONT: case ExtContentPolicy::TYPE_UA_FONT: return MozContentPolicyType::Font; case ExtContentPolicy::TYPE_MEDIA: return MozContentPolicyType::Media; case ExtContentPolicy::TYPE_WEBSOCKET: return MozContentPolicyType::Websocket; case ExtContentPolicy::TYPE_CSP_REPORT: return MozContentPolicyType::Csp_report; case ExtContentPolicy::TYPE_IMAGESET: return MozContentPolicyType::Imageset; case ExtContentPolicy::TYPE_WEB_MANIFEST: return MozContentPolicyType::Web_manifest; case ExtContentPolicy::TYPE_SPECULATIVE: return MozContentPolicyType::Speculative; case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA: case ExtContentPolicy::TYPE_INVALID: case ExtContentPolicy::TYPE_OTHER: case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD: break; // Do not add default: so that compilers can catch the missing case. } return MozContentPolicyType::Other; } MozContentPolicyType ChannelWrapper::Type() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { return GetContentPolicyType(loadInfo->GetExternalContentPolicyType()); } return MozContentPolicyType::Other; } void ChannelWrapper::GetMethod(nsCString& aMethod) const { if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetRequestMethod(aMethod); } } /***************************************************************************** * ... *****************************************************************************/ uint32_t ChannelWrapper::StatusCode() const { uint32_t result = 0; if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetResponseStatus(&result); } return result; } void ChannelWrapper::GetStatusLine(nsCString& aRetVal) const { nsCOMPtr chan = MaybeHttpChannel(); nsCOMPtr internal = do_QueryInterface(chan); if (internal) { nsAutoCString statusText; uint32_t major, minor, status; if (NS_FAILED(chan->GetResponseStatus(&status)) || NS_FAILED(chan->GetResponseStatusText(statusText)) || NS_FAILED(internal->GetResponseVersion(&major, &minor))) { return; } aRetVal = nsPrintfCString("HTTP/%u.%u %u %s", major, minor, status, statusText.get()); } } uint64_t ChannelWrapper::ResponseSize() const { uint64_t result = 0; if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetTransferSize(&result); } return result; } uint64_t ChannelWrapper::RequestSize() const { uint64_t result = 0; if (nsCOMPtr chan = MaybeHttpChannel()) { Unused << chan->GetRequestSize(&result); } return result; } /***************************************************************************** * ... *****************************************************************************/ already_AddRefed ChannelWrapper::FinalURI() const { nsCOMPtr uri; if (nsCOMPtr chan = MaybeChannel()) { NS_GetFinalChannelURI(chan, getter_AddRefs(uri)); } return uri.forget(); } void ChannelWrapper::GetFinalURL(nsString& aRetVal) const { if (HaveChannel()) { aRetVal = FinalURLInfo().Spec(); } } /***************************************************************************** * ... *****************************************************************************/ nsresult FillProxyInfo(MozProxyInfo& aDict, nsIProxyInfo* aProxyInfo) { MOZ_TRY(aProxyInfo->GetHost(aDict.mHost)); MOZ_TRY(aProxyInfo->GetPort(&aDict.mPort)); MOZ_TRY(aProxyInfo->GetType(aDict.mType)); MOZ_TRY(aProxyInfo->GetUsername(aDict.mUsername)); MOZ_TRY( aProxyInfo->GetProxyAuthorizationHeader(aDict.mProxyAuthorizationHeader)); MOZ_TRY(aProxyInfo->GetConnectionIsolationKey(aDict.mConnectionIsolationKey)); MOZ_TRY(aProxyInfo->GetFailoverTimeout(&aDict.mFailoverTimeout.Construct())); uint32_t flags; MOZ_TRY(aProxyInfo->GetFlags(&flags)); aDict.mProxyDNS = flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST; return NS_OK; } void ChannelWrapper::GetProxyInfo(dom::Nullable& aRetVal, ErrorResult& aRv) const { nsCOMPtr proxyInfo; if (nsCOMPtr proxied = QueryChannel()) { Unused << proxied->GetProxyInfo(getter_AddRefs(proxyInfo)); } if (proxyInfo) { MozProxyInfo result; nsresult rv = FillProxyInfo(result, proxyInfo); if (NS_FAILED(rv)) { aRv.Throw(rv); } else { aRetVal.SetValue(std::move(result)); } } } void ChannelWrapper::GetRemoteAddress(nsCString& aRetVal) const { aRetVal.SetIsVoid(true); if (nsCOMPtr internal = QueryChannel()) { Unused << internal->GetRemoteAddress(aRetVal); } } void FillClassification( Sequence& classifications, uint32_t classificationFlags, ErrorResult& aRv) { if (classificationFlags == 0) { return; } for (const auto& entry : classificationArray) { if (classificationFlags & entry.mFlag) { if (!classifications.AppendElement(entry.mValue, mozilla::fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } } } void ChannelWrapper::GetUrlClassification( dom::Nullable& aRetVal, ErrorResult& aRv) const { MozUrlClassification classification; if (nsCOMPtr chan = MaybeHttpChannel()) { nsCOMPtr classified = do_QueryInterface(chan); MOZ_DIAGNOSTIC_ASSERT( classified, "Must be an object inheriting from both nsIHttpChannel and " "nsIClassifiedChannel"); uint32_t classificationFlags; classified->GetFirstPartyClassificationFlags(&classificationFlags); FillClassification(classification.mFirstParty, classificationFlags, aRv); if (aRv.Failed()) { return; } classified->GetThirdPartyClassificationFlags(&classificationFlags); FillClassification(classification.mThirdParty, classificationFlags, aRv); } aRetVal.SetValue(std::move(classification)); } bool ChannelWrapper::ThirdParty() const { nsCOMPtr thirdPartyUtil = components::ThirdPartyUtil::Service(); if (NS_WARN_IF(!thirdPartyUtil)) { return true; } nsCOMPtr chan = MaybeHttpChannel(); if (!chan) { return false; } bool thirdParty = false; nsresult rv = thirdPartyUtil->IsThirdPartyChannel(chan, nullptr, &thirdParty); if (NS_WARN_IF(NS_FAILED(rv))) { return true; } return thirdParty; } /***************************************************************************** * Error handling *****************************************************************************/ void ChannelWrapper::GetErrorString(nsString& aRetVal) const { if (nsCOMPtr chan = MaybeChannel()) { nsCOMPtr securityInfo; Unused << chan->GetSecurityInfo(getter_AddRefs(securityInfo)); if (securityInfo) { int32_t errorCode = 0; securityInfo->GetErrorCode(&errorCode); if (psm::IsNSSErrorCode(errorCode)) { nsCOMPtr nsserr = do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); nsresult rv = psm::GetXPCOMFromNSSError(errorCode); if (nsserr && NS_SUCCEEDED(nsserr->GetErrorMessage(rv, aRetVal))) { return; } } } nsresult status; if (NS_SUCCEEDED(chan->GetStatus(&status)) && NS_FAILED(status)) { nsAutoCString name; GetErrorName(status, name); AppendUTF8toUTF16(name, aRetVal); } else { aRetVal.SetIsVoid(true); } } else { aRetVal.AssignLiteral("NS_ERROR_UNEXPECTED"); } } void ChannelWrapper::ErrorCheck() { if (!mFiredErrorEvent) { nsAutoString error; GetErrorString(error); if (error.Length()) { mChannelEntry = nullptr; mFiredErrorEvent = true; ChannelWrapper_Binding::ClearCachedErrorStringValue(this); FireEvent(u"error"_ns); } } } /***************************************************************************** * nsIWebRequestListener *****************************************************************************/ NS_IMPL_ISUPPORTS(ChannelWrapper::RequestListener, nsIStreamListener, nsIMultiPartChannelListener, nsIRequestObserver, nsIThreadRetargetableStreamListener) ChannelWrapper::RequestListener::~RequestListener() { NS_ReleaseOnMainThread("RequestListener::mChannelWrapper", mChannelWrapper.forget()); } nsresult ChannelWrapper::RequestListener::Init() { if (nsCOMPtr chan = mChannelWrapper->QueryChannel()) { return chan->SetNewListener(this, false, getter_AddRefs(mOrigStreamListener)); } return NS_ERROR_UNEXPECTED; } NS_IMETHODIMP ChannelWrapper::RequestListener::OnStartRequest(nsIRequest* request) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); mChannelWrapper->mChannelEntry = nullptr; mChannelWrapper->mResponseStarted = true; mChannelWrapper->ErrorCheck(); mChannelWrapper->FireEvent(u"start"_ns); return mOrigStreamListener->OnStartRequest(request); } NS_IMETHODIMP ChannelWrapper::RequestListener::OnStopRequest(nsIRequest* request, nsresult aStatus) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); mChannelWrapper->mChannelEntry = nullptr; mChannelWrapper->ErrorCheck(); mChannelWrapper->FireEvent(u"stop"_ns); return mOrigStreamListener->OnStopRequest(request, aStatus); } NS_IMETHODIMP ChannelWrapper::RequestListener::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr, uint64_t sourceOffset, uint32_t count) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); return mOrigStreamListener->OnDataAvailable(request, inStr, sourceOffset, count); } NS_IMETHODIMP ChannelWrapper::RequestListener::OnAfterLastPart(nsresult aStatus) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); if (nsCOMPtr listener = do_QueryInterface(mOrigStreamListener)) { return listener->OnAfterLastPart(aStatus); } return NS_OK; } NS_IMETHODIMP ChannelWrapper::RequestListener::CheckListenerChain() { MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread!"); nsresult rv; nsCOMPtr retargetableListener = do_QueryInterface(mOrigStreamListener, &rv); if (retargetableListener) { return retargetableListener->CheckListenerChain(); } return rv; } /***************************************************************************** * Event dispatching *****************************************************************************/ void ChannelWrapper::FireEvent(const nsAString& aType) { EventInit init; init.mBubbles = false; init.mCancelable = false; RefPtr event = Event::Constructor(this, aType, init); event->SetTrusted(true); DispatchEvent(*event); } void ChannelWrapper::CheckEventListeners() { if (!mAddedStreamListener && (HasListenersFor(nsGkAtoms::onerror) || HasListenersFor(nsGkAtoms::onstart) || HasListenersFor(nsGkAtoms::onstop) || mChannelEntry)) { auto listener = MakeRefPtr(this); if (!NS_WARN_IF(NS_FAILED(listener->Init()))) { mAddedStreamListener = true; } } } void ChannelWrapper::EventListenerAdded(nsAtom* aType) { CheckEventListeners(); } void ChannelWrapper::EventListenerRemoved(nsAtom* aType) { CheckEventListeners(); } /***************************************************************************** * Glue *****************************************************************************/ JSObject* ChannelWrapper::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return ChannelWrapper_Binding::Wrap(aCx, this, aGivenProto); } NS_IMPL_CYCLE_COLLECTION_CLASS(ChannelWrapper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper) NS_INTERFACE_MAP_ENTRY_CONCRETE(ChannelWrapper) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK(mStub) NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStub) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_ADDREF_INHERITED(ChannelWrapper, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(ChannelWrapper, DOMEventTargetHelper) } // namespace extensions } // namespace mozilla