/* 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 "nsWyciwyg.h" #include "base/compiler_specific.h" #include "mozilla/net/ChannelEventQueue.h" #include "WyciwygChannelChild.h" #include "mozilla/dom/TabChild.h" #include "mozilla/dom/ContentChild.h" #include "nsCharsetSource.h" #include "nsContentUtils.h" #include "nsStringStream.h" #include "nsNetUtil.h" #include "nsISerializable.h" #include "nsSerializationHelper.h" #include "nsIProgressEventSink.h" #include "mozilla/ipc/URIUtils.h" #include "SerializedLoadContext.h" #include "mozilla/ipc/BackgroundUtils.h" #include "nsProxyRelease.h" #include "nsContentSecurityManager.h" using namespace mozilla::ipc; using namespace mozilla::dom; namespace mozilla { namespace net { NS_IMPL_ISUPPORTS(WyciwygChannelChild, nsIRequest, nsIChannel, nsIWyciwygChannel, nsIPrivateBrowsingChannel) WyciwygChannelChild::WyciwygChannelChild() : mStatus(NS_OK) , mIsPending(false) , mCanceled(false) , mLoadFlags(LOAD_NORMAL) , mContentLength(-1) , mCharsetSource(kCharsetUninitialized) , mState(WCC_NEW) , mIPCOpen(false) , mSentAppData(false) { LOG(("Creating WyciwygChannelChild @%x\n", this)); mEventQ = new ChannelEventQueue(NS_ISUPPORTS_CAST(nsIWyciwygChannel*, this)); } WyciwygChannelChild::~WyciwygChannelChild() { LOG(("Destroying WyciwygChannelChild @%x\n", this)); if (mLoadInfo) { nsCOMPtr mainThread; NS_GetMainThread(getter_AddRefs(mainThread)); nsILoadInfo *forgetableLoadInfo; mLoadInfo.forget(&forgetableLoadInfo); NS_ProxyRelease(mainThread, forgetableLoadInfo, false); } } void WyciwygChannelChild::AddIPDLReference() { MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference"); mIPCOpen = true; AddRef(); } void WyciwygChannelChild::ReleaseIPDLReference() { MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference"); mIPCOpen = false; Release(); } nsresult WyciwygChannelChild::Init(nsIURI* uri) { NS_ENSURE_ARG_POINTER(uri); mState = WCC_INIT; mURI = uri; mOriginalURI = uri; URIParams serializedUri; SerializeURI(uri, serializedUri); // propagate loadInfo mozilla::ipc::PrincipalInfo requestingPrincipalInfo; mozilla::ipc::PrincipalInfo triggeringPrincipalInfo; uint32_t securityFlags; uint32_t policyType; if (mLoadInfo) { mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->LoadingPrincipal(), &requestingPrincipalInfo); mozilla::ipc::PrincipalToPrincipalInfo(mLoadInfo->TriggeringPrincipal(), &triggeringPrincipalInfo); securityFlags = mLoadInfo->GetSecurityFlags(); policyType = mLoadInfo->InternalContentPolicyType(); } else { // use default values if no loadInfo is provided mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(), &requestingPrincipalInfo); mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(), &triggeringPrincipalInfo); securityFlags = nsILoadInfo::SEC_NORMAL; policyType = nsIContentPolicy::TYPE_OTHER; } SendInit(serializedUri, requestingPrincipalInfo, triggeringPrincipalInfo, securityFlags, policyType); return NS_OK; } //----------------------------------------------------------------------------- // WyciwygChannelChild::PWyciwygChannelChild //----------------------------------------------------------------------------- class WyciwygStartRequestEvent : public ChannelEvent { public: WyciwygStartRequestEvent(WyciwygChannelChild* child, const nsresult& statusCode, const int64_t& contentLength, const int32_t& source, const nsCString& charset, const nsCString& securityInfo) : mChild(child), mStatusCode(statusCode), mContentLength(contentLength), mSource(source), mCharset(charset), mSecurityInfo(securityInfo) {} void Run() { mChild->OnStartRequest(mStatusCode, mContentLength, mSource, mCharset, mSecurityInfo); } private: WyciwygChannelChild* mChild; nsresult mStatusCode; int64_t mContentLength; int32_t mSource; nsCString mCharset; nsCString mSecurityInfo; }; bool WyciwygChannelChild::RecvOnStartRequest(const nsresult& statusCode, const int64_t& contentLength, const int32_t& source, const nsCString& charset, const nsCString& securityInfo) { if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new WyciwygStartRequestEvent(this, statusCode, contentLength, source, charset, securityInfo)); } else { OnStartRequest(statusCode, contentLength, source, charset, securityInfo); } return true; } void WyciwygChannelChild::OnStartRequest(const nsresult& statusCode, const int64_t& contentLength, const int32_t& source, const nsCString& charset, const nsCString& securityInfo) { LOG(("WyciwygChannelChild::RecvOnStartRequest [this=%p]\n", this)); mState = WCC_ONSTART; mStatus = statusCode; mContentLength = contentLength; mCharsetSource = source; mCharset = charset; if (!securityInfo.IsEmpty()) { NS_DeserializeObject(securityInfo, getter_AddRefs(mSecurityInfo)); } AutoEventEnqueuer ensureSerialDispatch(mEventQ); nsresult rv = mListener->OnStartRequest(this, mListenerContext); if (NS_FAILED(rv)) Cancel(rv); } class WyciwygDataAvailableEvent : public ChannelEvent { public: WyciwygDataAvailableEvent(WyciwygChannelChild* child, const nsCString& data, const uint64_t& offset) : mChild(child), mData(data), mOffset(offset) {} void Run() { mChild->OnDataAvailable(mData, mOffset); } private: WyciwygChannelChild* mChild; nsCString mData; uint64_t mOffset; }; bool WyciwygChannelChild::RecvOnDataAvailable(const nsCString& data, const uint64_t& offset) { if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new WyciwygDataAvailableEvent(this, data, offset)); } else { OnDataAvailable(data, offset); } return true; } void WyciwygChannelChild::OnDataAvailable(const nsCString& data, const uint64_t& offset) { LOG(("WyciwygChannelChild::RecvOnDataAvailable [this=%p]\n", this)); if (mCanceled) return; mState = WCC_ONDATA; // NOTE: the OnDataAvailable contract requires the client to read all the data // in the inputstream. This code relies on that ('data' will go away after // this function). Apparently the previous, non-e10s behavior was to actually // support only reading part of the data, allowing later calls to read the // rest. nsCOMPtr stringStream; nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(), data.Length(), NS_ASSIGNMENT_DEPEND); if (NS_FAILED(rv)) { Cancel(rv); return; } AutoEventEnqueuer ensureSerialDispatch(mEventQ); rv = mListener->OnDataAvailable(this, mListenerContext, stringStream, offset, data.Length()); if (NS_FAILED(rv)) Cancel(rv); if (mProgressSink && NS_SUCCEEDED(rv)) { mProgressSink->OnProgress(this, nullptr, offset + data.Length(), mContentLength); } } class WyciwygStopRequestEvent : public ChannelEvent { public: WyciwygStopRequestEvent(WyciwygChannelChild* child, const nsresult& statusCode) : mChild(child), mStatusCode(statusCode) {} void Run() { mChild->OnStopRequest(mStatusCode); } private: WyciwygChannelChild* mChild; nsresult mStatusCode; }; bool WyciwygChannelChild::RecvOnStopRequest(const nsresult& statusCode) { if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new WyciwygStopRequestEvent(this, statusCode)); } else { OnStopRequest(statusCode); } return true; } void WyciwygChannelChild::OnStopRequest(const nsresult& statusCode) { LOG(("WyciwygChannelChild::RecvOnStopRequest [this=%p status=%u]\n", this, statusCode)); { // We need to ensure that all IPDL message dispatching occurs // before we delete the protocol below AutoEventEnqueuer ensureSerialDispatch(mEventQ); mState = WCC_ONSTOP; mIsPending = false; if (!mCanceled) mStatus = statusCode; mListener->OnStopRequest(this, mListenerContext, statusCode); mListener = 0; mListenerContext = 0; if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); mCallbacks = 0; mProgressSink = 0; } if (mIPCOpen) PWyciwygChannelChild::Send__delete__(this); } class WyciwygCancelEvent : public ChannelEvent { public: WyciwygCancelEvent(WyciwygChannelChild* child, const nsresult& status) : mChild(child) , mStatus(status) {} void Run() { mChild->CancelEarly(mStatus); } private: WyciwygChannelChild* mChild; nsresult mStatus; }; bool WyciwygChannelChild::RecvCancelEarly(const nsresult& statusCode) { if (mEventQ->ShouldEnqueue()) { mEventQ->Enqueue(new WyciwygCancelEvent(this, statusCode)); } else { CancelEarly(statusCode); } return true; } void WyciwygChannelChild::CancelEarly(const nsresult& statusCode) { LOG(("WyciwygChannelChild::CancelEarly [this=%p]\n", this)); if (mCanceled) return; mCanceled = true; mStatus = statusCode; mIsPending = false; if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); if (mListener) { mListener->OnStartRequest(this, mListenerContext); mListener->OnStopRequest(this, mListenerContext, mStatus); } mListener = nullptr; mListenerContext = nullptr; if (mIPCOpen) PWyciwygChannelChild::Send__delete__(this); } //----------------------------------------------------------------------------- // nsIRequest //----------------------------------------------------------------------------- NS_IMETHODIMP WyciwygChannelChild::GetName(nsACString & aName) { return mURI->GetSpec(aName); } NS_IMETHODIMP WyciwygChannelChild::IsPending(bool *aIsPending) { *aIsPending = mIsPending; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetStatus(nsresult *aStatus) { *aStatus = mStatus; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::Cancel(nsresult aStatus) { if (mCanceled) return NS_OK; mCanceled = true; mStatus = aStatus; if (mIPCOpen) SendCancel(aStatus); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::Resume() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::GetLoadGroup(nsILoadGroup * *aLoadGroup) { *aLoadGroup = mLoadGroup; NS_IF_ADDREF(*aLoadGroup); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetLoadGroup(nsILoadGroup * aLoadGroup) { if (!CanSetLoadGroup(aLoadGroup)) { return NS_ERROR_FAILURE; } mLoadGroup = aLoadGroup; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); UpdatePrivateBrowsing(); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetLoadFlags(nsLoadFlags *aLoadFlags) { *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetLoadFlags(nsLoadFlags aLoadFlags) { mLoadFlags = aLoadFlags; return NS_OK; } //----------------------------------------------------------------------------- // nsIChannel //----------------------------------------------------------------------------- NS_IMETHODIMP WyciwygChannelChild::GetOriginalURI(nsIURI * *aOriginalURI) { *aOriginalURI = mOriginalURI; NS_ADDREF(*aOriginalURI); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetOriginalURI(nsIURI * aOriginalURI) { NS_ENSURE_TRUE(mState == WCC_INIT, NS_ERROR_UNEXPECTED); NS_ENSURE_ARG_POINTER(aOriginalURI); mOriginalURI = aOriginalURI; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetURI(nsIURI * *aURI) { *aURI = mURI; NS_IF_ADDREF(*aURI); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetOwner(nsISupports * *aOwner) { NS_IF_ADDREF(*aOwner = mOwner); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetOwner(nsISupports * aOwner) { mOwner = aOwner; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetLoadInfo(nsILoadInfo **aLoadInfo) { NS_IF_ADDREF(*aLoadInfo = mLoadInfo); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetLoadInfo(nsILoadInfo* aLoadInfo) { mLoadInfo = aLoadInfo; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetNotificationCallbacks(nsIInterfaceRequestor * *aCallbacks) { *aCallbacks = mCallbacks; NS_IF_ADDREF(*aCallbacks); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks) { if (!CanSetCallbacks(aCallbacks)) { return NS_ERROR_FAILURE; } mCallbacks = aCallbacks; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_IID(nsIProgressEventSink), getter_AddRefs(mProgressSink)); UpdatePrivateBrowsing(); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetSecurityInfo(nsISupports * *aSecurityInfo) { NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetContentType(nsACString & aContentType) { aContentType.AssignLiteral(WYCIWYG_TYPE); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetContentType(const nsACString & aContentType) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::GetContentCharset(nsACString & aContentCharset) { aContentCharset.AssignLiteral("UTF-16"); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetContentCharset(const nsACString & aContentCharset) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::GetContentDisposition(uint32_t *aContentDisposition) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP WyciwygChannelChild::SetContentDisposition(uint32_t aContentDisposition) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP WyciwygChannelChild::GetContentDispositionFilename(nsAString &aContentDispositionFilename) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP WyciwygChannelChild::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP WyciwygChannelChild::GetContentDispositionHeader(nsACString &aContentDispositionHeader) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP WyciwygChannelChild::GetContentLength(int64_t *aContentLength) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::SetContentLength(int64_t aContentLength) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::Open(nsIInputStream **_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP WyciwygChannelChild::Open2(nsIInputStream** aStream) { nsCOMPtr listener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); return Open(aStream); } static mozilla::dom::TabChild* GetTabChild(nsIChannel* aChannel) { nsCOMPtr iTabChild; NS_QueryNotificationCallbacks(aChannel, iTabChild); return iTabChild ? static_cast(iTabChild.get()) : nullptr; } NS_IMETHODIMP WyciwygChannelChild::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) { MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || mLoadInfo->GetInitialSecurityCheckDone() || (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), "security flags in loadInfo but asyncOpen2() not called"); LOG(("WyciwygChannelChild::AsyncOpen [this=%p]\n", this)); // The only places creating wyciwyg: channels should be // HTMLDocument::OpenCommon and session history. Both should be setting an // owner or loadinfo. NS_PRECONDITION(mOwner || mLoadInfo, "Must have a principal"); NS_ENSURE_STATE(mOwner || mLoadInfo); NS_ENSURE_ARG_POINTER(aListener); NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); mListener = aListener; mListenerContext = aContext; mIsPending = true; if (mLoadGroup) { mLoadGroup->AddRequest(this, nullptr); } URIParams originalURI; SerializeURI(mOriginalURI, originalURI); mozilla::dom::TabChild* tabChild = GetTabChild(this); if (MissingRequiredTabChild(tabChild, "wyciwyg")) { return NS_ERROR_ILLEGAL_VALUE; } PBrowserOrId browser = static_cast(Manager()->Manager()) ->GetBrowserOrId(tabChild); SendAsyncOpen(originalURI, mLoadFlags, IPC::SerializedLoadContext(this), browser); mSentAppData = true; mState = WCC_OPENED; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::AsyncOpen2(nsIStreamListener *aListener) { nsCOMPtr listener = aListener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); return AsyncOpen(listener, nullptr); } //----------------------------------------------------------------------------- // nsIWyciwygChannel //----------------------------------------------------------------------------- NS_IMETHODIMP WyciwygChannelChild::WriteToCacheEntry(const nsAString & aData) { NS_ENSURE_TRUE((mState == WCC_INIT) || (mState == WCC_ONWRITE), NS_ERROR_UNEXPECTED); if (!mSentAppData) { mozilla::dom::TabChild* tabChild = GetTabChild(this); PBrowserOrId browser = static_cast(Manager()->Manager()) ->GetBrowserOrId(tabChild); SendAppData(IPC::SerializedLoadContext(this), browser); mSentAppData = true; } SendWriteToCacheEntry(PromiseFlatString(aData)); mState = WCC_ONWRITE; return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::CloseCacheEntry(nsresult reason) { NS_ENSURE_TRUE(mState == WCC_ONWRITE, NS_ERROR_UNEXPECTED); SendCloseCacheEntry(reason); mState = WCC_ONCLOSED; if (mIPCOpen) PWyciwygChannelChild::Send__delete__(this); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetSecurityInfo(nsISupports *aSecurityInfo) { mSecurityInfo = aSecurityInfo; if (mSecurityInfo) { nsCOMPtr serializable = do_QueryInterface(mSecurityInfo); if (serializable) { nsCString secInfoStr; NS_SerializeToString(serializable, secInfoStr); SendSetSecurityInfo(secInfoStr); } else { NS_WARNING("Can't serialize security info"); } } return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::SetCharsetAndSource(int32_t aSource, const nsACString & aCharset) { // mState == WCC_ONSTART when reading from the channel // mState == WCC_INIT when writing to the cache NS_ENSURE_TRUE((mState == WCC_ONSTART) || (mState == WCC_INIT), NS_ERROR_UNEXPECTED); mCharsetSource = aSource; mCharset = aCharset; // TODO ensure that nsWyciwygChannel in the parent has still the cache entry SendSetCharsetAndSource(mCharsetSource, mCharset); return NS_OK; } NS_IMETHODIMP WyciwygChannelChild::GetCharsetAndSource(int32_t *aSource, nsACString & _retval) { NS_ENSURE_TRUE((mState == WCC_ONSTART) || (mState == WCC_ONDATA) || (mState == WCC_ONSTOP), NS_ERROR_NOT_AVAILABLE); if (mCharsetSource == kCharsetUninitialized) return NS_ERROR_NOT_AVAILABLE; *aSource = mCharsetSource; _retval = mCharset; return NS_OK; } //------------------------------------------------------------------------------ } // namespace net } // namespace mozilla