/* -*- 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/ErrorNames.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Unused.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/TabParent.h" #include "nsIContentPolicy.h" #include "nsIHttpChannelInternal.h" #include "nsIHttpHeaderVisitor.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILoadContext.h" #include "nsILoadGroup.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 \ NS_LITERAL_STRING("ChannelWrapper::CachedInstance") /***************************************************************************** * Lifetimes *****************************************************************************/ namespace { class ChannelListHolder : public LinkedList { public: ChannelListHolder() : LinkedList() {} ~ChannelListHolder(); }; } // anonymous namespace ChannelListHolder::~ChannelListHolder() { while (ChannelWrapper* wrapper = popFirst()) { wrapper->Die(); } } static LinkedList& ChannelList() { static UniquePtr sChannelList; if (!sChannelList) { sChannelList.reset(new ChannelListHolder()); ClearOnShutdown(&sChannelList, ShutdownPhase::Shutdown); } return *sChannelList; } 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(ChannelWrapper, 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); ChannelList().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) { Unused << props->GetPropertyAsInterface(CHANNELWRAPPER_PROP_KEY, NS_GET_IID(ChannelWrapper), getter_AddRefs(wrapper)); 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, nsITabParent* aTabParent) { nsIContentParent* contentParent = nullptr; if (TabParent* parent = static_cast(aTabParent)) { contentParent = static_cast(parent->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); if (!mFiredErrorEvent) { ChannelWrapper_Binding::ClearCachedErrorStringValue(this); } } /***************************************************************************** * ... *****************************************************************************/ void ChannelWrapper::Cancel(uint32_t aResult, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { 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::SetSuspended(bool aSuspended, ErrorResult& aRv) { if (aSuspended != mSuspended) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { if (aSuspended) { rv = chan->Suspend(); } else { rv = chan->Resume(); } } if (NS_FAILED(rv)) { aRv.Throw(rv); } else { mSuspended = aSuspended; } } } 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::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; 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; } static inline bool IsSystemPrincipal(nsIPrincipal* aPrincipal) { return BasePrincipal::Cast(aPrincipal)->Is(); } bool ChannelWrapper::IsSystemLoad() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) { return IsSystemPrincipal(prin); } if (loadInfo->GetOuterWindowID() == loadInfo->GetTopOuterWindowID()) { 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->LoadingPrincipal()) { 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->GetIsCodebasePrincipal()) { Unused << prin->GetURI(getter_AddRefs(uri)); } } } return uri.forget(); } already_AddRefed ChannelWrapper::GetDocumentURI() const { nsCOMPtr uri; if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) { if (prin->GetIsCodebasePrincipal()) { Unused << prin->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); 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, NS_LITERAL_CSTRING("ws")); 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; } if (aExtension) { // Verify extension access to private requests if (!aExtension->PrivateBrowsingAllowed()) { nsCOMPtr loadInfo = GetLoadInfo(); if (loadInfo && loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0) { 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)) { 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; } if (auto origin = DocumentURLInfo()) { nsAutoCString baseURL; aExtension->GetBaseURL(baseURL); if (!StringBeginsWith(origin->CSpec(), baseURL) && !aExtension->CanAccessURI(*origin)) { return false; } } } } return true; } int64_t NormalizeWindowID(nsILoadInfo* aLoadInfo, uint64_t windowID) { if (windowID == aLoadInfo->GetTopOuterWindowID()) { return 0; } return windowID; } uint64_t ChannelWrapper::WindowId(nsILoadInfo* aLoadInfo) const { auto frameID = aLoadInfo->GetFrameOuterWindowID(); if (!frameID) { frameID = aLoadInfo->GetOuterWindowID(); } return frameID; } int64_t ChannelWrapper::WindowId() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { return NormalizeWindowID(loadInfo, WindowId(loadInfo)); } return 0; } int64_t ChannelWrapper::ParentWindowId() const { if (nsCOMPtr loadInfo = GetLoadInfo()) { if (WindowId(loadInfo) == loadInfo->GetTopOuterWindowID()) { return -1; } uint64_t parentID; if (loadInfo->GetFrameOuterWindowID()) { parentID = loadInfo->GetOuterWindowID(); } else { parentID = loadInfo->GetParentOuterWindowID(); } return NormalizeWindowID(loadInfo, parentID); } return -1; } void ChannelWrapper::GetFrameAncestors( dom::Nullable>& aFrameAncestors, ErrorResult& aRv) const { nsCOMPtr loadInfo = GetLoadInfo(); if (!loadInfo || WindowId(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& ancestorOuterWindowIDs = aLoadInfo->AncestorOuterWindowIDs(); uint32_t size = ancestorPrincipals.Length(); MOZ_DIAGNOSTIC_ASSERT(size == ancestorOuterWindowIDs.Length()); if (size != ancestorOuterWindowIDs.Length()) { return NS_ERROR_UNEXPECTED; } bool subFrame = aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::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.parentWindowId == // 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 = ParentWindowId(); } for (uint32_t i = 0; i < size; ++i) { auto ancestor = aFrameAncestors.AppendElement(); nsCOMPtr uri; MOZ_TRY(ancestorPrincipals[i]->GetURI(getter_AddRefs(uri))); if (!uri) { return NS_ERROR_UNEXPECTED; } MOZ_TRY(uri->GetSpec(ancestor->mUrl)); ancestor->mFrameId = NormalizeWindowID(aLoadInfo, ancestorOuterWindowIDs[i]); } return NS_OK; } /***************************************************************************** * Response filtering *****************************************************************************/ void ChannelWrapper::RegisterTraceableChannel(const WebExtensionPolicy& aAddon, nsITabParent* aTabParent) { // We can't attach new listeners after the response has started, so don't // bother registering anything. if (mResponseStarted || !CanModify()) { return; } mAddonEntries.Put(aAddon.Id(), aTabParent); if (!mChannelEntry) { mChannelEntry = WebRequestService::GetSingleton().RegisterChannel(this); CheckEventListeners(); } } already_AddRefed ChannelWrapper::GetTraceableChannel( nsAtom* aAddonId, dom::nsIContentParent* aContentParent) const { nsCOMPtr tabParent; if (mAddonEntries.Get(aAddonId, getter_AddRefs(tabParent))) { nsIContentParent* contentParent = nullptr; if (tabParent) { contentParent = static_cast( static_cast(tabParent.get())->Manager()); } if (contentParent == aContentParent) { nsCOMPtr chan = QueryChannel(); return chan.forget(); } } return nullptr; } /***************************************************************************** * ... *****************************************************************************/ MozContentPolicyType GetContentPolicyType(uint32_t aType) { // Note: Please keep this function in sync with the external types in // nsIContentPolicy.idl switch (aType) { case nsIContentPolicy::TYPE_DOCUMENT: return MozContentPolicyType::Main_frame; case nsIContentPolicy::TYPE_SUBDOCUMENT: return MozContentPolicyType::Sub_frame; case nsIContentPolicy::TYPE_STYLESHEET: return MozContentPolicyType::Stylesheet; case nsIContentPolicy::TYPE_SCRIPT: return MozContentPolicyType::Script; case nsIContentPolicy::TYPE_IMAGE: return MozContentPolicyType::Image; case nsIContentPolicy::TYPE_OBJECT: return MozContentPolicyType::Object; case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: return MozContentPolicyType::Object_subrequest; case nsIContentPolicy::TYPE_XMLHTTPREQUEST: return MozContentPolicyType::Xmlhttprequest; // TYPE_FETCH returns xmlhttprequest for cross-browser compatibility. case nsIContentPolicy::TYPE_FETCH: return MozContentPolicyType::Xmlhttprequest; case nsIContentPolicy::TYPE_XBL: return MozContentPolicyType::Xbl; case nsIContentPolicy::TYPE_XSLT: return MozContentPolicyType::Xslt; case nsIContentPolicy::TYPE_PING: return MozContentPolicyType::Ping; case nsIContentPolicy::TYPE_BEACON: return MozContentPolicyType::Beacon; case nsIContentPolicy::TYPE_DTD: return MozContentPolicyType::Xml_dtd; case nsIContentPolicy::TYPE_FONT: return MozContentPolicyType::Font; case nsIContentPolicy::TYPE_MEDIA: return MozContentPolicyType::Media; case nsIContentPolicy::TYPE_WEBSOCKET: return MozContentPolicyType::Websocket; case nsIContentPolicy::TYPE_CSP_REPORT: return MozContentPolicyType::Csp_report; case nsIContentPolicy::TYPE_IMAGESET: return MozContentPolicyType::Imageset; case nsIContentPolicy::TYPE_WEB_MANIFEST: return MozContentPolicyType::Web_manifest; case nsIContentPolicy::TYPE_SPECULATIVE: return MozContentPolicyType::Speculative; default: 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()); } } /***************************************************************************** * ... *****************************************************************************/ 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->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); } } /***************************************************************************** * Error handling *****************************************************************************/ void ChannelWrapper::GetErrorString(nsString& aRetVal) const { if (nsCOMPtr chan = MaybeChannel()) { nsCOMPtr securityInfo; Unused << chan->GetSecurityInfo(getter_AddRefs(securityInfo)); if (nsCOMPtr tsi = do_QueryInterface(securityInfo)) { int32_t errorCode = 0; tsi->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(NS_LITERAL_STRING("error")); } } } /***************************************************************************** * nsIWebRequestListener *****************************************************************************/ NS_IMPL_ISUPPORTS(ChannelWrapper::RequestListener, nsIStreamListener, nsIRequestObserver, nsIThreadRetargetableStreamListener) ChannelWrapper::RequestListener::~RequestListener() { NS_ReleaseOnMainThreadSystemGroup("RequestListener::mChannelWrapper", mChannelWrapper.forget()); } nsresult ChannelWrapper::RequestListener::Init() { if (nsCOMPtr chan = mChannelWrapper->QueryChannel()) { return chan->SetNewListener(this, getter_AddRefs(mOrigStreamListener)); } return NS_ERROR_UNEXPECTED; } NS_IMETHODIMP ChannelWrapper::RequestListener::OnStartRequest(nsIRequest* request, nsISupports* aCtxt) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); mChannelWrapper->mChannelEntry = nullptr; mChannelWrapper->mResponseStarted = true; mChannelWrapper->ErrorCheck(); mChannelWrapper->FireEvent(NS_LITERAL_STRING("start")); return mOrigStreamListener->OnStartRequest(request, aCtxt); } NS_IMETHODIMP ChannelWrapper::RequestListener::OnStopRequest(nsIRequest* request, nsISupports* aCtxt, nsresult aStatus) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); mChannelWrapper->mChannelEntry = nullptr; mChannelWrapper->ErrorCheck(); mChannelWrapper->FireEvent(NS_LITERAL_STRING("stop")); return mOrigStreamListener->OnStopRequest(request, aCtxt, aStatus); } NS_IMETHODIMP ChannelWrapper::RequestListener::OnDataAvailable(nsIRequest* request, nsISupports* aCtxt, nsIInputStream* inStr, uint64_t sourceOffset, uint32_t count) { MOZ_ASSERT(mOrigStreamListener, "Should have mOrigStreamListener"); return mOrigStreamListener->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count); } 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, HandleObject 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(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_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_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ChannelWrapper, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_ADDREF_INHERITED(ChannelWrapper, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(ChannelWrapper, DOMEventTargetHelper) } // namespace extensions } // namespace mozilla