Files
tubestation/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
Kris Maglione 77ecf1916f Bug 1396856: Part 3 - Add a WebIDL wrapper class for necko channels. r=ehsan,mixedpuppy
Ehsan, can you please review the DOM bindings, and Shane the request logic?

The bulk of the overhead WebRequest API is in its access to nsIChannel and
friends through XPConnect. Since it's not really feasible to convert channels
to use WebIDL bindings directly, this generic channel wrapper class serves the
same purpose.

MozReview-Commit-ID: 4mNP8HiKWK
2017-09-06 14:38:23 -07:00

680 lines
18 KiB
C++

/* -*- 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>
ChannelWrapper::Get(const GlobalObject& global, nsIChannel* channel)
{
RefPtr<ChannelWrapper> wrapper;
nsCOMPtr<nsIWritablePropertyBag2> 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<nsIChannel> 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<nsIHttpChannel> 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<nsIChannel> 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<nsIHttpChannel> chan = MaybeHttpChannel()) {
Unused << chan->GetContentType(aContentType);
}
}
void
ChannelWrapper::SetContentType(const nsACString& aContentType)
{
if (nsCOMPtr<nsIHttpChannel> 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<JSObject*> aRetVal, ErrorResult& aRv) const
{
if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
HeaderVisitor visitor(cx);
aRetVal.set(visitor.VisitRequestHeaders(chan, aRv));
} else {
aRv.Throw(NS_ERROR_UNEXPECTED);
}
}
void
ChannelWrapper::GetResponseHeaders(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv) const
{
if (nsCOMPtr<nsIHttpChannel> 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<nsIHttpChannel> 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<nsIHttpChannel> chan = MaybeHttpChannel()) {
rv = chan->SetResponseHeader(aHeader, aValue, false);
}
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
}
/*****************************************************************************
* LoadInfo
*****************************************************************************/
already_AddRefed<nsILoadContext>
ChannelWrapper::GetLoadContext() const
{
if (nsCOMPtr<nsIChannel> chan = MaybeChannel()) {
nsCOMPtr<nsILoadContext> ctxt;
NS_QueryNotificationCallbacks(chan, ctxt);
return ctxt.forget();
}
return nullptr;
}
already_AddRefed<nsIDOMElement>
ChannelWrapper::GetBrowserElement() const
{
if (nsCOMPtr<nsILoadContext> ctxt = GetLoadContext()) {
nsCOMPtr<nsIDOMElement> 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<SystemPrincipal>();
}
bool
ChannelWrapper::IsSystemLoad() const
{
if (nsCOMPtr<nsILoadInfo> 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<nsIURI> uri = GetFinalURI(aRv);
nsAutoCString spec;
if (uri) {
uri->GetSpec(spec);
}
if (!uri || AddonManagerWebAPI::IsValidSite(uri)) {
return false;
}
if (nsCOMPtr<nsILoadInfo> 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;
}
void
ChannelWrapper::GetOriginURL(nsCString& aRetVal) const
{
if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
if (nsIPrincipal* prin = loadInfo->TriggeringPrincipal()) {
nsCOMPtr<nsIURI> uri;
if (prin->GetIsCodebasePrincipal() &&
NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
Unused << uri->GetSpec(aRetVal);
}
}
}
}
void
ChannelWrapper::GetDocumentURL(nsCString& aRetVal) const
{
if (nsCOMPtr<nsILoadInfo> loadInfo = GetLoadInfo()) {
if (nsIPrincipal* prin = loadInfo->LoadingPrincipal()) {
nsCOMPtr<nsIURI> uri;
if (prin->GetIsCodebasePrincipal() &&
NS_SUCCEEDED(prin->GetURI(getter_AddRefs(uri)))) {
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<nsILoadInfo> loadInfo = GetLoadInfo()) {
return NormalizeWindowID(loadInfo, WindowId(loadInfo));
}
return 0;
}
int64_t
ChannelWrapper::ParentWindowId() const
{
if (nsCOMPtr<nsILoadInfo> 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;
}
/*****************************************************************************
* ...
*****************************************************************************/
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<nsILoadInfo> loadInfo = GetLoadInfo()) {
return GetContentPolicyType(loadInfo->GetExternalContentPolicyType());
}
return MozContentPolicyType::Other;
}
void
ChannelWrapper::GetMethod(nsCString& aMethod) const
{
if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
Unused << chan->GetRequestMethod(aMethod);
}
}
/*****************************************************************************
* ...
*****************************************************************************/
uint32_t
ChannelWrapper::StatusCode() const
{
uint32_t result = 0;
if (nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel()) {
Unused << chan->GetResponseStatus(&result);
}
return result;
}
void
ChannelWrapper::GetStatusLine(nsCString& aRetVal) const
{
nsCOMPtr<nsIHttpChannel> chan = MaybeHttpChannel();
nsCOMPtr<nsIHttpChannelInternal> 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<nsIURI>
ChannelWrapper::GetFinalURI(ErrorResult& aRv) const
{
nsresult rv = NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIURI> uri;
if (nsCOMPtr<nsIChannel> 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<nsIURI> 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<MozProxyInfo>& aRetVal, ErrorResult& aRv) const
{
nsCOMPtr<nsIProxyInfo> proxyInfo;
if (nsCOMPtr<nsIProxiedChannel> 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<nsIHttpChannelInternal> 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