/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "SystemPrincipal.h" #include "mozilla/AddonManagerWebAPI.h" #include "mozilla/ResultExtensions.h" #include "mozilla/Unused.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 "nsIWritablePropertyBag2.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" using namespace mozilla::dom; using namespace JS; namespace mozilla { namespace extensions { #define CHANNELWRAPPER_PROP_KEY NS_LITERAL_STRING("ChannelWrapper::CachedInstance") /***************************************************************************** * Initialization *****************************************************************************/ /* 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); } } return wrapper.forget(); } void ChannelWrapper::SetChannel(nsIChannel* aChannel) { detail::ChannelHolder::SetChannel(aChannel); ClearCachedAttributes(); } void ChannelWrapper::ClearCachedAttributes() { ChannelWrapperBinding::ClearCachedFinalURIValue(this); ChannelWrapperBinding::ClearCachedFinalURLValue(this); ChannelWrapperBinding::ClearCachedProxyInfoValue(this); ChannelWrapperBinding::ClearCachedRemoteAddressValue(this); ChannelWrapperBinding::ClearCachedStatusCodeValue(this); ChannelWrapperBinding::ClearCachedStatusLineValue(this); } /***************************************************************************** * ... *****************************************************************************/ void ChannelWrapper::Cancel(uint32_t aResult, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeChannel()) { rv = chan->Cancel(nsresult(aResult)); } 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::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(JSContext* aCx) : mCx(aCx) , mMap(aCx) {} JSObject* VisitRequestHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) { if (!Init()) { return nullptr; } if (!CheckResult(aChannel->VisitRequestHeaders(this), aRv)) { return nullptr; } return mMap; } JSObject* VisitResponseHeaders(nsIHttpChannel* aChannel, ErrorResult& aRv) { if (!Init()) { return nullptr; } if (!CheckResult(aChannel->VisitResponseHeaders(this), aRv)) { return nullptr; } return mMap; } 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 Init() { mMap = NewMapObject(mCx); return mMap; } bool CheckResult(nsresult aNSRv, ErrorResult& aRv) { if (JS_IsExceptionPending(mCx)) { aRv.NoteJSContextException(mCx); return false; } if (NS_FAILED(aNSRv)) { aRv.Throw(aNSRv); return false; } return true; } JSContext* mCx; RootedObject mMap; nsrefcnt mRefCnt = 0; }; NS_IMETHODIMP HeaderVisitor::VisitHeader(const nsACString& aHeader, const nsACString& aValue) { RootedValue header(mCx); RootedValue value(mCx); if (!xpc::NonVoidStringToJsval(mCx, NS_ConvertUTF8toUTF16(aHeader), &header) || !xpc::NonVoidStringToJsval(mCx, NS_ConvertUTF8toUTF16(aValue), &value) || !MapSet(mCx, mMap, header, value)) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } NS_IMPL_QUERY_INTERFACE(HeaderVisitor, nsIHttpHeaderVisitor) } // anonymous namespace void ChannelWrapper::GetRequestHeaders(JSContext* cx, JS::MutableHandle aRetVal, ErrorResult& aRv) const { if (nsCOMPtr chan = MaybeHttpChannel()) { HeaderVisitor visitor(cx); aRetVal.set(visitor.VisitRequestHeaders(chan, aRv)); } else { aRv.Throw(NS_ERROR_UNEXPECTED); } } void ChannelWrapper::GetResponseHeaders(JSContext* cx, JS::MutableHandle aRetVal, ErrorResult& aRv) const { if (nsCOMPtr chan = MaybeHttpChannel()) { HeaderVisitor visitor(cx); aRetVal.set(visitor.VisitResponseHeaders(chan, aRv)); } else { aRv.Throw(NS_ERROR_UNEXPECTED); } } void ChannelWrapper::SetRequestHeader(const nsCString& aHeader, const nsCString& aValue, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { rv = chan->SetRequestHeader(aHeader, aValue, false); } if (NS_FAILED(rv)) { aRv.Throw(rv); } } void ChannelWrapper::SetResponseHeader(const nsCString& aHeader, const nsCString& aValue, ErrorResult& aRv) { nsresult rv = NS_ERROR_UNEXPECTED; if (nsCOMPtr chan = MaybeHttpChannel()) { rv = chan->SetResponseHeader(aHeader, aValue, false); } 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()) { nsCOMPtr 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::GetCanModify(ErrorResult& aRv) const { nsCOMPtr uri = GetFinalURI(aRv); nsAutoCString spec; if (uri) { uri->GetSpec(spec); } if (!uri || AddonManagerWebAPI::IsValidSite(uri)) { return false; } if (nsCOMPtr loadInfo = GetLoadInfo()) { if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) { if (IsSystemPrincipal(prin)) { return false; } if (prin->GetIsCodebasePrincipal() && (NS_FAILED(prin->GetURI(getter_AddRefs(uri))) || AddonManagerWebAPI::IsValidSite(uri))) { 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); } } 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; } /***************************************************************************** * ... *****************************************************************************/ 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; 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::GetFinalURI(ErrorResult& aRv) const { nsresult rv = NS_ERROR_UNEXPECTED; nsCOMPtr uri; if (nsCOMPtr chan = MaybeChannel()) { rv = NS_GetFinalChannelURI(chan, getter_AddRefs(uri)); } if (NS_FAILED(rv)) { aRv.Throw(rv); } return uri.forget(); } void ChannelWrapper::GetFinalURL(nsCString& aRetVal, ErrorResult& aRv) const { nsCOMPtr uri = GetFinalURI(aRv); if (uri) { Unused << uri->GetSpec(aRetVal); } } /***************************************************************************** * ... *****************************************************************************/ 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(Move(result)); } } } void ChannelWrapper::GetRemoteAddress(nsCString& aRetVal) const { aRetVal.SetIsVoid(true); if (nsCOMPtr internal = QueryChannel()) { Unused << internal->GetRemoteAddress(aRetVal); } } /***************************************************************************** * Glue *****************************************************************************/ JSObject* ChannelWrapper::WrapObject(JSContext* aCx, HandleObject aGivenProto) { return ChannelWrapperBinding::Wrap(aCx, this, aGivenProto); } NS_IMPL_CYCLE_COLLECTION_CLASS(ChannelWrapper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChannelWrapper) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(ChannelWrapper) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChannelWrapper) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChannelWrapper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(ChannelWrapper) NS_IMPL_CYCLE_COLLECTING_ADDREF(ChannelWrapper) NS_IMPL_CYCLE_COLLECTING_RELEASE(ChannelWrapper) } // namespace extensions } // namespace mozilla